Repository: apache/rocketmq Branch: develop Commit: ebf159541817 Files: 2627 Total size: 16.4 MB Directory structure: gitextract_8f2xr136/ ├── .asf.yaml ├── .bazelrc ├── .bazelversion ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── doc.yml │ │ ├── enhancement_request.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── asf-deploy-settings.xml │ └── workflows/ │ ├── bazel.yml │ ├── codeql_analysis.yml │ ├── coverage.yml │ ├── integration-test.yml │ ├── license-checker.yaml │ ├── maven.yaml │ ├── misspell_check.yml │ ├── pr-ci.yml │ ├── pr-e2e-test.yml │ ├── push-ci.yml │ ├── rerun-workflow.yml │ ├── snapshot-automation.yml │ └── stale.yml ├── .gitignore ├── .licenserc.yaml ├── BUILD.bazel ├── BUILDING ├── CONTRIBUTING.md ├── LICENSE ├── MODULE.bazel ├── NOTICE ├── README.md ├── WORKSPACE ├── auth/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── auth/ │ │ ├── authentication/ │ │ │ ├── AuthenticationEvaluator.java │ │ │ ├── builder/ │ │ │ │ ├── AuthenticationContextBuilder.java │ │ │ │ └── DefaultAuthenticationContextBuilder.java │ │ │ ├── chain/ │ │ │ │ └── DefaultAuthenticationHandler.java │ │ │ ├── context/ │ │ │ │ ├── AuthenticationContext.java │ │ │ │ └── DefaultAuthenticationContext.java │ │ │ ├── enums/ │ │ │ │ ├── SubjectType.java │ │ │ │ ├── UserStatus.java │ │ │ │ └── UserType.java │ │ │ ├── exception/ │ │ │ │ └── AuthenticationException.java │ │ │ ├── factory/ │ │ │ │ └── AuthenticationFactory.java │ │ │ ├── manager/ │ │ │ │ ├── AuthenticationMetadataManager.java │ │ │ │ └── AuthenticationMetadataManagerImpl.java │ │ │ ├── model/ │ │ │ │ ├── Subject.java │ │ │ │ └── User.java │ │ │ ├── provider/ │ │ │ │ ├── AuthenticationMetadataProvider.java │ │ │ │ ├── AuthenticationProvider.java │ │ │ │ ├── DefaultAuthenticationProvider.java │ │ │ │ └── LocalAuthenticationMetadataProvider.java │ │ │ └── strategy/ │ │ │ ├── AbstractAuthenticationStrategy.java │ │ │ ├── AuthenticationStrategy.java │ │ │ ├── StatefulAuthenticationStrategy.java │ │ │ └── StatelessAuthenticationStrategy.java │ │ ├── authorization/ │ │ │ ├── AuthorizationEvaluator.java │ │ │ ├── builder/ │ │ │ │ ├── AuthorizationContextBuilder.java │ │ │ │ └── DefaultAuthorizationContextBuilder.java │ │ │ ├── chain/ │ │ │ │ ├── AclAuthorizationHandler.java │ │ │ │ └── UserAuthorizationHandler.java │ │ │ ├── context/ │ │ │ │ ├── AuthorizationContext.java │ │ │ │ └── DefaultAuthorizationContext.java │ │ │ ├── enums/ │ │ │ │ ├── Decision.java │ │ │ │ └── PolicyType.java │ │ │ ├── exception/ │ │ │ │ └── AuthorizationException.java │ │ │ ├── factory/ │ │ │ │ └── AuthorizationFactory.java │ │ │ ├── manager/ │ │ │ │ ├── AuthorizationMetadataManager.java │ │ │ │ └── AuthorizationMetadataManagerImpl.java │ │ │ ├── model/ │ │ │ │ ├── Acl.java │ │ │ │ ├── Environment.java │ │ │ │ ├── Policy.java │ │ │ │ ├── PolicyEntry.java │ │ │ │ ├── RequestContext.java │ │ │ │ └── Resource.java │ │ │ ├── provider/ │ │ │ │ ├── AuthorizationMetadataProvider.java │ │ │ │ ├── AuthorizationProvider.java │ │ │ │ ├── DefaultAuthorizationProvider.java │ │ │ │ └── LocalAuthorizationMetadataProvider.java │ │ │ └── strategy/ │ │ │ ├── AbstractAuthorizationStrategy.java │ │ │ ├── AuthorizationStrategy.java │ │ │ ├── StatefulAuthorizationStrategy.java │ │ │ └── StatelessAuthorizationStrategy.java │ │ ├── config/ │ │ │ └── AuthConfig.java │ │ └── migration/ │ │ ├── AuthMigrator.java │ │ └── v1/ │ │ ├── AccessResource.java │ │ ├── AclConfig.java │ │ ├── PlainAccessConfig.java │ │ ├── PlainAccessData.java │ │ ├── PlainAccessResource.java │ │ └── PlainPermissionManager.java │ └── test/ │ └── java/ │ └── org/ │ └── apache/ │ └── rocketmq/ │ └── auth/ │ ├── authentication/ │ │ ├── AuthenticationEvaluatorTest.java │ │ ├── builder/ │ │ │ └── DefaultAuthenticationContextBuilderTest.java │ │ ├── manager/ │ │ │ └── AuthenticationMetadataManagerTest.java │ │ └── provider/ │ │ └── LocalAuthenticationMetadataProviderTest.java │ ├── authorization/ │ │ ├── AuthorizationEvaluatorTest.java │ │ ├── builder/ │ │ │ └── DefaultAuthorizationContextBuilderTest.java │ │ ├── chain/ │ │ │ ├── AclAuthorizationHandlerTest.java │ │ │ └── UserAuthorizationHandlerTest.java │ │ ├── manager/ │ │ │ └── AuthorizationMetadataManagerTest.java │ │ ├── model/ │ │ │ └── ResourceTest.java │ │ ├── provider/ │ │ │ └── LocalAuthorizationMetadataProviderTest.java │ │ └── strategy/ │ │ └── StatefulAuthorizationStrategyTest.java │ ├── helper/ │ │ └── AuthTestHelper.java │ └── migration/ │ └── AuthMigratorTest.java ├── bazel/ │ ├── BUILD.bazel │ └── GenTestRules.bzl ├── broker/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── rocketmq/ │ │ │ └── broker/ │ │ │ ├── BrokerController.java │ │ │ ├── BrokerPathConfigHelper.java │ │ │ ├── BrokerPreOnlineService.java │ │ │ ├── BrokerStartup.java │ │ │ ├── ConfigContext.java │ │ │ ├── ShutdownHook.java │ │ │ ├── auth/ │ │ │ │ ├── converter/ │ │ │ │ │ ├── AclConverter.java │ │ │ │ │ └── UserConverter.java │ │ │ │ └── pipeline/ │ │ │ │ ├── AuthenticationPipeline.java │ │ │ │ └── AuthorizationPipeline.java │ │ │ ├── client/ │ │ │ │ ├── ClientChannelAttributeHelper.java │ │ │ │ ├── ClientChannelInfo.java │ │ │ │ ├── ClientHousekeepingService.java │ │ │ │ ├── ConsumerGroupEvent.java │ │ │ │ ├── ConsumerGroupInfo.java │ │ │ │ ├── ConsumerIdsChangeListener.java │ │ │ │ ├── ConsumerManager.java │ │ │ │ ├── DefaultConsumerIdsChangeListener.java │ │ │ │ ├── ProducerChangeListener.java │ │ │ │ ├── ProducerGroupEvent.java │ │ │ │ ├── ProducerManager.java │ │ │ │ ├── net/ │ │ │ │ │ └── Broker2Client.java │ │ │ │ └── rebalance/ │ │ │ │ └── RebalanceLockManager.java │ │ │ ├── coldctr/ │ │ │ │ ├── ColdCtrStrategy.java │ │ │ │ ├── ColdDataCgCtrService.java │ │ │ │ ├── ColdDataPullRequestHoldService.java │ │ │ │ ├── PIDAdaptiveColdCtrStrategy.java │ │ │ │ └── SimpleColdCtrStrategy.java │ │ │ ├── config/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── RocksDBConfigManager.java │ │ │ │ │ ├── RocksDBConsumerOffsetManager.java │ │ │ │ │ ├── RocksDBLmqSubscriptionGroupManager.java │ │ │ │ │ ├── RocksDBLmqTopicConfigManager.java │ │ │ │ │ ├── RocksDBOffsetSerializeWrapper.java │ │ │ │ │ ├── RocksDBSubscriptionGroupManager.java │ │ │ │ │ └── RocksDBTopicConfigManager.java │ │ │ │ └── v2/ │ │ │ │ ├── ConfigHelper.java │ │ │ │ ├── ConfigStorage.java │ │ │ │ ├── ConsumerOffsetManagerV2.java │ │ │ │ ├── RecordPrefix.java │ │ │ │ ├── SerializationType.java │ │ │ │ ├── SubscriptionGroupManagerV2.java │ │ │ │ ├── TableId.java │ │ │ │ ├── TablePrefix.java │ │ │ │ ├── TopicConfigManagerV2.java │ │ │ │ └── package-info.java │ │ │ ├── controller/ │ │ │ │ └── ReplicasManager.java │ │ │ ├── dledger/ │ │ │ │ └── DLedgerRoleChangeHandler.java │ │ │ ├── failover/ │ │ │ │ └── EscapeBridge.java │ │ │ ├── filter/ │ │ │ │ ├── CommitLogDispatcherCalcBitMap.java │ │ │ │ ├── ConsumerFilterData.java │ │ │ │ ├── ConsumerFilterManager.java │ │ │ │ ├── ExpressionForRetryMessageFilter.java │ │ │ │ ├── ExpressionMessageFilter.java │ │ │ │ └── MessageEvaluationContext.java │ │ │ ├── latency/ │ │ │ │ └── BrokerFastFailure.java │ │ │ ├── lite/ │ │ │ │ ├── AbstractLiteLifecycleManager.java │ │ │ │ ├── LiteCtlListener.java │ │ │ │ ├── LiteEventDispatcher.java │ │ │ │ ├── LiteLifecycleManager.java │ │ │ │ ├── LiteMetadataUtil.java │ │ │ │ ├── LiteQuotaException.java │ │ │ │ ├── LiteSharding.java │ │ │ │ ├── LiteShardingImpl.java │ │ │ │ ├── LiteSubscriptionRegistry.java │ │ │ │ ├── LiteSubscriptionRegistryImpl.java │ │ │ │ └── RocksDBLiteLifecycleManager.java │ │ │ ├── loadbalance/ │ │ │ │ └── MessageRequestModeManager.java │ │ │ ├── longpolling/ │ │ │ │ ├── LmqPullRequestHoldService.java │ │ │ │ ├── ManyPullRequest.java │ │ │ │ ├── NotificationRequest.java │ │ │ │ ├── NotifyMessageArrivingListener.java │ │ │ │ ├── PollingHeader.java │ │ │ │ ├── PollingResult.java │ │ │ │ ├── PopCommandCallback.java │ │ │ │ ├── PopLiteLongPollingService.java │ │ │ │ ├── PopLongPollingService.java │ │ │ │ ├── PopRequest.java │ │ │ │ ├── PullRequest.java │ │ │ │ └── PullRequestHoldService.java │ │ │ ├── metrics/ │ │ │ │ ├── BrokerMetricsConstant.java │ │ │ │ ├── BrokerMetricsManager.java │ │ │ │ ├── ConsumerAttr.java │ │ │ │ ├── ConsumerLagCalculator.java │ │ │ │ ├── InvocationStatus.java │ │ │ │ ├── LiteConsumerLagCalculator.java │ │ │ │ ├── PopMetricsConstant.java │ │ │ │ ├── PopMetricsManager.java │ │ │ │ ├── PopReviveMessageType.java │ │ │ │ └── ProducerAttr.java │ │ │ ├── mqtrace/ │ │ │ │ ├── ConsumeMessageContext.java │ │ │ │ ├── ConsumeMessageHook.java │ │ │ │ ├── SendMessageContext.java │ │ │ │ └── SendMessageHook.java │ │ │ ├── offset/ │ │ │ │ ├── BroadcastOffsetManager.java │ │ │ │ ├── BroadcastOffsetStore.java │ │ │ │ ├── ConsumerOffsetManager.java │ │ │ │ ├── LmqConsumerOffsetManager.java │ │ │ │ └── MemoryConsumerOrderInfoManager.java │ │ │ ├── out/ │ │ │ │ └── BrokerOuterAPI.java │ │ │ ├── pagecache/ │ │ │ │ ├── ManyMessageTransfer.java │ │ │ │ ├── OneMessageTransfer.java │ │ │ │ └── QueryMessageTransfer.java │ │ │ ├── plugin/ │ │ │ │ ├── BrokerAttachedPlugin.java │ │ │ │ └── PullMessageResultHandler.java │ │ │ ├── pop/ │ │ │ │ ├── PopConsumerCache.java │ │ │ │ ├── PopConsumerContext.java │ │ │ │ ├── PopConsumerKVStore.java │ │ │ │ ├── PopConsumerLockService.java │ │ │ │ ├── PopConsumerRecord.java │ │ │ │ ├── PopConsumerRocksdbStore.java │ │ │ │ ├── PopConsumerService.java │ │ │ │ └── orderly/ │ │ │ │ ├── ConsumerOrderInfoManager.java │ │ │ │ ├── QueueLevelConsumerManager.java │ │ │ │ └── QueueLevelConsumerOrderInfoLockManager.java │ │ │ ├── processor/ │ │ │ │ ├── AbstractSendMessageProcessor.java │ │ │ │ ├── AckMessageProcessor.java │ │ │ │ ├── AdminBrokerProcessor.java │ │ │ │ ├── ChangeInvisibleTimeProcessor.java │ │ │ │ ├── ClientManageProcessor.java │ │ │ │ ├── ConsumerManageProcessor.java │ │ │ │ ├── DefaultPullMessageResultHandler.java │ │ │ │ ├── EndTransactionProcessor.java │ │ │ │ ├── LiteManagerProcessor.java │ │ │ │ ├── LiteSubscriptionCtlProcessor.java │ │ │ │ ├── NotificationProcessor.java │ │ │ │ ├── PeekMessageProcessor.java │ │ │ │ ├── PollingInfoProcessor.java │ │ │ │ ├── PopBufferMergeService.java │ │ │ │ ├── PopInflightMessageCounter.java │ │ │ │ ├── PopLiteMessageProcessor.java │ │ │ │ ├── PopMessageProcessor.java │ │ │ │ ├── PopReviveService.java │ │ │ │ ├── PullMessageProcessor.java │ │ │ │ ├── QueryAssignmentProcessor.java │ │ │ │ ├── QueryMessageProcessor.java │ │ │ │ ├── RecallMessageProcessor.java │ │ │ │ ├── ReplyMessageProcessor.java │ │ │ │ ├── SendMessageCallback.java │ │ │ │ └── SendMessageProcessor.java │ │ │ ├── schedule/ │ │ │ │ ├── DelayOffsetSerializeWrapper.java │ │ │ │ └── ScheduleMessageService.java │ │ │ ├── slave/ │ │ │ │ └── SlaveSynchronize.java │ │ │ ├── subscription/ │ │ │ │ ├── LmqSubscriptionGroupManager.java │ │ │ │ └── SubscriptionGroupManager.java │ │ │ ├── topic/ │ │ │ │ ├── LmqTopicConfigManager.java │ │ │ │ ├── TopicConfigManager.java │ │ │ │ ├── TopicQueueMappingCleanService.java │ │ │ │ ├── TopicQueueMappingManager.java │ │ │ │ └── TopicRouteInfoManager.java │ │ │ ├── transaction/ │ │ │ │ ├── AbstractTransactionalMessageCheckListener.java │ │ │ │ ├── OperationResult.java │ │ │ │ ├── TransactionMetrics.java │ │ │ │ ├── TransactionMetricsFlushService.java │ │ │ │ ├── TransactionalMessageCheckService.java │ │ │ │ ├── TransactionalMessageService.java │ │ │ │ ├── queue/ │ │ │ │ │ ├── DefaultTransactionalMessageCheckListener.java │ │ │ │ │ ├── GetResult.java │ │ │ │ │ ├── MessageQueueOpContext.java │ │ │ │ │ ├── TransactionalMessageBridge.java │ │ │ │ │ ├── TransactionalMessageServiceImpl.java │ │ │ │ │ ├── TransactionalMessageUtil.java │ │ │ │ │ └── TransactionalOpBatchService.java │ │ │ │ └── rocksdb/ │ │ │ │ └── TransactionalMessageRocksDBService.java │ │ │ └── util/ │ │ │ ├── HookUtils.java │ │ │ └── PositiveAtomicCounter.java │ │ └── resources/ │ │ ├── rmq.broker.logback.xml │ │ └── transaction.sql │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── broker/ │ │ ├── BrokerControllerTest.java │ │ ├── BrokerOuterAPITest.java │ │ ├── BrokerPathConfigHelperTest.java │ │ ├── BrokerShutdownTest.java │ │ ├── BrokerStartupTest.java │ │ ├── RocksDBConfigManagerTest.java │ │ ├── client/ │ │ │ ├── ConsumerManagerScannerTest.java │ │ │ ├── ConsumerManagerTest.java │ │ │ ├── ProducerManagerTest.java │ │ │ ├── net/ │ │ │ │ └── Broker2ClientTest.java │ │ │ └── rebalance/ │ │ │ └── RebalanceLockManagerTest.java │ │ ├── coldctr/ │ │ │ └── ColdDataCgCtrServiceTest.java │ │ ├── config/ │ │ │ ├── v1/ │ │ │ │ ├── RocksDBConsumerOffsetManagerMigrationTest.java │ │ │ │ ├── RocksDBSubscriptionGroupManagerMigrationTest.java │ │ │ │ ├── RocksDBSubscriptionGroupManagerTest.java │ │ │ │ ├── RocksDBTopicConfigManagerMigrationTest.java │ │ │ │ └── RocksDBTopicConfigManagerTest.java │ │ │ └── v2/ │ │ │ ├── ConsumerOffsetManagerV2Test.java │ │ │ ├── SubscriptionGroupManagerV2Test.java │ │ │ └── TopicConfigManagerV2Test.java │ │ ├── controller/ │ │ │ ├── ReplicasManagerRegisterTest.java │ │ │ └── ReplicasManagerTest.java │ │ ├── failover/ │ │ │ └── EscapeBridgeTest.java │ │ ├── filter/ │ │ │ ├── CommitLogDispatcherCalcBitMapTest.java │ │ │ ├── ConsumerFilterManagerTest.java │ │ │ └── MessageStoreWithFilterTest.java │ │ ├── latency/ │ │ │ └── BrokerFastFailureTest.java │ │ ├── lite/ │ │ │ ├── AbstractLiteLifecycleManagerTest.java │ │ │ ├── LiteEventDispatcherTest.java │ │ │ ├── LiteLifecycleManagerTest.java │ │ │ ├── LiteShardingImplTest.java │ │ │ ├── LiteSubscriptionRegistryImplTest.java │ │ │ ├── LiteTestUtil.java │ │ │ └── RocksDBLiteLifecycleManagerTest.java │ │ ├── longpolling/ │ │ │ ├── PopLiteLongPollingServiceTest.java │ │ │ ├── PopLongPollingServiceTest.java │ │ │ └── PullRequestHoldServiceTest.java │ │ ├── metrics/ │ │ │ ├── BrokerMetricsManagerTest.java │ │ │ └── LiteConsumerLagCalculatorTest.java │ │ ├── offset/ │ │ │ ├── BroadcastOffsetManagerTest.java │ │ │ ├── BroadcastOffsetStoreTest.java │ │ │ ├── ConsumerOffsetManagerTest.java │ │ │ ├── LmqConsumerOffsetManagerTest.java │ │ │ ├── RocksDBConsumerOffsetManagerTest.java │ │ │ ├── RocksDBLmqConsumerOffsetManagerTest.java │ │ │ ├── RocksDBOffsetSerializeWrapperTest.java │ │ │ └── RocksdbTransferOffsetAndCqTest.java │ │ ├── pagecache/ │ │ │ ├── ManyMessageTransferTest.java │ │ │ ├── OneMessageTransferTest.java │ │ │ └── QueryMessageTransferTest.java │ │ ├── pop/ │ │ │ ├── PopConsumerCacheTest.java │ │ │ ├── PopConsumerContextTest.java │ │ │ ├── PopConsumerLockServiceTest.java │ │ │ ├── PopConsumerRecordTest.java │ │ │ ├── PopConsumerRocksdbStoreTest.java │ │ │ ├── PopConsumerServiceTest.java │ │ │ └── orderly/ │ │ │ ├── ConsumerOrderInfoManagerLockFreeNotifyTest.java │ │ │ └── ConsumerOrderInfoManagerTest.java │ │ ├── processor/ │ │ │ ├── AckMessageProcessorTest.java │ │ │ ├── AdminBrokerProcessorTest.java │ │ │ ├── ChangeInvisibleTimeProcessorTest.java │ │ │ ├── ClientManageProcessorTest.java │ │ │ ├── ConsumerManageProcessorTest.java │ │ │ ├── EndTransactionProcessorTest.java │ │ │ ├── LiteManagerProcessorTest.java │ │ │ ├── LiteSubscriptionCtlProcessorTest.java │ │ │ ├── PeekMessageProcessorTest.java │ │ │ ├── PopBufferMergeServiceTest.java │ │ │ ├── PopInflightMessageCounterTest.java │ │ │ ├── PopLiteMessageProcessorTest.java │ │ │ ├── PopMessageProcessorTest.java │ │ │ ├── PopReviveServiceTest.java │ │ │ ├── PullMessageProcessorTest.java │ │ │ ├── QueryAssignmentProcessorTest.java │ │ │ ├── QueryMessageProcessorTest.java │ │ │ ├── RecallMessageProcessorTest.java │ │ │ ├── ReplyMessageProcessorTest.java │ │ │ └── SendMessageProcessorTest.java │ │ ├── schedule/ │ │ │ └── ScheduleMessageServiceTest.java │ │ ├── slave/ │ │ │ ├── SlaveSynchronizeAtomicTest.java │ │ │ └── SlaveSynchronizeTest.java │ │ ├── subscription/ │ │ │ ├── ForbiddenTest.java │ │ │ ├── RocksdbGroupConfigTransferTest.java │ │ │ └── SubscriptionGroupManagerTest.java │ │ ├── topic/ │ │ │ ├── RocksdbTopicConfigManagerTest.java │ │ │ ├── RocksdbTopicConfigTransferTest.java │ │ │ ├── TopicConfigManagerTest.java │ │ │ ├── TopicQueueMappingCleanServiceTest.java │ │ │ └── TopicQueueMappingManagerTest.java │ │ ├── transaction/ │ │ │ └── queue/ │ │ │ ├── DefaultTransactionalMessageCheckListenerTest.java │ │ │ ├── TransactionMetricsTest.java │ │ │ ├── TransactionalMessageBridgeTest.java │ │ │ ├── TransactionalMessageServiceImplTest.java │ │ │ └── TransactionalMessageUtilTest.java │ │ └── util/ │ │ ├── HookUtilsTest.java │ │ ├── LogTransactionalMessageCheckListener.java │ │ ├── ServiceProviderTest.java │ │ └── TransactionalMessageServiceImpl.java │ └── resources/ │ ├── META-INF/ │ │ └── service/ │ │ ├── org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener │ │ └── org.apache.rocketmq.broker.transaction.TransactionalMessageService │ └── rmq.logback-test.xml ├── client/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── rocketmq/ │ │ │ ├── acl/ │ │ │ │ └── common/ │ │ │ │ ├── AclClientRPCHook.java │ │ │ │ ├── AclConstants.java │ │ │ │ ├── AclException.java │ │ │ │ ├── AclSigner.java │ │ │ │ ├── AclUtils.java │ │ │ │ ├── Permission.java │ │ │ │ ├── SessionCredentials.java │ │ │ │ └── SigningAlgorithm.java │ │ │ └── client/ │ │ │ ├── AccessChannel.java │ │ │ ├── ClientConfig.java │ │ │ ├── MQAdmin.java │ │ │ ├── MQHelper.java │ │ │ ├── MqClientAdmin.java │ │ │ ├── QueryResult.java │ │ │ ├── Validators.java │ │ │ ├── admin/ │ │ │ │ └── MQAdminExtInner.java │ │ │ ├── common/ │ │ │ │ ├── ClientErrorCode.java │ │ │ │ ├── NameserverAccessConfig.java │ │ │ │ └── ThreadLocalIndex.java │ │ │ ├── consumer/ │ │ │ │ ├── AckCallback.java │ │ │ │ ├── AckResult.java │ │ │ │ ├── AckStatus.java │ │ │ │ ├── AllocateMessageQueueStrategy.java │ │ │ │ ├── DefaultLitePullConsumer.java │ │ │ │ ├── DefaultMQPullConsumer.java │ │ │ │ ├── DefaultMQPushConsumer.java │ │ │ │ ├── LitePullConsumer.java │ │ │ │ ├── MQConsumer.java │ │ │ │ ├── MQPullConsumer.java │ │ │ │ ├── MQPullConsumerScheduleService.java │ │ │ │ ├── MQPushConsumer.java │ │ │ │ ├── MessageQueueListener.java │ │ │ │ ├── MessageSelector.java │ │ │ │ ├── NotifyResult.java │ │ │ │ ├── PopCallback.java │ │ │ │ ├── PopResult.java │ │ │ │ ├── PopStatus.java │ │ │ │ ├── PullCallback.java │ │ │ │ ├── PullResult.java │ │ │ │ ├── PullStatus.java │ │ │ │ ├── PullTaskCallback.java │ │ │ │ ├── PullTaskContext.java │ │ │ │ ├── TopicMessageQueueChangeListener.java │ │ │ │ ├── listener/ │ │ │ │ │ ├── ConsumeConcurrentlyContext.java │ │ │ │ │ ├── ConsumeConcurrentlyStatus.java │ │ │ │ │ ├── ConsumeOrderlyContext.java │ │ │ │ │ ├── ConsumeOrderlyStatus.java │ │ │ │ │ ├── ConsumeReturnType.java │ │ │ │ │ ├── MessageListener.java │ │ │ │ │ ├── MessageListenerConcurrently.java │ │ │ │ │ └── MessageListenerOrderly.java │ │ │ │ ├── rebalance/ │ │ │ │ │ ├── AbstractAllocateMessageQueueStrategy.java │ │ │ │ │ ├── AllocateMachineRoomNearby.java │ │ │ │ │ ├── AllocateMessageQueueAveragely.java │ │ │ │ │ ├── AllocateMessageQueueAveragelyByCircle.java │ │ │ │ │ ├── AllocateMessageQueueByConfig.java │ │ │ │ │ ├── AllocateMessageQueueByMachineRoom.java │ │ │ │ │ └── AllocateMessageQueueConsistentHash.java │ │ │ │ └── store/ │ │ │ │ ├── ControllableOffset.java │ │ │ │ ├── LocalFileOffsetStore.java │ │ │ │ ├── OffsetSerializeWrapper.java │ │ │ │ ├── OffsetStore.java │ │ │ │ ├── ReadOffsetType.java │ │ │ │ └── RemoteBrokerOffsetStore.java │ │ │ ├── exception/ │ │ │ │ ├── MQBrokerException.java │ │ │ │ ├── MQClientException.java │ │ │ │ ├── OffsetNotFoundException.java │ │ │ │ └── RequestTimeoutException.java │ │ │ ├── hook/ │ │ │ │ ├── CheckForbiddenContext.java │ │ │ │ ├── CheckForbiddenHook.java │ │ │ │ ├── ConsumeMessageContext.java │ │ │ │ ├── ConsumeMessageHook.java │ │ │ │ ├── EndTransactionContext.java │ │ │ │ ├── EndTransactionHook.java │ │ │ │ ├── FilterMessageContext.java │ │ │ │ ├── FilterMessageHook.java │ │ │ │ ├── SendMessageContext.java │ │ │ │ └── SendMessageHook.java │ │ │ ├── impl/ │ │ │ │ ├── ClientRemotingProcessor.java │ │ │ │ ├── CommunicationMode.java │ │ │ │ ├── FindBrokerResult.java │ │ │ │ ├── MQAdminImpl.java │ │ │ │ ├── MQClientAPIImpl.java │ │ │ │ ├── MQClientManager.java │ │ │ │ ├── admin/ │ │ │ │ │ └── MqClientAdminImpl.java │ │ │ │ ├── consumer/ │ │ │ │ │ ├── AssignedMessageQueue.java │ │ │ │ │ ├── ConsumeMessageConcurrentlyService.java │ │ │ │ │ ├── ConsumeMessageOrderlyService.java │ │ │ │ │ ├── ConsumeMessagePopConcurrentlyService.java │ │ │ │ │ ├── ConsumeMessagePopOrderlyService.java │ │ │ │ │ ├── ConsumeMessageService.java │ │ │ │ │ ├── DefaultLitePullConsumerImpl.java │ │ │ │ │ ├── DefaultMQPullConsumerImpl.java │ │ │ │ │ ├── DefaultMQPushConsumerImpl.java │ │ │ │ │ ├── MQConsumerInner.java │ │ │ │ │ ├── MessageQueueLock.java │ │ │ │ │ ├── MessageRequest.java │ │ │ │ │ ├── PopProcessQueue.java │ │ │ │ │ ├── PopRequest.java │ │ │ │ │ ├── ProcessQueue.java │ │ │ │ │ ├── PullAPIWrapper.java │ │ │ │ │ ├── PullMessageService.java │ │ │ │ │ ├── PullRequest.java │ │ │ │ │ ├── PullResultExt.java │ │ │ │ │ ├── RebalanceImpl.java │ │ │ │ │ ├── RebalanceLitePullImpl.java │ │ │ │ │ ├── RebalancePullImpl.java │ │ │ │ │ ├── RebalancePushImpl.java │ │ │ │ │ └── RebalanceService.java │ │ │ │ ├── factory/ │ │ │ │ │ └── MQClientInstance.java │ │ │ │ ├── mqclient/ │ │ │ │ │ ├── DoNothingClientRemotingProcessor.java │ │ │ │ │ ├── MQClientAPIExt.java │ │ │ │ │ └── MQClientAPIFactory.java │ │ │ │ └── producer/ │ │ │ │ ├── DefaultMQProducerImpl.java │ │ │ │ ├── MQProducerInner.java │ │ │ │ └── TopicPublishInfo.java │ │ │ ├── latency/ │ │ │ │ ├── LatencyFaultTolerance.java │ │ │ │ ├── LatencyFaultToleranceImpl.java │ │ │ │ ├── MQFaultStrategy.java │ │ │ │ ├── Resolver.java │ │ │ │ └── ServiceDetector.java │ │ │ ├── lock/ │ │ │ │ └── ReadWriteCASLock.java │ │ │ ├── producer/ │ │ │ │ ├── DefaultMQProducer.java │ │ │ │ ├── LocalTransactionState.java │ │ │ │ ├── MQProducer.java │ │ │ │ ├── MessageQueueSelector.java │ │ │ │ ├── ProduceAccumulator.java │ │ │ │ ├── RequestCallback.java │ │ │ │ ├── RequestFutureHolder.java │ │ │ │ ├── RequestResponseFuture.java │ │ │ │ ├── SendCallback.java │ │ │ │ ├── SendResult.java │ │ │ │ ├── SendStatus.java │ │ │ │ ├── TransactionCheckListener.java │ │ │ │ ├── TransactionListener.java │ │ │ │ ├── TransactionMQProducer.java │ │ │ │ ├── TransactionSendResult.java │ │ │ │ └── selector/ │ │ │ │ ├── SelectMessageQueueByHash.java │ │ │ │ ├── SelectMessageQueueByMachineRoom.java │ │ │ │ └── SelectMessageQueueByRandom.java │ │ │ ├── rpchook/ │ │ │ │ └── NamespaceRpcHook.java │ │ │ ├── stat/ │ │ │ │ └── ConsumerStatsManager.java │ │ │ ├── trace/ │ │ │ │ ├── AsyncTraceDispatcher.java │ │ │ │ ├── TraceBean.java │ │ │ │ ├── TraceConstants.java │ │ │ │ ├── TraceContext.java │ │ │ │ ├── TraceDataEncoder.java │ │ │ │ ├── TraceDispatcher.java │ │ │ │ ├── TraceDispatcherType.java │ │ │ │ ├── TraceTransferBean.java │ │ │ │ ├── TraceType.java │ │ │ │ ├── TraceView.java │ │ │ │ └── hook/ │ │ │ │ ├── ConsumeMessageOpenTracingHookImpl.java │ │ │ │ ├── ConsumeMessageTraceHookImpl.java │ │ │ │ ├── DefaultRecallMessageTraceHook.java │ │ │ │ ├── EndTransactionOpenTracingHookImpl.java │ │ │ │ ├── EndTransactionTraceHookImpl.java │ │ │ │ ├── SendMessageOpenTracingHookImpl.java │ │ │ │ └── SendMessageTraceHookImpl.java │ │ │ └── utils/ │ │ │ └── MessageUtil.java │ │ └── resources/ │ │ └── rmq.client.logback.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ ├── acl/ │ │ │ └── common/ │ │ │ ├── AclClientRPCHookTest.java │ │ │ ├── AclSignerTest.java │ │ │ ├── AclUtilsTest.java │ │ │ ├── PermissionTest.java │ │ │ └── SessionCredentialsTest.java │ │ └── client/ │ │ ├── ClientConfigTest.java │ │ ├── ValidatorsTest.java │ │ ├── common/ │ │ │ └── ThreadLocalIndexTest.java │ │ ├── consumer/ │ │ │ ├── DefaultLitePullConsumerTest.java │ │ │ ├── DefaultMQPullConsumerTest.java │ │ │ ├── DefaultMQPushConsumerTest.java │ │ │ ├── rebalance/ │ │ │ │ ├── AllocateMachineRoomNearByTest.java │ │ │ │ ├── AllocateMessageQueueAveragelyByCircleTest.java │ │ │ │ ├── AllocateMessageQueueAveragelyTest.java │ │ │ │ ├── AllocateMessageQueueByConfigTest.java │ │ │ │ ├── AllocateMessageQueueByMachineRoomTest.java │ │ │ │ └── AllocateMessageQueueConsitentHashTest.java │ │ │ └── store/ │ │ │ ├── ControllableOffsetTest.java │ │ │ ├── LocalFileOffsetStoreTest.java │ │ │ └── RemoteBrokerOffsetStoreTest.java │ │ ├── impl/ │ │ │ ├── ClientRemotingProcessorTest.java │ │ │ ├── MQAdminImplTest.java │ │ │ ├── MQClientAPIImplTest.java │ │ │ ├── admin/ │ │ │ │ └── MqClientAdminImplTest.java │ │ │ ├── consumer/ │ │ │ │ ├── ConsumeMessageConcurrentlyServiceTest.java │ │ │ │ ├── ConsumeMessageOrderlyServiceTest.java │ │ │ │ ├── ConsumeMessagePopConcurrentlyServiceTest.java │ │ │ │ ├── ConsumeMessagePopOrderlyServiceTest.java │ │ │ │ ├── DefaultLitePullConsumerImplTest.java │ │ │ │ ├── DefaultMQPushConsumerImplTest.java │ │ │ │ ├── PopProcessQueueTest.java │ │ │ │ ├── ProcessQueueTest.java │ │ │ │ ├── PullAPIWrapperTest.java │ │ │ │ ├── PullMessageServiceTest.java │ │ │ │ ├── RebalanceLitePullImplTest.java │ │ │ │ └── RebalancePushImplTest.java │ │ │ ├── factory/ │ │ │ │ └── MQClientInstanceTest.java │ │ │ └── mqclient/ │ │ │ ├── MQClientAPIExtTest.java │ │ │ └── MQClientAPITest.java │ │ ├── latency/ │ │ │ └── LatencyFaultToleranceImplTest.java │ │ ├── producer/ │ │ │ ├── DefaultMQProducerTest.java │ │ │ ├── ProduceAccumulatorTest.java │ │ │ ├── RequestResponseFutureTest.java │ │ │ ├── SendResultTest.java │ │ │ └── selector/ │ │ │ ├── DefaultMQProducerImplTest.java │ │ │ ├── SelectMessageQueueByHashTest.java │ │ │ ├── SelectMessageQueueByRandomTest.java │ │ │ └── SelectMessageQueueRetryTest.java │ │ ├── rpchook/ │ │ │ └── NamespaceRpcHookTest.java │ │ ├── trace/ │ │ │ ├── DefaultMQConsumerWithOpenTracingTest.java │ │ │ ├── DefaultMQConsumerWithTraceTest.java │ │ │ ├── DefaultMQLitePullConsumerWithTraceTest.java │ │ │ ├── DefaultMQProducerWithOpenTracingTest.java │ │ │ ├── DefaultMQProducerWithTraceTest.java │ │ │ ├── TraceDataEncoderTest.java │ │ │ ├── TraceViewTest.java │ │ │ ├── TransactionMQProducerWithOpenTracingTest.java │ │ │ └── TransactionMQProducerWithTraceTest.java │ │ └── utils/ │ │ └── MessageUtilsTest.java │ └── resources/ │ ├── acl_hook/ │ │ └── plain_acl.yml │ ├── conf/ │ │ └── plain_acl_incomplete.yml │ ├── org/ │ │ └── powermock/ │ │ └── extensions/ │ │ └── configuration.properties │ └── rmq.logback-test.xml ├── common/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── rocketmq/ │ │ │ └── common/ │ │ │ ├── AbortProcessException.java │ │ │ ├── BoundaryType.java │ │ │ ├── BrokerConfig.java │ │ │ ├── BrokerConfigSingleton.java │ │ │ ├── BrokerIdentity.java │ │ │ ├── CheckRocksdbCqWriteResult.java │ │ │ ├── ConfigManager.java │ │ │ ├── ControllerConfig.java │ │ │ ├── CountDownLatch2.java │ │ │ ├── JraftConfig.java │ │ │ ├── KeyBuilder.java │ │ │ ├── LifecycleAwareServiceThread.java │ │ │ ├── LockCallback.java │ │ │ ├── MQVersion.java │ │ │ ├── MixAll.java │ │ │ ├── ObjectCreator.java │ │ │ ├── OrderedConsumptionLevel.java │ │ │ ├── Pair.java │ │ │ ├── PopAckConstants.java │ │ │ ├── ServiceState.java │ │ │ ├── ServiceThread.java │ │ │ ├── SubscriptionGroupAttributes.java │ │ │ ├── SystemClock.java │ │ │ ├── ThreadFactoryImpl.java │ │ │ ├── TopicAttributes.java │ │ │ ├── TopicConfig.java │ │ │ ├── TopicFilterType.java │ │ │ ├── TopicQueueId.java │ │ │ ├── UnlockCallback.java │ │ │ ├── UtilAll.java │ │ │ ├── action/ │ │ │ │ ├── Action.java │ │ │ │ └── RocketMQAction.java │ │ │ ├── annotation/ │ │ │ │ └── ImportantField.java │ │ │ ├── attribute/ │ │ │ │ ├── Attribute.java │ │ │ │ ├── AttributeParser.java │ │ │ │ ├── AttributeUtil.java │ │ │ │ ├── BooleanAttribute.java │ │ │ │ ├── CQType.java │ │ │ │ ├── CleanupPolicy.java │ │ │ │ ├── EnumAttribute.java │ │ │ │ ├── LiteSubModel.java │ │ │ │ ├── LongRangeAttribute.java │ │ │ │ ├── StringAttribute.java │ │ │ │ └── TopicMessageType.java │ │ │ ├── chain/ │ │ │ │ ├── Handler.java │ │ │ │ └── HandlerChain.java │ │ │ ├── coldctr/ │ │ │ │ └── AccAndTimeStamp.java │ │ │ ├── compression/ │ │ │ │ ├── CompressionType.java │ │ │ │ ├── Compressor.java │ │ │ │ ├── CompressorFactory.java │ │ │ │ ├── Lz4Compressor.java │ │ │ │ ├── ZlibCompressor.java │ │ │ │ └── ZstdCompressor.java │ │ │ ├── config/ │ │ │ │ ├── AbstractRocksDBStorage.java │ │ │ │ ├── ConfigHelper.java │ │ │ │ ├── ConfigManagerVersion.java │ │ │ │ └── ConfigRocksDBStorage.java │ │ │ ├── consistenthash/ │ │ │ │ ├── ConsistentHashRouter.java │ │ │ │ ├── HashFunction.java │ │ │ │ ├── Node.java │ │ │ │ └── VirtualNode.java │ │ │ ├── constant/ │ │ │ │ ├── CommonConstants.java │ │ │ │ ├── ConsumeInitMode.java │ │ │ │ ├── DBMsgConstants.java │ │ │ │ ├── FIleReadaheadMode.java │ │ │ │ ├── GrpcConstants.java │ │ │ │ ├── HAProxyConstants.java │ │ │ │ ├── LoggerName.java │ │ │ │ └── PermName.java │ │ │ ├── consumer/ │ │ │ │ ├── ConsumeFromWhere.java │ │ │ │ └── ReceiptHandle.java │ │ │ ├── entity/ │ │ │ │ ├── ClientGroup.java │ │ │ │ └── TopicGroup.java │ │ │ ├── fastjson/ │ │ │ │ └── GenericMapSuperclassDeserializer.java │ │ │ ├── filter/ │ │ │ │ ├── ExpressionType.java │ │ │ │ ├── FilterContext.java │ │ │ │ ├── MessageFilter.java │ │ │ │ └── impl/ │ │ │ │ ├── Op.java │ │ │ │ ├── Operand.java │ │ │ │ ├── Operator.java │ │ │ │ ├── PolishExpr.java │ │ │ │ └── Type.java │ │ │ ├── future/ │ │ │ │ └── FutureTaskExt.java │ │ │ ├── help/ │ │ │ │ └── FAQUrl.java │ │ │ ├── hook/ │ │ │ │ └── FilterCheckHook.java │ │ │ ├── lite/ │ │ │ │ ├── LiteLagInfo.java │ │ │ │ ├── LiteSubscription.java │ │ │ │ ├── LiteSubscriptionAction.java │ │ │ │ ├── LiteSubscriptionDTO.java │ │ │ │ ├── LiteUtil.java │ │ │ │ └── OffsetOption.java │ │ │ ├── logging/ │ │ │ │ ├── DefaultJoranConfiguratorExt.java │ │ │ │ └── JoranConfiguratorExt.java │ │ │ ├── message/ │ │ │ │ ├── Message.java │ │ │ │ ├── MessageAccessor.java │ │ │ │ ├── MessageBatch.java │ │ │ │ ├── MessageClientExt.java │ │ │ │ ├── MessageClientIDSetter.java │ │ │ │ ├── MessageConst.java │ │ │ │ ├── MessageDecoder.java │ │ │ │ ├── MessageExt.java │ │ │ │ ├── MessageExtBatch.java │ │ │ │ ├── MessageExtBrokerInner.java │ │ │ │ ├── MessageId.java │ │ │ │ ├── MessageQueue.java │ │ │ │ ├── MessageQueueAssignment.java │ │ │ │ ├── MessageQueueForC.java │ │ │ │ ├── MessageRequestMode.java │ │ │ │ ├── MessageType.java │ │ │ │ └── MessageVersion.java │ │ │ ├── metrics/ │ │ │ │ ├── MetricsExporterType.java │ │ │ │ ├── NopLongCounter.java │ │ │ │ ├── NopLongHistogram.java │ │ │ │ ├── NopLongUpDownCounter.java │ │ │ │ ├── NopObservableDoubleGauge.java │ │ │ │ └── NopObservableLongGauge.java │ │ │ ├── namesrv/ │ │ │ │ ├── DefaultTopAddressing.java │ │ │ │ ├── NameServerUpdateCallback.java │ │ │ │ ├── NamesrvConfig.java │ │ │ │ ├── NamesrvUtil.java │ │ │ │ └── TopAddressing.java │ │ │ ├── producer/ │ │ │ │ └── RecallMessageHandle.java │ │ │ ├── queue/ │ │ │ │ ├── ConcurrentTreeMap.java │ │ │ │ └── RoundQueue.java │ │ │ ├── resource/ │ │ │ │ ├── ResourcePattern.java │ │ │ │ ├── ResourceType.java │ │ │ │ └── RocketMQResource.java │ │ │ ├── running/ │ │ │ │ └── RunningStats.java │ │ │ ├── state/ │ │ │ │ └── StateEventListener.java │ │ │ ├── statistics/ │ │ │ │ ├── FutureHolder.java │ │ │ │ ├── Interceptor.java │ │ │ │ ├── StatisticsBrief.java │ │ │ │ ├── StatisticsBriefInterceptor.java │ │ │ │ ├── StatisticsItem.java │ │ │ │ ├── StatisticsItemFormatter.java │ │ │ │ ├── StatisticsItemPrinter.java │ │ │ │ ├── StatisticsItemScheduledIncrementPrinter.java │ │ │ │ ├── StatisticsItemScheduledPrinter.java │ │ │ │ ├── StatisticsItemStateGetter.java │ │ │ │ ├── StatisticsKindMeta.java │ │ │ │ └── StatisticsManager.java │ │ │ ├── stats/ │ │ │ │ ├── MomentStatsItem.java │ │ │ │ ├── MomentStatsItemSet.java │ │ │ │ ├── RTStatsItem.java │ │ │ │ ├── Stats.java │ │ │ │ ├── StatsItem.java │ │ │ │ ├── StatsItemSet.java │ │ │ │ └── StatsSnapshot.java │ │ │ ├── sysflag/ │ │ │ │ ├── MessageSysFlag.java │ │ │ │ ├── PullSysFlag.java │ │ │ │ ├── SubscriptionSysFlag.java │ │ │ │ └── TopicSysFlag.java │ │ │ ├── thread/ │ │ │ │ ├── FutureTaskExtThreadPoolExecutor.java │ │ │ │ ├── ThreadPoolMonitor.java │ │ │ │ ├── ThreadPoolQueueSizeMonitor.java │ │ │ │ ├── ThreadPoolStatusMonitor.java │ │ │ │ └── ThreadPoolWrapper.java │ │ │ ├── topic/ │ │ │ │ └── TopicValidator.java │ │ │ └── utils/ │ │ │ ├── AbstractStartAndShutdown.java │ │ │ ├── AsyncShutdownHelper.java │ │ │ ├── BinaryUtil.java │ │ │ ├── ChannelUtil.java │ │ │ ├── CheckpointFile.java │ │ │ ├── CleanupPolicyUtils.java │ │ │ ├── ConcurrentHashMapUtils.java │ │ │ ├── CorrelationIdUtil.java │ │ │ ├── DataConverter.java │ │ │ ├── ExceptionUtils.java │ │ │ ├── FastJsonSerializer.java │ │ │ ├── FutureUtils.java │ │ │ ├── HttpTinyClient.java │ │ │ ├── IOTinyUtils.java │ │ │ ├── IPAddressUtils.java │ │ │ ├── MessageUtils.java │ │ │ ├── NameServerAddressUtils.java │ │ │ ├── NetworkUtil.java │ │ │ ├── PositiveAtomicCounter.java │ │ │ ├── QueueTypeUtils.java │ │ │ ├── Serializer.java │ │ │ ├── ServiceProvider.java │ │ │ ├── Shutdown.java │ │ │ ├── Start.java │ │ │ ├── StartAndShutdown.java │ │ │ └── ThreadUtils.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── common/ │ │ ├── BrokerConfigSingletonTest.java │ │ ├── BrokerConfigTest.java │ │ ├── ConfigManagerTest.java │ │ ├── CountDownLatch2Test.java │ │ ├── KeyBuilderTest.java │ │ ├── MQVersionTest.java │ │ ├── MessageBatchTest.java │ │ ├── MessageEncodeDecodeTest.java │ │ ├── MessageExtBrokerInnerTest.java │ │ ├── MixAllTest.java │ │ ├── NetworkUtilTest.java │ │ ├── ServiceThreadTest.java │ │ ├── TopicConfigTest.java │ │ ├── UtilAllTest.java │ │ ├── action/ │ │ │ ├── ActionTest.java │ │ │ └── RocketMQActionTest.java │ │ ├── attribute/ │ │ │ ├── AttributeParserTest.java │ │ │ ├── AttributeTest.java │ │ │ ├── AttributeUtilTest.java │ │ │ ├── BooleanAttributeTest.java │ │ │ ├── CQTypeTest.java │ │ │ ├── CleanupPolicyTest.java │ │ │ ├── EnumAttributeTest.java │ │ │ ├── LongRangeAttributeTest.java │ │ │ └── TopicMessageTypeTest.java │ │ ├── chain/ │ │ │ └── HandlerChainTest.java │ │ ├── coldctr/ │ │ │ └── AccAndTimeStampTest.java │ │ ├── compression/ │ │ │ ├── CompressionTest.java │ │ │ ├── CompressionTypeTest.java │ │ │ ├── CompressorFactoryTest.java │ │ │ ├── Lz4CompressorTest.java │ │ │ ├── ZlibCompressorTest.java │ │ │ └── ZstdCompressorTest.java │ │ ├── config/ │ │ │ └── ConfigHelperTest.java │ │ ├── consumer/ │ │ │ └── ReceiptHandleTest.java │ │ ├── fastjson/ │ │ │ └── GenericMapSuperclassDeserializerTest.java │ │ ├── help/ │ │ │ └── FAQUrlTest.java │ │ ├── message/ │ │ │ ├── MessageClientIDSetterTest.java │ │ │ ├── MessageDecoderTest.java │ │ │ └── MessageTest.java │ │ ├── producer/ │ │ │ └── RecallMessageHandleTest.java │ │ ├── stats/ │ │ │ └── StatsItemSetTest.java │ │ ├── sysflag/ │ │ │ ├── CompressionFlagTest.java │ │ │ └── PullSysFlagTest.java │ │ ├── topic/ │ │ │ └── TopicValidatorTest.java │ │ └── utils/ │ │ ├── ConcurrentHashMapUtilsTest.java │ │ ├── IOTinyUtilsTest.java │ │ ├── IPAddressUtilsTest.java │ │ ├── LiteUtilTest.java │ │ └── NameServerAddressUtilsTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── container/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── container/ │ │ ├── BrokerBootHook.java │ │ ├── BrokerContainer.java │ │ ├── BrokerContainerConfig.java │ │ ├── BrokerContainerProcessor.java │ │ ├── BrokerContainerStartup.java │ │ ├── ContainerClientHouseKeepingService.java │ │ ├── IBrokerContainer.java │ │ ├── InnerBrokerController.java │ │ └── InnerSalveBrokerController.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── container/ │ │ ├── BrokerContainerExtensibilityTest.java │ │ ├── BrokerContainerStartupTest.java │ │ ├── BrokerContainerTest.java │ │ └── BrokerPreOnlineServiceTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── controller/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── rocketmq/ │ │ │ └── controller/ │ │ │ ├── BrokerHeartbeatManager.java │ │ │ ├── BrokerHousekeepingService.java │ │ │ ├── Controller.java │ │ │ ├── ControllerManager.java │ │ │ ├── ControllerStartup.java │ │ │ ├── elect/ │ │ │ │ ├── ElectPolicy.java │ │ │ │ └── impl/ │ │ │ │ └── DefaultElectPolicy.java │ │ │ ├── helper/ │ │ │ │ ├── BrokerLifecycleListener.java │ │ │ │ ├── BrokerLiveInfoGetter.java │ │ │ │ └── BrokerValidPredicate.java │ │ │ ├── impl/ │ │ │ │ ├── DLedgerController.java │ │ │ │ ├── DLedgerControllerStateMachine.java │ │ │ │ ├── JRaftController.java │ │ │ │ ├── JRaftControllerStateMachine.java │ │ │ │ ├── closure/ │ │ │ │ │ └── ControllerClosure.java │ │ │ │ ├── event/ │ │ │ │ │ ├── AlterSyncStateSetEvent.java │ │ │ │ │ ├── ApplyBrokerIdEvent.java │ │ │ │ │ ├── CleanBrokerDataEvent.java │ │ │ │ │ ├── ControllerResult.java │ │ │ │ │ ├── ElectMasterEvent.java │ │ │ │ │ ├── EventMessage.java │ │ │ │ │ ├── EventSerializer.java │ │ │ │ │ ├── EventType.java │ │ │ │ │ ├── ListEventSerializer.java │ │ │ │ │ └── UpdateBrokerAddressEvent.java │ │ │ │ ├── heartbeat/ │ │ │ │ │ ├── BrokerIdentityInfo.java │ │ │ │ │ ├── BrokerLiveInfo.java │ │ │ │ │ ├── DefaultBrokerHeartbeatManager.java │ │ │ │ │ └── RaftBrokerHeartBeatManager.java │ │ │ │ ├── manager/ │ │ │ │ │ ├── BrokerReplicaInfo.java │ │ │ │ │ ├── RaftReplicasInfoManager.java │ │ │ │ │ ├── ReplicasInfoManager.java │ │ │ │ │ └── SyncStateInfo.java │ │ │ │ └── task/ │ │ │ │ ├── BrokerCloseChannelRequest.java │ │ │ │ ├── BrokerCloseChannelResponse.java │ │ │ │ ├── CheckNotActiveBrokerRequest.java │ │ │ │ ├── CheckNotActiveBrokerResponse.java │ │ │ │ ├── GetBrokerLiveInfoRequest.java │ │ │ │ ├── GetBrokerLiveInfoResponse.java │ │ │ │ ├── GetSyncStateDataRequest.java │ │ │ │ ├── RaftBrokerHeartBeatEventRequest.java │ │ │ │ └── RaftBrokerHeartBeatEventResponse.java │ │ │ ├── metrics/ │ │ │ │ ├── ControllerMetricsConstant.java │ │ │ │ └── ControllerMetricsManager.java │ │ │ └── processor/ │ │ │ └── ControllerRequestProcessor.java │ │ └── resources/ │ │ └── rmq.controller.logback.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── controller/ │ │ ├── ControllerManagerTest.java │ │ ├── ControllerRequestProcessorTest.java │ │ ├── ControllerTestBase.java │ │ └── impl/ │ │ ├── DLedgerControllerTest.java │ │ ├── DefaultBrokerHeartbeatManagerTest.java │ │ ├── RaftBrokerHeartBeatManagerTest.java │ │ ├── event/ │ │ │ ├── EventSerializerTest.java │ │ │ └── ListEventSerializerTest.java │ │ ├── heartbeat/ │ │ │ └── RaftBrokerHeartBeatManagerTest.java │ │ └── manager/ │ │ ├── RaftReplicasInfoManagerTest.java │ │ └── ReplicasInfoManagerTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── dev/ │ └── merge_rocketmq_pr.py ├── distribution/ │ ├── LICENSE-BIN │ ├── NOTICE-BIN │ ├── benchmark/ │ │ ├── batchproducer.sh │ │ ├── consumer.sh │ │ ├── producer.sh │ │ ├── runclass.sh │ │ ├── shutdown.sh │ │ └── tproducer.sh │ ├── bin/ │ │ ├── README.md │ │ ├── cachedog.sh │ │ ├── cleancache.sh │ │ ├── cleancache.v1.sh │ │ ├── controller/ │ │ │ ├── fast-try-independent-deployment.cmd │ │ │ ├── fast-try-independent-deployment.sh │ │ │ ├── fast-try-namesrv-plugin.cmd │ │ │ ├── fast-try-namesrv-plugin.sh │ │ │ ├── fast-try.cmd │ │ │ └── fast-try.sh │ │ ├── dledger/ │ │ │ └── fast-try.sh │ │ ├── export.sh │ │ ├── mqadmin │ │ ├── mqadmin.cmd │ │ ├── mqbroker │ │ ├── mqbroker.cmd │ │ ├── mqbroker.numanode0 │ │ ├── mqbroker.numanode1 │ │ ├── mqbroker.numanode2 │ │ ├── mqbroker.numanode3 │ │ ├── mqbrokercontainer │ │ ├── mqcontroller │ │ ├── mqcontroller.cmd │ │ ├── mqnamesrv │ │ ├── mqnamesrv.cmd │ │ ├── mqproxy │ │ ├── mqproxy.cmd │ │ ├── mqshutdown │ │ ├── mqshutdown.cmd │ │ ├── os.sh │ │ ├── play.cmd │ │ ├── play.sh │ │ ├── runbroker.cmd │ │ ├── runbroker.sh │ │ ├── runserver.cmd │ │ ├── runserver.sh │ │ ├── setcache.sh │ │ ├── startfsrv.sh │ │ ├── tools.cmd │ │ └── tools.sh │ ├── conf/ │ │ ├── 2m-2s-async/ │ │ │ ├── broker-a-s.properties │ │ │ ├── broker-a.properties │ │ │ ├── broker-b-s.properties │ │ │ └── broker-b.properties │ │ ├── 2m-2s-sync/ │ │ │ ├── broker-a-s.properties │ │ │ ├── broker-a.properties │ │ │ ├── broker-b-s.properties │ │ │ └── broker-b.properties │ │ ├── 2m-noslave/ │ │ │ ├── broker-a.properties │ │ │ ├── broker-b.properties │ │ │ └── broker-trace.properties │ │ ├── broker.conf │ │ ├── container/ │ │ │ └── 2container-2m-2s/ │ │ │ ├── broker-a-in-container1.conf │ │ │ ├── broker-a-in-container2.conf │ │ │ ├── broker-b-in-container1.conf │ │ │ ├── broker-b-in-container2.conf │ │ │ ├── broker-container1.conf │ │ │ ├── broker-container2.conf │ │ │ └── nameserver.conf │ │ ├── controller/ │ │ │ ├── cluster-3n-independent/ │ │ │ │ ├── controller-n0.conf │ │ │ │ ├── controller-n1.conf │ │ │ │ └── controller-n2.conf │ │ │ ├── cluster-3n-namesrv-plugin/ │ │ │ │ ├── namesrv-n0.conf │ │ │ │ ├── namesrv-n1.conf │ │ │ │ └── namesrv-n2.conf │ │ │ ├── controller-standalone.conf │ │ │ └── quick-start/ │ │ │ ├── broker-n0.conf │ │ │ ├── broker-n1.conf │ │ │ └── namesrv.conf │ │ ├── dledger/ │ │ │ ├── broker-n0.conf │ │ │ ├── broker-n1.conf │ │ │ └── broker-n2.conf │ │ ├── rmq-proxy.json │ │ └── tools.yml │ ├── pom.xml │ ├── release-client.xml │ └── release.xml ├── docs/ │ ├── cn/ │ │ ├── BrokerContainer.md │ │ ├── Configuration_System.md │ │ ├── Configuration_TLS.md │ │ ├── Debug_In_Idea.md │ │ ├── Deployment.md │ │ ├── Example_Batch.md │ │ ├── Example_Compaction_Topic_cn.md │ │ ├── Example_CreateTopic.md │ │ ├── Example_Delay.md │ │ ├── Example_LMQ.md │ │ ├── Example_Simple_cn.md │ │ ├── FAQ.md │ │ ├── QuorumACK.md │ │ ├── README.md │ │ ├── RocketMQ_Example.md │ │ ├── SlaveActingMasterMode.md │ │ ├── acl/ │ │ │ ├── RocketMQ_Multiple_ACL_Files_设计.md │ │ │ └── user_guide.md │ │ ├── architecture.md │ │ ├── best_practice.md │ │ ├── client/ │ │ │ └── java/ │ │ │ ├── API_Reference_ DefaultPullConsumer.md │ │ │ └── API_Reference_DefaultMQProducer.md │ │ ├── concept.md │ │ ├── controller/ │ │ │ ├── deploy.md │ │ │ ├── design.md │ │ │ ├── persistent_unique_broker_id.md │ │ │ └── quick_start.md │ │ ├── design.md │ │ ├── dledger/ │ │ │ ├── deploy_guide.md │ │ │ └── quick_start.md │ │ ├── features.md │ │ ├── msg_trace/ │ │ │ └── user_guide.md │ │ ├── operation.md │ │ ├── proxy/ │ │ │ └── deploy_guide.md │ │ ├── rpc_request.md │ │ └── statictopic/ │ │ ├── RocketMQ_Static_Topic_Logic_Queue_设计.md │ │ └── The_Scope_Of_Static_Topic.md │ └── en/ │ ├── CLITools.md │ ├── Concept.md │ ├── Configuration_Client.md │ ├── Configuration_System.md │ ├── Configuration_TLS.md │ ├── Debug_In_Idea.md │ ├── Deployment.md │ ├── Design_Filter.md │ ├── Design_LoadBlancing.md │ ├── Design_Query.md │ ├── Design_Remoting.md │ ├── Design_Store.md │ ├── Design_Trancation.md │ ├── Example_Batch.md │ ├── Example_Compaction_Topic.md │ ├── Example_CreateTopic.md │ ├── Example_Delay.md │ ├── Example_Filter.md │ ├── Example_OpenMessaging.md │ ├── Example_Orderly.md │ ├── Example_Simple.md │ ├── Example_Transaction.md │ ├── FAQ.md │ ├── Feature.md │ ├── Operations_Broker.md │ ├── Operations_Consumer.md │ ├── Operations_Producer.md │ ├── Operations_Trace.md │ ├── QuorumACK.md │ ├── README.md │ ├── RocketMQ_Example.md │ ├── Troubleshoopting.md │ ├── acl/ │ │ └── Operations_ACL.md │ ├── architecture.md │ ├── best_practice.md │ ├── client/ │ │ └── java/ │ │ └── API_Reference_DefaultMQProducer.md │ ├── controller/ │ │ ├── deploy.md │ │ ├── design.md │ │ ├── persistent_unique_broker_id.md │ │ └── quick_start.md │ ├── design.md │ ├── dledger/ │ │ ├── deploy_guide.md │ │ └── quick_start.md │ ├── msg_trace/ │ │ └── user_guide.md │ ├── operation.md │ └── proxy/ │ └── deploy_guide.md ├── example/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── apache/ │ └── rocketmq/ │ └── example/ │ ├── batch/ │ │ ├── SimpleBatchProducer.java │ │ └── SplitBatchProducer.java │ ├── benchmark/ │ │ ├── AclClient.java │ │ ├── BatchProducer.java │ │ ├── Consumer.java │ │ ├── Producer.java │ │ ├── TransactionProducer.java │ │ └── timer/ │ │ ├── TimerConsumer.java │ │ └── TimerProducer.java │ ├── broadcast/ │ │ └── PushConsumer.java │ ├── filter/ │ │ ├── SqlFilterConsumer.java │ │ ├── SqlFilterProducer.java │ │ ├── TagFilterConsumer.java │ │ └── TagFilterProducer.java │ ├── lmq/ │ │ ├── LMQProducer.java │ │ ├── LMQPullConsumer.java │ │ ├── LMQPushConsumer.java │ │ └── LMQPushPopConsumer.java │ ├── namespace/ │ │ ├── ProducerWithNamespace.java │ │ ├── PullConsumerWithNamespace.java │ │ └── PushConsumerWithNamespace.java │ ├── openmessaging/ │ │ ├── SimpleProducer.java │ │ ├── SimplePullConsumer.java │ │ └── SimplePushConsumer.java │ ├── operation/ │ │ ├── Consumer.java │ │ └── Producer.java │ ├── ordermessage/ │ │ ├── Consumer.java │ │ └── Producer.java │ ├── quickstart/ │ │ ├── Consumer.java │ │ └── Producer.java │ ├── rpc/ │ │ ├── AsyncRequestProducer.java │ │ ├── RequestProducer.java │ │ └── ResponseConsumer.java │ ├── schedule/ │ │ ├── ScheduledMessageConsumer.java │ │ ├── ScheduledMessageProducer.java │ │ ├── TimerMessageConsumer.java │ │ └── TimerMessageProducer.java │ ├── simple/ │ │ ├── AclClient.java │ │ ├── AsyncProducer.java │ │ ├── CachedQueue.java │ │ ├── LitePullConsumerAssign.java │ │ ├── LitePullConsumerAssignWithSubExpression.java │ │ ├── LitePullConsumerSubscribe.java │ │ ├── OnewayProducer.java │ │ ├── PopConsumer.java │ │ ├── Producer.java │ │ ├── PullConsumer.java │ │ ├── PullScheduleService.java │ │ ├── PushConsumer.java │ │ └── RandomAsyncCommit.java │ ├── tracemessage/ │ │ ├── OpenTracingProducer.java │ │ ├── OpenTracingPushConsumer.java │ │ ├── OpenTracingTransactionProducer.java │ │ ├── TraceProducer.java │ │ └── TracePushConsumer.java │ └── transaction/ │ ├── TransactionListenerImpl.java │ └── TransactionProducer.java ├── filter/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── filter/ │ │ ├── FilterFactory.java │ │ ├── FilterSpi.java │ │ ├── SqlFilter.java │ │ ├── constant/ │ │ │ └── UnaryType.java │ │ ├── expression/ │ │ │ ├── BinaryExpression.java │ │ │ ├── BooleanConstantExpression.java │ │ │ ├── BooleanExpression.java │ │ │ ├── ComparisonExpression.java │ │ │ ├── ConstantExpression.java │ │ │ ├── EmptyEvaluationContext.java │ │ │ ├── EvaluationContext.java │ │ │ ├── Expression.java │ │ │ ├── LogicExpression.java │ │ │ ├── MQFilterException.java │ │ │ ├── NowExpression.java │ │ │ ├── PropertyExpression.java │ │ │ ├── UnaryExpression.java │ │ │ └── UnaryInExpression.java │ │ ├── parser/ │ │ │ ├── ParseException.java │ │ │ ├── SelectorParser.java │ │ │ ├── SelectorParser.jj │ │ │ ├── SelectorParserConstants.java │ │ │ ├── SelectorParserTokenManager.java │ │ │ ├── SimpleCharStream.java │ │ │ ├── Token.java │ │ │ └── TokenMgrError.java │ │ └── util/ │ │ ├── BitsArray.java │ │ ├── BloomFilter.java │ │ └── BloomFilterData.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── filter/ │ │ ├── BitsArrayTest.java │ │ ├── BloomFilterTest.java │ │ ├── ExpressionTest.java │ │ ├── FilterSpiTest.java │ │ └── ParserTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── namesrv/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── rocketmq/ │ │ │ └── namesrv/ │ │ │ ├── NamesrvController.java │ │ │ ├── NamesrvStartup.java │ │ │ ├── kvconfig/ │ │ │ │ ├── KVConfigManager.java │ │ │ │ └── KVConfigSerializeWrapper.java │ │ │ ├── processor/ │ │ │ │ ├── ClientRequestProcessor.java │ │ │ │ ├── ClusterTestRequestProcessor.java │ │ │ │ └── DefaultRequestProcessor.java │ │ │ ├── route/ │ │ │ │ └── ZoneRouteRPCHook.java │ │ │ └── routeinfo/ │ │ │ ├── BatchUnregistrationService.java │ │ │ ├── BrokerHousekeepingService.java │ │ │ └── RouteInfoManager.java │ │ └── resources/ │ │ └── rmq.namesrv.logback.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── namesrv/ │ │ ├── NameServerInstanceTest.java │ │ ├── NamesrvControllerTest.java │ │ ├── NamesrvStartupTest.java │ │ ├── kvconfig/ │ │ │ ├── KVConfigManagerTest.java │ │ │ └── KVConfigSerializeWrapperTest.java │ │ ├── processor/ │ │ │ ├── ClientRequestProcessorTest.java │ │ │ ├── ClusterTestRequestProcessorTest.java │ │ │ └── RequestProcessorTest.java │ │ ├── route/ │ │ │ ├── ZoneRouteRPCHookMoreTest.java │ │ │ └── ZoneRouteRPCHookTest.java │ │ └── routeinfo/ │ │ ├── BrokerHousekeepingServiceTest.java │ │ ├── GetRouteInfoBenchmark.java │ │ ├── RegisterBrokerBenchmark.java │ │ ├── RouteInfoManagerBrokerPermTest.java │ │ ├── RouteInfoManagerBrokerRegisterTest.java │ │ ├── RouteInfoManagerNewTest.java │ │ ├── RouteInfoManagerStaticRegisterTest.java │ │ ├── RouteInfoManagerTest.java │ │ └── RouteInfoManagerTestBase.java │ └── resources/ │ └── rmq.logback-test.xml ├── openmessaging/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── openmessaging/ │ │ └── rocketmq/ │ │ ├── MessagingAccessPointImpl.java │ │ ├── config/ │ │ │ └── ClientConfig.java │ │ ├── consumer/ │ │ │ ├── LocalMessageCache.java │ │ │ ├── PullConsumerImpl.java │ │ │ └── PushConsumerImpl.java │ │ ├── domain/ │ │ │ ├── BytesMessageImpl.java │ │ │ ├── ConsumeRequest.java │ │ │ ├── NonStandardKeys.java │ │ │ ├── RocketMQConstants.java │ │ │ └── SendResultImpl.java │ │ ├── producer/ │ │ │ ├── AbstractOMSProducer.java │ │ │ └── ProducerImpl.java │ │ ├── promise/ │ │ │ ├── DefaultPromise.java │ │ │ └── FutureState.java │ │ └── utils/ │ │ ├── BeanUtils.java │ │ └── OMSUtil.java │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── openmessaging/ │ │ └── rocketmq/ │ │ ├── consumer/ │ │ │ ├── LocalMessageCacheTest.java │ │ │ ├── PullConsumerImplTest.java │ │ │ └── PushConsumerImplTest.java │ │ ├── producer/ │ │ │ └── ProducerImplTest.java │ │ ├── promise/ │ │ │ └── DefaultPromiseTest.java │ │ └── utils/ │ │ └── BeanUtilsTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── pom.xml ├── proxy/ │ ├── BUILD.bazel │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── rocketmq/ │ │ │ └── proxy/ │ │ │ ├── CommandLineArgument.java │ │ │ ├── ProxyMode.java │ │ │ ├── ProxyStartup.java │ │ │ ├── auth/ │ │ │ │ ├── ProxyAuthenticationMetadataProvider.java │ │ │ │ └── ProxyAuthorizationMetadataProvider.java │ │ │ ├── common/ │ │ │ │ ├── AbstractCacheLoader.java │ │ │ │ ├── Address.java │ │ │ │ ├── ContextVariable.java │ │ │ │ ├── MessageReceiptHandle.java │ │ │ │ ├── ProxyContext.java │ │ │ │ ├── ProxyException.java │ │ │ │ ├── ProxyExceptionCode.java │ │ │ │ ├── ReceiptHandleGroup.java │ │ │ │ ├── ReceiptHandleGroupKey.java │ │ │ │ ├── RenewEvent.java │ │ │ │ ├── RenewStrategyPolicy.java │ │ │ │ ├── channel/ │ │ │ │ │ └── ChannelHelper.java │ │ │ │ └── utils/ │ │ │ │ ├── FilterUtils.java │ │ │ │ ├── GrpcUtils.java │ │ │ │ └── ProxyUtils.java │ │ │ ├── config/ │ │ │ │ ├── ConfigFile.java │ │ │ │ ├── Configuration.java │ │ │ │ ├── ConfigurationManager.java │ │ │ │ ├── MetricCollectorMode.java │ │ │ │ └── ProxyConfig.java │ │ │ ├── grpc/ │ │ │ │ ├── GrpcServer.java │ │ │ │ ├── GrpcServerBuilder.java │ │ │ │ ├── ProxyAndTlsProtocolNegotiator.java │ │ │ │ ├── constant/ │ │ │ │ │ └── AttributeKeys.java │ │ │ │ ├── interceptor/ │ │ │ │ │ ├── ContextInterceptor.java │ │ │ │ │ ├── GlobalExceptionInterceptor.java │ │ │ │ │ ├── HeaderInterceptor.java │ │ │ │ │ └── RequestMapping.java │ │ │ │ ├── pipeline/ │ │ │ │ │ ├── AuthenticationPipeline.java │ │ │ │ │ ├── AuthorizationPipeline.java │ │ │ │ │ ├── ContextInitPipeline.java │ │ │ │ │ └── RequestPipeline.java │ │ │ │ └── v2/ │ │ │ │ ├── AbstractMessagingActivity.java │ │ │ │ ├── ContextStreamObserver.java │ │ │ │ ├── DefaultGrpcMessagingActivity.java │ │ │ │ ├── GrpcMessagingActivity.java │ │ │ │ ├── GrpcMessagingApplication.java │ │ │ │ ├── channel/ │ │ │ │ │ ├── GrpcChannelManager.java │ │ │ │ │ └── GrpcClientChannel.java │ │ │ │ ├── client/ │ │ │ │ │ └── ClientActivity.java │ │ │ │ ├── common/ │ │ │ │ │ ├── GrpcClientSettingsManager.java │ │ │ │ │ ├── GrpcConverter.java │ │ │ │ │ ├── GrpcProxyException.java │ │ │ │ │ ├── GrpcValidator.java │ │ │ │ │ ├── ResponseBuilder.java │ │ │ │ │ └── ResponseWriter.java │ │ │ │ ├── consumer/ │ │ │ │ │ ├── AckMessageActivity.java │ │ │ │ │ ├── ChangeInvisibleDurationActivity.java │ │ │ │ │ ├── PopMessageResultFilterImpl.java │ │ │ │ │ ├── ReceiveMessageActivity.java │ │ │ │ │ └── ReceiveMessageResponseStreamWriter.java │ │ │ │ ├── producer/ │ │ │ │ │ ├── ForwardMessageToDLQActivity.java │ │ │ │ │ ├── RecallMessageActivity.java │ │ │ │ │ └── SendMessageActivity.java │ │ │ │ ├── route/ │ │ │ │ │ └── RouteActivity.java │ │ │ │ └── transaction/ │ │ │ │ └── EndTransactionActivity.java │ │ │ ├── metrics/ │ │ │ │ ├── ProxyMetricsConstant.java │ │ │ │ └── ProxyMetricsManager.java │ │ │ ├── processor/ │ │ │ │ ├── AbstractProcessor.java │ │ │ │ ├── BatchAckResult.java │ │ │ │ ├── ClientProcessor.java │ │ │ │ ├── ConsumerProcessor.java │ │ │ │ ├── DefaultMessagingProcessor.java │ │ │ │ ├── MessagingProcessor.java │ │ │ │ ├── PopMessageResultFilter.java │ │ │ │ ├── ProducerProcessor.java │ │ │ │ ├── QueueSelector.java │ │ │ │ ├── ReceiptHandleProcessor.java │ │ │ │ ├── RequestBrokerProcessor.java │ │ │ │ ├── TransactionProcessor.java │ │ │ │ ├── TransactionStatus.java │ │ │ │ ├── channel/ │ │ │ │ │ ├── ChannelExtendAttributeGetter.java │ │ │ │ │ ├── ChannelProtocolType.java │ │ │ │ │ ├── RemoteChannel.java │ │ │ │ │ ├── RemoteChannelConverter.java │ │ │ │ │ └── RemoteChannelSerializer.java │ │ │ │ └── validator/ │ │ │ │ ├── DefaultTopicMessageTypeValidator.java │ │ │ │ └── TopicMessageTypeValidator.java │ │ │ ├── remoting/ │ │ │ │ ├── ClientHousekeepingService.java │ │ │ │ ├── MultiProtocolRemotingServer.java │ │ │ │ ├── MultiProtocolTlsHelper.java │ │ │ │ ├── RemotingProtocolServer.java │ │ │ │ ├── RemotingProxyOutClient.java │ │ │ │ ├── activity/ │ │ │ │ │ ├── AbstractRemotingActivity.java │ │ │ │ │ ├── AckMessageActivity.java │ │ │ │ │ ├── ChangeInvisibleTimeActivity.java │ │ │ │ │ ├── ClientManagerActivity.java │ │ │ │ │ ├── ConsumerManagerActivity.java │ │ │ │ │ ├── GetTopicRouteActivity.java │ │ │ │ │ ├── PopMessageActivity.java │ │ │ │ │ ├── PullMessageActivity.java │ │ │ │ │ ├── RecallMessageActivity.java │ │ │ │ │ ├── SendMessageActivity.java │ │ │ │ │ └── TransactionActivity.java │ │ │ │ ├── channel/ │ │ │ │ │ ├── RemotingChannel.java │ │ │ │ │ └── RemotingChannelManager.java │ │ │ │ ├── common/ │ │ │ │ │ └── RemotingConverter.java │ │ │ │ ├── pipeline/ │ │ │ │ │ ├── AuthenticationPipeline.java │ │ │ │ │ ├── AuthorizationPipeline.java │ │ │ │ │ ├── ContextInitPipeline.java │ │ │ │ │ └── RequestPipeline.java │ │ │ │ └── protocol/ │ │ │ │ ├── ProtocolHandler.java │ │ │ │ ├── ProtocolNegotiationHandler.java │ │ │ │ ├── http2proxy/ │ │ │ │ │ ├── HAProxyMessageForwarder.java │ │ │ │ │ ├── Http2ProtocolProxyHandler.java │ │ │ │ │ ├── Http2ProxyBackendHandler.java │ │ │ │ │ └── Http2ProxyFrontendHandler.java │ │ │ │ └── remoting/ │ │ │ │ └── RemotingProtocolHandler.java │ │ │ └── service/ │ │ │ ├── ClusterServiceManager.java │ │ │ ├── LocalServiceManager.java │ │ │ ├── ServiceManager.java │ │ │ ├── ServiceManagerFactory.java │ │ │ ├── admin/ │ │ │ │ ├── AdminService.java │ │ │ │ └── DefaultAdminService.java │ │ │ ├── cert/ │ │ │ │ └── TlsCertificateManager.java │ │ │ ├── channel/ │ │ │ │ ├── ChannelManager.java │ │ │ │ ├── InvocationChannel.java │ │ │ │ ├── InvocationContext.java │ │ │ │ ├── InvocationContextInterface.java │ │ │ │ ├── SimpleChannel.java │ │ │ │ └── SimpleChannelHandlerContext.java │ │ │ ├── client/ │ │ │ │ ├── ClusterConsumerManager.java │ │ │ │ └── ProxyClientRemotingProcessor.java │ │ │ ├── lite/ │ │ │ │ └── LiteSubscriptionService.java │ │ │ ├── message/ │ │ │ │ ├── ClusterMessageService.java │ │ │ │ ├── LocalMessageService.java │ │ │ │ ├── LocalRemotingCommand.java │ │ │ │ ├── MessageService.java │ │ │ │ └── ReceiptHandleMessage.java │ │ │ ├── metadata/ │ │ │ │ ├── ClusterMetadataService.java │ │ │ │ ├── LocalMetadataService.java │ │ │ │ └── MetadataService.java │ │ │ ├── receipt/ │ │ │ │ ├── DefaultReceiptHandleManager.java │ │ │ │ └── ReceiptHandleManager.java │ │ │ ├── relay/ │ │ │ │ ├── AbstractProxyRelayService.java │ │ │ │ ├── ClusterProxyRelayService.java │ │ │ │ ├── LocalProxyRelayService.java │ │ │ │ ├── ProxyChannel.java │ │ │ │ ├── ProxyRelayResult.java │ │ │ │ ├── ProxyRelayService.java │ │ │ │ └── RelayData.java │ │ │ ├── route/ │ │ │ │ ├── AddressableMessageQueue.java │ │ │ │ ├── ClusterTopicRouteService.java │ │ │ │ ├── DefaultMessageQueuePriorityProvider.java │ │ │ │ ├── LocalTopicRouteService.java │ │ │ │ ├── MessageQueuePenalizer.java │ │ │ │ ├── MessageQueuePriorityProvider.java │ │ │ │ ├── MessageQueueSelector.java │ │ │ │ ├── MessageQueueView.java │ │ │ │ ├── ProxyTopicRouteData.java │ │ │ │ ├── TopicRouteHelper.java │ │ │ │ ├── TopicRouteService.java │ │ │ │ └── TopicRouteWrapper.java │ │ │ ├── sysmessage/ │ │ │ │ ├── AbstractSystemMessageSyncer.java │ │ │ │ ├── HeartbeatSyncer.java │ │ │ │ ├── HeartbeatSyncerData.java │ │ │ │ └── HeartbeatType.java │ │ │ └── transaction/ │ │ │ ├── AbstractTransactionService.java │ │ │ ├── ClusterTransactionService.java │ │ │ ├── EndTransactionRequestData.java │ │ │ ├── LocalTransactionService.java │ │ │ ├── TransactionData.java │ │ │ ├── TransactionDataManager.java │ │ │ └── TransactionService.java │ │ └── resources/ │ │ └── rmq.proxy.logback.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── proxy/ │ │ ├── ProxyStartupTest.java │ │ ├── common/ │ │ │ ├── AddressTest.java │ │ │ ├── ReceiptHandleGroupTest.java │ │ │ ├── RenewStrategyPolicyTest.java │ │ │ └── utils/ │ │ │ └── FilterUtilTest.java │ │ ├── config/ │ │ │ ├── ConfigurationManagerTest.java │ │ │ ├── ConfigurationTest.java │ │ │ ├── InitConfigTest.java │ │ │ └── MetricCollectorModeTest.java │ │ ├── grpc/ │ │ │ ├── ProxyAndTlsProtocolNegotiatorTest.java │ │ │ └── v2/ │ │ │ ├── AbstractMessagingActivityTest.java │ │ │ ├── BaseActivityTest.java │ │ │ ├── GrpcMessagingApplicationTest.java │ │ │ ├── channel/ │ │ │ │ └── GrpcClientChannelTest.java │ │ │ ├── client/ │ │ │ │ └── ClientActivityTest.java │ │ │ ├── common/ │ │ │ │ ├── GrpcClientSettingsManagerTest.java │ │ │ │ ├── GrpcConverterTest.java │ │ │ │ └── GrpcValidatorTest.java │ │ │ ├── consumer/ │ │ │ │ ├── AckMessageActivityTest.java │ │ │ │ ├── ChangeInvisibleDurationActivityTest.java │ │ │ │ ├── ReceiveMessageActivityTest.java │ │ │ │ └── ReceiveMessageResponseStreamWriterTest.java │ │ │ ├── producer/ │ │ │ │ ├── ForwardMessageToDLQActivityTest.java │ │ │ │ ├── RecallMessageActivityTest.java │ │ │ │ └── SendMessageActivityTest.java │ │ │ ├── route/ │ │ │ │ └── RouteActivityTest.java │ │ │ └── transaction/ │ │ │ └── EndTransactionActivityTest.java │ │ ├── processor/ │ │ │ ├── BaseProcessorTest.java │ │ │ ├── ClientProcessorTest.java │ │ │ ├── ConsumerProcessorTest.java │ │ │ ├── ProducerProcessorTest.java │ │ │ ├── ReceiptHandleProcessorTest.java │ │ │ ├── TransactionProcessorTest.java │ │ │ └── channel/ │ │ │ └── RemoteChannelTest.java │ │ ├── remoting/ │ │ │ ├── activity/ │ │ │ │ ├── AbstractRemotingActivityTest.java │ │ │ │ ├── GetTopicRouteActivityTest.java │ │ │ │ ├── PullMessageActivityTest.java │ │ │ │ ├── RecallMessageActivityTest.java │ │ │ │ └── SendMessageActivityTest.java │ │ │ ├── channel/ │ │ │ │ ├── RemotingChannelManagerTest.java │ │ │ │ └── RemotingChannelTest.java │ │ │ └── protocol/ │ │ │ └── http2proxy/ │ │ │ ├── HAProxyMessageForwarderTest.java │ │ │ └── Http2ProtocolProxyHandlerTest.java │ │ └── service/ │ │ ├── BaseServiceTest.java │ │ ├── admin/ │ │ │ └── DefaultAdminServiceTest.java │ │ ├── cert/ │ │ │ └── TlsCertificateManagerTest.java │ │ ├── lite/ │ │ │ └── LiteSubscriptionServiceTest.java │ │ ├── message/ │ │ │ ├── ClusterMessageServiceTest.java │ │ │ └── LocalMessageServiceTest.java │ │ ├── metadata/ │ │ │ └── ClusterMetadataServiceTest.java │ │ ├── mqclient/ │ │ │ ├── MQClientAPIExtTest.java │ │ │ └── ProxyClientRemotingProcessorTest.java │ │ ├── receipt/ │ │ │ └── DefaultReceiptHandleManagerTest.java │ │ ├── relay/ │ │ │ ├── LocalProxyRelayServiceTest.java │ │ │ └── ProxyChannelTest.java │ │ ├── route/ │ │ │ ├── ClusterTopicRouteServiceTest.java │ │ │ ├── LocalTopicRouteServiceTest.java │ │ │ ├── MessageQueuePenalizerTest.java │ │ │ ├── MessageQueuePriorityProviderTest.java │ │ │ └── MessageQueueSelectorTest.java │ │ ├── sysmessage/ │ │ │ └── HeartbeatSyncerTest.java │ │ └── transaction/ │ │ ├── AbstractTransactionServiceTest.java │ │ ├── ClusterTransactionServiceTest.java │ │ └── TransactionDataManagerTest.java │ └── resources/ │ ├── certs/ │ │ ├── client.key │ │ ├── client.pem │ │ ├── server.key │ │ └── server.pem │ ├── mockito-extensions/ │ │ └── org.mockito.plugins.MockMaker │ ├── rmq-proxy-home/ │ │ └── conf/ │ │ ├── broker.conf │ │ ├── logback_proxy.xml │ │ └── rmq-proxy.json │ └── rmq.logback-test.xml ├── remoting/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── remoting/ │ │ ├── ChannelEventListener.java │ │ ├── CommandCallback.java │ │ ├── CommandCustomHeader.java │ │ ├── Configuration.java │ │ ├── InvokeCallback.java │ │ ├── RPCHook.java │ │ ├── RemotingClient.java │ │ ├── RemotingServer.java │ │ ├── RemotingService.java │ │ ├── annotation/ │ │ │ ├── CFNotNull.java │ │ │ └── CFNullable.java │ │ ├── common/ │ │ │ ├── HeartbeatV2Result.java │ │ │ ├── RemotingHelper.java │ │ │ ├── SemaphoreReleaseOnlyOnce.java │ │ │ ├── ServiceThread.java │ │ │ └── TlsMode.java │ │ ├── exception/ │ │ │ ├── RemotingCommandException.java │ │ │ ├── RemotingConnectException.java │ │ │ ├── RemotingException.java │ │ │ ├── RemotingSendRequestException.java │ │ │ ├── RemotingTimeoutException.java │ │ │ └── RemotingTooMuchRequestException.java │ │ ├── metrics/ │ │ │ ├── RemotingMetricsConstant.java │ │ │ └── RemotingMetricsManager.java │ │ ├── netty/ │ │ │ ├── AttributeKeys.java │ │ │ ├── FileRegionEncoder.java │ │ │ ├── NettyClientConfig.java │ │ │ ├── NettyDecoder.java │ │ │ ├── NettyEncoder.java │ │ │ ├── NettyEvent.java │ │ │ ├── NettyEventType.java │ │ │ ├── NettyLogger.java │ │ │ ├── NettyRemotingAbstract.java │ │ │ ├── NettyRemotingClient.java │ │ │ ├── NettyRemotingServer.java │ │ │ ├── NettyRequestProcessor.java │ │ │ ├── NettyServerConfig.java │ │ │ ├── NettySystemConfig.java │ │ │ ├── RemotingCodeDistributionHandler.java │ │ │ ├── RemotingResponseCallback.java │ │ │ ├── RequestTask.java │ │ │ ├── ResponseFuture.java │ │ │ ├── TlsHelper.java │ │ │ └── TlsSystemConfig.java │ │ ├── pipeline/ │ │ │ └── RequestPipeline.java │ │ ├── protocol/ │ │ │ ├── BitSetSerializerDeserializer.java │ │ │ ├── BrokerSyncInfo.java │ │ │ ├── DataVersion.java │ │ │ ├── EpochEntry.java │ │ │ ├── FastCodesHeader.java │ │ │ ├── ForbiddenType.java │ │ │ ├── LanguageCode.java │ │ │ ├── MQProtosHelper.java │ │ │ ├── NamespaceUtil.java │ │ │ ├── RemotingCommand.java │ │ │ ├── RemotingCommandType.java │ │ │ ├── RemotingSerializable.java │ │ │ ├── RemotingSysResponseCode.java │ │ │ ├── RequestCode.java │ │ │ ├── RequestHeaderRegistry.java │ │ │ ├── RequestSource.java │ │ │ ├── RequestType.java │ │ │ ├── ResponseCode.java │ │ │ ├── RocketMQSerializable.java │ │ │ ├── SerializeType.java │ │ │ ├── admin/ │ │ │ │ ├── ConsumeStats.java │ │ │ │ ├── OffsetWrapper.java │ │ │ │ ├── RollbackStats.java │ │ │ │ ├── TopicOffset.java │ │ │ │ └── TopicStatsTable.java │ │ │ ├── body/ │ │ │ │ ├── AclInfo.java │ │ │ │ ├── BatchAck.java │ │ │ │ ├── BatchAckMessageRequestBody.java │ │ │ │ ├── BrokerMemberGroup.java │ │ │ │ ├── BrokerReplicasInfo.java │ │ │ │ ├── BrokerStatsData.java │ │ │ │ ├── BrokerStatsItem.java │ │ │ │ ├── CMResult.java │ │ │ │ ├── CheckClientRequestBody.java │ │ │ │ ├── ClusterInfo.java │ │ │ │ ├── Connection.java │ │ │ │ ├── ConsumeByWho.java │ │ │ │ ├── ConsumeMessageDirectlyResult.java │ │ │ │ ├── ConsumeQueueData.java │ │ │ │ ├── ConsumeStatsList.java │ │ │ │ ├── ConsumeStatus.java │ │ │ │ ├── ConsumerConnection.java │ │ │ │ ├── ConsumerOffsetSerializeWrapper.java │ │ │ │ ├── ConsumerRunningInfo.java │ │ │ │ ├── CreateTopicListRequestBody.java │ │ │ │ ├── ElectMasterResponseBody.java │ │ │ │ ├── EpochEntryCache.java │ │ │ │ ├── GetBrokerLiteInfoResponseBody.java │ │ │ │ ├── GetBrokerMemberGroupResponseBody.java │ │ │ │ ├── GetConsumerStatusBody.java │ │ │ │ ├── GetLiteClientInfoResponseBody.java │ │ │ │ ├── GetLiteGroupInfoResponseBody.java │ │ │ │ ├── GetLiteTopicInfoResponseBody.java │ │ │ │ ├── GetParentTopicInfoResponseBody.java │ │ │ │ ├── GroupList.java │ │ │ │ ├── HARuntimeInfo.java │ │ │ │ ├── KVTable.java │ │ │ │ ├── LiteSubscriptionCtlRequestBody.java │ │ │ │ ├── LockBatchRequestBody.java │ │ │ │ ├── LockBatchResponseBody.java │ │ │ │ ├── MessageRequestModeSerializeWrapper.java │ │ │ │ ├── PopProcessQueueInfo.java │ │ │ │ ├── ProcessQueueInfo.java │ │ │ │ ├── ProducerConnection.java │ │ │ │ ├── ProducerInfo.java │ │ │ │ ├── ProducerTableInfo.java │ │ │ │ ├── QueryAssignmentRequestBody.java │ │ │ │ ├── QueryAssignmentResponseBody.java │ │ │ │ ├── QueryConsumeQueueResponseBody.java │ │ │ │ ├── QueryConsumeTimeSpanBody.java │ │ │ │ ├── QueryCorrectionOffsetBody.java │ │ │ │ ├── QuerySubscriptionResponseBody.java │ │ │ │ ├── QueueTimeSpan.java │ │ │ │ ├── RegisterBrokerBody.java │ │ │ │ ├── ResetOffsetBody.java │ │ │ │ ├── ResetOffsetBodyForC.java │ │ │ │ ├── RoleChangeNotifyEntry.java │ │ │ │ ├── SetMessageRequestModeRequestBody.java │ │ │ │ ├── SubscriptionGroupList.java │ │ │ │ ├── SubscriptionGroupWrapper.java │ │ │ │ ├── SyncStateSet.java │ │ │ │ ├── TopicConfigAndMappingSerializeWrapper.java │ │ │ │ ├── TopicConfigSerializeWrapper.java │ │ │ │ ├── TopicList.java │ │ │ │ ├── TopicQueueMappingSerializeWrapper.java │ │ │ │ ├── UnlockBatchRequestBody.java │ │ │ │ └── UserInfo.java │ │ │ ├── filter/ │ │ │ │ └── FilterAPI.java │ │ │ ├── header/ │ │ │ │ ├── AckMessageRequestHeader.java │ │ │ │ ├── AddBrokerRequestHeader.java │ │ │ │ ├── ChangeInvisibleTimeRequestHeader.java │ │ │ │ ├── ChangeInvisibleTimeResponseHeader.java │ │ │ │ ├── CheckRocksdbCqWriteProgressRequestHeader.java │ │ │ │ ├── CheckTransactionStateRequestHeader.java │ │ │ │ ├── CheckTransactionStateResponseHeader.java │ │ │ │ ├── CloneGroupOffsetRequestHeader.java │ │ │ │ ├── ConsumeMessageDirectlyResultRequestHeader.java │ │ │ │ ├── ConsumerSendMsgBackRequestHeader.java │ │ │ │ ├── CreateAclRequestHeader.java │ │ │ │ ├── CreateTopicListRequestHeader.java │ │ │ │ ├── CreateTopicRequestHeader.java │ │ │ │ ├── CreateUserRequestHeader.java │ │ │ │ ├── DeleteAclRequestHeader.java │ │ │ │ ├── DeleteSubscriptionGroupRequestHeader.java │ │ │ │ ├── DeleteTopicRequestHeader.java │ │ │ │ ├── DeleteUserRequestHeader.java │ │ │ │ ├── EndTransactionRequestHeader.java │ │ │ │ ├── EndTransactionResponseHeader.java │ │ │ │ ├── ExchangeHAInfoRequestHeader.java │ │ │ │ ├── ExchangeHAInfoResponseHeader.java │ │ │ │ ├── ExportRocksDBConfigToJsonRequestHeader.java │ │ │ │ ├── ExtraInfoUtil.java │ │ │ │ ├── GetAclRequestHeader.java │ │ │ │ ├── GetAllProducerInfoRequestHeader.java │ │ │ │ ├── GetAllSubscriptionGroupRequestHeader.java │ │ │ │ ├── GetAllSubscriptionGroupResponseHeader.java │ │ │ │ ├── GetAllTopicConfigRequestHeader.java │ │ │ │ ├── GetAllTopicConfigResponseHeader.java │ │ │ │ ├── GetBrokerConfigResponseHeader.java │ │ │ │ ├── GetBrokerMemberGroupRequestHeader.java │ │ │ │ ├── GetConsumeStatsInBrokerHeader.java │ │ │ │ ├── GetConsumeStatsRequestHeader.java │ │ │ │ ├── GetConsumerConnectionListRequestHeader.java │ │ │ │ ├── GetConsumerListByGroupRequestHeader.java │ │ │ │ ├── GetConsumerListByGroupResponseBody.java │ │ │ │ ├── GetConsumerListByGroupResponseHeader.java │ │ │ │ ├── GetConsumerRunningInfoRequestHeader.java │ │ │ │ ├── GetConsumerStatusRequestHeader.java │ │ │ │ ├── GetEarliestMsgStoretimeRequestHeader.java │ │ │ │ ├── GetEarliestMsgStoretimeResponseHeader.java │ │ │ │ ├── GetLiteClientInfoRequestHeader.java │ │ │ │ ├── GetLiteGroupInfoRequestHeader.java │ │ │ │ ├── GetLiteTopicInfoRequestHeader.java │ │ │ │ ├── GetMaxOffsetRequestHeader.java │ │ │ │ ├── GetMaxOffsetResponseHeader.java │ │ │ │ ├── GetMinOffsetRequestHeader.java │ │ │ │ ├── GetMinOffsetResponseHeader.java │ │ │ │ ├── GetParentTopicInfoRequestHeader.java │ │ │ │ ├── GetProducerConnectionListRequestHeader.java │ │ │ │ ├── GetSubscriptionGroupConfigRequestHeader.java │ │ │ │ ├── GetTopicConfigRequestHeader.java │ │ │ │ ├── GetTopicStatsInfoRequestHeader.java │ │ │ │ ├── GetTopicsByClusterRequestHeader.java │ │ │ │ ├── GetUserRequestHeader.java │ │ │ │ ├── HeartbeatRequestHeader.java │ │ │ │ ├── InitConsumerOffsetRequestHeader.java │ │ │ │ ├── ListAclsRequestHeader.java │ │ │ │ ├── ListUsersRequestHeader.java │ │ │ │ ├── LiteSubscriptionCtlRequestHeader.java │ │ │ │ ├── LockBatchMqRequestHeader.java │ │ │ │ ├── NotificationRequestHeader.java │ │ │ │ ├── NotificationResponseHeader.java │ │ │ │ ├── NotifyBrokerRoleChangedRequestHeader.java │ │ │ │ ├── NotifyConsumerIdsChangedRequestHeader.java │ │ │ │ ├── NotifyMinBrokerIdChangeRequestHeader.java │ │ │ │ ├── NotifyUnsubscribeLiteRequestHeader.java │ │ │ │ ├── PeekMessageRequestHeader.java │ │ │ │ ├── PollingInfoRequestHeader.java │ │ │ │ ├── PollingInfoResponseHeader.java │ │ │ │ ├── PopLiteMessageRequestHeader.java │ │ │ │ ├── PopLiteMessageResponseHeader.java │ │ │ │ ├── PopMessageRequestHeader.java │ │ │ │ ├── PopMessageResponseHeader.java │ │ │ │ ├── PullMessageRequestHeader.java │ │ │ │ ├── PullMessageResponseHeader.java │ │ │ │ ├── QueryConsumeQueueRequestHeader.java │ │ │ │ ├── QueryConsumeTimeSpanRequestHeader.java │ │ │ │ ├── QueryConsumerOffsetRequestHeader.java │ │ │ │ ├── QueryConsumerOffsetResponseHeader.java │ │ │ │ ├── QueryCorrectionOffsetHeader.java │ │ │ │ ├── QueryMessageRequestHeader.java │ │ │ │ ├── QueryMessageResponseHeader.java │ │ │ │ ├── QuerySubscriptionByConsumerRequestHeader.java │ │ │ │ ├── QueryTopicConsumeByWhoRequestHeader.java │ │ │ │ ├── QueryTopicsByConsumerRequestHeader.java │ │ │ │ ├── RecallMessageRequestHeader.java │ │ │ │ ├── RecallMessageResponseHeader.java │ │ │ │ ├── RemoveBrokerRequestHeader.java │ │ │ │ ├── ReplyMessageRequestHeader.java │ │ │ │ ├── ResetMasterFlushOffsetHeader.java │ │ │ │ ├── ResetOffsetRequestHeader.java │ │ │ │ ├── ResumeCheckHalfMessageRequestHeader.java │ │ │ │ ├── SearchOffsetRequestHeader.java │ │ │ │ ├── SearchOffsetResponseHeader.java │ │ │ │ ├── SendMessageRequestHeader.java │ │ │ │ ├── SendMessageRequestHeaderV2.java │ │ │ │ ├── SendMessageResponseHeader.java │ │ │ │ ├── StatisticsMessagesRequestHeader.java │ │ │ │ ├── TriggerLiteDispatchRequestHeader.java │ │ │ │ ├── UnlockBatchMqRequestHeader.java │ │ │ │ ├── UnregisterClientRequestHeader.java │ │ │ │ ├── UnregisterClientResponseHeader.java │ │ │ │ ├── UpdateAclRequestHeader.java │ │ │ │ ├── UpdateConsumerOffsetRequestHeader.java │ │ │ │ ├── UpdateConsumerOffsetResponseHeader.java │ │ │ │ ├── UpdateGroupForbiddenRequestHeader.java │ │ │ │ ├── UpdateUserRequestHeader.java │ │ │ │ ├── ViewBrokerStatsDataRequestHeader.java │ │ │ │ ├── ViewMessageRequestHeader.java │ │ │ │ ├── ViewMessageResponseHeader.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── AlterSyncStateSetRequestHeader.java │ │ │ │ │ ├── AlterSyncStateSetResponseHeader.java │ │ │ │ │ ├── ElectMasterRequestHeader.java │ │ │ │ │ ├── ElectMasterResponseHeader.java │ │ │ │ │ ├── GetMetaDataResponseHeader.java │ │ │ │ │ ├── GetReplicaInfoRequestHeader.java │ │ │ │ │ ├── GetReplicaInfoResponseHeader.java │ │ │ │ │ ├── admin/ │ │ │ │ │ │ └── CleanControllerBrokerDataRequestHeader.java │ │ │ │ │ └── register/ │ │ │ │ │ ├── ApplyBrokerIdRequestHeader.java │ │ │ │ │ ├── ApplyBrokerIdResponseHeader.java │ │ │ │ │ ├── GetNextBrokerIdRequestHeader.java │ │ │ │ │ ├── GetNextBrokerIdResponseHeader.java │ │ │ │ │ ├── RegisterBrokerToControllerRequestHeader.java │ │ │ │ │ └── RegisterBrokerToControllerResponseHeader.java │ │ │ │ └── namesrv/ │ │ │ │ ├── AddWritePermOfBrokerRequestHeader.java │ │ │ │ ├── AddWritePermOfBrokerResponseHeader.java │ │ │ │ ├── BrokerHeartbeatRequestHeader.java │ │ │ │ ├── DeleteKVConfigRequestHeader.java │ │ │ │ ├── DeleteTopicFromNamesrvRequestHeader.java │ │ │ │ ├── GetKVConfigRequestHeader.java │ │ │ │ ├── GetKVConfigResponseHeader.java │ │ │ │ ├── GetKVListByNamespaceRequestHeader.java │ │ │ │ ├── GetRouteInfoRequestHeader.java │ │ │ │ ├── PutKVConfigRequestHeader.java │ │ │ │ ├── QueryDataVersionRequestHeader.java │ │ │ │ ├── QueryDataVersionResponseHeader.java │ │ │ │ ├── RegisterBrokerRequestHeader.java │ │ │ │ ├── RegisterBrokerResponseHeader.java │ │ │ │ ├── RegisterOrderTopicRequestHeader.java │ │ │ │ ├── RegisterTopicRequestHeader.java │ │ │ │ ├── UnRegisterBrokerRequestHeader.java │ │ │ │ ├── WipeWritePermOfBrokerRequestHeader.java │ │ │ │ └── WipeWritePermOfBrokerResponseHeader.java │ │ │ ├── heartbeat/ │ │ │ │ ├── ConsumeType.java │ │ │ │ ├── ConsumerData.java │ │ │ │ ├── HeartbeatData.java │ │ │ │ ├── MessageModel.java │ │ │ │ ├── ProducerData.java │ │ │ │ └── SubscriptionData.java │ │ │ ├── namesrv/ │ │ │ │ └── RegisterBrokerResult.java │ │ │ ├── route/ │ │ │ │ ├── BrokerData.java │ │ │ │ ├── MessageQueueRouteState.java │ │ │ │ ├── QueueData.java │ │ │ │ └── TopicRouteData.java │ │ │ ├── statictopic/ │ │ │ │ ├── LogicQueueMappingItem.java │ │ │ │ ├── TopicConfigAndQueueMapping.java │ │ │ │ ├── TopicQueueMappingContext.java │ │ │ │ ├── TopicQueueMappingDetail.java │ │ │ │ ├── TopicQueueMappingInfo.java │ │ │ │ ├── TopicQueueMappingOne.java │ │ │ │ ├── TopicQueueMappingUtils.java │ │ │ │ └── TopicRemappingDetailWrapper.java │ │ │ ├── subscription/ │ │ │ │ ├── CustomizedRetryPolicy.java │ │ │ │ ├── ExponentialRetryPolicy.java │ │ │ │ ├── GroupForbidden.java │ │ │ │ ├── GroupRetryPolicy.java │ │ │ │ ├── GroupRetryPolicyType.java │ │ │ │ ├── RetryPolicy.java │ │ │ │ ├── SimpleSubscriptionData.java │ │ │ │ └── SubscriptionGroupConfig.java │ │ │ └── topic/ │ │ │ └── OffsetMovedEvent.java │ │ ├── proxy/ │ │ │ └── SocksProxyConfig.java │ │ ├── rpc/ │ │ │ ├── ClientMetadata.java │ │ │ ├── RequestBuilder.java │ │ │ ├── RpcClient.java │ │ │ ├── RpcClientHook.java │ │ │ ├── RpcClientImpl.java │ │ │ ├── RpcClientUtils.java │ │ │ ├── RpcException.java │ │ │ ├── RpcRequest.java │ │ │ ├── RpcRequestHeader.java │ │ │ ├── RpcResponse.java │ │ │ ├── TopicQueueRequestHeader.java │ │ │ └── TopicRequestHeader.java │ │ └── rpchook/ │ │ ├── DynamicalExtFieldRPCHook.java │ │ └── StreamTypeRPCHook.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── remoting/ │ │ ├── ProxyProtocolTest.java │ │ ├── RemotingServerTest.java │ │ ├── SubRemotingServerTest.java │ │ ├── TlsTest.java │ │ ├── netty/ │ │ │ ├── FileRegionEncoderTest.java │ │ │ ├── MockChannel.java │ │ │ ├── MockChannelPromise.java │ │ │ ├── NettyClientConfigTest.java │ │ │ ├── NettyRemotingAbstractTest.java │ │ │ ├── NettyRemotingClientTest.java │ │ │ ├── NettyRemotingServerTest.java │ │ │ ├── NettyServerConfigTest.java │ │ │ └── RemotingCodeDistributionHandlerTest.java │ │ ├── protocol/ │ │ │ ├── CheckpointFileTest.java │ │ │ ├── ClusterInfoTest.java │ │ │ ├── ConsumeStatusTest.java │ │ │ ├── DataVersionTest.java │ │ │ ├── GroupListTest.java │ │ │ ├── LanguageCodeTest.java │ │ │ ├── NamespaceUtilTest.java │ │ │ ├── QueryConsumeTimeSpanBodyTest.java │ │ │ ├── RegisterBrokerBodyTest.java │ │ │ ├── RemotingCommandTest.java │ │ │ ├── RemotingSerializableCompatTest.java │ │ │ ├── RemotingSerializableTest.java │ │ │ ├── RequestSourceTest.java │ │ │ ├── RequestTypeTest.java │ │ │ ├── RocketMQSerializableTest.java │ │ │ ├── admin/ │ │ │ │ ├── ConsumeStatsTest.java │ │ │ │ └── TopicStatsTableTest.java │ │ │ ├── body/ │ │ │ │ ├── BatchAckTest.java │ │ │ │ ├── BrokerStatsDataTest.java │ │ │ │ ├── CheckClientRequestBodyTest.java │ │ │ │ ├── ConsumeMessageDirectlyResultTest.java │ │ │ │ ├── ConsumeStatsListTest.java │ │ │ │ ├── ConsumerConnectionTest.java │ │ │ │ ├── ConsumerRunningInfoTest.java │ │ │ │ ├── KVTableTest.java │ │ │ │ ├── MessageRequestModeSerializeWrapperTest.java │ │ │ │ ├── QueryConsumeQueueResponseBodyTest.java │ │ │ │ ├── QueryCorrectionOffsetBodyTest.java │ │ │ │ ├── ResetOffsetBodyTest.java │ │ │ │ └── SubscriptionGroupWrapperTest.java │ │ │ ├── filter/ │ │ │ │ └── FilterAPITest.java │ │ │ ├── header/ │ │ │ │ ├── ExportRocksDBConfigToJsonRequestHeaderTest.java │ │ │ │ ├── ExtraInfoUtilTest.java │ │ │ │ ├── FastCodesHeaderTest.java │ │ │ │ ├── GetConsumeStatsRequestHeaderTest.java │ │ │ │ └── SendMessageRequestHeaderV2Test.java │ │ │ ├── heartbeat/ │ │ │ │ └── SubscriptionDataTest.java │ │ │ ├── route/ │ │ │ │ └── TopicRouteDataTest.java │ │ │ ├── statictopic/ │ │ │ │ ├── TopicQueueMappingTest.java │ │ │ │ └── TopicQueueMappingUtilsTest.java │ │ │ ├── subscription/ │ │ │ │ ├── CustomizedRetryPolicyTest.java │ │ │ │ ├── ExponentialRetryPolicyTest.java │ │ │ │ ├── GroupRetryPolicyTest.java │ │ │ │ └── SimpleSubscriptionDataTest.java │ │ │ └── topic/ │ │ │ └── OffsetMovedEventTest.java │ │ └── rpc/ │ │ ├── ClientMetadataTest.java │ │ ├── RpcClientImplTest.java │ │ └── RpcRequestHeaderTest.java │ └── resources/ │ ├── certs/ │ │ ├── badClient.key │ │ ├── badClient.pem │ │ ├── badServer.key │ │ ├── badServer.pem │ │ ├── ca.pem │ │ ├── client.key │ │ ├── client.pem │ │ ├── privkey.pem │ │ ├── server.key │ │ └── server.pem │ └── rmq.logback-test.xml ├── srvutil/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── srvutil/ │ │ ├── FileWatchService.java │ │ ├── ServerUtil.java │ │ └── ShutdownHookThread.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── srvutil/ │ │ └── FileWatchServiceTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── store/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── store/ │ │ ├── AllocateMappedFileService.java │ │ ├── AppendMessageCallback.java │ │ ├── AppendMessageResult.java │ │ ├── AppendMessageStatus.java │ │ ├── CommitLog.java │ │ ├── CommitLogDispatchStore.java │ │ ├── CommitLogDispatcher.java │ │ ├── CompactionAppendMsgCallback.java │ │ ├── ConsumeQueue.java │ │ ├── ConsumeQueueExt.java │ │ ├── DefaultMessageFilter.java │ │ ├── DefaultMessageStore.java │ │ ├── DispatchRequest.java │ │ ├── FileQueueSnapshot.java │ │ ├── FlushDiskWatcher.java │ │ ├── FlushManager.java │ │ ├── GetMessageResult.java │ │ ├── GetMessageStatus.java │ │ ├── LmqDispatch.java │ │ ├── MappedFileQueue.java │ │ ├── MessageArrivingListener.java │ │ ├── MessageExtEncoder.java │ │ ├── MessageFilter.java │ │ ├── MessageStore.java │ │ ├── MessageStoreStateMachine.java │ │ ├── MultiPathMappedFileQueue.java │ │ ├── PutMessageContext.java │ │ ├── PutMessageLock.java │ │ ├── PutMessageReentrantLock.java │ │ ├── PutMessageResult.java │ │ ├── PutMessageSpinLock.java │ │ ├── PutMessageStatus.java │ │ ├── QueryMessageResult.java │ │ ├── ReferenceResource.java │ │ ├── RocksDBMessageStore.java │ │ ├── RunningFlags.java │ │ ├── SelectMappedBufferResult.java │ │ ├── SelectMappedFileResult.java │ │ ├── StoreCheckpoint.java │ │ ├── StoreStatsService.java │ │ ├── StoreType.java │ │ ├── StoreUtil.java │ │ ├── Swappable.java │ │ ├── TopicQueueLock.java │ │ ├── TransientStorePool.java │ │ ├── config/ │ │ │ ├── BrokerRole.java │ │ │ ├── FlushDiskType.java │ │ │ ├── MessageStoreConfig.java │ │ │ └── StorePathConfigHelper.java │ │ ├── dledger/ │ │ │ └── DLedgerCommitLog.java │ │ ├── exception/ │ │ │ ├── ConsumeQueueException.java │ │ │ └── StoreException.java │ │ ├── ha/ │ │ │ ├── DefaultHAClient.java │ │ │ ├── DefaultHAConnection.java │ │ │ ├── DefaultHAService.java │ │ │ ├── FlowMonitor.java │ │ │ ├── GroupTransferService.java │ │ │ ├── HAClient.java │ │ │ ├── HAConnection.java │ │ │ ├── HAConnectionState.java │ │ │ ├── HAConnectionStateNotificationRequest.java │ │ │ ├── HAConnectionStateNotificationService.java │ │ │ ├── HAService.java │ │ │ ├── WaitNotifyObject.java │ │ │ ├── autoswitch/ │ │ │ │ ├── AutoSwitchHAClient.java │ │ │ │ ├── AutoSwitchHAConnection.java │ │ │ │ ├── AutoSwitchHAService.java │ │ │ │ ├── BrokerMetadata.java │ │ │ │ ├── EpochFileCache.java │ │ │ │ ├── MetadataFile.java │ │ │ │ └── TempBrokerMetadata.java │ │ │ └── io/ │ │ │ ├── AbstractHAReader.java │ │ │ ├── HAReadHook.java │ │ │ ├── HAWriteHook.java │ │ │ └── HAWriter.java │ │ ├── hook/ │ │ │ ├── PutMessageHook.java │ │ │ └── SendMessageBackHook.java │ │ ├── index/ │ │ │ ├── IndexFile.java │ │ │ ├── IndexHeader.java │ │ │ ├── IndexService.java │ │ │ ├── QueryOffsetResult.java │ │ │ └── rocksdb/ │ │ │ ├── IndexRocksDBRecord.java │ │ │ └── IndexRocksDBStore.java │ │ ├── kv/ │ │ │ ├── CommitLogDispatcherCompaction.java │ │ │ ├── CompactionLog.java │ │ │ ├── CompactionPositionMgr.java │ │ │ ├── CompactionService.java │ │ │ ├── CompactionStore.java │ │ │ └── MessageFetcher.java │ │ ├── lock/ │ │ │ ├── AdaptiveBackOffSpinLock.java │ │ │ ├── AdaptiveBackOffSpinLockImpl.java │ │ │ ├── BackOffReentrantLock.java │ │ │ └── BackOffSpinLock.java │ │ ├── logfile/ │ │ │ ├── AbstractMappedFile.java │ │ │ ├── DefaultMappedFile.java │ │ │ ├── MappedFile.java │ │ │ └── SharedByteBufferManager.java │ │ ├── metrics/ │ │ │ ├── DefaultStoreMetricsConstant.java │ │ │ ├── DefaultStoreMetricsManager.java │ │ │ ├── RocksDBStoreMetricsManager.java │ │ │ └── StoreMetricsManager.java │ │ ├── plugin/ │ │ │ ├── AbstractPluginMessageStore.java │ │ │ ├── MessageStoreFactory.java │ │ │ └── MessageStorePluginContext.java │ │ ├── pop/ │ │ │ ├── AckMsg.java │ │ │ ├── BatchAckMsg.java │ │ │ └── PopCheckPoint.java │ │ ├── queue/ │ │ │ ├── AbstractConsumeQueueStore.java │ │ │ ├── BatchConsumeQueue.java │ │ │ ├── BatchOffsetIndex.java │ │ │ ├── CombineConsumeQueueStore.java │ │ │ ├── ConsumeQueueInterface.java │ │ │ ├── ConsumeQueueStore.java │ │ │ ├── ConsumeQueueStoreInterface.java │ │ │ ├── CqUnit.java │ │ │ ├── DispatchEntry.java │ │ │ ├── FileQueueLifeCycle.java │ │ │ ├── MultiDispatchUtils.java │ │ │ ├── OffsetInitializer.java │ │ │ ├── OffsetInitializerRocksDBImpl.java │ │ │ ├── QueueOffsetOperator.java │ │ │ ├── ReferredIterator.java │ │ │ ├── RocksDBConsumeQueue.java │ │ │ ├── RocksDBConsumeQueueOffsetTable.java │ │ │ ├── RocksDBConsumeQueueStore.java │ │ │ ├── RocksDBConsumeQueueTable.java │ │ │ ├── RocksGroupCommitService.java │ │ │ ├── SparseConsumeQueue.java │ │ │ └── offset/ │ │ │ ├── OffsetEntry.java │ │ │ └── OffsetEntryType.java │ │ ├── rocksdb/ │ │ │ ├── ConsumeQueueCompactionFilterFactory.java │ │ │ ├── ConsumeQueueRocksDBStorage.java │ │ │ ├── MessageRocksDBStorage.java │ │ │ └── RocksDBOptionsFactory.java │ │ ├── stats/ │ │ │ ├── BrokerStats.java │ │ │ ├── BrokerStatsManager.java │ │ │ └── LmqBrokerStatsManager.java │ │ ├── timer/ │ │ │ ├── Slot.java │ │ │ ├── TimerCheckpoint.java │ │ │ ├── TimerLog.java │ │ │ ├── TimerMessageStore.java │ │ │ ├── TimerMetrics.java │ │ │ ├── TimerRequest.java │ │ │ ├── TimerWheel.java │ │ │ └── rocksdb/ │ │ │ ├── Timeline.java │ │ │ ├── TimerMessageRocksDBStore.java │ │ │ └── TimerRocksDBRecord.java │ │ ├── transaction/ │ │ │ ├── TransMessageRocksDBStore.java │ │ │ └── TransRocksDBRecord.java │ │ └── util/ │ │ ├── LibC.java │ │ └── PerfCounter.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── store/ │ │ ├── AppendCallbackTest.java │ │ ├── AppendPropCRCTest.java │ │ ├── BatchPutMessageTest.java │ │ ├── ConsumeQueueExtTest.java │ │ ├── ConsumeQueueTest.java │ │ ├── DefaultMessageStoreCleanFilesTest.java │ │ ├── DefaultMessageStoreShutDownTest.java │ │ ├── DefaultMessageStoreTest.java │ │ ├── FlushDiskWatcherTest.java │ │ ├── GetMessageResultTest.java │ │ ├── HATest.java │ │ ├── MappedFileQueueTest.java │ │ ├── MappedFileTest.java │ │ ├── MessageExtBrokerInnerTest.java │ │ ├── MessageStoreStateMachineTest.java │ │ ├── MultiPathMappedFileQueueTest.java │ │ ├── ReputMessageServiceTest.java │ │ ├── RocksDBMessageStoreTest.java │ │ ├── StoreCheckpointTest.java │ │ ├── StoreStatsServiceTest.java │ │ ├── StoreTestBase.java │ │ ├── StoreTestUtil.java │ │ ├── dledger/ │ │ │ ├── DLedgerCommitlogTest.java │ │ │ ├── DLedgerMultiPathTest.java │ │ │ ├── MessageStoreTestBase.java │ │ │ └── MixCommitlogTest.java │ │ ├── ha/ │ │ │ ├── FlowMonitorTest.java │ │ │ ├── HAClientTest.java │ │ │ ├── HAServerTest.java │ │ │ ├── WaitNotifyObjectTest.java │ │ │ └── autoswitch/ │ │ │ ├── AutoSwitchHATest.java │ │ │ └── EpochFileCacheTest.java │ │ ├── index/ │ │ │ ├── IndexFileTest.java │ │ │ └── IndexServiceTest.java │ │ ├── kv/ │ │ │ ├── CompactionLogTest.java │ │ │ ├── CompactionPositionMgrTest.java │ │ │ └── OffsetMapTest.java │ │ ├── lock/ │ │ │ ├── AdaptiveBackOffSpinLockImplTest.java │ │ │ └── AdaptiveLockTest.java │ │ ├── logfile/ │ │ │ ├── DefaultMappedFileConcurrencyTest.java │ │ │ ├── DefaultMappedFileErrorHandlingTest.java │ │ │ ├── DefaultMappedFilePerformanceTest.java │ │ │ ├── DefaultMappedFileTest.java │ │ │ └── DefaultMappedFileWriteWithoutMmapTest.java │ │ ├── pop/ │ │ │ ├── AckMsgTest.java │ │ │ └── BatchAckMsgTest.java │ │ ├── queue/ │ │ │ ├── BatchConsumeMessageTest.java │ │ │ ├── BatchConsumeQueueTest.java │ │ │ ├── CombineConsumeQueueStoreTest.java │ │ │ ├── ConsumeQueueStoreTest.java │ │ │ ├── ConsumeQueueTest.java │ │ │ ├── QueueTestBase.java │ │ │ ├── RocksDBConsumeQueueOffsetTableTest.java │ │ │ ├── RocksDBConsumeQueueTableTest.java │ │ │ ├── RocksDBConsumeQueueTest.java │ │ │ └── SparseConsumeQueueTest.java │ │ ├── rocksdb/ │ │ │ └── RocksDBOptionsFactoryTest.java │ │ ├── stats/ │ │ │ └── BrokerStatsManagerTest.java │ │ └── timer/ │ │ ├── StoreTestUtils.java │ │ ├── TimerCheckPointTest.java │ │ ├── TimerLogTest.java │ │ ├── TimerMessageStoreTest.java │ │ ├── TimerMetricsTest.java │ │ └── TimerWheelTest.java │ └── resources/ │ └── rmq.logback-test.xml ├── style/ │ ├── copyright/ │ │ ├── Apache.xml │ │ └── profiles_settings.xml │ ├── rmq_checkstyle.xml │ ├── rmq_codeStyle.xml │ └── spotbugs-suppressions.xml ├── test/ │ ├── BUILD.bazel │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── test/ │ │ ├── client/ │ │ │ ├── mq/ │ │ │ │ └── MQAsyncProducer.java │ │ │ └── rmq/ │ │ │ ├── RMQAsyncSendProducer.java │ │ │ ├── RMQBroadCastConsumer.java │ │ │ ├── RMQNormalConsumer.java │ │ │ ├── RMQNormalProducer.java │ │ │ ├── RMQPopClient.java │ │ │ ├── RMQPopConsumer.java │ │ │ ├── RMQSqlConsumer.java │ │ │ └── RMQTransactionalProducer.java │ │ ├── clientinterface/ │ │ │ ├── AbstractMQConsumer.java │ │ │ ├── AbstractMQProducer.java │ │ │ ├── MQCollector.java │ │ │ ├── MQConsumer.java │ │ │ └── MQProducer.java │ │ ├── factory/ │ │ │ ├── ConsumerFactory.java │ │ │ ├── MQMessageFactory.java │ │ │ ├── MessageFactory.java │ │ │ ├── ProducerFactory.java │ │ │ ├── SendCallBackFactory.java │ │ │ └── TagMessage.java │ │ ├── listener/ │ │ │ ├── AbstractListener.java │ │ │ └── rmq/ │ │ │ ├── concurrent/ │ │ │ │ ├── RMQBlockListener.java │ │ │ │ ├── RMQDelayListener.java │ │ │ │ └── RMQNormalListener.java │ │ │ └── order/ │ │ │ └── RMQOrderListener.java │ │ ├── lmq/ │ │ │ └── benchmark/ │ │ │ └── BenchLmqStore.java │ │ ├── message/ │ │ │ └── MessageQueueMsg.java │ │ ├── schema/ │ │ │ ├── SchemaDefiner.java │ │ │ └── SchemaTools.java │ │ ├── sendresult/ │ │ │ └── ResultWrapper.java │ │ └── util/ │ │ ├── Condition.java │ │ ├── DuplicateMessageInfo.java │ │ ├── FileUtil.java │ │ ├── MQAdminTestUtils.java │ │ ├── MQRandomUtils.java │ │ ├── MQWait.java │ │ ├── RandomUtil.java │ │ ├── RandomUtils.java │ │ ├── StatUtil.java │ │ ├── TestUtil.java │ │ ├── TestUtils.java │ │ ├── VerifyUtils.java │ │ ├── data/ │ │ │ └── collect/ │ │ │ ├── DataCollector.java │ │ │ ├── DataCollectorManager.java │ │ │ ├── DataFilter.java │ │ │ └── impl/ │ │ │ ├── ListDataCollectorImpl.java │ │ │ └── MapDataCollectorImpl.java │ │ └── parallel/ │ │ ├── ParallelTask.java │ │ ├── ParallelTaskExecutor.java │ │ └── Task4Test.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── test/ │ │ ├── autoswitchrole/ │ │ │ ├── AutoSwitchRoleBase.java │ │ │ └── AutoSwitchRoleIntegrationTest.java │ │ ├── base/ │ │ │ ├── BaseConf.java │ │ │ └── IntegrationTestBase.java │ │ ├── client/ │ │ │ ├── consumer/ │ │ │ │ ├── balance/ │ │ │ │ │ ├── NormalMsgDynamicBalanceIT.java │ │ │ │ │ └── NormalMsgStaticBalanceIT.java │ │ │ │ ├── broadcast/ │ │ │ │ │ ├── BaseBroadcast.java │ │ │ │ │ ├── normal/ │ │ │ │ │ │ ├── BroadcastNormalMsgNotReceiveIT.java │ │ │ │ │ │ ├── BroadcastNormalMsgRecvCrashIT.java │ │ │ │ │ │ ├── BroadcastNormalMsgRecvFailIT.java │ │ │ │ │ │ ├── BroadcastNormalMsgRecvStartLaterIT.java │ │ │ │ │ │ ├── BroadcastNormalMsgTwoDiffGroupRecvIT.java │ │ │ │ │ │ └── NormalMsgTwoSameGroupConsumerIT.java │ │ │ │ │ ├── order/ │ │ │ │ │ │ └── OrderMsgBroadcastIT.java │ │ │ │ │ └── tag/ │ │ │ │ │ ├── BroadcastTwoConsumerFilterIT.java │ │ │ │ │ ├── BroadcastTwoConsumerSubDiffTagIT.java │ │ │ │ │ └── BroadcastTwoConsumerSubTagIT.java │ │ │ │ ├── cluster/ │ │ │ │ │ ├── DynamicAddAndCrashIT.java │ │ │ │ │ ├── DynamicAddConsumerIT.java │ │ │ │ │ └── DynamicCrashConsumerIT.java │ │ │ │ ├── filter/ │ │ │ │ │ └── SqlFilterIT.java │ │ │ │ ├── pop/ │ │ │ │ │ ├── BasePop.java │ │ │ │ │ ├── BasePopNormally.java │ │ │ │ │ ├── BasePopOrderly.java │ │ │ │ │ ├── BatchAckIT.java │ │ │ │ │ ├── NotificationIT.java │ │ │ │ │ ├── PopBigMessageIT.java │ │ │ │ │ ├── PopMessageAndForwardingIT.java │ │ │ │ │ ├── PopOrderlyIT.java │ │ │ │ │ ├── PopPriorityIT.java │ │ │ │ │ └── PopSubCheckIT.java │ │ │ │ ├── tag/ │ │ │ │ │ ├── MulTagSubIT.java │ │ │ │ │ ├── TagMessageWith1ConsumerIT.java │ │ │ │ │ ├── TagMessageWithMulConsumerIT.java │ │ │ │ │ └── TagMessageWithSameGroupConsumerIT.java │ │ │ │ └── topic/ │ │ │ │ ├── MulConsumerMulTopicIT.java │ │ │ │ └── OneConsumerMulTopicIT.java │ │ │ └── producer/ │ │ │ ├── async/ │ │ │ │ ├── AsyncSendExceptionIT.java │ │ │ │ ├── AsyncSendWithMessageQueueIT.java │ │ │ │ ├── AsyncSendWithMessageQueueSelectorIT.java │ │ │ │ └── AsyncSendWithOnlySendCallBackIT.java │ │ │ ├── batch/ │ │ │ │ └── BatchSendIT.java │ │ │ ├── exception/ │ │ │ │ ├── msg/ │ │ │ │ │ ├── ChinaPropIT.java │ │ │ │ │ ├── MessageExceptionIT.java │ │ │ │ │ └── MessageUserPropIT.java │ │ │ │ └── producer/ │ │ │ │ └── ProducerGroupAndInstanceNameValidityIT.java │ │ │ ├── oneway/ │ │ │ │ ├── OneWaySendExceptionIT.java │ │ │ │ ├── OneWaySendIT.java │ │ │ │ ├── OneWaySendWithMQIT.java │ │ │ │ └── OneWaySendWithSelectorIT.java │ │ │ ├── order/ │ │ │ │ ├── OrderMsgDynamicRebalanceIT.java │ │ │ │ ├── OrderMsgIT.java │ │ │ │ ├── OrderMsgRebalanceIT.java │ │ │ │ └── OrderMsgWithTagIT.java │ │ │ ├── querymsg/ │ │ │ │ ├── QueryMsgByIdExceptionIT.java │ │ │ │ ├── QueryMsgByIdIT.java │ │ │ │ └── QueryMsgByKeyIT.java │ │ │ └── transaction/ │ │ │ └── TransactionalMsgIT.java │ │ ├── container/ │ │ │ ├── AddAndRemoveBrokerIT.java │ │ │ ├── BrokerFailoverIT.java │ │ │ ├── BrokerMemberGroupIT.java │ │ │ ├── ContainerIntegrationTestBase.java │ │ │ ├── GetMaxOffsetFromSlaveIT.java │ │ │ ├── GetMetadataReverseIT.java │ │ │ ├── PopSlaveActingMasterIT.java │ │ │ ├── PullMultipleReplicasIT.java │ │ │ ├── PushMultipleReplicasIT.java │ │ │ ├── RebalanceLockOnSlaveIT.java │ │ │ ├── ScheduleSlaveActingMasterIT.java │ │ │ ├── ScheduledMessageIT.java │ │ │ ├── SendMultipleReplicasIT.java │ │ │ ├── SlaveBrokerIT.java │ │ │ ├── SyncConsumerOffsetIT.java │ │ │ ├── TransactionListenerImpl.java │ │ │ └── TransactionMessageIT.java │ │ ├── delay/ │ │ │ ├── DelayConf.java │ │ │ └── NormalMsgDelayIT.java │ │ ├── dledger/ │ │ │ └── DLedgerProduceAndConsumeIT.java │ │ ├── grpc/ │ │ │ └── v2/ │ │ │ ├── ClusterGrpcIT.java │ │ │ ├── GrpcBaseIT.java │ │ │ └── LocalGrpcIT.java │ │ ├── lmq/ │ │ │ └── TestBenchLmqStore.java │ │ ├── offset/ │ │ │ ├── LagCalculationIT.java │ │ │ ├── OffsetNotFoundIT.java │ │ │ ├── OffsetResetForPopIT.java │ │ │ └── OffsetResetIT.java │ │ ├── recall/ │ │ │ ├── RecallWithTraceIT.java │ │ │ └── SendAndRecallDelayMessageIT.java │ │ ├── retry/ │ │ │ └── PopConsumerRetryIT.java │ │ ├── route/ │ │ │ └── CreateAndUpdateTopicIT.java │ │ ├── schema/ │ │ │ └── SchemaTest.java │ │ ├── smoke/ │ │ │ └── NormalMessageSendAndRecvIT.java │ │ ├── statictopic/ │ │ │ └── StaticTopicIT.java │ │ └── tls/ │ │ ├── TlsIT.java │ │ ├── TlsMix2IT.java │ │ └── TlsMixIT.java │ └── resources/ │ ├── rmq-proxy-home/ │ │ └── conf/ │ │ ├── broker.conf │ │ ├── logback_proxy.xml │ │ └── rmq-proxy.json │ ├── rmq.logback-test.xml │ └── schema/ │ ├── api/ │ │ ├── client.consumer.AllocateMessageQueueStrategy.schema │ │ ├── client.consumer.DefaultLitePullConsumer.schema │ │ ├── client.consumer.DefaultMQPullConsumer.schema │ │ ├── client.consumer.DefaultMQPushConsumer.schema │ │ ├── client.consumer.PullCallback.schema │ │ ├── client.consumer.PullResult.schema │ │ ├── client.consumer.PullStatus.schema │ │ ├── client.consumer.listener.ConsumeConcurrentlyContext.schema │ │ ├── client.consumer.listener.ConsumeConcurrentlyStatus.schema │ │ ├── client.consumer.listener.ConsumeOrderlyContext.schema │ │ ├── client.consumer.listener.ConsumeOrderlyStatus.schema │ │ ├── client.consumer.listener.MessageListener.schema │ │ ├── client.consumer.listener.MessageListenerConcurrently.schema │ │ ├── client.consumer.listener.MessageListenerOrderly.schema │ │ ├── client.hook.CheckForbiddenHook.schema │ │ ├── client.hook.ConsumeMessageContext.schema │ │ ├── client.hook.ConsumeMessageHook.schema │ │ ├── client.hook.EndTransactionContext.schema │ │ ├── client.hook.EndTransactionHook.schema │ │ ├── client.hook.FilterMessageContext.schema │ │ ├── client.hook.FilterMessageHook.schema │ │ ├── client.hook.SendMessageContext.schema │ │ ├── client.hook.SendMessageHook.schema │ │ ├── client.producer.DefaultMQProducer.schema │ │ ├── client.producer.MessageQueueSelector.schema │ │ ├── client.producer.SendCallback.schema │ │ ├── client.producer.SendResult.schema │ │ ├── client.producer.SendStatus.schema │ │ ├── common.message.Message.schema │ │ ├── common.message.MessageExt.schema │ │ ├── common.message.MessageQueue.schema │ │ ├── remoting.RPCHook.schema │ │ └── tools.admin.DefaultMQAdminExt.schema │ └── protocol/ │ ├── common.protocol.RequestCode.schema │ ├── common.protocol.header.CheckTransactionStateRequestHeader.schema │ ├── common.protocol.header.CheckTransactionStateResponseHeader.schema │ ├── common.protocol.header.CloneGroupOffsetRequestHeader.schema │ ├── common.protocol.header.ConsumeMessageDirectlyResultRequestHeader.schema │ ├── common.protocol.header.ConsumerSendMsgBackRequestHeader.schema │ ├── common.protocol.header.CreateAccessConfigRequestHeader.schema │ ├── common.protocol.header.CreateTopicRequestHeader.schema │ ├── common.protocol.header.DeleteAccessConfigRequestHeader.schema │ ├── common.protocol.header.DeleteSubscriptionGroupRequestHeader.schema │ ├── common.protocol.header.DeleteTopicRequestHeader.schema │ ├── common.protocol.header.EndTransactionRequestHeader.schema │ ├── common.protocol.header.EndTransactionResponseHeader.schema │ ├── common.protocol.header.GetAllProducerInfoRequestHeader.schema │ ├── common.protocol.header.GetAllTopicConfigResponseHeader.schema │ ├── common.protocol.header.GetBrokerAclConfigResponseHeader.schema │ ├── common.protocol.header.GetBrokerClusterAclConfigResponseHeader.schema │ ├── common.protocol.header.GetBrokerConfigResponseHeader.schema │ ├── common.protocol.header.GetConsumeStatsInBrokerHeader.schema │ ├── common.protocol.header.GetConsumeStatsRequestHeader.schema │ ├── common.protocol.header.GetConsumerConnectionListRequestHeader.schema │ ├── common.protocol.header.GetConsumerListByGroupRequestHeader.schema │ ├── common.protocol.header.GetConsumerListByGroupResponseHeader.schema │ ├── common.protocol.header.GetConsumerRunningInfoRequestHeader.schema │ ├── common.protocol.header.GetConsumerStatusRequestHeader.schema │ ├── common.protocol.header.GetEarliestMsgStoretimeRequestHeader.schema │ ├── common.protocol.header.GetEarliestMsgStoretimeResponseHeader.schema │ ├── common.protocol.header.GetMaxOffsetRequestHeader.schema │ ├── common.protocol.header.GetMaxOffsetResponseHeader.schema │ ├── common.protocol.header.GetMinOffsetRequestHeader.schema │ ├── common.protocol.header.GetMinOffsetResponseHeader.schema │ ├── common.protocol.header.GetProducerConnectionListRequestHeader.schema │ ├── common.protocol.header.GetTopicStatsInfoRequestHeader.schema │ ├── common.protocol.header.GetTopicsByClusterRequestHeader.schema │ ├── common.protocol.header.NotifyConsumerIdsChangedRequestHeader.schema │ ├── common.protocol.header.PullMessageRequestHeader.schema │ ├── common.protocol.header.PullMessageResponseHeader.schema │ ├── common.protocol.header.QueryConsumeQueueRequestHeader.schema │ ├── common.protocol.header.QueryConsumeTimeSpanRequestHeader.schema │ ├── common.protocol.header.QueryConsumerOffsetRequestHeader.schema │ ├── common.protocol.header.QueryConsumerOffsetResponseHeader.schema │ ├── common.protocol.header.QueryCorrectionOffsetHeader.schema │ ├── common.protocol.header.QueryMessageRequestHeader.schema │ ├── common.protocol.header.QueryMessageResponseHeader.schema │ ├── common.protocol.header.QueryTopicConsumeByWhoRequestHeader.schema │ ├── common.protocol.header.ReplyMessageRequestHeader.schema │ ├── common.protocol.header.ResetOffsetRequestHeader.schema │ ├── common.protocol.header.ResumeCheckHalfMessageRequestHeader.schema │ ├── common.protocol.header.SearchOffsetRequestHeader.schema │ ├── common.protocol.header.SearchOffsetResponseHeader.schema │ ├── common.protocol.header.SendMessageRequestHeader.schema │ ├── common.protocol.header.SendMessageRequestHeaderV2.schema │ ├── common.protocol.header.SendMessageResponseHeader.schema │ ├── common.protocol.header.UnregisterClientRequestHeader.schema │ ├── common.protocol.header.UnregisterClientResponseHeader.schema │ ├── common.protocol.header.UpdateConsumerOffsetRequestHeader.schema │ ├── common.protocol.header.UpdateConsumerOffsetResponseHeader.schema │ ├── common.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader.schema │ ├── common.protocol.header.ViewBrokerStatsDataRequestHeader.schema │ ├── common.protocol.header.ViewMessageRequestHeader.schema │ ├── common.protocol.header.ViewMessageResponseHeader.schema │ ├── common.protocol.header.filtersrv.RegisterFilterServerRequestHeader.schema │ ├── common.protocol.header.filtersrv.RegisterFilterServerResponseHeader.schema │ ├── common.protocol.header.filtersrv.RegisterMessageFilterClassRequestHeader.schema │ ├── common.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader.schema │ ├── common.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader.schema │ ├── common.protocol.header.namesrv.DeleteKVConfigRequestHeader.schema │ ├── common.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader.schema │ ├── common.protocol.header.namesrv.GetKVConfigRequestHeader.schema │ ├── common.protocol.header.namesrv.GetKVConfigResponseHeader.schema │ ├── common.protocol.header.namesrv.GetKVListByNamespaceRequestHeader.schema │ ├── common.protocol.header.namesrv.GetRouteInfoRequestHeader.schema │ ├── common.protocol.header.namesrv.PutKVConfigRequestHeader.schema │ ├── common.protocol.header.namesrv.QueryDataVersionRequestHeader.schema │ ├── common.protocol.header.namesrv.QueryDataVersionResponseHeader.schema │ ├── common.protocol.header.namesrv.RegisterBrokerRequestHeader.schema │ ├── common.protocol.header.namesrv.RegisterBrokerResponseHeader.schema │ ├── common.protocol.header.namesrv.RegisterOrderTopicRequestHeader.schema │ ├── common.protocol.header.namesrv.UnRegisterBrokerRequestHeader.schema │ ├── common.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader.schema │ └── common.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader.schema ├── tieredstore/ │ ├── BUILD.bazel │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── tieredstore/ │ │ ├── MessageStoreConfig.java │ │ ├── MessageStoreExecutor.java │ │ ├── TieredMessageStore.java │ │ ├── common/ │ │ │ ├── AppendResult.java │ │ │ ├── FileSegmentType.java │ │ │ ├── GetMessageResultExt.java │ │ │ ├── GroupCommitContext.java │ │ │ └── SelectBufferResult.java │ │ ├── core/ │ │ │ ├── MessageStoreDispatcher.java │ │ │ ├── MessageStoreDispatcherImpl.java │ │ │ ├── MessageStoreFetcher.java │ │ │ ├── MessageStoreFetcherImpl.java │ │ │ ├── MessageStoreFilter.java │ │ │ └── MessageStoreTopicFilter.java │ │ ├── exception/ │ │ │ ├── TieredStoreErrorCode.java │ │ │ └── TieredStoreException.java │ │ ├── file/ │ │ │ ├── FlatAppendFile.java │ │ │ ├── FlatCommitLogFile.java │ │ │ ├── FlatConsumeQueueFile.java │ │ │ ├── FlatFileFactory.java │ │ │ ├── FlatFileInterface.java │ │ │ ├── FlatFileStore.java │ │ │ └── FlatMessageFile.java │ │ ├── index/ │ │ │ ├── IndexFile.java │ │ │ ├── IndexItem.java │ │ │ ├── IndexService.java │ │ │ ├── IndexStoreFile.java │ │ │ └── IndexStoreService.java │ │ ├── metadata/ │ │ │ ├── DefaultMetadataStore.java │ │ │ ├── MetadataStore.java │ │ │ └── entity/ │ │ │ ├── FileSegmentMetadata.java │ │ │ ├── QueueMetadata.java │ │ │ └── TopicMetadata.java │ │ ├── metrics/ │ │ │ ├── TieredStoreMetricsConstant.java │ │ │ └── TieredStoreMetricsManager.java │ │ ├── provider/ │ │ │ ├── FileSegment.java │ │ │ ├── FileSegmentFactory.java │ │ │ ├── FileSegmentProvider.java │ │ │ ├── MemoryFileSegment.java │ │ │ └── PosixFileSegment.java │ │ ├── stream/ │ │ │ ├── CommitLogInputStream.java │ │ │ ├── FileSegmentInputStream.java │ │ │ └── FileSegmentInputStreamFactory.java │ │ └── util/ │ │ ├── MessageFormatUtil.java │ │ └── MessageStoreUtil.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── tieredstore/ │ │ ├── TieredMessageStoreTest.java │ │ ├── common/ │ │ │ ├── FileSegmentTypeTest.java │ │ │ ├── GetMessageResultExtTest.java │ │ │ ├── GroupCommitContextTest.java │ │ │ └── SelectBufferResultTest.java │ │ ├── core/ │ │ │ ├── MessageStoreDispatcherImplTest.java │ │ │ ├── MessageStoreFetcherImplTest.java │ │ │ └── MessageStoreTopicFilterTest.java │ │ ├── exception/ │ │ │ └── TieredStoreExceptionTest.java │ │ ├── file/ │ │ │ ├── FlatAppendFileTest.java │ │ │ ├── FlatCommitLogFileTest.java │ │ │ ├── FlatFileFactoryTest.java │ │ │ ├── FlatFileStoreTest.java │ │ │ └── FlatMessageFileTest.java │ │ ├── index/ │ │ │ ├── IndexItemTest.java │ │ │ ├── IndexStoreFileTest.java │ │ │ ├── IndexStoreServiceBenchTest.java │ │ │ └── IndexStoreServiceTest.java │ │ ├── metadata/ │ │ │ └── DefaultMetadataStoreTest.java │ │ ├── metrics/ │ │ │ └── TieredStoreMetricsManagerTest.java │ │ ├── provider/ │ │ │ ├── FileSegmentFactoryTest.java │ │ │ ├── FileSegmentTest.java │ │ │ └── MemoryFileSegmentTest.java │ │ ├── stream/ │ │ │ └── FileSegmentInputStreamTest.java │ │ └── util/ │ │ ├── MessageFormatUtilTest.java │ │ └── MessageStoreUtilTest.java │ └── resources/ │ └── rmq.logback-test.xml └── tools/ ├── BUILD.bazel ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── rocketmq/ │ │ └── tools/ │ │ ├── admin/ │ │ │ ├── DefaultMQAdminExt.java │ │ │ ├── DefaultMQAdminExtImpl.java │ │ │ ├── MQAdminExt.java │ │ │ ├── MQAdminUtils.java │ │ │ ├── api/ │ │ │ │ ├── BrokerOperatorResult.java │ │ │ │ ├── MessageTrack.java │ │ │ │ └── TrackType.java │ │ │ └── common/ │ │ │ ├── AdminToolHandler.java │ │ │ ├── AdminToolResult.java │ │ │ └── AdminToolsResultCodeEnum.java │ │ ├── command/ │ │ │ ├── CommandUtil.java │ │ │ ├── MQAdminStartup.java │ │ │ ├── SubCommand.java │ │ │ ├── SubCommandException.java │ │ │ ├── auth/ │ │ │ │ ├── CopyAclsSubCommand.java │ │ │ │ ├── CopyUsersSubCommand.java │ │ │ │ ├── CreateAclSubCommand.java │ │ │ │ ├── CreateUserSubCommand.java │ │ │ │ ├── DeleteAclSubCommand.java │ │ │ │ ├── DeleteUserSubCommand.java │ │ │ │ ├── GetAclSubCommand.java │ │ │ │ ├── GetUserSubCommand.java │ │ │ │ ├── ListAclSubCommand.java │ │ │ │ ├── ListUserSubCommand.java │ │ │ │ ├── UpdateAclSubCommand.java │ │ │ │ └── UpdateUserSubCommand.java │ │ │ ├── broker/ │ │ │ │ ├── BrokerConsumeStatsSubCommand.java │ │ │ │ ├── BrokerStatusSubCommand.java │ │ │ │ ├── CleanExpiredCQSubCommand.java │ │ │ │ ├── CleanUnusedTopicCommand.java │ │ │ │ ├── CommitLogSetReadAheadSubCommand.java │ │ │ │ ├── DeleteExpiredCommitLogSubCommand.java │ │ │ │ ├── GetBrokerConfigCommand.java │ │ │ │ ├── GetBrokerEpochSubCommand.java │ │ │ │ ├── GetColdDataFlowCtrInfoSubCommand.java │ │ │ │ ├── RemoveColdDataFlowCtrGroupConfigSubCommand.java │ │ │ │ ├── ResetMasterFlushOffsetSubCommand.java │ │ │ │ ├── SendMsgStatusCommand.java │ │ │ │ ├── SwitchTimerEngineSubCommand.java │ │ │ │ ├── UpdateBrokerConfigSubCommand.java │ │ │ │ └── UpdateColdDataFlowCtrGroupConfigSubCommand.java │ │ │ ├── cluster/ │ │ │ │ ├── CLusterSendMsgRTCommand.java │ │ │ │ └── ClusterListSubCommand.java │ │ │ ├── connection/ │ │ │ │ ├── ConsumerConnectionSubCommand.java │ │ │ │ └── ProducerConnectionSubCommand.java │ │ │ ├── consumer/ │ │ │ │ ├── ConsumerProgressSubCommand.java │ │ │ │ ├── ConsumerStatusSubCommand.java │ │ │ │ ├── ConsumerSubCommand.java │ │ │ │ ├── DeleteSubscriptionGroupCommand.java │ │ │ │ ├── GetConsumerConfigSubCommand.java │ │ │ │ ├── SetConsumeModeSubCommand.java │ │ │ │ ├── StartMonitoringSubCommand.java │ │ │ │ ├── UpdateSubGroupListSubCommand.java │ │ │ │ └── UpdateSubGroupSubCommand.java │ │ │ ├── container/ │ │ │ │ ├── AddBrokerSubCommand.java │ │ │ │ └── RemoveBrokerSubCommand.java │ │ │ ├── controller/ │ │ │ │ ├── CleanControllerBrokerMetaSubCommand.java │ │ │ │ ├── GetControllerConfigSubCommand.java │ │ │ │ ├── GetControllerMetaDataSubCommand.java │ │ │ │ ├── ReElectMasterSubCommand.java │ │ │ │ └── UpdateControllerConfigSubCommand.java │ │ │ ├── export/ │ │ │ │ ├── ExportConfigsCommand.java │ │ │ │ ├── ExportMetadataCommand.java │ │ │ │ ├── ExportMetadataInRocksDBCommand.java │ │ │ │ ├── ExportMetricsCommand.java │ │ │ │ └── ExportPopRecordCommand.java │ │ │ ├── ha/ │ │ │ │ ├── GetSyncStateSetSubCommand.java │ │ │ │ └── HAStatusSubCommand.java │ │ │ ├── lite/ │ │ │ │ ├── GetBrokerLiteInfoSubCommand.java │ │ │ │ ├── GetLiteClientInfoSubCommand.java │ │ │ │ ├── GetLiteGroupInfoSubCommand.java │ │ │ │ ├── GetLiteTopicInfoSubCommand.java │ │ │ │ ├── GetParentTopicInfoSubCommand.java │ │ │ │ └── TriggerLiteDispatchSubCommand.java │ │ │ ├── message/ │ │ │ │ ├── CheckMsgSendRTCommand.java │ │ │ │ ├── ConsumeMessageCommand.java │ │ │ │ ├── DecodeMessageIdCommond.java │ │ │ │ ├── DumpCompactionLogCommand.java │ │ │ │ ├── PrintMessageByQueueCommand.java │ │ │ │ ├── PrintMessageSubCommand.java │ │ │ │ ├── QueryMsgByIdSubCommand.java │ │ │ │ ├── QueryMsgByKeySubCommand.java │ │ │ │ ├── QueryMsgByOffsetSubCommand.java │ │ │ │ ├── QueryMsgByUniqueKeySubCommand.java │ │ │ │ ├── QueryMsgTraceByIdSubCommand.java │ │ │ │ └── SendMessageCommand.java │ │ │ ├── metadata/ │ │ │ │ └── RocksDBConfigToJsonCommand.java │ │ │ ├── namesrv/ │ │ │ │ ├── AddWritePermSubCommand.java │ │ │ │ ├── DeleteKvConfigCommand.java │ │ │ │ ├── GetNamesrvConfigCommand.java │ │ │ │ ├── UpdateKvConfigCommand.java │ │ │ │ ├── UpdateNamesrvConfigCommand.java │ │ │ │ └── WipeWritePermSubCommand.java │ │ │ ├── offset/ │ │ │ │ ├── CloneGroupOffsetCommand.java │ │ │ │ ├── GetConsumerStatusCommand.java │ │ │ │ ├── ResetOffsetByTimeCommand.java │ │ │ │ ├── ResetOffsetByTimeOldCommand.java │ │ │ │ └── SkipAccumulationSubCommand.java │ │ │ ├── producer/ │ │ │ │ └── ProducerSubCommand.java │ │ │ ├── queue/ │ │ │ │ ├── CheckRocksdbCqWriteProgressCommand.java │ │ │ │ └── QueryConsumeQueueCommand.java │ │ │ ├── stats/ │ │ │ │ └── StatsAllSubCommand.java │ │ │ └── topic/ │ │ │ ├── AllocateMQSubCommand.java │ │ │ ├── DeleteTopicSubCommand.java │ │ │ ├── RebalanceResult.java │ │ │ ├── RemappingStaticTopicSubCommand.java │ │ │ ├── TopicClusterSubCommand.java │ │ │ ├── TopicListSubCommand.java │ │ │ ├── TopicRouteSubCommand.java │ │ │ ├── TopicStatusSubCommand.java │ │ │ ├── UpdateOrderConfCommand.java │ │ │ ├── UpdateStaticTopicSubCommand.java │ │ │ ├── UpdateTopicListSubCommand.java │ │ │ ├── UpdateTopicPermSubCommand.java │ │ │ └── UpdateTopicSubCommand.java │ │ └── monitor/ │ │ ├── DefaultMonitorListener.java │ │ ├── DeleteMsgsEvent.java │ │ ├── FailedMsgs.java │ │ ├── MonitorConfig.java │ │ ├── MonitorListener.java │ │ ├── MonitorService.java │ │ └── UndoneMsgs.java │ └── resources/ │ └── rmq.tools.logback.xml └── test/ ├── java/ │ └── org/ │ └── apache/ │ └── rocketmq/ │ └── tools/ │ ├── admin/ │ │ ├── DefaultMQAdminExtImplTest.java │ │ └── DefaultMQAdminExtTest.java │ ├── command/ │ │ ├── CommandUtilTest.java │ │ ├── broker/ │ │ │ ├── BrokerConsumeStatsSubCommandTest.java │ │ │ ├── BrokerStatusSubCommandTest.java │ │ │ ├── CleanExpiredCQSubCommandTest.java │ │ │ ├── CleanUnusedTopicCommandTest.java │ │ │ ├── DeleteExpiredCommitLogSubCommandTest.java │ │ │ ├── GetBrokerConfigCommandTest.java │ │ │ ├── SendMsgStatusCommandTest.java │ │ │ ├── SwitchTimerEngineSubCommandTest.java │ │ │ └── UpdateBrokerConfigSubCommandTest.java │ │ ├── connection/ │ │ │ ├── ConsumerConnectionSubCommandTest.java │ │ │ └── ProducerConnectionSubCommandTest.java │ │ ├── consumer/ │ │ │ ├── ConsumerProgressSubCommandTest.java │ │ │ ├── ConsumerStatusSubCommandTest.java │ │ │ ├── GetConsumerConfigSubCommandTest.java │ │ │ └── UpdateSubGroupListSubCommandTest.java │ │ ├── lite/ │ │ │ ├── GetBrokerLiteInfoSubCommandTest.java │ │ │ └── GetLiteClientInfoSubCommandTest.java │ │ ├── message/ │ │ │ ├── ConsumeMessageCommandTest.java │ │ │ ├── QueryMsgByUniqueKeySubCommandTest.java │ │ │ ├── QueryMsgTraceByIdSubCommandTest.java │ │ │ └── SendMessageCommandTest.java │ │ ├── metadata/ │ │ │ └── ExportMetadataInRocksDBCommandTest.java │ │ ├── namesrv/ │ │ │ ├── AddWritePermSubCommandTest.java │ │ │ ├── GetNamesrvConfigCommandTest.java │ │ │ ├── UpdateKvConfigCommandTest.java │ │ │ └── WipeWritePermSubCommandTest.java │ │ ├── offset/ │ │ │ ├── GetConsumerStatusCommandTest.java │ │ │ ├── ResetOffsetByTimeCommandTest.java │ │ │ ├── ResetOffsetByTimeOldCommandTest.java │ │ │ └── SkipAccumulationCommandTest.java │ │ ├── producer/ │ │ │ └── ProducerSubCommandTest.java │ │ ├── server/ │ │ │ ├── NameServerMocker.java │ │ │ └── ServerResponseMocker.java │ │ └── topic/ │ │ ├── AllocateMQSubCommandTest.java │ │ ├── DeleteTopicSubCommandTest.java │ │ ├── TopicClusterSubCommandTest.java │ │ ├── TopicRouteSubCommandTest.java │ │ ├── TopicStatusSubCommandTest.java │ │ ├── UpdateOrderConfCommandTest.java │ │ ├── UpdateTopicListSubCommandTest.java │ │ ├── UpdateTopicPermSubCommandTest.java │ │ └── UpdateTopicSubCommandTest.java │ └── monitor/ │ ├── DefaultMonitorListenerTest.java │ └── MonitorServiceTest.java └── resources/ └── rmq.logback-test.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .asf.yaml ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. github: description: "Apache RocketMQ is a cloud native messaging and streaming platform, making it simple to build event-driven applications." homepage: https://rocketmq.apache.org/ labels: - messaging - streaming - eventing - cloud-native - rocketmq - java - hacktoberfest enabled_merge_buttons: # Enable squash button squash: true # Disable merge button merge: false # Disable rebase button rebase: false protected_branches: master: {} develop: required_pull_request_reviews: dismiss_stale_reviews: true require_code_owner_reviews: false required_approving_review_count: 1 required_status_checks: contexts: - misspell-check - check-license - maven-compile (ubuntu-latest, JDK-8) - maven-compile (windows-latest, JDK-8) - maven-compile (macos-latest, JDK-8) notifications: commits: commits@rocketmq.apache.org issues: commits@rocketmq.apache.org pullrequests: commits@rocketmq.apache.org jobs: commits@rocketmq.apache.org discussions: dev@rocketmq.apache.org ================================================ FILE: .bazelrc ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # startup --host_jvm_args=-Xmx2g run --color=yes build --color=yes build --enable_platform_specific_config test --action_env=TEST_TMPDIR=/tmp test --experimental_strict_java_deps=warn test --experimental_ui_max_stdouterr_bytes=10485760 build --experimental_strict_java_deps=warn test --test_output=errors # This .bazelrc file contains all of the flags required for the provided # toolchain with Remote Build Execution. # Note your WORKSPACE must contain an rbe_autoconfig target with # name="rbe_default" to use these flags as-is. # Depending on how many machines are in the remote execution instance, setting # this higher can make builds faster by allowing more jobs to run in parallel. # Setting it too high can result in jobs that timeout, however, while waiting # for a remote machine to execute them. build:remote --jobs=150 build:remote --remote_executor=grpcs://remote.buildbuddy.io build:remote --host_platform=@buildbuddy_toolchain//:platform build:remote --platforms=@buildbuddy_toolchain//:platform build:remote --extra_execution_platforms=@buildbuddy_toolchain//:platform build:remote --crosstool_top=@buildbuddy_toolchain//:toolchain build:remote --extra_toolchains=@buildbuddy_toolchain//:cc_toolchain build:remote --java_language_version=8 build:remote --java_runtime_version=8 build:remote --define=EXECUTOR=remote # Enable remote execution so actions are performed on the remote systems. build:remote --remote_executor=grpcs://remote.buildbuddy.io # Enforce stricter environment rules, which eliminates some non-hermetic # behavior and therefore improves both the remote cache hit rate and the # correctness and repeatability of the build. build:remote --incompatible_strict_action_env=true # Set a higher timeout value, just in case. build:remote --remote_timeout=3600 # Use a pre-configured account, such that we may have pull-request replacing pull-request-target build:remote --remote_header=x-buildbuddy-api-key=FD819nUEY7WjvqmoufsU test:remote --remote_header=x-buildbuddy-api-key=FD819nUEY7WjvqmoufsU ================================================ FILE: .bazelversion ================================================ 6.5.0 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # name: Bug Report title: "[Bug] Bug title " description: Create a report to help us identify any unintended flaws, errors, or faults. body: - type: checkboxes attributes: label: Before Creating the Bug Report options: - label: > I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions). required: true - label: > I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate. required: true - label: > I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ. required: true - type: textarea attributes: label: Runtime platform environment description: Describe the runtime platform environment. placeholder: > OS: (e.g., "Ubuntu 20.04") OS: (e.g., "Windows Server 2019") validations: required: true - type: textarea attributes: label: RocketMQ version description: Describe the RocketMQ version. placeholder: > branch: (e.g develop|4.9.x) version: (e.g. 5.1.0|4.9.5) Git commit id: (e.g. c88b5cfa72e204962929eea105687647146112c6) validations: required: true - type: textarea attributes: label: JDK Version description: Run or Compiler version. placeholder: > Compiler: (e.g., "Oracle JDK 11.0.17") OS: (e.g., "Ubuntu 20.04") Runtime (if different from JDK above): (e.g., "Oracle JRE 8u251") OS (if different from OS compiled on): (e.g., "Windows Server 2019") validations: required: false - type: textarea attributes: label: Describe the Bug description: Describe what happened. placeholder: > A clear and concise description of what the bug is. validations: required: true - type: textarea attributes: label: Steps to Reproduce description: Describe the steps to reproduce the bug here. placeholder: > If possible, provide a recipe for reproducing the error. validations: required: true - type: textarea attributes: label: What Did You Expect to See? description: You expect to see result. placeholder: > A clear and concise description of what you expected to see. validations: required: true - type: textarea attributes: label: What Did You See Instead? description: You instead to see result. placeholder: > A clear and concise description of what you saw instead. validations: required: true - type: textarea attributes: label: Additional Context description: Additional context. placeholder: > Add any other context about the problem here. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # blank_issues_enabled: false contact_links: - name: Ask Question url: https://github.com/apache/rocketmq/discussions about: Please go to GitHub Disccusions to ask questions ================================================ FILE: .github/ISSUE_TEMPLATE/doc.yml ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # name: Documentation Related title: "[Doc] Documentation Related " description: I find some issues related to the documentation. labels: [ "module/doc" ] body: - type: checkboxes attributes: label: Search before creation description: > Please make sure to search in the [issues](https://github.com/apache/rocketmq/issues) first to see whether the same issue was reported already. options: - label: > I had searched in the [issues](https://github.com/apache/rocketmq/issues) and found no similar issues. required: true - type: textarea attributes: label: Documentation Related description: Describe the suggestion about document. placeholder: > e.g There is a typo validations: required: true - type: checkboxes attributes: label: Are you willing to submit PR? description: > This is absolutely not required, but we are happy to guide you in the contribution process especially if you already have a good understanding of how to implement the fix. options: - label: Yes I am willing to submit a PR! - type: markdown attributes: value: "Thanks for completing our form!" ================================================ FILE: .github/ISSUE_TEMPLATE/enhancement_request.yml ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # name: Enhancement Request title: "[Enhancement] Enhancement title" description: Suggest an enhancement for this project labels: [ "type/enhancement" ] body: - type: checkboxes attributes: label: Before Creating the Enhancement Request description: > Most of issues should be classified as bug or feature request. An issue should be considered as an enhancement when it proposes improvements to existing functionality or user experience, without necessarily introducing new features or fixing existing bugs. options: - label: > I have confirmed that this should be classified as an enhancement rather than a bug/feature. required: true - type: textarea attributes: label: Summary placeholder: > A clear and concise description of the enhancement you would like to see in the project. validations: required: true - type: textarea attributes: label: Motivation placeholder: > Explain why you believe this enhancement is necessary, and how it benefits the project and community. Include any specific use cases that you have in mind. validations: required: true - type: textarea attributes: label: Describe the Solution You'd Like placeholder: > Describe the enhancement you propose, detailing the change and implementation steps involved. If you have multiple solutions, please list them separately. validations: required: true - type: textarea attributes: label: Describe Alternatives You've Considered placeholder: > List any alternative enhancements or implementations you have considered, and explain why they may not be as effective or appropriate. validations: required: true - type: textarea attributes: label: Additional Context placeholder: > Add any relevant context, screenshots, prototypes, or other supplementary information to help illustrate the enhancement. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # name: Feature Request title: "[Feature] New feature title" description: Suggest an idea for this project. labels: [ "type/new feature" ] body: - type: textarea attributes: label: Is Your Feature Request Related to a Problem? description: Please Describe It. placeholder: > A clear and concise description of what the problem is. validations: required: true - type: textarea attributes: label: Describe the Solution You'd Like description: Describe how you solved it. placeholder: > A clear and concise description of what you want to happen. validations: required: true - type: textarea attributes: label: Describe Alternatives You've Considered description: Describe your solution placeholder: > A clear and concise description of any alternative solutions or features you've considered. validations: required: true - type: textarea attributes: label: Additional Context description: Additional context. placeholder: > Add any other context about the problem here. validations: required: false ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### Which Issue(s) This PR Fixes - Fixes #issue_id ### Brief Description ### How Did You Test This Change? ================================================ FILE: .github/asf-deploy-settings.xml ================================================ apache.snapshots.https ${env.NEXUS_DEPLOY_USERNAME} ${env.NEXUS_DEPLOY_PASSWORD} 60 ================================================ FILE: .github/workflows/bazel.yml ================================================ name: Build and Run Tests by Bazel on: pull_request: types: [opened, reopened, synchronize] push: branches: - master - develop - bazel jobs: build: name: "bazel-compile (${{ matrix.os }})" runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] steps: - uses: actions/checkout@v2 - name: Build run: bazel build --config=remote //... - name: Run Tests run: bazel test --config=remote //... ================================================ FILE: .github/workflows/codeql_analysis.yml ================================================ name: CodeQL Analysis on: pull_request: types: [opened, reopened, synchronize] push: branches: - master jobs: CodeQL-Build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: Cache Maven packages uses: actions/cache@v3 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: java - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ================================================ FILE: .github/workflows/coverage.yml ================================================ name: Coverage on: pull_request: types: [opened, reopened, synchronize] push: branches: [master, develop] jobs: calculate-coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Set up JDK 8 uses: actions/setup-java@v4 with: java-version: "8" distribution: "corretto" cache: "maven" - name: Generate coverage report run: mvn -B test -T 2C --file pom.xml - name: Upload to Codecov uses: codecov/codecov-action@v3 with: fail_ci_if_error: true verbose: true token: cf0cba0a-22f8-4580-89ab-4f1dec3bda6f ================================================ FILE: .github/workflows/integration-test.yml ================================================ name: Run Integration Tests on: pull_request: types: [opened, reopened, synchronize] push: branches: [master, develop] jobs: it-test: name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})" runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] jdk: [8] steps: - name: Cache Maven Repos uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Checkout uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.jdk }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.jdk }} distribution: "corretto" cache: "maven" - name: Run integration tests with Maven run: mvn clean verify -Pit-test -Pskip-unit-tests - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() with: report_paths: 'test/target/failsafe-reports/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true ================================================ FILE: .github/workflows/license-checker.yaml ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: License checker on: pull_request: branches: - develop - master jobs: check-license: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Check License Header uses: apache/skywalking-eyes@v0.2.0 with: log: info config: .licenserc.yaml ================================================ FILE: .github/workflows/maven.yaml ================================================ name: Build and Run Tests by Maven on: pull_request: types: [opened, reopened, synchronize] push: branches: [master, develop, bazel] jobs: java_build: name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources os: [ubuntu-latest, windows-latest, macos-latest] jdk: [8] steps: - name: Checkout uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.jdk }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.jdk }} # See https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions # AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. distribution: "corretto" cache: "maven" - name: Build with Maven run: mvn -B package --file pom.xml - name: Upload Auth JVM crash logs if: failure() uses: actions/upload-artifact@v4 with: name: jvm-crash-logs path: /Users/runner/work/rocketmq/rocketmq/auth/hs_err_pid*.log retention-days: 1 - name: Upload broker JVM crash logs if: failure() uses: actions/upload-artifact@v4 with: name: jvm-crash-logs path: /Users/runner/work/rocketmq/rocketmq/broker/hs_err_pid*.log retention-days: 1 ================================================ FILE: .github/workflows/misspell_check.yml ================================================ name: Misspell Check on: pull_request: types: [opened, reopened, synchronize] push: branches: [master, develop] jobs: misspell-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install misspell run: | curl -L -o ./install-misspell.sh https://git.io/misspell sh ./install-misspell.sh - name: Run misspell run: find . -type f -print0 | xargs -0 bin/misspell -error -i transfered,derivate ================================================ FILE: .github/workflows/pr-ci.yml ================================================ name: PR-CI on: pull_request: types: [opened, reopened, synchronize] jobs: dist-tar: name: Build distribution tar runs-on: ubuntu-latest timeout-minutes: 120 steps: - uses: actions/checkout@v3 with: submodules: true - uses: actions/setup-java@v3 with: distribution: "temurin" java-version: "8" cache: "maven" - name: Build distribution tar run: | mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U - uses: actions/upload-artifact@v4 name: Upload distribution tar with: name: rocketmq path: distribution/target/rocketmq*/rocketmq* - name: Save PR number run: | mkdir -p ./pr echo ${{ github.event.number }} > ./pr/NR - uses: actions/upload-artifact@v4 with: name: pr path: pr/ ================================================ FILE: .github/workflows/pr-e2e-test.yml ================================================ name: E2E test for pull request # read-write repo token # access to secrets on: workflow_run: workflows: ["PR-CI"] types: - completed env: DOCKER_REPO: apache/rocketmq-ci jobs: docker: if: > github.repository == 'apache/rocketmq' && github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest timeout-minutes: 30 strategy: matrix: base-image: ["ubuntu"] java-version: ["8"] steps: - name: 'Download artifact' uses: actions/github-script@v6 with: script: | let artifacts = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{github.event.workflow_run.id }}, }); let matchArtifactRmq = artifacts.data.artifacts.filter((artifact) => { return artifact.name == "rocketmq" })[0]; let download = await github.rest.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: matchArtifactRmq.id, archive_format: 'zip', }); var fs = require('fs'); fs.writeFileSync('${{github.workspace}}/rocketmq.zip', Buffer.from(download.data)); - run: | unzip rocketmq.zip mkdir rocketmq cp -r rocketmq-* rocketmq/ ls - uses: actions/checkout@v3 with: repository: apache/rocketmq-docker.git ref: master path: rocketmq-docker - name: docker-login uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and save docker images id: build-images run: | cd rocketmq-docker/image-build-ci version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen) mkdir versionlist touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`" sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO} - uses: actions/upload-artifact@v4 name: Upload distribution tar with: name: versionlist path: rocketmq-docker/image-build-ci/versionlist/* list-version: if: > github.repository == 'apache/rocketmq' && always() name: List version needs: [docker] runs-on: ubuntu-latest timeout-minutes: 30 outputs: version-json: ${{ steps.show_versions.outputs.version-json }} steps: - uses: actions/download-artifact@v4 name: Download versionlist with: name: versionlist path: versionlist - name: Show versions id: show_versions run: | a=(`ls versionlist`) printf '%s\n' "${a[@]}" | jq -R . | jq -s . echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT deploy: if: ${{ success() }} name: Deploy RocketMQ needs: [list-version,docker] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: Deploy rocketmq with: action: "deploy" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git" chart-branch: "master" chart-path: "./rocketmq-k8s-helm" job-id: ${{ strategy.job-index }} helm-values: | nameserver: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} broker: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} proxy: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} test-e2e-grpc-java: if: ${{ success() }} name: Test E2E grpc java needs: [list-version, deploy] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e" test-code-branch: "master" test-code-path: java/e2e test-cmd: "mvn -B test" job-id: 0 - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: test-e2e-grpc-java-log.txt path: testlog.txt test-e2e-golang: if: ${{ success() }} name: Test E2E golang needs: [list-version, deploy] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e" test-code-branch: "master" test-code-path: golang test-cmd: | cd ../common && mvn -Prelease -DskipTests clean package -U cd ../rocketmq-admintools && source bin/env.sh LATEST_GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | awk 'NR==1') wget "https://go.dev/dl/${LATEST_GO_VERSION}.linux-amd64.tar.gz" && \ rm -rf /usr/local/go && tar -C /usr/local -xzf ${LATEST_GO_VERSION}.linux-amd64.tar.gz cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m -v job-id: 0 - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: test-e2e-golang-log.txt path: testlog.txt test-e2e-remoting-java: if: ${{ success() }} name: Test E2E remoting java needs: [ list-version, deploy ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e" test-code-branch: "master" test-code-path: java/e2e-v4 test-cmd: "mvn -B test" job-id: 0 - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: test-e2e-remoting-java-log.txt path: testlog.txt clean: if: always() name: Clean needs: [list-version, test-e2e-grpc-java, test-e2e-golang, test-e2e-remoting-java] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: clean with: action: "clean" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" job-id: ${{ strategy.job-index }} ================================================ FILE: .github/workflows/push-ci.yml ================================================ name: PUSH-CI on: push: branches: [master, develop] #schedule: # - cron: "0 18 * * *" # TimeZone: UTC 0 concurrency: group: rocketmq-${{ github.ref }} env: MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 DOCKER_REPO: apache/rocketmq-ci jobs: dist-tar: if: github.repository == 'apache/rocketmq' name: Build dist tar runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: submodules: true - uses: actions/setup-java@v3 with: distribution: "temurin" java-version: "8" cache: "maven" - name: Build distribution tar run: | mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U - uses: actions/upload-artifact@v4 name: Upload distribution tar with: name: rocketmq path: distribution/target/rocketmq*/rocketmq* docker: if: ${{ success() }} name: Docker images needs: [dist-tar] runs-on: ubuntu-latest timeout-minutes: 30 strategy: matrix: base-image: ["ubuntu"] java-version: ["8"] steps: - uses: actions/checkout@v3 with: repository: apache/rocketmq-docker.git ref: master path: rocketmq-docker - uses: actions/download-artifact@v4 name: Download distribution tar with: name: rocketmq path: rocketmq - name: docker-login uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and save docker images id: build-images run: | cd rocketmq-docker/image-build-ci version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen) mkdir versionlist touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`" sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO} - uses: actions/upload-artifact@v4 name: Upload distribution tar with: name: versionlist path: rocketmq-docker/image-build-ci/versionlist/* list-version: if: > github.repository == 'apache/rocketmq' && always() name: List version needs: [docker] runs-on: ubuntu-latest timeout-minutes: 30 outputs: version-json: ${{ steps.show_versions.outputs.version-json }} steps: - uses: actions/download-artifact@v4 name: Download versionlist with: name: versionlist path: versionlist - name: Show versions id: show_versions run: | a=(`ls versionlist`) printf '%s\n' "${a[@]}" | jq -R . | jq -s . echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT deploy-e2e: if: ${{ success() }} name: Deploy RocketMQ For E2E needs: [list-version,docker] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: Deploy rocketmq with: action: "deploy" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git" chart-branch: "master" chart-path: "./rocketmq-k8s-helm" job-id: ${{ strategy.job-index }} helm-values: | nameserver: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} broker: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} proxy: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} deploy-benchmark: if: ${{ success() }} name: Deploy RocketMQ For Benchmarking needs: [list-version,docker] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: Deploy rocketmq with: action: "deploy" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git" chart-branch: "master" chart-path: "./rocketmq-k8s-helm" job-id: "001-${{ strategy.job-index }}" helm-values: | nameserver: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} broker: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} proxy: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} test-e2e-grpc-java: if: ${{ success() }} name: Test E2E grpc java needs: [list-version, deploy-e2e] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e" test-code-branch: "master" test-code-path: java/e2e test-cmd: "mvn -B test" job-id: 0 - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: test-e2e-grpc-java-log.txt path: testlog.txt test-e2e-golang: if: ${{ success() }} name: Test E2E golang needs: [list-version, deploy-e2e] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e" test-code-branch: "master" test-code-path: golang test-cmd: | cd ../common && mvn -Prelease -DskipTests clean package -U cd ../rocketmq-admintools && source bin/env.sh LATEST_GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | awk 'NR==1') wget "https://go.dev/dl/${LATEST_GO_VERSION}.linux-amd64.tar.gz" && \ rm -rf /usr/local/go && tar -C /usr/local -xzf ${LATEST_GO_VERSION}.linux-amd64.tar.gz cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m -v job-id: 0 - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: test-e2e-golang-log.txt path: testlog.txt test-e2e-remoting-java: if: ${{ success() }} name: Test E2E remoting java needs: [ list-version, deploy-e2e ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e" test-code-branch: "master" test-code-path: java/e2e-v4 test-cmd: "mvn -B test" job-id: 0 - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: test-e2e-remoting-java-log.txt path: testlog.txt benchmark-test: if: ${{ success() }} runs-on: ubuntu-latest name: Performance benchmark test needs: [ list-version, deploy-benchmark ] timeout-minutes: 60 steps: - uses: apache/rocketmq-test-tool/benchmark-runner@ce372e5f3906ca1891e4918b05be14608eae608e name: Performance benchmark with: action: "performance-benchmark" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" job-id: "001-${{ strategy.job-index }}" # The time to run the test, 15 minutes test-time: "900" # Some thresholds set in advance min-send-tps-threshold: "12000" max-rt-ms-threshold: "500" avg-rt-ms-threshold: "10" max-2c-rt-ms-threshold: "150" avg-2c-rt-ms-threshold: "10" - name: Upload test report if: always() uses: actions/upload-artifact@v4 with: name: benchmark-report path: benchmark/ clean-e2e: if: always() name: Clean E2E needs: [ list-version, test-e2e-grpc-java, test-e2e-golang, test-e2e-remoting-java ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: clean with: action: "clean" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" job-id: ${{ strategy.job-index }} clean-benchmark: if: always() name: Clean Benchmarking needs: [ list-version, benchmark-test ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288 name: clean with: action: "clean" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" job-id: "001-${{ strategy.job-index }}" ================================================ FILE: .github/workflows/rerun-workflow.yml ================================================ name: Rerun workflow on: workflow_run: workflows: ["Build and Run Tests by Maven" , "Build and Run Tests by Bazel"] types: - completed permissions: actions: write jobs: rerun: if: github.event.workflow_run.conclusion == 'failure' && fromJSON(github.event.workflow_run.run_attempt) < 3 runs-on: ubuntu-latest steps: - name: rerun ${{ github.event.workflow_run.id }} env: GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh run watch ${{ github.event.workflow_run.id }} > /dev/null 2>&1 gh run rerun ${{ github.event.workflow_run.id }} --failed ================================================ FILE: .github/workflows/snapshot-automation.yml ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Snapshot Daily Release Automation on: schedule: # schedule the job to run at 12 a.m. daily - cron: "0 0 * * *" workflow_dispatch: inputs: branch: description: 'The branch to trigger the workflow, The default branch is "develop" when both branch and commit_id are empty' required: false commit_id: description: 'The commit id to trigger the workflow. Do not set branch and commit_id together' required: false rocketmq_version: description: 'Name of the SNAPSHOT version to be generated. The default version is "$VERSION-stable-SNAPSHOT"' required: false env: MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 DOCKER_REPO: apache/rocketmq-ci jobs: dist-tar: if: github.repository == 'apache/rocketmq' name: Build dist tar runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout develop if: github.event.inputs.branch == '' && github.event.inputs.commit_id == '' uses: actions/checkout@v3 with: ref: develop - name: Checkout specific commit if: github.event.inputs.branch == '' && github.event.inputs.commit_id != '' uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.commit_id }} - name: Checkout specific branch if: github.event.inputs.branch != '' && github.event.inputs.commit_id == '' uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.branch }} - uses: actions/setup-java@v4 with: distribution: "corretto" java-version: "8" cache: "maven" - name: Build distribution tar env: MAVEN_SETTINGS: ${{ github.workspace }}/.github/asf-deploy-settings.xml run: | mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U - uses: actions/upload-artifact@v4 name: Upload distribution tar with: name: rocketmq path: distribution/target/rocketmq*/rocketmq* docker-build: if: ${{ success() }} name: Docker images needs: [ dist-tar ] runs-on: ubuntu-latest timeout-minutes: 30 strategy: matrix: base-image: [ "ubuntu" ] java-version: [ "8" ] steps: - uses: actions/checkout@v3 with: repository: apache/rocketmq-docker.git ref: master path: rocketmq-docker - uses: actions/download-artifact@v4 name: Download distribution tar with: name: rocketmq path: rocketmq - name: docker-login uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and save docker images id: build-images run: | cd rocketmq-docker/image-build-ci version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen) mkdir versionlist touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`" sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO} - uses: actions/upload-artifact@v4 name: Upload distribution tar with: name: versionlist path: rocketmq-docker/image-build-ci/versionlist/* list-version: if: > github.repository == 'apache/rocketmq' && always() name: List version needs: [ docker-build ] runs-on: ubuntu-latest timeout-minutes: 30 outputs: version-json: ${{ steps.show_versions.outputs.version-json }} steps: - uses: actions/download-artifact@v4 name: Download versionlist with: name: versionlist path: versionlist - name: Show versions id: show_versions run: | a=(`ls versionlist`) printf '%s\n' "${a[@]}" | jq -R . | jq -s . echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT deploy-rocketmq: if: ${{ success() }} name: Deploy RocketMQ needs: [ list-version,docker-build ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666 name: Deploy rocketmq with: action: "deploy" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" chart-git: "https://github.com/apache/rocketmq-docker.git" chart-branch: "master" chart-path: "./rocketmq-k8s-helm" job-id: ${{ strategy.job-index }} helm-values: | nameserver: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} broker: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} proxy: image: repository: ${{env.DOCKER_REPO}} tag: ${{ matrix.version }} java-grpc-e2e-test: if: ${{ success() }} name: E2E Test needs: [ list-version, deploy-rocketmq ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666 name: e2e test with: action: "test" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" test-code-git: "https://github.com/apache/rocketmq-e2e.git" test-code-branch: "master" test-code-path: java/e2e test-cmd: "mvn -B test" job-id: ${{ strategy.job-index }} - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/test_report/TEST-*.xml' annotate_only: true include_passed: true detailed_summary: true - uses: actions/upload-artifact@v4 if: always() name: Upload test log with: name: testlog.txt path: testlog.txt clean: if: always() name: Clean needs: [ list-version, java-grpc-e2e-test ] runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: version: ${{ fromJSON(needs.list-version.outputs.version-json) }} steps: - uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666 name: clean with: action: "clean" ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}" test-version: "${{ matrix.version }}" job-id: ${{ strategy.job-index }} snapshot: runs-on: ubuntu-latest needs: [ java-grpc-e2e-test ] env: NEXUS_DEPLOY_USERNAME: ${{ secrets.NEXUS_USER }} NEXUS_DEPLOY_PASSWORD: ${{ secrets.NEXUS_PW }} steps: - name: Checkout uses: actions/checkout@v3 with: ref: develop persist-credentials: false - name: Set up JDK uses: actions/setup-java@v4 with: java-version: 8 distribution: "corretto" cache: "maven" - name: Update default pom version if: github.event.inputs.rocketmq_version == '' run: | VERSION=$(mvn -q -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec) VERSION=$(echo $VERSION | awk -F '-SNAPSHOT' '{print $1}') VERSION=$VERSION-stable-SNAPSHOT mvn versions:set -DnewVersion=$VERSION - name: Update User-defined pom version if: github.event.inputs.rocketmq_version != '' run: | mvn versions:set -DnewVersion=${{ github.event.inputs.rocketmq_version }} - name: Deploy to ASF Snapshots Repository timeout-minutes: 40 run: mvn clean deploy -DskipTests=true --settings .github/asf-deploy-settings.xml ================================================ FILE: .github/workflows/stale.yml ================================================ name: Close Stale Issues/PRs permissions: issues: write pull-requests: write on: workflow_dispatch: schedule: - cron: "0 0 * * *" jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v5 with: operations-per-run: 128 days-before-issue-stale: 365 days-before-issue-close: 3 exempt-issue-labels: "no stale" stale-issue-label: "stale" stale-issue-message: "This issue is stale because it has been open for 365 days with no activity. It will be closed in 3 days if no further activity occurs." close-issue-message: "This issue was closed because it has been inactive for 3 days since being marked as stale." remove-issue-stale-when-updated: true days-before-pr-stale: 365 days-before-pr-close: 3 exempt-pr-labels: "no stale" stale-pr-label: "stale" stale-pr-message: "This PR is stale because it has been open for 365 days with no activity. It will be closed in 3 days if no further activity occurs. If you wish not to mark it as stale, please leave a comment in this PR." close-pr-message: "This PR was closed because it has been inactive for 3 days since being marked as stale." remove-pr-stale-when-updated: true ================================================ FILE: .gitignore ================================================ *dependency-reduced-pom.xml .classpath .project .settings/ target/ devenv *.log.* *.iml .idea/ *.versionsBackup !NOTICE-BIN !LICENSE-BIN .DS_Store localbin nohup.out bazel-out bazel-bin bazel-rocketmq bazel-testlogs .vscode MODULE.bazel.lock *.flattened-pom.xml ================================================ FILE: .licenserc.yaml ================================================ # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # header: license: spdx-id: Apache-2.0 copyright-owner: Apache Software Foundation paths-ignore: - '.gitignore' - '.travis.yml' - 'CONTRIBUTING.md' - 'LICENSE' - 'NOTICE' - '**/*.md' - 'BUILDING' - '.github/**' - '*/src/test/resources/certs/*' - 'src/test/**/*.log' - '*/src/test/resources/META-INF/service/*' - '*/src/main/resources/META-INF/service/*' - '*/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json' - '*/src/test/resources/mockito-extensions/*' - '**/target/**' - '**/*.iml' - 'docs/**' - 'localbin/**' - 'distribution/LICENSE-BIN' - 'distribution/NOTICE-BIN' - 'distribution/conf/rmq-proxy.json' - '.bazelversion' - 'common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator' comment: on-failure ================================================ FILE: BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # java_library( name = "test_deps", visibility = ["//visibility:public"], exports = [ "@maven//:junit_junit", "@maven//:org_assertj_assertj_core", "@maven//:org_hamcrest_hamcrest_library", "@maven//:org_mockito_mockito_core", "@maven//:org_powermock_powermock_module_junit4", "@maven//:org_powermock_powermock_api_mockito2", "@maven//:org_hamcrest_hamcrest_core", "@maven//:ch_qos_logback_logback_classic", "@maven//:org_awaitility_awaitility", "@maven//:org_openjdk_jmh_jmh_core", "@maven//:org_openjdk_jmh_jmh_generator_annprocess", "@maven//:org_mockito_mockito_junit_jupiter", ], ) ================================================ FILE: BUILDING ================================================ Build Instructions for Apache RocketMQ ==================================================== (1) Prerequisites JDK 1.8+ is required in order to compile and run RocketMQ. RocketMQ utilizes Maven as a distribution management and packaging tool. Version 3.0.3 or later is required. Maven installation and configuration instructions can be found here: http://maven.apache.org/run-maven/index.html (2) Run test cases Execute the following command in order to compile and run test cases of each components: $ mvn test (3) Import projects to Eclipse IDE First, generate eclipse project files: $ mvn -U eclipse:eclipse Then, import to eclipse by specifying the root directory of the project via: [File] > [Import] > [Existing Projects into Workspace]. (4) Build distribution packages Execute the following command in order to build the tar.gz packages and install JAR into local repository: $ mvn -Prelease-all -DskipTests clean install -U ================================================ FILE: CONTRIBUTING.md ================================================ ## How To Contribute We are always very happy to have contributions, whether for trivial cleanups or big new features. We want to have high quality, well documented codes for each programming language, as well as the surrounding [ecosystem](https://github.com/apache/rocketmq-externals) of integration tools that people use with RocketMQ. Nor is code the only way to contribute to the project. We strongly value documentation, integration with other projects, and gladly accept improvements for these aspects. Recommend reading: * [Contributors Tech Guide](http://www.apache.org/dev/contributors) * [Get involved!](http://www.apache.org/foundation/getinvolved.html) ## Contributing code To submit a change for inclusion, please do the following: #### If the change is non-trivial please include some unit tests that cover the new functionality. #### If you are introducing a completely new feature or API it is a good idea to start a [RIP](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal) and get consensus on the basic design first. #### It is our job to follow up on patches in a timely fashion. Nag us if we aren't doing our job (sometimes we drop things). ### Squash commits If you have a pull request on GitHub, and updated more than once, it's better to squash all commits. 1. Identify how many commits you made since you began: ``git log``. 2. Squash these commits by N: ``git rebase -i HEAD~N`` . 3. Leave "pick" tag in the first line. 4. Change all other commits from "pick" to "fixup". 5. Then do "force push" to overwrite remote history: ``git push -u origin ROCKETMQ-9999 --force`` 6. All your changes are now in a single commit, that would be much better for review. More details of squash can be found at [stackoverflow](https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git). ## Becoming a Committer We are always interested in adding new contributors. What we look for are series of contributions, good taste and ongoing interest in the project. If you are interested in becoming a committer, please let one of the existing committers know and they can help you walk through the process. Nowadays, we have several important contribution points: #### Wiki & JavaDoc #### RocketMQ SDK(C++\.Net\Php\Python\Go\Node.js) #### RocketMQ Connectors ##### Prerequisites If you want to contribute to the above listed points, you must abide by the following prerequisites: ###### Readability - API must have Javadoc, and some very important methods must also have Javadoc ###### Testability - Above 80% unit test coverage for the main process ###### Maintainability - Comply with our [checkstyle spec](style/rmq_checkstyle.xml), and at least a 3-month update frequency ###### Deployability - We encourage you to deploy into [maven repository](http://search.maven.org/) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MODULE.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################### # Bazel now uses Bzlmod by default to manage external dependencies. # Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel. # # For more details, please check https://github.com/bazelbuild/bazel/issues/18958 ############################################################################### ================================================ FILE: NOTICE ================================================ Apache RocketMQ Copyright 2016-2026 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). ================================================ FILE: README.md ================================================ ## Apache RocketMQ [![Build Status][maven-build-image]][maven-build-url] [![CodeCov][codecov-image]][codecov-url] [![Maven Central][maven-central-image]][maven-central-url] [![Release][release-image]][release-url] [![License][license-image]][license-url] [![Average Time to Resolve An Issue][average-time-to-resolve-an-issue-image]][average-time-to-resolve-an-issue-url] [![Percentage of Issues Still Open][percentage-of-issues-still-open-image]][percentage-of-issues-still-open-url] [![Twitter Follow][twitter-follow-image]][twitter-follow-url] **[Apache RocketMQ](https://rocketmq.apache.org) is a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability.** It offers a variety of features: * Messaging patterns including publish/subscribe, request/reply and streaming * Financial grade transactional message * Built-in fault tolerance and high availability configuration options based on [DLedger Controller](docs/en/controller/quick_start.md) * Built-in message tracing capability, also supports opentracing * Versatile big-data and streaming ecosystem integration * Message retroactivity by time or offset * Reliable FIFO and strict ordered messaging in the same queue * Efficient pull and push consumption model * Million-level message accumulation capacity in a single queue * Multiple messaging protocols like gRPC, MQTT, JMS and OpenMessaging * Flexible distributed scale-out deployment architecture * Lightning-fast batch message exchange system * Various message filter mechanics such as SQL and Tag * Docker images for isolated testing and cloud isolated clusters * Feature-rich administrative dashboard for configuration, metrics and monitoring * Authentication and authorization * Free open source connectors, for both sources and sinks * Lightweight real-time computing ---------- ## Quick Start This paragraph guides you through steps of installing RocketMQ in different ways. For local development and testing, only one instance will be created for each component. ### Run RocketMQ locally RocketMQ runs on all major operating systems and requires only a Java JDK version 8 or higher to be installed. To check, run `java -version`: ```shell $ java -version java version "1.8.0_121" ``` For Windows users, click [here](https://dist.apache.org/repos/dist/release/rocketmq/5.4.0/rocketmq-all-5.4.0-bin-release.zip) to download the 5.4.0 RocketMQ binary release, unpack it to your local disk, such as `D:\rocketmq`. For macOS and Linux users, execute following commands: ```shell # Download release from the Apache mirror $ wget https://dist.apache.org/repos/dist/release/rocketmq/5.4.0/rocketmq-all-5.4.0-bin-release.zip # Unpack the release $ unzip rocketmq-all-5.4.0-bin-release.zip ``` Prepare a terminal and change to the extracted `bin` directory: ```shell $ cd rocketmq-all-5.4.0-bin-release/bin ``` **1) Start NameServer** NameServer will be listening at `0.0.0.0:9876`, make sure that the port is not used by others on the local machine, and then do as follows. For macOS and Linux users: ```shell ### start Name Server $ nohup sh mqnamesrv & ### check whether Name Server is successfully started $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` For Windows users, you need to set environment variables first: - From the desktop, right click the Computer icon. - Choose Properties from the context menu. - Click the Advanced system settings link. - Click Environment Variables. - Add Environment `ROCKETMQ_HOME="D:\rocketmq"`. Then change directory to rocketmq, type and run: ```shell $ mqnamesrv.cmd The Name Server boot success... ``` **2) Start Broker** For macOS and Linux users: ```shell ### start Broker $ nohup sh mqbroker -n localhost:9876 & ### check whether Broker is successfully started, eg: Broker's IP is 192.168.1.2, Broker's name is broker-a $ tail -f ~/logs/rocketmqlogs/broker.log The broker[broker-a, 192.168.1.2:10911] boot success... ``` For Windows users: ```shell $ mqbroker.cmd -n localhost:9876 The broker[broker-a, 192.168.1.2:10911] boot success... ``` ### Run RocketMQ in Docker You can run RocketMQ on your own machine within Docker containers, `host` network will be used to expose listening port in the container. **1) Start NameServer** ```shell $ docker run -it --net=host apache/rocketmq ./mqnamesrv ``` **2) Start Broker** ```shell $ docker run -it --net=host --mount type=bind,source=/tmp/store,target=/home/rocketmq/store apache/rocketmq ./mqbroker -n localhost:9876 ``` ### Run RocketMQ in Kubernetes You can also run a RocketMQ cluster within a Kubernetes cluster using [RocketMQ Operator](https://github.com/apache/rocketmq-operator). Before your operations, make sure that `kubectl` and related kubeconfig file installed on your machine. **1) Install CRDs** ```shell ### install CRDs $ git clone https://github.com/apache/rocketmq-operator $ cd rocketmq-operator && make deploy ### check whether CRDs are successfully installed $ kubectl get crd | grep rocketmq.apache.org brokers.rocketmq.apache.org 2022-05-12T09:23:18Z consoles.rocketmq.apache.org 2022-05-12T09:23:19Z nameservices.rocketmq.apache.org 2022-05-12T09:23:18Z topictransfers.rocketmq.apache.org 2022-05-12T09:23:19Z ### check whether operator is running $ kubectl get pods | grep rocketmq-operator rocketmq-operator-6f65c77c49-8hwmj 1/1 Running 0 93s ``` **2) Create Cluster Instance** ```shell ### create RocketMQ cluster resource $ cd example && kubectl create -f rocketmq_v1alpha1_rocketmq_cluster.yaml ### check whether cluster resources are running $ kubectl get sts NAME READY AGE broker-0-master 1/1 107m broker-0-replica-1 1/1 107m name-service 1/1 107m ``` --- ## Apache RocketMQ Community * [RocketMQ Streams](https://github.com/apache/rocketmq-streams): A lightweight stream computing engine based on Apache RocketMQ. * [RocketMQ Flink](https://github.com/apache/rocketmq-flink): The Apache RocketMQ connector of Apache Flink that supports source and sink connector in data stream and Table. * [RocketMQ APIs](https://github.com/apache/rocketmq-apis): RocketMQ protobuf protocol. * [RocketMQ Clients](https://github.com/apache/rocketmq-clients): gRPC/protobuf-based RocketMQ clients. * RocketMQ Remoting-based Clients - [RocketMQ Client CPP](https://github.com/apache/rocketmq-client-cpp) - [RocketMQ Client Go](https://github.com/apache/rocketmq-client-go) - [RocketMQ Client Python](https://github.com/apache/rocketmq-client-python) - [RocketMQ Client Nodejs](https://github.com/apache/rocketmq-client-nodejs) * [RocketMQ Spring](https://github.com/apache/rocketmq-spring): A project which helps developers quickly integrate Apache RocketMQ with Spring Boot. * [RocketMQ Exporter](https://github.com/apache/rocketmq-exporter): An Apache RocketMQ exporter for Prometheus. * [RocketMQ Operator](https://github.com/apache/rocketmq-operator): Providing a way to run an Apache RocketMQ cluster on Kubernetes. * [RocketMQ Docker](https://github.com/apache/rocketmq-docker): The Git repo of the Docker Image for Apache RocketMQ. * [RocketMQ Dashboard](https://github.com/apache/rocketmq-dashboard): Operation and maintenance console of Apache RocketMQ. * [RocketMQ Connect](https://github.com/apache/rocketmq-connect): A tool for scalably and reliably streaming data between Apache RocketMQ and other systems. * [RocketMQ MQTT](https://github.com/apache/rocketmq-mqtt): A new MQTT protocol architecture model, based on which Apache RocketMQ can better support messages from terminals such as IoT devices and Mobile APP. * [RocketMQ EventBridge](https://github.com/apache/rocketmq-eventbridge): EventBridge makes it easier to build an event-driven application. * [RocketMQ Incubating Community Projects](https://github.com/apache/rocketmq-externals): Incubator community projects of Apache RocketMQ, including [logappender](https://github.com/apache/rocketmq-externals/tree/master/logappender), [rocketmq-ansible](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-ansible), [rocketmq-beats-integration](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-beats-integration), [rocketmq-cloudevents-binding](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-cloudevents-binding), etc. * [RocketMQ Site](https://github.com/apache/rocketmq-site): The repository for Apache RocketMQ website. * [RocketMQ E2E](https://github.com/apache/rocketmq-e2e): A project for testing Apache RocketMQ, including end-to-end, performance, compatibility tests. ---------- ## Learn it & Contact us * Mailing Lists: * Home: * Docs: * Issues: * Rips: * Ask: * Slack: ---------- ## Contributing We always welcome new contributions, whether for trivial cleanups, [big new features](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal) or other material rewards, more details see [here](http://rocketmq.apache.org/docs/how-to-contribute/). ---------- ## License [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation ---------- ## Export Control Notice This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. See for more information. The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms. The form and manner of this Apache Software Foundation distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. The following provides more details on the included cryptographic software: This software uses Apache Commons Crypto (https://commons.apache.org/proper/commons-crypto/) to support authentication, and encryption and decryption of data sent across the network between services. [maven-build-image]: https://github.com/apache/rocketmq/actions/workflows/maven.yaml/badge.svg [maven-build-url]: https://github.com/apache/rocketmq/actions/workflows/maven.yaml [codecov-image]: https://codecov.io/gh/apache/rocketmq/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/apache/rocketmq [maven-central-image]: https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-all/badge.svg [maven-central-url]: http://search.maven.org/#search%7Cga%7C1%7Corg.apache.rocketmq [release-image]: https://img.shields.io/badge/release-download-orange.svg [release-url]: https://rocketmq.apache.org/download/ [license-image]: https://img.shields.io/badge/license-Apache%202-4EB1BA.svg [license-url]: https://www.apache.org/licenses/LICENSE-2.0.html [average-time-to-resolve-an-issue-image]: http://isitmaintained.com/badge/resolution/apache/rocketmq.svg [average-time-to-resolve-an-issue-url]: http://isitmaintained.com/project/apache/rocketmq [percentage-of-issues-still-open-image]: http://isitmaintained.com/badge/open/apache/rocketmq.svg [percentage-of-issues-still-open-url]: http://isitmaintained.com/project/apache/rocketmq [twitter-follow-image]: https://img.shields.io/twitter/follow/ApacheRocketMQ?style=social [twitter-follow-url]: https://twitter.com/intent/follow?screen_name=ApacheRocketMQ ================================================ FILE: WORKSPACE ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") RULES_JVM_EXTERNAL_TAG = "4.2" RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca" http_archive( name = "rules_jvm_external", sha256 = RULES_JVM_EXTERNAL_SHA, strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, ) load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") rules_jvm_external_deps() load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") rules_jvm_external_setup() load("@rules_jvm_external//:defs.bzl", "maven_install") maven_install( artifacts = [ "junit:junit:4.13.2", "com.alibaba:fastjson:1.2.76", "com.alibaba.fastjson2:fastjson2:2.0.59", "org.hamcrest:hamcrest-library:1.3", "io.netty:netty-all:4.1.65.Final", "org.assertj:assertj-core:3.22.0", "org.mockito:mockito-core:3.10.0", "org.powermock:powermock-module-junit4:2.0.9", "org.powermock:powermock-api-mockito2:2.0.9", "org.powermock:powermock-core:2.0.9", "com.github.luben:zstd-jni:1.5.2-2", "org.lz4:lz4-java:1.8.0", "commons-validator:commons-validator:1.7", "org.apache.commons:commons-lang3:3.12.0", "org.hamcrest:hamcrest-core:1.3", "io.openmessaging.storage:dledger:0.3.2", "net.java.dev.jna:jna:4.2.2", "ch.qos.logback:logback-classic:1.2.10", "ch.qos.logback:logback-core:1.2.10", "io.opentracing:opentracing-api:0.33.0", "io.opentracing:opentracing-mock:0.33.0", "commons-collections:commons-collections:3.2.2", "org.awaitility:awaitility:4.1.0", "commons-cli:commons-cli:1.5.0", "com.google.guava:guava:31.0.1-jre", "org.yaml:snakeyaml:2.0", "commons-codec:commons-codec:1.13", "commons-io:commons-io:2.7", "com.google.truth:truth:0.30", "org.bouncycastle:bcpkix-jdk15on:1.69", "com.google.code.gson:gson:2.8.9", "com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2", "org.apache.rocketmq:rocketmq-proto:2.1.1", "com.google.protobuf:protobuf-java:3.20.1", "com.google.protobuf:protobuf-java-util:3.20.1", "com.conversantmedia:disruptor:1.2.10", "org.apache.tomcat:annotations-api:6.0.53", "com.google.code.findbugs:jsr305:3.0.2", "org.checkerframework:checker-qual:3.12.0", "org.reflections:reflections:0.9.11", "org.openjdk.jmh:jmh-core:1.19", "org.openjdk.jmh:jmh-generator-annprocess:1.19", "com.github.ben-manes.caffeine:caffeine:2.9.3", "io.grpc:grpc-services:1.47.0", "io.grpc:grpc-netty-shaded:1.47.0", "io.grpc:grpc-context:1.47.0", "io.grpc:grpc-stub:1.47.0", "io.grpc:grpc-api:1.47.0", "io.grpc:grpc-testing:1.47.0", "org.springframework:spring-core:5.3.26", "io.opentelemetry:opentelemetry-exporter-otlp:1.29.0", "io.opentelemetry:opentelemetry-exporter-prometheus:1.29.0-alpha", "io.opentelemetry:opentelemetry-exporter-logging:1.29.0", "io.opentelemetry:opentelemetry-sdk:1.29.0", "io.opentelemetry:opentelemetry-exporter-logging-otlp:1.29.0", "com.squareup.okio:okio-jvm:3.0.0", "io.opentelemetry:opentelemetry-api:1.29.0", "io.opentelemetry:opentelemetry-sdk-metrics:1.29.0", "io.opentelemetry:opentelemetry-sdk-common:1.29.0", "io.github.aliyunmq:rocketmq-slf4j-api:1.0.0", "io.github.aliyunmq:rocketmq-logback-classic:1.0.0", "org.slf4j:jul-to-slf4j:2.0.6", "org.jetbrains:annotations:23.1.0", "io.github.aliyunmq:rocketmq-shaded-slf4j-api-bridge:1.0.0", "software.amazon.awssdk:s3:2.20.29", "com.fasterxml.jackson.core:jackson-databind:2.13.4.2", "com.adobe.testing:s3mock-junit4:2.11.0", "io.github.aliyunmq:rocketmq-grpc-netty-codec-haproxy:1.0.0", "org.apache.rocketmq:rocketmq-rocksdb:1.0.6", "com.alipay.sofa:jraft-core:1.3.14", "com.alipay.sofa:hessian:3.3.6", "io.netty:netty-tcnative-boringssl-static:2.0.48.Final", "org.mockito:mockito-junit-jupiter:4.11.0", "com.alibaba.fastjson2:fastjson2:2.0.59", "org.junit.jupiter:junit-jupiter-api:5.9.1", ], fetch_sources = True, repositories = [ # Private repositories are supported through HTTP Basic auth "https://repo1.maven.org/maven2", ], ) http_archive( name = "io_buildbuddy_buildbuddy_toolchain", sha256 = "b12273608db627eb14051eb75f8a2134590172cd69392086d392e25f3954ea6e", strip_prefix = "buildbuddy-toolchain-8d5d18373adfca9d8e33b4812915abc9b132f1ee", urls = ["https://github.com/buildbuddy-io/buildbuddy-toolchain/archive/8d5d18373adfca9d8e33b4812915abc9b132f1ee.tar.gz"], ) load("@io_buildbuddy_buildbuddy_toolchain//:deps.bzl", "buildbuddy_deps") buildbuddy_deps() load("@io_buildbuddy_buildbuddy_toolchain//:rules.bzl", "buildbuddy") buildbuddy(name = "buildbuddy_toolchain") http_archive( name = "bazel_skylib", sha256 = "51b5105a760b353773f904d2bbc5e664d0987fbaf22265164de65d43e910d8ac", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.8.1/bazel-skylib-1.8.1.tar.gz", "https://github.com/bazelbuild/bazel-skylib/releases/download/1.8.1/bazel-skylib-1.8.1.tar.gz", ], ) load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") bazel_skylib_workspace() http_archive( name = "rules_java", urls = [ "https://github.com/bazelbuild/rules_java/releases/download/7.12.5/rules_java-7.12.5.tar.gz", ], sha256 = "17b18cb4f92ab7b94aa343ce78531b73960b1bed2ba166e5b02c9fdf0b0ac270", ) load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") rules_java_dependencies() rules_java_toolchains() load("@rules_java//toolchains:local_java_repository.bzl", "local_java_repository") local_java_repository( name = "jdk8", version = "8", java_home = "/usr/lib/jvm/java-8-openjdk-amd64", ) ================================================ FILE: auth/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "auth", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "//remoting", "//client", "@maven//:commons_codec_commons_codec", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_collections_commons_collections", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:org_apache_rocketmq_rocketmq_proto", "@maven//:org_slf4j_slf4j_api", "@maven//:com_github_ben_manes_caffeine_caffeine", "@maven//:io_grpc_grpc_api", "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), resources = glob(["src/test/resources/**/*.yml"]), visibility = ["//visibility:public"], deps = [ ":auth", "//:test_deps", "//common", "//remoting", "//client", "@maven//:commons_codec_commons_codec", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_collections_commons_collections", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:org_apache_rocketmq_rocketmq_proto", "@maven//:org_slf4j_slf4j_api", "@maven//:com_github_ben_manes_caffeine_caffeine", "@maven//:io_grpc_grpc_api", "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", ], ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: auth/pom.xml ================================================ 4.0.0 org.apache.rocketmq rocketmq-all ${revision} rocketmq-auth rocketmq-auth ${project.version} ${basedir}/.. ${project.groupId} rocketmq-proto ${project.groupId} rocketmq-client commons-codec commons-codec org.apache.commons commons-lang3 com.google.protobuf protobuf-java-util org.slf4j slf4j-api com.github.ben-manes.caffeine caffeine org.checkerframework checker-qual junit junit maven-surefire-plugin ${maven-surefire-plugin.version} 1 false ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/AuthenticationEvaluator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.strategy.AuthenticationStrategy; import org.apache.rocketmq.auth.config.AuthConfig; public class AuthenticationEvaluator { private final AuthenticationStrategy authenticationStrategy; public AuthenticationEvaluator(AuthConfig authConfig) { this(authConfig, null); } public AuthenticationEvaluator(AuthConfig authConfig, Supplier metadataService) { this.authenticationStrategy = AuthenticationFactory.getStrategy(authConfig, metadataService); } public void evaluate(AuthenticationContext context) { if (context == null) { return; } this.authenticationStrategy.evaluate(context); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/AuthenticationContextBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.builder; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface AuthenticationContextBuilder { AuthenticationContext build(Metadata metadata, GeneratedMessageV3 request); AuthenticationContext build(ChannelHandlerContext context, RemotingCommand request); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.builder; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.CommonConstants; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class DefaultAuthenticationContextBuilder implements AuthenticationContextBuilder { private static final String CREDENTIAL = "Credential"; private static final String SIGNATURE = "Signature"; @Override public DefaultAuthenticationContext build(Metadata metadata, GeneratedMessageV3 request) { try { DefaultAuthenticationContext context = new DefaultAuthenticationContext(); context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID)); context.setRpcCode(request.getDescriptorForType().getFullName()); String authorization = metadata.get(GrpcConstants.AUTHORIZATION); if (StringUtils.isEmpty(authorization)) { return context; } String datetime = metadata.get(GrpcConstants.DATE_TIME); if (StringUtils.isEmpty(datetime)) { throw new AuthenticationException("datetime is null."); } String[] result = authorization.split(CommonConstants.SPACE, 2); if (result.length != 2) { throw new AuthenticationException("authentication header is incorrect."); } String[] keyValues = result[1].split(CommonConstants.COMMA); for (String keyValue : keyValues) { String[] kv = keyValue.trim().split(CommonConstants.EQUAL, 2); int kvLength = kv.length; if (kv.length != 2) { throw new AuthenticationException("authentication keyValues length is incorrect, actual length={}.", kvLength); } String authItem = kv[0]; if (CREDENTIAL.equals(authItem)) { String[] credential = kv[1].split(CommonConstants.SLASH); int credentialActualLength = credential.length; if (credentialActualLength == 0) { throw new AuthenticationException("authentication credential length is incorrect, actual length={}.", credentialActualLength); } context.setUsername(credential[0]); continue; } if (SIGNATURE.equals(authItem)) { context.setSignature(this.hexToBase64(kv[1])); } } context.setContent(datetime.getBytes(StandardCharsets.UTF_8)); return context; } catch (AuthenticationException e) { throw e; } catch (Throwable e) { throw new AuthenticationException("create authentication context error.", e); } } @Override public DefaultAuthenticationContext build(ChannelHandlerContext context, RemotingCommand request) { HashMap fields = request.getExtFields(); DefaultAuthenticationContext result = new DefaultAuthenticationContext(); result.setChannelId(context.channel().id().asLongText()); result.setRpcCode(String.valueOf(request.getCode())); if (MapUtils.isEmpty(fields)) { return result; } if (!fields.containsKey(SessionCredentials.ACCESS_KEY)) { return result; } result.setUsername(fields.get(SessionCredentials.ACCESS_KEY)); result.setSignature(fields.get(SessionCredentials.SIGNATURE)); // Content SortedMap map = new TreeMap<>(); for (Map.Entry entry : fields.entrySet()) { if (request.getVersion() <= MQVersion.Version.V4_9_3.ordinal() && MixAll.UNIQUE_MSG_QUERY_FLAG.equals(entry.getKey())) { continue; } if (!SessionCredentials.SIGNATURE.equals(entry.getKey())) { map.put(entry.getKey(), entry.getValue()); } } result.setContent(AclUtils.combineRequestContent(request, map)); return result; } public String hexToBase64(String input) throws DecoderException { byte[] bytes = Hex.decodeHex(input); return Base64.encodeBase64String(bytes); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/chain/DefaultAuthenticationHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.chain; import java.security.MessageDigest; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.AclSigner; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.auth.authentication.enums.UserStatus; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.chain.Handler; import org.apache.rocketmq.common.chain.HandlerChain; public class DefaultAuthenticationHandler implements Handler> { private final AuthenticationMetadataProvider authenticationMetadataProvider; public DefaultAuthenticationHandler(AuthConfig config, Supplier metadataService) { this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService); } @Override public CompletableFuture handle(DefaultAuthenticationContext context, HandlerChain> chain) { return getUser(context).thenAccept(user -> doAuthenticate(context, user)); } protected CompletableFuture getUser(DefaultAuthenticationContext context) { if (this.authenticationMetadataProvider == null) { throw new AuthenticationException("The authenticationMetadataProvider is not configured"); } if (StringUtils.isEmpty(context.getUsername())) { throw new AuthenticationException("username cannot be null."); } return this.authenticationMetadataProvider.getUser(context.getUsername()); } protected void doAuthenticate(DefaultAuthenticationContext context, User user) { if (user == null) { throw new AuthenticationException("User:{} is not found.", context.getUsername()); } if (user.getUserStatus() == UserStatus.DISABLE) { throw new AuthenticationException("User:{} is disabled.", context.getUsername()); } String signature = AclSigner.calSignature(context.getContent(), user.getPassword()); if (context.getSignature() == null || !MessageDigest.isEqual(signature.getBytes(AclSigner.DEFAULT_CHARSET), context.getSignature().getBytes(AclSigner.DEFAULT_CHARSET))) { throw new AuthenticationException("check signature failed."); } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/context/AuthenticationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.context; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; public abstract class AuthenticationContext { private String channelId; private String rpcCode; private Map extInfo; public String getChannelId() { return channelId; } public void setChannelId(String channelId) { this.channelId = channelId; } public String getRpcCode() { return rpcCode; } public void setRpcCode(String rpcCode) { this.rpcCode = rpcCode; } @SuppressWarnings("unchecked") public T getExtInfo(String key) { if (StringUtils.isBlank(key)) { return null; } if (this.extInfo == null) { return null; } Object value = this.extInfo.get(key); if (value == null) { return null; } return (T) value; } public void setExtInfo(String key, Object value) { if (StringUtils.isBlank(key) || value == null) { return; } if (this.extInfo == null) { this.extInfo = new HashMap<>(); } this.extInfo.put(key, value); } public boolean hasExtInfo(String key) { Object value = getExtInfo(key); return value != null; } public Map getExtInfo() { return extInfo; } public void setExtInfo(Map extInfo) { this.extInfo = extInfo; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/context/DefaultAuthenticationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.context; public class DefaultAuthenticationContext extends AuthenticationContext { private String username; private byte[] content; private String signature; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/SubjectType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.enums; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum SubjectType { USER((byte) 1, "User"); @JSONField(value = true) private final byte code; private final String name; SubjectType(byte code, String name) { this.code = code; this.name = name; } public static SubjectType getByName(String name) { for (SubjectType subjectType : SubjectType.values()) { if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) { return subjectType; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.enums; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum UserStatus { ENABLE((byte) 1, "enable"), DISABLE((byte) 2, "disable"); @JSONField(value = true) private final byte code; private final String name; UserStatus(byte code, String name) { this.code = code; this.name = name; } public static UserStatus getByName(String name) { for (UserStatus subjectType : UserStatus.values()) { if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) { return subjectType; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.enums; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum UserType { SUPER((byte) 1, "Super"), NORMAL((byte) 2, "Normal"); @JSONField(value = true) private final byte code; private final String name; UserType(byte code, String name) { this.code = code; this.name = name; } public static UserType getByName(String name) { for (UserType subjectType : UserType.values()) { if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) { return subjectType; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/exception/AuthenticationException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.exception; import org.slf4j.helpers.MessageFormatter; public class AuthenticationException extends RuntimeException { public AuthenticationException(String message) { super(message); } public AuthenticationException(String message, Throwable cause) { super(message, cause); } public AuthenticationException(String messagePattern, Object... argArray) { super(MessageFormatter.arrayFormat(messagePattern, argArray).getMessage()); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/factory/AuthenticationFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.factory; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.HashMap; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManagerImpl; import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider; import org.apache.rocketmq.auth.authentication.provider.AuthenticationProvider; import org.apache.rocketmq.auth.authentication.provider.DefaultAuthenticationProvider; import org.apache.rocketmq.auth.authentication.strategy.AuthenticationStrategy; import org.apache.rocketmq.auth.authentication.strategy.StatelessAuthenticationStrategy; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class AuthenticationFactory { private static final Map INSTANCE_MAP = new HashMap<>(); private static final String PROVIDER_PREFIX = "PROVIDER_"; private static final String METADATA_PROVIDER_PREFIX = "METADATA_PROVIDER_"; private static final String EVALUATOR_PREFIX = "EVALUATOR_"; @SuppressWarnings("unchecked") public static AuthenticationProvider getProvider(AuthConfig config) { if (config == null) { return null; } return computeIfAbsent(PROVIDER_PREFIX + config.getConfigName(), key -> { try { Class> clazz = DefaultAuthenticationProvider.class; if (StringUtils.isNotBlank(config.getAuthenticationProvider())) { clazz = (Class>) Class.forName(config.getAuthenticationProvider()); } return (AuthenticationProvider) clazz.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to load the authentication provider.", e); } }); } public static AuthenticationMetadataProvider getMetadataProvider(AuthConfig config) { return getMetadataProvider(config, null); } public static AuthenticationMetadataManager getMetadataManager(AuthConfig config) { return new AuthenticationMetadataManagerImpl(config); } @SuppressWarnings("unchecked") public static AuthenticationMetadataProvider getMetadataProvider(AuthConfig config, Supplier metadataService) { if (config == null) { return null; } return computeIfAbsent(METADATA_PROVIDER_PREFIX + config.getConfigName(), key -> { try { if (StringUtils.isBlank(config.getAuthenticationMetadataProvider())) { return null; } Class clazz = (Class) Class.forName(config.getAuthenticationMetadataProvider()); AuthenticationMetadataProvider result = clazz.getDeclaredConstructor().newInstance(); result.initialize(config, metadataService); return result; } catch (Exception e) { throw new RuntimeException("Failed to load the authentication metadata provider", e); } }); } public static AuthenticationEvaluator getEvaluator(AuthConfig config) { return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthenticationEvaluator(config)); } public static AuthenticationEvaluator getEvaluator(AuthConfig config, Supplier metadataService) { return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthenticationEvaluator(config, metadataService)); } @SuppressWarnings("unchecked") public static AuthenticationStrategy getStrategy(AuthConfig config, Supplier metadataService) { try { Class clazz = StatelessAuthenticationStrategy.class; if (StringUtils.isNotBlank(config.getAuthenticationStrategy())) { clazz = (Class) Class.forName(config.getAuthenticationStrategy()); } return clazz.getDeclaredConstructor(AuthConfig.class, Supplier.class).newInstance(config, metadataService); } catch (Exception e) { throw new RuntimeException(e); } } public static AuthenticationContext newContext(AuthConfig config, Metadata metadata, GeneratedMessageV3 request) { AuthenticationProvider authenticationProvider = getProvider(config); if (authenticationProvider == null) { return null; } return authenticationProvider.newContext(metadata, request); } public static AuthenticationContext newContext(AuthConfig config, ChannelHandlerContext context, RemotingCommand command) { AuthenticationProvider authenticationProvider = getProvider(config); if (authenticationProvider == null) { return null; } return authenticationProvider.newContext(context, command); } @SuppressWarnings("unchecked") private static V computeIfAbsent(String key, Function function) { Object result = null; if (INSTANCE_MAP.containsKey(key)) { result = INSTANCE_MAP.get(key); } if (result == null) { synchronized (INSTANCE_MAP) { if (INSTANCE_MAP.containsKey(key)) { result = INSTANCE_MAP.get(key); } if (result == null) { result = function.apply(key); if (result != null) { INSTANCE_MAP.put(key, result); } } } } return result != null ? (V) result : null; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.manager; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.config.AuthConfig; public interface AuthenticationMetadataManager { void shutdown(); void initUser(AuthConfig authConfig); CompletableFuture createUser(User user); CompletableFuture updateUser(User user); CompletableFuture deleteUser(String username); CompletableFuture getUser(String username); CompletableFuture> listUser(String filter); CompletableFuture isSuperUser(String username); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.manager; import com.alibaba.fastjson2.JSON; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.auth.authentication.enums.UserStatus; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.utils.ExceptionUtils; public class AuthenticationMetadataManagerImpl implements AuthenticationMetadataManager { private final AuthenticationMetadataProvider authenticationMetadataProvider; private final AuthorizationMetadataProvider authorizationMetadataProvider; public AuthenticationMetadataManagerImpl(AuthConfig authConfig) { this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(authConfig); this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(authConfig); this.initUser(authConfig); } @Override public void shutdown() { if (this.authenticationMetadataProvider != null) { this.authenticationMetadataProvider.shutdown(); } if (this.authorizationMetadataProvider != null) { this.authorizationMetadataProvider.shutdown(); } } @Override public void initUser(AuthConfig authConfig) { if (authConfig == null) { return; } if (StringUtils.isNotBlank(authConfig.getInitAuthenticationUser())) { try { User initUser = JSON.parseObject(authConfig.getInitAuthenticationUser(), User.class); initUser.setUserType(UserType.SUPER); this.getUser(initUser.getUsername()).thenCompose(user -> { if (user != null) { return CompletableFuture.completedFuture(null); } return this.createUser(initUser); }).join(); } catch (Exception e) { throw new AuthenticationException("Init authentication user error.", e); } } if (StringUtils.isNotBlank(authConfig.getInnerClientAuthenticationCredentials())) { try { SessionCredentials credentials = JSON.parseObject(authConfig.getInnerClientAuthenticationCredentials(), SessionCredentials.class); User innerUser = User.of(credentials.getAccessKey(), credentials.getSecretKey(), UserType.SUPER); this.getUser(innerUser.getUsername()).thenCompose(user -> { if (user != null) { return CompletableFuture.completedFuture(null); } return this.createUser(innerUser); }).join(); } catch (Exception e) { throw new AuthenticationException("Init inner client authentication credentials error", e); } } } @Override public CompletableFuture createUser(User user) { CompletableFuture result = new CompletableFuture<>(); try { this.validate(user, true); if (user.getUserType() == null) { user.setUserType(UserType.NORMAL); } if (user.getUserStatus() == null) { user.setUserStatus(UserStatus.ENABLE); } result = this.getAuthenticationMetadataProvider().getUser(user.getUsername()).thenCompose(old -> { if (old != null) { throw new AuthenticationException("The user is existed"); } return this.getAuthenticationMetadataProvider().createUser(user); }); } catch (Exception e) { this.handleException(e, result); } return result; } @Override public CompletableFuture updateUser(User user) { CompletableFuture result = new CompletableFuture<>(); try { this.validate(user, false); result = this.getAuthenticationMetadataProvider().getUser(user.getUsername()).thenCompose(old -> { if (old == null) { throw new AuthenticationException("The user is not exist"); } if (StringUtils.isNotBlank(user.getPassword())) { old.setPassword(user.getPassword()); } if (user.getUserType() != null) { old.setUserType(user.getUserType()); } if (user.getUserStatus() != null) { old.setUserStatus(user.getUserStatus()); } return this.getAuthenticationMetadataProvider().updateUser(old); }); } catch (Exception e) { this.handleException(e, result); } return result; } @Override public CompletableFuture deleteUser(String username) { CompletableFuture result = new CompletableFuture<>(); try { if (StringUtils.isBlank(username)) { throw new AuthenticationException("username can not be blank"); } CompletableFuture deleteUser = this.getAuthenticationMetadataProvider().deleteUser(username); CompletableFuture deleteAcl = this.getAuthorizationMetadataProvider().deleteAcl(User.of(username)); return CompletableFuture.allOf(deleteUser, deleteAcl); } catch (Exception e) { this.handleException(e, result); } return result; } @Override public CompletableFuture getUser(String username) { CompletableFuture result = new CompletableFuture<>(); try { if (StringUtils.isBlank(username)) { throw new AuthenticationException("username can not be blank"); } result = this.getAuthenticationMetadataProvider().getUser(username); } catch (Exception e) { this.handleException(e, result); } return result; } @Override public CompletableFuture> listUser(String filter) { CompletableFuture> result = new CompletableFuture<>(); try { result = this.getAuthenticationMetadataProvider().listUser(filter); } catch (Exception e) { this.handleException(e, result); } return result; } @Override public CompletableFuture isSuperUser(String username) { return this.getUser(username).thenApply(user -> { if (user == null) { throw new AuthenticationException("User:{} is not found", username); } return user.getUserType() == UserType.SUPER; }); } private void validate(User user, boolean isCreate) { if (user == null) { throw new AuthenticationException("user can not be null"); } if (StringUtils.isBlank(user.getUsername())) { throw new AuthenticationException("username can not be blank"); } if (isCreate && StringUtils.isBlank(user.getPassword())) { throw new AuthenticationException("password can not be blank"); } } private void handleException(Exception e, CompletableFuture result) { Throwable throwable = ExceptionUtils.getRealException(e); result.completeExceptionally(throwable); } private AuthenticationMetadataProvider getAuthenticationMetadataProvider() { if (authenticationMetadataProvider == null) { throw new IllegalStateException("The authenticationMetadataProvider is not configured."); } return authenticationMetadataProvider; } private AuthorizationMetadataProvider getAuthorizationMetadataProvider() { if (authorizationMetadataProvider == null) { throw new IllegalStateException("The authorizationMetadataProvider is not configured."); } return authorizationMetadataProvider; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/model/Subject.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.model; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.enums.SubjectType; import org.apache.rocketmq.common.constant.CommonConstants; public interface Subject { @JSONField(serialize = false) String getSubjectKey(); SubjectType getSubjectType(); default boolean isSubject(SubjectType subjectType) { return subjectType == this.getSubjectType(); } @SuppressWarnings("unchecked") static T of(String subjectKey) { String type = StringUtils.substringBefore(subjectKey, CommonConstants.COLON); SubjectType subjectType = SubjectType.getByName(type); if (subjectType == null) { return null; } if (subjectType == SubjectType.USER) { return (T) User.of(StringUtils.substringAfter(subjectKey, CommonConstants.COLON)); } return null; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/model/User.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.model; import org.apache.rocketmq.auth.authentication.enums.SubjectType; import org.apache.rocketmq.auth.authentication.enums.UserStatus; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.common.constant.CommonConstants; public class User implements Subject { private String username; private String password; private UserType userType; private UserStatus userStatus; public static User of(String username) { User user = new User(); user.setUsername(username); return user; } public static User of(String username, String password) { User user = new User(); user.setUsername(username); user.setPassword(password); return user; } public static User of(String username, String password, UserType userType) { User user = new User(); user.setUsername(username); user.setPassword(password); user.setUserType(userType); return user; } @Override public String getSubjectKey() { return this.getSubjectType().getName() + CommonConstants.COLON + this.username; } @Override public SubjectType getSubjectType() { return SubjectType.USER; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public UserType getUserType() { return userType; } public void setUserType(UserType userType) { this.userType = userType; } public UserStatus getUserStatus() { return userStatus; } public void setUserStatus(UserStatus userStatus) { this.userStatus = userStatus; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationMetadataProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.provider; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.config.AuthConfig; public interface AuthenticationMetadataProvider { void initialize(AuthConfig authConfig, Supplier metadataService); void shutdown(); CompletableFuture createUser(User user); CompletableFuture deleteUser(String username); CompletableFuture updateUser(User user); CompletableFuture getUser(String username); CompletableFuture> listUser(String filter); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.provider; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface AuthenticationProvider { void initialize(AuthConfig config, Supplier metadataService); CompletableFuture authenticate(AuthenticationContext context); AuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request); AuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/DefaultAuthenticationProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.provider; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.builder.AuthenticationContextBuilder; import org.apache.rocketmq.auth.authentication.builder.DefaultAuthenticationContextBuilder; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.auth.authentication.chain.DefaultAuthenticationHandler; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.chain.HandlerChain; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefaultAuthenticationProvider implements AuthenticationProvider { protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME); protected AuthConfig authConfig; protected Supplier metadataService; protected AuthenticationContextBuilder authenticationContextBuilder; @Override public void initialize(AuthConfig config, Supplier metadataService) { this.authConfig = config; this.metadataService = metadataService; this.authenticationContextBuilder = new DefaultAuthenticationContextBuilder(); } @Override public CompletableFuture authenticate(DefaultAuthenticationContext context) { return this.newHandlerChain().handle(context) .whenComplete((nil, ex) -> doAuditLog(context, ex)); } @Override public DefaultAuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request) { return this.authenticationContextBuilder.build(metadata, request); } @Override public DefaultAuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command) { return this.authenticationContextBuilder.build(context, command); } protected HandlerChain> newHandlerChain() { return HandlerChain.>create() .addNext(new DefaultAuthenticationHandler(this.authConfig, metadataService)); } protected void doAuditLog(DefaultAuthenticationContext context, Throwable ex) { if (StringUtils.isBlank(context.getUsername())) { return; } if (ex != null) { log.info("[AUTHENTICATION] User:{} is authenticated failed with Signature = {}.", context.getUsername(), context.getSignature()); } else { log.debug("[AUTHENTICATION] User:{} is authenticated success with Signature = {}.", context.getUsername(), context.getSignature()); } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/LocalAuthenticationMetadataProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.provider; import com.alibaba.fastjson2.JSON; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.config.ConfigRocksDBStorage; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.rocksdb.RocksDB; public class LocalAuthenticationMetadataProvider implements AuthenticationMetadataProvider { private final static String AUTH_METADATA_COLUMN_FAMILY = new String(RocksDB.DEFAULT_COLUMN_FAMILY, StandardCharsets.UTF_8); private ConfigRocksDBStorage storage; private LoadingCache userCache; protected ThreadPoolExecutor cacheRefreshExecutor; @Override public void initialize(AuthConfig authConfig, Supplier metadataService) { this.storage = ConfigRocksDBStorage.getStore(authConfig.getAuthConfigPath() + File.separator + "users", false); if (!this.storage.start()) { throw new RuntimeException("Failed to load rocksdb for auth_user, please check whether it is occupied"); } this.cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor( 1, 1, 1000 * 60, TimeUnit.MILLISECONDS, "UserCacheRefresh", 100000 ); this.userCache = Caffeine.newBuilder() .maximumSize(authConfig.getUserCacheMaxNum()) .expireAfterAccess(authConfig.getUserCacheExpiredSecond(), TimeUnit.SECONDS) .refreshAfterWrite(authConfig.getUserCacheRefreshSecond(), TimeUnit.SECONDS) .executor(cacheRefreshExecutor) .build(new UserCacheLoader(this.storage)); } @Override public CompletableFuture createUser(User user) { try { byte[] keyBytes = user.getUsername().getBytes(StandardCharsets.UTF_8); byte[] valueBytes = JSON.toJSONBytes(user); this.storage.put(AUTH_METADATA_COLUMN_FAMILY, keyBytes, keyBytes.length, valueBytes); this.storage.flushWAL(); this.userCache.invalidate(user.getUsername()); } catch (Exception e) { throw new AuthenticationException("create user to RocksDB failed", e); } return CompletableFuture.completedFuture(null); } @Override public CompletableFuture deleteUser(String username) { try { this.storage.delete(AUTH_METADATA_COLUMN_FAMILY, username.getBytes(StandardCharsets.UTF_8)); this.storage.flushWAL(); this.userCache.invalidate(username); } catch (Exception e) { throw new AuthenticationException("delete user from RocksDB failed", e); } return CompletableFuture.completedFuture(null); } @Override public CompletableFuture updateUser(User user) { try { byte[] keyBytes = user.getUsername().getBytes(StandardCharsets.UTF_8); byte[] valueBytes = JSON.toJSONBytes(user); this.storage.put(AUTH_METADATA_COLUMN_FAMILY, keyBytes, keyBytes.length, valueBytes); this.storage.flushWAL(); this.userCache.invalidate(user.getUsername()); } catch (Exception e) { throw new AuthenticationException("update user to RocksDB failed", e); } return CompletableFuture.completedFuture(null); } @Override public CompletableFuture getUser(String username) { User user = this.userCache.get(username); if (user == UserCacheLoader.EMPTY_USER) { return CompletableFuture.completedFuture(null); } return CompletableFuture.completedFuture(user); } @Override public CompletableFuture> listUser(String filter) { List result = new ArrayList<>(); CompletableFuture> future = new CompletableFuture<>(); try { this.storage.iterate(AUTH_METADATA_COLUMN_FAMILY, (key, value) -> { String username = new String(key, StandardCharsets.UTF_8); if (StringUtils.isNotBlank(filter) && !username.contains(filter)) { return; } User user = JSON.parseObject(new String(value, StandardCharsets.UTF_8), User.class); result.add(user); }); } catch (Exception e) { future.completeExceptionally(e); } future.complete(result); return future; } @Override public void shutdown() { if (this.storage != null) { this.storage.shutdown(); } if (this.cacheRefreshExecutor != null) { this.cacheRefreshExecutor.shutdown(); } } private static class UserCacheLoader implements CacheLoader { private final ConfigRocksDBStorage storage; public static final User EMPTY_USER = new User(); public UserCacheLoader(ConfigRocksDBStorage storage) { this.storage = storage; } @Override public User load(String username) { try { byte[] keyBytes = username.getBytes(StandardCharsets.UTF_8); byte[] valueBytes = storage.get(AUTH_METADATA_COLUMN_FAMILY, keyBytes); if (ArrayUtils.isEmpty(valueBytes)) { return EMPTY_USER; } return JSON.parseObject(new String(valueBytes, StandardCharsets.UTF_8), User.class); } catch (Exception e) { throw new AuthenticationException("Get user from RocksDB failed.", e); } } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.strategy; import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.provider.AuthenticationProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.utils.ExceptionUtils; public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy { protected final AuthConfig authConfig; protected final Set authenticationWhiteSet = new HashSet<>(); protected final AuthenticationProvider authenticationProvider; public AbstractAuthenticationStrategy(AuthConfig authConfig, Supplier metadataService) { this.authConfig = authConfig; this.authenticationProvider = AuthenticationFactory.getProvider(authConfig); if (this.authenticationProvider != null) { this.authenticationProvider.initialize(authConfig, metadataService); } if (StringUtils.isNotBlank(authConfig.getAuthenticationWhitelist())) { String[] whitelist = StringUtils.split(authConfig.getAuthenticationWhitelist(), ","); for (String rpcCode : whitelist) { this.authenticationWhiteSet.add(StringUtils.trim(rpcCode)); } } } protected void doEvaluate(AuthenticationContext context) { if (context == null) { return; } if (!authConfig.isAuthenticationEnabled()) { return; } if (this.authenticationProvider == null) { return; } if (this.authenticationWhiteSet.contains(context.getRpcCode())) { return; } try { this.authenticationProvider.authenticate(context).join(); } catch (AuthenticationException ex) { throw ex; } catch (Throwable ex) { Throwable exception = ExceptionUtils.getRealException(ex); if (exception instanceof AuthenticationException) { throw (AuthenticationException) exception; } throw new AuthenticationException("Authentication failed. Please verify the credentials and try again.", exception); } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AuthenticationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.strategy; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; public interface AuthenticationStrategy { void evaluate(AuthenticationContext context); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.strategy; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.CommonConstants; public class StatefulAuthenticationStrategy extends AbstractAuthenticationStrategy { protected Cache> authCache; public StatefulAuthenticationStrategy(AuthConfig authConfig, Supplier metadataService) { super(authConfig, metadataService); this.authCache = Caffeine.newBuilder() .expireAfterWrite(authConfig.getStatefulAuthenticationCacheExpiredSecond(), TimeUnit.SECONDS) .maximumSize(authConfig.getStatefulAuthenticationCacheMaxNum()) .build(); } @Override public void evaluate(AuthenticationContext context) { if (StringUtils.isBlank(context.getChannelId())) { this.doEvaluate(context); return; } Pair result = this.authCache.get(buildKey(context), key -> { try { this.doEvaluate(context); return Pair.of(true, null); } catch (AuthenticationException ex) { return Pair.of(false, ex); } }); if (result != null && result.getObject1() == Boolean.FALSE) { throw result.getObject2(); } } private String buildKey(AuthenticationContext context) { if (context instanceof DefaultAuthenticationContext) { DefaultAuthenticationContext ctx = (DefaultAuthenticationContext) context; if (StringUtils.isBlank(ctx.getUsername())) { return ctx.getChannelId(); } return ctx.getChannelId() + CommonConstants.POUND + ctx.getUsername(); } throw new AuthenticationException("The request of {} is not support.", context.getClass().getSimpleName()); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatelessAuthenticationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.strategy; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.config.AuthConfig; public class StatelessAuthenticationStrategy extends AbstractAuthenticationStrategy { public StatelessAuthenticationStrategy(AuthConfig authConfig, Supplier metadataService) { super(authConfig, metadataService); } @Override public void evaluate(AuthenticationContext context) { super.doEvaluate(context); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization; import java.util.List; import java.util.function.Supplier; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.strategy.AuthorizationStrategy; import org.apache.rocketmq.auth.config.AuthConfig; public class AuthorizationEvaluator { private final AuthorizationStrategy authorizationStrategy; public AuthorizationEvaluator(AuthConfig authConfig) { this(authConfig, null); } public AuthorizationEvaluator(AuthConfig authConfig, Supplier metadataService) { this.authorizationStrategy = AuthorizationFactory.getStrategy(authConfig, metadataService); } public void evaluate(List contexts) { if (CollectionUtils.isEmpty(contexts)) { return; } contexts.forEach(this.authorizationStrategy::evaluate); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/AuthorizationContextBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.builder; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.List; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface AuthorizationContextBuilder { List build(Metadata metadata, GeneratedMessageV3 message); List build(ChannelHandlerContext context, RemotingCommand command); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.builder; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.Subscription; import apache.rocketmq.v2.SubscriptionEntry; import apache.rocketmq.v2.TelemetryCommand; import apache.rocketmq.v2.SyncLiteSubscriptionRequest; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.constant.CommonConstants; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.resource.ResourcePattern; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.RequestHeaderRegistry; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class DefaultAuthorizationContextBuilder implements AuthorizationContextBuilder { private static final String TOPIC = "topic"; private static final String GROUP = "group"; private static final String A = "a"; private static final String B = "b"; private static final String CONSUMER_GROUP = "consumerGroup"; private final AuthConfig authConfig; private static final EnumSet CONSUMER_CLIENT_TYPES = EnumSet.of(ClientType.PUSH_CONSUMER, ClientType.SIMPLE_CONSUMER, ClientType.PULL_CONSUMER); private final RequestHeaderRegistry requestHeaderRegistry; public DefaultAuthorizationContextBuilder(AuthConfig authConfig) { this.authConfig = authConfig; this.requestHeaderRegistry = RequestHeaderRegistry.getInstance(); } @Override public List build(Metadata metadata, GeneratedMessageV3 message) { List result = null; if (message instanceof SendMessageRequest) { SendMessageRequest request = (SendMessageRequest) message; if (request.getMessagesCount() <= 0) { throw new AuthorizationException("message is null."); } result = newPubContext(metadata, request.getMessages(0).getTopic()); } if (message instanceof RecallMessageRequest) { RecallMessageRequest request = (RecallMessageRequest) message; result = newPubContext(metadata, request.getTopic()); } if (message instanceof EndTransactionRequest) { EndTransactionRequest request = (EndTransactionRequest) message; result = newPubContext(metadata, request.getTopic()); } if (message instanceof HeartbeatRequest) { HeartbeatRequest request = (HeartbeatRequest) message; if (!isConsumerClientType(request.getClientType())) { return null; } result = newGroupSubContexts(metadata, request.getGroup()); } if (message instanceof ReceiveMessageRequest) { ReceiveMessageRequest request = (ReceiveMessageRequest) message; if (!request.hasMessageQueue()) { throw new AuthorizationException("messageQueue is null."); } result = newSubContexts(metadata, request.getGroup(), request.getMessageQueue().getTopic()); } if (message instanceof SyncLiteSubscriptionRequest) { SyncLiteSubscriptionRequest request = (SyncLiteSubscriptionRequest) message; if (request.getLiteTopicSetCount() <= 0) { return null; } result = newSubContexts(metadata, request.getGroup(), request.getTopic()); } if (message instanceof AckMessageRequest) { AckMessageRequest request = (AckMessageRequest) message; result = newSubContexts(metadata, request.getGroup(), request.getTopic()); } if (message instanceof ForwardMessageToDeadLetterQueueRequest) { ForwardMessageToDeadLetterQueueRequest request = (ForwardMessageToDeadLetterQueueRequest) message; result = newSubContexts(metadata, request.getGroup(), request.getTopic()); } if (message instanceof NotifyClientTerminationRequest) { NotifyClientTerminationRequest request = (NotifyClientTerminationRequest) message; if (StringUtils.isNotBlank(request.getGroup().getName())) { result = newGroupSubContexts(metadata, request.getGroup()); } } if (message instanceof ChangeInvisibleDurationRequest) { ChangeInvisibleDurationRequest request = (ChangeInvisibleDurationRequest) message; result = newGroupSubContexts(metadata, request.getGroup()); } if (message instanceof QueryRouteRequest) { QueryRouteRequest request = (QueryRouteRequest) message; result = newContext(metadata, request); } if (message instanceof QueryAssignmentRequest) { QueryAssignmentRequest request = (QueryAssignmentRequest) message; result = newSubContexts(metadata, request.getGroup(), request.getTopic()); } if (message instanceof TelemetryCommand) { TelemetryCommand request = (TelemetryCommand) message; result = newContext(metadata, request); } if (CollectionUtils.isNotEmpty(result)) { result.forEach(context -> { context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID)); context.setRpcCode(message.getDescriptorForType().getFullName()); }); } return result; } @Override public List build(ChannelHandlerContext context, RemotingCommand command) { List result = new ArrayList<>(); try { HashMap fields = command.getExtFields(); if (MapUtils.isEmpty(fields)) { return result; } Subject subject = null; if (fields.containsKey(SessionCredentials.ACCESS_KEY)) { subject = User.of(fields.get(SessionCredentials.ACCESS_KEY)); } String remoteAddr = RemotingHelper.parseChannelRemoteAddr(context.channel()); String sourceIp = StringUtils.substringBeforeLast(remoteAddr, CommonConstants.COLON); Resource topic; Resource group; switch (command.getCode()) { case RequestCode.GET_ROUTEINFO_BY_TOPIC: if (NamespaceUtil.isRetryTopic(fields.get(TOPIC))) { group = Resource.ofGroup(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp)); } else { topic = Resource.ofTopic(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.PUB, Action.SUB, Action.GET), sourceIp)); } break; case RequestCode.SEND_MESSAGE: if (NamespaceUtil.isRetryTopic(fields.get(TOPIC))) { group = Resource.ofGroup(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); } else { topic = Resource.ofTopic(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp)); } break; case RequestCode.SEND_MESSAGE_V2: case RequestCode.SEND_BATCH_MESSAGE: if (NamespaceUtil.isRetryTopic(fields.get(B))) { group = Resource.ofGroup(fields.get(B)); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); } else { topic = Resource.ofTopic(fields.get(B)); result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp)); } break; case RequestCode.RECALL_MESSAGE: topic = Resource.ofTopic(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp)); break; case RequestCode.END_TRANSACTION: if (StringUtils.isNotBlank(fields.get(TOPIC))) { topic = Resource.ofTopic(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp)); } break; case RequestCode.CONSUMER_SEND_MSG_BACK: group = Resource.ofGroup(fields.get(GROUP)); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); break; case RequestCode.PULL_MESSAGE: if (!NamespaceUtil.isRetryTopic(fields.get(TOPIC))) { topic = Resource.ofTopic(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp)); } group = Resource.ofGroup(fields.get(CONSUMER_GROUP)); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); break; case RequestCode.QUERY_MESSAGE: topic = Resource.ofTopic(fields.get(TOPIC)); result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp)); break; case RequestCode.HEART_BEAT: HeartbeatData heartbeatData = HeartbeatData.decode(command.getBody(), HeartbeatData.class); for (ConsumerData data : heartbeatData.getConsumerDataSet()) { group = Resource.ofGroup(data.getGroupName()); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); for (SubscriptionData subscriptionData : data.getSubscriptionDataSet()) { if (NamespaceUtil.isRetryTopic(subscriptionData.getTopic())) { continue; } topic = Resource.ofTopic(subscriptionData.getTopic()); result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp)); } } break; case RequestCode.UNREGISTER_CLIENT: final UnregisterClientRequestHeader unregisterClientRequestHeader = command.decodeCommandCustomHeader(UnregisterClientRequestHeader.class); if (StringUtils.isNotBlank(unregisterClientRequestHeader.getConsumerGroup())) { group = Resource.ofGroup(unregisterClientRequestHeader.getConsumerGroup()); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); } break; case RequestCode.GET_CONSUMER_LIST_BY_GROUP: final GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = command.decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class); group = Resource.ofGroup(getConsumerListByGroupRequestHeader.getConsumerGroup()); result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp)); break; case RequestCode.QUERY_CONSUMER_OFFSET: final QueryConsumerOffsetRequestHeader queryConsumerOffsetRequestHeader = command.decodeCommandCustomHeader(QueryConsumerOffsetRequestHeader.class); if (!NamespaceUtil.isRetryTopic(queryConsumerOffsetRequestHeader.getTopic())) { topic = Resource.ofTopic(queryConsumerOffsetRequestHeader.getTopic()); result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp)); } group = Resource.ofGroup(queryConsumerOffsetRequestHeader.getConsumerGroup()); result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp)); break; case RequestCode.UPDATE_CONSUMER_OFFSET: final UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = command.decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class); if (!NamespaceUtil.isRetryTopic(updateConsumerOffsetRequestHeader.getTopic())) { topic = Resource.ofTopic(updateConsumerOffsetRequestHeader.getTopic()); result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.UPDATE), sourceIp)); } group = Resource.ofGroup(updateConsumerOffsetRequestHeader.getConsumerGroup()); result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.UPDATE), sourceIp)); break; case RequestCode.LOCK_BATCH_MQ: LockBatchRequestBody lockBatchRequestBody = LockBatchRequestBody.decode(command.getBody(), LockBatchRequestBody.class); group = Resource.ofGroup(lockBatchRequestBody.getConsumerGroup()); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); if (CollectionUtils.isNotEmpty(lockBatchRequestBody.getMqSet())) { for (MessageQueue messageQueue : lockBatchRequestBody.getMqSet()) { if (NamespaceUtil.isRetryTopic(messageQueue.getTopic())) { continue; } topic = Resource.ofTopic(messageQueue.getTopic()); result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp)); } } break; case RequestCode.UNLOCK_BATCH_MQ: UnlockBatchRequestBody unlockBatchRequestBody = UnlockBatchRequestBody.decode(command.getBody(), UnlockBatchRequestBody.class); group = Resource.ofGroup(unlockBatchRequestBody.getConsumerGroup()); result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp)); if (CollectionUtils.isNotEmpty(unlockBatchRequestBody.getMqSet())) { for (MessageQueue messageQueue : unlockBatchRequestBody.getMqSet()) { if (NamespaceUtil.isRetryTopic(messageQueue.getTopic())) { continue; } topic = Resource.ofTopic(messageQueue.getTopic()); result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp)); } } break; default: result = buildContextByAnnotation(subject, command, sourceIp); break; } if (CollectionUtils.isNotEmpty(result)) { result.forEach(r -> { r.setChannelId(context.channel().id().asLongText()); r.setRpcCode(String.valueOf(command.getCode())); }); } } catch (AuthorizationException ex) { throw ex; } catch (Throwable t) { throw new AuthorizationException("parse authorization context error.", t); } return result; } private List buildContextByAnnotation(Subject subject, RemotingCommand request, String sourceIp) throws Exception { List result = new ArrayList<>(); Class clazz = this.requestHeaderRegistry.getRequestHeader(request.getCode()); if (clazz == null) { return result; } CommandCustomHeader header = request.decodeCommandCustomHeader(clazz); RocketMQAction rocketMQAction = clazz.getAnnotation(RocketMQAction.class); ResourceType resourceType = rocketMQAction.resource(); Action[] actions = rocketMQAction.action(); Resource resource = null; if (resourceType == ResourceType.CLUSTER) { resource = Resource.ofCluster(authConfig.getClusterName()); } Field[] fields = clazz.getDeclaredFields(); if (ArrayUtils.isNotEmpty(fields)) { for (Field field : fields) { RocketMQResource rocketMQResource = field.getAnnotation(RocketMQResource.class); if (rocketMQResource == null) { continue; } field.setAccessible(true); try { resourceType = rocketMQResource.value(); String splitter = rocketMQResource.splitter(); Object value = field.get(header); if (value == null) { continue; } String[] resourceValues; if (StringUtils.isNotBlank(splitter)) { resourceValues = StringUtils.split(value.toString(), splitter); } else { resourceValues = new String[] {value.toString()}; } for (String resourceValue : resourceValues) { if (resourceType == ResourceType.TOPIC && NamespaceUtil.isRetryTopic(resourceValue)) { resource = Resource.ofGroup(resourceValue); result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp)); } else { resource = Resource.of(resourceType, resourceValue, ResourcePattern.LITERAL); result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp)); } } } finally { field.setAccessible(false); } } } if (CollectionUtils.isEmpty(result) && resource != null) { result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp)); } return result; } private List newContext(Metadata metadata, QueryRouteRequest request) { apache.rocketmq.v2.Resource topic = request.getTopic(); if (StringUtils.isBlank(topic.getName())) { throw new AuthorizationException("topic is null."); } Subject subject = null; if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) { subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK)); } Resource resource = Resource.ofTopic(topic.getName()); String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON); DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, Arrays.asList(Action.PUB, Action.SUB), sourceIp); return Collections.singletonList(context); } private static List newContext(Metadata metadata, TelemetryCommand request) { if (request.getCommandCase() != TelemetryCommand.CommandCase.SETTINGS) { return null; } if (!request.getSettings().hasPublishing() && !request.getSettings().hasSubscription()) { throw new AclException("settings command doesn't have publishing or subscription."); } List result = new ArrayList<>(); if (request.getSettings().hasPublishing()) { List topicList = request.getSettings().getPublishing().getTopicsList(); for (apache.rocketmq.v2.Resource topic : topicList) { result.addAll(newPubContext(metadata, topic)); } } if (request.getSettings().hasSubscription()) { Subscription subscription = request.getSettings().getSubscription(); result.addAll(newSubContexts(metadata, ResourceType.GROUP, subscription.getGroup())); for (SubscriptionEntry entry : subscription.getSubscriptionsList()) { result.addAll(newSubContexts(metadata, ResourceType.TOPIC, entry.getTopic())); } } return result; } private boolean isConsumerClientType(ClientType clientType) { return CONSUMER_CLIENT_TYPES.contains(clientType); } private static List newPubContext(Metadata metadata, apache.rocketmq.v2.Resource topic) { if (topic == null || StringUtils.isBlank(topic.getName())) { throw new AuthorizationException("topic is null."); } Subject subject = null; if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) { subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK)); } Resource resource = Resource.ofTopic(topic.getName()); String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON); DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, Action.PUB, sourceIp); return Collections.singletonList(context); } private List newSubContexts(Metadata metadata, apache.rocketmq.v2.Resource group, apache.rocketmq.v2.Resource topic) { List result = new ArrayList<>(); result.addAll(newGroupSubContexts(metadata, group)); result.addAll(newTopicSubContexts(metadata, topic)); return result; } private static List newTopicSubContexts(Metadata metadata, apache.rocketmq.v2.Resource resource) { return newSubContexts(metadata, ResourceType.TOPIC, resource); } private static List newGroupSubContexts(Metadata metadata, apache.rocketmq.v2.Resource resource) { return newSubContexts(metadata, ResourceType.GROUP, resource); } private static List newSubContexts(Metadata metadata, ResourceType resourceType, apache.rocketmq.v2.Resource resource) { if (resourceType == ResourceType.GROUP) { if (resource == null || StringUtils.isBlank(resource.getName())) { throw new AuthorizationException("group is null."); } return newSubContexts(metadata, Resource.ofGroup(resource.getName())); } if (resourceType == ResourceType.TOPIC) { if (resource == null || StringUtils.isBlank(resource.getName())) { throw new AuthorizationException("topic is null."); } return newSubContexts(metadata, Resource.ofTopic(resource.getName())); } throw new AuthorizationException("unknown resource type."); } private static List newSubContexts(Metadata metadata, Resource resource) { List result = new ArrayList<>(); Subject subject = null; if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) { subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK)); } String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON); result.add(DefaultAuthorizationContext.of(subject, resource, Action.SUB, sourceIp)); return result; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/AclAuthorizationHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.chain; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Environment; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.chain.Handler; import org.apache.rocketmq.common.chain.HandlerChain; import org.apache.rocketmq.common.resource.ResourcePattern; import org.apache.rocketmq.common.resource.ResourceType; public class AclAuthorizationHandler implements Handler> { private final AuthorizationMetadataProvider authorizationMetadataProvider; public AclAuthorizationHandler(AuthConfig config) { this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(config); } public AclAuthorizationHandler(AuthConfig config, Supplier metadataService) { this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(config, metadataService); } @Override public CompletableFuture handle(DefaultAuthorizationContext context, HandlerChain> chain) { if (this.authorizationMetadataProvider == null) { throw new AuthorizationException("The authorizationMetadataProvider is not configured"); } return this.authorizationMetadataProvider.getAcl(context.getSubject()).thenAccept(acl -> { if (acl == null) { throwException(context, "no matched policies."); } // 1. get the defined acl entries which match the request. PolicyEntry matchedEntry = matchPolicyEntries(context, acl); // 2. if no matched acl entries, return deny if (matchedEntry == null) { throwException(context, "no matched policies."); } // 3. judge is the entries has denied decision. if (matchedEntry.getDecision() == Decision.DENY) { throwException(context, "the decision is deny."); } }); } private PolicyEntry matchPolicyEntries(DefaultAuthorizationContext context, Acl acl) { List policyEntries = new ArrayList<>(); Policy policy = acl.getPolicy(PolicyType.CUSTOM); if (policy != null) { List entries = matchPolicyEntries(context, policy.getEntries()); if (CollectionUtils.isNotEmpty(entries)) { policyEntries.addAll(entries); } } if (CollectionUtils.isEmpty(policyEntries)) { policy = acl.getPolicy(PolicyType.DEFAULT); if (policy != null) { List entries = matchPolicyEntries(context, policy.getEntries()); if (CollectionUtils.isNotEmpty(entries)) { policyEntries.addAll(entries); } } } if (CollectionUtils.isEmpty(policyEntries)) { return null; } policyEntries.sort(this::comparePolicyEntries); return policyEntries.get(0); } private List matchPolicyEntries(DefaultAuthorizationContext context, List entries) { if (CollectionUtils.isEmpty(entries)) { return null; } return entries.stream() .filter(entry -> entry.isMatchResource(context.getResource())) .filter(entry -> entry.isMatchAction(context.getActions())) .filter(entry -> entry.isMatchEnvironment(Environment.of(context.getSourceIp()))) .collect(Collectors.toList()); } private int comparePolicyEntries(PolicyEntry o1, PolicyEntry o2) { int compare = 0; Resource r1 = o1.getResource(); Resource r2 = o2.getResource(); if (r1.getResourceType() != r2.getResourceType()) { if (r1.getResourceType() == ResourceType.ANY) { compare = 1; } if (r2.getResourceType() == ResourceType.ANY) { compare = -1; } } else if (r1.getResourcePattern() == r2.getResourcePattern()) { if (r1.getResourcePattern() == ResourcePattern.PREFIXED) { String n1 = r1.getResourceName(); String n2 = r2.getResourceName(); compare = -1 * Integer.compare(n1.length(), n2.length()); } } else { if (r1.getResourcePattern() == ResourcePattern.LITERAL) { compare = -1; } else if (r2.getResourcePattern() == ResourcePattern.LITERAL) { compare = 1; } else if (r1.getResourcePattern() == ResourcePattern.PREFIXED) { compare = -1; } else if (r2.getResourcePattern() == ResourcePattern.PREFIXED) { compare = 1; } } if (compare != 0) { return compare; } // the decision deny has higher priority Decision d1 = o1.getDecision(); Decision d2 = o2.getDecision(); if (d1 != d2) { return d1 == Decision.DENY ? -1 : 1; } return 0; } private static void throwException(DefaultAuthorizationContext context, String detail) { throw new AuthorizationException("{} has no permission to access {} from {}, " + detail, context.getSubject().getSubjectKey(), context.getResource().getResourceKey(), context.getSourceIp()); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/UserAuthorizationHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.chain; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.enums.SubjectType; import org.apache.rocketmq.auth.authentication.enums.UserStatus; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.chain.Handler; import org.apache.rocketmq.common.chain.HandlerChain; public class UserAuthorizationHandler implements Handler> { private final AuthenticationMetadataProvider authenticationMetadataProvider; public UserAuthorizationHandler(AuthConfig config, Supplier metadataService) { this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService); } @Override public CompletableFuture handle(DefaultAuthorizationContext context, HandlerChain> chain) { if (!context.getSubject().isSubject(SubjectType.USER)) { return chain.handle(context); } return this.getUser(context.getSubject()).thenCompose(user -> { if (user.getUserType() == UserType.SUPER) { return CompletableFuture.completedFuture(null); } return chain.handle(context); }); } private CompletableFuture getUser(Subject subject) { if (this.authenticationMetadataProvider == null) { throw new AuthorizationException("The authenticationMetadataProvider is not configured"); } User user = (User) subject; return authenticationMetadataProvider.getUser(user.getUsername()).thenApply(result -> { if (result == null) { throw new AuthorizationException("User:{} not found.", user.getUsername()); } if (result.getUserStatus() == UserStatus.DISABLE) { throw new AuthorizationException("User:{} is disabled.", result.getUsername()); } return result; }); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/context/AuthorizationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.context; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; public abstract class AuthorizationContext { private String channelId; private String rpcCode; private Map extInfo; @SuppressWarnings("unchecked") public T getExtInfo(String key) { if (StringUtils.isBlank(key)) { return null; } if (this.extInfo == null) { return null; } Object value = this.extInfo.get(key); if (value == null) { return null; } return (T) value; } public void setExtInfo(String key, Object value) { if (StringUtils.isBlank(key) || value == null) { return; } if (this.extInfo == null) { this.extInfo = new HashMap<>(); } this.extInfo.put(key, value); } public boolean hasExtInfo(String key) { Object value = getExtInfo(key); return value != null; } public String getChannelId() { return channelId; } public void setChannelId(String channelId) { this.channelId = channelId; } public String getRpcCode() { return rpcCode; } public void setRpcCode(String rpcCode) { this.rpcCode = rpcCode; } public Map getExtInfo() { return extInfo; } public void setExtInfo(Map extInfo) { this.extInfo = extInfo; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/context/DefaultAuthorizationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.context; import java.util.Collections; import java.util.List; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.common.action.Action; public class DefaultAuthorizationContext extends AuthorizationContext { private Subject subject; private Resource resource; private List actions; private String sourceIp; public static DefaultAuthorizationContext of(Subject subject, Resource resource, Action action, String sourceIp) { DefaultAuthorizationContext context = new DefaultAuthorizationContext(); context.setSubject(subject); context.setResource(resource); context.setActions(Collections.singletonList(action)); context.setSourceIp(sourceIp); return context; } public static DefaultAuthorizationContext of(Subject subject, Resource resource, List actions, String sourceIp) { DefaultAuthorizationContext context = new DefaultAuthorizationContext(); context.setSubject(subject); context.setResource(resource); context.setActions(actions); context.setSourceIp(sourceIp); return context; } public String getSubjectKey() { return this.subject != null ? this.subject.getSubjectKey() : null; } public String getResourceKey() { return this.resource != null ? this.resource.getResourceKey() : null; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } public Resource getResource() { return resource; } public void setResource(Resource resource) { this.resource = resource; } public List getActions() { return actions; } public void setActions(List actions) { this.actions = actions; } public String getSourceIp() { return sourceIp; } public void setSourceIp(String sourceIp) { this.sourceIp = sourceIp; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/Decision.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.enums; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum Decision { ALLOW((byte) 1, "Allow"), DENY((byte) 2, "Deny"); @JSONField(value = true) private final byte code; private final String name; Decision(byte code, String name) { this.code = code; this.name = name; } public static Decision getByName(String name) { for (Decision decision : Decision.values()) { if (StringUtils.equalsIgnoreCase(decision.getName(), name)) { return decision; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/PolicyType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.enums; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum PolicyType { CUSTOM((byte) 1, "Custom"), DEFAULT((byte) 2, "Default"); @JSONField(value = true) private final byte code; private final String name; PolicyType(byte code, String name) { this.code = code; this.name = name; } public static PolicyType getByName(String name) { for (PolicyType policyType : PolicyType.values()) { if (StringUtils.equalsIgnoreCase(policyType.getName(), name)) { return policyType; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/exception/AuthorizationException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.exception; import org.slf4j.helpers.MessageFormatter; public class AuthorizationException extends RuntimeException { public AuthorizationException(String message) { super(message); } public AuthorizationException(String message, Throwable cause) { super(message, cause); } public AuthorizationException(String messagePattern, Object... argArray) { super(MessageFormatter.arrayFormat(messagePattern, argArray).getMessage()); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/factory/AuthorizationFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.factory; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManagerImpl; import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider; import org.apache.rocketmq.auth.authorization.provider.AuthorizationProvider; import org.apache.rocketmq.auth.authorization.provider.DefaultAuthorizationProvider; import org.apache.rocketmq.auth.authorization.strategy.AuthorizationStrategy; import org.apache.rocketmq.auth.authorization.strategy.StatelessAuthorizationStrategy; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class AuthorizationFactory { private static final Map INSTANCE_MAP = new HashMap<>(); private static final String PROVIDER_PREFIX = "PROVIDER_"; private static final String METADATA_PROVIDER_PREFIX = "METADATA_PROVIDER_"; private static final String EVALUATOR_PREFIX = "EVALUATOR_"; @SuppressWarnings("unchecked") public static AuthorizationProvider getProvider(AuthConfig config) { if (config == null) { return null; } return computeIfAbsent(PROVIDER_PREFIX + config.getConfigName(), key -> { try { Class> clazz = DefaultAuthorizationProvider.class; if (StringUtils.isNotBlank(config.getAuthorizationProvider())) { clazz = (Class>) Class.forName(config.getAuthorizationProvider()); } return (AuthorizationProvider) clazz .getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to load the authorization provider.", e); } }); } public static AuthorizationMetadataProvider getMetadataProvider(AuthConfig config) { return getMetadataProvider(config, null); } public static AuthorizationMetadataManager getMetadataManager(AuthConfig config) { return new AuthorizationMetadataManagerImpl(config); } @SuppressWarnings("unchecked") public static AuthorizationMetadataProvider getMetadataProvider(AuthConfig config, Supplier metadataService) { if (config == null) { return null; } return computeIfAbsent(METADATA_PROVIDER_PREFIX + config.getConfigName(), key -> { try { if (StringUtils.isBlank(config.getAuthorizationMetadataProvider())) { return null; } Class clazz = (Class) Class.forName(config.getAuthorizationMetadataProvider()); AuthorizationMetadataProvider result = clazz.getDeclaredConstructor().newInstance(); result.initialize(config, metadataService); return result; } catch (Exception e) { throw new RuntimeException("Failed to load the authorization metadata provider.", e); } }); } public static AuthorizationEvaluator getEvaluator(AuthConfig config) { return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthorizationEvaluator(config)); } public static AuthorizationEvaluator getEvaluator(AuthConfig config, Supplier metadataService) { return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthorizationEvaluator(config, metadataService)); } @SuppressWarnings("unchecked") public static AuthorizationStrategy getStrategy(AuthConfig config, Supplier metadataService) { try { Class clazz = StatelessAuthorizationStrategy.class; if (StringUtils.isNotBlank(config.getAuthorizationStrategy())) { clazz = (Class) Class.forName(config.getAuthorizationStrategy()); } return clazz.getDeclaredConstructor(AuthConfig.class, Supplier.class).newInstance(config, metadataService); } catch (Exception e) { throw new RuntimeException(e); } } public static List newContexts(AuthConfig config, Metadata metadata, GeneratedMessageV3 message) { AuthorizationProvider authorizationProvider = getProvider(config); if (authorizationProvider == null) { return null; } return authorizationProvider.newContexts(metadata, message); } public static List newContexts(AuthConfig config, ChannelHandlerContext context, RemotingCommand command) { AuthorizationProvider authorizationProvider = getProvider(config); if (authorizationProvider == null) { return null; } return authorizationProvider.newContexts(context, command); } @SuppressWarnings("unchecked") private static V computeIfAbsent(String key, Function function) { Object result = null; if (INSTANCE_MAP.containsKey(key)) { result = INSTANCE_MAP.get(key); } if (result == null) { synchronized (INSTANCE_MAP) { if (INSTANCE_MAP.containsKey(key)) { result = INSTANCE_MAP.get(key); } if (result == null) { result = function.apply(key); if (result != null) { INSTANCE_MAP.put(key, result); } } } } return result != null ? (V) result : null; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.manager; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Resource; public interface AuthorizationMetadataManager { void shutdown(); CompletableFuture createAcl(Acl acl); CompletableFuture updateAcl(Acl acl); CompletableFuture deleteAcl(Subject subject); CompletableFuture deleteAcl(Subject subject, PolicyType policyType, Resource resource); CompletableFuture getAcl(Subject subject); CompletableFuture> listAcl(String subjectFilter, String resourceFilter); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.manager; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.enums.SubjectType; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Environment; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.common.utils.IPAddressUtils; public class AuthorizationMetadataManagerImpl implements AuthorizationMetadataManager { private final AuthorizationMetadataProvider authorizationMetadataProvider; private final AuthenticationMetadataProvider authenticationMetadataProvider; public AuthorizationMetadataManagerImpl(AuthConfig authConfig) { this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(authConfig); this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(authConfig); } @Override public void shutdown() { if (this.authenticationMetadataProvider != null) { this.authenticationMetadataProvider.shutdown(); } if (this.authorizationMetadataProvider != null) { this.authorizationMetadataProvider.shutdown(); } } @Override public CompletableFuture createAcl(Acl acl) { try { validate(acl); initAcl(acl); CompletableFuture subjectFuture; if (acl.getSubject().isSubject(SubjectType.USER)) { User user = (User) acl.getSubject(); subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername()); } else { subjectFuture = CompletableFuture.completedFuture(acl.getSubject()); } return subjectFuture.thenCompose(subject -> { if (subject == null) { throw new AuthorizationException("The subject of {} is not exist.", acl.getSubject().getSubjectKey()); } return this.getAuthorizationMetadataProvider().getAcl(acl.getSubject()); }).thenCompose(oldAcl -> { if (oldAcl == null) { return this.getAuthorizationMetadataProvider().createAcl(acl); } oldAcl.updatePolicy(acl.getPolicies()); return this.getAuthorizationMetadataProvider().updateAcl(oldAcl); }); } catch (Exception e) { return this.handleException(e); } } @Override public CompletableFuture updateAcl(Acl acl) { try { validate(acl); initAcl(acl); CompletableFuture subjectFuture; if (acl.getSubject().isSubject(SubjectType.USER)) { User user = (User) acl.getSubject(); subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername()); } else { subjectFuture = CompletableFuture.completedFuture(acl.getSubject()); } return subjectFuture.thenCompose(subject -> { if (subject == null) { throw new AuthorizationException("The subject of {} is not exist.", acl.getSubject().getSubjectKey()); } return this.getAuthorizationMetadataProvider().getAcl(acl.getSubject()); }).thenCompose(oldAcl -> { if (oldAcl == null) { return this.getAuthorizationMetadataProvider().createAcl(acl); } oldAcl.updatePolicy(acl.getPolicies()); return this.getAuthorizationMetadataProvider().updateAcl(oldAcl); }); } catch (Exception e) { return this.handleException(e); } } @Override public CompletableFuture deleteAcl(Subject subject) { return this.deleteAcl(subject, null, null); } @Override public CompletableFuture deleteAcl(Subject subject, PolicyType policyType, Resource resource) { try { if (subject == null) { throw new AuthorizationException("The subject is null."); } if (policyType == null) { policyType = PolicyType.CUSTOM; } CompletableFuture subjectFuture; if (subject.isSubject(SubjectType.USER)) { User user = (User) subject; subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername()); } else { subjectFuture = CompletableFuture.completedFuture(subject); } CompletableFuture aclFuture = this.getAuthorizationMetadataProvider().getAcl(subject); PolicyType finalPolicyType = policyType; return subjectFuture.thenCombine(aclFuture, (sub, oldAcl) -> { if (sub == null) { throw new AuthorizationException("The subject is not exist."); } if (oldAcl == null) { throw new AuthorizationException("The acl is not exist."); } return oldAcl; }).thenCompose(oldAcl -> { if (resource != null) { oldAcl.deletePolicy(finalPolicyType, resource); } if (resource == null || CollectionUtils.isEmpty(oldAcl.getPolicies())) { return this.getAuthorizationMetadataProvider().deleteAcl(subject); } return this.getAuthorizationMetadataProvider().updateAcl(oldAcl); }); } catch (Exception e) { return this.handleException(e); } } @Override public CompletableFuture getAcl(Subject subject) { try { if (subject == null) { throw new AuthorizationException("The subject is null."); } CompletableFuture subjectFuture; if (subject.isSubject(SubjectType.USER)) { User user = (User) subject; subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername()); } else { subjectFuture = CompletableFuture.completedFuture(subject); } return subjectFuture.thenCompose(sub -> { if (sub == null) { throw new AuthorizationException("The subject is not exist."); } return this.getAuthorizationMetadataProvider().getAcl(sub); }); } catch (Exception e) { return this.handleException(e); } } @Override public CompletableFuture> listAcl(String subjectFilter, String resourceFilter) { return this.getAuthorizationMetadataProvider().listAcl(subjectFilter, resourceFilter); } private static void initAcl(Acl acl) { acl.getPolicies().forEach(policy -> { if (policy.getPolicyType() == null) { policy.setPolicyType(PolicyType.CUSTOM); } }); } private void validate(Acl acl) { Subject subject = acl.getSubject(); if (subject.getSubjectType() == null) { throw new AuthorizationException("The subject type is null."); } List policies = acl.getPolicies(); if (CollectionUtils.isEmpty(policies)) { throw new AuthorizationException("The policies is empty."); } for (Policy policy : policies) { this.validate(policy); } } private void validate(Policy policy) { List policyEntries = policy.getEntries(); if (CollectionUtils.isEmpty(policyEntries)) { throw new AuthorizationException("The policy entries is empty."); } for (PolicyEntry policyEntry : policyEntries) { this.validate(policyEntry); } } private void validate(PolicyEntry entry) { Resource resource = entry.getResource(); if (resource == null) { throw new AuthorizationException("The resource is null."); } if (resource.getResourceType() == null) { throw new AuthorizationException("The resource type is null."); } if (resource.getResourcePattern() == null) { throw new AuthorizationException("The resource pattern is null."); } if (CollectionUtils.isEmpty(entry.getActions())) { throw new AuthorizationException("The actions is empty."); } if (entry.getActions().contains(Action.ANY)) { throw new AuthorizationException("The actions can not be Any."); } Environment environment = entry.getEnvironment(); if (environment != null && CollectionUtils.isNotEmpty(environment.getSourceIps())) { for (String sourceIp : environment.getSourceIps()) { if (StringUtils.isBlank(sourceIp)) { throw new AuthorizationException("The source ip is empty."); } if (!IPAddressUtils.isValidIPOrCidr(sourceIp)) { throw new AuthorizationException("The source ip is invalid."); } } } if (entry.getDecision() == null) { throw new AuthorizationException("The decision is null or illegal."); } } private CompletableFuture handleException(Exception e) { CompletableFuture result = new CompletableFuture<>(); Throwable throwable = ExceptionUtils.getRealException(e); result.completeExceptionally(throwable); return result; } private AuthenticationMetadataProvider getAuthenticationMetadataProvider() { if (authenticationMetadataProvider == null) { throw new IllegalStateException("The authenticationMetadataProvider is not configured."); } return authenticationMetadataProvider; } private AuthorizationMetadataProvider getAuthorizationMetadataProvider() { if (authorizationMetadataProvider == null) { throw new IllegalStateException("The authorizationMetadataProvider is not configured."); } return authorizationMetadataProvider; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Acl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.common.action.Action; public class Acl { private Subject subject; private List policies; public static Acl of(Subject subject, Policy policy) { return of(subject, Lists.newArrayList(policy)); } public static Acl of(Subject subject, List policies) { Acl acl = new Acl(); acl.setSubject(subject); acl.setPolicies(policies); return acl; } public static Acl of(Subject subject, List resources, List actions, Environment environment, Decision decision) { Acl acl = new Acl(); acl.setSubject(subject); Policy policy = Policy.of(resources, actions, environment, decision); acl.setPolicies(Lists.newArrayList(policy)); return acl; } public void updatePolicy(Policy policy) { this.updatePolicy(Lists.newArrayList(policy)); } public void updatePolicy(List policies) { if (this.policies == null) { this.policies = new ArrayList<>(); } policies.forEach(newPolicy -> { Policy oldPolicy = this.getPolicy(newPolicy.getPolicyType()); if (oldPolicy == null) { this.policies.add(newPolicy); } else { oldPolicy.updateEntry(newPolicy.getEntries()); } }); } public void deletePolicy(PolicyType policyType, Resource resource) { Policy policy = getPolicy(policyType); if (policy == null) { return; } policy.deleteEntry(resource); if (CollectionUtils.isEmpty(policy.getEntries())) { this.policies.remove(policy); } } public Policy getPolicy(PolicyType policyType) { if (CollectionUtils.isEmpty(this.policies)) { return null; } for (Policy policy : this.policies) { if (policy.getPolicyType() == policyType) { return policy; } } return null; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } public List getPolicies() { return policies; } public void setPolicies(List policies) { this.policies = policies; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Environment.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import java.util.Collections; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.utils.IPAddressUtils; public class Environment { private List sourceIps; public static Environment of(String sourceIp) { if (StringUtils.isEmpty(sourceIp)) { return null; } return of(Collections.singletonList(sourceIp)); } public static Environment of(List sourceIps) { if (CollectionUtils.isEmpty(sourceIps)) { return null; } Environment environment = new Environment(); environment.setSourceIps(sourceIps); return environment; } public boolean isMatch(Environment environment) { if (CollectionUtils.isEmpty(this.sourceIps)) { return true; } if (CollectionUtils.isEmpty(environment.getSourceIps())) { return false; } String targetIp = environment.getSourceIps().get(0); for (String sourceIp : this.sourceIps) { if (IPAddressUtils.isIPInRange(targetIp, sourceIp)) { return true; } } return false; } public List getSourceIps() { return sourceIps; } public void setSourceIps(List sourceIps) { this.sourceIps = sourceIps; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Policy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; public class Policy { private PolicyType policyType; private List entries; public static Policy of(List resources, List actions, Environment environment, Decision decision) { return of(PolicyType.CUSTOM, resources, actions, environment, decision); } public static Policy of(PolicyType policyType, List resources, List actions, Environment environment, Decision decision) { Policy policy = new Policy(); policy.setPolicyType(policyType); List entries = resources.stream() .map(resource -> PolicyEntry.of(resource, actions, environment, decision)) .collect(Collectors.toList()); policy.setEntries(entries); return policy; } public static Policy of(PolicyType type, List entries) { Policy policy = new Policy(); policy.setPolicyType(type); policy.setEntries(entries); return policy; } public void updateEntry(List newEntries) { if (this.entries == null) { this.entries = new ArrayList<>(); } newEntries.forEach(newEntry -> { PolicyEntry entry = getEntry(newEntry.getResource()); if (entry == null) { this.entries.add(newEntry); } else { entry.updateEntry(newEntry.getActions(), newEntry.getEnvironment(), newEntry.getDecision()); } }); } public void deleteEntry(Resource resources) { PolicyEntry entry = getEntry(resources); if (entry != null) { this.entries.remove(entry); } } private PolicyEntry getEntry(Resource resource) { if (CollectionUtils.isEmpty(this.entries)) { return null; } for (PolicyEntry entry : this.entries) { if (Objects.equals(entry.getResource(), resource)) { return entry; } } return null; } public PolicyType getPolicyType() { return policyType; } public void setPolicyType(PolicyType policyType) { this.policyType = policyType; } public List getEntries() { return entries; } public void setEntries(List entries) { this.entries = entries; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/model/PolicyEntry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.auth.authorization.enums.Decision; public class PolicyEntry { private Resource resource; private List actions; private Environment environment; private Decision decision; public static PolicyEntry of(Resource resource, List actions, Environment environment, Decision decision) { PolicyEntry policyEntry = new PolicyEntry(); policyEntry.setResource(resource); policyEntry.setActions(actions); policyEntry.setEnvironment(environment); policyEntry.setDecision(decision); return policyEntry; } public void updateEntry(List actions, Environment environment, Decision decision) { this.setActions(actions); this.setEnvironment(environment); this.setDecision(decision); } public boolean isMatchResource(Resource resource) { return this.resource.isMatch(resource); } public boolean isMatchAction(List actions) { if (CollectionUtils.isEmpty(this.actions)) { return false; } if (actions.contains(Action.ANY)) { return true; } return actions.stream() .anyMatch(action -> this.actions.contains(action) || this.actions.contains(Action.ALL)); } public boolean isMatchEnvironment(Environment environment) { if (this.environment == null) { return true; } return this.environment.isMatch(environment); } public String toResourceStr() { if (resource == null) { return null; } return resource.getResourceKey(); } public List toActionsStr() { if (CollectionUtils.isEmpty(actions)) { return null; } return actions.stream().map(Action::getName) .collect(Collectors.toList()); } public Resource getResource() { return resource; } public void setResource(Resource resource) { this.resource = resource; } public List getActions() { return actions; } public void setActions(List actions) { this.actions = actions; } public Environment getEnvironment() { return environment; } public void setEnvironment(Environment environment) { this.environment = environment; } public Decision getDecision() { return decision; } public void setDecision(Decision decision) { this.decision = decision; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/model/RequestContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.common.action.Action; public class RequestContext { private Subject subject; private Resource resource; private Action action; private String sourceIp; public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } public Resource getResource() { return resource; } public void setResource(Resource resource) { this.resource = resource; } public Action getAction() { return action; } public void setAction(Action action) { this.action = action; } public String getSourceIp() { return sourceIp; } public void setSourceIp(String sourceIp) { this.sourceIp = sourceIp; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Resource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import com.alibaba.fastjson2.annotation.JSONField; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.ResourcePattern; import org.apache.rocketmq.common.constant.CommonConstants; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class Resource { private ResourceType resourceType; private String resourceName; private ResourcePattern resourcePattern; public static Resource ofCluster(String clusterName) { return of(ResourceType.CLUSTER, clusterName, ResourcePattern.LITERAL); } public static Resource ofTopic(String topicName) { return of(ResourceType.TOPIC, topicName, ResourcePattern.LITERAL); } public static Resource ofGroup(String groupName) { if (NamespaceUtil.isRetryTopic(groupName)) { groupName = NamespaceUtil.withOutRetryAndDLQ(groupName); } return of(ResourceType.GROUP, groupName, ResourcePattern.LITERAL); } public static Resource of(ResourceType resourceType, String resourceName, ResourcePattern resourcePattern) { Resource resource = new Resource(); resource.resourceType = resourceType; resource.resourceName = resourceName; resource.resourcePattern = resourcePattern; return resource; } public static List of(List resourceKeys) { if (CollectionUtils.isEmpty(resourceKeys)) { return null; } return resourceKeys.stream().map(Resource::of).collect(Collectors.toList()); } public static Resource of(String resourceKey) { if (StringUtils.isBlank(resourceKey)) { return null; } if (StringUtils.equals(resourceKey, CommonConstants.ASTERISK)) { return of(ResourceType.ANY, null, ResourcePattern.ANY); } String type = StringUtils.substringBefore(resourceKey, CommonConstants.COLON); ResourceType resourceType = ResourceType.getByName(type); if (resourceType == null) { return null; } String resourceName = StringUtils.substringAfter(resourceKey, CommonConstants.COLON); ResourcePattern resourcePattern = ResourcePattern.LITERAL; if (StringUtils.equals(resourceName, CommonConstants.ASTERISK)) { resourceName = null; resourcePattern = ResourcePattern.ANY; } else if (StringUtils.endsWith(resourceName, CommonConstants.ASTERISK)) { resourceName = StringUtils.substringBefore(resourceName, CommonConstants.ASTERISK); resourcePattern = ResourcePattern.PREFIXED; } return of(resourceType, resourceName, resourcePattern); } @JSONField(serialize = false) public String getResourceKey() { if (resourceType == ResourceType.ANY) { return CommonConstants.ASTERISK; } switch (resourcePattern) { case ANY: return resourceType.getName() + CommonConstants.COLON + CommonConstants.ASTERISK; case LITERAL: return resourceType.getName() + CommonConstants.COLON + resourceName; case PREFIXED: return resourceType.getName() + CommonConstants.COLON + resourceName + CommonConstants.ASTERISK; default: return null; } } public boolean isMatch(Resource resource) { if (this.resourceType == ResourceType.ANY) { return true; } if (this.resourceType != resource.resourceType) { return false; } switch (resourcePattern) { case ANY: return true; case LITERAL: return StringUtils.equals(resource.resourceName, this.resourceName); case PREFIXED: return StringUtils.startsWith(resource.resourceName, this.resourceName); default: return false; } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Resource resource = (Resource) o; return resourceType == resource.resourceType && Objects.equals(resourceName, resource.resourceName) && resourcePattern == resource.resourcePattern; } @Override public int hashCode() { return Objects.hash(resourceType, resourceName, resourcePattern); } public ResourceType getResourceType() { return resourceType; } public void setResourceType(ResourceType resourceType) { this.resourceType = resourceType; } public String getResourceName() { return resourceName; } public void setResourceName(String resourceName) { this.resourceName = resourceName; } public ResourcePattern getResourcePattern() { return resourcePattern; } public void setResourcePattern(ResourcePattern resourcePattern) { this.resourcePattern = resourcePattern; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationMetadataProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.provider; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.config.AuthConfig; public interface AuthorizationMetadataProvider { void initialize(AuthConfig authConfig, Supplier metadataService); void shutdown(); CompletableFuture createAcl(Acl acl); CompletableFuture deleteAcl(Subject subject); CompletableFuture updateAcl(Acl acl); CompletableFuture getAcl(Subject subject); CompletableFuture> listAcl(String subjectFilter, String resourceFilter); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.provider; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface AuthorizationProvider { void initialize(AuthConfig config); void initialize(AuthConfig config, Supplier metadataService); CompletableFuture authorize(AuthorizationContext context); List newContexts(Metadata metadata, GeneratedMessageV3 message); List newContexts(ChannelHandlerContext context, RemotingCommand command); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/DefaultAuthorizationProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.provider; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.ChannelHandlerContext; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.rocketmq.auth.authorization.builder.AuthorizationContextBuilder; import org.apache.rocketmq.auth.authorization.builder.DefaultAuthorizationContextBuilder; import org.apache.rocketmq.auth.authorization.chain.AclAuthorizationHandler; import org.apache.rocketmq.auth.authorization.chain.UserAuthorizationHandler; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.chain.HandlerChain; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefaultAuthorizationProvider implements AuthorizationProvider { protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME); protected AuthConfig authConfig; protected Supplier metadataService; protected AuthorizationContextBuilder authorizationContextBuilder; @Override public void initialize(AuthConfig config) { this.initialize(config, null); } @Override public void initialize(AuthConfig config, Supplier metadataService) { this.authConfig = config; this.metadataService = metadataService; this.authorizationContextBuilder = new DefaultAuthorizationContextBuilder(config); } @Override public CompletableFuture authorize(DefaultAuthorizationContext context) { return this.newHandlerChain().handle(context) .whenComplete((nil, ex) -> doAuditLog(context, ex)); } @Override public List newContexts(Metadata metadata, GeneratedMessageV3 message) { return this.authorizationContextBuilder.build(metadata, message); } @Override public List newContexts(ChannelHandlerContext context, RemotingCommand command) { return this.authorizationContextBuilder.build(context, command); } protected HandlerChain> newHandlerChain() { return HandlerChain.>create() .addNext(new UserAuthorizationHandler(authConfig, metadataService)) .addNext(new AclAuthorizationHandler(authConfig, metadataService)); } protected void doAuditLog(DefaultAuthorizationContext context, Throwable ex) { if (context.getSubject() == null) { return; } Decision decision = Decision.ALLOW; if (ex != null) { decision = Decision.DENY; } String subject = context.getSubject().getSubjectKey(); String actions = context.getActions().stream().map(Action::getName) .collect(Collectors.joining(",")); String sourceIp = context.getSourceIp(); String resource = context.getResource().getResourceKey(); String request = context.getRpcCode(); String format = "[AUTHORIZATION] Subject = {} is {} Action = {} from sourceIp = {} on resource = {} for request = {}."; if (decision == Decision.ALLOW) { log.debug(format, subject, decision.getName(), actions, sourceIp, resource, request); } else { log.info(format, subject, decision.getName(), actions, sourceIp, resource, request); } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/LocalAuthorizationMetadataProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.provider; import com.alibaba.fastjson2.JSON; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.config.ConfigRocksDBStorage; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.rocksdb.RocksDB; public class LocalAuthorizationMetadataProvider implements AuthorizationMetadataProvider { private final static String AUTH_METADATA_COLUMN_FAMILY = new String(RocksDB.DEFAULT_COLUMN_FAMILY, StandardCharsets.UTF_8); private ConfigRocksDBStorage storage; private LoadingCache aclCache; protected ThreadPoolExecutor cacheRefreshExecutor; @Override public void initialize(AuthConfig authConfig, Supplier metadataService) { this.storage = ConfigRocksDBStorage.getStore(authConfig.getAuthConfigPath() + File.separator + "acls", false); if (!this.storage.start()) { throw new RuntimeException("Failed to load rocksdb for auth_acl, please check whether it is occupied."); } this.cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor( 1, 1, 1000 * 60, TimeUnit.MILLISECONDS, "AclCacheRefresh", 100000 ); this.aclCache = Caffeine.newBuilder() .maximumSize(authConfig.getAclCacheMaxNum()) .expireAfterAccess(authConfig.getAclCacheExpiredSecond(), TimeUnit.SECONDS) .refreshAfterWrite(authConfig.getAclCacheRefreshSecond(), TimeUnit.SECONDS) .executor(cacheRefreshExecutor) .build(new AclCacheLoader(this.storage)); } @Override public CompletableFuture createAcl(Acl acl) { try { Subject subject = acl.getSubject(); byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8); byte[] valueBytes = JSON.toJSONBytes(acl); this.storage.put(AUTH_METADATA_COLUMN_FAMILY, keyBytes, keyBytes.length, valueBytes); this.storage.flushWAL(); this.aclCache.invalidate(subject.getSubjectKey()); } catch (Exception e) { throw new AuthorizationException("create Acl to RocksDB failed.", e); } return CompletableFuture.completedFuture(null); } @Override public CompletableFuture deleteAcl(Subject subject) { try { byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8); this.storage.delete(AUTH_METADATA_COLUMN_FAMILY, keyBytes); this.storage.flushWAL(); this.aclCache.invalidate(subject.getSubjectKey()); } catch (Exception e) { throw new AuthorizationException("delete Acl from RocksDB failed.", e); } return CompletableFuture.completedFuture(null); } @Override public CompletableFuture updateAcl(Acl acl) { try { Subject subject = acl.getSubject(); byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8); byte[] valueBytes = JSON.toJSONBytes(acl); this.storage.put(AUTH_METADATA_COLUMN_FAMILY, keyBytes, keyBytes.length, valueBytes); this.storage.flushWAL(); this.aclCache.invalidate(subject.getSubjectKey()); } catch (Exception e) { throw new AuthorizationException("update Acl to RocksDB failed.", e); } return CompletableFuture.completedFuture(null); } @Override public CompletableFuture getAcl(Subject subject) { Acl acl = aclCache.get(subject.getSubjectKey()); if (acl == AclCacheLoader.EMPTY_ACL) { return CompletableFuture.completedFuture(null); } return CompletableFuture.completedFuture(acl); } @Override public CompletableFuture> listAcl(String subjectFilter, String resourceFilter) { List result = new ArrayList<>(); CompletableFuture> future = new CompletableFuture<>(); try { this.storage.iterate(AUTH_METADATA_COLUMN_FAMILY, (key, value) -> { String subjectKey = new String(key, StandardCharsets.UTF_8); if (StringUtils.isNotBlank(subjectFilter) && !subjectKey.contains(subjectFilter)) { return; } Subject subject = Subject.of(subjectKey); Acl acl = JSON.parseObject(new String(value, StandardCharsets.UTF_8), Acl.class); List policies = acl.getPolicies(); if (!CollectionUtils.isNotEmpty(policies)) { return; } Iterator policyIterator = policies.iterator(); while (policyIterator.hasNext()) { Policy policy = policyIterator.next(); List entries = policy.getEntries(); if (CollectionUtils.isEmpty(entries)) { continue; } if (StringUtils.isNotBlank(resourceFilter)) { entries.removeIf(entry -> !entry.toResourceStr().contains(resourceFilter)); } if (CollectionUtils.isEmpty(entries)) { policyIterator.remove(); } } if (CollectionUtils.isNotEmpty(policies)) { result.add(Acl.of(subject, policies)); } }); } catch (Exception e) { future.completeExceptionally(e); } future.complete(result); return future; } @Override public void shutdown() { if (this.storage != null) { this.storage.shutdown(); } if (this.cacheRefreshExecutor != null) { this.cacheRefreshExecutor.shutdown(); } } private static class AclCacheLoader implements CacheLoader { private final ConfigRocksDBStorage storage; public static final Acl EMPTY_ACL = new Acl(); public AclCacheLoader(ConfigRocksDBStorage storage) { this.storage = storage; } @Override public Acl load(String subjectKey) { try { byte[] keyBytes = subjectKey.getBytes(StandardCharsets.UTF_8); Subject subject = Subject.of(subjectKey); byte[] valueBytes = this.storage.get(AUTH_METADATA_COLUMN_FAMILY, keyBytes); if (ArrayUtils.isEmpty(valueBytes)) { return EMPTY_ACL; } Acl acl = JSON.parseObject(valueBytes, Acl.class); return Acl.of(subject, acl.getPolicies()); } catch (Exception e) { throw new AuthorizationException("get Acl from RocksDB failed.", e); } } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AbstractAuthorizationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.strategy; import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.provider.AuthorizationProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.utils.ExceptionUtils; public abstract class AbstractAuthorizationStrategy implements AuthorizationStrategy { protected final AuthConfig authConfig; protected final Set authorizationWhiteSet = new HashSet<>(); protected final AuthorizationProvider authorizationProvider; public AbstractAuthorizationStrategy(AuthConfig authConfig, Supplier metadataService) { this.authConfig = authConfig; this.authorizationProvider = AuthorizationFactory.getProvider(authConfig); if (this.authorizationProvider != null) { this.authorizationProvider.initialize(authConfig, metadataService); } if (StringUtils.isNotBlank(authConfig.getAuthorizationWhitelist())) { String[] whitelist = StringUtils.split(authConfig.getAuthorizationWhitelist(), ","); for (String rpcCode : whitelist) { this.authorizationWhiteSet.add(StringUtils.trim(rpcCode)); } } } public void doEvaluate(AuthorizationContext context) { if (context == null) { return; } if (!this.authConfig.isAuthorizationEnabled()) { return; } if (this.authorizationProvider == null) { return; } if (this.authorizationWhiteSet.contains(context.getRpcCode())) { return; } try { this.authorizationProvider.authorize(context).join(); } catch (AuthorizationException ex) { throw ex; } catch (Throwable ex) { Throwable exception = ExceptionUtils.getRealException(ex); if (exception instanceof AuthorizationException) { throw (AuthorizationException) exception; } throw new AuthorizationException("Authorization failed. Please verify your access rights and try again.", exception); } } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AuthorizationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.strategy; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; public interface AuthorizationStrategy { void evaluate(AuthorizationContext context); } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatefulAuthorizationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.strategy; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.CommonConstants; public class StatefulAuthorizationStrategy extends AbstractAuthorizationStrategy { protected Cache> authCache; public StatefulAuthorizationStrategy(AuthConfig authConfig, Supplier metadataService) { super(authConfig, metadataService); this.authCache = Caffeine.newBuilder() .expireAfterWrite(authConfig.getStatefulAuthorizationCacheExpiredSecond(), TimeUnit.SECONDS) .maximumSize(authConfig.getStatefulAuthorizationCacheMaxNum()) .build(); } @Override public void evaluate(AuthorizationContext context) { if (StringUtils.isBlank(context.getChannelId())) { this.doEvaluate(context); return; } Pair result = this.authCache.get(buildKey(context), key -> { try { this.doEvaluate(context); return Pair.of(true, null); } catch (AuthorizationException ex) { return Pair.of(false, ex); } }); if (result != null && result.getObject1() == Boolean.FALSE) { throw result.getObject2(); } } private String buildKey(AuthorizationContext context) { if (context instanceof DefaultAuthorizationContext) { DefaultAuthorizationContext ctx = (DefaultAuthorizationContext) context; return ctx.getChannelId() + (ctx.getSubject() != null ? CommonConstants.POUND + ctx.getSubjectKey() : "") + CommonConstants.POUND + ctx.getResourceKey() + CommonConstants.POUND + StringUtils.join(ctx.getActions(), CommonConstants.COMMA) + CommonConstants.POUND + ctx.getSourceIp(); } throw new AuthorizationException("The request of {} is not support.", context.getClass().getSimpleName()); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatelessAuthorizationStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.strategy; import java.util.function.Supplier; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.config.AuthConfig; public class StatelessAuthorizationStrategy extends AbstractAuthorizationStrategy { public StatelessAuthorizationStrategy(AuthConfig authConfig, Supplier metadataService) { super(authConfig, metadataService); } @Override public void evaluate(AuthorizationContext context) { super.doEvaluate(context); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.config; public class AuthConfig implements Cloneable { private String configName; private String clusterName; private String authConfigPath; private boolean authenticationEnabled = false; private String authenticationProvider; private String authenticationMetadataProvider; private String authenticationStrategy; private String authenticationWhitelist; private String initAuthenticationUser; private String innerClientAuthenticationCredentials; private boolean authorizationEnabled = false; private String authorizationProvider; private String authorizationMetadataProvider; private String authorizationStrategy; private String authorizationWhitelist; private boolean migrateAuthFromV1Enabled = false; private int userCacheMaxNum = 1000; private int userCacheExpiredSecond = 600; private int userCacheRefreshSecond = 60; private int aclCacheMaxNum = 1000; private int aclCacheExpiredSecond = 600; private int aclCacheRefreshSecond = 60; private int statefulAuthenticationCacheMaxNum = 10000; private int statefulAuthenticationCacheExpiredSecond = 60; private int statefulAuthorizationCacheMaxNum = 10000; private int statefulAuthorizationCacheExpiredSecond = 60; @Override public AuthConfig clone() { try { return (AuthConfig) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } } public String getConfigName() { return configName; } public void setConfigName(String configName) { this.configName = configName; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getAuthConfigPath() { return authConfigPath; } public void setAuthConfigPath(String authConfigPath) { this.authConfigPath = authConfigPath; } public boolean isAuthenticationEnabled() { return authenticationEnabled; } public void setAuthenticationEnabled(boolean authenticationEnabled) { this.authenticationEnabled = authenticationEnabled; } public String getAuthenticationProvider() { return authenticationProvider; } public void setAuthenticationProvider(String authenticationProvider) { this.authenticationProvider = authenticationProvider; } public String getAuthenticationMetadataProvider() { return authenticationMetadataProvider; } public void setAuthenticationMetadataProvider(String authenticationMetadataProvider) { this.authenticationMetadataProvider = authenticationMetadataProvider; } public String getAuthenticationStrategy() { return authenticationStrategy; } public void setAuthenticationStrategy(String authenticationStrategy) { this.authenticationStrategy = authenticationStrategy; } public String getAuthenticationWhitelist() { return authenticationWhitelist; } public void setAuthenticationWhitelist(String authenticationWhitelist) { this.authenticationWhitelist = authenticationWhitelist; } public String getInitAuthenticationUser() { return initAuthenticationUser; } public void setInitAuthenticationUser(String initAuthenticationUser) { this.initAuthenticationUser = initAuthenticationUser; } public String getInnerClientAuthenticationCredentials() { return innerClientAuthenticationCredentials; } public void setInnerClientAuthenticationCredentials(String innerClientAuthenticationCredentials) { this.innerClientAuthenticationCredentials = innerClientAuthenticationCredentials; } public boolean isAuthorizationEnabled() { return authorizationEnabled; } public void setAuthorizationEnabled(boolean authorizationEnabled) { this.authorizationEnabled = authorizationEnabled; } public String getAuthorizationProvider() { return authorizationProvider; } public void setAuthorizationProvider(String authorizationProvider) { this.authorizationProvider = authorizationProvider; } public String getAuthorizationMetadataProvider() { return authorizationMetadataProvider; } public void setAuthorizationMetadataProvider(String authorizationMetadataProvider) { this.authorizationMetadataProvider = authorizationMetadataProvider; } public String getAuthorizationStrategy() { return authorizationStrategy; } public void setAuthorizationStrategy(String authorizationStrategy) { this.authorizationStrategy = authorizationStrategy; } public String getAuthorizationWhitelist() { return authorizationWhitelist; } public void setAuthorizationWhitelist(String authorizationWhitelist) { this.authorizationWhitelist = authorizationWhitelist; } public boolean isMigrateAuthFromV1Enabled() { return migrateAuthFromV1Enabled; } public void setMigrateAuthFromV1Enabled(boolean migrateAuthFromV1Enabled) { this.migrateAuthFromV1Enabled = migrateAuthFromV1Enabled; } public int getUserCacheMaxNum() { return userCacheMaxNum; } public void setUserCacheMaxNum(int userCacheMaxNum) { this.userCacheMaxNum = userCacheMaxNum; } public int getUserCacheExpiredSecond() { return userCacheExpiredSecond; } public void setUserCacheExpiredSecond(int userCacheExpiredSecond) { this.userCacheExpiredSecond = userCacheExpiredSecond; } public int getUserCacheRefreshSecond() { return userCacheRefreshSecond; } public void setUserCacheRefreshSecond(int userCacheRefreshSecond) { this.userCacheRefreshSecond = userCacheRefreshSecond; } public int getAclCacheMaxNum() { return aclCacheMaxNum; } public void setAclCacheMaxNum(int aclCacheMaxNum) { this.aclCacheMaxNum = aclCacheMaxNum; } public int getAclCacheExpiredSecond() { return aclCacheExpiredSecond; } public void setAclCacheExpiredSecond(int aclCacheExpiredSecond) { this.aclCacheExpiredSecond = aclCacheExpiredSecond; } public int getAclCacheRefreshSecond() { return aclCacheRefreshSecond; } public void setAclCacheRefreshSecond(int aclCacheRefreshSecond) { this.aclCacheRefreshSecond = aclCacheRefreshSecond; } public int getStatefulAuthenticationCacheMaxNum() { return statefulAuthenticationCacheMaxNum; } public void setStatefulAuthenticationCacheMaxNum(int statefulAuthenticationCacheMaxNum) { this.statefulAuthenticationCacheMaxNum = statefulAuthenticationCacheMaxNum; } public int getStatefulAuthenticationCacheExpiredSecond() { return statefulAuthenticationCacheExpiredSecond; } public void setStatefulAuthenticationCacheExpiredSecond(int statefulAuthenticationCacheExpiredSecond) { this.statefulAuthenticationCacheExpiredSecond = statefulAuthenticationCacheExpiredSecond; } public int getStatefulAuthorizationCacheMaxNum() { return statefulAuthorizationCacheMaxNum; } public void setStatefulAuthorizationCacheMaxNum(int statefulAuthorizationCacheMaxNum) { this.statefulAuthorizationCacheMaxNum = statefulAuthorizationCacheMaxNum; } public int getStatefulAuthorizationCacheExpiredSecond() { return statefulAuthorizationCacheExpiredSecond; } public void setStatefulAuthorizationCacheExpiredSecond(int statefulAuthorizationCacheExpiredSecond) { this.statefulAuthorizationCacheExpiredSecond = statefulAuthorizationCacheExpiredSecond; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/AuthMigrator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.AclConstants; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.migration.v1.PlainPermissionManager; import org.apache.rocketmq.auth.migration.v1.AclConfig; import org.apache.rocketmq.auth.migration.v1.PlainAccessConfig; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.constant.CommonConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.resource.ResourcePattern; import org.apache.rocketmq.common.resource.ResourceType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; public class AuthMigrator { protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final AuthConfig authConfig; private final PlainPermissionManager plainPermissionManager; private final AuthenticationMetadataManager authenticationMetadataManager; private final AuthorizationMetadataManager authorizationMetadataManager; public AuthMigrator(AuthConfig authConfig) { this.authConfig = authConfig; this.plainPermissionManager = new PlainPermissionManager(); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig); this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(authConfig); } public void migrate() { if (!authConfig.isMigrateAuthFromV1Enabled()) { return; } AclConfig aclConfig = this.plainPermissionManager.getAllAclConfig(); List accessConfigs = aclConfig.getPlainAccessConfigs(); if (CollectionUtils.isEmpty(accessConfigs)) { return; } for (PlainAccessConfig accessConfig : accessConfigs) { doMigrate(accessConfig); } } private void doMigrate(PlainAccessConfig accessConfig) { this.isUserExisted(accessConfig.getAccessKey()).thenCompose(existed -> { if (existed) { return CompletableFuture.completedFuture(null); } return createUserAndAcl(accessConfig); }).exceptionally(ex -> { LOG.error("[ACL MIGRATE] An error occurred while migrating ACL configurations for AccessKey:{}.", accessConfig.getAccessKey(), ex); return null; }).join(); } private CompletableFuture createUserAndAcl(PlainAccessConfig accessConfig) { return createUser(accessConfig).thenCompose(nil -> createAcl(accessConfig)); } private CompletableFuture createUser(PlainAccessConfig accessConfig) { User user = new User(); user.setUsername(accessConfig.getAccessKey()); user.setPassword(accessConfig.getSecretKey()); if (accessConfig.isAdmin()) { user.setUserType(UserType.SUPER); } else { user.setUserType(UserType.NORMAL); } return this.authenticationMetadataManager.createUser(user); } private CompletableFuture createAcl(PlainAccessConfig config) { Subject subject = User.of(config.getAccessKey()); List policies = new ArrayList<>(); Policy customPolicy = null; if (CollectionUtils.isNotEmpty(config.getTopicPerms())) { for (String topicPerm : config.getTopicPerms()) { String[] temp = StringUtils.split(topicPerm, CommonConstants.EQUAL); if (temp.length != 2) { continue; } String topicName = StringUtils.trim(temp[0]); String perm = StringUtils.trim(temp[1]); Resource resource = Resource.ofTopic(topicName); List actions = parseActions(perm); Decision decision = parseDecision(perm); PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision); if (customPolicy == null) { customPolicy = Policy.of(PolicyType.CUSTOM, new ArrayList<>()); } customPolicy.getEntries().add(policyEntry); } } if (CollectionUtils.isNotEmpty(config.getGroupPerms())) { for (String groupPerm : config.getGroupPerms()) { String[] temp = StringUtils.split(groupPerm, CommonConstants.EQUAL); if (temp.length != 2) { continue; } String groupName = StringUtils.trim(temp[0]); String perm = StringUtils.trim(temp[1]); Resource resource = Resource.ofGroup(groupName); List actions = parseActions(perm); Decision decision = parseDecision(perm); PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision); if (customPolicy == null) { customPolicy = Policy.of(PolicyType.CUSTOM, new ArrayList<>()); } customPolicy.getEntries().add(policyEntry); } } if (customPolicy != null) { policies.add(customPolicy); } Policy defaultPolicy = null; if (StringUtils.isNotBlank(config.getDefaultTopicPerm())) { String topicPerm = StringUtils.trim(config.getDefaultTopicPerm()); Resource resource = Resource.of(ResourceType.TOPIC, null, ResourcePattern.ANY); List actions = parseActions(topicPerm); Decision decision = parseDecision(topicPerm); PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision); defaultPolicy = Policy.of(PolicyType.DEFAULT, new ArrayList<>()); defaultPolicy.getEntries().add(policyEntry); } if (StringUtils.isNotBlank(config.getDefaultGroupPerm())) { String groupPerm = StringUtils.trim(config.getDefaultGroupPerm()); Resource resource = Resource.of(ResourceType.GROUP, null, ResourcePattern.ANY); List actions = parseActions(groupPerm); Decision decision = parseDecision(groupPerm); PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision); if (defaultPolicy == null) { defaultPolicy = Policy.of(PolicyType.DEFAULT, new ArrayList<>()); } defaultPolicy.getEntries().add(policyEntry); } if (defaultPolicy != null) { policies.add(defaultPolicy); } if (CollectionUtils.isEmpty(policies)) { return CompletableFuture.completedFuture(null); } Acl acl = Acl.of(subject, policies); return this.authorizationMetadataManager.createAcl(acl); } private Decision parseDecision(String str) { if (StringUtils.isBlank(str)) { return Decision.DENY; } return StringUtils.equals(str, AclConstants.DENY) ? Decision.DENY : Decision.ALLOW; } private List parseActions(String str) { List result = new ArrayList<>(); if (StringUtils.isBlank(str)) { result.add(Action.ALL); } switch (StringUtils.trim(str)) { case AclConstants.PUB: result.add(Action.PUB); break; case AclConstants.SUB: result.add(Action.SUB); break; case AclConstants.PUB_SUB: case AclConstants.SUB_PUB: result.add(Action.PUB); result.add(Action.SUB); break; case AclConstants.DENY: result.add(Action.ALL); break; default: result.add(Action.ALL); break; } return result; } private CompletableFuture isUserExisted(String username) { return this.authenticationMetadataManager.getUser(username).thenApply(Objects::nonNull); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/v1/AccessResource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration.v1; public interface AccessResource { } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/v1/AclConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration.v1; import java.util.List; public class AclConfig { private List globalWhiteAddrs; private List plainAccessConfigs; public List getGlobalWhiteAddrs() { return globalWhiteAddrs; } public void setGlobalWhiteAddrs(List globalWhiteAddrs) { this.globalWhiteAddrs = globalWhiteAddrs; } public List getPlainAccessConfigs() { return plainAccessConfigs; } public void setPlainAccessConfigs(List plainAccessConfigs) { this.plainAccessConfigs = plainAccessConfigs; } @Override public String toString() { return "AclConfig{" + "globalWhiteAddrs=" + globalWhiteAddrs + ", plainAccessConfigs=" + plainAccessConfigs + '}'; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/v1/PlainAccessConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration.v1; import java.io.Serializable; import java.util.List; import java.util.Objects; public class PlainAccessConfig implements Serializable { private static final long serialVersionUID = -4517357000307227637L; private String accessKey; private String secretKey; private String whiteRemoteAddress; private boolean admin; private String defaultTopicPerm; private String defaultGroupPerm; private List topicPerms; private List groupPerms; public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getWhiteRemoteAddress() { return whiteRemoteAddress; } public void setWhiteRemoteAddress(String whiteRemoteAddress) { this.whiteRemoteAddress = whiteRemoteAddress; } public boolean isAdmin() { return admin; } public void setAdmin(boolean admin) { this.admin = admin; } public String getDefaultTopicPerm() { return defaultTopicPerm; } public void setDefaultTopicPerm(String defaultTopicPerm) { this.defaultTopicPerm = defaultTopicPerm; } public String getDefaultGroupPerm() { return defaultGroupPerm; } public void setDefaultGroupPerm(String defaultGroupPerm) { this.defaultGroupPerm = defaultGroupPerm; } public List getTopicPerms() { return topicPerms; } public void setTopicPerms(List topicPerms) { this.topicPerms = topicPerms; } public List getGroupPerms() { return groupPerms; } public void setGroupPerms(List groupPerms) { this.groupPerms = groupPerms; } @Override public String toString() { return "PlainAccessConfig{" + "accessKey='" + accessKey + '\'' + ", whiteRemoteAddress='" + whiteRemoteAddress + '\'' + ", admin=" + admin + ", defaultTopicPerm='" + defaultTopicPerm + '\'' + ", defaultGroupPerm='" + defaultGroupPerm + '\'' + ", topicPerms=" + topicPerms + ", groupPerms=" + groupPerms + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PlainAccessConfig config = (PlainAccessConfig) o; return admin == config.admin && Objects.equals(accessKey, config.accessKey) && Objects.equals(secretKey, config.secretKey) && Objects.equals(whiteRemoteAddress, config.whiteRemoteAddress) && Objects.equals(defaultTopicPerm, config.defaultTopicPerm) && Objects.equals(defaultGroupPerm, config.defaultGroupPerm) && Objects.equals(topicPerms, config.topicPerms) && Objects.equals(groupPerms, config.groupPerms); } @Override public int hashCode() { return Objects.hash(accessKey, secretKey, whiteRemoteAddress, admin, defaultTopicPerm, defaultGroupPerm, topicPerms, groupPerms); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/v1/PlainAccessData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration.v1; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Objects; public class PlainAccessData implements Serializable { private static final long serialVersionUID = -7971775135605117152L; private List globalWhiteRemoteAddresses = new ArrayList<>(); private List accounts = new ArrayList<>(); private List dataVersion = new ArrayList<>(); public List getGlobalWhiteRemoteAddresses() { return globalWhiteRemoteAddresses; } public void setGlobalWhiteRemoteAddresses(List globalWhiteRemoteAddresses) { this.globalWhiteRemoteAddresses = globalWhiteRemoteAddresses; } public List getAccounts() { return accounts; } public void setAccounts(List accounts) { this.accounts = accounts; } public List getDataVersion() { return dataVersion; } public void setDataVersion(List dataVersion) { this.dataVersion = dataVersion; } public static class DataVersion implements Serializable { private static final long serialVersionUID = 6437361970079056954L; private long timestamp; private long counter; public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public long getCounter() { return counter; } public void setCounter(long counter) { this.counter = counter; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DataVersion that = (DataVersion) o; return timestamp == that.timestamp && counter == that.counter; } @Override public int hashCode() { return Objects.hash(timestamp, counter); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PlainAccessData that = (PlainAccessData) o; return Objects.equals(globalWhiteRemoteAddresses, that.globalWhiteRemoteAddresses) && Objects.equals(accounts, that.accounts) && Objects.equals(dataVersion, that.dataVersion); } @Override public int hashCode() { return Objects.hash(globalWhiteRemoteAddresses, accounts, dataVersion); } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/v1/PlainAccessResource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration.v1; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import java.util.HashMap; import java.util.Map; public class PlainAccessResource implements AccessResource { // Identify the user private String accessKey; private String secretKey; private String whiteRemoteAddress; private boolean admin; private byte defaultTopicPerm = 1; private byte defaultGroupPerm = 1; private Map resourcePermMap; private int requestCode; // The content to calculate the content private byte[] content; private String signature; private String secretToken; private String recognition; public PlainAccessResource() { } public static String getGroupFromRetryTopic(String retryTopic) { if (retryTopic == null) { return null; } return KeyBuilder.parseGroup(retryTopic); } public static String getRetryTopic(String group) { if (group == null) { return null; } return MixAll.getRetryTopic(group); } public void addResourceAndPerm(String resource, byte perm) { if (resource == null) { return; } if (resourcePermMap == null) { resourcePermMap = new HashMap<>(); } resourcePermMap.put(resource, perm); } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getWhiteRemoteAddress() { return whiteRemoteAddress; } public void setWhiteRemoteAddress(String whiteRemoteAddress) { this.whiteRemoteAddress = whiteRemoteAddress; } public boolean isAdmin() { return admin; } public void setAdmin(boolean admin) { this.admin = admin; } public byte getDefaultTopicPerm() { return defaultTopicPerm; } public void setDefaultTopicPerm(byte defaultTopicPerm) { this.defaultTopicPerm = defaultTopicPerm; } public byte getDefaultGroupPerm() { return defaultGroupPerm; } public void setDefaultGroupPerm(byte defaultGroupPerm) { this.defaultGroupPerm = defaultGroupPerm; } public Map getResourcePermMap() { return resourcePermMap; } public String getRecognition() { return recognition; } public void setRecognition(String recognition) { this.recognition = recognition; } public int getRequestCode() { return requestCode; } public void setRequestCode(int requestCode) { this.requestCode = requestCode; } public String getSecretToken() { return secretToken; } public void setSecretToken(String secretToken) { this.secretToken = secretToken; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } } ================================================ FILE: auth/src/main/java/org/apache/rocketmq/auth/migration/v1/PlainPermissionManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration.v1; import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class PlainPermissionManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private String fileHome = MixAll.ROCKETMQ_HOME_DIR; private String defaultAclDir; private String defaultAclFile; private List fileList = new ArrayList<>(); public PlainPermissionManager() { this.defaultAclDir = MixAll.dealFilePath(fileHome + File.separator + "conf" + File.separator + "acl"); this.defaultAclFile = MixAll.dealFilePath(fileHome + File.separator + System.getProperty("rocketmq.acl.plain.file", "conf" + File.separator + "plain_acl.yml")); load(); } public List getAllAclFiles(String path) { if (!new File(path).exists()) { log.info("The default acl dir {} is not exist", path); return new ArrayList<>(); } List allAclFileFullPath = new ArrayList<>(); File file = new File(path); File[] files = file.listFiles(); for (int i = 0; files != null && i < files.length; i++) { String fileName = files[i].getAbsolutePath(); File f = new File(fileName); if (fileName.equals(fileHome + MixAll.ACL_CONF_TOOLS_FILE)) { continue; } else if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) { allAclFileFullPath.add(fileName); } else if (f.isDirectory()) { allAclFileFullPath.addAll(getAllAclFiles(fileName)); } } return allAclFileFullPath; } public void load() { if (fileHome == null || fileHome.isEmpty()) { return; } assureAclConfigFilesExist(); fileList = getAllAclFiles(defaultAclDir); if (new File(defaultAclFile).exists() && !fileList.contains(defaultAclFile)) { fileList.add(defaultAclFile); } } /** * Currently GlobalWhiteAddress is defined in {@link #defaultAclFile}, so make sure it exists. */ private void assureAclConfigFilesExist() { final Path defaultAclFilePath = Paths.get(this.defaultAclFile); if (!Files.exists(defaultAclFilePath)) { try { Files.createFile(defaultAclFilePath); } catch (FileAlreadyExistsException e) { // Maybe created by other threads } catch (IOException e) { log.error("Error in creating " + this.defaultAclFile, e); throw new AclException(e.getMessage()); } } } public AclConfig getAllAclConfig() { AclConfig aclConfig = new AclConfig(); List configs = new ArrayList<>(); List whiteAddrs = new ArrayList<>(); Set accessKeySets = new HashSet<>(); for (String path : fileList) { PlainAccessData plainAclConfData = AclUtils.getYamlDataObject(path, PlainAccessData.class); if (plainAclConfData == null) { continue; } List globalWhiteAddrs = plainAclConfData.getGlobalWhiteRemoteAddresses(); if (globalWhiteAddrs != null && !globalWhiteAddrs.isEmpty()) { whiteAddrs.addAll(globalWhiteAddrs); } List plainAccessConfigs = plainAclConfData.getAccounts(); if (plainAccessConfigs != null && !plainAccessConfigs.isEmpty()) { for (PlainAccessConfig accessConfig : plainAccessConfigs) { if (!accessKeySets.contains(accessConfig.getAccessKey())) { accessKeySets.add(accessConfig.getAccessKey()); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); plainAccessConfig.setGroupPerms(accessConfig.getGroupPerms()); plainAccessConfig.setDefaultTopicPerm(accessConfig.getDefaultTopicPerm()); plainAccessConfig.setDefaultGroupPerm(accessConfig.getDefaultGroupPerm()); plainAccessConfig.setAccessKey(accessConfig.getAccessKey()); plainAccessConfig.setSecretKey(accessConfig.getSecretKey()); plainAccessConfig.setAdmin(accessConfig.isAdmin()); plainAccessConfig.setTopicPerms(accessConfig.getTopicPerms()); plainAccessConfig.setWhiteRemoteAddress(accessConfig.getWhiteRemoteAddress()); configs.add(plainAccessConfig); } } } } aclConfig.setPlainAccessConfigs(configs); aclConfig.setGlobalWhiteAddrs(whiteAddrs); return aclConfig; } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authentication/AuthenticationEvaluatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.apache.rocketmq.common.MixAll; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class AuthenticationEvaluatorTest { private AuthConfig authConfig; private AuthenticationEvaluator evaluator; private AuthenticationMetadataManager authenticationMetadataManager; @Before public void setUp() throws Exception { if (MixAll.isMac()) { return; } this.authConfig = AuthTestHelper.createDefaultConfig(); this.evaluator = new AuthenticationEvaluator(authConfig); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig); this.clearAllUsers(); } @After public void tearDown() throws Exception { if (MixAll.isMac()) { return; } this.clearAllUsers(); this.authenticationMetadataManager.shutdown(); } @Test public void evaluate1() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user); DefaultAuthenticationContext context = new DefaultAuthenticationContext(); context.setRpcCode("11"); context.setUsername("test"); context.setContent("test".getBytes(StandardCharsets.UTF_8)); context.setSignature("DJRRXBXlCVuKh6ULoN87847QX+Y="); this.evaluator.evaluate(context); } @Test public void evaluate2() { if (MixAll.isMac()) { return; } DefaultAuthenticationContext context = new DefaultAuthenticationContext(); context.setRpcCode("11"); context.setUsername("test"); context.setContent("test".getBytes(StandardCharsets.UTF_8)); context.setSignature("DJRRXBXlCVuKh6ULoN87847QX+Y="); Assert.assertThrows(AuthenticationException.class, () -> this.evaluator.evaluate(context)); } @Test public void evaluate3() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user); DefaultAuthenticationContext context = new DefaultAuthenticationContext(); context.setRpcCode("11"); context.setUsername("test"); context.setContent("test".getBytes(StandardCharsets.UTF_8)); context.setSignature("test"); Assert.assertThrows(AuthenticationException.class, () -> this.evaluator.evaluate(context)); } @Test public void evaluate4() { if (MixAll.isMac()) { return; } this.authConfig.setAuthenticationWhitelist("11"); this.evaluator = new AuthenticationEvaluator(authConfig); DefaultAuthenticationContext context = new DefaultAuthenticationContext(); context.setRpcCode("11"); context.setUsername("test"); context.setContent("test".getBytes(StandardCharsets.UTF_8)); context.setSignature("test"); this.evaluator.evaluate(context); } @Test public void evaluate5() { if (MixAll.isMac()) { return; } this.authConfig.setAuthenticationEnabled(false); this.evaluator = new AuthenticationEvaluator(authConfig); DefaultAuthenticationContext context = new DefaultAuthenticationContext(); context.setRpcCode("11"); context.setUsername("test"); context.setContent("test".getBytes(StandardCharsets.UTF_8)); context.setSignature("test"); this.evaluator.evaluate(context); } private void clearAllUsers() { if (MixAll.isMac()) { return; } List users = this.authenticationMetadataManager.listUser(null).join(); if (CollectionUtils.isEmpty(users)) { return; } users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.builder; import apache.rocketmq.v2.Message; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.SendMessageRequest; import com.google.protobuf.ByteString; import io.grpc.Metadata; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelId; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class DefaultAuthenticationContextBuilderTest { private DefaultAuthenticationContextBuilder builder; @Mock private ChannelHandlerContext channelHandlerContext; @Mock private Channel channel; @Before public void setUp() throws Exception { builder = new DefaultAuthenticationContextBuilder(); } @Test public void build1() { Resource topic = Resource.newBuilder().setName("topic-test").build(); { SendMessageRequest request = SendMessageRequest.newBuilder() .addMessages(Message.newBuilder().setTopic(topic) .setBody(ByteString.copyFromUtf8("message-body")) .build()) .build(); Metadata metadata = new Metadata(); metadata.put(GrpcConstants.AUTHORIZATION, "MQv2-HMAC-SHA1 Credential=abc, SignedHeaders=x-mq-date-time, Signature=D18A9CBCDDBA9041D6693268FEF15A989E64430B"); metadata.put(GrpcConstants.DATE_TIME, "20231227T194619Z"); DefaultAuthenticationContext context = builder.build(metadata, request); Assert.assertNotNull(context); Assert.assertEquals("abc", context.getUsername()); Assert.assertEquals("0YqcvN26kEHWaTJo/vFamJ5kQws=", context.getSignature()); Assert.assertEquals("20231227T194619Z", new String(context.getContent(), StandardCharsets.UTF_8)); } } @Test public void build2() { when(channel.id()).thenReturn(mockChannelId("channel-id")); when(channelHandlerContext.channel()).thenReturn(channel); SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setTopic("topic-test"); requestHeader.setQueueId(0); requestHeader.setBornTimestamp(117036786441330L); requestHeader.setBname("brokerName-1"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader); request.setVersion(441); request.addExtField("AccessKey", "abc"); request.addExtField("Signature", "ZG26exJ5u9q1fwZlO4DCmz2Rs88="); request.makeCustomHeaderToNet(); DefaultAuthenticationContext context = builder.build(channelHandlerContext, request); Assert.assertNotNull(context); Assert.assertEquals("abc", context.getUsername()); Assert.assertEquals("ZG26exJ5u9q1fwZlO4DCmz2Rs88=", context.getSignature()); Assert.assertEquals("abcbrokerName-11170367864413300topic-test", new String(context.getContent(), StandardCharsets.UTF_8)); } private ChannelId mockChannelId(String channelId) { return new ChannelId() { @Override public String asShortText() { return channelId; } @Override public String asLongText() { return channelId; } @Override public int compareTo(ChannelId o) { return 0; } }; } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.manager; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.apache.rocketmq.common.MixAll; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class AuthenticationMetadataManagerTest { private AuthConfig authConfig; private AuthenticationMetadataManager authenticationMetadataManager; @Before public void setUp() throws Exception { if (MixAll.isMac()) { return; } this.authConfig = AuthTestHelper.createDefaultConfig(); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig); this.clearAllUsers(); } @After public void tearDown() throws Exception { if (MixAll.isMac()) { return; } this.clearAllUsers(); this.authenticationMetadataManager.shutdown(); } @Test public void createUser() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNotNull(user); Assert.assertEquals(user.getUsername(), "test"); Assert.assertEquals(user.getPassword(), "test"); Assert.assertEquals(user.getUserType(), UserType.NORMAL); user = User.of("super", "super", UserType.SUPER); this.authenticationMetadataManager.createUser(user).join(); user = this.authenticationMetadataManager.getUser("super").join(); Assert.assertNotNull(user); Assert.assertEquals(user.getUsername(), "super"); Assert.assertEquals(user.getPassword(), "super"); Assert.assertEquals(user.getUserType(), UserType.SUPER); Assert.assertThrows(AuthenticationException.class, () -> { try { User user2 = User.of("test", "test"); this.authenticationMetadataManager.createUser(user2).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); } @Test public void updateUser() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNotNull(user); Assert.assertEquals(user.getUsername(), "test"); Assert.assertEquals(user.getPassword(), "test"); Assert.assertEquals(user.getUserType(), UserType.NORMAL); user.setPassword("123"); this.authenticationMetadataManager.updateUser(user).join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNotNull(user); Assert.assertEquals(user.getUsername(), "test"); Assert.assertEquals(user.getPassword(), "123"); Assert.assertEquals(user.getUserType(), UserType.NORMAL); user.setUserType(UserType.SUPER); this.authenticationMetadataManager.updateUser(user).join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNotNull(user); Assert.assertEquals(user.getUsername(), "test"); Assert.assertEquals(user.getPassword(), "123"); Assert.assertEquals(user.getUserType(), UserType.SUPER); Assert.assertThrows(AuthenticationException.class, () -> { try { User user2 = User.of("no_user", "no_user"); this.authenticationMetadataManager.updateUser(user2).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); } @Test public void deleteUser() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNotNull(user); this.authenticationMetadataManager.deleteUser("test").join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNull(user); this.authenticationMetadataManager.deleteUser("no_user").join(); } @Test public void getUser() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); user = this.authenticationMetadataManager.getUser("test").join(); Assert.assertNotNull(user); Assert.assertEquals(user.getUsername(), "test"); Assert.assertEquals(user.getPassword(), "test"); Assert.assertEquals(user.getUserType(), UserType.NORMAL); user = this.authenticationMetadataManager.getUser("no_user").join(); Assert.assertNull(user); } @Test public void listUser() { if (MixAll.isMac()) { return; } List users = this.authenticationMetadataManager.listUser(null).join(); Assert.assertTrue(CollectionUtils.isEmpty(users)); User user = User.of("test-1", "test-1"); this.authenticationMetadataManager.createUser(user).join(); users = this.authenticationMetadataManager.listUser(null).join(); Assert.assertEquals(users.size(), 1); user = User.of("test-2", "test-2"); this.authenticationMetadataManager.createUser(user).join(); users = this.authenticationMetadataManager.listUser("test").join(); Assert.assertEquals(users.size(), 2); } private void clearAllUsers() { List users = this.authenticationMetadataManager.listUser(null).join(); if (CollectionUtils.isEmpty(users)) { return; } users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authentication/provider/LocalAuthenticationMetadataProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authentication.provider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class LocalAuthenticationMetadataProviderTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @Test public void testShutdownReleasesCacheExecutor() throws Exception { AuthConfig authConfig = AuthTestHelper.createDefaultConfig(); authConfig.setAuthConfigPath(tempFolder.newFolder("auth-test").getAbsolutePath()); LocalAuthenticationMetadataProvider provider = new LocalAuthenticationMetadataProvider(); // Initialize provider to create the internal cache refresh executor provider.initialize(authConfig, () -> null); // After initialization, the executor should exist and not be shutdown Assert.assertNotNull(provider.cacheRefreshExecutor); Assert.assertFalse(provider.cacheRefreshExecutor.isShutdown()); // Shutdown provider should also shutdown its executor to release resources provider.shutdown(); // Verify that the cache refresh executor has been shutdown Assert.assertTrue(provider.cacheRefreshExecutor.isShutdown()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.action.Action; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class AuthorizationEvaluatorTest { private AuthConfig authConfig; private AuthorizationEvaluator evaluator; private AuthenticationMetadataManager authenticationMetadataManager; private AuthorizationMetadataManager authorizationMetadataManager; @Before public void setUp() throws Exception { if (MixAll.isMac()) { return; } this.authConfig = AuthTestHelper.createDefaultConfig(); this.evaluator = new AuthorizationEvaluator(authConfig); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig); this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(authConfig); this.clearAllAcls(); this.clearAllUsers(); } @After public void tearDown() throws Exception { if (MixAll.isMac()) { return; } this.clearAllAcls(); this.clearAllUsers(); this.authenticationMetadataManager.shutdown(); } @Test public void evaluate1() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl).join(); Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); // acl sourceIp is null acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", null, Decision.ALLOW); this.authorizationMetadataManager.updateAcl(acl).join(); subject = Subject.of("User:test"); resource = Resource.ofTopic("test"); action = Action.PUB; sourceIp = "192.168.0.1"; context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); } @Test public void evaluate2() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*,Group:test*", "Sub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl).join(); List contexts = new ArrayList<>(); Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.SUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context1 = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context1.setRpcCode("11"); contexts.add(context1); subject = Subject.of("User:test"); resource = Resource.ofGroup("test"); action = Action.SUB; sourceIp = "192.168.0.1"; DefaultAuthorizationContext context2 = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context2.setRpcCode("11"); contexts.add(context2); this.evaluator.evaluate(contexts); } @Test public void evaluate4() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl).join(); // user not exist Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:abc"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); // resource not match Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("abc"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); // action not match Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.SUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); // sourceIp not match Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "10.10.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); // decision is deny acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.DENY); this.authorizationMetadataManager.updateAcl(acl).join(); Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); } @Test public void evaluate5() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl = AuthTestHelper.buildAcl("User:test", "*", "Pub,Sub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl).join(); acl = AuthTestHelper.buildAcl("User:test", "Topic:*", "Pub,Sub", "192.168.0.0/24", Decision.DENY); this.authorizationMetadataManager.updateAcl(acl).join(); acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub,Sub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.updateAcl(acl).join(); acl = AuthTestHelper.buildAcl("User:test", "Topic:test-1", "Pub,Sub", "192.168.0.0/24", Decision.DENY); this.authorizationMetadataManager.updateAcl(acl).join(); Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test-1"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test-2"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); } Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("abc"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofGroup("test-2"); Action action = Action.SUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); } } @Test public void evaluate6() { if (MixAll.isMac()) { return; } this.authConfig.setAuthorizationWhitelist("10"); this.evaluator = new AuthorizationEvaluator(this.authConfig); Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); } @Test public void evaluate7() { if (MixAll.isMac()) { return; } this.authConfig.setAuthorizationEnabled(false); this.evaluator = new AuthorizationEvaluator(this.authConfig); Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); } @Test public void evaluate8() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.DENY); this.authorizationMetadataManager.createAcl(acl).join(); Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("abc"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); acl = AuthTestHelper.buildAcl("User:test", PolicyType.DEFAULT, "Topic:*", "Pub", null, Decision.ALLOW); this.authorizationMetadataManager.updateAcl(acl).join(); { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("abc"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); } } @Test public void evaluate9() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl0 = AuthTestHelper.buildAcl("User:test", "*", "Pub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl0).join(); Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:*", "Pub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl1).join(); Acl acl2 = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl2).join(); Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test_*", "Pub", "192.168.0.0/24", Decision.DENY); this.authorizationMetadataManager.createAcl(acl3).join(); Acl acl4 = AuthTestHelper.buildAcl("User:test", "Topic:test_001", "Pub", "192.168.0.0/24", Decision.DENY); this.authorizationMetadataManager.createAcl(acl4).join(); Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test_001"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); Assert.assertThrows(AuthorizationException.class, () -> { Subject subject = Subject.of("User:test"); Resource resource = Resource.ofTopic("test_002"); Action action = Action.PUB; String sourceIp = "192.168.0.1"; DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp); context.setRpcCode("10"); this.evaluator.evaluate(Collections.singletonList(context)); }); } private void clearAllUsers() { List users = this.authenticationMetadataManager.listUser(null).join(); if (CollectionUtils.isEmpty(users)) { return; } users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join()); } private void clearAllAcls() { List acls = this.authorizationMetadataManager.listAcl(null, null).join(); if (CollectionUtils.isEmpty(acls)) { return; } acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.builder; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.Message; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.Publishing; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Subscription; import apache.rocketmq.v2.SubscriptionEntry; import apache.rocketmq.v2.TelemetryCommand; import com.alibaba.fastjson2.JSON; import com.google.common.collect.Sets; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelId; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import java.util.Arrays; import java.util.List; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.netty.AttributeKeys; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.RequestHeaderRegistry; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class DefaultAuthorizationContextBuilderTest { private AuthorizationContextBuilder builder; @Mock private ChannelHandlerContext channelHandlerContext; @Mock private Channel channel; @Before public void setUp() throws Exception { AuthConfig authConfig = new AuthConfig(); authConfig.setClusterName("DefaultCluster"); builder = new DefaultAuthorizationContextBuilder(authConfig); RequestHeaderRegistry.getInstance().initialize(); } @Test public void buildGrpc() { Metadata metadata = new Metadata(); metadata.put(GrpcConstants.AUTHORIZATION_AK, "rocketmq"); metadata.put(GrpcConstants.REMOTE_ADDRESS, "192.168.0.1"); metadata.put(GrpcConstants.CHANNEL_ID, "channel-id"); GeneratedMessageV3 request = SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .build()) .build(); List result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); Assert.assertEquals(result.get(0).getSourceIp(), "192.168.0.1"); Assert.assertEquals(result.get(0).getChannelId(), "channel-id"); Assert.assertEquals(result.get(0).getRpcCode(), SendMessageRequest.getDescriptor().getFullName()); request = RecallMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setRecallHandle("handle") .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); Assert.assertEquals(result.get(0).getSourceIp(), "192.168.0.1"); Assert.assertEquals(result.get(0).getChannelId(), "channel-id"); Assert.assertEquals(result.get(0).getRpcCode(), RecallMessageRequest.getDescriptor().getFullName()); request = EndTransactionRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); request = HeartbeatRequest.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); request = ReceiveMessageRequest.newBuilder() .setMessageQueue(MessageQueue.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .build()) .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(2, result.size()); Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); request = AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(2, result.size()); Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); request = ForwardMessageToDeadLetterQueueRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(2, result.size()); Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); request = NotifyClientTerminationRequest.newBuilder() .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); request = ChangeInvisibleDurationRequest.newBuilder() .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); request = QueryRouteRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB, Action.SUB))); request = QueryAssignmentRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setGroup(Resource.newBuilder().setName("group").build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(2, result.size()); Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); request = TelemetryCommand.newBuilder() .setSettings(Settings.newBuilder() .setPublishing(Publishing.newBuilder() .addTopics(Resource.newBuilder().setName("topic").build()) .build()) .build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(1, result.size()); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.PUB))); request = TelemetryCommand.newBuilder() .setSettings(Settings.newBuilder() .setSubscription(Subscription.newBuilder() .setGroup(Resource.newBuilder().setName("group").build()) .addSubscriptions(SubscriptionEntry.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .build()) .build()) .build()) .build(); result = builder.build(metadata, request); Assert.assertEquals(2, result.size()); Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group"); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq"); Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic"); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); } @Test public void buildRemoting() { when(channel.id()).thenReturn(mockChannelId("channel-id")); when(channel.hasAttr(eq(AttributeKeys.PROXY_PROTOCOL_ADDR))).thenReturn(true); when(channel.attr(eq(AttributeKeys.PROXY_PROTOCOL_ADDR))).thenReturn(mockAttribute("192.168.0.1")); when(channel.hasAttr(eq(AttributeKeys.PROXY_PROTOCOL_PORT))).thenReturn(true); when(channel.attr(eq(AttributeKeys.PROXY_PROTOCOL_PORT))).thenReturn(mockAttribute("1234")); when(channelHandlerContext.channel()).thenReturn(channel); SendMessageRequestHeader sendMessageRequestHeader = new SendMessageRequestHeader(); sendMessageRequestHeader.setTopic("topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); List result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); Assert.assertEquals("192.168.0.1", result.get(0).getSourceIp()); Assert.assertEquals("channel-id", result.get(0).getChannelId()); Assert.assertEquals(RequestCode.SEND_MESSAGE + "", result.get(0).getRpcCode()); sendMessageRequestHeader = new SendMessageRequestHeader(); sendMessageRequestHeader.setTopic("%RETRY%group"); request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); SendMessageRequestHeaderV2 sendMessageRequestHeaderV2 = new SendMessageRequestHeaderV2(); sendMessageRequestHeaderV2.setTopic("topic"); request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, sendMessageRequestHeaderV2); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); sendMessageRequestHeaderV2 = new SendMessageRequestHeaderV2(); sendMessageRequestHeaderV2.setTopic("%RETRY%group"); request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, sendMessageRequestHeaderV2); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); RecallMessageRequestHeader recallMessageRequestHeader = new RecallMessageRequestHeader(); recallMessageRequestHeader.setTopic("topic"); recallMessageRequestHeader.setRecallHandle("handle"); request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, recallMessageRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); Assert.assertEquals("192.168.0.1", result.get(0).getSourceIp()); Assert.assertEquals("channel-id", result.get(0).getChannelId()); Assert.assertEquals(RequestCode.RECALL_MESSAGE + "", result.get(0).getRpcCode()); EndTransactionRequestHeader endTransactionRequestHeader = new EndTransactionRequestHeader(); endTransactionRequestHeader.setTopic("topic"); request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, endTransactionRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB))); endTransactionRequestHeader = new EndTransactionRequestHeader(); request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, endTransactionRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(0, result.size()); ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader(); consumerSendMsgBackRequestHeader.setGroup("group"); request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, consumerSendMsgBackRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader(); pullMessageRequestHeader.setTopic("topic"); pullMessageRequestHeader.setConsumerGroup("group"); request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(2, result.size()); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); QueryMessageRequestHeader queryMessageRequestHeader = new QueryMessageRequestHeader(); queryMessageRequestHeader.setTopic("topic"); request = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE, queryMessageRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB, Action.GET))); HeartbeatRequestHeader heartbeatRequestHeader = new HeartbeatRequestHeader(); request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, heartbeatRequestHeader); HeartbeatData heartbeatData = new HeartbeatData(); ConsumerData consumerData = new ConsumerData(); consumerData.setGroupName("group"); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic("topic"); consumerData.setSubscriptionDataSet(Sets.newHashSet(subscriptionData)); heartbeatData.setConsumerDataSet(Sets.newHashSet(consumerData)); request.setBody(JSON.toJSONBytes(heartbeatData)); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(2, result.size()); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); UnregisterClientRequestHeader unregisterClientRequestHeader = new UnregisterClientRequestHeader(); unregisterClientRequestHeader.setConsumerGroup("group"); request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, unregisterClientRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB))); GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = new GetConsumerListByGroupRequestHeader(); getConsumerListByGroupRequestHeader.setConsumerGroup("group"); request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, getConsumerListByGroupRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB, Action.GET))); QueryConsumerOffsetRequestHeader queryConsumerOffsetRequestHeader = new QueryConsumerOffsetRequestHeader(); queryConsumerOffsetRequestHeader.setTopic("topic"); queryConsumerOffsetRequestHeader.setConsumerGroup("group"); request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, queryConsumerOffsetRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(2, result.size()); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = new UpdateConsumerOffsetRequestHeader(); updateConsumerOffsetRequestHeader.setTopic("topic"); updateConsumerOffsetRequestHeader.setConsumerGroup("group"); request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, updateConsumerOffsetRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(2, result.size()); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB, Action.UPDATE))); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB, Action.UPDATE))); CreateTopicRequestHeader createTopicRequestHeader = new CreateTopicRequestHeader(); createTopicRequestHeader.setTopic("topic"); createTopicRequestHeader.setTopicFilterType(TopicFilterType.SINGLE_TAG.name()); request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, createTopicRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.CREATE))); CreateUserRequestHeader createUserRequestHeader = new CreateUserRequestHeader(); createUserRequestHeader.setUsername("abc"); request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(1, result.size()); Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey()); Assert.assertEquals("Cluster:DefaultCluster", result.get(0).getResource().getResourceKey()); Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.UPDATE))); LockBatchRequestBody lockBatchRequestBody = new LockBatchRequestBody(); lockBatchRequestBody.setConsumerGroup("group"); java.util.Set lockMqSet = new java.util.HashSet<>(); lockMqSet.add(new org.apache.rocketmq.common.message.MessageQueue("topic", "broker-a", 0)); // retry topic, should be skipped lockMqSet.add(new org.apache.rocketmq.common.message.MessageQueue("%RETRY%group", "broker-a", 1)); lockBatchRequestBody.setMqSet(lockMqSet); request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, null); request.setBody(JSON.toJSONBytes(lockBatchRequestBody)); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(2, result.size()); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("192.168.0.1", getContext(result, ResourceType.TOPIC).getSourceIp()); Assert.assertEquals("channel-id", getContext(result, ResourceType.TOPIC).getChannelId()); Assert.assertEquals(String.valueOf(RequestCode.LOCK_BATCH_MQ), getContext(result, ResourceType.TOPIC).getRpcCode()); UnlockBatchRequestBody unlockBatchRequestBody = new UnlockBatchRequestBody(); unlockBatchRequestBody.setConsumerGroup("group"); java.util.Set unlockMqSet = new java.util.HashSet<>(); unlockMqSet.add(new org.apache.rocketmq.common.message.MessageQueue("topic", "broker-a", 0)); // retry topic, should be skipped unlockMqSet.add(new org.apache.rocketmq.common.message.MessageQueue("%RETRY%group", "broker-a", 1)); unlockBatchRequestBody.setMqSet(unlockMqSet); request = RemotingCommand.createRequestCommand(RequestCode.UNLOCK_BATCH_MQ, null); request.setBody(JSON.toJSONBytes(unlockBatchRequestBody)); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); result = builder.build(channelHandlerContext, request); Assert.assertEquals(2, result.size()); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey()); Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey()); Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey()); Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB))); Assert.assertEquals("192.168.0.1", getContext(result, ResourceType.TOPIC).getSourceIp()); Assert.assertEquals("channel-id", getContext(result, ResourceType.TOPIC).getChannelId()); Assert.assertEquals(String.valueOf(RequestCode.UNLOCK_BATCH_MQ), getContext(result, ResourceType.TOPIC).getRpcCode()); } private DefaultAuthorizationContext getContext(List contexts, ResourceType resourceType) { return contexts.stream().filter(context -> context.getResource().getResourceType() == resourceType) .findFirst().orElse(null); } private ChannelId mockChannelId(String channelId) { return new ChannelId() { @Override public String asShortText() { return channelId; } @Override public String asLongText() { return channelId; } @Override public int compareTo(ChannelId o) { return 0; } }; } private Attribute mockAttribute(String value) { return new Attribute() { @Override public AttributeKey key() { return null; } @Override public String get() { return value; } @Override public void set(String value) { } @Override public String getAndSet(String value) { return null; } @Override public String setIfAbsent(String value) { return null; } @Override public String getAndRemove() { return null; } @Override public boolean compareAndSet(String oldValue, String newValue) { return false; } @Override public void remove() { } }; } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/chain/AclAuthorizationHandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.chain; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.chain.HandlerChain; import org.apache.rocketmq.common.resource.ResourcePattern; import org.apache.rocketmq.common.resource.ResourceType; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import static org.mockito.Mockito.mock; public class AclAuthorizationHandlerTest { private AuthConfig authConfig; private AuthenticationMetadataManager authenticationMetadataManager; private AuthorizationMetadataManager authorizationMetadataManager; private AclAuthorizationHandler handler; private HandlerChain> nextChain; @Before public void setUp() { if (MixAll.isMac()) { return; } this.authConfig = AuthTestHelper.createDefaultConfig(); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig); this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(this.authConfig); this.handler = new AclAuthorizationHandler(this.authConfig); this.nextChain = mock(HandlerChain.class); clearAllAcls(); clearAllUsers(); } @After public void tearDown() { if (MixAll.isMac()) { return; } clearAllAcls(); clearAllUsers(); this.authenticationMetadataManager.shutdown(); this.authorizationMetadataManager.shutdown(); } @Test public void testNoAclThrows() { if (MixAll.isMac()) { return; } // Create a user with no ACL entries. User user = User.of("noacl", "pwd"); authenticationMetadataManager.createUser(user).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:noacl has no permission to access Topic:t1 from 127.0.0.1, no matched policies.", authorizationException.getMessage()); } @Test public void testNoMatchedPolicyThrows() { if (MixAll.isMac()) { return; } User user = User.of("no_match_acl", "pwd"); authenticationMetadataManager.createUser(user).join(); Acl acl = AuthTestHelper.buildAcl("User:no_match_acl", "Topic:abc", Action.SUB.getName(), null, Decision.ALLOW); authorizationMetadataManager.createAcl(acl).join(); // Ensure an ACL has been created. List acls = authorizationMetadataManager.listAcl(null, null).join(); Assert.assertEquals(1, acls.size()); // The requested resource does not match any ACL entry. DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:no_match_acl has no permission to access Topic:t1 from 127.0.0.1, no matched policies.", authorizationException.getMessage()); } @Test public void testDecisionDenyThrows() { if (MixAll.isMac()) { return; } User user = User.of("deny", "pwd"); authenticationMetadataManager.createUser(user).join(); // The ACL entry matches, but the decision is DENY. Acl acl = AuthTestHelper.buildAcl("User:deny", "Topic:t1", Action.SUB.getName(), null, Decision.DENY); authorizationMetadataManager.createAcl(acl).join(); List acls = authorizationMetadataManager.listAcl(null, null).join(); Assert.assertEquals(1, acls.size()); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:deny has no permission to access Topic:t1 from 127.0.0.1, the decision is deny.", authorizationException.getMessage()); } @Test public void testAllowDoesNotThrow() { if (MixAll.isMac()) { return; } User user = User.of("allow", "pwd"); authenticationMetadataManager.createUser(user).join(); // The ACL matches and the decision is ALLOW. Acl acl = AuthTestHelper.buildAcl("User:allow", "Topic:t1", Action.SUB.getName(), null, Decision.ALLOW); authorizationMetadataManager.createAcl(acl).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); handler.handle(ctx, nextChain).join(); } @Test public void testDenyBeatsAllow() { if (MixAll.isMac()) { return; } User user = User.of("user", "pwd"); authenticationMetadataManager.createUser(user).join(); // Set up policy entries with both ALLOW and DENY for the same resource. Resource resource = Resource.of(ResourceType.TOPIC, "t1", ResourcePattern.LITERAL); PolicyEntry allowLiteral = PolicyEntry.of(resource, Collections.singletonList(Action.SUB), null, Decision.ALLOW); PolicyEntry denyLiteral = PolicyEntry.of(resource, Collections.singletonList(Action.SUB), null, Decision.DENY); // Include both entries in the policy to verify precedence. Policy policy = Policy.of(PolicyType.CUSTOM, new ArrayList<>(Arrays.asList(allowLiteral, denyLiteral, allowLiteral))); authorizationMetadataManager.createAcl(Acl.of(user, policy)).join(); DefaultAuthorizationContext ctx = buildContext(user, resource, Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Throwable e) { AuthTestHelper.handleException(e); } }); // DENY should take precedence. Assert.assertEquals("User:user has no permission to access Topic:t1 from 127.0.0.1, the decision is deny.", authorizationException.getMessage()); } @Test public void testPrefixedLongerDenyBeatsPrefixedShorterAllow() { if (MixAll.isMac()) { return; } User user = User.of("user", "pwd"); authenticationMetadataManager.createUser(user).join(); // The longer PREFIXED DENY policy entry should take precedence over the shorter PREFIXED ALLOW policy entry. PolicyEntry denyLonger = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t1-abc", ResourcePattern.PREFIXED), Collections.singletonList(Action.SUB), null, Decision.DENY); PolicyEntry allowShorter = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t1-", ResourcePattern.PREFIXED), Collections.singletonList(Action.SUB), null, Decision.ALLOW); Policy policy = Policy.of(PolicyType.CUSTOM, new ArrayList<>(Arrays.asList(allowShorter, denyLonger))); authorizationMetadataManager.createAcl(Acl.of(user, policy)).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1-abcd"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Throwable e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:user has no permission to access Topic:t1-abcd from 127.0.0.1, the decision is deny.", authorizationException.getMessage()); } @Test public void testLiteralAllowBeatsPrefixedDeny() { if (MixAll.isMac()) { return; } User user = User.of("user", "pwd"); authenticationMetadataManager.createUser(user).join(); // The LITERAL ALLOW policy entry should take precedence over the PREFIXED DENY policy entry. PolicyEntry allowLiteral = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t1", ResourcePattern.LITERAL), Collections.singletonList(Action.SUB), null, Decision.ALLOW); PolicyEntry denyPrefixed = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t", ResourcePattern.PREFIXED), Collections.singletonList(Action.SUB), null, Decision.DENY); Policy policy = Policy.of(PolicyType.CUSTOM, new ArrayList<>(Arrays.asList(denyPrefixed, allowLiteral))); authorizationMetadataManager.createAcl(Acl.of(user, policy)).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); handler.handle(ctx, nextChain).join(); } @Test public void testTopicTypeAllowBeatsAnyTypeDeny() { if (MixAll.isMac()) { return; } User user = User.of("user", "pwd"); authenticationMetadataManager.createUser(user).join(); // The ALLOW policy entry with resource type TOPIC should take precedence over the DENY policy entry with resource type ANY. PolicyEntry denyAnyType = PolicyEntry.of( Resource.of(ResourceType.ANY, "t1", ResourcePattern.LITERAL), Collections.singletonList(Action.SUB), null, Decision.DENY); PolicyEntry allowTopicType = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t1", ResourcePattern.LITERAL), Collections.singletonList(Action.SUB), null, Decision.ALLOW); Policy policy = Policy.of(PolicyType.CUSTOM, new ArrayList<>(Arrays.asList(allowTopicType, denyAnyType))); authorizationMetadataManager.createAcl(Acl.of(user, policy)).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); handler.handle(ctx, nextChain).join(); } @Test public void testPrefixedPatternAllowBeatsAnyPatternDeny() { if (MixAll.isMac()) { return; } User user = User.of("user", "pwd"); authenticationMetadataManager.createUser(user).join(); // The PREFIXED pattern ALLOW policy entry should take precedence over the ANY pattern DENY policy entry. PolicyEntry denyAny = PolicyEntry.of( Resource.of(ResourceType.TOPIC, null, ResourcePattern.ANY), Collections.singletonList(Action.SUB), null, Decision.DENY); PolicyEntry allowPrefixed = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t1", ResourcePattern.PREFIXED), Collections.singletonList(Action.SUB), null, Decision.ALLOW); Policy policy = Policy.of(PolicyType.CUSTOM, new ArrayList<>(Arrays.asList(allowPrefixed, denyAny))); authorizationMetadataManager.createAcl(Acl.of(user, policy)).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); handler.handle(ctx, nextChain).join(); } @Test public void testLiteralPatternDenyBeatsAnyPatternAllow() { if (MixAll.isMac()) { return; } User user = User.of("user", "pwd"); authenticationMetadataManager.createUser(user).join(); // The LITERAL pattern DENY policy entry should take precedence over the ANY pattern ALLOW policy entry. PolicyEntry allowAny = PolicyEntry.of( Resource.of(ResourceType.TOPIC, null, ResourcePattern.ANY), Collections.singletonList(Action.SUB), null, Decision.ALLOW); PolicyEntry denyLiteral = PolicyEntry.of( Resource.of(ResourceType.TOPIC, "t1", ResourcePattern.LITERAL), Collections.singletonList(Action.SUB), null, Decision.DENY); Policy policy = Policy.of(PolicyType.CUSTOM, new ArrayList<>(Arrays.asList(allowAny, denyLiteral))); authorizationMetadataManager.createAcl(Acl.of(user, policy)).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Throwable e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:user has no permission to access Topic:t1 from 127.0.0.1, the decision is deny.", authorizationException.getMessage()); } private DefaultAuthorizationContext buildContext(Subject subject, Resource resource, Action action, String sourceIp) { return DefaultAuthorizationContext.of(subject, resource, action, sourceIp); } private void clearAllUsers() { List users = this.authenticationMetadataManager.listUser(null).join(); if (CollectionUtils.isEmpty(users)) { return; } users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join()); } private void clearAllAcls() { List acls = this.authorizationMetadataManager.listAcl(null, null).join(); if (CollectionUtils.isEmpty(acls)) { return; } acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/chain/UserAuthorizationHandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.chain; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.enums.UserStatus; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.chain.HandlerChain; import org.apache.rocketmq.auth.authorization.model.Resource; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class UserAuthorizationHandlerTest { private AuthConfig authConfig; private AuthenticationMetadataManager authenticationMetadataManager; private UserAuthorizationHandler handler; private HandlerChain> nextChain; @Before public void setUp() { if (MixAll.isMac()) { return; } this.authConfig = AuthTestHelper.createDefaultConfig(); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig); this.handler = new UserAuthorizationHandler(this.authConfig, null); this.nextChain = mock(HandlerChain.class); clearAllUsers(); } @After public void tearDown() { if (MixAll.isMac()) { return; } clearAllUsers(); this.authenticationMetadataManager.shutdown(); } @Test public void testUserNotFoundThrows() { if (MixAll.isMac()) { return; } User noSuchUser = User.of("no_such_user", "pwd"); DefaultAuthorizationContext ctx = buildContext(noSuchUser, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:no_such_user not found.", authorizationException.getMessage()); } @Test public void testUserDisabledThrows() { if (MixAll.isMac()) { return; } User user = User.of("disabled", "pwd"); authenticationMetadataManager.createUser(user).join(); User saved = authenticationMetadataManager.getUser("disabled").join(); saved.setUserStatus(UserStatus.DISABLE); authenticationMetadataManager.updateUser(saved).join(); DefaultAuthorizationContext ctx = buildContext(user, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { handler.handle(ctx, nextChain).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("User:disabled is disabled.", authorizationException.getMessage()); verify(nextChain, never()).handle(any()); } @Test public void testSuperUserBypassNextChain() { if (MixAll.isMac()) { return; } User superUser = User.of("super", "pwd", UserType.SUPER); authenticationMetadataManager.createUser(superUser).join(); DefaultAuthorizationContext ctx = buildContext(superUser, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); handler.handle(ctx, nextChain).join(); // super user should bypass the next chain verify(nextChain, never()).handle(any()); } @Test public void testNormalUserGoesToNextChain() { if (MixAll.isMac()) { return; } User normalUser = User.of("normal", "pwd", UserType.NORMAL); authenticationMetadataManager.createUser(normalUser).join(); DefaultAuthorizationContext ctx = buildContext(normalUser, Resource.ofTopic("t1"), Action.SUB, "127.0.0.1"); when(nextChain.handle(any())).thenReturn(CompletableFuture.completedFuture(null)); handler.handle(ctx, nextChain).join(); // normal user should go to the next chain verify(nextChain, times(1)).handle(any()); } private DefaultAuthorizationContext buildContext(Subject subject, Resource resource, Action action, String sourceIp) { return DefaultAuthorizationContext.of(subject, resource, action, sourceIp); } private void clearAllUsers() { List users = this.authenticationMetadataManager.listUser(null).join(); if (CollectionUtils.isEmpty(users)) { return; } users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.manager; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.apache.rocketmq.common.MixAll; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class AuthorizationMetadataManagerTest { private AuthConfig authConfig; private AuthenticationMetadataManager authenticationMetadataManager; private AuthorizationMetadataManager authorizationMetadataManager; @Before public void setUp() throws Exception { if (MixAll.isMac()) { return; } this.authConfig = AuthTestHelper.createDefaultConfig(); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig); this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(this.authConfig); this.clearAllAcls(); this.clearAllUsers(); } @After public void tearDown() throws Exception { if (MixAll.isMac()) { return; } this.clearAllAcls(); this.clearAllUsers(); this.authenticationMetadataManager.shutdown(); this.authorizationMetadataManager.shutdown(); } @Test public void createAcl() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl1).join(); Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2)); user = User.of("abc", "abc"); this.authenticationMetadataManager.createUser(user).join(); acl1 = AuthTestHelper.buildAcl("User:abc", PolicyType.DEFAULT, "Topic:*,Group:*", "PUB,SUB", null, Decision.DENY); this.authorizationMetadataManager.createAcl(acl1).join(); acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2)); Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl3).join(); Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4)); Assert.assertThrows(AuthorizationException.class, () -> { try { Acl acl5 = AuthTestHelper.buildAcl("User:ddd", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl5).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); } @Test public void updateAcl() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl1).join(); Acl acl2 = AuthTestHelper.buildAcl("User:test", "Topic:abc,Group:abc", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.updateAcl(acl2).join(); Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test,Topic:abc,Group:abc", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4)); Policy policy = AuthTestHelper.buildPolicy("Topic:test,Group:test", "PUB,SUB,Create", "192.168.0.0/24", Decision.DENY); acl4.updatePolicy(policy); this.authorizationMetadataManager.updateAcl(acl4); Acl acl5 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl4, acl5)); User user2 = User.of("abc", "abc"); this.authenticationMetadataManager.createUser(user2).join(); Acl acl6 = AuthTestHelper.buildAcl("User:abc", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.updateAcl(acl6).join(); Acl acl7 = this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl6, acl7)); } @Test public void deleteAcl() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl1).join(); this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"), PolicyType.CUSTOM, Resource.ofTopic("abc")).join(); Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2)); this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"), PolicyType.CUSTOM, Resource.ofTopic("test")).join(); Acl acl3 = AuthTestHelper.buildAcl("User:test", "Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4)); this.authorizationMetadataManager.deleteAcl(Subject.of("User:test")); Acl acl5 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertNull(acl5); Assert.assertThrows(AuthorizationException.class, () -> { try { this.authorizationMetadataManager.deleteAcl(Subject.of("User:abc")).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); } @Test public void getAcl() { if (MixAll.isMac()) { return; } User user = User.of("test", "test"); this.authenticationMetadataManager.createUser(user).join(); Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl1).join(); Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join(); Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2)); Assert.assertThrows(AuthorizationException.class, () -> { try { this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); } @Test public void testGetAclWithNullSubject() { if (MixAll.isMac()) { return; } AuthorizationException authorizationException = Assert.assertThrows(AuthorizationException.class, () -> { try { this.authorizationMetadataManager.getAcl(null).join(); } catch (Exception e) { AuthTestHelper.handleException(e); } }); Assert.assertEquals("The subject is null.", authorizationException.getMessage()); } @Test public void listAcl() { if (MixAll.isMac()) { return; } User user1 = User.of("test-1", "test-1"); this.authenticationMetadataManager.createUser(user1).join(); User user2 = User.of("test-2", "test-2"); this.authenticationMetadataManager.createUser(user2).join(); Acl acl1 = AuthTestHelper.buildAcl("User:test-1", "Topic:test-1,Group:test-1", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl1).join(); Acl acl2 = AuthTestHelper.buildAcl("User:test-2", "Topic:test-2,Group:test-2", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl2).join(); Acl acl3 = AuthTestHelper.buildAcl("User:test-2", "Topic:acl-2,Group:acl-2", "PUB,SUB", "192.168.0.0/24,10.10.0.0/24", Decision.ALLOW); this.authorizationMetadataManager.createAcl(acl3).join(); List acls1 = this.authorizationMetadataManager.listAcl(null, null).join(); Assert.assertEquals(acls1.size(), 2); List acls2 = this.authorizationMetadataManager.listAcl("User:test-1", null).join(); Assert.assertEquals(acls2.size(), 1); List acls3 = this.authorizationMetadataManager.listAcl("test", null).join(); Assert.assertEquals(acls3.size(), 2); List acls4 = this.authorizationMetadataManager.listAcl(null, "Topic:test-1").join(); Assert.assertEquals(acls4.size(), 1); Assert.assertEquals(acls4.get(0).getPolicy(PolicyType.CUSTOM).getEntries().size(), 1); List acls5 = this.authorizationMetadataManager.listAcl(null, "test-1").join(); Assert.assertEquals(acls5.size(), 1); Assert.assertEquals(acls5.get(0).getPolicy(PolicyType.CUSTOM).getEntries().size(), 2); List acls6 = this.authorizationMetadataManager.listAcl("User:abc", null).join(); Assert.assertTrue(CollectionUtils.isEmpty(acls6)); List acls7 = this.authorizationMetadataManager.listAcl(null, "Topic:abc").join(); Assert.assertTrue(CollectionUtils.isEmpty(acls7)); List acls8 = this.authorizationMetadataManager.listAcl("test-2", "test-2").join(); Assert.assertEquals(acls8.size(), 1); List policyEntries = acls8.get(0).getPolicy(PolicyType.CUSTOM).getEntries(); Assert.assertEquals(policyEntries.size(), 2); for (PolicyEntry policyEntry : policyEntries) { Assert.assertTrue(policyEntry.toResourceStr().contains("test-2")); } } private void clearAllUsers() { List users = this.authenticationMetadataManager.listUser(null).join(); if (CollectionUtils.isEmpty(users)) { return; } users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join()); } private void clearAllAcls() { List acls = this.authorizationMetadataManager.listAcl(null, null).join(); if (CollectionUtils.isEmpty(acls)) { return; } acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/model/ResourceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.model; import org.apache.rocketmq.common.resource.ResourcePattern; import org.apache.rocketmq.common.resource.ResourceType; import org.junit.Assert; import org.junit.Test; public class ResourceTest { @Test public void parseResource() { Resource resource = Resource.of("*"); Assert.assertEquals(resource.getResourceType(), ResourceType.ANY); Assert.assertNull(resource.getResourceName()); Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.ANY); resource = Resource.of("Topic:*"); Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC); Assert.assertNull(resource.getResourceName()); Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.ANY); resource = Resource.of("Topic:test-*"); Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC); Assert.assertEquals(resource.getResourceName(), "test-"); Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.PREFIXED); resource = Resource.of("Topic:test-1"); Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC); Assert.assertEquals(resource.getResourceName(), "test-1"); Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.LITERAL); } @Test public void isMatch() { } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/provider/LocalAuthorizationMetadataProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.provider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.helper.AuthTestHelper; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class LocalAuthorizationMetadataProviderTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @Test public void testShutdownReleasesCacheExecutor() throws Exception { AuthConfig authConfig = AuthTestHelper.createDefaultConfig(); authConfig.setAuthConfigPath(tempFolder.newFolder("auth-test").getAbsolutePath()); LocalAuthorizationMetadataProvider provider = new LocalAuthorizationMetadataProvider(); // Initialize provider to create the internal cache refresh executor provider.initialize(authConfig, () -> null); // After initialization, the executor should exist and not be shutdown Assert.assertNotNull(provider.cacheRefreshExecutor); Assert.assertFalse(provider.cacheRefreshExecutor.isShutdown()); // Shutdown provider should also shutdown its executor to release resources provider.shutdown(); // Verify that the cache refresh executor has been shutdown Assert.assertTrue(provider.cacheRefreshExecutor.isShutdown()); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/authorization/strategy/StatefulAuthorizationStrategyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.authorization.strategy; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.action.Action; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class StatefulAuthorizationStrategyTest { @Mock private AuthConfig authConfig; private StatefulAuthorizationStrategy statefulAuthorizationStrategy; @Before public void setUp() { when(authConfig.getStatefulAuthorizationCacheExpiredSecond()).thenReturn(60); when(authConfig.getStatefulAuthorizationCacheMaxNum()).thenReturn(100); Supplier metadataService = mock(Supplier.class); statefulAuthorizationStrategy = spy(new StatefulAuthorizationStrategy(authConfig, metadataService)); } @Test public void testEvaluateChannelIdBlankDoesNotUseCache() { AuthorizationContext context = mock(AuthorizationContext.class); when(context.getChannelId()).thenReturn(null); statefulAuthorizationStrategy.evaluate(context); verify(statefulAuthorizationStrategy, times(1)).doEvaluate(context); } @Test public void testEvaluateChannelIdNotNullCacheHit() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { DefaultAuthorizationContext context = new DefaultAuthorizationContext(); context.setChannelId("channelId"); context.setSubject(Subject.of("User")); context.setResource(Resource.of("Cluster")); context.setActions(new ArrayList<>()); context.setSourceIp("sourceIp"); Pair pair = Pair.of(true, null); Cache> authCache = Caffeine.newBuilder() .expireAfterWrite(60, TimeUnit.SECONDS) .maximumSize(100) .build(); authCache.put(buildKey(context), pair); statefulAuthorizationStrategy.authCache = authCache; statefulAuthorizationStrategy.evaluate(context); verify(statefulAuthorizationStrategy, never()).doEvaluate(context); } @Test public void testEvaluateChannelIdNotNullCacheMiss() { DefaultAuthorizationContext context = new DefaultAuthorizationContext(); context.setChannelId("channelId"); context.setSubject(Subject.of("User")); context.setResource(Resource.of("Cluster")); context.setActions(Collections.singletonList(Action.PUB)); context.setSourceIp("sourceIp"); statefulAuthorizationStrategy.authCache = Caffeine.newBuilder() .expireAfterWrite(60, TimeUnit.SECONDS) .maximumSize(100) .build(); statefulAuthorizationStrategy.evaluate(context); verify(statefulAuthorizationStrategy, times(1)).doEvaluate(context); } @Test public void testEvaluateChannelIdNotNullCacheException() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { DefaultAuthorizationContext context = new DefaultAuthorizationContext(); context.setChannelId("channelId"); context.setSubject(Subject.of("subjectKey")); context.setResource(Resource.of("resourceKey")); context.setActions(Collections.singletonList(Action.PUB)); context.setSourceIp("sourceIp"); AuthorizationException exception = new AuthorizationException("test"); Pair pair = Pair.of(false, exception); Cache> authCache = Caffeine.newBuilder() .expireAfterWrite(60, TimeUnit.SECONDS) .maximumSize(100) .build(); authCache.put(buildKey(context), pair); statefulAuthorizationStrategy.authCache = authCache; try { statefulAuthorizationStrategy.evaluate(context); fail("Expected AuthorizationException to be thrown"); } catch (final AuthorizationException ex) { assertEquals(exception, ex); } verify(statefulAuthorizationStrategy, never()).doEvaluate(context); } private String buildKey(AuthorizationContext context) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { return (String) MethodUtils.invokeMethod(statefulAuthorizationStrategy, true, "buildKey", context); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/helper/AuthTestHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.helper; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.provider.DefaultAuthenticationProvider; import org.apache.rocketmq.auth.authentication.provider.LocalAuthenticationMetadataProvider; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Environment; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.auth.authorization.provider.DefaultAuthorizationProvider; import org.apache.rocketmq.auth.authorization.provider.LocalAuthorizationMetadataProvider; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.utils.ExceptionUtils; public class AuthTestHelper { public static AuthConfig createDefaultConfig() { AuthConfig authConfig = new AuthConfig(); authConfig.setConfigName("test-" + System.nanoTime()); authConfig.setAuthConfigPath("~/config"); authConfig.setAuthenticationEnabled(true); authConfig.setAuthenticationProvider(DefaultAuthenticationProvider.class.getName()); authConfig.setAuthenticationMetadataProvider(LocalAuthenticationMetadataProvider.class.getName()); authConfig.setAuthorizationEnabled(true); authConfig.setAuthorizationProvider(DefaultAuthorizationProvider.class.getName()); authConfig.setAuthorizationMetadataProvider(LocalAuthorizationMetadataProvider.class.getName()); return authConfig; } public static Acl buildAcl(String subjectKey, String resources, String actions, String sourceIps, Decision decision) { return buildAcl(subjectKey, null, resources, actions, sourceIps, decision); } public static Acl buildAcl(String subjectKey, PolicyType policyType, String resources, String actions, String sourceIps, Decision decision) { Subject subject = Subject.of(subjectKey); Policy policy = buildPolicy(policyType, resources, actions, sourceIps, decision); return Acl.of(subject, policy); } public static Policy buildPolicy(String resources, String actions, String sourceIps, Decision decision) { return buildPolicy(null, resources, actions, sourceIps, decision); } public static Policy buildPolicy(PolicyType policyType, String resources, String actions, String sourceIps, Decision decision) { List resourceList = Arrays.stream(StringUtils.split(resources, ",")) .map(Resource::of).collect(Collectors.toList()); List actionList = Arrays.stream(StringUtils.split(actions, ",")) .map(Action::getByName).collect(Collectors.toList()); Environment environment = null; if (StringUtils.isNotBlank(sourceIps)) { environment = Environment.of(Arrays.stream(StringUtils.split(sourceIps, ",")) .collect(Collectors.toList())); } return Policy.of(policyType, resourceList, actionList, environment, decision); } public static boolean isEquals(Acl acl1, Acl acl2) { if (acl1 == null && acl2 == null) { return true; } if (acl1 == null || acl2 == null) { return false; } Subject subject1 = acl1.getSubject(); Subject subject2 = acl2.getSubject(); if (!isEquals(subject1, subject2)) { return false; } Map policyMap1 = new HashMap<>(); Map policyMap2 = new HashMap<>(); if (CollectionUtils.isNotEmpty(acl1.getPolicies())) { acl1.getPolicies().forEach(policy -> { if (policy.getPolicyType() == null) { policy.setPolicyType(PolicyType.CUSTOM); } policyMap1.put(policy.getPolicyType(), policy); }); } if (CollectionUtils.isNotEmpty(acl2.getPolicies())) { acl2.getPolicies().forEach(policy -> { if (policy.getPolicyType() == null) { policy.setPolicyType(PolicyType.CUSTOM); } policyMap2.put(policy.getPolicyType(), policy); }); } if (policyMap1.size() != policyMap2.size()) { return false; } Policy customPolicy1 = policyMap1.get(PolicyType.CUSTOM); Policy customPolicy2 = policyMap2.get(PolicyType.CUSTOM); if (!isEquals(customPolicy1, customPolicy2)) { return false; } Policy defaultPolicy1 = policyMap1.get(PolicyType.DEFAULT); Policy defaultPolicy2 = policyMap2.get(PolicyType.DEFAULT); if (!isEquals(defaultPolicy1, defaultPolicy2)) { return false; } return true; } private static boolean isEquals(Policy policy1, Policy policy2) { if (policy1 == null && policy2 == null) { return true; } if (policy1 == null || policy2 == null) { return false; } if (policy1.getPolicyType() != policy2.getPolicyType()) { return false; } Map policyEntryMap1 = new HashMap<>(); Map policyEntryMap2 = new HashMap<>(); if (CollectionUtils.isNotEmpty(policy1.getEntries())) { policy1.getEntries().forEach(policyEntry -> { policyEntryMap1.put(policyEntry.getResource().getResourceKey(), policyEntry); }); } if (CollectionUtils.isNotEmpty(policy2.getEntries())) { policy2.getEntries().forEach(policyEntry -> { policyEntryMap2.put(policyEntry.getResource().getResourceKey(), policyEntry); }); } if (policyEntryMap1.size() != policyEntryMap2.size()) { return false; } for (String resourceKey : policyEntryMap1.keySet()) { if (!isEquals(policyEntryMap1.get(resourceKey), policyEntryMap2.get(resourceKey))) { return false; } } for (String resourceKey : policyEntryMap2.keySet()) { if (!isEquals(policyEntryMap1.get(resourceKey), policyEntryMap2.get(resourceKey))) { return false; } } return true; } private static boolean isEquals(PolicyEntry entry1, PolicyEntry entry2) { if (entry1 == null && entry2 == null) { return true; } if (entry1 == null || entry2 == null) { return false; } Resource resource1 = entry1.getResource(); Resource resource2 = entry2.getResource(); if (!isEquals(resource1, resource2)) { return false; } List actions1 = entry1.getActions(); List actions2 = entry2.getActions(); if (CollectionUtils.isEmpty(actions1) && CollectionUtils.isNotEmpty(actions2)) { return false; } if (CollectionUtils.isNotEmpty(actions1) && CollectionUtils.isEmpty(actions2)) { return false; } if (CollectionUtils.isNotEmpty(actions1) && CollectionUtils.isNotEmpty(actions2) && !CollectionUtils.isEqualCollection(actions1, actions2)) { return false; } Environment environment1 = entry1.getEnvironment(); Environment environment2 = entry2.getEnvironment(); if (!isEquals(environment1, environment2)) { return false; } return entry1.getDecision() == entry2.getDecision(); } private static boolean isEquals(Resource resource1, Resource resource2) { if (resource1 == null && resource2 == null) { return true; } if (resource1 == null || resource2 == null) { return false; } return Objects.equals(resource1, resource2); } private static boolean isEquals(Environment environment1, Environment environment2) { if (environment1 == null && environment2 == null) { return true; } if (environment1 == null || environment2 == null) { return false; } List sourceIp1 = environment1.getSourceIps(); List sourceIp2 = environment2.getSourceIps(); if (CollectionUtils.isEmpty(sourceIp1) && CollectionUtils.isEmpty(sourceIp2)) { return true; } if (CollectionUtils.isEmpty(sourceIp1) || CollectionUtils.isEmpty(sourceIp2)) { return false; } return CollectionUtils.isEqualCollection(sourceIp1, sourceIp2); } private static boolean isEquals(Subject subject1, Subject subject2) { if (subject1 == null && subject2 == null) { return true; } if (subject1 == null || subject2 == null) { return false; } return subject1.getSubjectType() == subject2.getSubjectType() && StringUtils.equals(subject1.getSubjectKey(), subject2.getSubjectKey()); } public static void handleException(Throwable e) { Throwable throwable = ExceptionUtils.getRealException(e); if (throwable instanceof AuthenticationException) { throw (AuthenticationException) throwable; } if (throwable instanceof AuthorizationException) { throw (AuthorizationException) throwable; } throw new RuntimeException(e); } } ================================================ FILE: auth/src/test/java/org/apache/rocketmq/auth/migration/AuthMigratorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.auth.migration; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.migration.v1.PlainPermissionManager; import org.apache.rocketmq.auth.migration.v1.AclConfig; import org.apache.rocketmq.auth.migration.v1.PlainAccessConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class AuthMigratorTest { @Mock private AuthenticationMetadataManager authenticationMetadataManager; @Mock private AuthorizationMetadataManager authorizationMetadataManager; @Mock private PlainPermissionManager plainPermissionManager; @Mock private AuthConfig authConfig; private AuthMigrator authMigrator; @Before public void setUp() throws IllegalAccessException { when(authConfig.isMigrateAuthFromV1Enabled()).thenReturn(true); authMigrator = new AuthMigrator(authConfig); FieldUtils.writeDeclaredField(authMigrator, "authenticationMetadataManager", authenticationMetadataManager, true); FieldUtils.writeDeclaredField(authMigrator, "authorizationMetadataManager", authorizationMetadataManager, true); FieldUtils.writeDeclaredField(authMigrator, "plainPermissionManager", plainPermissionManager, true); } @Test public void testMigrateNoAclConfigDoesNothing() { AclConfig aclConfig = mock(AclConfig.class); when(aclConfig.getPlainAccessConfigs()).thenReturn(new ArrayList<>()); when(plainPermissionManager.getAllAclConfig()).thenReturn(aclConfig); authMigrator.migrate(); verify(authConfig, times(1)).isMigrateAuthFromV1Enabled(); verify(plainPermissionManager, times(1)).getAllAclConfig(); verify(authenticationMetadataManager, never()).createUser(any()); verify(authorizationMetadataManager, never()).createAcl(any()); } @Test public void testMigrateWithAclConfigCreatesUserAndAcl() { AclConfig aclConfig = mock(AclConfig.class); List accessConfigs = new ArrayList<>(); accessConfigs.add(createPlainAccessConfig()); when(aclConfig.getPlainAccessConfigs()).thenReturn(accessConfigs); when(plainPermissionManager.getAllAclConfig()).thenReturn(aclConfig); when(authenticationMetadataManager.getUser(anyString())) .thenReturn(CompletableFuture.completedFuture(null)); when(authenticationMetadataManager.createUser(any())) .thenReturn(CompletableFuture.completedFuture(null)); authMigrator.migrate(); verify(authConfig, times(1)).isMigrateAuthFromV1Enabled(); verify(plainPermissionManager, times(1)).getAllAclConfig(); verify(authenticationMetadataManager, times(1)).createUser(any()); verify(authorizationMetadataManager, times(1)).createAcl(any()); } @Test public void testMigrateExceptionInMigrateLogsError() { PlainAccessConfig accessConfig = mock(PlainAccessConfig.class); when(accessConfig.getAccessKey()).thenReturn("testAk"); when(authenticationMetadataManager.createUser(any(User.class))) .thenThrow(new RuntimeException("Test Exception")); AclConfig aclConfig = mock(AclConfig.class); List accessConfigs = new ArrayList<>(); accessConfigs.add(accessConfig); when(aclConfig.getPlainAccessConfigs()).thenReturn(accessConfigs); when(plainPermissionManager.getAllAclConfig()).thenReturn(aclConfig); when(authenticationMetadataManager.getUser(anyString())) .thenReturn(CompletableFuture.completedFuture(null)); try { authMigrator.migrate(); verify(authConfig, times(1)).isMigrateAuthFromV1Enabled(); verify(plainPermissionManager, times(1)).getAllAclConfig(); verify(authenticationMetadataManager, times(1)).createUser(any()); verify(authorizationMetadataManager, never()).createAcl(any()); } catch (final RuntimeException ex) { assertEquals("Test Exception", ex.getMessage()); } } private PlainAccessConfig createPlainAccessConfig() { PlainAccessConfig result = mock(PlainAccessConfig.class); when(result.getAccessKey()).thenReturn("testAk"); when(result.getSecretKey()).thenReturn("testSk"); when(result.isAdmin()).thenReturn(false); when(result.getTopicPerms()).thenReturn(new ArrayList<>()); when(result.getGroupPerms()).thenReturn(new ArrayList<>()); when(result.getDefaultTopicPerm()).thenReturn("PUB"); when(result.getDefaultGroupPerm()).thenReturn(null); return result; } } ================================================ FILE: bazel/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ================================================ FILE: bazel/GenTestRules.bzl ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """Generate java test rules from given test_files. Instead of having to create one test rule per test in the BUILD file, this rule provides a handy way to create a bunch of test rules for the specified test files. """ def GenTestRules( name, test_files, deps, exclude_tests = [], default_test_size = "small", small_tests = [], medium_tests = [], large_tests = [], enormous_tests = [], resources = [], data = [], flaky_tests = [], tags = [], prefix = "", jvm_flags = [], args = [], visibility = None, shard_count = 1): for test in _get_test_names(test_files): if test in exclude_tests: continue test_size = default_test_size if test in small_tests: test_size = "small" if test in medium_tests: test_size = "medium" if test in large_tests: test_size = "large" if test in enormous_tests: test_size = "enormous" flaky = 0 if (test in flaky_tests) or ("flaky" in tags): flaky = 1 java_class = _package_from_path( native.package_name() + "/" + _strip_right(test, ".java"), ) package = java_class[:java_class.rfind(".")] native.java_test( name = prefix + test, runtime_deps = deps, resources = resources, size = test_size, jvm_flags = jvm_flags + ["-Dbuild.bazel=true"], args = args, flaky = flaky, tags = tags, test_class = java_class, visibility = visibility, shard_count = shard_count, data = data, ) def _get_test_names(test_files): test_names = [] for test_file in test_files: if not test_file.endswith("Test.java") and not test_file.endswith("IT.java"): continue test_names += [test_file[:-5]] return test_names def _package_from_path(package_path, src_impls = None): src_impls = src_impls or ["javatests/", "java/"] for src_impl in src_impls: if not src_impl.endswith("/"): src_impl += "/" index = _index_of_end(package_path, src_impl) if index >= 0: package_path = package_path[index:] break return package_path.replace("/", ".") def _strip_right(str, suffix): """Returns str without the suffix if it ends with suffix.""" if str.endswith(suffix): return str[0:len(str) - len(suffix)] else: return str def _index_of_end(str, part): """If part is in str, return the index of the first character after part. Return -1 if part is not in str.""" index = str.find(part) if index >= 0: return index + len(part) return -1 ================================================ FILE: broker/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "broker", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//auth", "//client", "//common", "//filter", "//remoting", "//srvutil", "//store", "//tieredstore", "@maven//:org_slf4j_slf4j_api", "@maven//:ch_qos_logback_logback_classic", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_github_luben_zstd_jni", "@maven//:com_google_guava_guava", "@maven//:com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru", "@maven//:commons_cli_commons_cli", "@maven//:commons_collections_commons_collections", "@maven//:commons_io_commons_io", "@maven//:commons_validator_commons_validator", "@maven//:io_netty_netty_all", "@maven//:io_openmessaging_storage_dledger", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_exporter_logging", "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_codec_commons_codec", "@maven//:org_lz4_lz4_java", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:org_slf4j_jul_to_slf4j", "@maven//:io_github_aliyunmq_rocketmq_shaded_slf4j_api_bridge", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", "@maven//:net_java_dev_jna_jna", "@maven//:com_github_ben_manes_caffeine_caffeine", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), resources = [ "src/test/resources/META-INF/service/org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener", "src/test/resources/META-INF/service/org.apache.rocketmq.broker.transaction.TransactionalMessageService", "src/test/resources/rmq.logback-test.xml", ], visibility = ["//visibility:public"], deps = [ ":broker", "//:test_deps", "//auth", "//client", "//common", "//filter", "//remoting", "//store", "//tieredstore", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:org_slf4j_slf4j_api", "@maven//:com_google_guava_guava", "@maven//:io_netty_netty_all", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_codec_commons_codec", "@maven//:commons_io_commons_io", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:org_powermock_powermock_core", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", "@maven//:commons_collections_commons_collections", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:com_github_ben_manes_caffeine_caffeine", ], ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), exclude_tests = [ # These tests are extremely slow and flaky, exclude them before they are properly fixed. "src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerRegisterTest", "src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest", ], deps = [ ":tests", ], ) ================================================ FILE: broker/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-broker rocketmq-broker ${project.version} ${basedir}/.. ${project.groupId} rocketmq-remoting ${project.groupId} rocketmq-store ${project.groupId} rocketmq-tiered-store io.github.aliyunmq rocketmq-slf4j-api io.github.aliyunmq rocketmq-logback-classic ${project.groupId} rocketmq-client ${project.groupId} rocketmq-srvutil ${project.groupId} rocketmq-filter org.apache.rocketmq rocketmq-auth commons-io commons-io org.javassist javassist org.bouncycastle bcpkix-jdk18on com.googlecode.concurrentlinkedhashmap concurrentlinkedhashmap-lru io.github.aliyunmq rocketmq-shaded-slf4j-api-bridge org.slf4j jul-to-slf4j maven-surefire-plugin ${maven-surefire-plugin.version} 1 false ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import java.net.InetSocketAddress; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.auth.migration.AuthMigrator; import org.apache.rocketmq.broker.auth.pipeline.AuthenticationPipeline; import org.apache.rocketmq.broker.auth.pipeline.AuthorizationPipeline; import org.apache.rocketmq.broker.client.ClientHousekeepingService; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.client.DefaultConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.broker.client.net.Broker2Client; import org.apache.rocketmq.broker.client.rebalance.RebalanceLockManager; import org.apache.rocketmq.broker.coldctr.ColdDataCgCtrService; import org.apache.rocketmq.broker.coldctr.ColdDataPullRequestHoldService; import org.apache.rocketmq.broker.config.v1.RocksDBConsumerOffsetManager; import org.apache.rocketmq.broker.config.v1.RocksDBLmqSubscriptionGroupManager; import org.apache.rocketmq.broker.config.v1.RocksDBLmqTopicConfigManager; import org.apache.rocketmq.broker.config.v1.RocksDBSubscriptionGroupManager; import org.apache.rocketmq.broker.config.v1.RocksDBTopicConfigManager; import org.apache.rocketmq.broker.config.v2.ConfigStorage; import org.apache.rocketmq.broker.config.v2.ConsumerOffsetManagerV2; import org.apache.rocketmq.broker.config.v2.SubscriptionGroupManagerV2; import org.apache.rocketmq.broker.config.v2.TopicConfigManagerV2; import org.apache.rocketmq.broker.controller.ReplicasManager; import org.apache.rocketmq.broker.dledger.DLedgerRoleChangeHandler; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.filter.CommitLogDispatcherCalcBitMap; import org.apache.rocketmq.broker.filter.ConsumerFilterManager; import org.apache.rocketmq.broker.latency.BrokerFastFailure; import org.apache.rocketmq.broker.lite.AbstractLiteLifecycleManager; import org.apache.rocketmq.broker.lite.LiteEventDispatcher; import org.apache.rocketmq.broker.lite.LiteSubscriptionRegistry; import org.apache.rocketmq.broker.lite.LiteSubscriptionRegistryImpl; import org.apache.rocketmq.broker.lite.LiteLifecycleManager; import org.apache.rocketmq.broker.lite.LiteSharding; import org.apache.rocketmq.broker.lite.LiteShardingImpl; import org.apache.rocketmq.broker.lite.RocksDBLiteLifecycleManager; import org.apache.rocketmq.broker.longpolling.LmqPullRequestHoldService; import org.apache.rocketmq.broker.longpolling.NotifyMessageArrivingListener; import org.apache.rocketmq.broker.longpolling.PullRequestHoldService; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook; import org.apache.rocketmq.broker.mqtrace.SendMessageHook; import org.apache.rocketmq.broker.offset.BroadcastOffsetManager; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.offset.LmqConsumerOffsetManager; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.plugin.BrokerAttachedPlugin; import org.apache.rocketmq.broker.pop.PopConsumerService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.pop.orderly.QueueLevelConsumerManager; import org.apache.rocketmq.broker.processor.AckMessageProcessor; import org.apache.rocketmq.broker.processor.AdminBrokerProcessor; import org.apache.rocketmq.broker.processor.ChangeInvisibleTimeProcessor; import org.apache.rocketmq.broker.processor.ClientManageProcessor; import org.apache.rocketmq.broker.processor.ConsumerManageProcessor; import org.apache.rocketmq.broker.processor.EndTransactionProcessor; import org.apache.rocketmq.broker.processor.LiteManagerProcessor; import org.apache.rocketmq.broker.processor.LiteSubscriptionCtlProcessor; import org.apache.rocketmq.broker.processor.NotificationProcessor; import org.apache.rocketmq.broker.processor.PeekMessageProcessor; import org.apache.rocketmq.broker.processor.PollingInfoProcessor; import org.apache.rocketmq.broker.processor.PopInflightMessageCounter; import org.apache.rocketmq.broker.processor.PopLiteMessageProcessor; import org.apache.rocketmq.broker.processor.PopMessageProcessor; import org.apache.rocketmq.broker.processor.PullMessageProcessor; import org.apache.rocketmq.broker.processor.QueryAssignmentProcessor; import org.apache.rocketmq.broker.processor.QueryMessageProcessor; import org.apache.rocketmq.broker.processor.RecallMessageProcessor; import org.apache.rocketmq.broker.processor.ReplyMessageProcessor; import org.apache.rocketmq.broker.processor.SendMessageProcessor; import org.apache.rocketmq.broker.schedule.ScheduleMessageService; import org.apache.rocketmq.broker.slave.SlaveSynchronize; import org.apache.rocketmq.broker.subscription.LmqSubscriptionGroupManager; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.LmqTopicConfigManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.broker.topic.TopicQueueMappingCleanService; import org.apache.rocketmq.broker.topic.TopicQueueMappingManager; import org.apache.rocketmq.broker.topic.TopicRouteInfoManager; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.broker.transaction.TransactionMetricsFlushService; import org.apache.rocketmq.broker.transaction.TransactionalMessageCheckService; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.broker.transaction.rocksdb.TransactionalMessageRocksDBService; import org.apache.rocketmq.broker.transaction.queue.DefaultTransactionalMessageCheckListener; import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge; import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl; import org.apache.rocketmq.broker.util.HookUtils; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.config.ConfigManagerVersion; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.stats.MomentStatsItem; import org.apache.rocketmq.common.utils.ServiceProvider; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.Configuration; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import org.apache.rocketmq.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.BrokerSyncInfo; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.RequestHeaderRegistry; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import org.apache.rocketmq.srvutil.FileWatchService; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageArrivingListener; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.dledger.DLedgerCommitLog; import org.apache.rocketmq.store.hook.PutMessageHook; import org.apache.rocketmq.store.hook.SendMessageBackHook; import org.apache.rocketmq.store.plugin.MessageStoreFactory; import org.apache.rocketmq.store.plugin.MessageStorePluginContext; import org.apache.rocketmq.store.stats.BrokerStats; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.stats.LmqBrokerStatsManager; import org.apache.rocketmq.store.timer.TimerCheckpoint; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.TimerMetrics; import org.apache.rocketmq.store.timer.rocksdb.TimerMessageRocksDBStore; import org.apache.rocketmq.store.transaction.TransMessageRocksDBStore; public class BrokerController { protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final Logger LOG_PROTECTION = LoggerFactory.getLogger(LoggerName.PROTECTION_LOGGER_NAME); private static final Logger LOG_WATER_MARK = LoggerFactory.getLogger(LoggerName.WATER_MARK_LOGGER_NAME); protected static final int HA_ADDRESS_MIN_LENGTH = 6; protected final BrokerConfig brokerConfig; private final NettyServerConfig nettyServerConfig; private final NettyClientConfig nettyClientConfig; protected final MessageStoreConfig messageStoreConfig; private final AuthConfig authConfig; protected ConsumerOffsetManager consumerOffsetManager; protected final BroadcastOffsetManager broadcastOffsetManager; protected final ConsumerManager consumerManager; protected final ConsumerFilterManager consumerFilterManager; protected final ConsumerOrderInfoManager consumerOrderInfoManager; protected final PopInflightMessageCounter popInflightMessageCounter; protected final PopConsumerService popConsumerService; protected final ProducerManager producerManager; protected final ScheduleMessageService scheduleMessageService; protected final ClientHousekeepingService clientHousekeepingService; protected final PullMessageProcessor pullMessageProcessor; protected final PeekMessageProcessor peekMessageProcessor; protected final PopMessageProcessor popMessageProcessor; protected final PopLiteMessageProcessor popLiteMessageProcessor; protected final AckMessageProcessor ackMessageProcessor; protected final ChangeInvisibleTimeProcessor changeInvisibleTimeProcessor; protected final NotificationProcessor notificationProcessor; protected final PollingInfoProcessor pollingInfoProcessor; protected final QueryAssignmentProcessor queryAssignmentProcessor; protected final ClientManageProcessor clientManageProcessor; protected final LiteSubscriptionCtlProcessor liteSubscriptionCtlProcessor; protected final LiteSharding liteSharding; protected final AbstractLiteLifecycleManager liteLifecycleManager; protected final LiteSubscriptionRegistry liteSubscriptionRegistry; protected final LiteEventDispatcher liteEventDispatcher; protected final LiteManagerProcessor liteManagerProcessor; protected final SendMessageProcessor sendMessageProcessor; protected final RecallMessageProcessor recallMessageProcessor; protected final ReplyMessageProcessor replyMessageProcessor; protected final PullRequestHoldService pullRequestHoldService; protected final MessageArrivingListener messageArrivingListener; protected final Broker2Client broker2Client; protected final ConsumerIdsChangeListener consumerIdsChangeListener; protected final EndTransactionProcessor endTransactionProcessor; private final RebalanceLockManager rebalanceLockManager = new RebalanceLockManager(); private final TopicRouteInfoManager topicRouteInfoManager; protected BrokerOuterAPI brokerOuterAPI; protected ScheduledExecutorService scheduledExecutorService; protected ScheduledExecutorService syncBrokerMemberGroupExecutorService; protected ScheduledExecutorService brokerHeartbeatExecutorService; protected final SlaveSynchronize slaveSynchronize; protected final BlockingQueue sendThreadPoolQueue; protected final BlockingQueue putThreadPoolQueue; protected final BlockingQueue ackThreadPoolQueue; protected final BlockingQueue pullThreadPoolQueue; protected final BlockingQueue litePullThreadPoolQueue; protected final BlockingQueue replyThreadPoolQueue; protected final BlockingQueue queryThreadPoolQueue; protected final BlockingQueue clientManagerThreadPoolQueue; protected final BlockingQueue heartbeatThreadPoolQueue; protected final BlockingQueue consumerManagerThreadPoolQueue; protected final BlockingQueue endTransactionThreadPoolQueue; protected final BlockingQueue adminBrokerThreadPoolQueue; protected final BlockingQueue loadBalanceThreadPoolQueue; protected BrokerStatsManager brokerStatsManager; protected final List sendMessageHookList = new ArrayList<>(); protected final List consumeMessageHookList = new ArrayList<>(); protected MessageStore messageStore; protected static final String TCP_REMOTING_SERVER = "TCP_REMOTING_SERVER"; protected static final String FAST_REMOTING_SERVER = "FAST_REMOTING_SERVER"; protected final Map remotingServerMap = new ConcurrentHashMap<>(); protected CountDownLatch remotingServerStartLatch; /** * If {Topic, SubscriptionGroup, Offset}ManagerV2 are used, config entries are stored in RocksDB. */ protected ConfigStorage configStorage; protected TopicConfigManager topicConfigManager; protected SubscriptionGroupManager subscriptionGroupManager; protected TopicQueueMappingManager topicQueueMappingManager; protected ExecutorService sendMessageExecutor; protected ExecutorService pullMessageExecutor; protected ExecutorService litePullMessageExecutor; protected ExecutorService putMessageFutureExecutor; protected ExecutorService ackMessageExecutor; protected ExecutorService replyMessageExecutor; protected ExecutorService queryMessageExecutor; protected ExecutorService adminBrokerExecutor; protected ExecutorService clientManageExecutor; protected ExecutorService heartbeatExecutor; protected ExecutorService consumerManageExecutor; protected ExecutorService loadBalanceExecutor; protected ExecutorService endTransactionExecutor; protected boolean updateMasterHAServerAddrPeriodically = false; private BrokerStats brokerStats; private InetSocketAddress storeHost; private TimerMessageStore timerMessageStore; private TimerMessageRocksDBStore timerMessageRocksDBStore; private TransMessageRocksDBStore transMessageRocksDBStore; private TimerCheckpoint timerCheckpoint; protected BrokerFastFailure brokerFastFailure; private Configuration configuration; protected TopicQueueMappingCleanService topicQueueMappingCleanService; protected FileWatchService fileWatchService; protected TransactionalMessageCheckService transactionalMessageCheckService; protected TransactionalMessageService transactionalMessageService; protected AbstractTransactionalMessageCheckListener transactionalMessageCheckListener; protected TransactionalMessageRocksDBService transactionalMessageRocksDBService; protected volatile boolean shutdown = false; protected ShutdownHook shutdownHook; private volatile boolean isScheduleServiceStart = false; private volatile boolean isTransactionCheckServiceStart = false; protected volatile BrokerMemberGroup brokerMemberGroup; protected EscapeBridge escapeBridge; protected List brokerAttachedPlugins = new ArrayList<>(); protected volatile long shouldStartTime; private BrokerPreOnlineService brokerPreOnlineService; protected volatile boolean isIsolated = false; protected volatile long minBrokerIdInGroup = 0; protected volatile String minBrokerAddrInGroup = null; private final Lock lock = new ReentrantLock(); protected final List> scheduledFutures = new ArrayList<>(); protected ReplicasManager replicasManager; private long lastSyncTimeMs = System.currentTimeMillis(); protected BrokerMetricsManager brokerMetricsManager; private ColdDataPullRequestHoldService coldDataPullRequestHoldService; private ColdDataCgCtrService coldDataCgCtrService; private TransactionMetricsFlushService transactionMetricsFlushService; private AuthenticationMetadataManager authenticationMetadataManager; private AuthorizationMetadataManager authorizationMetadataManager; private ConfigContext configContext; public BrokerController( final BrokerConfig brokerConfig, final NettyServerConfig nettyServerConfig, final NettyClientConfig nettyClientConfig, final MessageStoreConfig messageStoreConfig, final AuthConfig authConfig, final ShutdownHook shutdownHook ) { this(brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig, authConfig); this.shutdownHook = shutdownHook; } public BrokerController( final BrokerConfig brokerConfig, final MessageStoreConfig messageStoreConfig ) { this(brokerConfig, null, null, messageStoreConfig, null); } public BrokerController( final BrokerConfig brokerConfig, final MessageStoreConfig messageStoreConfig, final AuthConfig authConfig ) { this(brokerConfig, null, null, messageStoreConfig, authConfig); } public BrokerController( final BrokerConfig brokerConfig, final NettyServerConfig nettyServerConfig, final NettyClientConfig nettyClientConfig, final MessageStoreConfig messageStoreConfig ) { this(brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig, null); } public BrokerController( final BrokerConfig brokerConfig, final NettyServerConfig nettyServerConfig, final NettyClientConfig nettyClientConfig, final MessageStoreConfig messageStoreConfig, final AuthConfig authConfig ) { this.brokerConfig = brokerConfig; this.nettyServerConfig = nettyServerConfig; this.nettyClientConfig = nettyClientConfig; this.messageStoreConfig = messageStoreConfig; this.authConfig = authConfig; this.setStoreHost(new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), getListenPort())); this.brokerStatsManager = messageStoreConfig.isEnableLmq() ? new LmqBrokerStatsManager(this.brokerConfig) : new BrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()); this.broadcastOffsetManager = new BroadcastOffsetManager(this); if (ConfigManagerVersion.V2.getVersion().equals(brokerConfig.getConfigManagerVersion())) { this.configStorage = new ConfigStorage(messageStoreConfig); this.topicConfigManager = new TopicConfigManagerV2(this, configStorage); this.subscriptionGroupManager = new SubscriptionGroupManagerV2(this, configStorage); this.consumerOffsetManager = new ConsumerOffsetManagerV2(this, configStorage); } else if (this.messageStoreConfig.isEnableRocksDBStore()) { this.topicConfigManager = messageStoreConfig.isEnableLmq() ? new RocksDBLmqTopicConfigManager(this) : new RocksDBTopicConfigManager(this); this.subscriptionGroupManager = messageStoreConfig.isEnableLmq() ? new RocksDBLmqSubscriptionGroupManager(this) : new RocksDBSubscriptionGroupManager(this); this.consumerOffsetManager = new RocksDBConsumerOffsetManager(this); } else { this.topicConfigManager = messageStoreConfig.isEnableLmq() ? new LmqTopicConfigManager(this) : new TopicConfigManager(this); this.subscriptionGroupManager = messageStoreConfig.isEnableLmq() ? new LmqSubscriptionGroupManager(this) : new SubscriptionGroupManager(this); this.consumerOffsetManager = messageStoreConfig.isEnableLmq() ? new LmqConsumerOffsetManager(this) : new ConsumerOffsetManager(this); } this.topicQueueMappingManager = new TopicQueueMappingManager(this); this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig); this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(this.authConfig); this.topicRouteInfoManager = new TopicRouteInfoManager(this); this.liteSharding = new LiteShardingImpl(this, this.topicRouteInfoManager); this.liteLifecycleManager = this.messageStoreConfig.isEnableRocksDBStore() ? new RocksDBLiteLifecycleManager(this, this.liteSharding) : new LiteLifecycleManager(this, this.liteSharding); this.liteSubscriptionRegistry = new LiteSubscriptionRegistryImpl(this, liteLifecycleManager); this.liteSubscriptionCtlProcessor = new LiteSubscriptionCtlProcessor(this, liteSubscriptionRegistry); this.liteEventDispatcher = new LiteEventDispatcher(this, this.liteSubscriptionRegistry, this.liteLifecycleManager); this.liteManagerProcessor = new LiteManagerProcessor(this, liteLifecycleManager, liteSharding); this.pullMessageProcessor = new PullMessageProcessor(this); this.peekMessageProcessor = new PeekMessageProcessor(this); this.pullRequestHoldService = messageStoreConfig.isEnableLmq() ? new LmqPullRequestHoldService(this) : new PullRequestHoldService(this); this.popMessageProcessor = new PopMessageProcessor(this); this.popLiteMessageProcessor = new PopLiteMessageProcessor(this, this.liteEventDispatcher); this.notificationProcessor = new NotificationProcessor(this); this.pollingInfoProcessor = new PollingInfoProcessor(this); this.ackMessageProcessor = new AckMessageProcessor(this); this.changeInvisibleTimeProcessor = new ChangeInvisibleTimeProcessor(this); this.sendMessageProcessor = new SendMessageProcessor(this); this.recallMessageProcessor = new RecallMessageProcessor(this); this.replyMessageProcessor = new ReplyMessageProcessor(this); this.messageArrivingListener = new NotifyMessageArrivingListener(this.pullRequestHoldService, this.popMessageProcessor, this.notificationProcessor, this.liteEventDispatcher); this.consumerIdsChangeListener = new DefaultConsumerIdsChangeListener(this); this.consumerManager = new ConsumerManager(this.consumerIdsChangeListener, this.brokerStatsManager, this.brokerConfig); this.producerManager = new ProducerManager(this.brokerStatsManager); this.consumerFilterManager = new ConsumerFilterManager(this); this.consumerOrderInfoManager = new QueueLevelConsumerManager(this); this.popInflightMessageCounter = new PopInflightMessageCounter(this); this.popConsumerService = brokerConfig.isPopConsumerKVServiceInit() ? new PopConsumerService(this) : null; this.clientHousekeepingService = new ClientHousekeepingService(this); this.broker2Client = new Broker2Client(this); this.scheduleMessageService = new ScheduleMessageService(this); this.coldDataPullRequestHoldService = new ColdDataPullRequestHoldService(this); this.coldDataCgCtrService = new ColdDataCgCtrService(this); if (nettyClientConfig != null) { this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig, authConfig); } this.queryAssignmentProcessor = new QueryAssignmentProcessor(this); this.clientManageProcessor = new ClientManageProcessor(this); this.slaveSynchronize = new SlaveSynchronize(this); this.endTransactionProcessor = new EndTransactionProcessor(this); this.sendThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getSendThreadPoolQueueCapacity()); this.putThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getPutThreadPoolQueueCapacity()); this.pullThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getPullThreadPoolQueueCapacity()); this.litePullThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getLitePullThreadPoolQueueCapacity()); this.ackThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getAckThreadPoolQueueCapacity()); this.replyThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getReplyThreadPoolQueueCapacity()); this.queryThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getQueryThreadPoolQueueCapacity()); this.clientManagerThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getClientManagerThreadPoolQueueCapacity()); this.consumerManagerThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getConsumerManagerThreadPoolQueueCapacity()); this.heartbeatThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getHeartbeatThreadPoolQueueCapacity()); this.endTransactionThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getEndTransactionPoolQueueCapacity()); this.adminBrokerThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getAdminBrokerThreadPoolQueueCapacity()); this.loadBalanceThreadPoolQueue = new LinkedBlockingQueue<>(this.brokerConfig.getLoadBalanceThreadPoolQueueCapacity()); this.brokerFastFailure = new BrokerFastFailure(this); String brokerConfigPath; if (brokerConfig.getBrokerConfigPath() != null && !brokerConfig.getBrokerConfigPath().isEmpty()) { brokerConfigPath = brokerConfig.getBrokerConfigPath(); } else { brokerConfigPath = BrokerPathConfigHelper.getBrokerConfigPath(); } this.configuration = new Configuration( LOG, brokerConfigPath, this.brokerConfig, this.nettyServerConfig, this.nettyClientConfig, this.messageStoreConfig ); this.brokerStatsManager.setProducerStateGetter(new BrokerStatsManager.StateGetter() { @Override public boolean online(String instanceId, String group, String topic) { if (getTopicConfigManager().getTopicConfigTable().containsKey(NamespaceUtil.wrapNamespace(instanceId, topic))) { return getProducerManager().groupOnline(NamespaceUtil.wrapNamespace(instanceId, group)); } else { return getProducerManager().groupOnline(group); } } }); this.brokerStatsManager.setConsumerStateGetter(new BrokerStatsManager.StateGetter() { @Override public boolean online(String instanceId, String group, String topic) { String topicFullName = NamespaceUtil.wrapNamespace(instanceId, topic); if (getTopicConfigManager().getTopicConfigTable().containsKey(topicFullName)) { return getConsumerManager().findSubscriptionData(NamespaceUtil.wrapNamespace(instanceId, group), topicFullName) != null; } else { return getConsumerManager().findSubscriptionData(group, topic) != null; } } }); this.brokerMemberGroup = new BrokerMemberGroup(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.getBrokerName()); this.brokerMemberGroup.getBrokerAddrs().put(this.brokerConfig.getBrokerId(), this.getBrokerAddr()); this.escapeBridge = new EscapeBridge(this); if (this.brokerConfig.isEnableSlaveActingMaster() && !this.brokerConfig.isSkipPreOnline()) { this.brokerPreOnlineService = new BrokerPreOnlineService(this); } if (this.authConfig != null && this.authConfig.isMigrateAuthFromV1Enabled()) { new AuthMigrator(this.authConfig).migrate(); } } public AuthConfig getAuthConfig() { return authConfig; } public BrokerConfig getBrokerConfig() { return brokerConfig; } public NettyServerConfig getNettyServerConfig() { return nettyServerConfig; } public NettyClientConfig getNettyClientConfig() { return nettyClientConfig; } public BlockingQueue getPullThreadPoolQueue() { return pullThreadPoolQueue; } public BlockingQueue getQueryThreadPoolQueue() { return queryThreadPoolQueue; } public BrokerMetricsManager getBrokerMetricsManager() { return brokerMetricsManager; } public void setBrokerMetricsManager(BrokerMetricsManager brokerMetricsManager) { this.brokerMetricsManager = brokerMetricsManager; } protected void initializeRemotingServer() throws CloneNotSupportedException { NettyRemotingServer tcpRemotingServer = new NettyRemotingServer(this.nettyServerConfig, this.clientHousekeepingService); NettyServerConfig fastConfig = (NettyServerConfig) this.nettyServerConfig.clone(); int listeningPort = nettyServerConfig.getListenPort() - 2; if (listeningPort < 0) { listeningPort = 0; } fastConfig.setListenPort(listeningPort); NettyRemotingServer fastRemotingServer = new NettyRemotingServer(fastConfig, this.clientHousekeepingService); // Set RemotingMetricsManager on both remoting servers if (this.brokerMetricsManager != null) { tcpRemotingServer.setRemotingMetricsManager(this.brokerMetricsManager.getRemotingMetricsManager()); fastRemotingServer.setRemotingMetricsManager(this.brokerMetricsManager.getRemotingMetricsManager()); } remotingServerMap.put(TCP_REMOTING_SERVER, tcpRemotingServer); remotingServerMap.put(FAST_REMOTING_SERVER, fastRemotingServer); } /** * Initialize resources including remoting server and thread executors. */ protected void initializeResources() { this.scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("BrokerControllerScheduledThread", true, getBrokerIdentity())); this.sendMessageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getSendMessageThreadPoolNums(), this.brokerConfig.getSendMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.sendThreadPoolQueue, new ThreadFactoryImpl("SendMessageThread_", getBrokerIdentity())); this.pullMessageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getPullMessageThreadPoolNums(), this.brokerConfig.getPullMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.pullThreadPoolQueue, new ThreadFactoryImpl("PullMessageThread_", getBrokerIdentity())); this.litePullMessageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getLitePullMessageThreadPoolNums(), this.brokerConfig.getLitePullMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.litePullThreadPoolQueue, new ThreadFactoryImpl("LitePullMessageThread_", getBrokerIdentity())); this.putMessageFutureExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getPutMessageFutureThreadPoolNums(), this.brokerConfig.getPutMessageFutureThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.putThreadPoolQueue, new ThreadFactoryImpl("PutMessageThread_", getBrokerIdentity())); this.ackMessageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getAckMessageThreadPoolNums(), this.brokerConfig.getAckMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.ackThreadPoolQueue, new ThreadFactoryImpl("AckMessageThread_", getBrokerIdentity())); this.queryMessageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getQueryMessageThreadPoolNums(), this.brokerConfig.getQueryMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.queryThreadPoolQueue, new ThreadFactoryImpl("QueryMessageThread_", getBrokerIdentity())); this.adminBrokerExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getAdminBrokerThreadPoolNums(), this.brokerConfig.getAdminBrokerThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.adminBrokerThreadPoolQueue, new ThreadFactoryImpl("AdminBrokerThread_", getBrokerIdentity())); this.clientManageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getClientManageThreadPoolNums(), this.brokerConfig.getClientManageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientManagerThreadPoolQueue, new ThreadFactoryImpl("ClientManageThread_", getBrokerIdentity())); this.heartbeatExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getHeartbeatThreadPoolNums(), this.brokerConfig.getHeartbeatThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.heartbeatThreadPoolQueue, new ThreadFactoryImpl("HeartbeatThread_", true, getBrokerIdentity())); this.consumerManageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getConsumerManageThreadPoolNums(), this.brokerConfig.getConsumerManageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.consumerManagerThreadPoolQueue, new ThreadFactoryImpl("ConsumerManageThread_", true, getBrokerIdentity())); this.replyMessageExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getProcessReplyMessageThreadPoolNums(), this.brokerConfig.getProcessReplyMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.replyThreadPoolQueue, new ThreadFactoryImpl("ProcessReplyMessageThread_", getBrokerIdentity())); this.endTransactionExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getEndTransactionThreadPoolNums(), this.brokerConfig.getEndTransactionThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.endTransactionThreadPoolQueue, new ThreadFactoryImpl("EndTransactionThread_", getBrokerIdentity())); this.loadBalanceExecutor = ThreadUtils.newThreadPoolExecutor( this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(), this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.loadBalanceThreadPoolQueue, new ThreadFactoryImpl("LoadBalanceProcessorThread_", getBrokerIdentity())); this.syncBrokerMemberGroupExecutorService = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("BrokerControllerSyncBrokerScheduledThread", getBrokerIdentity())); this.brokerHeartbeatExecutorService = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("BrokerControllerHeartbeatScheduledThread", getBrokerIdentity())); this.topicQueueMappingCleanService = new TopicQueueMappingCleanService(this); } protected void initializeBrokerScheduledTasks() { final long initialDelay = UtilAll.computeNextMorningTimeMillis() - System.currentTimeMillis(); final long period = TimeUnit.DAYS.toMillis(1); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.getBrokerStats().record(); } catch (Throwable e) { LOG.error("BrokerController: failed to record broker stats", e); } } }, initialDelay, period, TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.consumerOffsetManager.persist(); } catch (Throwable e) { LOG.error( "BrokerController: failed to persist config file of consumerOffset", e); } } }, 1000 * 10, this.brokerConfig.getFlushConsumerOffsetInterval(), TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.consumerFilterManager.persist(); BrokerController.this.consumerOrderInfoManager.persist(); } catch (Throwable e) { LOG.error( "BrokerController: failed to persist config file of consumerFilter or consumerOrderInfo", e); } } }, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.protectBroker(); } catch (Throwable e) { LOG.error("BrokerController: failed to protectBroker", e); } } }, 3, 3, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.printWaterMark(); } catch (Throwable e) { LOG.error("BrokerController: failed to print broker watermark", e); } } }, 10, 1, TimeUnit.SECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.messageStore.getTimerMessageStore().getTimerMetrics() .cleanMetrics(BrokerController.this.topicConfigManager.getTopicConfigTable().keySet()); } catch (Throwable e) { LOG.error("BrokerController: failed to clean unused timer metrics.", e); } } }, 3, 3, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { LOG.info("Dispatch task fall behind commit log {}bytes", BrokerController.this.getMessageStore().dispatchBehindBytes()); } catch (Throwable e) { LOG.error("Failed to print dispatchBehindBytes", e); } } }, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS); if (!messageStoreConfig.isEnableDLegerCommitLog() && !messageStoreConfig.isDuplicationEnable() && !brokerConfig.isEnableControllerMode()) { if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) { if (this.messageStoreConfig.getHaMasterAddress() != null && this.messageStoreConfig.getHaMasterAddress().length() >= HA_ADDRESS_MIN_LENGTH) { this.messageStore.updateHaMasterAddress(this.messageStoreConfig.getHaMasterAddress()); this.updateMasterHAServerAddrPeriodically = false; } else { this.updateMasterHAServerAddrPeriodically = true; } this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { if (System.currentTimeMillis() - lastSyncTimeMs > 60 * 1000) { BrokerController.this.getSlaveSynchronize().syncAll(); lastSyncTimeMs = System.currentTimeMillis(); } //timer checkpoint, latency-sensitive, so sync it more frequently if (messageStoreConfig.isTimerWheelEnable()) { BrokerController.this.getSlaveSynchronize().syncTimerCheckPoint(); } } catch (Throwable e) { LOG.error("Failed to sync all config for slave.", e); } } }, 1000 * 10, 3 * 1000, TimeUnit.MILLISECONDS); } else { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.printMasterAndSlaveDiff(); } catch (Throwable e) { LOG.error("Failed to print diff of master and slave.", e); } } }, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS); } } if (this.brokerConfig.isEnableControllerMode()) { this.updateMasterHAServerAddrPeriodically = true; } } protected void initializeScheduledTasks() { initializeBrokerScheduledTasks(); if (this.brokerConfig.getNamesrvAddr() != null) { this.updateNamesrvAddr(); LOG.info("Set user specified name server address: {}", this.brokerConfig.getNamesrvAddr()); // also auto update namesrv if specify this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.updateNamesrvAddr(); } catch (Throwable e) { LOG.error("Failed to update nameServer address list", e); } } }, 1000 * 10, this.brokerConfig.getUpdateNameServerAddrPeriod(), TimeUnit.MILLISECONDS); } else if (this.brokerConfig.isFetchNamesrvAddrByAddressServer()) { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.brokerOuterAPI.fetchNameServerAddr(); } catch (Throwable e) { LOG.error("Failed to fetch nameServer address", e); } } }, 1000 * 10, this.brokerConfig.getFetchNamesrvAddrInterval(), TimeUnit.MILLISECONDS); } } private void updateNamesrvAddr() { if (this.brokerConfig.isFetchNameSrvAddrByDnsLookup()) { this.brokerOuterAPI.updateNameServerAddressListByDnsLookup(this.brokerConfig.getNamesrvAddr()); } else { this.brokerOuterAPI.updateNameServerAddressList(this.brokerConfig.getNamesrvAddr()); } } public boolean initializeMetadata() { boolean result = true; if (null != configStorage) { result = configStorage.start(); } result = result && this.topicConfigManager.load(); result = result && this.topicQueueMappingManager.load(); result = result && this.consumerOffsetManager.load(); result = result && this.subscriptionGroupManager.load(); result = result && this.consumerFilterManager.load(); result = result && this.consumerOrderInfoManager.load(); return result; } public boolean initializeMessageStore() { boolean result = true; try { DefaultMessageStore defaultMessageStore; if (this.messageStoreConfig.isEnableRocksDBStore()) { defaultMessageStore = new RocksDBMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener, this.brokerConfig, topicConfigManager.getTopicConfigTable()); } else { defaultMessageStore = new DefaultMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener, this.brokerConfig, topicConfigManager.getTopicConfigTable()); } if (messageStoreConfig.isEnableDLegerCommitLog()) { DLedgerRoleChangeHandler roleChangeHandler = new DLedgerRoleChangeHandler(this, defaultMessageStore); ((DLedgerCommitLog) defaultMessageStore.getCommitLog()) .getdLedgerServer().getDLedgerLeaderElector().addRoleChangeHandler(roleChangeHandler); } this.brokerStats = new BrokerStats(defaultMessageStore); // Load store plugin MessageStorePluginContext context = new MessageStorePluginContext( messageStoreConfig, brokerStatsManager, messageArrivingListener, brokerConfig, configuration); this.messageStore = MessageStoreFactory.build(context, defaultMessageStore); this.messageStore.getDispatcherList().addFirst(new CommitLogDispatcherCalcBitMap(this.brokerConfig, this.consumerFilterManager)); if (messageStoreConfig.isTimerWheelEnable()) { this.timerCheckpoint = new TimerCheckpoint(BrokerPathConfigHelper.getTimerCheckPath(messageStoreConfig.getStorePathRootDir())); TimerMetrics timerMetrics = new TimerMetrics(BrokerPathConfigHelper.getTimerMetricsPath(messageStoreConfig.getStorePathRootDir())); this.timerMessageStore = new TimerMessageStore(messageStore, messageStoreConfig, timerCheckpoint, timerMetrics, brokerStatsManager); this.timerMessageStore.registerEscapeBridgeHook(msg -> escapeBridge.putMessage(msg)); this.messageStore.setTimerMessageStore(this.timerMessageStore); if (messageStoreConfig.isTimerRocksDBEnable()) { this.timerMessageRocksDBStore = new TimerMessageRocksDBStore(messageStore, timerMetrics, brokerStatsManager); this.messageStore.setTimerMessageRocksDBStore(timerMessageRocksDBStore); } } if (messageStoreConfig.isTransRocksDBEnable()) { this.transMessageRocksDBStore = new TransMessageRocksDBStore(messageStore, brokerStatsManager, new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), this.getNettyServerConfig().getListenPort())); this.messageStore.setTransMessageRocksDBStore(transMessageRocksDBStore); } } catch (Exception e) { result = false; LOG.error("BrokerController#initialize: unexpected error occurs", e); } return result; } public boolean initialize() throws CloneNotSupportedException { boolean result = this.initializeMetadata(); if (!result) { return false; } result = this.initializeMessageStore(); if (!result) { return false; } return this.recoverAndInitService(); } public boolean recoverAndInitService() throws CloneNotSupportedException { boolean result = true; if (this.brokerConfig.isEnableControllerMode()) { this.replicasManager = new ReplicasManager(this); this.replicasManager.setFenced(true); } if (messageStore != null) { registerMessageStoreHook(); result = this.messageStore.load(); } if (messageStoreConfig.isTimerWheelEnable()) { result = result && this.timerMessageStore.load(); if (messageStoreConfig.isTimerRocksDBEnable()) { result = result && this.timerMessageRocksDBStore.load(); } } //scheduleMessageService load after messageStore load success result = result && this.scheduleMessageService.load(); for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) { if (brokerAttachedPlugin != null) { result = result && brokerAttachedPlugin.load(); } } this.brokerMetricsManager = new BrokerMetricsManager(this); if (result) { initializeRemotingServer(); initializeResources(); registerProcessor(); initializeScheduledTasks(); initialTransaction(); initialRpcHooks(); initialRequestPipeline(); initLiteService(); if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) { // Register a listener to reload SslContext try { fileWatchService = new FileWatchService( new String[] { TlsSystemConfig.tlsServerCertPath, TlsSystemConfig.tlsServerKeyPath, TlsSystemConfig.tlsServerTrustCertPath }, new FileWatchService.Listener() { boolean certChanged, keyChanged = false; @Override public void onChanged(String path) { if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) { LOG.info("The trust certificate changed, reload the ssl context"); reloadServerSslContext(); } if (path.equals(TlsSystemConfig.tlsServerCertPath)) { certChanged = true; } if (path.equals(TlsSystemConfig.tlsServerKeyPath)) { keyChanged = true; } if (certChanged && keyChanged) { LOG.info("The certificate and private key changed, reload the ssl context"); certChanged = keyChanged = false; reloadServerSslContext(); } } private void reloadServerSslContext() { for (Map.Entry entry : remotingServerMap.entrySet()) { RemotingServer remotingServer = entry.getValue(); if (remotingServer instanceof NettyRemotingServer) { ((NettyRemotingServer) remotingServer).loadSslContext(); } } } }); } catch (Exception e) { result = false; LOG.warn("FileWatchService created error, can't load the certificate dynamically"); } } } return result; } public void registerMessageStoreHook() { List putMessageHookList = messageStore.getPutMessageHookList(); putMessageHookList.add(new PutMessageHook() { @Override public String hookName() { return "checkBeforePutMessage"; } @Override public PutMessageResult executeBeforePutMessage(MessageExt msg) { return HookUtils.checkBeforePutMessage(BrokerController.this, msg); } }); putMessageHookList.add(new PutMessageHook() { @Override public String hookName() { return "innerBatchChecker"; } @Override public PutMessageResult executeBeforePutMessage(MessageExt msg) { if (msg instanceof MessageExtBrokerInner) { return HookUtils.checkInnerBatch(BrokerController.this, msg); } return null; } }); putMessageHookList.add(new PutMessageHook() { @Override public String hookName() { return "handleScheduleMessage"; } @Override public PutMessageResult executeBeforePutMessage(MessageExt msg) { if (msg instanceof MessageExtBrokerInner) { return HookUtils.handleScheduleMessage(BrokerController.this, (MessageExtBrokerInner) msg); } return null; } }); putMessageHookList.add(new PutMessageHook() { @Override public String hookName() { return "handleLmqQuota"; } @Override public PutMessageResult executeBeforePutMessage(MessageExt msg) { if (msg instanceof MessageExtBrokerInner) { return HookUtils.handleLmqQuota(BrokerController.this, (MessageExtBrokerInner) msg); } return null; } }); SendMessageBackHook sendMessageBackHook = new SendMessageBackHook() { @Override public boolean executeSendMessageBack(List msgList, String brokerName, String brokerAddr) { return HookUtils.sendMessageBack(BrokerController.this, msgList, brokerName, brokerAddr); } }; if (messageStore != null) { messageStore.setSendMessageBackHook(sendMessageBackHook); } } private void initialTransaction() { this.transactionalMessageService = ServiceProvider.loadClass(TransactionalMessageService.class); if (null == this.transactionalMessageService) { this.transactionalMessageService = new TransactionalMessageServiceImpl( new TransactionalMessageBridge(this, this.getMessageStore())); LOG.warn("Load default transaction message hook service: {}", TransactionalMessageServiceImpl.class.getSimpleName()); } this.transactionalMessageCheckListener = ServiceProvider.loadClass( AbstractTransactionalMessageCheckListener.class); if (null == this.transactionalMessageCheckListener) { this.transactionalMessageCheckListener = new DefaultTransactionalMessageCheckListener(); LOG.warn("Load default discard message hook service: {}", DefaultTransactionalMessageCheckListener.class.getSimpleName()); } this.transactionalMessageCheckListener.setBrokerController(this); this.transactionalMessageCheckService = new TransactionalMessageCheckService(this); this.transactionMetricsFlushService = new TransactionMetricsFlushService(this); this.transactionMetricsFlushService.start(); if (messageStoreConfig.isTransRocksDBEnable()) { this.transactionalMessageRocksDBService = new TransactionalMessageRocksDBService(messageStore, this); this.transactionalMessageRocksDBService.start(); } } private void initialRpcHooks() { List rpcHooks = ServiceProvider.load(RPCHook.class); if (rpcHooks == null || rpcHooks.isEmpty()) { return; } for (RPCHook rpcHook : rpcHooks) { this.registerServerRPCHook(rpcHook); } } private void initialRequestPipeline() { if (this.authConfig == null) { return; } RequestPipeline pipeline = (ctx, request) -> { }; // add pipeline // the last pipe add will execute at the first try { pipeline = pipeline.pipe(new AuthorizationPipeline(authConfig)) .pipe(new AuthenticationPipeline(authConfig)); this.setRequestPipeline(pipeline); } catch (Exception e) { throw new RuntimeException(e); } } private void initLiteService() { this.liteEventDispatcher.init(); this.liteLifecycleManager.init(); } public void registerProcessor() { RemotingServer remotingServer = remotingServerMap.get(TCP_REMOTING_SERVER); RemotingServer fastRemotingServer = remotingServerMap.get(FAST_REMOTING_SERVER); /* * SendMessageProcessor */ sendMessageProcessor.registerSendMessageHook(sendMessageHookList); sendMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageProcessor, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageProcessor, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.RECALL_MESSAGE, recallMessageProcessor, this.sendMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageProcessor, this.sendMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageProcessor, this.sendMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.RECALL_MESSAGE, recallMessageProcessor, this.sendMessageExecutor); /** * PullMessageProcessor */ remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, this.pullMessageExecutor); remotingServer.registerProcessor(RequestCode.LITE_PULL_MESSAGE, this.pullMessageProcessor, this.litePullMessageExecutor); this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); /** * PeekMessageProcessor */ remotingServer.registerProcessor(RequestCode.PEEK_MESSAGE, this.peekMessageProcessor, this.pullMessageExecutor); /** * PopMessageProcessor */ remotingServer.registerProcessor(RequestCode.POP_MESSAGE, this.popMessageProcessor, this.pullMessageExecutor); remotingServer.registerProcessor(RequestCode.POP_LITE_MESSAGE, this.popLiteMessageProcessor, this.pullMessageExecutor); /** * AckMessageProcessor */ remotingServer.registerProcessor(RequestCode.ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); remotingServer.registerProcessor(RequestCode.BATCH_ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.BATCH_ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); /** * ChangeInvisibleTimeProcessor */ remotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, this.changeInvisibleTimeProcessor, this.ackMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, this.changeInvisibleTimeProcessor, this.ackMessageExecutor); /** * notificationProcessor */ remotingServer.registerProcessor(RequestCode.NOTIFICATION, this.notificationProcessor, this.pullMessageExecutor); /** * pollingInfoProcessor */ remotingServer.registerProcessor(RequestCode.POLLING_INFO, this.pollingInfoProcessor, this.pullMessageExecutor); /** * ReplyMessageProcessor */ replyMessageProcessor.registerSendMessageHook(sendMessageHookList); remotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE, replyMessageProcessor, replyMessageExecutor); remotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE_V2, replyMessageProcessor, replyMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE, replyMessageProcessor, replyMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE_V2, replyMessageProcessor, replyMessageExecutor); /** * QueryMessageProcessor */ NettyRequestProcessor queryProcessor = new QueryMessageProcessor(this); remotingServer.registerProcessor(RequestCode.QUERY_MESSAGE, queryProcessor, this.queryMessageExecutor); remotingServer.registerProcessor(RequestCode.VIEW_MESSAGE_BY_ID, queryProcessor, this.queryMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.QUERY_MESSAGE, queryProcessor, this.queryMessageExecutor); fastRemotingServer.registerProcessor(RequestCode.VIEW_MESSAGE_BY_ID, queryProcessor, this.queryMessageExecutor); /** * ClientManageProcessor */ remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManageProcessor, this.heartbeatExecutor); remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManageProcessor, this.clientManageExecutor); remotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManageProcessor, this.clientManageExecutor); remotingServer.registerProcessor(RequestCode.LITE_SUBSCRIPTION_CTL, liteSubscriptionCtlProcessor, this.clientManageExecutor); fastRemotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManageProcessor, this.heartbeatExecutor); fastRemotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManageProcessor, this.clientManageExecutor); fastRemotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManageProcessor, this.clientManageExecutor); fastRemotingServer.registerProcessor(RequestCode.LITE_SUBSCRIPTION_CTL, liteSubscriptionCtlProcessor, this.clientManageExecutor); /** * ConsumerManageProcessor */ ConsumerManageProcessor consumerManageProcessor = new ConsumerManageProcessor(this); remotingServer.registerProcessor(RequestCode.GET_CONSUMER_LIST_BY_GROUP, consumerManageProcessor, this.consumerManageExecutor); remotingServer.registerProcessor(RequestCode.UPDATE_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor); remotingServer.registerProcessor(RequestCode.QUERY_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor); fastRemotingServer.registerProcessor(RequestCode.GET_CONSUMER_LIST_BY_GROUP, consumerManageProcessor, this.consumerManageExecutor); fastRemotingServer.registerProcessor(RequestCode.UPDATE_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor); fastRemotingServer.registerProcessor(RequestCode.QUERY_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor); /** * QueryAssignmentProcessor */ remotingServer.registerProcessor(RequestCode.QUERY_ASSIGNMENT, queryAssignmentProcessor, loadBalanceExecutor); fastRemotingServer.registerProcessor(RequestCode.QUERY_ASSIGNMENT, queryAssignmentProcessor, loadBalanceExecutor); remotingServer.registerProcessor(RequestCode.SET_MESSAGE_REQUEST_MODE, queryAssignmentProcessor, loadBalanceExecutor); fastRemotingServer.registerProcessor(RequestCode.SET_MESSAGE_REQUEST_MODE, queryAssignmentProcessor, loadBalanceExecutor); /** * EndTransactionProcessor */ remotingServer.registerProcessor(RequestCode.END_TRANSACTION, endTransactionProcessor, this.endTransactionExecutor); fastRemotingServer.registerProcessor(RequestCode.END_TRANSACTION, endTransactionProcessor, this.endTransactionExecutor); /* * lite admin */ remotingServer.registerProcessor(RequestCode.GET_BROKER_LITE_INFO, liteManagerProcessor, adminBrokerExecutor); remotingServer.registerProcessor(RequestCode.GET_PARENT_TOPIC_INFO, liteManagerProcessor, adminBrokerExecutor); remotingServer.registerProcessor(RequestCode.GET_LITE_TOPIC_INFO, liteManagerProcessor, adminBrokerExecutor); remotingServer.registerProcessor(RequestCode.GET_LITE_CLIENT_INFO, liteManagerProcessor, adminBrokerExecutor); remotingServer.registerProcessor(RequestCode.GET_LITE_GROUP_INFO, liteManagerProcessor, adminBrokerExecutor); remotingServer.registerProcessor(RequestCode.TRIGGER_LITE_DISPATCH, liteManagerProcessor, adminBrokerExecutor); fastRemotingServer.registerProcessor(RequestCode.GET_BROKER_LITE_INFO, liteManagerProcessor, adminBrokerExecutor); fastRemotingServer.registerProcessor(RequestCode.GET_PARENT_TOPIC_INFO, liteManagerProcessor, adminBrokerExecutor); fastRemotingServer.registerProcessor(RequestCode.GET_LITE_TOPIC_INFO, liteManagerProcessor, adminBrokerExecutor); fastRemotingServer.registerProcessor(RequestCode.GET_LITE_CLIENT_INFO, liteManagerProcessor, adminBrokerExecutor); fastRemotingServer.registerProcessor(RequestCode.GET_LITE_GROUP_INFO, liteManagerProcessor, adminBrokerExecutor); fastRemotingServer.registerProcessor(RequestCode.TRIGGER_LITE_DISPATCH, liteManagerProcessor, adminBrokerExecutor); /* * Default */ AdminBrokerProcessor adminProcessor = new AdminBrokerProcessor(this); remotingServer.registerDefaultProcessor(adminProcessor, this.adminBrokerExecutor); fastRemotingServer.registerDefaultProcessor(adminProcessor, this.adminBrokerExecutor); /* * Initialize the mapping of request codes to request headers. */ RequestHeaderRegistry.getInstance().initialize(); } public BrokerStats getBrokerStats() { return brokerStats; } public void setBrokerStats(BrokerStats brokerStats) { this.brokerStats = brokerStats; } public void protectBroker() { if (this.brokerConfig.isDisableConsumeIfConsumerReadSlowly()) { for (Map.Entry next : this.brokerStatsManager.getMomentStatsItemSetFallSize().getStatsItemTable().entrySet()) { final long fallBehindBytes = next.getValue().getValue().get(); if (fallBehindBytes > this.brokerConfig.getConsumerFallbehindThreshold()) { final String[] split = next.getValue().getStatsKey().split("@"); final String group = split[2]; LOG_PROTECTION.info("[PROTECT_BROKER] the consumer[{}] consume slowly, {} bytes, disable it", group, fallBehindBytes); this.subscriptionGroupManager.disableConsume(group); } } } } public long headSlowTimeMills(BlockingQueue q) { long slowTimeMills = 0; final Runnable peek = q.peek(); if (peek != null) { RequestTask rt = BrokerFastFailure.castRunnable(peek); slowTimeMills = rt == null ? 0 : this.messageStore.now() - rt.getCreateTimestamp(); } if (slowTimeMills < 0) { slowTimeMills = 0; } return slowTimeMills; } public long headSlowTimeMills4SendThreadPoolQueue() { return this.headSlowTimeMills(this.sendThreadPoolQueue); } public long headSlowTimeMills4PullThreadPoolQueue() { return this.headSlowTimeMills(this.pullThreadPoolQueue); } public long headSlowTimeMills4LitePullThreadPoolQueue() { return this.headSlowTimeMills(this.litePullThreadPoolQueue); } public long headSlowTimeMills4QueryThreadPoolQueue() { return this.headSlowTimeMills(this.queryThreadPoolQueue); } public long headSlowTimeMills4AckThreadPoolQueue() { return this.headSlowTimeMills(this.ackThreadPoolQueue); } public long headSlowTimeMills4EndTransactionThreadPoolQueue() { return this.headSlowTimeMills(this.endTransactionThreadPoolQueue); } public long headSlowTimeMills4ClientManagerThreadPoolQueue() { return this.headSlowTimeMills(this.clientManagerThreadPoolQueue); } public long headSlowTimeMills4HeartbeatThreadPoolQueue() { return this.headSlowTimeMills(this.heartbeatThreadPoolQueue); } public long headSlowTimeMills4AdminBrokerThreadPoolQueue() { return this.headSlowTimeMills(this.adminBrokerThreadPoolQueue); } public void printWaterMark() { logWaterMarkQueueInfo("Send", this.sendThreadPoolQueue, this::headSlowTimeMills4SendThreadPoolQueue); logWaterMarkQueueInfo("Pull", this.pullThreadPoolQueue, this::headSlowTimeMills4PullThreadPoolQueue); logWaterMarkQueueInfo("Query", this.queryThreadPoolQueue, this::headSlowTimeMills4QueryThreadPoolQueue); logWaterMarkQueueInfo("Lite Pull", this.litePullThreadPoolQueue, this::headSlowTimeMills4LitePullThreadPoolQueue); logWaterMarkQueueInfo("Transaction", this.endTransactionThreadPoolQueue, this::headSlowTimeMills4EndTransactionThreadPoolQueue); logWaterMarkQueueInfo("ClientManager", this.clientManagerThreadPoolQueue, this::headSlowTimeMills4ClientManagerThreadPoolQueue); logWaterMarkQueueInfo("Heartbeat", this.heartbeatThreadPoolQueue, this::headSlowTimeMills4HeartbeatThreadPoolQueue); logWaterMarkQueueInfo("Ack", this.ackThreadPoolQueue, this::headSlowTimeMills4AckThreadPoolQueue); logWaterMarkQueueInfo("Admin", this.adminBrokerThreadPoolQueue, this::headSlowTimeMills4AdminBrokerThreadPoolQueue); } private void logWaterMarkQueueInfo(String queueName, BlockingQueue queue, Supplier slowTimeSupplier) { LOG_WATER_MARK.info("[WATERMARK] {} Queue Size: {} SlowTimeMills: {}", queueName, queue.size(), slowTimeSupplier.get()); } public MessageStore getMessageStore() { return messageStore; } public void setMessageStore(MessageStore messageStore) { this.messageStore = messageStore; } protected void printMasterAndSlaveDiff() { if (messageStore.getHaService() != null && messageStore.getHaService().getConnectionCount().get() > 0) { long diff = this.messageStore.slaveFallBehindMuch(); LOG.info("CommitLog: slave fall behind master {}bytes", diff); } } public Broker2Client getBroker2Client() { return broker2Client; } public ConsumerManager getConsumerManager() { return consumerManager; } public ConsumerFilterManager getConsumerFilterManager() { return consumerFilterManager; } public ConsumerOrderInfoManager getConsumerOrderInfoManager() { return consumerOrderInfoManager; } public PopInflightMessageCounter getPopInflightMessageCounter() { return popInflightMessageCounter; } public PopConsumerService getPopConsumerService() { return popConsumerService; } public ConsumerOffsetManager getConsumerOffsetManager() { return consumerOffsetManager; } public void setConsumerOffsetManager(ConsumerOffsetManager consumerOffsetManager) { this.consumerOffsetManager = consumerOffsetManager; } public BroadcastOffsetManager getBroadcastOffsetManager() { return broadcastOffsetManager; } public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } public ProducerManager getProducerManager() { return producerManager; } public PullMessageProcessor getPullMessageProcessor() { return pullMessageProcessor; } public PullRequestHoldService getPullRequestHoldService() { return pullRequestHoldService; } public void setSubscriptionGroupManager(SubscriptionGroupManager subscriptionGroupManager) { this.subscriptionGroupManager = subscriptionGroupManager; } public SubscriptionGroupManager getSubscriptionGroupManager() { return subscriptionGroupManager; } public PopMessageProcessor getPopMessageProcessor() { return popMessageProcessor; } public PopLiteMessageProcessor getPopLiteMessageProcessor() { return popLiteMessageProcessor; } public NotificationProcessor getNotificationProcessor() { return notificationProcessor; } public TimerMessageStore getTimerMessageStore() { return timerMessageStore; } public void setTimerMessageStore(TimerMessageStore timerMessageStore) { this.timerMessageStore = timerMessageStore; } public TimerMessageRocksDBStore getTimerMessageRocksDBStore() { return timerMessageRocksDBStore; } public void setTimerMessageRocksDBStore(TimerMessageRocksDBStore timerMessageRocksDBStore) { this.timerMessageRocksDBStore = timerMessageRocksDBStore; } public AckMessageProcessor getAckMessageProcessor() { return ackMessageProcessor; } public ChangeInvisibleTimeProcessor getChangeInvisibleTimeProcessor() { return changeInvisibleTimeProcessor; } public LiteSubscriptionRegistry getLiteSubscriptionRegistry() { return liteSubscriptionRegistry; } public AbstractLiteLifecycleManager getLiteLifecycleManager() { return liteLifecycleManager; } protected void shutdownBasicService() { shutdown = true; this.unregisterBrokerAll(); if (this.shutdownHook != null) { this.shutdownHook.beforeShutdown(this); } for (Map.Entry entry : remotingServerMap.entrySet()) { RemotingServer remotingServer = entry.getValue(); if (remotingServer != null) { remotingServer.shutdown(); } } if (this.brokerMetricsManager != null) { this.brokerMetricsManager.shutdown(); } if (this.brokerStatsManager != null) { this.brokerStatsManager.shutdown(); } if (this.clientHousekeepingService != null) { this.clientHousekeepingService.shutdown(); } if (this.pullRequestHoldService != null) { this.pullRequestHoldService.shutdown(); } if (this.popMessageProcessor.getPopLongPollingService() != null) { this.popMessageProcessor.getPopLongPollingService().shutdown(); } if (this.popLiteMessageProcessor != null) { this.popLiteMessageProcessor.stopPopLiteLockManager(); if (this.popLiteMessageProcessor.getPopLiteLongPollingService() != null) { this.popLiteMessageProcessor.getPopLiteLongPollingService().shutdown(); } } if (this.popMessageProcessor.getQueueLockManager() != null) { this.popMessageProcessor.getQueueLockManager().shutdown(); } if (this.popMessageProcessor.getPopBufferMergeService() != null) { this.popMessageProcessor.getPopBufferMergeService().shutdown(); } if (this.ackMessageProcessor.getPopReviveServices() != null) { this.ackMessageProcessor.shutdownPopReviveService(); } if (this.transactionalMessageService != null) { this.transactionalMessageService.close(); } if (this.transactionalMessageCheckListener != null) { this.transactionalMessageCheckListener.shutdown(); } if (transactionalMessageCheckService != null) { this.transactionalMessageCheckService.shutdown(); } if (transactionMetricsFlushService != null) { this.transactionMetricsFlushService.shutdown(); } if (this.transactionalMessageRocksDBService != null) { this.transactionalMessageRocksDBService.shutdown(); } if (this.notificationProcessor != null) { this.notificationProcessor.getPopLongPollingService().shutdown(); } if (this.consumerIdsChangeListener != null) { this.consumerIdsChangeListener.shutdown(); } if (this.topicQueueMappingCleanService != null) { this.topicQueueMappingCleanService.shutdown(); } //it is better to make sure the timerMessageStore shutdown firstly if (this.timerMessageStore != null) { this.timerMessageStore.shutdown(); } if (this.timerMessageRocksDBStore != null) { this.timerMessageRocksDBStore.shutdown(); } if (this.transMessageRocksDBStore != null) { this.transMessageRocksDBStore.shutdown(); } if (this.fileWatchService != null) { this.fileWatchService.shutdown(); } if (this.broadcastOffsetManager != null) { this.broadcastOffsetManager.shutdown(); } if (this.replicasManager != null) { this.replicasManager.shutdown(); } shutdownScheduledExecutorService(this.scheduledExecutorService); if (this.sendMessageExecutor != null) { this.sendMessageExecutor.shutdown(); } if (this.litePullMessageExecutor != null) { this.litePullMessageExecutor.shutdown(); } if (this.pullMessageExecutor != null) { this.pullMessageExecutor.shutdown(); } if (this.replyMessageExecutor != null) { this.replyMessageExecutor.shutdown(); } if (this.putMessageFutureExecutor != null) { this.putMessageFutureExecutor.shutdown(); } if (this.ackMessageExecutor != null) { this.ackMessageExecutor.shutdown(); } if (this.adminBrokerExecutor != null) { this.adminBrokerExecutor.shutdown(); } if (this.brokerFastFailure != null) { this.brokerFastFailure.shutdown(); } if (this.consumerFilterManager != null) { this.consumerFilterManager.persist(); } if (this.scheduleMessageService != null) { this.scheduleMessageService.persist(); this.scheduleMessageService.shutdown(); } if (this.clientManageExecutor != null) { this.clientManageExecutor.shutdown(); } if (this.queryMessageExecutor != null) { this.queryMessageExecutor.shutdown(); } if (this.heartbeatExecutor != null) { this.heartbeatExecutor.shutdown(); } if (this.consumerManageExecutor != null) { this.consumerManageExecutor.shutdown(); } if (this.transactionalMessageCheckService != null) { this.transactionalMessageCheckService.shutdown(false); } if (this.loadBalanceExecutor != null) { this.loadBalanceExecutor.shutdown(); } if (this.endTransactionExecutor != null) { this.endTransactionExecutor.shutdown(); } if (this.transactionMetricsFlushService != null) { this.transactionMetricsFlushService.shutdown(); } if (this.escapeBridge != null) { this.escapeBridge.shutdown(); } if (this.topicRouteInfoManager != null) { this.topicRouteInfoManager.shutdown(); } if (this.brokerPreOnlineService != null && !this.brokerPreOnlineService.isStopped()) { this.brokerPreOnlineService.shutdown(); } if (this.coldDataPullRequestHoldService != null) { this.coldDataPullRequestHoldService.shutdown(); } if (this.coldDataCgCtrService != null) { this.coldDataCgCtrService.shutdown(); } if (this.liteEventDispatcher != null) { this.liteEventDispatcher.shutdown(); } if (this.liteLifecycleManager != null) { this.liteLifecycleManager.shutdown(); } if (this.liteSubscriptionRegistry != null) { this.liteSubscriptionRegistry.shutdown(); } shutdownScheduledExecutorService(this.syncBrokerMemberGroupExecutorService); shutdownScheduledExecutorService(this.brokerHeartbeatExecutorService); if (this.topicConfigManager != null) { this.topicConfigManager.persist(); this.topicConfigManager.stop(); } if (this.subscriptionGroupManager != null) { this.subscriptionGroupManager.persist(); this.subscriptionGroupManager.stop(); } if (this.consumerOffsetManager != null) { this.consumerOffsetManager.persist(); this.consumerOffsetManager.stop(); } if (this.consumerOrderInfoManager != null) { this.consumerOrderInfoManager.persist(); this.consumerOrderInfoManager.shutdown(); } if (this.configStorage != null) { this.configStorage.shutdown(); } if (this.authenticationMetadataManager != null) { this.authenticationMetadataManager.shutdown(); } if (this.authorizationMetadataManager != null) { this.authorizationMetadataManager.shutdown(); } for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) { if (brokerAttachedPlugin != null) { brokerAttachedPlugin.shutdown(); } } if (this.popConsumerService != null) { this.popConsumerService.shutdown(); } if (this.messageStore != null) { this.messageStore.shutdown(); } } public void shutdown() { shutdownBasicService(); for (ScheduledFuture scheduledFuture : scheduledFutures) { scheduledFuture.cancel(true); } if (this.brokerOuterAPI != null) { this.brokerOuterAPI.shutdown(); } } protected void shutdownScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) { if (scheduledExecutorService == null) { return; } scheduledExecutorService.shutdown(); try { scheduledExecutorService.awaitTermination(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException ignore) { BrokerController.LOG.warn("shutdown ScheduledExecutorService was Interrupted! ", ignore); Thread.currentThread().interrupt(); } } protected void unregisterBrokerAll() { this.brokerOuterAPI.unregisterBrokerAll( this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId()); } public String getBrokerAddr() { return this.brokerConfig.getBrokerIP1() + ":" + this.nettyServerConfig.getListenPort(); } protected void startBasicService() throws Exception { if (this.messageStore != null) { this.messageStore.start(); } if (this.timerMessageStore != null) { this.timerMessageStore.start(); } if (this.timerMessageRocksDBStore != null && this.messageStoreConfig.isTimerRocksDBEnable()) { this.timerMessageRocksDBStore.start(); } if (this.replicasManager != null) { this.replicasManager.start(); } if (remotingServerStartLatch != null) { remotingServerStartLatch.await(); } for (Map.Entry entry : remotingServerMap.entrySet()) { RemotingServer remotingServer = entry.getValue(); if (remotingServer != null) { remotingServer.start(); if (TCP_REMOTING_SERVER.equals(entry.getKey())) { // In test scenarios where it is up to OS to pick up an available port, set the listening port back to config if (null != nettyServerConfig && 0 == nettyServerConfig.getListenPort()) { nettyServerConfig.setListenPort(remotingServer.localListenPort()); } } } } this.storeHost = new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), this.getNettyServerConfig().getListenPort()); for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) { if (brokerAttachedPlugin != null) { brokerAttachedPlugin.start(); } } if (this.popMessageProcessor != null) { this.popMessageProcessor.getPopLongPollingService().start(); if (brokerConfig.isPopConsumerFSServiceInit()) { this.popMessageProcessor.getPopBufferMergeService().start(); } this.popMessageProcessor.getQueueLockManager().start(); } if (this.popLiteMessageProcessor != null) { this.popLiteMessageProcessor.startPopLiteLockManager(); if (this.popLiteMessageProcessor.getPopLiteLongPollingService() != null) { this.popLiteMessageProcessor.getPopLiteLongPollingService().start(); } } if (this.ackMessageProcessor != null) { if (brokerConfig.isPopConsumerFSServiceInit()) { this.ackMessageProcessor.startPopReviveService(); } } if (this.notificationProcessor != null) { this.notificationProcessor.getPopLongPollingService().start(); } if (this.popConsumerService != null) { this.popConsumerService.start(); } if (this.topicQueueMappingCleanService != null) { this.topicQueueMappingCleanService.start(); } if (this.fileWatchService != null) { this.fileWatchService.start(); } if (this.pullRequestHoldService != null) { this.pullRequestHoldService.start(); } if (this.clientHousekeepingService != null) { this.clientHousekeepingService.start(); } if (this.brokerStatsManager != null) { this.brokerStatsManager.start(); } if (this.brokerFastFailure != null) { this.brokerFastFailure.start(); } if (this.broadcastOffsetManager != null) { this.broadcastOffsetManager.start(); } if (this.escapeBridge != null) { this.escapeBridge.start(); } if (this.topicRouteInfoManager != null) { this.topicRouteInfoManager.start(); } if (this.brokerPreOnlineService != null) { this.brokerPreOnlineService.start(); } if (this.coldDataPullRequestHoldService != null) { this.coldDataPullRequestHoldService.start(); } if (this.coldDataCgCtrService != null) { this.coldDataCgCtrService.start(); } if (this.liteEventDispatcher != null) { this.liteEventDispatcher.start(); } if (this.liteLifecycleManager != null) { this.liteLifecycleManager.start(); } if (this.liteSubscriptionRegistry != null) { this.liteSubscriptionRegistry.start(); } } public void start() throws Exception { this.shouldStartTime = System.currentTimeMillis() + messageStoreConfig.getDisappearTimeAfterStart(); if (messageStoreConfig.getTotalReplicas() > 1 && this.brokerConfig.isEnableSlaveActingMaster()) { isIsolated = true; } if (this.brokerOuterAPI != null) { this.brokerOuterAPI.start(); } startBasicService(); if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, true); } scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { 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()); } catch (Throwable e) { BrokerController.LOG.error("registerBrokerAll Exception", e); } } }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS)); if (this.brokerConfig.isEnableSlaveActingMaster()) { scheduleSendHeartbeat(); scheduledFutures.add(this.syncBrokerMemberGroupExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.syncBrokerMemberGroup(); } catch (Throwable e) { BrokerController.LOG.error("sync BrokerMemberGroup error. ", e); } } }, 1000, this.brokerConfig.getSyncBrokerMemberGroupPeriod(), TimeUnit.MILLISECONDS)); } if (this.brokerConfig.isEnableControllerMode()) { scheduleSendHeartbeat(); } if (brokerConfig.isSkipPreOnline()) { startServiceWithoutCondition(); } this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerController.this.brokerOuterAPI.refreshMetadata(); } catch (Exception e) { LOG.error("ScheduledTask refresh metadata exception", e); } } }, 10, 5, TimeUnit.SECONDS); } protected void scheduleSendHeartbeat() { scheduledFutures.add(this.brokerHeartbeatExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (isIsolated) { return; } try { BrokerController.this.sendHeartbeat(); } catch (Exception e) { BrokerController.LOG.error("sendHeartbeat Exception", e); } } }, 1000, brokerConfig.getBrokerHeartbeatInterval(), TimeUnit.MILLISECONDS)); } public synchronized void registerSingleTopicAll(final TopicConfig topicConfig) { TopicConfig tmpTopic = topicConfig; if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) { // Copy the topic config and modify the perm tmpTopic = new TopicConfig(topicConfig); tmpTopic.setPerm(topicConfig.getPerm() & this.brokerConfig.getBrokerPermission()); } this.brokerOuterAPI.registerSingleTopicAll(this.brokerConfig.getBrokerName(), tmpTopic, 3000); } public synchronized void registerIncrementBrokerData(TopicConfig topicConfig, DataVersion dataVersion) { this.registerIncrementBrokerData(Collections.singletonList(topicConfig), dataVersion); } public synchronized void registerIncrementBrokerData(List topicConfigList, DataVersion dataVersion) { if (topicConfigList == null || topicConfigList.isEmpty()) { return; } TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(dataVersion); ConcurrentMap topicConfigTable = topicConfigList.stream() .map(topicConfig -> { TopicConfig registerTopicConfig; if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) { registerTopicConfig = new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(), topicConfig.getPerm() & this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag()); } else { registerTopicConfig = new TopicConfig(topicConfig); } return registerTopicConfig; }) .collect(Collectors.toConcurrentMap(TopicConfig::getTopicName, Function.identity())); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigTable); Map topicQueueMappingInfoMap = topicConfigList.stream() .map(TopicConfig::getTopicName) .map(topicName -> Optional.ofNullable(this.topicQueueMappingManager.getTopicQueueMapping(topicName)) .map(info -> new AbstractMap.SimpleImmutableEntry<>(topicName, TopicQueueMappingDetail.cloneAsMappingInfo(info))) .orElse(null)) .filter(Objects::nonNull) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (!topicQueueMappingInfoMap.isEmpty()) { topicConfigSerializeWrapper.setTopicQueueMappingInfoMap(topicQueueMappingInfoMap); } doRegisterBrokerAll(true, false, topicConfigSerializeWrapper); } public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) { ConcurrentMap topicConfigMap = this.getTopicConfigManager().getTopicConfigTable(); ConcurrentHashMap topicConfigTable = new ConcurrentHashMap<>(); for (TopicConfig topicConfig : topicConfigMap.values()) { if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) { topicConfigTable.put(topicConfig.getTopicName(), new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(), topicConfig.getPerm() & getBrokerConfig().getBrokerPermission())); } else { topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } if (this.brokerConfig.isEnableSplitRegistration() && topicConfigTable.size() >= this.brokerConfig.getSplitRegistrationSize()) { TopicConfigAndMappingSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildSerializeWrapper(topicConfigTable); doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper); topicConfigTable.clear(); } } Map topicQueueMappingInfoMap = this.getTopicQueueMappingManager().getTopicQueueMappingTable().entrySet().stream() .map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), TopicQueueMappingDetail.cloneAsMappingInfo(entry.getValue()))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); TopicConfigAndMappingSerializeWrapper topicConfigWrapper = this.getTopicConfigManager(). buildSerializeWrapper(topicConfigTable, topicQueueMappingInfoMap); if (this.brokerConfig.isEnableSplitRegistration() || forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.brokerConfig.getRegisterBrokerTimeoutMills(), this.brokerConfig.isInBrokerContainer())) { doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper); } } protected void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway, TopicConfigSerializeWrapper topicConfigWrapper) { if (shutdown) { BrokerController.LOG.info("BrokerController#doRegisterBrokerAll: broker has shutdown, no need to register any more."); return; } List registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll( this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.getHAServerAddr(), topicConfigWrapper, Lists.newArrayList(), oneway, this.brokerConfig.getRegisterBrokerTimeoutMills(), this.brokerConfig.isEnableSlaveActingMaster(), this.brokerConfig.isCompressedRegister(), this.brokerConfig.isEnableSlaveActingMaster() ? this.brokerConfig.getBrokerNotActiveTimeoutMillis() : null, this.getBrokerIdentity()); handleRegisterBrokerResult(registerBrokerResultList, checkOrderConfig); } protected void sendHeartbeat() { if (this.brokerConfig.isEnableControllerMode()) { this.replicasManager.sendHeartbeatToController(); } if (this.brokerConfig.isEnableSlaveActingMaster()) { if (this.brokerConfig.isCompatibleWithOldNameSrv()) { this.brokerOuterAPI.sendHeartbeatViaDataVersion( this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.brokerConfig.getSendHeartbeatTimeoutMillis(), this.getTopicConfigManager().getDataVersion(), this.brokerConfig.isInBrokerContainer()); } else { this.brokerOuterAPI.sendHeartbeat( this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.brokerConfig.getSendHeartbeatTimeoutMillis(), this.brokerConfig.isInBrokerContainer()); } } } protected void syncBrokerMemberGroup() { try { brokerMemberGroup = this.getBrokerOuterAPI() .syncBrokerMemberGroup(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.getBrokerName(), this.brokerConfig.isCompatibleWithOldNameSrv()); } catch (Exception e) { BrokerController.LOG.error("syncBrokerMemberGroup from namesrv failed, ", e); return; } if (brokerMemberGroup == null || brokerMemberGroup.getBrokerAddrs().size() == 0) { BrokerController.LOG.warn("Couldn't find any broker member from namesrv in {}/{}", this.brokerConfig.getBrokerClusterName(), this.brokerConfig.getBrokerName()); return; } this.messageStore.setAliveReplicaNumInGroup(calcAliveBrokerNumInGroup(brokerMemberGroup.getBrokerAddrs())); if (!this.isIsolated) { long minBrokerId = brokerMemberGroup.minimumBrokerId(); this.updateMinBroker(minBrokerId, brokerMemberGroup.getBrokerAddrs().get(minBrokerId)); } } private int calcAliveBrokerNumInGroup(Map brokerAddrTable) { if (brokerAddrTable.containsKey(this.brokerConfig.getBrokerId())) { return brokerAddrTable.size(); } else { return brokerAddrTable.size() + 1; } } protected void handleRegisterBrokerResult(List registerBrokerResultList, boolean checkOrderConfig) { for (RegisterBrokerResult registerBrokerResult : registerBrokerResultList) { if (registerBrokerResult != null) { if (this.updateMasterHAServerAddrPeriodically && registerBrokerResult.getHaServerAddr() != null) { this.messageStore.updateHaMasterAddress(registerBrokerResult.getHaServerAddr()); this.messageStore.updateMasterAddress(registerBrokerResult.getMasterAddr()); } this.slaveSynchronize.setMasterAddr(registerBrokerResult.getMasterAddr()); if (checkOrderConfig) { this.getTopicConfigManager().updateOrderTopicConfig(registerBrokerResult.getKvTable()); } break; } } } private boolean needRegister(final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final int timeoutMills, final boolean isInBrokerContainer) { TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper(); List changeList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigWrapper, timeoutMills, isInBrokerContainer); boolean needRegister = false; for (Boolean changed : changeList) { if (changed) { needRegister = true; break; } } return needRegister; } public void startService(long minBrokerId, String minBrokerAddr) { BrokerController.LOG.info("{} start service, min broker id is {}, min broker addr: {}", this.brokerConfig.getCanonicalName(), minBrokerId, minBrokerAddr); this.minBrokerIdInGroup = minBrokerId; this.minBrokerAddrInGroup = minBrokerAddr; this.changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == minBrokerId); this.registerBrokerAll(true, false, brokerConfig.isForceRegister()); isIsolated = false; } public void startServiceWithoutCondition() { BrokerController.LOG.info("{} start service", this.brokerConfig.getCanonicalName()); this.changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, brokerConfig.isForceRegister()); isIsolated = false; } public void stopService() { BrokerController.LOG.info("{} stop service", this.getBrokerConfig().getCanonicalName()); isIsolated = true; this.changeSpecialServiceStatus(false); } public boolean isSpecialServiceRunning() { if (isScheduleServiceStart() && isTransactionCheckServiceStart()) { return true; } return this.ackMessageProcessor != null && this.ackMessageProcessor.isPopReviveServiceRunning(); } private void onMasterOffline() { // close channels with master broker String masterAddr = this.slaveSynchronize.getMasterAddr(); if (masterAddr != null) { this.brokerOuterAPI.getRemotingClient().closeChannels( Arrays.asList(masterAddr, MixAll.brokerVIPChannel(true, masterAddr))); } // master not available, stop sync this.slaveSynchronize.setMasterAddr(null); this.messageStore.updateHaMasterAddress(null); } private void onMasterOnline(String masterAddr, String masterHaAddr) { boolean needSyncMasterFlushOffset = this.messageStore.getMasterFlushedOffset() == 0 && this.messageStoreConfig.isSyncMasterFlushOffsetWhenStartup(); if (masterHaAddr == null || needSyncMasterFlushOffset) { try { BrokerSyncInfo brokerSyncInfo = this.brokerOuterAPI.retrieveBrokerHaInfo(masterAddr); if (needSyncMasterFlushOffset) { LOG.info("Set master flush offset in slave to {}", brokerSyncInfo.getMasterFlushOffset()); this.messageStore.setMasterFlushedOffset(brokerSyncInfo.getMasterFlushOffset()); } if (masterHaAddr == null) { this.messageStore.updateHaMasterAddress(brokerSyncInfo.getMasterHaAddress()); this.messageStore.updateMasterAddress(brokerSyncInfo.getMasterAddress()); } } catch (Exception e) { LOG.error("retrieve master ha info exception, {}", e); } } // set master HA address. if (masterHaAddr != null) { this.messageStore.updateHaMasterAddress(masterHaAddr); } // wakeup HAClient this.messageStore.wakeupHAClient(); } private void onMinBrokerChange(long minBrokerId, String minBrokerAddr, String offlineBrokerAddr, String masterHaAddr) { LOG.info("Min broker changed, old: {}-{}, new {}-{}", this.minBrokerIdInGroup, this.minBrokerAddrInGroup, minBrokerId, minBrokerAddr); this.minBrokerIdInGroup = minBrokerId; this.minBrokerAddrInGroup = minBrokerAddr; this.changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == this.minBrokerIdInGroup); if (offlineBrokerAddr != null && offlineBrokerAddr.equals(this.slaveSynchronize.getMasterAddr())) { // master offline onMasterOffline(); } if (minBrokerId == MixAll.MASTER_ID && minBrokerAddr != null) { // master online onMasterOnline(minBrokerAddr, masterHaAddr); } // notify PullRequest on hold to pull from master. if (this.minBrokerIdInGroup == MixAll.MASTER_ID) { this.pullRequestHoldService.notifyMasterOnline(); } } public void updateMinBroker(long minBrokerId, String minBrokerAddr) { if (brokerConfig.isEnableSlaveActingMaster() && brokerConfig.getBrokerId() != MixAll.MASTER_ID) { if (lock.tryLock()) { try { if (minBrokerId != this.minBrokerIdInGroup) { String offlineBrokerAddr = null; if (minBrokerId > this.minBrokerIdInGroup) { offlineBrokerAddr = this.minBrokerAddrInGroup; } onMinBrokerChange(minBrokerId, minBrokerAddr, offlineBrokerAddr, null); } } finally { lock.unlock(); } } } } public void updateMinBroker(long minBrokerId, String minBrokerAddr, String offlineBrokerAddr, String masterHaAddr) { if (brokerConfig.isEnableSlaveActingMaster() && brokerConfig.getBrokerId() != MixAll.MASTER_ID) { try { if (lock.tryLock(3000, TimeUnit.MILLISECONDS)) { try { if (minBrokerId != this.minBrokerIdInGroup) { onMinBrokerChange(minBrokerId, minBrokerAddr, offlineBrokerAddr, masterHaAddr); } } finally { lock.unlock(); } } } catch (InterruptedException e) { LOG.error("Update min broker error, {}", e); } } } public void changeSpecialServiceStatus(boolean shouldStart) { for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) { if (brokerAttachedPlugin != null) { brokerAttachedPlugin.statusChanged(shouldStart); } } changeScheduleServiceStatus(shouldStart); changeTransactionCheckServiceStatus(shouldStart); if (this.ackMessageProcessor != null) { LOG.info("Set PopReviveService Status to {}", shouldStart); this.ackMessageProcessor.setPopReviveServiceStatus(shouldStart); } } private synchronized void changeTransactionCheckServiceStatus(boolean shouldStart) { if (isTransactionCheckServiceStart != shouldStart) { LOG.info("TransactionCheckService status changed to {}", shouldStart); if (shouldStart) { this.transactionalMessageCheckService.start(); } else { this.transactionalMessageCheckService.shutdown(true); } isTransactionCheckServiceStart = shouldStart; } } public synchronized void changeScheduleServiceStatus(boolean shouldStart) { if (isScheduleServiceStart != shouldStart) { LOG.info("ScheduleServiceStatus changed to {}", shouldStart); if (shouldStart) { this.scheduleMessageService.start(); } else { this.scheduleMessageService.stop(); } isScheduleServiceStart = shouldStart; if (timerMessageStore != null) { timerMessageStore.syncLastReadTimeMs(); timerMessageStore.setShouldRunningDequeue(shouldStart); } } } public MessageStore getMessageStoreByBrokerName(String brokerName) { if (this.brokerConfig.getBrokerName().equals(brokerName)) { return this.getMessageStore(); } return null; } public BrokerIdentity getBrokerIdentity() { if (messageStoreConfig.isEnableDLegerCommitLog()) { return new BrokerIdentity( brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), Integer.parseInt(messageStoreConfig.getdLegerSelfId().substring(1)), brokerConfig.isInBrokerContainer()); } else { return new BrokerIdentity( brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerConfig.getBrokerId(), brokerConfig.isInBrokerContainer()); } } public TopicConfigManager getTopicConfigManager() { return topicConfigManager; } public void setTopicConfigManager(TopicConfigManager topicConfigManager) { this.topicConfigManager = topicConfigManager; } public TopicQueueMappingManager getTopicQueueMappingManager() { return topicQueueMappingManager; } public AuthenticationMetadataManager getAuthenticationMetadataManager() { return authenticationMetadataManager; } @VisibleForTesting public void setAuthenticationMetadataManager( AuthenticationMetadataManager authenticationMetadataManager) { this.authenticationMetadataManager = authenticationMetadataManager; } public AuthorizationMetadataManager getAuthorizationMetadataManager() { return authorizationMetadataManager; } @VisibleForTesting public void setAuthorizationMetadataManager( AuthorizationMetadataManager authorizationMetadataManager) { this.authorizationMetadataManager = authorizationMetadataManager; } public String getHAServerAddr() { return this.brokerConfig.getBrokerIP2() + ":" + this.messageStoreConfig.getHaListenPort(); } public RebalanceLockManager getRebalanceLockManager() { return rebalanceLockManager; } public SlaveSynchronize getSlaveSynchronize() { return slaveSynchronize; } public ScheduledExecutorService getScheduledExecutorService() { return scheduledExecutorService; } public ExecutorService getPullMessageExecutor() { return pullMessageExecutor; } public ExecutorService getPutMessageFutureExecutor() { return putMessageFutureExecutor; } public void setPullMessageExecutor(ExecutorService pullMessageExecutor) { this.pullMessageExecutor = pullMessageExecutor; } public BlockingQueue getSendThreadPoolQueue() { return sendThreadPoolQueue; } public BlockingQueue getAckThreadPoolQueue() { return ackThreadPoolQueue; } public BrokerStatsManager getBrokerStatsManager() { return brokerStatsManager; } public void setBrokerStatsManager(BrokerStatsManager brokerStatsManager) { this.brokerStatsManager = brokerStatsManager; } public List getSendMessageHookList() { return sendMessageHookList; } public void registerSendMessageHook(final SendMessageHook hook) { this.sendMessageHookList.add(hook); LOG.info("register SendMessageHook Hook, {}", hook.hookName()); } public List getConsumeMessageHookList() { return consumeMessageHookList; } public void registerConsumeMessageHook(final ConsumeMessageHook hook) { this.consumeMessageHookList.add(hook); LOG.info("register ConsumeMessageHook Hook, {}", hook.hookName()); } public void registerServerRPCHook(RPCHook rpcHook) { for (Map.Entry entry : remotingServerMap.entrySet()) { RemotingServer remotingServer = entry.getValue(); if (remotingServer != null) { remotingServer.registerRPCHook(rpcHook); } } } public void setRequestPipeline(RequestPipeline pipeline) { for (Map.Entry entry : remotingServerMap.entrySet()) { RemotingServer remotingServer = entry.getValue(); if (remotingServer != null) { remotingServer.setRequestPipeline(pipeline); } } } public RemotingServer getRemotingServer() { return remotingServerMap.get(TCP_REMOTING_SERVER); } public void setRemotingServer(RemotingServer remotingServer) { remotingServerMap.put(TCP_REMOTING_SERVER, remotingServer); } public RemotingServer getFastRemotingServer() { return remotingServerMap.get(FAST_REMOTING_SERVER); } public void setFastRemotingServer(RemotingServer fastRemotingServer) { remotingServerMap.put(FAST_REMOTING_SERVER, fastRemotingServer); } public RemotingServer getRemotingServerByName(String name) { return remotingServerMap.get(name); } public void setRemotingServerByName(String name, RemotingServer remotingServer) { remotingServerMap.put(name, remotingServer); } public ClientHousekeepingService getClientHousekeepingService() { return clientHousekeepingService; } public CountDownLatch getRemotingServerStartLatch() { return remotingServerStartLatch; } public void setRemotingServerStartLatch(CountDownLatch remotingServerStartLatch) { this.remotingServerStartLatch = remotingServerStartLatch; } public void registerClientRPCHook(RPCHook rpcHook) { this.getBrokerOuterAPI().registerRPCHook(rpcHook); } public BrokerOuterAPI getBrokerOuterAPI() { return brokerOuterAPI; } public InetSocketAddress getStoreHost() { return storeHost; } public void setStoreHost(InetSocketAddress storeHost) { this.storeHost = storeHost; } public Configuration getConfiguration() { return this.configuration; } public BlockingQueue getHeartbeatThreadPoolQueue() { return heartbeatThreadPoolQueue; } public TransactionalMessageCheckService getTransactionalMessageCheckService() { return transactionalMessageCheckService; } public void setTransactionalMessageCheckService( TransactionalMessageCheckService transactionalMessageCheckService) { this.transactionalMessageCheckService = transactionalMessageCheckService; } public TransactionalMessageService getTransactionalMessageService() { return transactionalMessageService; } public void setTransactionalMessageService(TransactionalMessageService transactionalMessageService) { this.transactionalMessageService = transactionalMessageService; } public AbstractTransactionalMessageCheckListener getTransactionalMessageCheckListener() { return transactionalMessageCheckListener; } public void setTransactionalMessageCheckListener( AbstractTransactionalMessageCheckListener transactionalMessageCheckListener) { this.transactionalMessageCheckListener = transactionalMessageCheckListener; } public BlockingQueue getEndTransactionThreadPoolQueue() { return endTransactionThreadPoolQueue; } public ExecutorService getSendMessageExecutor() { return sendMessageExecutor; } public SendMessageProcessor getSendMessageProcessor() { return sendMessageProcessor; } public RecallMessageProcessor getRecallMessageProcessor() { return recallMessageProcessor; } public QueryAssignmentProcessor getQueryAssignmentProcessor() { return queryAssignmentProcessor; } public TopicQueueMappingCleanService getTopicQueueMappingCleanService() { return topicQueueMappingCleanService; } public ExecutorService getAdminBrokerExecutor() { return adminBrokerExecutor; } public BlockingQueue getLitePullThreadPoolQueue() { return litePullThreadPoolQueue; } public ShutdownHook getShutdownHook() { return shutdownHook; } public void setShutdownHook(ShutdownHook shutdownHook) { this.shutdownHook = shutdownHook; } public long getMinBrokerIdInGroup() { return this.brokerConfig.getBrokerId(); } public BrokerController peekMasterBroker() { return brokerConfig.getBrokerId() == MixAll.MASTER_ID ? this : null; } public BrokerMemberGroup getBrokerMemberGroup() { return this.brokerMemberGroup; } public int getListenPort() { return this.nettyServerConfig.getListenPort(); } public List getBrokerAttachedPlugins() { return brokerAttachedPlugins; } public EscapeBridge getEscapeBridge() { return escapeBridge; } public long getShouldStartTime() { return shouldStartTime; } public BrokerPreOnlineService getBrokerPreOnlineService() { return brokerPreOnlineService; } public EndTransactionProcessor getEndTransactionProcessor() { return endTransactionProcessor; } public boolean isScheduleServiceStart() { return isScheduleServiceStart; } public boolean isTransactionCheckServiceStart() { return isTransactionCheckServiceStart; } public ScheduleMessageService getScheduleMessageService() { return scheduleMessageService; } public ReplicasManager getReplicasManager() { return replicasManager; } public void setIsolated(boolean isolated) { isIsolated = isolated; } public boolean isIsolated() { return this.isIsolated; } public TimerCheckpoint getTimerCheckpoint() { return timerCheckpoint; } public TopicRouteInfoManager getTopicRouteInfoManager() { return this.topicRouteInfoManager; } public BlockingQueue getClientManagerThreadPoolQueue() { return clientManagerThreadPoolQueue; } public BlockingQueue getConsumerManagerThreadPoolQueue() { return consumerManagerThreadPoolQueue; } public BlockingQueue getAsyncPutThreadPoolQueue() { return putThreadPoolQueue; } public BlockingQueue getReplyThreadPoolQueue() { return replyThreadPoolQueue; } public BlockingQueue getAdminBrokerThreadPoolQueue() { return adminBrokerThreadPoolQueue; } public ColdDataPullRequestHoldService getColdDataPullRequestHoldService() { return coldDataPullRequestHoldService; } public void setColdDataPullRequestHoldService( ColdDataPullRequestHoldService coldDataPullRequestHoldService) { this.coldDataPullRequestHoldService = coldDataPullRequestHoldService; } public ColdDataCgCtrService getColdDataCgCtrService() { return coldDataCgCtrService; } public void setColdDataCgCtrService(ColdDataCgCtrService coldDataCgCtrService) { this.coldDataCgCtrService = coldDataCgCtrService; } public ConfigContext getConfigContext() { return configContext; } public void setConfigContext(ConfigContext configContext) { this.configContext = configContext; } public LiteEventDispatcher getLiteEventDispatcher() { return liteEventDispatcher; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.io.File; public class BrokerPathConfigHelper { private static String brokerConfigPath = System.getProperty("user.home") + File.separator + "store" + File.separator + "config" + File.separator + "broker.properties"; public static String getBrokerConfigPath() { return brokerConfigPath; } public static void setBrokerConfigPath(String path) { brokerConfigPath = path; } public static String getTopicConfigPath(final String rootDir) { return getConfigDir(rootDir) + "topics.json"; } public static String getTopicQueueMappingPath(final String rootDir) { return getConfigDir(rootDir) + "topicQueueMapping.json"; } public static String getConsumerOffsetPath(final String rootDir) { return getConfigDir(rootDir) + "consumerOffset.json"; } public static String getLmqConsumerOffsetPath(final String rootDir) { return getConfigDir(rootDir) + "lmqConsumerOffset.json"; } public static String getConsumerOrderInfoPath(final String rootDir) { return getConfigDir(rootDir) + "consumerOrderInfo.json"; } public static String getSubscriptionGroupPath(final String rootDir) { return getConfigDir(rootDir) + "subscriptionGroup.json"; } public static String getTimerCheckPath(final String rootDir) { return getConfigDir(rootDir) + "timercheck"; } public static String getTimerMetricsPath(final String rootDir) { return getConfigDir(rootDir) + "timermetrics"; } public static String getTransactionMetricsPath(final String rootDir) { return getConfigDir(rootDir) + "transactionMetrics"; } public static String getConsumerFilterPath(final String rootDir) { return getConfigDir(rootDir) + "consumerFilter.json"; } public static String getMessageRequestModePath(final String rootDir) { return getConfigDir(rootDir) + "messageRequestMode.json"; } private static String getConfigDir(final String rootDir) { return rootDir + File.separator + "config" + File.separator; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/BrokerPreOnlineService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.plugin.BrokerAttachedPlugin; import org.apache.rocketmq.broker.schedule.DelayOffsetSerializeWrapper; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.BrokerSyncInfo; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.ConsumerOffsetSerializeWrapper; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.ha.HAConnectionState; import org.apache.rocketmq.store.ha.HAConnectionStateNotificationRequest; import org.apache.rocketmq.store.timer.TimerCheckpoint; public class BrokerPreOnlineService extends ServiceThread { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private int waitBrokerIndex = 0; public BrokerPreOnlineService(BrokerController brokerController) { this.brokerController = brokerController; } @Override public String getServiceName() { if (this.brokerController != null && this.brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + BrokerPreOnlineService.class.getSimpleName(); } return BrokerPreOnlineService.class.getSimpleName(); } @Override public void run() { LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { if (!this.brokerController.isIsolated()) { LOGGER.info("broker {} is online", this.brokerController.getBrokerConfig().getCanonicalName()); break; } try { boolean isSuccess = this.prepareForBrokerOnline(); if (!isSuccess) { this.waitForRunning(1000); } else { break; } } catch (Exception e) { LOGGER.error("Broker preOnline error, ", e); } } LOGGER.info(this.getServiceName() + " service end"); } CompletableFuture waitForHaHandshakeComplete(String brokerAddr) { LOGGER.info("wait for handshake completion with {}", brokerAddr); HAConnectionStateNotificationRequest request = new HAConnectionStateNotificationRequest(HAConnectionState.TRANSFER, RemotingHelper.parseHostFromAddress(brokerAddr), true); if (this.brokerController.getMessageStore().getHaService() != null) { this.brokerController.getMessageStore().getHaService().putGroupConnectionStateRequest(request); } else { LOGGER.error("HAService is null, maybe broker config is wrong. For example, duplicationEnable is true"); request.getRequestFuture().complete(false); } return request.getRequestFuture(); } private boolean futureWaitAction(boolean result, BrokerMemberGroup brokerMemberGroup) { if (!result) { LOGGER.error("wait for handshake completion failed, HA connection lost"); return false; } if (this.brokerController.getBrokerConfig().getBrokerId() != MixAll.MASTER_ID) { LOGGER.info("slave preOnline complete, start service"); long minBrokerId = getMinBrokerId(brokerMemberGroup.getBrokerAddrs()); this.brokerController.startService(minBrokerId, brokerMemberGroup.getBrokerAddrs().get(minBrokerId)); } return true; } private boolean prepareForMasterOnline(BrokerMemberGroup brokerMemberGroup) { List brokerIdList = new ArrayList<>(brokerMemberGroup.getBrokerAddrs().keySet()); Collections.sort(brokerIdList); while (true) { if (waitBrokerIndex >= brokerIdList.size()) { LOGGER.info("master preOnline complete, start service"); this.brokerController.startService(MixAll.MASTER_ID, this.brokerController.getBrokerAddr()); return true; } String brokerAddrToWait = brokerMemberGroup.getBrokerAddrs().get(brokerIdList.get(waitBrokerIndex)); try { this.brokerController.getBrokerOuterAPI(). sendBrokerHaInfo(brokerAddrToWait, this.brokerController.getHAServerAddr(), this.brokerController.getMessageStore().getBrokerInitMaxOffset(), this.brokerController.getBrokerAddr()); } catch (Exception e) { LOGGER.error("send ha address to {} exception, {}", brokerAddrToWait, e); return false; } CompletableFuture haHandshakeFuture = waitForHaHandshakeComplete(brokerAddrToWait) .thenApply(result -> futureWaitAction(result, brokerMemberGroup)); try { if (!haHandshakeFuture.get()) { return false; } } catch (Exception e) { LOGGER.error("Wait handshake completion exception, {}", e); return false; } if (syncMetadataReverse(brokerAddrToWait)) { waitBrokerIndex++; } else { return false; } } } private boolean syncMetadataReverse(String brokerAddr) { try { LOGGER.info("Get metadata reverse from {}", brokerAddr); String delayOffset = this.brokerController.getBrokerOuterAPI().getAllDelayOffset(brokerAddr); DelayOffsetSerializeWrapper delayOffsetSerializeWrapper = DelayOffsetSerializeWrapper.fromJson(delayOffset, DelayOffsetSerializeWrapper.class); ConsumerOffsetSerializeWrapper consumerOffsetSerializeWrapper = this.brokerController.getBrokerOuterAPI().getAllConsumerOffset(brokerAddr); TimerCheckpoint timerCheckpoint = this.brokerController.getBrokerOuterAPI().getTimerCheckPoint(brokerAddr); if (null != consumerOffsetSerializeWrapper && brokerController.getConsumerOffsetManager().getDataVersion().compare(consumerOffsetSerializeWrapper.getDataVersion()) <= 0) { LOGGER.info("{}'s consumerOffset data version is larger than master broker, {}'s consumerOffset will be used.", brokerAddr, brokerAddr); this.brokerController.getConsumerOffsetManager().getOffsetTable() .putAll(consumerOffsetSerializeWrapper.getOffsetTable()); this.brokerController.getConsumerOffsetManager().getDataVersion().assignNewOne(consumerOffsetSerializeWrapper.getDataVersion()); this.brokerController.getConsumerOffsetManager().persist(); } if (null != delayOffset && brokerController.getScheduleMessageService().getDataVersion().compare(delayOffsetSerializeWrapper.getDataVersion()) <= 0) { LOGGER.info("{}'s scheduleMessageService data version is larger than master broker, {}'s delayOffset will be used.", brokerAddr, brokerAddr); String fileName = StorePathConfigHelper.getDelayOffsetStorePath(this.brokerController .getMessageStoreConfig().getStorePathRootDir()); try { MixAll.string2File(delayOffset, fileName); this.brokerController.getScheduleMessageService().load(); } catch (IOException e) { LOGGER.error("Persist file Exception, {}", fileName, e); } } if (null != this.brokerController.getTimerCheckpoint() && this.brokerController.getTimerCheckpoint().getDataVersion().compare(timerCheckpoint.getDataVersion()) <= 0) { LOGGER.info("{}'s timerCheckpoint data version is larger than master broker, {}'s timerCheckpoint will be used.", brokerAddr, brokerAddr); this.brokerController.getTimerCheckpoint().setLastReadTimeMs(timerCheckpoint.getLastReadTimeMs()); this.brokerController.getTimerCheckpoint().setMasterTimerQueueOffset(timerCheckpoint.getMasterTimerQueueOffset()); this.brokerController.getTimerCheckpoint().getDataVersion().assignNewOne(timerCheckpoint.getDataVersion()); this.brokerController.getTimerCheckpoint().flush(); } for (BrokerAttachedPlugin brokerAttachedPlugin : brokerController.getBrokerAttachedPlugins()) { if (brokerAttachedPlugin != null) { brokerAttachedPlugin.syncMetadataReverse(brokerAddr); } } } catch (Exception e) { LOGGER.error("GetMetadataReverse Failed", e); return false; } return true; } private boolean prepareForSlaveOnline(BrokerMemberGroup brokerMemberGroup) { BrokerSyncInfo brokerSyncInfo; try { brokerSyncInfo = this.brokerController.getBrokerOuterAPI() .retrieveBrokerHaInfo(brokerMemberGroup.getBrokerAddrs().get(MixAll.MASTER_ID)); } catch (Exception e) { LOGGER.error("retrieve master ha info exception, {}", e); return false; } if (this.brokerController.getMessageStore().getMasterFlushedOffset() == 0 && this.brokerController.getMessageStoreConfig().isSyncMasterFlushOffsetWhenStartup()) { LOGGER.info("Set master flush offset in slave to {}", brokerSyncInfo.getMasterFlushOffset()); this.brokerController.getMessageStore().setMasterFlushedOffset(brokerSyncInfo.getMasterFlushOffset()); } if (brokerSyncInfo.getMasterHaAddress() != null) { this.brokerController.getMessageStore().updateHaMasterAddress(brokerSyncInfo.getMasterHaAddress()); this.brokerController.getMessageStore().updateMasterAddress(brokerSyncInfo.getMasterAddress()); } else { LOGGER.info("fetch master ha address return null, start service directly"); long minBrokerId = getMinBrokerId(brokerMemberGroup.getBrokerAddrs()); this.brokerController.startService(minBrokerId, brokerMemberGroup.getBrokerAddrs().get(minBrokerId)); return true; } CompletableFuture haHandshakeFuture = waitForHaHandshakeComplete(brokerSyncInfo.getMasterHaAddress()) .thenApply(result -> futureWaitAction(result, brokerMemberGroup)); try { if (!haHandshakeFuture.get()) { return false; } } catch (Exception e) { LOGGER.error("Wait handshake completion exception, {}", e); return false; } return true; } private boolean prepareForBrokerOnline() { BrokerMemberGroup brokerMemberGroup; try { brokerMemberGroup = this.brokerController.getBrokerOuterAPI().syncBrokerMemberGroup( this.brokerController.getBrokerConfig().getBrokerClusterName(), this.brokerController.getBrokerConfig().getBrokerName(), this.brokerController.getBrokerConfig().isCompatibleWithOldNameSrv()); } catch (Exception e) { LOGGER.error("syncBrokerMemberGroup from namesrv error, start service failed, will try later, ", e); return false; } if (brokerMemberGroup != null && !brokerMemberGroup.getBrokerAddrs().isEmpty()) { long minBrokerId = getMinBrokerId(brokerMemberGroup.getBrokerAddrs()); if (this.brokerController.getBrokerConfig().getBrokerId() == MixAll.MASTER_ID) { return prepareForMasterOnline(brokerMemberGroup); } else if (minBrokerId == MixAll.MASTER_ID) { return prepareForSlaveOnline(brokerMemberGroup); } else { LOGGER.info("no master online, start service directly"); this.brokerController.startService(minBrokerId, brokerMemberGroup.getBrokerAddrs().get(minBrokerId)); } } else { LOGGER.info("no other broker online, will start service directly"); this.brokerController.startService(this.brokerController.getBrokerConfig().getBrokerId(), this.brokerController.getBrokerAddr()); } return true; } private long getMinBrokerId(Map brokerAddrMap) { Map brokerAddrMapCopy = new HashMap<>(brokerAddrMap); brokerAddrMapCopy.remove(this.brokerController.getBrokerConfig().getBrokerId()); if (!brokerAddrMapCopy.isEmpty()) { return Collections.min(brokerAddrMapCopy.keySet()); } return this.brokerController.getBrokerConfig().getBrokerId(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.io.BufferedInputStream; import java.io.File; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.store.config.MessageStoreConfig; public class BrokerStartup { public static Logger log; public static void main(String[] args) { start(createBrokerController(args)); } public static BrokerController start(BrokerController controller) { try { controller.start(); String tip = String.format("The broker[%s, %s] boot success. serializeType=%s", controller.getBrokerConfig().getBrokerName(), controller.getBrokerAddr(), RemotingCommand.getSerializeTypeConfigInThisServer()); if (null != controller.getBrokerConfig().getNamesrvAddr()) { tip += " and name server is " + controller.getBrokerConfig().getNamesrvAddr(); } log.info(tip); System.out.printf("%s%n", tip); return controller; } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } return null; } public static void shutdown(final BrokerController controller) { if (null != controller) { controller.shutdown(); } } public static ConfigContext parseCmdLine(String[] args) throws Exception { Options options = ServerUtil.buildCommandlineOptions(new Options()); CommandLine commandLine = ServerUtil.parseCmdLine( "mqbroker", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } ConfigContext configContext; String filePath = null; if (commandLine.hasOption('c')) { filePath = commandLine.getOptionValue('c'); } configContext = configFileToConfigContext(filePath); if (commandLine.hasOption('p') && configContext != null) { Logger console = LoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); MixAll.printObjectProperties(console, configContext.getBrokerConfig()); MixAll.printObjectProperties(console, configContext.getNettyServerConfig()); MixAll.printObjectProperties(console, configContext.getNettyClientConfig()); MixAll.printObjectProperties(console, configContext.getAuthConfig()); System.exit(0); } else if (commandLine.hasOption('m') && configContext != null) { Logger console = LoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); MixAll.printObjectProperties(console, configContext.getBrokerConfig(), true); MixAll.printObjectProperties(console, configContext.getNettyServerConfig(), true); MixAll.printObjectProperties(console, configContext.getNettyClientConfig(), true); MixAll.printObjectProperties(console, configContext.getAuthConfig(), true); System.exit(0); } assert configContext != null; MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), configContext.getBrokerConfig()); return configContext; } public static ConfigContext configFileToConfigContext(String filePath) throws Exception { SystemConfigFileHelper systemConfigFileHelper = new SystemConfigFileHelper(); BrokerConfig brokerConfig = new BrokerConfig(); NettyServerConfig nettyServerConfig = new NettyServerConfig(); NettyClientConfig nettyClientConfig = new NettyClientConfig(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); AuthConfig authConfig = new AuthConfig(); nettyServerConfig.setListenPort(10911); messageStoreConfig.setHaListenPort(0); Properties properties = new Properties(); if (StringUtils.isNotBlank(filePath)) { systemConfigFileHelper.setFile(filePath); BrokerPathConfigHelper.setBrokerConfigPath(filePath); properties = systemConfigFileHelper.loadConfig(); } if (properties != null) { properties2SystemEnv(properties); MixAll.properties2Object(properties, brokerConfig); MixAll.properties2Object(properties, nettyServerConfig); MixAll.properties2Object(properties, nettyClientConfig); MixAll.properties2Object(properties, messageStoreConfig); MixAll.properties2Object(properties, authConfig); } return new ConfigContext.Builder() .configFilePath(filePath) .properties(properties) .brokerConfig(brokerConfig) .messageStoreConfig(messageStoreConfig) .nettyServerConfig(nettyServerConfig) .nettyClientConfig(nettyClientConfig) .authConfig(authConfig) .build(); } public static BrokerController buildBrokerController(ConfigContext configContext) { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); BrokerConfig brokerConfig = configContext.getBrokerConfig(); MessageStoreConfig messageStoreConfig = configContext.getMessageStoreConfig(); NettyClientConfig nettyClientConfig = configContext.getNettyClientConfig(); NettyServerConfig nettyServerConfig = configContext.getNettyServerConfig(); AuthConfig authConfig = configContext.getAuthConfig(); Properties properties = configContext.getProperties(); if (null == brokerConfig.getRocketmqHome()) { System.out.printf("Please set the %s variable in your environment " + "to match the location of the RocketMQ installation", MixAll.ROCKETMQ_HOME_ENV); System.exit(-2); } // Validate namesrvAddr String namesrvAddr = brokerConfig.getNamesrvAddr(); if (StringUtils.isNotBlank(namesrvAddr)) { try { String[] addrArray = namesrvAddr.split(";"); for (String addr : addrArray) { NetworkUtil.string2SocketAddress(addr); } } catch (Exception e) { System.out.printf("The Name Server Address[%s] illegal, please set it as follows, " + "\"127.0.0.1:9876;192.168.0.1:9876\"%n", namesrvAddr); System.exit(-3); } } // Set broker role according to ha config if (!brokerConfig.isEnableControllerMode()) { switch (messageStoreConfig.getBrokerRole()) { case ASYNC_MASTER: case SYNC_MASTER: brokerConfig.setBrokerId(MixAll.MASTER_ID); break; case SLAVE: if (brokerConfig.getBrokerId() <= MixAll.MASTER_ID) { System.out.printf("Slave's brokerId must be > 0%n"); System.exit(-3); } break; default: break; } } if (messageStoreConfig.isEnableDLegerCommitLog()) { brokerConfig.setBrokerId(-1); } if (brokerConfig.isEnableControllerMode() && messageStoreConfig.isEnableDLegerCommitLog()) { System.out.printf("The config enableControllerMode and enableDLegerCommitLog cannot both be true.%n"); System.exit(-4); } if (messageStoreConfig.getHaListenPort() <= 0) { messageStoreConfig.setHaListenPort(nettyServerConfig.getListenPort() + 1); } brokerConfig.setInBrokerContainer(false); System.setProperty("brokerLogDir", ""); if (brokerConfig.isIsolateLogEnable()) { System.setProperty("brokerLogDir", brokerConfig.getBrokerName() + "_" + brokerConfig.getBrokerId()); } if (brokerConfig.isIsolateLogEnable() && messageStoreConfig.isEnableDLegerCommitLog()) { System.setProperty("brokerLogDir", brokerConfig.getBrokerName() + "_" + messageStoreConfig.getdLegerSelfId()); } log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); MixAll.printObjectProperties(log, brokerConfig); MixAll.printObjectProperties(log, nettyServerConfig); MixAll.printObjectProperties(log, nettyClientConfig); MixAll.printObjectProperties(log, messageStoreConfig); authConfig.setConfigName(brokerConfig.getBrokerName()); authConfig.setClusterName(brokerConfig.getBrokerClusterName()); authConfig.setAuthConfigPath(messageStoreConfig.getStorePathRootDir() + File.separator + "config"); final BrokerController controller = new BrokerController( brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig, authConfig); // Remember all configs to prevent discard controller.getConfiguration().registerConfig(properties); controller.setConfigContext(configContext); return controller; } public static Runnable buildShutdownHook(BrokerController brokerController) { return new Runnable() { private volatile boolean hasShutdown = false; private final AtomicInteger shutdownTimes = new AtomicInteger(0); @Override public void run() { synchronized (this) { log.info("Shutdown hook was invoked, {}", this.shutdownTimes.incrementAndGet()); if (!this.hasShutdown) { this.hasShutdown = true; long beginTime = System.currentTimeMillis(); brokerController.shutdown(); long consumingTimeTotal = System.currentTimeMillis() - beginTime; log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal); } } } }; } public static BrokerController createBrokerController(String[] args) { try { ConfigContext configContext = parseCmdLine(args); BrokerController controller = buildBrokerController(configContext); boolean initResult = controller.initialize(); if (!initResult) { controller.shutdown(); System.exit(-3); } Runtime.getRuntime().addShutdownHook(new Thread(buildShutdownHook(controller))); return controller; } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } return null; } private static void properties2SystemEnv(Properties properties) { if (properties == null) { return; } String rmqAddressServerDomain = properties.getProperty("rmqAddressServerDomain", MixAll.WS_DOMAIN_NAME); String rmqAddressServerSubGroup = properties.getProperty("rmqAddressServerSubGroup", MixAll.WS_DOMAIN_SUBGROUP); System.setProperty("rocketmq.namesrv.domain", rmqAddressServerDomain); System.setProperty("rocketmq.namesrv.domain.subgroup", rmqAddressServerSubGroup); } private static Options buildCommandlineOptions(final Options options) { Option opt = new Option("c", "configFile", true, "Broker config properties file"); opt.setRequired(false); options.addOption(opt); opt = new Option("p", "printConfigItem", false, "Print all config item"); opt.setRequired(false); options.addOption(opt); opt = new Option("m", "printImportantConfig", false, "Print important config item"); opt.setRequired(false); options.addOption(opt); return options; } public static class SystemConfigFileHelper { private static final Logger LOGGER = LoggerFactory.getLogger(SystemConfigFileHelper.class); private String file; public SystemConfigFileHelper() { } public Properties loadConfig() throws Exception { Properties properties = new Properties(); try (InputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(file)))) { properties.load(in); } return properties; } public void update(Properties properties) throws Exception { LOGGER.error("[SystemConfigFileHelper] update no thing."); } public void setFile(String file) { this.file = file; } public String getFile() { return file; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/ConfigContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.util.Properties; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; public class ConfigContext { private String configFilePath; private Properties properties; private BrokerConfig brokerConfig; private NettyServerConfig nettyServerConfig; private NettyClientConfig nettyClientConfig; private MessageStoreConfig messageStoreConfig; private AuthConfig authConfig; private ConfigContext(Builder builder) { this.configFilePath = builder.configFilePath; this.properties = builder.properties; this.brokerConfig = builder.brokerConfig; this.nettyServerConfig = builder.nettyServerConfig; this.nettyClientConfig = builder.nettyClientConfig; this.messageStoreConfig = builder.messageStoreConfig; this.authConfig = builder.authConfig; } public String getConfigFilePath() { return configFilePath; } public Properties getProperties() { return properties; } public BrokerConfig getBrokerConfig() { return brokerConfig; } public NettyServerConfig getNettyServerConfig() { return nettyServerConfig; } public NettyClientConfig getNettyClientConfig() { return nettyClientConfig; } public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } public AuthConfig getAuthConfig() { return authConfig; } public static class Builder { private String configFilePath; private Properties properties; private BrokerConfig brokerConfig; private NettyServerConfig nettyServerConfig; private NettyClientConfig nettyClientConfig; private MessageStoreConfig messageStoreConfig; private AuthConfig authConfig; public Builder() { } public Builder configFilePath(String configFilePath) { this.configFilePath = configFilePath; return this; } public Builder properties(Properties properties) { this.properties = properties; return this; } public Builder brokerConfig(BrokerConfig brokerConfig) { this.brokerConfig = brokerConfig; return this; } public Builder nettyServerConfig(NettyServerConfig nettyServerConfig) { this.nettyServerConfig = nettyServerConfig; return this; } public Builder nettyClientConfig(NettyClientConfig nettyClientConfig) { this.nettyClientConfig = nettyClientConfig; return this; } public Builder messageStoreConfig(MessageStoreConfig messageStoreConfig) { this.messageStoreConfig = messageStoreConfig; return this; } public Builder authConfig(AuthConfig authConfig) { this.authConfig = authConfig; return this; } public ConfigContext build() { return new ConfigContext(this); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/ShutdownHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; public interface ShutdownHook { /** * Code to execute before broker shutdown. * * @param controller broker to shutdown */ void beforeShutdown(BrokerController controller); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/auth/converter/AclConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.auth.converter; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Environment; import org.apache.rocketmq.auth.authorization.model.Policy; import org.apache.rocketmq.auth.authorization.model.PolicyEntry; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.remoting.protocol.body.AclInfo; public class AclConverter { public static Acl convertAcl(AclInfo aclInfo) { if (aclInfo == null) { return null; } Subject subject = Subject.of(aclInfo.getSubject()); List policies = new ArrayList<>(); for (AclInfo.PolicyInfo policy : aclInfo.getPolicies()) { PolicyType policyType = PolicyType.getByName(policy.getPolicyType()); List entryInfos = policy.getEntries(); if (CollectionUtils.isEmpty(entryInfos)) { continue; } List entries = new ArrayList<>(); for (AclInfo.PolicyEntryInfo entryInfo : entryInfos) { Resource resource = Resource.of(entryInfo.getResource()); List actions = new ArrayList<>(); for (String a : entryInfo.getActions()) { Action action = Action.getByName(a); if (action == null) { continue; } actions.add(action); } Environment environment = new Environment(); if (CollectionUtils.isNotEmpty(entryInfo.getSourceIps())) { environment.setSourceIps(entryInfo.getSourceIps()); } Decision decision = Decision.getByName(entryInfo.getDecision()); entries.add(PolicyEntry.of(resource, actions, environment, decision)); } policies.add(Policy.of(policyType, entries)); } return Acl.of(subject, policies); } public static List convertAcls(List acls) { if (CollectionUtils.isEmpty(acls)) { return null; } return acls.stream().map(AclConverter::convertAcl) .collect(Collectors.toList()); } public static AclInfo convertAcl(Acl acl) { if (acl == null) { return null; } AclInfo aclInfo = new AclInfo(); aclInfo.setSubject(acl.getSubject().getSubjectKey()); if (CollectionUtils.isEmpty(acl.getPolicies())) { return aclInfo; } List policyInfos = acl.getPolicies().stream() .map(AclConverter::convertPolicy) .collect(Collectors.toList()); aclInfo.setPolicies(policyInfos); return aclInfo; } private static AclInfo.PolicyInfo convertPolicy(Policy policy) { AclInfo.PolicyInfo policyInfo = new AclInfo.PolicyInfo(); if (policy.getPolicyType() != null) { policyInfo.setPolicyType(policy.getPolicyType().getName()); } if (CollectionUtils.isEmpty(policy.getEntries())) { return policyInfo; } List entryInfos = policy.getEntries().stream() .map(AclConverter::convertPolicyEntry).collect(Collectors.toList()); policyInfo.setEntries(entryInfos); return policyInfo; } private static AclInfo.PolicyEntryInfo convertPolicyEntry(PolicyEntry entry) { AclInfo.PolicyEntryInfo entryInfo = new AclInfo.PolicyEntryInfo(); entryInfo.setResource(entry.toResourceStr()); entryInfo.setActions(entry.toActionsStr()); if (entry.getEnvironment() != null) { entryInfo.setSourceIps(entry.getEnvironment().getSourceIps()); } entryInfo.setDecision(entry.getDecision().getName()); return entryInfo; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/auth/converter/UserConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.auth.converter; import java.util.List; import java.util.stream.Collectors; import org.apache.rocketmq.auth.authentication.enums.UserStatus; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.remoting.protocol.body.UserInfo; public class UserConverter { public static List convertUsers(List users) { return users.stream().map(UserConverter::convertUser) .collect(Collectors.toList()); } public static UserInfo convertUser(User user) { UserInfo result = new UserInfo(); result.setUsername(user.getUsername()); result.setPassword(user.getPassword()); if (user.getUserType() != null) { result.setUserType(user.getUserType().getName()); } if (user.getUserStatus() != null) { result.setUserStatus(user.getUserStatus().getName()); } return result; } public static User convertUser(UserInfo userInfo) { User result = new User(); result.setUsername(userInfo.getUsername()); result.setPassword(userInfo.getPassword()); result.setUserType(UserType.getByName(userInfo.getUserType())); result.setUserStatus(UserStatus.getByName(userInfo.getUserStatus())); return result; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/auth/pipeline/AuthenticationPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.auth.pipeline; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; public class AuthenticationPipeline implements RequestPipeline { protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final AuthConfig authConfig; private final AuthenticationEvaluator evaluator; public AuthenticationPipeline(AuthConfig authConfig) { this.authConfig = authConfig; this.evaluator = AuthenticationFactory.getEvaluator(authConfig); } @Override public void execute(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { if (!authConfig.isAuthenticationEnabled()) { return; } try { AuthenticationContext authenticationContext = newContext(ctx, request); evaluator.evaluate(authenticationContext); } catch (AuthenticationException ex) { throw new AbortProcessException(ResponseCode.NO_PERMISSION, ex.getMessage()); } catch (Throwable ex) { LOGGER.error("authenticate failed, request:{}", request, ex); throw ex; } } protected AuthenticationContext newContext(ChannelHandlerContext ctx, RemotingCommand request) { return AuthenticationFactory.newContext(authConfig, ctx, request); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/auth/pipeline/AuthorizationPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.auth.pipeline; import io.netty.channel.ChannelHandlerContext; import java.util.List; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; public class AuthorizationPipeline implements RequestPipeline { protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final AuthConfig authConfig; private final AuthorizationEvaluator evaluator; public AuthorizationPipeline(AuthConfig authConfig) { this.authConfig = authConfig; this.evaluator = AuthorizationFactory.getEvaluator(authConfig); } @Override public void execute(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { if (!authConfig.isAuthorizationEnabled()) { return; } try { List contexts = newContexts(ctx, request); evaluator.evaluate(contexts); } catch (AuthorizationException | AuthenticationException ex) { throw new AbortProcessException(ResponseCode.NO_PERMISSION, ex.getMessage()); } catch (Throwable ex) { LOGGER.error("authorization failed, request:{}", request, ex); throw ex; } } protected List newContexts(ChannelHandlerContext ctx, RemotingCommand request) { return AuthorizationFactory.newContexts(authConfig, ctx, request); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ClientChannelAttributeHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import io.netty.util.AttributeKey; import java.util.Arrays; import java.util.Collections; import java.util.List; public class ClientChannelAttributeHelper { private static final AttributeKey ATTR_CG = AttributeKey.valueOf("CHANNEL_CONSUMER_GROUP"); private static final AttributeKey ATTR_PG = AttributeKey.valueOf("CHANNEL_PRODUCER_GROUP"); private static final String SEPARATOR = "|"; public static void addProducerGroup(Channel channel, String group) { addGroup(channel, group, ATTR_PG); } public static void addConsumerGroup(Channel channel, String group) { addGroup(channel, group, ATTR_CG); } public static List getProducerGroups(Channel channel) { return getGroups(channel, ATTR_PG); } public static List getConsumerGroups(Channel channel) { return getGroups(channel, ATTR_CG); } private static void addGroup(Channel channel, String group, AttributeKey key) { if (null == channel || !channel.isActive()) { // no side effect if check active status. return; } if (null == group || group.length() == 0 || null == key) { return; } String groups = channel.attr(key).get(); if (null == groups) { channel.attr(key).set(group + SEPARATOR); } else { if (groups.contains(SEPARATOR + group + SEPARATOR)) { return; } else { channel.attr(key).compareAndSet(groups, groups + group + SEPARATOR); } } } private static List getGroups(Channel channel, AttributeKey key) { if (null == channel) { return Collections.emptyList(); } if (null == key) { return Collections.emptyList(); } String groups = channel.attr(key).get(); return null == groups ? Collections.emptyList() : Arrays.asList(groups.split("\\|")); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ClientChannelInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import org.apache.rocketmq.remoting.protocol.LanguageCode; public class ClientChannelInfo { private final Channel channel; private final String clientId; private final LanguageCode language; private final int version; private volatile long lastUpdateTimestamp = System.currentTimeMillis(); public ClientChannelInfo(Channel channel) { this(channel, null, null, 0); } public ClientChannelInfo(Channel channel, String clientId, LanguageCode language, int version) { this.channel = channel; this.clientId = clientId; this.language = language; this.version = version; } public Channel getChannel() { return channel; } public String getClientId() { return clientId; } public LanguageCode getLanguage() { return language; } public int getVersion() { return version; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((channel == null) ? 0 : channel.hashCode()); result = prime * result + ((clientId == null) ? 0 : clientId.hashCode()); result = prime * result + ((language == null) ? 0 : language.hashCode()); result = prime * result + (int) (lastUpdateTimestamp ^ (lastUpdateTimestamp >>> 32)); result = prime * result + version; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ClientChannelInfo other = (ClientChannelInfo) obj; if (channel == null) { if (other.channel != null) return false; } else if (this.channel != other.channel) { return false; } return true; } @Override public String toString() { return "ClientChannelInfo [channel=" + channel + ", clientId=" + clientId + ", language=" + language + ", version=" + version + ", lastUpdateTimestamp=" + lastUpdateTimestamp + "]"; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; public class ClientHousekeepingService implements ChannelEventListener { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private ScheduledExecutorService scheduledExecutorService; public ClientHousekeepingService(final BrokerController brokerController) { this.brokerController = brokerController; scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("ClientHousekeepingScheduledThread", brokerController.getBrokerIdentity())); } public void start() { this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { ClientHousekeepingService.this.scanExceptionChannel(); } catch (Throwable e) { log.error("Error occurred when scan not active client channels.", e); } }, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS); } private void scanExceptionChannel() { this.brokerController.getProducerManager().scanNotActiveChannel(); this.brokerController.getConsumerManager().scanNotActiveChannel(); } public void shutdown() { this.scheduledExecutorService.shutdown(); } @Override public void onChannelConnect(String remoteAddr, Channel channel) { this.brokerController.getBrokerStatsManager().incChannelConnectNum(); } @Override public void onChannelClose(String remoteAddr, Channel channel) { this.brokerController.getProducerManager().doChannelCloseEvent(remoteAddr, channel); this.brokerController.getConsumerManager().doChannelCloseEvent(remoteAddr, channel); this.brokerController.getBrokerStatsManager().incChannelCloseNum(); } @Override public void onChannelException(String remoteAddr, Channel channel) { this.brokerController.getProducerManager().doChannelCloseEvent(remoteAddr, channel); this.brokerController.getConsumerManager().doChannelCloseEvent(remoteAddr, channel); this.brokerController.getBrokerStatsManager().incChannelExceptionNum(); } @Override public void onChannelIdle(String remoteAddr, Channel channel) { this.brokerController.getProducerManager().doChannelCloseEvent(remoteAddr, channel); this.brokerController.getConsumerManager().doChannelCloseEvent(remoteAddr, channel); this.brokerController.getBrokerStatsManager().incChannelIdleNum(); } @Override public void onChannelActive(String remoteAddr, Channel channel) { } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; public enum ConsumerGroupEvent { /** * Some consumers in the group are changed. */ CHANGE, /** * The group of consumer is unregistered. */ UNREGISTER, /** * The group of consumer is registered. */ REGISTER, /** * The client of this consumer is new registered. */ CLIENT_REGISTER, /** * The client of this consumer is unregistered. */ CLIENT_UNREGISTER } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ConsumerGroupInfo { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final String groupName; private final ConcurrentMap subscriptionTable = new ConcurrentHashMap<>(); private final ConcurrentMap channelInfoTable = new ConcurrentHashMap<>(16); private volatile ConsumeType consumeType; private volatile MessageModel messageModel; private volatile ConsumeFromWhere consumeFromWhere; private volatile long lastUpdateTimestamp = System.currentTimeMillis(); public ConsumerGroupInfo(String groupName, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere) { this.groupName = groupName; this.consumeType = consumeType; this.messageModel = messageModel; this.consumeFromWhere = consumeFromWhere; } public ConsumerGroupInfo(String groupName) { this.groupName = groupName; } public ClientChannelInfo findChannel(final String clientId) { Iterator> it = this.channelInfoTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getValue().getClientId().equals(clientId)) { return next.getValue(); } } return null; } public ConcurrentMap getSubscriptionTable() { return subscriptionTable; } public ClientChannelInfo findChannel(final Channel channel) { return this.channelInfoTable.get(channel); } public ConcurrentMap getChannelInfoTable() { return channelInfoTable; } public List getAllChannel() { List result = new ArrayList<>(); result.addAll(this.channelInfoTable.keySet()); return result; } public List getAllClientId() { List result = new ArrayList<>(); Iterator> it = this.channelInfoTable.entrySet().iterator(); while (it.hasNext()) { Entry entry = it.next(); ClientChannelInfo clientChannelInfo = entry.getValue(); result.add(clientChannelInfo.getClientId()); } return result; } public boolean unregisterChannel(final ClientChannelInfo clientChannelInfo) { ClientChannelInfo old = this.channelInfoTable.remove(clientChannelInfo.getChannel()); if (old != null) { log.info("unregister a consumer[{}] from consumerGroupInfo {}", this.groupName, old.toString()); return true; } return false; } public ClientChannelInfo doChannelCloseEvent(final String remoteAddr, final Channel channel) { final ClientChannelInfo info = this.channelInfoTable.remove(channel); if (info != null) { log.warn( "NETTY EVENT: remove not active channel[{}] from ConsumerGroupInfo groupChannelTable, consumer group: {}", info.toString(), groupName); } return info; } /** * Update {@link #channelInfoTable} in {@link ConsumerGroupInfo} * * @param infoNew Channel info of new client. * @param consumeType consume type of new client. * @param messageModel message consuming model (CLUSTERING/BROADCASTING) of new client. * @param consumeFromWhere indicate the position when the client consume message firstly. * @return the result that if new connector is connected or not. */ public boolean updateChannel(final ClientChannelInfo infoNew, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere) { boolean updated = false; this.consumeType = consumeType; this.messageModel = messageModel; this.consumeFromWhere = consumeFromWhere; ClientChannelInfo infoOld = this.channelInfoTable.get(infoNew.getChannel()); if (null == infoOld) { ClientChannelInfo prev = this.channelInfoTable.put(infoNew.getChannel(), infoNew); if (null == prev) { log.info("new consumer connected, group: {} {} {} channel: {}", this.groupName, consumeType, messageModel, infoNew.toString()); updated = true; } infoOld = infoNew; } else { if (!infoOld.getClientId().equals(infoNew.getClientId())) { log.error( "ConsumerGroupInfo: consumer channel exists in broker, but clientId is not the same one, " + "group={}, old clientChannelInfo={}, new clientChannelInfo={}", groupName, infoOld.toString(), infoNew.toString()); this.channelInfoTable.put(infoNew.getChannel(), infoNew); } } this.lastUpdateTimestamp = System.currentTimeMillis(); infoOld.setLastUpdateTimestamp(this.lastUpdateTimestamp); return updated; } /** * Update subscription. * * @param subList set of {@link SubscriptionData} * @return the boolean indicates the subscription has changed or not. */ public boolean updateSubscription(final Set subList) { boolean updated = false; Set topicSet = new HashSet<>(); for (SubscriptionData sub : subList) { SubscriptionData old = this.subscriptionTable.get(sub.getTopic()); if (old == null) { SubscriptionData prev = this.subscriptionTable.putIfAbsent(sub.getTopic(), sub); if (null == prev) { updated = true; log.info("subscription changed, add new topic, group: {} {}", this.groupName, sub.toString()); } } else if (sub.getSubVersion() > old.getSubVersion()) { if (this.consumeType == ConsumeType.CONSUME_PASSIVELY) { log.info("subscription changed, group: {} OLD: {} NEW: {}", this.groupName, old.toString(), sub.toString() ); } this.subscriptionTable.put(sub.getTopic(), sub); } // Add all new topics to the HashSet topicSet.add(sub.getTopic()); } Iterator> it = this.subscriptionTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String oldTopic = next.getKey(); // Check HashSet with O(1) time complexity if (!topicSet.contains(oldTopic)) { log.warn("subscription changed, group: {} remove topic {} {}", this.groupName, oldTopic, next.getValue().toString() ); it.remove(); updated = true; } } this.lastUpdateTimestamp = System.currentTimeMillis(); return updated; } public Set getSubscribeTopics() { return subscriptionTable.keySet(); } public SubscriptionData findSubscriptionData(final String topic) { return this.subscriptionTable.get(topic); } public ConsumeType getConsumeType() { return consumeType; } public void setConsumeType(ConsumeType consumeType) { this.consumeType = consumeType; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public String getGroupName() { return groupName; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public void setConsumeFromWhere(ConsumeFromWhere consumeFromWhere) { this.consumeFromWhere = consumeFromWhere; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerIdsChangeListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; public interface ConsumerIdsChangeListener { void handle(ConsumerGroupEvent event, String group, Object... args); void shutdown(); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class ConsumerManager { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final ConcurrentMap consumerTable = new ConcurrentHashMap<>(1024); private final ConcurrentMap> topicGroupTable = new ConcurrentHashMap<>(1024); private final ConcurrentMap consumerCompensationTable = new ConcurrentHashMap<>(1024); private final List consumerIdsChangeListenerList = new CopyOnWriteArrayList<>(); protected final BrokerStatsManager brokerStatsManager; private final long channelExpiredTimeout; private final long subscriptionExpiredTimeout; private final BrokerConfig brokerConfig; public ConsumerManager(final ConsumerIdsChangeListener consumerIdsChangeListener, long expiredTimeout) { this.consumerIdsChangeListenerList.add(consumerIdsChangeListener); this.brokerStatsManager = null; this.channelExpiredTimeout = expiredTimeout; this.subscriptionExpiredTimeout = expiredTimeout; this.brokerConfig = null; } public ConsumerManager(final ConsumerIdsChangeListener consumerIdsChangeListener, final BrokerStatsManager brokerStatsManager, BrokerConfig brokerConfig) { this.consumerIdsChangeListenerList.add(consumerIdsChangeListener); this.brokerStatsManager = brokerStatsManager; this.channelExpiredTimeout = brokerConfig.getChannelExpiredTimeout(); this.subscriptionExpiredTimeout = brokerConfig.getSubscriptionExpiredTimeout(); this.brokerConfig = brokerConfig; } public ClientChannelInfo findChannel(final String group, final String clientId) { ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group); if (consumerGroupInfo != null) { return consumerGroupInfo.findChannel(clientId); } return null; } public ClientChannelInfo findChannel(final String group, final Channel channel) { ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group); if (consumerGroupInfo != null) { return consumerGroupInfo.findChannel(channel); } return null; } public SubscriptionData findSubscriptionData(final String group, final String topic) { return findSubscriptionData(group, topic, true); } public SubscriptionData findSubscriptionData(final String group, final String topic, boolean fromCompensationTable) { ConsumerGroupInfo consumerGroupInfo = getConsumerGroupInfo(group, false); if (consumerGroupInfo != null) { SubscriptionData subscriptionData = consumerGroupInfo.findSubscriptionData(topic); if (subscriptionData != null) { return subscriptionData; } } if (fromCompensationTable) { ConsumerGroupInfo consumerGroupCompensationInfo = consumerCompensationTable.get(group); if (consumerGroupCompensationInfo != null) { return consumerGroupCompensationInfo.findSubscriptionData(topic); } } return null; } public ConcurrentMap getConsumerTable() { return this.consumerTable; } public ConsumerGroupInfo getConsumerGroupInfo(final String group) { return getConsumerGroupInfo(group, false); } public ConsumerGroupInfo getConsumerGroupInfo(String group, boolean fromCompensationTable) { ConsumerGroupInfo consumerGroupInfo = consumerTable.get(group); if (consumerGroupInfo == null && fromCompensationTable) { consumerGroupInfo = consumerCompensationTable.get(group); } return consumerGroupInfo; } public int findSubscriptionDataCount(final String group) { ConsumerGroupInfo consumerGroupInfo = this.getConsumerGroupInfo(group); if (consumerGroupInfo != null) { return consumerGroupInfo.getSubscriptionTable().size(); } return 0; } public boolean doChannelCloseEvent(final String remoteAddr, final Channel channel) { boolean removed = false; if (this.brokerConfig != null && this.brokerConfig.isEnableFastChannelEventProcess()) { List groups = ClientChannelAttributeHelper.getConsumerGroups(channel); if (this.brokerConfig.isPrintChannelGroups() && groups.size() >= 5 && groups.size() >= this.brokerConfig.getPrintChannelGroupsMinNum()) { LOGGER.warn("channel close event, too many consumer groups one channel, {}, {}, {}", groups.size(), remoteAddr, groups); } for (String group : groups) { if (null == group || group.length() == 0) { continue; } ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group); if (null == consumerGroupInfo) { continue; } ClientChannelInfo clientChannelInfo = consumerGroupInfo.doChannelCloseEvent(remoteAddr, channel); if (clientChannelInfo != null) { removed = true; callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo, consumerGroupInfo.getSubscribeTopics()); if (consumerGroupInfo.getChannelInfoTable().isEmpty()) { ConsumerGroupInfo remove = this.consumerTable.remove(group); if (remove != null) { LOGGER.info("unregister consumer ok, no any connection, and remove consumer group, {}", group); callConsumerIdsChangeListener(ConsumerGroupEvent.UNREGISTER, group); clearTopicGroupTable(remove); } } callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); } } return removed; } Iterator> it = this.consumerTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); ConsumerGroupInfo info = next.getValue(); ClientChannelInfo clientChannelInfo = info.doChannelCloseEvent(remoteAddr, channel); if (clientChannelInfo != null) { removed = true; callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_UNREGISTER, next.getKey(), clientChannelInfo, info.getSubscribeTopics()); if (info.getChannelInfoTable().isEmpty()) { ConsumerGroupInfo remove = this.consumerTable.remove(next.getKey()); if (remove != null) { LOGGER.info("unregister consumer ok, no any connection, and remove consumer group, {}", next.getKey()); callConsumerIdsChangeListener(ConsumerGroupEvent.UNREGISTER, next.getKey()); clearTopicGroupTable(remove); } } if (!isBroadcastMode(info.getMessageModel())) { callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, next.getKey(), info.getAllChannel()); } } } return removed; } private void clearTopicGroupTable(final ConsumerGroupInfo groupInfo) { for (String subscribeTopic : groupInfo.getSubscribeTopics()) { Set groups = this.topicGroupTable.get(subscribeTopic); if (groups != null) { groups.remove(groupInfo.getGroupName()); } if (groups != null && groups.isEmpty()) { this.topicGroupTable.remove(subscribeTopic); } } } // compensate consumer info for consumer without heartbeat public void compensateBasicConsumerInfo(String group, ConsumeType consumeType, MessageModel messageModel) { ConsumerGroupInfo consumerGroupInfo = consumerCompensationTable.computeIfAbsent(group, ConsumerGroupInfo::new); consumerGroupInfo.setConsumeType(consumeType); consumerGroupInfo.setMessageModel(messageModel); } // compensate subscription for pull consumer and consumer via proxy public void compensateSubscribeData(String group, String topic, SubscriptionData subscriptionData) { ConsumerGroupInfo consumerGroupInfo = consumerCompensationTable.computeIfAbsent(group, ConsumerGroupInfo::new); consumerGroupInfo.getSubscriptionTable().put(topic, subscriptionData); } public boolean registerConsumer(final String group, final ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, final Set subList, boolean isNotifyConsumerIdsChangedEnable) { return registerConsumer(group, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList, isNotifyConsumerIdsChangedEnable, true); } public boolean registerConsumer(final String group, final ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, final Set subList, boolean isNotifyConsumerIdsChangedEnable, boolean updateSubscription) { long start = System.currentTimeMillis(); ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group); if (null == consumerGroupInfo) { ConsumerGroupInfo tmp = new ConsumerGroupInfo(group, consumeType, messageModel, consumeFromWhere); ConsumerGroupInfo prev = this.consumerTable.putIfAbsent(group, tmp); consumerGroupInfo = prev != null ? prev : tmp; } for (SubscriptionData subscriptionData : subList) { Set groups = this.topicGroupTable.get(subscriptionData.getTopic()); if (groups == null) { Set tmp = new HashSet<>(); Set prev = this.topicGroupTable.putIfAbsent(subscriptionData.getTopic(), tmp); groups = prev != null ? prev : tmp; } groups.add(group); } boolean r1 = consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel, consumeFromWhere); if (r1) { callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_REGISTER, group, clientChannelInfo, subList.stream().map(SubscriptionData::getTopic).collect(Collectors.toSet())); } boolean r2 = false; if (updateSubscription) { r2 = consumerGroupInfo.updateSubscription(subList); } if (r1 || r2) { if (isNotifyConsumerIdsChangedEnable && !isBroadcastMode(consumerGroupInfo.getMessageModel())) { callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); } } if (this.brokerConfig != null && this.brokerConfig.isEnableFastChannelEventProcess() && r1) { ClientChannelAttributeHelper.addConsumerGroup(clientChannelInfo.getChannel(), group); } if (null != this.brokerStatsManager) { this.brokerStatsManager.incConsumerRegisterTime((int) (System.currentTimeMillis() - start)); } callConsumerIdsChangeListener(ConsumerGroupEvent.REGISTER, group, subList, clientChannelInfo); return r1 || r2; } public boolean registerConsumerWithoutSub(final String group, final ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, boolean isNotifyConsumerIdsChangedEnable) { long start = System.currentTimeMillis(); ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group); if (null == consumerGroupInfo) { ConsumerGroupInfo tmp = new ConsumerGroupInfo(group, consumeType, messageModel, consumeFromWhere); ConsumerGroupInfo prev = this.consumerTable.putIfAbsent(group, tmp); consumerGroupInfo = prev != null ? prev : tmp; } for (SubscriptionData subscriptionData : consumerGroupInfo.getSubscriptionTable().values()) { Set groups = this.topicGroupTable.get(subscriptionData.getTopic()); if (groups == null) { Set tmp = new HashSet<>(); Set prev = this.topicGroupTable.putIfAbsent(subscriptionData.getTopic(), tmp); groups = prev != null ? prev : tmp; } groups.add(group); } boolean updateChannelRst = consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel, consumeFromWhere); if (updateChannelRst && isNotifyConsumerIdsChangedEnable && !isBroadcastMode(consumerGroupInfo.getMessageModel())) { callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); } if (null != this.brokerStatsManager) { this.brokerStatsManager.incConsumerRegisterTime((int) (System.currentTimeMillis() - start)); } return updateChannelRst; } public void unregisterConsumer(final String group, final ClientChannelInfo clientChannelInfo, boolean isNotifyConsumerIdsChangedEnable) { ConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group); if (null != consumerGroupInfo) { boolean removed = consumerGroupInfo.unregisterChannel(clientChannelInfo); if (removed) { callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo, consumerGroupInfo.getSubscribeTopics()); } if (consumerGroupInfo.getChannelInfoTable().isEmpty()) { ConsumerGroupInfo remove = this.consumerTable.remove(group); if (remove != null) { LOGGER.info("unregister consumer ok, no any connection, and remove consumer group, {}", group); callConsumerIdsChangeListener(ConsumerGroupEvent.UNREGISTER, group); clearTopicGroupTable(remove); } } if (isNotifyConsumerIdsChangedEnable && !isBroadcastMode(consumerGroupInfo.getMessageModel())) { callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); } } } public void removeExpireConsumerGroupInfo() { List removeList = new ArrayList<>(); consumerCompensationTable.forEach((group, consumerGroupInfo) -> { List removeTopicList = new ArrayList<>(); ConcurrentMap subscriptionTable = consumerGroupInfo.getSubscriptionTable(); subscriptionTable.forEach((topic, subscriptionData) -> { long diff = System.currentTimeMillis() - subscriptionData.getSubVersion(); if (diff > subscriptionExpiredTimeout) { removeTopicList.add(topic); } }); for (String topic : removeTopicList) { subscriptionTable.remove(topic); if (subscriptionTable.isEmpty()) { removeList.add(group); } } }); for (String group : removeList) { consumerCompensationTable.remove(group); } } public void scanNotActiveChannel() { Iterator> it = this.consumerTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String group = next.getKey(); ConsumerGroupInfo consumerGroupInfo = next.getValue(); ConcurrentMap channelInfoTable = consumerGroupInfo.getChannelInfoTable(); Iterator> itChannel = channelInfoTable.entrySet().iterator(); while (itChannel.hasNext()) { Entry nextChannel = itChannel.next(); ClientChannelInfo clientChannelInfo = nextChannel.getValue(); long diff = System.currentTimeMillis() - clientChannelInfo.getLastUpdateTimestamp(); if (diff > channelExpiredTimeout) { LOGGER.warn( "SCAN: remove expired channel from ConsumerManager consumerTable. channel={}, consumerGroup={}", RemotingHelper.parseChannelRemoteAddr(clientChannelInfo.getChannel()), group); callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo, consumerGroupInfo.getSubscribeTopics()); RemotingHelper.closeChannel(clientChannelInfo.getChannel()); itChannel.remove(); } } if (channelInfoTable.isEmpty()) { LOGGER.warn( "SCAN: remove expired channel from ConsumerManager consumerTable, all clear, consumerGroup={}", group); it.remove(); } } removeExpireConsumerGroupInfo(); } public HashSet queryTopicConsumeByWho(final String topic) { return new HashSet<>(Optional.ofNullable(topicGroupTable.get(topic)).orElseGet(HashSet::new)); } public void appendConsumerIdsChangeListener(ConsumerIdsChangeListener listener) { consumerIdsChangeListenerList.add(listener); } protected void callConsumerIdsChangeListener(ConsumerGroupEvent event, String group, Object... args) { for (ConsumerIdsChangeListener listener : consumerIdsChangeListenerList) { try { listener.handle(event, group, args); } catch (Throwable t) { LOGGER.error("err when call consumerIdsChangeListener", t); } } } private boolean isBroadcastMode(final MessageModel messageModel) { return MessageModel.BROADCASTING.equals(messageModel); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class DefaultConsumerIdsChangeListener implements ConsumerIdsChangeListener { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private final int cacheSize = 8096; private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, ThreadUtils.newGenericThreadFactory("DefaultConsumerIdsChangeListener", true)); private ConcurrentHashMap> consumerChannelMap = new ConcurrentHashMap<>(cacheSize); private final ConcurrentHashMap activeGroupNotifyMap = new ConcurrentHashMap<>(); public DefaultConsumerIdsChangeListener(BrokerController brokerController) { this.brokerController = brokerController; scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { notifyConsumerChange(); } catch (Exception e) { log.error( "DefaultConsumerIdsChangeListen#notifyConsumerChange: unexpected error occurs", e); } } }, 30, 15, TimeUnit.SECONDS); } @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { if (event == null) { return; } switch (event) { case CHANGE: if (args == null || args.length < 1) { return; } List channels = (List) args[0]; if (channels != null && brokerController.getBrokerConfig().isNotifyConsumerIdsChangedEnable()) { if (this.brokerController.getBrokerConfig().isRealTimeNotifyConsumerChange()) { NotifyTaskControl currentNotifyTaskControl = new NotifyTaskControl(channels); activeGroupNotifyMap.compute(group, (k, oldVal) -> { if (null != oldVal) { oldVal.interrupt(); } return currentNotifyTaskControl; }); boolean isNormalCompletion = true; for (Channel chl : currentNotifyTaskControl.getChannels()) { if (currentNotifyTaskControl.isInterrupted()) { isNormalCompletion = false; break; } this.brokerController.getBroker2Client().notifyConsumerIdsChanged(chl, group); } if (isNormalCompletion) { activeGroupNotifyMap.computeIfPresent(group, (k, val) -> val == currentNotifyTaskControl ? null : val); } } else { consumerChannelMap.put(group, channels); } } break; case UNREGISTER: this.brokerController.getConsumerFilterManager().unRegister(group); break; case REGISTER: if (args == null || args.length < 1) { return; } Collection subscriptionDataList = (Collection) args[0]; this.brokerController.getConsumerFilterManager().register(group, subscriptionDataList); break; case CLIENT_REGISTER: case CLIENT_UNREGISTER: break; default: throw new RuntimeException("Unknown event " + event); } } private void notifyConsumerChange() { if (consumerChannelMap.isEmpty()) { return; } ConcurrentHashMap> processMap = new ConcurrentHashMap<>(consumerChannelMap); consumerChannelMap = new ConcurrentHashMap<>(cacheSize); for (Map.Entry> entry : processMap.entrySet()) { String consumerId = entry.getKey(); List channelList = entry.getValue(); try { if (channelList != null && brokerController.getBrokerConfig().isNotifyConsumerIdsChangedEnable()) { for (Channel chl : channelList) { this.brokerController.getBroker2Client().notifyConsumerIdsChanged(chl, consumerId); } } } catch (Exception e) { log.error("Failed to notify consumer when some consumers changed, consumerId to notify: {}", consumerId, e); } } } @Override public void shutdown() { this.scheduledExecutorService.shutdown(); } private static class NotifyTaskControl { private final AtomicBoolean interrupted = new AtomicBoolean(false); private final List channels; public NotifyTaskControl(List channels) { this.channels = channels; } public boolean isInterrupted() { return interrupted.get(); } public void interrupt() { interrupted.set(true); } public List getChannels() { return channels; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; /** * producer manager will call this listener when something happen *

* event type: {@link ProducerGroupEvent} */ public interface ProducerChangeListener { void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; public enum ProducerGroupEvent { /** * The group of producer is unregistered. */ GROUP_UNREGISTER, /** * The client of this producer is unregistered. */ CLIENT_UNREGISTER } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.rocketmq.broker.util.PositiveAtomicCounter; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.body.ProducerInfo; import org.apache.rocketmq.remoting.protocol.body.ProducerTableInfo; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class ProducerManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final long CHANNEL_EXPIRED_TIMEOUT = 1000 * 120; private static final int GET_AVAILABLE_CHANNEL_RETRY_COUNT = 3; private final ConcurrentMap> groupChannelTable = new ConcurrentHashMap<>(); private final ConcurrentMap clientChannelTable = new ConcurrentHashMap<>(); protected final BrokerStatsManager brokerStatsManager; private final BrokerConfig brokerConfig; private final PositiveAtomicCounter positiveAtomicCounter = new PositiveAtomicCounter(); private final List producerChangeListenerList = new CopyOnWriteArrayList<>(); public ProducerManager() { this.brokerStatsManager = null; this.brokerConfig = null; } public ProducerManager(final BrokerStatsManager brokerStatsManager) { this.brokerStatsManager = brokerStatsManager; this.brokerConfig = null; } public ProducerManager(final BrokerStatsManager brokerStatsManager, final BrokerConfig brokerConfig) { this.brokerStatsManager = brokerStatsManager; this.brokerConfig = brokerConfig; } public int groupSize() { return this.groupChannelTable.size(); } public boolean groupOnline(String group) { Map channels = this.groupChannelTable.get(group); return channels != null && !channels.isEmpty(); } public ConcurrentMap> getGroupChannelTable() { return groupChannelTable; } public ProducerTableInfo getProducerTable() { Map> map = new HashMap<>(); for (String group : this.groupChannelTable.keySet()) { for (Entry entry : this.groupChannelTable.get(group).entrySet()) { ClientChannelInfo clientChannelInfo = entry.getValue(); if (map.containsKey(group)) { map.get(group).add(new ProducerInfo( clientChannelInfo.getClientId(), clientChannelInfo.getChannel().remoteAddress().toString(), clientChannelInfo.getLanguage(), clientChannelInfo.getVersion(), clientChannelInfo.getLastUpdateTimestamp() )); } else { map.put(group, new ArrayList<>(Collections.singleton(new ProducerInfo( clientChannelInfo.getClientId(), clientChannelInfo.getChannel().remoteAddress().toString(), clientChannelInfo.getLanguage(), clientChannelInfo.getVersion(), clientChannelInfo.getLastUpdateTimestamp() )))); } } } return new ProducerTableInfo(map); } public void scanNotActiveChannel() { Iterator>> iterator = this.groupChannelTable.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); final String group = entry.getKey(); final ConcurrentMap chlMap = entry.getValue(); Iterator> it = chlMap.entrySet().iterator(); while (it.hasNext()) { Entry item = it.next(); // final Integer id = item.getKey(); final ClientChannelInfo info = item.getValue(); long diff = System.currentTimeMillis() - info.getLastUpdateTimestamp(); if (diff > CHANNEL_EXPIRED_TIMEOUT) { it.remove(); Channel channelInClientTable = clientChannelTable.get(info.getClientId()); if (channelInClientTable != null && channelInClientTable.equals(info.getChannel())) { clientChannelTable.remove(info.getClientId()); } log.warn( "ProducerManager#scanNotActiveChannel: remove expired channel[{}] from ProducerManager groupChannelTable, producer group name: {}", RemotingHelper.parseChannelRemoteAddr(info.getChannel()), group); callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, info); RemotingHelper.closeChannel(info.getChannel()); } } if (chlMap.isEmpty()) { log.warn("SCAN: remove expired channel from ProducerManager groupChannelTable, all clear, group={}", group); iterator.remove(); callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null); } } } public boolean doChannelCloseEvent(final String remoteAddr, final Channel channel) { boolean removed = false; if (channel != null) { if (this.brokerConfig != null && this.brokerConfig.isEnableFastChannelEventProcess()) { List groups = ClientChannelAttributeHelper.getProducerGroups(channel); if (this.brokerConfig.isPrintChannelGroups() && groups.size() >= 5 && groups.size() >= this.brokerConfig.getPrintChannelGroupsMinNum()) { log.warn("channel close event, too many producer groups one channel, {}, {}, {}", groups.size(), remoteAddr, groups); } for (String group : groups) { if (null == group || group.length() == 0) { continue; } ConcurrentMap clientChannelInfoTable = this.groupChannelTable.get(group); if (null == clientChannelInfoTable) { continue; } final ClientChannelInfo clientChannelInfo = clientChannelInfoTable.remove(channel); if (clientChannelInfo != null) { clientChannelTable.remove(clientChannelInfo.getClientId()); removed = true; log.info( "NETTY EVENT: remove channel[{}][{}] from ProducerManager groupChannelTable, producer group: {}", clientChannelInfo.toString(), remoteAddr, group); callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo); if (clientChannelInfoTable.isEmpty()) { ConcurrentMap oldGroupTable = this.groupChannelTable.remove(group); if (oldGroupTable != null) { log.info("unregister a producer group[{}] from groupChannelTable", group); callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null); } } } } return removed; // must return here, degrade to scanNotActiveChannel at worst. } for (final Map.Entry> entry : this.groupChannelTable.entrySet()) { final String group = entry.getKey(); final ConcurrentMap clientChannelInfoTable = entry.getValue(); final ClientChannelInfo clientChannelInfo = clientChannelInfoTable.remove(channel); if (clientChannelInfo != null) { clientChannelTable.remove(clientChannelInfo.getClientId()); removed = true; log.info( "NETTY EVENT: remove channel[{}][{}] from ProducerManager groupChannelTable, producer group: {}", clientChannelInfo.toString(), remoteAddr, group); callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo); if (clientChannelInfoTable.isEmpty()) { ConcurrentMap oldGroupTable = this.groupChannelTable.remove(group); if (oldGroupTable != null) { log.info("unregister a producer group[{}] from groupChannelTable", group); callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null); } } } } } return removed; } public void registerProducer(final String group, final ClientChannelInfo clientChannelInfo) { long start = System.currentTimeMillis(); ClientChannelInfo clientChannelInfoFound; ConcurrentMap channelTable = this.groupChannelTable.get(group); // note that we must take care of the exist groups and channels, // only can return when groups or channels not exist. if (this.brokerConfig != null && !this.brokerConfig.isEnableRegisterProducer() && this.brokerConfig.isRejectTransactionMessage()) { boolean needRegister = true; if (null == channelTable) { needRegister = false; } else { clientChannelInfoFound = channelTable.get(clientChannelInfo.getChannel()); if (null == clientChannelInfoFound) { needRegister = false; } } if (!needRegister) { if (null != this.brokerStatsManager) { this.brokerStatsManager.incProducerRegisterTime((int) (System.currentTimeMillis() - start)); } return; } } if (null == channelTable) { channelTable = new ConcurrentHashMap<>(); ConcurrentMap prev = this.groupChannelTable.putIfAbsent(group, channelTable); channelTable = prev != null ? prev : channelTable; } clientChannelInfoFound = channelTable.get(clientChannelInfo.getChannel()); // Add client-channel info to existing producer group if (null == clientChannelInfoFound) { channelTable.put(clientChannelInfo.getChannel(), clientChannelInfo); clientChannelTable.put(clientChannelInfo.getClientId(), clientChannelInfo.getChannel()); log.info("new producer connected, group: {} channel: {}", group, clientChannelInfo.toString()); if (this.brokerConfig != null && this.brokerConfig.isEnableFastChannelEventProcess()) { ClientChannelAttributeHelper.addProducerGroup(clientChannelInfo.getChannel(), group); } } // Refresh existing client-channel-info update-timestamp if (clientChannelInfoFound != null) { clientChannelInfoFound.setLastUpdateTimestamp(System.currentTimeMillis()); } if (null != this.brokerStatsManager) { this.brokerStatsManager.incProducerRegisterTime((int) (System.currentTimeMillis() - start)); } } public void unregisterProducer(final String group, final ClientChannelInfo clientChannelInfo) { ConcurrentMap channelTable = this.groupChannelTable.get(group); if (null != channelTable && !channelTable.isEmpty()) { ClientChannelInfo old = channelTable.remove(clientChannelInfo.getChannel()); clientChannelTable.remove(clientChannelInfo.getClientId()); if (old != null) { log.info("unregister a producer[{}] from groupChannelTable {}", group, clientChannelInfo.toString()); callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo); } if (channelTable.isEmpty()) { this.groupChannelTable.remove(group); callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null); log.info("unregister a producer group[{}] from groupChannelTable", group); } } } public Channel getAvailableChannel(String groupId) { if (groupId == null) { return null; } List channelList; ConcurrentMap channelClientChannelInfoHashMap = groupChannelTable.get(groupId); if (channelClientChannelInfoHashMap != null) { channelList = new ArrayList<>(channelClientChannelInfoHashMap.keySet()); } else { log.warn("Check transaction failed, channel table is empty. groupId={}", groupId); return null; } int size = channelList.size(); if (0 == size) { log.warn("Channel list is empty. groupId={}", groupId); return null; } Channel lastActiveChannel = null; int index = positiveAtomicCounter.incrementAndGet() % size; Channel channel = channelList.get(index); int count = 0; boolean isOk = channel.isActive() && channel.isWritable(); while (count++ < GET_AVAILABLE_CHANNEL_RETRY_COUNT) { if (isOk) { return channel; } if (channel.isActive()) { lastActiveChannel = channel; } index = (++index) % size; channel = channelList.get(index); isOk = channel.isActive() && channel.isWritable(); } return lastActiveChannel; } public Channel findChannel(String clientId) { return clientChannelTable.get(clientId); } private void callProducerChangeListener(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) { for (ProducerChangeListener listener : producerChangeListenerList) { try { listener.handle(event, group, clientChannelInfo); } catch (Throwable t) { log.error("err when call producerChangeListener", t); } } } public void appendProducerChangeListener(ProducerChangeListener producerChangeListener) { producerChangeListenerList.add(producerChangeListener); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client.net; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueForC; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.GetConsumerStatusBody; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBodyForC; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerStatusRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyConsumerIdsChangedRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.store.exception.ConsumeQueueException; public class Broker2Client { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; public Broker2Client(BrokerController brokerController) { this.brokerController = brokerController; } public void notifyUnsubscribeLite(Channel channel, NotifyUnsubscribeLiteRequestHeader requestHeader) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFY_UNSUBSCRIBE_LITE, requestHeader); try { this.brokerController.getRemotingServer().invokeOneway(channel, request, 100); } catch (Exception e) { log.error("notifyUnsubscribeLite failed. header={}, error={}", requestHeader, e.toString()); } } public void checkProducerTransactionState( final String group, final Channel channel, final CheckTransactionStateRequestHeader requestHeader, final MessageExt messageExt) throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHECK_TRANSACTION_STATE, requestHeader); request.setBody(MessageDecoder.encode(messageExt, false)); try { this.brokerController.getRemotingServer().invokeOneway(channel, request, 10); } catch (Exception e) { log.error("Check transaction failed because invoke producer exception. group={}, msgId={}, error={}", group, messageExt.getMsgId(), e.toString()); } } public RemotingCommand callClient(final Channel channel, final RemotingCommand request ) throws RemotingSendRequestException, RemotingTimeoutException, InterruptedException { return this.brokerController.getRemotingServer().invokeSync(channel, request, 10000); } public void notifyConsumerIdsChanged( final Channel channel, final String consumerGroup) { if (null == consumerGroup) { log.error("notifyConsumerIdsChanged consumerGroup is null"); return; } NotifyConsumerIdsChangedRequestHeader requestHeader = new NotifyConsumerIdsChangedRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, requestHeader); try { this.brokerController.getRemotingServer().invokeOneway(channel, request, 10); } catch (Exception e) { log.error("notifyConsumerIdsChanged exception. group={}, error={}", consumerGroup, e.toString()); } } public RemotingCommand resetOffset(String topic, String group, long timeStamp, boolean isForce) throws RemotingCommandException { return resetOffset(topic, group, timeStamp, isForce, false); } public RemotingCommand resetOffset(String topic, String group, long timeStamp, boolean isForce, boolean isC) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { log.error("[reset-offset] reset offset failed, no topic in this broker. topic={}", topic); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("[reset-offset] reset offset failed, no topic in this broker. topic=" + topic); return response; } Map offsetTable = new HashMap<>(); for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) { MessageQueue mq = new MessageQueue(); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setTopic(topic); mq.setQueueId(i); long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset(group, topic, i); if (-1 == consumerOffset) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("THe consumer group <%s> not exist", group)); return response; } long timeStampOffset; if (timeStamp == -1) { try { timeStampOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset in queue", e); } } else { timeStampOffset = this.brokerController.getMessageStore().getOffsetInQueueByTime(topic, i, timeStamp); } if (timeStampOffset < 0) { log.warn("reset offset is invalid. topic={}, queueId={}, timeStampOffset={}", topic, i, timeStampOffset); timeStampOffset = 0; } if (isForce || timeStampOffset < consumerOffset) { offsetTable.put(mq, timeStampOffset); } else { offsetTable.put(mq, consumerOffset); } } ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); requestHeader.setTimestamp(timeStamp); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESET_CONSUMER_CLIENT_OFFSET, requestHeader); if (isC) { // c++ language ResetOffsetBodyForC body = new ResetOffsetBodyForC(); List offsetList = convertOffsetTable2OffsetList(offsetTable); body.setOffsetTable(offsetList); request.setBody(body.encode()); } else { // other language ResetOffsetBody body = new ResetOffsetBody(); body.setOffsetTable(offsetTable); request.setBody(body.encode()); } ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(group); if (consumerGroupInfo != null && !consumerGroupInfo.getAllChannel().isEmpty()) { ConcurrentMap channelInfoTable = consumerGroupInfo.getChannelInfoTable(); for (Map.Entry entry : channelInfoTable.entrySet()) { int version = entry.getValue().getVersion(); if (version >= MQVersion.Version.V3_0_7_SNAPSHOT.ordinal()) { try { this.brokerController.getRemotingServer().invokeOneway(entry.getKey(), request, 5000); log.info("[reset-offset] reset offset success. topic={}, group={}, clientId={}", topic, group, entry.getValue().getClientId()); } catch (Exception e) { log.error("[reset-offset] reset offset exception. topic={}, group={} ,error={}", topic, group, e.toString()); } } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("the client does not support this feature. version=" + MQVersion.getVersionDesc(version)); log.warn("[reset-offset] the client does not support this feature. channel={}, version={}", RemotingHelper.parseChannelRemoteAddr(entry.getKey()), MQVersion.getVersionDesc(version)); return response; } } } else { String errorInfo = String.format("Consumer not online, so can not reset offset, Group: %s Topic: %s Timestamp: %d", requestHeader.getGroup(), requestHeader.getTopic(), requestHeader.getTimestamp()); log.error(errorInfo); response.setCode(ResponseCode.CONSUMER_NOT_ONLINE); response.setRemark(errorInfo); return response; } response.setCode(ResponseCode.SUCCESS); ResetOffsetBody resBody = new ResetOffsetBody(); resBody.setOffsetTable(offsetTable); response.setBody(resBody.encode()); return response; } private List convertOffsetTable2OffsetList(Map table) { List list = new ArrayList<>(); for (Entry entry : table.entrySet()) { MessageQueue mq = entry.getKey(); MessageQueueForC tmp = new MessageQueueForC(mq.getTopic(), mq.getBrokerName(), mq.getQueueId(), entry.getValue()); list.add(tmp); } return list; } public RemotingCommand getConsumeStatus(String topic, String group, String originClientId) { final RemotingCommand result = RemotingCommand.createResponseCommand(null); GetConsumerStatusRequestHeader requestHeader = new GetConsumerStatusRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT, requestHeader); Map> consumerStatusTable = new HashMap<>(); ConcurrentMap channelInfoTable = this.brokerController.getConsumerManager().getConsumerGroupInfo(group).getChannelInfoTable(); if (null == channelInfoTable || channelInfoTable.isEmpty()) { result.setCode(ResponseCode.SYSTEM_ERROR); result.setRemark(String.format("No Any Consumer online in the consumer group: [%s]", group)); return result; } for (Map.Entry entry : channelInfoTable.entrySet()) { int version = entry.getValue().getVersion(); String clientId = entry.getValue().getClientId(); if (version < MQVersion.Version.V3_0_7_SNAPSHOT.ordinal()) { result.setCode(ResponseCode.SYSTEM_ERROR); result.setRemark("the client does not support this feature. version=" + MQVersion.getVersionDesc(version)); log.warn("[get-consumer-status] the client does not support this feature. channel={}, version={}", RemotingHelper.parseChannelRemoteAddr(entry.getKey()), MQVersion.getVersionDesc(version)); return result; } else if (UtilAll.isBlank(originClientId) || originClientId.equals(clientId)) { try { RemotingCommand response = this.brokerController.getRemotingServer().invokeSync(entry.getKey(), request, 5000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getBody() != null) { GetConsumerStatusBody body = GetConsumerStatusBody.decode(response.getBody(), GetConsumerStatusBody.class); consumerStatusTable.put(clientId, body.getMessageQueueTable()); log.info( "[get-consumer-status] get consumer status success. topic={}, group={}, channelRemoteAddr={}", topic, group, clientId); } } default: break; } } catch (Exception e) { log.error( "[get-consumer-status] get consumer status exception. topic={}, group={}, error={}", topic, group, e.toString()); } if (!UtilAll.isBlank(originClientId) && originClientId.equals(clientId)) { break; } } } result.setCode(ResponseCode.SUCCESS); GetConsumerStatusBody resBody = new GetConsumerStatusBody(); resBody.setConsumerTable(consumerStatusTable); result.setBody(resBody.encode()); return result; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/client/rebalance/RebalanceLockManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client.rebalance; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RebalanceLockManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.REBALANCE_LOCK_LOGGER_NAME); private final static long REBALANCE_LOCK_MAX_LIVE_TIME = Long.parseLong(System.getProperty( "rocketmq.broker.rebalance.lockMaxLiveTime", "60000")); private final Lock lock = new ReentrantLock(); private final ConcurrentMap> mqLockTable = new ConcurrentHashMap<>(1024); public boolean isLockAllExpired(final String group) { final ConcurrentHashMap lockEntryMap = mqLockTable.get(group); if (null == lockEntryMap) { return true; } for (LockEntry entry : lockEntryMap.values()) { if (!entry.isExpired()) { return false; } } return true; } public boolean tryLock(final String group, final MessageQueue mq, final String clientId) { if (!this.isLocked(group, mq, clientId)) { try { this.lock.lockInterruptibly(); try { ConcurrentHashMap groupValue = this.mqLockTable.get(group); if (null == groupValue) { groupValue = new ConcurrentHashMap<>(32); this.mqLockTable.put(group, groupValue); } LockEntry lockEntry = groupValue.get(mq); if (null == lockEntry) { lockEntry = new LockEntry(); lockEntry.setClientId(clientId); groupValue.put(mq, lockEntry); log.info( "RebalanceLockManager#tryLock: lock a message queue which has not been locked yet, " + "group={}, clientId={}, mq={}", group, clientId, mq); } if (lockEntry.isLocked(clientId)) { lockEntry.setLastUpdateTimestamp(System.currentTimeMillis()); return true; } String oldClientId = lockEntry.getClientId(); if (lockEntry.isExpired()) { lockEntry.setClientId(clientId); lockEntry.setLastUpdateTimestamp(System.currentTimeMillis()); log.warn( "RebalanceLockManager#tryLock: try to lock a expired message queue, group={}, mq={}, old " + "client id={}, new client id={}", group, mq, oldClientId, clientId); return true; } log.warn( "RebalanceLockManager#tryLock: message queue has been locked by other client, group={}, " + "mq={}, locked client id={}, current client id={}", group, mq, oldClientId, clientId); return false; } finally { this.lock.unlock(); } } catch (InterruptedException e) { log.error("RebalanceLockManager#tryLock: unexpected error, group={}, mq={}, clientId={}", group, mq, clientId, e); } } return true; } private boolean isLocked(final String group, final MessageQueue mq, final String clientId) { ConcurrentHashMap groupValue = this.mqLockTable.get(group); if (groupValue != null) { LockEntry lockEntry = groupValue.get(mq); if (lockEntry != null) { boolean locked = lockEntry.isLocked(clientId); if (locked) { lockEntry.setLastUpdateTimestamp(System.currentTimeMillis()); } return locked; } } return false; } public Set tryLockBatch(final String group, final Set mqs, final String clientId) { Set lockedMqs = new HashSet<>(mqs.size()); Set notLockedMqs = new HashSet<>(mqs.size()); for (MessageQueue mq : mqs) { if (this.isLocked(group, mq, clientId)) { lockedMqs.add(mq); } else { notLockedMqs.add(mq); } } if (!notLockedMqs.isEmpty()) { try { this.lock.lockInterruptibly(); try { ConcurrentHashMap groupValue = this.mqLockTable.get(group); if (null == groupValue) { groupValue = new ConcurrentHashMap<>(32); this.mqLockTable.put(group, groupValue); } for (MessageQueue mq : notLockedMqs) { LockEntry lockEntry = groupValue.get(mq); if (null == lockEntry) { lockEntry = new LockEntry(); lockEntry.setClientId(clientId); groupValue.put(mq, lockEntry); log.info( "RebalanceLockManager#tryLockBatch: lock a message which has not been locked yet, " + "group={}, clientId={}, mq={}", group, clientId, mq); } if (lockEntry.isLocked(clientId)) { lockEntry.setLastUpdateTimestamp(System.currentTimeMillis()); lockedMqs.add(mq); continue; } String oldClientId = lockEntry.getClientId(); if (lockEntry.isExpired()) { lockEntry.setClientId(clientId); lockEntry.setLastUpdateTimestamp(System.currentTimeMillis()); log.warn( "RebalanceLockManager#tryLockBatch: try to lock a expired message queue, group={}, " + "mq={}, old client id={}, new client id={}", group, mq, oldClientId, clientId); lockedMqs.add(mq); continue; } log.warn( "RebalanceLockManager#tryLockBatch: message queue has been locked by other client, " + "group={}, mq={}, locked client id={}, current client id={}", group, mq, oldClientId, clientId); } } finally { this.lock.unlock(); } } catch (InterruptedException e) { log.error("RebalanceLockManager#tryBatch: unexpected error, group={}, mqs={}, clientId={}", group, mqs, clientId, e); } } return lockedMqs; } public void unlockBatch(final String group, final Set mqs, final String clientId) { try { this.lock.lockInterruptibly(); try { ConcurrentHashMap groupValue = this.mqLockTable.get(group); if (null != groupValue) { for (MessageQueue mq : mqs) { LockEntry lockEntry = groupValue.get(mq); if (null != lockEntry) { if (lockEntry.getClientId().equals(clientId)) { groupValue.remove(mq); log.info("RebalanceLockManager#unlockBatch: unlock mq, group={}, clientId={}, mqs={}", group, clientId, mq); } else { log.warn( "RebalanceLockManager#unlockBatch: mq locked by other client, group={}, locked " + "clientId={}, current clientId={}, mqs={}", group, lockEntry.getClientId(), clientId, mq); } } else { log.warn("RebalanceLockManager#unlockBatch: mq not locked, group={}, clientId={}, mq={}", group, clientId, mq); } } } else { log.warn("RebalanceLockManager#unlockBatch: group not exist, group={}, clientId={}, mqs={}", group, clientId, mqs); } } finally { this.lock.unlock(); } } catch (InterruptedException e) { log.error("RebalanceLockManager#unlockBatch: unexpected error, group={}, mqs={}, clientId={}", group, mqs, clientId); } } static class LockEntry { private String clientId; private volatile long lastUpdateTimestamp = System.currentTimeMillis(); public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } public boolean isLocked(final String clientId) { boolean eq = this.clientId.equals(clientId); return eq && !this.isExpired(); } public boolean isExpired() { boolean expired = (System.currentTimeMillis() - this.lastUpdateTimestamp) > REBALANCE_LOCK_MAX_LIVE_TIME; return expired; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdCtrStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.coldctr; public interface ColdCtrStrategy { /** * Calculate the determining factor about whether to accelerate or decelerate * @return */ Double decisionFactor(); /** * Promote the speed for consumerGroup to read cold data * @param consumerGroup * @param currentThreshold */ void promote(String consumerGroup, Long currentThreshold); /** * Decelerate the speed for consumerGroup to read cold data * @param consumerGroup * @param currentThreshold */ void decelerate(String consumerGroup, Long currentThreshold); /** * Collect the total number of cold read data in the system * @param globalAcc */ void collect(Long globalAcc); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataCgCtrService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.coldctr; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.fastjson2.JSONObject; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.coldctr.AccAndTimeStamp; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.config.MessageStoreConfig; /** * store the cg cold read ctr table and acc the size of the cold * reading msg, timing to clear the table and set acc to zero */ public class ColdDataCgCtrService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_COLDCTR_LOGGER_NAME); private final SystemClock systemClock = new SystemClock(); private final long cgColdAccResideTimeoutMills = 60 * 1000; private static final AtomicLong GLOBAL_ACC = new AtomicLong(0L); private static final String ADAPTIVE = "||adaptive"; /** * as soon as the consumerGroup read the cold data then it will be put into @code cgColdThresholdMapRuntime, * and it also will be removed when does not read cold data in @code cgColdAccResideTimeoutMills later; */ private final ConcurrentHashMap cgColdThresholdMapRuntime = new ConcurrentHashMap<>(); /** * if the system admin wants to set the special cold read threshold for some consumerGroup, the configuration will * be putted into @code cgColdThresholdMapConfig */ private final ConcurrentHashMap cgColdThresholdMapConfig = new ConcurrentHashMap<>(); private final BrokerConfig brokerConfig; private final MessageStoreConfig messageStoreConfig; private final ColdCtrStrategy coldCtrStrategy; public ColdDataCgCtrService(BrokerController brokerController) { this.brokerConfig = brokerController.getBrokerConfig(); this.messageStoreConfig = brokerController.getMessageStoreConfig(); this.coldCtrStrategy = brokerConfig.isUsePIDColdCtrStrategy() ? new PIDAdaptiveColdCtrStrategy(this, (long)(brokerConfig.getGlobalColdReadThreshold() * 0.8)) : new SimpleColdCtrStrategy(this); } @Override public String getServiceName() { return ColdDataCgCtrService.class.getSimpleName(); } @Override public void run() { log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { if (messageStoreConfig.isColdDataFlowControlEnable()) { this.waitForRunning(5 * 1000); } else { this.waitForRunning(180 * 1000); } long beginLockTimestamp = this.systemClock.now(); clearDataAcc(); if (!brokerConfig.isColdCtrStrategyEnable()) { clearAdaptiveConfig(); } long costTime = this.systemClock.now() - beginLockTimestamp; log.info("[{}] clearTheDataAcc-cost {} ms.", costTime > 3 * 1000 ? "NOTIFYME" : "OK", costTime); } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception", e); } } log.info("{} service end", this.getServiceName()); } public String getColdDataFlowCtrInfo() { JSONObject result = new JSONObject(); result.put("runtimeTable", this.cgColdThresholdMapRuntime); result.put("configTable", this.cgColdThresholdMapConfig); result.put("cgColdReadThreshold", this.brokerConfig.getCgColdReadThreshold()); result.put("globalColdReadThreshold", this.brokerConfig.getGlobalColdReadThreshold()); result.put("globalAcc", GLOBAL_ACC.get()); return result.toJSONString(); } /** * clear the long time no cold read cg in the table; * update the acc to zero for the cg in the table; * use the strategy to promote or decelerate the cg; */ private void clearDataAcc() { log.info("clearDataAcc cgColdThresholdMapRuntime key size: {}", cgColdThresholdMapRuntime.size()); if (brokerConfig.isColdCtrStrategyEnable()) { coldCtrStrategy.collect(GLOBAL_ACC.get()); } Iterator> iterator = cgColdThresholdMapRuntime.entrySet().iterator(); while (iterator.hasNext()) { Entry next = iterator.next(); if (System.currentTimeMillis() >= cgColdAccResideTimeoutMills + next.getValue().getLastColdReadTimeMills()) { if (brokerConfig.isColdCtrStrategyEnable()) { cgColdThresholdMapConfig.remove(buildAdaptiveKey(next.getKey())); } iterator.remove(); } else if (next.getValue().getColdAcc().get() >= getThresholdByConsumerGroup(next.getKey())) { log.info("Coldctr consumerGroup: {}, acc: {}, threshold: {}", next.getKey(), next.getValue().getColdAcc().get(), getThresholdByConsumerGroup(next.getKey())); if (brokerConfig.isColdCtrStrategyEnable() && !isGlobalColdCtr() && !isAdminConfig(next.getKey())) { coldCtrStrategy.promote(buildAdaptiveKey(next.getKey()), getThresholdByConsumerGroup(next.getKey())); } } next.getValue().getColdAcc().set(0L); } if (isGlobalColdCtr()) { log.info("Coldctr global acc: {}, threshold: {}", GLOBAL_ACC.get(), this.brokerConfig.getGlobalColdReadThreshold()); } if (brokerConfig.isColdCtrStrategyEnable()) { sortAndDecelerate(); } GLOBAL_ACC.set(0L); } private void sortAndDecelerate() { List> configMapList = new ArrayList>(cgColdThresholdMapConfig.entrySet()); configMapList.sort(new Comparator>() { @Override public int compare(Entry o1, Entry o2) { return (int)(o2.getValue() - o1.getValue()); } }); Iterator> iterator = configMapList.iterator(); int maxDecelerate = 3; while (iterator.hasNext() && maxDecelerate > 0) { Entry next = iterator.next(); if (!isAdminConfig(next.getKey())) { coldCtrStrategy.decelerate(next.getKey(), getThresholdByConsumerGroup(next.getKey())); maxDecelerate --; } } } public void coldAcc(String consumerGroup, long coldDataToAcc) { if (coldDataToAcc <= 0) { return; } GLOBAL_ACC.addAndGet(coldDataToAcc); AccAndTimeStamp atomicAcc = cgColdThresholdMapRuntime.get(consumerGroup); if (null == atomicAcc) { atomicAcc = new AccAndTimeStamp(new AtomicLong(coldDataToAcc)); atomicAcc = cgColdThresholdMapRuntime.putIfAbsent(consumerGroup, atomicAcc); } if (null != atomicAcc) { atomicAcc.getColdAcc().addAndGet(coldDataToAcc); atomicAcc.setLastColdReadTimeMills(System.currentTimeMillis()); } } public void addOrUpdateGroupConfig(String consumerGroup, Long threshold) { cgColdThresholdMapConfig.put(consumerGroup, threshold); } public void removeGroupConfig(String consumerGroup) { cgColdThresholdMapConfig.remove(consumerGroup); } public boolean isCgNeedColdDataFlowCtr(String consumerGroup) { if (!this.messageStoreConfig.isColdDataFlowControlEnable()) { return false; } if (MixAll.isSysConsumerGroupPullMessage(consumerGroup)) { return false; } AccAndTimeStamp accAndTimeStamp = cgColdThresholdMapRuntime.get(consumerGroup); if (null == accAndTimeStamp) { return false; } Long threshold = getThresholdByConsumerGroup(consumerGroup); if (accAndTimeStamp.getColdAcc().get() >= threshold) { return true; } return GLOBAL_ACC.get() >= this.brokerConfig.getGlobalColdReadThreshold(); } public boolean isGlobalColdCtr() { return GLOBAL_ACC.get() > this.brokerConfig.getGlobalColdReadThreshold(); } public BrokerConfig getBrokerConfig() { return brokerConfig; } private Long getThresholdByConsumerGroup(String consumerGroup) { if (isAdminConfig(consumerGroup)) { if (consumerGroup.endsWith(ADAPTIVE)) { return cgColdThresholdMapConfig.get(consumerGroup.split(ADAPTIVE)[0]); } return cgColdThresholdMapConfig.get(consumerGroup); } Long threshold = null; if (brokerConfig.isColdCtrStrategyEnable()) { if (consumerGroup.endsWith(ADAPTIVE)) { threshold = cgColdThresholdMapConfig.get(consumerGroup); } else { threshold = cgColdThresholdMapConfig.get(buildAdaptiveKey(consumerGroup)); } } if (null == threshold) { threshold = this.brokerConfig.getCgColdReadThreshold(); } return threshold; } private String buildAdaptiveKey(String consumerGroup) { return consumerGroup + ADAPTIVE; } private boolean isAdminConfig(String consumerGroup) { if (consumerGroup.endsWith(ADAPTIVE)) { consumerGroup = consumerGroup.split(ADAPTIVE)[0]; } return cgColdThresholdMapConfig.containsKey(consumerGroup); } private void clearAdaptiveConfig() { cgColdThresholdMapConfig.entrySet().removeIf(next -> next.getKey().endsWith(ADAPTIVE)); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataPullRequestHoldService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.coldctr; import java.util.Iterator; import java.util.concurrent.LinkedBlockingQueue; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.longpolling.PullRequest; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * just requests are type of pull have the qualification to be put into this hold queue. * if the pull request is reading cold data and that request will be cold at the first time, * then the pull request will be cold in this @code pullRequestLinkedBlockingQueue, * in @code coldTimeoutMillis later the pull request will be warm and marked holded */ public class ColdDataPullRequestHoldService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_COLDCTR_LOGGER_NAME); public static final String NO_SUSPEND_KEY = "_noSuspend_"; private final long coldHoldTimeoutMillis = 3000; private final SystemClock systemClock = new SystemClock(); private final BrokerController brokerController; private final LinkedBlockingQueue pullRequestColdHoldQueue = new LinkedBlockingQueue<>(10000); public void suspendColdDataReadRequest(PullRequest pullRequest) { if (this.brokerController.getMessageStoreConfig().isColdDataFlowControlEnable()) { pullRequestColdHoldQueue.offer(pullRequest); } } public ColdDataPullRequestHoldService(BrokerController brokerController) { this.brokerController = brokerController; } @Override public String getServiceName() { return ColdDataPullRequestHoldService.class.getSimpleName(); } @Override public void run() { log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { if (!this.brokerController.getMessageStoreConfig().isColdDataFlowControlEnable()) { this.waitForRunning(20 * 1000); } else { this.waitForRunning(5 * 1000); } long beginClockTimestamp = this.systemClock.now(); this.checkColdDataPullRequest(); long costTime = this.systemClock.now() - beginClockTimestamp; log.info("[{}] checkColdDataPullRequest-cost {} ms.", costTime > 5 * 1000 ? "NOTIFYME" : "OK", costTime); } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception", e); } } log.info("{} service end", this.getServiceName()); } private void checkColdDataPullRequest() { int succTotal = 0, errorTotal = 0, queueSize = pullRequestColdHoldQueue.size() ; Iterator iterator = pullRequestColdHoldQueue.iterator(); while (iterator.hasNext()) { PullRequest pullRequest = iterator.next(); if (System.currentTimeMillis() >= pullRequest.getSuspendTimestamp() + coldHoldTimeoutMillis) { try { pullRequest.getRequestCommand().addExtField(NO_SUSPEND_KEY, "1"); this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup( pullRequest.getClientChannel(), pullRequest.getRequestCommand()); succTotal++; } catch (Exception e) { log.error("PullRequestColdHoldService checkColdDataPullRequest error", e); errorTotal++; } //remove the timeout request from the iterator iterator.remove(); } } log.info("checkColdPullRequest-info-finish, queueSize: {} successTotal: {} errorTotal: {}", queueSize, succTotal, errorTotal); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/coldctr/PIDAdaptiveColdCtrStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.coldctr; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class PIDAdaptiveColdCtrStrategy implements ColdCtrStrategy { /** * Stores the maximum number of recent et val */ private static final int MAX_STORE_NUMS = 10; /** * The weights of the three modules of the PID formula */ private static final Double KP = 0.5, KI = 0.3, KD = 0.2; private final List historyEtValList = new ArrayList<>(); private final ColdDataCgCtrService coldDataCgCtrService; private final Long expectGlobalVal; private long et = 0L; public PIDAdaptiveColdCtrStrategy(ColdDataCgCtrService coldDataCgCtrService, Long expectGlobalVal) { this.coldDataCgCtrService = coldDataCgCtrService; this.expectGlobalVal = expectGlobalVal; } @Override public Double decisionFactor() { if (historyEtValList.size() < MAX_STORE_NUMS) { return 0.0; } Long et1 = historyEtValList.get(historyEtValList.size() - 1); Long et2 = historyEtValList.get(historyEtValList.size() - 2); Long differential = et1 - et2; Double integration = 0.0; for (Long item: historyEtValList) { integration += item; } return KP * et + KI * integration + KD * differential; } @Override public void promote(String consumerGroup, Long currentThreshold) { if (decisionFactor() > 0) { coldDataCgCtrService.addOrUpdateGroupConfig(consumerGroup, (long)(currentThreshold * 1.5)); } } @Override public void decelerate(String consumerGroup, Long currentThreshold) { if (decisionFactor() < 0) { long changedThresholdVal = (long)(currentThreshold * 0.8); if (changedThresholdVal < coldDataCgCtrService.getBrokerConfig().getCgColdReadThreshold()) { changedThresholdVal = coldDataCgCtrService.getBrokerConfig().getCgColdReadThreshold(); } coldDataCgCtrService.addOrUpdateGroupConfig(consumerGroup, changedThresholdVal); } } @Override public void collect(Long globalAcc) { et = expectGlobalVal - globalAcc; historyEtValList.add(et); Iterator iterator = historyEtValList.iterator(); while (historyEtValList.size() > MAX_STORE_NUMS && iterator.hasNext()) { iterator.next(); iterator.remove(); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/coldctr/SimpleColdCtrStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.coldctr; public class SimpleColdCtrStrategy implements ColdCtrStrategy { private final ColdDataCgCtrService coldDataCgCtrService; public SimpleColdCtrStrategy(ColdDataCgCtrService coldDataCgCtrService) { this.coldDataCgCtrService = coldDataCgCtrService; } @Override public Double decisionFactor() { return null; } @Override public void promote(String consumerGroup, Long currentThreshold) { coldDataCgCtrService.addOrUpdateGroupConfig(consumerGroup, (long)(currentThreshold * 1.5)); } @Override public void decelerate(String consumerGroup, Long currentThreshold) { if (!coldDataCgCtrService.isGlobalColdCtr()) { return; } long changedThresholdVal = (long)(currentThreshold * 0.8); if (changedThresholdVal < coldDataCgCtrService.getBrokerConfig().getCgColdReadThreshold()) { changedThresholdVal = coldDataCgCtrService.getBrokerConfig().getCgColdReadThreshold(); } coldDataCgCtrService.addOrUpdateGroupConfig(consumerGroup, changedThresholdVal); } @Override public void collect(Long globalAcc) { } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import com.alibaba.fastjson2.JSON; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.config.ConfigRocksDBStorage; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.rocksdb.CompressionType; import org.rocksdb.FlushOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.Statistics; import org.rocksdb.WriteBatch; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.function.BiConsumer; public class RocksDBConfigManager { protected static final Logger BROKER_LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public static final Charset CHARSET = StandardCharsets.UTF_8; public ConfigRocksDBStorage configRocksDBStorage = null; private FlushOptions flushOptions = null; private volatile long lastFlushMemTableMicroSecond = 0; private final String filePath; private final long memTableFlushInterval; private final CompressionType compressionType; private DataVersion kvDataVersion = new DataVersion(); public static final byte[] KV_DATA_VERSION_COLUMN_FAMILY_NAME = "kvDataVersion".getBytes(CHARSET); public static final byte[] KV_DATA_VERSION_KEY = "kvDataVersionKey".getBytes(CHARSET); private final String defaultCF; private final String versionCF; public RocksDBConfigManager(String filePath, long memTableFlushInterval, CompressionType compressionType, String defaultCF, String versionCF) { this.filePath = filePath; this.memTableFlushInterval = memTableFlushInterval; this.compressionType = compressionType; this.defaultCF = defaultCF; this.versionCF = versionCF; } public RocksDBConfigManager(String filePath, long memTableFlushInterval, CompressionType compressionType) { this.filePath = filePath; this.memTableFlushInterval = memTableFlushInterval; this.compressionType = compressionType; this.defaultCF = new String(RocksDB.DEFAULT_COLUMN_FAMILY, CHARSET); this.versionCF = new String(KV_DATA_VERSION_COLUMN_FAMILY_NAME, CHARSET); } public boolean init(boolean readOnly) { this.configRocksDBStorage = ConfigRocksDBStorage.getStore(filePath, readOnly, compressionType); return this.configRocksDBStorage.start(); } public boolean isLoaded() { return this.configRocksDBStorage != null && this.configRocksDBStorage.isLoaded(); } public boolean init() { return this.init(false); } public boolean loadDataVersion() { String currDataVersionString = null; try { byte[] dataVersion = this.configRocksDBStorage.get(versionCF, KV_DATA_VERSION_KEY); if (dataVersion != null && dataVersion.length > 0) { currDataVersionString = new String(dataVersion, StandardCharsets.UTF_8); } kvDataVersion = StringUtils.isNotBlank(currDataVersionString) ? JSON.parseObject(currDataVersionString, DataVersion.class) : new DataVersion(); return true; } catch (Exception e) { throw new RuntimeException(e); } } public boolean loadData(BiConsumer biConsumer) { try { configRocksDBStorage.iterate(this.defaultCF, biConsumer); } catch (Exception e) { BROKER_LOG.error("RocksDBConfigManager loadData failed", e); return false; } this.flushOptions = new FlushOptions(); this.flushOptions.setWaitForFlush(false); this.flushOptions.setAllowWriteStall(false); return true; } public void start() { } public boolean stop() { ConfigRocksDBStorage.shutdown(filePath); if (this.flushOptions != null) { this.flushOptions.close(); } return true; } public void flushWAL() { try { if (!isLoaded()) { return; } if (this.configRocksDBStorage != null) { this.configRocksDBStorage.flushWAL(); long now = System.currentTimeMillis(); if (now > this.lastFlushMemTableMicroSecond + this.memTableFlushInterval) { this.configRocksDBStorage.flush(this.flushOptions); this.lastFlushMemTableMicroSecond = now; } } } catch (Exception e) { BROKER_LOG.error("kv flush WAL Failed.", e); } } public void put(final byte[] keyBytes, final byte[] valueBytes) throws Exception { this.configRocksDBStorage.put(defaultCF, keyBytes, keyBytes.length, valueBytes); } public void put(String cf, String key, String value) throws Exception { byte[] keyBytes = key.getBytes(CHARSET); this.configRocksDBStorage.put(cf, keyBytes, keyBytes.length, value.getBytes(CHARSET)); } public void put(String cf, final byte[] keyBytes, final byte[] valueBytes) throws Exception { this.configRocksDBStorage.put(cf, keyBytes, keyBytes.length, valueBytes); } public void delete(final byte[] keyBytes) throws Exception { this.configRocksDBStorage.delete(defaultCF, keyBytes); } public void updateKvDataVersion() throws Exception { kvDataVersion.nextVersion(); this.configRocksDBStorage.put(versionCF, KV_DATA_VERSION_KEY, KV_DATA_VERSION_KEY.length, JSON.toJSONString(kvDataVersion).getBytes(StandardCharsets.UTF_8)); } public DataVersion getKvDataVersion() { return kvDataVersion; } // batch operations public void writeBatchPutOperation(WriteBatch writeBatch, final byte[] key, final byte[] value) throws RocksDBException { configRocksDBStorage.writeBatchPutOperation(defaultCF, writeBatch, key, value); } public void batchPutWithWal(final WriteBatch batch) throws Exception { this.configRocksDBStorage.batchPutWithWal(batch); } public Statistics getStatistics() { if (this.configRocksDBStorage == null) { return null; } return configRocksDBStorage.getStatistics(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBConsumerOffsetManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.DataConverter; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.rocksdb.CompressionType; import org.rocksdb.WriteBatch; public class RocksDBConsumerOffsetManager extends ConsumerOffsetManager { protected static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final String VERSION_COLUMN_FAMILY = "consumerOffsetVersion"; private static final String OFFSET_COLUMN_FAMILY = "consumerOffset"; protected transient RocksDBConfigManager rocksDBConfigManager; private final boolean useSingleRocksDBForAllConfigs; private final String storePathRootDir; public RocksDBConsumerOffsetManager(BrokerController brokerController, boolean useSingleRocksDB, String storePathRootDir) { super(brokerController); this.useSingleRocksDBForAllConfigs = useSingleRocksDB; this.storePathRootDir = StringUtils.isBlank(storePathRootDir) ? brokerController.getMessageStoreConfig().getStorePathRootDir() : storePathRootDir; long flushInterval = brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs(); CompressionType compressionType = CompressionType.getCompressionType(brokerController.getMessageStoreConfig().getRocksdbCompressionType()); String rocksDBPath = rocksdbConfigFilePath(storePathRootDir, useSingleRocksDB); this.rocksDBConfigManager = useSingleRocksDB ? new RocksDBConfigManager(rocksDBPath, flushInterval, compressionType, OFFSET_COLUMN_FAMILY, VERSION_COLUMN_FAMILY) : new RocksDBConfigManager(rocksDBPath, flushInterval, compressionType); } public RocksDBConsumerOffsetManager(BrokerController brokerController, boolean useSingleRocksDBForAllConfigs) { this(brokerController, useSingleRocksDBForAllConfigs, null); } public RocksDBConsumerOffsetManager(BrokerController brokerController) { this(brokerController, brokerController.getBrokerConfig().isUseSingleRocksDBForAllConfigs(), null); } @Override public boolean load() { if (!rocksDBConfigManager.init()) { return false; } if (!loadDataVersion() || !loadConsumerOffset()) { return false; } if (useSingleRocksDBForAllConfigs) { migrateFromSeparateRocksDBs(); } return true; } public boolean loadConsumerOffset() { return this.rocksDBConfigManager.loadData(this::decodeOffset) && merge(); } private boolean merge() { if (!UtilAll.isPathExists(this.configFilePath()) && !UtilAll.isPathExists(this.configFilePath() + ".bak")) { log.info("consumerOffset json file does not exist, so skip merge"); return true; } if (!super.loadDataVersion()) { log.error("load json consumerOffset dataVersion error, startup will exit"); return false; } final DataVersion dataVersion = super.getDataVersion(); final DataVersion kvDataVersion = this.getDataVersion(); if (dataVersion.getCounter().get() > kvDataVersion.getCounter().get()) { if (!super.load()) { log.error("load json consumerOffset info failed, startup will exit"); return false; } this.persist(); this.getDataVersion().assignNewOne(dataVersion); updateDataVersion(); log.info("update offset from json, dataVersion:{}, offsetTable: {} ", this.getDataVersion(), JSON.toJSONString(this.getOffsetTable())); } return true; } @Override public boolean stop() { return this.rocksDBConfigManager.stop(); } @Override public void removeConsumerOffset(String topicAtGroup) { try { byte[] keyBytes = topicAtGroup.getBytes(DataConverter.CHARSET_UTF8); this.rocksDBConfigManager.delete(keyBytes); } catch (Exception e) { log.error("kv remove consumerOffset Failed, {}", topicAtGroup); } } protected void decodeOffset(final byte[] key, final byte[] body) { String topicAtGroup = new String(key, DataConverter.CHARSET_UTF8); RocksDBOffsetSerializeWrapper wrapper = JSON.parseObject(body, RocksDBOffsetSerializeWrapper.class); this.offsetTable.put(topicAtGroup, wrapper.getOffsetTable()); log.info("load exist local offset, {}, {}", topicAtGroup, wrapper.getOffsetTable()); } public String rocksdbConfigFilePath(String storePathRootDir, boolean useSingleRocksDBForAllConfigs) { if (StringUtils.isBlank(storePathRootDir)) { storePathRootDir = brokerController.getMessageStoreConfig().getStorePathRootDir(); } Path rootPath = Paths.get(storePathRootDir); if (useSingleRocksDBForAllConfigs) { return rootPath.resolve("config").resolve("metadata").toString(); } return rootPath.resolve("config").resolve("consumerOffsets").toString(); } @Override public String configFilePath() { return BrokerPathConfigHelper.getConsumerOffsetPath(this.storePathRootDir); } @Override public synchronized void persist() { if (rocksDBConfigManager.isLoaded()) { if (brokerController.getBrokerConfig().isPersistConsumerOffsetIncrementally()) { updateDataVersion(); this.rocksDBConfigManager.flushWAL(); return; } try (WriteBatch writeBatch = new WriteBatch()) { for (Entry> entry : this.offsetTable.entrySet()) { putWriteBatch(writeBatch, entry.getKey(), entry.getValue()); if (writeBatch.getDataSize() >= 4 * 1024) { this.rocksDBConfigManager.batchPutWithWal(writeBatch); } } this.rocksDBConfigManager.batchPutWithWal(writeBatch); this.rocksDBConfigManager.flushWAL(); } catch (Exception e) { log.error("consumer offset persist Failed", e); } } else { log.warn("RocksDBConsumerOffsetManager has been stopped, persist fail"); } } @Override public void commitOffset(String clientHost, String group, String topic, int queueId, long offset) { String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap map = this.offsetTable.get(key); if (null == map) { map = MixAll.isLmq(topic) ? new ConcurrentHashMap<>(1, 1.0F) : new ConcurrentHashMap<>(); map.put(queueId, offset); this.offsetTable.put(key, map); } else { Long storeOffset = map.put(queueId, offset); if (storeOffset != null && offset < storeOffset) { LOG.warn("[NOTIFYME]update consumer offset less than store. clientHost={}, key={}, queueId={}, requestOffset={}, storeOffset={}", clientHost, key, queueId, offset, storeOffset); } } if (versionChangeCounter.incrementAndGet() % brokerController.getBrokerConfig().getConsumerOffsetUpdateVersionStep() == 0) { updateDataVersion(); } if (!brokerController.getBrokerConfig().isPersistConsumerOffsetIncrementally()) { return; } try (WriteBatch writeBatch = new WriteBatch()) { putWriteBatch(writeBatch, key, map); this.rocksDBConfigManager.batchPutWithWal(writeBatch); } catch (Exception e) { log.error("consumer offset persist Failed", e); } } public synchronized void exportToJson() { log.info("RocksDBConsumerOffsetManager export consumer offset to json file"); super.persist(); } private void putWriteBatch(final WriteBatch writeBatch, final String topicGroupName, final ConcurrentMap offsetMap) throws Exception { byte[] keyBytes = topicGroupName.getBytes(DataConverter.CHARSET_UTF8); RocksDBOffsetSerializeWrapper wrapper = new RocksDBOffsetSerializeWrapper(); wrapper.setOffsetTable(offsetMap); byte[] valueBytes = JSON.toJSONBytes(wrapper, JSONWriter.Feature.BrowserCompatible); rocksDBConfigManager.writeBatchPutOperation(writeBatch, keyBytes, valueBytes); } @Override public boolean loadDataVersion() { return this.rocksDBConfigManager.loadDataVersion(); } @Override public DataVersion getDataVersion() { return rocksDBConfigManager.getKvDataVersion(); } @Override public void updateDataVersion() { try { rocksDBConfigManager.updateKvDataVersion(); } catch (Exception e) { log.error("update consumer offset dataVersion error", e); throw new RuntimeException(e); } } /** * Migrate data from separate RocksDB instances to the unified RocksDB when useSingleRocksDBForAllConfigs is * enabled. * This method will only be called when switching from separate RocksDB mode to unified mode. * It opens the separate RocksDB in read-only mode, compares versions, and imports data if needed. */ private void migrateFromSeparateRocksDBs() { String separateRocksDBPath = rocksdbConfigFilePath(this.storePathRootDir, false); // Check if separate RocksDB exists if (!UtilAll.isPathExists(separateRocksDBPath)) { log.info("Separate RocksDB for consumer offsets does not exist at {}, no migration needed", separateRocksDBPath); return; } log.info("Starting migration from separate RocksDB at {} to unified RocksDB", separateRocksDBPath); // Open separate RocksDB in read-only mode RocksDBConfigManager separateRocksDBConfigManager = null; try { long memTableFlushIntervalMs = brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs(); org.rocksdb.CompressionType compressionType = org.rocksdb.CompressionType.getCompressionType(brokerController.getMessageStoreConfig().getRocksdbCompressionType()); separateRocksDBConfigManager = new RocksDBConfigManager(separateRocksDBPath, memTableFlushIntervalMs, compressionType); // Initialize in read-only mode if (!separateRocksDBConfigManager.init(true)) { log.error("Failed to initialize separate RocksDB in read-only mode"); return; } // Load data version from separate RocksDB if (!separateRocksDBConfigManager.loadDataVersion()) { log.error("Failed to load data version from separate RocksDB"); return; } DataVersion separateDataVersion = separateRocksDBConfigManager.getKvDataVersion(); DataVersion unifiedDataVersion = this.getDataVersion(); log.info("Comparing data versions - Separate: {}, Unified: {}", separateDataVersion, unifiedDataVersion); // Compare versions and import if separate version is newer if (separateDataVersion.getCounter().get() > unifiedDataVersion.getCounter().get()) { log.info("Separate RocksDB has newer data, importing..."); // Load consumer offsets from separate RocksDB if (separateRocksDBConfigManager.loadData(this::importConsumerOffset)) { log.info("Successfully imported consumer offsets from separate RocksDB"); // Update unified data version to be newer than separate one this.getDataVersion().assignNewOne(separateDataVersion); this.getDataVersion().nextVersion(); // Make it one version higher updateDataVersion(); log.info("Updated unified data version to {}", this.getDataVersion()); } else { log.error("Failed to import consumer offsets from separate RocksDB"); } } else { log.info("Unified RocksDB is already up-to-date, no migration needed"); } } catch (Exception e) { log.error("Error during migration from separate RocksDB", e); } finally { // Clean up resources if (separateRocksDBConfigManager != null) { try { separateRocksDBConfigManager.stop(); } catch (Exception e) { log.warn("Error stopping separate RocksDB config manager", e); } } } } /** * Import a consumer offset from the separate RocksDB during migration * * @param key The topic@group name bytes * @param body The consumer offset data bytes */ private void importConsumerOffset(final byte[] key, final byte[] body) { try { decodeOffset(key, body); this.rocksDBConfigManager.put(key, body); } catch (Exception e) { log.error("Error importing consumer offset", e); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBLmqSubscriptionGroupManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class RocksDBLmqSubscriptionGroupManager extends RocksDBSubscriptionGroupManager { public RocksDBLmqSubscriptionGroupManager(BrokerController brokerController) { super(brokerController); } @Override public SubscriptionGroupConfig findSubscriptionGroupConfig(final String group) { if (MixAll.isLmq(group)) { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); return subscriptionGroupConfig; } return super.findSubscriptionGroupConfig(group); } @Override public void updateSubscriptionGroupConfig(final SubscriptionGroupConfig config) { if (config == null || MixAll.isLmq(config.getGroupName())) { return; } super.updateSubscriptionGroupConfig(config); } @Override public boolean containsSubscriptionGroup(String group) { if (MixAll.isLmq(group)) { return true; } else { return super.containsSubscriptionGroup(group); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBLmqTopicConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; public class RocksDBLmqTopicConfigManager extends RocksDBTopicConfigManager { public RocksDBLmqTopicConfigManager(BrokerController brokerController) { super(brokerController); } @Override public TopicConfig selectTopicConfig(final String topic) { if (MixAll.isLmq(topic)) { return simpleLmqTopicConfig(topic); } return super.selectTopicConfig(topic); } @Override public void updateTopicConfig(final TopicConfig topicConfig) { if (topicConfig == null || MixAll.isLmq(topicConfig.getTopicName())) { return; } super.updateTopicConfig(topicConfig); } @Override public boolean containsTopic(String topic) { if (MixAll.isLmq(topic)) { return true; } return super.containsTopic(topic); } private TopicConfig simpleLmqTopicConfig(String topic) { return new TopicConfig(topic, 1, 1, PermName.PERM_READ | PermName.PERM_WRITE); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBOffsetSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class RocksDBOffsetSerializeWrapper extends RemotingSerializable { private ConcurrentMap offsetTable = null; public ConcurrentMap getOffsetTable() { return offsetTable; } public void setOffsetTable(ConcurrentMap offsetTable) { this.offsetTable = offsetTable; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBSubscriptionGroupManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.rocksdb.CompressionType; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.BiConsumer; public class RocksDBSubscriptionGroupManager extends SubscriptionGroupManager { protected transient RocksDBConfigManager rocksDBConfigManager; private static final String VERSION_COLUMN_FAMILY = "subscriptionGroupVersion"; private static final String GROUP_COLUMN_FAMILY = "subscriptionGroup"; private static final String FORBIDDEN_COLUMN_FAMILY_NAME = "forbidden"; private final boolean useSingleRocksDBForAllConfigs; private final String storePathRootDir; public RocksDBSubscriptionGroupManager(BrokerController brokerController, boolean useSingleRocksDB, String storePathRootDir) { super(brokerController, false); this.useSingleRocksDBForAllConfigs = useSingleRocksDB; this.storePathRootDir = StringUtils.isBlank(storePathRootDir) ? brokerController.getMessageStoreConfig().getStorePathRootDir() : storePathRootDir; long flushInterval = brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs(); CompressionType compressionType = CompressionType.getCompressionType(brokerController.getMessageStoreConfig().getRocksdbCompressionType()); String rocksDBPath = rocksdbConfigFilePath(storePathRootDir, useSingleRocksDB); this.rocksDBConfigManager = useSingleRocksDB ? new RocksDBConfigManager(rocksDBPath, flushInterval, compressionType, GROUP_COLUMN_FAMILY, VERSION_COLUMN_FAMILY) : new RocksDBConfigManager(rocksDBPath, flushInterval, compressionType); } public RocksDBSubscriptionGroupManager(BrokerController brokerController, boolean useSingleRocksDBForAllConfigs) { this(brokerController, useSingleRocksDBForAllConfigs, null); } public RocksDBSubscriptionGroupManager(BrokerController brokerController) { this(brokerController, brokerController.getBrokerConfig().isUseSingleRocksDBForAllConfigs(), null); } @Override public boolean load() { if (!rocksDBConfigManager.init()) { return false; } if (!loadDataVersion() || !loadSubscriptionGroupAndForbidden()) { return false; } if (useSingleRocksDBForAllConfigs) { migrateFromSeparateRocksDBs(); } this.init(); return true; } public boolean loadDataVersion() { return this.rocksDBConfigManager.loadDataVersion(); } public boolean loadSubscriptionGroupAndForbidden() { return this.rocksDBConfigManager.loadData(this::decodeSubscriptionGroup) && this.loadForbidden(this::decodeForbidden) && merge(); } public boolean loadForbidden(BiConsumer biConsumer) { try { this.rocksDBConfigManager.configRocksDBStorage.iterate(FORBIDDEN_COLUMN_FAMILY_NAME, biConsumer); return true; } catch (Exception e) { log.error("loadForbidden exception", e); } return false; } private boolean merge() { if (!UtilAll.isPathExists(this.configFilePath()) && !UtilAll.isPathExists(this.configFilePath() + ".bak")) { log.info("subGroup json file does not exist, so skip merge"); return true; } if (!super.loadDataVersion()) { log.error("load json subGroup dataVersion error, startup will exit"); return false; } final DataVersion dataVersion = super.getDataVersion(); final DataVersion kvDataVersion = this.getDataVersion(); if (dataVersion.getCounter().get() > kvDataVersion.getCounter().get()) { if (!super.load()) { log.error("load group and forbidden info from json file error, startup will exit"); return false; } final ConcurrentMap groupTable = this.getSubscriptionGroupTable(); for (Map.Entry entry : groupTable.entrySet()) { putSubscriptionGroupConfig(entry.getValue()); log.info("import subscription config to rocksdb, group={}", entry.getValue()); } final ConcurrentMap> forbiddenTable = this.getForbiddenTable(); for (Map.Entry> entry : forbiddenTable.entrySet()) { try { this.rocksDBConfigManager.put(FORBIDDEN_COLUMN_FAMILY_NAME, entry.getKey(), JSON.toJSONString(entry.getValue())); log.info("import forbidden config to rocksdb, group={}", entry.getValue()); } catch (Exception e) { log.error("import forbidden config to rocksdb failed, group={}", entry.getValue()); return false; } } this.getDataVersion().assignNewOne(dataVersion); updateDataVersion(); } else { log.info("dataVersion is not greater than kvDataVersion, no need to merge group metaData, dataVersion={}, kvDataVersion={}", dataVersion, kvDataVersion); } log.info("finish merge subscription config from json file and merge to rocksdb"); this.persist(); return true; } @Override public boolean stop() { return this.rocksDBConfigManager.stop(); } @Override public SubscriptionGroupConfig putSubscriptionGroupConfig(SubscriptionGroupConfig subscriptionGroupConfig) { String groupName = subscriptionGroupConfig.getGroupName(); SubscriptionGroupConfig oldConfig = this.subscriptionGroupTable.put(groupName, subscriptionGroupConfig); try { byte[] keyBytes = groupName.getBytes(RocksDBConfigManager.CHARSET); byte[] valueBytes = JSON.toJSONBytes(subscriptionGroupConfig, JSONWriter.Feature.BrowserCompatible); this.rocksDBConfigManager.put(keyBytes, valueBytes); } catch (Exception e) { log.error("kv put sub Failed, {}", subscriptionGroupConfig.toString()); } return oldConfig; } @Override protected SubscriptionGroupConfig putSubscriptionGroupConfigIfAbsent(SubscriptionGroupConfig subscriptionGroupConfig) { String groupName = subscriptionGroupConfig.getGroupName(); SubscriptionGroupConfig oldConfig = this.subscriptionGroupTable.putIfAbsent(groupName, subscriptionGroupConfig); if (oldConfig == null) { try { byte[] keyBytes = groupName.getBytes(RocksDBConfigManager.CHARSET); byte[] valueBytes = JSON.toJSONBytes(subscriptionGroupConfig, JSONWriter.Feature.BrowserCompatible); this.rocksDBConfigManager.put(keyBytes, valueBytes); } catch (Exception e) { log.error("kv put sub Failed, {}", subscriptionGroupConfig.toString()); } } return oldConfig; } @Override protected SubscriptionGroupConfig removeSubscriptionGroupConfig(String groupName) { SubscriptionGroupConfig subscriptionGroupConfig = this.subscriptionGroupTable.remove(groupName); try { this.rocksDBConfigManager.delete(groupName.getBytes(RocksDBConfigManager.CHARSET)); } catch (Exception e) { log.error("kv delete sub Failed, {}", subscriptionGroupConfig.toString()); } return subscriptionGroupConfig; } protected void decodeSubscriptionGroup(byte[] key, byte[] body) { String groupName = new String(key, RocksDBConfigManager.CHARSET); SubscriptionGroupConfig subscriptionGroupConfig = JSON.parseObject(body, SubscriptionGroupConfig.class); this.subscriptionGroupTable.put(groupName, subscriptionGroupConfig); log.info("load exist local sub, {}", subscriptionGroupConfig.toString()); } @Override public synchronized void persist() { if (brokerController.getMessageStoreConfig().isRealTimePersistRocksDBConfig()) { this.rocksDBConfigManager.flushWAL(); } } public synchronized void exportToJson() { log.info("RocksDBSubscriptionGroupManager export subscription group to json file"); super.persist(); } public String rocksdbConfigFilePath(String storePathRootDir, boolean useSingleRocksDBForAllConfigs) { if (StringUtils.isBlank(storePathRootDir)) { storePathRootDir = brokerController.getMessageStoreConfig().getStorePathRootDir(); } Path rootPath = Paths.get(storePathRootDir); if (useSingleRocksDBForAllConfigs) { return rootPath.resolve("config").resolve("metadata").toString(); } return rootPath.resolve("config").resolve("subscriptionGroups").toString(); } @Override public String configFilePath() { return BrokerPathConfigHelper.getSubscriptionGroupPath(this.storePathRootDir); } @Override public DataVersion getDataVersion() { return rocksDBConfigManager.getKvDataVersion(); } @Override public void updateDataVersion() { try { rocksDBConfigManager.updateKvDataVersion(); } catch (Exception e) { log.error("update group config dataVersion error", e); throw new RuntimeException(e); } } protected void decodeForbidden(byte[] key, byte[] body) { String forbiddenGroupName = new String(key, RocksDBConfigManager.CHARSET); JSONObject jsonObject = JSON.parseObject(new String(body, RocksDBConfigManager.CHARSET)); Set> entries = jsonObject.entrySet(); ConcurrentMap forbiddenGroup = new ConcurrentHashMap<>(entries.size()); for (Map.Entry entry : entries) { forbiddenGroup.put(entry.getKey(), (Integer) entry.getValue()); } this.getForbiddenTable().put(forbiddenGroupName, forbiddenGroup); log.info("load forbidden,{} value {}", forbiddenGroupName, forbiddenGroup.toString()); } @Override public void updateForbidden(String group, String topic, int forbiddenIndex, boolean setOrClear) { try { super.updateForbidden(group, topic, forbiddenIndex, setOrClear); this.rocksDBConfigManager.put(FORBIDDEN_COLUMN_FAMILY_NAME, group, JSON.toJSONString(this.getForbiddenTable().get(group))); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void setForbidden(String group, String topic, int forbiddenIndex) { try { super.setForbidden(group, topic, forbiddenIndex); this.rocksDBConfigManager.put(FORBIDDEN_COLUMN_FAMILY_NAME, group, JSON.toJSONString(this.getForbiddenTable().get(group))); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void clearForbidden(String group, String topic, int forbiddenIndex) { try { super.clearForbidden(group, topic, forbiddenIndex); this.rocksDBConfigManager.put(FORBIDDEN_COLUMN_FAMILY_NAME, group, JSON.toJSONString(this.getForbiddenTable().get(group))); } catch (Exception e) { throw new RuntimeException(e); } } /** * Migrate data from separate RocksDB instances to the unified RocksDB when useSingleRocksDBForAllConfigs is * enabled. * This method will only be called when switching from separate RocksDB mode to unified mode. * It opens the separate RocksDB in read-only mode, compares versions, and imports data if needed. */ private void migrateFromSeparateRocksDBs() { String separateRocksDBPath = rocksdbConfigFilePath(this.storePathRootDir, false); // Check if separate RocksDB exists if (!org.apache.rocketmq.common.UtilAll.isPathExists(separateRocksDBPath)) { log.info("Separate RocksDB for subscription groups does not exist at {}, no migration needed", separateRocksDBPath); return; } log.info("Starting migration from separate RocksDB at {} to unified RocksDB", separateRocksDBPath); // Open separate RocksDB in read-only mode RocksDBConfigManager separateRocksDBConfigManager = null; try { long memTableFlushIntervalMs = brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs(); org.rocksdb.CompressionType compressionType = org.rocksdb.CompressionType.getCompressionType(brokerController.getMessageStoreConfig().getRocksdbCompressionType()); separateRocksDBConfigManager = new RocksDBConfigManager(separateRocksDBPath, memTableFlushIntervalMs, compressionType); // Initialize in read-only mode if (!separateRocksDBConfigManager.init(true)) { log.error("Failed to initialize separate RocksDB in read-only mode"); return; } // Load data version from separate RocksDB if (!separateRocksDBConfigManager.loadDataVersion()) { log.error("Failed to load data version from separate RocksDB"); return; } org.apache.rocketmq.remoting.protocol.DataVersion separateDataVersion = separateRocksDBConfigManager.getKvDataVersion(); org.apache.rocketmq.remoting.protocol.DataVersion unifiedDataVersion = this.getDataVersion(); log.info("Comparing data versions - Separate: {}, Unified: {}", separateDataVersion, unifiedDataVersion); // Compare versions and import if separate version is newer if (separateDataVersion.getCounter().get() > unifiedDataVersion.getCounter().get()) { log.info("Separate RocksDB has newer data, importing..."); // Load subscription groups from separate RocksDB boolean success = separateRocksDBConfigManager.loadData(this::importSubscriptionGroup); if (success) { // Load forbidden data directly using the storage try { separateRocksDBConfigManager.configRocksDBStorage.iterate(FORBIDDEN_COLUMN_FAMILY_NAME, this::importForbidden); log.info("Successfully imported subscription groups and forbidden data from separate RocksDB"); // Update unified data version to be newer than separate one this.getDataVersion().assignNewOne(separateDataVersion); this.getDataVersion().nextVersion(); // Make it one version higher updateDataVersion(); log.info("Updated unified data version to {}", this.getDataVersion()); } catch (Exception e) { log.error("Failed to import forbidden data from separate RocksDB", e); success = false; } } if (!success) { log.error("Failed to import subscription groups or forbidden data from separate RocksDB"); } } else { log.info("Unified RocksDB is already up-to-date, no migration needed"); } } catch (Exception e) { log.error("Error during migration from separate RocksDB", e); } finally { // Clean up resources if (separateRocksDBConfigManager != null) { try { separateRocksDBConfigManager.stop(); } catch (Exception e) { log.warn("Error stopping separate RocksDB config manager", e); } } } } /** * Import a subscription group from the separate RocksDB during migration * * @param key The group name bytes * @param body The subscription group data bytes */ private void importSubscriptionGroup(byte[] key, byte[] body) { try { decodeSubscriptionGroup(key, body); this.rocksDBConfigManager.put(key, body); } catch (Exception e) { log.error("Error importing subscription group", e); } } /** * Import forbidden data from the separate RocksDB during migration * * @param key The group name bytes * @param body The forbidden data bytes */ private void importForbidden(byte[] key, byte[] body) { try { decodeForbidden(key, body); this.rocksDBConfigManager.put(FORBIDDEN_COLUMN_FAMILY_NAME, key, body); } catch (Exception e) { log.error("Error importing forbidden data", e); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v1/RocksDBTopicConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.utils.DataConverter; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.rocksdb.CompressionType; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; import java.util.concurrent.ConcurrentMap; public class RocksDBTopicConfigManager extends TopicConfigManager { private static final String VERSION_COLUMN_FAMILY = "topicVersion"; private static final String TOPIC_COLUMN_FAMILY = "topic"; protected transient RocksDBConfigManager rocksDBConfigManager; private final boolean useSingleRocksDBForAllConfigs; private final String storePathRootDir; public RocksDBTopicConfigManager(BrokerController brokerController, boolean useSingleRocksDB, String storePathRootDir) { super(brokerController, false); this.useSingleRocksDBForAllConfigs = useSingleRocksDB; this.storePathRootDir = StringUtils.isBlank(storePathRootDir) ? brokerController.getMessageStoreConfig().getStorePathRootDir() : storePathRootDir; long flushInterval = brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs(); CompressionType compressionType = CompressionType.getCompressionType(brokerController.getMessageStoreConfig().getRocksdbCompressionType()); String rocksDBPath = rocksdbConfigFilePath(storePathRootDir, useSingleRocksDB); this.rocksDBConfigManager = useSingleRocksDB ? new RocksDBConfigManager(rocksDBPath, flushInterval, compressionType, TOPIC_COLUMN_FAMILY, VERSION_COLUMN_FAMILY) : new RocksDBConfigManager(rocksDBPath, flushInterval, compressionType); } public RocksDBTopicConfigManager(BrokerController brokerController, boolean useSingleRocksDBForAllConfigs) { this(brokerController, useSingleRocksDBForAllConfigs, null); } public RocksDBTopicConfigManager(BrokerController brokerController) { this(brokerController, brokerController.getBrokerConfig().isUseSingleRocksDBForAllConfigs(), null); } @Override public boolean load() { if (!rocksDBConfigManager.init()) { return false; } if (!loadDataVersion() || !loadTopicConfig()) { return false; } if (useSingleRocksDBForAllConfigs) { migrateFromSeparateRocksDBs(); } this.init(); return true; } public boolean loadTopicConfig() { return this.rocksDBConfigManager.loadData(this::decodeTopicConfig) && merge(); } public boolean loadDataVersion() { return this.rocksDBConfigManager.loadDataVersion(); } private boolean merge() { if (!UtilAll.isPathExists(this.configFilePath()) && !UtilAll.isPathExists(this.configFilePath() + ".bak")) { log.info("topic json file does not exist, so skip merge"); return true; } if (!super.loadDataVersion()) { log.error("load json topic dataVersion error, startup will exit"); return false; } final DataVersion dataVersion = super.getDataVersion(); final DataVersion kvDataVersion = this.getDataVersion(); if (dataVersion.getCounter().get() > kvDataVersion.getCounter().get()) { if (!super.load()) { log.error("load topic config from json file error, startup will exit"); return false; } final ConcurrentMap topicConfigTable = this.getTopicConfigTable(); for (Map.Entry entry : topicConfigTable.entrySet()) { putTopicConfig(entry.getValue()); log.info("import topic config to rocksdb, topic={}", entry.getValue()); } this.getDataVersion().assignNewOne(dataVersion); updateDataVersion(); } else { log.info("dataVersion is not greater than kvDataVersion, no need to merge topic metaData, dataVersion={}, kvDataVersion={}", dataVersion, kvDataVersion); } log.info("finish read topic config from json file and merge to rocksdb"); this.persist(); return true; } @Override public boolean stop() { return this.rocksDBConfigManager.stop(); } protected void decodeTopicConfig(byte[] key, byte[] body) { String topicName = new String(key, DataConverter.CHARSET_UTF8); TopicConfig topicConfig = JSON.parseObject(body, TopicConfig.class); this.topicConfigTable.put(topicName, topicConfig); log.info("load exist local topic, {}", topicConfig.toString()); } @Override public TopicConfig putTopicConfig(TopicConfig topicConfig) { String topicName = topicConfig.getTopicName(); TopicConfig oldTopicConfig = this.topicConfigTable.put(topicName, topicConfig); try { byte[] keyBytes = topicName.getBytes(DataConverter.CHARSET_UTF8); byte[] valueBytes = JSON.toJSONBytes(topicConfig, JSONWriter.Feature.BrowserCompatible); this.rocksDBConfigManager.put(keyBytes, valueBytes); } catch (Exception e) { log.error("kv put topic Failed, {}", topicConfig.toString(), e); } return oldTopicConfig; } @Override protected TopicConfig removeTopicConfig(String topicName) { TopicConfig topicConfig = this.topicConfigTable.remove(topicName); try { this.rocksDBConfigManager.delete(topicName.getBytes(DataConverter.CHARSET_UTF8)); } catch (Exception e) { log.error("kv remove topic Failed, {}", topicConfig.toString()); } return topicConfig; } @Override public synchronized void persist() { if (brokerController.getMessageStoreConfig().isRealTimePersistRocksDBConfig()) { this.rocksDBConfigManager.flushWAL(); } } public synchronized void exportToJson() { log.info("RocksDBTopicConfigManager export topic config to json file"); super.persist(); } public String rocksdbConfigFilePath(String storePathRootDir, boolean useSingleRocksDBForAllConfigs) { if (StringUtils.isBlank(storePathRootDir)) { storePathRootDir = brokerController.getMessageStoreConfig().getStorePathRootDir(); } Path rootPath = Paths.get(storePathRootDir); if (useSingleRocksDBForAllConfigs) { return rootPath.resolve("config").resolve("metadata").toString(); } return rootPath.resolve("config").resolve("topics").toString(); } @Override public String configFilePath() { return BrokerPathConfigHelper.getTopicConfigPath(this.storePathRootDir); } @Override public DataVersion getDataVersion() { return rocksDBConfigManager.getKvDataVersion(); } @Override public void updateDataVersion() { try { rocksDBConfigManager.updateKvDataVersion(); } catch (Exception e) { log.error("update topic config dataVersion error", e); throw new RuntimeException(e); } } /** * Migrate data from separate RocksDB instances to the unified RocksDB when useSingleRocksDBForAllConfigs is * enabled. * This method will only be called when switching from separate RocksDB mode to unified mode. * It opens the separate RocksDB in read-only mode, compares versions, and imports data if needed. */ private void migrateFromSeparateRocksDBs() { String separateRocksDBPath = rocksdbConfigFilePath(this.storePathRootDir, false); // Check if separate RocksDB exists if (!UtilAll.isPathExists(separateRocksDBPath)) { log.info("Separate RocksDB for topics does not exist at {}, no migration needed", separateRocksDBPath); return; } log.info("Starting migration from separate RocksDB at {} to unified RocksDB", separateRocksDBPath); // Open separate RocksDB in read-only mode RocksDBConfigManager separateRocksDBConfigManager = null; try { long memTableFlushIntervalMs = brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs(); org.rocksdb.CompressionType compressionType = org.rocksdb.CompressionType.getCompressionType(brokerController.getMessageStoreConfig().getRocksdbCompressionType()); separateRocksDBConfigManager = new RocksDBConfigManager(separateRocksDBPath, memTableFlushIntervalMs, compressionType); // Initialize in read-only mode if (!separateRocksDBConfigManager.init(true)) { log.error("Failed to initialize separate RocksDB in read-only mode"); return; } // Load data version from separate RocksDB if (!separateRocksDBConfigManager.loadDataVersion()) { log.error("Failed to load data version from separate RocksDB"); return; } DataVersion separateDataVersion = separateRocksDBConfigManager.getKvDataVersion(); DataVersion unifiedDataVersion = this.getDataVersion(); log.info("Comparing data versions - Separate: {}, Unified: {}", separateDataVersion, unifiedDataVersion); // Compare versions and import if separate version is newer if (separateDataVersion.getCounter().get() > unifiedDataVersion.getCounter().get()) { log.info("Separate RocksDB has newer data, importing..."); // Load topic configs from separate RocksDB if (separateRocksDBConfigManager.loadData(this::importTopicConfig)) { log.info("Successfully imported topic configs from separate RocksDB"); this.getDataVersion().assignNewOne(separateDataVersion); this.getDataVersion().nextVersion(); // Make it one version higher updateDataVersion(); log.info("Updated unified data version to {}", this.getDataVersion()); } else { log.error("Failed to import topic configs from separate RocksDB"); } } else { log.info("Unified RocksDB is already up-to-date, no migration needed"); } } catch (Exception e) { log.error("Error during migration from separate RocksDB", e); } finally { if (separateRocksDBConfigManager != null) { try { separateRocksDBConfigManager.stop(); } catch (Exception e) { log.warn("Error stopping separate RocksDB config manager", e); } } } } /** * Import a topic config from the separate RocksDB during migration * * @param key The topic name bytes * @param body The topic config data bytes */ private void importTopicConfig(byte[] key, byte[] body) { try { decodeTopicConfig(key, body); this.rocksDBConfigManager.put(key, body); } catch (Exception e) { log.error("Error importing topic config", e); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/ConfigHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import com.alibaba.fastjson2.JSON; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.rocksdb.RocksDBException; import org.rocksdb.WriteBatch; public class ConfigHelper { /** *

* Layout of data version key: * [table-prefix, 1 byte][table-id, 2 byte][record-prefix, 1 byte][data-version-bytes] *

* *

* Layout of data version value: * [state-machine-version, 8 bytes][timestamp, 8 bytes][sequence counter, 8 bytes] *

* * @throws RocksDBException if RocksDB raises an error */ public static Optional loadDataVersion(ConfigStorage configStorage, TableId tableId) throws RocksDBException { int keyLen = 1 /* table-prefix */ + Short.BYTES /* table-id */ + 1 /* record-prefix */ + ConfigStorage.DATA_VERSION_KEY_BYTES.length; ByteBuf keyBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); try { keyBuf.writeByte(TablePrefix.TABLE.getValue()); keyBuf.writeShort(tableId.getValue()); keyBuf.writeByte(RecordPrefix.DATA_VERSION.getValue()); keyBuf.writeBytes(ConfigStorage.DATA_VERSION_KEY_BYTES); byte[] valueByes = configStorage.get(keyBuf.nioBuffer()); if (null != valueByes) { ByteBuf valueBuf = Unpooled.wrappedBuffer(valueByes); return Optional.of(valueBuf); } } finally { keyBuf.release(); } return Optional.empty(); } public static void stampDataVersion(WriteBatch writeBatch, TableId table, DataVersion dataVersion, long stateMachineVersion) throws RocksDBException { // Increase data version dataVersion.nextVersion(stateMachineVersion); int keyLen = 1 /* table-prefix */ + Short.BYTES /* table-id */ + 1 /* record-prefix */ + ConfigStorage.DATA_VERSION_KEY_BYTES.length; ByteBuf keyBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); ByteBuf valueBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(Long.BYTES * 3); try { keyBuf.writeByte(TablePrefix.TABLE.getValue()); keyBuf.writeShort(table.getValue()); keyBuf.writeByte(RecordPrefix.DATA_VERSION.getValue()); keyBuf.writeBytes(ConfigStorage.DATA_VERSION_KEY_BYTES); valueBuf.writeLong(dataVersion.getStateVersion()); valueBuf.writeLong(dataVersion.getTimestamp()); valueBuf.writeLong(dataVersion.getCounter().get()); writeBatch.put(keyBuf.nioBuffer(), valueBuf.nioBuffer()); } finally { keyBuf.release(); valueBuf.release(); } } public static void onDataVersionLoad(ByteBuf buf, DataVersion dataVersion) { if (buf.readableBytes() == 8 /* state machine version */ + 8 /* timestamp */ + 8 /* counter */) { long stateMachineVersion = buf.readLong(); long timestamp = buf.readLong(); long counter = buf.readLong(); dataVersion.setStateVersion(stateMachineVersion); dataVersion.setTimestamp(timestamp); dataVersion.setCounter(new AtomicLong(counter)); } buf.release(); } public static ByteBuf keyBufOf(TableId tableId, final String name) { Preconditions.checkNotNull(name); byte[] bytes = name.getBytes(StandardCharsets.UTF_8); int keyLen = 1 /* table-prefix */ + 2 /* table-id */ + 1 /* record-type-prefix */ + 2 /* name-length */ + bytes.length; ByteBuf keyBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); keyBuf.writeByte(TablePrefix.TABLE.getValue()); keyBuf.writeShort(tableId.getValue()); keyBuf.writeByte(RecordPrefix.DATA.getValue()); keyBuf.writeShort(bytes.length); keyBuf.writeBytes(bytes); return keyBuf; } public static ByteBuf valueBufOf(final Object config, SerializationType serializationType) { if (SerializationType.JSON == serializationType) { byte[] payload = JSON.toJSONBytes(config); ByteBuf valueBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(1 + payload.length); valueBuf.writeByte(SerializationType.JSON.getValue()); valueBuf.writeBytes(payload); return valueBuf; } throw new RuntimeException("Unsupported serialization type: " + serializationType); } public static byte[] readBytes(final ByteBuf buf) { byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); return bytes; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/ConfigStorage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.buffer.PooledByteBufAllocatorMetric; import io.netty.util.internal.PlatformDependent; import java.io.File; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.common.config.ConfigHelper; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.FlushOptions; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Slice; import org.rocksdb.WriteBatch; import org.rocksdb.WriteOptions; /** * https://book.tidb.io/session1/chapter3/tidb-kv-to-relation.html */ public class ConfigStorage extends AbstractRocksDBStorage { public static final String DATA_VERSION_KEY = "data_version"; public static final byte[] DATA_VERSION_KEY_BYTES = DATA_VERSION_KEY.getBytes(StandardCharsets.UTF_8); private final ScheduledExecutorService scheduledExecutorService; /** * Number of write ops since previous flush. */ private final AtomicInteger writeOpsCounter; private final AtomicLong estimateWalFileSize = new AtomicLong(0L); private final MessageStoreConfig messageStoreConfig; private final FlushSyncService flushSyncService; public ConfigStorage(MessageStoreConfig messageStoreConfig) { super(messageStoreConfig.getStorePathRootDir() + File.separator + "config" + File.separator + "rdb"); this.messageStoreConfig = messageStoreConfig; ThreadFactory threadFactory = new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("config-storage-%d") .build(); scheduledExecutorService = new ScheduledThreadPoolExecutor(1, threadFactory); writeOpsCounter = new AtomicInteger(0); this.flushSyncService = new FlushSyncService(); this.flushSyncService.setDaemon(true); } private void statNettyMemory() { PooledByteBufAllocatorMetric metric = AbstractRocksDBStorage.POOLED_ALLOCATOR.metric(); LOGGER.info("Netty Memory Usage: {}", metric); } @Override public synchronized boolean start() { boolean started = super.start(); if (started) { scheduledExecutorService.scheduleWithFixedDelay(() -> statRocksdb(LOGGER), 1, 10, TimeUnit.SECONDS); scheduledExecutorService.scheduleWithFixedDelay(this::statNettyMemory, 10, 10, TimeUnit.SECONDS); this.flushSyncService.start(); } else { LOGGER.error("Failed to start config storage"); } return started; } @Override protected boolean postLoad() { if (!PlatformDependent.hasUnsafe()) { LOGGER.error("Unsafe not available and POOLED_ALLOCATOR cannot work correctly"); return false; } try { UtilAll.ensureDirOK(this.dbPath); initOptions(); List cfDescriptors = new ArrayList<>(); ColumnFamilyOptions defaultOptions = ConfigHelper.createConfigColumnFamilyOptions(); this.cfOptions.add(defaultOptions); cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, defaultOptions)); // Start RocksDB instance open(cfDescriptors); this.defaultCFHandle = cfHandles.get(0); } catch (Exception e) { AbstractRocksDBStorage.LOGGER.error("postLoad Failed. {}", this.dbPath, e); return false; } return true; } @Override protected void preShutdown() { scheduledExecutorService.shutdown(); flushSyncService.shutdown(); } protected void initOptions() { this.options = ConfigHelper.createConfigDBOptions(); super.initOptions(); } @Override protected void initAbleWalWriteOptions() { this.ableWalWriteOptions = new WriteOptions(); // Given that fdatasync is kind of expensive, sync-WAL for every write cannot be afforded. this.ableWalWriteOptions.setSync(false); // We need WAL for config changes this.ableWalWriteOptions.setDisableWAL(false); // No fast failure on block, wait synchronously even if there is wait for the write request this.ableWalWriteOptions.setNoSlowdown(false); } public byte[] get(ByteBuffer key) throws RocksDBException { byte[] keyBytes = new byte[key.remaining()]; key.get(keyBytes); return super.get(getDefaultCFHandle(), totalOrderReadOptions, keyBytes); } public void write(WriteBatch writeBatch) throws RocksDBException { db.write(ableWalWriteOptions, writeBatch); accountWriteOps(writeBatch.getDataSize()); } private void accountWriteOps(long dataSize) { writeOpsCounter.incrementAndGet(); estimateWalFileSize.addAndGet(dataSize); } public RocksIterator iterate(ByteBuffer beginKey, ByteBuffer endKey) { try (ReadOptions readOptions = new ReadOptions()) { readOptions.setTotalOrderSeek(true); readOptions.setTailing(false); readOptions.setAutoPrefixMode(true); // Use DirectSlice till the follow issue is fixed: // https://github.com/facebook/rocksdb/issues/13098 // // readOptions.setIterateUpperBound(new DirectSlice(endKey)); byte[] buf = new byte[endKey.remaining()]; endKey.slice().get(buf); readOptions.setIterateUpperBound(new Slice(buf)); RocksIterator iterator = db.newIterator(defaultCFHandle, readOptions); iterator.seek(beginKey.slice()); return iterator; } } /** * RocksDB writes contain 3 stages: application memory buffer --> OS Page Cache --> Disk. * Given that we are having DBOptions::manual_wal_flush, we need to manually call DB::FlushWAL and DB::SyncWAL * Note: DB::FlushWAL(true) will internally call DB::SyncWAL. *

* See Flush And Sync WAL */ class FlushSyncService extends ServiceThread { private long lastSyncTime = 0; private static final long MAX_SYNC_INTERVAL_IN_MILLIS = 100; private final Stopwatch stopwatch = Stopwatch.createUnstarted(); private final FlushOptions flushOptions = new FlushOptions(); @Override public String getServiceName() { return "FlushSyncService"; } @Override public void run() { flushOptions.setAllowWriteStall(false); flushOptions.setWaitForFlush(true); log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { this.waitForRunning(10); this.flushAndSyncWAL(false); } catch (Exception e) { log.warn("{} service has exception. ", this.getServiceName(), e); } } try { flushAndSyncWAL(true); } catch (Exception e) { log.warn("{} raised an exception while performing flush-and-sync WAL on exit", this.getServiceName(), e); } flushOptions.close(); log.info("{} service end", this.getServiceName()); } private void flushAndSyncWAL(boolean onExit) throws RocksDBException { int writeOps = writeOpsCounter.get(); if (0 == writeOps) { // No write ops to flush return; } /* * Normally, when MemTables become full then immutable, RocksDB threads will automatically flush them to L0 * SST files. The use case here is different: the MemTable may never get full and immutable given that the * volume of data involved is relatively small. Further, we are constantly modifying the key-value pairs and * generating WAL entries. The WAL file size can grow up to dozens of gigabytes without manual triggering of * flush. */ if (ConfigStorage.this.estimateWalFileSize.get() >= messageStoreConfig.getRocksdbWalFileRollingThreshold()) { ConfigStorage.this.flush(flushOptions); estimateWalFileSize.set(0L); } // Flush and Sync WAL if we have committed enough writes if (writeOps >= messageStoreConfig.getRocksdbFlushWalFrequency() || onExit) { stopwatch.reset().start(); ConfigStorage.this.db.flushWal(true); long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS); writeOpsCounter.getAndAdd(-writeOps); lastSyncTime = System.currentTimeMillis(); LOGGER.debug("Flush and Sync WAL of RocksDB[{}] costs {}ms, write-ops={}", dbPath, elapsed, writeOps); return; } // Flush and Sync WAL if some writes are out there for a period of time long elapsedTime = System.currentTimeMillis() - lastSyncTime; if (elapsedTime > MAX_SYNC_INTERVAL_IN_MILLIS) { stopwatch.reset().start(); ConfigStorage.this.db.flushWal(true); long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS); LOGGER.debug("Flush and Sync WAL of RocksDB[{}] costs {}ms, write-ops={}", dbPath, elapsed, writeOps); writeOpsCounter.getAndAdd(-writeOps); lastSyncTime = System.currentTimeMillis(); } } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/ConsumerOffsetManagerV2.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import com.google.common.base.Strings; import io.netty.buffer.ByteBuf; import io.netty.util.internal.PlatformDependent; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.store.MessageStore; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; /** *

* Layout of consumer offset key: * [table-prefix, 1 byte][table-id, 2 bytes][record-prefix, 1 byte][group-len, 2 bytes][group bytes][CTRL_1, 1 byte] * [topic-len, 2 bytes][topic bytes][CTRL_1, 1 byte][queue-id, 4 bytes] *

* *

* Layout of consumer offset value: [offset, 8 bytes] *

*/ public class ConsumerOffsetManagerV2 extends ConsumerOffsetManager { private final ConfigStorage configStorage; public ConsumerOffsetManagerV2(BrokerController brokerController, ConfigStorage configStorage) { super(brokerController); this.configStorage = configStorage; } @Override public void removeConsumerOffset(String topicAtGroup) { if (!MixAll.isLmq(topicAtGroup)) { super.removeConsumerOffset(topicAtGroup); } String[] topicGroup = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (topicGroup.length != 2) { LOG.error("Invalid topic group: {}", topicAtGroup); return; } byte[] topicBytes = topicGroup[0].getBytes(StandardCharsets.UTF_8); byte[] groupBytes = topicGroup[1].getBytes(StandardCharsets.UTF_8); int keyLen = 1 /* table-prefix */ + Short.BYTES /* table-id */ + 1 /* record-prefix */ + Short.BYTES /* group-len */ + groupBytes.length + 1 /* CTRL_1 */ + Short.BYTES + topicBytes.length + 1; // [table-prefix, 1 byte][table-id, 2 bytes][record-prefix, 1 byte][group-len, 2 bytes][group-bytes][CTRL_1, 1 byte] // [topic-len, 2 bytes][topic-bytes][CTRL_1] ByteBuf beginKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); beginKey.writeByte(TablePrefix.TABLE.getValue()); beginKey.writeShort(TableId.CONSUMER_OFFSET.getValue()); beginKey.writeByte(RecordPrefix.DATA.getValue()); beginKey.writeShort(groupBytes.length); beginKey.writeBytes(groupBytes); beginKey.writeByte(AbstractRocksDBStorage.CTRL_1); beginKey.writeShort(topicBytes.length); beginKey.writeBytes(topicBytes); beginKey.writeByte(AbstractRocksDBStorage.CTRL_1); ByteBuf endKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); endKey.writeByte(TablePrefix.TABLE.getValue()); endKey.writeShort(TableId.CONSUMER_OFFSET.getValue()); endKey.writeByte(RecordPrefix.DATA.getValue()); endKey.writeShort(groupBytes.length); endKey.writeBytes(groupBytes); endKey.writeByte(AbstractRocksDBStorage.CTRL_1); endKey.writeShort(topicBytes.length); endKey.writeBytes(topicBytes); endKey.writeByte(AbstractRocksDBStorage.CTRL_2); try (WriteBatch writeBatch = new WriteBatch()) { // TODO: we have to make a copy here as WriteBatch lacks ByteBuffer API here writeBatch.deleteRange(ConfigHelper.readBytes(beginKey), ConfigHelper.readBytes(endKey)); long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.CONSUMER_OFFSET, dataVersion, stateMachineVersion); configStorage.write(writeBatch); } catch (RocksDBException e) { LOG.error("Failed to removeConsumerOffset, topicAtGroup={}", topicAtGroup, e); } finally { beginKey.release(); endKey.release(); } } @Override public void removeOffset(String group) { if (!MixAll.isLmq(group)) { super.removeOffset(group); } byte[] groupBytes = group.getBytes(StandardCharsets.UTF_8); int keyLen = 1 /* table-prefix */ + Short.BYTES /* table-id */ + 1 /* record-prefix */ + Short.BYTES /* group-len */ + groupBytes.length + 1 /* CTRL_1 */; // [table-prefix, 1 byte][table-id, 2 bytes][record-prefix, 1 byte][group-len, 2 bytes][group bytes][CTRL_1, 1 byte] ByteBuf consumerOffsetBeginKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); consumerOffsetBeginKey.writeByte(TablePrefix.TABLE.getValue()); consumerOffsetBeginKey.writeShort(TableId.CONSUMER_OFFSET.getValue()); consumerOffsetBeginKey.writeByte(RecordPrefix.DATA.getValue()); consumerOffsetBeginKey.writeShort(groupBytes.length); consumerOffsetBeginKey.writeBytes(groupBytes); consumerOffsetBeginKey.writeByte(AbstractRocksDBStorage.CTRL_1); ByteBuf consumerOffsetEndKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); consumerOffsetEndKey.writeByte(TablePrefix.TABLE.getValue()); consumerOffsetEndKey.writeShort(TableId.CONSUMER_OFFSET.getValue()); consumerOffsetEndKey.writeByte(RecordPrefix.DATA.getValue()); consumerOffsetEndKey.writeShort(groupBytes.length); consumerOffsetEndKey.writeBytes(groupBytes); consumerOffsetEndKey.writeByte(AbstractRocksDBStorage.CTRL_2); // [table-prefix, 1 byte][table-id, 2 bytes][record-prefix, 1 byte][group-len, 2 bytes][group bytes][CTRL_1, 1 byte] ByteBuf pullOffsetBeginKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); pullOffsetBeginKey.writeByte(TablePrefix.TABLE.getValue()); pullOffsetBeginKey.writeShort(TableId.PULL_OFFSET.getValue()); pullOffsetBeginKey.writeByte(RecordPrefix.DATA.getValue()); pullOffsetBeginKey.writeShort(groupBytes.length); pullOffsetBeginKey.writeBytes(groupBytes); pullOffsetBeginKey.writeByte(AbstractRocksDBStorage.CTRL_1); ByteBuf pullOffsetEndKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); pullOffsetEndKey.writeByte(TablePrefix.TABLE.getValue()); pullOffsetEndKey.writeShort(TableId.PULL_OFFSET.getValue()); pullOffsetEndKey.writeByte(RecordPrefix.DATA.getValue()); pullOffsetEndKey.writeShort(groupBytes.length); pullOffsetEndKey.writeBytes(groupBytes); pullOffsetEndKey.writeByte(AbstractRocksDBStorage.CTRL_2); try (WriteBatch writeBatch = new WriteBatch()) { // TODO: we have to make a copy here as WriteBatch lacks ByteBuffer API here writeBatch.deleteRange(ConfigHelper.readBytes(consumerOffsetBeginKey), ConfigHelper.readBytes(consumerOffsetEndKey)); writeBatch.deleteRange(ConfigHelper.readBytes(pullOffsetBeginKey), ConfigHelper.readBytes(pullOffsetEndKey)); MessageStore messageStore = brokerController.getMessageStore(); long stateMachineVersion = messageStore != null ? messageStore.getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.CONSUMER_OFFSET, dataVersion, stateMachineVersion); ConfigHelper.stampDataVersion(writeBatch, TableId.PULL_OFFSET, dataVersion, stateMachineVersion); configStorage.write(writeBatch); } catch (RocksDBException e) { LOG.error("Failed to consumer offsets by group={}", group, e); } finally { consumerOffsetBeginKey.release(); consumerOffsetEndKey.release(); pullOffsetBeginKey.release(); pullOffsetEndKey.release(); } } /** *

* Layout of consumer offset key: * [table-prefix, 1 byte][table-id, 2 bytes][record-prefix, 1 byte][group-len, 2 bytes][group bytes][CTRL_1, 1 byte] * [topic-len, 2 bytes][topic bytes][CTRL_1, 1 byte][queue-id, 4 bytes] *

* *

* Layout of consumer offset value: * [offset, 8 bytes] *

* * @param clientHost The client that submits consumer offsets * @param group Group name * @param topic Topic name * @param queueId Queue ID * @param offset Consumer offset of the specified queue */ @Override public void commitOffset(String clientHost, String group, String topic, int queueId, long offset) { String key = topic + TOPIC_GROUP_SEPARATOR + group; // We maintain a copy of classic consumer offset table in memory as they take very limited memory footprint. // For LMQ offsets, given the volume and number of these type of records, they are maintained in RocksDB // directly. Frequently used LMQ consumer offsets should reside either in block-cache or MemTable, so read/write // should be blazingly fast. if (!MixAll.isLmq(topic)) { if (offsetTable.containsKey(key)) { offsetTable.get(key).put(queueId, offset); } else { ConcurrentMap map = new ConcurrentHashMap<>(); ConcurrentMap prev = offsetTable.putIfAbsent(key, map); if (null != prev) { map = prev; } map.put(queueId, offset); } } ByteBuf keyBuf = keyOfConsumerOffset(group, topic, queueId); ByteBuf valueBuf = ConfigStorage.POOLED_ALLOCATOR.buffer(Long.BYTES); try (WriteBatch writeBatch = new WriteBatch()) { valueBuf.writeLong(offset); writeBatch.put(keyBuf.nioBuffer(), valueBuf.nioBuffer()); MessageStore messageStore = brokerController.getMessageStore(); long stateMachineVersion = messageStore != null ? messageStore.getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.CONSUMER_OFFSET, dataVersion, stateMachineVersion); configStorage.write(writeBatch); } catch (RocksDBException e) { LOG.error("Failed to commit consumer offset", e); } finally { keyBuf.release(); valueBuf.release(); } } private ByteBuf keyOfConsumerOffset(String group, String topic, int queueId) { byte[] groupBytes = group.getBytes(StandardCharsets.UTF_8); byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); int keyLen = 1 /*table prefix*/ + Short.BYTES /*table-id*/ + 1 /*record-prefix*/ + Short.BYTES /*group-len*/ + groupBytes.length + 1 /*CTRL_1*/ + 2 /*topic-len*/ + topicBytes.length + 1 /* CTRL_1*/ + Integer.BYTES /*queue-id*/; ByteBuf keyBuf = ConfigStorage.POOLED_ALLOCATOR.buffer(keyLen); keyBuf.writeByte(TablePrefix.TABLE.getValue()); keyBuf.writeShort(TableId.CONSUMER_OFFSET.getValue()); keyBuf.writeByte(RecordPrefix.DATA.getValue()); keyBuf.writeShort(groupBytes.length); keyBuf.writeBytes(groupBytes); keyBuf.writeByte(AbstractRocksDBStorage.CTRL_1); keyBuf.writeShort(topicBytes.length); keyBuf.writeBytes(topicBytes); keyBuf.writeByte(AbstractRocksDBStorage.CTRL_1); keyBuf.writeInt(queueId); return keyBuf; } private ByteBuf keyOfPullOffset(String group, String topic, int queueId) { byte[] groupBytes = group.getBytes(StandardCharsets.UTF_8); byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); int keyLen = 1 /*table prefix*/ + Short.BYTES /*table-id*/ + 1 /*record-prefix*/ + Short.BYTES /*group-len*/ + groupBytes.length + 1 /*CTRL_1*/ + 2 /*topic-len*/ + topicBytes.length + 1 /* CTRL_1*/ + Integer.BYTES /*queue-id*/; ByteBuf keyBuf = ConfigStorage.POOLED_ALLOCATOR.buffer(keyLen); keyBuf.writeByte(TablePrefix.TABLE.getValue()); keyBuf.writeShort(TableId.PULL_OFFSET.getValue()); keyBuf.writeByte(RecordPrefix.DATA.getValue()); keyBuf.writeShort(groupBytes.length); keyBuf.writeBytes(groupBytes); keyBuf.writeByte(AbstractRocksDBStorage.CTRL_1); keyBuf.writeShort(topicBytes.length); keyBuf.writeBytes(topicBytes); keyBuf.writeByte(AbstractRocksDBStorage.CTRL_1); keyBuf.writeInt(queueId); return keyBuf; } @Override public boolean load() { return loadDataVersion() && loadConsumerOffsets(); } @Override public synchronized void persist() { try { configStorage.flushWAL(); } catch (RocksDBException e) { LOG.error("Failed to flush RocksDB config instance WAL", e); } } /** *

* Layout of data version key: * [table-prefix, 1 byte][table-id, 2 byte][record-prefix, 1 byte][data-version-bytes] *

* *

* Layout of data version value: * [state-machine-version, 8 bytes][timestamp, 8 bytes][sequence counter, 8 bytes] *

*/ public boolean loadDataVersion() { try { ConfigHelper.loadDataVersion(configStorage, TableId.CONSUMER_OFFSET) .ifPresent(buf -> ConfigHelper.onDataVersionLoad(buf, dataVersion)); } catch (RocksDBException e) { LOG.error("Failed to load RocksDB config", e); return false; } return true; } private boolean loadConsumerOffsets() { // [table-prefix, 1 byte][table-id, 2 bytes][record-prefix, 1 byte] ByteBuf beginKeyBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(4); beginKeyBuf.writeByte(TablePrefix.TABLE.getValue()); beginKeyBuf.writeShort(TableId.CONSUMER_OFFSET.getValue()); beginKeyBuf.writeByte(RecordPrefix.DATA.getValue()); ByteBuf endKeyBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(4); endKeyBuf.writeByte(TablePrefix.TABLE.getValue()); endKeyBuf.writeShort(TableId.CONSUMER_OFFSET.getValue()); endKeyBuf.writeByte(RecordPrefix.DATA.getValue() + 1); try (RocksIterator iterator = configStorage.iterate(beginKeyBuf.nioBuffer(), endKeyBuf.nioBuffer())) { int keyCapacity = 256; // We may iterate millions of LMQ consumer offsets here, use direct byte buffers here to avoid memory // fragment ByteBuffer keyBuffer = ByteBuffer.allocateDirect(keyCapacity); ByteBuffer valueBuffer = ByteBuffer.allocateDirect(Long.BYTES); while (iterator.isValid()) { keyBuffer.clear(); valueBuffer.clear(); int len = iterator.key(keyBuffer); if (len > keyCapacity) { keyCapacity = len; PlatformDependent.freeDirectBuffer(keyBuffer); // Reserve more space for key keyBuffer = ByteBuffer.allocateDirect(keyCapacity); continue; } len = iterator.value(valueBuffer); assert len == Long.BYTES; // skip table-prefix, table-id, record-prefix keyBuffer.position(1 + 2 + 1); short groupLen = keyBuffer.getShort(); byte[] groupBytes = new byte[groupLen]; keyBuffer.get(groupBytes); byte ctrl = keyBuffer.get(); assert ctrl == AbstractRocksDBStorage.CTRL_1; short topicLen = keyBuffer.getShort(); byte[] topicBytes = new byte[topicLen]; keyBuffer.get(topicBytes); String topic = new String(topicBytes, StandardCharsets.UTF_8); ctrl = keyBuffer.get(); assert ctrl == AbstractRocksDBStorage.CTRL_1; int queueId = keyBuffer.getInt(); long offset = valueBuffer.getLong(); if (!MixAll.isLmq(topic)) { String group = new String(groupBytes, StandardCharsets.UTF_8); onConsumerOffsetRecordLoad(topic, group, queueId, offset); } iterator.next(); } PlatformDependent.freeDirectBuffer(keyBuffer); PlatformDependent.freeDirectBuffer(valueBuffer); } finally { beginKeyBuf.release(); endKeyBuf.release(); } return true; } private void onConsumerOffsetRecordLoad(String topic, String group, int queueId, long offset) { if (MixAll.isLmq(topic)) { return; } String key = topic + TOPIC_GROUP_SEPARATOR + group; if (!offsetTable.containsKey(key)) { ConcurrentMap map = new ConcurrentHashMap<>(); offsetTable.putIfAbsent(key, map); } offsetTable.get(key).put(queueId, offset); } @Override public long queryOffset(String group, String topic, int queueId) { if (!MixAll.isLmq(topic)) { return super.queryOffset(group, topic, queueId); } ByteBuf keyBuf = keyOfConsumerOffset(group, topic, queueId); try { byte[] slice = configStorage.get(keyBuf.nioBuffer()); if (null == slice) { return -1; } assert slice.length == Long.BYTES; return ByteBuffer.wrap(slice).getLong(); } catch (RocksDBException e) { throw new RuntimeException(e); } finally { keyBuf.release(); } } @Override public void commitPullOffset(String clientHost, String group, String topic, int queueId, long offset) { if (!MixAll.isLmq(topic)) { super.commitPullOffset(clientHost, group, topic, queueId, offset); } ByteBuf keyBuf = keyOfPullOffset(group, topic, queueId); ByteBuf valueBuf = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(8); valueBuf.writeLong(offset); try (WriteBatch writeBatch = new WriteBatch()) { writeBatch.put(keyBuf.nioBuffer(), valueBuf.nioBuffer()); long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.PULL_OFFSET, dataVersion, stateMachineVersion); configStorage.write(writeBatch); } catch (RocksDBException e) { LOG.error("Failed to commit pull offset. group={}, topic={}, queueId={}, offset={}", group, topic, queueId, offset); } finally { keyBuf.release(); valueBuf.release(); } } @Override public long queryPullOffset(String group, String topic, int queueId) { if (!MixAll.isLmq(topic)) { return super.queryPullOffset(group, topic, queueId); } ByteBuf keyBuf = keyOfPullOffset(group, topic, queueId); try { byte[] valueBytes = configStorage.get(keyBuf.nioBuffer()); if (null == valueBytes) { return -1; } return ByteBuffer.wrap(valueBytes).getLong(); } catch (RocksDBException e) { LOG.error("Failed to queryPullOffset. group={}, topic={}, queueId={}", group, topic, queueId); } finally { keyBuf.release(); } return -1; } @Override public void assignResetOffset(String topic, String group, int queueId, long offset) { if (Strings.isNullOrEmpty(topic) || Strings.isNullOrEmpty(group) || queueId < 0 || offset < 0) { LOG.warn("Illegal arguments when assigning reset offset. Topic={}, group={}, queueId={}, offset={}", topic, group, queueId, offset); return; } if (!MixAll.isLmq(topic) || !MixAll.isLmq(group)) { super.assignResetOffset(topic, group, queueId, offset); } else { String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap map = resetOffsetTable.get(key); if (null == map) { map = new ConcurrentHashMap<>(); ConcurrentMap previous = resetOffsetTable.putIfAbsent(key, map); if (null != previous) { map = previous; } } map.put(queueId, offset); } this.commitOffset(null, topic, group, queueId, offset); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/RecordPrefix.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; public enum RecordPrefix { UNSPECIFIED((byte)0), DATA_VERSION((byte)1), DATA((byte)2); private final byte value; RecordPrefix(byte value) { this.value = value; } public byte getValue() { return value; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/SerializationType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; public enum SerializationType { UNSPECIFIED((byte) 0), JSON((byte) 1), PROTOBUF((byte) 2), FLAT_BUFFERS((byte) 3); private final byte value; SerializationType(byte value) { this.value = value; } public byte getValue() { return value; } public static SerializationType valueOf(byte value) { for (SerializationType type : SerializationType.values()) { if (type.getValue() == value) { return type; } } return SerializationType.UNSPECIFIED; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/SubscriptionGroupManagerV2.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import com.alibaba.fastjson2.JSON; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; public class SubscriptionGroupManagerV2 extends SubscriptionGroupManager { private final ConfigStorage configStorage; public SubscriptionGroupManagerV2(BrokerController brokerController, ConfigStorage configStorage) { super(brokerController); this.configStorage = configStorage; } @Override public boolean load() { return loadDataVersion() && loadSubscriptions(); } public boolean loadDataVersion() { try { ConfigHelper.loadDataVersion(configStorage, TableId.SUBSCRIPTION_GROUP) .ifPresent(buf -> { ConfigHelper.onDataVersionLoad(buf, dataVersion); }); } catch (RocksDBException e) { log.error("loadDataVersion error", e); return false; } return true; } private boolean loadSubscriptions() { int keyLen = 1 /* table prefix */ + 2 /* table-id */ + 1 /* record-type-prefix */; ByteBuf beginKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); beginKey.writeByte(TablePrefix.TABLE.getValue()); beginKey.writeShort(TableId.SUBSCRIPTION_GROUP.getValue()); beginKey.writeByte(RecordPrefix.DATA.getValue()); ByteBuf endKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); endKey.writeByte(TablePrefix.TABLE.getValue()); endKey.writeShort(TableId.SUBSCRIPTION_GROUP.getValue()); endKey.writeByte(RecordPrefix.DATA.getValue() + 1); try (RocksIterator iterator = configStorage.iterate(beginKey.nioBuffer(), endKey.nioBuffer())) { while (iterator.isValid()) { SubscriptionGroupConfig subscriptionGroupConfig = parseSubscription(iterator.key(), iterator.value()); if (null != subscriptionGroupConfig) { super.putSubscriptionGroupConfig(subscriptionGroupConfig); } iterator.next(); } } finally { beginKey.release(); endKey.release(); } return true; } private SubscriptionGroupConfig parseSubscription(byte[] key, byte[] value) { ByteBuf keyBuf = Unpooled.wrappedBuffer(key); ByteBuf valueBuf = Unpooled.wrappedBuffer(value); try { // Skip table-prefix, table-id, record-type-prefix keyBuf.readerIndex(4); short groupNameLen = keyBuf.readShort(); assert groupNameLen == keyBuf.readableBytes(); CharSequence groupName = keyBuf.readCharSequence(groupNameLen, StandardCharsets.UTF_8); assert null != groupName; byte serializationType = valueBuf.readByte(); if (SerializationType.JSON == SerializationType.valueOf(serializationType)) { CharSequence json = valueBuf.readCharSequence(valueBuf.readableBytes(), StandardCharsets.UTF_8); SubscriptionGroupConfig subscriptionGroupConfig = JSON.parseObject(json.toString(), SubscriptionGroupConfig.class); assert subscriptionGroupConfig != null; assert groupName.equals(subscriptionGroupConfig.getGroupName()); return subscriptionGroupConfig; } } finally { keyBuf.release(); valueBuf.release(); } return null; } @Override public synchronized void persist() { try { configStorage.flushWAL(); } catch (RocksDBException e) { log.error("Failed to flush RocksDB WAL", e); } } @Override public SubscriptionGroupConfig findSubscriptionGroupConfig(final String group) { if (MixAll.isLmq(group)) { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); return subscriptionGroupConfig; } return super.findSubscriptionGroupConfig(group); } @Override public void updateSubscriptionGroupConfig(final SubscriptionGroupConfig config) { if (config == null || MixAll.isLmq(config.getGroupName())) { return; } super.updateSubscriptionGroupConfigWithoutPersist(config); ByteBuf keyBuf = ConfigHelper.keyBufOf(TableId.SUBSCRIPTION_GROUP, config.getGroupName()); ByteBuf valueBuf = ConfigHelper.valueBufOf(config, SerializationType.JSON); try (WriteBatch writeBatch = new WriteBatch()) { writeBatch.put(keyBuf.nioBuffer(), valueBuf.nioBuffer()); long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.SUBSCRIPTION_GROUP, dataVersion, stateMachineVersion); configStorage.write(writeBatch); // fdatasync on core metadata change persist(); } catch (RocksDBException e) { log.error("update subscription group config error", e); } finally { keyBuf.release(); valueBuf.release(); } } @Override public boolean containsSubscriptionGroup(String group) { if (MixAll.isLmq(group)) { return true; } else { return super.containsSubscriptionGroup(group); } } @Override protected SubscriptionGroupConfig removeSubscriptionGroupConfig(String groupName) { ByteBuf keyBuf = ConfigHelper.keyBufOf(TableId.SUBSCRIPTION_GROUP, groupName); try (WriteBatch writeBatch = new WriteBatch()) { writeBatch.delete(ConfigHelper.readBytes(keyBuf)); long stateMachineVersion = brokerController.getMessageStore().getStateMachineVersion(); ConfigHelper.stampDataVersion(writeBatch, TableId.SUBSCRIPTION_GROUP, dataVersion, stateMachineVersion); configStorage.write(writeBatch); } catch (RocksDBException e) { log.error("Failed to remove subscription group config by group-name={}", groupName, e); } return super.removeSubscriptionGroupConfig(groupName); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/TableId.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; /** * See Table, Key Value Mapping */ public enum TableId { UNSPECIFIED((short) 0), CONSUMER_OFFSET((short) 1), PULL_OFFSET((short) 2), TOPIC((short) 3), SUBSCRIPTION_GROUP((short) 4); private final short value; TableId(short value) { this.value = value; } public short getValue() { return value; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/TablePrefix.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; public enum TablePrefix { UNSPECIFIED((byte) 0), TABLE((byte) 1); private final byte value; TablePrefix(byte value) { this.value = value; } public byte getValue() { return value; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/TopicConfigManagerV2.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import com.alibaba.fastjson2.JSON; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.common.constant.PermName; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; /** * Key layout: [table-prefix, 1 byte][table-id, 2 bytes][record-type-prefix, 1 byte][topic-len, 2 bytes][topic-bytes] * Value layout: [serialization-type, 1 byte][topic-config-bytes] */ public class TopicConfigManagerV2 extends TopicConfigManager { private final ConfigStorage configStorage; public TopicConfigManagerV2(BrokerController brokerController, ConfigStorage configStorage) { super(brokerController); this.configStorage = configStorage; } @Override public boolean load() { return loadDataVersion() && loadTopicConfig(); } public boolean loadDataVersion() { try { ConfigHelper.loadDataVersion(configStorage, TableId.TOPIC) .ifPresent(buf -> ConfigHelper.onDataVersionLoad(buf, dataVersion)); } catch (RocksDBException e) { log.error("Failed to load data version of topic", e); return false; } return true; } private boolean loadTopicConfig() { int keyLen = 1 /* table-prefix */ + 2 /* table-id */ + 1 /* record-type-prefix */; ByteBuf beginKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); beginKey.writeByte(TablePrefix.TABLE.getValue()); beginKey.writeShort(TableId.TOPIC.getValue()); beginKey.writeByte(RecordPrefix.DATA.getValue()); ByteBuf endKey = AbstractRocksDBStorage.POOLED_ALLOCATOR.buffer(keyLen); endKey.writeByte(TablePrefix.TABLE.getValue()); endKey.writeShort(TableId.TOPIC.getValue()); endKey.writeByte(RecordPrefix.DATA.getValue() + 1); try (RocksIterator iterator = configStorage.iterate(beginKey.nioBuffer(), endKey.nioBuffer())) { while (iterator.isValid()) { byte[] key = iterator.key(); byte[] value = iterator.value(); TopicConfig topicConfig = parseTopicConfig(key, value); if (null != topicConfig) { super.putTopicConfig(topicConfig); } iterator.next(); } } finally { beginKey.release(); endKey.release(); } return true; } /** * Key layout: [table-prefix, 1 byte][table-id, 2 bytes][record-type-prefix, 1 byte][topic-len, 2 bytes][topic-bytes] * Value layout: [serialization-type, 1 byte][topic-config-bytes] * * @param key Topic config key representation in RocksDB * @param value Topic config value representation in RocksDB * @return decoded topic config */ private TopicConfig parseTopicConfig(byte[] key, byte[] value) { ByteBuf keyBuf = Unpooled.wrappedBuffer(key); ByteBuf valueBuf = Unpooled.wrappedBuffer(value); try { // Skip table-prefix, table-id, record-type-prefix keyBuf.readerIndex(4); short topicLen = keyBuf.readShort(); assert topicLen == keyBuf.readableBytes(); CharSequence topic = keyBuf.readCharSequence(topicLen, StandardCharsets.UTF_8); assert null != topic; byte serializationType = valueBuf.readByte(); if (SerializationType.JSON == SerializationType.valueOf(serializationType)) { CharSequence json = valueBuf.readCharSequence(valueBuf.readableBytes(), StandardCharsets.UTF_8); TopicConfig topicConfig = JSON.parseObject(json.toString(), TopicConfig.class); assert topicConfig != null; assert topic.equals(topicConfig.getTopicName()); return topicConfig; } } finally { keyBuf.release(); valueBuf.release(); } return null; } @Override public synchronized void persist() { try { configStorage.flushWAL(); } catch (RocksDBException e) { log.error("Failed to flush WAL", e); } } @Override public TopicConfig selectTopicConfig(final String topic) { if (MixAll.isLmq(topic)) { return simpleLmqTopicConfig(topic); } return super.selectTopicConfig(topic); } @Override public void updateTopicConfig(final TopicConfig topicConfig) { if (topicConfig == null || MixAll.isLmq(topicConfig.getTopicName())) { return; } super.updateSingleTopicConfigWithoutPersist(topicConfig); ByteBuf keyBuf = ConfigHelper.keyBufOf(TableId.TOPIC, topicConfig.getTopicName()); ByteBuf valueBuf = ConfigHelper.valueBufOf(topicConfig, SerializationType.JSON); try (WriteBatch writeBatch = new WriteBatch()) { writeBatch.put(keyBuf.nioBuffer(), valueBuf.nioBuffer()); long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.TOPIC, dataVersion, stateMachineVersion); configStorage.write(writeBatch); // fdatasync on core metadata change this.persist(); } catch (RocksDBException e) { log.error("Failed to update topic config", e); } finally { keyBuf.release(); valueBuf.release(); } } @Override protected TopicConfig removeTopicConfig(String topicName) { ByteBuf keyBuf = ConfigHelper.keyBufOf(TableId.TOPIC, topicName); try (WriteBatch writeBatch = new WriteBatch()) { writeBatch.delete(keyBuf.nioBuffer()); long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; ConfigHelper.stampDataVersion(writeBatch, TableId.TOPIC, dataVersion, stateMachineVersion); configStorage.write(writeBatch); } catch (RocksDBException e) { log.error("Failed to delete topic config by topicName={}", topicName, e); } finally { keyBuf.release(); } return super.removeTopicConfig(topicName); } @Override public boolean containsTopic(String topic) { if (MixAll.isLmq(topic)) { return true; } return super.containsTopic(topic); } private TopicConfig simpleLmqTopicConfig(String topic) { return new TopicConfig(topic, 1, 1, PermName.PERM_READ | PermName.PERM_WRITE); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/config/v2/package-info.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; /* * Endian: we use network byte order for all integrals, aka, always big endian. * * Unlike v1 config managers, implementations in this package prioritize data integrity and reliability. * As a result,RocksDB write-ahead-log is always on and changes are immediately flushed. Another significant * difference is that heap-based cache is removed because it is not necessary and duplicated to RocksDB * MemTable/BlockCache. */ ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.EpochEntry; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; import org.apache.rocketmq.store.ha.autoswitch.BrokerMetadata; import org.apache.rocketmq.store.ha.autoswitch.TempBrokerMetadata; import static org.apache.rocketmq.remoting.protocol.ResponseCode.CONTROLLER_BROKER_METADATA_NOT_EXIST; /** * The manager of broker replicas, including: 0.regularly syncing controller metadata, change controller leader address, * both master and slave will start this timed task. 1.regularly syncing metadata from controllers, and changing broker * roles and master if needed, both master and slave will start this timed task. 2.regularly expanding and Shrinking * syncStateSet, only master will start this timed task. */ public class ReplicasManager { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final int RETRY_INTERVAL_SECOND = 5; private final ScheduledExecutorService scheduledService; private final ExecutorService executorService; private final ExecutorService scanExecutor; private final BrokerController brokerController; private final AutoSwitchHAService haService; private final BrokerConfig brokerConfig; private final String brokerAddress; private final BrokerOuterAPI brokerOuterAPI; private List controllerAddresses; private final ConcurrentMap availableControllerAddresses; private volatile String controllerLeaderAddress = ""; private volatile State state = State.INITIAL; private volatile RegisterState registerState = RegisterState.INITIAL; private ScheduledFuture checkSyncStateSetTaskFuture; private ScheduledFuture slaveSyncFuture; private Long brokerControllerId; private Long masterBrokerId; private BrokerMetadata brokerMetadata; private TempBrokerMetadata tempBrokerMetadata; private Set syncStateSet; private int syncStateSetEpoch = 0; private String masterAddress = ""; private int masterEpoch = 0; private long lastSyncTimeMs = System.currentTimeMillis(); private Random random = new Random(); public ReplicasManager(final BrokerController brokerController) { this.brokerController = brokerController; this.brokerOuterAPI = brokerController.getBrokerOuterAPI(); this.scheduledService = ThreadUtils.newScheduledThreadPool(3, new ThreadFactoryImpl("ReplicasManager_ScheduledService_", brokerController.getBrokerIdentity())); this.executorService = ThreadUtils.newThreadPoolExecutor(3, new ThreadFactoryImpl("ReplicasManager_ExecutorService_", brokerController.getBrokerIdentity())); this.scanExecutor = ThreadUtils.newThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("ReplicasManager_scan_thread_", brokerController.getBrokerIdentity())); this.haService = (AutoSwitchHAService) brokerController.getMessageStore().getHaService(); this.brokerConfig = brokerController.getBrokerConfig(); this.availableControllerAddresses = new ConcurrentHashMap<>(); this.syncStateSet = new HashSet<>(); this.brokerAddress = brokerController.getBrokerAddr(); this.brokerMetadata = new BrokerMetadata(this.brokerController.getMessageStoreConfig().getStorePathBrokerIdentity()); this.tempBrokerMetadata = new TempBrokerMetadata(this.brokerController.getMessageStoreConfig().getStorePathBrokerIdentity() + "-temp"); } enum State { INITIAL, FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, REGISTER_TO_CONTROLLER_DONE, RUNNING, SHUTDOWN, } enum RegisterState { INITIAL, CREATE_TEMP_METADATA_FILE_DONE, CREATE_METADATA_FILE_DONE, REGISTERED } public void start() { this.state = State.INITIAL; updateControllerAddr(); scanAvailableControllerAddresses(); this.scheduledService.scheduleAtFixedRate(this::updateControllerAddr, 2 * 60 * 1000, 2 * 60 * 1000, TimeUnit.MILLISECONDS); this.scheduledService.scheduleAtFixedRate(this::scanAvailableControllerAddresses, 3 * 1000, 3 * 1000, TimeUnit.MILLISECONDS); if (!startBasicService()) { LOGGER.error("Failed to start replicasManager"); this.executorService.submit(() -> { int retryTimes = 0; do { try { TimeUnit.SECONDS.sleep(RETRY_INTERVAL_SECOND); } catch (InterruptedException ignored) { } retryTimes++; LOGGER.warn("Failed to start replicasManager, retry times:{}, current state:{}, try it again", retryTimes, this.state); } while (!startBasicService()); LOGGER.info("Start replicasManager success, retry times:{}", retryTimes); }); } } private boolean startBasicService() { if (this.state == State.SHUTDOWN) return false; if (this.state == State.INITIAL) { if (schedulingSyncControllerMetadata()) { this.state = State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE; LOGGER.info("First time sync controller metadata success, change state to: {}", this.state); } else { return false; } } if (this.state == State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE) { for (int retryTimes = 0; retryTimes < 5; retryTimes++) { if (register()) { this.state = State.REGISTER_TO_CONTROLLER_DONE; LOGGER.info("First time register broker success, change state to: {}", this.state); break; } // Try to avoid registration concurrency conflicts in random sleep try { Thread.sleep(random.nextInt(1000)); } catch (Exception ignore) { } } // register 5 times but still unsuccessful if (this.state != State.REGISTER_TO_CONTROLLER_DONE) { LOGGER.error("Register to broker failed 5 times"); return false; } } if (this.state == State.REGISTER_TO_CONTROLLER_DONE) { // The scheduled task for heartbeat sending is not starting now, so we should manually send heartbeat request this.sendHeartbeatToController(); if (this.masterBrokerId != null || brokerElect()) { LOGGER.info("Master in this broker set is elected, masterBrokerId: {}, masterBrokerAddr: {}", this.masterBrokerId, this.masterAddress); this.state = State.RUNNING; setFenced(false); LOGGER.info("All register process has been done, change state to: {}", this.state); } else { return false; } } schedulingSyncBrokerMetadata(); // Register syncStateSet changed listener. this.haService.registerSyncStateSetChangedListener(this::doReportSyncStateSetChanged); return true; } public void shutdown() { this.state = State.SHUTDOWN; this.registerState = RegisterState.INITIAL; this.executorService.shutdownNow(); this.scheduledService.shutdownNow(); this.scanExecutor.shutdownNow(); } public synchronized void changeBrokerRole(final Long newMasterBrokerId, final String newMasterAddress, final Integer newMasterEpoch, final Integer syncStateSetEpoch, final Set syncStateSet) throws Exception { if (newMasterBrokerId != null && newMasterEpoch > this.masterEpoch) { if (newMasterBrokerId.equals(this.brokerControllerId)) { changeToMaster(newMasterEpoch, syncStateSetEpoch, syncStateSet); } else { changeToSlave(newMasterAddress, newMasterEpoch, newMasterBrokerId); } } } public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set syncStateSet) throws Exception { synchronized (this) { if (newMasterEpoch > this.masterEpoch) { LOGGER.info("Begin to change to master, brokerName:{}, replicas:{}, new Epoch:{}", this.brokerConfig.getBrokerName(), this.brokerAddress, newMasterEpoch); this.masterEpoch = newMasterEpoch; if (this.masterBrokerId != null && this.masterBrokerId.equals(this.brokerControllerId) && this.brokerController.getBrokerConfig().getBrokerId() == MixAll.MASTER_ID) { // Change SyncStateSet final HashSet newSyncStateSet = new HashSet<>(syncStateSet); changeSyncStateSet(newSyncStateSet, syncStateSetEpoch); // if master doesn't change this.haService.changeToMasterWhenLastRoleIsMaster(newMasterEpoch); this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(newMasterEpoch); this.executorService.submit(this::checkSyncStateSetAndDoReport); registerBrokerWhenRoleChange(); return; } // Change SyncStateSet final HashSet newSyncStateSet = new HashSet<>(syncStateSet); changeSyncStateSet(newSyncStateSet, syncStateSetEpoch); // Handle the slave synchronise handleSlaveSynchronize(BrokerRole.SYNC_MASTER); // Notify ha service, change to master this.haService.changeToMaster(newMasterEpoch); this.brokerController.getBrokerConfig().setBrokerId(MixAll.MASTER_ID); this.brokerController.getMessageStoreConfig().setBrokerRole(BrokerRole.SYNC_MASTER); this.brokerController.changeSpecialServiceStatus(true); // Change record this.masterAddress = this.brokerAddress; this.masterBrokerId = this.brokerControllerId; schedulingCheckSyncStateSet(); this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(newMasterEpoch); this.executorService.submit(this::checkSyncStateSetAndDoReport); registerBrokerWhenRoleChange(); } } } public void changeToSlave(final String newMasterAddress, final int newMasterEpoch, Long newMasterBrokerId) { synchronized (this) { if (newMasterEpoch > this.masterEpoch) { LOGGER.info("Begin to change to slave, brokerName={}, brokerId={}, newMasterBrokerId={}, newMasterAddress={}, newMasterEpoch={}", this.brokerConfig.getBrokerName(), this.brokerControllerId, newMasterBrokerId, newMasterAddress, newMasterEpoch); this.masterEpoch = newMasterEpoch; if (newMasterBrokerId.equals(this.masterBrokerId)) { // if master doesn't change this.haService.changeToSlaveWhenMasterNotChange(newMasterAddress, newMasterEpoch); this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(newMasterEpoch); registerBrokerWhenRoleChange(); return; } // Stop checking syncStateSet because only master is able to check stopCheckSyncStateSet(); // Change config(compatibility problem) this.brokerController.getMessageStoreConfig().setBrokerRole(BrokerRole.SLAVE); this.brokerController.changeSpecialServiceStatus(false); // The brokerId in brokerConfig just means its role(master[0] or slave[>=1]) this.brokerConfig.setBrokerId(brokerControllerId); // Change record this.masterAddress = newMasterAddress; this.masterBrokerId = newMasterBrokerId; // Handle the slave synchronise handleSlaveSynchronize(BrokerRole.SLAVE); // Notify ha service, change to slave this.haService.changeToSlave(newMasterAddress, newMasterEpoch, brokerControllerId); this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(newMasterEpoch); registerBrokerWhenRoleChange(); } } } public void registerBrokerWhenRoleChange() { this.executorService.submit(() -> { // Register broker to name-srv try { this.brokerController.registerBrokerAll(true, false, this.brokerController.getBrokerConfig().isForceRegister()); } catch (final Throwable e) { LOGGER.error("Error happen when register broker to name-srv, Failed to change broker to {}", this.brokerController.getMessageStoreConfig().getBrokerRole(), e); return; } LOGGER.info("Change broker [id:{}][address:{}] to {}, newMasterBrokerId:{}, newMasterAddress:{}, newMasterEpoch:{}, syncStateSetEpoch:{}", this.brokerControllerId, this.brokerAddress, this.brokerController.getMessageStoreConfig().getBrokerRole(), this.masterBrokerId, this.masterAddress, this.masterEpoch, this.syncStateSetEpoch); }); } private void changeSyncStateSet(final Set newSyncStateSet, final int newSyncStateSetEpoch) { synchronized (this) { if (newSyncStateSetEpoch > this.syncStateSetEpoch) { LOGGER.info("SyncStateSet changed from {} to {}", this.syncStateSet, newSyncStateSet); this.syncStateSetEpoch = newSyncStateSetEpoch; this.syncStateSet = new HashSet<>(newSyncStateSet); this.haService.setSyncStateSet(newSyncStateSet); } } } private void handleSlaveSynchronize(final BrokerRole role) { if (role == BrokerRole.SLAVE) { if (this.slaveSyncFuture != null) { this.slaveSyncFuture.cancel(false); } this.brokerController.getSlaveSynchronize().setMasterAddr(this.masterAddress); slaveSyncFuture = this.brokerController.getScheduledExecutorService().scheduleAtFixedRate(() -> { try { if (System.currentTimeMillis() - lastSyncTimeMs > 10 * 1000) { brokerController.getSlaveSynchronize().syncAll(); lastSyncTimeMs = System.currentTimeMillis(); } //timer checkpoint, latency-sensitive, so sync it more frequently brokerController.getSlaveSynchronize().syncTimerCheckPoint(); } catch (final Throwable e) { LOGGER.error("ScheduledTask SlaveSynchronize syncAll error.", e); } }, 1000 * 3, 1000 * 3, TimeUnit.MILLISECONDS); } else { if (this.slaveSyncFuture != null) { this.slaveSyncFuture.cancel(false); } this.brokerController.getSlaveSynchronize().setMasterAddr(null); } } private boolean brokerElect() { // Broker try to elect itself as a master in broker set. try { Pair> tryElectResponsePair = this.brokerOuterAPI.brokerElect(this.controllerLeaderAddress, this.brokerConfig.getBrokerClusterName(), this.brokerConfig.getBrokerName(), this.brokerControllerId); ElectMasterResponseHeader tryElectResponse = tryElectResponsePair.getObject1(); Set syncStateSet = tryElectResponsePair.getObject2(); final String masterAddress = tryElectResponse.getMasterAddress(); final Long masterBrokerId = tryElectResponse.getMasterBrokerId(); if (StringUtils.isEmpty(masterAddress) || masterBrokerId == null) { LOGGER.warn("Now no master in broker set"); return false; } if (masterBrokerId.equals(this.brokerControllerId)) { changeToMaster(tryElectResponse.getMasterEpoch(), tryElectResponse.getSyncStateSetEpoch(), syncStateSet); } else { changeToSlave(masterAddress, tryElectResponse.getMasterEpoch(), tryElectResponse.getMasterBrokerId()); } return true; } catch (Exception e) { LOGGER.error("Failed to try elect", e); return false; } } public void sendHeartbeatToController() { final List controllerAddresses = this.getAvailableControllerAddresses(); for (String controllerAddress : controllerAddresses) { if (StringUtils.isNotEmpty(controllerAddress)) { this.brokerOuterAPI.sendHeartbeatToController( controllerAddress, this.brokerConfig.getBrokerClusterName(), this.brokerAddress, this.brokerConfig.getBrokerName(), this.brokerControllerId, this.brokerConfig.getSendHeartbeatTimeoutMillis(), this.brokerConfig.isInBrokerContainer(), this.getLastEpoch(), this.brokerController.getMessageStore().getMaxPhyOffset(), this.brokerController.getMessageStore().getConfirmOffset(), this.brokerConfig.getControllerHeartBeatTimeoutMills(), this.brokerConfig.getBrokerElectionPriority() ); } } } /** * Register broker to controller, and persist the metadata to file * * @return whether registering process succeeded */ private boolean register() { try { // 1. confirm now registering state confirmNowRegisteringState(); LOGGER.info("Confirm now register state: {}", this.registerState); // 2. check metadata/tempMetadata if valid if (!checkMetadataValid()) { LOGGER.error("Check and find that metadata/tempMetadata invalid, you can modify the broker config to make them valid"); return false; } // 2. get next assigning brokerId, and create temp metadata file if (this.registerState == RegisterState.INITIAL) { Long nextBrokerId = getNextBrokerId(); if (nextBrokerId == null || !createTempMetadataFile(nextBrokerId)) { LOGGER.error("Failed to create temp metadata file, nextBrokerId: {}", nextBrokerId); return false; } this.registerState = RegisterState.CREATE_TEMP_METADATA_FILE_DONE; LOGGER.info("Register state change to {}, temp metadata: {}", this.registerState, this.tempBrokerMetadata); } // 3. apply brokerId to controller, and create metadata file if (this.registerState == RegisterState.CREATE_TEMP_METADATA_FILE_DONE) { if (!applyBrokerId()) { // apply broker id failed, means that this brokerId has been used // delete temp metadata file this.tempBrokerMetadata.clear(); // back to the first step this.registerState = RegisterState.INITIAL; LOGGER.info("Register state change to: {}", this.registerState); return false; } if (!createMetadataFileAndDeleteTemp()) { LOGGER.error("Failed to create metadata file and delete temp metadata file, temp metadata: {}", this.tempBrokerMetadata); return false; } this.registerState = RegisterState.CREATE_METADATA_FILE_DONE; LOGGER.info("Register state change to: {}, metadata: {}", this.registerState, this.brokerMetadata); } // 4. register if (this.registerState == RegisterState.CREATE_METADATA_FILE_DONE) { if (!registerBrokerToController()) { LOGGER.error("Failed to register broker to controller"); return false; } this.registerState = RegisterState.REGISTERED; LOGGER.info("Register state change to: {}, masterBrokerId: {}, masterBrokerAddr: {}", this.registerState, this.masterBrokerId, this.masterAddress); } return true; } catch (final Exception e) { LOGGER.error("Failed to register broker to controller", e); return false; } } /** * Send GetNextBrokerRequest to controller for getting next assigning brokerId in this broker-set * * @return next brokerId in this broker-set */ private Long getNextBrokerId() { try { GetNextBrokerIdResponseHeader nextBrokerIdResp = this.brokerOuterAPI.getNextBrokerId(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.getBrokerName(), this.controllerLeaderAddress); return nextBrokerIdResp.getNextBrokerId(); } catch (Exception e) { LOGGER.error("fail to get next broker id from controller", e); return null; } } /** * Create temp metadata file in local file system, records the brokerId and registerCheckCode * * @param brokerId the brokerId that is expected to be assigned * @return whether the temp meta file is created successfully */ private boolean createTempMetadataFile(Long brokerId) { // generate register check code, format like that: $ipAddress;$timestamp String registerCheckCode = this.brokerAddress + ";" + System.currentTimeMillis(); try { this.tempBrokerMetadata.updateAndPersist(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerId, registerCheckCode); return true; } catch (Exception e) { LOGGER.error("update and persist temp broker metadata file failed", e); this.tempBrokerMetadata.clear(); return false; } } /** * Send applyBrokerId request to controller * * @return whether controller has assigned this brokerId for this broker */ private boolean applyBrokerId() { try { ApplyBrokerIdResponseHeader response = this.brokerOuterAPI.applyBrokerId(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), tempBrokerMetadata.getBrokerId(), tempBrokerMetadata.getRegisterCheckCode(), this.controllerLeaderAddress); return true; } catch (Exception e) { LOGGER.error("fail to apply broker id: {}", tempBrokerMetadata.getBrokerId(), e); return false; } } /** * Create metadata file and delete temp metadata file * * @return whether process success */ private boolean createMetadataFileAndDeleteTemp() { // create metadata file and delete temp metadata file try { this.brokerMetadata.updateAndPersist(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), tempBrokerMetadata.getBrokerId()); this.tempBrokerMetadata.clear(); this.brokerControllerId = this.brokerMetadata.getBrokerId(); this.haService.setLocalBrokerId(this.brokerControllerId); return true; } catch (Exception e) { LOGGER.error("fail to create metadata file", e); this.brokerMetadata.clear(); return false; } } /** * Send registerBrokerToController request to inform controller that now broker has been registered successfully and * controller should update broker ipAddress if changed * * @return whether request success */ private boolean registerBrokerToController() { try { Pair> responsePair = this.brokerOuterAPI.registerBrokerToController(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerControllerId, brokerAddress, controllerLeaderAddress); if (responsePair == null) return false; RegisterBrokerToControllerResponseHeader response = responsePair.getObject1(); Set syncStateSet = responsePair.getObject2(); final Long masterBrokerId = response.getMasterBrokerId(); final String masterAddress = response.getMasterAddress(); if (masterBrokerId == null) { return true; } if (this.brokerControllerId.equals(masterBrokerId)) { changeToMaster(response.getMasterEpoch(), response.getSyncStateSetEpoch(), syncStateSet); } else { changeToSlave(masterAddress, response.getMasterEpoch(), masterBrokerId); } return true; } catch (Exception e) { LOGGER.error("fail to send registerBrokerToController request to controller", e); return false; } } /** * Confirm the registering state now */ private void confirmNowRegisteringState() { // 1. check if metadata exist try { this.brokerMetadata.readFromFile(); } catch (Exception e) { LOGGER.error("Read metadata file failed", e); } if (this.brokerMetadata.isLoaded()) { this.registerState = RegisterState.CREATE_METADATA_FILE_DONE; this.brokerControllerId = brokerMetadata.getBrokerId(); this.haService.setLocalBrokerId(this.brokerControllerId); return; } // 2. check if temp metadata exist try { this.tempBrokerMetadata.readFromFile(); } catch (Exception e) { LOGGER.error("Read temp metadata file failed", e); } if (this.tempBrokerMetadata.isLoaded()) { this.registerState = RegisterState.CREATE_TEMP_METADATA_FILE_DONE; } } private boolean checkMetadataValid() { if (this.registerState == RegisterState.CREATE_TEMP_METADATA_FILE_DONE) { if (this.tempBrokerMetadata.getClusterName() == null || !this.tempBrokerMetadata.getClusterName().equals(this.brokerConfig.getBrokerClusterName())) { LOGGER.error("The clusterName: {} in broker temp metadata is different from the clusterName: {} in broker config", this.tempBrokerMetadata.getClusterName(), this.brokerConfig.getBrokerClusterName()); return false; } if (this.tempBrokerMetadata.getBrokerName() == null || !this.tempBrokerMetadata.getBrokerName().equals(this.brokerConfig.getBrokerName())) { LOGGER.error("The brokerName: {} in broker temp metadata is different from the brokerName: {} in broker config", this.tempBrokerMetadata.getBrokerName(), this.brokerConfig.getBrokerName()); return false; } } if (this.registerState == RegisterState.CREATE_METADATA_FILE_DONE) { if (this.brokerMetadata.getClusterName() == null || !this.brokerMetadata.getClusterName().equals(this.brokerConfig.getBrokerClusterName())) { LOGGER.error("The clusterName: {} in broker metadata is different from the clusterName: {} in broker config", this.brokerMetadata.getClusterName(), this.brokerConfig.getBrokerClusterName()); return false; } if (this.brokerMetadata.getBrokerName() == null || !this.brokerMetadata.getBrokerName().equals(this.brokerConfig.getBrokerName())) { LOGGER.error("The brokerName: {} in broker metadata is different from the brokerName: {} in broker config", this.brokerMetadata.getBrokerName(), this.brokerConfig.getBrokerName()); return false; } } return true; } /** * Scheduling sync broker metadata form controller. */ private void schedulingSyncBrokerMetadata() { this.scheduledService.scheduleAtFixedRate(() -> { try { final Pair result = this.brokerOuterAPI.getReplicaInfo(this.controllerLeaderAddress, this.brokerConfig.getBrokerName()); final GetReplicaInfoResponseHeader info = result.getObject1(); final SyncStateSet syncStateSet = result.getObject2(); final String newMasterAddress = info.getMasterAddress(); final int newMasterEpoch = info.getMasterEpoch(); final Long masterBrokerId = info.getMasterBrokerId(); synchronized (this) { // Check if master changed if (newMasterEpoch > this.masterEpoch) { if (StringUtils.isNoneEmpty(newMasterAddress) && masterBrokerId != null) { if (masterBrokerId.equals(this.brokerControllerId)) { // If this broker is now the master changeToMaster(newMasterEpoch, syncStateSet.getSyncStateSetEpoch(), syncStateSet.getSyncStateSet()); } else { // If this broker is now the slave, and master has been changed changeToSlave(newMasterAddress, newMasterEpoch, masterBrokerId); } } else { // In this case, the master in controller is null, try elect in controller, this will trigger the electMasterEvent in controller. brokerElect(); } } else if (newMasterEpoch == this.masterEpoch) { // Check if SyncStateSet changed if (isMasterState()) { changeSyncStateSet(syncStateSet.getSyncStateSet(), syncStateSet.getSyncStateSetEpoch()); } } } } catch (final MQBrokerException exception) { LOGGER.warn("Error happen when get broker {}'s metadata", this.brokerConfig.getBrokerName(), exception); if (exception.getResponseCode() == CONTROLLER_BROKER_METADATA_NOT_EXIST) { try { registerBrokerToController(); TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ignore) { } } } catch (final Exception e) { LOGGER.warn("Error happen when get broker {}'s metadata", this.brokerConfig.getBrokerName(), e); } }, 3 * 1000, this.brokerConfig.getSyncBrokerMetadataPeriod(), TimeUnit.MILLISECONDS); } /** * Scheduling sync controller metadata. */ private boolean schedulingSyncControllerMetadata() { // Get controller metadata first. int tryTimes = 0; while (tryTimes < 3) { boolean flag = updateControllerMetadata(); if (flag) { this.scheduledService.scheduleAtFixedRate(this::updateControllerMetadata, 1000 * 3, this.brokerConfig.getSyncControllerMetadataPeriod(), TimeUnit.MILLISECONDS); return true; } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ignore) { } tryTimes++; } LOGGER.error("Failed to init controller metadata, maybe the controllers in {} is not available", this.controllerAddresses); return false; } /** * Update controller leader address by rpc. */ private boolean updateControllerMetadata() { for (String address : this.availableControllerAddresses.keySet()) { try { final GetMetaDataResponseHeader responseHeader = this.brokerOuterAPI.getControllerMetaData(address); if (responseHeader != null && StringUtils.isNoneEmpty(responseHeader.getControllerLeaderAddress())) { this.controllerLeaderAddress = responseHeader.getControllerLeaderAddress(); LOGGER.info("Update controller leader address to {}", this.controllerLeaderAddress); return true; } } catch (final Exception e) { LOGGER.error("Failed to update controller metadata", e); } } return false; } /** * Scheduling check syncStateSet. */ private void schedulingCheckSyncStateSet() { if (this.checkSyncStateSetTaskFuture != null) { this.checkSyncStateSetTaskFuture.cancel(false); } this.checkSyncStateSetTaskFuture = this.scheduledService.scheduleAtFixedRate(this::checkSyncStateSetAndDoReport, 3 * 1000, this.brokerConfig.getCheckSyncStateSetPeriod(), TimeUnit.MILLISECONDS); } private void checkSyncStateSetAndDoReport() { try { final Set newSyncStateSet = this.haService.maybeShrinkSyncStateSet(); newSyncStateSet.add(this.brokerControllerId); synchronized (this) { if (this.syncStateSet != null) { // Check if syncStateSet changed if (this.syncStateSet.size() == newSyncStateSet.size() && this.syncStateSet.containsAll(newSyncStateSet)) { return; } } } doReportSyncStateSetChanged(newSyncStateSet); } catch (Exception e) { LOGGER.error("Check syncStateSet error", e); } } private void doReportSyncStateSetChanged(Set newSyncStateSet) { try { final SyncStateSet result = this.brokerOuterAPI.alterSyncStateSet(this.controllerLeaderAddress, this.brokerConfig.getBrokerName(), this.brokerControllerId, this.masterEpoch, newSyncStateSet, this.syncStateSetEpoch); if (result != null) { changeSyncStateSet(result.getSyncStateSet(), result.getSyncStateSetEpoch()); } } catch (final Exception e) { LOGGER.error("Error happen when change SyncStateSet, broker:{}, masterAddress:{}, masterEpoch:{}, oldSyncStateSet:{}, newSyncStateSet:{}, syncStateSetEpoch:{}", this.brokerConfig.getBrokerName(), this.masterAddress, this.masterEpoch, this.syncStateSet, newSyncStateSet, this.syncStateSetEpoch, e); } } private void stopCheckSyncStateSet() { if (this.checkSyncStateSetTaskFuture != null) { this.checkSyncStateSetTaskFuture.cancel(false); } } private void scanAvailableControllerAddresses() { try { if (controllerAddresses == null) { LOGGER.warn("scanAvailableControllerAddresses addresses of controller is null!"); return; } for (String address : availableControllerAddresses.keySet()) { if (!controllerAddresses.contains(address)) { LOGGER.warn("scanAvailableControllerAddresses remove invalid address {}", address); availableControllerAddresses.remove(address); } } for (String address : controllerAddresses) { scanExecutor.submit(() -> { if (brokerOuterAPI.checkAddressReachable(address)) { availableControllerAddresses.putIfAbsent(address, true); } else { Boolean value = availableControllerAddresses.remove(address); if (value != null) { LOGGER.warn("scanAvailableControllerAddresses remove unconnected address {}", address); } } }); } } catch (final Throwable t) { LOGGER.error("scanAvailableControllerAddresses unexpected exception", t); } } private void updateControllerAddr() { if (brokerConfig.isFetchControllerAddrByDnsLookup()) { List adders = brokerOuterAPI.dnsLookupAddressByDomain(this.brokerConfig.getControllerAddr()); if (CollectionUtils.isNotEmpty(adders)) { this.controllerAddresses = adders; } } else { final String controllerPaths = this.brokerConfig.getControllerAddr(); final String[] controllers = controllerPaths.split(";"); assert controllers.length > 0; this.controllerAddresses = Arrays.asList(controllers); } } public int getLastEpoch() { return this.haService.getLastEpoch(); } public BrokerRole getBrokerRole() { return this.brokerController.getMessageStoreConfig().getBrokerRole(); } public boolean isMasterState() { return getBrokerRole() == BrokerRole.SYNC_MASTER; } public SyncStateSet getSyncStateSet() { return new SyncStateSet(this.syncStateSet, this.syncStateSetEpoch); } public String getBrokerAddress() { return brokerAddress; } public String getMasterAddress() { return masterAddress; } public int getMasterEpoch() { return masterEpoch; } public List getControllerAddresses() { return controllerAddresses; } public List getEpochEntries() { return this.haService.getEpochEntries(); } public List getAvailableControllerAddresses() { return new ArrayList<>(availableControllerAddresses.keySet()); } public Long getBrokerControllerId() { return brokerControllerId; } public RegisterState getRegisterState() { return registerState; } public State getState() { return state; } public BrokerMetadata getBrokerMetadata() { return brokerMetadata; } public TempBrokerMetadata getTempBrokerMetadata() { return tempBrokerMetadata; } public void setFenced(boolean fenced) { this.brokerController.setIsolated(fenced); this.brokerController.getMessageStore().getRunningFlags().makeFenced(fenced); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.dledger; import io.openmessaging.storage.dledger.DLedgerLeaderElector; import io.openmessaging.storage.dledger.DLedgerServer; import io.openmessaging.storage.dledger.MemberState; import io.openmessaging.storage.dledger.utils.DLedgerUtils; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.dledger.DLedgerCommitLog; public class DLedgerRoleChangeHandler implements DLedgerLeaderElector.RoleChangeHandler { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private ExecutorService executorService; private BrokerController brokerController; private DefaultMessageStore messageStore; private DLedgerCommitLog dLedgerCommitLog; private DLedgerServer dLegerServer; private Future slaveSyncFuture; private long lastSyncTimeMs = System.currentTimeMillis(); public DLedgerRoleChangeHandler(BrokerController brokerController, DefaultMessageStore messageStore) { this.brokerController = brokerController; this.messageStore = messageStore; this.dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); this.dLegerServer = dLedgerCommitLog.getdLedgerServer(); this.executorService = ThreadUtils.newSingleThreadExecutor( new ThreadFactoryImpl("DLegerRoleChangeHandler_", brokerController.getBrokerIdentity())); } @Override public void handle(long term, MemberState.Role role) { Runnable runnable = new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); try { boolean succ = true; LOGGER.info("Begin handling broker role change term={} role={} currStoreRole={}", term, role, messageStore.getMessageStoreConfig().getBrokerRole()); switch (role) { case CANDIDATE: if (messageStore.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE) { changeToSlave(dLedgerCommitLog.getId()); } break; case FOLLOWER: changeToSlave(dLedgerCommitLog.getId()); break; case LEADER: while (true) { if (!dLegerServer.getMemberState().isLeader()) { succ = false; break; } if (dLegerServer.getDLedgerStore().getLedgerEndIndex() == -1) { break; } if (dLegerServer.getDLedgerStore().getLedgerEndIndex() == dLegerServer.getDLedgerStore().getCommittedIndex() && messageStore.dispatchBehindBytes() == 0) { break; } Thread.sleep(100); } if (succ) { messageStore.recoverTopicQueueTable(); changeToMaster(BrokerRole.SYNC_MASTER); } break; default: break; } LOGGER.info("Finish handling broker role change succ={} term={} role={} currStoreRole={} cost={}", succ, term, role, messageStore.getMessageStoreConfig().getBrokerRole(), DLedgerUtils.elapsed(start)); } catch (Throwable t) { LOGGER.info("[MONITOR]Failed handling broker role change term={} role={} currStoreRole={} cost={}", term, role, messageStore.getMessageStoreConfig().getBrokerRole(), DLedgerUtils.elapsed(start), t); } } }; executorService.submit(runnable); } private void handleSlaveSynchronize(BrokerRole role) { if (role == BrokerRole.SLAVE) { if (null != slaveSyncFuture) { slaveSyncFuture.cancel(false); } this.brokerController.getSlaveSynchronize().setMasterAddr(null); slaveSyncFuture = this.brokerController.getScheduledExecutorService().scheduleAtFixedRate(new Runnable() { @Override public void run() { try { if (System.currentTimeMillis() - lastSyncTimeMs > 10 * 1000) { brokerController.getSlaveSynchronize().syncAll(); lastSyncTimeMs = System.currentTimeMillis(); } //timer checkpoint, latency-sensitive, so sync it more frequently brokerController.getSlaveSynchronize().syncTimerCheckPoint(); } catch (Throwable e) { LOGGER.error("ScheduledTask SlaveSynchronize syncAll error.", e); } } }, 1000 * 3, 1000 * 3, TimeUnit.MILLISECONDS); } else { //handle the slave synchronise if (null != slaveSyncFuture) { slaveSyncFuture.cancel(false); } this.brokerController.getSlaveSynchronize().setMasterAddr(null); } } public void changeToSlave(int brokerId) { LOGGER.info("Begin to change to slave brokerName={} brokerId={}", this.brokerController.getBrokerConfig().getBrokerName(), brokerId); //change the role this.brokerController.getBrokerConfig().setBrokerId(brokerId == 0 ? 1 : brokerId); //TO DO check this.brokerController.getMessageStoreConfig().setBrokerRole(BrokerRole.SLAVE); this.brokerController.changeSpecialServiceStatus(false); //handle the slave synchronise handleSlaveSynchronize(BrokerRole.SLAVE); try { this.brokerController.registerBrokerAll(true, true, this.brokerController.getBrokerConfig().isForceRegister()); } catch (Throwable ignored) { } LOGGER.info("Finish to change to slave brokerName={} brokerId={}", this.brokerController.getBrokerConfig().getBrokerName(), brokerId); } public void changeToMaster(BrokerRole role) { if (role == BrokerRole.SLAVE) { return; } LOGGER.info("Begin to change to master brokerName={}", this.brokerController.getBrokerConfig().getBrokerName()); //handle the slave synchronise handleSlaveSynchronize(role); this.brokerController.changeSpecialServiceStatus(true); //if the operations above are totally successful, we change to master this.brokerController.getBrokerConfig().setBrokerId(0); //TO DO check this.brokerController.getMessageStoreConfig().setBrokerRole(role); try { this.brokerController.registerBrokerAll(true, true, this.brokerController.getBrokerConfig().isForceRegister()); } catch (Throwable ignored) { } LOGGER.info("Finish to change to master brokerName={}", this.brokerController.getBrokerConfig().getBrokerName()); } @Override public void startup() { } @Override public void shutdown() { executorService.shutdown(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.failover; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.tieredstore.TieredMessageStore; public class EscapeBridge { protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final long SEND_TIMEOUT = 3000L; private static final long DEFAULT_PULL_TIMEOUT_MILLIS = 1000 * 10L; private final String innerProducerGroupName; private final String innerConsumerGroupName; private final BrokerController brokerController; private ExecutorService defaultAsyncSenderExecutor; public EscapeBridge(BrokerController brokerController) { this.brokerController = brokerController; this.innerProducerGroupName = "InnerProducerGroup_" + brokerController.getBrokerConfig().getBrokerName() + "_" + brokerController.getBrokerConfig().getBrokerId(); this.innerConsumerGroupName = "InnerConsumerGroup_" + brokerController.getBrokerConfig().getBrokerName() + "_" + brokerController.getBrokerConfig().getBrokerId(); } public void start() throws Exception { if (brokerController.getBrokerConfig().isEnableSlaveActingMaster() && brokerController.getBrokerConfig().isEnableRemoteEscape()) { final BlockingQueue asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000); this.defaultAsyncSenderExecutor = ThreadUtils.newThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 1000 * 60, TimeUnit.MILLISECONDS, asyncSenderThreadPoolQueue, new ThreadFactoryImpl("AsyncEscapeBridgeExecutor_", this.brokerController.getBrokerIdentity()) ); LOG.info("init executor for escaping messages asynchronously success."); } } public void shutdown() { if (null != this.defaultAsyncSenderExecutor) { this.defaultAsyncSenderExecutor.shutdown(); } } public PutMessageResult putMessage(MessageExtBrokerInner messageExt) { BrokerController masterBroker = this.brokerController.peekMasterBroker(); if (masterBroker != null) { return masterBroker.getMessageStore().putMessage(messageExt); } else if (this.brokerController.getBrokerConfig().isEnableSlaveActingMaster() && this.brokerController.getBrokerConfig().isEnableRemoteEscape()) { try { messageExt.setWaitStoreMsgOK(false); final SendResult sendResult = putMessageToRemoteBroker(messageExt, null); return transformSendResult2PutResult(sendResult); } catch (Exception e) { LOG.error("sendMessageInFailover to remote failed", e); return new PutMessageResult(PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL, null, true); } } else { LOG.warn("Put message failed, enableSlaveActingMaster={}, enableRemoteEscape={}.", this.brokerController.getBrokerConfig().isEnableSlaveActingMaster(), this.brokerController.getBrokerConfig().isEnableRemoteEscape()); return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null); } } public SendResult putMessageToRemoteBroker(MessageExtBrokerInner messageExt, String brokerNameToSend) { if (this.brokerController.getBrokerConfig().getBrokerName().equals(brokerNameToSend)) { // not remote broker return null; } final boolean isTransHalfMessage = TransactionalMessageUtil.buildHalfTopic().equals(messageExt.getTopic()); MessageExtBrokerInner messageToPut = messageExt; if (isTransHalfMessage) { messageToPut = TransactionalMessageUtil.buildTransactionalMessageFromHalfMessage(messageExt); } final TopicPublishInfo topicPublishInfo = this.brokerController.getTopicRouteInfoManager().tryToFindTopicPublishInfo(messageToPut.getTopic()); if (null == topicPublishInfo || !topicPublishInfo.ok()) { LOG.warn("putMessageToRemoteBroker: no route info of topic {} when escaping message, msgId={}", messageToPut.getTopic(), messageToPut.getMsgId()); return null; } final MessageQueue mqSelected; if (StringUtils.isEmpty(brokerNameToSend)) { mqSelected = topicPublishInfo.selectOneMessageQueue(this.brokerController.getBrokerConfig().getBrokerName()); messageToPut.setQueueId(mqSelected.getQueueId()); brokerNameToSend = mqSelected.getBrokerName(); if (this.brokerController.getBrokerConfig().getBrokerName().equals(brokerNameToSend)) { LOG.warn("putMessageToRemoteBroker failed, remote broker not found. Topic: {}, MsgId: {}, Broker: {}", messageExt.getTopic(), messageExt.getMsgId(), brokerNameToSend); return null; } } else { mqSelected = new MessageQueue(messageExt.getTopic(), brokerNameToSend, messageExt.getQueueId()); } final String brokerAddrToSend = this.brokerController.getTopicRouteInfoManager().findBrokerAddressInPublish(brokerNameToSend); if (null == brokerAddrToSend) { LOG.warn("putMessageToRemoteBroker failed, remote broker address not found. Topic: {}, MsgId: {}, Broker: {}", messageExt.getTopic(), messageExt.getMsgId(), brokerNameToSend); return null; } final long beginTimestamp = System.currentTimeMillis(); try { final SendResult sendResult = this.brokerController.getBrokerOuterAPI().sendMessageToSpecificBroker( brokerAddrToSend, brokerNameToSend, messageToPut, this.getProducerGroup(messageToPut), SEND_TIMEOUT); if (null != sendResult && SendStatus.SEND_OK.equals(sendResult.getSendStatus())) { return sendResult; } else { LOG.error("Escaping failed! cost {}ms, Topic: {}, MsgId: {}, Broker: {}", System.currentTimeMillis() - beginTimestamp, messageExt.getTopic(), messageExt.getMsgId(), brokerNameToSend); } } catch (RemotingException | MQBrokerException e) { LOG.error(String.format("putMessageToRemoteBroker exception, MsgId: %s, RT: %sms, Broker: %s", messageToPut.getMsgId(), System.currentTimeMillis() - beginTimestamp, mqSelected), e); } catch (InterruptedException e) { LOG.error(String.format("putMessageToRemoteBroker interrupted, MsgId: %s, RT: %sms, Broker: %s", messageToPut.getMsgId(), System.currentTimeMillis() - beginTimestamp, mqSelected), e); Thread.currentThread().interrupt(); } return null; } public CompletableFuture asyncPutMessage(MessageExtBrokerInner messageExt) { BrokerController masterBroker = this.brokerController.peekMasterBroker(); if (masterBroker != null) { return masterBroker.getMessageStore().asyncPutMessage(messageExt); } else if (this.brokerController.getBrokerConfig().isEnableSlaveActingMaster() && this.brokerController.getBrokerConfig().isEnableRemoteEscape()) { try { messageExt.setWaitStoreMsgOK(false); final TopicPublishInfo topicPublishInfo = this.brokerController.getTopicRouteInfoManager().tryToFindTopicPublishInfo(messageExt.getTopic()); final String producerGroup = getProducerGroup(messageExt); final MessageQueue mqSelected = topicPublishInfo.selectOneMessageQueue(); messageExt.setQueueId(mqSelected.getQueueId()); final String brokerNameToSend = mqSelected.getBrokerName(); final String brokerAddrToSend = this.brokerController.getTopicRouteInfoManager().findBrokerAddressInPublish(brokerNameToSend); final CompletableFuture future = this.brokerController.getBrokerOuterAPI().sendMessageToSpecificBrokerAsync(brokerAddrToSend, brokerNameToSend, messageExt, producerGroup, SEND_TIMEOUT); return future.exceptionally(throwable -> null) .thenApplyAsync(this::transformSendResult2PutResult, this.defaultAsyncSenderExecutor) .exceptionally(throwable -> transformSendResult2PutResult(null)); } catch (Exception e) { LOG.error("sendMessageInFailover to remote failed", e); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL, null, true)); } } else { LOG.warn("Put message failed, enableSlaveActingMaster={}, enableRemoteEscape={}.", this.brokerController.getBrokerConfig().isEnableSlaveActingMaster(), this.brokerController.getBrokerConfig().isEnableRemoteEscape()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null)); } } private String getProducerGroup(MessageExtBrokerInner messageExt) { if (null == messageExt) { return this.innerProducerGroupName; } String producerGroup = messageExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); if (StringUtils.isEmpty(producerGroup)) { producerGroup = this.innerProducerGroupName; } return producerGroup; } public PutMessageResult putMessageToSpecificQueue(MessageExtBrokerInner messageExt) { BrokerController masterBroker = this.brokerController.peekMasterBroker(); if (masterBroker != null) { return masterBroker.getMessageStore().putMessage(messageExt); } try { return asyncRemotePutMessageToSpecificQueue(messageExt).get(SEND_TIMEOUT, TimeUnit.MILLISECONDS); } catch (Exception e) { LOG.error("Put message to specific queue error", e); return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, null, true); } } public CompletableFuture asyncPutMessageToSpecificQueue(MessageExtBrokerInner messageExt) { BrokerController masterBroker = this.brokerController.peekMasterBroker(); if (masterBroker != null) { return masterBroker.getMessageStore().asyncPutMessage(messageExt); } return asyncRemotePutMessageToSpecificQueue(messageExt); } public CompletableFuture asyncRemotePutMessageToSpecificQueue(MessageExtBrokerInner messageExt) { if (this.brokerController.getBrokerConfig().isEnableSlaveActingMaster() && this.brokerController.getBrokerConfig().isEnableRemoteEscape()) { try { messageExt.setWaitStoreMsgOK(false); final TopicPublishInfo topicPublishInfo = this.brokerController.getTopicRouteInfoManager().tryToFindTopicPublishInfo(messageExt.getTopic()); List mqs = topicPublishInfo.getMessageQueueList(); if (null == mqs || mqs.isEmpty()) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL, null, true)); } String id = messageExt.getTopic() + messageExt.getStoreHost(); final int index = Math.floorMod(id.hashCode(), mqs.size()); MessageQueue mq = mqs.get(index); messageExt.setQueueId(mq.getQueueId()); String brokerNameToSend = mq.getBrokerName(); String brokerAddrToSend = this.brokerController.getTopicRouteInfoManager().findBrokerAddressInPublish(brokerNameToSend); return this.brokerController.getBrokerOuterAPI().sendMessageToSpecificBrokerAsync( brokerAddrToSend, brokerNameToSend, messageExt, this.getProducerGroup(messageExt), SEND_TIMEOUT).thenCompose(sendResult -> CompletableFuture.completedFuture(transformSendResult2PutResult(sendResult))); } catch (Exception e) { LOG.error("sendMessageInFailover to remote failed", e); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL, null, true)); } } else { LOG.warn("Put message to specific queue failed, enableSlaveActingMaster={}, enableRemoteEscape={}.", this.brokerController.getBrokerConfig().isEnableSlaveActingMaster(), this.brokerController.getBrokerConfig().isEnableRemoteEscape()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null)); } } private PutMessageResult transformSendResult2PutResult(SendResult sendResult) { if (sendResult == null) { return new PutMessageResult(PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL, null, true); } switch (sendResult.getSendStatus()) { case SEND_OK: return new PutMessageResult(PutMessageStatus.PUT_OK, null, true); case SLAVE_NOT_AVAILABLE: return new PutMessageResult(PutMessageStatus.SLAVE_NOT_AVAILABLE, null, true); case FLUSH_DISK_TIMEOUT: return new PutMessageResult(PutMessageStatus.FLUSH_DISK_TIMEOUT, null, true); case FLUSH_SLAVE_TIMEOUT: return new PutMessageResult(PutMessageStatus.FLUSH_SLAVE_TIMEOUT, null, true); default: return new PutMessageResult(PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL, null, true); } } public Triple getMessage(String topic, long offset, int queueId, String brokerName, boolean deCompressBody) { return getMessageAsync(topic, offset, queueId, brokerName, deCompressBody).join(); } // Triple, check info and retry if and only if MessageExt is null public CompletableFuture> getMessageAsync(String topic, long offset, int queueId, String brokerName, boolean deCompressBody) { MessageStore messageStore = brokerController.getMessageStoreByBrokerName(brokerName); if (messageStore != null) { return messageStore.getMessageAsync(innerConsumerGroupName, topic, queueId, offset, 1, null) .thenApply(result -> { if (result == null) { LOG.warn("getMessageResult is null , innerConsumerGroupName {}, topic {}, offset {}, queueId {}", innerConsumerGroupName, topic, offset, queueId); return Triple.of(null, "getMessageResult is null", false); // local store, so no retry } List list = decodeMsgList(result, deCompressBody); if (list == null || list.isEmpty()) { // OFFSET_FOUND_NULL returned by TieredMessageStore indicates exception occurred boolean needRetry = GetMessageStatus.OFFSET_FOUND_NULL.equals(result.getStatus()) && messageStore instanceof TieredMessageStore; LOG.warn("Can not get msg , topic {}, offset {}, queueId {}, needRetry {}, result is {}", topic, offset, queueId, needRetry, result); return Triple.of(null, "Can not get msg", needRetry); } return Triple.of(list.get(0), "", false); }); } else { return getMessageFromRemoteAsync(topic, offset, queueId, brokerName); } } protected List decodeMsgList(GetMessageResult getMessageResult, boolean deCompressBody) { List foundList = new ArrayList<>(); try { List messageBufferList = getMessageResult.getMessageBufferList(); if (messageBufferList != null) { for (int i = 0; i < messageBufferList.size(); i++) { ByteBuffer bb = messageBufferList.get(i); if (bb == null) { LOG.error("bb is null {}", getMessageResult); continue; } MessageExt msgExt = MessageDecoder.decode(bb, true, deCompressBody); if (msgExt == null) { LOG.error("decode msgExt is null {}", getMessageResult); continue; } // use CQ offset, not offset in Message msgExt.setQueueOffset(getMessageResult.getMessageQueueOffset().get(i)); foundList.add(msgExt); } } } finally { getMessageResult.release(); } return foundList; } protected Triple getMessageFromRemote(String topic, long offset, int queueId, String brokerName) { return getMessageFromRemoteAsync(topic, offset, queueId, brokerName).join(); } // Triple, check info and retry if and only if MessageExt is null protected CompletableFuture> getMessageFromRemoteAsync(String topic, long offset, int queueId, String brokerName) { try { String brokerAddr = this.brokerController.getTopicRouteInfoManager().findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, false); if (null == brokerAddr) { this.brokerController.getTopicRouteInfoManager().updateTopicRouteInfoFromNameServer(topic, true, false); brokerAddr = this.brokerController.getTopicRouteInfoManager().findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, false); if (null == brokerAddr) { LOG.warn("can't find broker address for topic {}, {}", topic, brokerName); return CompletableFuture.completedFuture(Triple.of(null, "brokerAddress not found", true)); // maybe offline temporarily, so need retry } } return this.brokerController.getBrokerOuterAPI().pullMessageFromSpecificBrokerAsync(brokerName, brokerAddr, this.innerConsumerGroupName, topic, queueId, offset, 1, DEFAULT_PULL_TIMEOUT_MILLIS) .thenApply(pullResult -> { if (pullResult.getLeft() != null && PullStatus.FOUND.equals(pullResult.getLeft().getPullStatus()) && CollectionUtils.isNotEmpty(pullResult.getLeft().getMsgFoundList())) { return Triple.of(pullResult.getLeft().getMsgFoundList().get(0), "", false); } return Triple.of(null, pullResult.getMiddle(), pullResult.getRight()); }); } catch (Exception e) { LOG.error("Get message from remote failed. {}, {}, {}, {}", topic, offset, queueId, brokerName, e); } return CompletableFuture.completedFuture(Triple.of(null, "Get message from remote failed", true)); // need retry } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMap.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.filter.util.BitsArray; import org.apache.rocketmq.store.CommitLogDispatcher; import org.apache.rocketmq.store.DispatchRequest; import java.util.Collection; import java.util.Iterator; /** * Calculate bit map of filter. */ public class CommitLogDispatcherCalcBitMap implements CommitLogDispatcher { private static final Logger log = LoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); protected final BrokerConfig brokerConfig; protected final ConsumerFilterManager consumerFilterManager; public CommitLogDispatcherCalcBitMap(BrokerConfig brokerConfig, ConsumerFilterManager consumerFilterManager) { this.brokerConfig = brokerConfig; this.consumerFilterManager = consumerFilterManager; } @Override public void dispatch(DispatchRequest request) { if (!this.brokerConfig.isEnableCalcFilterBitMap()) { return; } try { Collection filterDatas = consumerFilterManager.get(request.getTopic()); if (filterDatas == null || filterDatas.isEmpty()) { return; } Iterator iterator = filterDatas.iterator(); BitsArray filterBitMap = BitsArray.create( this.consumerFilterManager.getBloomFilter().getM() ); long startTime = System.currentTimeMillis(); while (iterator.hasNext()) { ConsumerFilterData filterData = iterator.next(); if (filterData.getCompiledExpression() == null) { log.error("[BUG] Consumer in filter manager has no compiled expression! {}", filterData); continue; } if (filterData.getBloomFilterData() == null) { log.error("[BUG] Consumer in filter manager has no bloom data! {}", filterData); continue; } Object ret = null; try { MessageEvaluationContext context = new MessageEvaluationContext(request.getPropertiesMap()); ret = filterData.getCompiledExpression().evaluate(context); } catch (Throwable e) { log.error("Calc filter bit map error!commitLogOffset={}, consumer={}, {}", request.getCommitLogOffset(), filterData, e); } log.debug("Result of Calc bit map:ret={}, data={}, props={}, offset={}", ret, filterData, request.getPropertiesMap(), request.getCommitLogOffset()); // eval true if (ret != null && ret instanceof Boolean && (Boolean) ret) { consumerFilterManager.getBloomFilter().hashTo( filterData.getBloomFilterData(), filterBitMap ); } } request.setBitMap(filterBitMap.bytes()); long elapsedTime = UtilAll.computeElapsedTimeMilliseconds(startTime); // 1ms if (elapsedTime >= 1) { log.warn("Spend {} ms to calc bit map, consumerNum={}, topic={}", elapsedTime, filterDatas.size(), request.getTopic()); } } catch (Throwable e) { log.error("Calc bit map error! topic={}, offset={}, queueId={}, {}", request.getTopic(), request.getCommitLogOffset(), request.getQueueId(), e); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.util.BloomFilterData; import java.util.Collections; /** * Filter data of consumer. */ public class ConsumerFilterData { private String consumerGroup; private String topic; private String expression; private String expressionType; private transient Expression compiledExpression; private long bornTime; private long deadTime = 0; private BloomFilterData bloomFilterData; private long clientVersion; public boolean isDead() { return this.deadTime >= this.bornTime; } public long howLongAfterDeath() { if (isDead()) { return System.currentTimeMillis() - getDeadTime(); } return -1; } /** * Check this filter data has been used to calculate bit map when msg was stored in server. */ public boolean isMsgInLive(long msgStoreTime) { return msgStoreTime > getBornTime(); } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(final String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(final String topic) { this.topic = topic; } public String getExpression() { return expression; } public void setExpression(final String expression) { this.expression = expression; } public String getExpressionType() { return expressionType; } public void setExpressionType(final String expressionType) { this.expressionType = expressionType; } public Expression getCompiledExpression() { return compiledExpression; } public void setCompiledExpression(final Expression compiledExpression) { this.compiledExpression = compiledExpression; } public long getBornTime() { return bornTime; } public void setBornTime(final long bornTime) { this.bornTime = bornTime; } public long getDeadTime() { return deadTime; } public void setDeadTime(final long deadTime) { this.deadTime = deadTime; } public BloomFilterData getBloomFilterData() { return bloomFilterData; } public void setBloomFilterData(final BloomFilterData bloomFilterData) { this.bloomFilterData = bloomFilterData; } public long getClientVersion() { return clientVersion; } public void setClientVersion(long clientVersion) { this.clientVersion = clientVersion; } @Override public boolean equals(Object o) { return EqualsBuilder.reflectionEquals(this, o, Collections.emptyList()); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this, Collections.emptyList()); } @Override public String toString() { return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.filter.FilterFactory; import org.apache.rocketmq.filter.util.BloomFilter; import org.apache.rocketmq.filter.util.BloomFilterData; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; /** * Consumer filter data manager.Just manage the consumers use expression filter. */ public class ConsumerFilterManager extends ConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); private static final long MS_24_HOUR = 24 * 3600 * 1000; private ConcurrentMap filterDataByTopic = new ConcurrentHashMap<>(256); private transient BrokerController brokerController; private transient BloomFilter bloomFilter; public ConsumerFilterManager() { // just for test this.bloomFilter = BloomFilter.createByFn(20, 64); } public ConsumerFilterManager(BrokerController brokerController) { this.brokerController = brokerController; this.bloomFilter = BloomFilter.createByFn( brokerController.getBrokerConfig().getMaxErrorRateOfBloomFilter(), brokerController.getBrokerConfig().getExpectConsumerNumUseFilter() ); // then set bit map length of store config. brokerController.getMessageStoreConfig().setBitMapLengthConsumeQueueExt( this.bloomFilter.getM() ); } /** * Build consumer filter data.Be care, bloom filter data is not included. * * @return maybe null */ public static ConsumerFilterData build(final String topic, final String consumerGroup, final String expression, final String type, final long clientVersion) { if (ExpressionType.isTagType(type)) { return null; } ConsumerFilterData consumerFilterData = new ConsumerFilterData(); consumerFilterData.setTopic(topic); consumerFilterData.setConsumerGroup(consumerGroup); consumerFilterData.setBornTime(System.currentTimeMillis()); consumerFilterData.setDeadTime(0); consumerFilterData.setExpression(expression); consumerFilterData.setExpressionType(type); consumerFilterData.setClientVersion(clientVersion); try { consumerFilterData.setCompiledExpression( FilterFactory.INSTANCE.get(type).compile(expression) ); } catch (Throwable e) { log.error("parse error: expr={}, topic={}, group={}, error={}", expression, topic, consumerGroup, e.getMessage()); return null; } return consumerFilterData; } public void register(final String consumerGroup, final Collection subList) { for (SubscriptionData subscriptionData : subList) { register( subscriptionData.getTopic(), consumerGroup, subscriptionData.getSubString(), subscriptionData.getExpressionType(), subscriptionData.getSubVersion() ); } // make illegal topic dead. Collection groupFilterData = getByGroup(consumerGroup); Iterator iterator = groupFilterData.iterator(); while (iterator.hasNext()) { ConsumerFilterData filterData = iterator.next(); boolean exist = false; for (SubscriptionData subscriptionData : subList) { if (subscriptionData.getTopic().equals(filterData.getTopic())) { exist = true; break; } } if (!exist && !filterData.isDead()) { filterData.setDeadTime(System.currentTimeMillis()); log.info("Consumer filter changed: {}, make illegal topic dead:{}", consumerGroup, filterData); } } } public boolean register(final String topic, final String consumerGroup, final String expression, final String type, final long clientVersion) { if (ExpressionType.isTagType(type)) { return false; } if (expression == null || expression.length() == 0) { return false; } FilterDataMapByTopic filterDataMapByTopic = this.filterDataByTopic.get(topic); if (filterDataMapByTopic == null) { FilterDataMapByTopic temp = new FilterDataMapByTopic(topic); FilterDataMapByTopic prev = this.filterDataByTopic.putIfAbsent(topic, temp); filterDataMapByTopic = prev != null ? prev : temp; } BloomFilterData bloomFilterData = bloomFilter.generate(consumerGroup + "#" + topic); return filterDataMapByTopic.register(consumerGroup, expression, type, bloomFilterData, clientVersion); } public void unRegister(final String consumerGroup) { for (Entry entry : filterDataByTopic.entrySet()) { entry.getValue().unRegister(consumerGroup); } } public ConsumerFilterData get(final String topic, final String consumerGroup) { if (!this.filterDataByTopic.containsKey(topic)) { return null; } if (this.filterDataByTopic.get(topic).getGroupFilterData().isEmpty()) { return null; } return this.filterDataByTopic.get(topic).getGroupFilterData().get(consumerGroup); } public Collection getByGroup(final String consumerGroup) { Collection ret = new HashSet<>(); Iterator topicIterator = this.filterDataByTopic.values().iterator(); while (topicIterator.hasNext()) { FilterDataMapByTopic filterDataMapByTopic = topicIterator.next(); Iterator filterDataIterator = filterDataMapByTopic.getGroupFilterData().values().iterator(); while (filterDataIterator.hasNext()) { ConsumerFilterData filterData = filterDataIterator.next(); if (filterData.getConsumerGroup().equals(consumerGroup)) { ret.add(filterData); } } } return ret; } public final Collection get(final String topic) { if (!this.filterDataByTopic.containsKey(topic)) { return null; } if (this.filterDataByTopic.get(topic).getGroupFilterData().isEmpty()) { return null; } return this.filterDataByTopic.get(topic).getGroupFilterData().values(); } public BloomFilter getBloomFilter() { return bloomFilter; } @Override public String encode() { return encode(false); } @Override public String configFilePath() { if (this.brokerController != null) { return BrokerPathConfigHelper.getConsumerFilterPath( this.brokerController.getMessageStoreConfig().getStorePathRootDir() ); } return BrokerPathConfigHelper.getConsumerFilterPath("./unit_test"); } @Override public void decode(final String jsonString) { ConsumerFilterManager load = RemotingSerializable.fromJson(jsonString, ConsumerFilterManager.class); if (load != null && load.filterDataByTopic != null) { boolean bloomChanged = false; for (Entry entry : load.filterDataByTopic.entrySet()) { FilterDataMapByTopic dataMapByTopic = entry.getValue(); if (dataMapByTopic == null) { continue; } for (Entry groupEntry : dataMapByTopic.getGroupFilterData().entrySet()) { ConsumerFilterData filterData = groupEntry.getValue(); if (filterData == null) { continue; } try { filterData.setCompiledExpression( FilterFactory.INSTANCE.get(filterData.getExpressionType()).compile(filterData.getExpression()) ); } catch (Exception e) { log.error("load filter data error, " + filterData, e); } // check whether bloom filter is changed // if changed, ignore the bit map calculated before. if (!this.bloomFilter.isValid(filterData.getBloomFilterData())) { bloomChanged = true; log.info("Bloom filter is changed!So ignore all filter data persisted! {}, {}", this.bloomFilter, filterData.getBloomFilterData()); break; } log.info("load exist consumer filter data: {}", filterData); if (filterData.getDeadTime() == 0) { // we think all consumers are dead when load long deadTime = System.currentTimeMillis() - 30 * 1000; filterData.setDeadTime( deadTime <= filterData.getBornTime() ? filterData.getBornTime() : deadTime ); } } } if (!bloomChanged) { this.filterDataByTopic = load.filterDataByTopic; } } } @Override public String encode(final boolean prettyFormat) { // clean { clean(); } return RemotingSerializable.toJson(this, prettyFormat); } public void clean() { Iterator> topicIterator = this.filterDataByTopic.entrySet().iterator(); while (topicIterator.hasNext()) { Map.Entry filterDataMapByTopic = topicIterator.next(); Iterator> filterDataIterator = filterDataMapByTopic.getValue().getGroupFilterData().entrySet().iterator(); while (filterDataIterator.hasNext()) { Map.Entry filterDataByGroup = filterDataIterator.next(); ConsumerFilterData filterData = filterDataByGroup.getValue(); if (filterData.howLongAfterDeath() >= (this.brokerController == null ? MS_24_HOUR : this.brokerController.getBrokerConfig().getFilterDataCleanTimeSpan())) { log.info("Remove filter consumer {}, died too long!", filterDataByGroup.getValue()); filterDataIterator.remove(); } } if (filterDataMapByTopic.getValue().getGroupFilterData().isEmpty()) { log.info("Topic has no consumer, remove it! {}", filterDataMapByTopic.getKey()); topicIterator.remove(); } } } public ConcurrentMap getFilterDataByTopic() { return filterDataByTopic; } public void setFilterDataByTopic(final ConcurrentHashMap filterDataByTopic) { this.filterDataByTopic = filterDataByTopic; } public static class FilterDataMapByTopic { private ConcurrentMap groupFilterData = new ConcurrentHashMap<>(); private String topic; public FilterDataMapByTopic() { } public FilterDataMapByTopic(String topic) { this.topic = topic; } public void unRegister(String consumerGroup) { if (!this.groupFilterData.containsKey(consumerGroup)) { return; } ConsumerFilterData data = this.groupFilterData.get(consumerGroup); if (data == null || data.isDead()) { return; } long now = System.currentTimeMillis(); log.info("Unregister consumer filter: {}, deadTime: {}", data, now); data.setDeadTime(now); } public boolean register(String consumerGroup, String expression, String type, BloomFilterData bloomFilterData, long clientVersion) { ConsumerFilterData old = this.groupFilterData.get(consumerGroup); if (old == null) { ConsumerFilterData consumerFilterData = build(topic, consumerGroup, expression, type, clientVersion); if (consumerFilterData == null) { return false; } consumerFilterData.setBloomFilterData(bloomFilterData); old = this.groupFilterData.putIfAbsent(consumerGroup, consumerFilterData); if (old == null) { log.info("New consumer filter registered: {}", consumerFilterData); return true; } else { if (clientVersion <= old.getClientVersion()) { if (!type.equals(old.getExpressionType()) || !expression.equals(old.getExpression())) { log.warn("Ignore consumer({} : {}) filter(concurrent), because of version {} <= {}, but maybe info changed!old={}:{}, ignored={}:{}", consumerGroup, topic, clientVersion, old.getClientVersion(), old.getExpressionType(), old.getExpression(), type, expression); } if (clientVersion == old.getClientVersion() && old.isDead()) { reAlive(old); return true; } return false; } else { this.groupFilterData.put(consumerGroup, consumerFilterData); log.info("New consumer filter registered(concurrent): {}, old: {}", consumerFilterData, old); return true; } } } else { if (clientVersion <= old.getClientVersion()) { if (!type.equals(old.getExpressionType()) || !expression.equals(old.getExpression())) { log.info("Ignore consumer({}:{}) filter, because of version {} <= {}, but maybe info changed!old={}:{}, ignored={}:{}", consumerGroup, topic, clientVersion, old.getClientVersion(), old.getExpressionType(), old.getExpression(), type, expression); } if (clientVersion == old.getClientVersion() && old.isDead()) { reAlive(old); return true; } return false; } boolean change = !old.getExpression().equals(expression) || !old.getExpressionType().equals(type); if (old.getBloomFilterData() == null && bloomFilterData != null) { change = true; } if (old.getBloomFilterData() != null && !old.getBloomFilterData().equals(bloomFilterData)) { change = true; } // if subscribe data is changed, or consumer is died too long. if (change) { ConsumerFilterData consumerFilterData = build(topic, consumerGroup, expression, type, clientVersion); if (consumerFilterData == null) { // new expression compile error, remove old, let client report error. this.groupFilterData.remove(consumerGroup); return false; } consumerFilterData.setBloomFilterData(bloomFilterData); this.groupFilterData.put(consumerGroup, consumerFilterData); log.info("Consumer filter info change, old: {}, new: {}, change: {}", old, consumerFilterData, change); return true; } else { old.setClientVersion(clientVersion); if (old.isDead()) { reAlive(old); } return true; } } } protected void reAlive(ConsumerFilterData filterData) { long oldDeadTime = filterData.getDeadTime(); filterData.setDeadTime(0); log.info("Re alive consumer filter: {}, oldDeadTime: {}", filterData, oldDeadTime); } public final ConsumerFilterData get(String consumerGroup) { return this.groupFilterData.get(consumerGroup); } public final ConcurrentMap getGroupFilterData() { return this.groupFilterData; } public void setGroupFilterData(final ConcurrentHashMap groupFilterData) { this.groupFilterData = groupFilterData; } public String getTopic() { return topic; } public void setTopic(final String topic) { this.topic = topic; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionForRetryMessageFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import java.nio.ByteBuffer; import java.util.Map; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; /** * Support filter to retry topic. *
It will decode properties first in order to get real topic. */ public class ExpressionForRetryMessageFilter extends ExpressionMessageFilter { public ExpressionForRetryMessageFilter(SubscriptionData subscriptionData, ConsumerFilterData consumerFilterData, ConsumerFilterManager consumerFilterManager) { super(subscriptionData, consumerFilterData, consumerFilterManager); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { if (subscriptionData == null) { return true; } if (subscriptionData.isClassFilterMode()) { return true; } if (ExpressionType.isTagType(subscriptionData.getExpressionType())) { return true; } boolean isRetryTopic = subscriptionData.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX); ConsumerFilterData realFilterData = this.consumerFilterData; Map tempProperties = properties; boolean decoded = false; if (isRetryTopic) { // retry topic, use original filter data. // poor performance to support retry filter. if (tempProperties == null && msgBuffer != null) { decoded = true; tempProperties = MessageDecoder.decodeProperties(msgBuffer); } String realTopic = tempProperties.get(MessageConst.PROPERTY_RETRY_TOPIC); String group = KeyBuilder.parseGroup(subscriptionData.getTopic()); realFilterData = this.consumerFilterManager.get(realTopic, group); } // no expression if (realFilterData == null || realFilterData.getExpression() == null || realFilterData.getCompiledExpression() == null) { return true; } if (!decoded && tempProperties == null && msgBuffer != null) { tempProperties = MessageDecoder.decodeProperties(msgBuffer); } Object ret = null; try { MessageEvaluationContext context = new MessageEvaluationContext(tempProperties); ret = realFilterData.getCompiledExpression().evaluate(context); } catch (Throwable e) { log.error("Message Filter error, " + realFilterData + ", " + tempProperties, e); } log.debug("Pull eval result: {}, {}, {}", ret, realFilterData, tempProperties); if (ret == null || !(ret instanceof Boolean)) { return false; } return (Boolean) ret; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionMessageFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import java.nio.ByteBuffer; import java.util.Map; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.filter.util.BitsArray; import org.apache.rocketmq.filter.util.BloomFilter; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.MessageFilter; public class ExpressionMessageFilter implements MessageFilter { protected static final Logger log = LoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); protected final SubscriptionData subscriptionData; protected final ConsumerFilterData consumerFilterData; protected final ConsumerFilterManager consumerFilterManager; protected final boolean bloomDataValid; public ExpressionMessageFilter(SubscriptionData subscriptionData, ConsumerFilterData consumerFilterData, ConsumerFilterManager consumerFilterManager) { this.subscriptionData = subscriptionData; this.consumerFilterData = consumerFilterData; this.consumerFilterManager = consumerFilterManager; if (consumerFilterData == null) { bloomDataValid = false; return; } BloomFilter bloomFilter = this.consumerFilterManager.getBloomFilter(); if (bloomFilter != null && bloomFilter.isValid(consumerFilterData.getBloomFilterData())) { bloomDataValid = true; } else { bloomDataValid = false; } } @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { if (null == subscriptionData) { return true; } if (subscriptionData.isClassFilterMode()) { return true; } // by tags code. if (ExpressionType.isTagType(subscriptionData.getExpressionType())) { if (tagsCode == null) { return true; } if (subscriptionData.getSubString().equals(SubscriptionData.SUB_ALL)) { return true; } return subscriptionData.getCodeSet().contains(tagsCode.intValue()); } else { // no expression or no bloom if (consumerFilterData == null || consumerFilterData.getExpression() == null || consumerFilterData.getCompiledExpression() == null || consumerFilterData.getBloomFilterData() == null) { return true; } // message is before consumer if (cqExtUnit == null || !consumerFilterData.isMsgInLive(cqExtUnit.getMsgStoreTime())) { log.debug("Pull matched because not in live: {}, {}", consumerFilterData, cqExtUnit); return true; } byte[] filterBitMap = cqExtUnit.getFilterBitMap(); BloomFilter bloomFilter = this.consumerFilterManager.getBloomFilter(); if (filterBitMap == null || !this.bloomDataValid || filterBitMap.length * Byte.SIZE != consumerFilterData.getBloomFilterData().getBitNum()) { return true; } BitsArray bitsArray = null; try { bitsArray = BitsArray.create(filterBitMap); boolean ret = bloomFilter.isHit(consumerFilterData.getBloomFilterData(), bitsArray); log.debug("Pull {} by bit map:{}, {}, {}", ret, consumerFilterData, bitsArray, cqExtUnit); return ret; } catch (Throwable e) { log.error("bloom filter error, sub=" + subscriptionData + ", filter=" + consumerFilterData + ", bitMap=" + bitsArray, e); } } return true; } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { if (subscriptionData == null) { return true; } if (subscriptionData.isClassFilterMode()) { return true; } if (ExpressionType.isTagType(subscriptionData.getExpressionType())) { return true; } ConsumerFilterData realFilterData = this.consumerFilterData; Map tempProperties = properties; // no expression if (realFilterData == null || realFilterData.getExpression() == null || realFilterData.getCompiledExpression() == null) { return true; } if (tempProperties == null && msgBuffer != null) { tempProperties = MessageDecoder.decodeProperties(msgBuffer); } Object ret = null; try { MessageEvaluationContext context = new MessageEvaluationContext(tempProperties); ret = realFilterData.getCompiledExpression().evaluate(context); } catch (Throwable e) { log.error("Message Filter error, " + realFilterData + ", " + tempProperties, e); } log.debug("Pull eval result: {}, {}, {}", ret, realFilterData, tempProperties); if (ret == null || !(ret instanceof Boolean)) { return false; } return (Boolean) ret; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/filter/MessageEvaluationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import org.apache.rocketmq.filter.expression.EvaluationContext; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; /** * Evaluation context from message. */ public class MessageEvaluationContext implements EvaluationContext { private Map properties; public MessageEvaluationContext(Map properties) { this.properties = properties; } @Override public Object get(final String name) { if (this.properties == null) { return null; } return this.properties.get(name); } @Override public Map keyValues() { if (properties == null) { return null; } Map copy = new HashMap<>(properties.size(), 1); for (Entry entry : properties.entrySet()) { copy.put(entry.getKey(), entry.getValue()); } return copy; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.latency; import java.util.List; import java.util.ArrayList; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.future.FutureTaskExt; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; /** * BrokerFastFailure will cover {@link BrokerController#getSendThreadPoolQueue()} and {@link * BrokerController#getPullThreadPoolQueue()} */ public class BrokerFastFailure { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final ScheduledExecutorService scheduledExecutorService; private final BrokerController brokerController; private volatile long jstackTime = System.currentTimeMillis(); private final List, Supplier>> cleanExpiredRequestQueueList = new ArrayList<>(); public BrokerFastFailure(final BrokerController brokerController) { this.brokerController = brokerController; initCleanExpiredRequestQueueList(); this.scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("BrokerFastFailureScheduledThread", true, brokerController == null ? null : brokerController.getBrokerConfig())); } private void initCleanExpiredRequestQueueList() { cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getSendThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInSendQueue())); cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getPullThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInPullQueue())); cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getLitePullThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInLitePullQueue())); cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getHeartbeatThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInHeartbeatQueue())); cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getEndTransactionThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInTransactionQueue())); cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getAckThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInAckQueue())); cleanExpiredRequestQueueList.add(new Pair<>(this.brokerController.getAdminBrokerThreadPoolQueue(), () -> this.brokerController.getBrokerConfig().getWaitTimeMillsInAdminBrokerQueue())); } public static RequestTask castRunnable(final Runnable runnable) { try { if (runnable instanceof FutureTaskExt) { FutureTaskExt object = (FutureTaskExt) runnable; return (RequestTask) object.getRunnable(); } } catch (Throwable e) { LOGGER.error(String.format("castRunnable exception, %s", runnable.getClass().getName()), e); } return null; } public void start() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (brokerController.getBrokerConfig().isBrokerFastFailureEnable()) { cleanExpiredRequest(); } } }, 1000, 10, TimeUnit.MILLISECONDS); } private void cleanExpiredRequest() { while (this.brokerController.getMessageStore().isOSPageCacheBusy()) { try { if (!this.brokerController.getSendThreadPoolQueue().isEmpty()) { final Runnable runnable = this.brokerController.getSendThreadPoolQueue().poll(0, TimeUnit.SECONDS); if (null == runnable) { break; } final RequestTask rt = castRunnable(runnable); if (rt != null) { rt.returnResponse(RemotingSysResponseCode.SYSTEM_BUSY, String.format( "[PCBUSY_CLEAN_QUEUE]broker busy, start flow control for a while, period in queue: %sms, " + "size of queue: %d", System.currentTimeMillis() - rt.getCreateTimestamp(), this.brokerController.getSendThreadPoolQueue().size())); } } else { break; } } catch (Throwable ignored) { } } for (Pair, Supplier> pair : cleanExpiredRequestQueueList) { cleanExpiredRequestInQueue(pair.getObject1(), pair.getObject2().get()); } } void cleanExpiredRequestInQueue(final BlockingQueue blockingQueue, final long maxWaitTimeMillsInQueue) { while (true) { try { if (!blockingQueue.isEmpty()) { final Runnable runnable = blockingQueue.peek(); if (null == runnable) { break; } final RequestTask rt = castRunnable(runnable); if (rt == null || rt.isStopRun()) { break; } final long behind = System.currentTimeMillis() - rt.getCreateTimestamp(); if (behind >= maxWaitTimeMillsInQueue) { if (blockingQueue.remove(runnable)) { rt.setStopRun(true); rt.returnResponse(RemotingSysResponseCode.SYSTEM_BUSY, String.format("[TIMEOUT_CLEAN_QUEUE]broker busy, start flow control for a while, period in queue: %sms, size of queue: %d", behind, blockingQueue.size())); if (System.currentTimeMillis() - jstackTime > 15000) { jstackTime = System.currentTimeMillis(); LOGGER.warn("broker jstack \n " + UtilAll.jstack()); } } } else { break; } } else { break; } } catch (Throwable ignored) { } } } public synchronized void addCleanExpiredRequestQueue(BlockingQueue cleanExpiredRequestQueue, Supplier maxWaitTimeMillsInQueueSupplier) { cleanExpiredRequestQueueList.add(new Pair<>(cleanExpiredRequestQueue, maxWaitTimeMillsInQueueSupplier)); } public void shutdown() { this.scheduledExecutorService.shutdown(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/AbstractLiteLifecycleManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import com.google.common.collect.Sets; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.MessageStore; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import static org.apache.rocketmq.broker.offset.ConsumerOffsetManager.TOPIC_GROUP_SEPARATOR; /** * Abstract class of lite lifecycle manager, which is used to manage the TTL of lite topics * and the validity of subscription. The subclasses provide file CQ and rocksdb CQ implementations. */ public abstract class AbstractLiteLifecycleManager extends ServiceThread { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); protected final BrokerController brokerController; protected final String brokerName; protected final LiteSharding liteSharding; protected MessageStore messageStore; protected Map ttlMap = Collections.emptyMap(); protected Map> subscriberGroupMap = Collections.emptyMap(); public AbstractLiteLifecycleManager(BrokerController brokerController, LiteSharding liteSharding) { this.brokerController = brokerController; this.brokerName = brokerController.getBrokerConfig().getBrokerName(); this.liteSharding = liteSharding; } public void init() { this.messageStore = brokerController.getMessageStore(); assert messageStore != null; } /** * This method actually returns NEXT slot index to use, starting from 0 */ public abstract long getMaxOffsetInQueue(String lmqName); /** * Collect expired LMQ of lite topic, and also attach its parent topic name * return Pair of parent topic and lmq name, not null */ public abstract List> collectExpiredLiteTopic(); /** * Collect LMQ by parent topic * return lmq name list, not null */ public abstract List collectByParentTopic(String parentTopic); /** * Check if the subscription for the given LMQ is active. * A subscription is considered active if either: * - the current broker is responsible for this LMQ according to the sharding strategy * - the LMQ exists (has messages) in the message store */ public boolean isSubscriptionActive(String parentTopic, String lmqName) { return brokerName.equals(liteSharding.shardingByLmqName(parentTopic, lmqName)) || isLmqExist(lmqName); } public int getLiteTopicCount(String parentTopic) { if (!LiteMetadataUtil.isLiteMessageType(parentTopic, brokerController)) { return 0; } return collectByParentTopic(parentTopic).size(); } public boolean isLmqExist(String lmqName) { return getMaxOffsetInQueue(lmqName) > 0; } public void cleanExpiredLiteTopic() { try { updateMetadata(); // necessary List> lmqToDelete = collectExpiredLiteTopic(); LOGGER.info("collect expired topic, size:{}", lmqToDelete.size()); lmqToDelete.forEach(pair -> deleteLmq(pair.getObject1(), pair.getObject2())); if (!lmqToDelete.isEmpty()) { brokerController.getMessageStore().getQueueStore().flush(); } } catch (Exception e) { LOGGER.error("cleanExpiredLiteTopic error", e); } } public void cleanByParentTopic(String parentTopic) { try { if (!LiteMetadataUtil.isLiteMessageType(parentTopic, brokerController)) { return; } updateMetadata(); // necessary List lmqToDelete = collectByParentTopic(parentTopic); LOGGER.info("clean by parent topic, {}, size:{}", parentTopic, lmqToDelete.size()); lmqToDelete.forEach(lmqName -> deleteLmq(parentTopic, lmqName)); } catch (Exception e) { LOGGER.error("cleanByParentTopic error", e); } } @Override public void run() { LOGGER.info("Start checking lite ttl."); while (!this.isStopped()) { long runningTime = System.currentTimeMillis() - brokerController.getShouldStartTime(); if (runningTime < brokerController.getBrokerConfig().getMinLiteTTl()) { // base protection for restart this.waitForRunning(20 * 1000); continue; } cleanExpiredLiteTopic(); long checkInterval = brokerController.getBrokerConfig().getLiteTtlCheckInterval(); this.waitForRunning(checkInterval); } LOGGER.info("End checking lite ttl."); } public void updateMetadata() { ttlMap = LiteMetadataUtil.getTopicTtlMap(brokerController); subscriberGroupMap = LiteMetadataUtil.getSubscriberGroupMap(brokerController); } public boolean isLiteTopicExpired(String parentTopic, String lmqName, long maxOffset) { if (!LiteUtil.isLiteTopicQueue(lmqName)) { return false; } if (maxOffset <= 0) { LOGGER.warn("unexpected condition, max offset <= 0, {}, {}", lmqName, maxOffset); return false; } long latestStoreTime = this.brokerController.getMessageStore().getMessageStoreTimeStamp(lmqName, 0, maxOffset - 1); long inactiveTime = System.currentTimeMillis() - latestStoreTime; if (inactiveTime < brokerController.getBrokerConfig().getMinLiteTTl()) { return false; } Integer minutes = ttlMap.get(parentTopic); if (null == minutes) { LOGGER.warn("unexpected condition, topic ttl not found. {}", lmqName); return false; } if (minutes <= 0) { return false; } if (hasConsumerLag(lmqName, maxOffset, latestStoreTime, parentTopic)) { return false; } return inactiveTime > minutes * 60 * 1000; } public void deleteLmq(String parentTopic, String lmqName) { try { Set groups = subscriberGroupMap.getOrDefault(parentTopic, Collections.emptySet()); groups.forEach(group -> { String topicAtGroup = lmqName + TOPIC_GROUP_SEPARATOR + group; brokerController.getConsumerOffsetManager().getOffsetTable().remove(topicAtGroup); brokerController.getConsumerOffsetManager().removeConsumerOffset(topicAtGroup); // no iteration brokerController.getPopLiteMessageProcessor().getConsumerOrderInfoManager().remove(lmqName, group); }); brokerController.getMessageStore().deleteTopics(Sets.newHashSet(lmqName)); boolean sharding = brokerName.equals(liteSharding.shardingByLmqName(parentTopic, lmqName)); brokerController.getLiteSubscriptionRegistry().cleanSubscription(lmqName, false); brokerController.getConsumerOffsetManager().getPullOffsetTable().remove( lmqName + TOPIC_GROUP_SEPARATOR + MixAll.TOOLS_CONSUMER_GROUP); LOGGER.info("delete lmq finish. {}, sharding:{}", lmqName, sharding); } catch (Exception e) { LOGGER.error("delete lmq error. {}", lmqName, e); } } /** * Maybe we can check all subscriber groups, but currently consumer lag checking is not performed. * Only inactive time of message sending is considered for TTL expiration. */ public boolean hasConsumerLag(String lmqName, long maxOffset, long latestStoreTime, String parentTopic) { return false; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteCtlListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; public interface LiteCtlListener { void onRegister(String clientId, String group, String lmqName); void onUnregister(String clientId, String group, String lmqName); void onRemoveAll(String clientId, String group); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteEventDispatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class LiteEventDispatcher extends ServiceThread { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); private static final Object PRESENT = new Object(); private static final long CLIENT_INACTIVE_INTERVAL = 10 * 1000; // inactive time when it has unprocessed events private static final long CLIENT_LONG_POLLING_INTERVAL = 30 * 1000 + 5000; // at least a period of long polling as 30s private static final long ACTIVE_CONSUMING_WINDOW = 5000; private static final double LOW_WATER_MARK = 0.2; private static final int BLACKLIST_EXPIRE_SECONDS = 10; private static final int SCAN_LOG_INTERVAL = 10000; private final BrokerController brokerController; private final LiteSubscriptionRegistry liteSubscriptionRegistry; private final AbstractLiteLifecycleManager liteLifecycleManager; private final ConsumerOffsetManager consumerOffsetManager; private ConsumerOrderInfoManager consumerOrderInfoManager; private final ConcurrentMap clientEventMap = new ConcurrentHashMap<>(); private final ConcurrentSkipListSet fullDispatchSet = new ConcurrentSkipListSet<>(COMPARATOR); private final ConcurrentMap fullDispatchMap = new ConcurrentHashMap<>(); // deduplication private final Cache blacklist = CacheBuilder.newBuilder().expireAfterWrite(BLACKLIST_EXPIRE_SECONDS, TimeUnit.SECONDS).build(); private final Random random = ThreadLocalRandom.current(); private long lastLogTime = System.currentTimeMillis(); public LiteEventDispatcher(BrokerController brokerController, LiteSubscriptionRegistry liteSubscriptionRegistry, AbstractLiteLifecycleManager liteLifecycleManager) { this.brokerController = brokerController; this.liteSubscriptionRegistry = liteSubscriptionRegistry; this.liteLifecycleManager = liteLifecycleManager; this.consumerOffsetManager = brokerController.getConsumerOffsetManager(); } public void init() { this.consumerOrderInfoManager = brokerController.getPopLiteMessageProcessor().getConsumerOrderInfoManager(); this.liteSubscriptionRegistry.addListener(new LiteCtlListenerImpl()); } /** * If event mode is enabled, try to dispatch event to one client when message arriving or available. * In most cases, there is only one subscriber for a LMQ under a consumer group, * but also supports multiple clients consuming in share mode. * When group is null, dispatch to all subscribers regardless of their group, * when group is specified, only dispatch to subscribers belonging to this group. *

* If the expected number of subscriptions by each client is small, disabling event mode can be a choice. */ public void dispatch(String group, String lmqName, int queueId, long offset, long msgStoreTime) { if (!this.brokerController.getBrokerConfig().isEnableLiteEventMode()) { return; } if (queueId != 0 || !LiteUtil.isLiteTopicQueue(lmqName)) { return; } doDispatch(group, lmqName, null); } @SuppressWarnings("unchecked") private void doDispatch(String group, String lmqName, String excludeClientId) { if (!this.brokerController.getBrokerConfig().isEnableLiteEventMode()) { return; } Object subscribers = getAllSubscriber(group, lmqName); if (null == subscribers) { return; } if (subscribers instanceof List) { selectAndDispatch(lmqName, (List) subscribers, excludeClientId); } if (subscribers instanceof Map) { Map> map = (Map>) subscribers; map.forEach((key, value) -> selectAndDispatch(lmqName, value, excludeClientId)); } } /** * Select an appropriate client from the client list and try to dispatch the event to it. * If there's only one client, dispatch directly to it. * If there are multiple clients, randomly select one and consider fallback options * Try to avoid dispatching to the excluded one but fallback if no other choice. * * @param clients all clients of one group * @param excludeClientId the client ID to exclude from selection, probably consuming blocked. */ @VisibleForTesting public void selectAndDispatch(String lmqName, List clients, String excludeClientId) { if (!this.brokerController.getBrokerConfig().isEnableLiteEventMode()) { return; } if (CollectionUtils.isEmpty(clients)) { return; } String clientId = null; // the selected one if (clients.size() == 1) { clientId = clients.get(0).clientId; if (brokerController.getBrokerConfig().isEnableLitePopLog() && clientId.equals(excludeClientId)) { LOGGER.info("no others, still dispatch to {}, {}", clientId, lmqName); } if (!tryDispatchToClient(lmqName, clientId, clients.get(0).group)) { clientId = null; } } else { int start = random.nextInt(clients.size()); boolean dispatched = false; List fallbackList = new ArrayList<>(clients.size()); for (int i = 0; i < clients.size(); i++) { int index = (start + i) % clients.size(); clientId = clients.get(index).clientId; if (clientId.equals(excludeClientId)) { fallbackList.add(clients.get(index)); continue; } if (blacklist.getIfPresent(clientId) != null) { fallbackList.add(clients.get(index)); continue; } if (tryDispatchToClient(lmqName, clientId, clients.get(index).group)) { dispatched = true; break; } } if (!dispatched) { clientId = null; for (ClientGroup clientGroup : fallbackList) { if (tryDispatchToClient(lmqName, clientGroup.clientId, clientGroup.group)) { clientId = clientGroup.clientId; break; } } } } if (clientId != null) { this.brokerController.getPopLiteMessageProcessor().getPopLiteLongPollingService() .notifyMessageArriving(clientId, true, 0, clients.get(0).group); } } /** * Try to dispatch an event to a selected client by adding it to the client's event queue. * If the event queue is full, mark a full dispatch for retry later. */ @VisibleForTesting public boolean tryDispatchToClient(String lmqName, String clientId, String group) { ClientEventSet eventSet = clientEventMap.computeIfAbsent(clientId, key -> new ClientEventSet(group)); if (eventSet.offer(lmqName)) { return true; } scheduleFullDispatch(clientId, group, blacklist.getIfPresent(clientId) != null); LOGGER.warn("client event set is full. {}", clientId); return false; } /** * Get an iterator for iterating over events for a specific client. * In lite event mode, returns events from the client's event queue, * or else returns topics from the client's subscription. */ public Iterator getEventIterator(String clientId) { if (this.brokerController.getBrokerConfig().isEnableLiteEventMode()) { return new EventSetIterator(clientEventMap.get(clientId)); } else { LiteSubscription liteSubscription = liteSubscriptionRegistry.getLiteSubscription(clientId); return liteSubscription != null && liteSubscription.getLiteTopicSet() != null ? new LiteSubscriptionIterator(liteSubscription.getTopic(), liteSubscription.getLiteTopicSet().iterator()) : Collections.emptyIterator(); } } /** * Perform a full dispatch for a client which was previously marked for a delayed full dispatch. * This always happens when a client's event queue is full or re-dispatching is needed. * It iterates through all LMQ topics subscribed by the client and dispatches events for those * with available messages. */ public void doFullDispatch(String clientId, String group) { if (!this.brokerController.getBrokerConfig().isEnableLiteEventMode()) { return; } LiteSubscription subscription = liteSubscriptionRegistry.getLiteSubscription(clientId); if (null == subscription || CollectionUtils.isEmpty(subscription.getLiteTopicSet())) { LOGGER.info("client full dispatch, but no subscription. {}", clientId); return; } ClientEventSet eventSet = clientEventMap.computeIfAbsent(clientId, key -> new ClientEventSet(group)); if (eventSet.maybeBlock()) { LOGGER.warn("client may block for a while, wait another period. {}", clientId); scheduleFullDispatch(clientId, group, true); return; } boolean isActiveConsuming = eventSet.isActiveConsuming(); if (!eventSet.isLowWaterMark()) { LOGGER.warn("client event set high water mark, wait another period. {}, {}", clientId, isActiveConsuming); scheduleFullDispatch(clientId, group, !isActiveConsuming); return; } LOGGER.info("client full dispatch, {}, total:{}", clientId, subscription.getLiteTopicSet().size()); int count = 0; for (String lmqName : subscription.getLiteTopicSet()) { long maxOffset = liteLifecycleManager.getMaxOffsetInQueue(lmqName); if (maxOffset <= 0) { continue; } long consumerOffset = consumerOffsetManager.queryOffset(group, lmqName, 0); if (consumerOffset >= maxOffset) { continue; } if (eventSet.offer(lmqName)) { if (count++ % 10 == 0) { brokerController.getPopLiteMessageProcessor().getPopLiteLongPollingService() .notifyMessageArriving(clientId, true, 0, group); } } else { LOGGER.warn("client event set full again, wait another period. {}, {}", clientId, isActiveConsuming); scheduleFullDispatch(clientId, group, !isActiveConsuming); break; } } brokerController.getPopLiteMessageProcessor().getPopLiteLongPollingService() .notifyMessageArriving(clientId, true, 0, group); LOGGER.info("client full dispatch finish. {}, dispatch:{}", clientId, count); } /** * Perform a full dispatch for all clients under a specific group, only invoked by admin for now. */ public void doFullDispatchByGroup(String group) { List clientIds = liteSubscriptionRegistry.getAllClientIdByGroup(group); LOGGER.info("do full dispatch by group, {}, size:{}", group, clientIds.size()); for (String clientId : clientIds) { doFullDispatch(clientId, group); } } public void scheduleFullDispatch(String clientId, String group, boolean reentry) { if (fullDispatchMap.putIfAbsent(clientId, PRESENT) != null) { return; } int randomDelay = reentry ? random.nextInt(25 * 1000) : 0; fullDispatchSet.add(new FullDispatchRequest(clientId, group, brokerController.getBrokerConfig().getLiteEventFullDispatchDelayTime() + randomDelay)); } /** * Get all subscribers for a specific LMQ, with optional group filtering. * To avoid unnecessary comparisons and wrapping, Object is used as the return type here. * This method returns different types based on the subscription scenario: * 1. When there's only one subscriber, return List * 2. When group is specified, return List containing subscribers of that group * 3. When group is null and multiple groups exist, return Map> * mapping each group to its subscribers * * @return Object that can be either List or Map> or null if not found */ @VisibleForTesting public Object getAllSubscriber(String group, String lmqName) { Set observers = liteSubscriptionRegistry.getSubscriber(lmqName); if (null == observers || observers.isEmpty()) { return null; } if (observers.size() == 1) { if (null == group || group.equals(observers.iterator().next().group)) { return new ArrayList<>(observers); } return null; } if (group != null) { List result = new ArrayList<>(4); for (ClientGroup ele : observers) { if (group.equals(ele.group)) { result.add(ele); } } return !result.isEmpty() ? result : null; } Map> group2Clients = new HashMap<>(4); for (ClientGroup ele : observers) { group2Clients.computeIfAbsent(ele.group, k -> new ArrayList<>(2)).add(ele); } return group2Clients; } /** * Get the last access time of a client's event set. * * @param clientId the client id * @return the last access time in milliseconds, or -1 if client not found */ public long getClientLastAccessTime(String clientId) { ClientEventSet eventSet = clientEventMap.get(clientId); if (eventSet != null) { return eventSet.lastAccessTime; } return -1; } @Override public String getServiceName() { if (brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + LiteEventDispatcher.class.getSimpleName(); } return LiteEventDispatcher.class.getSimpleName(); } @Override public void run() { while (!this.isStopped()) { long checkInterval = brokerController.getBrokerConfig().getLiteEventCheckInterval(); this.waitForRunning(checkInterval); try { scan(); } catch (Exception e) { LOGGER.error("LiteEventDispatcher-scan error.", e); } } } /** * Due to the event pre-allocation mechanism, it is necessary to perform * two main tasks to check inactive event queues and do full dispatch to reduce potential delivery latency. * 1. Check client event set for inactive clients and re-dispatches their events * 2. Process delayed full dispatch requests that are ready to be executed */ public void scan() { boolean needLog = System.currentTimeMillis() - lastLogTime > SCAN_LOG_INTERVAL; // 1. check all client event set if (needLog) { LOGGER.info("Check client event set. size:{}", clientEventMap.size()); lastLogTime = System.currentTimeMillis(); } Iterator> iterator = clientEventMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); ClientEventSet eventSet = entry.getValue(); if (!eventSet.maybeBlock()) { continue; } String clientId = entry.getKey(); LOGGER.warn("remove inactive client and re-dispatch. {}, {}", clientId, eventSet.events.size()); iterator.remove(); blacklist.put(clientId, PRESENT); String event; while ((event = eventSet.poll()) != null) { doDispatch(eventSet.group, event, clientId); // may still dispatch to current client } } // 2. perform full dispatch if (needLog) { LOGGER.info("Begin to trigger full dispatch. size:{}, mapSize:{}", fullDispatchSet.size(), fullDispatchMap.size()); lastLogTime = System.currentTimeMillis(); } FullDispatchRequest request; while ((request = fullDispatchSet.pollFirst()) != null) { if (request.timestamp > System.currentTimeMillis()) { fullDispatchSet.add(request); break; } fullDispatchMap.remove(request.clientId); doFullDispatch(request.clientId, request.group); } } public int getEventMapSize() { return clientEventMap.size(); } /** * We use dual data structure to maintain the event queue for each client * and ensure event deduplication to avoid duplicate events, although it * has a bit more memory usage than a single concurrent set. */ class ClientEventSet { private final BlockingQueue events; private final ConcurrentMap map = new ConcurrentHashMap<>(); private final String group; private volatile long lastAccessTime = System.currentTimeMillis(); private volatile long lastConsumeTime = System.currentTimeMillis(); public ClientEventSet(String group) { this.group = group; events = new LinkedBlockingQueue<>(LiteMetadataUtil.getMaxClientEventCount(group, brokerController)); } // return false if and only if the queue is full, has race condition with poll(), but no side effect. public boolean offer(String event) { if (events.remainingCapacity() == 0) { return false; } boolean rst; if (map.putIfAbsent(event, PRESENT) == null) { rst = events.offer(event); if (!rst) { map.remove(event); } } else { rst = true; } return rst; } public String poll() { lastAccessTime = System.currentTimeMillis(); String event = events.poll(); if (event != null) { map.remove(event); lastConsumeTime = System.currentTimeMillis(); } return event; } public boolean maybeBlock() { long inactiveTime = System.currentTimeMillis() - lastAccessTime; return inactiveTime > CLIENT_LONG_POLLING_INTERVAL || !events.isEmpty() && inactiveTime > CLIENT_INACTIVE_INTERVAL; } public boolean isLowWaterMark() { int used = events.size(); return (double) used / (used + events.remainingCapacity()) < LOW_WATER_MARK; } public boolean isActiveConsuming() { return System.currentTimeMillis() - lastAccessTime < ACTIVE_CONSUMING_WINDOW; } public int size() { return events.size(); } } class LiteCtlListenerImpl implements LiteCtlListener { @Override public void onRegister(String clientId, String group, String lmqName) { if (liteLifecycleManager.isLmqExist(lmqName)) { doDispatch(group, lmqName, null); } } @Override public void onUnregister(String clientId, String group, String lmqName) { } /** * Mostly triggered when client channel closed, ensure that lite subscriptions is cleared before. */ @Override public void onRemoveAll(String clientId, String group) { ClientEventSet eventSet = clientEventMap.remove(clientId); if (null == eventSet) { return; } LOGGER.warn("Maybe client offline. {}", clientId); String event; while ((event = eventSet.poll()) != null) { doDispatch(eventSet.group, event, clientId); } } } static class EventSetIterator implements Iterator { private final ClientEventSet eventSet; public EventSetIterator(ClientEventSet eventSet) { this.eventSet = eventSet; } @Override public boolean hasNext() { return eventSet != null && !eventSet.events.isEmpty(); } @Override public String next() { return eventSet.poll(); } } static class LiteSubscriptionIterator implements Iterator { private final Iterator iterator; private final String parentTopic; public LiteSubscriptionIterator(String parentTopic, Iterator iterator) { this.parentTopic = parentTopic; this.iterator = iterator; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public String next() { return iterator.next(); } } static class FullDispatchRequest { private final String clientId; private final String group; private final long timestamp; public FullDispatchRequest(String clientId, String group, long delayMillis) { this.clientId = clientId; this.group = group; this.timestamp = System.currentTimeMillis() + delayMillis; } } // no need to compare group static final Comparator COMPARATOR = (r1, r2) -> { if (null == r1 || null == r2 || null == r1.clientId || null == r2.clientId) { return 0; } if (r1.clientId.equals(r2.clientId)) { return 0; } int ret = Long.compare(r1.timestamp, r2.timestamp); if (ret != 0) { return ret; } return r1.clientId.compareTo(r2.clientId); }; } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteLifecycleManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; public class LiteLifecycleManager extends AbstractLiteLifecycleManager { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); public LiteLifecycleManager(BrokerController brokerController, LiteSharding liteSharding) { super(brokerController, liteSharding); } @Override public long getMaxOffsetInQueue(String lmqName) { ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(lmqName, 0); return consumeQueue != null ? consumeQueue.getMaxOffsetInQueue() : 0L; } @Override public List collectByParentTopic(String parentTopic) { if (StringUtils.isEmpty(parentTopic)) { return Collections.emptyList(); } List resultList = new ArrayList<>(); Iterator>> iterator = messageStore.getQueueStore().getConsumeQueueTable().entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); if (LiteUtil.belongsTo(entry.getKey(), parentTopic)) { resultList.add(entry.getKey()); } } return resultList; } @Override public List> collectExpiredLiteTopic() { List> lmqToDelete = new ArrayList<>(); Iterator>> iterator = messageStore.getQueueStore().getConsumeQueueTable().entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); String lmqName = entry.getKey(); String parentTopic = LiteUtil.getParentTopic(lmqName); if (null == parentTopic) { continue; } Map map = entry.getValue(); if (map.size() != 1 || null == map.get(0)) { LOGGER.warn("unexpected lmq count. {}", lmqName); continue; } if (isLiteTopicExpired(parentTopic, entry.getKey(), map.get(0).getMaxOffsetInQueue())) { lmqToDelete.add(new Pair<>(parentTopic, lmqName)); } } return lmqToDelete; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteMetadataUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class LiteMetadataUtil { public static boolean isConsumeEnable(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return false; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); return null != groupConfig && groupConfig.isConsumeEnable(); } public static boolean isLiteMessageType(String parentTopic, BrokerController brokerController) { if (null == parentTopic || null == brokerController) { return false; } TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(parentTopic); return topicConfig != null && TopicMessageType.LITE.equals(topicConfig.getTopicMessageType()); } public static boolean isLiteGroupType(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return false; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); return null != groupConfig && groupConfig.getLiteBindTopic() != null; } public static String getLiteBindTopic(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return null; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); return null != groupConfig ? groupConfig.getLiteBindTopic() : null; } public static boolean isSubLiteExclusive(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return false; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); return null != groupConfig && groupConfig.isLiteSubExclusive(); } public static boolean isResetOffsetInExclusiveMode(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return false; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); return null != groupConfig && groupConfig.isResetOffsetInExclusiveMode(); } public static boolean isResetOffsetOnUnsubscribe(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return false; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); return null != groupConfig && groupConfig.isResetOffsetOnUnsubscribe(); } public static int getMaxClientEventCount(String group, BrokerController brokerController) { if (null == group || null == brokerController) { return -1; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); if (null == groupConfig || groupConfig.getMaxClientEventCount() <= 0) { return brokerController.getBrokerConfig().getMaxClientEventCount(); } return groupConfig.getMaxClientEventCount(); } public static Map getTopicTtlMap(BrokerController brokerController) { if (null == brokerController) { return Collections.emptyMap(); } ConcurrentMap topicConfigTable = brokerController.getTopicConfigManager().getTopicConfigTable(); return topicConfigTable.entrySet().stream() .filter(entry -> entry.getValue().getTopicMessageType().equals(TopicMessageType.LITE)) .collect(Collectors.toMap( entry -> entry.getKey(), entry -> entry.getValue().getLiteTopicExpiration() )); } public static Map> getSubscriberGroupMap(BrokerController brokerController) { if (null == brokerController) { return Collections.emptyMap(); } ConcurrentMap groupTable = brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable(); return groupTable.entrySet().stream() .filter(entry -> entry.getValue().getLiteBindTopic() != null) .collect(Collectors.groupingBy( entry -> entry.getValue().getLiteBindTopic(), Collectors.mapping(Map.Entry::getKey, Collectors.toSet()) )); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteQuotaException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; public class LiteQuotaException extends RuntimeException { public LiteQuotaException(String message) { super(message); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteSharding.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; public interface LiteSharding { String shardingByLmqName(String parentTopic, String lmqName); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteShardingImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import com.google.common.hash.Hashing; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.topic.TopicRouteInfoManager; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageQueue; import java.util.List; public class LiteShardingImpl implements LiteSharding { private final BrokerController brokerController; private final TopicRouteInfoManager topicRouteInfoManager; public LiteShardingImpl(BrokerController brokerController, TopicRouteInfoManager topicRouteInfoManager) { this.brokerController = brokerController; this.topicRouteInfoManager = topicRouteInfoManager; } @Override public String shardingByLmqName(String parentTopic, String lmqName) { TopicPublishInfo topicPublishInfo = topicRouteInfoManager.tryToFindTopicPublishInfo(parentTopic); if (topicPublishInfo == null) { // if topic not exist, return current broker return brokerController.getBrokerConfig().getBrokerName(); } List writeQueues = topicPublishInfo.getMessageQueueList(); if (CollectionUtils.isEmpty(writeQueues)) { return brokerController.getBrokerConfig().getBrokerName(); } String liteTopic = LiteUtil.getLiteTopic(lmqName); if (StringUtils.isEmpty(liteTopic)) { return brokerController.getBrokerConfig().getBrokerName(); } int bucket = Hashing.consistentHash(liteTopic.hashCode(), writeQueues.size()); MessageQueue targetQueue = writeQueues.get(bucket); return targetQueue.getBrokerName(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteSubscriptionRegistry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import io.netty.channel.Channel; import java.util.List; import java.util.Set; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.OffsetOption; public interface LiteSubscriptionRegistry { void updateClientChannel(String clientId, Channel channel); LiteSubscription getLiteSubscription(String clientId); int getActiveSubscriptionNum(); void addPartialSubscription(String clientId, String group, String topic, Set lmqNameSet, OffsetOption offsetOption); void removePartialSubscription(String clientId, String group, String topic, Set lmqNameSet); void addCompleteSubscription(String clientId, String group, String topic, Set newLmqNameSet, long version); void removeCompleteSubscription(String clientId); void addListener(LiteCtlListener listener); Set getSubscriber(String lmqName); List getAllClientIdByGroup(String group); void cleanSubscription(String lmqName, boolean notifyClient); void start(); void shutdown(); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/LiteSubscriptionRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import com.google.common.annotations.VisibleForTesting; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.lite.OffsetOption; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; public class LiteSubscriptionRegistryImpl extends ServiceThread implements LiteSubscriptionRegistry { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); protected final ConcurrentMap clientChannels = new ConcurrentHashMap<>(); protected final ConcurrentMap client2Subscription = new ConcurrentHashMap<>(); protected final ConcurrentMap> liteTopic2Group = new ConcurrentHashMap<>(); private final List listeners = new ArrayList<>(); private final BrokerController brokerController; private final AbstractLiteLifecycleManager liteLifecycleManager; public LiteSubscriptionRegistryImpl(BrokerController brokerController, AbstractLiteLifecycleManager liteLifecycleManager) { this.brokerController = brokerController; this.liteLifecycleManager = liteLifecycleManager; } // Number of active liteTopic references. // [(client1, liteTopic1), (client2, liteTopic1)] counts as two active references. protected final AtomicInteger activeNum = new AtomicInteger(0); @Override public void updateClientChannel(String clientId, Channel channel) { clientChannels.put(clientId, channel); } @Override public void addPartialSubscription(String clientId, String group, String topic, Set lmqNameSet, OffsetOption offsetOption) { long maxCount = brokerController.getBrokerConfig().getMaxLiteSubscriptionCount(); if (getActiveSubscriptionNum() >= maxCount) { // No need to check existence, if reach here, it must be new. throw new LiteQuotaException("lite subscription quota exceeded " + maxCount); } LiteSubscription thisSub = getOrCreateLiteSubscription(clientId, group, topic); // Utilize existing string object final ClientGroup clientGroup = new ClientGroup(clientId, thisSub.getGroup()); for (String lmqName : lmqNameSet) { if (!liteLifecycleManager.isSubscriptionActive(topic, lmqName)) { continue; } thisSub.addLiteTopic(lmqName); // First remove the old subscription if (LiteMetadataUtil.isSubLiteExclusive(group, brokerController)) { excludeClientByLmqName(clientId, group, lmqName); } resetOffset(lmqName, group, clientId, offsetOption); addTopicGroup(clientGroup, lmqName); } } @Override public void removePartialSubscription(String clientId, String group, String topic, Set lmqNameSet) { LiteSubscription thisSub = getOrCreateLiteSubscription(clientId, group, topic); ClientGroup clientGroup = new ClientGroup(clientId, thisSub.getGroup()); boolean isResetOffsetOnUnsubscribe = LiteMetadataUtil.isResetOffsetOnUnsubscribe(group, brokerController); for (String lmqName : lmqNameSet) { thisSub.removeLiteTopic(lmqName); removeTopicGroup(clientGroup, lmqName, isResetOffsetOnUnsubscribe); } } @Override public void addCompleteSubscription(String clientId, String group, String topic, Set lmqNameAll, long version) { Set lmqNameNew = lmqNameAll.stream() .filter(lmqName -> liteLifecycleManager.isSubscriptionActive(topic, lmqName)) .collect(Collectors.toSet()); LiteSubscription thisSub = getOrCreateLiteSubscription(clientId, group, topic); Set lmqNamePrev = thisSub.getLiteTopicSet(); // Find topics to remove (in current set but not in new set) Set lmqNameRemove = lmqNamePrev.stream() .filter(lmqName -> !lmqNameNew.contains(lmqName)) .collect(Collectors.toSet()); ClientGroup clientGroup = new ClientGroup(clientId, thisSub.getGroup()); lmqNameRemove.forEach(lmqName -> { thisSub.removeLiteTopic(lmqName); removeTopicGroup(clientGroup, lmqName, false); }); lmqNameNew.forEach(lmqName -> { thisSub.addLiteTopic(lmqName); addTopicGroup(clientGroup, lmqName); }); } @Override public void removeCompleteSubscription(String clientId) { clientChannels.remove(clientId); LiteSubscription thisSub = client2Subscription.remove(clientId); if (thisSub == null) { return; } LOGGER.info("removeCompleteSubscription, topic:{}, group:{}, clientId:{}", thisSub.getTopic(), thisSub.getGroup(), clientId); ClientGroup clientGroup = new ClientGroup(clientId, thisSub.getGroup()); thisSub.getLiteTopicSet().forEach(lmqName -> { removeTopicGroup(clientGroup, lmqName, false); }); for (LiteCtlListener listener : listeners) { listener.onRemoveAll(clientId, thisSub.getGroup()); } } @Override public void addListener(LiteCtlListener listener) { listeners.add(listener); } @Override public Set getSubscriber(String lmqName) { return liteTopic2Group.get(lmqName); } /** * Cleans up subscription for the given LMQ name. * Removes all related client subscriptions and notifies listeners. * * @param lmqName the LMQ name to clean up */ @Override public void cleanSubscription(String lmqName, boolean notifyClient) { Set topicGroupSet = liteTopic2Group.remove(lmqName); if (CollectionUtils.isEmpty(topicGroupSet)) { return; } for (ClientGroup topicGroup : topicGroupSet) { LiteSubscription liteSubscription = client2Subscription.get(topicGroup.clientId); if (liteSubscription == null) { continue; } if (liteSubscription.removeLiteTopic(lmqName)) { if (notifyClient) { notifyUnsubscribeLite(topicGroup.clientId, topicGroup.group, lmqName); } activeNum.decrementAndGet(); } } } protected void addTopicGroup(ClientGroup clientGroup, String lmqName) { Set topicGroupSet = liteTopic2Group .computeIfAbsent(lmqName, k -> ConcurrentHashMap.newKeySet()); if (topicGroupSet.add(clientGroup)) { activeNum.incrementAndGet(); for (LiteCtlListener listener : listeners) { listener.onRegister(clientGroup.clientId, clientGroup.group, lmqName); } } } protected void removeTopicGroup(ClientGroup clientGroup, String lmqName, boolean resetOffset) { Set topicGroupSet = liteTopic2Group.get(lmqName); if (topicGroupSet == null) { return; } if (topicGroupSet.remove(clientGroup)) { activeNum.decrementAndGet(); for (LiteCtlListener listener : listeners) { listener.onUnregister(clientGroup.clientId, clientGroup.group, lmqName); } if (resetOffset) { resetOffset(lmqName, clientGroup.group, clientGroup.clientId, new OffsetOption(OffsetOption.Type.POLICY, OffsetOption.POLICY_MIN_VALUE)); } } if (topicGroupSet.isEmpty()) { liteTopic2Group.remove(lmqName); } } /** * Remove clients that subscribe to the same liteTopic under the same group */ protected void excludeClientByLmqName(String newClientId, String group, String lmqName) { Set clientSet = liteTopic2Group.get(lmqName); if (CollectionUtils.isEmpty(clientSet)) { return; } List toRemove = clientSet.stream() .filter(clientGroup -> Objects.equals(group, clientGroup.group)) .collect(Collectors.toList()); toRemove.forEach(clientGroup -> { LiteSubscription liteSubscription = client2Subscription.get(clientGroup.clientId); if (liteSubscription != null) { liteSubscription.removeLiteTopic(lmqName); } notifyUnsubscribeLite(clientGroup.clientId, clientGroup.group, lmqName); boolean resetOffset = LiteMetadataUtil.isResetOffsetInExclusiveMode(group, brokerController); LOGGER.info("excludeClientByLmqName group:{}, lmqName:{}, resetOffset:{}, clientId:{} -> {}", group, lmqName, resetOffset, clientGroup.clientId, newClientId); removeTopicGroup(clientGroup, lmqName, resetOffset); }); } /** * Notify the client to remove the liteTopic subscription from its local memory */ private void notifyUnsubscribeLite(String clientId, String group, String lmqName) { String topic = LiteUtil.getParentTopic(lmqName); String liteTopic = LiteUtil.getLiteTopic(lmqName); Channel channel = clientChannels.get(clientId); if (channel == null) { LOGGER.warn("notifyUnsubscribeLite but channel is null, liteTopic:{}, group:{}, topic:{}, clientId:{},", liteTopic, group, topic, clientId); return; } NotifyUnsubscribeLiteRequestHeader header = new NotifyUnsubscribeLiteRequestHeader(); header.setClientId(clientId); header.setConsumerGroup(group); header.setLiteTopic(liteTopic); brokerController.getBroker2Client().notifyUnsubscribeLite(channel, header); LOGGER.info("notifyUnsubscribeLite liteTopic:{}, group:{}, topic:{}, clientId:{}", liteTopic, group, topic, clientId); } @Override public LiteSubscription getLiteSubscription(String clientId) { return client2Subscription.get(clientId); } @Override public int getActiveSubscriptionNum() { return activeNum.get(); } @Override public List getAllClientIdByGroup(String group) { return client2Subscription.entrySet().stream() .filter(entry -> entry.getValue().getGroup().equals(group)) .map(Map.Entry::getKey) .collect(Collectors.toList()); } protected void resetOffset(String lmqName, String group, String clientId, OffsetOption offsetOption) { if (null == offsetOption) { return; } Long targetOffset = null; long currentOffset = brokerController.getConsumerOffsetManager().queryOffset(group, lmqName, 0); switch (offsetOption.getType()) { case POLICY: if (OffsetOption.POLICY_MIN_VALUE == offsetOption.getValue()) { targetOffset = 0L; } else if (OffsetOption.POLICY_MAX_VALUE == offsetOption.getValue()) { targetOffset = liteLifecycleManager.getMaxOffsetInQueue(lmqName); } break; case OFFSET: targetOffset = offsetOption.getValue(); break; case TAIL_N: if (currentOffset >= 0) { // only when consumer offset exists targetOffset = Math.max(0L, currentOffset - offsetOption.getValue()); } break; case TIMESTAMP: // timestamp option is disabled silently for now break; } LOGGER.info("try to reset lite offset. {}, {}, {}, {}, current:{}, target:{}", group, lmqName, clientId, offsetOption, currentOffset, targetOffset); if (targetOffset != null && currentOffset != targetOffset) { brokerController.getConsumerOffsetManager().assignResetOffset(lmqName, group, 0, targetOffset); brokerController.getPopLiteMessageProcessor().getConsumerOrderInfoManager().remove(lmqName, group); } } private LiteSubscription getOrCreateLiteSubscription(String clientId, String group, String topic) { LiteSubscription curLiteSubscription = ConcurrentHashMapUtils.computeIfAbsent(client2Subscription, clientId, k -> new LiteSubscription().setGroup(group).setTopic(topic)); assert curLiteSubscription != null; return curLiteSubscription; } @Override public void run() { LOGGER.info("Start checking lite subscription."); while (!this.isStopped()) { long checkInterval = brokerController.getBrokerConfig().getLiteSubscriptionCheckInterval(); this.waitForRunning(checkInterval); long checkTimeout = brokerController.getBrokerConfig().getLiteSubscriptionCheckTimeoutMills(); cleanupExpiredSubscriptions(checkTimeout); } LOGGER.info("End checking lite subscription."); } /** * Cleans up expired client subscriptions based on the provided timeout. * * @param checkTimeout the timeout in milliseconds to determine if a subscription is expired */ @VisibleForTesting protected void cleanupExpiredSubscriptions(long checkTimeout) { // Step 1: Find expired clients and their subscription information long currentTime = System.currentTimeMillis(); List> expiredEntries = client2Subscription.entrySet() .stream() .filter(entry -> currentTime - entry.getValue().getUpdateTime() > checkTimeout) .collect(Collectors.toList()); // Step 2: Remove expired clients and their subscriptions expiredEntries.forEach(expiredEntry -> { String clientId = expiredEntry.getKey(); LiteSubscription liteSubscription = expiredEntry.getValue(); String group = liteSubscription.getGroup(); String topic = liteSubscription.getTopic(); removeCompleteSubscription(clientId); LOGGER.info("Remove expired LiteSubscription, topic: {}, group: {}, clientId: {}, timeout: {}ms, expired: {}ms", topic, group, clientId, checkTimeout, System.currentTimeMillis() - liteSubscription.getUpdateTime()); }); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/lite/RocksDBLiteLifecycleManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore; import org.apache.rocketmq.tieredstore.TieredMessageStore; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; public class RocksDBLiteLifecycleManager extends AbstractLiteLifecycleManager { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); private Map maxCqOffsetTable; public RocksDBLiteLifecycleManager(BrokerController brokerController, LiteSharding liteSharding) { super(brokerController, liteSharding); } @Override public long getMaxOffsetInQueue(String lmqName) { return maxCqOffsetTable.getOrDefault(lmqName + "-0", -1L) + 1; } @Override public List collectByParentTopic(String parentTopic) { if (StringUtils.isEmpty(parentTopic)) { return Collections.emptyList(); } List resultList = new ArrayList<>(); Iterator> iterator = maxCqOffsetTable.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); String queueAndQid = entry.getKey(); String lmqName = queueAndQid.substring(0, queueAndQid.lastIndexOf("-")); if (LiteUtil.belongsTo(lmqName, parentTopic)) { resultList.add(lmqName); } } return resultList; } @Override public List> collectExpiredLiteTopic() { List> lmqToDelete = new ArrayList<>(); Iterator> iterator = maxCqOffsetTable.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); String queueAndQid = entry.getKey(); String lmqName = queueAndQid.substring(0, queueAndQid.lastIndexOf("-")); String parentTopic = LiteUtil.getParentTopic(lmqName); if (null == parentTopic) { continue; } if (isLiteTopicExpired(parentTopic, lmqName, entry.getValue() + 1)) { lmqToDelete.add(new Pair<>(parentTopic, lmqName)); } } return lmqToDelete; } @Override public void init() { super.init(); if (messageStore instanceof TieredMessageStore) { // only support TieredMessageStore plugin messageStore = ((TieredMessageStore) messageStore).getDefaultStore(); } if (!(messageStore instanceof RocksDBMessageStore)) { LOGGER.warn("init failed, not a RocksDB store. {}", messageStore.getClass()); return; // startup with lite feature disabled } try { RocksDBConsumeQueueStore queueStore = (RocksDBConsumeQueueStore) messageStore.getQueueStore(); RocksDBConsumeQueueOffsetTable cqOffsetTable = (RocksDBConsumeQueueOffsetTable) FieldUtils.readField( FieldUtils.getField(RocksDBConsumeQueueStore.class, "rocksDBConsumeQueueOffsetTable", true), queueStore); @SuppressWarnings("unchecked") ConcurrentMap innerMaxCqOffsetTable = (ConcurrentMap) FieldUtils.readField( FieldUtils.getField(RocksDBConsumeQueueOffsetTable.class, "topicQueueMaxCqOffset", true), cqOffsetTable); maxCqOffsetTable = Collections.unmodifiableMap(innerMaxCqOffsetTable); } catch (Exception e) { LOGGER.error("LiteLifecycleManager-init error", e); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.loadbalance; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.body.SetMessageRequestModeRequestBody; public class MessageRequestModeManager extends ConfigManager { private transient BrokerController brokerController; private ConcurrentHashMap> messageRequestModeMap = new ConcurrentHashMap<>(); public MessageRequestModeManager() { // empty construct for decode } public MessageRequestModeManager(BrokerController brokerController) { this.brokerController = brokerController; } public void setMessageRequestMode(String topic, String consumerGroup, SetMessageRequestModeRequestBody requestBody) { ConcurrentHashMap consumerGroup2ModeMap = messageRequestModeMap.get(topic); if (consumerGroup2ModeMap == null) { consumerGroup2ModeMap = new ConcurrentHashMap<>(); ConcurrentHashMap pre = messageRequestModeMap.putIfAbsent(topic, consumerGroup2ModeMap); if (pre != null) { consumerGroup2ModeMap = pre; } } consumerGroup2ModeMap.put(consumerGroup, requestBody); } public SetMessageRequestModeRequestBody getMessageRequestMode(String topic, String consumerGroup) { ConcurrentHashMap consumerGroup2ModeMap = messageRequestModeMap.get(topic); if (consumerGroup2ModeMap != null) { return consumerGroup2ModeMap.get(consumerGroup); } return null; } public ConcurrentHashMap> getMessageRequestModeMap() { return this.messageRequestModeMap; } public void setMessageRequestModeMap(ConcurrentHashMap> messageRequestModeMap) { this.messageRequestModeMap = messageRequestModeMap; } @Override public String encode() { return this.encode(false); } @Override public String configFilePath() { return BrokerPathConfigHelper.getMessageRequestModePath(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { MessageRequestModeManager obj = RemotingSerializable.fromJson(jsonString, MessageRequestModeManager.class); if (obj != null) { this.messageRequestModeMap = obj.messageRequestModeMap; } } } @Override public String encode(boolean prettyFormat) { return RemotingSerializable.toJson(this, prettyFormat); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/LmqPullRequestHoldService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class LmqPullRequestHoldService extends PullRequestHoldService { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public LmqPullRequestHoldService(BrokerController brokerController) { super(brokerController); } @Override public String getServiceName() { if (brokerController != null && brokerController.getBrokerConfig().isInBrokerContainer()) { return this.brokerController.getBrokerIdentity().getIdentifier() + LmqPullRequestHoldService.class.getSimpleName(); } return LmqPullRequestHoldService.class.getSimpleName(); } @Override public void checkHoldRequest() { for (String key : pullRequestTable.keySet()) { int idx = key.lastIndexOf(TOPIC_QUEUEID_SEPARATOR); if (idx <= 0 || idx >= key.length() - 1) { pullRequestTable.remove(key); continue; } String topic = key.substring(0, idx); int queueId = Integer.parseInt(key.substring(idx + 1)); try { final long offset = brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); this.notifyMessageArriving(topic, queueId, offset); } catch (Throwable e) { LOGGER.error("check hold request failed. topic={}, queueId={}", topic, queueId, e); } if (MixAll.isLmq(topic)) { ManyPullRequest mpr = pullRequestTable.get(key); if (mpr == null || mpr.getPullRequestList() == null || mpr.getPullRequestList().isEmpty()) { pullRequestTable.remove(key); } } } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/ManyPullRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import java.util.ArrayList; import java.util.List; public class ManyPullRequest { private final ArrayList pullRequestList = new ArrayList<>(); public synchronized void addPullRequest(final PullRequest pullRequest) { this.pullRequestList.add(pullRequest); } public synchronized void addPullRequest(final List many) { this.pullRequestList.addAll(many); } public synchronized List cloneListAndClear() { if (!this.pullRequestList.isEmpty()) { List result = (ArrayList) this.pullRequestList.clone(); this.pullRequestList.clear(); return result; } return null; } public ArrayList getPullRequestList() { return pullRequestList; } public synchronized boolean isEmpty() { return this.pullRequestList.isEmpty(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotificationRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import io.netty.channel.Channel; public class NotificationRequest { private RemotingCommand remotingCommand; private Channel channel; private long expired; private AtomicBoolean complete = new AtomicBoolean(false); public NotificationRequest(RemotingCommand remotingCommand, Channel channel, long expired) { this.channel = channel; this.remotingCommand = remotingCommand; this.expired = expired; } public Channel getChannel() { return channel; } public RemotingCommand getRemotingCommand() { return remotingCommand; } public boolean isTimeout() { return System.currentTimeMillis() > (expired - 500); } public boolean complete() { return complete.compareAndSet(false, true); } @Override public String toString() { return remotingCommand.toString(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import java.util.Map; import org.apache.rocketmq.broker.lite.LiteEventDispatcher; import org.apache.rocketmq.broker.processor.NotificationProcessor; import org.apache.rocketmq.broker.processor.PopMessageProcessor; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.store.MessageArrivingListener; public class NotifyMessageArrivingListener implements MessageArrivingListener { private final PullRequestHoldService pullRequestHoldService; private final PopMessageProcessor popMessageProcessor; private final NotificationProcessor notificationProcessor; private final LiteEventDispatcher liteEventDispatcher; public NotifyMessageArrivingListener(final PullRequestHoldService pullRequestHoldService, final PopMessageProcessor popMessageProcessor, final NotificationProcessor notificationProcessor, final LiteEventDispatcher liteEventDispatcher) { this.pullRequestHoldService = pullRequestHoldService; this.popMessageProcessor = popMessageProcessor; this.notificationProcessor = notificationProcessor; this.liteEventDispatcher = liteEventDispatcher; } @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { if (LiteUtil.isLiteTopicQueue(topic)) { this.liteEventDispatcher.dispatch(null, topic, queueId, logicOffset, msgStoreTime); return; } this.pullRequestHoldService.notifyMessageArriving( topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties); this.popMessageProcessor.notifyMessageArriving( topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties); this.notificationProcessor.notifyMessageArriving( topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import org.apache.rocketmq.remoting.protocol.header.NotificationRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; public class PollingHeader { private final String consumerGroup; private final String topic; private final int queueId; private final long bornTime; private final long pollTime; public PollingHeader(PopMessageRequestHeader requestHeader) { this.consumerGroup = requestHeader.getConsumerGroup(); this.topic = requestHeader.getTopic(); this.queueId = requestHeader.getQueueId(); this.bornTime = requestHeader.getBornTime(); this.pollTime = requestHeader.getPollTime(); } public PollingHeader(NotificationRequestHeader requestHeader) { this.consumerGroup = requestHeader.getConsumerGroup(); this.topic = requestHeader.getTopic(); this.queueId = requestHeader.getQueueId(); this.bornTime = requestHeader.getBornTime(); this.pollTime = requestHeader.getPollTime(); } public String getConsumerGroup() { return consumerGroup; } public String getTopic() { return topic; } public int getQueueId() { return queueId; } public long getBornTime() { return bornTime; } public long getPollTime() { return pollTime; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; public enum PollingResult { POLLING_SUC, POLLING_FULL, POLLING_TIMEOUT, NOT_POLLING; } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopCommandCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import org.apache.rocketmq.broker.metrics.ConsumerLagCalculator; import org.apache.rocketmq.remoting.CommandCallback; public class PopCommandCallback implements CommandCallback { private final BiConsumer> biConsumer; private final ConsumerLagCalculator.ProcessGroupInfo info; private final CompletableFuture future; public PopCommandCallback( BiConsumer> biConsumer, ConsumerLagCalculator.ProcessGroupInfo info, CompletableFuture future) { this.biConsumer = biConsumer; this.info = info; this.future = future; } @Override public void accept() { biConsumer.accept(info, future); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLiteLongPollingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import io.netty.channel.ChannelHandlerContext; import java.util.Map; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import static org.apache.rocketmq.broker.longpolling.PollingResult.NOT_POLLING; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_FULL; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_SUC; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_TIMEOUT; /** * Long polling service specifically designed for lite consumption. * Stores pending requests in memory using clientId as the key instead of topic@cid@qid. * Notification and resource checking mechanisms are identical to those in PopLongPollingService. */ public class PopLiteLongPollingService extends ServiceThread { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); private final BrokerController brokerController; private final NettyRequestProcessor processor; private final ConcurrentLinkedHashMap> pollingMap; private long lastCleanTime = 0; private final AtomicLong totalPollingNum = new AtomicLong(0); private final boolean notifyLast; public PopLiteLongPollingService(BrokerController brokerController, NettyRequestProcessor processor, boolean notifyLast) { this.brokerController = brokerController; this.processor = processor; this.pollingMap = new ConcurrentLinkedHashMap.Builder>() .maximumWeightedCapacity(this.brokerController.getBrokerConfig().getPopPollingMapSize()).build(); this.notifyLast = notifyLast; } @Override public String getServiceName() { if (brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + PopLiteLongPollingService.class.getSimpleName(); } return PopLiteLongPollingService.class.getSimpleName(); } @Override public void run() { int i = 0; while (!this.stopped) { try { this.waitForRunning(20); i++; if (pollingMap.isEmpty()) { continue; } long tmpTotalPollingNum = 0; for (Map.Entry> entry : pollingMap.entrySet()) { String key = entry.getKey(); ConcurrentSkipListSet popQ = entry.getValue(); if (popQ == null) { continue; } PopRequest first; do { first = popQ.pollFirst(); if (first == null) { break; } if (!first.isTimeout()) { if (popQ.add(first)) { break; } else { LOGGER.info("lite polling, add back again but failed. {}", first); } } if (brokerController.getBrokerConfig().isEnablePopLog()) { LOGGER.info("timeout , wakeUp lite polling : {}", first); } totalPollingNum.decrementAndGet(); wakeUp(first); } while (true); if (i >= 100) { long tmpPollingNum = popQ.size(); tmpTotalPollingNum = tmpTotalPollingNum + tmpPollingNum; if (tmpPollingNum > 20) { LOGGER.info("lite polling queue {} , size={} ", key, tmpPollingNum); } } } if (i >= 100) { LOGGER.info("litePollingMapSize={}, tmpTotalSize={}, atomicTotalSize={}, diffSize={}", pollingMap.size(), tmpTotalPollingNum, totalPollingNum.get(), Math.abs(totalPollingNum.get() - tmpTotalPollingNum)); totalPollingNum.set(tmpTotalPollingNum); i = 0; } // clean unused if (lastCleanTime == 0 || System.currentTimeMillis() - lastCleanTime > 3 * 60 * 1000) { cleanUnusedResource(); } } catch (Throwable e) { LOGGER.error("checkLitePolling error", e); } } // clean all; try { for (Map.Entry> entry : pollingMap.entrySet()) { ConcurrentSkipListSet popQ = entry.getValue(); PopRequest first; while ((first = popQ.pollFirst()) != null) { wakeUp(first); } } } catch (Throwable ignored) { } } public boolean notifyMessageArriving(final String clientId, boolean force, long msgStoreTime, String group) { String pollingKey = getPollingKey(clientId, group); ConcurrentSkipListSet remotingCommands = pollingMap.get(pollingKey); if (remotingCommands == null || remotingCommands.isEmpty()) { return false; } PopRequest popRequest = pollRemotingCommands(remotingCommands); if (popRequest == null) { return false; } if (brokerController.getBrokerConfig().isEnableLitePopLog()) { LOGGER.info("notify lite polling, wakeUp: {}", popRequest); } return wakeUp(popRequest); } public boolean wakeUp(final PopRequest request) { if (request == null || !request.complete()) { return false; } if (!request.getCtx().channel().isActive()) { return false; } Runnable run = () -> { try { final RemotingCommand response = processor.processRequest(request.getCtx(), request.getRemotingCommand()); if (response != null) { response.setOpaque(request.getRemotingCommand().getOpaque()); response.markResponseType(); NettyRemotingAbstract.writeResponse(request.getChannel(), request.getRemotingCommand(), response, future -> { if (!future.isSuccess()) { LOGGER.error("ProcessRequestWrapper response to {} failed", request.getChannel().remoteAddress(), future.cause()); LOGGER.error(request.toString()); LOGGER.error(response.toString()); } }, brokerController.getBrokerMetricsManager().getRemotingMetricsManager()); } } catch (Exception e) { LOGGER.error("ExecuteRequestWhenWakeup error.", e); } }; this.brokerController.getPullMessageExecutor().submit( new RequestTask(run, request.getChannel(), request.getRemotingCommand())); return true; } public PollingResult polling(final ChannelHandlerContext ctx, RemotingCommand remotingCommand, long bornTime, long pollTime, String clientId, String group) { if (pollTime <= 0 || this.isStopped()) { return NOT_POLLING; } long expired = bornTime + pollTime; final PopRequest request = new PopRequest(remotingCommand, ctx, expired, null, null); boolean isFull = totalPollingNum.get() >= this.brokerController.getBrokerConfig().getMaxPopPollingSize(); if (isFull) { LOGGER.info("lite polling {}, result POLLING_FULL, total:{}", remotingCommand, totalPollingNum.get()); return POLLING_FULL; } boolean isTimeout = request.isTimeout(); if (isTimeout) { if (brokerController.getBrokerConfig().isEnablePopLog()) { LOGGER.info("lite polling {}, result POLLING_TIMEOUT", remotingCommand); } return POLLING_TIMEOUT; } String pollingKey = getPollingKey(clientId, group); ConcurrentSkipListSet queue = pollingMap.get(pollingKey); if (queue == null) { queue = new ConcurrentSkipListSet<>(PopRequest.COMPARATOR); ConcurrentSkipListSet old = pollingMap.putIfAbsent(pollingKey, queue); if (old != null) { queue = old; } } else { // check size int size = queue.size(); if (size > brokerController.getBrokerConfig().getPopPollingSize()) { LOGGER.info("lite polling {}, result POLLING_FULL, singleSize:{}", remotingCommand, size); return POLLING_FULL; } } if (queue.add(request)) { remotingCommand.setSuspended(true); totalPollingNum.incrementAndGet(); if (brokerController.getBrokerConfig().isEnableLitePopLog()) { LOGGER.info("lite polling {}, result POLLING_SUC", remotingCommand); } return POLLING_SUC; } else { LOGGER.info("lite polling {}, result POLLING_FULL, add fail, {}", request, queue); return POLLING_FULL; } } private void cleanUnusedResource() { try { pollingMap.entrySet().removeIf(entry -> { if (CollectionUtils.isEmpty(entry.getValue())) { LOGGER.info("clean polling structure of {}", entry.getKey()); // see getPollingKey() return true; } return false; }); } catch (Throwable ignored) { } lastCleanTime = System.currentTimeMillis(); } private PopRequest pollRemotingCommands(ConcurrentSkipListSet remotingCommands) { if (remotingCommands == null || remotingCommands.isEmpty()) { return null; } PopRequest popRequest; do { if (notifyLast) { popRequest = remotingCommands.pollLast(); } else { popRequest = remotingCommands.pollFirst(); } if (popRequest != null) { totalPollingNum.decrementAndGet(); } } while (popRequest != null && !popRequest.getChannel().isActive()); return popRequest; } // Assume that clientId is unique, so we use it as the key for now. private String getPollingKey(String clientId, String group) { return clientId; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import io.netty.channel.ChannelHandlerContext; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.CommandCallback; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.MessageFilter; import static org.apache.rocketmq.broker.longpolling.PollingResult.NOT_POLLING; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_FULL; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_SUC; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_TIMEOUT; public class PopLongPollingService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final BrokerController brokerController; private final NettyRequestProcessor processor; private final Cache> topicCidMap; private final Cache> pollingMap; private long lastCleanTime = 0; private final AtomicLong totalPollingNum = new AtomicLong(0); private final boolean notifyLast; public PopLongPollingService(BrokerController brokerController, NettyRequestProcessor processor, boolean notifyLast) { this.brokerController = brokerController; this.processor = processor; // 100000 topic default, 100000 lru topic + cid + qid this.topicCidMap = Caffeine.newBuilder() .maximumSize(this.brokerController.getBrokerConfig().getPopPollingMapSize() * 2L) .expireAfterAccess(this.brokerController.getBrokerConfig().getPopPollingMapExpireTimeSeconds(), TimeUnit.SECONDS) .build(); this.pollingMap = Caffeine.newBuilder() .maximumSize(this.brokerController.getBrokerConfig().getPopPollingMapSize()) .expireAfterAccess(this.brokerController.getBrokerConfig().getPopPollingMapExpireTimeSeconds(), TimeUnit.SECONDS) .build(); this.notifyLast = notifyLast; } @Override public String getServiceName() { if (brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + PopLongPollingService.class.getSimpleName(); } return PopLongPollingService.class.getSimpleName(); } @Override public void run() { int i = 0; while (!this.stopped) { try { this.waitForRunning(20); i++; if (pollingMap.estimatedSize() == 0) { continue; } long tmpTotalPollingNum = 0; for (Map.Entry> entry : pollingMap.asMap().entrySet()) { String key = entry.getKey(); ConcurrentSkipListSet popQ = entry.getValue(); if (popQ == null) { continue; } PopRequest first; do { first = popQ.pollFirst(); if (first == null) { break; } if (!first.isTimeout()) { if (popQ.add(first)) { break; } else { POP_LOGGER.info("polling, add fail again: {}", first); } } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("timeout , wakeUp polling : {}", first); } totalPollingNum.decrementAndGet(); wakeUp(first); } while (true); if (i >= 100) { long tmpPollingNum = popQ.size(); tmpTotalPollingNum = tmpTotalPollingNum + tmpPollingNum; if (tmpPollingNum > 100) { POP_LOGGER.info("polling queue {} , size={} ", key, tmpPollingNum); } } } if (i >= 100) { POP_LOGGER.info("pollingMapSize={},tmpTotalSize={},atomicTotalSize={},diffSize={}", pollingMap.estimatedSize(), tmpTotalPollingNum, totalPollingNum.get(), Math.abs(totalPollingNum.get() - tmpTotalPollingNum)); totalPollingNum.set(tmpTotalPollingNum); i = 0; } // clean unused if (lastCleanTime == 0 || System.currentTimeMillis() - lastCleanTime > 5 * 60 * 1000) { cleanUnusedResource(); } } catch (Throwable e) { POP_LOGGER.error("checkPolling error", e); } } // clean all; try { for (Map.Entry> entry : pollingMap.asMap().entrySet()) { ConcurrentSkipListSet popQ = entry.getValue(); PopRequest first; while ((first = popQ.pollFirst()) != null) { wakeUp(first); } } } catch (Throwable e) { } } public void notifyMessageArrivingWithRetryTopic(final String topic, final int queueId) { this.notifyMessageArrivingWithRetryTopic(topic, queueId, -1L, null, 0L, null, null); } public void notifyMessageArrivingWithRetryTopic(final String topic, final int queueId, long offset, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { if (NamespaceUtil.isRetryTopic(topic)) { notifyMessageArrivingFromRetry(topic, queueId, tagsCode, msgStoreTime, filterBitMap, properties); } else { notifyMessageArriving(topic, queueId, offset, tagsCode, msgStoreTime, filterBitMap, properties); } } private void notifyMessageArrivingFromRetry(String topic, int queueId, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { String prefix = MixAll.RETRY_GROUP_TOPIC_PREFIX; String originGroup = properties.get(MessageConst.PROPERTY_ORIGIN_GROUP); // In the case of pop consumption, there is no long polling hanging on the retry topic, so the wake-up is skipped. if (StringUtils.isBlank(originGroup)) { return; } // %RETRY%GROUP is used for pull mode, so the wake-up is skipped. int originTopicStartIndex = prefix.length() + originGroup.length() + 1; if (topic.length() <= originTopicStartIndex) { return; } String originTopic = topic.substring(originTopicStartIndex); if (queueId >= 0) { notifyMessageArriving(originTopic, -1, originGroup, true, tagsCode, msgStoreTime, filterBitMap, properties); } notifyMessageArriving(originTopic, queueId, originGroup, true, tagsCode, msgStoreTime, filterBitMap, properties); } public void notifyMessageArriving(final String topic, final int queueId, long offset, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { ConcurrentHashMap cids = topicCidMap.getIfPresent(topic); if (cids == null) { return; } long interval = brokerController.getBrokerConfig().getPopLongPollingForceNotifyInterval(); boolean force = interval > 0L && offset % interval == 0L; for (Map.Entry cid : cids.entrySet()) { if (queueId >= 0) { notifyMessageArriving(topic, -1, cid.getKey(), force, tagsCode, msgStoreTime, filterBitMap, properties); } notifyMessageArriving(topic, queueId, cid.getKey(), force, tagsCode, msgStoreTime, filterBitMap, properties); } } public boolean notifyMessageArriving(final String topic, final int queueId, final String cid, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { return notifyMessageArriving(topic, queueId, cid, false, tagsCode, msgStoreTime, filterBitMap, properties, null); } public boolean notifyMessageArriving(final String topic, final int queueId, final String cid, boolean force, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { return notifyMessageArriving(topic, queueId, cid, force, tagsCode, msgStoreTime, filterBitMap, properties, null); } public boolean notifyMessageArriving(final String topic, final int queueId, final String cid, boolean force, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties, CommandCallback callback) { ConcurrentSkipListSet remotingCommands = pollingMap.getIfPresent(KeyBuilder.buildPollingKey(topic, cid, queueId)); if (remotingCommands == null || remotingCommands.isEmpty()) { return false; } PopRequest popRequest = pollRemotingCommands(remotingCommands); if (popRequest == null) { return false; } if (!force && popRequest.getMessageFilter() != null && popRequest.getSubscriptionData() != null) { boolean match = popRequest.getMessageFilter().isMatchedByConsumeQueue(tagsCode, new ConsumeQueueExt.CqExtUnit(tagsCode, msgStoreTime, filterBitMap)); if (match && properties != null) { match = popRequest.getMessageFilter().isMatchedByCommitLog(null, properties); } if (!match) { remotingCommands.add(popRequest); totalPollingNum.incrementAndGet(); return false; } } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("lock release, new msg arrive, wakeUp: {}", popRequest); } return wakeUp(popRequest, callback); } public boolean wakeUp(final PopRequest request) { return wakeUp(request, null); } public boolean wakeUp(final PopRequest request, CommandCallback callback) { if (request == null || !request.complete()) { return false; } if (callback != null && request.getRemotingCommand() != null) { if (request.getRemotingCommand().getCallbackList() == null) { request.getRemotingCommand().setCallbackList(new ArrayList<>()); } request.getRemotingCommand().getCallbackList().add(callback); } if (!request.getCtx().channel().isActive()) { return false; } Runnable run = () -> { try { final RemotingCommand response = processor.processRequest(request.getCtx(), request.getRemotingCommand()); if (response != null) { response.setOpaque(request.getRemotingCommand().getOpaque()); response.markResponseType(); NettyRemotingAbstract.writeResponse(request.getChannel(), request.getRemotingCommand(), response, future -> { if (!future.isSuccess()) { POP_LOGGER.error("ProcessRequestWrapper response to {} failed", request.getChannel().remoteAddress(), future.cause()); POP_LOGGER.error(request.toString()); POP_LOGGER.error(response.toString()); } }, brokerController.getBrokerMetricsManager().getRemotingMetricsManager()); } } catch (Exception e1) { POP_LOGGER.error("ExecuteRequestWhenWakeup run", e1); } }; this.brokerController.getPullMessageExecutor().submit( new RequestTask(run, request.getChannel(), request.getRemotingCommand())); return true; } /** * @param ctx * @param remotingCommand * @param requestHeader * @return */ public PollingResult polling(final ChannelHandlerContext ctx, RemotingCommand remotingCommand, final PollingHeader requestHeader) { return this.polling(ctx, remotingCommand, requestHeader, null, null); } public PollingResult polling(final ChannelHandlerContext ctx, RemotingCommand remotingCommand, final PollingHeader requestHeader, SubscriptionData subscriptionData, MessageFilter messageFilter) { if (requestHeader.getPollTime() <= 0 || this.isStopped()) { return NOT_POLLING; } ConcurrentHashMap cids = topicCidMap.get(requestHeader.getTopic(), key -> new ConcurrentHashMap<>()); cids.putIfAbsent(requestHeader.getConsumerGroup(), Byte.MIN_VALUE); long expired = requestHeader.getBornTime() + requestHeader.getPollTime(); final PopRequest request = new PopRequest(remotingCommand, ctx, expired, subscriptionData, messageFilter); boolean isFull = totalPollingNum.get() >= this.brokerController.getBrokerConfig().getMaxPopPollingSize(); if (isFull) { POP_LOGGER.info("polling {}, result POLLING_FULL, total:{}", remotingCommand, totalPollingNum.get()); return POLLING_FULL; } boolean isTimeout = request.isTimeout(); if (isTimeout) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("polling {}, result POLLING_TIMEOUT", remotingCommand); } return POLLING_TIMEOUT; } String key = KeyBuilder.buildPollingKey(requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueId()); ConcurrentSkipListSet queue = pollingMap.get(key, k -> new ConcurrentSkipListSet<>(PopRequest.COMPARATOR)); int size = queue.size(); if (size > brokerController.getBrokerConfig().getPopPollingSize()) { POP_LOGGER.info("polling {}, result POLLING_FULL, singleSize:{}", remotingCommand, size); return POLLING_FULL; } if (queue.add(request)) { remotingCommand.setSuspended(true); totalPollingNum.incrementAndGet(); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("polling {}, result POLLING_SUC", remotingCommand); } return POLLING_SUC; } else { POP_LOGGER.info("polling {}, result POLLING_FULL, add fail, {}", request, queue); return POLLING_FULL; } } public Cache> getPollingMap() { return pollingMap; } public Cache> getTopicCidMap() { return topicCidMap; } private void cleanUnusedResource() { try { { Iterator>> topicCidMapIter = topicCidMap.asMap().entrySet().iterator(); while (topicCidMapIter.hasNext()) { Map.Entry> entry = topicCidMapIter.next(); String topic = entry.getKey(); if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) { POP_LOGGER.info("remove nonexistent topic {} in topicCidMap!", topic); topicCidMapIter.remove(); continue; } Iterator> cidMapIter = entry.getValue().entrySet().iterator(); while (cidMapIter.hasNext()) { Map.Entry cidEntry = cidMapIter.next(); String cid = cidEntry.getKey(); if (!brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(cid)) { POP_LOGGER.info("remove nonexistent subscription group {} of topic {} in topicCidMap!", cid, topic); cidMapIter.remove(); } } } } { Iterator>> pollingMapIter = pollingMap.asMap().entrySet().iterator(); while (pollingMapIter.hasNext()) { Map.Entry> entry = pollingMapIter.next(); if (entry.getKey() == null) { continue; } String[] keyArray = entry.getKey().split(PopAckConstants.SPLIT); if (keyArray.length != 3) { continue; } String topic = keyArray[0]; String cid = keyArray[1]; if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) { POP_LOGGER.info("remove nonexistent topic {} in pollingMap!", topic); pollingMapIter.remove(); continue; } if (!brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(cid)) { POP_LOGGER.info("remove nonexistent subscription group {} of topic {} in pollingMap!", cid, topic); pollingMapIter.remove(); } } } } catch (Throwable e) { POP_LOGGER.error("cleanUnusedResource", e); } lastCleanTime = System.currentTimeMillis(); } private PopRequest pollRemotingCommands(ConcurrentSkipListSet remotingCommands) { if (remotingCommands == null || remotingCommands.isEmpty()) { return null; } PopRequest popRequest; do { if (notifyLast) { popRequest = remotingCommands.pollLast(); } else { popRequest = remotingCommands.pollFirst(); } totalPollingNum.decrementAndGet(); } while (popRequest != null && !popRequest.getChannel().isActive()); return popRequest; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.Comparator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.MessageFilter; public class PopRequest { private static final AtomicLong COUNTER = new AtomicLong(Long.MIN_VALUE); private final RemotingCommand remotingCommand; private final ChannelHandlerContext ctx; private final AtomicBoolean complete = new AtomicBoolean(false); private final long op = COUNTER.getAndIncrement(); private final long expired; private final SubscriptionData subscriptionData; private final MessageFilter messageFilter; public PopRequest(RemotingCommand remotingCommand, ChannelHandlerContext ctx, long expired, SubscriptionData subscriptionData, MessageFilter messageFilter) { this.ctx = ctx; this.remotingCommand = remotingCommand; this.expired = expired; this.subscriptionData = subscriptionData; this.messageFilter = messageFilter; } public Channel getChannel() { return ctx.channel(); } public ChannelHandlerContext getCtx() { return ctx; } public RemotingCommand getRemotingCommand() { return remotingCommand; } public boolean isTimeout() { return System.currentTimeMillis() > (expired - 50); } public boolean complete() { return complete.compareAndSet(false, true); } public long getExpired() { return expired; } public SubscriptionData getSubscriptionData() { return subscriptionData; } public MessageFilter getMessageFilter() { return messageFilter; } @Override public String toString() { final StringBuilder sb = new StringBuilder("PopRequest{"); sb.append("cmd=").append(remotingCommand); sb.append(", ctx=").append(ctx); sb.append(", expired=").append(expired); sb.append(", complete=").append(complete); sb.append(", op=").append(op); sb.append('}'); return sb.toString(); } public static final Comparator COMPARATOR = (o1, o2) -> { int ret = (int) (o1.getExpired() - o2.getExpired()); if (ret != 0) { return ret; } ret = (int) (o1.op - o2.op); if (ret != 0) { return ret; } return -1; }; } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import io.netty.channel.Channel; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.MessageFilter; public class PullRequest { private final RemotingCommand requestCommand; private final Channel clientChannel; private final long timeoutMillis; private final long suspendTimestamp; private final long pullFromThisOffset; private final SubscriptionData subscriptionData; private final MessageFilter messageFilter; public PullRequest(RemotingCommand requestCommand, Channel clientChannel, long timeoutMillis, long suspendTimestamp, long pullFromThisOffset, SubscriptionData subscriptionData, MessageFilter messageFilter) { this.requestCommand = requestCommand; this.clientChannel = clientChannel; this.timeoutMillis = timeoutMillis; this.suspendTimestamp = suspendTimestamp; this.pullFromThisOffset = pullFromThisOffset; this.subscriptionData = subscriptionData; this.messageFilter = messageFilter; } public RemotingCommand getRequestCommand() { return requestCommand; } public Channel getClientChannel() { return clientChannel; } public long getTimeoutMillis() { return timeoutMillis; } public long getSuspendTimestamp() { return suspendTimestamp; } public long getPullFromThisOffset() { return pullFromThisOffset; } public SubscriptionData getSubscriptionData() { return subscriptionData; } public MessageFilter getMessageFilter() { return messageFilter; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.exception.ConsumeQueueException; public class PullRequestHoldService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); protected static final String TOPIC_QUEUEID_SEPARATOR = "@"; protected final BrokerController brokerController; private final SystemClock systemClock = new SystemClock(); protected ConcurrentMap pullRequestTable = new ConcurrentHashMap<>(1024); public PullRequestHoldService(final BrokerController brokerController) { this.brokerController = brokerController; } public void suspendPullRequest(final String topic, final int queueId, final PullRequest pullRequest) { String key = this.buildKey(topic, queueId); ManyPullRequest mpr = this.pullRequestTable.get(key); if (null == mpr) { mpr = new ManyPullRequest(); ManyPullRequest prev = this.pullRequestTable.putIfAbsent(key, mpr); if (prev != null) { mpr = prev; } } pullRequest.getRequestCommand().setSuspended(true); mpr.addPullRequest(pullRequest); } private String buildKey(final String topic, final int queueId) { StringBuilder sb = new StringBuilder(topic.length() + 5); sb.append(topic); sb.append(TOPIC_QUEUEID_SEPARATOR); sb.append(queueId); return sb.toString(); } @Override public void run() { log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { if (this.brokerController.getBrokerConfig().isLongPollingEnable()) { this.waitForRunning(5 * 1000); } else { this.waitForRunning(this.brokerController.getBrokerConfig().getShortPollingTimeMills()); } long beginLockTimestamp = this.systemClock.now(); this.checkHoldRequest(); long costTime = this.systemClock.now() - beginLockTimestamp; if (costTime > 5 * 1000) { log.warn("PullRequestHoldService: check hold pull request cost {}ms", costTime); } } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info("{} service end", this.getServiceName()); } @Override public String getServiceName() { if (brokerController != null && brokerController.getBrokerConfig().isInBrokerContainer()) { return this.brokerController.getBrokerIdentity().getIdentifier() + PullRequestHoldService.class.getSimpleName(); } return PullRequestHoldService.class.getSimpleName(); } protected void checkHoldRequest() { for (String key : this.pullRequestTable.keySet()) { String[] kArray = key.split(TOPIC_QUEUEID_SEPARATOR); if (2 == kArray.length) { String topic = kArray[0]; int queueId = Integer.parseInt(kArray[1]); try { final long offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); this.notifyMessageArriving(topic, queueId, offset); } catch (Throwable e) { log.error( "PullRequestHoldService: failed to check hold request failed, topic={}, queueId={}", topic, queueId, e); } } } } public void notifyMessageArriving(final String topic, final int queueId, final long maxOffset) { notifyMessageArriving(topic, queueId, maxOffset, null, 0, null, null); } public void notifyMessageArriving(final String topic, final int queueId, final long maxOffset, final Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { String key = this.buildKey(topic, queueId); ManyPullRequest mpr = this.pullRequestTable.get(key); if (mpr != null) { List requestList = mpr.cloneListAndClear(); if (requestList != null) { List replayList = new ArrayList<>(); for (PullRequest request : requestList) { long newestOffset = maxOffset; if (newestOffset <= request.getPullFromThisOffset()) { try { newestOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); } catch (ConsumeQueueException e) { log.error("Failed tp get max offset in queue", e); continue; } } if (newestOffset > request.getPullFromThisOffset()) { boolean match = request.getMessageFilter().isMatchedByConsumeQueue(tagsCode, new ConsumeQueueExt.CqExtUnit(tagsCode, msgStoreTime, filterBitMap)); // match by bit map, need eval again when properties is not null. if (match && properties != null) { match = request.getMessageFilter().isMatchedByCommitLog(null, properties); } if (match) { try { this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(), request.getRequestCommand()); } catch (Throwable e) { log.error( "PullRequestHoldService#notifyMessageArriving: failed to execute request when " + "message matched, topic={}, queueId={}", topic, queueId, e); } continue; } } if (System.currentTimeMillis() >= (request.getSuspendTimestamp() + request.getTimeoutMillis())) { try { this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(), request.getRequestCommand()); } catch (Throwable e) { log.error( "PullRequestHoldService#notifyMessageArriving: failed to execute request when time's " + "up, topic={}, queueId={}", topic, queueId, e); } continue; } replayList.add(request); } if (!replayList.isEmpty()) { mpr.addPullRequest(replayList); } } } } public void notifyMasterOnline() { for (ManyPullRequest mpr : this.pullRequestTable.values()) { if (mpr == null || mpr.isEmpty()) { continue; } for (PullRequest request : mpr.cloneListAndClear()) { try { log.info("notify master online, wakeup {} {}", request.getClientChannel(), request.getRequestCommand()); this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(), request.getRequestCommand()); } catch (Throwable e) { log.error("execute request when master online failed.", e); } } } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsConstant.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; public class BrokerMetricsConstant { public static final String OPEN_TELEMETRY_METER_NAME = "broker-meter"; public static final String GAUGE_PROCESSOR_WATERMARK = "rocketmq_processor_watermark"; public static final String GAUGE_BROKER_PERMISSION = "rocketmq_broker_permission"; public static final String GAUGE_TOPIC_NUM = "rocketmq_topic_number"; public static final String GAUGE_CONSUMER_GROUP_NUM = "rocketmq_consumer_group_number"; public static final String COUNTER_MESSAGES_IN_TOTAL = "rocketmq_messages_in_total"; public static final String COUNTER_MESSAGES_OUT_TOTAL = "rocketmq_messages_out_total"; public static final String COUNTER_THROUGHPUT_IN_TOTAL = "rocketmq_throughput_in_total"; public static final String COUNTER_THROUGHPUT_OUT_TOTAL = "rocketmq_throughput_out_total"; public static final String HISTOGRAM_MESSAGE_SIZE = "rocketmq_message_size"; public static final String HISTOGRAM_TOPIC_CREATE_EXECUTE_TIME = "rocketmq_topic_create_execution_time"; public static final String HISTOGRAM_CONSUMER_GROUP_CREATE_EXECUTE_TIME = "rocketmq_consumer_group_create_execution_time"; public static final String GAUGE_PRODUCER_CONNECTIONS = "rocketmq_producer_connections"; public static final String GAUGE_CONSUMER_CONNECTIONS = "rocketmq_consumer_connections"; public static final String GAUGE_CONSUMER_LAG_MESSAGES = "rocketmq_consumer_lag_messages"; public static final String GAUGE_CONSUMER_LAG_LATENCY = "rocketmq_consumer_lag_latency"; public static final String GAUGE_CONSUMER_INFLIGHT_MESSAGES = "rocketmq_consumer_inflight_messages"; public static final String GAUGE_CONSUMER_QUEUEING_LATENCY = "rocketmq_consumer_queueing_latency"; public static final String GAUGE_CONSUMER_READY_MESSAGES = "rocketmq_consumer_ready_messages"; public static final String COUNTER_CONSUMER_SEND_TO_DLQ_MESSAGES_TOTAL = "rocketmq_send_to_dlq_messages_total"; public static final String COUNTER_COMMIT_MESSAGES_TOTAL = "rocketmq_commit_messages_total"; public static final String COUNTER_ROLLBACK_MESSAGES_TOTAL = "rocketmq_rollback_messages_total"; public static final String HISTOGRAM_FINISH_MSG_LATENCY = "rocketmq_finish_message_latency"; public static final String GAUGE_HALF_MESSAGES = "rocketmq_half_messages"; public static final String LABEL_CLUSTER_NAME = "cluster"; public static final String LABEL_NODE_TYPE = "node_type"; public static final String NODE_TYPE_BROKER = "broker"; public static final String LABEL_NODE_ID = "node_id"; public static final String LABEL_AGGREGATION = "aggregation"; public static final String AGGREGATION_DELTA = "delta"; public static final String LABEL_PROCESSOR = "processor"; public static final String LABEL_TOPIC = "topic"; public static final String LABEL_INVOCATION_STATUS = "invocation_status"; public static final String LABEL_IS_RETRY = "is_retry"; public static final String LABEL_IS_SYSTEM = "is_system"; public static final String LABEL_CONSUMER_GROUP = "consumer_group"; public static final String LABEL_MESSAGE_TYPE = "message_type"; public static final String LABEL_LANGUAGE = "language"; public static final String LABEL_VERSION = "version"; public static final String LABEL_CONSUME_MODE = "consume_mode"; } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import com.google.common.base.Splitter; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.ViewBuilder; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import io.opentelemetry.sdk.resources.Resource; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.metrics.MetricsExporterType; import org.apache.rocketmq.common.metrics.NopLongCounter; import org.apache.rocketmq.common.metrics.NopLongHistogram; import org.apache.rocketmq.common.metrics.NopObservableLongGauge; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant; import org.slf4j.bridge.SLF4JBridgeHandler; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.AGGREGATION_DELTA; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_COMMIT_MESSAGES_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_CONSUMER_SEND_TO_DLQ_MESSAGES_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_MESSAGES_IN_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_MESSAGES_OUT_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_ROLLBACK_MESSAGES_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_THROUGHPUT_IN_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_THROUGHPUT_OUT_TOTAL; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_TOPIC_NUM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_GROUP_NUM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_BROKER_PERMISSION; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_CONNECTIONS; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_INFLIGHT_MESSAGES; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_LAG_LATENCY; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_LAG_MESSAGES; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_QUEUEING_LATENCY; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_CONSUMER_READY_MESSAGES; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_HALF_MESSAGES; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_PROCESSOR_WATERMARK; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_PRODUCER_CONNECTIONS; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.HISTOGRAM_FINISH_MSG_LATENCY; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.HISTOGRAM_MESSAGE_SIZE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.HISTOGRAM_TOPIC_CREATE_EXECUTE_TIME; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.HISTOGRAM_CONSUMER_GROUP_CREATE_EXECUTE_TIME; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_AGGREGATION; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CLUSTER_NAME; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUME_MODE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_RETRY; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_LANGUAGE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_NODE_ID; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_NODE_TYPE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_PROCESSOR; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_VERSION; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.NODE_TYPE_BROKER; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.OPEN_TELEMETRY_METER_NAME; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_PROTOCOL_TYPE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.PROTOCOL_TYPE_REMOTING; public class BrokerMetricsManager { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerConfig brokerConfig; private final MessageStore messageStore; private final BrokerController brokerController; private final ConsumerLagCalculator consumerLagCalculator; private final LiteConsumerLagCalculator liteConsumerLagCalculator; private final Map labelMap = new HashMap<>(); private OtlpGrpcMetricExporter metricExporter; private PeriodicMetricReader periodicMetricReader; private PrometheusHttpServer prometheusHttpServer; private MetricExporter loggingMetricExporter; private Meter brokerMeter; private Supplier attributesBuilderSupplier = Attributes::builder; // broker stats metrics private ObservableLongGauge processorWatermark = new NopObservableLongGauge(); private ObservableLongGauge brokerPermission = new NopObservableLongGauge(); private ObservableLongGauge topicNum = new NopObservableLongGauge(); private ObservableLongGauge consumerGroupNum = new NopObservableLongGauge(); // request metrics private LongCounter messagesInTotal = new NopLongCounter(); private LongCounter messagesOutTotal = new NopLongCounter(); private LongCounter throughputInTotal = new NopLongCounter(); private LongCounter throughputOutTotal = new NopLongCounter(); private LongHistogram messageSize = new NopLongHistogram(); private LongHistogram topicCreateExecuteTime = new NopLongHistogram(); private LongHistogram consumerGroupCreateExecuteTime = new NopLongHistogram(); // client connection metrics private ObservableLongGauge producerConnection = new NopObservableLongGauge(); private ObservableLongGauge consumerConnection = new NopObservableLongGauge(); // Lag metrics private ObservableLongGauge consumerLagMessages = new NopObservableLongGauge(); private ObservableLongGauge consumerLagLatency = new NopObservableLongGauge(); private ObservableLongGauge consumerInflightMessages = new NopObservableLongGauge(); private ObservableLongGauge consumerQueueingLatency = new NopObservableLongGauge(); private ObservableLongGauge consumerReadyMessages = new NopObservableLongGauge(); private LongCounter sendToDlqMessages = new NopLongCounter(); private ObservableLongGauge halfMessages = new NopObservableLongGauge(); private LongCounter commitMessagesTotal = new NopLongCounter(); private LongCounter rollBackMessagesTotal = new NopLongCounter(); private LongHistogram transactionFinishLatency = new NopLongHistogram(); private final RemotingMetricsManager remotingMetricsManager; private final PopMetricsManager popMetricsManager; @SuppressWarnings("DoubleBraceInitialization") public static final List SYSTEM_GROUP_PREFIX_LIST = new ArrayList() { { add(MixAll.CID_RMQ_SYS_PREFIX.toLowerCase()); } }; public BrokerMetricsManager(BrokerController brokerController) { this.brokerController = brokerController; brokerConfig = brokerController.getBrokerConfig(); this.messageStore = brokerController.getMessageStore(); this.consumerLagCalculator = new ConsumerLagCalculator(brokerController); this.remotingMetricsManager = new RemotingMetricsManager(); this.popMetricsManager = new PopMetricsManager(); this.liteConsumerLagCalculator = new LiteConsumerLagCalculator(brokerController); init(); } public AttributesBuilder newAttributesBuilder() { AttributesBuilder attributesBuilder; if (attributesBuilderSupplier == null) { attributesBuilderSupplier = Attributes::builder; } attributesBuilder = attributesBuilderSupplier.get(); labelMap.forEach(attributesBuilder::put); return attributesBuilder; } private Attributes buildLagAttributes(ConsumerLagCalculator.BaseCalculateResult result) { AttributesBuilder attributesBuilder = newAttributesBuilder(); attributesBuilder.put(LABEL_CONSUMER_GROUP, result.group); attributesBuilder.put(LABEL_TOPIC, result.topic); attributesBuilder.put(LABEL_IS_RETRY, result.isRetry); attributesBuilder.put(LABEL_IS_SYSTEM, isSystem(result.topic, result.group)); return attributesBuilder.build(); } public static boolean isRetryOrDlqTopic(String topic) { if (StringUtils.isBlank(topic)) { return false; } return topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) || topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX); } public static boolean isSystemGroup(String group) { if (StringUtils.isBlank(group)) { return false; } String groupInLowerCase = group.toLowerCase(); for (String prefix : SYSTEM_GROUP_PREFIX_LIST) { if (groupInLowerCase.startsWith(prefix)) { return true; } } return false; } public static boolean isSystem(String topic, String group) { return TopicValidator.isSystemTopic(topic) || isSystemGroup(group); } public static TopicMessageType getMessageType(SendMessageRequestHeader requestHeader) { Map properties = MessageDecoder.string2messageProperties(requestHeader.getProperties()); String traFlag = properties.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); TopicMessageType topicMessageType = TopicMessageType.NORMAL; if (Boolean.parseBoolean(traFlag)) { topicMessageType = TopicMessageType.TRANSACTION; } else if (properties.containsKey(MessageConst.PROPERTY_SHARDING_KEY)) { topicMessageType = TopicMessageType.FIFO; } else if (properties.get("__STARTDELIVERTIME") != null || properties.get(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null || properties.get(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null || properties.get(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null || properties.get(MessageConst.PROPERTY_TIMER_DELAY_MS) != null) { topicMessageType = TopicMessageType.DELAY; } return topicMessageType; } public Meter getBrokerMeter() { return brokerMeter; } // Getter methods for metrics variables public LongCounter getMessagesInTotal() { return messagesInTotal; } public LongCounter getMessagesOutTotal() { return messagesOutTotal; } public LongCounter getThroughputInTotal() { return throughputInTotal; } public LongCounter getThroughputOutTotal() { return throughputOutTotal; } public LongHistogram getMessageSize() { return messageSize; } public LongCounter getSendToDlqMessages() { return sendToDlqMessages; } public LongCounter getCommitMessagesTotal() { return commitMessagesTotal; } public LongCounter getRollBackMessagesTotal() { return rollBackMessagesTotal; } public LongHistogram getTransactionFinishLatency() { return transactionFinishLatency; } public LongHistogram getTopicCreateExecuteTime() { return topicCreateExecuteTime; } public LongHistogram getConsumerGroupCreateExecuteTime() { return consumerGroupCreateExecuteTime; } // Setter method for testing purposes public void setAttributesBuilderSupplier(Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; } private boolean checkConfig() { if (brokerConfig == null) { return false; } MetricsExporterType exporterType = brokerConfig.getMetricsExporterType(); if (!exporterType.isEnable()) { return false; } switch (exporterType) { case OTLP_GRPC: return StringUtils.isNotBlank(brokerConfig.getMetricsGrpcExporterTarget()); case PROM: return true; case LOG: return true; } return false; } private void init() { MetricsExporterType metricsExporterType = brokerConfig.getMetricsExporterType(); if (metricsExporterType == MetricsExporterType.DISABLE) { return; } if (!checkConfig()) { LOGGER.error("check metrics config failed, will not export metrics"); return; } String labels = brokerConfig.getMetricsLabel(); if (StringUtils.isNotBlank(labels)) { List kvPairs = Splitter.on(',').omitEmptyStrings().splitToList(labels); for (String item : kvPairs) { String[] split = item.split(":"); if (split.length != 2) { LOGGER.warn("metricsLabel is not valid: {}", labels); continue; } labelMap.put(split[0], split[1]); } } if (brokerConfig.isMetricsInDelta()) { labelMap.put(LABEL_AGGREGATION, AGGREGATION_DELTA); } labelMap.put(LABEL_NODE_TYPE, NODE_TYPE_BROKER); labelMap.put(LABEL_CLUSTER_NAME, brokerConfig.getBrokerClusterName()); labelMap.put(LABEL_NODE_ID, brokerConfig.getBrokerName()); SdkMeterProviderBuilder providerBuilder = SdkMeterProvider.builder() .setResource(Resource.empty()); if (metricsExporterType == MetricsExporterType.OTLP_GRPC) { String endpoint = brokerConfig.getMetricsGrpcExporterTarget(); if (!endpoint.startsWith("http")) { endpoint = "https://" + endpoint; } OtlpGrpcMetricExporterBuilder metricExporterBuilder = OtlpGrpcMetricExporter.builder() .setEndpoint(endpoint) .setTimeout(brokerConfig.getMetricGrpcExporterTimeOutInMills(), TimeUnit.MILLISECONDS) .setAggregationTemporalitySelector(type -> { if (brokerConfig.isMetricsInDelta() && (type == InstrumentType.COUNTER || type == InstrumentType.OBSERVABLE_COUNTER || type == InstrumentType.HISTOGRAM)) { return AggregationTemporality.DELTA; } return AggregationTemporality.CUMULATIVE; }); String headers = brokerConfig.getMetricsGrpcExporterHeader(); if (StringUtils.isNotBlank(headers)) { Map headerMap = new HashMap<>(); List kvPairs = Splitter.on(',').omitEmptyStrings().splitToList(headers); for (String item : kvPairs) { String[] split = item.split(":"); if (split.length != 2) { LOGGER.warn("metricsGrpcExporterHeader is not valid: {}", headers); continue; } headerMap.put(split[0], split[1]); } headerMap.forEach(metricExporterBuilder::addHeader); } metricExporter = metricExporterBuilder.build(); periodicMetricReader = PeriodicMetricReader.builder(metricExporter) .setInterval(brokerConfig.getMetricGrpcExporterIntervalInMills(), TimeUnit.MILLISECONDS) .build(); providerBuilder.registerMetricReader(periodicMetricReader); } if (metricsExporterType == MetricsExporterType.PROM) { String promExporterHost = brokerConfig.getMetricsPromExporterHost(); if (StringUtils.isBlank(promExporterHost)) { promExporterHost = brokerConfig.getBrokerIP1(); } prometheusHttpServer = PrometheusHttpServer.builder() .setHost(promExporterHost) .setPort(brokerConfig.getMetricsPromExporterPort()) .build(); providerBuilder.registerMetricReader(prometheusHttpServer); } if (metricsExporterType == MetricsExporterType.LOG) { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); loggingMetricExporter = OtlpJsonLoggingMetricExporter.create(brokerConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE); java.util.logging.Logger.getLogger(OtlpJsonLoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST); periodicMetricReader = PeriodicMetricReader.builder(loggingMetricExporter) .setInterval(brokerConfig.getMetricLoggingExporterIntervalInMills(), TimeUnit.MILLISECONDS) .build(); providerBuilder.registerMetricReader(periodicMetricReader); } registerMetricsView(providerBuilder); brokerMeter = OpenTelemetrySdk.builder() .setMeterProvider(providerBuilder.build()) .build() .getMeter(OPEN_TELEMETRY_METER_NAME); initStatsMetrics(); initRequestMetrics(); initConnectionMetrics(); initLagAndDlqMetrics(); initTransactionMetrics(); initOtherMetrics(); } private void registerMetricsView(SdkMeterProviderBuilder providerBuilder) { // message size buckets, 1k, 4k, 512k, 1M, 2M, 4M List messageSizeBuckets = Arrays.asList( 1d * 1024, //1KB 4d * 1024, //4KB 512d * 1024, //512KB 1d * 1024 * 1024, //1MB 2d * 1024 * 1024, //2MB 4d * 1024 * 1024 //4MB ); List commitLatencyBuckets = Arrays.asList( 1d * 1 * 1 * 5, //5s 1d * 1 * 1 * 60, //1min 1d * 1 * 10 * 60, //10min 1d * 1 * 60 * 60, //1h 1d * 12 * 60 * 60, //12h 1d * 24 * 60 * 60 //24h ); List createTimeBuckets = Arrays.asList( (double) Duration.ofMillis(10).toMillis(), //10ms (double) Duration.ofMillis(100).toMillis(), //100ms (double) Duration.ofSeconds(1).toMillis(), //1s (double) Duration.ofSeconds(3).toMillis(), //3s (double) Duration.ofSeconds(5).toMillis() //5s ); InstrumentSelector messageSizeSelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_MESSAGE_SIZE) .build(); ViewBuilder messageSizeViewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(messageSizeBuckets)); // To config the cardinalityLimit for openTelemetry metrics exporting. SdkMeterProviderUtil.setCardinalityLimit(messageSizeViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(messageSizeSelector, messageSizeViewBuilder.build()); InstrumentSelector commitLatencySelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_FINISH_MSG_LATENCY) .build(); ViewBuilder commitLatencyViewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(commitLatencyBuckets)); // To config the cardinalityLimit for openTelemetry metrics exporting. SdkMeterProviderUtil.setCardinalityLimit(commitLatencyViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(commitLatencySelector, commitLatencyViewBuilder.build()); InstrumentSelector createTopicTimeSelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_TOPIC_CREATE_EXECUTE_TIME) .build(); InstrumentSelector createSubGroupTimeSelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_CONSUMER_GROUP_CREATE_EXECUTE_TIME) .build(); ViewBuilder createTopicTimeViewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(createTimeBuckets)); ViewBuilder createSubGroupTimeViewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(createTimeBuckets)); // To config the cardinalityLimit for openTelemetry metrics exporting. SdkMeterProviderUtil.setCardinalityLimit(createTopicTimeViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(createTopicTimeSelector, createTopicTimeViewBuilder.build()); SdkMeterProviderUtil.setCardinalityLimit(createSubGroupTimeViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(createSubGroupTimeSelector, createSubGroupTimeViewBuilder.build()); for (Pair selectorViewPair : this.remotingMetricsManager.getMetricsView()) { ViewBuilder viewBuilder = selectorViewPair.getObject2(); SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build()); } for (Pair selectorViewPair : messageStore.getMetricsView()) { ViewBuilder viewBuilder = selectorViewPair.getObject2(); SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build()); } for (Pair selectorViewPair : this.popMetricsManager.getMetricsView()) { ViewBuilder viewBuilder = selectorViewPair.getObject2(); SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build()); } // default view builder for all counter. InstrumentSelector defaultCounterSelector = InstrumentSelector.builder() .setType(InstrumentType.COUNTER) .build(); ViewBuilder defaultCounterViewBuilder = View.builder().setDescription("default view for counter."); SdkMeterProviderUtil.setCardinalityLimit(defaultCounterViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(defaultCounterSelector, defaultCounterViewBuilder.build()); //default view builder for all observable gauge. InstrumentSelector defaultGaugeSelector = InstrumentSelector.builder() .setType(InstrumentType.OBSERVABLE_GAUGE) .build(); ViewBuilder defaultGaugeViewBuilder = View.builder().setDescription("default view for gauge."); SdkMeterProviderUtil.setCardinalityLimit(defaultGaugeViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); providerBuilder.registerView(defaultGaugeSelector, defaultGaugeViewBuilder.build()); } private void initStatsMetrics() { if (!brokerConfig.isEnableStatsMetrics()) { return; } processorWatermark = brokerMeter.gaugeBuilder(GAUGE_PROCESSOR_WATERMARK) .setDescription("Request processor watermark") .ofLongs() .buildWithCallback(measurement -> { measurement.record(brokerController.getSendThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "send").build()); measurement.record(brokerController.getAsyncPutThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "async_put").build()); measurement.record(brokerController.getPullThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "pull").build()); measurement.record(brokerController.getAckThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "ack").build()); measurement.record(brokerController.getQueryThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "query_message").build()); measurement.record(brokerController.getClientManagerThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "client_manager").build()); measurement.record(brokerController.getHeartbeatThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "heartbeat").build()); measurement.record(brokerController.getLitePullThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "lite_pull").build()); measurement.record(brokerController.getEndTransactionThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "transaction").build()); measurement.record(brokerController.getConsumerManagerThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "consumer_manager").build()); measurement.record(brokerController.getAdminBrokerThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "admin").build()); measurement.record(brokerController.getReplyThreadPoolQueue().size(), newAttributesBuilder().put(LABEL_PROCESSOR, "reply").build()); }); brokerPermission = brokerMeter.gaugeBuilder(GAUGE_BROKER_PERMISSION) .setDescription("Broker permission") .ofLongs() .buildWithCallback(measurement -> measurement.record(brokerConfig.getBrokerPermission(), newAttributesBuilder().build())); topicNum = brokerMeter.gaugeBuilder(GAUGE_TOPIC_NUM) .setDescription("Active topic number") .ofLongs() .buildWithCallback(measurement -> measurement.record(brokerController.getTopicConfigManager().getTopicConfigTable().size(), newAttributesBuilder().build())); consumerGroupNum = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_GROUP_NUM) .setDescription("Active subscription group number") .ofLongs() .buildWithCallback(measurement -> measurement.record(brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().size(), newAttributesBuilder().build())); } private void initRequestMetrics() { if (!brokerConfig.isEnableRequestMetrics()) { return; } messagesInTotal = brokerMeter.counterBuilder(COUNTER_MESSAGES_IN_TOTAL) .setDescription("Total number of incoming messages") .build(); messagesOutTotal = brokerMeter.counterBuilder(COUNTER_MESSAGES_OUT_TOTAL) .setDescription("Total number of outgoing messages") .build(); throughputInTotal = brokerMeter.counterBuilder(COUNTER_THROUGHPUT_IN_TOTAL) .setDescription("Total traffic of incoming messages") .build(); throughputOutTotal = brokerMeter.counterBuilder(COUNTER_THROUGHPUT_OUT_TOTAL) .setDescription("Total traffic of outgoing messages") .build(); messageSize = brokerMeter.histogramBuilder(HISTOGRAM_MESSAGE_SIZE) .setDescription("Incoming messages size") .ofLongs() .build(); topicCreateExecuteTime = brokerMeter.histogramBuilder(HISTOGRAM_TOPIC_CREATE_EXECUTE_TIME) .setDescription("The distribution of create topic time") .ofLongs() .setUnit("milliseconds") .build(); consumerGroupCreateExecuteTime = brokerMeter.histogramBuilder(HISTOGRAM_CONSUMER_GROUP_CREATE_EXECUTE_TIME) .setDescription("The distribution of create subscription time") .ofLongs() .setUnit("milliseconds") .build(); } private void initConnectionMetrics() { if (!brokerConfig.isEnableConnectionMetrics()) { return; } producerConnection = brokerMeter.gaugeBuilder(GAUGE_PRODUCER_CONNECTIONS) .setDescription("Producer connections") .ofLongs() .buildWithCallback(measurement -> { Map metricsMap = new HashMap<>(); brokerController.getProducerManager() .getGroupChannelTable() .values() .stream() .flatMap(map -> map.values().stream()) .forEach(info -> { ProducerAttr attr = new ProducerAttr(info.getLanguage(), info.getVersion()); Integer count = metricsMap.computeIfAbsent(attr, k -> 0); metricsMap.put(attr, count + 1); }); metricsMap.forEach((attr, count) -> { Attributes attributes = newAttributesBuilder() .put(LABEL_LANGUAGE, attr.language.name().toLowerCase()) .put(LABEL_VERSION, MQVersion.getVersionDesc(attr.version).toLowerCase()) .put(LABEL_PROTOCOL_TYPE, PROTOCOL_TYPE_REMOTING) .build(); measurement.record(count, attributes); }); }); consumerConnection = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_CONNECTIONS) .setDescription("Consumer connections") .ofLongs() .buildWithCallback(measurement -> { Map metricsMap = new HashMap<>(); ConsumerManager consumerManager = brokerController.getConsumerManager(); consumerManager.getConsumerTable() .forEach((group, groupInfo) -> { if (groupInfo != null) { groupInfo.getChannelInfoTable().values().forEach(info -> { ConsumerAttr attr = new ConsumerAttr(group, info.getLanguage(), info.getVersion(), groupInfo.getConsumeType()); Integer count = metricsMap.computeIfAbsent(attr, k -> 0); metricsMap.put(attr, count + 1); }); } }); metricsMap.forEach((attr, count) -> { Attributes attributes = newAttributesBuilder() .put(LABEL_CONSUMER_GROUP, attr.group) .put(LABEL_LANGUAGE, attr.language.name().toLowerCase()) .put(LABEL_VERSION, MQVersion.getVersionDesc(attr.version).toLowerCase()) .put(LABEL_CONSUME_MODE, attr.consumeMode.getTypeCN().toLowerCase()) .put(LABEL_PROTOCOL_TYPE, PROTOCOL_TYPE_REMOTING) .put(LABEL_IS_SYSTEM, isSystemGroup(attr.group)) .build(); measurement.record(count, attributes); }); }); } private void initLagAndDlqMetrics() { if (!brokerConfig.isEnableLagAndDlqMetrics()) { return; } consumerLagMessages = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_LAG_MESSAGES) .setDescription("Consumer lag messages") .ofLongs() .buildWithCallback(measurement -> { consumerLagCalculator.calculateLag(result -> measurement.record(result.lag, buildLagAttributes(result)) ); liteConsumerLagCalculator.calculateLiteLagCount(result -> measurement.record(result.lag, buildLagAttributes(result)) ); }); consumerLagLatency = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_LAG_LATENCY) .setDescription("Consumer lag time") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> { consumerLagCalculator.calculateLag(lagResult -> measurement.record(lagResult.getLagLatency(), buildLagAttributes(lagResult))); liteConsumerLagCalculator.calculateLiteLagLatency(lagResult -> measurement.record(lagResult.getLagLatency(), buildLagAttributes(lagResult))); }); consumerInflightMessages = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_INFLIGHT_MESSAGES) .setDescription("Consumer inflight messages") .ofLongs() .buildWithCallback(measurement -> consumerLagCalculator.calculateInflight(result -> measurement.record(result.inFlight, buildLagAttributes(result)))); consumerQueueingLatency = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_QUEUEING_LATENCY) .setDescription("Consumer queueing time") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> consumerLagCalculator.calculateInflight(result -> { long latency = 0; long curTimeStamp = System.currentTimeMillis(); if (result.earliestUnPulledTimestamp != 0) { latency = curTimeStamp - result.earliestUnPulledTimestamp; } measurement.record(latency, buildLagAttributes(result)); })); consumerReadyMessages = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_READY_MESSAGES) .setDescription("Consumer ready messages") .ofLongs() .buildWithCallback(measurement -> { consumerLagCalculator.calculateAvailable(result -> measurement.record(result.available, buildLagAttributes(result))); // for lite, ready == lag liteConsumerLagCalculator.calculateLiteLagCount(result -> measurement.record(result.lag, buildLagAttributes(result))); }); sendToDlqMessages = brokerMeter.counterBuilder(COUNTER_CONSUMER_SEND_TO_DLQ_MESSAGES_TOTAL) .setDescription("Consumer send to DLQ messages") .build(); } private void initTransactionMetrics() { if (!brokerController.getBrokerConfig().isEnableTransactionMetrics()) { return; } commitMessagesTotal = brokerMeter.counterBuilder(COUNTER_COMMIT_MESSAGES_TOTAL) .setDescription("Total number of commit messages") .build(); rollBackMessagesTotal = brokerMeter.counterBuilder(COUNTER_ROLLBACK_MESSAGES_TOTAL) .setDescription("Total number of rollback messages") .build(); transactionFinishLatency = brokerMeter.histogramBuilder(HISTOGRAM_FINISH_MSG_LATENCY) .setDescription("Transaction finish latency") .ofLongs() .setUnit("ms") .build(); halfMessages = brokerMeter.gaugeBuilder(GAUGE_HALF_MESSAGES) .setDescription("Half messages of all topics") .ofLongs() .buildWithCallback(measurement -> { brokerController.getTransactionalMessageService().getTransactionMetrics().getTransactionCounts() .forEach((topic, metric) -> { measurement.record( metric.getCount().get(), newAttributesBuilder().put(DefaultStoreMetricsConstant.LABEL_TOPIC, topic).build() ); }); }); } private void initOtherMetrics() { if (brokerConfig.isEnableRemotingMetrics()) { this.remotingMetricsManager.initMetrics(brokerMeter, this::newAttributesBuilder); } if (brokerConfig.isEnableMessageStoreMetrics()) { messageStore.initMetrics(brokerMeter, this::newAttributesBuilder); } if (brokerConfig.isEnablePopMetrics()) { this.popMetricsManager.initMetrics(brokerMeter, brokerController, this::newAttributesBuilder); } } public LiteConsumerLagCalculator getLiteConsumerLagCalculator() { return liteConsumerLagCalculator; } public void shutdown() { if (brokerConfig.isInBrokerContainer()) { // only rto need if (brokerConfig.getMetricsExporterType() == MetricsExporterType.OTLP_GRPC) { while (!periodicMetricReader.forceFlush().join(60, TimeUnit.SECONDS).isDone()) { } while (!periodicMetricReader.shutdown().join(60, TimeUnit.SECONDS).isSuccess()) { } while (!metricExporter.shutdown().join(60, TimeUnit.SECONDS).isSuccess()) { } } if (brokerConfig.getMetricsExporterType() == MetricsExporterType.PROM) { while (!prometheusHttpServer.forceFlush().join(60, TimeUnit.SECONDS).isDone()) { } while (!prometheusHttpServer.shutdown().join(60, TimeUnit.SECONDS).isSuccess()) { } } if (brokerConfig.getMetricsExporterType() == MetricsExporterType.LOG) { while (!periodicMetricReader.forceFlush().join(60, TimeUnit.SECONDS).isDone()) { } while (!periodicMetricReader.shutdown().join(60, TimeUnit.SECONDS).isSuccess()) { } while (!loggingMetricExporter.shutdown().join(60, TimeUnit.SECONDS).isSuccess()) { } } } else { if (brokerConfig.getMetricsExporterType() == MetricsExporterType.OTLP_GRPC) { periodicMetricReader.forceFlush(); periodicMetricReader.shutdown(); metricExporter.shutdown(); } if (brokerConfig.getMetricsExporterType() == MetricsExporterType.PROM) { prometheusHttpServer.forceFlush(); prometheusHttpServer.shutdown(); } if (brokerConfig.getMetricsExporterType() == MetricsExporterType.LOG) { periodicMetricReader.forceFlush(); periodicMetricReader.shutdown(); loggingMetricExporter.shutdown(); } } } public RemotingMetricsManager getRemotingMetricsManager() { return remotingMetricsManager; } public PopMetricsManager getPopMetricsManager() { return popMetricsManager; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerAttr.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import com.google.common.base.Objects; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; public class ConsumerAttr { String group; LanguageCode language; int version; ConsumeType consumeMode; public ConsumerAttr(String group, LanguageCode language, int version, ConsumeType consumeMode) { this.group = group; this.language = language; this.version = version; this.consumeMode = consumeMode; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ConsumerAttr attr = (ConsumerAttr) o; return version == attr.version && Objects.equal(group, attr.group) && language == attr.language && consumeMode == attr.consumeMode; } @Override public int hashCode() { return Objects.hashCode(group, language, version, consumeMode); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerLagCalculator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ConsumerFilterManager; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.broker.longpolling.PopCommandCallback; import org.apache.rocketmq.broker.longpolling.PopLongPollingService; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.processor.PopBufferMergeService; import org.apache.rocketmq.broker.processor.PopInflightMessageCounter; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SimpleSubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.DefaultMessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.exception.ConsumeQueueException; public class ConsumerLagCalculator { private final BrokerConfig brokerConfig; private final TopicConfigManager topicConfigManager; private final ConsumerManager consumerManager; private final ConsumerOffsetManager offsetManager; private final ConsumerFilterManager consumerFilterManager; private final SubscriptionGroupManager subscriptionGroupManager; private final MessageStore messageStore; private final PopBufferMergeService popBufferMergeService; private final PopLongPollingService popLongPollingService; private final PopInflightMessageCounter popInflightMessageCounter; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public ConsumerLagCalculator(BrokerController brokerController) { this.brokerConfig = brokerController.getBrokerConfig(); this.topicConfigManager = brokerController.getTopicConfigManager(); this.consumerManager = brokerController.getConsumerManager(); this.offsetManager = brokerController.getConsumerOffsetManager(); this.consumerFilterManager = brokerController.getConsumerFilterManager(); this.subscriptionGroupManager = brokerController.getSubscriptionGroupManager(); this.messageStore = brokerController.getMessageStore(); this.popBufferMergeService = brokerController.getPopMessageProcessor().getPopBufferMergeService(); this.popLongPollingService = brokerController.getPopMessageProcessor().getPopLongPollingService(); this.popInflightMessageCounter = brokerController.getPopInflightMessageCounter(); } public static class ProcessGroupInfo { public String group; public String topic; public boolean isPop; public String retryTopic; public ProcessGroupInfo(String group, String topic, boolean isPop, String retryTopic) { this.group = group; this.topic = topic; this.isPop = isPop; this.retryTopic = retryTopic; } } public static class BaseCalculateResult { public String group; public String topic; public boolean isRetry; public BaseCalculateResult(String group, String topic, boolean isRetry) { this.group = group; this.topic = topic; this.isRetry = isRetry; } } public static class CalculateLagResult extends BaseCalculateResult { public long lag; public long earliestUnconsumedTimestamp; public CalculateLagResult(String group, String topic, boolean isRetry) { super(group, topic, isRetry); } public long getLagLatency() { return earliestUnconsumedTimestamp == 0 ? 0 : System.currentTimeMillis() - earliestUnconsumedTimestamp; } } public static class CalculateInflightResult extends BaseCalculateResult { public long inFlight; public long earliestUnPulledTimestamp; public CalculateInflightResult(String group, String topic, boolean isRetry) { super(group, topic, isRetry); } } public static class CalculateAvailableResult extends BaseCalculateResult { public long available; public CalculateAvailableResult(String group, String topic, boolean isRetry) { super(group, topic, isRetry); } } private void processAllGroup(Consumer consumer) { for (Map.Entry subscriptionEntry : subscriptionGroupManager.getSubscriptionGroupTable().entrySet()) { String group = subscriptionEntry.getKey(); SubscriptionGroupConfig subscriptionGroupConfig = subscriptionEntry.getValue(); ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(group, true); boolean isLite = StringUtils.isNotEmpty(subscriptionGroupConfig.getLiteBindTopic()); if (isLite) { // lite consumer metrics are calculated by LiteConsumerLagCalculator continue; } boolean isPop = false; if (consumerGroupInfo != null) { isPop = consumerGroupInfo.getConsumeType() == ConsumeType.CONSUME_POP; } Set topics; if (brokerConfig.isUseStaticSubscription()) { if (subscriptionGroupConfig.getSubscriptionDataSet() == null || subscriptionGroupConfig.getSubscriptionDataSet().isEmpty()) { continue; } topics = subscriptionGroupConfig.getSubscriptionDataSet() .stream() .map(SimpleSubscriptionData::getTopic) .collect(Collectors.toSet()); } else { if (consumerGroupInfo == null) { continue; } topics = consumerGroupInfo.getSubscribeTopics(); } if (null == topics || topics.isEmpty()) { continue; } for (String topic : topics) { // skip retry topic if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { continue; } TopicConfig topicConfig = topicConfigManager.selectTopicConfig(topic); if (topicConfig == null) { continue; } // skip no perm topic int topicPerm = topicConfig.getPerm() & brokerConfig.getBrokerPermission(); if (!PermName.isReadable(topicPerm) && !PermName.isWriteable(topicPerm)) { continue; } if (isPop) { String retryTopic = KeyBuilder.buildPopRetryTopic(topic, group, brokerConfig.isEnableRetryTopicV2()); TopicConfig retryTopicConfig = topicConfigManager.selectTopicConfig(retryTopic); if (retryTopicConfig != null) { int retryTopicPerm = retryTopicConfig.getPerm() & brokerConfig.getBrokerPermission(); if (PermName.isReadable(retryTopicPerm) || PermName.isWriteable(retryTopicPerm)) { consumer.accept(new ProcessGroupInfo(group, topic, true, retryTopic)); continue; } } if (brokerConfig.isEnableRetryTopicV2() && brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { String retryTopicV1 = KeyBuilder.buildPopRetryTopicV1(topic, group); TopicConfig retryTopicConfigV1 = topicConfigManager.selectTopicConfig(retryTopicV1); if (retryTopicConfigV1 != null) { int retryTopicPerm = retryTopicConfigV1.getPerm() & brokerConfig.getBrokerPermission(); if (PermName.isReadable(retryTopicPerm) || PermName.isWriteable(retryTopicPerm)) { consumer.accept(new ProcessGroupInfo(group, topic, true, retryTopicV1)); continue; } } } consumer.accept(new ProcessGroupInfo(group, topic, true, null)); } else { consumer.accept(new ProcessGroupInfo(group, topic, false, null)); } } } } public void calculateLag(Consumer lagRecorder) { List> futures = new ArrayList<>(); BiConsumer> biConsumer = (info, future) -> calculate(info, future::complete); processAllGroup(info -> { if (info.group == null || info.topic == null) { return; } CompletableFuture future = new CompletableFuture<>(); if (info.isPop && brokerConfig.isEnableNotifyBeforePopCalculateLag()) { if (popLongPollingService.notifyMessageArriving(info.topic, -1, info.group, true, null, 0, null, null, new PopCommandCallback(biConsumer, info, future))) { futures.add(future); return; } } calculate(info, lagRecorder); }); // Set the maximum wait time to 10 seconds to avoid indefinite blocking // in case of a fast fail that causes the future to not complete its execution. try { CompletableFuture.allOf(futures.toArray( new CompletableFuture[0])).get(10, TimeUnit.SECONDS); futures.forEach(future -> { if (future.isDone() && !future.isCompletedExceptionally()) { lagRecorder.accept(future.join()); } }); } catch (Exception e) { LOGGER.error("Calculate lag timeout after 10 seconds", e); } } public void calculate(ProcessGroupInfo info, Consumer lagRecorder) { CalculateLagResult result = new CalculateLagResult(info.group, info.topic, false); try { Pair lag = getConsumerLagStats(info.group, info.topic, info.isPop); if (lag != null) { result.lag = lag.getObject1(); result.earliestUnconsumedTimestamp = lag.getObject2(); } lagRecorder.accept(result); } catch (ConsumeQueueException e) { LOGGER.error("Failed to get lag stats", e); } if (info.isPop) { try { Pair retryLag = getConsumerLagStats(info.group, info.retryTopic, true); result = new CalculateLagResult(info.group, info.topic, true); if (retryLag != null) { result.lag = retryLag.getObject1(); result.earliestUnconsumedTimestamp = retryLag.getObject2(); } lagRecorder.accept(result); } catch (ConsumeQueueException e) { LOGGER.error("Failed to get lag stats", e); } } } public void calculateInflight(Consumer inflightRecorder) { processAllGroup(info -> { CalculateInflightResult result = new CalculateInflightResult(info.group, info.topic, false); try { Pair inFlight = getInFlightMsgStats(info.group, info.topic, info.isPop); if (inFlight != null) { result.inFlight = inFlight.getObject1(); result.earliestUnPulledTimestamp = inFlight.getObject2(); } inflightRecorder.accept(result); } catch (ConsumeQueueException e) { LOGGER.error("Failed to get inflight message stats", e); } if (info.isPop) { try { Pair retryInFlight = getInFlightMsgStats(info.group, info.retryTopic, true); result = new CalculateInflightResult(info.group, info.topic, true); if (retryInFlight != null) { result.inFlight = retryInFlight.getObject1(); result.earliestUnPulledTimestamp = retryInFlight.getObject2(); } inflightRecorder.accept(result); } catch (ConsumeQueueException e) { LOGGER.error("Failed to get inflight message stats", e); } } }); } public void calculateAvailable(Consumer availableRecorder) { processAllGroup(info -> { CalculateAvailableResult result = new CalculateAvailableResult(info.group, info.topic, false); try { result.available = getAvailableMsgCount(info.group, info.topic, info.isPop); availableRecorder.accept(result); } catch (ConsumeQueueException e) { LOGGER.error("Failed to get available message count", e); } if (info.isPop) { try { long retryAvailable = getAvailableMsgCount(info.group, info.retryTopic, true); result = new CalculateAvailableResult(info.group, info.topic, true); result.available = retryAvailable; availableRecorder.accept(result); } catch (ConsumeQueueException e) { LOGGER.error("Failed to get available message count", e); } } }); } public Pair getConsumerLagStats(String group, String topic, boolean isPop) throws ConsumeQueueException { long total = 0L; long earliestUnconsumedTimestamp = Long.MAX_VALUE; if (group == null || topic == null) { return new Pair<>(total, earliestUnconsumedTimestamp); } TopicConfig topicConfig = topicConfigManager.selectTopicConfig(topic); if (topicConfig != null) { for (int queueId = 0; queueId < topicConfig.getWriteQueueNums(); queueId++) { Pair pair = getConsumerLagStats(group, topic, queueId, isPop); total += pair.getObject1(); earliestUnconsumedTimestamp = Math.min(earliestUnconsumedTimestamp, pair.getObject2()); } } else { LOGGER.warn("failed to get config of topic {}", topic); } if (earliestUnconsumedTimestamp < 0 || earliestUnconsumedTimestamp == Long.MAX_VALUE) { earliestUnconsumedTimestamp = 0L; } LOGGER.debug("GetConsumerLagStats, topic={}, group={}, lag={}, latency={}", topic, group, total, earliestUnconsumedTimestamp > 0 ? System.currentTimeMillis() - earliestUnconsumedTimestamp : 0); return new Pair<>(total, earliestUnconsumedTimestamp); } public Pair getConsumerLagStats(String group, String topic, int queueId, boolean isPop) throws ConsumeQueueException { long brokerOffset = messageStore.getMaxOffsetInQueue(topic, queueId); if (brokerOffset < 0) { brokerOffset = 0; } if (isPop && !brokerConfig.isPopConsumerKVServiceEnable()) { long pullOffset = popBufferMergeService.getLatestOffset(topic, group, queueId); if (pullOffset < 0) { pullOffset = offsetManager.queryOffset(group, topic, queueId); } if (pullOffset < 0) { pullOffset = brokerOffset; } long inFlightNum = popInflightMessageCounter.getGroupPopInFlightMessageNum(topic, group, queueId); long lag = calculateMessageCount(group, topic, queueId, pullOffset, brokerOffset) + inFlightNum; long consumerOffset = pullOffset - inFlightNum; long consumerStoreTimeStamp = getStoreTimeStamp(topic, queueId, consumerOffset); return new Pair<>(lag, consumerStoreTimeStamp); } long consumerOffset = offsetManager.queryOffset(group, topic, queueId); if (consumerOffset < 0) { consumerOffset = brokerOffset; } long lag = calculateMessageCount(group, topic, queueId, consumerOffset, brokerOffset); long consumerStoreTimeStamp = getStoreTimeStamp(topic, queueId, consumerOffset); return new Pair<>(lag, consumerStoreTimeStamp); } public Pair getInFlightMsgStats(String group, String topic, boolean isPop) throws ConsumeQueueException { long total = 0L; long earliestUnPulledTimestamp = Long.MAX_VALUE; if (group == null || topic == null) { return new Pair<>(total, earliestUnPulledTimestamp); } TopicConfig topicConfig = topicConfigManager.selectTopicConfig(topic); if (topicConfig != null) { for (int queueId = 0; queueId < topicConfig.getWriteQueueNums(); queueId++) { Pair pair = getInFlightMsgStats(group, topic, queueId, isPop); total += pair.getObject1(); earliestUnPulledTimestamp = Math.min(earliestUnPulledTimestamp, pair.getObject2()); } } else { LOGGER.warn("failed to get config of topic {}", topic); } if (earliestUnPulledTimestamp < 0 || earliestUnPulledTimestamp == Long.MAX_VALUE) { earliestUnPulledTimestamp = 0L; } return new Pair<>(total, earliestUnPulledTimestamp); } public Pair getInFlightMsgStats(String group, String topic, int queueId, boolean isPop) throws ConsumeQueueException { if (isPop && !brokerConfig.isPopConsumerKVServiceEnable()) { long inflight = popInflightMessageCounter.getGroupPopInFlightMessageNum(topic, group, queueId); long pullOffset = popBufferMergeService.getLatestOffset(topic, group, queueId); if (pullOffset < 0) { pullOffset = offsetManager.queryOffset(group, topic, queueId); } if (pullOffset < 0) { pullOffset = messageStore.getMaxOffsetInQueue(topic, queueId); } long pullStoreTimeStamp = getStoreTimeStamp(topic, queueId, pullOffset); return new Pair<>(inflight, pullStoreTimeStamp); } long pullOffset = offsetManager.queryPullOffset(group, topic, queueId); if (pullOffset < 0) { pullOffset = 0; } long commitOffset = offsetManager.queryOffset(group, topic, queueId); if (commitOffset < 0) { commitOffset = pullOffset; } long inflight = calculateMessageCount(group, topic, queueId, commitOffset, pullOffset); long pullStoreTimeStamp = getStoreTimeStamp(topic, queueId, pullOffset); return new Pair<>(inflight, pullStoreTimeStamp); } public long getAvailableMsgCount(String group, String topic, boolean isPop) throws ConsumeQueueException { long total = 0L; if (group == null || topic == null) { return total; } TopicConfig topicConfig = topicConfigManager.selectTopicConfig(topic); if (topicConfig != null) { for (int queueId = 0; queueId < topicConfig.getWriteQueueNums(); queueId++) { total += getAvailableMsgCount(group, topic, queueId, isPop); } } else { LOGGER.warn("failed to get config of topic {}", topic); } return total; } public long getAvailableMsgCount(String group, String topic, int queueId, boolean isPop) throws ConsumeQueueException { long brokerOffset = messageStore.getMaxOffsetInQueue(topic, queueId); if (brokerOffset < 0) { brokerOffset = 0; } long pullOffset; if (isPop && !brokerConfig.isPopConsumerKVServiceEnable()) { pullOffset = popBufferMergeService.getLatestOffset(topic, group, queueId); if (pullOffset < 0) { pullOffset = offsetManager.queryOffset(group, topic, queueId); } } else { pullOffset = offsetManager.queryPullOffset(group, topic, queueId); } if (pullOffset < 0) { pullOffset = brokerOffset; } return calculateMessageCount(group, topic, queueId, pullOffset, brokerOffset); } public long getStoreTimeStamp(String topic, int queueId, long offset) { long storeTimeStamp = Long.MAX_VALUE; if (offset >= 0) { storeTimeStamp = messageStore.getMessageStoreTimeStamp(topic, queueId, offset); storeTimeStamp = storeTimeStamp > 0 ? storeTimeStamp : Long.MAX_VALUE; } return storeTimeStamp; } public long calculateMessageCount(String group, String topic, int queueId, long from, long to) { long count = to - from; if (brokerConfig.isEstimateAccumulation() && to > from) { SubscriptionData subscriptionData = null; if (brokerConfig.isUseStaticSubscription()) { SubscriptionGroupConfig subscriptionGroupConfig = subscriptionGroupManager.findSubscriptionGroupConfig(group); if (subscriptionGroupConfig != null) { for (SimpleSubscriptionData simpleSubscriptionData : subscriptionGroupConfig.getSubscriptionDataSet()) { if (topic.equals(simpleSubscriptionData.getTopic())) { try { subscriptionData = FilterAPI.buildSubscriptionData(simpleSubscriptionData.getTopic(), simpleSubscriptionData.getExpression(), simpleSubscriptionData.getExpressionType()); } catch (Exception e) { LOGGER.error("Try to build subscription for group:{}, topic:{} exception.", group, topic, e); } break; } } } } else { ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(group, true); if (consumerGroupInfo != null) { subscriptionData = consumerGroupInfo.findSubscriptionData(topic); } } if (null != subscriptionData) { if (ExpressionType.TAG.equalsIgnoreCase(subscriptionData.getExpressionType()) && !SubscriptionData.SUB_ALL.equals(subscriptionData.getSubString())) { count = messageStore.estimateMessageCount(topic, queueId, from, to, new DefaultMessageFilter(subscriptionData)); } else if (ExpressionType.SQL92.equalsIgnoreCase(subscriptionData.getExpressionType())) { ConsumerFilterData consumerFilterData = consumerFilterManager.get(topic, group); count = messageStore.estimateMessageCount(topic, queueId, from, to, new ExpressionMessageFilter(subscriptionData, consumerFilterData, consumerFilterManager)); } } } return count < 0 ? 0 : count; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/InvocationStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; public enum InvocationStatus { SUCCESS("success"), FAILURE("failure"); private final String name; InvocationStatus(String name) { this.name = name; } public String getName() { return name; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/LiteConsumerLagCalculator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.PriorityQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.entity.TopicGroup; import org.apache.rocketmq.common.lite.LiteLagInfo; import org.apache.rocketmq.common.lite.LiteUtil; public class LiteConsumerLagCalculator { protected static final long INIT_CONSUME_TIMESTAMP = -1L; @VisibleForTesting protected final ConcurrentHashMap> topicGroupLagTimeMap = new ConcurrentHashMap<>(); private final BrokerController brokerController; public LiteConsumerLagCalculator(BrokerController brokerController) { this.brokerController = brokerController; } public void removeLagInfo(String group, String bindTopic, String lmqName) { PriorityBlockingQueue lagHeap = topicGroupLagTimeMap.get(new TopicGroup(bindTopic, group)); if (lagHeap != null) { lagHeap.removeIf(info -> info.getLmqName().equals(lmqName)); } } public void updateLagInfo(String group, String bindTopic, String lmqName, long storeTimestamp) { PriorityBlockingQueue lagHeap = topicGroupLagTimeMap.computeIfAbsent( new TopicGroup(bindTopic, group), k -> new PriorityBlockingQueue<>(8, Comparator.comparingLong(LagTimeInfo::getLagTimestamp).reversed())); lagHeap.removeIf(info -> info.getLmqName().equals(lmqName)); lagHeap.offer(new LagTimeInfo(lmqName, storeTimestamp)); int topK = brokerController.getBrokerConfig().getLiteLagLatencyTopK(); if (lagHeap.size() > topK) { lagHeap.remove(); } } @VisibleForTesting protected long getStoreTimestamp(String lmqName, long offset) { return this.brokerController.getMessageStore().getMessageStoreTimeStamp(lmqName, 0, offset); } @VisibleForTesting protected long getOffset(String group, String topic) { return brokerController.getConsumerOffsetManager().queryOffset(group, topic, 0); } @VisibleForTesting protected long getMaxOffset(String lmqName) { return brokerController.getLiteLifecycleManager().getMaxOffsetInQueue(lmqName); } private long offsetDiff(Long offset, String lmqName) { long consumerOffset = offset == null ? -1L : offset; if (consumerOffset < 0) { return 0L; } long maxOffset = getMaxOffset(lmqName); return Math.max(0L, maxOffset - consumerOffset); } public void calculateLiteLagCount(Consumer lagRecorder) { if (!brokerController.getBrokerConfig().isLiteLagCountMetricsEnable()) { return; } Map counter = new HashMap<>(); offsetTableForEachByGroup(null, (topicGroup, consumerOffset) -> { String lmqName = topicGroup.topic; String group = topicGroup.group; String parentTopic = LiteUtil.getParentTopic(lmqName); long diff = offsetDiff(consumerOffset, lmqName); if (diff > 0) { TopicGroup key = new TopicGroup(parentTopic, group); counter.merge(key, diff, Long::sum); } }); counter.forEach((topicGroup, totalCount) -> { ConsumerLagCalculator.CalculateLagResult lagResult = new ConsumerLagCalculator.CalculateLagResult(topicGroup.group, topicGroup.topic, false); lagResult.lag = totalCount; lagRecorder.accept(lagResult); }); } public void calculateLiteLagLatency(Consumer lagRecorder) { if (!brokerController.getBrokerConfig().isLiteLagLatencyMetricsEnable()) { return; } topicGroupLagTimeMap.forEach((topicGroup, lagHeap) -> { if (CollectionUtils.isEmpty(lagHeap)) { return; } // Find the minimum storeTimestamp in the heap long minTimestamp = lagHeap.stream() .mapToLong(LagTimeInfo::getLagTimestamp) .min() .orElse(0L); ConsumerLagCalculator.CalculateLagResult lagResult = new ConsumerLagCalculator.CalculateLagResult(topicGroup.group, topicGroup.topic, false); lagResult.earliestUnconsumedTimestamp = minTimestamp; lagRecorder.accept(lagResult); }); } /** * Get top K LiteLagInfo entries with the smallest lag timestamps for a topic group. * * @param group consumer group name * @param parentTopic parent topic name * @param topK max number of entries to retrieve * @return Pair containing: * - Left: list of at most topK LiteLagInfo entries sorted by timestamp * - Right: minimum lag timestamp (or initial consume timestamp if no data) */ public Pair/*topK*/, Long/*timestamp*/> getLagTimestampTopK( String group, String parentTopic, int topK ) { TopicGroup key = new TopicGroup(parentTopic, group); PriorityBlockingQueue lagHeap = topicGroupLagTimeMap.get(key); if (CollectionUtils.isEmpty(lagHeap)) { return Pair.of(Collections.emptyList(), INIT_CONSUME_TIMESTAMP); } // Evict the largest timestamp when heap is full, keeping smallest topK timestamps PriorityQueue maxHeap = new PriorityQueue<>(topK, Comparator.comparingLong(LagTimeInfo::getLagTimestamp).reversed()); for (LagTimeInfo lagInfo : lagHeap) { if (maxHeap.size() < topK) { maxHeap.offer(lagInfo); } else if (maxHeap.peek() != null && lagInfo.getLagTimestamp() < maxHeap.peek().getLagTimestamp()) { maxHeap.poll(); maxHeap.offer(lagInfo); } } // Convert results to LiteLagInfo list and sort by timestamp List topList = new ArrayList<>(maxHeap.size()); for (LagTimeInfo lagInfo : maxHeap) { String lmqName = lagInfo.getLmqName(); LiteLagInfo liteLagInfo = new LiteLagInfo(); liteLagInfo.setLiteTopic(LiteUtil.getLiteTopic(lmqName)); liteLagInfo.setEarliestUnconsumedTimestamp(lagInfo.getLagTimestamp()); liteLagInfo.setLagCount(offsetDiff(getOffset(group, lmqName), lmqName)); topList.add(liteLagInfo); } // Sort by timestamp in ascending order topList.sort(Comparator.comparingLong(LiteLagInfo::getEarliestUnconsumedTimestamp)); long minLagTimestamp = topList.isEmpty() ? INIT_CONSUME_TIMESTAMP : topList.get(0).getEarliestUnconsumedTimestamp(); return Pair.of(topList, minLagTimestamp); } /** * Get top K LiteLagInfo entries with the largest lag counts for a topic group. * * @param group consumer group name * @param topK max number of entries to retrieve * @return Pair containing: * - Left: list of at most topK LiteLagInfo entries sorted by lag count * - Right: total lag count */ public Pair, Long> getLagCountTopK( String group, int topK ) { // Use a min heap to maintain the largest topK lag counts PriorityQueue minHeap = new PriorityQueue<>(topK, Comparator.comparingLong(LiteLagInfo::getLagCount)); AtomicLong totalLagCount = new AtomicLong(0L); offsetTableForEachByGroup(group, (topicGroup, consumerOffset) -> { String topic = topicGroup.topic; long diff = offsetDiff(consumerOffset, topic); if (diff > 0) { totalLagCount.addAndGet(diff); LiteLagInfo liteLagInfo = new LiteLagInfo(); liteLagInfo.setLiteTopic(LiteUtil.getLiteTopic(topic)); liteLagInfo.setLagCount(diff); liteLagInfo.setEarliestUnconsumedTimestamp(getStoreTimestamp(topic, consumerOffset)); if (minHeap.size() < topK) { minHeap.offer(liteLagInfo); } else if (minHeap.peek() != null && liteLagInfo.getLagCount() > minHeap.peek().getLagCount()) { minHeap.poll(); minHeap.offer(liteLagInfo); } } }); // Convert heap elements to list and sort by lag count in descending order List topList = new ArrayList<>(minHeap); topList.sort(Comparator.comparingLong(LiteLagInfo::getLagCount).reversed()); return Pair.of(topList, totalLagCount.get()); } /** * Filters the lite group offset by the specified group and processes each entry via BiConsumer. * * @param group The specified consumer group. If null, all offset information is processed. * @param consumer The BiConsumer used to process each entry. */ protected void offsetTableForEachByGroup( String group, BiConsumer consumer ) { ConcurrentMap> offsetTable = brokerController.getConsumerOffsetManager().getOffsetTable(); offsetTable.forEach((topicAtGroup, queueOffset) -> { String[] topicGroup = topicAtGroup.split(ConsumerOffsetManager.TOPIC_GROUP_SEPARATOR); if (topicGroup.length == 2) { if (!LiteUtil.isLiteTopicQueue(topicGroup[0])) { return; } // If group specified, only process the matching group if (StringUtils.isEmpty(group) || group.equals(topicGroup[1])) { TopicGroup tg = new TopicGroup(topicGroup[0], topicGroup[1]); Long consumerOffset = queueOffset.get(0); if (consumerOffset == null) { return; } consumer.accept(tg, consumerOffset); } } }); } protected static class LagTimeInfo { private final String lmqName; // earliest unconsumed timestamp private final long lagTimestamp; public LagTimeInfo(String lmqName, long lagTimestamp) { this.lmqName = lmqName; this.lagTimestamp = lagTimestamp; } public String getLmqName() { return lmqName; } public long getLagTimestamp() { return lagTimestamp; } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } LagTimeInfo lagInfo = (LagTimeInfo) o; return Objects.equals(lmqName, lagInfo.lmqName); } @Override public int hashCode() { return Objects.hashCode(lmqName); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsConstant.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; public class PopMetricsConstant { public static final String HISTOGRAM_POP_BUFFER_SCAN_TIME_CONSUME = "rocketmq_pop_buffer_scan_time_consume"; public static final String COUNTER_POP_REVIVE_IN_MESSAGE_TOTAL = "rocketmq_pop_revive_in_message_total"; public static final String COUNTER_POP_REVIVE_OUT_MESSAGE_TOTAL = "rocketmq_pop_revive_out_message_total"; public static final String COUNTER_POP_REVIVE_RETRY_MESSAGES_TOTAL = "rocketmq_pop_revive_retry_messages_total"; public static final String GAUGE_POP_REVIVE_LAG = "rocketmq_pop_revive_lag"; public static final String GAUGE_POP_REVIVE_LATENCY = "rocketmq_pop_revive_latency"; public static final String GAUGE_POP_OFFSET_BUFFER_SIZE = "rocketmq_pop_offset_buffer_size"; public static final String GAUGE_POP_CHECKPOINT_BUFFER_SIZE = "rocketmq_pop_checkpoint_buffer_size"; public static final String LABEL_REVIVE_MESSAGE_TYPE = "revive_message_type"; public static final String LABEL_PUT_STATUS = "put_status"; public static final String LABEL_QUEUE_ID = "queue_id"; } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import com.google.common.collect.Lists; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongMeasurement; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.processor.PopBufferMergeService; import org.apache.rocketmq.broker.processor.PopReviveService; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.metrics.NopLongCounter; import org.apache.rocketmq.common.metrics.NopLongHistogram; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.COUNTER_POP_REVIVE_IN_MESSAGE_TOTAL; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.COUNTER_POP_REVIVE_OUT_MESSAGE_TOTAL; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.COUNTER_POP_REVIVE_RETRY_MESSAGES_TOTAL; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.GAUGE_POP_CHECKPOINT_BUFFER_SIZE; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.GAUGE_POP_OFFSET_BUFFER_SIZE; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.GAUGE_POP_REVIVE_LAG; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.GAUGE_POP_REVIVE_LATENCY; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.HISTOGRAM_POP_BUFFER_SCAN_TIME_CONSUME; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.LABEL_PUT_STATUS; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.LABEL_QUEUE_ID; import static org.apache.rocketmq.broker.metrics.PopMetricsConstant.LABEL_REVIVE_MESSAGE_TYPE; public class PopMetricsManager { private static final Logger log = LoggerFactory.getLogger(PopMetricsManager.class); private Supplier attributesBuilderSupplier; private LongHistogram popBufferScanTimeConsume = new NopLongHistogram(); private LongCounter popRevivePutTotal = new NopLongCounter(); private LongCounter popReviveGetTotal = new NopLongCounter(); private LongCounter popReviveRetryMessageTotal = new NopLongCounter(); public PopMetricsManager() { } public List> getMetricsView() { List rpcCostTimeBuckets = Arrays.asList( (double) Duration.ofMillis(1).toMillis(), (double) Duration.ofMillis(10).toMillis(), (double) Duration.ofMillis(100).toMillis(), (double) Duration.ofSeconds(1).toMillis(), (double) Duration.ofSeconds(2).toMillis(), (double) Duration.ofSeconds(3).toMillis() ); InstrumentSelector popBufferScanTimeConsumeSelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_POP_BUFFER_SCAN_TIME_CONSUME) .build(); ViewBuilder popBufferScanTimeConsumeViewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets)); return Lists.newArrayList(new Pair<>(popBufferScanTimeConsumeSelector, popBufferScanTimeConsumeViewBuilder)); } public void initMetrics(Meter meter, BrokerController brokerController, Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; this.popBufferScanTimeConsume = meter.histogramBuilder(HISTOGRAM_POP_BUFFER_SCAN_TIME_CONSUME) .setDescription("Time consuming of pop buffer scan") .setUnit("milliseconds") .ofLongs() .build(); this.popRevivePutTotal = meter.counterBuilder(COUNTER_POP_REVIVE_IN_MESSAGE_TOTAL) .setDescription("Total number of put message to revive topic") .build(); this.popReviveGetTotal = meter.counterBuilder(COUNTER_POP_REVIVE_OUT_MESSAGE_TOTAL) .setDescription("Total number of get message from revive topic") .build(); this.popReviveRetryMessageTotal = meter.counterBuilder(COUNTER_POP_REVIVE_RETRY_MESSAGES_TOTAL) .setDescription("Total number of put message to pop retry topic") .build(); meter.gaugeBuilder(GAUGE_POP_OFFSET_BUFFER_SIZE) .setDescription("Time number of buffered offset") .ofLongs() .buildWithCallback(measurement -> calculatePopBufferOffsetSize(brokerController, measurement)); meter.gaugeBuilder(GAUGE_POP_CHECKPOINT_BUFFER_SIZE) .setDescription("The number of buffered checkpoint") .ofLongs() .buildWithCallback(measurement -> calculatePopBufferCkSize(brokerController, measurement)); meter.gaugeBuilder(GAUGE_POP_REVIVE_LAG) .setDescription("The processing lag of revive topic") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> calculatePopReviveLag(brokerController, measurement)); meter.gaugeBuilder(GAUGE_POP_REVIVE_LATENCY) .setDescription("The processing latency of revive topic") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> calculatePopReviveLatency(brokerController, measurement)); } private void calculatePopBufferOffsetSize(BrokerController brokerController, ObservableLongMeasurement measurement) { PopBufferMergeService popBufferMergeService = brokerController.getPopMessageProcessor().getPopBufferMergeService(); measurement.record(popBufferMergeService.getOffsetTotalSize(), this.newAttributesBuilder().build()); } private void calculatePopBufferCkSize(BrokerController brokerController, ObservableLongMeasurement measurement) { PopBufferMergeService popBufferMergeService = brokerController.getPopMessageProcessor().getPopBufferMergeService(); measurement.record(popBufferMergeService.getBufferedCKSize(), this.newAttributesBuilder().build()); } private void calculatePopReviveLatency(BrokerController brokerController, ObservableLongMeasurement measurement) { PopReviveService[] popReviveServices = brokerController.getAckMessageProcessor().getPopReviveServices(); for (PopReviveService popReviveService : popReviveServices) { try { measurement.record(popReviveService.getReviveBehindMillis(), this.newAttributesBuilder() .put(LABEL_QUEUE_ID, popReviveService.getQueueId()) .build()); } catch (ConsumeQueueException e) { log.error("Failed to get revive behind duration", e); } } } private void calculatePopReviveLag(BrokerController brokerController, ObservableLongMeasurement measurement) { PopReviveService[] popReviveServices = brokerController.getAckMessageProcessor().getPopReviveServices(); for (PopReviveService popReviveService : popReviveServices) { try { measurement.record(popReviveService.getReviveBehindMessages(), this.newAttributesBuilder() .put(LABEL_QUEUE_ID, popReviveService.getQueueId()) .build()); } catch (ConsumeQueueException e) { log.error("Failed to get revive behind message count", e); } } } public void incPopReviveAckPutCount(AckMsg ackMsg, PutMessageStatus status) { incPopRevivePutCount(ackMsg.getConsumerGroup(), ackMsg.getTopic(), PopReviveMessageType.ACK, status, 1); } public void incPopReviveCkPutCount(PopCheckPoint checkPoint, PutMessageStatus status) { incPopRevivePutCount(checkPoint.getCId(), checkPoint.getTopic(), PopReviveMessageType.CK, status, 1); } public void incPopRevivePutCount(String group, String topic, PopReviveMessageType messageType, PutMessageStatus status, int num) { Attributes attributes = this.newAttributesBuilder() .put(LABEL_CONSUMER_GROUP, group) .put(LABEL_TOPIC, topic) .put(LABEL_REVIVE_MESSAGE_TYPE, messageType.name()) .put(LABEL_PUT_STATUS, status.name()) .build(); this.popRevivePutTotal.add(num, attributes); } public void incPopReviveAckGetCount(AckMsg ackMsg, int queueId) { incPopReviveGetCount(ackMsg.getConsumerGroup(), ackMsg.getTopic(), PopReviveMessageType.ACK, queueId, 1); } public void incPopReviveCkGetCount(PopCheckPoint checkPoint, int queueId) { incPopReviveGetCount(checkPoint.getCId(), checkPoint.getTopic(), PopReviveMessageType.CK, queueId, 1); } public void incPopReviveGetCount(String group, String topic, PopReviveMessageType messageType, int queueId, int num) { AttributesBuilder builder = this.newAttributesBuilder(); Attributes attributes = builder .put(LABEL_CONSUMER_GROUP, group) .put(LABEL_TOPIC, topic) .put(LABEL_QUEUE_ID, queueId) .put(LABEL_REVIVE_MESSAGE_TYPE, messageType.name()) .build(); this.popReviveGetTotal.add(num, attributes); } public void incPopReviveRetryMessageCount(PopCheckPoint checkPoint, PutMessageStatus status) { AttributesBuilder builder = this.newAttributesBuilder(); Attributes attributes = builder .put(LABEL_CONSUMER_GROUP, checkPoint.getCId()) .put(LABEL_TOPIC, checkPoint.getTopic()) .put(LABEL_PUT_STATUS, status.name()) .build(); this.popReviveRetryMessageTotal.add(1, attributes); } public void recordPopBufferScanTimeConsume(long time) { this.popBufferScanTimeConsume.record(time, this.newAttributesBuilder().build()); } public AttributesBuilder newAttributesBuilder() { return this.attributesBuilderSupplier != null ? this.attributesBuilderSupplier.get() : Attributes.builder(); } // Getter methods for external access public LongHistogram getPopBufferScanTimeConsume() { return popBufferScanTimeConsume; } public LongCounter getPopRevivePutTotal() { return popRevivePutTotal; } public LongCounter getPopReviveGetTotal() { return popReviveGetTotal; } public LongCounter getPopReviveRetryMessageTotal() { return popReviveRetryMessageTotal; } public Supplier getAttributesBuilderSupplier() { return attributesBuilderSupplier; } // Setter methods for testing public void setAttributesBuilderSupplier(Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/PopReviveMessageType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; public enum PopReviveMessageType { CK, ACK } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/metrics/ProducerAttr.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import com.google.common.base.Objects; import org.apache.rocketmq.remoting.protocol.LanguageCode; public class ProducerAttr { LanguageCode language; int version; public ProducerAttr(LanguageCode language, int version) { this.language = language; this.version = version; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProducerAttr attr = (ProducerAttr) o; return version == attr.version && language == attr.language; } @Override public int hashCode() { return Objects.hashCode(language, version); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/mqtrace/ConsumeMessageContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.mqtrace; import java.util.Map; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class ConsumeMessageContext { private String consumerGroup; private String topic; private Integer queueId; private String clientHost; private String storeHost; private Map messageIds; private int bodyLength; private boolean success; private String status; private Object mqTraceContext; private TopicConfig topicConfig; private String accountAuthType; private String accountOwnerParent; private String accountOwnerSelf; private int rcvMsgNum; private int rcvMsgSize; private BrokerStatsManager.StatsType rcvStat; private int commercialRcvMsgNum; private String commercialOwner; private BrokerStatsManager.StatsType commercialRcvStats; private int commercialRcvTimes; private int commercialRcvSize; private int filterMessageCount; private String namespace; public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public String getClientHost() { return clientHost; } public void setClientHost(String clientHost) { this.clientHost = clientHost; } public String getStoreHost() { return storeHost; } public void setStoreHost(String storeHost) { this.storeHost = storeHost; } public Map getMessageIds() { return messageIds; } public void setMessageIds(Map messageIds) { this.messageIds = messageIds; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Object getMqTraceContext() { return mqTraceContext; } public void setMqTraceContext(Object mqTraceContext) { this.mqTraceContext = mqTraceContext; } public TopicConfig getTopicConfig() { return topicConfig; } public void setTopicConfig(TopicConfig topicConfig) { this.topicConfig = topicConfig; } public int getBodyLength() { return bodyLength; } public void setBodyLength(int bodyLength) { this.bodyLength = bodyLength; } public String getAccountAuthType() { return accountAuthType; } public void setAccountAuthType(String accountAuthType) { this.accountAuthType = accountAuthType; } public String getAccountOwnerParent() { return accountOwnerParent; } public void setAccountOwnerParent(String accountOwnerParent) { this.accountOwnerParent = accountOwnerParent; } public String getAccountOwnerSelf() { return accountOwnerSelf; } public void setAccountOwnerSelf(String accountOwnerSelf) { this.accountOwnerSelf = accountOwnerSelf; } public int getRcvMsgNum() { return rcvMsgNum; } public void setRcvMsgNum(int rcvMsgNum) { this.rcvMsgNum = rcvMsgNum; } public int getRcvMsgSize() { return rcvMsgSize; } public void setRcvMsgSize(int rcvMsgSize) { this.rcvMsgSize = rcvMsgSize; } public BrokerStatsManager.StatsType getRcvStat() { return rcvStat; } public void setRcvStat(BrokerStatsManager.StatsType rcvStat) { this.rcvStat = rcvStat; } public int getCommercialRcvMsgNum() { return commercialRcvMsgNum; } public void setCommercialRcvMsgNum(int commercialRcvMsgNum) { this.commercialRcvMsgNum = commercialRcvMsgNum; } public String getCommercialOwner() { return commercialOwner; } public void setCommercialOwner(final String commercialOwner) { this.commercialOwner = commercialOwner; } public BrokerStatsManager.StatsType getCommercialRcvStats() { return commercialRcvStats; } public void setCommercialRcvStats(final BrokerStatsManager.StatsType commercialRcvStats) { this.commercialRcvStats = commercialRcvStats; } public int getCommercialRcvTimes() { return commercialRcvTimes; } public void setCommercialRcvTimes(final int commercialRcvTimes) { this.commercialRcvTimes = commercialRcvTimes; } public int getCommercialRcvSize() { return commercialRcvSize; } public void setCommercialRcvSize(final int commercialRcvSize) { this.commercialRcvSize = commercialRcvSize; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public int getFilterMessageCount() { return filterMessageCount; } public void setFilterMessageCount(int filterMessageCount) { this.filterMessageCount = filterMessageCount; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/mqtrace/ConsumeMessageHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.mqtrace; public interface ConsumeMessageHook { String hookName(); void consumeMessageBefore(final ConsumeMessageContext context); void consumeMessageAfter(final ConsumeMessageContext context); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.mqtrace; import java.util.Properties; import org.apache.rocketmq.common.message.MessageType; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class SendMessageContext { /** namespace */ private String namespace; /** producer group without namespace. */ private String producerGroup; /** topic without namespace. */ private String topic; private String msgId; private String originMsgId; private Integer queueId; private Long queueOffset; private String brokerAddr; private String bornHost; private int bodyLength; private int code; private String errorMsg; private String msgProps; private Object mqTraceContext; private Properties extProps; private String brokerRegionId; private String msgUniqueKey; private long bornTimeStamp; private long requestTimeStamp; private MessageType msgType = MessageType.Trans_msg_Commit; private boolean isSuccess = false; /** * Account Statistics */ private String accountAuthType; private String accountOwnerParent; private String accountOwnerSelf; private int sendMsgNum; private int sendMsgSize; private BrokerStatsManager.StatsType sendStat; private int commercialSendMsgNum; /** * For Commercial */ private String commercialOwner; private BrokerStatsManager.StatsType commercialSendStats; private int commercialSendSize; private int commercialSendTimes; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public boolean isSuccess() { return isSuccess; } public void setSuccess(final boolean success) { isSuccess = success; } public MessageType getMsgType() { return msgType; } public void setMsgType(final MessageType msgType) { this.msgType = msgType; } public String getMsgUniqueKey() { return msgUniqueKey; } public void setMsgUniqueKey(final String msgUniqueKey) { this.msgUniqueKey = msgUniqueKey; } public long getBornTimeStamp() { return bornTimeStamp; } public void setBornTimeStamp(final long bornTimeStamp) { this.bornTimeStamp = bornTimeStamp; } public long getRequestTimeStamp() { return requestTimeStamp; } public void setRequestTimeStamp(long requestTimeStamp) { this.requestTimeStamp = requestTimeStamp; } public String getBrokerRegionId() { return brokerRegionId; } public void setBrokerRegionId(final String brokerRegionId) { this.brokerRegionId = brokerRegionId; } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getOriginMsgId() { return originMsgId; } public void setOriginMsgId(String originMsgId) { this.originMsgId = originMsgId; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public Long getQueueOffset() { return queueOffset; } public void setQueueOffset(Long queueOffset) { this.queueOffset = queueOffset; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public String getBornHost() { return bornHost; } public void setBornHost(String bornHost) { this.bornHost = bornHost; } public int getBodyLength() { return bodyLength; } public void setBodyLength(int bodyLength) { this.bodyLength = bodyLength; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public String getMsgProps() { return msgProps; } public void setMsgProps(String msgProps) { this.msgProps = msgProps; } public Object getMqTraceContext() { return mqTraceContext; } public void setMqTraceContext(Object mqTraceContext) { this.mqTraceContext = mqTraceContext; } public Properties getExtProps() { return extProps; } public void setExtProps(Properties extProps) { this.extProps = extProps; } public String getCommercialOwner() { return commercialOwner; } public void setCommercialOwner(final String commercialOwner) { this.commercialOwner = commercialOwner; } public String getAccountAuthType() { return accountAuthType; } public void setAccountAuthType(String accountAuthType) { this.accountAuthType = accountAuthType; } public String getAccountOwnerParent() { return accountOwnerParent; } public void setAccountOwnerParent(String accountOwnerParent) { this.accountOwnerParent = accountOwnerParent; } public String getAccountOwnerSelf() { return accountOwnerSelf; } public void setAccountOwnerSelf(String accountOwnerSelf) { this.accountOwnerSelf = accountOwnerSelf; } public int getSendMsgNum() { return sendMsgNum; } public void setSendMsgNum(int sendMsgNum) { this.sendMsgNum = sendMsgNum; } public int getSendMsgSize() { return sendMsgSize; } public void setSendMsgSize(int sendMsgSize) { this.sendMsgSize = sendMsgSize; } public BrokerStatsManager.StatsType getSendStat() { return sendStat; } public void setSendStat(BrokerStatsManager.StatsType sendStat) { this.sendStat = sendStat; } public BrokerStatsManager.StatsType getCommercialSendStats() { return commercialSendStats; } public int getCommercialSendMsgNum() { return commercialSendMsgNum; } public void setCommercialSendMsgNum(int commercialSendMsgNum) { this.commercialSendMsgNum = commercialSendMsgNum; } public void setCommercialSendStats(final BrokerStatsManager.StatsType commercialSendStats) { this.commercialSendStats = commercialSendStats; } public int getCommercialSendSize() { return commercialSendSize; } public void setCommercialSendSize(final int commercialSendSize) { this.commercialSendSize = commercialSendSize; } public int getCommercialSendTimes() { return commercialSendTimes; } public void setCommercialSendTimes(final int commercialSendTimes) { this.commercialSendTimes = commercialSendTimes; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.mqtrace; public interface SendMessageHook { String hookName(); void sendMessageBefore(final SendMessageContext context); void sendMessageAfter(final SendMessageContext context); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.store.exception.ConsumeQueueException; /** * manage the offset of broadcast. * now, use this to support switch remoting client between proxy and broker */ public class BroadcastOffsetManager extends ServiceThread { private static final String TOPIC_GROUP_SEPARATOR = "@"; private final BrokerController brokerController; private final BrokerConfig brokerConfig; /** * k: topic@groupId * v: the pull offset of all client of all queue */ protected final ConcurrentHashMap offsetStoreMap = new ConcurrentHashMap<>(); public BroadcastOffsetManager(BrokerController brokerController) { this.brokerController = brokerController; this.brokerConfig = brokerController.getBrokerConfig(); } public void updateOffset(String topic, String group, int queueId, long offset, String clientId, boolean fromProxy) { BroadcastOffsetData broadcastOffsetData = offsetStoreMap.computeIfAbsent( buildKey(topic, group), key -> new BroadcastOffsetData(topic, group)); broadcastOffsetData.clientOffsetStore.compute(clientId, (clientIdKey, broadcastTimedOffsetStore) -> { if (broadcastTimedOffsetStore == null) { broadcastTimedOffsetStore = new BroadcastTimedOffsetStore(fromProxy); } broadcastTimedOffsetStore.timestamp = System.currentTimeMillis(); broadcastTimedOffsetStore.fromProxy = fromProxy; broadcastTimedOffsetStore.offsetStore.updateOffset(queueId, offset, true); return broadcastTimedOffsetStore; }); } /** * the time need init offset * 1. client connect to proxy -> client connect to broker * 2. client connect to broker -> client connect to proxy * 3. client connect to proxy at the first time * * @return -1 means no init offset, use the queueOffset in pullRequestHeader */ public Long queryInitOffset(String topic, String groupId, int queueId, String clientId, long requestOffset, boolean fromProxy) throws ConsumeQueueException { BroadcastOffsetData broadcastOffsetData = offsetStoreMap.get(buildKey(topic, groupId)); if (broadcastOffsetData == null) { if (fromProxy && requestOffset < 0) { return getOffset(null, topic, groupId, queueId); } else { return -1L; } } final AtomicLong offset = new AtomicLong(-1L); BroadcastTimedOffsetStore offsetStore = broadcastOffsetData.clientOffsetStore.get(clientId); if (offsetStore == null) { offsetStore = new BroadcastTimedOffsetStore(fromProxy); broadcastOffsetData.clientOffsetStore.put(clientId, offsetStore); } if (offsetStore.fromProxy && requestOffset < 0) { // when from proxy and requestOffset is -1 // means proxy need a init offset to pull message offset.set(getOffset(offsetStore, topic, groupId, queueId)); } else { if (offsetStore.fromProxy != fromProxy) { offset.set(getOffset(offsetStore, topic, groupId, queueId)); } } return offset.get(); } private long getOffset(BroadcastTimedOffsetStore offsetStore, String topic, String groupId, int queueId) throws ConsumeQueueException { long storeOffset = -1; if (offsetStore != null) { storeOffset = offsetStore.offsetStore.readOffset(queueId); } if (storeOffset < 0) { storeOffset = brokerController.getConsumerOffsetManager().queryOffset(broadcastGroupId(groupId), topic, queueId); } if (storeOffset < 0) { if (this.brokerController.getMessageStore().checkInMemByConsumeOffset(topic, queueId, 0, 1)) { storeOffset = 0; } else { storeOffset = brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId, true); } } return storeOffset; } /** * 1. scan expire offset * 2. calculate the min offset of all client of one topic@group, * and then commit consumer offset by group@broadcast */ protected void scanOffsetData() { for (String k : offsetStoreMap.keySet()) { BroadcastOffsetData broadcastOffsetData = offsetStoreMap.get(k); if (broadcastOffsetData == null) { continue; } Map queueMinOffset = new HashMap<>(); for (String clientId : broadcastOffsetData.clientOffsetStore.keySet()) { broadcastOffsetData.clientOffsetStore .computeIfPresent(clientId, (clientIdKey, broadcastTimedOffsetStore) -> { long interval = System.currentTimeMillis() - broadcastTimedOffsetStore.timestamp; boolean clientIsOnline = brokerController.getConsumerManager().findChannel(broadcastOffsetData.group, clientId) != null; if (clientIsOnline || interval < Duration.ofSeconds(brokerConfig.getBroadcastOffsetExpireSecond()).toMillis()) { Set queueSet = broadcastTimedOffsetStore.offsetStore.queueList(); for (Integer queue : queueSet) { long offset = broadcastTimedOffsetStore.offsetStore.readOffset(queue); offset = Math.min(queueMinOffset.getOrDefault(queue, offset), offset); queueMinOffset.put(queue, offset); } } if (clientIsOnline && interval >= Duration.ofSeconds(brokerConfig.getBroadcastOffsetExpireMaxSecond()).toMillis()) { return null; } if (!clientIsOnline && interval >= Duration.ofSeconds(brokerConfig.getBroadcastOffsetExpireSecond()).toMillis()) { return null; } return broadcastTimedOffsetStore; }); } offsetStoreMap.computeIfPresent(k, (key, broadcastOffsetDataVal) -> { if (broadcastOffsetDataVal.clientOffsetStore.isEmpty()) { return null; } return broadcastOffsetDataVal; }); queueMinOffset.forEach((queueId, offset) -> this.brokerController.getConsumerOffsetManager().commitOffset("BroadcastOffset", broadcastGroupId(broadcastOffsetData.group), broadcastOffsetData.topic, queueId, offset)); } } private String buildKey(String topic, String group) { return topic + TOPIC_GROUP_SEPARATOR + group; } /** * @param group group of users * @return the groupId used to commit offset */ private static String broadcastGroupId(String group) { return group + TOPIC_GROUP_SEPARATOR + "broadcast"; } @Override public String getServiceName() { return "BroadcastOffsetManager"; } @Override public void run() { while (!this.isStopped()) { this.waitForRunning(Duration.ofSeconds(5).toMillis()); } } @Override protected void onWaitEnd() { this.scanOffsetData(); } public static class BroadcastOffsetData { private final String topic; private final String group; private final ConcurrentHashMap clientOffsetStore; public BroadcastOffsetData(String topic, String group) { this.topic = topic; this.group = group; this.clientOffsetStore = new ConcurrentHashMap<>(); } } public static class BroadcastTimedOffsetStore { /** * the timeStamp of last update occurred */ private volatile long timestamp; /** * mark the offset of this client is updated by proxy or not */ private volatile boolean fromProxy; /** * the pulled offset of each queue */ private final BroadcastOffsetStore offsetStore; public BroadcastTimedOffsetStore(boolean fromProxy) { this.timestamp = System.currentTimeMillis(); this.fromProxy = fromProxy; this.offsetStore = new BroadcastOffsetStore(); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.MixAll; public class BroadcastOffsetStore { private final ConcurrentMap offsetTable = new ConcurrentHashMap<>(); public void updateOffset(int queueId, long offset, boolean increaseOnly) { AtomicLong offsetOld = this.offsetTable.get(queueId); if (null == offsetOld) { offsetOld = this.offsetTable.putIfAbsent(queueId, new AtomicLong(offset)); } if (null != offsetOld) { if (increaseOnly) { MixAll.compareAndIncreaseOnly(offsetOld, offset); } else { offsetOld.set(offset); } } } public long readOffset(int queueId) { AtomicLong offset = this.offsetTable.get(queueId); if (offset != null) { return offset.get(); } return -1L; } public Set queueList() { return offsetTable.keySet(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import com.google.common.base.Strings; import com.google.common.collect.Maps; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ConsumerOffsetManager extends ConfigManager { protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public static final String TOPIC_GROUP_SEPARATOR = "@"; protected DataVersion dataVersion = new DataVersion(); protected ConcurrentMap> offsetTable = new ConcurrentHashMap<>(512); protected final ConcurrentMap> resetOffsetTable = new ConcurrentHashMap<>(512); private final ConcurrentMap> pullOffsetTable = new ConcurrentHashMap<>(512); protected transient BrokerController brokerController; protected final transient AtomicLong versionChangeCounter = new AtomicLong(0); public ConsumerOffsetManager() { } public ConsumerOffsetManager(BrokerController brokerController) { this.brokerController = brokerController; } public void removeConsumerOffset(String topicAtGroup) { } public void cleanOffset(String group) { Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { Entry> next = it.next(); String topicAtGroup = next.getKey(); if (topicAtGroup.contains(group)) { String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2 && group.equals(arrays[1])) { it.remove(); removeConsumerOffset(topicAtGroup); LOG.warn("Clean group's offset, {}, {}", topicAtGroup, next.getValue()); } } } } public void cleanOffsetByTopic(String topic) { Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { Entry> next = it.next(); String topicAtGroup = next.getKey(); if (topicAtGroup.contains(topic)) { String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2 && topic.equals(arrays[0])) { it.remove(); removeConsumerOffset(topicAtGroup); pullOffsetTable.remove(topicAtGroup); resetOffsetTable.remove(topicAtGroup); LOG.warn("Clean topic's offset, {}, {}", topicAtGroup, next.getValue()); } } } } public void scanUnsubscribedTopic() { Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { Entry> next = it.next(); String topicAtGroup = next.getKey(); String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2) { String topic = arrays[0]; String group = arrays[1]; if (null == brokerController.getConsumerManager().findSubscriptionData(group, topic) && this.offsetBehindMuchThanData(topic, next.getValue())) { it.remove(); removeConsumerOffset(topicAtGroup); LOG.warn("remove topic offset, {}", topicAtGroup); } } } } private boolean offsetBehindMuchThanData(final String topic, ConcurrentMap table) { Iterator> it = table.entrySet().iterator(); boolean result = !table.isEmpty(); while (it.hasNext() && result) { Entry next = it.next(); long minOffsetInStore = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, next.getKey()); long offsetInPersist = next.getValue(); result = offsetInPersist <= minOffsetInStore; } return result; } public Set whichTopicByConsumer(final String group) { Set topics = new HashSet<>(); Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { Entry> next = it.next(); String topicAtGroup = next.getKey(); String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2) { if (group.equals(arrays[1])) { topics.add(arrays[0]); } } } return topics; } public Set whichGroupByTopic(final String topic) { Set groups = new HashSet<>(); Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { Entry> next = it.next(); String topicAtGroup = next.getKey(); String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2) { if (topic.equals(arrays[0])) { groups.add(arrays[1]); } } } return groups; } public Map> getGroupTopicMap() { Map> retMap = new HashMap<>(128); for (String key : this.offsetTable.keySet()) { String[] arr = key.split(TOPIC_GROUP_SEPARATOR); if (arr.length == 2) { String topic = arr[0]; String group = arr[1]; Set topics = retMap.get(group); if (topics == null) { topics = new HashSet<>(8); retMap.put(group, topics); } topics.add(topic); } } return retMap; } public void commitOffset(final String clientHost, final String group, final String topic, final int queueId, final long offset) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; this.commitOffset(clientHost, key, queueId, offset); } private void commitOffset(final String clientHost, final String key, final int queueId, final long offset) { ConcurrentMap map = this.offsetTable.get(key); if (null == map) { map = new ConcurrentHashMap<>(2); map.put(queueId, offset); this.offsetTable.put(key, map); } else { Long storeOffset = map.put(queueId, offset); if (storeOffset != null && offset < storeOffset) { LOG.warn("[NOTIFYME]update consumer offset less than store. clientHost={}, key={}, queueId={}, requestOffset={}, storeOffset={}", clientHost, key, queueId, offset, storeOffset); } } if (versionChangeCounter.incrementAndGet() % brokerController.getBrokerConfig().getConsumerOffsetUpdateVersionStep() == 0) { updateDataVersion(); } } public void commitPullOffset(final String clientHost, final String group, final String topic, final int queueId, final long offset) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap map = this.pullOffsetTable.computeIfAbsent( key, k -> new ConcurrentHashMap<>(32)); map.put(queueId, offset); } /** * If the target queue has temporary reset offset, return the reset-offset. * Otherwise, return the current consume offset in the offset store. * @param group Consumer group * @param topic Topic * @param queueId Queue ID * @return current consume offset or reset offset if there were one. */ public long queryOffset(final String group, final String topic, final int queueId) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; if (this.brokerController.getBrokerConfig().isUseServerSideResetOffset()) { Map reset = resetOffsetTable.get(key); if (null != reset && reset.containsKey(queueId)) { return reset.get(queueId); } } ConcurrentMap map = this.offsetTable.get(key); if (null != map) { Long offset = map.get(queueId); if (offset != null) { return offset; } } return -1L; } /** * Query pull offset in pullOffsetTable * @param group Consumer group * @param topic Topic * @param queueId Queue ID * @return latest pull offset of consumer group */ public long queryPullOffset(final String group, final String topic, final int queueId) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; Long offset = null; ConcurrentMap map = this.pullOffsetTable.get(key); if (null != map) { offset = map.get(queueId); } if (offset == null) { offset = queryOffset(group, topic, queueId); } return offset; } public void clearPullOffset(final String group, final String topic) { this.pullOffsetTable.remove(topic + TOPIC_GROUP_SEPARATOR + group); } @Override public String encode() { return this.encode(false); } @Override public String configFilePath() { return BrokerPathConfigHelper.getConsumerOffsetPath(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { ConsumerOffsetManager obj = RemotingSerializable.fromJson(jsonString, ConsumerOffsetManager.class); if (obj != null) { this.setOffsetTable(obj.getOffsetTable()); this.dataVersion = obj.dataVersion; } } } @Override public String encode(final boolean prettyFormat) { return RemotingSerializable.toJson(this, prettyFormat); } public ConcurrentMap> getOffsetTable() { return offsetTable; } public void setOffsetTable(ConcurrentMap> offsetTable) { this.offsetTable = offsetTable; } public ConcurrentMap> getPullOffsetTable() { return pullOffsetTable; } public Map queryMinOffsetInAllGroup(final String topic, final String filterGroups) { Map queueMinOffset = new HashMap<>(); Set topicGroups = this.offsetTable.keySet(); if (!UtilAll.isBlank(filterGroups)) { for (String group : filterGroups.split(",")) { Iterator it = topicGroups.iterator(); while (it.hasNext()) { String topicAtGroup = it.next(); if (group.equals(topicAtGroup.split(TOPIC_GROUP_SEPARATOR)[1])) { it.remove(); removeConsumerOffset(topicAtGroup); } } } } for (Map.Entry> offSetEntry : this.offsetTable.entrySet()) { String topicGroup = offSetEntry.getKey(); String[] topicGroupArr = topicGroup.split(TOPIC_GROUP_SEPARATOR); if (topic.equals(topicGroupArr[0])) { for (Entry entry : offSetEntry.getValue().entrySet()) { long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, entry.getKey()); if (entry.getValue() >= minOffset) { Long offset = queueMinOffset.get(entry.getKey()); if (offset == null) { queueMinOffset.put(entry.getKey(), Math.min(Long.MAX_VALUE, entry.getValue())); } else { queueMinOffset.put(entry.getKey(), Math.min(entry.getValue(), offset)); } } } } } return queueMinOffset; } public Map queryOffset(final String group, final String topic) { // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; return this.offsetTable.get(key); } public void cloneOffset(final String srcGroup, final String destGroup, final String topic) { ConcurrentMap offsets = this.offsetTable.get(topic + TOPIC_GROUP_SEPARATOR + srcGroup); if (offsets != null) { this.offsetTable.put(topic + TOPIC_GROUP_SEPARATOR + destGroup, new ConcurrentHashMap<>(offsets)); } } public DataVersion getDataVersion() { return dataVersion; } public void updateDataVersion() { long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; dataVersion.nextVersion(stateMachineVersion); } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } public boolean loadDataVersion() { String fileName = null; try { fileName = this.configFilePath(); String jsonString = MixAll.file2String(fileName); if (jsonString != null) { ConsumerOffsetManager obj = RemotingSerializable.fromJson(jsonString, ConsumerOffsetManager.class); if (obj != null) { this.dataVersion = obj.dataVersion; } LOG.info("load consumer offset dataVersion success,{},{} ", fileName, jsonString); } return true; } catch (Exception e) { LOG.error("load consumer offset dataVersion failed " + fileName, e); return false; } } public void removeOffset(final String group) { Function>>, Boolean> deleteFunction = it -> { boolean removed = false; while (it.hasNext()) { Entry> entry = it.next(); String topicAtGroup = entry.getKey(); if (topicAtGroup.contains(group)) { String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2 && group.equals(arrays[1])) { it.remove(); removeConsumerOffset(topicAtGroup); removed = true; } } } return removed; }; boolean clearOffset = deleteFunction.apply(this.offsetTable.entrySet().iterator()); boolean clearReset = deleteFunction.apply(this.resetOffsetTable.entrySet().iterator()); boolean clearPull = deleteFunction.apply(this.pullOffsetTable.entrySet().iterator()); LOG.info("Consumer offset manager clean group offset, groupName={}, " + "offsetTable={}, resetOffsetTable={}, pullOffsetTable={}", group, clearOffset, clearReset, clearPull); } public void assignResetOffset(String topic, String group, int queueId, long offset) { if (Strings.isNullOrEmpty(topic) || Strings.isNullOrEmpty(group) || queueId < 0 || offset < 0) { LOG.warn("Illegal arguments when assigning reset offset. Topic={}, group={}, queueId={}, offset={}", topic, group, queueId, offset); return; } String key = topic + TOPIC_GROUP_SEPARATOR + group; resetOffsetTable.computeIfAbsent(key, k -> Maps.newConcurrentMap()).put(queueId, offset); LOG.debug("Reset offset OK. Topic={}, group={}, queueId={}, resetOffset={}", topic, group, queueId, offset); // Two things are important here: // 1, currentOffsetMap might be null if there is no previous records; // 2, Our overriding here may get overridden by the client instantly in concurrent cases; But it still makes // sense in cases like clients are offline. offsetTable.computeIfAbsent(key, k -> Maps.newConcurrentMap()).put(queueId, offset); } public boolean hasOffsetReset(String topic, String group, int queueId) { String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap map = resetOffsetTable.get(key); if (null == map) { return false; } return map.containsKey(queueId); } public Long queryThenEraseResetOffset(String topic, String group, Integer queueId) { String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap map = resetOffsetTable.get(key); if (null == map) { return null; } else { return map.remove(queueId); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.google.common.base.Strings; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class LmqConsumerOffsetManager extends ConsumerOffsetManager { private ConcurrentHashMap lmqOffsetTable = new ConcurrentHashMap<>(512); public LmqConsumerOffsetManager() { } public LmqConsumerOffsetManager(BrokerController brokerController) { super(brokerController); } @Override public long queryOffset(final String group, final String topic, final int queueId) { if (!MixAll.isLmq(group)) { return super.queryOffset(group, topic, queueId); } // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; Long offset = lmqOffsetTable.get(key); if (offset != null) { return offset; } return -1; } @Override public Map queryOffset(final String group, final String topic) { if (!MixAll.isLmq(group)) { return super.queryOffset(group, topic); } Map map = new HashMap<>(); // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; Long offset = lmqOffsetTable.get(key); if (offset != null) { map.put(0, offset); } return map; } @Override public void commitOffset(final String clientHost, final String group, final String topic, final int queueId, final long offset) { if (!MixAll.isLmq(group)) { super.commitOffset(clientHost, group, topic, queueId, offset); return; } // topic@group String key = topic + TOPIC_GROUP_SEPARATOR + group; lmqOffsetTable.put(key, offset); } @Override public String encode() { return this.encode(false); } @Override public String configFilePath() { return BrokerPathConfigHelper.getLmqConsumerOffsetPath(brokerController.getMessageStoreConfig().getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { LmqConsumerOffsetManager obj = RemotingSerializable.fromJson(jsonString, LmqConsumerOffsetManager.class); if (obj != null) { super.setOffsetTable(obj.getOffsetTable()); this.lmqOffsetTable = obj.lmqOffsetTable; } } } @Override public String encode(final boolean prettyFormat) { return RemotingSerializable.toJson(this, prettyFormat); } public ConcurrentHashMap getLmqOffsetTable() { return lmqOffsetTable; } public void setLmqOffsetTable(ConcurrentHashMap lmqOffsetTable) { this.lmqOffsetTable = lmqOffsetTable; } @Override public void removeOffset(String group) { if (!MixAll.isLmq(group)) { super.removeOffset(group); return; } Iterator> it = this.lmqOffsetTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); String topicAtGroup = next.getKey(); if (topicAtGroup.contains(group)) { String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2 && group.equals(arrays[1])) { it.remove(); removeConsumerOffset(topicAtGroup); LOG.warn("clean lmq group offset {}", topicAtGroup); } } } } @Override public void assignResetOffset(String topic, String group, int queueId, long offset) { if (Strings.isNullOrEmpty(topic) || Strings.isNullOrEmpty(group) || queueId < 0 || offset < 0) { LOG.warn("Illegal arguments when assigning reset offset. Topic={}, group={}, queueId={}, offset={}", topic, group, queueId, offset); return; } if (!MixAll.isLmq(topic) || !MixAll.isLmq(group)) { super.assignResetOffset(topic, group, queueId, offset); return; } String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap map = resetOffsetTable.get(key); if (null == map) { map = new ConcurrentHashMap<>(); ConcurrentMap previous = resetOffsetTable.putIfAbsent(key, map); if (null != previous) { map = previous; } } map.put(queueId, offset); lmqOffsetTable.computeIfPresent(key, (k, oldValue) -> offset); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/offset/MemoryConsumerOrderInfoManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.pop.orderly.QueueLevelConsumerManager; /** * Memory-based Consumer Order Information Manager for Lite Topics * Trade-off considerations:: * 1. Lite Topics are primarily used for lightweight consumption where * strict ordering requirements are relatively low * 2. Considering compatibility with traditional PushConsumer, * a certain degree of ordering control failure is acceptable * 3. Avoiding I/O overhead from persistence operations *

* We may make structural adjustments and optimizations to reduce overhead and memory footprint. */ public class MemoryConsumerOrderInfoManager extends QueueLevelConsumerManager { public MemoryConsumerOrderInfoManager(BrokerController brokerController) { super(brokerController); } @Override protected void updateLockFreeTimestamp(String topic, String group, int queueId, OrderInfo orderInfo) { if (this.getConsumerOrderInfoLockManager() != null) { // use max lock free time to prevent unexpected blocking this.getConsumerOrderInfoLockManager().updateLockFreeTimestamp( topic, group, queueId, orderInfo.getMaxLockFreeTimestamp()); } } @Override public void persist() { // MemoryConsumerOrderInfoManager persist, do nothing. } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.out; import com.alibaba.fastjson2.JSON; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.LockCallback; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UnlockCallback; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.namesrv.DefaultTopAddressing; import org.apache.rocketmq.common.namesrv.TopAddressing; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.BrokerSyncInfo; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.ConsumerOffsetSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.ElectMasterResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetBrokerMemberGroupResponseBody; import org.apache.rocketmq.remoting.protocol.body.KVTable; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.LockBatchResponseBody; import org.apache.rocketmq.remoting.protocol.body.MessageRequestModeSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.RegisterBrokerBody; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetBrokerMemberGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.LockBatchMqRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UnlockBatchMqRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.BrokerHeartbeatRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.QueryDataVersionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.QueryDataVersionResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.UnRegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.rpc.ClientMetadata; import org.apache.rocketmq.remoting.rpc.RpcClient; import org.apache.rocketmq.remoting.rpc.RpcClientImpl; import org.apache.rocketmq.remoting.rpchook.DynamicalExtFieldRPCHook; import org.apache.rocketmq.store.timer.TimerCheckpoint; import org.apache.rocketmq.store.timer.TimerMetrics; import static org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode.SUCCESS; import static org.apache.rocketmq.remoting.protocol.ResponseCode.CONTROLLER_MASTER_STILL_EXIST; public class BrokerOuterAPI { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final RemotingClient remotingClient; private final TopAddressing topAddressing = new DefaultTopAddressing(MixAll.getWSAddr()); private final ExecutorService brokerOuterExecutor = ThreadUtils.newThreadPoolExecutor(4, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("brokerOutApi_thread_", true)); private final ClientMetadata clientMetadata; private final RpcClient rpcClient; private String nameSrvAddr = null; public BrokerOuterAPI(final NettyClientConfig nettyClientConfig, AuthConfig authConfig) { this(nettyClientConfig, authConfig, new DynamicalExtFieldRPCHook(), new ClientMetadata()); } private BrokerOuterAPI(final NettyClientConfig nettyClientConfig, AuthConfig authConfig, RPCHook rpcHook, ClientMetadata clientMetadata) { this.remotingClient = new NettyRemotingClient(nettyClientConfig); this.clientMetadata = clientMetadata; this.remotingClient.registerRPCHook(rpcHook); this.remotingClient.registerRPCHook(newAclRPCHook(authConfig)); this.rpcClient = new RpcClientImpl(this.clientMetadata, this.remotingClient); } private RPCHook newAclRPCHook(AuthConfig config) { if (config == null || StringUtils.isBlank(config.getInnerClientAuthenticationCredentials())) { return null; } SessionCredentials sessionCredentials = JSON.parseObject(config.getInnerClientAuthenticationCredentials(), SessionCredentials.class); if (StringUtils.isBlank(sessionCredentials.getAccessKey()) || StringUtils.isBlank(sessionCredentials.getSecretKey())) { return null; } return new AclClientRPCHook(sessionCredentials); } public void start() { this.remotingClient.start(); } public void shutdown() { this.remotingClient.shutdown(); this.brokerOuterExecutor.shutdown(); } public List getNameServerAddressList() { return this.remotingClient.getNameServerAddressList(); } public String fetchNameServerAddr() { try { String addrs = this.topAddressing.fetchNSAddr(); if (!UtilAll.isBlank(addrs)) { if (!addrs.equals(this.nameSrvAddr)) { LOGGER.info("name server address changed, old: {} new: {}", this.nameSrvAddr, addrs); this.updateNameServerAddressList(addrs); this.nameSrvAddr = addrs; return nameSrvAddr; } } } catch (Exception e) { LOGGER.error("fetchNameServerAddr Exception", e); } return nameSrvAddr; } public List dnsLookupAddressByDomain(String domain) { List addressList = new ArrayList<>(); try { java.security.Security.setProperty("networkaddress.cache.ttl", "10"); int index = domain.indexOf(":"); String portStr = domain.substring(index); String domainStr = domain.substring(0, index); InetAddress[] addresses = InetAddress.getAllByName(domainStr); for (InetAddress address : addresses) { addressList.add(address.getHostAddress() + portStr); } LOGGER.info("dns lookup address by domain success, domain={}, result={}", domain, addressList); } catch (Exception e) { LOGGER.error("dns lookup address by domain error, domain={}", domain, e); } return addressList; } public boolean checkAddressReachable(String address) { return this.remotingClient.isAddressReachable(address); } public void updateNameServerAddressList(final String addrs) { String[] addrArray = addrs.split(";"); List lst = new ArrayList(Arrays.asList(addrArray)); this.remotingClient.updateNameServerAddressList(lst); } public void updateNameServerAddressListByDnsLookup(final String domain) { List lst = this.dnsLookupAddressByDomain(domain); this.remotingClient.updateNameServerAddressList(lst); } public BrokerMemberGroup syncBrokerMemberGroup(String clusterName, String brokerName) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { return syncBrokerMemberGroup(clusterName, brokerName, false); } public BrokerMemberGroup syncBrokerMemberGroup(String clusterName, String brokerName, boolean isCompatibleWithOldNameSrv) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { if (isCompatibleWithOldNameSrv) { return getBrokerMemberGroupCompatible(clusterName, brokerName); } else { return getBrokerMemberGroup(clusterName, brokerName); } } public BrokerMemberGroup getBrokerMemberGroup(String clusterName, String brokerName) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { BrokerMemberGroup brokerMemberGroup = new BrokerMemberGroup(clusterName, brokerName); GetBrokerMemberGroupRequestHeader requestHeader = new GetBrokerMemberGroupRequestHeader(); requestHeader.setClusterName(clusterName); requestHeader.setBrokerName(brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_MEMBER_GROUP, requestHeader); RemotingCommand response = null; response = this.remotingClient.invokeSync(null, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { GetBrokerMemberGroupResponseBody brokerMemberGroupResponseBody = GetBrokerMemberGroupResponseBody.decode(body, GetBrokerMemberGroupResponseBody.class); return brokerMemberGroupResponseBody.getBrokerMemberGroup(); } } default: break; } return brokerMemberGroup; } public BrokerMemberGroup getBrokerMemberGroupCompatible(String clusterName, String brokerName) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { BrokerMemberGroup brokerMemberGroup = new BrokerMemberGroup(clusterName, brokerName); GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic(TopicValidator.SYNC_BROKER_MEMBER_GROUP_PREFIX + brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader); RemotingCommand response; response = this.remotingClient.invokeSync(null, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicRouteData topicRouteData = TopicRouteData.decode(body, TopicRouteData.class); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { if (brokerData != null && brokerData.getBrokerName().equals(brokerName) && brokerData.getCluster().equals(clusterName)) { brokerMemberGroup.getBrokerAddrs().putAll(brokerData.getBrokerAddrs()); break; } } return brokerMemberGroup; } } default: break; } return brokerMemberGroup; } public void sendHeartbeatViaDataVersion( final String clusterName, final String brokerAddr, final String brokerName, final Long brokerId, final int timeoutMillis, final DataVersion dataVersion, final boolean isInBrokerContainer) { List nameServerAddressList = this.remotingClient.getAvailableNameSrvList(); if (nameServerAddressList != null && !nameServerAddressList.isEmpty()) { final QueryDataVersionRequestHeader requestHeader = new QueryDataVersionRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerName(brokerName); requestHeader.setBrokerId(brokerId); requestHeader.setClusterName(clusterName); for (final String namesrvAddr : nameServerAddressList) { brokerOuterExecutor.execute(() -> { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_DATA_VERSION, requestHeader); request.setBody(dataVersion.encode()); try { BrokerOuterAPI.this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMillis); } catch (Exception e) { LOGGER.error("sendHeartbeat Exception " + namesrvAddr, e); } }); } } } public void sendHeartbeat(final String clusterName, final String brokerAddr, final String brokerName, final Long brokerId, final int timeoutMills, final boolean isInBrokerContainer) { List nameServerAddressList = this.remotingClient.getAvailableNameSrvList(); final BrokerHeartbeatRequestHeader requestHeader = new BrokerHeartbeatRequestHeader(); requestHeader.setClusterName(clusterName); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerName(brokerName); if (nameServerAddressList != null && nameServerAddressList.size() > 0) { for (final String namesrvAddr : nameServerAddressList) { brokerOuterExecutor.execute(new Runnable() { @Override public void run() { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BROKER_HEARTBEAT, requestHeader); try { BrokerOuterAPI.this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills); } catch (Exception e) { LOGGER.error("sendHeartbeat Exception " + namesrvAddr, e); } } }); } } } public BrokerSyncInfo retrieveBrokerHaInfo(String masterBrokerAddr) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException, RemotingCommandException { ExchangeHAInfoRequestHeader requestHeader = new ExchangeHAInfoRequestHeader(); requestHeader.setMasterHaAddress(null); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.EXCHANGE_BROKER_HA_INFO, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(masterBrokerAddr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { ExchangeHAInfoResponseHeader responseHeader = response.decodeCommandCustomHeader(ExchangeHAInfoResponseHeader.class); return new BrokerSyncInfo(responseHeader.getMasterHaAddress(), responseHeader.getMasterFlushOffset(), responseHeader.getMasterAddress()); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void sendBrokerHaInfo(String brokerAddr, String masterHaAddr, long brokerInitMaxOffset, String masterAddr) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { ExchangeHAInfoRequestHeader requestHeader = new ExchangeHAInfoRequestHeader(); requestHeader.setMasterHaAddress(masterHaAddr); requestHeader.setMasterFlushOffset(brokerInitMaxOffset); requestHeader.setMasterAddress(masterAddr); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.EXCHANGE_BROKER_HA_INFO, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public List registerBrokerAll( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final String haServerAddr, final TopicConfigSerializeWrapper topicConfigWrapper, final List filterServerList, final boolean oneway, final int timeoutMills, final boolean enableActingMaster, final boolean compressed, final BrokerIdentity brokerIdentity) { return registerBrokerAll(clusterName, brokerAddr, brokerName, brokerId, haServerAddr, topicConfigWrapper, filterServerList, oneway, timeoutMills, enableActingMaster, compressed, null, brokerIdentity); } /** * Considering compression brings much CPU overhead to name server, stream API will not support compression and * compression feature is deprecated. * * @param clusterName * @param brokerAddr * @param brokerName * @param brokerId * @param haServerAddr * @param topicConfigWrapper * @param filterServerList * @param oneway * @param timeoutMills * @param compressed default false * @return */ public List registerBrokerAll( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final String haServerAddr, final TopicConfigSerializeWrapper topicConfigWrapper, final List filterServerList, final boolean oneway, final int timeoutMills, final boolean enableActingMaster, final boolean compressed, final Long heartbeatTimeoutMillis, final BrokerIdentity brokerIdentity) { final List registerBrokerResultList = new CopyOnWriteArrayList<>(); List nameServerAddressList = this.remotingClient.getAvailableNameSrvList(); if (nameServerAddressList != null && nameServerAddressList.size() > 0) { final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerId(brokerId); requestHeader.setBrokerName(brokerName); requestHeader.setClusterName(clusterName); requestHeader.setHaServerAddr(haServerAddr); requestHeader.setEnableActingMaster(enableActingMaster); requestHeader.setCompressed(false); if (heartbeatTimeoutMillis != null) { requestHeader.setHeartbeatTimeoutMillis(heartbeatTimeoutMillis); } RegisterBrokerBody requestBody = new RegisterBrokerBody(); requestBody.setTopicConfigSerializeWrapper(TopicConfigAndMappingSerializeWrapper.from(topicConfigWrapper)); requestBody.setFilterServerList(filterServerList); final byte[] body = requestBody.encode(compressed); final int bodyCrc32 = UtilAll.crc32(body); requestHeader.setBodyCrc32(bodyCrc32); final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size()); for (final String namesrvAddr : nameServerAddressList) { brokerOuterExecutor.execute(new Runnable() { @Override public void run() { try { RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body); if (result != null) { registerBrokerResultList.add(result); } LOGGER.info("Registering current broker to name server completed. TargetHost={}", namesrvAddr); } catch (Exception e) { LOGGER.error("Failed to register current broker to name server. TargetHost={}", namesrvAddr, e); } finally { countDownLatch.countDown(); } } }); } try { if (!countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS)) { LOGGER.warn("Registration to one or more name servers does NOT complete within deadline. Timeout threshold: {}ms", timeoutMills); } } catch (InterruptedException ignore) { } } return registerBrokerResultList; } private RegisterBrokerResult registerBroker( final String namesrvAddr, final boolean oneway, final int timeoutMills, final RegisterBrokerRequestHeader requestHeader, final byte[] body ) throws RemotingCommandException, MQBrokerException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader); request.setBody(body); if (oneway) { try { this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills); } catch (RemotingTooMuchRequestException e) { // Ignore } return null; } RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { RegisterBrokerResponseHeader responseHeader = response.decodeCommandCustomHeader(RegisterBrokerResponseHeader.class); RegisterBrokerResult result = new RegisterBrokerResult(); result.setMasterAddr(responseHeader.getMasterAddr()); result.setHaServerAddr(responseHeader.getHaServerAddr()); if (response.getBody() != null) { result.setKvTable(KVTable.decode(response.getBody(), KVTable.class)); } return result; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), requestHeader == null ? null : requestHeader.getBrokerAddr()); } public void unregisterBrokerAll( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId ) { List nameServerAddressList = this.remotingClient.getNameServerAddressList(); if (nameServerAddressList != null) { for (String namesrvAddr : nameServerAddressList) { try { this.unregisterBroker(namesrvAddr, clusterName, brokerAddr, brokerName, brokerId); LOGGER.info("unregisterBroker OK, NamesrvAddr: {}", namesrvAddr); } catch (Exception e) { LOGGER.warn("unregisterBroker Exception, NamesrvAddr: {}", namesrvAddr, e); } } } } public void unregisterBroker( final String namesrvAddr, final String clusterName, final String brokerAddr, final String brokerName, final long brokerId ) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { UnRegisterBrokerRequestHeader requestHeader = new UnRegisterBrokerRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerId(brokerId); requestHeader.setBrokerName(brokerName); requestHeader.setClusterName(clusterName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_BROKER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr); } /** * Register the topic route info of single topic to all name server nodes. * This method is used to replace incremental broker registration feature. */ public void registerSingleTopicAll( final String brokerName, final TopicConfig topicConfig, final int timeoutMills) { String topic = topicConfig.getTopicName(); RegisterTopicRequestHeader requestHeader = new RegisterTopicRequestHeader(); requestHeader.setTopic(topic); TopicRouteData topicRouteData = new TopicRouteData(); List queueDatas = new ArrayList<>(); topicRouteData.setQueueDatas(queueDatas); final QueueData queueData = new QueueData(); queueData.setBrokerName(brokerName); queueData.setPerm(topicConfig.getPerm()); queueData.setReadQueueNums(topicConfig.getReadQueueNums()); queueData.setWriteQueueNums(topicConfig.getWriteQueueNums()); queueData.setTopicSysFlag(topicConfig.getTopicSysFlag()); queueDatas.add(queueData); final byte[] topicRouteBody = topicRouteData.encode(); List nameServerAddressList = this.remotingClient.getNameServerAddressList(); final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size()); for (final String namesrvAddr : nameServerAddressList) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_TOPIC_IN_NAMESRV, requestHeader); request.setBody(topicRouteBody); try { brokerOuterExecutor.execute(() -> { try { RemotingCommand response = BrokerOuterAPI.this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills); assert response != null; LOGGER.info("Register single topic {} to broker {} with response code {}", topic, brokerName, response.getCode()); } catch (Exception e) { LOGGER.warn("Register single topic {} to broker {} exception", topic, brokerName, e); } finally { countDownLatch.countDown(); } }); } catch (Exception e) { LOGGER.warn("Execute single topic registration task failed, topic {}, broker name {}", topic, brokerName); countDownLatch.countDown(); } } try { if (!countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS)) { LOGGER.warn("Registration single topic to one or more name servers timeout. Timeout threshold: {}ms", timeoutMills); } } catch (InterruptedException ignore) { } } public List needRegister( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final TopicConfigSerializeWrapper topicConfigWrapper, final int timeoutMills, final boolean isInBrokerContainer) { final List changedList = new CopyOnWriteArrayList<>(); List nameServerAddressList = this.remotingClient.getNameServerAddressList(); if (nameServerAddressList != null && nameServerAddressList.size() > 0) { final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size()); for (final String namesrvAddr : nameServerAddressList) { brokerOuterExecutor.execute(new Runnable() { @Override public void run() { try { QueryDataVersionRequestHeader requestHeader = new QueryDataVersionRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerId(brokerId); requestHeader.setBrokerName(brokerName); requestHeader.setClusterName(clusterName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_DATA_VERSION, requestHeader); request.setBody(topicConfigWrapper.getDataVersion().encode()); RemotingCommand response = remotingClient.invokeSync(namesrvAddr, request, timeoutMills); DataVersion nameServerDataVersion = null; Boolean changed = false; switch (response.getCode()) { case ResponseCode.SUCCESS: { QueryDataVersionResponseHeader queryDataVersionResponseHeader = response.decodeCommandCustomHeader(QueryDataVersionResponseHeader.class); changed = queryDataVersionResponseHeader.getChanged(); byte[] body = response.getBody(); if (body != null) { nameServerDataVersion = DataVersion.decode(body, DataVersion.class); if (!topicConfigWrapper.getDataVersion().equals(nameServerDataVersion)) { changed = true; } } if (changed == null || changed) { changedList.add(Boolean.TRUE); } } default: break; } LOGGER.warn("Query data version from name server {} OK, changed {}, broker {}, name server {}", namesrvAddr, changed, topicConfigWrapper.getDataVersion(), nameServerDataVersion == null ? "" : nameServerDataVersion); } catch (Exception e) { changedList.add(Boolean.TRUE); LOGGER.error("Query data version from name server {} exception", namesrvAddr, e); } finally { countDownLatch.countDown(); } } }); } try { countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { LOGGER.error("query dataversion from nameserver countDownLatch await Exception", e); } } return changedList; } public TopicConfigAndMappingSerializeWrapper getAllTopicConfig(final String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, RemotingCommandException { DataVersion topicConfigDataVersion = null; DataVersion mappingDataVersion = null; long timeoutMills = getTimeoutMillis(); int topicSeq = 0; long beginTime = System.nanoTime(); ConcurrentHashMap topicConfigTable = new ConcurrentHashMap<>(); Map topicQueueMappingDetailMap = new ConcurrentHashMap<>(); while (true) { long leftTime = timeoutMills - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginTime); if (leftTime < 0) { throw new RemotingTimeoutException("invokeSync call timeout"); } GetAllTopicConfigRequestHeader requestHeader = new GetAllTopicConfigRequestHeader(); requestHeader.setTopicSeq(topicSeq); requestHeader.setMaxTopicNum(getMaxPageSize()); requestHeader.setDataVersion(Optional.ofNullable(topicConfigDataVersion). map(DataVersion::toJson).orElse(StringUtils.EMPTY)); LOGGER.info("getAllTopicConfig from seq {}, max {}, dataVersion {}", topicSeq, requestHeader.getMaxTopicNum(), requestHeader.getDataVersion()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, requestHeader); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(true, addr), request, 30000); assert response != null; if (response.getCode() == SUCCESS) { TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper = TopicConfigAndMappingSerializeWrapper.decode(response.getBody(), TopicConfigAndMappingSerializeWrapper.class); topicConfigTable.putAll(topicConfigSerializeWrapper.getTopicConfigTable()); topicQueueMappingDetailMap.putAll(topicConfigSerializeWrapper.getTopicQueueMappingDetailMap()); topicSeq += topicConfigSerializeWrapper.getTopicConfigTable().size(); DataVersion newDataVersion = topicConfigSerializeWrapper.getDataVersion(); if (topicConfigDataVersion == null) { // fill dataVersion before break the loop to compatible with old version server topicConfigDataVersion = newDataVersion; mappingDataVersion = topicConfigSerializeWrapper.getMappingDataVersion(); } GetAllTopicConfigResponseHeader responseHeader = response.decodeCommandCustomHeader(GetAllTopicConfigResponseHeader.class); Integer totalTopicNum = Optional.ofNullable(responseHeader) .map(GetAllTopicConfigResponseHeader::getTotalTopicNum).orElse(null); if (Objects.isNull(totalTopicNum)) { // compatible with old version server // the server side don't support totalTopicNum, all data is returned break; } if (!Objects.equals(topicConfigDataVersion, newDataVersion)) { LOGGER.error("dataVersion changed, currentDataVersion: {}, newDataVersion: {}", topicConfigDataVersion, newDataVersion); topicConfigDataVersion = newDataVersion; mappingDataVersion = topicConfigSerializeWrapper.getMappingDataVersion(); topicSeq = 0; topicConfigTable.clear(); continue; } if (topicSeq >= totalTopicNum - 1) { LOGGER.info("get all topic config, totalTopicNum: {}", totalTopicNum); break; } } else { throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(topicConfigDataVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigTable); topicConfigSerializeWrapper.setMappingDataVersion(mappingDataVersion); topicConfigSerializeWrapper.setTopicQueueMappingDetailMap(topicQueueMappingDetailMap); return topicConfigSerializeWrapper; } public TimerCheckpoint getTimerCheckPoint( final String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TIMER_CHECK_POINT, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(true, addr), request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return TimerCheckpoint.decode(ByteBuffer.wrap(response.getBody())); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public TimerMetrics.TimerMetricsSerializeWrapper getTimerMetrics( final String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TIMER_METRICS, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(true, addr), request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return TimerMetrics.TimerMetricsSerializeWrapper.decode(response.getBody(), TimerMetrics.TimerMetricsSerializeWrapper.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public ConsumerOffsetSerializeWrapper getAllConsumerOffset( final String addr) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_CONSUMER_OFFSET, null); RemotingCommand response = this.remotingClient.invokeSync(addr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return ConsumerOffsetSerializeWrapper.decode(response.getBody(), ConsumerOffsetSerializeWrapper.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public String getAllDelayOffset( final String addr) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException, UnsupportedEncodingException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_DELAY_OFFSET, null); RemotingCommand response = this.remotingClient.invokeSync(addr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return new String(response.getBody(), MixAll.DEFAULT_CHARSET); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public SubscriptionGroupWrapper getAllSubscriptionGroupConfig(final String addr) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException, RemotingCommandException { long timeoutMills = getTimeoutMillis(); DataVersion currentDataVersion = null; int groupSeq = 0; long beginTime = System.nanoTime(); ConcurrentMap subscriptionGroupTable = new ConcurrentHashMap<>(); ConcurrentMap> forbiddenTable = new ConcurrentHashMap<>(); while (true) { long leftTime = timeoutMills - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginTime); if (leftTime < 0) { throw new RemotingTimeoutException("invokeSync call timeout"); } GetAllSubscriptionGroupRequestHeader requestHeader = new GetAllSubscriptionGroupRequestHeader(); requestHeader.setGroupSeq(groupSeq); requestHeader.setMaxGroupNum(getMaxPageSize()); requestHeader.setDataVersion(Optional.ofNullable(currentDataVersion) .map(DataVersion::toJson).orElse(StringUtils.EMPTY)); LOGGER.info("getAllSubscriptionGroup from seq {}, max {}, dataVersion {}", groupSeq, requestHeader.getMaxGroupNum(), requestHeader.getDataVersion()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, 30000); assert response != null; if (response.getCode() == SUCCESS) { SubscriptionGroupWrapper subscriptionGroupWrapper = SubscriptionGroupWrapper.decode(response.getBody(), SubscriptionGroupWrapper.class); subscriptionGroupTable.putAll(subscriptionGroupWrapper.getSubscriptionGroupTable()); forbiddenTable.putAll(subscriptionGroupWrapper.getForbiddenTable()); DataVersion newDataVersion = subscriptionGroupWrapper.getDataVersion(); if (currentDataVersion == null) { // fill dataVersion before break the loop to compatible with old version server currentDataVersion = newDataVersion; } groupSeq += subscriptionGroupWrapper.getSubscriptionGroupTable().size(); GetAllSubscriptionGroupResponseHeader responseHeader = response.decodeCommandCustomHeader(GetAllSubscriptionGroupResponseHeader.class); Integer totalGroupNum = Optional.ofNullable(responseHeader) .map(GetAllSubscriptionGroupResponseHeader::getTotalGroupNum).orElse(null); if (Objects.isNull(totalGroupNum)) { // the server side don't support totalGroupNum, all data is returned break; } if (!Objects.equals(currentDataVersion, newDataVersion)) { LOGGER.error("dataVersion changed, currentDataVersion: {}, newDataVersion: {}", currentDataVersion, newDataVersion); currentDataVersion = newDataVersion; groupSeq = 0; subscriptionGroupTable.clear(); forbiddenTable.clear(); continue; } if (groupSeq >= totalGroupNum - 1) { LOGGER.info("get all subscription group config, totalGroupNum: {}", totalGroupNum); break; } } else { throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } SubscriptionGroupWrapper allSubscriptionGroup = new SubscriptionGroupWrapper(); allSubscriptionGroup.setSubscriptionGroupTable(subscriptionGroupTable); allSubscriptionGroup.setForbiddenTable(forbiddenTable); allSubscriptionGroup.setDataVersion(currentDataVersion); return allSubscriptionGroup; } public void registerRPCHook(RPCHook rpcHook) { remotingClient.registerRPCHook(rpcHook); } public void clearRPCHook() { remotingClient.clearRPCHook(); } public long getMaxOffset(final String addr, final String topic, final int queueId, final boolean committed, final boolean isOnlyThisBroker) throws RemotingException, MQBrokerException, InterruptedException { GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setCommitted(committed); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MAX_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { GetMaxOffsetResponseHeader responseHeader = response.decodeCommandCustomHeader(GetMaxOffsetResponseHeader.class); return responseHeader.getOffset(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public long getMinOffset(final String addr, final String topic, final int queueId, final boolean isOnlyThisBroker) throws RemotingException, MQBrokerException, InterruptedException { GetMinOffsetRequestHeader requestHeader = new GetMinOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MIN_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { GetMinOffsetResponseHeader responseHeader = response.decodeCommandCustomHeader(GetMinOffsetResponseHeader.class); return responseHeader.getOffset(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void lockBatchMQAsync( final String addr, final LockBatchRequestBody requestBody, final long timeoutMillis, final LockCallback callback) throws RemotingException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, new LockBatchMqRequestHeader()); request.setBody(requestBody.encode()); this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { if (callback == null) { return; } if (response.getCode() == ResponseCode.SUCCESS) { LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class); Set messageQueues = responseBody.getLockOKMQSet(); callback.onSuccess(messageQueues); } else { callback.onException(new MQBrokerException(response.getCode(), response.getRemark())); } } @Override public void operationFail(Throwable throwable) { if (callback == null) { return; } callback.onException(throwable); } }); } public void unlockBatchMQAsync( final String addr, final UnlockBatchRequestBody requestBody, final long timeoutMillis, final UnlockCallback callback) throws RemotingException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNLOCK_BATCH_MQ, new UnlockBatchMqRequestHeader()); request.setBody(requestBody.encode()); this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { if (callback == null) { return; } if (response.getCode() == ResponseCode.SUCCESS) { callback.onSuccess(); } else { callback.onException(new MQBrokerException(response.getCode(), response.getRemark())); } } @Override public void operationFail(Throwable throwable) { if (callback == null) { return; } callback.onException(throwable); } }); } public RemotingClient getRemotingClient() { return this.remotingClient; } public SendResult sendMessageToSpecificBroker(String brokerAddr, final String brokerName, final MessageExt msg, String group, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = buildSendMessageRequest(msg, group); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, timeoutMillis); return this.processSendResponse(brokerName, msg, response); } public CompletableFuture sendMessageToSpecificBrokerAsync(String brokerAddr, final String brokerName, final MessageExt msg, String group, long timeoutMillis) { RemotingCommand request = buildSendMessageRequest(msg, group); CompletableFuture cf = new CompletableFuture<>(); final String msgId = msg.getMsgId(); try { this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { SendResult sendResult = processSendResponse(brokerName, msg, response); cf.complete(sendResult); } catch (MQBrokerException | RemotingCommandException e) { LOGGER.error("processSendResponse in sendMessageToSpecificBrokerAsync failed, msgId=" + msgId, e); cf.completeExceptionally(e); } } @Override public void operationFail(Throwable throwable) { cf.completeExceptionally(throwable); } }); } catch (Throwable t) { LOGGER.error("invokeAsync failed in sendMessageToSpecificBrokerAsync, msgId=" + msgId, t); cf.completeExceptionally(t); } return cf; } private static RemotingCommand buildSendMessageRequest(MessageExt msg, String group) { SendMessageRequestHeaderV2 requestHeaderV2 = buildSendMessageRequestHeaderV2(msg, group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, requestHeaderV2); request.setBody(msg.getBody()); return request; } private static SendMessageRequestHeaderV2 buildSendMessageRequestHeaderV2(MessageExt msg, String group) { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setProducerGroup(group); requestHeader.setTopic(msg.getTopic()); requestHeader.setDefaultTopic(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC); requestHeader.setDefaultTopicQueueNums(8); requestHeader.setQueueId(msg.getQueueId()); requestHeader.setSysFlag(msg.getSysFlag()); requestHeader.setBornTimestamp(msg.getBornTimestamp()); requestHeader.setFlag(msg.getFlag()); requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties())); requestHeader.setReconsumeTimes(msg.getReconsumeTimes()); requestHeader.setBatch(false); SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader); return requestHeaderV2; } private SendResult processSendResponse( final String brokerName, final Message msg, final RemotingCommand response ) throws MQBrokerException, RemotingCommandException { SendStatus sendStatus = null; switch (response.getCode()) { case ResponseCode.FLUSH_DISK_TIMEOUT: sendStatus = SendStatus.FLUSH_DISK_TIMEOUT; break; case ResponseCode.FLUSH_SLAVE_TIMEOUT: sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT; break; case ResponseCode.SLAVE_NOT_AVAILABLE: sendStatus = SendStatus.SLAVE_NOT_AVAILABLE; break; case ResponseCode.SUCCESS: { sendStatus = SendStatus.SEND_OK; break; } default: break; } if (sendStatus != null) { SendMessageResponseHeader responseHeader = response.decodeCommandCustomHeader(SendMessageResponseHeader.class); //If namespace not null , reset Topic without namespace. String topic = msg.getTopic(); MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId()); String uniqMsgId = MessageClientIDSetter.getUniqID(msg); if (msg instanceof MessageBatch) { StringBuilder sb = new StringBuilder(); for (Message message : (MessageBatch) msg) { sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(message)); } uniqMsgId = sb.toString(); } SendResult sendResult = new SendResult(sendStatus, uniqMsgId, responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset()); sendResult.setTransactionId(responseHeader.getTransactionId()); String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION); String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH); if (regionId == null || regionId.isEmpty()) { regionId = MixAll.DEFAULT_TRACE_REGION_ID; } if (traceOn != null && traceOn.equals("false")) { sendResult.setTraceOn(false); } else { sendResult.setTraceOn(true); } sendResult.setRegionId(regionId); return sendResult; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public ExecutorService getBrokerOuterExecutor() { return brokerOuterExecutor; } public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { return getTopicRouteInfoFromNameServer(topic, timeoutMillis, true); } public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis, boolean allowTopicNotExist) throws MQBrokerException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.TOPIC_NOT_EXIST: { if (allowTopicNotExist) { LOGGER.warn("get Topic [{}] RouteInfoFromNameServer is not exist value", topic); } break; } case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { return TopicRouteData.decode(body, TopicRouteData.class); } } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public ClusterInfo getBrokerClusterInfo() throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CLUSTER_INFO, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, 3_000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return ClusterInfo.decode(response.getBody(), ClusterInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void forwardRequest(String brokerAddr, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException, RemotingTooMuchRequestException, RemotingConnectException { this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, invokeCallback); } public void refreshMetadata() throws Exception { ClusterInfo brokerClusterInfo = getBrokerClusterInfo(); clientMetadata.refreshClusterInfo(brokerClusterInfo); } public ClientMetadata getClientMetadata() { return clientMetadata; } public RpcClient getRpcClient() { return rpcClient; } public MessageRequestModeSerializeWrapper getAllMessageRequestMode( final String addr) throws RemotingSendRequestException, RemotingConnectException, MQBrokerException, RemotingTimeoutException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_MESSAGE_REQUEST_MODE, null); RemotingCommand response = this.remotingClient.invokeSync(addr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return MessageRequestModeSerializeWrapper.decode(response.getBody(), MessageRequestModeSerializeWrapper.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public GetMetaDataResponseHeader getControllerMetaData(final String controllerAddress) throws Exception { final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_METADATA_INFO, null); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; if (response.getCode() == SUCCESS) { return (GetMetaDataResponseHeader) response.decodeCommandCustomHeader(GetMetaDataResponseHeader.class); } throw new MQBrokerException(response.getCode(), response.getRemark()); } /** * Alter syncStateSet */ public SyncStateSet alterSyncStateSet( final String controllerAddress, final String brokerName, final Long masterBrokerId, final int masterEpoch, final Set newSyncStateSet, final int syncStateSetEpoch) throws Exception { final AlterSyncStateSetRequestHeader requestHeader = new AlterSyncStateSetRequestHeader(brokerName, masterBrokerId, masterEpoch); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET, requestHeader); request.setBody(new SyncStateSet(newSyncStateSet, syncStateSetEpoch).encode()); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; switch (response.getCode()) { case SUCCESS: { assert response.getBody() != null; return RemotingSerializable.decode(response.getBody(), SyncStateSet.class); } } throw new MQBrokerException(response.getCode(), response.getRemark()); } /** * Broker try to elect itself as a master in broker set */ public Pair> brokerElect(String controllerAddress, String clusterName, String brokerName, Long brokerId) throws Exception { final ElectMasterRequestHeader requestHeader = ElectMasterRequestHeader.ofBrokerTrigger(clusterName, brokerName, brokerId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ELECT_MASTER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; switch (response.getCode()) { // Only record success response. case CONTROLLER_MASTER_STILL_EXIST: case SUCCESS: final ElectMasterResponseHeader responseHeader = response.decodeCommandCustomHeader(ElectMasterResponseHeader.class); final ElectMasterResponseBody responseBody = RemotingSerializable.decode(response.getBody(), ElectMasterResponseBody.class); return new Pair<>(responseHeader, responseBody.getSyncStateSet()); } throw new MQBrokerException(response.getCode(), response.getRemark()); } public GetNextBrokerIdResponseHeader getNextBrokerId(final String clusterName, final String brokerName, final String controllerAddress) throws Exception { final GetNextBrokerIdRequestHeader requestHeader = new GetNextBrokerIdRequestHeader(clusterName, brokerName); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_NEXT_BROKER_ID, requestHeader); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; if (response.getCode() == SUCCESS) { return response.decodeCommandCustomHeader(GetNextBrokerIdResponseHeader.class); } throw new MQBrokerException(response.getCode(), response.getRemark()); } public ApplyBrokerIdResponseHeader applyBrokerId(final String clusterName, final String brokerName, final Long brokerId, final String registerCheckCode, final String controllerAddress) throws Exception { final ApplyBrokerIdRequestHeader requestHeader = new ApplyBrokerIdRequestHeader(clusterName, brokerName, brokerId, registerCheckCode); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_APPLY_BROKER_ID, requestHeader); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; if (response.getCode() == SUCCESS) { return response.decodeCommandCustomHeader(ApplyBrokerIdResponseHeader.class); } throw new MQBrokerException(response.getCode(), response.getRemark()); } public Pair> registerBrokerToController( final String clusterName, final String brokerName, final Long brokerId, final String brokerAddress, final String controllerAddress) throws Exception { final RegisterBrokerToControllerRequestHeader requestHeader = new RegisterBrokerToControllerRequestHeader(clusterName, brokerName, brokerId, brokerAddress); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_REGISTER_BROKER, requestHeader); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; if (response.getCode() == SUCCESS) { RegisterBrokerToControllerResponseHeader responseHeader = response.decodeCommandCustomHeader(RegisterBrokerToControllerResponseHeader.class); Set syncStateSet = RemotingSerializable.decode(response.getBody(), SyncStateSet.class).getSyncStateSet(); return new Pair<>(responseHeader, syncStateSet); } throw new MQBrokerException(response.getCode(), response.getRemark()); } /** * Get broker replica info */ public Pair getReplicaInfo(final String controllerAddress, final String brokerName) throws Exception { final GetReplicaInfoRequestHeader requestHeader = new GetReplicaInfoRequestHeader(brokerName); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_REPLICA_INFO, requestHeader); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; switch (response.getCode()) { case SUCCESS: { final GetReplicaInfoResponseHeader header = response.decodeCommandCustomHeader(GetReplicaInfoResponseHeader.class); assert response.getBody() != null; final SyncStateSet stateSet = RemotingSerializable.decode(response.getBody(), SyncStateSet.class); return new Pair<>(header, stateSet); } } throw new MQBrokerException(response.getCode(), response.getRemark()); } /** * Send heartbeat to controller */ public void sendHeartbeatToController(final String controllerAddress, final String clusterName, final String brokerAddr, final String brokerName, final Long brokerId, final int sendHeartBeatTimeoutMills, final boolean isInBrokerContainer, final int epoch, final long maxOffset, final long confirmOffset, final long controllerHeartBeatTimeoutMills, final int electionPriority) { if (StringUtils.isEmpty(controllerAddress)) { return; } final BrokerHeartbeatRequestHeader requestHeader = new BrokerHeartbeatRequestHeader(); requestHeader.setClusterName(clusterName); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerName(brokerName); requestHeader.setEpoch(epoch); requestHeader.setMaxOffset(maxOffset); requestHeader.setConfirmOffset(confirmOffset); requestHeader.setHeartbeatTimeoutMills(controllerHeartBeatTimeoutMills); requestHeader.setElectionPriority(electionPriority); requestHeader.setBrokerId(brokerId); brokerOuterExecutor.execute(new Runnable() { @Override public void run() { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BROKER_HEARTBEAT, requestHeader); try { BrokerOuterAPI.this.remotingClient.invokeOneway(controllerAddress, request, sendHeartBeatTimeoutMills); } catch (Exception e) { LOGGER.error("Error happen when send heartbeat to controller {}", controllerAddress, e); } } }); } // Triple, should check info and retry if and only if PullResult is null public CompletableFuture> pullMessageFromSpecificBrokerAsync(String brokerName, String brokerAddr, String consumerGroup, String topic, int queueId, long offset, int maxNums, long timeoutMillis) throws RemotingException, InterruptedException { PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setQueueOffset(offset); requestHeader.setMaxMsgNums(maxNums); requestHeader.setSysFlag(PullSysFlag.buildSysFlag(false, false, true, false)); requestHeader.setCommitOffset(0L); requestHeader.setSuspendTimeoutMillis(0L); requestHeader.setSubscription(SubscriptionData.SUB_ALL); requestHeader.setSubVersion(System.currentTimeMillis()); requestHeader.setMaxMsgBytes(Integer.MAX_VALUE); requestHeader.setExpressionType(ExpressionType.TAG); requestHeader.setBrokerName(brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); CompletableFuture> pullResultFuture = new CompletableFuture<>(); this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { PullResultExt pullResultExt = processPullResponse(response, brokerAddr); processPullResult(pullResultExt, brokerName, queueId); pullResultFuture.complete(Triple.of(pullResultExt, pullResultExt.getPullStatus().name(), false)); // found or not found really, so no retry } catch (Exception e) { // retry when NO_PERMISSION, SUBSCRIPTION_GROUP_NOT_EXIST etc. even when TOPIC_NOT_EXIST pullResultFuture.complete(Triple.of(null, "Response Code:" + response.getCode(), true)); } } @Override public void operationFail(Throwable throwable) { pullResultFuture.complete(Triple.of(null, throwable.getMessage(), true)); } }); return pullResultFuture; } private PullResultExt processPullResponse( final RemotingCommand response, final String addr) throws MQBrokerException, RemotingCommandException { PullStatus pullStatus = PullStatus.NO_NEW_MSG; switch (response.getCode()) { case ResponseCode.SUCCESS: pullStatus = PullStatus.FOUND; break; case ResponseCode.PULL_NOT_FOUND: pullStatus = PullStatus.NO_NEW_MSG; break; case ResponseCode.PULL_RETRY_IMMEDIATELY: pullStatus = PullStatus.NO_MATCHED_MSG; break; case ResponseCode.PULL_OFFSET_MOVED: pullStatus = PullStatus.OFFSET_ILLEGAL; break; default: throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } PullMessageResponseHeader responseHeader = response.decodeCommandCustomHeader(PullMessageResponseHeader.class); return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(), responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody(), responseHeader.getOffsetDelta()); } private PullResult processPullResult(final PullResultExt pullResult, String brokerName, int queueId) { if (PullStatus.FOUND == pullResult.getPullStatus()) { ByteBuffer byteBuffer = ByteBuffer.wrap(pullResult.getMessageBinary()); List msgList = MessageDecoder.decodesBatch( byteBuffer, true, true, true ); // Currently batch messages are not supported for (MessageExt msg : msgList) { String traFlag = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED); if (Boolean.parseBoolean(traFlag)) { msg.setTransactionId(msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); } MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET, Long.toString(pullResult.getMinOffset())); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET, Long.toString(pullResult.getMaxOffset())); msg.setBrokerName(brokerName); msg.setQueueId(queueId); if (pullResult.getOffsetDelta() != null) { msg.setQueueOffset(pullResult.getOffsetDelta() + msg.getQueueOffset()); } } pullResult.setMsgFoundList(msgList); } pullResult.setMessageBinary(null); return pullResult; } private int getMaxPageSize() { return 2000; } private long getTimeoutMillis() { return TimeUnit.SECONDS.toMillis(60); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pagecache/ManyMessageTransfer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pagecache; import io.netty.channel.FileRegion; import io.netty.util.AbstractReferenceCounted; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.List; import org.apache.rocketmq.store.GetMessageResult; public class ManyMessageTransfer extends AbstractReferenceCounted implements FileRegion { private final ByteBuffer byteBufferHeader; private final GetMessageResult getMessageResult; /** * Bytes which were transferred already. */ private long transferred; public ManyMessageTransfer(ByteBuffer byteBufferHeader, GetMessageResult getMessageResult) { this.byteBufferHeader = byteBufferHeader; this.getMessageResult = getMessageResult; } @Override public long position() { int pos = byteBufferHeader.position(); List messageBufferList = this.getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { pos += bb.position(); } return pos; } @Override public long transfered() { return transferred; } @Override public long transferred() { return transferred; } @Override public long count() { return byteBufferHeader.limit() + this.getMessageResult.getBufferTotalSize(); } @Override public long transferTo(WritableByteChannel target, long position) throws IOException { if (this.byteBufferHeader.hasRemaining()) { transferred += target.write(this.byteBufferHeader); return transferred; } else { List messageBufferList = this.getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { if (bb.hasRemaining()) { transferred += target.write(bb); return transferred; } } } return 0; } @Override public FileRegion retain() { super.retain(); return this; } @Override public FileRegion retain(int increment) { super.retain(increment); return this; } @Override public FileRegion touch() { return this; } @Override public FileRegion touch(Object hint) { return this; } public void close() { this.deallocate(); } @Override protected void deallocate() { this.getMessageResult.release(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pagecache/OneMessageTransfer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pagecache; import io.netty.channel.FileRegion; import io.netty.util.AbstractReferenceCounted; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import org.apache.rocketmq.store.SelectMappedBufferResult; public class OneMessageTransfer extends AbstractReferenceCounted implements FileRegion { private final ByteBuffer byteBufferHeader; private final SelectMappedBufferResult selectMappedBufferResult; /** * Bytes which were transferred already. */ private long transferred; public OneMessageTransfer(ByteBuffer byteBufferHeader, SelectMappedBufferResult selectMappedBufferResult) { this.byteBufferHeader = byteBufferHeader; this.selectMappedBufferResult = selectMappedBufferResult; } @Override public long position() { return this.byteBufferHeader.position() + this.selectMappedBufferResult.getByteBuffer().position(); } @Override public long transfered() { return transferred; } @Override public long transferred() { return transferred; } @Override public long count() { return this.byteBufferHeader.limit() + this.selectMappedBufferResult.getSize(); } @Override public long transferTo(WritableByteChannel target, long position) throws IOException { if (this.byteBufferHeader.hasRemaining()) { transferred += target.write(this.byteBufferHeader); return transferred; } else if (this.selectMappedBufferResult.getByteBuffer().hasRemaining()) { transferred += target.write(this.selectMappedBufferResult.getByteBuffer()); return transferred; } return 0; } @Override public FileRegion retain() { super.retain(); return this; } @Override public FileRegion retain(int increment) { super.retain(increment); return this; } @Override public FileRegion touch() { return this; } @Override public FileRegion touch(Object hint) { return this; } public void close() { this.deallocate(); } @Override protected void deallocate() { this.selectMappedBufferResult.release(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pagecache/QueryMessageTransfer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pagecache; import io.netty.channel.FileRegion; import io.netty.util.AbstractReferenceCounted; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.List; import org.apache.rocketmq.store.QueryMessageResult; public class QueryMessageTransfer extends AbstractReferenceCounted implements FileRegion { private final ByteBuffer byteBufferHeader; private final QueryMessageResult queryMessageResult; /** * Bytes which were transferred already. */ private long transferred; public QueryMessageTransfer(ByteBuffer byteBufferHeader, QueryMessageResult queryMessageResult) { this.byteBufferHeader = byteBufferHeader; this.queryMessageResult = queryMessageResult; } @Override public long position() { int pos = byteBufferHeader.position(); List messageBufferList = this.queryMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { pos += bb.position(); } return pos; } @Override public long transfered() { return transferred; } @Override public long transferred() { return transferred; } @Override public long count() { return byteBufferHeader.limit() + this.queryMessageResult.getBufferTotalSize(); } @Override public long transferTo(WritableByteChannel target, long position) throws IOException { if (this.byteBufferHeader.hasRemaining()) { transferred += target.write(this.byteBufferHeader); return transferred; } else { List messageBufferList = this.queryMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { if (bb.hasRemaining()) { transferred += target.write(bb); return transferred; } } } return 0; } @Override public FileRegion retain() { super.retain(); return this; } @Override public FileRegion retain(int increment) { super.retain(increment); return this; } @Override public FileRegion touch() { return this; } @Override public FileRegion touch(Object hint) { return this; } public void close() { this.deallocate(); } @Override protected void deallocate() { this.queryMessageResult.release(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/plugin/BrokerAttachedPlugin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.plugin; import java.util.Map; public interface BrokerAttachedPlugin { /** * Get plugin name * * @return plugin name */ String pluginName(); /** * Load broker attached plugin. * * @return load success or failed */ boolean load(); /** * Start broker attached plugin. */ void start(); /** * Shutdown broker attached plugin. */ void shutdown(); /** * Sync metadata from master. */ void syncMetadata(); /** * Sync metadata reverse from slave * * @param brokerAddr */ void syncMetadataReverse(String brokerAddr) throws Exception; /** * Some plugin need build runningInfo when prepare runtime info. * * @param runtimeInfo */ void buildRuntimeInfo(Map runtimeInfo); /** * Some plugin need do something when status changed. For example, brokerRole change to master or slave. * * @param shouldStart */ void statusChanged(boolean shouldStart); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/plugin/PullMessageResultHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.plugin; import io.netty.channel.Channel; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.MessageFilter; public interface PullMessageResultHandler { /** * Handle result of get message from store. * * @param getMessageResult store result * @param request request * @param requestHeader request header * @param channel channel * @param subscriptionData sub data * @param subscriptionGroupConfig sub config * @param brokerAllowSuspend brokerAllowSuspend * @param messageFilter store message filter * @param response response * @return response or null */ RemotingCommand handle(final GetMessageResult getMessageResult, final RemotingCommand request, final PullMessageRequestHeader requestHeader, final Channel channel, final SubscriptionData subscriptionData, final SubscriptionGroupConfig subscriptionGroupConfig, final boolean brokerAllowSuspend, final MessageFilter messageFilter, final RemotingCommand response, final TopicQueueMappingContext mappingContext, final long beginTimeMills); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerCache.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PopConsumerCache extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private static final long OFFSET_NOT_EXIST = -1L; private final BrokerController brokerController; private final PopConsumerKVStore consumerRecordStore; private final PopConsumerLockService consumerLockService; private final Consumer reviveConsumer; private final AtomicInteger estimateCacheSize; private final ConcurrentMap consumerRecordTable; public PopConsumerCache(BrokerController brokerController, PopConsumerKVStore consumerRecordStore, PopConsumerLockService popConsumerLockService, Consumer reviveConsumer) { this.reviveConsumer = reviveConsumer; this.brokerController = brokerController; this.consumerRecordStore = consumerRecordStore; this.consumerLockService = popConsumerLockService; this.estimateCacheSize = new AtomicInteger(); this.consumerRecordTable = new ConcurrentHashMap<>(); } public String getKey(String groupId, String topicId, int queueId) { return groupId + "@" + topicId + "@" + queueId; } public String getKey(PopConsumerRecord consumerRecord) { return consumerRecord.getGroupId() + "@" + consumerRecord.getTopicId() + "@" + consumerRecord.getQueueId(); } public int getCacheKeySize() { return this.consumerRecordTable.size(); } public int getCacheSize() { return this.estimateCacheSize.intValue(); } public boolean isCacheFull() { return this.estimateCacheSize.intValue() > brokerController.getBrokerConfig().getPopCkMaxBufferSize(); } public long getMinOffsetInCache(String groupId, String topicId, int queueId) { ConsumerRecords consumerRecords = consumerRecordTable.get(this.getKey(groupId, topicId, queueId)); return consumerRecords != null ? consumerRecords.getMinOffsetInBuffer() : OFFSET_NOT_EXIST; } public long getPopInFlightMessageCount(String groupId, String topicId, int queueId) { ConsumerRecords consumerRecords = consumerRecordTable.get(this.getKey(groupId, topicId, queueId)); return consumerRecords != null ? consumerRecords.getInFlightRecordCount() : 0L; } public void writeRecords(List consumerRecordList) { this.estimateCacheSize.addAndGet(consumerRecordList.size()); consumerRecordList.forEach(consumerRecord -> { ConsumerRecords consumerRecords = ConcurrentHashMapUtils.computeIfAbsent(consumerRecordTable, this.getKey(consumerRecord), k -> new ConsumerRecords(brokerController.getBrokerConfig(), consumerRecord.getGroupId(), consumerRecord.getTopicId(), consumerRecord.getQueueId())); assert consumerRecords != null; consumerRecords.write(consumerRecord); }); } /** * Remove the record from the input list then return the content that has not been deleted */ public List deleteRecords(List consumerRecordList) { int total = consumerRecordList.size(); List remain = new ArrayList<>(); consumerRecordList.forEach(consumerRecord -> { ConsumerRecords consumerRecords = consumerRecordTable.get(this.getKey(consumerRecord)); if (consumerRecords == null || !consumerRecords.delete(consumerRecord)) { remain.add(consumerRecord); } }); this.estimateCacheSize.addAndGet(remain.size() - total); return remain; } public int cleanupRecords(Consumer consumer) { int remain = 0; Iterator> iterator = consumerRecordTable.entrySet().iterator(); while (iterator.hasNext()) { // revive or write record to store ConsumerRecords records = iterator.next().getValue(); boolean timeout = consumerLockService.isLockTimeout( records.getGroupId(), records.getTopicId()); if (timeout) { records.stageExpiredRecords(Long.MAX_VALUE); List writeConsumerRecords = new ArrayList<>(records.getRemoveTreeMap().values()); if (!writeConsumerRecords.isEmpty()) { consumerRecordStore.writeRecords(writeConsumerRecords); } records.clearStagedRecords(); log.info("PopConsumerOffline, so clean expire records, groupId={}, topic={}, queueId={}, records={}", records.getGroupId(), records.getTopicId(), records.getQueueId(), records.getInFlightRecordCount()); iterator.remove(); continue; } long currentTime = System.currentTimeMillis(); records.stageExpiredRecords(currentTime); List writeConsumerRecords = new ArrayList<>(); records.getRemoveTreeMap().values().forEach(record -> { if (record.getVisibilityTimeout() <= currentTime) { consumer.accept(record); } else { writeConsumerRecords.add(record); } }); // write to store and handle it later consumerRecordStore.writeRecords(writeConsumerRecords); records.clearStagedRecords(); // commit min offset in buffer to offset store long offset = records.getMinOffsetInBuffer(); if (offset > OFFSET_NOT_EXIST) { this.commitOffset("PopConsumerCache", records.getGroupId(), records.getTopicId(), records.getQueueId(), offset); } remain += records.getInFlightRecordCount(); } return remain; } public void commitOffset(String clientHost, String groupId, String topicId, int queueId, long offset) { if (!consumerLockService.tryLock(groupId, topicId)) { return; } try { ConsumerOffsetManager consumerOffsetManager = brokerController.getConsumerOffsetManager(); long commit = consumerOffsetManager.queryOffset(groupId, topicId, queueId); if (commit != OFFSET_NOT_EXIST && offset < commit) { log.info("PopConsumerCache, consumer offset less than store, " + "groupId={}, topicId={}, queueId={}, offset={}", groupId, topicId, queueId, offset); } consumerOffsetManager.commitOffset(clientHost, groupId, topicId, queueId, offset); } finally { consumerLockService.unlock(groupId, topicId); } } public void removeRecords(String groupId, String topicId, int queueId) { this.consumerRecordTable.remove(this.getKey(groupId, topicId, queueId)); } @Override public String getServiceName() { return PopConsumerCache.class.getSimpleName(); } @Override public void run() { while (!this.isStopped()) { try { this.waitForRunning(TimeUnit.SECONDS.toMillis(1)); int cacheSize = this.cleanupRecords(reviveConsumer); this.estimateCacheSize.set(cacheSize); } catch (Exception e) { log.error("PopConsumerCacheService revive error", e); } } } protected static class ConsumerRecords { private final String groupId; private final String topicId; private final int queueId; private final BrokerConfig brokerConfig; private final ConcurrentSkipListMap removeTreeMap; private final ConcurrentSkipListMap recordTreeMap; public ConsumerRecords(BrokerConfig brokerConfig, String groupId, String topicId, int queueId) { this.groupId = groupId; this.topicId = topicId; this.queueId = queueId; this.brokerConfig = brokerConfig; this.removeTreeMap = new ConcurrentSkipListMap<>(); this.recordTreeMap = new ConcurrentSkipListMap<>(); } public void write(PopConsumerRecord record) { recordTreeMap.put(record.getOffset(), record); } public boolean delete(PopConsumerRecord record) { return recordTreeMap.remove(record.getOffset()) != null; } public long getMinOffsetInBuffer() { Map.Entry entry = removeTreeMap.firstEntry(); if (entry != null) { return entry.getKey(); } entry = recordTreeMap.firstEntry(); return entry != null ? entry.getKey() : OFFSET_NOT_EXIST; } public int getInFlightRecordCount() { return removeTreeMap.size() + recordTreeMap.size(); } public void stageExpiredRecords(long currentTime) { Iterator> iterator = recordTreeMap.entrySet().iterator(); // refer: org.apache.rocketmq.broker.processor.PopBufferMergeService.scan while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (entry.getValue().getVisibilityTimeout() <= currentTime || entry.getValue().getPopTime() + brokerConfig.getPopCkStayBufferTime() <= currentTime) { removeTreeMap.put(entry.getKey(), entry.getValue()); iterator.remove(); } } } public void clearStagedRecords() { removeTreeMap.clear(); } public ConcurrentSkipListMap getRemoveTreeMap() { return removeTreeMap; } public String getGroupId() { return groupId; } public String getTopicId() { return topicId; } public int getQueueId() { return queueId; } @Override public String toString() { return "ConsumerRecords{" + "topicId=" + topicId + ", groupId=" + groupId + ", queueId=" + queueId + ", recordTreeMap=" + recordTreeMap.size() + '}'; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; public class PopConsumerContext { private final String clientHost; private final long popTime; private final long invisibleTime; private final String groupId; private final boolean fifo; private final int initMode; private final String attemptId; private final AtomicLong restCount; private final StringBuilder startOffsetInfo; private final StringBuilder msgOffsetInfo; private final StringBuilder orderCountInfo; private List getMessageResultList; private List popConsumerRecordList; public PopConsumerContext(String clientHost, long popTime, long invisibleTime, String groupId, boolean fifo, int initMode, String attemptId) { this.clientHost = clientHost; this.popTime = popTime; this.invisibleTime = invisibleTime; this.groupId = groupId; this.fifo = fifo; this.initMode = initMode; this.attemptId = attemptId; this.restCount = new AtomicLong(0); this.startOffsetInfo = new StringBuilder(); this.msgOffsetInfo = new StringBuilder(); this.orderCountInfo = new StringBuilder(); } public boolean isFound() { return getMessageResultList != null && !getMessageResultList.isEmpty(); } // offset is consumer last request offset public void addGetMessageResult(GetMessageResult result, String topicId, int queueId, PopConsumerRecord.RetryType retryType, long offset) { if (result.getStatus() != GetMessageStatus.FOUND || result.getMessageQueueOffset().isEmpty()) { return; } if (this.getMessageResultList == null) { this.getMessageResultList = new ArrayList<>(); } if (this.popConsumerRecordList == null) { this.popConsumerRecordList = new ArrayList<>(); } this.getMessageResultList.add(result); this.addRestCount(result.getMaxOffset() - result.getNextBeginOffset()); for (int i = 0; i < result.getMessageQueueOffset().size(); i++) { this.popConsumerRecordList.add(new PopConsumerRecord(popTime, groupId, topicId, queueId, retryType.getCode(), invisibleTime, result.getMessageQueueOffset().get(i), attemptId)); } ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, topicId, queueId, offset); ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, topicId, queueId, result.getMessageQueueOffset()); } public String getClientHost() { return clientHost; } public String getGroupId() { return groupId; } public void addRestCount(long delta) { this.restCount.addAndGet(delta); } public long getRestCount() { return restCount.get(); } public long getPopTime() { return popTime; } public boolean isFifo() { return fifo; } public int getInitMode() { return initMode; } public long getInvisibleTime() { return invisibleTime; } public String getAttemptId() { return attemptId; } public int getMessageCount() { return getMessageResultList != null ? getMessageResultList.stream().mapToInt(GetMessageResult::getMessageCount).sum() : 0; } public String getStartOffsetInfo() { return startOffsetInfo.toString(); } public String getMsgOffsetInfo() { return msgOffsetInfo.toString(); } public StringBuilder getOrderCountInfoBuilder() { return orderCountInfo; } public String getOrderCountInfo() { return orderCountInfo.toString(); } public List getGetMessageResultList() { return getMessageResultList; } public List getPopConsumerRecordList() { return popConsumerRecordList; } @Override public String toString() { return "PopConsumerContext{" + "clientHost=" + clientHost + ", popTime=" + popTime + ", invisibleTime=" + invisibleTime + ", groupId=" + groupId + ", isFifo=" + fifo + ", attemptId=" + attemptId + ", restCount=" + restCount + ", startOffsetInfo=" + startOffsetInfo + ", msgOffsetInfo=" + msgOffsetInfo + ", orderCountInfo=" + orderCountInfo + ", getMessageResultList=" + (getMessageResultList != null ? getMessageResultList.size() : 0) + ", popConsumerRecordList=" + (popConsumerRecordList != null ? popConsumerRecordList.size() : 0) + '}'; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerKVStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.util.List; public interface PopConsumerKVStore { /** * Starts the storage service. */ boolean start(); /** * Shutdown the storage service. */ boolean shutdown(); /** * Gets the file path of the storage. * @return The file path of the storage. */ String getFilePath(); /** * Writes a list of consumer records to the storage. * @param consumerRecordList The list of consumer records to be written. */ void writeRecords(List consumerRecordList); /** * Deletes a list of consumer records from the storage. * @param consumerRecordList The list of consumer records to be deleted. */ void deleteRecords(List consumerRecordList); /** * Scans and returns a list of expired consumer records within the specified time range. * @param lowerTime The start time (inclusive) of the time range to search, in milliseconds. * @param upperTime The end time (exclusive) of the time range to search, in milliseconds. * @param maxCount The maximum number of records to return. * Even if more records match the criteria, only this many will be returned. * @return A list of expired consumer records within the specified time range. * If no matching records are found, an empty list is returned. */ List scanExpiredRecords(long lowerTime, long upperTime, int maxCount); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerLockService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PopConsumerLockService { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final long timeout; private final ConcurrentMap lockTable; public PopConsumerLockService(long timeout) { this.timeout = timeout; this.lockTable = new ConcurrentHashMap<>(); } public boolean tryLock(String key) { return Objects.requireNonNull(ConcurrentHashMapUtils.computeIfAbsent(lockTable, key, s -> new TimedLock())).tryLock(); } public boolean tryLock(String groupId, String topicId) { return tryLock(groupId + PopAckConstants.SPLIT + topicId); } public void unlock(String key) { TimedLock lock = lockTable.get(key); if (lock != null) { lock.unlock(); } } public void unlock(String groupId, String topicId) { unlock(groupId + PopAckConstants.SPLIT + topicId); } // For retry topics, should lock origin group and topic public boolean isLockTimeout(String groupId, String topicId) { topicId = KeyBuilder.parseNormalTopic(topicId, groupId); TimedLock lock = lockTable.get(groupId + PopAckConstants.SPLIT + topicId); return lock == null || System.currentTimeMillis() - lock.getLockTime() > timeout; } public void removeTimeout() { Iterator> iterator = lockTable.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (System.currentTimeMillis() - entry.getValue().getLockTime() > timeout) { log.info("PopConsumerLockService remove timeout lock, " + "key={}, locked={}", entry.getKey(), entry.getValue().lock.get()); iterator.remove(); } } } static class TimedLock { private volatile long lockTime; private final AtomicBoolean lock; public TimedLock() { this.lockTime = System.currentTimeMillis(); this.lock = new AtomicBoolean(false); } public boolean tryLock() { if (lock.compareAndSet(false, true)) { this.lockTime = System.currentTimeMillis(); return true; } return false; } public void unlock() { lock.set(false); } public long getLockTime() { return lockTime; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerRecord.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.annotation.JSONField; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; public class PopConsumerRecord { public enum RetryType { NORMAL_TOPIC(0), RETRY_TOPIC_V1(1), RETRY_TOPIC_V2(2); private final int code; RetryType(int code) { this.code = code; } public int getCode() { return code; } } @JSONField() private long popTime; @JSONField(ordinal = 1) private String groupId; @JSONField(ordinal = 2) private String topicId; @JSONField(ordinal = 3) private int queueId; @JSONField(ordinal = 4) private int retryFlag; @JSONField(ordinal = 5) private long invisibleTime; @JSONField(ordinal = 6) private long offset; @JSONField(ordinal = 7) private int attemptTimes; @JSONField(ordinal = 8) private String attemptId; @JSONField(ordinal = 9) private boolean suspend; // used for test and fastjson public PopConsumerRecord() { } public PopConsumerRecord(long popTime, String groupId, String topicId, int queueId, int retryFlag, long invisibleTime, long offset, String attemptId) { this(popTime, groupId, topicId, queueId, retryFlag, invisibleTime, offset, attemptId, false); } public PopConsumerRecord(long popTime, String groupId, String topicId, int queueId, int retryFlag, long invisibleTime, long offset, String attemptId, boolean suspend) { this.popTime = popTime; this.groupId = groupId; this.topicId = topicId; this.queueId = queueId; this.retryFlag = retryFlag; this.invisibleTime = invisibleTime; this.offset = offset; this.attemptId = attemptId; this.suspend = suspend; } @JSONField(serialize = false) public long getVisibilityTimeout() { return popTime + invisibleTime; } /** * Key: timestamp(8) + groupId + topicId + queueId + offset */ @JSONField(serialize = false) public byte[] getKeyBytes() { int length = Long.BYTES + groupId.length() + 1 + topicId.length() + 1 + Integer.BYTES + 1 + Long.BYTES; byte[] bytes = new byte[length]; ByteBuffer buffer = ByteBuffer.wrap(bytes); buffer.putLong(this.getVisibilityTimeout()); buffer.put(groupId.getBytes(StandardCharsets.UTF_8)).put((byte) '@'); buffer.put(topicId.getBytes(StandardCharsets.UTF_8)).put((byte) '@'); buffer.putInt(queueId).put((byte) '@'); buffer.putLong(offset); return bytes; } @JSONField(serialize = false) public boolean isRetry() { return retryFlag != 0; } @JSONField(serialize = false) public byte[] getValueBytes() { return JSON.toJSONBytes(this); } public static PopConsumerRecord decode(byte[] body) { return JSON.parseObject(body, PopConsumerRecord.class); } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getTopicId() { return topicId; } public void setTopicId(String topicId) { this.topicId = topicId; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } public int getRetryFlag() { return retryFlag; } public void setRetryFlag(int retryFlag) { this.retryFlag = retryFlag; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public long getOffset() { return offset; } public void setOffset(long offset) { this.offset = offset; } public int getAttemptTimes() { return attemptTimes; } public void setAttemptTimes(int attemptTimes) { this.attemptTimes = attemptTimes; } public String getAttemptId() { return attemptId; } public void setAttemptId(String attemptId) { this.attemptId = attemptId; } public boolean isSuspend() { return suspend; } public void setSuspend(boolean suspend) { this.suspend = suspend; } @Override public String toString() { return "PopDeliveryRecord{" + "popTime=" + popTime + ", groupId='" + groupId + '\'' + ", topicId='" + topicId + '\'' + ", queueId=" + queueId + ", retryFlag=" + retryFlag + ", invisibleTime=" + invisibleTime + ", offset=" + offset + ", attemptTimes=" + attemptTimes + ", attemptId='" + attemptId + '\'' + ", suspend=" + suspend + '}'; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerRocksdbStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.store.rocksdb.RocksDBOptionsFactory; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.CompactRangeOptions; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Slice; import org.rocksdb.WriteBatch; import org.rocksdb.WriteOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PopConsumerRocksdbStore extends AbstractRocksDBStorage implements PopConsumerKVStore { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private static final byte[] COLUMN_FAMILY_NAME = "popState".getBytes(StandardCharsets.UTF_8); private WriteOptions writeOptions; private WriteOptions deleteOptions; protected ColumnFamilyHandle columnFamilyHandle; public PopConsumerRocksdbStore(String filePath) { super(filePath); } // https://www.cnblogs.com/renjc/p/rocksdb-class-db.html // https://github.com/johnzeng/rocksdb-doc-cn/blob/master/doc/RocksDB-Tuning-Guide.md protected void initOptions() { this.options = RocksDBOptionsFactory.createDBOptions(); this.writeOptions = new WriteOptions(); this.writeOptions.setSync(true); this.writeOptions.setDisableWAL(false); this.writeOptions.setNoSlowdown(false); this.deleteOptions = new WriteOptions(); this.deleteOptions.setSync(true); this.deleteOptions.setDisableWAL(false); this.deleteOptions.setNoSlowdown(false); this.compactRangeOptions = new CompactRangeOptions(); this.compactRangeOptions.setBottommostLevelCompaction( CompactRangeOptions.BottommostLevelCompaction.kForce); this.compactRangeOptions.setAllowWriteStall(true); this.compactRangeOptions.setExclusiveManualCompaction(false); this.compactRangeOptions.setChangeLevel(true); this.compactRangeOptions.setTargetLevel(-1); this.compactRangeOptions.setMaxSubcompactions(4); } @Override protected boolean postLoad() { try { UtilAll.ensureDirOK(this.dbPath); initOptions(); // init column family here ColumnFamilyOptions defaultOptions = RocksDBOptionsFactory.createPopCFOptions(); ColumnFamilyOptions popStateOptions = RocksDBOptionsFactory.createPopCFOptions(); this.cfOptions.add(defaultOptions); this.cfOptions.add(popStateOptions); List cfDescriptors = new ArrayList<>(); cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, defaultOptions)); cfDescriptors.add(new ColumnFamilyDescriptor(COLUMN_FAMILY_NAME, popStateOptions)); this.open(cfDescriptors); this.defaultCFHandle = cfHandles.get(0); this.columnFamilyHandle = cfHandles.get(1); log.debug("PopConsumerRocksdbStore init, filePath={}", this.dbPath); } catch (final Exception e) { log.error("PopConsumerRocksdbStore init error, filePath={}", this.dbPath, e); return false; } return true; } public String getFilePath() { return this.dbPath; } @Override public void writeRecords(List consumerRecordList) { if (!consumerRecordList.isEmpty()) { try (WriteBatch writeBatch = new WriteBatch()) { for (PopConsumerRecord record : consumerRecordList) { writeBatch.put(columnFamilyHandle, record.getKeyBytes(), record.getValueBytes()); } this.db.write(writeOptions, writeBatch); } catch (RocksDBException e) { throw new RuntimeException("Write record error", e); } } } @Override public void deleteRecords(List consumerRecordList) { if (!consumerRecordList.isEmpty()) { try (WriteBatch writeBatch = new WriteBatch()) { for (PopConsumerRecord record : consumerRecordList) { writeBatch.delete(columnFamilyHandle, record.getKeyBytes()); } this.db.write(deleteOptions, writeBatch); } catch (RocksDBException e) { throw new RuntimeException("Delete record error", e); } } } @Override // https://github.com/facebook/rocksdb/issues/10300 public List scanExpiredRecords(long lower, long upper, int maxCount) { // In RocksDB, we can use SstPartitionerFixedPrefixFactory in cfOptions // and new ColumnFamilyOptions().useFixedLengthPrefixExtractor() to // configure prefix indexing to improve the performance of scans. // However, in the current implementation, this is not the bottleneck. List consumerRecordList = new ArrayList<>(); try (ReadOptions scanOptions = new ReadOptions() .setIterateLowerBound(new Slice(ByteBuffer.allocate(Long.BYTES).putLong(lower).array())) .setIterateUpperBound(new Slice(ByteBuffer.allocate(Long.BYTES).putLong(upper).array())); RocksIterator iterator = db.newIterator(this.columnFamilyHandle, scanOptions)) { iterator.seek(ByteBuffer.allocate(Long.BYTES).putLong(lower).array()); while (iterator.isValid() && consumerRecordList.size() < maxCount) { consumerRecordList.add(PopConsumerRecord.decode(iterator.value())); iterator.next(); } } return consumerRecordList; } @Override protected void preShutdown() { if (this.writeOptions != null) { this.writeOptions.close(); } if (this.deleteOptions != null) { this.deleteOptions.close(); } if (this.columnFamilyHandle != null) { this.columnFamilyHandle.close(); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import com.alibaba.fastjson2.JSON; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; public class PopConsumerService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private static final long OFFSET_NOT_EXIST = -1L; private static final String ROCKSDB_DIRECTORY = "kvStore"; private static final int[] REWRITE_INTERVALS_IN_SECONDS = new int[] {10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200}; private final AtomicBoolean consumerRunning; private final BrokerConfig brokerConfig; private final BrokerController brokerController; private final AtomicLong currentTime; private final AtomicLong lastCleanupLockTime; private final PopConsumerCache popConsumerCache; private final PopConsumerKVStore popConsumerStore; private final PopConsumerLockService consumerLockService; private final ConcurrentMap requestCountTable; public PopConsumerService(BrokerController brokerController) { this.brokerController = brokerController; this.brokerConfig = brokerController.getBrokerConfig(); this.consumerRunning = new AtomicBoolean(false); this.requestCountTable = new ConcurrentHashMap<>(); this.currentTime = new AtomicLong(TimeUnit.SECONDS.toMillis(3)); this.lastCleanupLockTime = new AtomicLong(System.currentTimeMillis()); this.consumerLockService = new PopConsumerLockService(TimeUnit.MINUTES.toMillis(2)); this.popConsumerStore = new PopConsumerRocksdbStore(Paths.get( brokerController.getMessageStoreConfig().getStorePathRootDir(), ROCKSDB_DIRECTORY).toString()); this.popConsumerCache = brokerConfig.isEnablePopBufferMerge() ? new PopConsumerCache( brokerController, this.popConsumerStore, this.consumerLockService, this::revive) : null; log.info("PopConsumerService init, buffer={}, rocksdb filePath={}", brokerConfig.isEnablePopBufferMerge(), this.popConsumerStore.getFilePath()); } /** * In-flight messages are those that have been received from a queue * by a consumer but have not yet been deleted. For standard queues, * there is a limit on the number of in-flight messages, depending on queue traffic and message backlog. */ public boolean isPopShouldStop(String group, String topic, int queueId) { return brokerConfig.isEnablePopMessageThreshold() && popConsumerCache != null && popConsumerCache.getPopInFlightMessageCount(group, topic, queueId) >= brokerConfig.getPopInflightMessageThreshold(); } public long getPendingFilterCount(String groupId, String topicId, int queueId) { try { long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topicId, queueId); long consumeOffset = this.brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, queueId); return maxOffset - consumeOffset; } catch (ConsumeQueueException e) { throw new RuntimeException(e); } } public GetMessageResult recodeRetryMessage(GetMessageResult getMessageResult, String topicId, long offset, long popTime, long invisibleTime) { if (getMessageResult.getMessageCount() == 0 || getMessageResult.getMessageMapedList().isEmpty()) { return getMessageResult; } GetMessageResult result = new GetMessageResult(getMessageResult.getMessageCount()); result.setStatus(GetMessageStatus.FOUND); String brokerName = brokerConfig.getBrokerName(); for (SelectMappedBufferResult bufferResult : getMessageResult.getMessageMapedList()) { List messageExtList = MessageDecoder.decodesBatch( bufferResult.getByteBuffer(), true, false, true); bufferResult.release(); for (MessageExt messageExt : messageExtList) { try { // When override retry message topic to origin topic, // need clear message store size to recode String ckInfo = ExtraInfoUtil.buildExtraInfo(offset, popTime, invisibleTime, 0, messageExt.getTopic(), brokerName, messageExt.getQueueId(), messageExt.getQueueOffset()); messageExt.getProperties().putIfAbsent(MessageConst.PROPERTY_POP_CK, ckInfo); messageExt.setTopic(topicId); messageExt.setStoreSize(0); byte[] encode = MessageDecoder.encode(messageExt, false); ByteBuffer buffer = ByteBuffer.wrap(encode); SelectMappedBufferResult tmpResult = new SelectMappedBufferResult( bufferResult.getStartOffset(), buffer, encode.length, null); result.addMessage(tmpResult); } catch (Exception e) { log.error("PopConsumerService exception in recode retry message, topic={}", topicId, e); } } } return result; } public PopConsumerContext handleGetMessageResult(PopConsumerContext context, GetMessageResult result, String topicId, int queueId, PopConsumerRecord.RetryType retryType, long offset) { if (GetMessageStatus.FOUND.equals(result.getStatus()) && !result.getMessageQueueOffset().isEmpty()) { if (context.isFifo()) { this.setFifoBlocked(context, context.getGroupId(), topicId, queueId, result.getMessageQueueOffset(), result); } // build response header here context.addGetMessageResult(result, topicId, queueId, retryType, offset); if (brokerConfig.isPopConsumerKVServiceLog()) { log.info("PopConsumerService pop, time={}, invisible={}, " + "groupId={}, topic={}, queueId={}, offset={}, attemptId={}", context.getPopTime(), context.getInvisibleTime(), context.getGroupId(), topicId, queueId, result.getMessageQueueOffset(), context.getAttemptId()); } } long commitOffset = offset; if (context.isFifo()) { if (!GetMessageStatus.FOUND.equals(result.getStatus())) { commitOffset = result.getNextBeginOffset(); } } else { this.brokerController.getConsumerOffsetManager().commitPullOffset( context.getClientHost(), context.getGroupId(), topicId, queueId, result.getNextBeginOffset()); if (brokerConfig.isEnablePopBufferMerge() && popConsumerCache != null) { long minOffset = popConsumerCache.getMinOffsetInCache(context.getGroupId(), topicId, queueId); if (minOffset != OFFSET_NOT_EXIST) { commitOffset = minOffset; } } } this.brokerController.getConsumerOffsetManager().commitOffset( context.getClientHost(), context.getGroupId(), topicId, queueId, commitOffset); return context; } public long getPopOffset(String groupId, String topicId, int queueId, int initMode, boolean fifo) { // For FIFO messages, the pull offset is not used. // This preserves compatibility when switching from pull consumer to pop consumer. long offset = fifo ? this.brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, queueId) : this.brokerController.getConsumerOffsetManager().queryPullOffset(groupId, topicId, queueId); if (offset < 0L) { try { offset = this.brokerController.getPopMessageProcessor() .getInitOffset(topicId, groupId, queueId, initMode, true); log.info("PopConsumerService init offset, groupId={}, topicId={}, queueId={}, init={}, offset={}", groupId, topicId, queueId, ConsumeInitMode.MIN == initMode ? "min" : "max", offset); } catch (ConsumeQueueException e) { throw new RuntimeException(e); } } Long resetOffset = this.brokerController.getConsumerOffsetManager().queryThenEraseResetOffset(topicId, groupId, queueId); if (resetOffset != null) { this.clearCache(groupId, topicId, queueId); this.brokerController.getConsumerOrderInfoManager().clearBlock(topicId, groupId, queueId); this.brokerController.getConsumerOffsetManager() .commitOffset("ResetPopOffset", groupId, topicId, queueId, resetOffset); } return resetOffset != null ? resetOffset : offset; } public CompletableFuture getMessageAsync(String clientHost, String groupId, String topicId, int queueId, long offset, int batchSize, MessageFilter filter) { log.debug("PopConsumerService getMessageAsync, groupId={}, topicId={}, queueId={}, offset={}, batchSize={}, filter={}", groupId, topicId, offset, queueId, batchSize, filter != null); CompletableFuture getMessageFuture = brokerController.getMessageStore().getMessageAsync(groupId, topicId, queueId, offset, batchSize, filter); // refer org.apache.rocketmq.broker.processor.PopMessageProcessor#popMsgFromQueue return getMessageFuture.thenCompose(result -> { if (result == null) { return CompletableFuture.completedFuture(null); } // maybe store offset is not correct. if (GetMessageStatus.OFFSET_TOO_SMALL.equals(result.getStatus()) || GetMessageStatus.OFFSET_OVERFLOW_BADLY.equals(result.getStatus()) || GetMessageStatus.OFFSET_FOUND_NULL.equals(result.getStatus())) { // commit offset, because the offset is not correct // If offset in store is greater than cq offset, it will cause duplicate messages, // because offset in PopBuffer is not committed. this.brokerController.getConsumerOffsetManager().commitOffset( clientHost, groupId, topicId, queueId, result.getNextBeginOffset()); log.warn("PopConsumerService getMessageAsync, initial offset because store is no correct, " + "groupId={}, topicId={}, queueId={}, batchSize={}, offset={}->{}", groupId, topicId, queueId, batchSize, offset, result.getNextBeginOffset()); return brokerController.getMessageStore().getMessageAsync( groupId, topicId, queueId, result.getNextBeginOffset(), batchSize, filter); } return CompletableFuture.completedFuture(result); }).whenComplete((result, throwable) -> { if (throwable != null) { log.error("Pop getMessageAsync error", throwable); } }); } /** * Fifo message does not have retry feature in broker */ public void setFifoBlocked(PopConsumerContext context, String groupId, String topicId, int queueId, List queueOffsetList, GetMessageResult getMessageResult) { brokerController.getConsumerOrderInfoManager().update( context.getAttemptId(), false, topicId, groupId, queueId, context.getPopTime(), context.getInvisibleTime(), queueOffsetList, context.getOrderCountInfoBuilder(), getMessageResult); } public boolean isFifoBlocked(PopConsumerContext context, String groupId, String topicId, int queueId) { return brokerController.getConsumerOrderInfoManager().checkBlock( context.getAttemptId(), topicId, groupId, queueId, context.getInvisibleTime()); } protected CompletableFuture getMessageAsync(CompletableFuture future, String clientHost, String groupId, String topicId, int queueId, int batchSize, MessageFilter filter, PopConsumerRecord.RetryType retryType) { return future.thenCompose(result -> { // pop request too much, should not add rest count here if (isPopShouldStop(groupId, topicId, queueId)) { return CompletableFuture.completedFuture(result); } // Current requests would calculate the total number of messages // waiting to be filtered for new message arrival notifications in // the long-polling service, need disregarding the backlog in order // consumption scenario. If rest message num including the blocked // queue accumulation would lead to frequent unnecessary wake-ups // of long-polling requests, resulting unnecessary CPU usage. // When client ack message, long-polling request would be notifications // by AckMessageProcessor.ackOrderly() and message will not be delayed. if (result.isFifo() && isFifoBlocked(result, groupId, topicId, queueId)) { // should not add accumulation(max offset - consumer offset) here return CompletableFuture.completedFuture(result); } int remain = batchSize - result.getMessageCount(); if (remain <= 0) { result.addRestCount(this.getPendingFilterCount(groupId, topicId, queueId)); return CompletableFuture.completedFuture(result); } else { final long consumeOffset = this.getPopOffset(groupId, topicId, queueId, result.getInitMode(), result.isFifo()); return getMessageAsync(clientHost, groupId, topicId, queueId, consumeOffset, remain, filter) .thenApply(getMessageResult -> handleGetMessageResult( result, getMessageResult, topicId, queueId, retryType, consumeOffset)); } }); } protected CompletableFuture getMessageFromTopicAsync(CompletableFuture future, String clientHost, String groupId, String topicId, long requestCount, int batchSize, MessageFilter filter, PopConsumerRecord.RetryType retryType) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topicId); if (null == topicConfig) { return future; } for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { long index = (brokerController.getBrokerConfig().isPriorityOrderAsc() ? topicConfig.getReadQueueNums() - 1 - i : i) + requestCount; int current = (int) index % topicConfig.getReadQueueNums(); future = this.getMessageAsync(future, clientHost, groupId, topicId, current, batchSize, filter, retryType); } return future; } public CompletableFuture popAsync(String clientHost, long popTime, long invisibleTime, String groupId, String topicId, int queueId, int batchSize, boolean fifo, String attemptId, int initMode, MessageFilter filter) { PopConsumerContext popConsumerContext = new PopConsumerContext(clientHost, popTime, invisibleTime, groupId, fifo, initMode, attemptId); TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topicId); if (topicConfig == null || !consumerLockService.tryLock(groupId, topicId)) { return CompletableFuture.completedFuture(popConsumerContext); } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(groupId); if (null == subscriptionGroupConfig || !subscriptionGroupConfig.isConsumeEnable()) { return CompletableFuture.completedFuture(popConsumerContext); } log.debug("PopConsumerService popAsync, groupId={}, topicId={}, queueId={}, " + "batchSize={}, invisibleTime={}, fifo={}, attemptId={}, filter={}", groupId, topicId, queueId, batchSize, invisibleTime, fifo, attemptId, filter); String requestKey = groupId + "@" + topicId; String retryTopicV1 = KeyBuilder.buildPopRetryTopicV1(topicId, groupId); String retryTopicV2 = KeyBuilder.buildPopRetryTopicV2(topicId, groupId); long requestCount = Objects.requireNonNull(ConcurrentHashMapUtils.computeIfAbsent( requestCountTable, requestKey, k -> new AtomicLong(0L))).getAndIncrement(); boolean usePriorityMode = TopicMessageType.PRIORITY.equals(topicConfig.getTopicMessageType()) && !fifo && requestCount % 100L < subscriptionGroupConfig.getPriorityFactor(); int probability = usePriorityMode ? brokerConfig.getPopFromRetryProbabilityForPriority() : brokerConfig.getPopFromRetryProbability(); probability = Math.max(0, Math.min(100, probability)); // [51, 100] means always boolean preferRetry = probability > 0 && requestCount % (100 / probability) == 0L; requestCount = usePriorityMode ? 0 : requestCount; // use requestCount as randomQ CompletableFuture getMessageFuture = CompletableFuture.completedFuture(popConsumerContext); try { if (!fifo && preferRetry) { if (brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, retryTopicV1, requestCount, batchSize, filter, PopConsumerRecord.RetryType.RETRY_TOPIC_V1); } if (brokerConfig.isEnableRetryTopicV2()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, retryTopicV2, requestCount, batchSize, filter, PopConsumerRecord.RetryType.RETRY_TOPIC_V2); } } if (queueId != -1) { getMessageFuture = this.getMessageAsync(getMessageFuture, clientHost, groupId, topicId, queueId, batchSize, filter, PopConsumerRecord.RetryType.NORMAL_TOPIC); } else { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, topicId, requestCount, batchSize, filter, PopConsumerRecord.RetryType.NORMAL_TOPIC); if (!fifo && !preferRetry) { if (brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, retryTopicV1, requestCount, batchSize, filter, PopConsumerRecord.RetryType.RETRY_TOPIC_V1); } if (brokerConfig.isEnableRetryTopicV2()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, retryTopicV2, requestCount, batchSize, filter, PopConsumerRecord.RetryType.RETRY_TOPIC_V2); } } } return getMessageFuture.thenCompose(result -> { if (result.isFound() && !result.isFifo()) { if (brokerConfig.isEnablePopBufferMerge() && popConsumerCache != null && !popConsumerCache.isCacheFull()) { this.popConsumerCache.writeRecords(result.getPopConsumerRecordList()); } else { this.popConsumerStore.writeRecords(result.getPopConsumerRecordList()); } for (int i = 0; i < result.getGetMessageResultList().size(); i++) { GetMessageResult getMessageResult = result.getGetMessageResultList().get(i); PopConsumerRecord popConsumerRecord = result.getPopConsumerRecordList().get(i); // If the buffer belong retries message, the message needs to be re-encoded. // The buffer should not be re-encoded when popResponseReturnActualRetryTopic // is true or the current topic is not a retry topic. boolean recode = brokerConfig.isPopResponseReturnActualRetryTopic(); if (recode && popConsumerRecord.isRetry()) { result.getGetMessageResultList().set(i, this.recodeRetryMessage( getMessageResult, popConsumerRecord.getTopicId(), popConsumerRecord.getQueueId(), result.getPopTime(), invisibleTime)); } } } return CompletableFuture.completedFuture(result); }).whenComplete((result, throwable) -> { try { if (throwable != null) { log.error("PopConsumerService popAsync get message error", throwable instanceof CompletionException ? throwable.getCause() : throwable); } if (result.getMessageCount() > 0) { log.debug("PopConsumerService popAsync result, found={}, groupId={}, topicId={}, queueId={}, " + "batchSize={}, invisibleTime={}, fifo={}, attemptId={}, filter={}", result.getMessageCount(), groupId, topicId, queueId, batchSize, invisibleTime, fifo, attemptId, filter); } } finally { consumerLockService.unlock(groupId, topicId); } }); } catch (Throwable t) { log.error("PopConsumerService popAsync error", t); } return getMessageFuture; } // Notify polling request when receive orderly ack public CompletableFuture ackAsync( long popTime, long invisibleTime, String groupId, String topicId, int queueId, long offset) { if (brokerConfig.isPopConsumerKVServiceLog()) { log.info("PopConsumerService ack, time={}, invisible={}, groupId={}, topic={}, queueId={}, offset={}", popTime, invisibleTime, groupId, topicId, queueId, offset); } PopConsumerRecord record = new PopConsumerRecord( popTime, groupId, topicId, queueId, 0, invisibleTime, offset, null); if (brokerConfig.isEnablePopBufferMerge() && popConsumerCache != null) { if (popConsumerCache.deleteRecords(Collections.singletonList(record)).isEmpty()) { return CompletableFuture.completedFuture(true); } } this.popConsumerStore.deleteRecords(Collections.singletonList(record)); return CompletableFuture.completedFuture(true); } // refer ChangeInvisibleTimeProcessor.appendCheckPointThenAckOrigin public void changeInvisibilityDuration(long popTime, long invisibleTime, long changedPopTime, long changedInvisibleTime, String groupId, String topicId, int queueId, long offset, boolean suspend) { if (brokerConfig.isPopConsumerKVServiceLog()) { log.info("PopConsumerService change, time={}, invisible={}, " + "groupId={}, topic={}, queueId={}, offset={}, new time={}, new invisible={}", popTime, invisibleTime, groupId, topicId, queueId, offset, changedPopTime, changedInvisibleTime); } PopConsumerRecord ckRecord = new PopConsumerRecord( changedPopTime, groupId, topicId, queueId, 0, changedInvisibleTime, offset, null, suspend); PopConsumerRecord ackRecord = new PopConsumerRecord( popTime, groupId, topicId, queueId, 0, invisibleTime, offset, null, suspend); // No need to generate new records when the group does not exist, // because these retry messages will not be consumed by anyone. if (brokerConfig.isPopReviveSkipIfGroupAbsent() && !brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(groupId)) { log.info("PopConsumerService change invisibility skip, time={}, " + "groupId={}, topicId={}, queueId={}, offset={}", popTime, groupId, topicId, queueId, offset); } else { this.popConsumerStore.writeRecords(Collections.singletonList(ckRecord)); } if (brokerConfig.isEnablePopBufferMerge() && popConsumerCache != null) { if (popConsumerCache.deleteRecords(Collections.singletonList(ackRecord)).isEmpty()) { return; } } this.popConsumerStore.deleteRecords(Collections.singletonList(ackRecord)); } // Use broker escape bridge to support remote read public CompletableFuture> getMessageAsync(PopConsumerRecord consumerRecord) { return this.brokerController.getEscapeBridge().getMessageAsync(consumerRecord.getTopicId(), consumerRecord.getOffset(), consumerRecord.getQueueId(), brokerConfig.getBrokerName(), false); } public CompletableFuture revive(PopConsumerRecord record) { if (brokerConfig.isPopReviveSkipIfGroupAbsent() && !brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(record.getGroupId())) { log.info("PopConsumerService skip revive message, record={}", record); return CompletableFuture.completedFuture(true); } return this.getMessageAsync(record) .thenCompose(result -> { if (result == null) { log.error("PopConsumerService revive error, message may be lost, record={}", record); return CompletableFuture.completedFuture(false); } // true in triple right means get message needs to be retried if (result.getLeft() == null) { log.info("PopConsumerService revive no need retry, record={}", record); return CompletableFuture.completedFuture(!result.getRight()); } return CompletableFuture.completedFuture(this.reviveRetry(record, result.getLeft())); }); } @SuppressWarnings("StatementWithEmptyBody") public void clearCache(String groupId, String topicId, int queueId) { while (consumerLockService.tryLock(groupId, topicId)) { } try { if (popConsumerCache != null) { popConsumerCache.removeRecords(groupId, topicId, queueId); } } finally { consumerLockService.unlock(groupId, topicId); } } public long revive(AtomicLong currentTime, int maxCount) { Stopwatch stopwatch = Stopwatch.createStarted(); long upperTime = System.currentTimeMillis() - 50L; List consumerRecords = this.popConsumerStore.scanExpiredRecords( currentTime.get() - TimeUnit.SECONDS.toMillis(3), upperTime, maxCount); long scanCostTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); // When reading messages from local storage, the current thread is used // directly for data retrieval. When reading original messages from remote // storage (such as distributed file systems), so concurrency needs to be // controlled via semaphore. Semaphore semaphore = new Semaphore(brokerConfig.getPopReviveConcurrency()); Queue failureList = new LinkedBlockingQueue<>(); List> futureList = new ArrayList<>(consumerRecords.size()); // could merge read operation here for (PopConsumerRecord record : consumerRecords) { CompletableFuture future; try { semaphore.acquire(); future = this.revive(record); } catch (Exception e) { semaphore.release(); throw new RuntimeException(e); } futureList.add(future.thenAccept(result -> { if (!result) { if (record.getAttemptTimes() < brokerConfig.getPopReviveMaxAttemptTimes()) { long backoffInterval = 1000L * REWRITE_INTERVALS_IN_SECONDS[ Math.min(REWRITE_INTERVALS_IN_SECONDS.length, record.getAttemptTimes())]; long nextInvisibleTime = record.getInvisibleTime() + backoffInterval; PopConsumerRecord retryRecord = new PopConsumerRecord(System.currentTimeMillis(), record.getGroupId(), record.getTopicId(), record.getQueueId(), record.getRetryFlag(), nextInvisibleTime, record.getOffset(), record.getAttemptId()); retryRecord.setAttemptTimes(record.getAttemptTimes() + 1); failureList.add(retryRecord); log.warn("PopConsumerService revive backoff retry, record={}", retryRecord); } else { log.error("PopConsumerService drop record, message may be lost, record={}", record); } } }).whenComplete((result, ex) -> semaphore.release())); } CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join(); this.popConsumerStore.writeRecords(new ArrayList<>(failureList)); this.popConsumerStore.deleteRecords(consumerRecords); currentTime.set(consumerRecords.isEmpty() ? upperTime : consumerRecords.get(consumerRecords.size() - 1).getVisibilityTimeout()); if (brokerConfig.isEnablePopBufferMerge()) { log.info("PopConsumerService, key size={}, cache size={}, revive count={}, failure count={}, " + "behindInMillis={}, scanInMillis={}, costInMillis={}", popConsumerCache.getCacheKeySize(), popConsumerCache.getCacheSize(), consumerRecords.size(), failureList.size(), upperTime - currentTime.get(), scanCostTime, stopwatch.elapsed(TimeUnit.MILLISECONDS)); } else { log.info("PopConsumerService, revive count={}, failure count={}, " + "behindInMillis={}, scanInMillis={}, costInMillis={}", consumerRecords.size(), failureList.size(), upperTime - currentTime.get(), scanCostTime, stopwatch.elapsed(TimeUnit.MILLISECONDS)); } return consumerRecords.size(); } public void createRetryTopicIfNeeded(String groupId, String retryTopic) { TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(retryTopic); if (topicConfig != null && !brokerController.getBrokerConfig().isUseSeparateRetryQueue()) { return; } int retryQueueNum = PopAckConstants.retryQueueNum; if (brokerController.getBrokerConfig().isUseSeparateRetryQueue()) { String normalTopic = KeyBuilder.parseNormalTopic(retryTopic, groupId); TopicConfig normalConfig = brokerController.getTopicConfigManager().selectTopicConfig(normalTopic); // always exists retryQueueNum = normalConfig.getWriteQueueNums(); if (topicConfig != null && topicConfig.getWriteQueueNums() == normalConfig.getWriteQueueNums()) { return; } } topicConfig = new TopicConfig(retryTopic, retryQueueNum, retryQueueNum, PermName.PERM_READ | PermName.PERM_WRITE, 0); topicConfig.setTopicFilterType(TopicFilterType.SINGLE_TAG); brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); for (int i = 0; i < retryQueueNum; i++) { long offset = this.brokerController.getConsumerOffsetManager().queryOffset(groupId, retryTopic, i); if (offset < 0) { this.brokerController.getConsumerOffsetManager().commitOffset( "InitPopOffset", groupId, retryTopic, i, 0); } } } @SuppressWarnings("DuplicatedCode") // org.apache.rocketmq.broker.processor.PopReviveService#reviveRetry public boolean reviveRetry(PopConsumerRecord record, MessageExt messageExt) { if (brokerConfig.isPopConsumerKVServiceLog()) { log.info("PopConsumerService revive, time={}, invisible={}, groupId={}, topic={}, queueId={}, offset={}", record.getPopTime(), record.getInvisibleTime(), record.getGroupId(), record.getTopicId(), record.getQueueId(), record.getOffset()); } boolean retry = StringUtils.startsWith(record.getTopicId(), MixAll.RETRY_GROUP_TOPIC_PREFIX); String retryTopic = retry ? record.getTopicId() : KeyBuilder.buildPopRetryTopic( record.getTopicId(), record.getGroupId(), brokerConfig.isEnableRetryTopicV2()); this.createRetryTopicIfNeeded(record.getGroupId(), retryTopic); // deep copy here MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(retryTopic); msgInner.setBody(messageExt.getBody() != null ? messageExt.getBody() : new byte[] {}); msgInner.setQueueId(getRetryQueueId(retryTopic, messageExt)); if (messageExt.getTags() != null) { msgInner.setTags(messageExt.getTags()); } else { MessageAccessor.setProperties(msgInner, new HashMap<>()); } msgInner.setBornTimestamp(messageExt.getBornTimestamp()); msgInner.setFlag(messageExt.getFlag()); msgInner.setSysFlag(messageExt.getSysFlag()); msgInner.setBornHost(brokerController.getStoreHost()); msgInner.setStoreHost(brokerController.getStoreHost()); if (record.isSuspend()) { msgInner.setReconsumeTimes(messageExt.getReconsumeTimes()); } else { msgInner.setReconsumeTimes(messageExt.getReconsumeTimes() + 1); } msgInner.getProperties().putAll(messageExt.getProperties()); // set first pop time here if (messageExt.getReconsumeTimes() == 0 || msgInner.getProperties().get(MessageConst.PROPERTY_FIRST_POP_TIME) == null) { msgInner.getProperties().put(MessageConst.PROPERTY_FIRST_POP_TIME, String.valueOf(record.getPopTime())); } msgInner.getProperties().put(MessageConst.PROPERTY_ORIGIN_GROUP, record.getGroupId()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); if (putMessageResult.getAppendMessageResult() == null || putMessageResult.getAppendMessageResult().getStatus() != AppendMessageStatus.PUT_OK) { log.error("PopConsumerService revive retry msg error, put status={}, ck={}, delay={}ms", putMessageResult, JSON.toJSONString(record), System.currentTimeMillis() - record.getVisibilityTimeout()); return false; } if (this.brokerController.getBrokerStatsManager() != null) { this.brokerController.getBrokerStatsManager().incBrokerPutNums(msgInner.getTopic(), 1); this.brokerController.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic()); this.brokerController.getBrokerStatsManager().incTopicPutSize( msgInner.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); } return true; } private int getRetryQueueId(String retryTopic, MessageExt oriMsg) { if (!brokerController.getBrokerConfig().isUseSeparateRetryQueue()) { return 0; } int oriQueueId = oriMsg.getQueueId(); // original qid of normal or retry topic if (oriQueueId > brokerController.getTopicConfigManager().selectTopicConfig(retryTopic).getWriteQueueNums() - 1) { log.warn("not expected, {}, {}, {}", retryTopic, oriQueueId, oriMsg.getMsgId()); return 0; // fallback } return oriQueueId; } // Export kv store record to revive topic @SuppressWarnings("ExtractMethodRecommender") public synchronized void transferToFsStore() { Stopwatch stopwatch = Stopwatch.createStarted(); while (true) { try { List consumerRecords = this.popConsumerStore.scanExpiredRecords( 0, Long.MAX_VALUE, brokerConfig.getPopReviveMaxReturnSizePerRead()); if (consumerRecords == null || consumerRecords.isEmpty()) { break; } for (PopConsumerRecord record : consumerRecords) { PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); ck.setNum((byte) 1); ck.setPopTime(record.getPopTime()); ck.setInvisibleTime(record.getInvisibleTime()); ck.setStartOffset(record.getOffset()); ck.setCId(record.getGroupId()); ck.setTopic(record.getTopicId()); ck.setQueueId(record.getQueueId()); ck.setBrokerName(brokerConfig.getBrokerName()); ck.addDiff(0); ck.setRePutTimes(ck.getRePutTimes()); int reviveQueueId = (int) record.getOffset() % brokerConfig.getReviveQueueNum(); MessageExtBrokerInner ckMsg = brokerController.getPopMessageProcessor().buildCkMsg(ck, reviveQueueId); brokerController.getMessageStore().asyncPutMessage(ckMsg).join(); } log.info("PopConsumerStore transfer from kvStore to fsStore, count={}", consumerRecords.size()); this.popConsumerStore.deleteRecords(consumerRecords); this.waitForRunning(1); } catch (Throwable t) { log.error("PopConsumerStore transfer from kvStore to fsStore failure", t); } } log.info("PopConsumerStore transfer to fsStore finish, cost={}ms", stopwatch.elapsed(TimeUnit.MILLISECONDS)); } @Override public String getServiceName() { return PopConsumerService.class.getSimpleName(); } @VisibleForTesting protected PopConsumerKVStore getPopConsumerStore() { return popConsumerStore; } public PopConsumerLockService getConsumerLockService() { return consumerLockService; } @Override public void start() { if (!this.popConsumerStore.start()) { throw new RuntimeException("PopConsumerStore init error"); } if (this.popConsumerCache != null) { this.popConsumerCache.start(); } super.start(); } @Override public void shutdown() { // Block shutdown thread until write records finish super.shutdown(); do { this.waitForRunning(10); } while (consumerRunning.get()); if (this.popConsumerCache != null) { this.popConsumerCache.shutdown(); } if (this.popConsumerStore != null) { this.popConsumerStore.shutdown(); } } @Override public void run() { this.consumerRunning.set(true); while (!isStopped()) { try { // to prevent concurrency issues during read and write operations long reviveCount = this.revive(this.currentTime, brokerConfig.getPopReviveMaxReturnSizePerRead()); long current = System.currentTimeMillis(); if (lastCleanupLockTime.get() + TimeUnit.MINUTES.toMillis(1) < current) { this.consumerLockService.removeTimeout(); this.lastCleanupLockTime.set(current); } if (reviveCount < brokerConfig.getPopReviveMaxReturnSizePerRead()) { this.waitForRunning(500); } } catch (Exception e) { log.error("PopConsumerService revive error", e); this.waitForRunning(500); } } this.consumerRunning.set(false); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/orderly/ConsumerOrderInfoManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop.orderly; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.OrderedConsumptionLevel; import org.apache.rocketmq.store.GetMessageResult; /** * * Ordered Consumption Controller Interface * This is the top-level interface that encapsulates complete ordered consumption management functionality, * supporting different concurrency strategy implementations *

* Design Goals: * 1. Support queue-level ordered consumption (existing implementation) * 2. Support message group-level ordered consumption (improve concurrency) * 3. Support custom ordered consumption strategies *

*/ public interface ConsumerOrderInfoManager { /** * Update the reception status of message list * Called by handleGetMessageResult when consumer POPs messages, used to record message status and build consumption information * * @param attemptId Distinguish different pop requests * @param isRetry Whether it is a retry topic * @param topic Topic name * @param group Consumer group name * @param queueId Queue ID * @param popTime Time when messages are popped * @param invisibleTime Message invisible time * @param msgQueueOffsetList List of message queue offsets * @param orderInfoBuilder String builder for constructing order information * @param getMessageResult Return new result */ void update(String attemptId, boolean isRetry, String topic, String group, int queueId, long popTime, long invisibleTime, List msgQueueOffsetList, StringBuilder orderInfoBuilder, GetMessageResult getMessageResult); /** * Check whether the current POP request needs to be blocked * Used to ensure ordered consumption of ordered messages * Called when consumer POPs messages * * @param attemptId Attempt ID * @param topic Topic name * @param group Consumer group name * @param queueId Queue ID * @param invisibleTime Invisible time * @return true indicates blocking is needed, false indicates can proceed */ boolean checkBlock(String attemptId, String topic, String group, int queueId, long invisibleTime); /** * Remove the specified topic and group * Usually called during topic deletion * * @param topic Topic name * @param group Consumer group name */ void remove(String topic, String group); /** * Get order info count */ int getOrderInfoCount(); /** * Commit message and calculate next consumption offset * Called when consumer ACKs messages * * @param topic Topic name * @param group Consumer group name * @param queueId Queue ID * @param queueOffset Message queue offset * @param popTime Pop time, used for validation * @return -1: invalid, -2: no need to commit, >=0: offset that needs to be committed (indicates messages below this offset have been consumed) */ long commitAndNext(String topic, String group, int queueId, long queueOffset, long popTime); /** * Update the next visible time of message * Used for delayed message re-consumption * * @param topic Topic name * @param group Consumer group name * @param queueId Queue ID * @param queueOffset Message offset * @param popTime Pop time, used for validation * @param nextVisibleTime Next visible time */ void updateNextVisibleTime(String topic, String group, int queueId, long queueOffset, long popTime, long nextVisibleTime); /** * Clear the blocking status of specified queue * Usually called during consumer rebalancing or queue reassignment * * @param topic Topic name * @param group Consumer group name * @param queueId Queue ID */ void clearBlock(String topic, String group, int queueId); /** * Get ordered consumption level * Used to distinguish different implementation strategies * * @return Ordered consumption level, such as: QUEUE, MESSAGE_GROUP, etc. */ OrderedConsumptionLevel getOrderedConsumptionLevel(); /** * Start the controller * Initialize necessary resources, such as timers, thread pools, etc. */ void start(); /** * Shutdown the controller * Release resources, clean up scheduled tasks, etc. */ void shutdown(); /** * Persist the controller * Persist the controller's data */ void persist(); boolean load(); /** * Get available message result * Used to retrieve messages from cache */ CompletableFuture getAvailableMessageResult(String attemptId, long popTime, long invisibleTime, String groupId, String topicId, int queueId, int batchSize, StringBuilder orderCountInfoBuilder); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/orderly/QueueLevelConsumerManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop.orderly; import com.alibaba.fastjson2.annotation.JSONField; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.OrderedConsumptionLevel; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.store.GetMessageResult; public class QueueLevelConsumerManager extends ConfigManager implements ConsumerOrderInfoManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final String TOPIC_GROUP_SEPARATOR = "@"; private static final long CLEAN_SPAN_FROM_LAST = 24 * 3600 * 1000; private ConcurrentHashMap> table = new ConcurrentHashMap<>(128); private transient QueueLevelConsumerOrderInfoLockManager queueLevelConsumerOrderInfoLockManager; private transient BrokerController brokerController; public QueueLevelConsumerManager() { } public QueueLevelConsumerManager(BrokerController brokerController) { this.brokerController = brokerController; this.queueLevelConsumerOrderInfoLockManager = new QueueLevelConsumerOrderInfoLockManager(brokerController); } public ConcurrentHashMap> getTable() { return table; } public void setTable(ConcurrentHashMap> table) { this.table = table; } protected static String buildKey(String topic, String group) { return topic + TOPIC_GROUP_SEPARATOR + group; } protected static String[] decodeKey(String key) { return key.split(TOPIC_GROUP_SEPARATOR); } protected void updateLockFreeTimestamp(String topic, String group, int queueId, OrderInfo orderInfo) { if (queueLevelConsumerOrderInfoLockManager != null) { queueLevelConsumerOrderInfoLockManager.updateLockFreeTimestamp(topic, group, queueId, orderInfo); } } /** * update the message list received * * @param isRetry is retry topic or not * @param topic topic * @param group group * @param queueId queue id of message * @param popTime the time of pop message * @param invisibleTime invisible time * @param msgQueueOffsetList the queue offsets of messages * @param orderInfoBuilder will append order info to this builder */ public void update(String attemptId, boolean isRetry, String topic, String group, int queueId, long popTime, long invisibleTime, List msgQueueOffsetList, StringBuilder orderInfoBuilder) { String key = buildKey(topic, group); ConcurrentHashMap qs = table.get(key); if (qs == null) { qs = new ConcurrentHashMap<>(16); ConcurrentHashMap old = table.putIfAbsent(key, qs); if (old != null) { qs = old; } } OrderInfo orderInfo = qs.get(queueId); if (orderInfo != null) { OrderInfo newOrderInfo = new OrderInfo(attemptId, popTime, invisibleTime, msgQueueOffsetList, System.currentTimeMillis(), 0); newOrderInfo.mergeOffsetConsumedCount(orderInfo.attemptId, orderInfo.offsetList, orderInfo.offsetConsumedCount); orderInfo = newOrderInfo; } else { orderInfo = new OrderInfo(attemptId, popTime, invisibleTime, msgQueueOffsetList, System.currentTimeMillis(), 0); } qs.put(queueId, orderInfo); Map offsetConsumedCount = orderInfo.offsetConsumedCount; int minConsumedTimes = Integer.MAX_VALUE; if (offsetConsumedCount != null) { Set offsetSet = offsetConsumedCount.keySet(); for (Long offset : offsetSet) { Integer consumedTimes = offsetConsumedCount.getOrDefault(offset, 0); ExtraInfoUtil.buildQueueOffsetOrderCountInfo(orderInfoBuilder, topic, queueId, offset, consumedTimes); minConsumedTimes = Math.min(minConsumedTimes, consumedTimes); } if (offsetConsumedCount.size() != orderInfo.offsetList.size()) { // offsetConsumedCount only save messages which consumed count is greater than 0 // if size not equal, means there are some new messages minConsumedTimes = 0; } } else { minConsumedTimes = 0; } // for compatibility // the old pop sdk use queueId to get consumedTimes from orderCountInfo ExtraInfoUtil.buildQueueIdOrderCountInfo(orderInfoBuilder, topic, queueId, minConsumedTimes); updateLockFreeTimestamp(topic, group, queueId, orderInfo); } @Override public void update(String attemptId, boolean isRetry, String topic, String group, int queueId, long popTime, long invisibleTime, List msgQueueOffsetList, StringBuilder orderInfoBuilder, GetMessageResult getMessageResult) { update(attemptId, isRetry, topic, group, queueId, popTime, invisibleTime, msgQueueOffsetList, orderInfoBuilder); } @Override public boolean checkBlock(String attemptId, String topic, String group, int queueId, long invisibleTime) { String key = buildKey(topic, group); ConcurrentHashMap qs = table.get(key); if (qs == null) { qs = new ConcurrentHashMap<>(16); ConcurrentHashMap old = table.putIfAbsent(key, qs); if (old != null) { qs = old; } } OrderInfo orderInfo = qs.get(queueId); if (orderInfo == null) { return false; } return orderInfo.needBlock(attemptId, invisibleTime); } @Override public void clearBlock(String topic, String group, int queueId) { table.computeIfPresent(buildKey(topic, group), (key, val) -> { val.remove(queueId); return val; }); } @Override public void remove(String topic, String group) { table.remove(buildKey(topic, group)); } @Override public int getOrderInfoCount() { return table.size(); } @Override public OrderedConsumptionLevel getOrderedConsumptionLevel() { return OrderedConsumptionLevel.QUEUE; } @Override public void start() { } /** * mark message is consumed finished. return the consumer offset * * @param topic topic * @param group group * @param queueId queue id of message * @param queueOffset queue offset of message * @return -1 : illegal, -2 : no need commit, >= 0 : commit */ @Override public long commitAndNext(String topic, String group, int queueId, long queueOffset, long popTime) { String key = buildKey(topic, group); ConcurrentHashMap qs = table.get(key); if (qs == null) { return queueOffset + 1; } OrderInfo orderInfo = qs.get(queueId); if (orderInfo == null) { log.warn("OrderInfo is null, {}, {}, {}", key, queueOffset, orderInfo); return queueOffset + 1; } List o = orderInfo.offsetList; if (o == null || o.isEmpty()) { log.warn("OrderInfo is empty, {}, {}, {}", key, queueOffset, orderInfo); return -1; } if (popTime != orderInfo.popTime) { log.warn("popTime is not equal to orderInfo saved. key: {}, offset: {}, orderInfo: {}, popTime: {}", key, queueOffset, orderInfo, popTime); return -2; } Long first = o.get(0); int i = 0, size = o.size(); for (; i < size; i++) { long temp; if (i == 0) { temp = first; } else { temp = first + o.get(i); } if (queueOffset == temp) { break; } } // not found if (i >= size) { log.warn("OrderInfo not found commit offset, {}, {}, {}", key, queueOffset, orderInfo); return -1; } //set bit orderInfo.setCommitOffsetBit(orderInfo.commitOffsetBit | (1L << i)); long nextOffset = orderInfo.getNextOffset(); updateLockFreeTimestamp(topic, group, queueId, orderInfo); return nextOffset; } /** * update next visible time of this message * * @param topic topic * @param group group * @param queueId queue id of message * @param queueOffset queue offset of message * @param nextVisibleTime nex visible time */ @Override public void updateNextVisibleTime(String topic, String group, int queueId, long queueOffset, long popTime, long nextVisibleTime) { String key = buildKey(topic, group); ConcurrentHashMap qs = table.get(key); if (qs == null) { log.warn("orderInfo of queueId is null. key: {}, queueOffset: {}, queueId: {}", key, queueOffset, queueId); return; } OrderInfo orderInfo = qs.get(queueId); if (orderInfo == null) { log.warn("orderInfo is null, key: {}, queueOffset: {}, queueId: {}", key, queueOffset, queueId); return; } if (popTime != orderInfo.popTime) { log.warn("popTime is not equal to orderInfo saved. key: {}, queueOffset: {}, orderInfo: {}, popTime: {}", key, queueOffset, orderInfo, popTime); return; } orderInfo.updateOffsetNextVisibleTime(queueOffset, nextVisibleTime); updateLockFreeTimestamp(topic, group, queueId, orderInfo); } @VisibleForTesting protected void autoClean() { if (brokerController == null) { return; } Iterator>> iterator = this.table.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); String topicAtGroup = entry.getKey(); ConcurrentHashMap qs = entry.getValue(); String[] arrays = decodeKey(topicAtGroup); if (arrays.length != 2) { continue; } String topic = arrays[0]; String group = arrays[1]; TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (topicConfig == null) { iterator.remove(); log.info("Topic not exist, Clean order info, {}:{}", topicAtGroup, qs); continue; } if (!this.brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(group)) { iterator.remove(); log.info("Group not exist, Clean order info, {}:{}", topicAtGroup, qs); continue; } if (qs.isEmpty()) { iterator.remove(); log.info("Order table is empty, Clean order info, {}:{}", topicAtGroup, qs); continue; } Iterator> qsIterator = qs.entrySet().iterator(); while (qsIterator.hasNext()) { Map.Entry qsEntry = qsIterator.next(); if (qsEntry.getKey() >= topicConfig.getReadQueueNums()) { qsIterator.remove(); log.info("Queue not exist, Clean order info, {}:{}, {}", topicAtGroup, entry.getValue(), topicConfig); continue; } if (System.currentTimeMillis() - qsEntry.getValue().getLastConsumeTimestamp() > CLEAN_SPAN_FROM_LAST) { qsIterator.remove(); log.info("Not consume long time, Clean order info, {}:{}, {}", topicAtGroup, entry.getValue(), topicConfig); } } } } @Override public String encode() { return this.encode(false); } @Override public String configFilePath() { if (brokerController != null) { return BrokerPathConfigHelper.getConsumerOrderInfoPath(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); } else { return BrokerPathConfigHelper.getConsumerOrderInfoPath("~"); } } @Override public void decode(String jsonString) { if (jsonString != null) { QueueLevelConsumerManager obj = RemotingSerializable.fromJson(jsonString, QueueLevelConsumerManager.class); if (obj != null) { this.table = obj.table; if (this.queueLevelConsumerOrderInfoLockManager != null) { this.queueLevelConsumerOrderInfoLockManager.recover(this.table); } } } } @Override public String encode(boolean prettyFormat) { this.autoClean(); return RemotingSerializable.toJson(this, prettyFormat); } public void shutdown() { if (this.queueLevelConsumerOrderInfoLockManager != null) { this.queueLevelConsumerOrderInfoLockManager.shutdown(); } } @Override public CompletableFuture getAvailableMessageResult(String attemptId, long popTime, long invisibleTime, String groupId, String topicId, int queueId, int batchSize, StringBuilder orderCountInfoBuilder) { return CompletableFuture.completedFuture(null); } @VisibleForTesting protected QueueLevelConsumerOrderInfoLockManager getConsumerOrderInfoLockManager() { return queueLevelConsumerOrderInfoLockManager; } public static class OrderInfo { private long popTime; /** * the invisibleTime when pop message */ @JSONField(name = "i") private Long invisibleTime; /** * offset * offsetList[0] is the queue offset of message * offsetList[i] (i > 0) is the distance between current message and offsetList[0] */ @JSONField(name = "o") private List offsetList; /** * next visible timestamp for message * key: message queue offset */ @JSONField(name = "ot") private Map offsetNextVisibleTime; /** * message consumed count for offset * key: message queue offset */ @JSONField(name = "oc") private Map offsetConsumedCount; /** * last consume timestamp */ @JSONField(name = "l") private long lastConsumeTimestamp; /** * commit offset bit */ @JSONField(name = "cm") private long commitOffsetBit; @JSONField(name = "a") private String attemptId; public OrderInfo() { } public OrderInfo(String attemptId, long popTime, long invisibleTime, List queueOffsetList, long lastConsumeTimestamp, long commitOffsetBit) { this.popTime = popTime; this.invisibleTime = invisibleTime; this.offsetList = buildOffsetList(queueOffsetList); this.lastConsumeTimestamp = lastConsumeTimestamp; this.commitOffsetBit = commitOffsetBit; this.attemptId = attemptId; } public List getOffsetList() { return offsetList; } public void setOffsetList(List offsetList) { this.offsetList = offsetList; } public long getLastConsumeTimestamp() { return lastConsumeTimestamp; } public void setLastConsumeTimestamp(long lastConsumeTimestamp) { this.lastConsumeTimestamp = lastConsumeTimestamp; } public long getCommitOffsetBit() { return commitOffsetBit; } public void setCommitOffsetBit(long commitOffsetBit) { this.commitOffsetBit = commitOffsetBit; } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public Long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(Long invisibleTime) { this.invisibleTime = invisibleTime; } public Map getOffsetNextVisibleTime() { return offsetNextVisibleTime; } public void setOffsetNextVisibleTime(Map offsetNextVisibleTime) { this.offsetNextVisibleTime = offsetNextVisibleTime; } public Map getOffsetConsumedCount() { return offsetConsumedCount; } public void setOffsetConsumedCount(Map offsetConsumedCount) { this.offsetConsumedCount = offsetConsumedCount; } public String getAttemptId() { return attemptId; } public void setAttemptId(String attemptId) { this.attemptId = attemptId; } public static List buildOffsetList(List queueOffsetList) { List simple = new ArrayList<>(); if (queueOffsetList.size() == 1) { simple.addAll(queueOffsetList); return simple; } Long first = queueOffsetList.get(0); simple.add(first); for (int i = 1; i < queueOffsetList.size(); i++) { simple.add(queueOffsetList.get(i) - first); } return simple; } @JSONField(serialize = false, deserialize = false) public boolean needBlock(String attemptId, long currentInvisibleTime) { if (offsetList == null || offsetList.isEmpty()) { return false; } if (this.attemptId != null && this.attemptId.equals(attemptId)) { return false; } int num = offsetList.size(); int i = 0; if (this.invisibleTime == null || this.invisibleTime <= 0) { this.invisibleTime = currentInvisibleTime; } long currentTime = System.currentTimeMillis(); for (; i < num; i++) { if (isNotAck(i)) { long nextVisibleTime = popTime + invisibleTime; if (offsetNextVisibleTime != null) { Long time = offsetNextVisibleTime.get(this.getQueueOffset(i)); if (time != null) { nextVisibleTime = time; } } if (currentTime < nextVisibleTime) { return true; } } } return false; } @JSONField(serialize = false, deserialize = false) public Long getLockFreeTimestamp() { if (offsetList == null || offsetList.isEmpty()) { return null; } int num = offsetList.size(); int i = 0; long currentTime = System.currentTimeMillis(); for (; i < num; i++) { if (isNotAck(i)) { if (invisibleTime == null || invisibleTime <= 0) { return null; } long nextVisibleTime = popTime + invisibleTime; if (offsetNextVisibleTime != null) { Long time = offsetNextVisibleTime.get(this.getQueueOffset(i)); if (time != null) { nextVisibleTime = time; } } if (currentTime < nextVisibleTime) { return nextVisibleTime; } } } return currentTime; } @JSONField(serialize = false, deserialize = false) public Long getMaxLockFreeTimestamp() { if (offsetList == null || offsetList.isEmpty()) { return null; } int num = offsetList.size(); long maxTime = System.currentTimeMillis(); for (int i = 0; i < num; i++) { if (isNotAck(i)) { if (invisibleTime == null || invisibleTime <= 0) { return null; } long nextVisibleTime = popTime + invisibleTime; if (offsetNextVisibleTime != null) { Long time = offsetNextVisibleTime.get(this.getQueueOffset(i)); if (time != null) { nextVisibleTime = time; } } if (maxTime < nextVisibleTime) { maxTime = nextVisibleTime; } } } return maxTime; } @JSONField(serialize = false, deserialize = false) public void updateOffsetNextVisibleTime(long queueOffset, long nextVisibleTime) { if (this.offsetNextVisibleTime == null) { this.offsetNextVisibleTime = new HashMap<>(); } this.offsetNextVisibleTime.put(queueOffset, nextVisibleTime); } @JSONField(serialize = false, deserialize = false) public long getNextOffset() { if (offsetList == null || offsetList.isEmpty()) { return -2; } int num = offsetList.size(); int i = 0; for (; i < num; i++) { if (isNotAck(i)) { break; } } if (i == num) { // all ack return getQueueOffset(num - 1) + 1; } return getQueueOffset(i); } /** * convert the offset at the index of offsetList to queue offset * * @param offsetIndex the index of offsetList * @return queue offset of message */ @JSONField(serialize = false, deserialize = false) public long getQueueOffset(int offsetIndex) { return getQueueOffset(this.offsetList, offsetIndex); } protected static long getQueueOffset(List offsetList, int offsetIndex) { if (offsetIndex == 0) { return offsetList.get(0); } return offsetList.get(0) + offsetList.get(offsetIndex); } @JSONField(serialize = false, deserialize = false) public boolean isNotAck(int offsetIndex) { return (commitOffsetBit & (1L << offsetIndex)) == 0; } /** * calculate message consumed count of each message, and put nonzero value into offsetConsumedCount * * @param prevOffsetConsumedCount the offset list of message */ @JSONField(serialize = false, deserialize = false) public void mergeOffsetConsumedCount(String preAttemptId, List preOffsetList, Map prevOffsetConsumedCount) { Map offsetConsumedCount = new HashMap<>(); if (prevOffsetConsumedCount == null) { prevOffsetConsumedCount = new HashMap<>(); } if (preAttemptId != null && preAttemptId.equals(this.attemptId)) { this.offsetConsumedCount = prevOffsetConsumedCount; return; } Set preQueueOffsetSet = new HashSet<>(); for (int i = 0; i < preOffsetList.size(); i++) { preQueueOffsetSet.add(getQueueOffset(preOffsetList, i)); } for (int i = 0; i < offsetList.size(); i++) { long queueOffset = this.getQueueOffset(i); if (preQueueOffsetSet.contains(queueOffset)) { int count = 1; Integer preCount = prevOffsetConsumedCount.get(queueOffset); if (preCount != null) { count = preCount + 1; } offsetConsumedCount.put(queueOffset, count); } } this.offsetConsumedCount = offsetConsumedCount; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("popTime", popTime) .add("invisibleTime", invisibleTime) .add("offsetList", offsetList) .add("offsetNextVisibleTime", offsetNextVisibleTime) .add("offsetConsumedCount", offsetConsumedCount) .add("lastConsumeTimestamp", lastConsumeTimestamp) .add("commitOffsetBit", commitOffsetBit) .add("attemptId", attemptId) .toString(); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/pop/orderly/QueueLevelConsumerOrderInfoLockManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop.orderly; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import io.netty.util.HashedWheelTimer; import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class QueueLevelConsumerOrderInfoLockManager { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private ConsumerOrderInfoManager consumerOrderInfoManager; private final BrokerController brokerController; private final Map timeoutMap = new ConcurrentHashMap<>(); private final Timer timer; private static final int TIMER_TICK_MS = 100; public QueueLevelConsumerOrderInfoLockManager(BrokerController brokerController) { this.brokerController = brokerController; this.timer = new HashedWheelTimer( new ThreadFactoryImpl("ConsumerOrderInfoLockManager_"), TIMER_TICK_MS, TimeUnit.MILLISECONDS); } /** * when QueueLevelConsumerManager load from disk, recover data */ public void recover(Map> table) { if (!this.brokerController.getBrokerConfig().isEnableNotifyAfterPopOrderLockRelease()) { return; } for (Map.Entry> entry : table.entrySet()) { String topicAtGroup = entry.getKey(); ConcurrentHashMap qs = entry.getValue(); String[] arrays = QueueLevelConsumerManager.decodeKey(topicAtGroup); if (arrays.length != 2) { continue; } String topic = arrays[0]; String group = arrays[1]; for (Map.Entry qsEntry : qs.entrySet()) { Long lockFreeTimestamp = qsEntry.getValue().getLockFreeTimestamp(); if (lockFreeTimestamp == null || lockFreeTimestamp <= System.currentTimeMillis()) { continue; } this.updateLockFreeTimestamp(topic, group, qsEntry.getKey(), lockFreeTimestamp); } } } public void updateLockFreeTimestamp(String topic, String group, int queueId, QueueLevelConsumerManager.OrderInfo orderInfo) { this.updateLockFreeTimestamp(topic, group, queueId, orderInfo.getLockFreeTimestamp()); } public void updateLockFreeTimestamp(String topic, String group, int queueId, Long lockFreeTimestamp) { if (!this.brokerController.getBrokerConfig().isEnableNotifyAfterPopOrderLockRelease()) { return; } if (lockFreeTimestamp == null) { return; } try { this.timeoutMap.compute(new Key(topic, group, queueId), (key, oldTimeout) -> { try { long delay = lockFreeTimestamp - System.currentTimeMillis(); Timeout newTimeout = this.timer.newTimeout(new NotifyLockFreeTimerTask(key), delay, TimeUnit.MILLISECONDS); if (oldTimeout != null) { // cancel prev timerTask oldTimeout.cancel(); } return newTimeout; } catch (Exception e) { POP_LOGGER.warn("add timeout task failed. key:{}, lockFreeTimestamp:{}", key, lockFreeTimestamp, e); return oldTimeout; } }); } catch (Exception e) { POP_LOGGER.error("unexpect error when updateLockFreeTimestamp. topic:{}, group:{}, queueId:{}, lockFreeTimestamp:{}", topic, group, queueId, lockFreeTimestamp, e); } } protected void notifyLockIsFree(Key key) { try { if (LiteUtil.isLiteTopicQueue(key.topic)) { this.brokerController.getLiteEventDispatcher().dispatch(key.group, key.topic, key.queueId, -1, -1); return; } this.brokerController.getPopMessageProcessor().notifyLongPollingRequestIfNeed(key.topic, key.group, key.queueId); } catch (Exception e) { POP_LOGGER.error("unexpect error when notifyLockIsFree. key:{}", key, e); } } public void shutdown() { this.timer.stop(); } @VisibleForTesting protected Map getTimeoutMap() { return timeoutMap; } private class NotifyLockFreeTimerTask implements TimerTask { private final Key key; private NotifyLockFreeTimerTask(Key key) { this.key = key; } @Override public void run(Timeout timeout) throws Exception { if (timeout.isCancelled() || !brokerController.getBrokerConfig().isEnableNotifyAfterPopOrderLockRelease()) { return; } notifyLockIsFree(key); timeoutMap.computeIfPresent(key, (key1, curTimeout) -> { if (curTimeout == timeout) { // remove from map return null; } return curTimeout; }); } } private static class Key { private final String topic; private final String group; private final int queueId; public Key(String topic, String group, int queueId) { this.topic = topic; this.group = group; this.queueId = queueId; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Key key = (Key) o; return queueId == key.queueId && Objects.equal(topic, key.topic) && Objects.equal(group, key.group); } @Override public int hashCode() { return Objects.hashCode(topic, group, queueId); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("group", group) .add("queueId", queueId) .toString(); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import io.opentelemetry.api.common.Attributes; import java.net.SocketAddress; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageContext; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook; import org.apache.rocketmq.broker.mqtrace.SendMessageContext; import org.apache.rocketmq.broker.mqtrace.SendMessageHook; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.DBMsgConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageType; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.sysflag.TopicSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.stats.BrokerStatsManager; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; public abstract class AbstractSendMessageProcessor implements NettyRequestProcessor { protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); protected static final Logger DLQ_LOG = LoggerFactory.getLogger(LoggerName.DLQ_LOGGER_NAME); protected List consumeMessageHookList; protected final static int DLQ_NUMS_PER_GROUP = 1; protected final BrokerController brokerController; protected final Random random = new Random(System.currentTimeMillis()); private List sendMessageHookList; public AbstractSendMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } public void registerConsumeMessageHook(List consumeMessageHookList) { this.consumeMessageHookList = consumeMessageHookList; } protected RemotingCommand consumerSendMsgBack(final ChannelHandlerContext ctx, final RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final ConsumerSendMsgBackRequestHeader requestHeader = (ConsumerSendMsgBackRequestHeader) request.decodeCommandCustomHeader(ConsumerSendMsgBackRequestHeader.class); // The send back requests sent to SlaveBroker will be forwarded to the master broker beside final BrokerController masterBroker = this.brokerController.peekMasterBroker(); if (null == masterBroker) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("no master available along with " + brokerController.getBrokerConfig().getBrokerIP1()); return response; } // The broker that received the request. // It may be a master broker or a slave broker final BrokerController currentBroker = this.brokerController; SubscriptionGroupConfig subscriptionGroupConfig = masterBroker.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark("subscription group not exist, " + requestHeader.getGroup() + " " + FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)); return response; } BrokerConfig masterBrokerConfig = masterBroker.getBrokerConfig(); if (!PermName.isWriteable(masterBrokerConfig.getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("the broker[" + masterBrokerConfig.getBrokerIP1() + "] sending message is forbidden"); return response; } if (subscriptionGroupConfig.getRetryQueueNums() <= 0) { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } String newTopic = MixAll.getRetryTopic(requestHeader.getGroup()); int queueIdInt = this.random.nextInt(subscriptionGroupConfig.getRetryQueueNums()); int topicSysFlag = 0; if (requestHeader.isUnitMode()) { topicSysFlag = TopicSysFlag.buildSysFlag(false, true); } // Create retry topic to master broker TopicConfig topicConfig = masterBroker.getTopicConfigManager().createTopicInSendMessageBackMethod( newTopic, subscriptionGroupConfig.getRetryQueueNums(), PermName.PERM_WRITE | PermName.PERM_READ, topicSysFlag); if (null == topicConfig) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("topic[" + newTopic + "] not exist"); return response; } if (!PermName.isWriteable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the topic[%s] sending message is forbidden", newTopic)); return response; } // Look message from the origin message store MessageExt msgExt = currentBroker.getMessageStore().lookMessageByOffset(requestHeader.getOffset()); if (null == msgExt) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("look message by offset failed, " + requestHeader.getOffset()); return response; } final String retryTopic = msgExt.getProperty(MessageConst.PROPERTY_RETRY_TOPIC); if (null == retryTopic) { MessageAccessor.putProperty(msgExt, MessageConst.PROPERTY_RETRY_TOPIC, msgExt.getTopic()); } msgExt.setWaitStoreMsgOK(false); int delayLevel = requestHeader.getDelayLevel(); int maxReconsumeTimes = subscriptionGroupConfig.getRetryMaxTimes(); if (request.getVersion() >= MQVersion.Version.V3_4_9.ordinal()) { Integer times = requestHeader.getMaxReconsumeTimes(); if (times != null) { maxReconsumeTimes = times; } } boolean isDLQ = false; if (msgExt.getReconsumeTimes() >= maxReconsumeTimes || delayLevel < 0) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_CONSUMER_GROUP, requestHeader.getGroup()) .put(LABEL_TOPIC, requestHeader.getOriginTopic()) .put(LABEL_IS_SYSTEM, BrokerMetricsManager.isSystem(requestHeader.getOriginTopic(), requestHeader.getGroup())) .build(); this.brokerController.getBrokerMetricsManager().getSendToDlqMessages().add(1, attributes); isDLQ = true; newTopic = MixAll.getDLQTopic(requestHeader.getGroup()); queueIdInt = randomQueueId(DLQ_NUMS_PER_GROUP); // Create DLQ topic to master broker topicConfig = masterBroker.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic, DLQ_NUMS_PER_GROUP, PermName.PERM_WRITE | PermName.PERM_READ, 0); if (null == topicConfig) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("topic[" + newTopic + "] not exist"); return response; } msgExt.setDelayTimeLevel(0); } else { if (0 == delayLevel) { delayLevel = 3 + msgExt.getReconsumeTimes(); } msgExt.setDelayTimeLevel(delayLevel); } MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(newTopic); msgInner.setBody(msgExt.getBody()); msgInner.setFlag(msgExt.getFlag()); MessageAccessor.setProperties(msgInner, msgExt.getProperties()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(null, msgExt.getTags())); msgInner.setQueueId(queueIdInt); msgInner.setSysFlag(msgExt.getSysFlag()); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setStoreHost(this.getStoreHost()); msgInner.setReconsumeTimes(msgExt.getReconsumeTimes() + 1); String originMsgId = MessageAccessor.getOriginMessageId(msgExt); MessageAccessor.setOriginMessageId(msgInner, UtilAll.isBlank(originMsgId) ? msgExt.getMsgId() : originMsgId); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); boolean succeeded = false; // Put retry topic to master message store PutMessageResult putMessageResult = masterBroker.getMessageStore().putMessage(msgInner); if (putMessageResult != null) { String commercialOwner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); switch (putMessageResult.getPutMessageStatus()) { case PUT_OK: String backTopic = msgExt.getTopic(); String correctTopic = msgExt.getProperty(MessageConst.PROPERTY_RETRY_TOPIC); if (correctTopic != null) { backTopic = correctTopic; } if (TopicValidator.RMQ_SYS_SCHEDULE_TOPIC.equals(msgInner.getTopic())) { masterBroker.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic()); masterBroker.getBrokerStatsManager().incTopicPutSize(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); masterBroker.getBrokerStatsManager().incQueuePutNums(msgInner.getTopic(), msgInner.getQueueId()); masterBroker.getBrokerStatsManager().incQueuePutSize(msgInner.getTopic(), msgInner.getQueueId(), putMessageResult.getAppendMessageResult().getWroteBytes()); } masterBroker.getBrokerStatsManager().incSendBackNums(requestHeader.getGroup(), backTopic); if (isDLQ) { masterBroker.getBrokerStatsManager().incDLQStatValue( BrokerStatsManager.SNDBCK2DLQ_TIMES, commercialOwner, requestHeader.getGroup(), requestHeader.getOriginTopic(), BrokerStatsManager.StatsType.SEND_BACK_TO_DLQ.name(), 1); String uniqKey = msgInner.getProperties().get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); DLQ_LOG.info("send msg to DLQ {}, owner={}, originalTopic={}, consumerId={}, msgUniqKey={}, storeTimestamp={}", newTopic, commercialOwner, requestHeader.getOriginTopic(), requestHeader.getGroup(), uniqKey, putMessageResult.getAppendMessageResult().getStoreTimestamp()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); succeeded = true; break; default: break; } if (!succeeded) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(putMessageResult.getPutMessageStatus().name()); } } else { if (isDLQ) { String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); String uniqKey = msgInner.getProperties().get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); DLQ_LOG.info("failed to send msg to DLQ {}, owner={}, originalTopic={}, consumerId={}, msgUniqKey={}, result={}", newTopic, owner, requestHeader.getOriginTopic(), requestHeader.getGroup(), uniqKey, "null"); } response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("putMessageResult is null"); } if (this.hasConsumeMessageHook() && !UtilAll.isBlank(requestHeader.getOriginMsgId())) { String namespace = NamespaceUtil.getNamespaceFromResource(requestHeader.getGroup()); ConsumeMessageContext context = new ConsumeMessageContext(); context.setNamespace(namespace); context.setTopic(requestHeader.getOriginTopic()); context.setConsumerGroup(requestHeader.getGroup()); context.setCommercialRcvStats(BrokerStatsManager.StatsType.SEND_BACK); context.setCommercialRcvTimes(1); context.setCommercialOwner(request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER)); context.setAccountAuthType(request.getExtFields().get(BrokerStatsManager.ACCOUNT_AUTH_TYPE)); context.setAccountOwnerParent(request.getExtFields().get(BrokerStatsManager.ACCOUNT_OWNER_PARENT)); context.setAccountOwnerSelf(request.getExtFields().get(BrokerStatsManager.ACCOUNT_OWNER_SELF)); context.setRcvStat(isDLQ ? BrokerStatsManager.StatsType.SEND_BACK_TO_DLQ : BrokerStatsManager.StatsType.SEND_BACK); context.setSuccess(succeeded); context.setRcvMsgNum(1); //Set msg body size 0 when sent back by consumer. context.setRcvMsgSize(0); context.setCommercialRcvMsgNum(succeeded ? 1 : 0); try { this.executeConsumeMessageHookAfter(context); } catch (AbortProcessException e) { response.setCode(e.getResponseCode()); response.setRemark(e.getErrorMessage()); } } return response; } public boolean hasConsumeMessageHook() { return consumeMessageHookList != null && !this.consumeMessageHookList.isEmpty(); } public void executeConsumeMessageHookAfter(final ConsumeMessageContext context) { if (hasConsumeMessageHook()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageAfter(context); } catch (Throwable e) { // Ignore } } } } protected SendMessageContext buildMsgContext(ChannelHandlerContext ctx, SendMessageRequestHeader requestHeader, RemotingCommand request) { String namespace = NamespaceUtil.getNamespaceFromResource(requestHeader.getTopic()); SendMessageContext sendMessageContext; sendMessageContext = new SendMessageContext(); sendMessageContext.setNamespace(namespace); sendMessageContext.setProducerGroup(requestHeader.getProducerGroup()); sendMessageContext.setTopic(requestHeader.getTopic()); sendMessageContext.setBodyLength(request.getBody().length); sendMessageContext.setMsgProps(requestHeader.getProperties()); sendMessageContext.setBornHost(RemotingHelper.parseChannelRemoteAddr(ctx.channel())); sendMessageContext.setBrokerAddr(this.brokerController.getBrokerAddr()); sendMessageContext.setQueueId(requestHeader.getQueueId()); sendMessageContext.setBrokerRegionId(this.brokerController.getBrokerConfig().getRegionId()); sendMessageContext.setBornTimeStamp(requestHeader.getBornTimestamp()); sendMessageContext.setRequestTimeStamp(System.currentTimeMillis()); String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); sendMessageContext.setCommercialOwner(owner); Map properties = MessageDecoder.string2messageProperties(requestHeader.getProperties()); properties.put(MessageConst.PROPERTY_MSG_REGION, this.brokerController.getBrokerConfig().getRegionId()); properties.put(MessageConst.PROPERTY_TRACE_SWITCH, String.valueOf(this.brokerController.getBrokerConfig().isTraceOn())); requestHeader.setProperties(MessageDecoder.messageProperties2String(properties)); String uniqueKey = properties.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); sendMessageContext.setMsgUniqueKey(Optional.ofNullable(uniqueKey).orElse("")); if (properties.containsKey(MessageConst.PROPERTY_SHARDING_KEY)) { sendMessageContext.setMsgType(MessageType.Order_Msg); } else if (properties.containsKey(MessageConst.PROPERTY_DELAY_TIME_LEVEL) || properties.containsKey(MessageConst.PROPERTY_TIMER_DELIVER_MS) || properties.containsKey(MessageConst.PROPERTY_TIMER_DELAY_SEC) || properties.containsKey(MessageConst.PROPERTY_TIMER_DELAY_MS)) { sendMessageContext.setMsgType(MessageType.Delay_Msg); } else if (Boolean.parseBoolean(properties.get(MessageConst.PROPERTY_TRANSACTION_PREPARED))) { sendMessageContext.setMsgType(MessageType.Trans_Msg_Half); } else { sendMessageContext.setMsgType(MessageType.Normal_Msg); } return sendMessageContext; } public boolean hasSendMessageHook() { return sendMessageHookList != null && !this.sendMessageHookList.isEmpty(); } protected MessageExtBrokerInner buildInnerMsg(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, final byte[] body, TopicConfig topicConfig) { int queueIdInt = requestHeader.getQueueId(); if (queueIdInt < 0) { queueIdInt = randomQueueId(topicConfig.getWriteQueueNums()); } int sysFlag = requestHeader.getSysFlag(); if (TopicFilterType.MULTI_TAG == topicConfig.getTopicFilterType()) { sysFlag |= MessageSysFlag.MULTI_TAGS_FLAG; } MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(requestHeader.getTopic()); msgInner.setBody(body); msgInner.setFlag(requestHeader.getFlag()); MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties())); msgInner.setPropertiesString(requestHeader.getProperties()); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(topicConfig.getTopicFilterType(), msgInner.getTags())); msgInner.setQueueId(queueIdInt); msgInner.setSysFlag(sysFlag); msgInner.setBornTimestamp(requestHeader.getBornTimestamp()); msgInner.setBornHost(ctx.channel().remoteAddress()); msgInner.setStoreHost(this.getStoreHost()); msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader .getReconsumeTimes()); return msgInner; } public SocketAddress getStoreHost() { return brokerController.getStoreHost(); } protected RemotingCommand msgContentCheck(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, RemotingCommand request, final RemotingCommand response) { String topic = requestHeader.getTopic(); if (topic.length() > Byte.MAX_VALUE) { LOGGER.warn("msgContentCheck: message topic length is too long, topic={}, topic length={}, threshold={}", topic, topic.length(), Byte.MAX_VALUE); response.setCode(ResponseCode.MESSAGE_ILLEGAL); return response; } if (requestHeader.getProperties() != null && requestHeader.getProperties().length() > Short.MAX_VALUE) { LOGGER.warn( "msgContentCheck: message properties length is too long, topic={}, properties length={}, threshold={}", topic, requestHeader.getProperties().length(), Short.MAX_VALUE); response.setCode(ResponseCode.MESSAGE_ILLEGAL); return response; } if (request.getBody().length > DBMsgConstants.MAX_BODY_SIZE) { LOGGER.warn( "msgContentCheck: message body size exceeds the threshold, topic={}, body size={}, threshold={}bytes", topic, request.getBody().length, DBMsgConstants.MAX_BODY_SIZE); response.setRemark("msg body must be less 64KB"); response.setCode(ResponseCode.MESSAGE_ILLEGAL); return response; } return response; } protected RemotingCommand msgCheck(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, final RemotingCommand request, final RemotingCommand response) { if (!PermName.isWriteable(this.brokerController.getBrokerConfig().getBrokerPermission()) && this.brokerController.getTopicConfigManager().isOrderTopic(requestHeader.getTopic())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() + "] sending message is forbidden"); return response; } TopicValidator.ValidateResult result = TopicValidator.validateTopic(requestHeader.getTopic()); if (!result.isValid()) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(result.getRemark()); return response; } if (TopicValidator.isNotAllowedSendTopic(requestHeader.getTopic())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("Sending message to topic[" + requestHeader.getTopic() + "] is forbidden."); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { int topicSysFlag = 0; if (requestHeader.isUnitMode()) { if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { topicSysFlag = TopicSysFlag.buildSysFlag(false, true); } else { topicSysFlag = TopicSysFlag.buildSysFlag(true, false); } } LOGGER.warn("the topic {} not exist, producer: {}", requestHeader.getTopic(), ctx.channel().remoteAddress()); topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageMethod( requestHeader.getTopic(), requestHeader.getDefaultTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getDefaultTopicQueueNums(), topicSysFlag); if (null == topicConfig) { if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod( requestHeader.getTopic(), 1, PermName.PERM_WRITE | PermName.PERM_READ, topicSysFlag); } } if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("topic[" + requestHeader.getTopic() + "] not exist, apply first please!" + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)); return response; } } int queueIdInt = requestHeader.getQueueId(); int idValid = Math.max(topicConfig.getWriteQueueNums(), topicConfig.getReadQueueNums()); if (queueIdInt >= idValid) { String errorInfo = String.format("request queueId[%d] is illegal, %s Producer: %s", queueIdInt, topicConfig, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); LOGGER.warn(errorInfo); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(errorInfo); return response; } return response; } public void registerSendMessageHook(List sendMessageHookList) { this.sendMessageHookList = sendMessageHookList; } protected void doResponse(ChannelHandlerContext ctx, RemotingCommand request, final RemotingCommand response) { NettyRemotingAbstract.writeResponse(ctx.channel(), request, response, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager()); } public void executeSendMessageHookBefore(SendMessageContext context) { if (hasSendMessageHook()) { for (SendMessageHook hook : this.sendMessageHookList) { try { hook.sendMessageBefore(context); } catch (AbortProcessException e) { throw e; } catch (Throwable e) { //ignore } } } } protected SendMessageRequestHeader parseRequestHeader(RemotingCommand request) throws RemotingCommandException { return SendMessageRequestHeader.parseRequestHeader(request); } protected int randomQueueId(int writeQueueNums) { return ThreadLocalRandom.current().nextInt(99999999) % writeQueueNums; } public void executeSendMessageHookAfter(final RemotingCommand response, final SendMessageContext context) { if (hasSendMessageHook()) { for (SendMessageHook hook : this.sendMessageHookList) { try { if (response != null) { final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader(); context.setMsgId(responseHeader.getMsgId()); context.setQueueId(responseHeader.getQueueId()); context.setQueueOffset(responseHeader.getQueueOffset()); context.setCode(response.getCode()); context.setErrorMsg(response.getRemark()); } hook.sendMessageAfter(context); } catch (Throwable e) { //ignore } } } } @Override public boolean rejectRequest() { return false; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.nio.charset.StandardCharsets; import java.util.BitSet; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.LiteMetadataUtil; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.PopConsumerLockService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BatchAck; import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.BatchAckMsg; public class AckMessageProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final BrokerController brokerController; private final String reviveTopic; private final PopReviveService[] popReviveServices; public AckMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; this.reviveTopic = PopAckConstants.buildClusterReviveTopic( this.brokerController.getBrokerConfig().getBrokerClusterName()); this.popReviveServices = new PopReviveService[this.brokerController.getBrokerConfig().getReviveQueueNum()]; for (int i = 0; i < this.brokerController.getBrokerConfig().getReviveQueueNum(); i++) { this.popReviveServices[i] = new PopReviveService(brokerController, reviveTopic, i); this.popReviveServices[i].setShouldRunPopRevive(brokerController.getBrokerConfig().getBrokerId() == 0); } } public PopReviveService[] getPopReviveServices() { return popReviveServices; } public void shutdown() throws Exception { for (PopReviveService popReviveService : popReviveServices) { popReviveService.shutdown(); } } public void startPopReviveService() { for (PopReviveService popReviveService : popReviveServices) { popReviveService.start(); } } public void shutdownPopReviveService() { for (PopReviveService popReviveService : popReviveServices) { popReviveService.shutdown(); } } public void setPopReviveServiceStatus(boolean shouldStart) { for (PopReviveService popReviveService : popReviveServices) { popReviveService.setShouldRunPopRevive(shouldStart); } } public boolean isPopReviveServiceRunning() { for (PopReviveService popReviveService : popReviveServices) { if (popReviveService.isShouldRunPopRevive()) { return true; } } return false; } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { return this.processRequest(ctx.channel(), request, true); } @Override public boolean rejectRequest() { return false; } private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { AckMessageRequestHeader requestHeader; BatchAckMessageRequestBody reqBody = null; final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); response.setOpaque(request.getOpaque()); if (request.getCode() == RequestCode.ACK_MESSAGE) { requestHeader = (AckMessageRequestHeader) request.decodeCommandCustomHeader(AckMessageRequestHeader.class); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums() || requestHeader.getQueueId() < 0) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(errorInfo); return response; } RemotingCommand ackLiteResponse = ackLite(requestHeader, null, response, channel); if (ackLiteResponse != null) { return ackLiteResponse; } long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); long maxOffset; try { maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset", e); } if (requestHeader.getOffset() < minOffset || requestHeader.getOffset() > maxOffset) { String errorInfo = String.format("offset is illegal, key:%s@%d, commit:%d, store:%d~%d", requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getOffset(), minOffset, maxOffset); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.NO_MESSAGE); response.setRemark(errorInfo); return response; } if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { appendAckNew(requestHeader, null, response, channel, null); } else { appendAck(requestHeader, null, response, channel, null); } } else if (request.getCode() == RequestCode.BATCH_ACK_MESSAGE) { if (request.getBody() != null) { reqBody = BatchAckMessageRequestBody.decode(request.getBody(), BatchAckMessageRequestBody.class); } if (reqBody == null || reqBody.getAcks() == null || reqBody.getAcks().isEmpty()) { response.setCode(ResponseCode.NO_MESSAGE); return response; } for (BatchAck bAck : reqBody.getAcks()) { if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { appendAckNew(null, bAck, response, channel, reqBody.getBrokerName()); } else { appendAck(null, bAck, response, channel, reqBody.getBrokerName()); } } } else { POP_LOGGER.error("AckMessageProcessor failed to process RequestCode: {}, consumer: {} ", request.getCode(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(String.format("AckMessageProcessor failed to process RequestCode: %d", request.getCode())); return response; } return response; } private void appendAck(final AckMessageRequestHeader requestHeader, final BatchAck batchAck, final RemotingCommand response, final Channel channel, String brokerName) throws RemotingCommandException { String[] extraInfo; String consumeGroup, topic; int qId, rqId; long startOffset, ackOffset; long popTime, invisibleTime; AckMsg ackMsg; int ackCount = 0; if (batchAck == null) { // single ack extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); brokerName = ExtraInfoUtil.getBrokerName(extraInfo); consumeGroup = requestHeader.getConsumerGroup(); topic = requestHeader.getTopic(); qId = requestHeader.getQueueId(); rqId = ExtraInfoUtil.getReviveQid(extraInfo); startOffset = ExtraInfoUtil.getCkQueueOffset(extraInfo); ackOffset = requestHeader.getOffset(); popTime = ExtraInfoUtil.getPopTime(extraInfo); invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfo); if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) { ackOrderly(topic, consumeGroup, qId, ackOffset, popTime, invisibleTime, channel, response); return; } ackMsg = new AckMsg(); ackCount = 1; } else { // batch ack consumeGroup = batchAck.getConsumerGroup(); topic = ExtraInfoUtil.getRealTopic(batchAck.getTopic(), batchAck.getConsumerGroup(), batchAck.getRetry()); qId = batchAck.getQueueId(); rqId = batchAck.getReviveQueueId(); startOffset = batchAck.getStartOffset(); ackOffset = -1; popTime = batchAck.getPopTime(); invisibleTime = batchAck.getInvisibleTime(); long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, qId); long maxOffset; try { maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, qId); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset in queue", e); } if (minOffset == -1 || maxOffset == -1) { POP_LOGGER.error("Illegal topic or queue found when batch ack {}", batchAck); return; } BatchAckMsg batchAckMsg = new BatchAckMsg(); BitSet bitSet = batchAck.getBitSet(); for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { if (i == Integer.MAX_VALUE) { break; } long offset = startOffset + i; if (offset < minOffset || offset > maxOffset) { continue; } if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) { ackOrderly(topic, consumeGroup, qId, offset, popTime, invisibleTime, channel, response); } else { batchAckMsg.getAckOffsetList().add(offset); } } if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE || batchAckMsg.getAckOffsetList().isEmpty()) { return; } ackMsg = batchAckMsg; ackCount = batchAckMsg.getAckOffsetList().size(); } this.brokerController.getBrokerStatsManager().incBrokerAckNums(ackCount); this.brokerController.getBrokerStatsManager().incGroupAckNums(consumeGroup, topic, ackCount); ackMsg.setConsumerGroup(consumeGroup); ackMsg.setTopic(topic); ackMsg.setQueueId(qId); ackMsg.setStartOffset(startOffset); ackMsg.setAckOffset(ackOffset); ackMsg.setPopTime(popTime); ackMsg.setBrokerName(brokerName); if (this.brokerController.getPopMessageProcessor().getPopBufferMergeService().addAk(rqId, ackMsg)) { brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, ackCount); return; } MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(reviveTopic); msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(rqId); if (ackMsg instanceof BatchAckMsg) { msgInner.setTags(PopAckConstants.BATCH_ACK_TAG); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genBatchAckUniqueId((BatchAckMsg) ackMsg)); } else { msgInner.setTags(PopAckConstants.ACK_TAG); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg)); } msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(this.brokerController.getStoreHost()); msgInner.setStoreHost(this.brokerController.getStoreHost()); msgInner.setDeliverTimeMs(popTime + invisibleTime); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); if (brokerController.getBrokerConfig().isAppendAckAsync()) { int finalAckCount = ackCount; this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handlePutMessageResult(putMessageResult, ackMsg, topic, consumeGroup, popTime, qId, finalAckCount); }).exceptionally(throwable -> { handlePutMessageResult(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, null, false), ackMsg, topic, consumeGroup, popTime, qId, finalAckCount); POP_LOGGER.error("put ack msg error ", throwable); return null; }); } else { PutMessageResult putMessageResult = this.brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); handlePutMessageResult(putMessageResult, ackMsg, topic, consumeGroup, popTime, qId, ackCount); } } private void appendAckNew(final AckMessageRequestHeader requestHeader, final BatchAck batchAck, final RemotingCommand response, final Channel channel, String brokerName) throws RemotingCommandException { if (requestHeader != null && batchAck == null) { String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); String groupId = requestHeader.getConsumerGroup(); String topicId = requestHeader.getTopic(); int queueId = requestHeader.getQueueId(); long ackOffset = requestHeader.getOffset(); long popTime = ExtraInfoUtil.getPopTime(extraInfo); long invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfo); int reviveQueueId = ExtraInfoUtil.getReviveQid(extraInfo); if (reviveQueueId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) { ackOrderlyNew(topicId, groupId, queueId, ackOffset, popTime, invisibleTime, channel, response); } else { this.brokerController.getPopConsumerService().ackAsync( popTime, invisibleTime, groupId, topicId, queueId, ackOffset); } this.brokerController.getBrokerStatsManager().incBrokerAckNums(1); this.brokerController.getBrokerStatsManager().incGroupAckNums(groupId, topicId, 1); } else { String groupId = batchAck.getConsumerGroup(); String topicId = ExtraInfoUtil.getRealTopic( batchAck.getTopic(), batchAck.getConsumerGroup(), batchAck.getRetry()); int queueId = batchAck.getQueueId(); int reviveQueueId = batchAck.getReviveQueueId(); long startOffset = batchAck.getStartOffset(); long popTime = batchAck.getPopTime(); long invisibleTime = batchAck.getInvisibleTime(); try { long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(topicId, queueId); long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topicId, queueId); if (minOffset == -1 || maxOffset == -1) { POP_LOGGER.error("Illegal topic or queue found when batch ack {}", batchAck); return; } int ackCount = 0; // Maintain consistency with the old implementation code style BitSet bitSet = batchAck.getBitSet(); for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { if (i == Integer.MAX_VALUE) { break; } long offset = startOffset + i; if (offset < minOffset || offset > maxOffset) { continue; } if (reviveQueueId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) { ackOrderlyNew(topicId, groupId, queueId, offset, popTime, invisibleTime, channel, response); } else { this.brokerController.getPopConsumerService().ackAsync( popTime, invisibleTime, groupId, topicId, queueId, offset); } ackCount++; } this.brokerController.getBrokerStatsManager().incBrokerAckNums(ackCount); this.brokerController.getBrokerStatsManager().incGroupAckNums(groupId, topicId, ackCount); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to ack message", e); } } } private void handlePutMessageResult(PutMessageResult putMessageResult, AckMsg ackMsg, String topic, String consumeGroup, long popTime, int qId, int ackCount) { if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { POP_LOGGER.error("put ack msg error:" + putMessageResult); } brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus()); brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, ackCount); } protected void ackOrderly(String topic, String consumeGroup, int qId, long ackOffset, long popTime, long invisibleTime, Channel channel, RemotingCommand response) { String lockKey = topic + PopAckConstants.SPLIT + consumeGroup + PopAckConstants.SPLIT + qId; long oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(consumeGroup, topic, qId); if (ackOffset < oldOffset) { return; } while (!this.brokerController.getPopMessageProcessor().getQueueLockManager().tryLock(lockKey)) { } try { oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(consumeGroup, topic, qId); if (ackOffset < oldOffset) { return; } long nextOffset = brokerController.getConsumerOrderInfoManager().commitAndNext( topic, consumeGroup, qId, ackOffset, popTime); if (nextOffset > -1) { if (!this.brokerController.getConsumerOffsetManager().hasOffsetReset(topic, consumeGroup, qId)) { this.brokerController.getConsumerOffsetManager().commitOffset( channel.remoteAddress().toString(), consumeGroup, topic, qId, nextOffset); } if (!this.brokerController.getConsumerOrderInfoManager().checkBlock(null, topic, consumeGroup, qId, invisibleTime)) { this.brokerController.getPopMessageProcessor().notifyMessageArriving(topic, qId, consumeGroup); } } else if (nextOffset == -1) { String errorInfo = String.format("offset is illegal, key:%s, old:%d, commit:%d, next:%d, %s", lockKey, oldOffset, ackOffset, nextOffset, channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(errorInfo); return; } } finally { this.brokerController.getPopMessageProcessor().getQueueLockManager().unLock(lockKey); } brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, 1); } protected void ackOrderlyNew(String topic, String consumeGroup, int qId, long ackOffset, long popTime, long invisibleTime, Channel channel, RemotingCommand response) { ConsumerOffsetManager consumerOffsetManager = this.brokerController.getConsumerOffsetManager(); ConsumerOrderInfoManager consumerOrderInfoManager = brokerController.getConsumerOrderInfoManager(); PopConsumerLockService consumerLockService = this.brokerController.getPopConsumerService().getConsumerLockService(); long oldOffset = consumerOffsetManager.queryOffset(consumeGroup, topic, qId); if (ackOffset < oldOffset) { return; } while (!consumerLockService.tryLock(consumeGroup, topic)) { } try { // double check oldOffset = consumerOffsetManager.queryOffset(consumeGroup, topic, qId); if (ackOffset < oldOffset) { return; } long nextOffset = consumerOrderInfoManager.commitAndNext(topic, consumeGroup, qId, ackOffset, popTime); if (brokerController.getBrokerConfig().isPopConsumerKVServiceLog()) { POP_LOGGER.info("PopConsumerService ack orderly, time={}, topicId={}, groupId={}, queueId={}, " + "offset={}, next={}", popTime, topic, consumeGroup, qId, ackOffset, nextOffset); } if (nextOffset > -1L) { if (!consumerOffsetManager.hasOffsetReset(topic, consumeGroup, qId)) { String remoteAddress = RemotingHelper.parseSocketAddressAddr(channel.remoteAddress()); consumerOffsetManager.commitOffset(remoteAddress, consumeGroup, topic, qId, nextOffset); } if (!consumerOrderInfoManager.checkBlock(null, topic, consumeGroup, qId, invisibleTime)) { this.brokerController.getPopMessageProcessor().notifyMessageArriving(topic, qId, consumeGroup); } return; } if (nextOffset == -1) { String errorInfo = String.format("offset is illegal, key:%s %s %s, old:%d, commit:%d, next:%d, %s", consumeGroup, topic, qId, oldOffset, ackOffset, nextOffset, channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(errorInfo); } } finally { consumerLockService.unlock(consumeGroup, topic); } } /** * Currently, batch ack for lite messages is not supported, so we should ensure that all acknowledgements are individual. */ protected RemotingCommand ackLite(AckMessageRequestHeader requestHeader, BatchAckMessageRequestBody batchAckBody, final RemotingCommand response, final Channel channel) { if (batchAckBody != null) { POP_LOGGER.warn("bad request, batch ack lite, {}", batchAckBody); response.setCode(ResponseCode.ILLEGAL_OPERATION); response.setRemark("batch ack lite is not supported."); return response; } if (StringUtils.isBlank(requestHeader.getLiteTopic())) { return null; } String group = requestHeader.getConsumerGroup(); if (!requestHeader.getTopic().equals(LiteMetadataUtil.getLiteBindTopic(group, brokerController))) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("group type or bind topic not match."); return response; } String lmqName = LiteUtil.toLmqName(requestHeader.getTopic(), requestHeader.getLiteTopic()); long ackOffset = requestHeader.getOffset(); long maxOffset = this.brokerController.getLiteLifecycleManager().getMaxOffsetInQueue(lmqName); if (ackOffset > maxOffset) { POP_LOGGER.warn("ack lite offset illegal, {}, {}, {}", lmqName, ackOffset, maxOffset); response.setCode(ResponseCode.NO_MESSAGE); response.setRemark("ack offset illegal."); return response; } String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); if (requestHeader.getQueueId() != 0 || ExtraInfoUtil.getReviveQid(extraInfo) != KeyBuilder.POP_ORDER_REVIVE_QUEUE) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("ack queue illegal."); return response; } long popTime = ExtraInfoUtil.getPopTime(extraInfo); long invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfo); ConsumerOffsetManager consumerOffsetManager = this.brokerController.getConsumerOffsetManager(); ConsumerOrderInfoManager consumerOrderInfoManager = brokerController.getPopLiteMessageProcessor().getConsumerOrderInfoManager(); PopConsumerLockService consumerLockService = this.brokerController.getPopLiteMessageProcessor().getLockService(); long oldOffset = consumerOffsetManager.queryOffset(group, lmqName, 0); if (ackOffset < oldOffset) { return response; } String lockKey = KeyBuilder.buildPopLiteLockKey(group, lmqName); while (!consumerLockService.tryLock(lockKey)) { } try { oldOffset = consumerOffsetManager.queryOffset(group, lmqName, 0); if (ackOffset < oldOffset) { return response; } long nextOffset = consumerOrderInfoManager.commitAndNext(lmqName, group, 0, ackOffset, popTime); if (nextOffset > -1L) { if (!consumerOffsetManager.hasOffsetReset(lmqName, group, 0)) { consumerOffsetManager.commitOffset("AckLiteHost", group, lmqName, 0, nextOffset); } if (!consumerOrderInfoManager.checkBlock(null, lmqName, group, 0, invisibleTime)) { this.brokerController.getLiteEventDispatcher().dispatch(group, lmqName, 0, nextOffset, -1); } } if (nextOffset == -1) { POP_LOGGER.warn("ack lite, nextOffset illegal. lmq:{}, old:{}, commit:{}", lmqName, oldOffset, ackOffset); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark("ack offset illegal."); return response; } } finally { consumerLockService.unlock(lockKey); } this.brokerController.getBrokerStatsManager().incBrokerAckNums(1); this.brokerController.getBrokerStatsManager().incGroupAckNums(group, requestHeader.getTopic(), 1); response.setCode(ResponseCode.SUCCESS); return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import com.google.common.collect.Sets; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.opentelemetry.api.common.Attributes; import java.io.UnsupportedEncodingException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.enums.PolicyType; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.auth.converter.AclConverter; import org.apache.rocketmq.broker.auth.converter.UserConverter; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.config.v1.RocksDBConsumerOffsetManager; import org.apache.rocketmq.broker.config.v1.RocksDBSubscriptionGroupManager; import org.apache.rocketmq.broker.config.v1.RocksDBTopicConfigManager; import org.apache.rocketmq.broker.controller.ReplicasManager; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.broker.lite.LiteMetadataUtil; import org.apache.rocketmq.broker.metrics.InvocationStatus; import org.apache.rocketmq.broker.plugin.BrokerAttachedPlugin; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.broker.topic.TopicQueueMappingManager; import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.CheckRocksdbCqWriteResult; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.LockCallback; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UnlockCallback; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.AttributeParser; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.constant.FIleReadaheadMode; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageId; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.stats.StatsItem; import org.apache.rocketmq.common.stats.StatsSnapshot; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.filter.util.BitsArray; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.AclInfo; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData; import org.apache.rocketmq.remoting.protocol.body.BrokerStatsItem; import org.apache.rocketmq.remoting.protocol.body.Connection; import org.apache.rocketmq.remoting.protocol.body.ConsumeQueueData; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatsList; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.CreateTopicListRequestBody; import org.apache.rocketmq.remoting.protocol.body.EpochEntryCache; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.remoting.protocol.body.KVTable; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.LockBatchResponseBody; import org.apache.rocketmq.remoting.protocol.body.ProducerConnection; import org.apache.rocketmq.remoting.protocol.body.ProducerTableInfo; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeQueueResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeTimeSpanBody; import org.apache.rocketmq.remoting.protocol.body.QueryCorrectionOffsetBody; import org.apache.rocketmq.remoting.protocol.body.QuerySubscriptionResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupList; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.header.CheckRocksdbCqWriteProgressRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CloneGroupOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.ExportRocksDBConfigToJsonRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllProducerInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetBrokerConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsInBrokerHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerStatusRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetProducerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetSubscriptionGroupConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ListAclsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ListUsersRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyBrokerRoleChangedRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyMinBrokerIdChangeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeTimeSpanRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryCorrectionOffsetHeader; import org.apache.rocketmq.remoting.protocol.header.QuerySubscriptionByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicConsumeByWhoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicsByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetMasterFlushOffsetHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewBrokerStatsDataRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.protocol.subscription.GroupForbidden; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.rpc.RpcClientUtils; import org.apache.rocketmq.remoting.rpc.RpcException; import org.apache.rocketmq.remoting.rpc.RpcRequest; import org.apache.rocketmq.remoting.rpc.RpcResponse; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.StoreType; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.plugin.AbstractPluginMessageStore; import org.apache.rocketmq.store.queue.CombineConsumeQueueStore; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.apache.rocketmq.store.timer.TimerCheckpoint; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.util.LibC; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_INVOCATION_STATUS; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.common.message.MessageConst.TIMER_ENGINE_TYPE; import static org.apache.rocketmq.remoting.protocol.RemotingCommand.buildErrorResponse; public class AdminBrokerProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); protected final BrokerController brokerController; protected Set configBlackList = new HashSet<>(); private final ExecutorService asyncExecuteWorker = new ThreadPoolExecutor(0, 4, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); public AdminBrokerProcessor(final BrokerController brokerController) { this.brokerController = brokerController; initConfigBlackList(); } private void initConfigBlackList() { configBlackList.add("brokerConfigPath"); configBlackList.add("rocketmqHome"); configBlackList.add("configBlackList"); String[] configArray = brokerController.getBrokerConfig().getConfigBlackList().split(";"); configBlackList.addAll(Arrays.asList(configArray)); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { switch (request.getCode()) { case RequestCode.UPDATE_AND_CREATE_TOPIC: return this.updateAndCreateTopic(ctx, request); case RequestCode.UPDATE_AND_CREATE_TOPIC_LIST: return this.updateAndCreateTopicList(ctx, request); case RequestCode.DELETE_TOPIC_IN_BROKER: return this.deleteTopic(ctx, request); case RequestCode.GET_ALL_TOPIC_CONFIG: return this.getAllTopicConfig(ctx, request); case RequestCode.GET_TIMER_CHECK_POINT: return this.getTimerCheckPoint(ctx, request); case RequestCode.GET_TIMER_METRICS: return this.getTimerMetrics(ctx, request); case RequestCode.UPDATE_BROKER_CONFIG: return this.updateBrokerConfig(ctx, request); case RequestCode.GET_BROKER_CONFIG: return this.getBrokerConfig(ctx, request); case RequestCode.UPDATE_COLD_DATA_FLOW_CTR_CONFIG: return this.updateColdDataFlowCtrGroupConfig(ctx, request); case RequestCode.REMOVE_COLD_DATA_FLOW_CTR_CONFIG: return this.removeColdDataFlowCtrGroupConfig(ctx, request); case RequestCode.GET_COLD_DATA_FLOW_CTR_INFO: return this.getColdDataFlowCtrInfo(ctx); case RequestCode.SET_COMMITLOG_READ_MODE: return this.setCommitLogReadaheadMode(ctx, request); case RequestCode.SEARCH_OFFSET_BY_TIMESTAMP: return this.searchOffsetByTimestamp(ctx, request); case RequestCode.GET_MAX_OFFSET: return this.getMaxOffset(ctx, request); case RequestCode.GET_MIN_OFFSET: return this.getMinOffset(ctx, request); case RequestCode.GET_EARLIEST_MSG_STORETIME: return this.getEarliestMsgStoretime(ctx, request); case RequestCode.GET_BROKER_RUNTIME_INFO: return this.getBrokerRuntimeInfo(ctx, request); case RequestCode.LOCK_BATCH_MQ: return this.lockBatchMQ(ctx, request); case RequestCode.UNLOCK_BATCH_MQ: return this.unlockBatchMQ(ctx, request); case RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP: return this.updateAndCreateSubscriptionGroup(ctx, request); case RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP_LIST: return this.updateAndCreateSubscriptionGroupList(ctx, request); case RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG: return this.getAllSubscriptionGroup(ctx, request); case RequestCode.DELETE_SUBSCRIPTIONGROUP: return this.deleteSubscriptionGroup(ctx, request); case RequestCode.GET_TOPIC_STATS_INFO: return this.getTopicStatsInfo(ctx, request); case RequestCode.GET_CONSUMER_CONNECTION_LIST: return this.getConsumerConnectionList(ctx, request); case RequestCode.GET_PRODUCER_CONNECTION_LIST: return this.getProducerConnectionList(ctx, request); case RequestCode.GET_ALL_PRODUCER_INFO: return this.getAllProducerInfo(ctx, request); case RequestCode.GET_CONSUME_STATS: return this.getConsumeStats(ctx, request); case RequestCode.GET_ALL_CONSUMER_OFFSET: return this.getAllConsumerOffset(ctx, request); case RequestCode.GET_ALL_DELAY_OFFSET: return this.getAllDelayOffset(ctx, request); case RequestCode.GET_ALL_MESSAGE_REQUEST_MODE: return this.getAllMessageRequestMode(ctx, request); case RequestCode.INVOKE_BROKER_TO_RESET_OFFSET: return this.resetOffset(ctx, request); case RequestCode.INVOKE_BROKER_TO_GET_CONSUMER_STATUS: return this.getConsumerStatus(ctx, request); case RequestCode.QUERY_TOPIC_CONSUME_BY_WHO: return this.queryTopicConsumeByWho(ctx, request); case RequestCode.QUERY_TOPICS_BY_CONSUMER: return this.queryTopicsByConsumer(ctx, request); case RequestCode.QUERY_SUBSCRIPTION_BY_CONSUMER: return this.querySubscriptionByConsumer(ctx, request); case RequestCode.QUERY_CONSUME_TIME_SPAN: return this.queryConsumeTimeSpan(ctx, request); case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_BROKER: return this.getSystemTopicListFromBroker(ctx, request); case RequestCode.CLEAN_EXPIRED_CONSUMEQUEUE: return this.cleanExpiredConsumeQueue(); case RequestCode.DELETE_EXPIRED_COMMITLOG: return this.deleteExpiredCommitLog(); case RequestCode.CLEAN_UNUSED_TOPIC: return this.cleanUnusedTopic(); case RequestCode.GET_CONSUMER_RUNNING_INFO: return this.getConsumerRunningInfo(ctx, request); case RequestCode.QUERY_CORRECTION_OFFSET: return this.queryCorrectionOffset(ctx, request); case RequestCode.CONSUME_MESSAGE_DIRECTLY: return this.consumeMessageDirectly(ctx, request); case RequestCode.CLONE_GROUP_OFFSET: return this.cloneGroupOffset(ctx, request); case RequestCode.VIEW_BROKER_STATS_DATA: return ViewBrokerStatsData(ctx, request); case RequestCode.GET_BROKER_CONSUME_STATS: return fetchAllConsumeStatsInBroker(ctx, request); case RequestCode.QUERY_CONSUME_QUEUE: return queryConsumeQueue(ctx, request); case RequestCode.CHECK_ROCKSDB_CQ_WRITE_PROGRESS: return this.checkRocksdbCqWriteProgress(ctx, request); case RequestCode.EXPORT_ROCKSDB_CONFIG_TO_JSON: return this.exportRocksDBConfigToJson(ctx, request); case RequestCode.UPDATE_AND_GET_GROUP_FORBIDDEN: return this.updateAndGetGroupForbidden(ctx, request); case RequestCode.GET_SUBSCRIPTIONGROUP_CONFIG: return this.getSubscriptionGroup(ctx, request); case RequestCode.RESUME_CHECK_HALF_MESSAGE: return resumeCheckHalfMessage(ctx, request); case RequestCode.GET_TOPIC_CONFIG: return getTopicConfig(ctx, request); case RequestCode.UPDATE_AND_CREATE_STATIC_TOPIC: return this.updateAndCreateStaticTopic(ctx, request); case RequestCode.NOTIFY_MIN_BROKER_ID_CHANGE: return this.notifyMinBrokerIdChange(ctx, request); case RequestCode.EXCHANGE_BROKER_HA_INFO: return this.updateBrokerHaInfo(ctx, request); case RequestCode.GET_BROKER_HA_STATUS: return this.getBrokerHaStatus(ctx, request); case RequestCode.RESET_MASTER_FLUSH_OFFSET: return this.resetMasterFlushOffset(ctx, request); case RequestCode.GET_BROKER_EPOCH_CACHE: return this.getBrokerEpochCache(ctx, request); case RequestCode.NOTIFY_BROKER_ROLE_CHANGED: return this.notifyBrokerRoleChanged(ctx, request); case RequestCode.AUTH_CREATE_USER: return this.createUser(ctx, request); case RequestCode.AUTH_UPDATE_USER: return this.updateUser(ctx, request); case RequestCode.AUTH_DELETE_USER: return this.deleteUser(ctx, request); case RequestCode.AUTH_GET_USER: return this.getUser(ctx, request); case RequestCode.AUTH_LIST_USER: return this.listUser(ctx, request); case RequestCode.AUTH_CREATE_ACL: return this.createAcl(ctx, request); case RequestCode.AUTH_UPDATE_ACL: return this.updateAcl(ctx, request); case RequestCode.AUTH_DELETE_ACL: return this.deleteAcl(ctx, request); case RequestCode.AUTH_GET_ACL: return this.getAcl(ctx, request); case RequestCode.AUTH_LIST_ACL: return this.listAcl(ctx, request); case RequestCode.POP_ROLLBACK: return this.transferPopToFsStore(ctx, request); case RequestCode.SWITCH_TIMER_ENGINE: return this.switchTimerEngine(ctx, request); default: return getUnknownCmdResponse(ctx, request); } } /** * @param ctx * @param request * @return * @throws RemotingCommandException */ private RemotingCommand getSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { GetSubscriptionGroupConfigRequestHeader requestHeader = (GetSubscriptionGroupConfigRequestHeader) request.decodeCommandCustomHeader(GetSubscriptionGroupConfigRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); SubscriptionGroupConfig groupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getGroup()); if (groupConfig == null) { LOGGER.error("No group in this broker, client: {} group: {}", ctx.channel().remoteAddress(), requestHeader.getGroup()); response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark("No group in this broker"); return response; } String content = JSONObject.toJSONString(groupConfig); try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("UnsupportedEncodingException getSubscriptionGroup: group=" + groupConfig.getGroupName(), e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e.getMessage()); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } /** * @param ctx * @param request * @return */ private RemotingCommand updateAndGetGroupForbidden(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); UpdateGroupForbiddenRequestHeader requestHeader = (UpdateGroupForbiddenRequestHeader) // request.decodeCommandCustomHeader(UpdateGroupForbiddenRequestHeader.class); String group = requestHeader.getGroup(); String topic = requestHeader.getTopic(); LOGGER.info("updateAndGetGroupForbidden called by {} for object {}@{} readable={}",// RemotingHelper.parseChannelRemoteAddr(ctx.channel()), group, // topic, requestHeader.getReadable()); SubscriptionGroupManager groupManager = this.brokerController.getSubscriptionGroupManager(); if (requestHeader.getReadable() != null) { groupManager.updateForbidden(group, topic, PermName.INDEX_PERM_READ, !requestHeader.getReadable()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(""); GroupForbidden groupForbidden = new GroupForbidden(); groupForbidden.setGroup(group); groupForbidden.setTopic(topic); groupForbidden.setReadable(!groupManager.getForbidden(group, topic, PermName.INDEX_PERM_READ)); response.setBody(groupForbidden.toJson().getBytes(StandardCharsets.UTF_8)); return response; } private RemotingCommand checkRocksdbCqWriteProgress(ChannelHandlerContext ctx, RemotingCommand request) { CheckRocksdbCqWriteResult result = new CheckRocksdbCqWriteResult(); result.setCheckStatus(CheckRocksdbCqWriteResult.CheckStatus.CHECK_IN_PROGRESS.getValue()); Runnable runnable = () -> { try { CheckRocksdbCqWriteResult checkResult = doCheckRocksdbCqWriteProgress(ctx, request); LOGGER.info("checkRocksdbCqWriteProgress result: {}", JSON.toJSONString(checkResult)); } catch (Exception e) { LOGGER.error("checkRocksdbCqWriteProgress error", e); } }; asyncExecuteWorker.submit(runnable); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setBody(JSON.toJSONBytes(result)); return response; } private RemotingCommand exportRocksDBConfigToJson(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { ExportRocksDBConfigToJsonRequestHeader requestHeader = request.decodeCommandCustomHeader(ExportRocksDBConfigToJsonRequestHeader.class); List configTypes = requestHeader.fetchConfigType(); List> futureList = new ArrayList<>(configTypes.size()); for (ExportRocksDBConfigToJsonRequestHeader.ConfigType type : configTypes) { switch (type) { case TOPICS: if (this.brokerController.getTopicConfigManager() instanceof RocksDBTopicConfigManager) { RocksDBTopicConfigManager rocksDBTopicConfigManager = (RocksDBTopicConfigManager) this.brokerController.getTopicConfigManager(); futureList.add(CompletableFuture.runAsync(rocksDBTopicConfigManager::exportToJson, asyncExecuteWorker)); } break; case SUBSCRIPTION_GROUPS: if (this.brokerController.getSubscriptionGroupManager() instanceof RocksDBSubscriptionGroupManager) { RocksDBSubscriptionGroupManager rocksDBSubscriptionGroupManager = (RocksDBSubscriptionGroupManager) this.brokerController.getSubscriptionGroupManager(); futureList.add(CompletableFuture.runAsync(rocksDBSubscriptionGroupManager::exportToJson, asyncExecuteWorker)); } break; case CONSUMER_OFFSETS: if (this.brokerController.getConsumerOffsetManager() instanceof RocksDBConsumerOffsetManager) { RocksDBConsumerOffsetManager rocksDBConsumerOffsetManager = (RocksDBConsumerOffsetManager) this.brokerController.getConsumerOffsetManager(); futureList.add(CompletableFuture.runAsync(rocksDBConsumerOffsetManager::exportToJson, asyncExecuteWorker)); } break; default: break; } } try { CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join(); } catch (CompletionException e) { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.valueOf(e)); return response; } RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setRemark("export done."); return response; } @Override public boolean rejectRequest() { return false; } private synchronized RemotingCommand updateAndCreateTopic(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { long startTime = System.currentTimeMillis(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); final CreateTopicRequestHeader requestHeader = (CreateTopicRequestHeader) request.decodeCommandCustomHeader(CreateTopicRequestHeader.class); LOGGER.info("Broker receive request to update or create topic={}, caller address={}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); String topic = requestHeader.getTopic(); long executionTime; try { TopicValidator.ValidateResult result = TopicValidator.validateTopic(topic); if (!result.isValid()) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(result.getRemark()); return response; } if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { if (TopicValidator.isSystemTopic(topic)) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The topic[" + topic + "] is conflict with system topic."); return response; } } TopicConfig topicConfig = new TopicConfig(topic); topicConfig.setReadQueueNums(requestHeader.getReadQueueNums()); topicConfig.setWriteQueueNums(requestHeader.getWriteQueueNums()); topicConfig.setTopicFilterType(requestHeader.getTopicFilterTypeEnum()); topicConfig.setPerm(requestHeader.getPerm()); topicConfig.setTopicSysFlag(requestHeader.getTopicSysFlag() == null ? 0 : requestHeader.getTopicSysFlag()); topicConfig.setOrder(requestHeader.getOrder()); String attributesModification = requestHeader.getAttributes(); topicConfig.setAttributes(AttributeParser.parseToMap(attributesModification)); if (!brokerController.getBrokerConfig().isEnableMixedMessageType() && topicConfig.getAttributes() != null) { // Get attribute by key with prefix sign String msgTypeAttrKey = AttributeParser.ATTR_ADD_PLUS_SIGN + TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(); String msgTypeAttrValue = topicConfig.getAttributes().get(msgTypeAttrKey); if (msgTypeAttrValue != null && msgTypeAttrValue.equals(TopicMessageType.MIXED.getValue())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("MIXED message type is not supported."); return response; } } if (topicConfig.equals(this.brokerController.getTopicConfigManager().getTopicConfigTable().get(topic))) { LOGGER.info("Broker receive request to update or create topic={}, but topicConfig has no changes , so idempotent, caller address={}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); response.setCode(ResponseCode.SUCCESS); return response; } this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) { this.brokerController.registerSingleTopicAll(topicConfig); } else { this.brokerController.registerIncrementBrokerData(topicConfig, this.brokerController.getTopicConfigManager().getDataVersion()); } response.setCode(ResponseCode.SUCCESS); } catch (Exception e) { LOGGER.error("Update / create topic failed for [{}]", request, e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); return response; } finally { executionTime = System.currentTimeMillis() - startTime; InvocationStatus status = response.getCode() == ResponseCode.SUCCESS ? InvocationStatus.SUCCESS : InvocationStatus.FAILURE; Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_INVOCATION_STATUS, status.getName()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic)) .build(); this.brokerController.getBrokerMetricsManager().getTopicCreateExecuteTime().record(executionTime, attributes); } LOGGER.info("executionTime of create topic:{} is {} ms", topic, executionTime); return response; } private synchronized RemotingCommand updateAndCreateTopicList(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { long startTime = System.currentTimeMillis(); final CreateTopicListRequestBody requestBody = CreateTopicListRequestBody.decode(request.getBody(), CreateTopicListRequestBody.class); List topicConfigList = requestBody.getTopicConfigList(); StringBuilder builder = new StringBuilder(); for (TopicConfig topicConfig : topicConfigList) { builder.append(topicConfig.getTopicName()).append(";"); } String topicNames = builder.toString(); LOGGER.info("AdminBrokerProcessor#updateAndCreateTopicList: topicNames: {}, called by {}", topicNames, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); final RemotingCommand response = RemotingCommand.createResponseCommand(null); long executionTime; try { // Valid topics for (TopicConfig topicConfig : topicConfigList) { String topic = topicConfig.getTopicName(); TopicValidator.ValidateResult result = TopicValidator.validateTopic(topic); if (!result.isValid()) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(result.getRemark()); return response; } if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { if (TopicValidator.isSystemTopic(topic)) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The topic[" + topic + "] is conflict with system topic."); return response; } } if (!brokerController.getBrokerConfig().isEnableMixedMessageType() && topicConfig.getAttributes() != null) { // Get attribute by key with prefix sign String msgTypeAttrKey = AttributeParser.ATTR_ADD_PLUS_SIGN + TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(); String msgTypeAttrValue = topicConfig.getAttributes().get(msgTypeAttrKey); if (msgTypeAttrValue != null && msgTypeAttrValue.equals(TopicMessageType.MIXED.getValue())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("MIXED message type is not supported."); return response; } } if (topicConfig.equals(this.brokerController.getTopicConfigManager().getTopicConfigTable().get(topic))) { LOGGER.info("Broker receive request to update or create topic={}, but topicConfig has no changes , so idempotent, caller address={}", topic, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); response.setCode(ResponseCode.SUCCESS); return response; } } this.brokerController.getTopicConfigManager().updateTopicConfigList(topicConfigList); if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) { for (TopicConfig topicConfig : topicConfigList) { this.brokerController.registerSingleTopicAll(topicConfig); } } else { this.brokerController.registerIncrementBrokerData(topicConfigList, this.brokerController.getTopicConfigManager().getDataVersion()); } response.setCode(ResponseCode.SUCCESS); } catch (Exception e) { LOGGER.error("Update / create topic failed for [{}]", request, e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); return response; } finally { executionTime = System.currentTimeMillis() - startTime; InvocationStatus status = response.getCode() == ResponseCode.SUCCESS ? InvocationStatus.SUCCESS : InvocationStatus.FAILURE; Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_INVOCATION_STATUS, status.getName()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topicNames)) .build(); this.brokerController.getBrokerMetricsManager().getTopicCreateExecuteTime().record(executionTime, attributes); } LOGGER.info("executionTime of all topics:{} is {} ms", topicNames, executionTime); return response; } private synchronized RemotingCommand updateAndCreateStaticTopic(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final CreateTopicRequestHeader requestHeader = (CreateTopicRequestHeader) request.decodeCommandCustomHeader(CreateTopicRequestHeader.class); LOGGER.info("Broker receive request to update or create static topic={}, caller address={}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); final TopicQueueMappingDetail topicQueueMappingDetail = RemotingSerializable.decode(request.getBody(), TopicQueueMappingDetail.class); String topic = requestHeader.getTopic(); TopicValidator.ValidateResult result = TopicValidator.validateTopic(topic); if (!result.isValid()) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(result.getRemark()); return response; } if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { if (TopicValidator.isSystemTopic(topic)) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The topic[" + topic + "] is conflict with system topic."); return response; } } boolean force = requestHeader.getForce() != null && requestHeader.getForce(); TopicConfig topicConfig = new TopicConfig(topic); topicConfig.setReadQueueNums(requestHeader.getReadQueueNums()); topicConfig.setWriteQueueNums(requestHeader.getWriteQueueNums()); topicConfig.setTopicFilterType(requestHeader.getTopicFilterTypeEnum()); topicConfig.setPerm(requestHeader.getPerm()); topicConfig.setTopicSysFlag(requestHeader.getTopicSysFlag() == null ? 0 : requestHeader.getTopicSysFlag()); try { this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); this.brokerController.getTopicQueueMappingManager().updateTopicQueueMapping(topicQueueMappingDetail, force, false, true); this.brokerController.registerIncrementBrokerData(topicConfig, this.brokerController.getTopicConfigManager().getDataVersion()); response.setCode(ResponseCode.SUCCESS); } catch (Exception e) { LOGGER.error("Update static topic failed for [{}]", request, e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); } return response; } private synchronized RemotingCommand deleteTopic(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); DeleteTopicRequestHeader requestHeader = (DeleteTopicRequestHeader) request.decodeCommandCustomHeader(DeleteTopicRequestHeader.class); LOGGER.info("AdminBrokerProcessor#deleteTopic: broker receive request to delete topic={}, caller={}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); String topic = requestHeader.getTopic(); if (UtilAll.isBlank(topic)) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The specified topic is blank."); return response; } if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { if (TopicValidator.isSystemTopic(topic)) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The topic[" + topic + "] is conflict with system topic."); return response; } } List topicsToClean = new ArrayList<>(); topicsToClean.add(topic); if (brokerController.getBrokerConfig().isClearRetryTopicWhenDeleteTopic()) { final Set groups = this.brokerController.getConsumerOffsetManager().whichGroupByTopic(topic); for (String group : groups) { final String popRetryTopicV2 = KeyBuilder.buildPopRetryTopic(topic, group, true); if (brokerController.getTopicConfigManager().selectTopicConfig(popRetryTopicV2) != null) { topicsToClean.add(popRetryTopicV2); } final String popRetryTopicV1 = KeyBuilder.buildPopRetryTopicV1(topic, group); if (brokerController.getTopicConfigManager().selectTopicConfig(popRetryTopicV1) != null) { topicsToClean.add(popRetryTopicV1); } } } try { if (LiteMetadataUtil.isLiteMessageType(topic, brokerController)) { brokerController.getLiteLifecycleManager().cleanByParentTopic(topic); } for (String topicToClean : topicsToClean) { // delete topic deleteTopicInBroker(topicToClean); } } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private void deleteTopicInBroker(String topic) { this.brokerController.getTopicConfigManager().deleteTopicConfig(topic); this.brokerController.getTopicQueueMappingManager().delete(topic); this.brokerController.getConsumerOffsetManager().cleanOffsetByTopic(topic); this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNumByTopicName(topic); this.brokerController.getMessageStore().deleteTopics(Sets.newHashSet(topic)); this.brokerController.getMessageStore().getTimerMessageStore().getTimerMetrics().removeTimingCount(topic); } private RemotingCommand getUnknownCmdResponse(ChannelHandlerContext ctx, RemotingCommand request) { String error = " request type " + request.getCode() + " not supported"; final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); return response; } private RemotingCommand getAllTopicConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(GetAllTopicConfigResponseHeader.class); final GetAllTopicConfigResponseHeader responseHeader = (GetAllTopicConfigResponseHeader) response.readCustomHeader(); final GetAllTopicConfigRequestHeader requestHeader = request.decodeCommandCustomHeader(GetAllTopicConfigRequestHeader.class); String dataVersionStr = requestHeader.getDataVersion(); Integer topicSeq = requestHeader.getTopicSeq(); Integer maxTopicNum = requestHeader.getMaxTopicNum(); TopicConfigManager tcManager = brokerController.getTopicConfigManager(); TopicQueueMappingManager tqmManager = brokerController.getTopicQueueMappingManager(); TopicConfigAndMappingSerializeWrapper topicConfigAndMappingSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); if (!brokerController.getBrokerConfig().isEnableSplitMetadata() || ObjectUtils.allNull(dataVersionStr, topicSeq, maxTopicNum)) { // old client, return all topic config topicConfigAndMappingSerializeWrapper.setDataVersion(tcManager.getDataVersion()); topicConfigAndMappingSerializeWrapper.setTopicConfigTable(tcManager.getTopicConfigTable()); topicConfigAndMappingSerializeWrapper.setMappingDataVersion(tqmManager.getDataVersion()); topicConfigAndMappingSerializeWrapper.setTopicQueueMappingDetailMap(tqmManager.getTopicQueueMappingTable()); } else { int topicNum = Math.min(brokerController.getBrokerConfig().getSplitMetadataSize(), Optional.ofNullable(maxTopicNum).orElse(Integer.MAX_VALUE)); // use smaller value ConcurrentHashMap subTopicConfigTable = tcManager.subTopicConfigTable(dataVersionStr, topicSeq, topicNum); topicConfigAndMappingSerializeWrapper.setTopicConfigTable(subTopicConfigTable); topicConfigAndMappingSerializeWrapper.setDataVersion(tcManager.getDataVersion()); topicConfigAndMappingSerializeWrapper.setMappingDataVersion(tqmManager.getDataVersion()); topicConfigAndMappingSerializeWrapper.setTopicQueueMappingDetailMap( tqmManager.subTopicQueueMappingTable(subTopicConfigTable.keySet())); } responseHeader.setTotalTopicNum(tcManager.getTopicConfigTable().size()); String content = topicConfigAndMappingSerializeWrapper.toJson(); if (StringUtils.isNotBlank(content)) { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); response.setBody(content.getBytes(StandardCharsets.UTF_8)); } else { LOGGER.error("No topic in this broker, client: {}", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("No topic in this broker"); } return response; } private RemotingCommand getTimerCheckPoint(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, "Unknown"); TimerCheckpoint timerCheckpoint = this.brokerController.getTimerCheckpoint(); if (null == timerCheckpoint) { LOGGER.error("AdminBrokerProcessor#getTimerCheckPoint: checkpoint is null, caller={}", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The checkpoint is null"); return response; } response.setBody(TimerCheckpoint.encode(timerCheckpoint).array()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getTimerMetrics(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, "Unknown"); TimerMessageStore timerMessageStore = this.brokerController.getMessageStore().getTimerMessageStore(); if (null == timerMessageStore) { LOGGER.error("The timer message store is null, client: {}", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The timer message store is null"); return response; } response.setBody(timerMessageStore.getTimerMetrics().encode().getBytes(StandardCharsets.UTF_8)); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private synchronized RemotingCommand updateColdDataFlowCtrGroupConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("updateColdDataFlowCtrGroupConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); byte[] body = request.getBody(); if (body != null) { try { String bodyStr = new String(body, MixAll.DEFAULT_CHARSET); Properties properties = MixAll.string2Properties(bodyStr); if (properties != null) { LOGGER.info("updateColdDataFlowCtrGroupConfig new config: {}, client: {}", properties, ctx.channel().remoteAddress()); properties.forEach((key, value) -> { try { String consumerGroup = String.valueOf(key); Long threshold = Long.valueOf(String.valueOf(value)); this.brokerController.getColdDataCgCtrService() .addOrUpdateGroupConfig(consumerGroup, threshold); } catch (Exception e) { LOGGER.error("updateColdDataFlowCtrGroupConfig properties on entry error, key: {}, val: {}", key, value, e); } }); } else { LOGGER.error("updateColdDataFlowCtrGroupConfig string2Properties error"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string2Properties error"); return response; } } catch (UnsupportedEncodingException e) { LOGGER.error("updateColdDataFlowCtrGroupConfig UnsupportedEncodingException", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private synchronized RemotingCommand removeColdDataFlowCtrGroupConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("removeColdDataFlowCtrGroupConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); byte[] body = request.getBody(); if (body != null) { try { String consumerGroup = new String(body, MixAll.DEFAULT_CHARSET); if (consumerGroup != null) { LOGGER.info("removeColdDataFlowCtrGroupConfig, consumerGroup: {} client: {}", consumerGroup, ctx.channel().remoteAddress()); this.brokerController.getColdDataCgCtrService().removeGroupConfig(consumerGroup); } else { LOGGER.error("removeColdDataFlowCtrGroupConfig string parse error"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string parse error"); return response; } } catch (UnsupportedEncodingException e) { LOGGER.error("removeColdDataFlowCtrGroupConfig UnsupportedEncodingException", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getColdDataFlowCtrInfo(ChannelHandlerContext ctx) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("getColdDataFlowCtrInfo called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); String content = this.brokerController.getColdDataCgCtrService().getColdDataFlowCtrInfo(); if (content != null) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("getColdDataFlowCtrInfo UnsupportedEncodingException", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand setCommitLogReadaheadMode(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("setCommitLogReadaheadMode called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); try { HashMap extFields = request.getExtFields(); if (null == extFields) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("set commitlog readahead mode param error"); return response; } int mode = Integer.parseInt(extFields.get(FIleReadaheadMode.READ_AHEAD_MODE)); if (mode != LibC.MADV_RANDOM && mode != LibC.MADV_NORMAL) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("set commitlog readahead mode param value error"); return response; } MessageStore messageStore = this.brokerController.getMessageStore(); if (messageStore instanceof DefaultMessageStore) { DefaultMessageStore defaultMessageStore = (DefaultMessageStore) messageStore; if (mode == LibC.MADV_NORMAL) { defaultMessageStore.getMessageStoreConfig().setDataReadAheadEnable(true); } else { defaultMessageStore.getMessageStoreConfig().setDataReadAheadEnable(false); } defaultMessageStore.getCommitLog().scanFileAndSetReadMode(mode); } response.setCode(ResponseCode.SUCCESS); response.setRemark("set commitlog readahead mode success, mode: " + mode); } catch (Exception e) { LOGGER.error("set commitlog readahead mode failed", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("set commitlog readahead mode failed"); } return response; } private synchronized RemotingCommand updateBrokerConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final String callerAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.info("Broker receive request to update config, caller address={}", callerAddress); byte[] body = request.getBody(); if (body != null) { try { String bodyStr = new String(body, MixAll.DEFAULT_CHARSET); Properties properties = MixAll.string2Properties(bodyStr); if (properties != null) { LOGGER.info("updateBrokerConfig, new config: [{}] client: {} ", properties, callerAddress); if (validateBlackListConfigExist(properties)) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("Can not update config in black list."); return response; } this.brokerController.getConfiguration().update(properties); if (properties.containsKey("brokerPermission")) { long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(stateMachineVersion); this.brokerController.registerBrokerAll(false, false, true); } } else { LOGGER.error("string2Properties error"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string2Properties error"); return response; } } catch (UnsupportedEncodingException e) { LOGGER.error("AdminBrokerProcessor#updateBrokerConfig: unexpected error, caller={}", callerAddress, e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getBrokerConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(GetBrokerConfigResponseHeader.class); final GetBrokerConfigResponseHeader responseHeader = (GetBrokerConfigResponseHeader) response.readCustomHeader(); String content = this.brokerController.getConfiguration().getAllConfigsFormatString(); if (content != null && content.length() > 0) { try { content = MixAll.adjustConfigForPlatform(content); response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("AdminBrokerProcessor#getBrokerConfig: unexpected error, caller={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } responseHeader.setVersion(this.brokerController.getConfiguration().getDataVersionJson()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand rewriteRequestForStaticTopic(SearchOffsetRequestHeader requestHeader, TopicQueueMappingContext mappingContext) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); List mappingItems = mappingContext.getMappingItemList(); if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", mappingContext.getTopic(), mappingContext.getGlobalId(), mappingDetail.getBname())); } //TO DO should make sure the timestampOfOffset is equal or bigger than the searched timestamp Long timestamp = requestHeader.getTimestamp(); long offset = -1; for (int i = 0; i < mappingItems.size(); i++) { LogicQueueMappingItem item = mappingItems.get(i); if (!item.checkIfLogicoffsetDecided()) { continue; } if (mappingDetail.getBname().equals(item.getBname())) { offset = this.brokerController.getMessageStore().getOffsetInQueueByTime(mappingContext.getTopic(), item.getQueueId(), timestamp, requestHeader.getBoundaryType()); if (offset > 0) { offset = item.computeStaticQueueOffsetStrictly(offset); break; } } else { requestHeader.setLo(false); requestHeader.setTimestamp(timestamp); requestHeader.setQueueId(item.getQueueId()); requestHeader.setBrokerName(item.getBname()); RpcRequest rpcRequest = new RpcRequest(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader, null); RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } SearchOffsetResponseHeader offsetResponseHeader = (SearchOffsetResponseHeader) rpcResponse.getHeader(); if (offsetResponseHeader.getOffset() < 0 || item.checkIfEndOffsetDecided() && offsetResponseHeader.getOffset() >= item.getEndOffset()) { continue; } else { offset = item.computeStaticQueueOffsetStrictly(offsetResponseHeader.getOffset()); } } } final RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class); final SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(offset); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } private RemotingCommand searchOffsetByTimestamp(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class); final SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); final SearchOffsetRequestHeader requestHeader = (SearchOffsetRequestHeader) request.decodeCommandCustomHeader(SearchOffsetRequestHeader.class); TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader); RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } boolean queryOffset = true; String topic = requestHeader.getTopic(); int queueId = requestHeader.getQueueId(); String liteTopic = requestHeader.getLiteTopic(); if (StringUtils.isNotBlank(liteTopic)) { topic = LiteUtil.toLmqName(topic, liteTopic); long maxOffset = 0; if (queueId == 0) { maxOffset = this.brokerController.getLiteLifecycleManager().getMaxOffsetInQueue(topic); } // lite topic check max offset first if (maxOffset <= 0) { queryOffset = false; } } long offset = 0L; if (queryOffset) { offset = this.brokerController.getMessageStore().getOffsetInQueueByTime(topic, queueId, requestHeader.getTimestamp(), requestHeader.getBoundaryType()); } responseHeader.setOffset(offset); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand rewriteRequestForStaticTopic(GetMaxOffsetRequestHeader requestHeader, TopicQueueMappingContext mappingContext) { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); LogicQueueMappingItem mappingItem = mappingContext.getLeaderItem(); if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", mappingContext.getTopic(), mappingContext.getGlobalId(), mappingDetail.getBname())); } try { LogicQueueMappingItem maxItem = TopicQueueMappingUtils.findLogicQueueMappingItem(mappingContext.getMappingItemList(), Long.MAX_VALUE, true); assert maxItem != null; assert maxItem.getLogicOffset() >= 0; requestHeader.setBrokerName(maxItem.getBname()); requestHeader.setLo(false); requestHeader.setQueueId(mappingItem.getQueueId()); long maxPhysicalOffset = Long.MAX_VALUE; if (maxItem.getBname().equals(mappingDetail.getBname())) { //current broker maxPhysicalOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(mappingContext.getTopic(), mappingItem.getQueueId()); } else { RpcRequest rpcRequest = new RpcRequest(RequestCode.GET_MAX_OFFSET, requestHeader, null); RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } GetMaxOffsetResponseHeader offsetResponseHeader = (GetMaxOffsetResponseHeader) rpcResponse.getHeader(); maxPhysicalOffset = offsetResponseHeader.getOffset(); } final RemotingCommand response = RemotingCommand.createResponseCommand(GetMaxOffsetResponseHeader.class); final GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(maxItem.computeStaticQueueOffsetStrictly(maxPhysicalOffset)); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } private RemotingCommand getMaxOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(GetMaxOffsetResponseHeader.class); final GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.readCustomHeader(); final GetMaxOffsetRequestHeader requestHeader = request.decodeCommandCustomHeader(GetMaxOffsetRequestHeader.class); TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader); RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } try { long offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); responseHeader.setOffset(offset); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset in queue", e); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private CompletableFuture handleGetMinOffsetForStaticTopic(RpcRequest request, TopicQueueMappingContext mappingContext) { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); if (!mappingContext.isLeader()) { //this may not return CompletableFuture.completedFuture(new RpcResponse(new RpcException(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d is not leader in broker %s, request code %d", mappingContext.getTopic(), mappingContext.getGlobalId(), mappingDetail.getBname(), request.getCode())))); } GetMinOffsetRequestHeader requestHeader = (GetMinOffsetRequestHeader) request.getHeader(); LogicQueueMappingItem mappingItem = TopicQueueMappingUtils.findLogicQueueMappingItem(mappingContext.getMappingItemList(), 0L, true); assert mappingItem != null; try { requestHeader.setBrokerName(mappingItem.getBname()); requestHeader.setLo(false); requestHeader.setQueueId(mappingItem.getQueueId()); long physicalOffset; //run in local if (mappingItem.getBname().equals(mappingDetail.getBname())) { physicalOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(mappingDetail.getTopic(), mappingItem.getQueueId()); } else { RpcRequest rpcRequest = new RpcRequest(RequestCode.GET_MIN_OFFSET, requestHeader, null); RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } GetMinOffsetResponseHeader offsetResponseHeader = (GetMinOffsetResponseHeader) rpcResponse.getHeader(); physicalOffset = offsetResponseHeader.getOffset(); } long offset = mappingItem.computeStaticQueueOffsetLoosely(physicalOffset); final GetMinOffsetResponseHeader responseHeader = new GetMinOffsetResponseHeader(); responseHeader.setOffset(offset); return CompletableFuture.completedFuture(new RpcResponse(ResponseCode.SUCCESS, responseHeader, null)); } catch (Throwable t) { LOGGER.error("rewriteRequestForStaticTopic failed", t); return CompletableFuture.completedFuture(new RpcResponse(new RpcException(ResponseCode.SYSTEM_ERROR, t.getMessage(), t))); } } private CompletableFuture handleGetMinOffset(RpcRequest request) { assert request.getCode() == RequestCode.GET_MIN_OFFSET; GetMinOffsetRequestHeader requestHeader = (GetMinOffsetRequestHeader) request.getHeader(); TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader, false); CompletableFuture rewriteResult = handleGetMinOffsetForStaticTopic(request, mappingContext); if (rewriteResult != null) { return rewriteResult; } final GetMinOffsetResponseHeader responseHeader = new GetMinOffsetResponseHeader(); long offset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); responseHeader.setOffset(offset); return CompletableFuture.completedFuture(new RpcResponse(ResponseCode.SUCCESS, responseHeader, null)); } private RemotingCommand getMinOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetMinOffsetRequestHeader requestHeader = (GetMinOffsetRequestHeader) request.decodeCommandCustomHeader(GetMinOffsetRequestHeader.class); try { CompletableFuture responseFuture = handleGetMinOffset(new RpcRequest(RequestCode.GET_MIN_OFFSET, requestHeader, null)); RpcResponse rpcResponse = responseFuture.get(); return RpcClientUtils.createCommandForRpcResponse(rpcResponse); } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } private RemotingCommand rewriteRequestForStaticTopic(GetEarliestMsgStoretimeRequestHeader requestHeader, TopicQueueMappingContext mappingContext) { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", mappingContext.getTopic(), mappingContext.getGlobalId(), mappingDetail.getBname())); } LogicQueueMappingItem mappingItem = TopicQueueMappingUtils.findLogicQueueMappingItem(mappingContext.getMappingItemList(), 0L, true); assert mappingItem != null; try { requestHeader.setBrokerName(mappingItem.getBname()); requestHeader.setLo(false); RpcRequest rpcRequest = new RpcRequest(RequestCode.GET_EARLIEST_MSG_STORETIME, requestHeader, null); //TO DO check if it is in current broker RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } GetEarliestMsgStoretimeResponseHeader offsetResponseHeader = (GetEarliestMsgStoretimeResponseHeader) rpcResponse.getHeader(); final RemotingCommand response = RemotingCommand.createResponseCommand(GetEarliestMsgStoretimeResponseHeader.class); final GetEarliestMsgStoretimeResponseHeader responseHeader = (GetEarliestMsgStoretimeResponseHeader) response.readCustomHeader(); responseHeader.setTimestamp(offsetResponseHeader.getTimestamp()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } private RemotingCommand getEarliestMsgStoretime(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(GetEarliestMsgStoretimeResponseHeader.class); final GetEarliestMsgStoretimeResponseHeader responseHeader = (GetEarliestMsgStoretimeResponseHeader) response.readCustomHeader(); final GetEarliestMsgStoretimeRequestHeader requestHeader = (GetEarliestMsgStoretimeRequestHeader) request.decodeCommandCustomHeader(GetEarliestMsgStoretimeRequestHeader.class); TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader, false); RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } long timestamp = this.brokerController.getMessageStore().getEarliestMessageTime(requestHeader.getTopic(), requestHeader.getQueueId()); responseHeader.setTimestamp(timestamp); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getBrokerRuntimeInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); HashMap runtimeInfo = this.prepareRuntimeInfo(); KVTable kvTable = new KVTable(); kvTable.setTable(runtimeInfo); byte[] body = kvTable.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand lockBatchMQ(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LockBatchRequestBody requestBody = LockBatchRequestBody.decode(request.getBody(), LockBatchRequestBody.class); Set lockOKMQSet = new HashSet<>(); Set selfLockOKMQSet = this.brokerController.getRebalanceLockManager().tryLockBatch( requestBody.getConsumerGroup(), requestBody.getMqSet(), requestBody.getClientId()); if (requestBody.isOnlyThisBroker() || !brokerController.getBrokerConfig().isLockInStrictMode()) { lockOKMQSet = selfLockOKMQSet; } else { requestBody.setOnlyThisBroker(true); int replicaSize = this.brokerController.getMessageStoreConfig().getTotalReplicas(); int quorum = replicaSize / 2 + 1; if (quorum <= 1) { lockOKMQSet = selfLockOKMQSet; } else { final ConcurrentMap mqLockMap = new ConcurrentHashMap<>(); for (MessageQueue mq : selfLockOKMQSet) { if (!mqLockMap.containsKey(mq)) { mqLockMap.put(mq, 0); } mqLockMap.put(mq, mqLockMap.get(mq) + 1); } BrokerMemberGroup memberGroup = this.brokerController.getBrokerMemberGroup(); if (memberGroup != null) { Map addrMap = new HashMap<>(memberGroup.getBrokerAddrs()); addrMap.remove(this.brokerController.getBrokerConfig().getBrokerId()); final CountDownLatch countDownLatch = new CountDownLatch(addrMap.size()); requestBody.setMqSet(selfLockOKMQSet); requestBody.setOnlyThisBroker(true); for (Long brokerId : addrMap.keySet()) { try { this.brokerController.getBrokerOuterAPI().lockBatchMQAsync(addrMap.get(brokerId), requestBody, 1000, new LockCallback() { @Override public void onSuccess(Set lockOKMQSet) { for (MessageQueue mq : lockOKMQSet) { if (!mqLockMap.containsKey(mq)) { mqLockMap.put(mq, 0); } mqLockMap.put(mq, mqLockMap.get(mq) + 1); } countDownLatch.countDown(); } @Override public void onException(Throwable e) { LOGGER.warn("lockBatchMQAsync on {} failed, {}", addrMap.get(brokerId), e); countDownLatch.countDown(); } }); } catch (Exception e) { LOGGER.warn("lockBatchMQAsync on {} failed, {}", addrMap.get(brokerId), e); countDownLatch.countDown(); } } try { countDownLatch.await(2000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { LOGGER.warn("lockBatchMQ exception on {}, {}", this.brokerController.getBrokerConfig().getBrokerName(), e); } } for (MessageQueue mq : mqLockMap.keySet()) { if (mqLockMap.get(mq) >= quorum) { lockOKMQSet.add(mq); } } } } LockBatchResponseBody responseBody = new LockBatchResponseBody(); responseBody.setLockOKMQSet(lockOKMQSet); response.setBody(responseBody.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand unlockBatchMQ(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); UnlockBatchRequestBody requestBody = UnlockBatchRequestBody.decode(request.getBody(), UnlockBatchRequestBody.class); if (requestBody.isOnlyThisBroker() || !this.brokerController.getBrokerConfig().isLockInStrictMode()) { this.brokerController.getRebalanceLockManager().unlockBatch( requestBody.getConsumerGroup(), requestBody.getMqSet(), requestBody.getClientId()); } else { requestBody.setOnlyThisBroker(true); BrokerMemberGroup memberGroup = this.brokerController.getBrokerMemberGroup(); if (memberGroup != null) { Map addrMap = memberGroup.getBrokerAddrs(); for (Long brokerId : addrMap.keySet()) { try { this.brokerController.getBrokerOuterAPI().unlockBatchMQAsync(addrMap.get(brokerId), requestBody, 1000, new UnlockCallback() { @Override public void onSuccess() { } @Override public void onException(Throwable e) { LOGGER.warn("unlockBatchMQ exception on {}, {}", addrMap.get(brokerId), e); } }); } catch (Exception e) { LOGGER.warn("unlockBatchMQ exception on {}, {}", addrMap.get(brokerId), e); } } } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand updateAndCreateSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { long startTime = System.currentTimeMillis(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("AdminBrokerProcessor#updateAndCreateSubscriptionGroup called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); SubscriptionGroupConfig config = RemotingSerializable.decode(request.getBody(), SubscriptionGroupConfig.class); if (null != config) { TopicValidator.ValidateResult result = TopicValidator.validateGroup(config.getGroupName()); if (!result.isValid()) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(result.getRemark()); return response; } this.brokerController.getSubscriptionGroupManager().updateSubscriptionGroupConfig(config); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); long executionTime = System.currentTimeMillis() - startTime; if (null != config) { LOGGER.info("executionTime of create subscriptionGroup:{} is {} ms", config.getGroupName(), executionTime); } InvocationStatus status = response.getCode() == ResponseCode.SUCCESS ? InvocationStatus.SUCCESS : InvocationStatus.FAILURE; Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_INVOCATION_STATUS, status.getName()) .build(); this.brokerController.getBrokerMetricsManager().getConsumerGroupCreateExecuteTime().record(executionTime, attributes); return response; } private RemotingCommand updateAndCreateSubscriptionGroupList(ChannelHandlerContext ctx, RemotingCommand request) { final long startTime = System.nanoTime(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); final SubscriptionGroupList subscriptionGroupList = SubscriptionGroupList.decode(request.getBody(), SubscriptionGroupList.class); final List groupConfigList = subscriptionGroupList.getGroupConfigList(); final StringBuilder builder = new StringBuilder(); for (SubscriptionGroupConfig config : groupConfigList) { TopicValidator.ValidateResult result = TopicValidator.validateGroup(config.getGroupName()); if (!result.isValid()) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(result.getRemark()); return response; } builder.append(config.getGroupName()).append(";"); } final String groupNames = builder.toString(); LOGGER.info("AdminBrokerProcessor#updateAndCreateSubscriptionGroupList: groupNames: {}, called by {}", groupNames, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); try { this.brokerController.getSubscriptionGroupManager().updateSubscriptionGroupConfigList(groupConfigList); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } finally { long executionTime = (System.nanoTime() - startTime) / 1000000L; LOGGER.info("executionTime of create updateAndCreateSubscriptionGroupList: {} is {} ms", groupNames, executionTime); InvocationStatus status = response.getCode() == ResponseCode.SUCCESS ? InvocationStatus.SUCCESS : InvocationStatus.FAILURE; Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_INVOCATION_STATUS, status.getName()) .build(); this.brokerController.getBrokerMetricsManager().getConsumerGroupCreateExecuteTime().record(executionTime, attributes); } return response; } private void initConsumerOffset(String clientHost, String groupName, int mode, TopicConfig topicConfig) throws ConsumeQueueException { String topic = topicConfig.getTopicName(); for (int queueId = 0; queueId < topicConfig.getReadQueueNums(); queueId++) { if (this.brokerController.getConsumerOffsetManager().queryOffset(groupName, topic, queueId) > -1) { continue; } long offset = 0; if (this.brokerController.getMessageStore().getConsumeQueue(topic, queueId) != null) { if (ConsumeInitMode.MAX == mode) { offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); } else if (ConsumeInitMode.MIN == mode) { offset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId); } } this.brokerController.getConsumerOffsetManager().commitOffset(clientHost, groupName, topic, queueId, offset); LOGGER.info("AdminBrokerProcessor#initConsumerOffset: consumerGroup={}, topic={}, queueId={}, offset={}", groupName, topic, queueId, offset); } } private RemotingCommand getAllSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(GetAllSubscriptionGroupResponseHeader.class); final GetAllSubscriptionGroupResponseHeader responseHeader = (GetAllSubscriptionGroupResponseHeader) response.readCustomHeader(); final GetAllSubscriptionGroupRequestHeader requestHeader = request.decodeCommandCustomHeader(GetAllSubscriptionGroupRequestHeader.class); String dataVersionStr = requestHeader.getDataVersion(); Integer groupSeq = requestHeader.getGroupSeq(); Integer maxGroupNum = requestHeader.getMaxGroupNum(); SubscriptionGroupManager sgManager = this.brokerController.getSubscriptionGroupManager(); SubscriptionGroupWrapper subscriptionGroupWrapper = new SubscriptionGroupWrapper(); if (!brokerController.getBrokerConfig().isEnableSplitMetadata() || ObjectUtils.allNull(dataVersionStr, groupSeq, maxGroupNum)) { subscriptionGroupWrapper.setSubscriptionGroupTable(sgManager.getSubscriptionGroupTable()); subscriptionGroupWrapper.setForbiddenTable(sgManager.getForbiddenTable()); subscriptionGroupWrapper.setDataVersion(sgManager.getDataVersion()); } else { int groupNum = Math.min(brokerController.getBrokerConfig().getSplitMetadataSize(), Optional.ofNullable(maxGroupNum).orElse(Integer.MAX_VALUE)); ConcurrentMap subGroupTable = sgManager.subGroupTable(dataVersionStr, groupSeq, groupNum); subscriptionGroupWrapper.setSubscriptionGroupTable(subGroupTable); subscriptionGroupWrapper.setDataVersion(sgManager.getDataVersion()); subscriptionGroupWrapper.setForbiddenTable(sgManager.subForbiddenTable(subGroupTable.keySet())); } responseHeader.setTotalGroupNum(sgManager.getSubscriptionGroupTable().size()); String content = subscriptionGroupWrapper.toJson(); if (StringUtils.isNotBlank(content)) { response.setBody(content.getBytes(StandardCharsets.UTF_8)); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { LOGGER.error("No subscription group in this broker, client:{} ", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("No subscription group in this broker"); } return response; } private RemotingCommand deleteSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); DeleteSubscriptionGroupRequestHeader requestHeader = (DeleteSubscriptionGroupRequestHeader) request.decodeCommandCustomHeader(DeleteSubscriptionGroupRequestHeader.class); LOGGER.info("AdminBrokerProcessor#deleteSubscriptionGroup, caller={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); this.brokerController.getSubscriptionGroupManager().deleteSubscriptionGroupConfig(requestHeader.getGroupName()); if (requestHeader.isCleanOffset() || LiteMetadataUtil.isLiteGroupType(requestHeader.getGroupName(), this.brokerController)) { this.brokerController.getConsumerOffsetManager().removeOffset(requestHeader.getGroupName()); this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNumByGroupName(requestHeader.getGroupName()); } if (this.brokerController.getBrokerConfig().isAutoDeleteUnusedStats()) { this.brokerController.getBrokerStatsManager().onGroupDeleted(requestHeader.getGroupName()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getTopicStatsInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetTopicStatsInfoRequestHeader requestHeader = request.decodeCommandCustomHeader(GetTopicStatsInfoRequestHeader.class); final String topic = requestHeader.getTopic(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("topic[" + topic + "] not exist"); return response; } TopicStatsTable topicStatsTable = new TopicStatsTable(); int maxQueueNums = Math.max(topicConfig.getWriteQueueNums(), topicConfig.getReadQueueNums()); try { for (int i = 0; i < maxQueueNums; i++) { MessageQueue mq = new MessageQueue(); mq.setTopic(topic); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setQueueId(i); TopicOffset topicOffset = new TopicOffset(); long min = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, i); if (min < 0) { min = 0; } long max = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); if (max < 0) { max = 0; } long timestamp = 0; if (max > 0) { timestamp = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, max - 1); } topicOffset.setMinOffset(min); topicOffset.setMaxOffset(max); topicOffset.setLastUpdateTimestamp(timestamp); topicStatsTable.getOffsetTable().put(mq, topicOffset); } topicStatsTable.setTopicPutTps(this.brokerController.getBrokerStatsManager().tpsTopicPutNums(requestHeader.getTopic())); byte[] body = topicStatsTable.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } catch (ConsumeQueueException e) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); } return response; } private RemotingCommand getConsumerConnectionList(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetConsumerConnectionListRequestHeader requestHeader = (GetConsumerConnectionListRequestHeader) request.decodeCommandCustomHeader(GetConsumerConnectionListRequestHeader.class); ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup()); if (consumerGroupInfo != null) { ConsumerConnection bodydata = new ConsumerConnection(); bodydata.setConsumeFromWhere(consumerGroupInfo.getConsumeFromWhere()); bodydata.setConsumeType(consumerGroupInfo.getConsumeType()); bodydata.setMessageModel(consumerGroupInfo.getMessageModel()); bodydata.getSubscriptionTable().putAll(consumerGroupInfo.getSubscriptionTable()); Iterator> it = consumerGroupInfo.getChannelInfoTable().entrySet().iterator(); while (it.hasNext()) { ClientChannelInfo info = it.next().getValue(); Connection connection = new Connection(); connection.setClientId(info.getClientId()); connection.setLanguage(info.getLanguage()); connection.setVersion(info.getVersion()); connection.setClientAddr(RemotingHelper.parseChannelRemoteAddr(info.getChannel())); bodydata.getConnectionSet().add(connection); } byte[] body = bodydata.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } response.setCode(ResponseCode.CONSUMER_NOT_ONLINE); response.setRemark("the consumer group[" + requestHeader.getConsumerGroup() + "] not online"); return response; } private RemotingCommand getAllProducerInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetAllProducerInfoRequestHeader requestHeader = (GetAllProducerInfoRequestHeader) request.decodeCommandCustomHeader(GetAllProducerInfoRequestHeader.class); ProducerTableInfo producerTable = this.brokerController.getProducerManager().getProducerTable(); if (producerTable != null) { byte[] body = producerTable.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } response.setCode(ResponseCode.SYSTEM_ERROR); return response; } private RemotingCommand getProducerConnectionList(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetProducerConnectionListRequestHeader requestHeader = (GetProducerConnectionListRequestHeader) request.decodeCommandCustomHeader(GetProducerConnectionListRequestHeader.class); ProducerConnection bodydata = new ProducerConnection(); Map channelInfoHashMap = this.brokerController.getProducerManager().getGroupChannelTable().get(requestHeader.getProducerGroup()); if (channelInfoHashMap != null) { Iterator> it = channelInfoHashMap.entrySet().iterator(); while (it.hasNext()) { ClientChannelInfo info = it.next().getValue(); Connection connection = new Connection(); connection.setClientId(info.getClientId()); connection.setLanguage(info.getLanguage()); connection.setVersion(info.getVersion()); connection.setClientAddr(RemotingHelper.parseChannelRemoteAddr(info.getChannel())); bodydata.getConnectionSet().add(connection); } byte[] body = bodydata.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("the producer group[" + requestHeader.getProducerGroup() + "] not exist"); return response; } private RemotingCommand getConsumeStats(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); try { final GetConsumeStatsRequestHeader requestHeader = request.decodeCommandCustomHeader(GetConsumeStatsRequestHeader.class); List topicListProvided = requestHeader.fetchTopicList(); String topicProvided = requestHeader.getTopic(); String group = requestHeader.getConsumerGroup(); ConsumeStats consumeStats = new ConsumeStats(); Set topicsForCollecting = getTopicsForCollectingConsumeStats(topicListProvided, topicProvided, group); for (String topic : topicsForCollecting) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { LOGGER.warn("AdminBrokerProcessor#getConsumeStats: topic config does not exist, topic={}", topic); continue; } TopicQueueMappingDetail mappingDetail = this.brokerController.getTopicQueueMappingManager().getTopicQueueMapping(topic); for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { MessageQueue mq = new MessageQueue(); mq.setTopic(topic); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setQueueId(i); OffsetWrapper offsetWrapper = new OffsetWrapper(); long brokerOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); if (brokerOffset < 0) { brokerOffset = 0; } long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset( requestHeader.getConsumerGroup(), topic, i); // the consumerOffset cannot be zero for static topic because of the "double read check" strategy // just remain the logic for dynamic topic // maybe we should remove it in the future if (mappingDetail == null) { if (consumerOffset < 0) { consumerOffset = 0; } } long pullOffset = this.brokerController.getConsumerOffsetManager().queryPullOffset( requestHeader.getConsumerGroup(), topic, i); offsetWrapper.setBrokerOffset(brokerOffset); offsetWrapper.setConsumerOffset(consumerOffset); offsetWrapper.setPullOffset(Math.max(consumerOffset, pullOffset)); long timeOffset = consumerOffset - 1; if (timeOffset >= 0) { long lastTimestamp = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, timeOffset); if (lastTimestamp > 0) { offsetWrapper.setLastTimestamp(lastTimestamp); } } consumeStats.getOffsetTable().put(mq, offsetWrapper); } double consumeTps = this.brokerController.getBrokerStatsManager().tpsGroupGetNums(requestHeader.getConsumerGroup(), topic); consumeTps += consumeStats.getConsumeTps(); consumeStats.setConsumeTps(consumeTps); } byte[] body = consumeStats.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } catch (ConsumeQueueException e) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); } return response; } private Set getTopicsForCollectingConsumeStats(List topicListProvided, String topicProvided, String group) { Set topicsForCollecting = new HashSet<>(); if (!topicListProvided.isEmpty()) { // if topic list is provided, only collect the topics in the list // and ignore subscription check topicsForCollecting.addAll(topicListProvided); } else { // In order to be compatible with the old logic, // even if the topic has been provided here, the subscription will be checked. if (UtilAll.isBlank(topicProvided)) { topicsForCollecting.addAll( this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(group)); } else { topicsForCollecting.add(topicProvided); } int subscriptionCount = this.brokerController.getConsumerManager().findSubscriptionDataCount(group); Iterator iterator = topicsForCollecting.iterator(); while (iterator.hasNext()) { String topic = iterator.next(); SubscriptionData findSubscriptionData = this.brokerController.getConsumerManager().findSubscriptionData(group, topic); if (findSubscriptionData == null && subscriptionCount > 0) { LOGGER.warn( "AdminBrokerProcessor#getConsumeStats: topic does not exist in consumer group's subscription, topic={}, consumer group={}", topic, group); iterator.remove(); } } } return topicsForCollecting; } private RemotingCommand getAllConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); String content = this.brokerController.getConsumerOffsetManager().encode(); if (content != null && content.length() > 0) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("get all consumer offset from master error.", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e.getMessage()); return response; } } else { LOGGER.error("No consumer offset in this broker, client: {} ", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("No consumer offset in this broker"); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getAllDelayOffset(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); String content = this.brokerController.getScheduleMessageService().encode(); if (content != null && content.length() > 0) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("AdminBrokerProcessor#getAllDelayOffset: unexpected error, caller={}.", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } else { LOGGER.error("AdminBrokerProcessor#getAllDelayOffset: no delay offset in this broker, caller={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("No delay offset in this broker"); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getAllMessageRequestMode(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); String content = this.brokerController.getQueryAssignmentProcessor().getMessageRequestModeManager().encode(); if (content != null && !content.isEmpty()) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("get all message request mode from master error.", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } else { LOGGER.error("No message request mode in this broker, client: {} ", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("No message request mode in this broker"); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand resetOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ResetOffsetRequestHeader requestHeader = (ResetOffsetRequestHeader) request.decodeCommandCustomHeader(ResetOffsetRequestHeader.class); LOGGER.info("[reset-offset] reset offset started by {}. topic={}, group={}, timestamp={}, isForce={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getTopic(), requestHeader.getGroup(), requestHeader.getTimestamp(), requestHeader.isForce()); if (this.brokerController.getBrokerConfig().isUseServerSideResetOffset()) { String topic = requestHeader.getTopic(); String group = requestHeader.getGroup(); int queueId = requestHeader.getQueueId(); long timestamp = requestHeader.getTimestamp(); Long offset = requestHeader.getOffset(); return resetOffsetInner(topic, group, queueId, timestamp, offset); } boolean isC = false; LanguageCode language = request.getLanguage(); switch (language) { case CPP: isC = true; break; } return this.brokerController.getBroker2Client().resetOffset(requestHeader.getTopic(), requestHeader.getGroup(), requestHeader.getTimestamp(), requestHeader.isForce(), isC); } private Long searchOffsetByTimestamp(String topic, int queueId, long timestamp) throws ConsumeQueueException { if (timestamp < 0) { return brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); } else { return brokerController.getMessageStore().getOffsetInQueueByTime(topic, queueId, timestamp); } } /** * Reset consumer offset. * * @param topic Required, not null. * @param group Required, not null. * @param queueId if target queue ID is negative, all message queues will be reset; otherwise, only the target queue * would get reset. * @param timestamp if timestamp is negative, offset would be reset to broker offset at the time being; otherwise, * binary search is performed to locate target offset. * @param offset Target offset to reset to if target queue ID is properly provided. * @return Affected queues and their new offset */ private RemotingCommand resetOffsetInner(String topic, String group, int queueId, long timestamp, Long offset) { RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Can not reset offset in slave broker"); return response; } Map queueOffsetMap = new HashMap<>(); // Reset offset for all queues belonging to the specified topic TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("Topic " + topic + " does not exist"); LOGGER.warn("Reset offset failed, topic does not exist. topic={}, group={}", topic, group); return response; } if (!brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(group)) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark("Group " + group + " does not exist"); LOGGER.warn("Reset offset failed, group does not exist. topic={}, group={}", topic, group); return response; } try { if (queueId >= 0) { if (null != offset && -1 != offset) { long min = brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId); long max = brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); if (min >= 0 && offset < min || offset > max + 1) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark( String.format("Target offset %d not in consume queue range [%d-%d]", offset, min, max)); return response; } } else { offset = searchOffsetByTimestamp(topic, queueId, timestamp); } queueOffsetMap.put(queueId, offset); } else { for (int index = 0; index < topicConfig.getReadQueueNums(); index++) { offset = searchOffsetByTimestamp(topic, index, timestamp); queueOffsetMap.put(index, offset); } } } catch (ConsumeQueueException e) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); return response; } if (queueOffsetMap.isEmpty()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("No queues to reset."); LOGGER.warn("Reset offset aborted: no queues to reset"); return response; } for (Map.Entry entry : queueOffsetMap.entrySet()) { brokerController.getConsumerOffsetManager() .assignResetOffset(topic, group, entry.getKey(), entry.getValue()); } // Prepare reset result. ResetOffsetBody body = new ResetOffsetBody(); String brokerName = brokerController.getBrokerConfig().getBrokerName(); for (Map.Entry entry : queueOffsetMap.entrySet()) { if (brokerController.getPopInflightMessageCounter() != null) { brokerController.getPopInflightMessageCounter().clearInFlightMessageNum(topic, group, entry.getKey()); } if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { brokerController.getPopConsumerService().clearCache(group, topic, entry.getKey()); brokerController.getConsumerOffsetManager().clearPullOffset(group, topic); } body.getOffsetTable().put(new MessageQueue(topic, brokerName, entry.getKey()), entry.getValue()); } LOGGER.info("Reset offset, topic={}, group={}, queues={}", topic, group, body.toJson(false)); response.setBody(body.encode()); return response; } public RemotingCommand getConsumerStatus(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetConsumerStatusRequestHeader requestHeader = (GetConsumerStatusRequestHeader) request.decodeCommandCustomHeader(GetConsumerStatusRequestHeader.class); LOGGER.info("[get-consumer-status] get consumer status by {}. topic={}, group={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getTopic(), requestHeader.getGroup()); return this.brokerController.getBroker2Client().getConsumeStatus(requestHeader.getTopic(), requestHeader.getGroup(), requestHeader.getClientAddr()); } private RemotingCommand queryTopicConsumeByWho(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); QueryTopicConsumeByWhoRequestHeader requestHeader = (QueryTopicConsumeByWhoRequestHeader) request.decodeCommandCustomHeader(QueryTopicConsumeByWhoRequestHeader.class); HashSet groups = this.brokerController.getConsumerManager().queryTopicConsumeByWho(requestHeader.getTopic()); Set groupInOffset = this.brokerController.getConsumerOffsetManager().whichGroupByTopic(requestHeader.getTopic()); if (groupInOffset != null && !groupInOffset.isEmpty()) { groups.addAll(groupInOffset); } GroupList groupList = new GroupList(); groupList.setGroupList(groups); byte[] body = groupList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand queryTopicsByConsumer(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); QueryTopicsByConsumerRequestHeader requestHeader = (QueryTopicsByConsumerRequestHeader) request.decodeCommandCustomHeader(QueryTopicsByConsumerRequestHeader.class); Set topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(requestHeader.getGroup()); TopicList topicList = new TopicList(); topicList.setTopicList(topics); topicList.setBrokerAddr(brokerController.getBrokerAddr()); byte[] body = topicList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand querySubscriptionByConsumer(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); QuerySubscriptionByConsumerRequestHeader requestHeader = (QuerySubscriptionByConsumerRequestHeader) request.decodeCommandCustomHeader(QuerySubscriptionByConsumerRequestHeader.class); SubscriptionData subscriptionData = this.brokerController.getConsumerManager() .findSubscriptionData(requestHeader.getGroup(), requestHeader.getTopic()); QuerySubscriptionResponseBody responseBody = new QuerySubscriptionResponseBody(); responseBody.setGroup(requestHeader.getGroup()); responseBody.setTopic(requestHeader.getTopic()); responseBody.setSubscriptionData(subscriptionData); byte[] body = responseBody.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand queryConsumeTimeSpan(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); QueryConsumeTimeSpanRequestHeader requestHeader = request.decodeCommandCustomHeader(QueryConsumeTimeSpanRequestHeader.class); final String topic = requestHeader.getTopic(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("topic[" + topic + "] not exist"); return response; } List timeSpanSet = new ArrayList<>(); for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) { QueueTimeSpan timeSpan = new QueueTimeSpan(); MessageQueue mq = new MessageQueue(); mq.setTopic(topic); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setQueueId(i); timeSpan.setMessageQueue(mq); long minTime = this.brokerController.getMessageStore().getEarliestMessageTime(topic, i); timeSpan.setMinTimeStamp(minTime); long max; try { max = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset in queue", e); } long maxTime = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, max - 1); timeSpan.setMaxTimeStamp(maxTime); long consumeTime; long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset( requestHeader.getGroup(), topic, i); if (consumerOffset > 0) { consumeTime = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, consumerOffset - 1); } else { consumeTime = minTime; } timeSpan.setConsumeTimeStamp(consumeTime); long maxBrokerOffset; try { maxBrokerOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), i); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset in queue", e); } if (consumerOffset < maxBrokerOffset) { long nextTime = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, consumerOffset); timeSpan.setDelayTime(System.currentTimeMillis() - nextTime); } timeSpanSet.add(timeSpan); } QueryConsumeTimeSpanBody queryConsumeTimeSpanBody = new QueryConsumeTimeSpanBody(); queryConsumeTimeSpanBody.setConsumeTimeSpanSet(timeSpanSet); response.setBody(queryConsumeTimeSpanBody.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getSystemTopicListFromBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); Set topics = TopicValidator.getSystemTopicSet(); TopicList topicList = new TopicList(); topicList.setTopicList(topics); response.setBody(topicList.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand cleanExpiredConsumeQueue() { LOGGER.info("AdminBrokerProcessor#cleanExpiredConsumeQueue: start."); final RemotingCommand response = RemotingCommand.createResponseCommand(null); try { brokerController.getMessageStore().cleanExpiredConsumerQueue(); } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } LOGGER.info("AdminBrokerProcessor#cleanExpiredConsumeQueue: end."); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand deleteExpiredCommitLog() { LOGGER.warn("invoke deleteExpiredCommitLog start."); final RemotingCommand response = RemotingCommand.createResponseCommand(null); brokerController.getMessageStore().executeDeleteFilesManually(); LOGGER.warn("invoke deleteExpiredCommitLog end."); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand cleanUnusedTopic() { LOGGER.warn("invoke cleanUnusedTopic start."); final RemotingCommand response = RemotingCommand.createResponseCommand(null); brokerController.getMessageStore().cleanUnusedTopic(brokerController.getTopicConfigManager().getTopicConfigTable().keySet()); LOGGER.warn("invoke cleanUnusedTopic end."); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getConsumerRunningInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetConsumerRunningInfoRequestHeader requestHeader = (GetConsumerRunningInfoRequestHeader) request.decodeCommandCustomHeader(GetConsumerRunningInfoRequestHeader.class); return this.callConsumer(RequestCode.GET_CONSUMER_RUNNING_INFO, request, requestHeader.getConsumerGroup(), requestHeader.getClientId()); } private RemotingCommand queryCorrectionOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); QueryCorrectionOffsetHeader requestHeader = (QueryCorrectionOffsetHeader) request.decodeCommandCustomHeader(QueryCorrectionOffsetHeader.class); Map correctionOffset = this.brokerController.getConsumerOffsetManager() .queryMinOffsetInAllGroup(requestHeader.getTopic(), requestHeader.getFilterGroups()); Map compareOffset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getCompareGroup(), requestHeader.getTopic()); if (compareOffset != null && !compareOffset.isEmpty()) { for (Map.Entry entry : compareOffset.entrySet()) { Integer queueId = entry.getKey(); correctionOffset.put(queueId, correctionOffset.get(queueId) > entry.getValue() ? Long.MAX_VALUE : correctionOffset.get(queueId)); } } QueryCorrectionOffsetBody body = new QueryCorrectionOffsetBody(); body.setCorrectionOffsets(correctionOffset); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand consumeMessageDirectly(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ConsumeMessageDirectlyResultRequestHeader requestHeader = (ConsumeMessageDirectlyResultRequestHeader) request .decodeCommandCustomHeader(ConsumeMessageDirectlyResultRequestHeader.class); // brokerName request.getExtFields().put("brokerName", this.brokerController.getBrokerConfig().getBrokerName()); // topicSysFlag if (StringUtils.isNotEmpty(requestHeader.getTopic())) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().getTopicConfigTable().get(requestHeader.getTopic()); if (topicConfig != null) { request.addExtField("topicSysFlag", String.valueOf(topicConfig.getTopicSysFlag())); } } // groupSysFlag if (StringUtils.isNotEmpty(requestHeader.getConsumerGroup())) { SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (groupConfig != null) { request.addExtField("groupSysFlag", String.valueOf(groupConfig.getGroupSysFlag())); } } SelectMappedBufferResult selectMappedBufferResult = null; try { MessageId messageId = MessageDecoder.decodeMessageId(requestHeader.getMsgId()); selectMappedBufferResult = this.brokerController.getMessageStore().selectOneMessageByOffset(messageId.getOffset()); byte[] body = new byte[selectMappedBufferResult.getSize()]; selectMappedBufferResult.getByteBuffer().get(body); request.setBody(body); } catch (UnknownHostException e) { } finally { if (selectMappedBufferResult != null) { selectMappedBufferResult.release(); } } return this.callConsumer(RequestCode.CONSUME_MESSAGE_DIRECTLY, request, requestHeader.getConsumerGroup(), requestHeader.getClientId()); } private RemotingCommand cloneGroupOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); CloneGroupOffsetRequestHeader requestHeader = (CloneGroupOffsetRequestHeader) request.decodeCommandCustomHeader(CloneGroupOffsetRequestHeader.class); Set topics; if (UtilAll.isBlank(requestHeader.getTopic())) { topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(requestHeader.getSrcGroup()); } else { topics = new HashSet<>(); topics.add(requestHeader.getTopic()); } for (String topic : topics) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { LOGGER.warn("[cloneGroupOffset], topic config not exist, {}", topic); continue; } if (!requestHeader.isOffline()) { SubscriptionData findSubscriptionData = this.brokerController.getConsumerManager().findSubscriptionData(requestHeader.getSrcGroup(), topic); if (this.brokerController.getConsumerManager().findSubscriptionDataCount(requestHeader.getSrcGroup()) > 0 && findSubscriptionData == null) { LOGGER.warn( "AdminBrokerProcessor#cloneGroupOffset: topic does not exist in consumer group's " + "subscription, topic={}, consumer group={}", topic, requestHeader.getSrcGroup()); continue; } } this.brokerController.getConsumerOffsetManager().cloneOffset(requestHeader.getSrcGroup(), requestHeader.getDestGroup(), requestHeader.getTopic()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand ViewBrokerStatsData(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ViewBrokerStatsDataRequestHeader requestHeader = (ViewBrokerStatsDataRequestHeader) request.decodeCommandCustomHeader(ViewBrokerStatsDataRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); MessageStore messageStore = this.brokerController.getMessageStore(); StatsItem statsItem = messageStore.getBrokerStatsManager().getStatsItem(requestHeader.getStatsName(), requestHeader.getStatsKey()); if (null == statsItem) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("The stats <%s> <%s> not exist", requestHeader.getStatsName(), requestHeader.getStatsKey())); return response; } BrokerStatsData brokerStatsData = new BrokerStatsData(); { BrokerStatsItem it = new BrokerStatsItem(); StatsSnapshot ss = statsItem.getStatsDataInMinute(); it.setSum(ss.getSum()); it.setTps(ss.getTps()); it.setAvgpt(ss.getAvgpt()); brokerStatsData.setStatsMinute(it); } { BrokerStatsItem it = new BrokerStatsItem(); StatsSnapshot ss = statsItem.getStatsDataInHour(); it.setSum(ss.getSum()); it.setTps(ss.getTps()); it.setAvgpt(ss.getAvgpt()); brokerStatsData.setStatsHour(it); } { BrokerStatsItem it = new BrokerStatsItem(); StatsSnapshot ss = statsItem.getStatsDataInDay(); it.setSum(ss.getSum()); it.setTps(ss.getTps()); it.setAvgpt(ss.getAvgpt()); brokerStatsData.setStatsDay(it); } response.setBody(brokerStatsData.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); GetConsumeStatsInBrokerHeader requestHeader = request.decodeCommandCustomHeader(GetConsumeStatsInBrokerHeader.class); boolean isOrder = requestHeader.isOrder(); ConcurrentMap subscriptionGroups = brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable(); List>> brokerConsumeStatsList = new ArrayList<>(); long totalDiff = 0L; long totalInflightDiff = 0L; for (String group : subscriptionGroups.keySet()) { Map> subscripTopicConsumeMap = new HashMap<>(); Set topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(group); List consumeStatsList = new ArrayList<>(); for (String topic : topics) { ConsumeStats consumeStats = new ConsumeStats(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { LOGGER.warn( "AdminBrokerProcessor#fetchAllConsumeStatsInBroker: topic config does not exist, topic={}", topic); continue; } if (isOrder && !topicConfig.isOrder()) { continue; } { SubscriptionData findSubscriptionData = this.brokerController.getConsumerManager().findSubscriptionData(group, topic); if (null == findSubscriptionData && this.brokerController.getConsumerManager().findSubscriptionDataCount(group) > 0) { LOGGER.warn( "AdminBrokerProcessor#fetchAllConsumeStatsInBroker: topic does not exist in consumer " + "group's subscription, topic={}, consumer group={}", topic, group); continue; } } for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) { MessageQueue mq = new MessageQueue(); mq.setTopic(topic); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setQueueId(i); OffsetWrapper offsetWrapper = new OffsetWrapper(); long brokerOffset; try { brokerOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset", e); } if (brokerOffset < 0) { brokerOffset = 0; } long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset( group, topic, i); if (consumerOffset < 0) consumerOffset = 0; offsetWrapper.setBrokerOffset(brokerOffset); offsetWrapper.setConsumerOffset(consumerOffset); long timeOffset = consumerOffset - 1; if (timeOffset >= 0) { long lastTimestamp = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, timeOffset); if (lastTimestamp > 0) { offsetWrapper.setLastTimestamp(lastTimestamp); } } consumeStats.getOffsetTable().put(mq, offsetWrapper); } double consumeTps = this.brokerController.getBrokerStatsManager().tpsGroupGetNums(group, topic); consumeTps += consumeStats.getConsumeTps(); consumeStats.setConsumeTps(consumeTps); totalDiff += consumeStats.computeTotalDiff(); totalInflightDiff += consumeStats.computeInflightTotalDiff(); consumeStatsList.add(consumeStats); } subscripTopicConsumeMap.put(group, consumeStatsList); brokerConsumeStatsList.add(subscripTopicConsumeMap); } ConsumeStatsList consumeStats = new ConsumeStatsList(); consumeStats.setBrokerAddr(brokerController.getBrokerAddr()); consumeStats.setConsumeStatsList(brokerConsumeStatsList); consumeStats.setTotalDiff(totalDiff); consumeStats.setTotalInflightDiff(totalInflightDiff); response.setBody(consumeStats.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private HashMap prepareRuntimeInfo() throws RemotingCommandException { HashMap runtimeInfo = this.brokerController.getMessageStore().getRuntimeInfo(); for (BrokerAttachedPlugin brokerAttachedPlugin : brokerController.getBrokerAttachedPlugins()) { if (brokerAttachedPlugin != null) { brokerAttachedPlugin.buildRuntimeInfo(runtimeInfo); } } try { this.brokerController.getScheduleMessageService().buildRunningStats(runtimeInfo); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max offset in queue", e); } runtimeInfo.put("brokerActive", String.valueOf(this.brokerController.isSpecialServiceRunning())); runtimeInfo.put("brokerVersionDesc", MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION)); runtimeInfo.put("msgPutTotalYesterdayMorning", String.valueOf(this.brokerController.getBrokerStats().getMsgPutTotalYesterdayMorning())); runtimeInfo.put("msgPutTotalTodayMorning", String.valueOf(this.brokerController.getBrokerStats().getMsgPutTotalTodayMorning())); runtimeInfo.put("msgPutTotalTodayNow", String.valueOf(this.brokerController.getBrokerStats().getMsgPutTotalTodayNow())); runtimeInfo.put("msgGetTotalYesterdayMorning", String.valueOf(this.brokerController.getBrokerStats().getMsgGetTotalYesterdayMorning())); runtimeInfo.put("msgGetTotalTodayMorning", String.valueOf(this.brokerController.getBrokerStats().getMsgGetTotalTodayMorning())); runtimeInfo.put("msgGetTotalTodayNow", String.valueOf(this.brokerController.getBrokerStats().getMsgGetTotalTodayNow())); runtimeInfo.put("dispatchBehindBytes", String.valueOf(this.brokerController.getMessageStore().dispatchBehindBytes())); runtimeInfo.put("pageCacheLockTimeMills", String.valueOf(this.brokerController.getMessageStore().lockTimeMills())); runtimeInfo.put("earliestMessageTimeStamp", String.valueOf(this.brokerController.getMessageStore().getEarliestMessageTime())); runtimeInfo.put("startAcceptSendRequestTimeStamp", String.valueOf(this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp())); if (this.brokerController.getMessageStoreConfig().isTimerWheelEnable()) { runtimeInfo.put("timerReadBehind", String.valueOf(this.brokerController.getMessageStore().getTimerMessageStore().getDequeueBehind())); runtimeInfo.put("timerOffsetBehind", String.valueOf(this.brokerController.getMessageStore().getTimerMessageStore().getEnqueueBehindMessages())); runtimeInfo.put("timerCongestNum", String.valueOf(this.brokerController.getMessageStore().getTimerMessageStore().getAllCongestNum())); runtimeInfo.put("timerEnqueueTps", String.valueOf(this.brokerController.getMessageStore().getTimerMessageStore().getEnqueueTps())); runtimeInfo.put("timerDequeueTps", String.valueOf(this.brokerController.getMessageStore().getTimerMessageStore().getDequeueTps())); } else { runtimeInfo.put("timerReadBehind", "0"); runtimeInfo.put("timerOffsetBehind", "0"); runtimeInfo.put("timerCongestNum", "0"); runtimeInfo.put("timerEnqueueTps", "0.0"); runtimeInfo.put("timerDequeueTps", "0.0"); } MessageStore messageStore = this.brokerController.getMessageStore(); runtimeInfo.put("remainTransientStoreBufferNumbs", String.valueOf(messageStore.remainTransientStoreBufferNumbs())); if (this.brokerController.getMessageStore() instanceof DefaultMessageStore && ((DefaultMessageStore) this.brokerController.getMessageStore()).isTransientStorePoolEnable()) { runtimeInfo.put("remainHowManyDataToCommit", MixAll.humanReadableByteCount(messageStore.remainHowManyDataToCommit(), false)); } runtimeInfo.put("remainHowManyDataToFlush", MixAll.humanReadableByteCount(messageStore.remainHowManyDataToFlush(), false)); java.io.File commitLogDir = new java.io.File(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); if (commitLogDir.exists()) { runtimeInfo.put("commitLogDirCapacity", String.format("Total : %s, Free : %s.", MixAll.humanReadableByteCount(commitLogDir.getTotalSpace(), false), MixAll.humanReadableByteCount(commitLogDir.getFreeSpace(), false))); } runtimeInfo.put("sendThreadPoolQueueSize", String.valueOf(this.brokerController.getSendThreadPoolQueue().size())); runtimeInfo.put("sendThreadPoolQueueCapacity", String.valueOf(this.brokerController.getBrokerConfig().getSendThreadPoolQueueCapacity())); runtimeInfo.put("pullThreadPoolQueueSize", String.valueOf(this.brokerController.getPullThreadPoolQueue().size())); runtimeInfo.put("pullThreadPoolQueueCapacity", String.valueOf(this.brokerController.getBrokerConfig().getPullThreadPoolQueueCapacity())); runtimeInfo.put("litePullThreadPoolQueueSize", String.valueOf(brokerController.getLitePullThreadPoolQueue().size())); runtimeInfo.put("litePullThreadPoolQueueCapacity", String.valueOf(this.brokerController.getBrokerConfig().getLitePullThreadPoolQueueCapacity())); runtimeInfo.put("queryThreadPoolQueueSize", String.valueOf(this.brokerController.getQueryThreadPoolQueue().size())); runtimeInfo.put("queryThreadPoolQueueCapacity", String.valueOf(this.brokerController.getBrokerConfig().getQueryThreadPoolQueueCapacity())); runtimeInfo.put("ackThreadPoolQueueSize", String.valueOf(this.brokerController.getAckThreadPoolQueue().size())); runtimeInfo.put("ackThreadPoolQueueCapacity", String.valueOf(this.brokerController.getBrokerConfig().getAckThreadPoolQueueCapacity())); runtimeInfo.put("sendThreadPoolQueueHeadWaitTimeMills", String.valueOf(this.brokerController.headSlowTimeMills4SendThreadPoolQueue())); runtimeInfo.put("pullThreadPoolQueueHeadWaitTimeMills", String.valueOf(brokerController.headSlowTimeMills4PullThreadPoolQueue())); runtimeInfo.put("queryThreadPoolQueueHeadWaitTimeMills", String.valueOf(this.brokerController.headSlowTimeMills4QueryThreadPoolQueue())); runtimeInfo.put("litePullThreadPoolQueueHeadWaitTimeMills", String.valueOf(brokerController.headSlowTimeMills4LitePullThreadPoolQueue())); runtimeInfo.put("ackThreadPoolQueueHeadWaitTimeMills", String.valueOf(brokerController.headSlowTimeMills4AckThreadPoolQueue())); runtimeInfo.put("EndTransactionQueueSize", String.valueOf(this.brokerController.getEndTransactionThreadPoolQueue().size())); runtimeInfo.put("EndTransactionThreadPoolQueueCapacity", String.valueOf(this.brokerController.getBrokerConfig().getEndTransactionPoolQueueCapacity())); return runtimeInfo; } private RemotingCommand callConsumer( final int requestCode, final RemotingCommand request, final String consumerGroup, final String clientId) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); ClientChannelInfo clientChannelInfo = this.brokerController.getConsumerManager().findChannel(consumerGroup, clientId); if (null == clientChannelInfo) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("The Consumer <%s> <%s> not online", consumerGroup, clientId)); return response; } if (clientChannelInfo.getVersion() < MQVersion.Version.V3_1_8_SNAPSHOT.ordinal()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("The Consumer <%s> Version <%s> too low to finish, please upgrade it to V3_1_8_SNAPSHOT", clientId, MQVersion.getVersionDesc(clientChannelInfo.getVersion()))); return response; } try { RemotingCommand newRequest = RemotingCommand.createRequestCommand(requestCode, null); newRequest.setExtFields(request.getExtFields()); newRequest.setBody(request.getBody()); return this.brokerController.getBroker2Client().callClient(clientChannelInfo.getChannel(), newRequest); } catch (RemotingTimeoutException e) { response.setCode(ResponseCode.CONSUME_MSG_TIMEOUT); response .setRemark(String.format("consumer <%s> <%s> Timeout: %s", consumerGroup, clientId, UtilAll.exceptionSimpleDesc(e))); return response; } catch (Exception e) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark( String.format("invoke consumer <%s> <%s> Exception: %s", consumerGroup, clientId, UtilAll.exceptionSimpleDesc(e))); return response; } } private RemotingCommand queryConsumeQueue(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { QueryConsumeQueueRequestHeader requestHeader = (QueryConsumeQueueRequestHeader) request.decodeCommandCustomHeader(QueryConsumeQueueRequestHeader.class); RemotingCommand response = RemotingCommand.createResponseCommand(null); ConsumeQueueInterface consumeQueue = this.brokerController.getMessageStore().getConsumeQueue(requestHeader.getTopic(), requestHeader.getQueueId()); if (consumeQueue == null) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("%d@%s is not exist!", requestHeader.getQueueId(), requestHeader.getTopic())); return response; } response.setCode(ResponseCode.SUCCESS); QueryConsumeQueueResponseBody body = new QueryConsumeQueueResponseBody(); body.setMaxQueueIndex(consumeQueue.getMaxOffsetInQueue()); body.setMinQueueIndex(consumeQueue.getMinOffsetInQueue()); MessageFilter messageFilter = null; if (requestHeader.getConsumerGroup() != null) { SubscriptionData subscriptionData = this.brokerController.getConsumerManager().findSubscriptionData( requestHeader.getConsumerGroup(), requestHeader.getTopic() ); body.setSubscriptionData(subscriptionData); if (subscriptionData == null) { body.setFilterData(String.format("%s@%s is not online!", requestHeader.getConsumerGroup(), requestHeader.getTopic())); } else { ConsumerFilterData filterData = this.brokerController.getConsumerFilterManager() .get(requestHeader.getTopic(), requestHeader.getConsumerGroup()); body.setFilterData(JSON.toJSONString(filterData, JSONWriter.Feature.PrettyFormat)); messageFilter = new ExpressionMessageFilter(subscriptionData, filterData, this.brokerController.getConsumerFilterManager()); } } ReferredIterator result = consumeQueue.iterateFrom(requestHeader.getIndex()); if (result == null) { response.setRemark(String.format("Index %d of %d@%s is not exist!", requestHeader.getIndex(), requestHeader.getQueueId(), requestHeader.getTopic())); return response; } try { List queues = new ArrayList<>(); while (result.hasNext()) { CqUnit cqUnit = result.next(); if (cqUnit.getQueueOffset() - requestHeader.getIndex() >= requestHeader.getCount()) { break; } ConsumeQueueData one = new ConsumeQueueData(); one.setPhysicOffset(cqUnit.getPos()); one.setPhysicSize(cqUnit.getSize()); one.setTagsCode(cqUnit.getTagsCode()); if (cqUnit.getCqExtUnit() == null && cqUnit.isTagsCodeValid()) { queues.add(one); continue; } if (cqUnit.getCqExtUnit() != null) { ConsumeQueueExt.CqExtUnit cqExtUnit = cqUnit.getCqExtUnit(); one.setExtendDataJson(JSON.toJSONString(cqExtUnit)); if (cqExtUnit.getFilterBitMap() != null) { one.setBitMap(BitsArray.create(cqExtUnit.getFilterBitMap()).toString()); } if (messageFilter != null) { one.setEval(messageFilter.isMatchedByConsumeQueue(cqExtUnit.getTagsCode(), cqExtUnit)); } } else { one.setMsg("Cq extend not exist!addr: " + one.getTagsCode()); } queues.add(one); } body.setQueueData(queues); } finally { result.release(); } response.setBody(body.encode()); return response; } private RemotingCommand resumeCheckHalfMessage(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ResumeCheckHalfMessageRequestHeader requestHeader = (ResumeCheckHalfMessageRequestHeader) request .decodeCommandCustomHeader(ResumeCheckHalfMessageRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); SelectMappedBufferResult selectMappedBufferResult = null; try { MessageId messageId = MessageDecoder.decodeMessageId(requestHeader.getMsgId()); selectMappedBufferResult = this.brokerController.getMessageStore() .selectOneMessageByOffset(messageId.getOffset()); MessageExt msg = MessageDecoder.decode(selectMappedBufferResult.getByteBuffer(), true, false); msg.putUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, String.valueOf(0)); PutMessageResult putMessageResult = this.brokerController.getMessageStore() .putMessage(toMessageExtBrokerInner(msg)); if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { LOGGER.info( "Put message back to RMQ_SYS_TRANS_HALF_TOPIC. real topic={}", msg.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC)); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { LOGGER.error("Put message back to RMQ_SYS_TRANS_HALF_TOPIC failed."); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Put message back to RMQ_SYS_TRANS_HALF_TOPIC failed."); } } catch (Exception e) { LOGGER.error("Exception was thrown when putting message back to RMQ_SYS_TRANS_HALF_TOPIC."); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Exception was thrown when putting message back to RMQ_SYS_TRANS_HALF_TOPIC."); } finally { if (selectMappedBufferResult != null) { selectMappedBufferResult.release(); } } return response; } private MessageExtBrokerInner toMessageExtBrokerInner(MessageExt msgExt) { MessageExtBrokerInner inner = new MessageExtBrokerInner(); if (brokerController.getMessageStoreConfig().isTransRocksDBEnable() && !brokerController.getMessageStoreConfig().isTransWriteOriginTransHalfEnable()) { inner.setTopic(TransactionalMessageUtil.buildHalfTopicForRocksDB()); } else { inner.setTopic(TransactionalMessageUtil.buildHalfTopic()); } inner.setBody(msgExt.getBody()); inner.setFlag(msgExt.getFlag()); MessageAccessor.setProperties(inner, msgExt.getProperties()); inner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); inner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgExt.getTags())); inner.setQueueId(0); inner.setSysFlag(msgExt.getSysFlag()); inner.setBornHost(msgExt.getBornHost()); inner.setBornTimestamp(msgExt.getBornTimestamp()); inner.setStoreHost(msgExt.getStoreHost()); inner.setReconsumeTimes(msgExt.getReconsumeTimes()); inner.setMsgId(msgExt.getMsgId()); inner.setWaitStoreMsgOK(false); return inner; } private RemotingCommand getTopicConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { GetTopicConfigRequestHeader requestHeader = (GetTopicConfigRequestHeader) request.decodeCommandCustomHeader(GetTopicConfigRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (topicConfig == null) { LOGGER.error("No topic in this broker, client: {} topic: {}", ctx.channel().remoteAddress(), requestHeader.getTopic()); //be care of the response code, should set "not-exist" explicitly response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("No topic in this broker. topic: " + requestHeader.getTopic()); return response; } TopicQueueMappingDetail topicQueueMappingDetail = null; if (Boolean.TRUE.equals(requestHeader.getLo())) { topicQueueMappingDetail = this.brokerController.getTopicQueueMappingManager().getTopicQueueMapping(requestHeader.getTopic()); } String content = JSONObject.toJSONString(new TopicConfigAndQueueMapping(topicConfig, topicQueueMappingDetail)); try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("UnsupportedEncodingException getTopicConfig: topic=" + topicConfig.getTopicName(), e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e.getMessage()); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand notifyMinBrokerIdChange(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { NotifyMinBrokerIdChangeRequestHeader requestHeader = (NotifyMinBrokerIdChangeRequestHeader) request.decodeCommandCustomHeader(NotifyMinBrokerIdChangeRequestHeader.class); RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.warn("min broker id changed, prev {}, new {}", this.brokerController.getMinBrokerIdInGroup(), requestHeader.getMinBrokerId()); this.brokerController.updateMinBroker(requestHeader.getMinBrokerId(), requestHeader.getMinBrokerAddr(), requestHeader.getOfflineBrokerAddr(), requestHeader.getHaBrokerAddr()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand updateBrokerHaInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { RemotingCommand response = RemotingCommand.createResponseCommand(ExchangeHAInfoResponseHeader.class); ExchangeHAInfoRequestHeader requestHeader = (ExchangeHAInfoRequestHeader) request.decodeCommandCustomHeader(ExchangeHAInfoRequestHeader.class); if (requestHeader.getMasterHaAddress() != null) { this.brokerController.getMessageStore().updateHaMasterAddress(requestHeader.getMasterHaAddress()); this.brokerController.getMessageStore().updateMasterAddress(requestHeader.getMasterAddress()); if (this.brokerController.getMessageStore().getMasterFlushedOffset() == 0 && this.brokerController.getMessageStoreConfig().isSyncMasterFlushOffsetWhenStartup()) { LOGGER.info("Set master flush offset in slave to {}", requestHeader.getMasterFlushOffset()); this.brokerController.getMessageStore().setMasterFlushedOffset(requestHeader.getMasterFlushOffset()); } } else if (this.brokerController.getBrokerConfig().getBrokerId() == MixAll.MASTER_ID) { final ExchangeHAInfoResponseHeader responseHeader = (ExchangeHAInfoResponseHeader) response.readCustomHeader(); responseHeader.setMasterHaAddress(this.brokerController.getHAServerAddr()); responseHeader.setMasterFlushOffset(this.brokerController.getMessageStore().getBrokerInitMaxOffset()); responseHeader.setMasterAddress(this.brokerController.getBrokerAddr()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getBrokerHaStatus(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); HARuntimeInfo runtimeInfo = this.brokerController.getMessageStore().getHARuntimeInfo(); if (runtimeInfo != null) { byte[] body = runtimeInfo.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Can not get HARuntimeInfo, may be duplicationEnable is true"); } return response; } private RemotingCommand getBrokerEpochCache(ChannelHandlerContext ctx, RemotingCommand request) { final ReplicasManager replicasManager = this.brokerController.getReplicasManager(); assert replicasManager != null; final BrokerConfig brokerConfig = this.brokerController.getBrokerConfig(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); if (!brokerConfig.isEnableControllerMode()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("this request only for controllerMode "); return response; } final EpochEntryCache entryCache = new EpochEntryCache(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerConfig.getBrokerId(), replicasManager.getEpochEntries(), this.brokerController.getMessageStore().getMaxPhyOffset()); response.setBody(entryCache.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand resetMasterFlushOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); if (this.brokerController.getBrokerConfig().getBrokerId() != MixAll.MASTER_ID) { ResetMasterFlushOffsetHeader requestHeader = (ResetMasterFlushOffsetHeader) request.decodeCommandCustomHeader(ResetMasterFlushOffsetHeader.class); if (requestHeader.getMasterFlushOffset() != null) { this.brokerController.getMessageStore().setMasterFlushedOffset(requestHeader.getMasterFlushOffset()); } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand notifyBrokerRoleChanged(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { NotifyBrokerRoleChangedRequestHeader requestHeader = (NotifyBrokerRoleChangedRequestHeader) request.decodeCommandCustomHeader(NotifyBrokerRoleChangedRequestHeader.class); SyncStateSet syncStateSetInfo = RemotingSerializable.decode(request.getBody(), SyncStateSet.class); RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("Receive notifyBrokerRoleChanged request, try to change brokerRole, request:{}", requestHeader); final ReplicasManager replicasManager = this.brokerController.getReplicasManager(); if (replicasManager != null) { try { replicasManager.changeBrokerRole(requestHeader.getMasterBrokerId(), requestHeader.getMasterAddress(), requestHeader.getMasterEpoch(), requestHeader.getSyncStateSetEpoch(), syncStateSetInfo.getSyncStateSet()); } catch (Exception e) { throw new RemotingCommandException(e.getMessage()); } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand createUser(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { RemotingCommand response = RemotingCommand.createResponseCommand(null); CreateUserRequestHeader requestHeader = request.decodeCommandCustomHeader(CreateUserRequestHeader.class); if (StringUtils.isEmpty(requestHeader.getUsername())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The username is blank"); return response; } UserInfo userInfo = RemotingSerializable.decode(request.getBody(), UserInfo.class); userInfo.setUsername(requestHeader.getUsername()); User user = UserConverter.convertUser(userInfo); if (user.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The super user can only be create by super user"); return response; } this.brokerController.getAuthenticationMetadataManager().createUser(user) .thenAccept(nil -> response.setCode(ResponseCode.SUCCESS)) .exceptionally(ex -> { LOGGER.error("create user {} error", user.getUsername(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand updateUser(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { RemotingCommand response = RemotingCommand.createResponseCommand(null); UpdateUserRequestHeader requestHeader = request.decodeCommandCustomHeader(UpdateUserRequestHeader.class); if (StringUtils.isEmpty(requestHeader.getUsername())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The username is blank"); return response; } UserInfo userInfo = RemotingSerializable.decode(request.getBody(), UserInfo.class); userInfo.setUsername(requestHeader.getUsername()); User user = UserConverter.convertUser(userInfo); if (user.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The super user can only be update by super user"); return response; } this.brokerController.getAuthenticationMetadataManager().getUser(requestHeader.getUsername()) .thenCompose(old -> { if (old == null) { throw new AuthenticationException("The user is not exist"); } if (old.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) { throw new AuthenticationException("The super user can only be update by super user"); } return this.brokerController.getAuthenticationMetadataManager().updateUser(user); }).thenAccept(nil -> response.setCode(ResponseCode.SUCCESS)) .exceptionally(ex -> { LOGGER.error("update user {} error", requestHeader.getUsername(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand deleteUser(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); DeleteUserRequestHeader requestHeader = request.decodeCommandCustomHeader(DeleteUserRequestHeader.class); this.brokerController.getAuthenticationMetadataManager().getUser(requestHeader.getUsername()) .thenCompose(user -> { if (user == null) { return CompletableFuture.completedFuture(null); } if (user.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) { throw new AuthenticationException("The super user can only be update by super user"); } return this.brokerController.getAuthenticationMetadataManager().deleteUser(requestHeader.getUsername()); }).thenAccept(nil -> response.setCode(ResponseCode.SUCCESS)) .exceptionally(ex -> { LOGGER.error("delete user {} error", requestHeader.getUsername(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand getUser(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); GetUserRequestHeader requestHeader = request.decodeCommandCustomHeader(GetUserRequestHeader.class); if (StringUtils.isBlank(requestHeader.getUsername())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("The username is blank"); return response; } this.brokerController.getAuthenticationMetadataManager().getUser(requestHeader.getUsername()) .thenAccept(user -> { response.setCode(ResponseCode.SUCCESS); if (user != null) { UserInfo userInfo = UserConverter.convertUser(user); response.setBody(JSON.toJSONString(userInfo).getBytes(StandardCharsets.UTF_8)); } }) .exceptionally(ex -> { LOGGER.error("get user {} error", requestHeader.getUsername(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand listUser(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); ListUsersRequestHeader requestHeader = request.decodeCommandCustomHeader(ListUsersRequestHeader.class); this.brokerController.getAuthenticationMetadataManager().listUser(requestHeader.getFilter()) .thenAccept(users -> { response.setCode(ResponseCode.SUCCESS); if (CollectionUtils.isNotEmpty(users)) { List userInfos = UserConverter.convertUsers(users); response.setBody(JSON.toJSONString(userInfos).getBytes(StandardCharsets.UTF_8)); } }) .exceptionally(ex -> { LOGGER.error("list user by {} error", requestHeader.getFilter(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand createAcl(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { RemotingCommand response = RemotingCommand.createResponseCommand(null); CreateAclRequestHeader requestHeader = request.decodeCommandCustomHeader(CreateAclRequestHeader.class); Subject subject = Subject.of(requestHeader.getSubject()); AclInfo aclInfo = RemotingSerializable.decode(request.getBody(), AclInfo.class); if (aclInfo == null || CollectionUtils.isEmpty(aclInfo.getPolicies())) { throw new AuthorizationException("The body of acl is null"); } Acl acl = AclConverter.convertAcl(aclInfo); if (acl != null && acl.getSubject() == null) { acl.setSubject(subject); } this.brokerController.getAuthorizationMetadataManager().createAcl(acl) .thenAccept(nil -> response.setCode(ResponseCode.SUCCESS)) .exceptionally(ex -> { LOGGER.error("create acl for {} error", requestHeader.getSubject(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand updateAcl(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { RemotingCommand response = RemotingCommand.createResponseCommand(null); UpdateAclRequestHeader requestHeader = request.decodeCommandCustomHeader(UpdateAclRequestHeader.class); Subject subject = Subject.of(requestHeader.getSubject()); AclInfo aclInfo = RemotingSerializable.decode(request.getBody(), AclInfo.class); if (aclInfo == null || CollectionUtils.isEmpty(aclInfo.getPolicies())) { throw new AuthorizationException("The body of acl is null"); } Acl acl = AclConverter.convertAcl(aclInfo); if (acl != null && acl.getSubject() == null) { acl.setSubject(subject); } this.brokerController.getAuthorizationMetadataManager().updateAcl(acl) .thenAccept(nil -> response.setCode(ResponseCode.SUCCESS)) .exceptionally(ex -> { LOGGER.error("update acl for {} error", requestHeader.getSubject(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand deleteAcl(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); DeleteAclRequestHeader requestHeader = request.decodeCommandCustomHeader(DeleteAclRequestHeader.class); Subject subject = Subject.of(requestHeader.getSubject()); PolicyType policyType = PolicyType.getByName(requestHeader.getPolicyType()); Resource resource = Resource.of(requestHeader.getResource()); this.brokerController.getAuthorizationMetadataManager().deleteAcl(subject, policyType, resource) .thenAccept(nil -> { response.setCode(ResponseCode.SUCCESS); }) .exceptionally(ex -> { LOGGER.error("delete acl for {} error", requestHeader.getSubject(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand getAcl(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); GetAclRequestHeader requestHeader = request.decodeCommandCustomHeader(GetAclRequestHeader.class); Subject subject = Subject.of(requestHeader.getSubject()); this.brokerController.getAuthorizationMetadataManager().getAcl(subject) .thenAccept(acl -> { response.setCode(ResponseCode.SUCCESS); if (acl != null) { AclInfo aclInfo = AclConverter.convertAcl(acl); String body = JSON.toJSONString(aclInfo); response.setBody(body.getBytes(StandardCharsets.UTF_8)); } }) .exceptionally(ex -> { LOGGER.error("get acl for {} error", requestHeader.getSubject(), ex); return handleAuthException(response, ex); }) .join(); return response; } private RemotingCommand listAcl(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); ListAclsRequestHeader requestHeader = request.decodeCommandCustomHeader(ListAclsRequestHeader.class); this.brokerController.getAuthorizationMetadataManager() .listAcl(requestHeader.getSubjectFilter(), requestHeader.getResourceFilter()) .thenAccept(acls -> { response.setCode(ResponseCode.SUCCESS); if (CollectionUtils.isNotEmpty(acls)) { List aclInfos = AclConverter.convertAcls(acls); String body = JSON.toJSONString(aclInfos); response.setBody(body.getBytes(StandardCharsets.UTF_8)); } }) .exceptionally(ex -> { LOGGER.error("list acl error, subjectFilter:{}, resourceFilter:{}", requestHeader.getSubjectFilter(), requestHeader.getResourceFilter(), ex); return handleAuthException(response, ex); }) .join(); return response; } private boolean isNotSuperUserLogin(RemotingCommand request) { String accessKey = request.getExtFields().get("AccessKey"); // if accessKey is null, it may be authentication is not enabled. if (StringUtils.isEmpty(accessKey)) { return false; } return !this.brokerController.getAuthenticationMetadataManager() .isSuperUser(accessKey).join(); } private Void handleAuthException(RemotingCommand response, Throwable ex) { Throwable throwable = ExceptionUtils.getRealException(ex); if (throwable instanceof AuthenticationException || throwable instanceof AuthorizationException) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(throwable.getMessage()); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("An system error occurred, please try again later."); LOGGER.error("An system error occurred when processing auth admin request.", ex); } return null; } private boolean validateSlave(RemotingCommand response) { if (this.brokerController.getMessageStoreConfig().getBrokerRole().equals(BrokerRole.SLAVE)) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Can't modify topic or subscription group from slave broker, " + "please execute it from master broker."); return true; } return false; } private boolean validateBlackListConfigExist(Properties properties) { for (String blackConfig : configBlackList) { if (properties.containsKey(blackConfig)) { return true; } } return false; } private CheckRocksdbCqWriteResult doCheckRocksdbCqWriteProgress(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { CheckRocksdbCqWriteProgressRequestHeader requestHeader = request.decodeCommandCustomHeader(CheckRocksdbCqWriteProgressRequestHeader.class); MessageStore messageStore = brokerController.getMessageStore(); DefaultMessageStore defaultMessageStore; if (messageStore instanceof AbstractPluginMessageStore) { defaultMessageStore = (DefaultMessageStore) ((AbstractPluginMessageStore) messageStore).getNext(); } else { defaultMessageStore = (DefaultMessageStore) messageStore; } ConsumeQueueStoreInterface consumeQueueStore = defaultMessageStore.getQueueStore(); if (!(consumeQueueStore instanceof CombineConsumeQueueStore)) { CheckRocksdbCqWriteResult result = new CheckRocksdbCqWriteResult(); result.setCheckResult("It is not CombineConsumeQueueStore, no need check"); result.setCheckStatus(CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue()); return result; } return ((CombineConsumeQueueStore) consumeQueueStore). doCheckCqWriteProgress(requestHeader.getTopic(), requestHeader.getCheckStoreTime(), StoreType.DEFAULT, StoreType.DEFAULT_ROCKSDB); } private RemotingCommand transferPopToFsStore(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); try { if (brokerController.getPopConsumerService() != null) { brokerController.getPopConsumerService().transferToFsStore(); } response.setCode(ResponseCode.SUCCESS); } catch (Exception e) { LOGGER.error("PopConsumerStore transfer from kvStore to fsStore finish [{}]", request, e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); } return response; } private synchronized RemotingCommand switchTimerEngine(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); if (!this.brokerController.getMessageStoreConfig().isTimerWheelEnable()) { LOGGER.info("switchTimerEngine error, broker timerWheelEnable is false"); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("broker timerWheelEnable is false"); return response; } if (null == request.getExtFields()) { LOGGER.info("switchTimerEngine extFields is null"); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("param error, extFields is null"); return response; } String engineType = request.getExtFields().get(TIMER_ENGINE_TYPE); if (StringUtils.isEmpty(engineType) || !MessageConst.TIMER_ENGINE_ROCKSDB_TIMELINE.equals(engineType) && !MessageConst.TIMER_ENGINE_FILE_TIME_WHEEL.equals(engineType)) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("param error"); return response; } try { Properties properties = new Properties(); boolean result = false; if (MessageConst.TIMER_ENGINE_ROCKSDB_TIMELINE.equals(engineType)) { if (this.brokerController.getTimerMessageRocksDBStore() == null) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("timerRocksDBEnable must be configured true when broker start"); return response; } result = this.brokerController.getTimerMessageRocksDBStore().restart(); if (result) { properties.put("timerStopEnqueue", Boolean.TRUE.toString()); properties.put("timerRocksDBEnable", Boolean.TRUE.toString()); properties.put("timerRocksDBStopScan", Boolean.FALSE.toString()); } } else { result = this.brokerController.getTimerMessageStore().restart(); if (result) { properties.put("timerRocksDBStopScan", Boolean.TRUE.toString()); properties.put("timerStopEnqueue", Boolean.FALSE.toString()); } } if (result) { this.brokerController.getConfiguration().update(properties); response.setCode(ResponseCode.SUCCESS); response.setRemark("switch timer engine success"); LOGGER.info("switchTimerEngine success"); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("switch timer engine error"); LOGGER.info("switchTimerEngine error"); } } catch (Exception e) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("switch timer engine error"); LOGGER.error("switchTimerEngine error : {}", e.getMessage()); } return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.PopConsumerLockService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; public class ChangeInvisibleTimeProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final BrokerController brokerController; private final String reviveTopic; public ChangeInvisibleTimeProcessor(final BrokerController brokerController) { this.brokerController = brokerController; this.reviveTopic = PopAckConstants.buildClusterReviveTopic(this.brokerController.getBrokerConfig().getBrokerClusterName()); } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { return this.processRequest(ctx.channel(), request, true); } @Override public boolean rejectRequest() { return false; } private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { CompletableFuture responseFuture = processRequestAsync(channel, request, brokerAllowSuspend); if (brokerController.getBrokerConfig().isAppendCkAsync() && brokerController.getBrokerConfig().isAppendAckAsync()) { responseFuture.thenAccept(response -> doResponse(channel, request, response)).exceptionally(throwable -> { RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); response.setCode(ResponseCode.SYSTEM_ERROR); response.setOpaque(request.getOpaque()); doResponse(channel, request, response); POP_LOGGER.error("append checkpoint or ack origin failed", throwable); return null; }); } else { RemotingCommand response; try { response = responseFuture.get(3000, TimeUnit.MILLISECONDS); } catch (Exception e) { response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); response.setCode(ResponseCode.SYSTEM_ERROR); response.setOpaque(request.getOpaque()); POP_LOGGER.error("append checkpoint or ack origin failed", e); } return response; } return null; } public CompletableFuture processRequestAsync(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { final ChangeInvisibleTimeRequestHeader requestHeader = (ChangeInvisibleTimeRequestHeader) request.decodeCommandCustomHeader(ChangeInvisibleTimeRequestHeader.class); RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); final ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.readCustomHeader(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return CompletableFuture.completedFuture(response); } if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums() || requestHeader.getQueueId() < 0) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(errorInfo); return CompletableFuture.completedFuture(response); } CompletableFuture future = processChangeInvisibleTimeForLite(requestHeader, response, responseHeader); if (future != null) { return future; } long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); long maxOffset; try { maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to get max consume offset", e); } if (requestHeader.getOffset() < minOffset || requestHeader.getOffset() >= maxOffset) { response.setCode(ResponseCode.NO_MESSAGE); return CompletableFuture.completedFuture(response); } String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { if (ExtraInfoUtil.isOrder(extraInfo)) { return this.processChangeInvisibleTimeForOrderNew( requestHeader, extraInfo, response, responseHeader); } try { long current = System.currentTimeMillis(); brokerController.getPopConsumerService().changeInvisibilityDuration( ExtraInfoUtil.getPopTime(extraInfo), ExtraInfoUtil.getInvisibleTime(extraInfo), current, requestHeader.getInvisibleTime(), requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getOffset(), requestHeader.isSuspend()); responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); responseHeader.setPopTime(current); responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo)); } catch (Exception e) { response.setCode(ResponseCode.SYSTEM_ERROR); } return CompletableFuture.completedFuture(response); } if (ExtraInfoUtil.isOrder(extraInfo)) { return CompletableFuture.completedFuture( processChangeInvisibleTimeForOrder(requestHeader, extraInfo, response, responseHeader)); } // add new ck long now = System.currentTimeMillis(); CompletableFuture futureResult = appendCheckPointThenAckOrigin(requestHeader, ExtraInfoUtil.getReviveQid(extraInfo), requestHeader.getQueueId(), requestHeader.getOffset(), now, extraInfo); return futureResult.thenCompose(result -> { if (result) { responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); responseHeader.setPopTime(now); responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo)); } else { response.setCode(ResponseCode.SYSTEM_ERROR); } return CompletableFuture.completedFuture(response); }); } @SuppressWarnings({"StatementWithEmptyBody", "DuplicatedCode"}) public CompletableFuture processChangeInvisibleTimeForOrderNew( ChangeInvisibleTimeRequestHeader requestHeader, String[] extraInfo, RemotingCommand response, ChangeInvisibleTimeResponseHeader responseHeader) { String groupId = requestHeader.getConsumerGroup(); String topicId = requestHeader.getTopic(); Integer queueId = requestHeader.getQueueId(); long popTime = ExtraInfoUtil.getPopTime(extraInfo); PopConsumerLockService consumerLockService = this.brokerController.getPopConsumerService().getConsumerLockService(); ConsumerOffsetManager consumerOffsetManager = this.brokerController.getConsumerOffsetManager(); ConsumerOrderInfoManager consumerOrderInfoManager = brokerController.getConsumerOrderInfoManager(); long oldOffset = consumerOffsetManager.queryOffset(groupId, topicId, queueId); if (requestHeader.getOffset() < oldOffset) { return CompletableFuture.completedFuture(response); } while (!consumerLockService.tryLock(groupId, topicId)) { } try { // double check oldOffset = consumerOffsetManager.queryOffset(groupId, topicId, queueId); if (requestHeader.getOffset() < oldOffset) { return CompletableFuture.completedFuture(response); } long visibilityTimeout = System.currentTimeMillis() + requestHeader.getInvisibleTime(); consumerOrderInfoManager.updateNextVisibleTime( topicId, groupId, queueId, requestHeader.getOffset(), popTime, visibilityTimeout); responseHeader.setInvisibleTime(visibilityTimeout - popTime); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo)); } finally { consumerLockService.unlock(groupId, topicId); } return CompletableFuture.completedFuture(response); } protected RemotingCommand processChangeInvisibleTimeForOrder(ChangeInvisibleTimeRequestHeader requestHeader, String[] extraInfo, RemotingCommand response, ChangeInvisibleTimeResponseHeader responseHeader) { long popTime = ExtraInfoUtil.getPopTime(extraInfo); long oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); if (requestHeader.getOffset() < oldOffset) { return response; } while (!this.brokerController.getPopMessageProcessor().getQueueLockManager().tryLock(requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueId())) { } try { oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); if (requestHeader.getOffset() < oldOffset) { return response; } long nextVisibleTime = System.currentTimeMillis() + requestHeader.getInvisibleTime(); this.brokerController.getConsumerOrderInfoManager().updateNextVisibleTime( requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueId(), requestHeader.getOffset(), popTime, nextVisibleTime); responseHeader.setInvisibleTime(nextVisibleTime - popTime); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo)); } finally { this.brokerController.getPopMessageProcessor().getQueueLockManager().unLock(requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueId()); } return response; } private CompletableFuture ackOrigin(final ChangeInvisibleTimeRequestHeader requestHeader, String[] extraInfo) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); AckMsg ackMsg = new AckMsg(); ackMsg.setAckOffset(requestHeader.getOffset()); ackMsg.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfo)); ackMsg.setConsumerGroup(requestHeader.getConsumerGroup()); ackMsg.setTopic(requestHeader.getTopic()); ackMsg.setQueueId(requestHeader.getQueueId()); ackMsg.setPopTime(ExtraInfoUtil.getPopTime(extraInfo)); ackMsg.setBrokerName(ExtraInfoUtil.getBrokerName(extraInfo)); int rqId = ExtraInfoUtil.getReviveQid(extraInfo); this.brokerController.getBrokerStatsManager().incBrokerAckNums(1); this.brokerController.getBrokerStatsManager().incGroupAckNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), 1); if (brokerController.getPopMessageProcessor().getPopBufferMergeService().addAk(rqId, ackMsg)) { return CompletableFuture.completedFuture(true); } msgInner.setTopic(reviveTopic); msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(rqId); msgInner.setTags(PopAckConstants.ACK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(this.brokerController.getStoreHost()); msgInner.setStoreHost(this.brokerController.getStoreHost()); msgInner.setDeliverTimeMs(ExtraInfoUtil.getPopTime(extraInfo) + ExtraInfoUtil.getInvisibleTime(extraInfo)); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenCompose(putMessageResult -> { if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { POP_LOGGER.error("change Invisible, put ack msg fail: {}, {}", ackMsg, putMessageResult); } brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus()); return CompletableFuture.completedFuture(true); }).exceptionally(e -> { POP_LOGGER.error("change Invisible, put ack msg error: {}, {}", requestHeader.getExtraInfo(), e.getMessage()); return false; }); } private CompletableFuture appendCheckPointThenAckOrigin( final ChangeInvisibleTimeRequestHeader requestHeader, int reviveQid, int queueId, long offset, long popTime, String[] extraInfo) { // add check point msg to revive log MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(reviveTopic); PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); ck.setNum((byte) 1); ck.setPopTime(popTime); ck.setInvisibleTime(requestHeader.getInvisibleTime()); ck.setStartOffset(offset); ck.setCId(requestHeader.getConsumerGroup()); ck.setTopic(requestHeader.getTopic()); ck.setQueueId(queueId); ck.addDiff(0); ck.setBrokerName(ExtraInfoUtil.getBrokerName(extraInfo)); ck.setSuspend(requestHeader.isSuspend()); msgInner.setBody(JSON.toJSONString(ck).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(reviveQid); msgInner.setTags(PopAckConstants.CK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(this.brokerController.getStoreHost()); msgInner.setStoreHost(this.brokerController.getStoreHost()); msgInner.setDeliverTimeMs(ck.getReviveTime() - PopAckConstants.ackTimeInterval); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genCkUniqueId(ck)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenCompose(putMessageResult -> { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("change Invisible, appendCheckPoint, topic {}, queueId {},reviveId {}, cid {}, startOffset {}, rt {}, result {}", requestHeader.getTopic(), queueId, reviveQid, requestHeader.getConsumerGroup(), offset, ck.getReviveTime(), putMessageResult); } if (putMessageResult != null) { brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveCkPutCount(ck, putMessageResult.getPutMessageStatus()); if (putMessageResult.isOk()) { this.brokerController.getBrokerStatsManager().incBrokerCkNums(1); this.brokerController.getBrokerStatsManager().incGroupCkNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), 1); } } if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { POP_LOGGER.error("change invisible, put new ck error: {}", putMessageResult); return CompletableFuture.completedFuture(false); } else { return ackOrigin(requestHeader, extraInfo); } }).exceptionally(throwable -> { POP_LOGGER.error("change invisible, put new ck error", throwable); return null; }); } protected CompletableFuture processChangeInvisibleTimeForLite( ChangeInvisibleTimeRequestHeader requestHeader, RemotingCommand response, ChangeInvisibleTimeResponseHeader responseHeader) { if (StringUtils.isBlank(requestHeader.getLiteTopic())) { return null; } String lmqName = LiteUtil.toLmqName(requestHeader.getTopic(), requestHeader.getLiteTopic()); long maxOffset = this.brokerController.getLiteLifecycleManager().getMaxOffsetInQueue(lmqName); if (requestHeader.getOffset() > maxOffset) { POP_LOGGER.warn("process lite offset illegal, {}, {}, {}", lmqName, requestHeader.getOffset(), maxOffset); response.setCode(ResponseCode.NO_MESSAGE); return CompletableFuture.completedFuture(response); } String group = requestHeader.getConsumerGroup(); String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); long popTime = ExtraInfoUtil.getPopTime(extraInfo); ConsumerOffsetManager consumerOffsetManager = this.brokerController.getConsumerOffsetManager(); ConsumerOrderInfoManager consumerOrderInfoManager = brokerController.getPopLiteMessageProcessor().getConsumerOrderInfoManager(); PopConsumerLockService consumerLockService = this.brokerController.getPopLiteMessageProcessor().getLockService(); long oldOffset = consumerOffsetManager.queryOffset(group, lmqName, 0); if (requestHeader.getOffset() < oldOffset) { return CompletableFuture.completedFuture(response); } while (!consumerLockService.tryLock(group, lmqName)) { } try { oldOffset = consumerOffsetManager.queryOffset(group, lmqName, 0); if (requestHeader.getOffset() < oldOffset) { return CompletableFuture.completedFuture(response); } long visibilityTimeout = System.currentTimeMillis() + requestHeader.getInvisibleTime(); consumerOrderInfoManager.updateNextVisibleTime( lmqName, group, 0, requestHeader.getOffset(), popTime, visibilityTimeout); responseHeader.setInvisibleTime(visibilityTimeout - popTime); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo)); } finally { consumerLockService.unlock(group, lmqName); } return CompletableFuture.completedFuture(response); } protected void doResponse(Channel channel, RemotingCommand request, final RemotingCommand response) { NettyRemotingAbstract.writeResponse(channel, request, response, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager()); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/ClientManageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.sysflag.TopicSysFlag; import org.apache.rocketmq.filter.FilterFactory; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.CheckClientRequestBody; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.ProducerData; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class ClientManageProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private final ConcurrentMap consumerGroupHeartbeatTable = new ConcurrentHashMap<>(); public ClientManageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { switch (request.getCode()) { case RequestCode.HEART_BEAT: return this.heartBeat(ctx, request); case RequestCode.UNREGISTER_CLIENT: return this.unregisterClient(ctx, request); case RequestCode.CHECK_CLIENT_CONFIG: return this.checkClientConfig(ctx, request); default: break; } return null; } @Override public boolean rejectRequest() { return false; } public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(null); HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( ctx.channel(), heartbeatData.getClientID(), request.getLanguage(), request.getVersion() ); int heartbeatFingerprint = heartbeatData.getHeartbeatFingerprint(); if (heartbeatFingerprint != 0) { return heartBeatV2(ctx, heartbeatData, clientChannelInfo, response); } for (ConsumerData consumerData : heartbeatData.getConsumerDataSet()) { //Reject the PullConsumer if (brokerController.getBrokerConfig().isRejectPullConsumerEnable()) { if (ConsumeType.CONSUME_ACTIVELY == consumerData.getConsumeType()) { continue; } } consumerGroupHeartbeatTable.put(consumerData.getGroupName(), heartbeatFingerprint); boolean hasOrderTopicSub = false; for (final SubscriptionData subscriptionData : consumerData.getSubscriptionDataSet()) { if (this.brokerController.getTopicConfigManager().isOrderTopic(subscriptionData.getTopic())) { hasOrderTopicSub = true; break; } } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager() .findSubscriptionGroupConfig(consumerData.getGroupName()); boolean isNotifyConsumerIdsChangedEnable = true; if (null == subscriptionGroupConfig) { continue; } isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable(); int topicSysFlag = 0; if (consumerData.isUnitMode()) { topicSysFlag = TopicSysFlag.buildSysFlag(false, true); } String newTopic = MixAll.getRetryTopic(consumerData.getGroupName()); this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic, subscriptionGroupConfig.getRetryQueueNums(), PermName.PERM_WRITE | PermName.PERM_READ, hasOrderTopicSub, topicSysFlag); boolean changed = this.brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientChannelInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), isNotifyConsumerIdsChangedEnable ); if (changed) { LOGGER.info("ClientManageProcessor: registerConsumer info changed, SDK address={}, consumerData={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), consumerData.toString()); } } for (ProducerData data : heartbeatData.getProducerDataSet()) { this.brokerController.getProducerManager().registerProducer(data.getGroupName(), clientChannelInfo); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); response.addExtField(MixAll.IS_SUPPORT_HEART_BEAT_V2, Boolean.TRUE.toString()); response.addExtField(MixAll.IS_SUB_CHANGE, Boolean.TRUE.toString()); return response; } private RemotingCommand heartBeatV2(ChannelHandlerContext ctx, HeartbeatData heartbeatData, ClientChannelInfo clientChannelInfo, RemotingCommand response) { boolean isSubChange = false; for (ConsumerData consumerData : heartbeatData.getConsumerDataSet()) { //Reject the PullConsumer if (brokerController.getBrokerConfig().isRejectPullConsumerEnable()) { if (ConsumeType.CONSUME_ACTIVELY == consumerData.getConsumeType()) { continue; } } if (null != consumerGroupHeartbeatTable.get(consumerData.getGroupName()) && consumerGroupHeartbeatTable.get(consumerData.getGroupName()) != heartbeatData.getHeartbeatFingerprint()) { isSubChange = true; } consumerGroupHeartbeatTable.put(consumerData.getGroupName(), heartbeatData.getHeartbeatFingerprint()); boolean hasOrderTopicSub = false; for (final SubscriptionData subscriptionData : consumerData.getSubscriptionDataSet()) { if (this.brokerController.getTopicConfigManager().isOrderTopic(subscriptionData.getTopic())) { hasOrderTopicSub = true; break; } } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(consumerData.getGroupName()); boolean isNotifyConsumerIdsChangedEnable = true; if (null == subscriptionGroupConfig) { continue; } isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable(); int topicSysFlag = 0; if (consumerData.isUnitMode()) { topicSysFlag = TopicSysFlag.buildSysFlag(false, true); } String newTopic = MixAll.getRetryTopic(consumerData.getGroupName()); this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic, subscriptionGroupConfig.getRetryQueueNums(), PermName.PERM_WRITE | PermName.PERM_READ, hasOrderTopicSub, topicSysFlag); boolean changed = false; if (heartbeatData.isWithoutSub()) { changed = this.brokerController.getConsumerManager().registerConsumerWithoutSub(consumerData.getGroupName(), clientChannelInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), isNotifyConsumerIdsChangedEnable); } else { changed = this.brokerController.getConsumerManager().registerConsumer(consumerData.getGroupName(), clientChannelInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), isNotifyConsumerIdsChangedEnable); } if (changed) { LOGGER.info("heartBeatV2 ClientManageProcessor: registerConsumer info changed, SDK address={}, consumerData={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), consumerData.toString()); } } for (ProducerData data : heartbeatData.getProducerDataSet()) { this.brokerController.getProducerManager().registerProducer(data.getGroupName(), clientChannelInfo); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); response.addExtField(MixAll.IS_SUPPORT_HEART_BEAT_V2, Boolean.TRUE.toString()); response.addExtField(MixAll.IS_SUB_CHANGE, Boolean.valueOf(isSubChange).toString()); return response; } public RemotingCommand unregisterClient(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(UnregisterClientResponseHeader.class); final UnregisterClientRequestHeader requestHeader = (UnregisterClientRequestHeader) request .decodeCommandCustomHeader(UnregisterClientRequestHeader.class); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( ctx.channel(), requestHeader.getClientID(), request.getLanguage(), request.getVersion()); { final String group = requestHeader.getProducerGroup(); if (group != null) { this.brokerController.getProducerManager().unregisterProducer(group, clientChannelInfo); } } { final String group = requestHeader.getConsumerGroup(); if (group != null) { SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); boolean isNotifyConsumerIdsChangedEnable = true; if (null != subscriptionGroupConfig) { isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable(); } this.brokerController.getConsumerManager().unregisterConsumer(group, clientChannelInfo, isNotifyConsumerIdsChangedEnable); } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand checkClientConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); CheckClientRequestBody requestBody = CheckClientRequestBody.decode(request.getBody(), CheckClientRequestBody.class); if (requestBody != null && requestBody.getSubscriptionData() != null) { SubscriptionData subscriptionData = requestBody.getSubscriptionData(); if (ExpressionType.isTagType(subscriptionData.getExpressionType())) { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } if (!this.brokerController.getBrokerConfig().isEnablePropertyFilter()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The broker does not support consumer to filter message by " + subscriptionData.getExpressionType()); return response; } try { FilterFactory.INSTANCE.get(subscriptionData.getExpressionType()).compile(subscriptionData.getSubString()); } catch (Exception e) { LOGGER.warn("Client {}@{} filter message, but failed to compile expression! sub={}, error={}", requestBody.getClientId(), requestBody.getGroup(), requestBody.getSubscriptionData(), e.getMessage()); response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED); response.setRemark(e.getMessage()); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import java.util.List; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.rpc.RpcClientUtils; import org.apache.rocketmq.remoting.rpc.RpcRequest; import org.apache.rocketmq.remoting.rpc.RpcResponse; import static org.apache.rocketmq.remoting.protocol.RemotingCommand.buildErrorResponse; public class ConsumerManageProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; public ConsumerManageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { switch (request.getCode()) { case RequestCode.GET_CONSUMER_LIST_BY_GROUP: return this.getConsumerListByGroup(ctx, request); case RequestCode.UPDATE_CONSUMER_OFFSET: return this.updateConsumerOffset(ctx, request); case RequestCode.QUERY_CONSUMER_OFFSET: return this.queryConsumerOffset(ctx, request); default: break; } return null; } @Override public boolean rejectRequest() { return false; } public RemotingCommand getConsumerListByGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(GetConsumerListByGroupResponseHeader.class); final GetConsumerListByGroupRequestHeader requestHeader = (GetConsumerListByGroupRequestHeader) request .decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class); ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo( requestHeader.getConsumerGroup()); if (consumerGroupInfo != null) { List clientIds = consumerGroupInfo.getAllClientId(); if (!clientIds.isEmpty()) { GetConsumerListByGroupResponseBody body = new GetConsumerListByGroupResponseBody(); body.setConsumerIdList(clientIds); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } else { LOGGER.warn("getAllClientId failed, {} {}", requestHeader.getConsumerGroup(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); } } else { LOGGER.warn("getConsumerGroupInfo failed, {} {}", requestHeader.getConsumerGroup(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); } response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("no consumer for this group, " + requestHeader.getConsumerGroup()); return response; } public RemotingCommand rewriteRequestForStaticTopic(final UpdateConsumerOffsetRequestHeader requestHeader, final TopicQueueMappingContext mappingContext) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", requestHeader.getTopic(), requestHeader.getQueueId(), mappingDetail.getBname())); } Long globalOffset = requestHeader.getCommitOffset(); LogicQueueMappingItem mappingItem = TopicQueueMappingUtils.findLogicQueueMappingItem(mappingContext.getMappingItemList(), globalOffset, true); requestHeader.setQueueId(mappingItem.getQueueId()); requestHeader.setLo(false); requestHeader.setBrokerName(mappingItem.getBname()); requestHeader.setCommitOffset(mappingItem.computePhysicalQueueOffset(globalOffset)); //leader, let it go, do not need to rewrite the response if (mappingDetail.getBname().equals(mappingItem.getBname())) { return null; } RpcRequest rpcRequest = new RpcRequest(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader, null); RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } return RpcClientUtils.createCommandForRpcResponse(rpcResponse); } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } private RemotingCommand updateConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(UpdateConsumerOffsetResponseHeader.class); final UpdateConsumerOffsetRequestHeader requestHeader = (UpdateConsumerOffsetRequestHeader) request.decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class); TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader); RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } String topic = requestHeader.getTopic(); String group = requestHeader.getConsumerGroup(); Integer queueId = requestHeader.getQueueId(); Long offset = requestHeader.getCommitOffset(); if (!this.brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(group)) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark("Group " + group + " not exist!"); return response; } if (!this.brokerController.getTopicConfigManager().containsTopic(requestHeader.getTopic())) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("Topic " + topic + " not exist!"); return response; } if (queueId == null) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("QueueId is null, topic is " + topic); return response; } if (offset == null) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("Offset is null, topic is " + topic); return response; } ConsumerOffsetManager consumerOffsetManager = brokerController.getConsumerOffsetManager(); if (this.brokerController.getBrokerConfig().isUseServerSideResetOffset()) { // Note, ignoring this update offset request if (consumerOffsetManager.hasOffsetReset(topic, group, queueId)) { response.setCode(ResponseCode.SUCCESS); response.setRemark("Offset has been previously reset"); LOGGER.info("Update consumer offset is rejected because of previous offset-reset. Group={}, " + "Topic={}, QueueId={}, Offset={}", group, topic, queueId, offset); return response; } } this.brokerController.getConsumerOffsetManager().commitOffset( RemotingHelper.parseChannelRemoteAddr(ctx.channel()), group, topic, queueId, offset); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand rewriteRequestForStaticTopic(QueryConsumerOffsetRequestHeader requestHeader, TopicQueueMappingContext mappingContext) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", requestHeader.getTopic(), requestHeader.getQueueId(), mappingDetail.getBname())); } List mappingItemList = mappingContext.getMappingItemList(); if (mappingItemList.size() == 1 && mappingItemList.get(0).getLogicOffset() == 0) { //as physical, just let it go mappingContext.setCurrentItem(mappingItemList.get(0)); requestHeader.setQueueId(mappingContext.getLeaderItem().getQueueId()); return null; } //double read check List itemList = mappingContext.getMappingItemList(); //by default, it is -1 long offset = -1; //double read, first from leader, then from second leader for (int i = itemList.size() - 1; i >= 0; i--) { LogicQueueMappingItem mappingItem = itemList.get(i); mappingContext.setCurrentItem(mappingItem); if (mappingItem.getBname().equals(mappingDetail.getBname())) { offset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getConsumerGroup(), requestHeader.getTopic(), mappingItem.getQueueId()); if (offset >= 0) { break; } else { //not found continue; } } else { //maybe we need to reconstruct an object requestHeader.setBrokerName(mappingItem.getBname()); requestHeader.setQueueId(mappingItem.getQueueId()); requestHeader.setLo(false); requestHeader.setSetZeroIfNotFound(false); RpcRequest rpcRequest = new RpcRequest(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader, null); RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } if (rpcResponse.getCode() == ResponseCode.SUCCESS) { offset = ((QueryConsumerOffsetResponseHeader) rpcResponse.getHeader()).getOffset(); break; } else if (rpcResponse.getCode() == ResponseCode.QUERY_NOT_FOUND) { continue; } else { //this should not happen throw new RuntimeException("Unknown response code " + rpcResponse.getCode()); } } } final RemotingCommand response = RemotingCommand.createResponseCommand(QueryConsumerOffsetResponseHeader.class); final QueryConsumerOffsetResponseHeader responseHeader = (QueryConsumerOffsetResponseHeader) response.readCustomHeader(); if (offset >= 0) { responseHeader.setOffset(offset); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.QUERY_NOT_FOUND); response.setRemark("Not found, maybe this group consumer boot first"); } RemotingCommand rewriteResponseResult = rewriteResponseForStaticTopic(requestHeader, responseHeader, mappingContext, response.getCode()); if (rewriteResponseResult != null) { return rewriteResponseResult; } return response; } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } public RemotingCommand rewriteResponseForStaticTopic(final QueryConsumerOffsetRequestHeader requestHeader, final QueryConsumerOffsetResponseHeader responseHeader, final TopicQueueMappingContext mappingContext, final int code) { try { if (mappingContext.getMappingDetail() == null) { return null; } if (code != ResponseCode.SUCCESS) { return null; } LogicQueueMappingItem item = mappingContext.getCurrentItem(); responseHeader.setOffset(item.computeStaticQueueOffsetStrictly(responseHeader.getOffset())); //no need to construct new object return null; } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } private RemotingCommand queryConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(QueryConsumerOffsetResponseHeader.class); final QueryConsumerOffsetResponseHeader responseHeader = (QueryConsumerOffsetResponseHeader) response.readCustomHeader(); final QueryConsumerOffsetRequestHeader requestHeader = (QueryConsumerOffsetRequestHeader) request .decodeCommandCustomHeader(QueryConsumerOffsetRequestHeader.class); TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader); RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } long offset = this.brokerController.getConsumerOffsetManager().queryOffset( requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); if (offset >= 0) { responseHeader.setOffset(offset); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); if (requestHeader.getSetZeroIfNotFound() != null && Boolean.FALSE.equals(requestHeader.getSetZeroIfNotFound())) { response.setCode(ResponseCode.QUERY_NOT_FOUND); response.setRemark("Not found, do not set to zero, maybe this group boot first"); } else if (minOffset <= 0 && this.brokerController.getMessageStore().checkInMemByConsumeOffset( requestHeader.getTopic(), requestHeader.getQueueId(), 0, 1)) { responseHeader.setOffset(0L); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.QUERY_NOT_FOUND); response.setRemark("Not found, V3_0_6_SNAPSHOT maybe this group consumer boot first"); } } RemotingCommand rewriteResponseResult = rewriteResponseForStaticTopic(requestHeader, responseHeader, mappingContext, response.getCode()); if (rewriteResponseResult != null) { return rewriteResponseResult; } return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/DefaultPullMessageResultHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.FileRegion; import io.opentelemetry.api.common.Attributes; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.longpolling.PullRequest; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer; import org.apache.rocketmq.broker.plugin.PullMessageResultHandler; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.protocol.topic.OffsetMovedEvent; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.config.BrokerRole; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_REQUEST_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESPONSE_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESULT; public class DefaultPullMessageResultHandler implements PullMessageResultHandler { protected static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); protected final BrokerController brokerController; public DefaultPullMessageResultHandler(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand handle(final GetMessageResult getMessageResult, final RemotingCommand request, final PullMessageRequestHeader requestHeader, final Channel channel, final SubscriptionData subscriptionData, final SubscriptionGroupConfig subscriptionGroupConfig, final boolean brokerAllowSuspend, final MessageFilter messageFilter, RemotingCommand response, TopicQueueMappingContext mappingContext, long beginTimeMills) { PullMessageProcessor processor = brokerController.getPullMessageProcessor(); final String clientAddress = RemotingHelper.parseChannelRemoteAddr(channel); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); processor.composeResponseHeader(requestHeader, getMessageResult, topicConfig.getTopicSysFlag(), subscriptionGroupConfig, response, clientAddress); try { processor.executeConsumeMessageHookBefore(request, requestHeader, getMessageResult, brokerAllowSuspend, response.getCode()); } catch (AbortProcessException e) { response.setCode(e.getResponseCode()); response.setRemark(e.getErrorMessage()); return response; } //rewrite the response for the static topic final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader(); RemotingCommand rewriteResult = processor.rewriteResponseForStaticTopic(requestHeader, responseHeader, mappingContext, response.getCode()); if (rewriteResult != null) { response = rewriteResult; } processor.updateBroadcastPulledOffset(requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueId(), requestHeader, channel, response, getMessageResult.getNextBeginOffset()); processor.tryCommitOffset(brokerAllowSuspend, requestHeader, getMessageResult.getNextBeginOffset(), clientAddress); switch (response.getCode()) { case ResponseCode.SUCCESS: this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), getMessageResult.getMessageCount()); this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), requestHeader.getTopic(), getMessageResult.getBufferTotalSize()); this.brokerController.getBrokerStatsManager().incBrokerGetNums(requestHeader.getTopic(), getMessageResult.getMessageCount()); if (!BrokerMetricsManager.isRetryOrDlqTopic(requestHeader.getTopic())) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, requestHeader.getTopic()) .put(LABEL_CONSUMER_GROUP, requestHeader.getConsumerGroup()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(requestHeader.getTopic()) || MixAll.isSysConsumerGroup(requestHeader.getConsumerGroup())) .build(); this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(getMessageResult.getMessageCount(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(getMessageResult.getBufferTotalSize(), attributes); } if (!channelIsWritable(channel, requestHeader)) { getMessageResult.release(); //ignore pull request return null; } if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); this.brokerController.getBrokerStatsManager().incGroupGetLatency(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), (int) (this.brokerController.getMessageStore().now() - beginTimeMills)); response.setBody(r); return response; } else { try { FileRegion fileRegion = new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult); RemotingCommand finalResponse = response; channel.writeAndFlush(fileRegion) .addListener((ChannelFutureListener) future -> { getMessageResult.release(); RemotingMetricsManager remotingMetricsManager = brokerController.getBrokerMetricsManager().getRemotingMetricsManager(); Attributes attributes = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(finalResponse.getCode())) .put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)) .build(); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { log.error("Fail to transfer messages from page cache to {}", channel.remoteAddress(), future.cause()); } }); } catch (Throwable e) { log.error("Error occurred when transferring messages from page cache", e); getMessageResult.release(); } return null; } case ResponseCode.PULL_NOT_FOUND: final boolean hasSuspendFlag = PullSysFlag.hasSuspendFlag(requestHeader.getSysFlag()); final long suspendTimeoutMillisLong = hasSuspendFlag ? requestHeader.getSuspendTimeoutMillis() : 0; if (brokerAllowSuspend && hasSuspendFlag) { long pollingTimeMills = suspendTimeoutMillisLong; if (!this.brokerController.getBrokerConfig().isLongPollingEnable()) { pollingTimeMills = this.brokerController.getBrokerConfig().getShortPollingTimeMills(); } String topic = requestHeader.getTopic(); long offset = requestHeader.getQueueOffset(); int queueId = requestHeader.getQueueId(); PullRequest pullRequest = new PullRequest(request, channel, pollingTimeMills, this.brokerController.getMessageStore().now(), offset, subscriptionData, messageFilter); this.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest); return null; } case ResponseCode.PULL_RETRY_IMMEDIATELY: break; case ResponseCode.PULL_OFFSET_MOVED: if (this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE || this.brokerController.getMessageStoreConfig().isOffsetCheckInSlave()) { MessageQueue mq = new MessageQueue(); mq.setTopic(requestHeader.getTopic()); mq.setQueueId(requestHeader.getQueueId()); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); OffsetMovedEvent event = new OffsetMovedEvent(); event.setConsumerGroup(requestHeader.getConsumerGroup()); event.setMessageQueue(mq); event.setOffsetRequest(requestHeader.getQueueOffset()); event.setOffsetNew(getMessageResult.getNextBeginOffset()); log.warn( "PULL_OFFSET_MOVED:correction offset. topic={}, groupId={}, requestOffset={}, newOffset={}, suggestBrokerId={}", requestHeader.getTopic(), requestHeader.getConsumerGroup(), event.getOffsetRequest(), event.getOffsetNew(), responseHeader.getSuggestWhichBrokerId()); } else { responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId()); response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); log.warn("PULL_OFFSET_MOVED:none correction. topic={}, groupId={}, requestOffset={}, suggestBrokerId={}", requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueOffset(), responseHeader.getSuggestWhichBrokerId()); } break; default: log.warn("[BUG] impossible result code of get message: {}", response.getCode()); assert false; } return response; } private boolean channelIsWritable(Channel channel, PullMessageRequestHeader requestHeader) { if (this.brokerController.getBrokerConfig().isEnableNetWorkFlowControl()) { if (!channel.isWritable()) { log.warn("channel {} not writable ,cid {}", channel.remoteAddress(), requestHeader.getConsumerGroup()); return false; } } return true; } protected byte[] readGetMessageResult(final GetMessageResult getMessageResult, final String group, final String topic, final int queueId) { final ByteBuffer byteBuffer = ByteBuffer.allocate(getMessageResult.getBufferTotalSize()); long storeTimestamp = 0; try { List messageBufferList = getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { byteBuffer.put(bb); int sysFlag = bb.getInt(MessageDecoder.SYSFLAG_POSITION); // bornhost has the IPv4 ip if the MessageSysFlag.BORNHOST_V6_FLAG bit of sysFlag is 0 // IPv4 host = ip(4 byte) + port(4 byte); IPv6 host = ip(16 byte) + port(4 byte) int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; int msgStoreTimePos = 4 // 1 TOTALSIZE + 4 // 2 MAGICCODE + 4 // 3 BODYCRC + 4 // 4 QUEUEID + 4 // 5 FLAG + 8 // 6 QUEUEOFFSET + 8 // 7 PHYSICALOFFSET + 4 // 8 SYSFLAG + 8 // 9 BORNTIMESTAMP + bornhostLength; // 10 BORNHOST storeTimestamp = bb.getLong(msgStoreTimePos); } } finally { getMessageResult.release(); } this.brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId, this.brokerController.getMessageStore().now() - storeTimestamp); return byteBuffer.array(); } protected void generateOffsetMovedEvent(final OffsetMovedEvent event) { try { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(TopicValidator.RMQ_SYS_OFFSET_MOVED_EVENT); msgInner.setTags(event.getConsumerGroup()); msgInner.setDelayTimeLevel(0); msgInner.setKeys(event.getConsumerGroup()); msgInner.setBody(event.encode()); msgInner.setFlag(0); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(TopicFilterType.SINGLE_TAG, msgInner.getTags())); msgInner.setQueueId(0); msgInner.setSysFlag(0); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(NetworkUtil.string2SocketAddress(this.brokerController.getBrokerAddr())); msgInner.setStoreHost(msgInner.getBornHost()); msgInner.setReconsumeTimes(0); PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); } catch (Exception e) { log.warn(String.format("generateOffsetMovedEvent Exception, %s", event.toString()), e); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.transaction.OperationResult; import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.config.BrokerRole; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; /** * EndTransaction processor: process commit and rollback message */ public class EndTransactionProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private final BrokerController brokerController; public EndTransactionProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final EndTransactionRequestHeader requestHeader = (EndTransactionRequestHeader) request.decodeCommandCustomHeader(EndTransactionRequestHeader.class); LOGGER.debug("Transaction request:{}", requestHeader); if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) { response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE); LOGGER.warn("Message store is slave mode, so end transaction is forbidden. "); return response; } if (requestHeader.getFromTransactionCheck()) { switch (requestHeader.getCommitOrRollback()) { case MessageSysFlag.TRANSACTION_NOT_TYPE: { LOGGER.warn("Check producer[{}] transaction state, but it's pending status." + "RequestHeader: {} Remark: {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.toString(), request.getRemark()); return null; } case MessageSysFlag.TRANSACTION_COMMIT_TYPE: { LOGGER.warn("Check producer[{}] transaction state, the producer commit the message." + "RequestHeader: {} Remark: {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.toString(), request.getRemark()); break; } case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: { LOGGER.warn("Check producer[{}] transaction state, the producer rollback the message." + "RequestHeader: {} Remark: {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.toString(), request.getRemark()); break; } default: return null; } } else { switch (requestHeader.getCommitOrRollback()) { case MessageSysFlag.TRANSACTION_NOT_TYPE: { LOGGER.warn("The producer[{}] end transaction in sending message, and it's pending status." + "RequestHeader: {} Remark: {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.toString(), request.getRemark()); return null; } case MessageSysFlag.TRANSACTION_COMMIT_TYPE: { break; } case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: { LOGGER.warn("The producer[{}] end transaction in sending message, rollback the message." + "RequestHeader: {} Remark: {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.toString(), request.getRemark()); break; } default: return null; } } OperationResult result = new OperationResult(); if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) { result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader); if (result.getResponseCode() == ResponseCode.SUCCESS) { if (rejectCommitOrRollback(requestHeader, result.getPrepareMessage())) { response.setCode(ResponseCode.ILLEGAL_OPERATION); LOGGER.warn("Message commit fail [producer end]. currentTimeMillis - bornTime > checkImmunityTime, msgId={},commitLogOffset={}, wait check", requestHeader.getMsgId(), requestHeader.getCommitLogOffset()); return response; } RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader); if (res.getCode() == ResponseCode.SUCCESS) { MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage()); msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback())); msgInner.setQueueOffset(requestHeader.getTranStateTableOffset()); msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset()); msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp()); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED); RemotingCommand sendResult = sendFinalMessage(msgInner); if (sendResult.getCode() == ResponseCode.SUCCESS) { deletePrepareMessage(result); // successful committed, then total num of half-messages minus 1 this.brokerController.getTransactionalMessageService().getTransactionMetrics().addAndGet(msgInner.getTopic(), -1); this.brokerController.getBrokerMetricsManager().getCommitMessagesTotal().add(1, this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, msgInner.getTopic()) .build()); // record the commit latency. Long commitLatency = (System.currentTimeMillis() - result.getPrepareMessage().getBornTimestamp()) / 1000; this.brokerController.getBrokerMetricsManager().getTransactionFinishLatency().record(commitLatency, this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, msgInner.getTopic()) .build()); } return sendResult; } return res; } } else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) { result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader); if (result.getResponseCode() == ResponseCode.SUCCESS) { if (rejectCommitOrRollback(requestHeader, result.getPrepareMessage())) { response.setCode(ResponseCode.ILLEGAL_OPERATION); LOGGER.warn("Message rollback fail [producer end]. currentTimeMillis - bornTime > checkImmunityTime, msgId={},commitLogOffset={}, wait check", requestHeader.getMsgId(), requestHeader.getCommitLogOffset()); return response; } RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader); if (res.getCode() == ResponseCode.SUCCESS) { deletePrepareMessage(result); // roll back, then total num of half-messages minus 1 this.brokerController.getTransactionalMessageService().getTransactionMetrics().addAndGet(result.getPrepareMessage().getProperty(MessageConst.PROPERTY_REAL_TOPIC), -1); this.brokerController.getBrokerMetricsManager().getRollBackMessagesTotal().add(1, this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, result.getPrepareMessage().getProperty(MessageConst.PROPERTY_REAL_TOPIC)) .build()); } return res; } } response.setCode(result.getResponseCode()); response.setRemark(result.getResponseRemark()); return response; } private void deletePrepareMessage(OperationResult result) { if (null == result || null == result.getPrepareMessage()) { LOGGER.error("deletePrepareMessage param error, result is null or prepareMessage is null"); return; } MessageExt prepareMessage = result.getPrepareMessage(); String halfTopic = prepareMessage.getTopic(); if (StringUtils.isEmpty(halfTopic)) { LOGGER.error("deletePrepareMessage halfTopic is empty, halfTopic: {}", halfTopic); return; } if (TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC.equals(halfTopic)) { this.brokerController.getTransactionalMessageService().deletePrepareMessage(prepareMessage); } else if (this.brokerController.getMessageStoreConfig().isTransRocksDBEnable() && TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC.equals(halfTopic)) { this.brokerController.getMessageStore().getTransMessageRocksDBStore().deletePrepareMessage(prepareMessage); } else { LOGGER.warn("deletePrepareMessage error, topic of half message is: {}, transRocksDBEnable: {}", halfTopic, this.brokerController.getMessageStoreConfig().isTransRocksDBEnable()); } } /** * If you specify a custom first check time CheckImmunityTimeInSeconds, * And the commit/rollback request whose validity period exceeds CheckImmunityTimeInSeconds and is not checked back will be processed and failed * returns ILLEGAL_OPERATION 604 error * @param requestHeader * @param messageExt * @return */ public boolean rejectCommitOrRollback(EndTransactionRequestHeader requestHeader, MessageExt messageExt) { if (requestHeader.getFromTransactionCheck()) { return false; } long transactionTimeout = brokerController.getBrokerConfig().getTransactionTimeOut(); String checkImmunityTimeStr = messageExt.getUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS); if (StringUtils.isNotEmpty(checkImmunityTimeStr)) { long valueOfCurrentMinusBorn = System.currentTimeMillis() - messageExt.getBornTimestamp(); long checkImmunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); //Non-check requests that exceed the specified custom first check time fail to return return valueOfCurrentMinusBorn > checkImmunityTime; } return false; } @Override public boolean rejectRequest() { return false; } private RemotingCommand checkPrepareMessage(MessageExt msgExt, EndTransactionRequestHeader requestHeader) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); if (msgExt != null) { final String pgroupRead = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); if (!pgroupRead.equals(requestHeader.getProducerGroup())) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The producer group wrong"); return response; } if (msgExt.getQueueOffset() != requestHeader.getTranStateTableOffset()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The transaction state table offset wrong"); return response; } if (msgExt.getCommitLogOffset() != requestHeader.getCommitLogOffset()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The commit log offset wrong"); return response; } } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Find prepared transaction message failed"); return response; } response.setCode(ResponseCode.SUCCESS); return response; } private MessageExtBrokerInner endMessageTransaction(MessageExt msgExt) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgInner.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID))); msgInner.setBody(msgExt.getBody()); msgInner.setFlag(msgExt.getFlag()); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setStoreHost(msgExt.getStoreHost()); msgInner.setReconsumeTimes(msgExt.getReconsumeTimes()); msgInner.setWaitStoreMsgOK(false); msgInner.setTransactionId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); msgInner.setSysFlag(msgExt.getSysFlag()); TopicFilterType topicFilterType = (msgInner.getSysFlag() & MessageSysFlag.MULTI_TAGS_FLAG) == MessageSysFlag.MULTI_TAGS_FLAG ? TopicFilterType.MULTI_TAG : TopicFilterType.SINGLE_TAG; long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags()); msgInner.setTagsCode(tagsCodeValue); String checkTimes = msgExt.getUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES); if (StringUtils.isEmpty(checkTimes) && this.brokerController.getMessageStoreConfig().isTransRocksDBEnable() && null != this.brokerController.getMessageStore().getTransMessageRocksDBStore()) { Integer checkTimesRocksDB = this.brokerController.getMessageStore().getTransMessageRocksDBStore().getCheckTimes(msgInner.getTopic(), msgInner.getTransactionId(), msgExt.getCommitLogOffset()); if (null != checkTimesRocksDB && checkTimesRocksDB >= 0) { msgExt.putUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, String.valueOf(checkTimesRocksDB)); } } MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(MessageDecoder.messageProperties2String(msgExt.getProperties()))); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return msgInner; } private RemotingCommand sendFinalMessage(MessageExtBrokerInner msgInner) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); if (putMessageResult != null) { switch (putMessageResult.getPutMessageStatus()) { // Success case PUT_OK: this.brokerController.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); this.brokerController.getBrokerStatsManager().incTopicPutSize(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); this.brokerController.getBrokerStatsManager().incBrokerPutNums(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum()); case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case SLAVE_NOT_AVAILABLE: response.setCode(ResponseCode.SUCCESS); response.setRemark(null); break; // Failed case CREATE_MAPPED_FILE_FAILED: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("Create mapped file failed."); break; case MESSAGE_ILLEGAL: case PROPERTIES_SIZE_EXCEEDED: response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(String.format("The message is illegal, maybe msg body or properties length not matched. msg body length limit %dB, msg properties length limit 32KB.", this.brokerController.getMessageStoreConfig().getMaxMessageSize())); break; case SERVICE_NOT_AVAILABLE: response.setCode(ResponseCode.SERVICE_NOT_AVAILABLE); response.setRemark("Service not available now."); break; case OS_PAGE_CACHE_BUSY: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("OS page cache busy, please try another machine"); break; case WHEEL_TIMER_MSG_ILLEGAL: response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(String.format("timer message illegal, the delay time should not be bigger than the max delay %dms; or if set del msg, the delay time should be bigger than the current time", this.brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L)); break; case WHEEL_TIMER_FLOW_CONTROL: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("timer message is under flow control, max num limit is %d or the current value is greater than %d and less than %d, trigger random flow control", this.brokerController.getMessageStoreConfig().getTimerCongestNumEachSlot() * 2L, this.brokerController.getMessageStoreConfig().getTimerCongestNumEachSlot(), this.brokerController.getMessageStoreConfig().getTimerCongestNumEachSlot() * 2L)); break; case WHEEL_TIMER_NOT_ENABLE: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("accurate timer message is not enabled, timerWheelEnable is %s", this.brokerController.getMessageStoreConfig().isTimerWheelEnable())); break; case UNKNOWN_ERROR: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UNKNOWN_ERROR"); break; case IN_SYNC_REPLICAS_NOT_ENOUGH: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("in-sync replicas not enough"); break; case PUT_TO_REMOTE_BROKER_FAIL: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("put to remote broker fail"); break; default: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UNKNOWN_ERROR DEFAULT"); break; } return response; } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("store putMessage return null"); } return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/LiteManagerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.google.common.annotations.VisibleForTesting; import io.netty.channel.ChannelHandlerContext; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.AbstractLiteLifecycleManager; import org.apache.rocketmq.broker.lite.LiteMetadataUtil; import org.apache.rocketmq.broker.lite.LiteSharding; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteLagInfo; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import org.apache.rocketmq.remoting.protocol.body.GetBrokerLiteInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteClientInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteGroupInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetParentTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetLiteClientInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteGroupInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetParentTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.TriggerLiteDispatchRequestHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import java.util.HashSet; import java.util.Map; import java.util.Set; public class LiteManagerProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); private static final int MAX_RETURN_COUNT = 10000; private final BrokerController brokerController; private final AbstractLiteLifecycleManager liteLifecycleManager; private final LiteSharding liteSharding; public LiteManagerProcessor(BrokerController brokerController, AbstractLiteLifecycleManager liteLifecycleManager, LiteSharding liteSharding) { this.brokerController = brokerController; this.liteLifecycleManager = liteLifecycleManager; this.liteSharding = liteSharding; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { switch (request.getCode()) { case RequestCode.GET_BROKER_LITE_INFO: return this.getBrokerLiteInfo(ctx, request); case RequestCode.GET_PARENT_TOPIC_INFO: return this.getParentTopicInfo(ctx, request); case RequestCode.GET_LITE_TOPIC_INFO: return this.getLiteTopicInfo(ctx, request); case RequestCode.GET_LITE_CLIENT_INFO: return this.getLiteClientInfo(ctx, request); case RequestCode.GET_LITE_GROUP_INFO: return this.getLiteGroupInfo(ctx, request); case RequestCode.TRIGGER_LITE_DISPATCH: return this.triggerLiteDispatch(ctx, request); default: break; } return null; } @VisibleForTesting protected RemotingCommand getBrokerLiteInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); GetBrokerLiteInfoResponseBody body = new GetBrokerLiteInfoResponseBody(); body.setStoreType(brokerController.getMessageStoreConfig().getStoreType()); body.setMaxLmqNum(brokerController.getMessageStoreConfig().getMaxLmqConsumeQueueNum()); body.setCurrentLmqNum(brokerController.getMessageStore().getQueueStore().getLmqNum()); body.setLiteSubscriptionCount(brokerController.getLiteSubscriptionRegistry().getActiveSubscriptionNum()); body.setOrderInfoCount(brokerController.getPopLiteMessageProcessor().getConsumerOrderInfoManager().getOrderInfoCount()); body.setCqTableSize(brokerController.getMessageStore().getQueueStore().getConsumeQueueTable().size()); body.setOffsetTableSize(brokerController.getConsumerOffsetManager().getOffsetTable().size()); body.setEventMapSize(brokerController.getLiteEventDispatcher().getEventMapSize()); body.setTopicMeta(LiteMetadataUtil.getTopicTtlMap(brokerController)); body.setGroupMeta(LiteMetadataUtil.getSubscriberGroupMap(brokerController)); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } @VisibleForTesting protected RemotingCommand getParentTopicInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetParentTopicInfoRequestHeader requestHeader = request.decodeCommandCustomHeader(GetParentTopicInfoRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); String topic = requestHeader.getTopic(); TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("Topic [%s] not exist.", topic)); return response; } if (!TopicMessageType.LITE.equals(topicConfig.getTopicMessageType())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("Topic [%s] type not match.", topic)); return response; } Map> subscriberGroupMap = LiteMetadataUtil.getSubscriberGroupMap(brokerController); GetParentTopicInfoResponseBody body = new GetParentTopicInfoResponseBody(); body.setTopic(topic); body.setTtl(topicConfig.getLiteTopicExpiration()); body.setLmqNum(brokerController.getMessageStore().getQueueStore().getLmqNum()); body.setLiteTopicCount(liteLifecycleManager.getLiteTopicCount(topic)); body.setGroups(subscriberGroupMap != null ? subscriberGroupMap.get(topic) : null); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } @VisibleForTesting protected RemotingCommand getLiteTopicInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetLiteTopicInfoRequestHeader requestHeader = request.decodeCommandCustomHeader(GetLiteTopicInfoRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); String parentTopic = requestHeader.getParentTopic(); String liteTopic = requestHeader.getLiteTopic(); TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(parentTopic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("Topic [%s] not exist.", parentTopic)); return response; } if (!TopicMessageType.LITE.equals(topicConfig.getTopicMessageType())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("Topic [%s] type not match.", parentTopic)); return response; } String lmqName = LiteUtil.toLmqName(parentTopic, liteTopic); TopicOffset topicOffset = new TopicOffset(); long minOffset = 0; long lastUpdateTimestamp = 0; long maxOffset = liteLifecycleManager.getMaxOffsetInQueue(lmqName); if (maxOffset > 0) { minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(lmqName, 0); lastUpdateTimestamp = brokerController.getMessageStore().getMessageStoreTimeStamp(lmqName, 0, maxOffset - 1); } topicOffset.setMinOffset(minOffset < 0 ? 0 : minOffset); topicOffset.setMaxOffset(maxOffset < 0 ? 0 : maxOffset); topicOffset.setLastUpdateTimestamp(lastUpdateTimestamp); GetLiteTopicInfoResponseBody body = new GetLiteTopicInfoResponseBody(); body.setParentTopic(parentTopic); body.setLiteTopic(liteTopic); body.setSubscriber(brokerController.getLiteSubscriptionRegistry().getSubscriber(lmqName)); body.setTopicOffset(topicOffset); body.setShardingToBroker(brokerController.getBrokerConfig().getBrokerName().equals( liteSharding.shardingByLmqName(parentTopic, lmqName))); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } @VisibleForTesting protected RemotingCommand getLiteClientInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetLiteClientInfoRequestHeader requestHeader = request.decodeCommandCustomHeader(GetLiteClientInfoRequestHeader.class); final RemotingCommand response = RemotingCommand.createResponseCommand(null); String parentTopic = requestHeader.getParentTopic(); String group = requestHeader.getGroup(); TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(parentTopic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("Topic [%s] not exist.", parentTopic)); return response; } if (!TopicMessageType.LITE.equals(topicConfig.getTopicMessageType())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("Topic [%s] type not match.", parentTopic)); return response; } SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); if (null == groupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("Group [%s] not exist.", group)); return response; } if (!parentTopic.equals(groupConfig.getLiteBindTopic())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("Subscription [%s]-[%s] not match.", group, parentTopic)); return response; } String clientId = requestHeader.getClientId(); int maxCount = Math.min(requestHeader.getMaxCount(), MAX_RETURN_COUNT); Set returnSet = null; int liteTopicCount = 0; LiteSubscription liteSubscription = brokerController.getLiteSubscriptionRegistry().getLiteSubscription(clientId); if (liteSubscription != null && liteSubscription.getLiteTopicSet() != null) { Set liteTopicSet = liteSubscription.getLiteTopicSet(); liteTopicCount = liteTopicSet.size(); if (maxCount >= liteTopicCount) { returnSet = liteTopicSet; } else { returnSet = new HashSet<>(maxCount); int count = 0; for (String topic : liteTopicSet) { if (count >= maxCount) { break; } returnSet.add(topic); count++; } } } else { liteTopicCount = -1; } GetLiteClientInfoResponseBody body = new GetLiteClientInfoResponseBody(); body.setParentTopic(parentTopic); body.setGroup(group); body.setClientId(clientId); body.setLiteTopicCount(liteTopicCount); body.setLiteTopicSet(returnSet); body.setLastAccessTime(brokerController.getLiteEventDispatcher().getClientLastAccessTime(clientId)); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } @VisibleForTesting protected RemotingCommand getLiteGroupInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final GetLiteGroupInfoRequestHeader requestHeader = request.decodeCommandCustomHeader(GetLiteGroupInfoRequestHeader.class); final String group = requestHeader.getGroup(); final String liteTopic = requestHeader.getLiteTopic(); final int topK = requestHeader.getTopK(); LOGGER.info("Broker receive request to getLiteGroupInfo, group:{}, liteTopic:{}, caller:{}", group, liteTopic, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); if (null == groupConfig) { return RemotingCommand.createResponseCommand(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST, String.format("Group [%s] not exist.", group)); } if (StringUtils.isEmpty(groupConfig.getLiteBindTopic())) { return RemotingCommand.createResponseCommand(ResponseCode.INVALID_PARAMETER, String.format("Group [%s] is not a LITE group.", group)); } String bindTopic = groupConfig.getLiteBindTopic(); GetLiteGroupInfoResponseBody body = new GetLiteGroupInfoResponseBody(); body.setGroup(group); body.setParentTopic(bindTopic); body.setLiteTopic(liteTopic); if (StringUtils.isEmpty(liteTopic)) { Pair, Long> lagCountPair = brokerController.getBrokerMetricsManager() .getLiteConsumerLagCalculator() .getLagCountTopK(group, topK); Pair, Long> lagTimePair = brokerController.getBrokerMetricsManager() .getLiteConsumerLagCalculator() .getLagTimestampTopK(group, bindTopic, topK); body.setLagCountTopK(lagCountPair.getObject1()); body.setTotalLagCount(lagCountPair.getObject2()); body.setLagTimestampTopK(lagTimePair.getObject1()); body.setEarliestUnconsumedTimestamp(lagTimePair.getObject2()); } else { String lmqName = LiteUtil.toLmqName(bindTopic, liteTopic); long maxOffset = liteLifecycleManager.getMaxOffsetInQueue(lmqName); if (maxOffset > 0) { long commitOffset = brokerController.getConsumerOffsetManager().queryOffset(group, lmqName, 0); if (commitOffset >= 0) { // lag count and unconsumedTimestamp, reuse total field body.setTotalLagCount(maxOffset - commitOffset); body.setEarliestUnconsumedTimestamp(brokerController.getMessageStore().getMessageStoreTimeStamp( lmqName, 0, commitOffset)); OffsetWrapper offsetWrapper = new OffsetWrapper(); offsetWrapper.setBrokerOffset(maxOffset); offsetWrapper.setConsumerOffset(commitOffset); if (commitOffset - 1 >= 0) { offsetWrapper.setLastTimestamp( brokerController.getMessageStore().getMessageStoreTimeStamp(lmqName, 0, commitOffset - 1)); } body.setLiteTopicOffsetWrapper(offsetWrapper); } } else { body.setTotalLagCount(-1); body.setEarliestUnconsumedTimestamp(-1); } } final RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); return response; } @VisibleForTesting protected RemotingCommand triggerLiteDispatch(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final TriggerLiteDispatchRequestHeader requestHeader = request.decodeCommandCustomHeader(TriggerLiteDispatchRequestHeader.class); final String group = requestHeader.getGroup(); final String clientId = requestHeader.getClientId(); LOGGER.info("Broker receive request to triggerLiteDispatch, group:{}, clientId:{}, caller:{}", group, clientId, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); SubscriptionGroupConfig groupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); if (null == groupConfig) { return RemotingCommand.createResponseCommand(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST, String.format("Group [%s] not exist.", group)); } if (StringUtils.isEmpty(groupConfig.getLiteBindTopic())) { return RemotingCommand.createResponseCommand(ResponseCode.INVALID_PARAMETER, String.format("Group [%s] is not a LITE group.", group)); } if (StringUtils.isNotEmpty(clientId)) { brokerController.getLiteEventDispatcher().doFullDispatch(clientId, group); } else { brokerController.getLiteEventDispatcher().doFullDispatchByGroup(group); } final RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); return response; } @Override public boolean rejectRequest() { return false; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/LiteSubscriptionCtlProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.LiteSubscriptionRegistry; import org.apache.rocketmq.broker.lite.LiteQuotaException; import org.apache.rocketmq.broker.lite.LiteMetadataUtil; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.LiteSubscriptionCtlRequestBody; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LiteSubscriptionCtlProcessor implements NettyRequestProcessor { protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); private final BrokerController brokerController; private final LiteSubscriptionRegistry liteSubscriptionRegistry; public LiteSubscriptionCtlProcessor(BrokerController brokerController, LiteSubscriptionRegistry liteSubscriptionRegistry) { this.brokerController = brokerController; this.liteSubscriptionRegistry = liteSubscriptionRegistry; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { if (request.getBody() == null) { return RemotingCommand.createResponseCommand(ResponseCode.ILLEGAL_OPERATION, "Request body is null."); } final LiteSubscriptionCtlRequestBody requestBody = LiteSubscriptionCtlRequestBody .decode(request.getBody(), LiteSubscriptionCtlRequestBody.class); Set entrySet = requestBody.getSubscriptionSet(); if (CollectionUtils.isEmpty(entrySet)) { return RemotingCommand.createResponseCommand(ResponseCode.ILLEGAL_OPERATION, "LiteSubscriptionCtlRequestBody is empty."); } try { for (LiteSubscriptionDTO entry : entrySet) { final String clientId = entry.getClientId(); final String group = entry.getGroup(); final String topic = entry.getTopic(); if (StringUtils.isBlank(clientId)) { log.warn("clientId is blank, {}", entry); continue; } if (StringUtils.isBlank(group)) { log.warn("group is blank, {}", entry); continue; } if (StringUtils.isBlank(topic)) { log.warn("topic is blank, {}", entry); continue; } final Set lmqNameSet = toLmqNameSet(entry); switch (entry.getAction()) { case PARTIAL_ADD: checkConsumeEnable(group); this.liteSubscriptionRegistry.updateClientChannel(clientId, ctx.channel()); this.liteSubscriptionRegistry.addPartialSubscription(clientId, group, topic, lmqNameSet, entry.getOffsetOption()); break; case PARTIAL_REMOVE: this.liteSubscriptionRegistry.removePartialSubscription(clientId, group, topic, lmqNameSet); break; case COMPLETE_ADD: checkConsumeEnable(group); this.liteSubscriptionRegistry.updateClientChannel(clientId, ctx.channel()); this.liteSubscriptionRegistry.addCompleteSubscription(clientId, group, topic, lmqNameSet, entry.getVersion()); break; case COMPLETE_REMOVE: this.liteSubscriptionRegistry.removeCompleteSubscription(clientId); break; } } return RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); } catch (LiteQuotaException e) { return RemotingCommand.createResponseCommand(ResponseCode.LITE_SUBSCRIPTION_QUOTA_EXCEEDED, e.toString()); } catch (IllegalStateException e) { return RemotingCommand.createResponseCommand(ResponseCode.ILLEGAL_OPERATION, e.toString()); } catch (Exception e) { log.error("LiteSubscriptionCtlProcessor error", e); return RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, e.toString()); } } private void checkConsumeEnable(String group) { if (!LiteMetadataUtil.isConsumeEnable(group, brokerController)) { throw new IllegalStateException("Consumer group is not allowed to consume."); } } private Set toLmqNameSet(LiteSubscriptionDTO liteSubscriptionDTO) { if (CollectionUtils.isEmpty(liteSubscriptionDTO.getLiteTopicSet())) { return Collections.emptySet(); } return liteSubscriptionDTO.getLiteTopicSet().stream() .map(liteTopic -> LiteUtil.toLmqName(liteSubscriptionDTO.getTopic(), liteTopic)) .collect(Collectors.toSet()); } @Override public boolean rejectRequest() { return false; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/NotificationProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.Map; import java.util.Random; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ConsumerFilterManager; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.broker.longpolling.PollingHeader; import org.apache.rocketmq.broker.longpolling.PollingResult; import org.apache.rocketmq.broker.longpolling.PopLongPollingService; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.header.NotificationRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotificationResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.rocksdb.RocksDBException; public class NotificationProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final BrokerController brokerController; private final Random random = new Random(System.currentTimeMillis()); private final PopLongPollingService popLongPollingService; private static final String BORN_TIME = "bornTime"; public NotificationProcessor(final BrokerController brokerController) { this.brokerController = brokerController; this.popLongPollingService = new PopLongPollingService(brokerController, this, true); } public void shutdown() throws Exception { this.popLongPollingService.shutdown(); } @Override public boolean rejectRequest() { return false; } // When a new message is written to CommitLog, this method would be called. // Suspended long polling will receive notification and be wakeup. public void notifyMessageArriving(final String topic, final int queueId, long offset, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { this.popLongPollingService.notifyMessageArrivingWithRetryTopic( topic, queueId, offset, tagsCode, msgStoreTime, filterBitMap, properties); } public void notifyMessageArriving(final String topic, final int queueId) { this.popLongPollingService.notifyMessageArrivingWithRetryTopic(topic, queueId); } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { Channel channel = ctx.channel(); RemotingCommand response = RemotingCommand.createResponseCommand(NotificationResponseHeader.class); final NotificationResponseHeader responseHeader = (NotificationResponseHeader) response.readCustomHeader(); final NotificationRequestHeader requestHeader = request.decodeCommandCustomHeader(NotificationRequestHeader.class, true); if (requestHeader.getBornTime() == 0) { final long beginTimeMills = this.brokerController.getMessageStore().now(); request.addExtField(BORN_TIME, String.valueOf(beginTimeMills)); requestHeader.setBornTime(beginTimeMills); } response.setOpaque(request.getOpaque()); if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the broker[%s] peeking message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("the topic[" + requestHeader.getTopic() + "] peeking message is forbidden"); return response; } if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(errorInfo); return response; } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST))); return response; } if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } int randomQ = random.nextInt(100); boolean hasMsg = false; BrokerConfig brokerConfig = brokerController.getBrokerConfig(); SubscriptionData subscriptionData = null; ExpressionMessageFilter messageFilter = null; if (brokerConfig.isUseMessageFilterForNotification() && StringUtils.isNotEmpty(requestHeader.getExpType()) && StringUtils.isNotEmpty(requestHeader.getExp())) { try { // origin topic subscriptionData = FilterAPI.build( requestHeader.getTopic(), requestHeader.getExp(), requestHeader.getExpType()); ConsumerFilterData consumerFilterData = null; if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) { consumerFilterData = ConsumerFilterManager.build( requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getExp(), requestHeader.getExpType(), System.currentTimeMillis()); if (consumerFilterData == null) { POP_LOGGER.warn("Parse the consumer's subscription[{}] failed, group: {}", requestHeader.getExp(), requestHeader.getConsumerGroup()); response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED); response.setRemark("parse the consumer's subscription failed"); return response; } } messageFilter = new ExpressionMessageFilter( subscriptionData, consumerFilterData, brokerController.getConsumerFilterManager()); } catch (Exception e) { POP_LOGGER.warn("Parse the consumer's subscription[{}] error, group: {}", requestHeader.getExp(), requestHeader.getConsumerGroup()); response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED); response.setRemark("parse the consumer's subscription failed"); return response; } } if (requestHeader.getQueueId() < 0) { // read all queue hasMsg = hasMsgFromTopic(topicConfig, randomQ, requestHeader, subscriptionData, messageFilter); } else { int queueId = requestHeader.getQueueId(); hasMsg = hasMsgFromQueue(topicConfig.getTopicName(), requestHeader, queueId, subscriptionData, messageFilter); } // if it doesn't have message, fetch retry if (!hasMsg) { String retryTopic = KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2()); hasMsg = hasMsgFromTopic(retryTopic, randomQ, requestHeader, null, null); if (!hasMsg && brokerConfig.isEnableRetryTopicV2() && brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { String retryTopicConfigV1 = KeyBuilder.buildPopRetryTopicV1(requestHeader.getTopic(), requestHeader.getConsumerGroup()); hasMsg = hasMsgFromTopic(retryTopicConfigV1, randomQ, requestHeader, null, null); } } if (!hasMsg) { PollingResult pollingResult = popLongPollingService.polling(ctx, request, new PollingHeader(requestHeader), subscriptionData, messageFilter); if (pollingResult == PollingResult.POLLING_SUC) { return null; } else if (pollingResult == PollingResult.POLLING_FULL) { responseHeader.setPollingFull(true); } } response.setCode(ResponseCode.SUCCESS); responseHeader.setHasMsg(hasMsg); return response; } private boolean hasMsgFromTopic(String topicName, int randomQ, NotificationRequestHeader requestHeader, SubscriptionData subscriptionData, MessageFilter messageFilter) throws RemotingCommandException { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topicName); return hasMsgFromTopic(topicConfig, randomQ, requestHeader, subscriptionData, messageFilter); } private boolean hasMsgFromTopic(TopicConfig topicConfig, int randomQ, NotificationRequestHeader requestHeader, SubscriptionData subscriptionData, MessageFilter messageFilter) throws RemotingCommandException { boolean hasMsg; if (topicConfig != null) { for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { int queueId = (randomQ + i) % topicConfig.getReadQueueNums(); hasMsg = hasMsgFromQueue(topicConfig.getTopicName(), requestHeader, queueId, subscriptionData, messageFilter); if (hasMsg) { return true; } } } return false; } private boolean hasMsgFromQueue(String targetTopic, NotificationRequestHeader requestHeader, int queueId, SubscriptionData subscriptionData, MessageFilter messageFilter) throws RemotingCommandException { if (Boolean.TRUE.equals(requestHeader.getOrder())) { if (this.brokerController.getConsumerOrderInfoManager().checkBlock(requestHeader.getAttemptId(), requestHeader.getTopic(), requestHeader.getConsumerGroup(), queueId, 0)) { return false; } } long offset = getPopOffset(targetTopic, requestHeader.getConsumerGroup(), queueId); try { long restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(targetTopic, queueId) - offset; int maxFilterMessageNum = this.brokerController.getBrokerConfig().getMaxMessageFilterNumForNotification(); boolean needFilter = restNum < maxFilterMessageNum && subscriptionData != null && messageFilter != null && ExpressionType.isTagType(subscriptionData.getExpressionType()); if (needFilter) { ConsumeQueueInterface queue = this.brokerController.getMessageStore().getConsumeQueue(targetTopic, queueId); // If the ConsumeQueue doesn't exist, it's not readable. if (queue == null) { return false; } ReferredIterator iterator = null; try { // In order to take into account both the file CQ and the Rocksdb CQ, // the count passed here is 32. iterator = queue.iterateFrom(offset, 32); if (iterator != null) { while (iterator.hasNext()) { CqUnit cqUnit = iterator.next(); if (messageFilter.isMatchedByConsumeQueue(cqUnit.getValidTagsCodeAsLong(), cqUnit.getCqExtUnit())) { return true; } } return false; } } finally { if (iterator != null) { iterator.release(); } } } return restNum > 0; } catch (ConsumeQueueException | RocksDBException e) { throw new RemotingCommandException("Failed to get max offset in queue or iterate in queue", e); } } private long getPopOffset(String topic, String cid, int queueId) { long offset = this.brokerController.getConsumerOffsetManager().queryOffset(cid, topic, queueId); if (offset < 0) { offset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId); } long bufferOffset; if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { bufferOffset = this.brokerController.getConsumerOffsetManager().queryPullOffset(cid, topic, queueId); } else { bufferOffset = this.brokerController.getPopMessageProcessor() .getPopBufferMergeService().getLatestOffset(topic, cid, queueId); } return bufferOffset < 0L ? offset : Math.max(bufferOffset, offset); } public PopLongPollingService getPopLongPollingService() { return popLongPollingService; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.FileRegion; import io.opentelemetry.api.common.Attributes; import java.nio.ByteBuffer; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PeekMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_REQUEST_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESPONSE_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESULT; public class PeekMessageProcessor implements NettyRequestProcessor { private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private Random random = new Random(System.currentTimeMillis()); public PeekMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { return this.processRequest(ctx.channel(), request, true); } @Override public boolean rejectRequest() { return false; } private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { final long beginTimeMills = this.brokerController.getMessageStore().now(); RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); final PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); final PeekMessageRequestHeader requestHeader = (PeekMessageRequestHeader) request.decodeCommandCustomHeader(PeekMessageRequestHeader.class); response.setOpaque(request.getOpaque()); if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the broker[%s] peeking message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { LOG.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("the topic[" + requestHeader.getTopic() + "] peeking message is forbidden"); return response; } if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); LOG.warn(errorInfo); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(errorInfo); return response; } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST))); return response; } if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } int randomQ = random.nextInt(100); int reviveQid = randomQ % this.brokerController.getBrokerConfig().getReviveQueueNum(); GetMessageResult getMessageResult = new GetMessageResult(requestHeader.getMaxMsgNums()); boolean needRetry = randomQ % 5 == 0; long popTime = System.currentTimeMillis(); long restNum = 0; BrokerConfig brokerConfig = brokerController.getBrokerConfig(); if (needRetry) { TopicConfig retryTopicConfig = this.brokerController.getTopicConfigManager() .selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2())); if (retryTopicConfig != null) { for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) { int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums(); restNum = peekMsgFromQueue(true, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime); } } } if (requestHeader.getQueueId() < 0) { // read all queue for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { int queueId = (randomQ + i) % topicConfig.getReadQueueNums(); restNum = peekMsgFromQueue(false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime); } } else { int queueId = requestHeader.getQueueId(); restNum = peekMsgFromQueue(false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime); } // if not full , fetch retry again if (!needRetry && getMessageResult.getMessageMapedList().size() < requestHeader.getMaxMsgNums()) { TopicConfig retryTopicConfig = this.brokerController.getTopicConfigManager() .selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2())); if (retryTopicConfig != null) { for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) { int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums(); restNum = peekMsgFromQueue(true, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime); } } } if (!getMessageResult.getMessageBufferList().isEmpty()) { response.setCode(ResponseCode.SUCCESS); getMessageResult.setStatus(GetMessageStatus.FOUND); } else { response.setCode(ResponseCode.PULL_NOT_FOUND); getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); } responseHeader.setRestNum(restNum); response.setRemark(getMessageResult.getStatus().name()); switch (response.getCode()) { case ResponseCode.SUCCESS: this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), getMessageResult.getMessageCount()); this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), requestHeader.getTopic(), getMessageResult.getBufferTotalSize()); this.brokerController.getBrokerStatsManager().incBrokerGetNums(requestHeader.getTopic(), getMessageResult.getMessageCount()); if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); this.brokerController.getBrokerStatsManager().incGroupGetLatency(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), (int) (this.brokerController.getMessageStore().now() - beginTimeMills)); response.setBody(r); } else { final GetMessageResult tmpGetMessageResult = getMessageResult; try { FileRegion fileRegion = new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult); RemotingCommand finalResponse = response; channel.writeAndFlush(fileRegion) .addListener((ChannelFutureListener) future -> { tmpGetMessageResult.release(); RemotingMetricsManager remotingMetricsManager = brokerController.getBrokerMetricsManager().getRemotingMetricsManager(); Attributes attributes = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(finalResponse.getCode())) .put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)) .build(); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { LOG.error("Fail to transfer messages from page cache to {}", channel.remoteAddress(), future.cause()); } }); } catch (Throwable e) { LOG.error("Error occurred when transferring messages from page cache", e); getMessageResult.release(); } response = null; } break; default: assert false; } return response; } private long peekMsgFromQueue(boolean isRetry, GetMessageResult getMessageResult, PeekMessageRequestHeader requestHeader, int queueId, long restNum, int reviveQid, Channel channel, long popTime) throws RemotingCommandException { String topic = isRetry ? KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerController.getBrokerConfig().isEnableRetryTopicV2()) : requestHeader.getTopic(); GetMessageResult getMessageTmpResult; long offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId); try { restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum; } catch (ConsumeQueueException e) { LOG.error("Failed to get max offset in queue. topic={}, queue-id={}", topic, queueId, e); throw new RemotingCommandException("Failed to get max offset in queue", e); } if (getMessageResult.getMessageMapedList().size() >= requestHeader.getMaxMsgNums()) { return restNum; } getMessageTmpResult = this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), topic, queueId, offset, requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), null); // maybe store offset is not correct. if (GetMessageStatus.OFFSET_TOO_SMALL.equals(getMessageTmpResult.getStatus()) || GetMessageStatus.OFFSET_OVERFLOW_BADLY.equals(getMessageTmpResult.getStatus())) { offset = getMessageTmpResult.getNextBeginOffset(); getMessageTmpResult = this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), topic, queueId, offset, requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), null); } if (getMessageTmpResult != null) { if (!getMessageTmpResult.getMessageMapedList().isEmpty() && !isRetry) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, requestHeader.getTopic()) .put(LABEL_CONSUMER_GROUP, requestHeader.getConsumerGroup()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(requestHeader.getTopic()) || MixAll.isSysConsumerGroup(requestHeader.getConsumerGroup())) .build(); this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(getMessageResult.getMessageCount(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(getMessageResult.getBufferTotalSize(), attributes); } for (SelectMappedBufferResult mappedBuffer : getMessageTmpResult.getMessageMapedList()) { getMessageResult.addMessage(mappedBuffer); } } return restNum; } private long getPopOffset(String topic, String cid, int queueId) { long offset = this.brokerController.getConsumerOffsetManager().queryOffset(cid, topic, queueId); if (offset < 0) { offset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId); } long bufferOffset = this.brokerController.getPopMessageProcessor().getPopBufferMergeService() .getLatestOffset(topic, cid, queueId); if (bufferOffset < 0) { return offset; } else { return bufferOffset > offset ? bufferOffset : offset; } } private byte[] readGetMessageResult(final GetMessageResult getMessageResult, final String group, final String topic, final int queueId) { final ByteBuffer byteBuffer = ByteBuffer.allocate(getMessageResult.getBufferTotalSize()); long storeTimestamp = 0; try { List messageBufferList = getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { byteBuffer.put(bb); storeTimestamp = bb.getLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION); } } finally { getMessageResult.release(); } this.brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId, this.brokerController.getMessageStore().now() - storeTimestamp); return byteBuffer.array(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PollingInfoProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.concurrent.ConcurrentSkipListSet; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.longpolling.PopRequest; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PollingInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PollingInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class PollingInfoProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final BrokerController brokerController; public PollingInfoProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { return this.processRequest(ctx.channel(), request); } @Override public boolean rejectRequest() { return false; } private RemotingCommand processRequest(final Channel channel, RemotingCommand request) throws RemotingCommandException { RemotingCommand response = RemotingCommand.createResponseCommand(PollingInfoResponseHeader.class); final PollingInfoResponseHeader responseHeader = (PollingInfoResponseHeader) response.readCustomHeader(); final PollingInfoRequestHeader requestHeader = (PollingInfoRequestHeader) request.decodeCommandCustomHeader(PollingInfoRequestHeader.class); response.setOpaque(request.getOpaque()); if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the broker[%s] peeking message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("the topic[" + requestHeader.getTopic() + "] peeking message is forbidden"); return response; } if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(errorInfo); return response; } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST))); return response; } if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } String key = KeyBuilder.buildPollingKey(requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueId()); ConcurrentSkipListSet queue = this.brokerController.getPopMessageProcessor().getPollingMap().getIfPresent(key); if (queue != null) { responseHeader.setPollingNum(queue.size()); } else { responseHeader.setPollingNum(0); } response.setCode(ResponseCode.SUCCESS); return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.utils.DataConverter; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.BatchAckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicInteger; public class PopBufferMergeService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); ConcurrentHashMap buffer = new ConcurrentHashMap<>(1024 * 16); ConcurrentHashMap> commitOffsets = new ConcurrentHashMap<>(); private volatile boolean serving = true; private AtomicInteger counter = new AtomicInteger(0); private int scanTimes = 0; private final BrokerController brokerController; private final PopMessageProcessor popMessageProcessor; private final PopMessageProcessor.QueueLockManager queueLockManager; private final long interval = 5; private final long minute5 = 5 * 60 * 1000; private final int countOfMinute1 = (int) (60 * 1000 / interval); private final int countOfSecond1 = (int) (1000 / interval); private final int countOfSecond30 = (int) (30 * 1000 / interval); private final List batchAckIndexList = new ArrayList<>(32); private volatile boolean master = false; public PopBufferMergeService(BrokerController brokerController, PopMessageProcessor popMessageProcessor) { this.brokerController = brokerController; this.popMessageProcessor = popMessageProcessor; this.queueLockManager = popMessageProcessor.getQueueLockManager(); } private boolean isShouldRunning() { if (this.brokerController.getBrokerConfig().isEnableSlaveActingMaster()) { return true; } this.master = brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE; return this.master; } @Override public String getServiceName() { if (this.brokerController != null && this.brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + PopBufferMergeService.class.getSimpleName(); } return PopBufferMergeService.class.getSimpleName(); } @Override public void run() { // scan while (!this.isStopped()) { try { if (!isShouldRunning()) { // slave this.waitForRunning(interval * 200 * 5); POP_LOGGER.info("Broker is {}, {}, clear all data", brokerController.getMessageStoreConfig().getBrokerRole(), this.master); this.buffer.clear(); this.commitOffsets.clear(); continue; } scan(); if (scanTimes % countOfSecond30 == 0) { scanGarbage(); } this.waitForRunning(interval); if (!this.serving && this.buffer.size() == 0 && getOffsetTotalSize() == 0) { this.serving = true; } } catch (Throwable e) { POP_LOGGER.error("PopBufferMergeService error", e); this.waitForRunning(3000); } } this.serving = false; try { Thread.sleep(2000); } catch (InterruptedException e) { } if (!isShouldRunning()) { return; } if (!brokerController.getBrokerConfig().isInBrokerContainer()) { while (this.buffer.size() > 0 || getOffsetTotalSize() > 0) { scan(); } } } private int scanCommitOffset() { Iterator>> iterator = this.commitOffsets.entrySet().iterator(); int count = 0; while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); LinkedBlockingDeque queue = entry.getValue().get(); PopCheckPointWrapper pointWrapper; while ((pointWrapper = queue.peek()) != null) { // 1. just offset & stored, not processed by scan // 2. ck is buffer(acked) // 3. ck is buffer(not all acked), all ak are stored and ck is stored if (pointWrapper.isJustOffset() && pointWrapper.isCkStored() || isCkDone(pointWrapper) || isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) { if (commitOffset(pointWrapper)) { queue.poll(); } else { break; } } else { if (System.currentTimeMillis() - pointWrapper.getCk().getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime() * 2) { POP_LOGGER.warn("[PopBuffer] ck offset long time not commit, {}", pointWrapper); } break; } } final int qs = queue.size(); count += qs; if (qs > 5000 && scanTimes % countOfSecond1 == 0) { POP_LOGGER.info("[PopBuffer] offset queue size too long, {}, {}", entry.getKey(), qs); } } return count; } public long getLatestOffset(String lockKey) { QueueWithTime queue = this.commitOffsets.get(lockKey); if (queue == null) { return -1; } PopCheckPointWrapper pointWrapper = queue.get().peekLast(); if (pointWrapper != null) { return pointWrapper.getNextBeginOffset(); } return -1; } public long getLatestOffset(String topic, String group, int queueId) { return getLatestOffset(KeyBuilder.buildPollingKey(topic, group, queueId)); } private void scanGarbage() { Iterator>> iterator = commitOffsets.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); if (entry.getKey() == null) { continue; } String[] keyArray = entry.getKey().split(PopAckConstants.SPLIT); if (keyArray == null || keyArray.length != 3) { continue; } String topic = keyArray[0]; String cid = keyArray[1]; if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) { POP_LOGGER.info("[PopBuffer]remove nonexistent topic {} in buffer!", topic); iterator.remove(); continue; } if (!brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(cid)) { POP_LOGGER.info("[PopBuffer]remove nonexistent subscription group {} of topic {} in buffer!", cid, topic); iterator.remove(); continue; } if (System.currentTimeMillis() - entry.getValue().getTime() > minute5) { POP_LOGGER.info("[PopBuffer]remove long time not used sub {} of topic {} in buffer!", cid, topic); iterator.remove(); continue; } } } private boolean isSubscriptionGroupNotExist(PopCheckPointWrapper pointWrapper) { String group = pointWrapper.getCk().getCId(); return brokerController.getSubscriptionGroupManager() .findSubscriptionGroupConfig(group) == null; } private void scan() { long startTime = System.currentTimeMillis(); AtomicInteger count = new AtomicInteger(0); int countCk = 0; Iterator> iterator = buffer.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); PopCheckPointWrapper pointWrapper = entry.getValue(); // Skip invalid POP records when consumer group does not exist if (isSubscriptionGroupNotExist(pointWrapper)) { POP_LOGGER.warn( "[PopBuffer] skip pop record because consumer group not exist, group={}, ck={}", pointWrapper.getCk().getCId(), pointWrapper ); iterator.remove(); counter.decrementAndGet(); continue; } // just process offset(already stored at pull thread), or buffer ck(not stored and ack finish) if (pointWrapper.isJustOffset() && pointWrapper.isCkStored() || isCkDone(pointWrapper) || isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]ck done, {}", pointWrapper); } iterator.remove(); counter.decrementAndGet(); continue; } PopCheckPoint point = pointWrapper.getCk(); long now = System.currentTimeMillis(); boolean removeCk = !this.serving; // ck will be timeout if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut()) { removeCk = true; } // the time stayed is too long if (now - point.getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime()) { removeCk = true; } if (now - point.getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime() * 2L) { POP_LOGGER.warn("[PopBuffer]ck finish fail, stay too long, {}", pointWrapper); } // double check if (isCkDone(pointWrapper)) { continue; } else if (pointWrapper.isJustOffset()) { // just offset should be in store. if (pointWrapper.getReviveQueueOffset() < 0) { putCkToStore(pointWrapper, this.brokerController.getBrokerConfig().isAppendCkAsync()); countCk++; } continue; } else if (removeCk) { // put buffer ak to store if (pointWrapper.getReviveQueueOffset() < 0) { putCkToStore(pointWrapper, this.brokerController.getBrokerConfig().isAppendCkAsync()); countCk++; } if (!pointWrapper.isCkStored()) { continue; } if (brokerController.getBrokerConfig().isEnablePopBatchAck()) { List indexList = this.batchAckIndexList; try { for (byte i = 0; i < point.getNum(); i++) { // reput buffer ak to store if (DataConverter.getBit(pointWrapper.getBits().get(), i) && !DataConverter.getBit(pointWrapper.getToStoreBits().get(), i)) { indexList.add(i); } } if (indexList.size() > 0) { putBatchAckToStore(pointWrapper, indexList, count); } } finally { indexList.clear(); } } else { for (byte i = 0; i < point.getNum(); i++) { // reput buffer ak to store if (DataConverter.getBit(pointWrapper.getBits().get(), i) && !DataConverter.getBit(pointWrapper.getToStoreBits().get(), i)) { putAckToStore(pointWrapper, i, count); } } } if (isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]ck finish, {}", pointWrapper); } iterator.remove(); counter.decrementAndGet(); } } } int offsetBufferSize = scanCommitOffset(); long eclipse = System.currentTimeMillis() - startTime; if (eclipse > brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() - 1000) { POP_LOGGER.warn("[PopBuffer]scan stop, because eclipse too long, PopBufferEclipse={}, " + "PopBufferToStoreAck={}, PopBufferToStoreCk={}, PopBufferSize={}, PopBufferOffsetSize={}", eclipse, count.get(), countCk, counter.get(), offsetBufferSize); this.serving = false; } else { if (scanTimes % countOfSecond1 == 0) { POP_LOGGER.info("[PopBuffer]scan, PopBufferEclipse={}, " + "PopBufferToStoreAck={}, PopBufferToStoreCk={}, PopBufferSize={}, PopBufferOffsetSize={}", eclipse, count.get(), countCk, counter.get(), offsetBufferSize); } } brokerController.getBrokerMetricsManager().getPopMetricsManager().recordPopBufferScanTimeConsume(eclipse); scanTimes++; if (scanTimes >= countOfMinute1) { counter.set(this.buffer.size()); scanTimes = 0; } } public int getOffsetTotalSize() { int count = 0; Iterator>> iterator = this.commitOffsets.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); LinkedBlockingDeque queue = entry.getValue().get(); count += queue.size(); } return count; } public int getBufferedCKSize() { return this.counter.get(); } private void markBitCAS(AtomicInteger setBits, int index) { while (true) { int bits = setBits.get(); if (DataConverter.getBit(bits, index)) { break; } int newBits = DataConverter.setBit(bits, index, true); if (setBits.compareAndSet(bits, newBits)) { break; } } } private boolean commitOffset(final PopCheckPointWrapper wrapper) { if (wrapper.getNextBeginOffset() < 0) { return true; } final PopCheckPoint popCheckPoint = wrapper.getCk(); final String lockKey = wrapper.getLockKey(); if (!queueLockManager.tryLock(lockKey)) { return false; } try { final long offset = brokerController.getConsumerOffsetManager().queryOffset(popCheckPoint.getCId(), popCheckPoint.getTopic(), popCheckPoint.getQueueId()); if (wrapper.getNextBeginOffset() > offset) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("Commit offset, {}, {}", wrapper, offset); } } else { // maybe store offset is not correct. POP_LOGGER.warn("Commit offset, consumer offset less than store, {}, {}", wrapper, offset); } brokerController.getConsumerOffsetManager().commitOffset(getServiceName(), popCheckPoint.getCId(), popCheckPoint.getTopic(), popCheckPoint.getQueueId(), wrapper.getNextBeginOffset()); } finally { queueLockManager.unLock(lockKey); } return true; } private boolean putOffsetQueue(PopCheckPointWrapper pointWrapper) { QueueWithTime queue = this.commitOffsets.get(pointWrapper.getLockKey()); if (queue == null) { queue = new QueueWithTime<>(); QueueWithTime old = this.commitOffsets.putIfAbsent(pointWrapper.getLockKey(), queue); if (old != null) { queue = old; } } queue.setTime(pointWrapper.getCk().getPopTime()); return queue.get().offer(pointWrapper); } private boolean checkQueueOk(PopCheckPointWrapper pointWrapper) { QueueWithTime queue = this.commitOffsets.get(pointWrapper.getLockKey()); if (queue == null) { return true; } return queue.get().size() < brokerController.getBrokerConfig().getPopCkOffsetMaxQueueSize(); } /** * put to store && add to buffer. * * @param point * @param reviveQueueId * @param reviveQueueOffset * @param nextBeginOffset * @return */ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) { PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, reviveQueueOffset, point, nextBeginOffset, true); if (this.buffer.containsKey(pointWrapper.getMergeKey())) { // when mergeKey conflict // will cause PopBufferMergeService.scanCommitOffset cannot poll PopCheckPointWrapper POP_LOGGER.warn("[PopBuffer]mergeKey conflict when add ckJustOffset. ck:{}, mergeKey:{}", pointWrapper, pointWrapper.getMergeKey()); return false; } this.putCkToStore(pointWrapper, checkQueueOk(pointWrapper)); putOffsetQueue(pointWrapper); this.buffer.put(pointWrapper.getMergeKey(), pointWrapper); this.counter.incrementAndGet(); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]add ck just offset, {}", pointWrapper); } return true; } public void addCkMock(String group, String topic, int queueId, long startOffset, long invisibleTime, long popTime, int reviveQueueId, long nextBeginOffset, String brokerName) { final PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); ck.setNum((byte) 0); ck.setPopTime(popTime); ck.setInvisibleTime(invisibleTime); ck.setStartOffset(startOffset); ck.setCId(group); ck.setTopic(topic); ck.setQueueId(queueId); ck.setBrokerName(brokerName); PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, Long.MAX_VALUE, ck, nextBeginOffset, true); pointWrapper.setCkStored(true); putOffsetQueue(pointWrapper); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]add ck just offset, mocked, {}", pointWrapper); } } public boolean addCk(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) { // key: point.getT() + point.getC() + point.getQ() + point.getSo() + point.getPt() if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) { return false; } if (!serving) { return false; } long now = System.currentTimeMillis(); if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() + 1500) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.warn("[PopBuffer]add ck, timeout, {}, {}", point, now); } return false; } if (this.counter.get() > brokerController.getBrokerConfig().getPopCkMaxBufferSize()) { POP_LOGGER.warn("[PopBuffer]add ck, max size, {}, {}", point, this.counter.get()); return false; } PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, reviveQueueOffset, point, nextBeginOffset); if (!checkQueueOk(pointWrapper)) { return false; } if (this.buffer.containsKey(pointWrapper.getMergeKey())) { // when mergeKey conflict // will cause PopBufferMergeService.scanCommitOffset cannot poll PopCheckPointWrapper POP_LOGGER.warn("[PopBuffer]mergeKey conflict when add ck. ck:{}, mergeKey:{}", pointWrapper, pointWrapper.getMergeKey()); return false; } putOffsetQueue(pointWrapper); this.buffer.put(pointWrapper.getMergeKey(), pointWrapper); this.counter.incrementAndGet(); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]add ck, {}", pointWrapper); } return true; } public boolean addAk(int reviveQid, AckMsg ackMsg) { if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) { return false; } if (!serving) { return false; } try { PopCheckPointWrapper pointWrapper = this.buffer.get(ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime() + ackMsg.getBrokerName()); if (pointWrapper == null) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.warn("[PopBuffer]add ack fail, rqId={}, no ck, {}", reviveQid, ackMsg); } return false; } if (pointWrapper.isJustOffset()) { return false; } PopCheckPoint point = pointWrapper.getCk(); long now = System.currentTimeMillis(); if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() + 1500) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.warn("[PopBuffer]add ack fail, rqId={}, almost timeout for revive, {}, {}, {}", reviveQid, pointWrapper, ackMsg, now); } return false; } if (now - point.getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime() - 1500) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.warn("[PopBuffer]add ack fail, rqId={}, stay too long, {}, {}, {}", reviveQid, pointWrapper, ackMsg, now); } return false; } if (ackMsg instanceof BatchAckMsg) { for (Long ackOffset : ((BatchAckMsg) ackMsg).getAckOffsetList()) { int indexOfAck = point.indexOfAck(ackOffset); if (indexOfAck > -1) { markBitCAS(pointWrapper.getBits(), indexOfAck); } else { POP_LOGGER.error("[PopBuffer]Invalid index of ack, reviveQid={}, {}, {}", reviveQid, ackMsg, point); } } } else { int indexOfAck = point.indexOfAck(ackMsg.getAckOffset()); if (indexOfAck > -1) { markBitCAS(pointWrapper.getBits(), indexOfAck); } else { POP_LOGGER.error("[PopBuffer]Invalid index of ack, reviveQid={}, {}, {}", reviveQid, ackMsg, point); return true; } } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]add ack, rqId={}, {}, {}", reviveQid, pointWrapper, ackMsg); } // // check ak done // if (isCkDone(pointWrapper)) { // // cancel ck for timer // cancelCkTimer(pointWrapper); // } return true; } catch (Throwable e) { POP_LOGGER.error("[PopBuffer]add ack error, rqId=" + reviveQid + ", " + ackMsg, e); } return false; } public void clearOffsetQueue(String lockKey) { this.commitOffsets.remove(lockKey); } private void putCkToStore(final PopCheckPointWrapper pointWrapper, final boolean runInCurrent) { if (pointWrapper.getReviveQueueOffset() >= 0) { return; } MessageExtBrokerInner msgInner = popMessageProcessor.buildCkMsg(pointWrapper.getCk(), pointWrapper.getReviveQueueId()); // Indicates that ck message is storing pointWrapper.setReviveQueueOffset(Long.MAX_VALUE); if (brokerController.getBrokerConfig().isAppendCkAsync() && runInCurrent) { brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handleCkMessagePutResult(putMessageResult, pointWrapper); }).exceptionally(throwable -> { POP_LOGGER.error("[PopBuffer]put ck to store fail: {}", pointWrapper, throwable); pointWrapper.setReviveQueueOffset(-1); return null; }); } else { PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); handleCkMessagePutResult(putMessageResult, pointWrapper); } } private void handleCkMessagePutResult(PutMessageResult putMessageResult, final PopCheckPointWrapper pointWrapper) { brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveCkPutCount(pointWrapper.getCk(), putMessageResult.getPutMessageStatus()); if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { pointWrapper.setReviveQueueOffset(-1); POP_LOGGER.error("[PopBuffer]put ck to store fail: {}, {}", pointWrapper, putMessageResult); return; } pointWrapper.setCkStored(true); if (putMessageResult.isRemotePut()) { //No AppendMessageResult when escaping remotely pointWrapper.setReviveQueueOffset(0); } else { pointWrapper.setReviveQueueOffset(putMessageResult.getAppendMessageResult().getLogicsOffset()); } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]put ck to store ok: {}, {}", pointWrapper, putMessageResult); } } private void putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgIndex, AtomicInteger count) { PopCheckPoint point = pointWrapper.getCk(); MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); final AckMsg ackMsg = new AckMsg(); ackMsg.setAckOffset(point.ackOffsetByIndex(msgIndex)); ackMsg.setStartOffset(point.getStartOffset()); ackMsg.setConsumerGroup(point.getCId()); ackMsg.setTopic(point.getTopic()); ackMsg.setQueueId(point.getQueueId()); ackMsg.setPopTime(point.getPopTime()); ackMsg.setBrokerName(point.getBrokerName()); msgInner.setTopic(popMessageProcessor.getReviveTopic()); msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8)); msgInner.setQueueId(pointWrapper.getReviveQueueId()); msgInner.setTags(PopAckConstants.ACK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(brokerController.getStoreHost()); msgInner.setStoreHost(brokerController.getStoreHost()); msgInner.setDeliverTimeMs(point.getReviveTime()); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); if (brokerController.getBrokerConfig().isAppendAckAsync()) { brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handleAckPutMessageResult(ackMsg, putMessageResult, pointWrapper, count, msgIndex); }).exceptionally(throwable -> { POP_LOGGER.error("[PopBuffer]put ack to store fail: {}, {}", pointWrapper, ackMsg, throwable); return null; }); } else { PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); handleAckPutMessageResult(ackMsg, putMessageResult, pointWrapper, count, msgIndex); } } private void handleAckPutMessageResult(AckMsg ackMsg, PutMessageResult putMessageResult, PopCheckPointWrapper pointWrapper, AtomicInteger count, byte msgIndex) { brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus()); if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { POP_LOGGER.error("[PopBuffer]put ack to store fail: {}, {}, {}", pointWrapper, ackMsg, putMessageResult); return; } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]put ack to store ok: {}, {}, {}", pointWrapper, ackMsg, putMessageResult); } count.incrementAndGet(); markBitCAS(pointWrapper.getToStoreBits(), msgIndex); } private void putBatchAckToStore(final PopCheckPointWrapper pointWrapper, final List msgIndexList, AtomicInteger count) { PopCheckPoint point = pointWrapper.getCk(); MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); final BatchAckMsg batchAckMsg = new BatchAckMsg(); for (Byte msgIndex : msgIndexList) { batchAckMsg.getAckOffsetList().add(point.ackOffsetByIndex(msgIndex)); } batchAckMsg.setStartOffset(point.getStartOffset()); batchAckMsg.setConsumerGroup(point.getCId()); batchAckMsg.setTopic(point.getTopic()); batchAckMsg.setQueueId(point.getQueueId()); batchAckMsg.setPopTime(point.getPopTime()); msgInner.setTopic(popMessageProcessor.getReviveTopic()); msgInner.setBody(JSON.toJSONString(batchAckMsg).getBytes(DataConverter.CHARSET_UTF8)); msgInner.setQueueId(pointWrapper.getReviveQueueId()); msgInner.setTags(PopAckConstants.BATCH_ACK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(brokerController.getStoreHost()); msgInner.setStoreHost(brokerController.getStoreHost()); msgInner.setDeliverTimeMs(point.getReviveTime()); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genBatchAckUniqueId(batchAckMsg)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); if (brokerController.getBrokerConfig().isAppendAckAsync()) { brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handleBatchAckPutMessageResult(batchAckMsg, putMessageResult, pointWrapper, count, msgIndexList); }).exceptionally(throwable -> { POP_LOGGER.error("[PopBuffer]put batchAckMsg to store fail: {}, {}", pointWrapper, batchAckMsg, throwable); return null; }); } else { PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); handleBatchAckPutMessageResult(batchAckMsg, putMessageResult, pointWrapper, count, msgIndexList); } } private void handleBatchAckPutMessageResult(BatchAckMsg batchAckMsg, PutMessageResult putMessageResult, PopCheckPointWrapper pointWrapper, AtomicInteger count, List msgIndexList) { if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { POP_LOGGER.error("[PopBuffer]put batch ack to store fail: {}, {}, {}", pointWrapper, batchAckMsg, putMessageResult); return; } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]put batch ack to store ok: {}, {}, {}", pointWrapper, batchAckMsg, putMessageResult); } count.addAndGet(msgIndexList.size()); for (Byte i : msgIndexList) { markBitCAS(pointWrapper.getToStoreBits(), i); } } private boolean cancelCkTimer(final PopCheckPointWrapper pointWrapper) { // not stored, no need cancel if (pointWrapper.getReviveQueueOffset() < 0) { return true; } PopCheckPoint point = pointWrapper.getCk(); MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(popMessageProcessor.getReviveTopic()); msgInner.setBody((pointWrapper.getReviveQueueId() + "-" + pointWrapper.getReviveQueueOffset()).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(pointWrapper.getReviveQueueId()); msgInner.setTags(PopAckConstants.CK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(brokerController.getStoreHost()); msgInner.setStoreHost(brokerController.getStoreHost()); msgInner.setDeliverTimeMs(point.getReviveTime() - PopAckConstants.ackTimeInterval); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { POP_LOGGER.error("[PopBuffer]PutMessageCallback cancelCheckPoint fail, {}, {}", pointWrapper, putMessageResult); return false; } if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]cancelCheckPoint, {}", pointWrapper); } return true; } private boolean isCkDone(PopCheckPointWrapper pointWrapper) { byte num = pointWrapper.getCk().getNum(); for (byte i = 0; i < num; i++) { if (!DataConverter.getBit(pointWrapper.getBits().get(), i)) { return false; } } return true; } private boolean isCkDoneForFinish(PopCheckPointWrapper pointWrapper) { byte num = pointWrapper.getCk().getNum(); int bits = pointWrapper.getBits().get() ^ pointWrapper.getToStoreBits().get(); for (byte i = 0; i < num; i++) { if (DataConverter.getBit(bits, i)) { return false; } } return true; } public class QueueWithTime { private final LinkedBlockingDeque queue; private long time; public QueueWithTime() { this.queue = new LinkedBlockingDeque<>(); this.time = System.currentTimeMillis(); } public void setTime(long popTime) { this.time = popTime; } public long getTime() { return time; } public LinkedBlockingDeque get() { return queue; } } public class PopCheckPointWrapper { private final int reviveQueueId; // -1: not stored, >=0: stored, Long.MAX: storing. private volatile long reviveQueueOffset; private final PopCheckPoint ck; // bit for concurrent private final AtomicInteger bits; // bit for stored buffer ak private final AtomicInteger toStoreBits; private final long nextBeginOffset; private final String lockKey; private final String mergeKey; private final boolean justOffset; private volatile boolean ckStored = false; public PopCheckPointWrapper(int reviveQueueId, long reviveQueueOffset, PopCheckPoint point, long nextBeginOffset) { this.reviveQueueId = reviveQueueId; this.reviveQueueOffset = reviveQueueOffset; this.ck = point; this.bits = new AtomicInteger(0); this.toStoreBits = new AtomicInteger(0); this.nextBeginOffset = nextBeginOffset; this.lockKey = ck.getTopic() + PopAckConstants.SPLIT + ck.getCId() + PopAckConstants.SPLIT + ck.getQueueId(); this.mergeKey = point.getTopic() + point.getCId() + point.getQueueId() + point.getStartOffset() + point.getPopTime() + point.getBrokerName(); this.justOffset = false; } public PopCheckPointWrapper(int reviveQueueId, long reviveQueueOffset, PopCheckPoint point, long nextBeginOffset, boolean justOffset) { this.reviveQueueId = reviveQueueId; this.reviveQueueOffset = reviveQueueOffset; this.ck = point; this.bits = new AtomicInteger(0); this.toStoreBits = new AtomicInteger(0); this.nextBeginOffset = nextBeginOffset; this.lockKey = ck.getTopic() + PopAckConstants.SPLIT + ck.getCId() + PopAckConstants.SPLIT + ck.getQueueId(); this.mergeKey = point.getTopic() + point.getCId() + point.getQueueId() + point.getStartOffset() + point.getPopTime() + point.getBrokerName(); this.justOffset = justOffset; } public int getReviveQueueId() { return reviveQueueId; } public long getReviveQueueOffset() { return reviveQueueOffset; } public boolean isCkStored() { return ckStored; } public void setReviveQueueOffset(long reviveQueueOffset) { this.reviveQueueOffset = reviveQueueOffset; } public PopCheckPoint getCk() { return ck; } public AtomicInteger getBits() { return bits; } public AtomicInteger getToStoreBits() { return toStoreBits; } public long getNextBeginOffset() { return nextBeginOffset; } public String getLockKey() { return lockKey; } public String getMergeKey() { return mergeKey; } public boolean isJustOffset() { return justOffset; } public void setCkStored(boolean ckStored) { this.ckStored = ckStored; } @Override public String toString() { final StringBuilder sb = new StringBuilder("CkWrap{"); sb.append("rq=").append(reviveQueueId); sb.append(", rqo=").append(reviveQueueOffset); sb.append(", ck=").append(ck); sb.append(", bits=").append(bits); sb.append(", sBits=").append(toStoreBits); sb.append(", nbo=").append(nextBeginOffset); sb.append(", cks=").append(ckStored); sb.append(", jo=").append(justOffset); sb.append('}'); return sb.toString(); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.pop.PopCheckPoint; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; public class PopInflightMessageCounter { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final String TOPIC_GROUP_SEPARATOR = "@"; private final Map> topicInFlightMessageNum = new ConcurrentHashMap<>(512); private final BrokerController brokerController; public PopInflightMessageCounter(BrokerController brokerController) { this.brokerController = brokerController; } public void incrementInFlightMessageNum(String topic, String group, int queueId, int num) { if (num <= 0) { return; } topicInFlightMessageNum.compute(buildKey(topic, group), (key, queueNum) -> { if (queueNum == null) { queueNum = new ConcurrentHashMap<>(8); } queueNum.compute(queueId, (queueIdKey, counter) -> { if (counter == null) { return new AtomicLong(num); } if (counter.addAndGet(num) <= 0) { return null; } return counter; }); return queueNum; }); } public void decrementInFlightMessageNum(String topic, String group, long popTime, int qId, int delta) { if (popTime < this.brokerController.getShouldStartTime()) { return; } decrementInFlightMessageNum(topic, group, qId, delta); } public void decrementInFlightMessageNum(PopCheckPoint checkPoint) { if (checkPoint.getPopTime() < this.brokerController.getShouldStartTime()) { return; } decrementInFlightMessageNum(checkPoint.getTopic(), checkPoint.getCId(), checkPoint.getQueueId(), 1); } private void decrementInFlightMessageNum(String topic, String group, int queueId, int delta) { topicInFlightMessageNum.computeIfPresent(buildKey(topic, group), (key, queueNum) -> { queueNum.computeIfPresent(queueId, (queueIdKey, counter) -> { if (counter.addAndGet(-delta) <= 0) { return null; } return counter; }); if (queueNum.isEmpty()) { return null; } return queueNum; }); } public void clearInFlightMessageNumByGroupName(String group) { Set topicGroupKey = this.topicInFlightMessageNum.keySet(); for (String key : topicGroupKey) { if (key.contains(group)) { Pair topicAndGroup = splitKey(key); if (topicAndGroup != null && topicAndGroup.getObject2().equals(group)) { this.topicInFlightMessageNum.remove(key); log.info("PopInflightMessageCounter#clearInFlightMessageNumByGroupName: clean by group, topic={}, group={}", topicAndGroup.getObject1(), topicAndGroup.getObject2()); } } } } public void clearInFlightMessageNumByTopicName(String topic) { Set topicGroupKey = this.topicInFlightMessageNum.keySet(); for (String key : topicGroupKey) { if (key.contains(topic)) { Pair topicAndGroup = splitKey(key); if (topicAndGroup != null && topicAndGroup.getObject1().equals(topic)) { this.topicInFlightMessageNum.remove(key); log.info("PopInflightMessageCounter#clearInFlightMessageNumByTopicName: clean by topic, topic={}, group={}", topicAndGroup.getObject1(), topicAndGroup.getObject2()); } } } } public void clearInFlightMessageNum(String topic, String group, int queueId) { topicInFlightMessageNum.computeIfPresent(buildKey(topic, group), (key, queueNum) -> { queueNum.computeIfPresent(queueId, (queueIdKey, counter) -> null); if (queueNum.isEmpty()) { return null; } return queueNum; }); } public long getGroupPopInFlightMessageNum(String topic, String group, int queueId) { Map queueCounter = topicInFlightMessageNum.get(buildKey(topic, group)); if (queueCounter == null) { return 0; } AtomicLong counter = queueCounter.get(queueId); if (counter == null) { return 0; } return Math.max(0, counter.get()); } private static Pair splitKey(String key) { String[] strings = key.split(TOPIC_GROUP_SEPARATOR); if (strings.length != 2) { return null; } return new Pair<>(strings[0], strings[1]); } private static String buildKey(String topic, String group) { return topic + TOPIC_GROUP_SEPARATOR + group; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PopLiteMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.google.common.annotations.VisibleForTesting; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.opentelemetry.api.common.Attributes; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.LiteEventDispatcher; import org.apache.rocketmq.broker.longpolling.PollingResult; import org.apache.rocketmq.broker.longpolling.PopLiteLongPollingService; import org.apache.rocketmq.broker.metrics.LiteConsumerLagCalculator; import org.apache.rocketmq.broker.offset.MemoryConsumerOrderInfoManager; import org.apache.rocketmq.broker.pop.PopConsumerLockService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_RETRY; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; /** * Pop lite implementation, support FIFO consuming. * This processor uses independent in-memory consumer order info and lock service, * along with a specialized long polling service. */ public class PopLiteMessageProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LITE_LOGGER_NAME); private static final String BORN_TIME = "bornTime"; private final BrokerController brokerController; private final PopLiteLongPollingService popLiteLongPollingService; private final PopConsumerLockService lockService; private final LiteEventDispatcher liteEventDispatcher; private final ConsumerOrderInfoManager consumerOrderInfoManager; private final PopLiteLockManager popLiteLockManager; public PopLiteMessageProcessor(final BrokerController brokerController, LiteEventDispatcher liteEventDispatcher) { this.brokerController = brokerController; this.popLiteLongPollingService = new PopLiteLongPollingService(brokerController, this, false); this.lockService = new PopConsumerLockService(TimeUnit.MINUTES.toMillis(1)); this.liteEventDispatcher = liteEventDispatcher; this.consumerOrderInfoManager = new MemoryConsumerOrderInfoManager(brokerController); this.popLiteLockManager = new PopLiteLockManager(); } @Override public boolean rejectRequest() { return false; } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final long beginTimeMills = brokerController.getMessageStore().now(); Channel channel = ctx.channel(); request.addExtFieldIfNotExist(BORN_TIME, String.valueOf(System.currentTimeMillis())); if (Objects.equals(request.getExtFields().get(BORN_TIME), "0")) { request.addExtField(BORN_TIME, String.valueOf(System.currentTimeMillis())); } RemotingCommand response = RemotingCommand.createResponseCommand(PopLiteMessageResponseHeader.class); response.setOpaque(request.getOpaque()); final PopLiteMessageRequestHeader requestHeader = request.decodeCommandCustomHeader(PopLiteMessageRequestHeader.class, true); final PopLiteMessageResponseHeader responseHeader = (PopLiteMessageResponseHeader) response.readCustomHeader(); RemotingCommand preCheckResponse = preCheck(ctx, requestHeader, response); if (preCheckResponse != null) { return preCheckResponse; } String clientId = requestHeader.getClientId(); String group = requestHeader.getConsumerGroup(); String parentTopic = requestHeader.getTopic(); int maxNum = requestHeader.getMaxMsgNum(); long popTime = System.currentTimeMillis(); long invisibleTime = requestHeader.getInvisibleTime(); Pair rst = popByClientId(channel.remoteAddress().toString(), parentTopic, group, clientId, popTime, invisibleTime, maxNum, requestHeader.getAttemptId()); final GetMessageResult getMessageResult = rst.getObject2(); if (getMessageResult != null && getMessageResult.getMessageCount() > 0) { final byte[] r = readGetMessageResult(getMessageResult); brokerController.getBrokerStatsManager().incGroupGetLatency(group, parentTopic, 0, (int) (brokerController.getMessageStore().now() - beginTimeMills)); brokerController.getBrokerStatsManager().incBrokerGetNums(parentTopic, getMessageResult.getMessageCount()); brokerController.getBrokerStatsManager().incGroupGetNums(group, parentTopic, getMessageResult.getMessageCount()); brokerController.getBrokerStatsManager().incGroupGetSize(group, parentTopic, getMessageResult.getBufferTotalSize()); response.setCode(ResponseCode.SUCCESS); response.setRemark(GetMessageStatus.FOUND.name()); response.setBody(r); } else { response.setRemark(GetMessageStatus.NO_MESSAGE_IN_QUEUE.name()); PollingResult pollingResult = popLiteLongPollingService.polling(ctx, request, requestHeader.getBornTime(), requestHeader.getPollTime(), clientId, group); if (PollingResult.POLLING_SUC.equals(pollingResult)) { return null; } else if (PollingResult.POLLING_FULL.equals(pollingResult)) { response.setCode(ResponseCode.POLLING_FULL); } else { response.setCode(ResponseCode.POLLING_TIMEOUT); } } responseHeader.setPopTime(popTime); responseHeader.setInvisibleTime(invisibleTime); responseHeader.setReviveQid(KeyBuilder.POP_ORDER_REVIVE_QUEUE); responseHeader.setOrderCountInfo(rst.getObject1().toString()); // Since a single read operation potentially retrieving messages from multiple LMQs, // we no longer utilize startOffset and msgOffset NettyRemotingAbstract.writeResponse(channel, request, response, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager()); return null; } @VisibleForTesting public RemotingCommand preCheck(ChannelHandlerContext ctx, PopLiteMessageRequestHeader requestHeader, RemotingCommand response) { if (requestHeader.isTimeoutTooMuch()) { response.setCode(ResponseCode.POLLING_TIMEOUT); response.setRemark(String.format("the broker[%s] pop message is timeout too much", brokerController.getBrokerConfig().getBrokerIP1())); return response; } if (!PermName.isReadable(brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the broker[%s] pop message is forbidden", brokerController.getBrokerConfig().getBrokerIP1())); return response; } if (requestHeader.getMaxMsgNum() > 32) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("the broker[%s] pop message's num is greater than 32", brokerController.getBrokerConfig().getBrokerIP1())); return response; } TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { LOGGER.error("The parentTopic {} not exist, consumer: {} ", requestHeader.getTopic()); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic [%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the topic [%s] peeking message is forbidden", requestHeader.getTopic())); return response; } if (!TopicMessageType.LITE.equals(topicConfig.getTopicMessageType())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("the topic [%s] message type not match", requestHeader.getTopic())); return response; } SubscriptionGroupConfig subscriptionGroupConfig = brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("subscription group [%s] not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST))); return response; } if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } if (!requestHeader.getTopic().equals(subscriptionGroupConfig.getLiteBindTopic())) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark("subscription bind topic not match, " + requestHeader.getConsumerGroup()); return response; } return null; } private byte[] readGetMessageResult(GetMessageResult getMessageResult) { final ByteBuffer byteBuffer = ByteBuffer.allocate(getMessageResult.getBufferTotalSize()); try { List messageBufferList = getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { byteBuffer.put(bb); } } finally { getMessageResult.release(); } return byteBuffer.array(); } public Pair popByClientId(String clientHost, String parentTopic, String group, String clientId, long popTime, long invisibleTime, int maxNum, String attemptId) { GetMessageResult getMessageResult = new GetMessageResult(); StringBuilder orderCountInfoAll = new StringBuilder(); AtomicLong total = new AtomicLong(0); Set processed = new HashSet<>(); // deduplication in one request Iterator iterator = liteEventDispatcher.getEventIterator(clientId); while (total.get() < maxNum && iterator.hasNext()) { String lmqName = iterator.next(); // here event represents a lmq name if (null == lmqName) { break; } if (!processed.add(lmqName)) { continue; // wait for next pop request or re-fetch in current process, here prefer the former approach } Pair pair = popLiteTopic(parentTopic, clientHost, group, lmqName, maxNum - total.get(), popTime, invisibleTime, attemptId); if (null == pair || pair.getObject2().getMessageCount() <= 0) { continue; } GetMessageResult singleResult = pair.getObject2(); total.addAndGet(singleResult.getMessageCount()); for (SelectMappedBufferResult mappedBuffer : singleResult.getMessageMapedList()) { getMessageResult.addMessage(mappedBuffer); } if (orderCountInfoAll.length() > 0) { orderCountInfoAll.append(";"); } orderCountInfoAll.append(pair.getObject1()); collectLiteConsumerLagMetrics(group, parentTopic, lmqName, singleResult, maxNum, total); } return new Pair<>(orderCountInfoAll, getMessageResult); } @VisibleForTesting public Pair popLiteTopic(String parentTopic, String clientHost, String group, String lmqName, long maxNum, long popTime, long invisibleTime, String attemptId) { if (!brokerController.getBrokerConfig().isEnableLiteEventMode() && !brokerController.getLiteLifecycleManager().isLmqExist(lmqName)) { return null; } String lockKey = KeyBuilder.buildPopLiteLockKey(group, lmqName); if (!lockService.tryLock(lockKey)) { return null; } try { if (isFifoBlocked(attemptId, group, lmqName, invisibleTime)) { return null; } final long consumeOffset = getPopOffset(group, lmqName); GetMessageResult result = getMessage(clientHost, group, lmqName, consumeOffset, (int) maxNum); return handleGetMessageResult(result, parentTopic, group, lmqName, popTime, invisibleTime, attemptId); } catch (Throwable e) { LOGGER.error("popLiteTopic error. {}, {}", group, lmqName, e); } finally { lockService.unlock(lockKey); } return null; } public boolean isFifoBlocked(String attemptId, String group, String lmqName, long invisibleTime) { return consumerOrderInfoManager.checkBlock(attemptId, lmqName, group, 0, invisibleTime); } public long getPopOffset(String group, String lmqName) { long offset = brokerController.getConsumerOffsetManager().queryOffset(group, lmqName, 0); if (offset < 0L) { try { offset = brokerController.getPopMessageProcessor().getInitOffset(lmqName, group, 0, ConsumeInitMode.MAX, true); // reuse code, init as max LOGGER.info("init offset, group:{}, topic:{}, offset:{}", group, lmqName, offset); } catch (ConsumeQueueException e) { throw new RuntimeException(e); } } Long resetOffset = brokerController.getConsumerOffsetManager().queryThenEraseResetOffset(lmqName, group, 0); if (resetOffset != null) { consumerOrderInfoManager.clearBlock(lmqName, group, 0); brokerController.getConsumerOffsetManager().commitOffset("ResetOffset", group, lmqName, 0, resetOffset); LOGGER.info("find resetOffset, group:{}, topic:{}, resetOffset:{}", group, lmqName, resetOffset); return resetOffset; } return offset; } public Pair handleGetMessageResult(GetMessageResult result, String parentTopic, String group, String lmqName, long popTime, long invisibleTime, String attemptId) { if (null == result) { return null; } StringBuilder orderCountInfo = new StringBuilder(); if (GetMessageStatus.FOUND.equals(result.getStatus()) && !result.getMessageQueueOffset().isEmpty()) { consumerOrderInfoManager.update(attemptId, false, lmqName, group, 0, popTime, invisibleTime, result.getMessageQueueOffset(), orderCountInfo, null); recordPopLiteMetrics(result, parentTopic, group); orderCountInfo = transformOrderCountInfo(orderCountInfo, result.getMessageCount()); } return new Pair<>(orderCountInfo, result); } /** * For order count information, we use a uniform format of one consume count per offset. */ @VisibleForTesting public StringBuilder transformOrderCountInfo(StringBuilder orderCountInfo, int msgCount) { if (null == orderCountInfo || orderCountInfo.length() <= 0) { return new StringBuilder(String.join(";", Collections.nCopies(msgCount, "0"))); } String infoStr = orderCountInfo.toString(); String[] infos = infoStr.split(";"); if (infos.length > 1) { // consume count of each offset + ";" + consume count of queueId return new StringBuilder(infoStr.substring(0, infoStr.lastIndexOf(";"))); } else { // just consume count of queueId, like "0 0 N" String[] split = orderCountInfo.toString().split(MessageConst.KEY_SEPARATOR); if (split.length == 3) { return new StringBuilder(String.join(";", Collections.nCopies(msgCount, split[2]))); } else { return new StringBuilder(String.join(";", Collections.nCopies(msgCount, "0"))); } } } @VisibleForTesting protected void recordPopLiteMetrics(GetMessageResult result, String parentTopic, String group) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, parentTopic) .put(LABEL_CONSUMER_GROUP, group) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(parentTopic) || MixAll.isSysConsumerGroup(group)) .put(LABEL_IS_RETRY, false) .build(); this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(result.getMessageCount(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(result.getBufferTotalSize(), attributes); } private void collectLiteConsumerLagMetrics(String group, String topic, String liteTopic, GetMessageResult getResult, long maxNum, AtomicLong total) { if (!brokerController.getBrokerConfig().isLiteLagLatencyCollectEnable()) { return; } try { final LiteConsumerLagCalculator lagCalculator = brokerController.getBrokerMetricsManager() .getLiteConsumerLagCalculator(); if (total.get() < maxNum) { // Batch not full, no consume lag lagCalculator.removeLagInfo(group, topic, liteTopic); return; } // Batch full, check for potential consume lag long storeTimestamp = brokerController.getMessageStore() .getMessageStoreTimeStamp(liteTopic, 0, getResult.getNextBeginOffset()); if (storeTimestamp > 0) { lagCalculator.updateLagInfo(group, topic, liteTopic, storeTimestamp); } else { // no next msg, no consume lag lagCalculator.removeLagInfo(group, topic, liteTopic); } } catch (Exception e) { LOGGER.warn("Failed to collect lite consumer lag metrics for group={}, topic={}, liteTopic={}", group, topic, liteTopic, e); } } // tiered store ensures reading lmq from local storage public GetMessageResult getMessage(String clientHost, String group, String lmqName, long offset, int batchSize) { GetMessageResult result = brokerController.getMessageStore().getMessage(group, lmqName, 0, offset, batchSize, null); if (null == result) { return null; } if (GetMessageStatus.OFFSET_TOO_SMALL.equals(result.getStatus()) || GetMessageStatus.OFFSET_OVERFLOW_BADLY.equals(result.getStatus()) || GetMessageStatus.OFFSET_FOUND_NULL.equals(result.getStatus()) || GetMessageStatus.NO_MATCHED_MESSAGE.equals(result.getStatus()) || GetMessageStatus.MESSAGE_WAS_REMOVING.equals(result.getStatus()) || GetMessageStatus.NO_MATCHED_LOGIC_QUEUE.equals(result.getStatus())) { long correctOffset = result.getNextBeginOffset(); // >=0 brokerController.getConsumerOffsetManager().commitOffset("CorrectOffset", group, lmqName, 0, correctOffset); LOGGER.warn("correct offset, {}, {}, from {} to {}", group, lmqName, offset, correctOffset); return brokerController.getMessageStore().getMessage(group, lmqName, 0, correctOffset, batchSize, null); } return result; } public class PopLiteLockManager extends ServiceThread { @Override public String getServiceName() { if (brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + PopLiteLockManager.class.getSimpleName(); } return PopLiteLockManager.class.getSimpleName(); } @Override public void run() { while (!isStopped()) { try { waitForRunning(60000); lockService.removeTimeout(); } catch (Exception ignored) { } } } } public PopLiteLongPollingService getPopLiteLongPollingService() { return popLiteLongPollingService; } public PopConsumerLockService getLockService() { return lockService; } public ConsumerOrderInfoManager getConsumerOrderInfoManager() { return consumerOrderInfoManager; } public void startPopLiteLockManager() { popLiteLockManager.start(); } public void stopPopLiteLockManager() { popLiteLockManager.shutdown(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import com.github.benmanes.caffeine.cache.Cache; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.FileRegion; import io.opentelemetry.api.common.Attributes; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ConsumerFilterManager; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.broker.longpolling.PollingHeader; import org.apache.rocketmq.broker.longpolling.PollingResult; import org.apache.rocketmq.broker.longpolling.PopLongPollingService; import org.apache.rocketmq.broker.longpolling.PopRequest; import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer; import org.apache.rocketmq.broker.pop.PopConsumerContext; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.CommandCallback; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.BatchAckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_RETRY; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_REQUEST_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESPONSE_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESULT; public class PopMessageProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private static final String BORN_TIME = "bornTime"; private final BrokerController brokerController; private final Random random = new Random(System.currentTimeMillis()); private final String reviveTopic; private final PopLongPollingService popLongPollingService; private final PopBufferMergeService popBufferMergeService; private final QueueLockManager queueLockManager; private final AtomicLong ckMessageNumber; public PopMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; this.reviveTopic = PopAckConstants.buildClusterReviveTopic( this.brokerController.getBrokerConfig().getBrokerClusterName()); this.popLongPollingService = new PopLongPollingService(brokerController, this, false); this.queueLockManager = new QueueLockManager(); this.popBufferMergeService = new PopBufferMergeService(this.brokerController, this); this.ckMessageNumber = new AtomicLong(); } public void shutdown() throws Exception { popLongPollingService.shutdown(); queueLockManager.shutdown(); popBufferMergeService.shutdown(); } protected String getReviveTopic() { return reviveTopic; } public PopLongPollingService getPopLongPollingService() { return popLongPollingService; } public PopBufferMergeService getPopBufferMergeService() { return this.popBufferMergeService; } public QueueLockManager getQueueLockManager() { return queueLockManager; } public static String genAckUniqueId(AckMsg ackMsg) { return ackMsg.getTopic() + PopAckConstants.SPLIT + ackMsg.getQueueId() + PopAckConstants.SPLIT + ackMsg.getAckOffset() + PopAckConstants.SPLIT + ackMsg.getConsumerGroup() + PopAckConstants.SPLIT + ackMsg.getPopTime() + PopAckConstants.SPLIT + ackMsg.getBrokerName() + PopAckConstants.SPLIT + PopAckConstants.ACK_TAG; } public static String genBatchAckUniqueId(BatchAckMsg batchAckMsg) { return batchAckMsg.getTopic() + PopAckConstants.SPLIT + batchAckMsg.getQueueId() + PopAckConstants.SPLIT + batchAckMsg.getAckOffsetList().toString() + PopAckConstants.SPLIT + batchAckMsg.getConsumerGroup() + PopAckConstants.SPLIT + batchAckMsg.getPopTime() + PopAckConstants.SPLIT + PopAckConstants.BATCH_ACK_TAG; } public static String genCkUniqueId(PopCheckPoint ck) { return ck.getTopic() + PopAckConstants.SPLIT + ck.getQueueId() + PopAckConstants.SPLIT + ck.getStartOffset() + PopAckConstants.SPLIT + ck.getCId() + PopAckConstants.SPLIT + ck.getPopTime() + PopAckConstants.SPLIT + ck.getBrokerName() + PopAckConstants.SPLIT + PopAckConstants.CK_TAG; } @Override public boolean rejectRequest() { return false; } public Cache> getPollingMap() { return popLongPollingService.getPollingMap(); } public void notifyLongPollingRequestIfNeed(String topic, String group, int queueId) throws ConsumeQueueException { this.notifyLongPollingRequestIfNeed( topic, group, queueId, null, 0L, null, null); } public void notifyLongPollingRequestIfNeed(String topic, String group, int queueId, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) throws ConsumeQueueException { long popBufferOffset = this.brokerController.getPopMessageProcessor().getPopBufferMergeService().getLatestOffset(topic, group, queueId); long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset(group, topic, queueId); long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); long offset = Math.max(popBufferOffset, consumerOffset); if (maxOffset > offset) { boolean notifySuccess = popLongPollingService.notifyMessageArriving( topic, -1, group, tagsCode, msgStoreTime, filterBitMap, properties); if (!notifySuccess) { // notify pop queue notifySuccess = popLongPollingService.notifyMessageArriving( topic, queueId, group, tagsCode, msgStoreTime, filterBitMap, properties); } this.brokerController.getNotificationProcessor().notifyMessageArriving(topic, queueId); if (this.brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("notify long polling request. topic:{}, group:{}, queueId:{}, success:{}", topic, group, queueId, notifySuccess); } } } public void notifyMessageArriving(final String topic, final int queueId, long offset, Long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { popLongPollingService.notifyMessageArrivingWithRetryTopic( topic, queueId, offset, tagsCode, msgStoreTime, filterBitMap, properties); } public void notifyMessageArriving(final String topic, final int queueId, final String cid) { popLongPollingService.notifyMessageArriving( topic, queueId, cid, false, null, 0L, null, null); } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final long beginTimeMills = this.brokerController.getMessageStore().now(); Channel channel = ctx.channel(); RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); response.setOpaque(request.getOpaque()); final PopMessageRequestHeader requestHeader = request.decodeCommandCustomHeader(PopMessageRequestHeader.class, true); if (requestHeader.getBornTime() == 0) { request.addExtField(BORN_TIME, String.valueOf(beginTimeMills)); requestHeader.setBornTime(beginTimeMills); } final PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); // Pop mode only supports consumption in cluster load balancing mode brokerController.getConsumerManager().compensateBasicConsumerInfo( requestHeader.getConsumerGroup(), ConsumeType.CONSUME_POP, MessageModel.CLUSTERING); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("receive PopMessage request command, {}", request); } if (requestHeader.isTimeoutTooMuch()) { response.setCode(ResponseCode.POLLING_TIMEOUT); response.setRemark(String.format("the broker[%s] pop message is timeout too much", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark(String.format("the broker[%s] pop message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } if (requestHeader.getMaxMsgNums() > 32) { response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(String.format("the broker[%s] pop message's num is greater than 32", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } if (!brokerController.getMessageStore().getMessageStoreConfig().isTimerWheelEnable()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("the broker[%s] pop message is forbidden because timerWheelEnable is false", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("the topic[" + requestHeader.getTopic() + "] peeking message is forbidden"); return response; } if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] " + "consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); POP_LOGGER.warn(errorInfo); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(errorInfo); return response; } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST))); return response; } if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } BrokerConfig brokerConfig = brokerController.getBrokerConfig(); SubscriptionData subscriptionData = null; ExpressionMessageFilter messageFilter = null; if (requestHeader.getExp() != null && !requestHeader.getExp().isEmpty()) { try { // origin topic subscriptionData = FilterAPI.build( requestHeader.getTopic(), requestHeader.getExp(), requestHeader.getExpType()); brokerController.getConsumerManager().compensateSubscribeData( requestHeader.getConsumerGroup(), requestHeader.getTopic(), subscriptionData); // retry topic String retryTopic = KeyBuilder.buildPopRetryTopic( requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2()); SubscriptionData retrySubscriptionData = FilterAPI.build( retryTopic, SubscriptionData.SUB_ALL, requestHeader.getExpType()); brokerController.getConsumerManager().compensateSubscribeData( requestHeader.getConsumerGroup(), retryTopic, retrySubscriptionData); ConsumerFilterData consumerFilterData = null; if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) { consumerFilterData = ConsumerFilterManager.build( requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getExp(), requestHeader.getExpType(), System.currentTimeMillis()); if (consumerFilterData == null) { POP_LOGGER.warn("Parse the consumer's subscription[{}] failed, group: {}", requestHeader.getExp(), requestHeader.getConsumerGroup()); response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED); response.setRemark("parse the consumer's subscription failed"); return response; } } messageFilter = new ExpressionMessageFilter( subscriptionData, consumerFilterData, brokerController.getConsumerFilterManager()); } catch (Exception e) { POP_LOGGER.warn("Parse the consumer's subscription[{}] error, group: {}", requestHeader.getExp(), requestHeader.getConsumerGroup()); response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED); response.setRemark("parse the consumer's subscription failed"); return response; } } else { try { // origin topic subscriptionData = FilterAPI.build(requestHeader.getTopic(), "*", ExpressionType.TAG); brokerController.getConsumerManager().compensateSubscribeData( requestHeader.getConsumerGroup(), requestHeader.getTopic(), subscriptionData); // retry topic String retryTopic = KeyBuilder.buildPopRetryTopic( requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2()); SubscriptionData retrySubscriptionData = FilterAPI.build(retryTopic, "*", ExpressionType.TAG); brokerController.getConsumerManager().compensateSubscribeData( requestHeader.getConsumerGroup(), retryTopic, retrySubscriptionData); } catch (Exception e) { POP_LOGGER.warn("Build default subscription error, group: {}", requestHeader.getConsumerGroup()); } } GetMessageResult getMessageResult = new GetMessageResult(requestHeader.getMaxMsgNums()); ExpressionMessageFilter finalMessageFilter = messageFilter; SubscriptionData finalSubscriptionData = subscriptionData; if (brokerConfig.isPopConsumerKVServiceEnable()) { CompletableFuture popAsyncFuture = brokerController.getPopConsumerService().popAsync( RemotingHelper.parseChannelRemoteAddr(channel), beginTimeMills, requestHeader.getInvisibleTime(), requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getMaxMsgNums(), requestHeader.isOrder(), requestHeader.getAttemptId(), requestHeader.getInitMode(), messageFilter); popAsyncFuture.thenApply(result -> { try { if (request.getCallbackList() != null) { request.getCallbackList().forEach(CommandCallback::accept); request.getCallbackList().clear(); } } catch (Throwable t) { POP_LOGGER.error("PopProcessor execute callback error", t); } if (result.isFound()) { response.setCode(ResponseCode.SUCCESS); getMessageResult.setStatus(GetMessageStatus.FOUND); // recursive processing if (result.getRestCount() > 0) { popLongPollingService.notifyMessageArriving( requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getConsumerGroup(), null, 0L, null, null); } } else { POP_LOGGER.debug("Processor not found, polling request, popTime={}, restCount={}", result.getPopTime(), result.getRestCount()); PollingResult pollingResult = popLongPollingService.polling( ctx, request, new PollingHeader(requestHeader), finalSubscriptionData, finalMessageFilter); if (PollingResult.POLLING_SUC == pollingResult) { // recursive processing if (result.getRestCount() > 0) { popLongPollingService.notifyMessageArriving( requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getConsumerGroup(), null, 0L, null, null); } return null; } else if (PollingResult.POLLING_FULL == pollingResult) { response.setCode(ResponseCode.POLLING_FULL); } else { response.setCode(ResponseCode.POLLING_TIMEOUT); } getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); } responseHeader.setPopTime(result.getPopTime()); responseHeader.setInvisibleTime(result.getInvisibleTime()); responseHeader.setReviveQid( requestHeader.isOrder() ? KeyBuilder.POP_ORDER_REVIVE_QUEUE : 0); responseHeader.setRestNum(result.getRestCount()); responseHeader.setStartOffsetInfo(result.getStartOffsetInfo()); responseHeader.setMsgOffsetInfo(result.getMsgOffsetInfo()); if (requestHeader.isOrder() && !result.getOrderCountInfo().isEmpty()) { responseHeader.setOrderCountInfo(result.getOrderCountInfo()); } response.setRemark(getMessageResult.getStatus().name()); if (response.getCode() != ResponseCode.SUCCESS) { return response; } // add message result.getGetMessageResultList().forEach(temp -> { for (int i = 0; i < temp.getMessageMapedList().size(); i++) { getMessageResult.addMessage(temp.getMessageMapedList().get(i)); } }); if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); this.brokerController.getBrokerStatsManager().incGroupGetLatency( requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), (int) (this.brokerController.getMessageStore().now() - beginTimeMills)); response.setBody(r); } else { final GetMessageResult tmpGetMessageResult = getMessageResult; try { FileRegion fileRegion = new ManyMessageTransfer( response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult); channel.writeAndFlush(fileRegion) .addListener((ChannelFutureListener) future -> { tmpGetMessageResult.release(); RemotingMetricsManager remotingMetricsManager = brokerController.getBrokerMetricsManager().getRemotingMetricsManager(); Attributes attributes = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(response.getCode())) .put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)) .build(); remotingMetricsManager.getRpcLatency().record( request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { POP_LOGGER.error("Fail to transfer messages from page cache to {}", channel.remoteAddress(), future.cause()); } }); } catch (Throwable e) { POP_LOGGER.error("Error occurred when transferring messages from page cache", e); getMessageResult.release(); } return null; } return response; }).thenAccept(result -> NettyRemotingAbstract.writeResponse(channel, request, result, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager())); return null; } int randomQ = random.nextInt(100); int reviveQid; if (requestHeader.isOrder()) { reviveQid = KeyBuilder.POP_ORDER_REVIVE_QUEUE; } else { reviveQid = (int) Math.abs(ckMessageNumber.getAndIncrement() % this.brokerController.getBrokerConfig().getReviveQueueNum()); } StringBuilder startOffsetInfo = new StringBuilder(64); StringBuilder msgOffsetInfo = new StringBuilder(64); StringBuilder orderCountInfo = requestHeader.isOrder() ? new StringBuilder(64) : null; // Due to the design of the fields startOffsetInfo, msgOffsetInfo, and orderCountInfo, // a single POP request could only invoke the popMsgFromQueue method once // for either a normal topic or a retry topic's queue. Retry topics v1 and v2 are // considered the same type because they share the same retry flag in previous fields. // Therefore, needRetryV1 is designed as a subset of needRetry, and within a single request, // only one type of retry topic is able to call popMsgFromQueue. boolean usePriorityMode = TopicMessageType.PRIORITY.equals(topicConfig.getTopicMessageType()) && !requestHeader.isOrder() && randomQ < subscriptionGroupConfig.getPriorityFactor(); boolean needRetry = randomQ < (usePriorityMode ? brokerConfig.getPopFromRetryProbabilityForPriority() : brokerConfig.getPopFromRetryProbability()); boolean needRetryV1 = false; if (brokerConfig.isEnableRetryTopicV2() && brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { needRetryV1 = randomQ % 2 == 0; } randomQ = usePriorityMode ? 0 : randomQ; // reset randomQ long popTime = System.currentTimeMillis(); CompletableFuture getMessageFuture = CompletableFuture.completedFuture(0L); if (needRetry && !requestHeader.isOrder()) { if (needRetryV1) { String retryTopic = KeyBuilder.buildPopRetryTopicV1(requestHeader.getTopic(), requestHeader.getConsumerGroup()); getMessageFuture = popMsgFromTopic(retryTopic, true, getMessageResult, requestHeader, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } else { String retryTopic = KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2()); getMessageFuture = popMsgFromTopic(retryTopic, true, getMessageResult, requestHeader, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } } if (requestHeader.getQueueId() < 0) { // read all queue getMessageFuture = popMsgFromTopic(topicConfig, false, getMessageResult, requestHeader, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } else { int queueId = requestHeader.getQueueId(); getMessageFuture = getMessageFuture.thenCompose(restNum -> popMsgFromQueue(topicConfig.getTopicName(), requestHeader.getAttemptId(), false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo)); } // if not full , fetch retry again if (!needRetry && getMessageResult.getMessageMapedList().size() < requestHeader.getMaxMsgNums() && !requestHeader.isOrder()) { if (needRetryV1) { String retryTopicV1 = KeyBuilder.buildPopRetryTopicV1(requestHeader.getTopic(), requestHeader.getConsumerGroup()); getMessageFuture = popMsgFromTopic(retryTopicV1, true, getMessageResult, requestHeader, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } else { String retryTopic = KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup(), brokerConfig.isEnableRetryTopicV2()); getMessageFuture = popMsgFromTopic(retryTopic, true, getMessageResult, requestHeader, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } } final RemotingCommand finalResponse = response; getMessageFuture.thenApply(restNum -> { try { if (request.getCallbackList() != null) { request.getCallbackList().forEach(CommandCallback::accept); request.getCallbackList().clear(); } } catch (Throwable t) { POP_LOGGER.error("PopProcessor execute callback error", t); } if (!getMessageResult.getMessageBufferList().isEmpty()) { finalResponse.setCode(ResponseCode.SUCCESS); getMessageResult.setStatus(GetMessageStatus.FOUND); if (restNum > 0) { // all queue pop can not notify specified queue pop, and vice versa popLongPollingService.notifyMessageArriving( requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getConsumerGroup(), null, 0L, null, null); } } else { PollingResult pollingResult = popLongPollingService.polling( ctx, request, new PollingHeader(requestHeader), finalSubscriptionData, finalMessageFilter); if (PollingResult.POLLING_SUC == pollingResult) { if (restNum > 0) { popLongPollingService.notifyMessageArriving( requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getConsumerGroup(), null, 0L, null, null); } return null; } else if (PollingResult.POLLING_FULL == pollingResult) { finalResponse.setCode(ResponseCode.POLLING_FULL); } else { finalResponse.setCode(ResponseCode.POLLING_TIMEOUT); } getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); } responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(reviveQid); responseHeader.setRestNum(restNum); responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); responseHeader.setMsgOffsetInfo(msgOffsetInfo.toString()); if (requestHeader.isOrder() && orderCountInfo != null) { responseHeader.setOrderCountInfo(orderCountInfo.toString()); } finalResponse.setRemark(getMessageResult.getStatus().name()); switch (finalResponse.getCode()) { case ResponseCode.SUCCESS: if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId()); this.brokerController.getBrokerStatsManager().incGroupGetLatency(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), (int) (this.brokerController.getMessageStore().now() - beginTimeMills)); finalResponse.setBody(r); } else { final GetMessageResult tmpGetMessageResult = getMessageResult; try { FileRegion fileRegion = new ManyMessageTransfer(finalResponse.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult); channel.writeAndFlush(fileRegion) .addListener((ChannelFutureListener) future -> { tmpGetMessageResult.release(); RemotingMetricsManager remotingMetricsManager = brokerController.getBrokerMetricsManager().getRemotingMetricsManager(); Attributes attributes = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(finalResponse.getCode())) .put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)) .build(); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { POP_LOGGER.error("Fail to transfer messages from page cache to {}", channel.remoteAddress(), future.cause()); } }); } catch (Throwable e) { POP_LOGGER.error("Error occurred when transferring messages from page cache", e); getMessageResult.release(); } return null; } break; default: return finalResponse; } return finalResponse; }).thenAccept(result -> NettyRemotingAbstract.writeResponse(channel, request, result, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager())); return null; } private CompletableFuture popMsgFromTopic(TopicConfig topicConfig, boolean isRetry, GetMessageResult getMessageResult, PopMessageRequestHeader requestHeader, int reviveQid, Channel channel, long popTime, ExpressionMessageFilter messageFilter, StringBuilder startOffsetInfo, StringBuilder msgOffsetInfo, StringBuilder orderCountInfo, int randomQ, CompletableFuture getMessageFuture) { if (topicConfig != null) { for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { int index = (brokerController.getBrokerConfig().isPriorityOrderAsc() ? topicConfig.getReadQueueNums() - 1 - i : i) + randomQ; int queueId = index % topicConfig.getReadQueueNums(); getMessageFuture = getMessageFuture.thenCompose(restNum -> popMsgFromQueue(topicConfig.getTopicName(), requestHeader.getAttemptId(), isRetry, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, messageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo)); } } return getMessageFuture; } private CompletableFuture popMsgFromTopic(String topic, boolean isRetry, GetMessageResult getMessageResult, PopMessageRequestHeader requestHeader, int reviveQid, Channel channel, long popTime, ExpressionMessageFilter messageFilter, StringBuilder startOffsetInfo, StringBuilder msgOffsetInfo, StringBuilder orderCountInfo, int randomQ, CompletableFuture getMessageFuture) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); return popMsgFromTopic(topicConfig, isRetry, getMessageResult, requestHeader, reviveQid, channel, popTime, messageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } private CompletableFuture popMsgFromQueue(String topic, String attemptId, boolean isRetry, GetMessageResult getMessageResult, PopMessageRequestHeader requestHeader, int queueId, long restNum, int reviveQid, Channel channel, long popTime, ExpressionMessageFilter messageFilter, StringBuilder startOffsetInfo, StringBuilder msgOffsetInfo, StringBuilder orderCountInfo) { String lockKey = topic + PopAckConstants.SPLIT + requestHeader.getConsumerGroup() + PopAckConstants.SPLIT + queueId; boolean isOrder = requestHeader.isOrder(); long offset; try { offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId, requestHeader.getInitMode(), false, lockKey, false); } catch (ConsumeQueueException e) { CompletableFuture failure = new CompletableFuture<>(); failure.completeExceptionally(e); return failure; } CompletableFuture future = new CompletableFuture<>(); if (!queueLockManager.tryLock(lockKey)) { try { if (!requestHeader.isOrder()) { restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum; } future.complete(restNum); } catch (ConsumeQueueException e) { future.completeExceptionally(e); } return future; } future.whenComplete((result, throwable) -> queueLockManager.unLock(lockKey)); if (isPopShouldStop(topic, requestHeader.getConsumerGroup(), queueId)) { POP_LOGGER.warn("Too much msgs unacked, then stop popping. topic={}, group={}, queueId={}", topic, requestHeader.getConsumerGroup(), queueId); try { restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum; future.complete(restNum); } catch (ConsumeQueueException e) { future.completeExceptionally(e); } return future; } try { offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId, requestHeader.getInitMode(), true, lockKey, true); // Current requests would calculate the total number of messages // waiting to be filtered for new message arrival notifications in // the long-polling service, need disregarding the backlog in order // consumption scenario. If rest message num including the blocked // queue accumulation would lead to frequent unnecessary wake-ups // of long-polling requests, resulting unnecessary CPU usage. // When client ack message, long-polling request would be notifications // by AckMessageProcessor.ackOrderly() and message will not be delayed. if (isOrder) { if (brokerController.getConsumerOrderInfoManager().checkBlock( attemptId, topic, requestHeader.getConsumerGroup(), queueId, requestHeader.getInvisibleTime())) { // should not add accumulation(max offset - consumer offset) here future.complete(restNum); return future; } this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNum( topic, requestHeader.getConsumerGroup(), queueId); } if (getMessageResult.getMessageMapedList().size() >= requestHeader.getMaxMsgNums()) { restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum; future.complete(restNum); return future; } } catch (Exception e) { POP_LOGGER.error("Exception in popMsgFromQueue", e); future.complete(restNum); return future; } AtomicLong atomicRestNum = new AtomicLong(restNum); AtomicLong atomicOffset = new AtomicLong(offset); long finalOffset = offset; return this.brokerController.getMessageStore() .getMessageAsync(requestHeader.getConsumerGroup(), topic, queueId, offset, requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), messageFilter) .thenCompose(result -> { if (result == null) { return CompletableFuture.completedFuture(null); } // maybe store offset is not correct. if (GetMessageStatus.OFFSET_TOO_SMALL.equals(result.getStatus()) || GetMessageStatus.OFFSET_OVERFLOW_BADLY.equals(result.getStatus()) || GetMessageStatus.OFFSET_FOUND_NULL.equals(result.getStatus())) { // commit offset, because the offset is not correct // If offset in store is greater than cq offset, it will cause duplicate messages, // because offset in PopBuffer is not committed. POP_LOGGER.warn("Pop initial offset, because store is no correct, {}, {}->{}", lockKey, atomicOffset.get(), result.getNextBeginOffset()); this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(), requestHeader.getConsumerGroup(), topic, queueId, result.getNextBeginOffset()); atomicOffset.set(result.getNextBeginOffset()); return this.brokerController.getMessageStore().getMessageAsync(requestHeader.getConsumerGroup(), topic, queueId, atomicOffset.get(), requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), messageFilter); } return CompletableFuture.completedFuture(result); }).thenApply(result -> { if (result == null) { try { atomicRestNum.set(brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - atomicOffset.get() + atomicRestNum.get()); } catch (ConsumeQueueException e) { POP_LOGGER.error("Failed to get max offset in queue", e); } return atomicRestNum.get(); } if (!result.getMessageMapedList().isEmpty()) { this.brokerController.getBrokerStatsManager().incBrokerGetNums(requestHeader.getTopic(), result.getMessageCount()); this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), topic, result.getMessageCount()); this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), topic, result.getBufferTotalSize()); Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, requestHeader.getTopic()) .put(LABEL_CONSUMER_GROUP, requestHeader.getConsumerGroup()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(requestHeader.getTopic()) || MixAll.isSysConsumerGroup(requestHeader.getConsumerGroup())) .put(LABEL_IS_RETRY, isRetry) .build(); this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(result.getMessageCount(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(result.getBufferTotalSize(), attributes); if (isOrder) { this.brokerController.getConsumerOrderInfoManager().update(requestHeader.getAttemptId(), isRetry, topic, requestHeader.getConsumerGroup(), queueId, popTime, requestHeader.getInvisibleTime(), result.getMessageQueueOffset(), orderCountInfo, result); this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(), requestHeader.getConsumerGroup(), topic, queueId, finalOffset); } else { if (!appendCheckPoint(requestHeader, topic, reviveQid, queueId, finalOffset, result, popTime, this.brokerController.getBrokerConfig().getBrokerName())) { return atomicRestNum.get() + result.getMessageCount(); } } ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, topic, queueId, finalOffset); ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, topic, queueId, result.getMessageQueueOffset()); } else if ((GetMessageStatus.NO_MATCHED_MESSAGE.equals(result.getStatus()) || GetMessageStatus.OFFSET_FOUND_NULL.equals(result.getStatus()) || GetMessageStatus.MESSAGE_WAS_REMOVING.equals(result.getStatus()) || GetMessageStatus.NO_MATCHED_LOGIC_QUEUE.equals(result.getStatus())) && result.getNextBeginOffset() > -1) { if (isOrder) { this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(), requestHeader.getConsumerGroup(), topic, queueId, result.getNextBeginOffset()); } else { popBufferMergeService.addCkMock(requestHeader.getConsumerGroup(), topic, queueId, finalOffset, requestHeader.getInvisibleTime(), popTime, reviveQid, result.getNextBeginOffset(), brokerController.getBrokerConfig().getBrokerName()); } } atomicRestNum.set(result.getMaxOffset() - result.getNextBeginOffset() + atomicRestNum.get()); String brokerName = brokerController.getBrokerConfig().getBrokerName(); for (SelectMappedBufferResult mapedBuffer : result.getMessageMapedList()) { // We should not recode buffer when popResponseReturnActualRetryTopic is true or topic is not retry topic if (brokerController.getBrokerConfig().isPopResponseReturnActualRetryTopic() || !isRetry) { getMessageResult.addMessage(mapedBuffer); } else { List messageExtList = MessageDecoder.decodesBatch(mapedBuffer.getByteBuffer(), true, false, true); mapedBuffer.release(); for (MessageExt messageExt : messageExtList) { try { String ckInfo = ExtraInfoUtil.buildExtraInfo(finalOffset, popTime, requestHeader.getInvisibleTime(), reviveQid, messageExt.getTopic(), brokerName, messageExt.getQueueId(), messageExt.getQueueOffset()); messageExt.getProperties().putIfAbsent(MessageConst.PROPERTY_POP_CK, ckInfo); // Set retry message topic to origin topic and clear message store size to recode messageExt.setTopic(requestHeader.getTopic()); messageExt.setStoreSize(0); byte[] encode = MessageDecoder.encode(messageExt, false); ByteBuffer buffer = ByteBuffer.wrap(encode); SelectMappedBufferResult tmpResult = new SelectMappedBufferResult(mapedBuffer.getStartOffset(), buffer, encode.length, null); getMessageResult.addMessage(tmpResult); } catch (Exception e) { POP_LOGGER.error("Exception in recode retry message buffer, topic={}", topic, e); } } } } this.brokerController.getPopInflightMessageCounter().incrementInFlightMessageNum( topic, requestHeader.getConsumerGroup(), queueId, result.getMessageCount() ); return atomicRestNum.get(); }).whenComplete((result, throwable) -> { if (throwable != null) { POP_LOGGER.error("Pop message error, {}", lockKey, throwable); } queueLockManager.unLock(lockKey); }); } private boolean isPopShouldStop(String topic, String group, int queueId) { return brokerController.getBrokerConfig().isEnablePopMessageThreshold() && brokerController.getPopInflightMessageCounter().getGroupPopInFlightMessageNum(topic, group, queueId) > brokerController.getBrokerConfig().getPopInflightMessageThreshold(); } private long getPopOffset(String topic, String group, int queueId, int initMode, boolean init, String lockKey, boolean checkResetOffset) throws ConsumeQueueException { long offset = this.brokerController.getConsumerOffsetManager().queryOffset(group, topic, queueId); if (offset < 0) { offset = this.getInitOffset(topic, group, queueId, initMode, init); } if (checkResetOffset) { Long resetOffset = resetPopOffset(topic, group, queueId); if (resetOffset != null) { return resetOffset; } } long bufferOffset = this.popBufferMergeService.getLatestOffset(lockKey); if (bufferOffset < 0) { return offset; } else { return Math.max(bufferOffset, offset); } } public long getInitOffset(String topic, String group, int queueId, int initMode, boolean init) throws ConsumeQueueException { long offset; if (ConsumeInitMode.MIN == initMode || topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { offset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId); } else { if (this.brokerController.getBrokerConfig().isInitPopOffsetByCheckMsgInMem() && this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId) <= 0 && this.brokerController.getMessageStore().checkInMemByConsumeOffset(topic, queueId, 0, 1)) { offset = 0; } else { // pop last one,then commit offset. offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - 1; // max & no consumer offset if (offset < 0) { offset = 0; } } } if (init) { // whichever initMode this.brokerController.getConsumerOffsetManager().commitOffset( "getPopOffset", group, topic, queueId, offset); } return offset; } public MessageExtBrokerInner buildCkMsg(final PopCheckPoint ck, final int reviveQid) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(reviveTopic); msgInner.setBody(JSON.toJSONString(ck).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(reviveQid); msgInner.setTags(PopAckConstants.CK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(this.brokerController.getStoreHost()); msgInner.setStoreHost(this.brokerController.getStoreHost()); msgInner.setDeliverTimeMs(ck.getReviveTime() - PopAckConstants.ackTimeInterval); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, genCkUniqueId(ck)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return msgInner; } private boolean appendCheckPoint(final PopMessageRequestHeader requestHeader, final String topic, final int reviveQid, final int queueId, final long offset, final GetMessageResult getMessageTmpResult, final long popTime, final String brokerName) { // add check point msg to revive log final PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); ck.setNum((byte) getMessageTmpResult.getMessageMapedList().size()); ck.setPopTime(popTime); ck.setInvisibleTime(requestHeader.getInvisibleTime()); ck.setStartOffset(offset); ck.setCId(requestHeader.getConsumerGroup()); ck.setTopic(topic); ck.setQueueId(queueId); ck.setBrokerName(brokerName); for (Long msgQueueOffset : getMessageTmpResult.getMessageQueueOffset()) { ck.addDiff((int) (msgQueueOffset - offset)); } this.brokerController.getBrokerStatsManager().incBrokerCkNums(1); this.brokerController.getBrokerStatsManager().incGroupCkNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), 1); final boolean addBufferSuc = this.popBufferMergeService.addCk( ck, reviveQid, -1, getMessageTmpResult.getNextBeginOffset() ); if (addBufferSuc) { return true; } return this.popBufferMergeService.addCkJustOffset( ck, reviveQid, -1, getMessageTmpResult.getNextBeginOffset() ); } private Long resetPopOffset(String topic, String group, int queueId) { String lockKey = topic + PopAckConstants.SPLIT + group + PopAckConstants.SPLIT + queueId; Long resetOffset = this.brokerController.getConsumerOffsetManager().queryThenEraseResetOffset(topic, group, queueId); if (resetOffset != null) { this.brokerController.getConsumerOrderInfoManager().clearBlock(topic, group, queueId); this.getPopBufferMergeService().clearOffsetQueue(lockKey); this.brokerController.getConsumerOffsetManager() .commitOffset("ResetPopOffset", group, topic, queueId, resetOffset); } return resetOffset; } private byte[] readGetMessageResult(final GetMessageResult getMessageResult, final String group, final String topic, final int queueId) { final ByteBuffer byteBuffer = ByteBuffer.allocate(getMessageResult.getBufferTotalSize()); long storeTimestamp = 0; try { List messageBufferList = getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { byteBuffer.put(bb); storeTimestamp = bb.getLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION); } } finally { getMessageResult.release(); } this.brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId, this.brokerController.getMessageStore().now() - storeTimestamp); return byteBuffer.array(); } static class TimedLock { private final AtomicBoolean lock; private volatile long lockTime; public TimedLock() { // init lock status, false means not locked this.lock = new AtomicBoolean(false); this.lockTime = System.currentTimeMillis(); } public boolean tryLock() { boolean ret = lock.compareAndSet(false, true); if (ret) { this.lockTime = System.currentTimeMillis(); return true; } else { return false; } } public void unLock() { lock.set(false); } public boolean isLock() { return lock.get(); } public long getLockTime() { return lockTime; } } public class QueueLockManager extends ServiceThread { private final ConcurrentHashMap expiredLocalCache = new ConcurrentHashMap<>(100000); public String buildLockKey(String topic, String consumerGroup, int queueId) { return topic + PopAckConstants.SPLIT + consumerGroup + PopAckConstants.SPLIT + queueId; } public boolean tryLock(String topic, String consumerGroup, int queueId) { return tryLock(buildLockKey(topic, consumerGroup, queueId)); } public boolean tryLock(String key) { TimedLock timedLock = ConcurrentHashMapUtils.computeIfAbsent(expiredLocalCache, key, k -> new TimedLock()); return timedLock.tryLock(); } /** * is not thread safe, may cause duplicate lock * * @param usedExpireMillis the expired time in millisecond * @return total numbers of TimedLock */ public int cleanUnusedLock(final long usedExpireMillis) { Iterator> iterator = expiredLocalCache.entrySet().iterator(); int total = 0; while (iterator.hasNext()) { Entry entry = iterator.next(); if (System.currentTimeMillis() - entry.getValue().getLockTime() > usedExpireMillis) { iterator.remove(); POP_LOGGER.info("Remove unused queue lock: {}, {}, {}", entry.getKey(), entry.getValue().getLockTime(), entry.getValue().isLock()); } total++; } return total; } public void unLock(String topic, String consumerGroup, int queueId) { unLock(buildLockKey(topic, consumerGroup, queueId)); } public void unLock(String key) { TimedLock timedLock = expiredLocalCache.get(key); if (timedLock != null) { timedLock.unLock(); } } @Override public String getServiceName() { if (PopMessageProcessor.this.brokerController.getBrokerConfig().isInBrokerContainer()) { return PopMessageProcessor.this.brokerController.getBrokerIdentity().getIdentifier() + QueueLockManager.class.getSimpleName(); } return QueueLockManager.class.getSimpleName(); } @Override public void run() { while (!isStopped()) { try { this.waitForRunning(60000); int count = cleanUnusedLock(60000); POP_LOGGER.info("QueueLockSize={}", count); } catch (Exception e) { PopMessageProcessor.POP_LOGGER.error("QueueLockManager run error", e); } } } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import io.opentelemetry.api.common.Attributes; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.DataConverter; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.BatchAckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; public class PopReviveService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final int[] ckRewriteIntervalsInSeconds = new int[] { 10, 20, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200 }; private int queueId; private BrokerController brokerController; private String reviveTopic; private long currentReviveMessageTimestamp = -1; private volatile boolean shouldRunPopRevive = false; private final NavigableMap> inflightReviveRequestMap = Collections.synchronizedNavigableMap(new TreeMap<>()); private long reviveOffset; public PopReviveService(BrokerController brokerController, String reviveTopic, int queueId) { this.queueId = queueId; this.brokerController = brokerController; this.reviveTopic = reviveTopic; this.reviveOffset = brokerController.getConsumerOffsetManager().queryOffset(PopAckConstants.REVIVE_GROUP, reviveTopic, queueId); } @Override public String getServiceName() { if (brokerController != null && brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + "PopReviveService_" + this.queueId; } return "PopReviveService_" + this.queueId; } public int getQueueId() { return queueId; } public void setShouldRunPopRevive(final boolean shouldRunPopRevive) { this.shouldRunPopRevive = shouldRunPopRevive; } public boolean isShouldRunPopRevive() { return shouldRunPopRevive; } private boolean reviveRetry(PopCheckPoint popCheckPoint, MessageExt messageExt) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); if (!popCheckPoint.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { msgInner.setTopic(KeyBuilder.buildPopRetryTopic(popCheckPoint.getTopic(), popCheckPoint.getCId(), brokerController.getBrokerConfig().isEnableRetryTopicV2())); } else { msgInner.setTopic(popCheckPoint.getTopic()); } msgInner.setBody(messageExt.getBody()); if (messageExt.getTags() != null) { msgInner.setTags(messageExt.getTags()); } else { MessageAccessor.setProperties(msgInner, new HashMap<>()); } msgInner.setBornTimestamp(messageExt.getBornTimestamp()); msgInner.setFlag(messageExt.getFlag()); msgInner.setSysFlag(messageExt.getSysFlag()); msgInner.setBornHost(brokerController.getStoreHost()); msgInner.setStoreHost(brokerController.getStoreHost()); if (popCheckPoint.isSuspend()) { msgInner.setReconsumeTimes(messageExt.getReconsumeTimes()); } else { msgInner.setReconsumeTimes(messageExt.getReconsumeTimes() + 1); } msgInner.getProperties().putAll(messageExt.getProperties()); if (messageExt.getReconsumeTimes() == 0 || msgInner.getProperties().get(MessageConst.PROPERTY_FIRST_POP_TIME) == null) { msgInner.getProperties().put(MessageConst.PROPERTY_FIRST_POP_TIME, String.valueOf(popCheckPoint.getPopTime())); } msgInner.getProperties().put(MessageConst.PROPERTY_ORIGIN_GROUP, popCheckPoint.getCId()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); addRetryTopicIfNotExist(msgInner.getTopic(), popCheckPoint.getCId()); msgInner.setQueueId(getRetryQueueId(msgInner.getTopic(), messageExt)); PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveRetryMessageCount(popCheckPoint, putMessageResult.getPutMessageStatus()); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("reviveQueueId={},retry msg, ck={}, msg queueId {}, offset {}, reviveDelay={}, result is {} ", queueId, popCheckPoint, messageExt.getQueueId(), messageExt.getQueueOffset(), (System.currentTimeMillis() - popCheckPoint.getReviveTime()) / 1000, putMessageResult); } if (putMessageResult.getAppendMessageResult() == null || putMessageResult.getAppendMessageResult().getStatus() != AppendMessageStatus.PUT_OK) { POP_LOGGER.error("reviveQueueId={}, revive error, msg is: {}", queueId, msgInner); return false; } this.brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(popCheckPoint); this.brokerController.getBrokerStatsManager().incBrokerPutNums(popCheckPoint.getTopic(), 1); this.brokerController.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic()); this.brokerController.getBrokerStatsManager().incTopicPutSize(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); return true; } private void initPopRetryOffset(String retryTopic, String consumerGroup, int retryQueueNum) { for (int i = 0; i < retryQueueNum; i++) { long offset = this.brokerController.getConsumerOffsetManager().queryOffset(consumerGroup, retryTopic, i); if (offset < 0) { this.brokerController.getConsumerOffsetManager().commitOffset("initPopRetryOffset", consumerGroup, retryTopic, i, 0); } } } public void addRetryTopicIfNotExist(String retryTopic, String consumerGroup) { if (brokerController != null) { TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(retryTopic); if (topicConfig != null && !brokerController.getBrokerConfig().isUseSeparateRetryQueue()) { return; } int retryQueueNum = PopAckConstants.retryQueueNum; if (brokerController.getBrokerConfig().isUseSeparateRetryQueue()) { String normalTopic = KeyBuilder.parseNormalTopic(retryTopic, consumerGroup); TopicConfig normalConfig = brokerController.getTopicConfigManager().selectTopicConfig(normalTopic); // always exists retryQueueNum = normalConfig.getWriteQueueNums(); if (topicConfig != null && topicConfig.getWriteQueueNums() == normalConfig.getWriteQueueNums()) { return; } } // create new one, or update in case of queue expansion topicConfig = new TopicConfig(retryTopic); topicConfig.setReadQueueNums(retryQueueNum); topicConfig.setWriteQueueNums(retryQueueNum); topicConfig.setTopicFilterType(TopicFilterType.SINGLE_TAG); topicConfig.setPerm(6); topicConfig.setTopicSysFlag(0); brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); initPopRetryOffset(retryTopic, consumerGroup, retryQueueNum); } } private int getRetryQueueId(String retryTopic, MessageExt messageExt) { if (!brokerController.getBrokerConfig().isUseSeparateRetryQueue()) { return 0; } int oriQueueId = messageExt.getQueueId(); // original qid of normal or retry topic if (oriQueueId > brokerController.getTopicConfigManager().selectTopicConfig(retryTopic).getWriteQueueNums() - 1) { POP_LOGGER.warn("not expected, {}, {}, {}", retryTopic, oriQueueId, messageExt.getMsgId()); return 0; // fallback } return oriQueueId; } protected List getReviveMessage(long offset, int queueId) { PullResult pullResult = getMessage(PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, offset, 32, true); if (pullResult == null) { return null; } if (reachTail(pullResult, offset)) { if (this.brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("reviveQueueId={}, reach tail,offset {}", queueId, offset); } } else if (pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL || pullResult.getPullStatus() == PullStatus.NO_MATCHED_MSG) { POP_LOGGER.error("reviveQueueId={}, OFFSET_ILLEGAL {}, result is {}", queueId, offset, pullResult); if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip offset correct topic={}, reviveQueueId={}", reviveTopic, queueId); return null; } this.brokerController.getConsumerOffsetManager().commitOffset(PopAckConstants.LOCAL_HOST, PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, pullResult.getNextBeginOffset() - 1); } return pullResult.getMsgFoundList(); } private boolean reachTail(PullResult pullResult, long offset) { return pullResult.getPullStatus() == PullStatus.NO_NEW_MSG || pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL && offset == pullResult.getMaxOffset(); } // Triple public CompletableFuture> getBizMessage(PopCheckPoint popCheckPoint, long offset) { return this.brokerController.getEscapeBridge().getMessageAsync(popCheckPoint.getTopic(), offset, popCheckPoint.getQueueId(), popCheckPoint.getBrokerName(), false); } public PullResult getMessage(String group, String topic, int queueId, long offset, int nums, boolean deCompressBody) { GetMessageResult getMessageResult = this.brokerController.getMessageStore().getMessage(group, topic, queueId, offset, nums, null); if (getMessageResult != null) { PullStatus pullStatus = PullStatus.NO_NEW_MSG; List foundList = null; switch (getMessageResult.getStatus()) { case FOUND: pullStatus = PullStatus.FOUND; foundList = decodeMsgList(getMessageResult, deCompressBody); brokerController.getBrokerStatsManager().incGroupGetNums(group, topic, getMessageResult.getMessageCount()); brokerController.getBrokerStatsManager().incGroupGetSize(group, topic, getMessageResult.getBufferTotalSize()); brokerController.getBrokerStatsManager().incBrokerGetNums(topic, getMessageResult.getMessageCount()); brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId, brokerController.getMessageStore().now() - foundList.get(foundList.size() - 1).getStoreTimestamp()); Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, topic) .put(LABEL_CONSUMER_GROUP, group) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic) || MixAll.isSysConsumerGroup(group)) .build(); this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(getMessageResult.getMessageCount(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(getMessageResult.getBufferTotalSize(), attributes); break; case NO_MATCHED_MESSAGE: pullStatus = PullStatus.NO_MATCHED_MSG; POP_LOGGER.debug("no matched message. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}", getMessageResult.getStatus(), topic, group, offset); break; case NO_MESSAGE_IN_QUEUE: POP_LOGGER.debug("no new message. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}", getMessageResult.getStatus(), topic, group, offset); break; case MESSAGE_WAS_REMOVING: case NO_MATCHED_LOGIC_QUEUE: case OFFSET_FOUND_NULL: case OFFSET_OVERFLOW_BADLY: case OFFSET_TOO_SMALL: pullStatus = PullStatus.OFFSET_ILLEGAL; POP_LOGGER.warn("offset illegal. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}", getMessageResult.getStatus(), topic, group, offset); break; case OFFSET_OVERFLOW_ONE: // no need to print WARN, because we use "offset + 1" to get the next message pullStatus = PullStatus.OFFSET_ILLEGAL; break; default: assert false; break; } return new PullResult(pullStatus, getMessageResult.getNextBeginOffset(), getMessageResult.getMinOffset(), getMessageResult.getMaxOffset(), foundList); } else { try { long maxQueueOffset = brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId); if (maxQueueOffset > offset) { POP_LOGGER.error("get message from store return null. topic={}, groupId={}, requestOffset={}, maxQueueOffset={}", topic, group, offset, maxQueueOffset); } } catch (ConsumeQueueException e) { POP_LOGGER.error("Failed to get max offset in queue", e); } return null; } } private List decodeMsgList(GetMessageResult getMessageResult, boolean deCompressBody) { List foundList = new ArrayList<>(); try { List messageBufferList = getMessageResult.getMessageBufferList(); if (messageBufferList != null) { for (int i = 0; i < messageBufferList.size(); i++) { ByteBuffer bb = messageBufferList.get(i); if (bb == null) { POP_LOGGER.error("bb is null {}", getMessageResult); continue; } MessageExt msgExt = MessageDecoder.decode(bb, true, deCompressBody); if (msgExt == null) { POP_LOGGER.error("decode msgExt is null {}", getMessageResult); continue; } // use CQ offset, not offset in Message msgExt.setQueueOffset(getMessageResult.getMessageQueueOffset().get(i)); foundList.add(msgExt); } } } finally { getMessageResult.release(); } return foundList; } protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { HashMap map = consumeReviveObj.map; HashMap mockPointMap = new HashMap<>(); long startScanTime = System.currentTimeMillis(); long endTime = 0; long consumeOffset = this.brokerController.getConsumerOffsetManager().queryOffset(PopAckConstants.REVIVE_GROUP, reviveTopic, queueId); long oldOffset = Math.max(reviveOffset, consumeOffset); consumeReviveObj.oldOffset = oldOffset; POP_LOGGER.info("reviveQueueId={}, old offset is {} ", queueId, oldOffset); long offset = oldOffset + 1; int noMsgCount = 0; long firstRt = 0; // offset self amend while (true) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip scan, revive topic={}, reviveQueueId={}", reviveTopic, queueId); break; } List messageExts = getReviveMessage(offset, queueId); if (messageExts == null || messageExts.isEmpty()) { long old = endTime; long timerDelay = brokerController.getMessageStore().getTimerMessageStore().getDequeueBehind(); long commitLogDelay = brokerController.getMessageStore().getTimerMessageStore().getEnqueueBehind(); // move endTime if (endTime != 0 && System.currentTimeMillis() - endTime > 3 * PopAckConstants.SECOND && timerDelay <= 0 && commitLogDelay <= 0) { endTime = System.currentTimeMillis(); } POP_LOGGER.debug("reviveQueueId={}, offset is {}, can not get new msg, old endTime {}, new endTime {}, timerDelay={}, commitLogDelay={} ", queueId, offset, old, endTime, timerDelay, commitLogDelay); if (endTime - firstRt > PopAckConstants.ackTimeInterval + PopAckConstants.SECOND) { break; } noMsgCount++; // Fixme: why sleep is useful here? try { Thread.sleep(100); } catch (Throwable ignore) { } if (noMsgCount * 100L > 4 * PopAckConstants.SECOND) { break; } else { continue; } } else { noMsgCount = 0; } if (System.currentTimeMillis() - startScanTime > brokerController.getBrokerConfig().getReviveScanTime()) { POP_LOGGER.info("reviveQueueId={}, scan timeout ", queueId); break; } for (MessageExt messageExt : messageExts) { if (PopAckConstants.CK_TAG.equals(messageExt.getTags())) { String raw = new String(messageExt.getBody(), DataConverter.CHARSET_UTF8); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("reviveQueueId={},find ck, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw); } PopCheckPoint point = JSON.parseObject(raw, PopCheckPoint.class); if (point.getTopic() == null || point.getCId() == null) { continue; } map.put(point.getTopic() + point.getCId() + point.getQueueId() + point.getStartOffset() + point.getPopTime() + point.getBrokerName(), point); brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveCkGetCount(point, queueId); point.setReviveOffset(messageExt.getQueueOffset()); if (firstRt == 0) { firstRt = point.getReviveTime(); } } else if (PopAckConstants.ACK_TAG.equals(messageExt.getTags())) { String raw = new String(messageExt.getBody(), StandardCharsets.UTF_8); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("reviveQueueId={}, find ack, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw); } AckMsg ackMsg = JSON.parseObject(raw, AckMsg.class); brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveAckGetCount(ackMsg, queueId); String brokerName = StringUtils.isNotBlank(ackMsg.getBrokerName()) ? ackMsg.getBrokerName() : brokerController.getBrokerConfig().getBrokerName(); String mergeKey = ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime() + brokerName; PopCheckPoint point = map.get(mergeKey); if (point == null) { if (!brokerController.getBrokerConfig().isEnableSkipLongAwaitingAck()) { continue; } if (mockCkForAck(messageExt, ackMsg, mergeKey, mockPointMap) && firstRt == 0) { firstRt = mockPointMap.get(mergeKey).getReviveTime(); } } else { int indexOfAck = point.indexOfAck(ackMsg.getAckOffset()); if (indexOfAck > -1) { point.setBitMap(DataConverter.setBit(point.getBitMap(), indexOfAck, true)); } else { POP_LOGGER.error("invalid ack index, {}, {}", ackMsg, point); } } } else if (PopAckConstants.BATCH_ACK_TAG.equals(messageExt.getTags())) { String raw = new String(messageExt.getBody(), StandardCharsets.UTF_8); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("reviveQueueId={}, find batch ack, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw); } BatchAckMsg bAckMsg = JSON.parseObject(raw, BatchAckMsg.class); brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveAckGetCount(bAckMsg, queueId); String brokerName = StringUtils.isNotBlank(bAckMsg.getBrokerName()) ? bAckMsg.getBrokerName() : brokerController.getBrokerConfig().getBrokerName(); String mergeKey = bAckMsg.getTopic() + bAckMsg.getConsumerGroup() + bAckMsg.getQueueId() + bAckMsg.getStartOffset() + bAckMsg.getPopTime() + brokerName; PopCheckPoint point = map.get(mergeKey); if (point == null) { if (!brokerController.getBrokerConfig().isEnableSkipLongAwaitingAck()) { continue; } if (mockCkForAck(messageExt, bAckMsg, mergeKey, mockPointMap) && firstRt == 0) { firstRt = mockPointMap.get(mergeKey).getReviveTime(); } } else { List ackOffsetList = bAckMsg.getAckOffsetList(); for (Long ackOffset : ackOffsetList) { int indexOfAck = point.indexOfAck(ackOffset); if (indexOfAck > -1) { point.setBitMap(DataConverter.setBit(point.getBitMap(), indexOfAck, true)); } else { POP_LOGGER.error("invalid batch ack index, {}, {}", bAckMsg, point); } } } } long deliverTime = messageExt.getDeliverTimeMs(); if (deliverTime > endTime) { endTime = deliverTime; } } offset = offset + messageExts.size(); } consumeReviveObj.map.putAll(mockPointMap); consumeReviveObj.endTime = endTime; } private boolean mockCkForAck(MessageExt messageExt, AckMsg ackMsg, String mergeKey, HashMap mockPointMap) { long ackWaitTime = System.currentTimeMillis() - messageExt.getDeliverTimeMs(); long reviveAckWaitMs = brokerController.getBrokerConfig().getReviveAckWaitMs(); if (ackWaitTime > reviveAckWaitMs) { // will use the reviveOffset of popCheckPoint to commit offset in mergeAndRevive PopCheckPoint mockPoint = createMockCkForAck(ackMsg, messageExt.getQueueOffset()); POP_LOGGER.warn( "ack wait for {}ms cannot find ck, skip this ack. mergeKey:{}, ack:{}, mockCk:{}", reviveAckWaitMs, mergeKey, ackMsg, mockPoint); mockPointMap.put(mergeKey, mockPoint); return true; } return false; } private PopCheckPoint createMockCkForAck(AckMsg ackMsg, long reviveOffset) { PopCheckPoint point = new PopCheckPoint(); point.setStartOffset(ackMsg.getStartOffset()); point.setPopTime(ackMsg.getPopTime()); point.setQueueId(ackMsg.getQueueId()); point.setCId(ackMsg.getConsumerGroup()); point.setTopic(ackMsg.getTopic()); point.setNum((byte) 0); point.setBitMap(0); point.setReviveOffset(reviveOffset); point.setBrokerName(ackMsg.getBrokerName()); return point; } protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwable { ArrayList sortList = consumeReviveObj.genSortList(); POP_LOGGER.info("reviveQueueId={}, ck listSize={}", queueId, sortList.size()); if (sortList.size() != 0) { POP_LOGGER.info("reviveQueueId={}, 1st ck, startOffset={}, reviveOffset={}; last ck, startOffset={}, reviveOffset={}", queueId, sortList.get(0).getStartOffset(), sortList.get(0).getReviveOffset(), sortList.get(sortList.size() - 1).getStartOffset(), sortList.get(sortList.size() - 1).getReviveOffset()); } long newOffset = consumeReviveObj.oldOffset; for (PopCheckPoint popCheckPoint : sortList) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip ck process, revive topic={}, reviveQueueId={}", reviveTopic, queueId); break; } if (consumeReviveObj.endTime - popCheckPoint.getReviveTime() <= (PopAckConstants.ackTimeInterval + PopAckConstants.SECOND)) { break; } // check normal topic, skip ck , if normal topic is not exist String normalTopic = KeyBuilder.parseNormalTopic(popCheckPoint.getTopic(), popCheckPoint.getCId()); if (brokerController.getTopicConfigManager().selectTopicConfig(normalTopic) == null) { POP_LOGGER.warn("reviveQueueId={}, can not get normal topic {}, then continue", queueId, popCheckPoint.getTopic()); newOffset = popCheckPoint.getReviveOffset(); continue; } if (null == brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(popCheckPoint.getCId())) { POP_LOGGER.warn("reviveQueueId={}, can not get cid {}, then continue", queueId, popCheckPoint.getCId()); newOffset = popCheckPoint.getReviveOffset(); continue; } while (inflightReviveRequestMap.size() > 3) { waitForRunning(100); Pair pair = inflightReviveRequestMap.firstEntry().getValue(); if (!pair.getObject2() && System.currentTimeMillis() - pair.getObject1() > 1000 * 30) { PopCheckPoint oldCK = inflightReviveRequestMap.firstKey(); rePutCK(oldCK, pair); inflightReviveRequestMap.remove(oldCK); POP_LOGGER.warn("stay too long, remove from reviveRequestMap, {}, {}, {}, {}", popCheckPoint.getTopic(), popCheckPoint.getBrokerName(), popCheckPoint.getQueueId(), popCheckPoint.getStartOffset()); } } reviveMsgFromCk(popCheckPoint); newOffset = popCheckPoint.getReviveOffset(); } if (newOffset > consumeReviveObj.oldOffset) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip commit, revive topic={}, reviveQueueId={}", reviveTopic, queueId); return; } this.brokerController.getConsumerOffsetManager().commitOffset(PopAckConstants.LOCAL_HOST, PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, newOffset); } reviveOffset = newOffset; consumeReviveObj.newOffset = newOffset; } private void reviveMsgFromCk(PopCheckPoint popCheckPoint) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip retry, revive topic={}, reviveQueueId={}", reviveTopic, queueId); return; } inflightReviveRequestMap.put(popCheckPoint, new Pair<>(System.currentTimeMillis(), false)); List>> futureList = new ArrayList<>(popCheckPoint.getNum()); for (int j = 0; j < popCheckPoint.getNum(); j++) { if (DataConverter.getBit(popCheckPoint.getBitMap(), j)) { continue; } // retry msg long msgOffset = popCheckPoint.ackOffsetByIndex((byte) j); CompletableFuture> future = getBizMessage(popCheckPoint, msgOffset) .thenApply(rst -> { MessageExt message = rst.getLeft(); if (message == null) { POP_LOGGER.info("reviveQueueId={}, can not get biz msg, topic:{}, qid:{}, offset:{}, brokerName:{}, info:{}, retry:{}, then continue", queueId, popCheckPoint.getTopic(), popCheckPoint.getQueueId(), msgOffset, popCheckPoint.getBrokerName(), UtilAll.frontStringAtLeast(rst.getMiddle(), 60), rst.getRight()); return new Pair<>(msgOffset, !rst.getRight()); // Pair.object2 means OK or not, Triple.right value means needRetry } boolean result = reviveRetry(popCheckPoint, message); return new Pair<>(msgOffset, result); }); futureList.add(future); } CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((v, e) -> { for (CompletableFuture> future : futureList) { Pair pair = future.getNow(new Pair<>(0L, false)); if (!pair.getObject2()) { rePutCK(popCheckPoint, pair); } } if (inflightReviveRequestMap.containsKey(popCheckPoint)) { inflightReviveRequestMap.get(popCheckPoint).setObject2(true); } for (Map.Entry> entry : inflightReviveRequestMap.entrySet()) { PopCheckPoint oldCK = entry.getKey(); Pair pair = entry.getValue(); if (pair.getObject2()) { brokerController.getConsumerOffsetManager().commitOffset(PopAckConstants.LOCAL_HOST, PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, oldCK.getReviveOffset()); inflightReviveRequestMap.remove(oldCK); } else { break; } } }); } private void rePutCK(PopCheckPoint oldCK, Pair pair) { int rePutTimes = oldCK.parseRePutTimes(); if (rePutTimes >= ckRewriteIntervalsInSeconds.length && brokerController.getBrokerConfig().isSkipWhenCKRePutReachMaxTimes()) { POP_LOGGER.warn("rePut CK reach max times, drop it. {}, {}, {}, {}-{}, {}, {}, {}", oldCK.getTopic(), oldCK.getCId(), oldCK.getBrokerName(), oldCK.getQueueId(), pair.getObject1(), oldCK.getPopTime(), oldCK.getInvisibleTime(), rePutTimes); return; } PopCheckPoint newCk = new PopCheckPoint(); newCk.setBitMap(0); newCk.setNum((byte) 1); newCk.setPopTime(oldCK.getPopTime()); newCk.setInvisibleTime(oldCK.getInvisibleTime()); newCk.setStartOffset(pair.getObject1()); newCk.setCId(oldCK.getCId()); newCk.setTopic(oldCK.getTopic()); newCk.setQueueId(oldCK.getQueueId()); newCk.setBrokerName(oldCK.getBrokerName()); newCk.addDiff(0); newCk.setRePutTimes(String.valueOf(rePutTimes + 1)); // always increment even if removed from reviveRequestMap if (oldCK.getReviveTime() <= System.currentTimeMillis()) { // never expect an ACK matched in the future, we just use it to rewrite CK and try to revive retry message next time int intervalIndex = rePutTimes >= ckRewriteIntervalsInSeconds.length ? ckRewriteIntervalsInSeconds.length - 1 : rePutTimes; newCk.setInvisibleTime(oldCK.getInvisibleTime() + ckRewriteIntervalsInSeconds[intervalIndex] * 1000); } MessageExtBrokerInner ckMsg = brokerController.getPopMessageProcessor().buildCkMsg(newCk, queueId); brokerController.getMessageStore().putMessage(ckMsg); } public long getReviveBehindMillis() throws ConsumeQueueException { if (currentReviveMessageTimestamp <= 0) { return 0; } long maxOffset = brokerController.getMessageStore().getMaxOffsetInQueue(reviveTopic, queueId); if (maxOffset - reviveOffset > 1) { return Math.max(0, System.currentTimeMillis() - currentReviveMessageTimestamp); } return 0; } public long getReviveBehindMessages() throws ConsumeQueueException { if (currentReviveMessageTimestamp <= 0) { return 0; } // the next pull offset is reviveOffset + 1 long diff = brokerController.getMessageStore().getMaxOffsetInQueue(reviveTopic, queueId) - reviveOffset - 1; return Math.max(0, diff); } @Override public void run() { int slow = 1; while (!this.isStopped()) { try { if (System.currentTimeMillis() < brokerController.getShouldStartTime()) { POP_LOGGER.info("PopReviveService Ready to run after {}", brokerController.getShouldStartTime()); this.waitForRunning(1000); continue; } this.waitForRunning(brokerController.getBrokerConfig().getReviveInterval()); if (!shouldRunPopRevive) { POP_LOGGER.info("skip start revive topic={}, reviveQueueId={}", reviveTopic, queueId); continue; } if (!brokerController.getMessageStore().getMessageStoreConfig().isTimerWheelEnable()) { POP_LOGGER.warn("skip revive topic because timerWheelEnable is false"); continue; } POP_LOGGER.info("start revive topic={}, reviveQueueId={}", reviveTopic, queueId); ConsumeReviveObj consumeReviveObj = new ConsumeReviveObj(); consumeReviveMessage(consumeReviveObj); if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip scan, revive topic={}, reviveQueueId={}", reviveTopic, queueId); continue; } mergeAndRevive(consumeReviveObj); ArrayList sortList = consumeReviveObj.sortList; long delay = 0; if (sortList != null && !sortList.isEmpty()) { delay = (System.currentTimeMillis() - sortList.get(0).getReviveTime()) / 1000; currentReviveMessageTimestamp = sortList.get(0).getReviveTime(); slow = 1; } else { currentReviveMessageTimestamp = System.currentTimeMillis(); } POP_LOGGER.info("reviveQueueId={}, revive finish,old offset is {}, new offset is {}, ckDelay={} ", queueId, consumeReviveObj.oldOffset, consumeReviveObj.newOffset, delay); if (sortList == null || sortList.isEmpty()) { POP_LOGGER.info("reviveQueueId={}, has no new msg, take a rest {}", queueId, slow); this.waitForRunning(slow * brokerController.getBrokerConfig().getReviveInterval()); if (slow < brokerController.getBrokerConfig().getReviveMaxSlow()) { slow++; } } } catch (Throwable e) { POP_LOGGER.error("reviveQueueId={}, revive error", queueId, e); } } } static class ConsumeReviveObj { HashMap map = new HashMap<>(); ArrayList sortList; long oldOffset; long endTime; long newOffset; ArrayList genSortList() { if (sortList != null) { return sortList; } sortList = new ArrayList<>(map.values()); sortList.sort((o1, o2) -> (int) (o1.getReviveOffset() - o2.getReviveOffset())); return sortList; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/PullMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.List; import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.coldctr.ColdDataPullRequestHoldService; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ConsumerFilterManager; import org.apache.rocketmq.broker.filter.ExpressionForRetryMessageFilter; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.broker.longpolling.PullRequest; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageContext; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook; import org.apache.rocketmq.broker.plugin.PullMessageResultHandler; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.protocol.ForbiddenType; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.RequestSource; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.rpc.RpcClientUtils; import org.apache.rocketmq.remoting.rpc.RpcRequest; import org.apache.rocketmq.remoting.rpc.RpcResponse; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.stats.BrokerStatsManager; import static org.apache.rocketmq.remoting.protocol.RemotingCommand.buildErrorResponse; public class PullMessageProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private List consumeMessageHookList; private PullMessageResultHandler pullMessageResultHandler; private final BrokerController brokerController; public PullMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; this.pullMessageResultHandler = new DefaultPullMessageResultHandler(brokerController); } private RemotingCommand rewriteRequestForStaticTopic(PullMessageRequestHeader requestHeader, TopicQueueMappingContext mappingContext) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); String topic = mappingContext.getTopic(); Integer globalId = mappingContext.getGlobalId(); // if the leader? consider the order consumer, which will lock the mq if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d cannot find mapping item in request process of current broker %s", topic, globalId, mappingDetail.getBname())); } Long globalOffset = requestHeader.getQueueOffset(); LogicQueueMappingItem mappingItem = TopicQueueMappingUtils.findLogicQueueMappingItem(mappingContext.getMappingItemList(), globalOffset, true); mappingContext.setCurrentItem(mappingItem); if (globalOffset < mappingItem.getLogicOffset()) { //handleOffsetMoved //If the physical queue is reused, we should handle the PULL_OFFSET_MOVED independently //Otherwise, we could just transfer it to the physical process } //below are physical info String bname = mappingItem.getBname(); Integer phyQueueId = mappingItem.getQueueId(); Long phyQueueOffset = mappingItem.computePhysicalQueueOffset(globalOffset); requestHeader.setQueueId(phyQueueId); requestHeader.setQueueOffset(phyQueueOffset); if (mappingItem.checkIfEndOffsetDecided() && requestHeader.getMaxMsgNums() != null) { requestHeader.setMaxMsgNums((int) Math.min(mappingItem.getEndOffset() - mappingItem.getStartOffset(), requestHeader.getMaxMsgNums())); } if (mappingDetail.getBname().equals(bname)) { //just let it go, do the local pull process return null; } int sysFlag = requestHeader.getSysFlag(); requestHeader.setLo(false); requestHeader.setBrokerName(bname); sysFlag = PullSysFlag.clearSuspendFlag(sysFlag); sysFlag = PullSysFlag.clearCommitOffsetFlag(sysFlag); requestHeader.setSysFlag(sysFlag); RpcRequest rpcRequest = new RpcRequest(RequestCode.PULL_MESSAGE, requestHeader, null); RpcResponse rpcResponse = this.brokerController.getBrokerOuterAPI().getRpcClient().invoke(rpcRequest, this.brokerController.getBrokerConfig().getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) rpcResponse.getHeader(); { RemotingCommand rewriteResult = rewriteResponseForStaticTopic(requestHeader, responseHeader, mappingContext, rpcResponse.getCode()); if (rewriteResult != null) { return rewriteResult; } } return RpcClientUtils.createCommandForRpcResponse(rpcResponse); } catch (Throwable t) { LOGGER.warn("", t); return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.toString()); } } protected RemotingCommand rewriteResponseForStaticTopic(PullMessageRequestHeader requestHeader, PullMessageResponseHeader responseHeader, TopicQueueMappingContext mappingContext, final int code) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); LogicQueueMappingItem leaderItem = mappingContext.getLeaderItem(); LogicQueueMappingItem currentItem = mappingContext.getCurrentItem(); LogicQueueMappingItem earlistItem = TopicQueueMappingUtils.findLogicQueueMappingItem(mappingContext.getMappingItemList(), 0L, true); assert currentItem.getLogicOffset() >= 0; long requestOffset = requestHeader.getQueueOffset(); long nextBeginOffset = responseHeader.getNextBeginOffset(); long minOffset = responseHeader.getMinOffset(); long maxOffset = responseHeader.getMaxOffset(); int responseCode = code; //consider the following situations // 1. read from slave, currently not supported // 2. the middle queue is truncated because of deleting commitlog if (code != ResponseCode.SUCCESS) { //note the currentItem maybe both the leader and the earliest boolean isRevised = false; if (leaderItem.getGen() == currentItem.getGen()) { //read the leader if (requestOffset > maxOffset) { //actually, we need do nothing, but keep the code structure here if (code == ResponseCode.PULL_OFFSET_MOVED) { responseCode = ResponseCode.PULL_OFFSET_MOVED; nextBeginOffset = maxOffset; } else { //maybe current broker is the slave responseCode = code; } } else if (requestOffset < minOffset) { nextBeginOffset = minOffset; responseCode = ResponseCode.PULL_RETRY_IMMEDIATELY; } else { responseCode = code; } } //note the currentItem maybe both the leader and the earliest if (earlistItem.getGen() == currentItem.getGen()) { //read the earliest one if (requestOffset < minOffset) { if (code == ResponseCode.PULL_OFFSET_MOVED) { responseCode = ResponseCode.PULL_OFFSET_MOVED; nextBeginOffset = minOffset; } else { //maybe read from slave, but we still set it to moved responseCode = ResponseCode.PULL_OFFSET_MOVED; nextBeginOffset = minOffset; } } else if (requestOffset >= maxOffset) { //just move to another item LogicQueueMappingItem nextItem = TopicQueueMappingUtils.findNext(mappingContext.getMappingItemList(), currentItem, true); if (nextItem != null) { isRevised = true; currentItem = nextItem; nextBeginOffset = currentItem.getStartOffset(); minOffset = currentItem.getStartOffset(); maxOffset = minOffset; responseCode = ResponseCode.PULL_RETRY_IMMEDIATELY; } else { //maybe the next one's logic offset is -1 responseCode = ResponseCode.PULL_NOT_FOUND; } } else { //let it go responseCode = code; } } //read from the middle item, ignore the PULL_OFFSET_MOVED if (!isRevised && leaderItem.getGen() != currentItem.getGen() && earlistItem.getGen() != currentItem.getGen()) { if (requestOffset < minOffset) { nextBeginOffset = minOffset; responseCode = ResponseCode.PULL_RETRY_IMMEDIATELY; } else if (requestOffset >= maxOffset) { //just move to another item LogicQueueMappingItem nextItem = TopicQueueMappingUtils.findNext(mappingContext.getMappingItemList(), currentItem, true); if (nextItem != null) { currentItem = nextItem; nextBeginOffset = currentItem.getStartOffset(); minOffset = currentItem.getStartOffset(); maxOffset = minOffset; responseCode = ResponseCode.PULL_RETRY_IMMEDIATELY; } else { //maybe the next one's logic offset is -1 responseCode = ResponseCode.PULL_NOT_FOUND; } } else { responseCode = code; } } } //handle nextBeginOffset //the next begin offset should no more than the end offset if (currentItem.checkIfEndOffsetDecided() && nextBeginOffset >= currentItem.getEndOffset()) { nextBeginOffset = currentItem.getEndOffset(); } responseHeader.setNextBeginOffset(currentItem.computeStaticQueueOffsetStrictly(nextBeginOffset)); //handle min offset responseHeader.setMinOffset(currentItem.computeStaticQueueOffsetStrictly(Math.max(currentItem.getStartOffset(), minOffset))); //handle max offset responseHeader.setMaxOffset(Math.max(currentItem.computeStaticQueueOffsetStrictly(maxOffset), TopicQueueMappingDetail.computeMaxOffsetFromMapping(mappingDetail, mappingContext.getGlobalId()))); //set the offsetDelta responseHeader.setOffsetDelta(currentItem.computeOffsetDelta()); if (code != ResponseCode.SUCCESS) { return RemotingCommand.createResponseCommandWithHeader(responseCode, responseHeader); } else { return null; } } catch (Throwable t) { LOGGER.warn("", t); return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.toString()); } } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { return this.processRequest(ctx.channel(), request, true, true); } @Override public boolean rejectRequest() { if (!this.brokerController.getBrokerConfig().isSlaveReadEnable() && this.brokerController.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { return true; } return false; } private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend, boolean brokerAllowFlowCtrSuspend) throws RemotingCommandException { final long beginTimeMills = this.brokerController.getMessageStore().now(); RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class); final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader(); final PullMessageRequestHeader requestHeader = (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class); response.setOpaque(request.getOpaque()); LOGGER.debug("receive PullMessage request command, {}", request); if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); responseHeader.setForbiddenType(ForbiddenType.BROKER_FORBIDDEN); response.setRemark(String.format("the broker[%s] pulling message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); return response; } if (request.getCode() == RequestCode.LITE_PULL_MESSAGE && !this.brokerController.getBrokerConfig().isLitePullMessageEnable()) { response.setCode(ResponseCode.NO_PERMISSION); responseHeader.setForbiddenType(ForbiddenType.BROKER_FORBIDDEN); response.setRemark( "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() + "] for lite pull consumer is forbidden"); return response; } SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST))); return response; } if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); responseHeader.setForbiddenType(ForbiddenType.GROUP_FORBIDDEN); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { LOGGER.error("the topic {} not exist, consumer: {}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); return response; } if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(ResponseCode.NO_PERMISSION); responseHeader.setForbiddenType(ForbiddenType.TOPIC_FORBIDDEN); response.setRemark("the topic[" + requestHeader.getTopic() + "] pulling message is forbidden"); return response; } TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader, false); { RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } } if (requestHeader.getQueueId() < 0 || requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); LOGGER.warn(errorInfo); response.setCode(ResponseCode.INVALID_PARAMETER); response.setRemark(errorInfo); return response; } ConsumerManager consumerManager = brokerController.getConsumerManager(); switch (RequestSource.parseInteger(requestHeader.getRequestSource())) { case PROXY_FOR_BROADCAST: consumerManager.compensateBasicConsumerInfo(requestHeader.getConsumerGroup(), ConsumeType.CONSUME_PASSIVELY, MessageModel.BROADCASTING); break; case PROXY_FOR_STREAM: consumerManager.compensateBasicConsumerInfo(requestHeader.getConsumerGroup(), ConsumeType.CONSUME_ACTIVELY, MessageModel.CLUSTERING); break; default: consumerManager.compensateBasicConsumerInfo(requestHeader.getConsumerGroup(), ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING); break; } SubscriptionData subscriptionData = null; ConsumerFilterData consumerFilterData = null; final boolean hasSubscriptionFlag = PullSysFlag.hasSubscriptionFlag(requestHeader.getSysFlag()); if (hasSubscriptionFlag) { try { subscriptionData = FilterAPI.build( requestHeader.getTopic(), requestHeader.getSubscription(), requestHeader.getExpressionType() ); consumerManager.compensateSubscribeData(requestHeader.getConsumerGroup(), requestHeader.getTopic(), subscriptionData); if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) { consumerFilterData = ConsumerFilterManager.build( requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getSubscription(), requestHeader.getExpressionType(), requestHeader.getSubVersion() ); assert consumerFilterData != null; } } catch (Exception e) { LOGGER.warn("Parse the consumer's subscription[{}] failed, group: {}", requestHeader.getSubscription(), requestHeader.getConsumerGroup()); response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED); response.setRemark("parse the consumer's subscription failed"); return response; } } else { ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup()); if (null == consumerGroupInfo) { LOGGER.warn("the consumer's group info not exist, group: {}", requestHeader.getConsumerGroup()); response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST); response.setRemark("the consumer's group info not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC)); return response; } if (!subscriptionGroupConfig.isConsumeBroadcastEnable() && consumerGroupInfo.getMessageModel() == MessageModel.BROADCASTING) { response.setCode(ResponseCode.NO_PERMISSION); responseHeader.setForbiddenType(ForbiddenType.BROADCASTING_DISABLE_FORBIDDEN); response.setRemark("the consumer group[" + requestHeader.getConsumerGroup() + "] can not consume by broadcast way"); return response; } boolean readForbidden = this.brokerController.getSubscriptionGroupManager().getForbidden(// subscriptionGroupConfig.getGroupName(), requestHeader.getTopic(), PermName.INDEX_PERM_READ); if (readForbidden) { response.setCode(ResponseCode.NO_PERMISSION); responseHeader.setForbiddenType(ForbiddenType.SUBSCRIPTION_FORBIDDEN); response.setRemark("the consumer group[" + requestHeader.getConsumerGroup() + "] is forbidden for topic[" + requestHeader.getTopic() + "]"); return response; } subscriptionData = consumerGroupInfo.findSubscriptionData(requestHeader.getTopic()); if (null == subscriptionData) { LOGGER.warn("the consumer's subscription not exist, group: {}, topic:{}", requestHeader.getConsumerGroup(), requestHeader.getTopic()); response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST); response.setRemark("the consumer's subscription not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC)); return response; } if (subscriptionData.getSubVersion() < requestHeader.getSubVersion()) { LOGGER.warn("The broker's subscription is not latest, group: {} {}", requestHeader.getConsumerGroup(), subscriptionData.getSubString()); response.setCode(ResponseCode.SUBSCRIPTION_NOT_LATEST); response.setRemark("the consumer's subscription not latest"); return response; } if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) { consumerFilterData = this.brokerController.getConsumerFilterManager().get(requestHeader.getTopic(), requestHeader.getConsumerGroup()); if (consumerFilterData == null) { response.setCode(ResponseCode.FILTER_DATA_NOT_EXIST); response.setRemark("The broker's consumer filter data is not exist!Your expression may be wrong!"); return response; } if (consumerFilterData.getClientVersion() < requestHeader.getSubVersion()) { LOGGER.warn("The broker's consumer filter data is not latest, group: {}, topic: {}, serverV: {}, clientV: {}", requestHeader.getConsumerGroup(), requestHeader.getTopic(), consumerFilterData.getClientVersion(), requestHeader.getSubVersion()); response.setCode(ResponseCode.FILTER_DATA_NOT_LATEST); response.setRemark("the consumer's consumer filter data not latest"); return response; } } } if (!ExpressionType.isTagType(subscriptionData.getExpressionType()) && !this.brokerController.getBrokerConfig().isEnablePropertyFilter()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("The broker does not support consumer to filter message by " + subscriptionData.getExpressionType()); return response; } MessageFilter messageFilter; if (this.brokerController.getBrokerConfig().isFilterSupportRetry()) { messageFilter = new ExpressionForRetryMessageFilter(subscriptionData, consumerFilterData, this.brokerController.getConsumerFilterManager()); } else { messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData, this.brokerController.getConsumerFilterManager()); } if (brokerController.getBrokerConfig().isRejectPullConsumerEnable()) { ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup()); if (null == consumerGroupInfo || ConsumeType.CONSUME_ACTIVELY == consumerGroupInfo.getConsumeType()) { if ((null == consumerGroupInfo || null == consumerGroupInfo.findChannel(channel)) && !MixAll.isSysConsumerGroupPullMessage(requestHeader.getConsumerGroup())) { response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST); response.setRemark("the consumer's group info not exist, or the pull consumer is rejected by server." + FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)); return response; } } } final MessageStore messageStore = brokerController.getMessageStore(); if (this.brokerController.getMessageStore() instanceof DefaultMessageStore) { DefaultMessageStore defaultMessageStore = (DefaultMessageStore) this.brokerController.getMessageStore(); boolean cgNeedColdDataFlowCtr = brokerController.getColdDataCgCtrService().isCgNeedColdDataFlowCtr(requestHeader.getConsumerGroup()); if (cgNeedColdDataFlowCtr) { boolean isMsgLogicCold = defaultMessageStore.getCommitLog() .getColdDataCheckService().isMsgInColdArea(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getQueueOffset()); if (isMsgLogicCold) { ConsumeType consumeType = this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup()).getConsumeType(); if (consumeType == ConsumeType.CONSUME_PASSIVELY) { response.setCode(ResponseCode.SYSTEM_BUSY); response.setRemark("This consumer group is reading cold data. It has been flow control"); return response; } else if (consumeType == ConsumeType.CONSUME_ACTIVELY) { if (brokerAllowFlowCtrSuspend) { // second arrived, which will not be held PullRequest pullRequest = new PullRequest(request, channel, 1000, this.brokerController.getMessageStore().now(), requestHeader.getQueueOffset(), subscriptionData, messageFilter); this.brokerController.getColdDataPullRequestHoldService().suspendColdDataReadRequest(pullRequest); return null; } requestHeader.setMaxMsgNums(1); } } } } final boolean useResetOffsetFeature = brokerController.getBrokerConfig().isUseServerSideResetOffset(); String topic = requestHeader.getTopic(); String liteTopic = requestHeader.getLiteTopic(); String group = requestHeader.getConsumerGroup(); int queueId = requestHeader.getQueueId(); Long resetOffset = brokerController.getConsumerOffsetManager().queryThenEraseResetOffset(topic, group, queueId); GetMessageResult getMessageResult = null; if (useResetOffsetFeature && null != resetOffset) { getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.OFFSET_RESET); getMessageResult.setNextBeginOffset(resetOffset); getMessageResult.setMinOffset(messageStore.getMinOffsetInQueue(topic, queueId)); try { getMessageResult.setMaxOffset(messageStore.getMaxOffsetInQueue(topic, queueId)); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed tp get max offset in queue", e); } getMessageResult.setSuggestPullingFromSlave(false); } else { long broadcastInitOffset = queryBroadcastPullInitOffset(topic, group, queueId, requestHeader, channel); if (broadcastInitOffset >= 0) { getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.OFFSET_RESET); getMessageResult.setNextBeginOffset(broadcastInitOffset); } else { SubscriptionData finalSubscriptionData = subscriptionData; RemotingCommand finalResponse = response; String storeTopic = topic; if (StringUtils.isNotBlank(liteTopic)) { storeTopic = LiteUtil.toLmqName(topic, liteTopic); } messageStore.getMessageAsync(group, storeTopic, queueId, requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter) .thenApply(result -> { if (null == result) { finalResponse.setCode(ResponseCode.SYSTEM_ERROR); finalResponse.setRemark("store getMessage return null"); return finalResponse; } brokerController.getColdDataCgCtrService().coldAcc(requestHeader.getConsumerGroup(), result.getColdDataSum()); return pullMessageResultHandler.handle( result, request, requestHeader, channel, finalSubscriptionData, subscriptionGroupConfig, brokerAllowSuspend, messageFilter, finalResponse, mappingContext, beginTimeMills ); }) .thenAccept(result -> NettyRemotingAbstract.writeResponse(channel, request, result, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager())); } } if (getMessageResult != null) { return this.pullMessageResultHandler.handle( getMessageResult, request, requestHeader, channel, subscriptionData, subscriptionGroupConfig, brokerAllowSuspend, messageFilter, response, mappingContext, beginTimeMills ); } return null; } public boolean hasConsumeMessageHook() { return consumeMessageHookList != null && !this.consumeMessageHookList.isEmpty(); } /** * Composes the header of the response message to be sent back to the client * * @param requestHeader - the header of the request message * @param getMessageResult - the result of the GetMessage request * @param topicSysFlag - the system flag of the topic * @param subscriptionGroupConfig - configuration of the subscription group * @param response - the response message to be sent back to the client * @param clientAddress - the address of the client */ protected void composeResponseHeader(PullMessageRequestHeader requestHeader, GetMessageResult getMessageResult, int topicSysFlag, SubscriptionGroupConfig subscriptionGroupConfig, RemotingCommand response, String clientAddress) { final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader(); response.setRemark(getMessageResult.getStatus().name()); responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset()); responseHeader.setMinOffset(getMessageResult.getMinOffset()); // this does not need to be modified since it's not an accurate value under logical queue. responseHeader.setMaxOffset(getMessageResult.getMaxOffset()); responseHeader.setTopicSysFlag(topicSysFlag); responseHeader.setGroupSysFlag(subscriptionGroupConfig.getGroupSysFlag()); switch (getMessageResult.getStatus()) { case FOUND: response.setCode(ResponseCode.SUCCESS); break; case MESSAGE_WAS_REMOVING: case NO_MATCHED_MESSAGE: response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); break; case NO_MATCHED_LOGIC_QUEUE: case NO_MESSAGE_IN_QUEUE: if (0 != requestHeader.getQueueOffset()) { response.setCode(ResponseCode.PULL_OFFSET_MOVED); // XXX: warn and notify me LOGGER.info("the broker stores no queue data, fix the request offset {} to {}, Topic: {} QueueId: {} Consumer Group: {}", requestHeader.getQueueOffset(), getMessageResult.getNextBeginOffset(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getConsumerGroup() ); } else { response.setCode(ResponseCode.PULL_NOT_FOUND); } break; case OFFSET_FOUND_NULL: case OFFSET_OVERFLOW_ONE: response.setCode(ResponseCode.PULL_NOT_FOUND); break; case OFFSET_OVERFLOW_BADLY: response.setCode(ResponseCode.PULL_OFFSET_MOVED); // XXX: warn and notify me LOGGER.info("the request offset: {} over flow badly, fix to {}, broker max offset: {}, consumer: {}", requestHeader.getQueueOffset(), getMessageResult.getNextBeginOffset(), getMessageResult.getMaxOffset(), clientAddress); break; case OFFSET_RESET: response.setCode(ResponseCode.PULL_OFFSET_MOVED); LOGGER.info("The queue under pulling was previously reset to start from {}", getMessageResult.getNextBeginOffset()); break; case OFFSET_TOO_SMALL: response.setCode(ResponseCode.PULL_OFFSET_MOVED); LOGGER.info("the request offset too small. group={}, topic={}, requestOffset={}, brokerMinOffset={}, clientIp={}", requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueOffset(), getMessageResult.getMinOffset(), clientAddress); break; default: assert false; break; } if (this.brokerController.getBrokerConfig().isSlaveReadEnable() && !this.brokerController.getBrokerConfig().isInBrokerContainer()) { // consume too slow ,redirect to another machine if (getMessageResult.isSuggestPullingFromSlave()) { responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly()); } // consume ok else { responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId()); } } else { responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID); } if (this.brokerController.getBrokerConfig().getBrokerId() != MixAll.MASTER_ID && !getMessageResult.isSuggestPullingFromSlave()) { if (this.brokerController.getMinBrokerIdInGroup() == MixAll.MASTER_ID) { LOGGER.debug("slave redirect pullRequest to master, topic: {}, queueId: {}, consumer group: {}, next: {}, min: {}, max: {}", requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getConsumerGroup(), responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(), responseHeader.getMaxOffset() ); responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID); if (!getMessageResult.getStatus().equals(GetMessageStatus.FOUND)) { response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); } } } } protected void executeConsumeMessageHookBefore(RemotingCommand request, PullMessageRequestHeader requestHeader, GetMessageResult getMessageResult, boolean brokerAllowSuspend, int responseCode) { if (this.hasConsumeMessageHook()) { String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); String authType = request.getExtFields().get(BrokerStatsManager.ACCOUNT_AUTH_TYPE); String ownerParent = request.getExtFields().get(BrokerStatsManager.ACCOUNT_OWNER_PARENT); String ownerSelf = request.getExtFields().get(BrokerStatsManager.ACCOUNT_OWNER_SELF); ConsumeMessageContext context = new ConsumeMessageContext(); context.setConsumerGroup(requestHeader.getConsumerGroup()); context.setTopic(requestHeader.getTopic()); context.setQueueId(requestHeader.getQueueId()); context.setAccountAuthType(authType); context.setAccountOwnerParent(ownerParent); context.setAccountOwnerSelf(ownerSelf); context.setNamespace(NamespaceUtil.getNamespaceFromResource(requestHeader.getTopic())); context.setFilterMessageCount(getMessageResult.getFilterMessageCount()); switch (responseCode) { case ResponseCode.SUCCESS: int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); int incValue = getMessageResult.getMsgCount4Commercial() * commercialBaseCount; context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_SUCCESS); context.setCommercialRcvTimes(incValue); context.setCommercialRcvSize(getMessageResult.getBufferTotalSize()); context.setCommercialOwner(owner); context.setRcvStat(BrokerStatsManager.StatsType.RCV_SUCCESS); context.setRcvMsgNum(getMessageResult.getMessageCount()); context.setRcvMsgSize(getMessageResult.getBufferTotalSize()); context.setCommercialRcvMsgNum(getMessageResult.getMsgCount4Commercial()); break; case ResponseCode.PULL_NOT_FOUND: if (!brokerAllowSuspend) { context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS); context.setCommercialRcvTimes(1); context.setCommercialOwner(owner); context.setRcvStat(BrokerStatsManager.StatsType.RCV_EPOLLS); context.setRcvMsgNum(0); context.setRcvMsgSize(0); context.setCommercialRcvMsgNum(0); } break; case ResponseCode.PULL_RETRY_IMMEDIATELY: case ResponseCode.PULL_OFFSET_MOVED: context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS); context.setCommercialRcvTimes(1); context.setCommercialOwner(owner); context.setRcvStat(BrokerStatsManager.StatsType.RCV_EPOLLS); context.setRcvMsgNum(0); context.setRcvMsgSize(0); context.setCommercialRcvMsgNum(0); break; default: assert false; break; } for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageBefore(context); } catch (Throwable ignored) { } } } } protected void tryCommitOffset(boolean brokerAllowSuspend, PullMessageRequestHeader requestHeader, long nextOffset, String clientAddress) { this.brokerController.getConsumerOffsetManager().commitPullOffset(clientAddress, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), nextOffset); boolean storeOffsetEnable = brokerAllowSuspend; final boolean hasCommitOffsetFlag = PullSysFlag.hasCommitOffsetFlag(requestHeader.getSysFlag()); storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag; if (storeOffsetEnable) { this.brokerController.getConsumerOffsetManager().commitOffset(clientAddress, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset()); } } public void executeRequestWhenWakeup(final Channel channel, final RemotingCommand request) { Runnable run = () -> { try { boolean brokerAllowFlowCtrSuspend = !(request.getExtFields() != null && request.getExtFields().containsKey(ColdDataPullRequestHoldService.NO_SUSPEND_KEY)); final RemotingCommand response = PullMessageProcessor.this.processRequest(channel, request, false, brokerAllowFlowCtrSuspend); if (response != null) { response.setOpaque(request.getOpaque()); response.markResponseType(); try { NettyRemotingAbstract.writeResponse(channel, request, response, future -> { if (!future.isSuccess()) { LOGGER.error("processRequestWrapper response to {} failed", channel.remoteAddress(), future.cause()); LOGGER.error(request.toString()); LOGGER.error(response.toString()); } }, brokerController.getBrokerMetricsManager().getRemotingMetricsManager()); } catch (Throwable e) { LOGGER.error("processRequestWrapper process request over, but response failed", e); LOGGER.error(request.toString()); LOGGER.error(response.toString()); } } } catch (RemotingCommandException e1) { LOGGER.error("executeRequestWhenWakeup run", e1); } }; this.brokerController.getPullMessageExecutor().submit(new RequestTask(run, channel, request)); } public void registerConsumeMessageHook(List consumeMessageHookList) { this.consumeMessageHookList = consumeMessageHookList; } public void setPullMessageResultHandler(PullMessageResultHandler pullMessageResultHandler) { this.pullMessageResultHandler = pullMessageResultHandler; } private boolean isBroadcast(boolean proxyPullBroadcast, ConsumerGroupInfo consumerGroupInfo) { return proxyPullBroadcast || consumerGroupInfo != null && MessageModel.BROADCASTING.equals(consumerGroupInfo.getMessageModel()) && ConsumeType.CONSUME_PASSIVELY.equals(consumerGroupInfo.getConsumeType()); } protected void updateBroadcastPulledOffset(String topic, String group, int queueId, PullMessageRequestHeader requestHeader, Channel channel, RemotingCommand response, long nextBeginOffset) { if (response == null || !this.brokerController.getBrokerConfig().isEnableBroadcastOffsetStore()) { return; } boolean proxyPullBroadcast = Objects.equals( RequestSource.PROXY_FOR_BROADCAST.getValue(), requestHeader.getRequestSource()); ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(group); if (isBroadcast(proxyPullBroadcast, consumerGroupInfo)) { long offset = requestHeader.getQueueOffset(); if (ResponseCode.PULL_OFFSET_MOVED == response.getCode()) { offset = nextBeginOffset; } String clientId; if (proxyPullBroadcast) { clientId = requestHeader.getProxyFrowardClientId(); } else { ClientChannelInfo clientChannelInfo = consumerGroupInfo.findChannel(channel); if (clientChannelInfo == null) { return; } clientId = clientChannelInfo.getClientId(); } this.brokerController.getBroadcastOffsetManager() .updateOffset(topic, group, queueId, offset, clientId, proxyPullBroadcast); } } /** * When pull request is not broadcast or not return -1 */ protected long queryBroadcastPullInitOffset(String topic, String group, int queueId, PullMessageRequestHeader requestHeader, Channel channel) throws RemotingCommandException { if (!this.brokerController.getBrokerConfig().isEnableBroadcastOffsetStore()) { return -1L; } ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(group); boolean proxyPullBroadcast = Objects.equals( RequestSource.PROXY_FOR_BROADCAST.getValue(), requestHeader.getRequestSource()); if (isBroadcast(proxyPullBroadcast, consumerGroupInfo)) { String clientId; if (proxyPullBroadcast) { clientId = requestHeader.getProxyFrowardClientId(); } else { ClientChannelInfo clientChannelInfo = consumerGroupInfo.findChannel(channel); if (clientChannelInfo == null) { return -1; } clientId = clientChannelInfo.getClientId(); } try { return this.brokerController.getBroadcastOffsetManager() .queryInitOffset(topic, group, queueId, clientId, requestHeader.getQueueOffset(), proxyPullBroadcast); } catch (ConsumeQueueException e) { throw new RemotingCommandException("Failed to query initial offset", e); } } return -1L; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.loadbalance.MessageRequestModeManager; import org.apache.rocketmq.broker.topic.TopicRouteInfoManager; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragelyByCircle; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentRequestBody; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentResponseBody; import org.apache.rocketmq.remoting.protocol.body.SetMessageRequestModeRequestBody; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class QueryAssignmentProcessor implements NettyRequestProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private final ConcurrentHashMap name2LoadStrategy = new ConcurrentHashMap<>(); private MessageRequestModeManager messageRequestModeManager; public QueryAssignmentProcessor(final BrokerController brokerController) { this.brokerController = brokerController; //register strategy //NOTE: init with broker's log instead of init with ClientLogger.getLog(); AllocateMessageQueueAveragely allocateMessageQueueAveragely = new AllocateMessageQueueAveragely(); name2LoadStrategy.put(allocateMessageQueueAveragely.getName(), allocateMessageQueueAveragely); AllocateMessageQueueAveragelyByCircle allocateMessageQueueAveragelyByCircle = new AllocateMessageQueueAveragelyByCircle(); name2LoadStrategy.put(allocateMessageQueueAveragelyByCircle.getName(), allocateMessageQueueAveragelyByCircle); this.messageRequestModeManager = new MessageRequestModeManager(brokerController); this.messageRequestModeManager.load(); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { switch (request.getCode()) { case RequestCode.QUERY_ASSIGNMENT: return this.queryAssignment(ctx, request); case RequestCode.SET_MESSAGE_REQUEST_MODE: return this.setMessageRequestMode(ctx, request); default: break; } return null; } @Override public boolean rejectRequest() { return false; } /** * */ private RemotingCommand queryAssignment(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final QueryAssignmentRequestBody requestBody = QueryAssignmentRequestBody.decode(request.getBody(), QueryAssignmentRequestBody.class); final String topic = requestBody.getTopic(); final String consumerGroup = requestBody.getConsumerGroup(); final String clientId = requestBody.getClientId(); final MessageModel messageModel = requestBody.getMessageModel(); final String strategyName = requestBody.getStrategyName(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); final QueryAssignmentResponseBody responseBody = new QueryAssignmentResponseBody(); SetMessageRequestModeRequestBody setMessageRequestModeRequestBody = this.messageRequestModeManager.getMessageRequestMode(topic, consumerGroup); if (setMessageRequestModeRequestBody == null) { setMessageRequestModeRequestBody = new SetMessageRequestModeRequestBody(); setMessageRequestModeRequestBody.setTopic(topic); setMessageRequestModeRequestBody.setConsumerGroup(consumerGroup); if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { // retry topic must be pull mode setMessageRequestModeRequestBody.setMode(MessageRequestMode.PULL); } else { setMessageRequestModeRequestBody.setMode(brokerController.getBrokerConfig().getDefaultMessageRequestMode()); } if (setMessageRequestModeRequestBody.getMode() == MessageRequestMode.POP) { setMessageRequestModeRequestBody.setPopShareQueueNum(brokerController.getBrokerConfig().getDefaultPopShareQueueNum()); } } Set messageQueues = doLoadBalance(topic, consumerGroup, clientId, messageModel, strategyName, setMessageRequestModeRequestBody, ctx); Set assignments = null; if (messageQueues != null) { assignments = new HashSet<>(); for (MessageQueue messageQueue : messageQueues) { MessageQueueAssignment messageQueueAssignment = new MessageQueueAssignment(); messageQueueAssignment.setMessageQueue(messageQueue); if (setMessageRequestModeRequestBody != null) { messageQueueAssignment.setMode(setMessageRequestModeRequestBody.getMode()); } assignments.add(messageQueueAssignment); } } responseBody.setMessageQueueAssignments(assignments); response.setBody(responseBody.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } /** * Returns empty set means the client should clear all load assigned to it before, null means invalid result and the * client should skip the update logic * * @param topic * @param consumerGroup * @param clientId * @param messageModel * @param strategyName * @return the MessageQueues assigned to this client */ private Set doLoadBalance(final String topic, final String consumerGroup, final String clientId, final MessageModel messageModel, final String strategyName, SetMessageRequestModeRequestBody setMessageRequestModeRequestBody, final ChannelHandlerContext ctx) { Set assignedQueueSet = null; final TopicRouteInfoManager topicRouteInfoManager = this.brokerController.getTopicRouteInfoManager(); switch (messageModel) { case BROADCASTING: { assignedQueueSet = topicRouteInfoManager.getTopicSubscribeInfo(topic); if (assignedQueueSet == null) { log.warn("QueryLoad: no assignment for group[{}], the topic[{}] does not exist.", consumerGroup, topic); } break; } case CLUSTERING: { Set mqSet; if (MixAll.isLmq(topic)) { mqSet = new HashSet<>(); mqSet.add(new MessageQueue( topic, brokerController.getBrokerConfig().getBrokerName(), (int)MixAll.LMQ_QUEUE_ID)); } else { mqSet = topicRouteInfoManager.getTopicSubscribeInfo(topic); } if (null == mqSet) { if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { log.warn("QueryLoad: no assignment for group[{}], the topic[{}] does not exist.", consumerGroup, topic); } return null; } if (!brokerController.getBrokerConfig().isServerLoadBalancerEnable()) { return mqSet; } List cidAll = null; ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(consumerGroup); if (consumerGroupInfo != null) { cidAll = consumerGroupInfo.getAllClientId(); } if (null == cidAll) { log.warn("QueryLoad: no assignment for group[{}] topic[{}], get consumer id list failed", consumerGroup, topic); return null; } List mqAll = new ArrayList<>(); mqAll.addAll(mqSet); Collections.sort(mqAll); Collections.sort(cidAll); List allocateResult = null; try { AllocateMessageQueueStrategy allocateMessageQueueStrategy = name2LoadStrategy.get(strategyName); if (null == allocateMessageQueueStrategy) { log.warn("QueryLoad: unsupported strategy [{}], {}", strategyName, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); return null; } if (setMessageRequestModeRequestBody != null && setMessageRequestModeRequestBody.getMode() == MessageRequestMode.POP) { allocateResult = allocate4Pop(allocateMessageQueueStrategy, consumerGroup, clientId, mqAll, cidAll, setMessageRequestModeRequestBody.getPopShareQueueNum()); } else { allocateResult = allocateMessageQueueStrategy.allocate(consumerGroup, clientId, mqAll, cidAll); } } catch (Throwable e) { log.error("QueryLoad: no assignment for group[{}] topic[{}], allocate message queue exception. strategy name: {}, ex: {}", consumerGroup, topic, strategyName, e); return null; } assignedQueueSet = new HashSet<>(); if (allocateResult != null) { assignedQueueSet.addAll(allocateResult); } break; } default: break; } return assignedQueueSet; } public List allocate4Pop(AllocateMessageQueueStrategy allocateMessageQueueStrategy, final String consumerGroup, final String clientId, List mqAll, List cidAll, int popShareQueueNum) { List allocateResult; if (popShareQueueNum <= 0 || popShareQueueNum >= cidAll.size() - 1) { //each client pop all messagequeue allocateResult = new ArrayList<>(mqAll.size()); for (MessageQueue mq : mqAll) { //must create new MessageQueue in case of change cache in AssignmentManager MessageQueue newMq = new MessageQueue(mq.getTopic(), mq.getBrokerName(), -1); allocateResult.add(newMq); } } else { if (cidAll.size() <= mqAll.size()) { //consumer working in pop mode could share the MessageQueues assigned to the N (N = popWorkGroupSize) consumer following it in the cid list allocateResult = allocateMessageQueueStrategy.allocate(consumerGroup, clientId, mqAll, cidAll); int index = cidAll.indexOf(clientId); if (index >= 0) { for (int i = 1; i <= popShareQueueNum; i++) { index++; index = index % cidAll.size(); List tmp = allocateMessageQueueStrategy.allocate(consumerGroup, cidAll.get(index), mqAll, cidAll); allocateResult.addAll(tmp); } } } else { //make sure each cid is assigned allocateResult = allocate(consumerGroup, clientId, mqAll, cidAll); } } return allocateResult; } private List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { if (StringUtils.isBlank(currentCID)) { throw new IllegalArgumentException("currentCID is empty"); } if (CollectionUtils.isEmpty(mqAll)) { throw new IllegalArgumentException("mqAll is null or mqAll empty"); } if (CollectionUtils.isEmpty(cidAll)) { throw new IllegalArgumentException("cidAll is null or cidAll empty"); } List result = new ArrayList<>(); if (!cidAll.contains(currentCID)) { log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}", consumerGroup, currentCID, cidAll); return result; } int index = cidAll.indexOf(currentCID); result.add(mqAll.get(index % mqAll.size())); return result; } private RemotingCommand setMessageRequestMode(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final SetMessageRequestModeRequestBody requestBody = SetMessageRequestModeRequestBody.decode(request.getBody(), SetMessageRequestModeRequestBody.class); final String topic = requestBody.getTopic(); if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("retry topic is not allowed to set mode"); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("topic[" + topic + "] not exist"); return response; } final String consumerGroup = requestBody.getConsumerGroup(); SubscriptionGroupConfig groupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(consumerGroup); if (null == groupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark("subscription group does not exist"); return response; } this.messageRequestModeManager.setMessageRequestMode(topic, consumerGroup, requestBody); this.messageRequestModeManager.persist(); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public MessageRequestModeManager getMessageRequestModeManager() { return messageRequestModeManager; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.FileRegion; import io.opentelemetry.api.common.Attributes; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.pagecache.OneMessageTransfer; import org.apache.rocketmq.broker.pagecache.QueryMessageTransfer; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; import org.apache.rocketmq.store.QueryMessageResult; import org.apache.rocketmq.store.SelectMappedBufferResult; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_REQUEST_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESPONSE_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESULT; public class QueryMessageProcessor implements NettyRequestProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; public QueryMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { switch (request.getCode()) { case RequestCode.QUERY_MESSAGE: return this.queryMessage(ctx, request); case RequestCode.VIEW_MESSAGE_BY_ID: return this.viewMessageById(ctx, request); default: break; } return null; } @Override public boolean rejectRequest() { return false; } public RemotingCommand queryMessage(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(QueryMessageResponseHeader.class); final QueryMessageResponseHeader responseHeader = (QueryMessageResponseHeader) response.readCustomHeader(); final QueryMessageRequestHeader requestHeader = (QueryMessageRequestHeader) request .decodeCommandCustomHeader(QueryMessageRequestHeader.class); response.setOpaque(request.getOpaque()); String indexType = requestHeader.getIndexType(); String lastKey = requestHeader.getLastKey(); String isUniqueKey = null; if (null != request.getExtFields()) { isUniqueKey = request.getExtFields().get(MixAll.UNIQUE_MSG_QUERY_FLAG); } if (!StringUtils.isEmpty(isUniqueKey) && Boolean.parseBoolean(isUniqueKey)) { requestHeader.setMaxNum(this.brokerController.getMessageStoreConfig().getDefaultQueryMaxNum()); indexType = MessageConst.INDEX_UNIQUE_TYPE; } else if (StringUtils.isEmpty(indexType)) { indexType = MessageConst.INDEX_KEY_TYPE; } final QueryMessageResult queryMessageResult = this.brokerController.getMessageStore().queryMessage(requestHeader.getTopic(), requestHeader.getKey(), requestHeader.getMaxNum(), requestHeader.getBeginTimestamp(), requestHeader.getEndTimestamp(), indexType, lastKey); assert queryMessageResult != null; responseHeader.setIndexLastUpdatePhyoffset(queryMessageResult.getIndexLastUpdatePhyoffset()); responseHeader.setIndexLastUpdateTimestamp(queryMessageResult.getIndexLastUpdateTimestamp()); if (queryMessageResult.getBufferTotalSize() > 0) { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); try { FileRegion fileRegion = new QueryMessageTransfer(response.encodeHeader(queryMessageResult .getBufferTotalSize()), queryMessageResult); ctx.channel() .writeAndFlush(fileRegion) .addListener((ChannelFutureListener) future -> { queryMessageResult.release(); RemotingMetricsManager remotingMetricsManager = brokerController.getBrokerMetricsManager().getRemotingMetricsManager(); Attributes attributes = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(response.getCode())) .put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)) .build(); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { LOGGER.error("transfer query message by page cache failed, ", future.cause()); } }); } catch (Throwable e) { LOGGER.error("", e); queryMessageResult.release(); } return null; } response.setCode(ResponseCode.QUERY_NOT_FOUND); response.setRemark("can not find message, maybe time range not correct"); return response; } public RemotingCommand viewMessageById(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final ViewMessageRequestHeader requestHeader = (ViewMessageRequestHeader) request.decodeCommandCustomHeader(ViewMessageRequestHeader.class); response.setOpaque(request.getOpaque()); final SelectMappedBufferResult selectMappedBufferResult = this.brokerController.getMessageStore().selectOneMessageByOffset(requestHeader.getOffset()); if (selectMappedBufferResult != null) { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); try { FileRegion fileRegion = new OneMessageTransfer(response.encodeHeader(selectMappedBufferResult.getSize()), selectMappedBufferResult); ctx.channel() .writeAndFlush(fileRegion) .addListener((ChannelFutureListener) future -> { selectMappedBufferResult.release(); RemotingMetricsManager remotingMetricsManager = brokerController.getBrokerMetricsManager().getRemotingMetricsManager(); Attributes attributes = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(response.getCode())) .put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)) .build(); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { LOGGER.error("Transfer one message from page cache failed, ", future.cause()); } }); } catch (Throwable e) { LOGGER.error("", e); selectMappedBufferResult.release(); } return null; } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("can not find message by the offset, " + requestHeader.getOffset()); } return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/RecallMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import org.apache.commons.codec.DecoderException; import org.apache.commons.lang3.math.NumberUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.timer.TimerMessageStore; import java.nio.charset.StandardCharsets; public class RecallMessageProcessor implements NettyRequestProcessor { private static final String RECALL_MESSAGE_TAG = "_RECALL_TAG_"; private final BrokerController brokerController; public RecallMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(RecallMessageResponseHeader.class); response.addExtField(MessageConst.PROPERTY_MSG_REGION, this.brokerController.getBrokerConfig().getRegionId()); final RecallMessageRequestHeader requestHeader = request.decodeCommandCustomHeader(RecallMessageRequestHeader.class); if (!brokerController.getBrokerConfig().isRecallMessageEnable()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("recall failed, operation is forbidden"); return response; } if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) { response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE); response.setRemark("recall failed, broker service not available"); return response; } final long startTimestamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp(); if (this.brokerController.getMessageStore().now() < startTimestamp) { response.setCode(ResponseCode.SERVICE_NOT_AVAILABLE); response.setRemark("recall failed, broker service not available"); return response; } if (!PermName.isWriteable(this.brokerController.getBrokerConfig().getBrokerPermission()) && !this.brokerController.getBrokerConfig().isAllowRecallWhenBrokerNotWriteable()) { response.setCode(ResponseCode.SERVICE_NOT_AVAILABLE); response.setRemark("recall failed, broker service not available"); return response; } TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { response.setCode(ResponseCode.TOPIC_NOT_EXIST); response.setRemark("recall failed, the topic[" + requestHeader.getTopic() + "] not exist"); return response; } RecallMessageHandle.HandleV1 handle; try { handle = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(requestHeader.getRecallHandle()); } catch (DecoderException e) { response.setCode(ResponseCode.ILLEGAL_OPERATION); response.setRemark(e.getMessage()); return response; } if (!requestHeader.getTopic().equals(handle.getTopic())) { response.setCode(ResponseCode.ILLEGAL_OPERATION); response.setRemark("recall failed, topic not match"); return response; } if (!brokerController.getBrokerConfig().getBrokerName().equals(handle.getBrokerName())) { response.setCode(ResponseCode.ILLEGAL_OPERATION); response.setRemark("recall failed, broker service not available"); return response; } long timestamp = NumberUtils.toLong(handle.getTimestampStr(), -1); long timeLeft = timestamp - System.currentTimeMillis(); if (timeLeft <= 0 || timeLeft >= brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L) { response.setCode(ResponseCode.ILLEGAL_OPERATION); response.setRemark("recall failed, timestamp invalid"); return response; } MessageExtBrokerInner msgInner = buildMessage(ctx, requestHeader, handle); long beginTimeMillis = this.brokerController.getMessageStore().now(); PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); handlePutMessageResult(putMessageResult, request, response, msgInner, ctx, beginTimeMillis); return response; } public MessageExtBrokerInner buildMessage(ChannelHandlerContext ctx, RecallMessageRequestHeader requestHeader, RecallMessageHandle.HandleV1 handle) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(handle.getTopic()); msgInner.setBody("0".getBytes(StandardCharsets.UTF_8)); msgInner.setTags(RECALL_MESSAGE_TAG); msgInner.setTagsCode(RECALL_MESSAGE_TAG.hashCode()); msgInner.setQueueId(0); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TIMER_DEL_UNIQKEY, TimerMessageStore.buildDeleteKey( handle.getTopic(), handle.getMessageId(), brokerController.getMessageStoreConfig().isAppendTopicForTimerDeleteKey())); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, handle.getMessageId()); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TIMER_DELIVER_MS, String.valueOf(handle.getTimestampStr())); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_BORN_TIMESTAMP, String.valueOf(System.currentTimeMillis())); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRACE_CONTEXT, ""); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_PRODUCER_GROUP, requestHeader.getProducerGroup()); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); msgInner.setBornHost(ctx.channel().remoteAddress()); msgInner.setStoreHost(this.brokerController.getStoreHost()); return msgInner; } public void handlePutMessageResult(PutMessageResult putMessageResult, RemotingCommand request, RemotingCommand response, MessageExt message, ChannelHandlerContext ctx, long beginTimeMillis) { if (null == putMessageResult) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("recall failed, execute error"); return; } RecallMessageResponseHeader responseHeader = (RecallMessageResponseHeader) response.readCustomHeader(); switch (putMessageResult.getPutMessageStatus()) { case PUT_OK: this.brokerController.getBrokerStatsManager().incTopicPutNums( message.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); // system timer topic this.brokerController.getBrokerStatsManager().incTopicPutSize( message.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); this.brokerController.getBrokerStatsManager().incBrokerPutNums( message.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum()); this.brokerController.getBrokerStatsManager().incTopicPutLatency( message.getTopic(), 0, (int) (this.brokerController.getMessageStore().now() - beginTimeMillis)); case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case SLAVE_NOT_AVAILABLE: response.setCode(ResponseCode.SUCCESS); responseHeader.setMsgId(MessageClientIDSetter.getUniqID(message)); break; default: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("recall failed, execute error"); break; } } @Override public boolean rejectRequest() { return false; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.opentelemetry.api.common.Attributes; import java.net.InetSocketAddress; import java.util.concurrent.ThreadLocalRandom; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.mqtrace.SendMessageContext; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.ReplyMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.stats.BrokerStatsManager; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_MESSAGE_TYPE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; public class ReplyMessageProcessor extends AbstractSendMessageProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public ReplyMessageProcessor(final BrokerController brokerController) { super(brokerController); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { SendMessageContext mqtraceContext = null; SendMessageRequestHeader requestHeader = parseRequestHeader(request); if (requestHeader == null) { return null; } mqtraceContext = buildMsgContext(ctx, requestHeader, request); this.executeSendMessageHookBefore(mqtraceContext); RemotingCommand response = this.processReplyMessageRequest(ctx, request, mqtraceContext, requestHeader); this.executeSendMessageHookAfter(response, mqtraceContext); return response; } @Override protected SendMessageRequestHeader parseRequestHeader(RemotingCommand request) throws RemotingCommandException { SendMessageRequestHeaderV2 requestHeaderV2 = null; SendMessageRequestHeader requestHeader = null; switch (request.getCode()) { case RequestCode.SEND_REPLY_MESSAGE_V2: requestHeaderV2 = (SendMessageRequestHeaderV2) request .decodeCommandCustomHeader(SendMessageRequestHeaderV2.class); case RequestCode.SEND_REPLY_MESSAGE: if (null == requestHeaderV2) { requestHeader = (SendMessageRequestHeader) request .decodeCommandCustomHeader(SendMessageRequestHeader.class); } else { requestHeader = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV1(requestHeaderV2); } default: break; } return requestHeader; } private RemotingCommand processReplyMessageRequest(final ChannelHandlerContext ctx, final RemotingCommand request, final SendMessageContext sendMessageContext, final SendMessageRequestHeader requestHeader) { final RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader(); response.setOpaque(request.getOpaque()); response.addExtField(MessageConst.PROPERTY_MSG_REGION, this.brokerController.getBrokerConfig().getRegionId()); response.addExtField(MessageConst.PROPERTY_TRACE_SWITCH, String.valueOf(this.brokerController.getBrokerConfig().isTraceOn())); log.debug("receive SendReplyMessage request command, {}", request); final long startTimestamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp(); if (this.brokerController.getMessageStore().now() < startTimestamp) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("broker unable to service, until %s", UtilAll.timeMillisToHumanString2(startTimestamp))); return response; } response.setCode(-1); super.msgCheck(ctx, requestHeader, request, response); if (response.getCode() != -1) { return response; } final byte[] body = request.getBody(); int queueIdInt = requestHeader.getQueueId(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (queueIdInt < 0) { queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % topicConfig.getWriteQueueNums(); } MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(requestHeader.getTopic()); msgInner.setQueueId(queueIdInt); msgInner.setBody(body); msgInner.setFlag(requestHeader.getFlag()); MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties())); msgInner.setPropertiesString(requestHeader.getProperties()); msgInner.setBornTimestamp(requestHeader.getBornTimestamp()); msgInner.setBornHost(ctx.channel().remoteAddress()); msgInner.setStoreHost(this.getStoreHost()); msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes()); PushReplyResult pushReplyResult = this.pushReplyMessage(ctx, requestHeader, msgInner); this.handlePushReplyResult(pushReplyResult, response, responseHeader, queueIdInt); if (this.brokerController.getBrokerConfig().isStoreReplyMessageEnable()) { PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); this.handlePutMessageResult(putMessageResult, request, msgInner, responseHeader, sendMessageContext, queueIdInt, BrokerMetricsManager.getMessageType(requestHeader)); } return response; } private PushReplyResult pushReplyMessage(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, final Message msg) { ReplyMessageRequestHeader replyMessageRequestHeader = new ReplyMessageRequestHeader(); InetSocketAddress bornAddress = (InetSocketAddress)(ctx.channel().remoteAddress()); replyMessageRequestHeader.setBornHost(bornAddress.getAddress().getHostAddress() + ":" + bornAddress.getPort()); InetSocketAddress storeAddress = (InetSocketAddress)(this.getStoreHost()); replyMessageRequestHeader.setStoreHost(storeAddress.getAddress().getHostAddress() + ":" + storeAddress.getPort()); replyMessageRequestHeader.setStoreTimestamp(System.currentTimeMillis()); replyMessageRequestHeader.setProducerGroup(requestHeader.getProducerGroup()); replyMessageRequestHeader.setTopic(requestHeader.getTopic()); replyMessageRequestHeader.setDefaultTopic(requestHeader.getDefaultTopic()); replyMessageRequestHeader.setDefaultTopicQueueNums(requestHeader.getDefaultTopicQueueNums()); replyMessageRequestHeader.setQueueId(requestHeader.getQueueId()); replyMessageRequestHeader.setSysFlag(requestHeader.getSysFlag()); replyMessageRequestHeader.setBornTimestamp(requestHeader.getBornTimestamp()); replyMessageRequestHeader.setFlag(requestHeader.getFlag()); replyMessageRequestHeader.setProperties(requestHeader.getProperties()); replyMessageRequestHeader.setReconsumeTimes(requestHeader.getReconsumeTimes()); replyMessageRequestHeader.setUnitMode(requestHeader.isUnitMode()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT, replyMessageRequestHeader); request.setBody(msg.getBody()); String senderId = msg.getProperties().get(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT); PushReplyResult pushReplyResult = new PushReplyResult(false); if (senderId != null) { Channel channel = this.brokerController.getProducerManager().findChannel(senderId); if (channel != null) { msg.getProperties().put(MessageConst.PROPERTY_PUSH_REPLY_TIME, String.valueOf(System.currentTimeMillis())); replyMessageRequestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties())); try { RemotingCommand pushResponse = this.brokerController.getBroker2Client().callClient(channel, request); assert pushResponse != null; switch (pushResponse.getCode()) { case ResponseCode.SUCCESS: { pushReplyResult.setPushOk(true); break; } default: { pushReplyResult.setPushOk(false); pushReplyResult.setRemark("push reply message to " + senderId + "fail."); log.warn("push reply message to <{}> return fail, response remark: {}", senderId, pushResponse.getRemark()); } } } catch (RemotingException | InterruptedException e) { pushReplyResult.setPushOk(false); pushReplyResult.setRemark("push reply message to " + senderId + "fail."); log.warn("push reply message to <{}> fail. {}", senderId, channel, e); } } else { pushReplyResult.setPushOk(false); pushReplyResult.setRemark("push reply message fail, channel of <" + senderId + "> not found."); log.warn(pushReplyResult.getRemark()); } } else { log.warn(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT + " is null, can not reply message"); pushReplyResult.setPushOk(false); pushReplyResult.setRemark("reply message properties[" + MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT + "] is null"); } return pushReplyResult; } private void handlePushReplyResult(PushReplyResult pushReplyResult, final RemotingCommand response, final SendMessageResponseHeader responseHeader, int queueIdInt) { if (!pushReplyResult.isPushOk()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(pushReplyResult.getRemark()); } else { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); //set to zero to avoid client decoding exception responseHeader.setMsgId("0"); responseHeader.setQueueId(queueIdInt); responseHeader.setQueueOffset(0L); } } private void handlePutMessageResult(PutMessageResult putMessageResult, final RemotingCommand request, final MessageExt msg, final SendMessageResponseHeader responseHeader, SendMessageContext sendMessageContext, int queueIdInt, TopicMessageType messageType) { if (putMessageResult == null) { log.warn("process reply message, store putMessage return null"); return; } boolean putOk = false; switch (putMessageResult.getPutMessageStatus()) { // Success case PUT_OK: case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case SLAVE_NOT_AVAILABLE: putOk = true; break; // Failed case CREATE_MAPPED_FILE_FAILED: log.warn("create mapped file failed, server is busy or broken."); break; case MESSAGE_ILLEGAL: log.warn( "the message is illegal, maybe msg body or properties length not matched. msg body length limit {}B.", this.brokerController.getMessageStoreConfig().getMaxMessageSize()); break; case PROPERTIES_SIZE_EXCEEDED: log.warn( "the message is illegal, maybe msg properties length limit 32KB."); break; case SERVICE_NOT_AVAILABLE: log.warn( "service not available now. It may be caused by one of the following reasons: " + "the broker's disk is full, messages are put to the slave, message store has been shut down, etc."); break; case OS_PAGE_CACHE_BUSY: log.warn("[PC_SYNCHRONIZED]broker busy, start flow control for a while"); break; case UNKNOWN_ERROR: log.warn("UNKNOWN_ERROR"); break; default: log.warn("UNKNOWN_ERROR DEFAULT"); break; } String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); int commercialSizePerMsg = brokerController.getBrokerConfig().getCommercialSizePerMsg(); if (putOk) { this.brokerController.getBrokerStatsManager().incTopicPutNums(msg.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); this.brokerController.getBrokerStatsManager().incTopicPutSize(msg.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); this.brokerController.getBrokerStatsManager().incBrokerPutNums(msg.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum()); if (!BrokerMetricsManager.isRetryOrDlqTopic(msg.getTopic())) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, msg.getTopic()) .put(LABEL_MESSAGE_TYPE, messageType.getMetricsValue()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(msg.getTopic())) .build(); this.brokerController.getBrokerMetricsManager().getMessagesInTotal().add(putMessageResult.getAppendMessageResult().getMsgNum(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputInTotal().add(putMessageResult.getAppendMessageResult().getWroteBytes(), attributes); this.brokerController.getBrokerMetricsManager().getMessageSize().record(putMessageResult.getAppendMessageResult().getWroteBytes() / putMessageResult.getAppendMessageResult().getMsgNum(), attributes); } responseHeader.setMsgId(putMessageResult.getAppendMessageResult().getMsgId()); responseHeader.setQueueId(queueIdInt); responseHeader.setQueueOffset(putMessageResult.getAppendMessageResult().getLogicsOffset()); if (hasSendMessageHook()) { sendMessageContext.setMsgId(responseHeader.getMsgId()); sendMessageContext.setQueueId(responseHeader.getQueueId()); sendMessageContext.setQueueOffset(responseHeader.getQueueOffset()); int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); int wroteSize = putMessageResult.getAppendMessageResult().getWroteBytes(); int incValue = (int) Math.ceil(wroteSize * 1.0 / commercialSizePerMsg) * commercialBaseCount; sendMessageContext.setCommercialSendStats(BrokerStatsManager.StatsType.SEND_SUCCESS); sendMessageContext.setCommercialSendTimes(incValue); sendMessageContext.setCommercialSendSize(wroteSize); sendMessageContext.setCommercialOwner(owner); } } else { if (hasSendMessageHook()) { int wroteSize = request.getBody().length; int incValue = (int) Math.ceil(wroteSize * 1.0 / commercialSizePerMsg); sendMessageContext.setCommercialSendStats(BrokerStatsManager.StatsType.SEND_FAILURE); sendMessageContext.setCommercialSendTimes(incValue); sendMessageContext.setCommercialSendSize(wroteSize); sendMessageContext.setCommercialOwner(owner); } } } class PushReplyResult { boolean pushOk; String remark; public PushReplyResult(boolean pushOk) { this.pushOk = pushOk; remark = ""; } public boolean isPushOk() { return pushOk; } public void setPushOk(boolean pushOk) { this.pushOk = pushOk; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import org.apache.rocketmq.broker.mqtrace.SendMessageContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface SendMessageCallback { /** * On send complete. * * @param ctx send context * @param response send response */ void onComplete(SendMessageContext ctx, RemotingCommand response); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import io.opentelemetry.api.common.Attributes; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.mqtrace.SendMessageContext; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CleanupPolicy; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.CleanupPolicyUtils; import org.apache.rocketmq.common.utils.MessageUtils; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.stats.BrokerStatsManager; import java.nio.ByteBuffer; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_MESSAGE_TYPE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; import static org.apache.rocketmq.remoting.protocol.RemotingCommand.buildErrorResponse; public class SendMessageProcessor extends AbstractSendMessageProcessor implements NettyRequestProcessor { public SendMessageProcessor(final BrokerController brokerController) { super(brokerController); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { SendMessageContext sendMessageContext; switch (request.getCode()) { case RequestCode.CONSUMER_SEND_MSG_BACK: return this.consumerSendMsgBack(ctx, request); default: SendMessageRequestHeader requestHeader = parseRequestHeader(request); if (requestHeader == null) { return null; } TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader, true); RemotingCommand rewriteResult = this.brokerController.getTopicQueueMappingManager().rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } sendMessageContext = buildMsgContext(ctx, requestHeader, request); try { this.executeSendMessageHookBefore(sendMessageContext); } catch (AbortProcessException e) { final RemotingCommand errorResponse = RemotingCommand.createResponseCommand(e.getResponseCode(), e.getErrorMessage()); errorResponse.setOpaque(request.getOpaque()); return errorResponse; } RemotingCommand response; clearReservedProperties(requestHeader); if (requestHeader.isBatch()) { response = this.sendBatchMessage(ctx, request, sendMessageContext, requestHeader, mappingContext, (ctx1, response1) -> executeSendMessageHookAfter(response1, ctx1)); } else { response = this.sendMessage(ctx, request, sendMessageContext, requestHeader, mappingContext, (ctx12, response12) -> executeSendMessageHookAfter(response12, ctx12)); } return response; } } @Override public boolean rejectRequest() { if (!this.brokerController.getBrokerConfig().isEnableSlaveActingMaster() && this.brokerController.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { return true; } if (this.brokerController.getMessageStore().isOSPageCacheBusy() || this.brokerController.getMessageStore().isTransientStorePoolDeficient()) { return true; } return false; } private void clearReservedProperties(SendMessageRequestHeader requestHeader) { String properties = requestHeader.getProperties(); properties = MessageUtils.deleteProperty(properties, MessageConst.PROPERTY_POP_CK); requestHeader.setProperties(properties); } /** * If the response is not null, it meets some errors * * @return */ private RemotingCommand rewriteResponseForStaticTopic(SendMessageResponseHeader responseHeader, TopicQueueMappingContext mappingContext) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); LogicQueueMappingItem mappingItem = mappingContext.getLeaderItem(); if (mappingItem == null) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", mappingContext.getTopic(), mappingContext.getGlobalId(), mappingDetail.getBname())); } //no need to care the broker name long staticLogicOffset = mappingItem.computeStaticQueueOffsetLoosely(responseHeader.getQueueOffset()); if (staticLogicOffset < 0) { //if the logic offset is -1, just let it go //maybe we need a dynamic config //return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d convert offset error in current broker %s", mappingContext.getTopic(), mappingContext.getGlobalId(), mappingDetail.getBname())); } responseHeader.setQueueId(mappingContext.getGlobalId()); responseHeader.setQueueOffset(staticLogicOffset); } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } return null; } private boolean handleRetryAndDLQ(SendMessageRequestHeader requestHeader, RemotingCommand response, RemotingCommand request, MessageExt msg, TopicConfig topicConfig, Map properties) { String newTopic = requestHeader.getTopic(); if (null != newTopic && newTopic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { String groupName = KeyBuilder.parseGroup(newTopic); SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(groupName); if (null == subscriptionGroupConfig) { response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); response.setRemark( "subscription group not exist, " + groupName + " " + FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)); return false; } int maxReconsumeTimes = subscriptionGroupConfig.getRetryMaxTimes(); if (request.getVersion() >= MQVersion.Version.V3_4_9.ordinal() && requestHeader.getMaxReconsumeTimes() != null) { maxReconsumeTimes = requestHeader.getMaxReconsumeTimes(); } int reconsumeTimes = requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes(); boolean sendRetryMessageToDeadLetterQueueDirectly = false; if (!brokerController.getRebalanceLockManager().isLockAllExpired(groupName)) { LOGGER.info("Group has unexpired lock record, which show it is ordered message, send it to DLQ " + "right now group={}, topic={}, reconsumeTimes={}, maxReconsumeTimes={}.", groupName, newTopic, reconsumeTimes, maxReconsumeTimes); sendRetryMessageToDeadLetterQueueDirectly = true; } if (reconsumeTimes > maxReconsumeTimes || sendRetryMessageToDeadLetterQueueDirectly) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_CONSUMER_GROUP, requestHeader.getProducerGroup()) .put(LABEL_TOPIC, requestHeader.getTopic()) .put(LABEL_IS_SYSTEM, BrokerMetricsManager.isSystem(requestHeader.getTopic(), requestHeader.getProducerGroup())) .build(); this.brokerController.getBrokerMetricsManager().getSendToDlqMessages().add(1, attributes); properties.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "-1"); newTopic = MixAll.getDLQTopic(groupName); int queueIdInt = randomQueueId(DLQ_NUMS_PER_GROUP); topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic, DLQ_NUMS_PER_GROUP, PermName.PERM_WRITE | PermName.PERM_READ, 0 ); msg.setTopic(newTopic); msg.setQueueId(queueIdInt); msg.setDelayTimeLevel(0); if (null == topicConfig) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("topic[" + newTopic + "] not exist"); return false; } } } int sysFlag = requestHeader.getSysFlag(); if (TopicFilterType.MULTI_TAG == topicConfig.getTopicFilterType()) { sysFlag |= MessageSysFlag.MULTI_TAGS_FLAG; } msg.setSysFlag(sysFlag); return true; } public RemotingCommand sendMessage(final ChannelHandlerContext ctx, final RemotingCommand request, final SendMessageContext sendMessageContext, final SendMessageRequestHeader requestHeader, final TopicQueueMappingContext mappingContext, final SendMessageCallback sendMessageCallback) throws RemotingCommandException { final RemotingCommand response = preSend(ctx, request, requestHeader); if (response.getCode() != -1) { return response; } final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader(); final byte[] body = request.getBody(); int queueIdInt = requestHeader.getQueueId(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (queueIdInt < 0) { queueIdInt = randomQueueId(topicConfig.getWriteQueueNums()); } MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(requestHeader.getTopic()); msgInner.setQueueId(queueIdInt); Map oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig, oriProps)) { return response; } msgInner.setBody(body); msgInner.setFlag(requestHeader.getFlag()); String uniqKey = oriProps.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (uniqKey == null || uniqKey.length() <= 0) { uniqKey = MessageClientIDSetter.createUniqID(); oriProps.put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, uniqKey); } // liteTopic multi dispatch String liteTopic = oriProps.get(MessageConst.PROPERTY_LITE_TOPIC); if (StringUtils.isNotEmpty(liteTopic)) { String lmqName = LiteUtil.toLmqName(requestHeader.getTopic(), liteTopic); oriProps.put(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqName); } MessageAccessor.setProperties(msgInner, oriProps); // check properties to ensure exclusive, don't check topic meta config to keep the behavior consistent int msgPriority = msgInner.getPriority(); if (msgPriority >= 0) { if (TopicMessageType.PRIORITY.equals(TopicMessageType.parseFromMessageProperty(msgInner.getProperties()))) { queueIdInt = Math.min(msgPriority, topicConfig.getWriteQueueNums() - 1); msgInner.setQueueId(queueIdInt); } else { MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_PRIORITY); } } CleanupPolicy cleanupPolicy = CleanupPolicyUtils.getDeletePolicy(Optional.of(topicConfig)); if (Objects.equals(cleanupPolicy, CleanupPolicy.COMPACTION)) { if (StringUtils.isBlank(msgInner.getKeys())) { response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark("Required message key is missing"); return response; } } msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(topicConfig.getTopicFilterType(), msgInner.getTags())); msgInner.setBornTimestamp(requestHeader.getBornTimestamp()); msgInner.setBornHost(ctx.channel().remoteAddress()); msgInner.setStoreHost(this.getStoreHost()); msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes()); String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName(); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_CLUSTER, clusterName); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); // Map oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); boolean sendTransactionPrepareMessage; if (Boolean.parseBoolean(traFlag) && !(msgInner.getReconsumeTimes() > 0 && msgInner.getDelayTimeLevel() > 0)) { //For client under version 4.6.1 if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark( "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() + "] sending transaction message is forbidden"); return response; } sendTransactionPrepareMessage = true; } else { sendTransactionPrepareMessage = false; } long beginTimeMillis = this.brokerController.getMessageStore().now(); if (brokerController.getBrokerConfig().isAsyncSendEnable()) { CompletableFuture asyncPutMessageFuture; if (sendTransactionPrepareMessage) { asyncPutMessageFuture = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner); } else { asyncPutMessageFuture = this.brokerController.getMessageStore().asyncPutMessage(msgInner); } final int finalQueueIdInt = queueIdInt; final MessageExtBrokerInner finalMsgInner = msgInner; asyncPutMessageFuture.thenAcceptAsync(putMessageResult -> { RemotingCommand responseFuture = handlePutMessageResult(putMessageResult, response, request, finalMsgInner, responseHeader, sendMessageContext, ctx, finalQueueIdInt, beginTimeMillis, mappingContext, BrokerMetricsManager.getMessageType(requestHeader)); if (responseFuture != null) { doResponse(ctx, request, responseFuture); } // record the transaction metrics, responseFuture == null means put successfully if (sendTransactionPrepareMessage && (responseFuture == null || responseFuture.getCode() == ResponseCode.SUCCESS)) { this.brokerController.getTransactionalMessageService().getTransactionMetrics().addAndGet(msgInner.getProperty(MessageConst.PROPERTY_REAL_TOPIC), 1); } sendMessageCallback.onComplete(sendMessageContext, response); }, this.brokerController.getPutMessageFutureExecutor()); // Returns null to release the send message thread return null; } else { PutMessageResult putMessageResult = null; if (sendTransactionPrepareMessage) { putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner); } else { putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); } handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt, beginTimeMillis, mappingContext, BrokerMetricsManager.getMessageType(requestHeader)); // record the transaction metrics if (sendTransactionPrepareMessage && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK && putMessageResult.getAppendMessageResult().isOk()) { this.brokerController.getTransactionalMessageService().getTransactionMetrics().addAndGet(msgInner.getProperty(MessageConst.PROPERTY_REAL_TOPIC), 1); } sendMessageCallback.onComplete(sendMessageContext, response); return response; } } private RemotingCommand handlePutMessageResult(PutMessageResult putMessageResult, RemotingCommand response, RemotingCommand request, MessageExt msg, SendMessageResponseHeader responseHeader, SendMessageContext sendMessageContext, ChannelHandlerContext ctx, int queueIdInt, long beginTimeMillis, TopicQueueMappingContext mappingContext, TopicMessageType messageType) { if (putMessageResult == null) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("store putMessage return null"); return response; } boolean sendOK = false; switch (putMessageResult.getPutMessageStatus()) { // Success case PUT_OK: sendOK = true; response.setCode(ResponseCode.SUCCESS); break; case FLUSH_DISK_TIMEOUT: response.setCode(ResponseCode.FLUSH_DISK_TIMEOUT); sendOK = true; break; case FLUSH_SLAVE_TIMEOUT: response.setCode(ResponseCode.FLUSH_SLAVE_TIMEOUT); sendOK = true; break; case SLAVE_NOT_AVAILABLE: response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE); sendOK = true; break; // Failed case IN_SYNC_REPLICAS_NOT_ENOUGH: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("in-sync replicas not enough"); break; case CREATE_MAPPED_FILE_FAILED: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("create mapped file failed, server is busy or broken."); break; case MESSAGE_ILLEGAL: case PROPERTIES_SIZE_EXCEEDED: response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(String.format("the message is illegal, maybe msg body or properties length not matched. msg body length limit %dB, msg properties length limit 32KB.", this.brokerController.getMessageStoreConfig().getMaxMessageSize())); break; case WHEEL_TIMER_MSG_ILLEGAL: response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(String.format("timer message illegal, the delay time should not be bigger than the max delay %dms; or if set del msg, the delay time should be bigger than the current time", this.brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L)); break; case WHEEL_TIMER_FLOW_CONTROL: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("timer message is under flow control, max num limit is %d or the current value is greater than %d and less than %d, trigger random flow control", this.brokerController.getMessageStoreConfig().getTimerCongestNumEachSlot() * 2L, this.brokerController.getMessageStoreConfig().getTimerCongestNumEachSlot(), this.brokerController.getMessageStoreConfig().getTimerCongestNumEachSlot() * 2L)); break; case WHEEL_TIMER_NOT_ENABLE: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("accurate timer message is not enabled, timerWheelEnable is %s", this.brokerController.getMessageStoreConfig().isTimerWheelEnable())); break; case SERVICE_NOT_AVAILABLE: response.setCode(ResponseCode.SERVICE_NOT_AVAILABLE); response.setRemark( "service not available now. It may be caused by one of the following reasons: " + "the broker's disk is full [" + diskUtil() + "], messages are put to the slave, message store has been shut down, etc."); break; case OS_PAGE_CACHE_BUSY: response.setCode(ResponseCode.SYSTEM_BUSY); response.setRemark("[PC_SYNCHRONIZED]broker busy, start flow control for a while"); break; case LMQ_CONSUME_QUEUE_NUM_EXCEEDED: response.setCode(ResponseCode.LMQ_QUOTA_EXCEEDED); response.setRemark("[LMQ_CONSUME_QUEUE_NUM_EXCEEDED]lmq consume queue num exceeded."); break; case UNKNOWN_ERROR: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UNKNOWN_ERROR"); break; default: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UNKNOWN_ERROR DEFAULT"); break; } String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); String authType = request.getExtFields().get(BrokerStatsManager.ACCOUNT_AUTH_TYPE); String ownerParent = request.getExtFields().get(BrokerStatsManager.ACCOUNT_OWNER_PARENT); String ownerSelf = request.getExtFields().get(BrokerStatsManager.ACCOUNT_OWNER_SELF); int commercialSizePerMsg = brokerController.getBrokerConfig().getCommercialSizePerMsg(); if (sendOK) { if (TopicValidator.RMQ_SYS_SCHEDULE_TOPIC.equals(msg.getTopic())) { this.brokerController.getBrokerStatsManager().incQueuePutNums(msg.getTopic(), msg.getQueueId(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); this.brokerController.getBrokerStatsManager().incQueuePutSize(msg.getTopic(), msg.getQueueId(), putMessageResult.getAppendMessageResult().getWroteBytes()); } this.brokerController.getBrokerStatsManager().incTopicPutNums(msg.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); this.brokerController.getBrokerStatsManager().incTopicPutSize(msg.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); this.brokerController.getBrokerStatsManager().incBrokerPutNums(msg.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum()); this.brokerController.getBrokerStatsManager().incTopicPutLatency(msg.getTopic(), queueIdInt, (int) (this.brokerController.getMessageStore().now() - beginTimeMillis)); if (!BrokerMetricsManager.isRetryOrDlqTopic(msg.getTopic())) { Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, msg.getTopic()) .put(LABEL_MESSAGE_TYPE, messageType.getMetricsValue()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(msg.getTopic())) .build(); this.brokerController.getBrokerMetricsManager().getMessagesInTotal().add(putMessageResult.getAppendMessageResult().getMsgNum(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputInTotal().add(putMessageResult.getAppendMessageResult().getWroteBytes(), attributes); this.brokerController.getBrokerMetricsManager().getMessageSize().record(putMessageResult.getAppendMessageResult().getWroteBytes() / putMessageResult.getAppendMessageResult().getMsgNum(), attributes); } response.setRemark(null); responseHeader.setMsgId(putMessageResult.getAppendMessageResult().getMsgId()); responseHeader.setQueueId(queueIdInt); responseHeader.setQueueOffset(putMessageResult.getAppendMessageResult().getLogicsOffset()); responseHeader.setTransactionId(MessageClientIDSetter.getUniqID(msg)); attachRecallHandle(request, msg, responseHeader); RemotingCommand rewriteResult = rewriteResponseForStaticTopic(responseHeader, mappingContext); if (rewriteResult != null) { return rewriteResult; } doResponse(ctx, request, response); if (hasSendMessageHook()) { sendMessageContext.setMsgId(responseHeader.getMsgId()); sendMessageContext.setQueueId(responseHeader.getQueueId()); sendMessageContext.setQueueOffset(responseHeader.getQueueOffset()); int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); int wroteSize = putMessageResult.getAppendMessageResult().getWroteBytes(); int msgNum = putMessageResult.getAppendMessageResult().getMsgNum(); int commercialMsgNum = (int) Math.ceil(wroteSize / (double) commercialSizePerMsg); int incValue = commercialMsgNum * commercialBaseCount; sendMessageContext.setCommercialSendStats(BrokerStatsManager.StatsType.SEND_SUCCESS); sendMessageContext.setCommercialSendTimes(incValue); sendMessageContext.setCommercialSendSize(wroteSize); sendMessageContext.setCommercialOwner(owner); sendMessageContext.setSendStat(BrokerStatsManager.StatsType.SEND_SUCCESS); sendMessageContext.setCommercialSendMsgNum(commercialMsgNum); sendMessageContext.setAccountAuthType(authType); sendMessageContext.setAccountOwnerParent(ownerParent); sendMessageContext.setAccountOwnerSelf(ownerSelf); sendMessageContext.setSendMsgSize(wroteSize); sendMessageContext.setSendMsgNum(msgNum); } return null; } else { if (hasSendMessageHook()) { AppendMessageResult appendMessageResult = putMessageResult.getAppendMessageResult(); // TODO process partial failures of batch message int wroteSize = request.getBody().length; int msgNum = Math.max(appendMessageResult != null ? appendMessageResult.getMsgNum() : 1, 1); int commercialMsgNum = (int) Math.ceil(wroteSize / (double) commercialSizePerMsg); sendMessageContext.setCommercialSendStats(BrokerStatsManager.StatsType.SEND_FAILURE); sendMessageContext.setCommercialSendTimes(commercialMsgNum); sendMessageContext.setCommercialSendSize(wroteSize); sendMessageContext.setCommercialOwner(owner); sendMessageContext.setSendStat(BrokerStatsManager.StatsType.SEND_FAILURE); sendMessageContext.setCommercialSendMsgNum(commercialMsgNum); sendMessageContext.setAccountAuthType(authType); sendMessageContext.setAccountOwnerParent(ownerParent); sendMessageContext.setAccountOwnerSelf(ownerSelf); sendMessageContext.setSendMsgSize(wroteSize); sendMessageContext.setSendMsgNum(msgNum); } } return response; } private RemotingCommand sendBatchMessage(final ChannelHandlerContext ctx, final RemotingCommand request, final SendMessageContext sendMessageContext, final SendMessageRequestHeader requestHeader, TopicQueueMappingContext mappingContext, final SendMessageCallback sendMessageCallback) { final RemotingCommand response = preSend(ctx, request, requestHeader); final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader(); if (response.getCode() != -1) { return response; } int queueIdInt = requestHeader.getQueueId(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (queueIdInt < 0) { queueIdInt = randomQueueId(topicConfig.getWriteQueueNums()); } if (requestHeader.getTopic().length() > Byte.MAX_VALUE) { response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark("message topic length too long " + requestHeader.getTopic().length()); return response; } if (requestHeader.getTopic() != null && requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark("batch request does not support retry group " + requestHeader.getTopic()); return response; } MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(requestHeader.getTopic()); messageExtBatch.setQueueId(queueIdInt); int sysFlag = requestHeader.getSysFlag(); if (TopicFilterType.MULTI_TAG == topicConfig.getTopicFilterType()) { sysFlag |= MessageSysFlag.MULTI_TAGS_FLAG; } messageExtBatch.setSysFlag(sysFlag); messageExtBatch.setFlag(requestHeader.getFlag()); MessageAccessor.setProperties(messageExtBatch, MessageDecoder.string2messageProperties(requestHeader.getProperties())); messageExtBatch.setBody(request.getBody()); messageExtBatch.setBornTimestamp(requestHeader.getBornTimestamp()); messageExtBatch.setBornHost(ctx.channel().remoteAddress()); messageExtBatch.setStoreHost(this.getStoreHost()); messageExtBatch.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes()); String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName(); MessageAccessor.putProperty(messageExtBatch, MessageConst.PROPERTY_CLUSTER, clusterName); boolean isInnerBatch = false; if (QueueTypeUtils.isBatchCq(Optional.of(topicConfig)) && MessageClientIDSetter.getUniqID(messageExtBatch) != null) { // newly introduced inner-batch message messageExtBatch.setSysFlag(messageExtBatch.getSysFlag() | MessageSysFlag.NEED_UNWRAP_FLAG); messageExtBatch.setSysFlag(messageExtBatch.getSysFlag() | MessageSysFlag.INNER_BATCH_FLAG); messageExtBatch.setInnerBatch(true); int innerNum = MessageDecoder.countInnerMsgNum(ByteBuffer.wrap(messageExtBatch.getBody())); MessageAccessor.putProperty(messageExtBatch, MessageConst.PROPERTY_INNER_NUM, String.valueOf(innerNum)); messageExtBatch.setPropertiesString(MessageDecoder.messageProperties2String(messageExtBatch.getProperties())); // tell the producer that it's an inner-batch message response. responseHeader.setBatchUniqId(MessageClientIDSetter.getUniqID(messageExtBatch)); isInnerBatch = true; } long beginTimeMillis = this.brokerController.getMessageStore().now(); if (this.brokerController.getBrokerConfig().isAsyncSendEnable()) { CompletableFuture asyncPutMessageFuture; if (isInnerBatch) { asyncPutMessageFuture = this.brokerController.getMessageStore().asyncPutMessage(messageExtBatch); } else { asyncPutMessageFuture = this.brokerController.getMessageStore().asyncPutMessages(messageExtBatch); } final int finalQueueIdInt = queueIdInt; asyncPutMessageFuture.thenAcceptAsync(putMessageResult -> { RemotingCommand responseFuture = handlePutMessageResult(putMessageResult, response, request, messageExtBatch, responseHeader, sendMessageContext, ctx, finalQueueIdInt, beginTimeMillis, mappingContext, BrokerMetricsManager.getMessageType(requestHeader)); if (responseFuture != null) { doResponse(ctx, request, responseFuture); } sendMessageCallback.onComplete(sendMessageContext, response); }, this.brokerController.getSendMessageExecutor()); // Returns null to release the send message thread return null; } else { PutMessageResult putMessageResult; if (isInnerBatch) { putMessageResult = this.brokerController.getMessageStore().putMessage(messageExtBatch); } else { putMessageResult = this.brokerController.getMessageStore().putMessages(messageExtBatch); } handlePutMessageResult(putMessageResult, response, request, messageExtBatch, responseHeader, sendMessageContext, ctx, queueIdInt, beginTimeMillis, mappingContext, BrokerMetricsManager.getMessageType(requestHeader)); sendMessageCallback.onComplete(sendMessageContext, response); return response; } } public void attachRecallHandle(RemotingCommand request, MessageExt msg, SendMessageResponseHeader responseHeader) { if (RequestCode.SEND_BATCH_MESSAGE == request.getCode() || RequestCode.CONSUMER_SEND_MSG_BACK == request.getCode()) { return; } String timestampStr = msg.getProperty(MessageConst.PROPERTY_TIMER_OUT_MS); String realTopic = msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC); if (timestampStr != null && realTopic != null && !realTopic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { timestampStr = String.valueOf(Long.parseLong(timestampStr) + 1); // consider of floor String recallHandle = RecallMessageHandle.HandleV1.buildHandle(realTopic, brokerController.getBrokerConfig().getBrokerName(), timestampStr, MessageClientIDSetter.getUniqID(msg)); responseHeader.setRecallHandle(recallHandle); } } private String diskUtil() { double physicRatio = 100; String storePath; MessageStore messageStore = this.brokerController.getMessageStore(); if (messageStore instanceof DefaultMessageStore) { storePath = ((DefaultMessageStore) messageStore).getStorePathPhysic(); } else { storePath = this.brokerController.getMessageStoreConfig().getStorePathCommitLog(); } String[] paths = storePath.trim().split(MixAll.MULTI_PATH_SPLITTER); for (String storePathPhysic : paths) { physicRatio = Math.min(physicRatio, UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic)); } String storePathLogis = StorePathConfigHelper.getStorePathConsumeQueue(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); double logisRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathLogis); String storePathIndex = StorePathConfigHelper.getStorePathIndex(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); double indexRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathIndex); return String.format("CL: %5.2f CQ: %5.2f INDEX: %5.2f", physicRatio, logisRatio, indexRatio); } private RemotingCommand preSend(ChannelHandlerContext ctx, RemotingCommand request, SendMessageRequestHeader requestHeader) { final RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setOpaque(request.getOpaque()); response.addExtField(MessageConst.PROPERTY_MSG_REGION, this.brokerController.getBrokerConfig().getRegionId()); response.addExtField(MessageConst.PROPERTY_TRACE_SWITCH, String.valueOf(this.brokerController.getBrokerConfig().isTraceOn())); LOGGER.debug("Receive SendMessage request command {}", request); final long startTimestamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp(); if (this.brokerController.getMessageStore().now() < startTimestamp) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("broker unable to service, until %s", UtilAll.timeMillisToHumanString2(startTimestamp))); return response; } response.setCode(-1); super.msgCheck(ctx, requestHeader, request, response); return response; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/schedule/DelayOffsetSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.schedule; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class DelayOffsetSerializeWrapper extends RemotingSerializable { private ConcurrentMap offsetTable = new ConcurrentHashMap<>(32); private DataVersion dataVersion; public ConcurrentMap getOffsetTable() { return offsetTable; } public void setOffsetTable(ConcurrentMap offsetTable) { this.offsetTable = offsetTable; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.schedule; import io.opentelemetry.api.common.Attributes; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.running.RunningStats; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_MESSAGE_TYPE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; public class ScheduleMessageService extends ConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final long FIRST_DELAY_TIME = 1000L; private static final long DELAY_FOR_A_WHILE = 100L; private static final long DELAY_FOR_A_PERIOD = 10000L; private static final long WAIT_FOR_SHUTDOWN = 5000L; private static final long DELAY_FOR_A_SLEEP = 10L; private final ConcurrentSkipListMap delayLevelTable = new ConcurrentSkipListMap<>(); private final ConcurrentMap offsetTable = new ConcurrentHashMap<>(32); private final AtomicBoolean started = new AtomicBoolean(false); private ScheduledExecutorService deliverExecutorService; private int maxDelayLevel; private DataVersion dataVersion = new DataVersion(); private boolean enableAsyncDeliver = false; private ScheduledExecutorService handleExecutorService; private final ScheduledExecutorService scheduledPersistService; private final Map> deliverPendingTable = new ConcurrentHashMap<>(32); private final BrokerController brokerController; private final transient AtomicLong versionChangeCounter = new AtomicLong(0); public ScheduleMessageService(final BrokerController brokerController) { this.brokerController = brokerController; this.enableAsyncDeliver = brokerController.getMessageStoreConfig().isEnableScheduleAsyncDeliver(); scheduledPersistService = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("ScheduleMessageServicePersistThread", true, brokerController.getBrokerConfig())); } public static int queueId2DelayLevel(final int queueId) { return queueId + 1; } public static int delayLevel2QueueId(final int delayLevel) { return delayLevel - 1; } public void buildRunningStats(HashMap stats) throws ConsumeQueueException { for (Map.Entry next : this.offsetTable.entrySet()) { int queueId = delayLevel2QueueId(next.getKey()); long delayOffset = next.getValue(); long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, queueId); String value = String.format("%d,%d", delayOffset, maxOffset); String key = String.format("%s_%d", RunningStats.scheduleMessageOffset.name(), next.getKey()); stats.put(key, value); } } private void updateOffset(int delayLevel, long offset) { this.offsetTable.put(delayLevel, offset); if (versionChangeCounter.incrementAndGet() % brokerController.getBrokerConfig().getDelayOffsetUpdateVersionStep() == 0) { long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; dataVersion.nextVersion(stateMachineVersion); } } public long computeDeliverTimestamp(final int delayLevel, final long storeTimestamp) { Long time = this.delayLevelTable.get(delayLevel); if (time != null) { return time + storeTimestamp; } return storeTimestamp + 1000; } public void start() { if (started.compareAndSet(false, true)) { this.load(); this.deliverExecutorService = ThreadUtils.newScheduledThreadPool(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageTimerThread_")); if (this.enableAsyncDeliver) { this.handleExecutorService = ThreadUtils.newScheduledThreadPool(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageExecutorHandleThread_")); } for (Map.Entry entry : this.delayLevelTable.entrySet()) { Integer level = entry.getKey(); Long timeDelay = entry.getValue(); Long offset = this.offsetTable.get(level); if (null == offset) { offset = 0L; } if (timeDelay != null) { if (this.enableAsyncDeliver) { this.handleExecutorService.schedule(new HandlePutResultTask(level), FIRST_DELAY_TIME, TimeUnit.MILLISECONDS); } this.deliverExecutorService.schedule(new DeliverDelayedMessageTimerTask(level, offset), FIRST_DELAY_TIME, TimeUnit.MILLISECONDS); } } scheduledPersistService.scheduleAtFixedRate(() -> { try { ScheduleMessageService.this.persist(); } catch (Throwable e) { log.error("scheduleAtFixedRate flush exception", e); } }, 10000, this.brokerController.getMessageStoreConfig().getFlushDelayOffsetInterval(), TimeUnit.MILLISECONDS); } } public void shutdown() { stop(); ThreadUtils.shutdown(scheduledPersistService); } public boolean stop() { if (this.started.compareAndSet(true, false) && null != this.deliverExecutorService) { this.deliverExecutorService.shutdown(); try { this.deliverExecutorService.awaitTermination(WAIT_FOR_SHUTDOWN, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("deliverExecutorService awaitTermination error", e); } if (this.handleExecutorService != null) { this.handleExecutorService.shutdown(); try { this.handleExecutorService.awaitTermination(WAIT_FOR_SHUTDOWN, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("handleExecutorService awaitTermination error", e); } } for (int i = 1; i <= this.deliverPendingTable.size(); i++) { log.warn("deliverPendingTable level: {}, size: {}", i, this.deliverPendingTable.get(i).size()); } this.persist(); } return true; } public boolean isStarted() { return started.get(); } public int getMaxDelayLevel() { return maxDelayLevel; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } @Override public String encode() { return this.encode(false); } @Override public boolean load() { boolean result = super.load(); result = result && this.parseDelayLevel(); result = result && this.correctDelayOffset(); return result; } public boolean loadWhenSyncDelayOffset() { boolean result = super.load(); result = result && this.parseDelayLevel(); return result; } public boolean correctDelayOffset() { try { for (int delayLevel : delayLevelTable.keySet()) { ConsumeQueueInterface cq = brokerController.getMessageStore().findConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel2QueueId(delayLevel)); Long currentDelayOffset = offsetTable.get(delayLevel); if (currentDelayOffset == null || cq == null) { continue; } long correctDelayOffset = currentDelayOffset; long cqMinOffset = cq.getMinOffsetInQueue(); long cqMaxOffset = cq.getMaxOffsetInQueue(); if (currentDelayOffset < cqMinOffset) { correctDelayOffset = cqMinOffset; log.error("schedule CQ offset invalid. offset={}, cqMinOffset={}, cqMaxOffset={}, queueId={}", currentDelayOffset, cqMinOffset, cqMaxOffset, cq.getQueueId()); } if (currentDelayOffset > cqMaxOffset) { correctDelayOffset = cqMaxOffset; log.error("schedule CQ offset invalid. offset={}, cqMinOffset={}, cqMaxOffset={}, queueId={}", currentDelayOffset, cqMinOffset, cqMaxOffset, cq.getQueueId()); } if (correctDelayOffset != currentDelayOffset) { log.error("correct delay offset [ delayLevel {} ] from {} to {}", delayLevel, currentDelayOffset, correctDelayOffset); offsetTable.put(delayLevel, correctDelayOffset); } } } catch (Exception e) { log.error("correctDelayOffset exception", e); return false; } return true; } @Override public String configFilePath() { return StorePathConfigHelper.getDelayOffsetStorePath(this.brokerController.getMessageStore().getMessageStoreConfig() .getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { DelayOffsetSerializeWrapper delayOffsetSerializeWrapper = DelayOffsetSerializeWrapper.fromJson(jsonString, DelayOffsetSerializeWrapper.class); if (delayOffsetSerializeWrapper != null) { this.offsetTable.putAll(delayOffsetSerializeWrapper.getOffsetTable()); // For compatible if (delayOffsetSerializeWrapper.getDataVersion() != null) { this.dataVersion.assignNewOne(delayOffsetSerializeWrapper.getDataVersion()); } } } } @Override public String encode(final boolean prettyFormat) { DelayOffsetSerializeWrapper delayOffsetSerializeWrapper = new DelayOffsetSerializeWrapper(); delayOffsetSerializeWrapper.setOffsetTable(this.offsetTable); delayOffsetSerializeWrapper.setDataVersion(this.dataVersion); return delayOffsetSerializeWrapper.toJson(prettyFormat); } public boolean parseDelayLevel() { HashMap timeUnitTable = new HashMap<>(); timeUnitTable.put("s", 1000L); timeUnitTable.put("m", 1000L * 60); timeUnitTable.put("h", 1000L * 60 * 60); timeUnitTable.put("d", 1000L * 60 * 60 * 24); String levelString = this.brokerController.getMessageStoreConfig().getMessageDelayLevel(); try { String[] levelArray = levelString.split(" "); for (int i = 0; i < levelArray.length; i++) { String value = levelArray[i]; String ch = value.substring(value.length() - 1); Long tu = timeUnitTable.get(ch); int level = i + 1; if (level > this.maxDelayLevel) { this.maxDelayLevel = level; } long num = Long.parseLong(value.substring(0, value.length() - 1)); long delayTimeMillis = tu * num; this.delayLevelTable.put(level, delayTimeMillis); if (this.enableAsyncDeliver) { this.deliverPendingTable.put(level, new LinkedBlockingQueue<>()); } } } catch (Exception e) { log.error("parse message delay level failed. messageDelayLevel = {}", levelString, e); return false; } return true; } private MessageExtBrokerInner messageTimeUp(MessageExt msgExt) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setBody(msgExt.getBody()); msgInner.setFlag(msgExt.getFlag()); MessageAccessor.setProperties(msgInner, msgExt.getProperties()); TopicFilterType topicFilterType = MessageExt.parseTopicFilterType(msgInner.getSysFlag()); long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags()); msgInner.setTagsCode(tagsCodeValue); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); msgInner.setSysFlag(msgExt.getSysFlag()); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setStoreHost(msgExt.getStoreHost()); msgInner.setReconsumeTimes(msgExt.getReconsumeTimes()); msgInner.setWaitStoreMsgOK(false); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_DELAY_TIME_LEVEL); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TIMER_DELIVER_MS); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TIMER_DELAY_SEC); msgInner.setTopic(msgInner.getProperty(MessageConst.PROPERTY_REAL_TOPIC)); String queueIdStr = msgInner.getProperty(MessageConst.PROPERTY_REAL_QUEUE_ID); int queueId = Integer.parseInt(queueIdStr); msgInner.setQueueId(queueId); return msgInner; } class DeliverDelayedMessageTimerTask implements Runnable { private final int delayLevel; private final long offset; public DeliverDelayedMessageTimerTask(int delayLevel, long offset) { this.delayLevel = delayLevel; this.offset = offset; } @Override public void run() { try { if (isStarted()) { this.executeOnTimeUp(); } } catch (Throwable e) { // XXX: warn and notify me log.error("ScheduleMessageService, executeOnTimeUp exception", e); this.scheduleNextTimerTask(this.offset, DELAY_FOR_A_PERIOD); } } private long correctDeliverTimestamp(final long now, final long deliverTimestamp) { long result = deliverTimestamp; long maxTimestamp = now + ScheduleMessageService.this.delayLevelTable.get(this.delayLevel); if (deliverTimestamp > maxTimestamp) { result = now; } return result; } public void executeOnTimeUp() { ConsumeQueueInterface cq = ScheduleMessageService.this.brokerController.getMessageStore().getConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel2QueueId(delayLevel)); if (cq == null) { this.scheduleNextTimerTask(this.offset, DELAY_FOR_A_WHILE); return; } ReferredIterator bufferCQ = cq.iterateFrom(this.offset); if (bufferCQ == null) { long resetOffset; if ((resetOffset = cq.getMinOffsetInQueue()) > this.offset) { log.error("schedule CQ offset invalid. offset={}, cqMinOffset={}, queueId={}", this.offset, resetOffset, cq.getQueueId()); } else if ((resetOffset = cq.getMaxOffsetInQueue()) < this.offset) { log.error("schedule CQ offset invalid. offset={}, cqMaxOffset={}, queueId={}", this.offset, resetOffset, cq.getQueueId()); } else { resetOffset = this.offset; } this.scheduleNextTimerTask(resetOffset, DELAY_FOR_A_WHILE); return; } long nextOffset = this.offset; try { while (bufferCQ.hasNext() && isStarted()) { CqUnit cqUnit = bufferCQ.next(); long offsetPy = cqUnit.getPos(); int sizePy = cqUnit.getSize(); long tagsCode = cqUnit.getTagsCode(); if (!cqUnit.isTagsCodeValid()) { //can't find ext content.So re compute tags code. log.error("[BUG] can't find consume queue extend file content!addr={}, offsetPy={}, sizePy={}", tagsCode, offsetPy, sizePy); long msgStoreTime = ScheduleMessageService.this.brokerController.getMessageStore().getCommitLog().pickupStoreTimestamp(offsetPy, sizePy); tagsCode = computeDeliverTimestamp(delayLevel, msgStoreTime); } long now = System.currentTimeMillis(); long deliverTimestamp = this.correctDeliverTimestamp(now, tagsCode); long currOffset = cqUnit.getQueueOffset(); assert cqUnit.getBatchNum() == 1; nextOffset = currOffset + cqUnit.getBatchNum(); long countdown = deliverTimestamp - now; if (countdown > 0) { this.scheduleNextTimerTask(currOffset, DELAY_FOR_A_WHILE); ScheduleMessageService.this.updateOffset(this.delayLevel, currOffset); return; } MessageExt msgExt = ScheduleMessageService.this.brokerController.getMessageStore().lookMessageByOffset(offsetPy, sizePy); if (msgExt == null) { continue; } MessageExtBrokerInner msgInner = ScheduleMessageService.this.messageTimeUp(msgExt); if (TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC.equals(msgInner.getTopic())) { log.error("[BUG] the real topic of schedule msg is {}, discard the msg. msg={}", msgInner.getTopic(), msgInner); continue; } boolean deliverSuc; if (ScheduleMessageService.this.enableAsyncDeliver) { deliverSuc = this.asyncDeliver(msgInner, msgExt.getMsgId(), currOffset, offsetPy, sizePy); } else { deliverSuc = this.syncDeliver(msgInner, msgExt.getMsgId(), currOffset, offsetPy, sizePy); } if (!deliverSuc) { this.scheduleNextTimerTask(currOffset, DELAY_FOR_A_WHILE); return; } } } catch (Exception e) { log.error("ScheduleMessageService, messageTimeUp execute error, offset = {}", nextOffset, e); } finally { bufferCQ.release(); } this.scheduleNextTimerTask(nextOffset, DELAY_FOR_A_WHILE); } public void scheduleNextTimerTask(long offset, long delay) { ScheduleMessageService.this.deliverExecutorService.schedule(new DeliverDelayedMessageTimerTask( this.delayLevel, offset), delay, TimeUnit.MILLISECONDS); } private boolean syncDeliver(MessageExtBrokerInner msgInner, String msgId, long offset, long offsetPy, int sizePy) { PutResultProcess resultProcess = deliverMessage(msgInner, msgId, offset, offsetPy, sizePy, false); PutMessageResult result = resultProcess.get(); boolean sendStatus = result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK; if (sendStatus) { ScheduleMessageService.this.updateOffset(this.delayLevel, resultProcess.getNextOffset()); } return sendStatus; } private boolean asyncDeliver(MessageExtBrokerInner msgInner, String msgId, long offset, long offsetPy, int sizePy) { Queue processesQueue = ScheduleMessageService.this.deliverPendingTable.get(this.delayLevel); //Flow Control int currentPendingNum = processesQueue.size(); int maxPendingLimit = brokerController.getMessageStoreConfig() .getScheduleAsyncDeliverMaxPendingLimit(); if (currentPendingNum > maxPendingLimit) { log.warn("Asynchronous deliver triggers flow control, " + "currentPendingNum={}, maxPendingLimit={}", currentPendingNum, maxPendingLimit); return false; } //Blocked PutResultProcess firstProcess = processesQueue.peek(); if (firstProcess != null && firstProcess.need2Blocked()) { log.warn("Asynchronous deliver block. info={}", firstProcess.toString()); return false; } PutResultProcess resultProcess = deliverMessage(msgInner, msgId, offset, offsetPy, sizePy, true); processesQueue.add(resultProcess); return true; } private PutResultProcess deliverMessage(MessageExtBrokerInner msgInner, String msgId, long offset, long offsetPy, int sizePy, boolean autoResend) { CompletableFuture future = brokerController.getEscapeBridge().asyncPutMessage(msgInner); return new PutResultProcess() .setTopic(msgInner.getTopic()) .setDelayLevel(this.delayLevel) .setOffset(offset) .setPhysicOffset(offsetPy) .setPhysicSize(sizePy) .setMsgId(msgId) .setAutoResend(autoResend) .setFuture(future) .thenProcess(); } } class HandlePutResultTask implements Runnable { private final int delayLevel; public HandlePutResultTask(int delayLevel) { this.delayLevel = delayLevel; } @Override public void run() { LinkedBlockingQueue pendingQueue = ScheduleMessageService.this.deliverPendingTable.get(this.delayLevel); // Check if the queue exists for the given level if (pendingQueue == null) { log.warn("No pending queue found for delay level: {}", this.delayLevel); return; } PutResultProcess putResultProcess; while ((putResultProcess = pendingQueue.peek()) != null) { try { switch (putResultProcess.getStatus()) { case SUCCESS: ScheduleMessageService.this.updateOffset(this.delayLevel, putResultProcess.getNextOffset()); pendingQueue.remove(); break; case RUNNING: scheduleNextTask(); return; case EXCEPTION: if (!isStarted()) { log.warn("HandlePutResultTask shutdown, info={}", putResultProcess.toString()); return; } log.warn("putResultProcess error, info={}", putResultProcess.toString()); putResultProcess.doResend(); break; case SKIP: log.warn("putResultProcess skip, info={}", putResultProcess.toString()); pendingQueue.remove(); break; } } catch (Exception e) { log.error("HandlePutResultTask exception. info={}", putResultProcess.toString(), e); putResultProcess.doResend(); } } scheduleNextTask(); } private void scheduleNextTask() { if (isStarted()) { ScheduleMessageService.this.handleExecutorService .schedule(new HandlePutResultTask(this.delayLevel), DELAY_FOR_A_SLEEP, TimeUnit.MILLISECONDS); } } } class PutResultProcess { private String topic; private long offset; private long physicOffset; private int physicSize; private int delayLevel; private String msgId; private boolean autoResend = false; private CompletableFuture future; private volatile AtomicInteger resendCount = new AtomicInteger(0); private volatile ProcessStatus status = ProcessStatus.RUNNING; public PutResultProcess setTopic(String topic) { this.topic = topic; return this; } public PutResultProcess setOffset(long offset) { this.offset = offset; return this; } public PutResultProcess setPhysicOffset(long physicOffset) { this.physicOffset = physicOffset; return this; } public PutResultProcess setPhysicSize(int physicSize) { this.physicSize = physicSize; return this; } public PutResultProcess setDelayLevel(int delayLevel) { this.delayLevel = delayLevel; return this; } public PutResultProcess setMsgId(String msgId) { this.msgId = msgId; return this; } public PutResultProcess setAutoResend(boolean autoResend) { this.autoResend = autoResend; return this; } public PutResultProcess setFuture(CompletableFuture future) { this.future = future; return this; } public String getTopic() { return topic; } public long getOffset() { return offset; } public long getNextOffset() { return offset + 1; } public long getPhysicOffset() { return physicOffset; } public int getPhysicSize() { return physicSize; } public Integer getDelayLevel() { return delayLevel; } public String getMsgId() { return msgId; } public boolean isAutoResend() { return autoResend; } public CompletableFuture getFuture() { return future; } public AtomicInteger getResendCount() { return resendCount; } public PutResultProcess thenProcess() { this.future.thenAccept(this::handleResult); this.future.exceptionally(e -> { log.error("ScheduleMessageService put message exceptionally, info: {}", PutResultProcess.this.toString(), e); onException(); return null; }); return this; } private void handleResult(PutMessageResult result) { if (result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { onSuccess(result); } else { log.warn("ScheduleMessageService put message failed. info: {}.", result); onException(); } } public void onSuccess(PutMessageResult result) { this.status = ProcessStatus.SUCCESS; if (ScheduleMessageService.this.brokerController.getMessageStore().getMessageStoreConfig().isEnableScheduleMessageStats() && !result.isRemotePut()) { ScheduleMessageService.this.brokerController.getBrokerStatsManager().incQueueGetNums(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel - 1, result.getAppendMessageResult().getMsgNum()); ScheduleMessageService.this.brokerController.getBrokerStatsManager().incQueueGetSize(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel - 1, result.getAppendMessageResult().getWroteBytes()); ScheduleMessageService.this.brokerController.getBrokerStatsManager().incGroupGetNums(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, result.getAppendMessageResult().getMsgNum()); ScheduleMessageService.this.brokerController.getBrokerStatsManager().incGroupGetSize(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, result.getAppendMessageResult().getWroteBytes()); Attributes attributes = ScheduleMessageService.this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC) .put(LABEL_CONSUMER_GROUP, MixAll.SCHEDULE_CONSUMER_GROUP) .put(LABEL_IS_SYSTEM, true) .build(); ScheduleMessageService.this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(result.getAppendMessageResult().getMsgNum(), attributes); ScheduleMessageService.this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(result.getAppendMessageResult().getWroteBytes(), attributes); ScheduleMessageService.this.brokerController.getBrokerStatsManager().incTopicPutNums(this.topic, result.getAppendMessageResult().getMsgNum(), 1); ScheduleMessageService.this.brokerController.getBrokerStatsManager().incTopicPutSize(this.topic, result.getAppendMessageResult().getWroteBytes()); ScheduleMessageService.this.brokerController.getBrokerStatsManager().incBrokerPutNums(this.topic, result.getAppendMessageResult().getMsgNum()); attributes = ScheduleMessageService.this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, topic) .put(LABEL_MESSAGE_TYPE, TopicMessageType.DELAY.getMetricsValue()) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic)) .build(); ScheduleMessageService.this.brokerController.getBrokerMetricsManager().getMessagesInTotal().add(result.getAppendMessageResult().getMsgNum(), attributes); ScheduleMessageService.this.brokerController.getBrokerMetricsManager().getThroughputInTotal().add(result.getAppendMessageResult().getWroteBytes(), attributes); ScheduleMessageService.this.brokerController.getBrokerMetricsManager().getMessageSize().record(result.getAppendMessageResult().getWroteBytes() / result.getAppendMessageResult().getMsgNum(), attributes); } } public void onException() { log.warn("ScheduleMessageService onException, info: {}", this.toString()); if (this.autoResend) { this.status = ProcessStatus.EXCEPTION; } else { this.status = ProcessStatus.SKIP; } } public ProcessStatus getStatus() { return this.status; } public PutMessageResult get() { try { return this.future.get(); } catch (InterruptedException | ExecutionException e) { return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, null); } } public void doResend() { log.info("Resend message, info: {}", this.toString()); // Gradually increase the resend interval. try { Thread.sleep(Math.min(this.resendCount.incrementAndGet() * 100, 60 * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } try { MessageExt msgExt = ScheduleMessageService.this.brokerController.getMessageStore().lookMessageByOffset(this.physicOffset, this.physicSize); if (msgExt == null) { log.warn("ScheduleMessageService resend not found message. info: {}", this.toString()); this.status = need2Skip() ? ProcessStatus.SKIP : ProcessStatus.EXCEPTION; return; } MessageExtBrokerInner msgInner = ScheduleMessageService.this.messageTimeUp(msgExt); PutMessageResult result = ScheduleMessageService.this.brokerController.getEscapeBridge().putMessage(msgInner); this.handleResult(result); if (result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { log.info("Resend message success, info: {}", this.toString()); } } catch (Exception e) { this.status = ProcessStatus.EXCEPTION; log.error("Resend message error, info: {}", this.toString(), e); } } public boolean need2Blocked() { int maxResendNum2Blocked = ScheduleMessageService.this.brokerController.getMessageStore().getMessageStoreConfig() .getScheduleAsyncDeliverMaxResendNum2Blocked(); return this.resendCount.get() > maxResendNum2Blocked; } public boolean need2Skip() { int maxResendNum2Blocked = ScheduleMessageService.this.brokerController.getMessageStore().getMessageStoreConfig() .getScheduleAsyncDeliverMaxResendNum2Blocked(); return this.resendCount.get() > maxResendNum2Blocked * 2; } @Override public String toString() { return "PutResultProcess{" + "topic='" + topic + '\'' + ", offset=" + offset + ", physicOffset=" + physicOffset + ", physicSize=" + physicSize + ", delayLevel=" + delayLevel + ", msgId='" + msgId + '\'' + ", autoResend=" + autoResend + ", resendCount=" + resendCount + ", status=" + status + '}'; } } enum ProcessStatus { /** * In process, the processing result has not yet been returned. */ RUNNING, /** * Put message success. */ SUCCESS, /** * Put message exception. When autoResend is true, the message will be resend. */ EXCEPTION, /** * Skip put message. When the message cannot be looked, the message will be skipped. */ SKIP, } public ConcurrentMap getOffsetTable() { return offsetTable; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.slave; import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.loadbalance.MessageRequestModeManager; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.body.ConsumerOffsetSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.MessageRequestModeSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.SetMessageRequestModeRequestBody; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.timer.TimerCheckpoint; import org.apache.rocketmq.store.timer.TimerMetrics; public class SlaveSynchronize { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private volatile String masterAddr = null; public SlaveSynchronize(BrokerController brokerController) { this.brokerController = brokerController; } public String getMasterAddr() { return masterAddr; } public void setMasterAddr(String masterAddr) { if (!StringUtils.equals(this.masterAddr, masterAddr)) { LOGGER.info("Update master address from {} to {}", this.masterAddr, masterAddr); this.masterAddr = masterAddr; } } public void syncAll() { this.syncTopicConfig(); this.syncConsumerOffset(); this.syncDelayOffset(); this.syncSubscriptionGroupConfig(); this.syncMessageRequestMode(); if (brokerController.getMessageStoreConfig().isTimerWheelEnable()) { this.syncTimerMetrics(); } } private void syncTopicConfig() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { TopicConfigAndMappingSerializeWrapper topicWrapper = this.brokerController.getBrokerOuterAPI().getAllTopicConfig(masterAddrBak); TopicConfigManager topicConfigManager = this.brokerController.getTopicConfigManager(); if (!topicConfigManager.getDataVersion().equals(topicWrapper.getDataVersion())) { topicConfigManager.getDataVersion().assignNewOne(topicWrapper.getDataVersion()); ConcurrentMap newTopicConfigTable = topicWrapper.getTopicConfigTable(); ConcurrentMap topicConfigTable = topicConfigManager.getTopicConfigTable(); //delete Iterator> iterator = topicConfigTable.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (!newTopicConfigTable.containsKey(entry.getKey())) { iterator.remove(); } topicConfigManager.deleteTopicConfig(entry.getKey()); } //update newTopicConfigTable.values().forEach(topicConfigManager::putTopicConfig); topicConfigManager.updateDataVersion(); topicConfigManager.persist(); } if (topicWrapper.getTopicQueueMappingDetailMap() != null && !topicWrapper.getMappingDataVersion().equals(this.brokerController.getTopicQueueMappingManager().getDataVersion())) { this.brokerController.getTopicQueueMappingManager().getDataVersion() .assignNewOne(topicWrapper.getMappingDataVersion()); ConcurrentMap newTopicConfigTable = topicWrapper.getTopicConfigTable(); //delete ConcurrentMap topicConfigTable = this.brokerController.getTopicConfigManager().getTopicConfigTable(); topicConfigTable.entrySet().removeIf(item -> !newTopicConfigTable.containsKey(item.getKey())); //update topicConfigTable.putAll(newTopicConfigTable); this.brokerController.getTopicQueueMappingManager().persist(); } LOGGER.info("Update slave topic config from master, {}", masterAddrBak); } catch (Exception e) { LOGGER.error("SyncTopicConfig Exception, {}", masterAddrBak, e); } } } private void syncConsumerOffset() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { ConsumerOffsetSerializeWrapper offsetWrapper = this.brokerController.getBrokerOuterAPI().getAllConsumerOffset(masterAddrBak); this.brokerController.getConsumerOffsetManager().getOffsetTable() .putAll(offsetWrapper.getOffsetTable()); this.brokerController.getConsumerOffsetManager().getDataVersion().assignNewOne(offsetWrapper.getDataVersion()); this.brokerController.getConsumerOffsetManager().persist(); LOGGER.info("Update slave consumer offset from master, {}", masterAddrBak); } catch (Exception e) { LOGGER.error("SyncConsumerOffset Exception, {}", masterAddrBak, e); } } } private void syncDelayOffset() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { String delayOffset = this.brokerController.getBrokerOuterAPI().getAllDelayOffset(masterAddrBak); if (delayOffset != null) { String fileName = StorePathConfigHelper.getDelayOffsetStorePath(this.brokerController .getMessageStoreConfig().getStorePathRootDir()); try { MixAll.string2File(delayOffset, fileName); this.brokerController.getScheduleMessageService().loadWhenSyncDelayOffset(); } catch (IOException e) { LOGGER.error("Persist file Exception, {}", fileName, e); } } LOGGER.info("Update slave delay offset from master, {}", masterAddrBak); } catch (Exception e) { LOGGER.error("SyncDelayOffset Exception, {}", masterAddrBak, e); } } } private void syncSubscriptionGroupConfig() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { SubscriptionGroupWrapper subscriptionWrapper = this.brokerController.getBrokerOuterAPI() .getAllSubscriptionGroupConfig(masterAddrBak); if (!this.brokerController.getSubscriptionGroupManager().getDataVersion() .equals(subscriptionWrapper.getDataVersion())) { SubscriptionGroupManager subscriptionGroupManager = this.brokerController.getSubscriptionGroupManager(); subscriptionGroupManager.getDataVersion().assignNewOne(subscriptionWrapper.getDataVersion()); ConcurrentMap curSubscriptionGroupTable = subscriptionGroupManager.getSubscriptionGroupTable(); ConcurrentMap newSubscriptionGroupTable = subscriptionWrapper.getSubscriptionGroupTable(); // delete Iterator> iterator = curSubscriptionGroupTable.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry configEntry = iterator.next(); if (!newSubscriptionGroupTable.containsKey(configEntry.getKey())) { iterator.remove(); } subscriptionGroupManager.deleteSubscriptionGroupConfig(configEntry.getKey()); } // update newSubscriptionGroupTable.values().forEach(subscriptionGroupManager::putSubscriptionGroupConfig); subscriptionGroupManager.updateDataVersion(); // persist subscriptionGroupManager.persist(); LOGGER.info("Update slave Subscription Group from master, {}", masterAddrBak); } } catch (Exception e) { LOGGER.error("SyncSubscriptionGroup Exception, {}", masterAddrBak, e); } } } private void syncMessageRequestMode() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { MessageRequestModeSerializeWrapper messageRequestModeSerializeWrapper = this.brokerController.getBrokerOuterAPI().getAllMessageRequestMode(masterAddrBak); MessageRequestModeManager messageRequestModeManager = this.brokerController.getQueryAssignmentProcessor().getMessageRequestModeManager(); ConcurrentHashMap> curMessageRequestModeMap = messageRequestModeManager.getMessageRequestModeMap(); ConcurrentHashMap> newMessageRequestModeMap = messageRequestModeSerializeWrapper.getMessageRequestModeMap(); // delete curMessageRequestModeMap.entrySet().removeIf(e -> !newMessageRequestModeMap.containsKey(e.getKey())); // update curMessageRequestModeMap.putAll(newMessageRequestModeMap); // persist messageRequestModeManager.persist(); LOGGER.info("Update slave Message Request Mode from master, {}", masterAddrBak); } catch (Exception e) { LOGGER.error("SyncMessageRequestMode Exception, {}", masterAddrBak, e); } } } public void syncTimerCheckPoint() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null) { try { if (null != brokerController.getMessageStore().getTimerMessageStore() && !brokerController.getTimerMessageStore().isShouldRunningDequeue()) { TimerCheckpoint checkpoint = this.brokerController.getBrokerOuterAPI().getTimerCheckPoint(masterAddrBak); if (null != this.brokerController.getTimerCheckpoint()) { this.brokerController.getTimerCheckpoint().setLastReadTimeMs(checkpoint.getLastReadTimeMs()); this.brokerController.getTimerCheckpoint().setMasterTimerQueueOffset(checkpoint.getMasterTimerQueueOffset()); this.brokerController.getTimerCheckpoint().getDataVersion().assignNewOne(checkpoint.getDataVersion()); } } } catch (Exception e) { LOGGER.error("syncTimerCheckPoint Exception, {}", masterAddrBak, e); } } } private void syncTimerMetrics() { String masterAddrBak = this.masterAddr; if (masterAddrBak != null) { try { if (null != brokerController.getMessageStore().getTimerMessageStore()) { TimerMetrics.TimerMetricsSerializeWrapper metricsSerializeWrapper = this.brokerController.getBrokerOuterAPI().getTimerMetrics(masterAddrBak); if (!brokerController.getMessageStore().getTimerMessageStore().getTimerMetrics().getDataVersion().equals(metricsSerializeWrapper.getDataVersion())) { this.brokerController.getMessageStore().getTimerMessageStore().getTimerMetrics().getDataVersion().assignNewOne(metricsSerializeWrapper.getDataVersion()); this.brokerController.getMessageStore().getTimerMessageStore().getTimerMetrics().getTimingCount().clear(); this.brokerController.getMessageStore().getTimerMessageStore().getTimerMetrics().getTimingCount().putAll(metricsSerializeWrapper.getTimingCount()); this.brokerController.getMessageStore().getTimerMessageStore().getTimerMetrics().persist(); } } } catch (Exception e) { LOGGER.error("SyncTimerMetrics Exception, {}", masterAddrBak, e); } } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/subscription/LmqSubscriptionGroupManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.subscription; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class LmqSubscriptionGroupManager extends SubscriptionGroupManager { public LmqSubscriptionGroupManager(BrokerController brokerController) { super(brokerController); } @Override public SubscriptionGroupConfig findSubscriptionGroupConfig(final String group) { if (MixAll.isLmq(group)) { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); return subscriptionGroupConfig; } return super.findSubscriptionGroupConfig(group); } @Override public void updateSubscriptionGroupConfig(final SubscriptionGroupConfig config) { if (config == null || MixAll.isLmq(config.getGroupName())) { return; } super.updateSubscriptionGroupConfig(config); } @Override public boolean containsSubscriptionGroup(String group) { if (MixAll.isLmq(group)) { return true; } else { return super.containsSubscriptionGroup(group); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.subscription; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Maps; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.SubscriptionGroupAttributes; import org.apache.rocketmq.common.attribute.AttributeUtil; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; @SuppressWarnings("Duplicates") public class SubscriptionGroupManager extends ConfigManager { protected static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); protected ConcurrentMap subscriptionGroupTable = new ConcurrentHashMap<>(1024); private ConcurrentMap> forbiddenTable = new ConcurrentHashMap<>(4); protected final DataVersion dataVersion = new DataVersion(); protected transient BrokerController brokerController; public SubscriptionGroupManager() { this.init(); } public SubscriptionGroupManager(BrokerController brokerController) { this(brokerController, true); } public SubscriptionGroupManager(BrokerController brokerController, boolean init) { this.brokerController = brokerController; if (init) { init(); } } protected void init() { { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.TOOLS_CONSUMER_GROUP); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.FILTERSRV_CONSUMER_GROUP); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.SELF_TEST_CONSUMER_GROUP); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.ONS_HTTP_PROXY_GROUP); subscriptionGroupConfig.setConsumeBroadcastEnable(true); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.CID_ONSAPI_PULL_GROUP); subscriptionGroupConfig.setConsumeBroadcastEnable(true); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.CID_ONSAPI_PERMISSION_GROUP); subscriptionGroupConfig.setConsumeBroadcastEnable(true); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.CID_ONSAPI_OWNER_GROUP); subscriptionGroupConfig.setConsumeBroadcastEnable(true); putSubscriptionGroupConfig(subscriptionGroupConfig); } { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(MixAll.CID_SYS_RMQ_TRANS); subscriptionGroupConfig.setConsumeBroadcastEnable(true); putSubscriptionGroupConfig(subscriptionGroupConfig); } } public SubscriptionGroupConfig putSubscriptionGroupConfig(SubscriptionGroupConfig subscriptionGroupConfig) { return this.subscriptionGroupTable.put(subscriptionGroupConfig.getGroupName(), subscriptionGroupConfig); } protected SubscriptionGroupConfig putSubscriptionGroupConfigIfAbsent(SubscriptionGroupConfig subscriptionGroupConfig) { return this.subscriptionGroupTable.putIfAbsent(subscriptionGroupConfig.getGroupName(), subscriptionGroupConfig); } protected SubscriptionGroupConfig getSubscriptionGroupConfig(String groupName) { return this.subscriptionGroupTable.get(groupName); } protected SubscriptionGroupConfig removeSubscriptionGroupConfig(String groupName) { return this.subscriptionGroupTable.remove(groupName); } public void updateSubscriptionGroupConfig(final SubscriptionGroupConfig config) { updateSubscriptionGroupConfigWithoutPersist(config); this.persist(); } public void updateSubscriptionGroupConfigWithoutPersist(SubscriptionGroupConfig config) { Map newAttributes = request(config); Map currentAttributes = current(config.getGroupName()); Map finalAttributes = AttributeUtil.alterCurrentAttributes( this.subscriptionGroupTable.get(config.getGroupName()) == null, SubscriptionGroupAttributes.ALL, ImmutableMap.copyOf(currentAttributes), ImmutableMap.copyOf(newAttributes)); config.setAttributes(finalAttributes); SubscriptionGroupConfig old = putSubscriptionGroupConfig(config); if (old != null) { log.info("update subscription group config, old: {} new: {}", old, config); } else { log.info("create new subscription group, {}", config); } updateDataVersion(); } public void updateSubscriptionGroupConfigList(List configList) { if (null == configList || configList.isEmpty()) { return; } configList.forEach(this::updateSubscriptionGroupConfigWithoutPersist); this.persist(); } public void updateForbidden(String group, String topic, int forbiddenIndex, boolean setOrClear) { if (setOrClear) { setForbidden(group, topic, forbiddenIndex); } else { clearForbidden(group, topic, forbiddenIndex); } } /** * set the bit value to 1 at the specific index (from 0) */ public void setForbidden(String group, String topic, int forbiddenIndex) { int topicForbidden = getForbidden(group, topic); topicForbidden |= 1 << forbiddenIndex; updateForbiddenValue(group, topic, topicForbidden); } /** * clear the bit value to 0 at the specific index (from 0) */ public void clearForbidden(String group, String topic, int forbiddenIndex) { int topicForbidden = getForbidden(group, topic); topicForbidden &= ~(1 << forbiddenIndex); updateForbiddenValue(group, topic, topicForbidden); } public boolean getForbidden(String group, String topic, int forbiddenIndex) { int topicForbidden = getForbidden(group, topic); int bitForbidden = 1 << forbiddenIndex; return (topicForbidden & bitForbidden) == bitForbidden; } public int getForbidden(String group, String topic) { ConcurrentMap topicForbiddens = this.forbiddenTable.get(group); if (topicForbiddens == null) { return 0; } Integer topicForbidden = topicForbiddens.get(topic); if (topicForbidden == null || topicForbidden < 0) { topicForbidden = 0; } return topicForbidden; } protected void updateForbiddenValue(String group, String topic, Integer forbidden) { if (forbidden == null || forbidden <= 0) { this.forbiddenTable.remove(group); log.info("clear group forbidden, {}@{} ", group, topic); return; } ConcurrentMap topicsPermMap = this.forbiddenTable.get(group); if (topicsPermMap == null) { this.forbiddenTable.putIfAbsent(group, new ConcurrentHashMap<>()); topicsPermMap = this.forbiddenTable.get(group); } Integer old = topicsPermMap.put(topic, forbidden); if (old != null) { log.info("set group forbidden, {}@{} old: {} new: {}", group, topic, old, forbidden); } else { log.info("set group forbidden, {}@{} old: {} new: {}", group, topic, 0, forbidden); } updateDataVersion(); this.persist(); } public void disableConsume(final String groupName) { SubscriptionGroupConfig old = getSubscriptionGroupConfig(groupName); if (old != null) { old.setConsumeEnable(false); updateDataVersion(); } } public SubscriptionGroupConfig findSubscriptionGroupConfig(final String group) { SubscriptionGroupConfig subscriptionGroupConfig = getSubscriptionGroupConfig(group); if (null == subscriptionGroupConfig) { if (brokerController.getBrokerConfig().isAutoCreateSubscriptionGroup() || MixAll.isSysConsumerGroupAndEnableCreate(group, brokerController.getBrokerConfig().isEnableCreateSysGroup())) { TopicValidator.ValidateResult result = TopicValidator.validateGroup(group); if (!result.isValid()) { return null; } subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); SubscriptionGroupConfig preConfig = putSubscriptionGroupConfigIfAbsent(subscriptionGroupConfig); if (null == preConfig) { log.info("auto create a subscription group, {}", subscriptionGroupConfig.toString()); } updateDataVersion(); this.persist(); } } return subscriptionGroupConfig; } @Override public String encode() { return this.encode(false); } @Override public String configFilePath() { return BrokerPathConfigHelper.getSubscriptionGroupPath(this.brokerController.getMessageStoreConfig() .getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { SubscriptionGroupManager obj = RemotingSerializable.fromJson(jsonString, SubscriptionGroupManager.class); if (obj != null) { this.subscriptionGroupTable.putAll(obj.subscriptionGroupTable); if (obj.forbiddenTable != null) { this.forbiddenTable.putAll(obj.forbiddenTable); } this.dataVersion.assignNewOne(obj.dataVersion); this.printLoadDataWhenFirstBoot(obj); } } } @Override public String encode(final boolean prettyFormat) { return RemotingSerializable.toJson(this, prettyFormat); } private void printLoadDataWhenFirstBoot(final SubscriptionGroupManager sgm) { for (Entry next : sgm.getSubscriptionGroupTable().entrySet()) { log.info("load exist subscription group, {}", next.getValue().toString()); } } public ConcurrentMap getSubscriptionGroupTable() { return subscriptionGroupTable; } public ConcurrentHashMap subGroupTable(String dataVersion, int groupSeq, int maxGroupNum) { // [groupSeq, groupSeq + maxGroupNum) int beginIndex = groupSeq; if (StringUtils.isBlank(dataVersion) || !Objects.equals(DataVersion.fromJson(dataVersion, DataVersion.class), this.dataVersion)) { beginIndex = 0; log.info("get sub subscription group table from {} due to {}", beginIndex, StringUtils.isBlank(dataVersion) ? "DataVersion Empty" : "DataVersion Changed"); } ConcurrentHashMap subGroupTable = new ConcurrentHashMap<>(); if (beginIndex < subscriptionGroupTable.size()) { int endIndex = Math.min(beginIndex + maxGroupNum, subscriptionGroupTable.size()); ImmutableSortedMap sortedMap = ImmutableSortedMap.copyOf(subscriptionGroupTable); subGroupTable.putAll(sortedMap.subMap(sortedMap.keySet().asList().get(beginIndex),true, sortedMap.keySet().asList().get(endIndex - 1),true)); } return subGroupTable; } public ConcurrentMap> getForbiddenTable() { return forbiddenTable; } public ConcurrentMap> subForbiddenTable(Set groupSet) { if (MapUtils.isEmpty(forbiddenTable) || CollectionUtils.isEmpty(groupSet)) { return Maps.newConcurrentMap(); } return forbiddenTable.entrySet().stream() .filter(e -> groupSet.contains(e.getKey())) .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); } public void setForbiddenTable( ConcurrentMap> forbiddenTable) { this.forbiddenTable = forbiddenTable; } public DataVersion getDataVersion() { return dataVersion; } public boolean loadDataVersion() { String fileName = null; try { fileName = this.configFilePath(); String jsonString = MixAll.file2String(fileName); if (jsonString != null) { SubscriptionGroupManager obj = RemotingSerializable.fromJson(jsonString, SubscriptionGroupManager.class); if (obj != null) { this.dataVersion.assignNewOne(obj.dataVersion); this.printLoadDataWhenFirstBoot(obj); log.info("load subGroup dataVersion success,{},{}", fileName, obj.dataVersion); } } return true; } catch (Exception e) { log.error("load subGroup dataVersion failed" + fileName, e); return false; } } public void deleteSubscriptionGroupConfig(final String groupName) { SubscriptionGroupConfig old = removeSubscriptionGroupConfig(groupName); this.forbiddenTable.remove(groupName); if (old != null) { log.info("delete subscription group OK, subscription group:{}", old); updateDataVersion(); this.persist(); } else { log.warn("delete subscription group failed, subscription groupName: {} not exist", groupName); } } public void setSubscriptionGroupTable(ConcurrentMap subscriptionGroupTable) { this.subscriptionGroupTable = subscriptionGroupTable; } public boolean containsSubscriptionGroup(String group) { if (StringUtils.isBlank(group)) { return false; } return subscriptionGroupTable.containsKey(group); } private Map request(SubscriptionGroupConfig subscriptionGroupConfig) { return subscriptionGroupConfig.getAttributes() == null ? new HashMap<>() : subscriptionGroupConfig.getAttributes(); } private Map current(String groupName) { SubscriptionGroupConfig subscriptionGroupConfig = this.subscriptionGroupTable.get(groupName); if (subscriptionGroupConfig == null) { return new HashMap<>(); } else { Map attributes = subscriptionGroupConfig.getAttributes(); if (attributes == null) { return new HashMap<>(); } else { return attributes; } } } public void setDataVersion(DataVersion dataVersion) { this.dataVersion.assignNewOne(dataVersion); } public void updateDataVersion() { long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; dataVersion.nextVersion(stateMachineVersion); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/topic/LmqTopicConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; public class LmqTopicConfigManager extends TopicConfigManager { public LmqTopicConfigManager(BrokerController brokerController) { super(brokerController); } @Override public TopicConfig selectTopicConfig(final String topic) { if (MixAll.isLmq(topic)) { return simpleLmqTopicConfig(topic); } return super.selectTopicConfig(topic); } @Override public void updateTopicConfig(final TopicConfig topicConfig) { if (topicConfig == null || MixAll.isLmq(topicConfig.getTopicName())) { return; } super.updateTopicConfig(topicConfig); } private TopicConfig simpleLmqTopicConfig(String topic) { return new TopicConfig(topic, 1, 1, PermName.PERM_READ | PermName.PERM_WRITE); } @Override public boolean containsTopic(String topic) { if (MixAll.isLmq(topic)) { return true; } return super.containsTopic(topic); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.Attribute; import org.apache.rocketmq.common.attribute.AttributeUtil; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.sysflag.TopicSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.KVTable; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.tieredstore.TieredMessageStore; import org.apache.rocketmq.tieredstore.metadata.MetadataStore; import org.apache.rocketmq.tieredstore.metadata.entity.TopicMetadata; import static com.google.common.base.Preconditions.checkNotNull; public class TopicConfigManager extends ConfigManager { protected static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final long LOCK_TIMEOUT_MILLIS = 3000; private static final int SCHEDULE_TOPIC_QUEUE_NUM = 18; private transient final Lock topicConfigTableLock = new ReentrantLock(); protected ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(1024); protected DataVersion dataVersion = new DataVersion(); protected transient BrokerController brokerController; public TopicConfigManager() { } public TopicConfigManager(BrokerController brokerController) { this(brokerController, true); } public TopicConfigManager(BrokerController brokerController, boolean init) { this.brokerController = brokerController; if (init) { init(); } } protected void init() { { String topic = TopicValidator.RMQ_SYS_SELF_TEST_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { if (this.brokerController.getBrokerConfig().isAutoCreateTopicEnable()) { String topic = TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(this.brokerController.getBrokerConfig() .getDefaultTopicQueueNums()); topicConfig.setWriteQueueNums(this.brokerController.getBrokerConfig() .getDefaultTopicQueueNums()); int perm = PermName.PERM_INHERIT | PermName.PERM_READ | PermName.PERM_WRITE; topicConfig.setPerm(perm); putTopicConfig(topicConfig); } } { String topic = TopicValidator.RMQ_SYS_BENCHMARK_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1024); topicConfig.setWriteQueueNums(1024); putTopicConfig(topicConfig); } { String topic = this.brokerController.getBrokerConfig().getBrokerClusterName(); TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); int perm = PermName.PERM_INHERIT; if (this.brokerController.getBrokerConfig().isClusterTopicEnable()) { perm |= PermName.PERM_READ | PermName.PERM_WRITE; } topicConfig.setPerm(perm); putTopicConfig(topicConfig); } { String topic = this.brokerController.getBrokerConfig().getBrokerName(); TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); int perm = PermName.PERM_INHERIT; if (this.brokerController.getBrokerConfig().isBrokerTopicEnable()) { perm |= PermName.PERM_READ | PermName.PERM_WRITE; } topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); topicConfig.setPerm(perm); putTopicConfig(topicConfig); } { String topic = TopicValidator.RMQ_SYS_OFFSET_MOVED_EVENT; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { String topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(SCHEDULE_TOPIC_QUEUE_NUM); topicConfig.setWriteQueueNums(SCHEDULE_TOPIC_QUEUE_NUM); putTopicConfig(topicConfig); } { if (this.brokerController.getBrokerConfig().isTraceTopicEnable()) { String topic = this.brokerController.getBrokerConfig().getMsgTraceTopicName(); TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } } { String topic = this.brokerController.getBrokerConfig().getBrokerClusterName() + "_" + MixAll.REPLY_TOPIC_POSTFIX; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { // PopAckConstants.REVIVE_TOPIC String topic = PopAckConstants.buildClusterReviveTopic(this.brokerController.getBrokerConfig().getBrokerClusterName()); TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(this.brokerController.getBrokerConfig().getReviveQueueNum()); topicConfig.setWriteQueueNums(this.brokerController.getBrokerConfig().getReviveQueueNum()); putTopicConfig(topicConfig); } { // sync broker member group topic String topic = TopicValidator.SYNC_BROKER_MEMBER_GROUP_PREFIX + this.brokerController.getBrokerConfig().getBrokerName(); TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); topicConfig.setPerm(PermName.PERM_INHERIT); putTopicConfig(topicConfig); } { // TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC String topic = TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { // TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC String topic = TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { // TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC String topic = TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { // TopicValidator.RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC String topic = TopicValidator.RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); putTopicConfig(topicConfig); } { if (this.brokerController.getMessageStoreConfig().isTimerWheelEnable()) { String topic = TimerMessageStore.TIMER_TOPIC; TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } } } public TopicConfig putTopicConfig(TopicConfig topicConfig) { return this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } protected TopicConfig getTopicConfig(String topicName) { return this.topicConfigTable.get(topicName); } protected TopicConfig removeTopicConfig(String topicName) { return this.topicConfigTable.remove(topicName); } public TopicConfig selectTopicConfig(final String topic) { return getTopicConfig(topic); } public TopicConfig createTopicInSendMessageMethod(final String topic, final String defaultTopic, final String remoteAddress, final int clientDefaultTopicQueueNums, final int topicSysFlag) { TopicConfig topicConfig = null; boolean createNew = false; try { if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { topicConfig = getTopicConfig(topic); if (topicConfig != null) { return topicConfig; } TopicConfig defaultTopicConfig = getTopicConfig(defaultTopic); if (defaultTopicConfig != null) { if (defaultTopic.equals(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC)) { if (!this.brokerController.getBrokerConfig().isAutoCreateTopicEnable()) { defaultTopicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); } } if (PermName.isInherited(defaultTopicConfig.getPerm())) { topicConfig = new TopicConfig(topic); int queueNums = Math.min(clientDefaultTopicQueueNums, defaultTopicConfig.getWriteQueueNums()); if (queueNums < 0) { queueNums = 0; } topicConfig.setReadQueueNums(queueNums); topicConfig.setWriteQueueNums(queueNums); int perm = defaultTopicConfig.getPerm(); perm &= ~PermName.PERM_INHERIT; topicConfig.setPerm(perm); topicConfig.setTopicSysFlag(topicSysFlag); topicConfig.setTopicFilterType(defaultTopicConfig.getTopicFilterType()); } else { log.warn("Create new topic failed, because the default topic[{}] has no perm [{}] producer:[{}]", defaultTopic, defaultTopicConfig.getPerm(), remoteAddress); } } else { log.warn("Create new topic failed, because the default topic[{}] not exist. producer:[{}]", defaultTopic, remoteAddress); } if (topicConfig != null) { log.info("Create new topic by default topic:[{}] config:[{}] producer:[{}]", defaultTopic, topicConfig, remoteAddress); putTopicConfig(topicConfig); updateDataVersion(); createNew = true; this.persist(); } } finally { this.topicConfigTableLock.unlock(); } } } catch (InterruptedException e) { log.error("createTopicInSendMessageMethod exception", e); } if (createNew) { registerBrokerData(topicConfig); } return topicConfig; } public TopicConfig createTopicIfAbsent(TopicConfig topicConfig) { return createTopicIfAbsent(topicConfig, true); } public TopicConfig createTopicIfAbsent(TopicConfig topicConfig, boolean register) { boolean createNew = false; if (topicConfig == null) { throw new NullPointerException("TopicConfig"); } if (StringUtils.isEmpty(topicConfig.getTopicName())) { throw new IllegalArgumentException("TopicName"); } try { if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { TopicConfig existedTopicConfig = getTopicConfig(topicConfig.getTopicName()); if (existedTopicConfig != null) { return existedTopicConfig; } log.info("Create new topic [{}] config:[{}]", topicConfig.getTopicName(), topicConfig); putTopicConfig(topicConfig); updateDataVersion(); createNew = true; this.persist(); } finally { this.topicConfigTableLock.unlock(); } } } catch (InterruptedException e) { log.error("createTopicIfAbsent ", e); } if (createNew && register) { registerBrokerData(topicConfig); } return getTopicConfig(topicConfig.getTopicName()); } public TopicConfig createTopicInSendMessageBackMethod( final String topic, final int clientDefaultTopicQueueNums, final int perm, final int topicSysFlag) { return createTopicInSendMessageBackMethod(topic, clientDefaultTopicQueueNums, perm, false, topicSysFlag); } public TopicConfig createTopicInSendMessageBackMethod( final String topic, final int clientDefaultTopicQueueNums, final int perm, final boolean isOrder, final int topicSysFlag) { TopicConfig topicConfig = getTopicConfig(topic); if (topicConfig != null) { if (isOrder != topicConfig.isOrder()) { topicConfig.setOrder(isOrder); this.updateTopicConfig(topicConfig); } return topicConfig; } boolean createNew = false; try { if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { topicConfig = getTopicConfig(topic); if (topicConfig != null) { return topicConfig; } topicConfig = new TopicConfig(topic); topicConfig.setReadQueueNums(clientDefaultTopicQueueNums); topicConfig.setWriteQueueNums(clientDefaultTopicQueueNums); topicConfig.setPerm(perm); topicConfig.setTopicSysFlag(topicSysFlag); topicConfig.setOrder(isOrder); log.info("create new topic {}", topicConfig); putTopicConfig(topicConfig); createNew = true; updateDataVersion(); this.persist(); } finally { this.topicConfigTableLock.unlock(); } } } catch (InterruptedException e) { log.error("createTopicInSendMessageBackMethod exception", e); } if (createNew) { registerBrokerData(topicConfig); } return topicConfig; } public TopicConfig createTopicOfTranCheckMaxTime(final int clientDefaultTopicQueueNums, final int perm) { TopicConfig topicConfig = getTopicConfig(TopicValidator.RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC); if (topicConfig != null) return topicConfig; boolean createNew = false; try { if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { topicConfig = getTopicConfig(TopicValidator.RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC); if (topicConfig != null) return topicConfig; topicConfig = new TopicConfig(TopicValidator.RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC); topicConfig.setReadQueueNums(clientDefaultTopicQueueNums); topicConfig.setWriteQueueNums(clientDefaultTopicQueueNums); topicConfig.setPerm(perm); topicConfig.setTopicSysFlag(0); log.info("create new topic {}", topicConfig); putTopicConfig(topicConfig); createNew = true; updateDataVersion(); this.persist(); } finally { this.topicConfigTableLock.unlock(); } } } catch (InterruptedException e) { log.error("create TRANS_CHECK_MAX_TIME_TOPIC exception", e); } if (createNew) { registerBrokerData(topicConfig); } return topicConfig; } public void updateTopicUnitFlag(final String topic, final boolean unit) { TopicConfig topicConfig = getTopicConfig(topic); if (topicConfig != null) { int oldTopicSysFlag = topicConfig.getTopicSysFlag(); if (unit) { topicConfig.setTopicSysFlag(TopicSysFlag.setUnitFlag(oldTopicSysFlag)); } else { topicConfig.setTopicSysFlag(TopicSysFlag.clearUnitFlag(oldTopicSysFlag)); } log.info("update topic sys flag. oldTopicSysFlag={}, newTopicSysFlag={}", oldTopicSysFlag, topicConfig.getTopicSysFlag()); putTopicConfig(topicConfig); updateDataVersion(); this.persist(); registerBrokerData(topicConfig); } } public void updateTopicUnitSubFlag(final String topic, final boolean hasUnitSub) { TopicConfig topicConfig = getTopicConfig(topic); if (topicConfig != null) { int oldTopicSysFlag = topicConfig.getTopicSysFlag(); if (hasUnitSub) { topicConfig.setTopicSysFlag(TopicSysFlag.setUnitSubFlag(oldTopicSysFlag)); } else { topicConfig.setTopicSysFlag(TopicSysFlag.clearUnitSubFlag(oldTopicSysFlag)); } log.info("update topic sys flag. oldTopicSysFlag={}, newTopicSysFlag={}", oldTopicSysFlag, topicConfig.getTopicSysFlag()); putTopicConfig(topicConfig); updateDataVersion(); this.persist(); registerBrokerData(topicConfig); } } public void updateSingleTopicConfigWithoutPersist(final TopicConfig topicConfig) { checkNotNull(topicConfig, "topicConfig shouldn't be null"); Map newAttributes = request(topicConfig); Map currentAttributes = current(topicConfig.getTopicName()); Map finalAttributes = AttributeUtil.alterCurrentAttributes( this.topicConfigTable.get(topicConfig.getTopicName()) == null, TopicAttributes.ALL, ImmutableMap.copyOf(currentAttributes), ImmutableMap.copyOf(newAttributes)); topicConfig.setAttributes(finalAttributes); updateTieredStoreTopicMetadata(topicConfig, newAttributes); TopicConfig old = putTopicConfig(topicConfig); if (old != null) { log.info("update topic config, old:[{}] new:[{}]", old, topicConfig); } else { log.info("create new topic [{}]", topicConfig); } updateDataVersion(); } public void updateTopicConfig(final TopicConfig topicConfig) { updateSingleTopicConfigWithoutPersist(topicConfig); this.persist(topicConfig.getTopicName(), topicConfig); } public void updateTopicConfigList(final List topicConfigList) { topicConfigList.forEach(this::updateSingleTopicConfigWithoutPersist); this.persist(); } private synchronized void updateTieredStoreTopicMetadata(final TopicConfig topicConfig, Map newAttributes) { if (!(brokerController.getMessageStore() instanceof TieredMessageStore)) { if (newAttributes.get(TopicAttributes.TOPIC_RESERVE_TIME_ATTRIBUTE.getName()) != null) { throw new IllegalArgumentException("Update topic reserveTime not supported"); } return; } String topic = topicConfig.getTopicName(); long reserveTime = TopicAttributes.TOPIC_RESERVE_TIME_ATTRIBUTE.getDefaultValue(); String attr = topicConfig.getAttributes().get(TopicAttributes.TOPIC_RESERVE_TIME_ATTRIBUTE.getName()); if (attr != null) { reserveTime = Long.parseLong(attr); } log.info("Update tiered storage metadata, topic {}, reserveTime {}", topic, reserveTime); TieredMessageStore tieredMessageStore = (TieredMessageStore) brokerController.getMessageStore(); MetadataStore metadataStore = tieredMessageStore.getMetadataStore(); TopicMetadata topicMetadata = metadataStore.getTopic(topic); if (topicMetadata == null) { metadataStore.addTopic(topic, reserveTime); } else if (topicMetadata.getReserveTime() != reserveTime) { topicMetadata.setReserveTime(reserveTime); metadataStore.updateTopic(topicMetadata); } } public void updateOrderTopicConfig(final KVTable orderKVTableFromNs) { if (orderKVTableFromNs != null && orderKVTableFromNs.getTable() != null) { boolean isChange = false; Set orderTopics = orderKVTableFromNs.getTable().keySet(); for (String topic : orderTopics) { TopicConfig topicConfig = getTopicConfig(topic); if (topicConfig != null && !topicConfig.isOrder()) { topicConfig.setOrder(true); isChange = true; log.info("update order topic config, topic={}, order={}", topic, true); } } if (isChange) { updateDataVersion(); this.persist(); } } } // make it testable public Map allAttributes() { return TopicAttributes.ALL; } public boolean isOrderTopic(final String topic) { TopicConfig topicConfig = getTopicConfig(topic); if (topicConfig == null) { return false; } else { return topicConfig.isOrder(); } } public void deleteTopicConfig(final String topic) { TopicConfig old = removeTopicConfig(topic); if (old != null) { log.info("delete topic config OK, topic: {}", old); updateDataVersion(); this.persist(); } else { log.warn("delete topic config failed, topic: {} not exists", topic); } } public TopicConfigSerializeWrapper buildTopicConfigSerializeWrapper() { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(this.topicConfigTable); DataVersion dataVersionCopy = new DataVersion(); dataVersionCopy.assignNewOne(this.dataVersion); topicConfigSerializeWrapper.setDataVersion(dataVersionCopy); return topicConfigSerializeWrapper; } public TopicConfigAndMappingSerializeWrapper buildSerializeWrapper(final ConcurrentMap topicConfigTable) { return buildSerializeWrapper(topicConfigTable, Maps.newHashMap()); } public TopicConfigAndMappingSerializeWrapper buildSerializeWrapper( final ConcurrentMap topicConfigTable, final Map topicQueueMappingInfoMap ) { TopicConfigAndMappingSerializeWrapper topicConfigWrapper = new TopicConfigAndMappingSerializeWrapper(); topicConfigWrapper.setTopicConfigTable(topicConfigTable); topicConfigWrapper.setTopicQueueMappingInfoMap(topicQueueMappingInfoMap); topicConfigWrapper.setDataVersion(this.getDataVersion()); return topicConfigWrapper; } @Override public String encode() { return encode(false); } public boolean loadDataVersion() { String fileName = null; try { fileName = this.configFilePath(); String jsonString = MixAll.file2String(fileName); if (jsonString != null) { TopicConfigSerializeWrapper topicConfigSerializeWrapper = TopicConfigSerializeWrapper.fromJson(jsonString, TopicConfigSerializeWrapper.class); if (topicConfigSerializeWrapper != null) { this.dataVersion.assignNewOne(topicConfigSerializeWrapper.getDataVersion()); log.info("load topic metadata dataVersion success {}, {}", fileName, topicConfigSerializeWrapper.getDataVersion()); } } return true; } catch (Exception e) { log.error("load topic metadata dataVersion failed" + fileName, e); return false; } } @Override public String configFilePath() { return BrokerPathConfigHelper.getTopicConfigPath(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { TopicConfigSerializeWrapper topicConfigSerializeWrapper = TopicConfigSerializeWrapper.fromJson(jsonString, TopicConfigSerializeWrapper.class); if (topicConfigSerializeWrapper != null) { this.topicConfigTable.putAll(topicConfigSerializeWrapper.getTopicConfigTable()); this.dataVersion.assignNewOne(topicConfigSerializeWrapper.getDataVersion()); this.printLoadDataWhenFirstBoot(topicConfigSerializeWrapper); } } } public String encode(final boolean prettyFormat) { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(this.topicConfigTable); topicConfigSerializeWrapper.setDataVersion(getDataVersion()); return topicConfigSerializeWrapper.toJson(prettyFormat); } private void printLoadDataWhenFirstBoot(final TopicConfigSerializeWrapper tcs) { Iterator> it = tcs.getTopicConfigTable().entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); log.info("load exist local topic, {}", next.getValue().toString()); } } public DataVersion getDataVersion() { return dataVersion; } public void setTopicConfigTable( ConcurrentMap topicConfigTable) { this.topicConfigTable = topicConfigTable; } public ConcurrentMap getTopicConfigTable() { return topicConfigTable; } public ConcurrentHashMap subTopicConfigTable(String dataVersion, int topicSeq, int maxTopicNum) { // [topicSeq, topicSeq + maxTopicNum) int beginIndex = topicSeq; if (StringUtils.isBlank(dataVersion) || !Objects.equals(DataVersion.fromJson(dataVersion, DataVersion.class), this.dataVersion)) { beginIndex = 0; log.info("get sub topic config table from {} due to {}", beginIndex, StringUtils.isBlank(dataVersion) ? "DataVersion Empty" : "DataVersion Changed"); } ConcurrentHashMap subTopicConfigTable = new ConcurrentHashMap<>(); if (beginIndex < topicConfigTable.size()) { int endIndex = Math.min(beginIndex + maxTopicNum, topicConfigTable.size()); ImmutableSortedMap sortedMap = ImmutableSortedMap.copyOf(topicConfigTable); subTopicConfigTable.putAll(sortedMap.subMap(sortedMap.keySet().asList().get(beginIndex),true, sortedMap.keySet().asList().get(endIndex - 1),true)); } return subTopicConfigTable; } private Map request(TopicConfig topicConfig) { return topicConfig.getAttributes() == null ? new HashMap<>() : topicConfig.getAttributes(); } private Map current(String topic) { TopicConfig topicConfig = getTopicConfig(topic); if (topicConfig == null) { return new HashMap<>(); } else { Map attributes = topicConfig.getAttributes(); if (attributes == null) { return new HashMap<>(); } else { return attributes; } } } private void registerBrokerData(TopicConfig topicConfig) { if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) { this.brokerController.registerSingleTopicAll(topicConfig); } else { this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion); } } public boolean containsTopic(String topic) { return topicConfigTable.containsKey(topic); } public void updateDataVersion() { long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; dataVersion.nextVersion(stateMachineVersion); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingCleanService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.rpc.ClientMetadata; import org.apache.rocketmq.remoting.rpc.RpcClient; import org.apache.rocketmq.remoting.rpc.RpcRequest; import org.apache.rocketmq.remoting.rpc.RpcResponse; import org.apache.rocketmq.store.config.MessageStoreConfig; public class TopicQueueMappingCleanService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private TopicQueueMappingManager topicQueueMappingManager; private BrokerOuterAPI brokerOuterAPI; private RpcClient rpcClient; private MessageStoreConfig messageStoreConfig; private BrokerConfig brokerConfig; private BrokerController brokerController; public TopicQueueMappingCleanService(BrokerController brokerController) { this.brokerController = brokerController; this.topicQueueMappingManager = brokerController.getTopicQueueMappingManager(); this.rpcClient = brokerController.getBrokerOuterAPI().getRpcClient(); this.messageStoreConfig = brokerController.getMessageStoreConfig(); this.brokerConfig = brokerController.getBrokerConfig(); this.brokerOuterAPI = brokerController.getBrokerOuterAPI(); } @Override public String getServiceName() { if (this.brokerConfig.isInBrokerContainer()) { return this.brokerController.getBrokerIdentity().getIdentifier() + TopicQueueMappingCleanService.class.getSimpleName(); } return TopicQueueMappingCleanService.class.getSimpleName(); } @Override public void run() { log.info("Start topic queue mapping clean service thread!"); while (!this.isStopped()) { try { this.waitForRunning(5L * 60 * 1000); } catch (Throwable ignored) { } try { cleanItemExpired(); } catch (Throwable t) { log.error("topic queue mapping cleanItemExpired failed", t); } try { cleanItemListMoreThanSecondGen(); } catch (Throwable t) { log.error("topic queue mapping cleanItemListMoreThanSecondGen failed", t); } } log.info("End topic queue mapping clean service thread!"); } public void cleanItemExpired() { String when = messageStoreConfig.getDeleteWhen(); if (!UtilAll.isItTimeToDo(when)) { return; } boolean changed = false; long start = System.currentTimeMillis(); try { for (String topic : this.topicQueueMappingManager.getTopicQueueMappingTable().keySet()) { try { if (isStopped()) { break; } TopicQueueMappingDetail mappingDetail = this.topicQueueMappingManager.getTopicQueueMappingTable().get(topic); if (mappingDetail == null || mappingDetail.getHostedQueues().isEmpty()) { continue; } if (!mappingDetail.getBname().equals(brokerConfig.getBrokerName())) { log.warn("The TopicQueueMappingDetail [{}] should not exist in this broker", mappingDetail); continue; } Set brokers = new HashSet<>(); for (List items: mappingDetail.getHostedQueues().values()) { if (items.size() <= 1) { continue; } if (!TopicQueueMappingUtils.checkIfLeader(items, mappingDetail)) { continue; } LogicQueueMappingItem earlistItem = items.get(0); brokers.add(earlistItem.getBname()); } Map statsTable = new HashMap<>(); for (String broker: brokers) { GetTopicStatsInfoRequestHeader header = new GetTopicStatsInfoRequestHeader(); header.setTopic(topic); header.setBrokerName(broker); header.setLo(false); try { RpcRequest rpcRequest = new RpcRequest(RequestCode.GET_TOPIC_STATS_INFO, header, null); RpcResponse rpcResponse = rpcClient.invoke(rpcRequest, brokerConfig.getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } statsTable.put(broker, (TopicStatsTable) rpcResponse.getBody()); } catch (Throwable rt) { log.error("Get remote topic {} state info failed from broker {}", topic, broker, rt); } } Map> newHostedQueues = new HashMap<>(); boolean changedForTopic = false; for (Map.Entry> entry : mappingDetail.getHostedQueues().entrySet()) { Integer qid = entry.getKey(); List items = entry.getValue(); if (items.size() <= 1) { continue; } if (!TopicQueueMappingUtils.checkIfLeader(items, mappingDetail)) { continue; } LogicQueueMappingItem earlistItem = items.get(0); TopicStatsTable topicStats = statsTable.get(earlistItem.getBname()); if (topicStats == null) { continue; } TopicOffset topicOffset = topicStats.getOffsetTable().get(new MessageQueue(topic, earlistItem.getBname(), earlistItem.getQueueId())); if (topicOffset == null) { //this may should not happen log.error("Get null topicOffset for {} {}",topic, earlistItem); continue; } //ignore the maxOffset < 0, which may in case of some error if (topicOffset.getMaxOffset() == topicOffset.getMinOffset() || topicOffset.getMaxOffset() == 0) { List newItems = new ArrayList<>(items); boolean result = newItems.remove(earlistItem); if (result) { changedForTopic = true; newHostedQueues.put(qid, newItems); } log.info("The logic queue item {} {} is removed {} because of {}", topic, earlistItem, result, topicOffset); } } if (changedForTopic) { TopicQueueMappingDetail newMappingDetail = new TopicQueueMappingDetail(mappingDetail.getTopic(), mappingDetail.getTotalQueues(), mappingDetail.getBname(), mappingDetail.getEpoch()); newMappingDetail.getHostedQueues().putAll(mappingDetail.getHostedQueues()); newMappingDetail.getHostedQueues().putAll(newHostedQueues); this.topicQueueMappingManager.updateTopicQueueMapping(newMappingDetail, false, true, false); changed = true; } } catch (Throwable tt) { log.error("Try CleanItemExpired failed for {}", topic, tt); } finally { UtilAll.sleep(10); } } } catch (Throwable t) { log.error("Try cleanItemExpired failed", t); } finally { if (changed) { this.topicQueueMappingManager.getDataVersion().nextVersion(); this.topicQueueMappingManager.persist(); log.info("CleanItemExpired changed"); } log.info("cleanItemExpired cost {} ms", System.currentTimeMillis() - start); } } public void cleanItemListMoreThanSecondGen() { String when = messageStoreConfig.getDeleteWhen(); if (!UtilAll.isItTimeToDo(when)) { return; } boolean changed = false; long start = System.currentTimeMillis(); try { ClientMetadata clientMetadata = new ClientMetadata(); for (String topic : this.topicQueueMappingManager.getTopicQueueMappingTable().keySet()) { try { if (isStopped()) { break; } TopicQueueMappingDetail mappingDetail = this.topicQueueMappingManager.getTopicQueueMappingTable().get(topic); if (mappingDetail == null || mappingDetail.getHostedQueues().isEmpty()) { continue; } if (!mappingDetail.getBname().equals(brokerConfig.getBrokerName())) { log.warn("The TopicQueueMappingDetail [{}] should not exist in this broker", mappingDetail); continue; } Map qid2CurrLeaderBroker = new HashMap<>(); for (Map.Entry> entry : mappingDetail.getHostedQueues().entrySet()) { Integer qId = entry.getKey(); List items = entry.getValue(); if (items.isEmpty()) { continue; } LogicQueueMappingItem leaderItem = items.get(items.size() - 1); if (!leaderItem.getBname().equals(mappingDetail.getBname())) { qid2CurrLeaderBroker.put(qId, leaderItem.getBname()); } } if (qid2CurrLeaderBroker.isEmpty()) { continue; } //find the topic route TopicRouteData topicRouteData = brokerOuterAPI.getTopicRouteInfoFromNameServer(topic, brokerConfig.getForwardTimeout()); clientMetadata.freshTopicRoute(topic, topicRouteData); Map qid2RealLeaderBroker = new HashMap<>(); //fine the real leader for (Map.Entry entry : qid2CurrLeaderBroker.entrySet()) { qid2RealLeaderBroker.put(entry.getKey(), clientMetadata.getBrokerNameFromMessageQueue(new MessageQueue(topic, TopicQueueMappingUtils.getMockBrokerName(mappingDetail.getScope()), entry.getKey()))); } //find the mapping detail of real leader Map mappingDetailMap = new HashMap<>(); for (Map.Entry entry : qid2RealLeaderBroker.entrySet()) { if (entry.getValue().startsWith(MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX)) { continue; } String broker = entry.getValue(); GetTopicConfigRequestHeader header = new GetTopicConfigRequestHeader(); header.setTopic(topic); header.setBrokerName(broker); header.setLo(true); try { RpcRequest rpcRequest = new RpcRequest(RequestCode.GET_TOPIC_CONFIG, header, null); RpcResponse rpcResponse = rpcClient.invoke(rpcRequest, brokerConfig.getForwardTimeout()).get(); if (rpcResponse.getException() != null) { throw rpcResponse.getException(); } TopicQueueMappingDetail mappingDetailRemote = ((TopicConfigAndQueueMapping) rpcResponse.getBody()).getMappingDetail(); if (broker.equals(mappingDetailRemote.getBname())) { mappingDetailMap.put(broker, mappingDetailRemote); } } catch (Throwable rt) { log.error("Get remote topic {} state info failed from broker {}", topic, broker, rt); } } //check all the info Set ids2delete = new HashSet<>(); for (Map.Entry entry : qid2CurrLeaderBroker.entrySet()) { Integer qId = entry.getKey(); String currLeaderBroker = entry.getValue(); String realLeaderBroker = qid2RealLeaderBroker.get(qId); TopicQueueMappingDetail remoteMappingDetail = mappingDetailMap.get(realLeaderBroker); if (remoteMappingDetail == null || remoteMappingDetail.getTotalQueues() != mappingDetail.getTotalQueues() || remoteMappingDetail.getEpoch() != mappingDetail.getEpoch()) { continue; } List items = remoteMappingDetail.getHostedQueues().get(qId); if (items.isEmpty()) { continue; } LogicQueueMappingItem leaderItem = items.get(items.size() - 1); if (!realLeaderBroker.equals(leaderItem.getBname())) { continue; } //all the check is ok if (!realLeaderBroker.equals(currLeaderBroker)) { ids2delete.add(qId); } } for (Integer qid : ids2delete) { List items = mappingDetail.getHostedQueues().remove(qid); changed = true; if (items != null) { log.info("Remove the ItemListMoreThanSecondGen topic {} qid {} items {}", topic, qid, items); } } } catch (Throwable tt) { log.error("Try cleanItemListMoreThanSecondGen failed for topic {}", topic, tt); } finally { UtilAll.sleep(10); } } } catch (Throwable t) { log.error("Try cleanItemListMoreThanSecondGen failed", t); } finally { if (changed) { this.topicQueueMappingManager.getDataVersion().nextVersion(); this.topicQueueMappingManager.persist(); } log.info("Try cleanItemListMoreThanSecondGen cost {} ms", System.currentTimeMillis() - start); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import com.google.common.collect.Maps; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.TopicQueueMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import static org.apache.rocketmq.remoting.protocol.RemotingCommand.buildErrorResponse; public class TopicQueueMappingManager extends ConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final long LOCK_TIMEOUT_MILLIS = 3000; private transient final Lock lock = new ReentrantLock(); //this data version should be equal to the TopicConfigManager private final DataVersion dataVersion = new DataVersion(); private transient BrokerController brokerController; private final ConcurrentMap topicQueueMappingTable = new ConcurrentHashMap<>(); public TopicQueueMappingManager(BrokerController brokerController) { this.brokerController = brokerController; } public void updateTopicQueueMapping(TopicQueueMappingDetail newDetail, boolean force, boolean isClean, boolean flush) throws Exception { boolean locked = false; boolean updated = false; TopicQueueMappingDetail oldDetail = null; try { if (lock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { locked = true; } else { return; } if (newDetail == null) { return; } assert newDetail.getBname().equals(this.brokerController.getBrokerConfig().getBrokerName()); newDetail.getHostedQueues().forEach((queueId, items) -> { TopicQueueMappingUtils.checkLogicQueueMappingItemOffset(items); }); oldDetail = topicQueueMappingTable.get(newDetail.getTopic()); if (oldDetail == null) { topicQueueMappingTable.put(newDetail.getTopic(), newDetail); updated = true; return; } if (force) { //bakeup the old items oldDetail.getHostedQueues().forEach((queueId, items) -> { newDetail.getHostedQueues().putIfAbsent(queueId, items); }); topicQueueMappingTable.put(newDetail.getTopic(), newDetail); updated = true; return; } //do more check if (newDetail.getEpoch() < oldDetail.getEpoch()) { throw new RuntimeException(String.format("Can't accept data with small epoch %d < %d", newDetail.getEpoch(), oldDetail.getEpoch())); } if (!newDetail.getScope().equals(oldDetail.getScope())) { throw new RuntimeException(String.format("Can't accept data with unmatched scope %s != %s", newDetail.getScope(), oldDetail.getScope())); } boolean epochEqual = newDetail.getEpoch() == oldDetail.getEpoch(); for (Integer globalId : oldDetail.getHostedQueues().keySet()) { List oldItems = oldDetail.getHostedQueues().get(globalId); List newItems = newDetail.getHostedQueues().get(globalId); if (newItems == null) { if (epochEqual) { throw new RuntimeException("Cannot accept equal epoch with null data"); } else { newDetail.getHostedQueues().put(globalId, oldItems); } } else { TopicQueueMappingUtils.makeSureLogicQueueMappingItemImmutable(oldItems, newItems, epochEqual, isClean); } } topicQueueMappingTable.put(newDetail.getTopic(), newDetail); updated = true; } finally { if (locked) { this.lock.unlock(); } if (updated && flush) { this.dataVersion.nextVersion(); this.persist(); log.info("Update topic queue mapping from [{}] to [{}], force {}", oldDetail, newDetail, force); } } } public void delete(final String topic) { TopicQueueMappingDetail old = this.topicQueueMappingTable.remove(topic); if (old != null) { log.info("delete topic queue mapping OK, static topic queue mapping: {}", old); this.dataVersion.nextVersion(); this.persist(); } else { log.warn("delete topic queue mapping failed, static topic: {} not exists", topic); } } public TopicQueueMappingDetail getTopicQueueMapping(String topic) { return topicQueueMappingTable.get(topic); } @Override public String encode(boolean pretty) { TopicQueueMappingSerializeWrapper wrapper = new TopicQueueMappingSerializeWrapper(); wrapper.setTopicQueueMappingInfoMap(topicQueueMappingTable); wrapper.setDataVersion(this.dataVersion); if (pretty) { return JSON.toJSONString(wrapper, JSONWriter.Feature.PrettyFormat); } return JSON.toJSONString(wrapper); } @Override public String encode() { return encode(false); } @Override public String configFilePath() { return BrokerPathConfigHelper.getTopicQueueMappingPath(this.brokerController.getMessageStoreConfig() .getStorePathRootDir()); } @Override public void decode(String jsonString) { if (jsonString != null) { TopicQueueMappingSerializeWrapper wrapper = TopicQueueMappingSerializeWrapper.fromJson(jsonString, TopicQueueMappingSerializeWrapper.class); if (wrapper != null) { this.topicQueueMappingTable.putAll(wrapper.getTopicQueueMappingInfoMap()); this.dataVersion.assignNewOne(wrapper.getDataVersion()); } } } public ConcurrentMap getTopicQueueMappingTable() { return topicQueueMappingTable; } public ConcurrentMap subTopicQueueMappingTable(Set topicSet) { if (MapUtils.isEmpty(topicQueueMappingTable) || CollectionUtils.isEmpty(topicSet)) { return Maps.newConcurrentMap(); } return topicQueueMappingTable.entrySet().stream() .filter(e -> topicSet.contains(e.getKey())) .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); } public DataVersion getDataVersion() { return dataVersion; } public TopicQueueMappingContext buildTopicQueueMappingContext(TopicRequestHeader requestHeader) { return buildTopicQueueMappingContext(requestHeader, false); } //Do not return a null context public TopicQueueMappingContext buildTopicQueueMappingContext(TopicRequestHeader requestHeader, boolean selectOneWhenMiss) { // if lo is set to false explicitly, it maybe the forwarded request if (requestHeader.getLo() != null && Boolean.FALSE.equals(requestHeader.getLo())) { return new TopicQueueMappingContext(requestHeader.getTopic(), null, null, null, null); } String topic = requestHeader.getTopic(); Integer globalId = null; if (requestHeader instanceof TopicQueueRequestHeader) { globalId = ((TopicQueueRequestHeader) requestHeader).getQueueId(); } TopicQueueMappingDetail mappingDetail = getTopicQueueMapping(topic); if (mappingDetail == null) { //it is not static topic return new TopicQueueMappingContext(topic, null, null, null, null); } assert mappingDetail.getBname().equals(this.brokerController.getBrokerConfig().getBrokerName()); if (globalId == null) { return new TopicQueueMappingContext(topic, null, mappingDetail, null, null); } //If not find mappingItem, it encounters some errors if (globalId < 0 && !selectOneWhenMiss) { return new TopicQueueMappingContext(topic, globalId, mappingDetail, null, null); } if (globalId < 0) { try { if (!mappingDetail.getHostedQueues().isEmpty()) { //do not check globalId = mappingDetail.getHostedQueues().keySet().iterator().next(); } } catch (Throwable ignored) { } } if (globalId < 0) { return new TopicQueueMappingContext(topic, globalId, mappingDetail, null, null); } List mappingItemList = TopicQueueMappingDetail.getMappingInfo(mappingDetail, globalId); LogicQueueMappingItem leaderItem = null; if (mappingItemList != null && mappingItemList.size() > 0) { leaderItem = mappingItemList.get(mappingItemList.size() - 1); } return new TopicQueueMappingContext(topic, globalId, mappingDetail, mappingItemList, leaderItem); } public RemotingCommand rewriteRequestForStaticTopic(TopicQueueRequestHeader requestHeader, TopicQueueMappingContext mappingContext) { try { if (mappingContext.getMappingDetail() == null) { return null; } TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); if (!mappingContext.isLeader()) { return buildErrorResponse(ResponseCode.NOT_LEADER_FOR_QUEUE, String.format("%s-%d does not exit in request process of current broker %s", requestHeader.getTopic(), requestHeader.getQueueId(), mappingDetail.getBname())); } LogicQueueMappingItem mappingItem = mappingContext.getLeaderItem(); requestHeader.setQueueId(mappingItem.getQueueId()); return null; } catch (Throwable t) { return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import com.google.common.collect.Sets; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class TopicRouteInfoManager { private static final long GET_TOPIC_ROUTE_TIMEOUT = 3000L; private static final long LOCK_TIMEOUT_MILLIS = 3000L; private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final Lock lockNamesrv = new ReentrantLock(); private final ConcurrentMap topicRouteTable = new ConcurrentHashMap<>(); private final ConcurrentMap> brokerAddrTable = new ConcurrentHashMap<>(); private final ConcurrentMap topicPublishInfoTable = new ConcurrentHashMap<>(); private final ConcurrentHashMap> topicSubscribeInfoTable = new ConcurrentHashMap<>(); private ScheduledExecutorService scheduledExecutorService; private BrokerController brokerController; public TopicRouteInfoManager(BrokerController brokerController) { this.brokerController = brokerController; } public void start() { this.scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("TopicRouteInfoManagerScheduledThread")); this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { updateTopicRouteInfoFromNameServer(); } catch (Exception e) { log.error("ScheduledTask: failed to pull TopicRouteData from NameServer", e); } }, 1000, this.brokerController.getBrokerConfig().getLoadBalancePollNameServerInterval(), TimeUnit.MILLISECONDS); } private void updateTopicRouteInfoFromNameServer() { final Set topicSetForPopAssignment = this.topicSubscribeInfoTable.keySet(); final Set topicSetForEscapeBridge = this.topicRouteTable.keySet(); final Set topicsAll = Sets.union(topicSetForPopAssignment, topicSetForEscapeBridge); for (String topic : topicsAll) { boolean isNeedUpdatePublishInfo = topicSetForEscapeBridge.contains(topic); boolean isNeedUpdateSubscribeInfo = topicSetForPopAssignment.contains(topic); updateTopicRouteInfoFromNameServer(topic, isNeedUpdatePublishInfo, isNeedUpdateSubscribeInfo); } } public void updateTopicRouteInfoFromNameServer(String topic, boolean isNeedUpdatePublishInfo, boolean isNeedUpdateSubscribeInfo) { try { if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { final TopicRouteData topicRouteData = this.brokerController.getBrokerOuterAPI() .getTopicRouteInfoFromNameServer(topic, GET_TOPIC_ROUTE_TIMEOUT); if (null == topicRouteData) { log.warn("TopicRouteInfoManager: updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}.", topic); return; } if (isNeedUpdateSubscribeInfo) { this.updateSubscribeInfoTable(topicRouteData, topic); } if (isNeedUpdatePublishInfo) { this.updateTopicRouteTable(topic, topicRouteData); } } catch (RemotingException e) { log.error("updateTopicRouteInfoFromNameServer Exception", e); } catch (MQBrokerException e) { log.error("updateTopicRouteInfoFromNameServer Exception", e); if (!NamespaceUtil.isRetryTopic(topic) && ResponseCode.TOPIC_NOT_EXIST == e.getResponseCode()) { // clean no used topic cleanNoneRouteTopic(topic); } } finally { this.lockNamesrv.unlock(); } } } catch (InterruptedException e) { log.warn("updateTopicRouteInfoFromNameServer Exception", e); } } private boolean updateTopicRouteTable(String topic, TopicRouteData topicRouteData) { TopicRouteData old = this.topicRouteTable.get(topic); boolean changed = topicRouteData.topicRouteDataChanged(old); if (!changed) { if (!this.isNeedUpdateTopicRouteInfo(topic)) { return false; } } else { log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData); } for (BrokerData bd : topicRouteData.getBrokerDatas()) { this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs()); } TopicPublishInfo publishInfo = MQClientInstance.topicRouteData2TopicPublishInfo(topic, topicRouteData); publishInfo.setHaveTopicRouterInfo(true); this.updateTopicPublishInfo(topic, publishInfo); TopicRouteData cloneTopicRouteData = new TopicRouteData(topicRouteData); log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData); this.topicRouteTable.put(topic, cloneTopicRouteData); return true; } private boolean updateSubscribeInfoTable(TopicRouteData topicRouteData, String topic) { final TopicRouteData tmp = new TopicRouteData(topicRouteData); tmp.setTopicQueueMappingByBroker(null); Set newSubscribeInfo = MQClientInstance.topicRouteData2TopicSubscribeInfo(topic, tmp); Set oldSubscribeInfo = topicSubscribeInfoTable.get(topic); if (Objects.equals(newSubscribeInfo, oldSubscribeInfo)) { return false; } log.info("the topic[{}] subscribe message queue changed, old[{}] ,new[{}]", topic, oldSubscribeInfo, newSubscribeInfo); topicSubscribeInfoTable.put(topic, newSubscribeInfo); return true; } private boolean isNeedUpdateTopicRouteInfo(final String topic) { final TopicPublishInfo prev = this.topicPublishInfoTable.get(topic); return null == prev || !prev.ok(); } private void cleanNoneRouteTopic(String topic) { // clean no used topic topicSubscribeInfoTable.remove(topic); } private void updateTopicPublishInfo(final String topic, final TopicPublishInfo info) { if (info != null && topic != null) { TopicPublishInfo prev = this.topicPublishInfoTable.put(topic, info); if (prev != null) { log.info("updateTopicPublishInfo prev is not null, " + prev); } } } public void shutdown() { if (null != this.scheduledExecutorService) { this.scheduledExecutorService.shutdown(); } } public TopicPublishInfo tryToFindTopicPublishInfo(final String topic) { TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic); if (null == topicPublishInfo || !topicPublishInfo.ok()) { this.updateTopicRouteInfoFromNameServer(topic, true, false); topicPublishInfo = this.topicPublishInfoTable.get(topic); } return topicPublishInfo; } public String findBrokerAddressInPublish(String brokerName) { if (brokerName == null) { return null; } Map map = this.brokerAddrTable.get(brokerName); if (map != null && !map.isEmpty()) { return map.get(MixAll.MASTER_ID); } return null; } public String findBrokerAddressInSubscribe( final String brokerName, final long brokerId, final boolean onlyThisBroker ) { if (brokerName == null) { return null; } String brokerAddr = null; boolean found = false; Map map = this.brokerAddrTable.get(brokerName); if (map != null && !map.isEmpty()) { brokerAddr = map.get(brokerId); boolean slave = brokerId != MixAll.MASTER_ID; found = brokerAddr != null; if (!found && slave) { brokerAddr = map.get(brokerId + 1); found = brokerAddr != null; } if (!found && !onlyThisBroker) { Map.Entry entry = map.entrySet().iterator().next(); brokerAddr = entry.getValue(); found = true; } } return brokerAddr; } public Set getTopicSubscribeInfo(String topic) { Set queues = topicSubscribeInfoTable.get(topic); if (null == queues || queues.isEmpty()) { this.updateTopicRouteInfoFromNameServer(topic, false, true); queues = this.topicSubscribeInfoTable.get(topic); } return queues; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction; import io.netty.channel.Channel; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; public abstract class AbstractTransactionalMessageCheckListener { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private BrokerController brokerController; //queue nums of topic TRANS_CHECK_MAX_TIME_TOPIC protected final static int TCMT_QUEUE_NUMS = 1; private volatile ExecutorService executorService; public AbstractTransactionalMessageCheckListener() { } public AbstractTransactionalMessageCheckListener(BrokerController brokerController) { this.brokerController = brokerController; } public void sendCheckMessage(MessageExt msgExt) throws Exception { CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader(); checkTransactionStateRequestHeader.setTopic(msgExt.getTopic()); checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset()); checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId()); checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId()); checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset()); checkTransactionStateRequestHeader.setBrokerName(brokerController.getBrokerConfig().getBrokerName()); msgExt.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgExt.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID))); msgExt.setStoreSize(0); String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); Channel channel = brokerController.getProducerManager().getAvailableChannel(groupId); if (channel != null) { brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt); } else { LOGGER.warn("Check transaction failed, channel is null. groupId={}", groupId); } } public void resolveHalfMsg(final MessageExt msgExt) { if (executorService != null) { executorService.execute(new Runnable() { @Override public void run() { try { sendCheckMessage(msgExt); } catch (Exception e) { LOGGER.error("Send check message error!", e); } } }); } else { LOGGER.error("TransactionalMessageCheckListener not init"); } } public BrokerController getBrokerController() { return brokerController; } public void shutdown() { if (executorService != null) { executorService.shutdown(); } } public synchronized void initExecutorService() { if (executorService == null) { executorService = ThreadUtils.newThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), new ThreadFactoryImpl("Transaction-msg-check-thread", brokerController.getBrokerIdentity()), new CallerRunsPolicy()); } } /** * Inject brokerController for this listener * * @param brokerController */ public void setBrokerController(BrokerController brokerController) { this.brokerController = brokerController; initExecutorService(); } /** * In order to avoid check back unlimited, we will discard the message that have been checked more than a certain * number of times. * * @param msgExt Message to be discarded. */ public abstract void resolveDiscardMsg(MessageExt msgExt); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/OperationResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction; import org.apache.rocketmq.common.message.MessageExt; public class OperationResult { private MessageExt prepareMessage; private int responseCode; private String responseRemark; public void setPrepareMessage(MessageExt prepareMessage) { this.prepareMessage = prepareMessage; } public int getResponseCode() { return responseCode; } public void setResponseCode(int responseCode) { this.responseCode = responseCode; } public String getResponseRemark() { return responseRemark; } public void setResponseRemark(String responseRemark) { this.responseRemark = responseRemark; } public MessageExt getPrepareMessage() { return prepareMessage; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionMetrics.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; public class TransactionMetrics extends ConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private ConcurrentMap transactionCounts = new ConcurrentHashMap<>(1024); private DataVersion dataVersion = new DataVersion(); private final String configPath; public TransactionMetrics(String configPath) { this.configPath = configPath; } public long addAndGet(String topic, int value) { Metric pair = getTopicPair(topic); getDataVersion().nextVersion(); pair.setTimeStamp(System.currentTimeMillis()); return pair.getCount().addAndGet(value); } public Metric getTopicPair(String topic) { Metric pair = transactionCounts.get(topic); if (null != pair) { return pair; } pair = new Metric(); final Metric previous = transactionCounts.putIfAbsent(topic, pair); if (null != previous) { return previous; } return pair; } public long getTransactionCount(String topic) { Metric pair = transactionCounts.get(topic); if (null == pair) { return 0; } else { return pair.getCount().get(); } } public Map getTransactionCounts() { return transactionCounts; } public void setTransactionCounts(ConcurrentMap transactionCounts) { this.transactionCounts = transactionCounts; } protected void write0(Writer writer) throws IOException { TransactionMetricsSerializeWrapper wrapper = new TransactionMetricsSerializeWrapper(); wrapper.setTransactionCount(transactionCounts); wrapper.setDataVersion(dataVersion); writer.write(JSON.toJSONString(wrapper, JSONWriter.Feature.BrowserCompatible)); } @Override public String encode() { return encode(false); } @Override public String configFilePath() { return configPath; } @Override public void decode(String jsonString) { if (jsonString != null) { TransactionMetricsSerializeWrapper transactionMetricsSerializeWrapper = TransactionMetricsSerializeWrapper.fromJson(jsonString, TransactionMetricsSerializeWrapper.class); if (transactionMetricsSerializeWrapper != null) { this.transactionCounts.putAll(transactionMetricsSerializeWrapper.getTransactionCount()); this.dataVersion.assignNewOne(transactionMetricsSerializeWrapper.getDataVersion()); } } } @Override public String encode(boolean prettyFormat) { TransactionMetricsSerializeWrapper metricsSerializeWrapper = new TransactionMetricsSerializeWrapper(); metricsSerializeWrapper.setDataVersion(this.dataVersion); metricsSerializeWrapper.setTransactionCount(this.transactionCounts); return metricsSerializeWrapper.toJson(prettyFormat); } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } public void cleanMetrics(Set topics) { if (topics == null || topics.isEmpty()) { return; } Iterator> iterator = transactionCounts.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); final String topic = entry.getKey(); if (topic.startsWith(TopicValidator.SYSTEM_TOPIC_PREFIX)) { continue; } if (!topics.contains(topic)) { continue; } // in the input topics set, then remove it. iterator.remove(); } } public static class TransactionMetricsSerializeWrapper extends RemotingSerializable { private ConcurrentMap transactionCount = new ConcurrentHashMap<>(1024); private DataVersion dataVersion = new DataVersion(); public ConcurrentMap getTransactionCount() { return transactionCount; } public void setTransactionCount( ConcurrentMap transactionCount) { this.transactionCount = transactionCount; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } @Override public synchronized void persist() { try { // bak metrics file String config = configFilePath(); String backup = config + ".bak"; File configFile = new File(config); File bakFile = new File(backup); if (configFile.exists()) { // atomic move Files.move(configFile.toPath(), bakFile.toPath(), StandardCopyOption.ATOMIC_MOVE); // sync the directory, ensure that the bak file is visible MixAll.fsyncDirectory(Paths.get(bakFile.getParent())); } File dir = new File(configFile.getParent()); if (!dir.exists()) { Files.createDirectories(dir.toPath()); } // persist metrics file StringWriter stringWriter = new StringWriter(); write0(stringWriter); try (RandomAccessFile randomAccessFile = new RandomAccessFile(config, "rw")) { randomAccessFile.write(stringWriter.toString().getBytes(StandardCharsets.UTF_8)); randomAccessFile.getChannel().force(true); // sync the directory, ensure that the config file is visible MixAll.fsyncDirectory(Paths.get(configFile.getParent())); } } catch (Throwable t) { log.error("Failed to persist", t); } } public static class Metric { private AtomicLong count; private long timeStamp; public Metric() { count = new AtomicLong(0); timeStamp = System.currentTimeMillis(); } public AtomicLong getCount() { return count; } public void setCount(AtomicLong count) { this.count = count; } public long getTimeStamp() { return timeStamp; } public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } @Override public String toString() { return String.format("[%d,%d]", count.get(), timeStamp); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionMetricsFlushService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class TransactionMetricsFlushService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private BrokerController brokerController; public TransactionMetricsFlushService(BrokerController brokerController) { this.brokerController = brokerController; } @Override public String getServiceName() { return "TransactionFlushService"; } @Override public void run() { log.info(this.getServiceName() + " service start"); long start = System.currentTimeMillis(); while (!this.isStopped()) { try { if (System.currentTimeMillis() - start > brokerController.getBrokerConfig().getTransactionMetricFlushInterval()) { start = System.currentTimeMillis(); brokerController.getTransactionalMessageService().getTransactionMetrics().persist(); waitForRunning(brokerController.getBrokerConfig().getTransactionMetricFlushInterval()); } } catch (Throwable e) { log.error("Error occurred in " + getServiceName(), e); } } log.info(this.getServiceName() + " service end"); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class TransactionalMessageCheckService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private BrokerController brokerController; public TransactionalMessageCheckService(BrokerController brokerController) { this.brokerController = brokerController; } @Override public String getServiceName() { if (brokerController != null && brokerController.getBrokerConfig().isInBrokerContainer()) { return brokerController.getBrokerIdentity().getIdentifier() + TransactionalMessageCheckService.class.getSimpleName(); } return TransactionalMessageCheckService.class.getSimpleName(); } @Override public void run() { log.info("Start transaction check service thread!"); while (!this.isStopped()) { long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval(); this.waitForRunning(checkInterval); } log.info("End transaction check service thread!"); } @Override protected void onWaitEnd() { long timeout = brokerController.getBrokerConfig().getTransactionTimeOut(); int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax(); long begin = System.currentTimeMillis(); log.info("Begin to check prepare message, begin time:{}", begin); this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener()); log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.PutMessageResult; public interface TransactionalMessageService { /** * Process prepare message, in common, we should put this message to storage service. * * @param messageInner Prepare(Half) message. * @return Prepare message storage result. */ PutMessageResult prepareMessage(MessageExtBrokerInner messageInner); /** * Process prepare message in async manner, we should put this message to storage service * * @param messageInner Prepare(Half) message. * @return CompletableFuture of put result, will be completed at put success(flush and replica done) */ CompletableFuture asyncPrepareMessage(MessageExtBrokerInner messageInner); /** * Delete prepare message when this message has been committed or rolled back. * * @param messageExt */ boolean deletePrepareMessage(MessageExt messageExt); /** * Invoked to process commit prepare message. * * @param requestHeader Commit message request header. * @return Operate result contains prepare message and relative error code. */ OperationResult commitMessage(EndTransactionRequestHeader requestHeader); /** * Invoked to roll back prepare message. * * @param requestHeader Prepare message request header. * @return Operate result contains prepare message and relative error code. */ OperationResult rollbackMessage(EndTransactionRequestHeader requestHeader); /** * Traverse uncommitted/unroll back half message and send check back request to producer to obtain transaction * status. * * @param transactionTimeout The minimum time of the transactional message to be checked firstly, one message only * exceed this time interval that can be checked. * @param transactionCheckMax The maximum number of times the message was checked, if exceed this value, this * message will be discarded. * @param listener When the message is considered to be checked or discarded, the relative method of this class will * be invoked. */ void check(long transactionTimeout, int transactionCheckMax, AbstractTransactionalMessageCheckListener listener); /** * Open transaction service. * * @return If open success, return true. */ boolean open(); /** * Close transaction service. */ void close(); TransactionMetrics getTransactionMetrics(); void setTransactionMetrics(TransactionMetrics transactionMetrics); } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; public class DefaultTransactionalMessageCheckListener extends AbstractTransactionalMessageCheckListener { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); public DefaultTransactionalMessageCheckListener() { super(); } @Override public void resolveDiscardMsg(MessageExt msgExt) { log.error("MsgExt:{} has been checked too many times, so discard it by moving it to system topic TRANS_CHECK_MAXTIME_TOPIC", msgExt); try { MessageExtBrokerInner brokerInner = toMessageExtBrokerInner(msgExt); PutMessageResult putMessageResult = this.getBrokerController().getMessageStore().putMessage(brokerInner); if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { log.info("Put checked-too-many-time half message to TRANS_CHECK_MAXTIME_TOPIC OK. Restored in queueOffset={}, " + "commitLogOffset={}, real topic={}", msgExt.getQueueOffset(), msgExt.getCommitLogOffset(), msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC)); // discarded, then the num of half-messages minus 1 this.getBrokerController().getTransactionalMessageService().getTransactionMetrics().addAndGet(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC), -1); } else { log.error("Put checked-too-many-time half message to TRANS_CHECK_MAXTIME_TOPIC failed, real topic={}, msgId={}", msgExt.getTopic(), msgExt.getMsgId()); } } catch (Exception e) { log.warn("Put checked-too-many-time message to TRANS_CHECK_MAXTIME_TOPIC error. {}", e); } } private MessageExtBrokerInner toMessageExtBrokerInner(MessageExt msgExt) { TopicConfig topicConfig = this.getBrokerController().getTopicConfigManager().createTopicOfTranCheckMaxTime(TCMT_QUEUE_NUMS, PermName.PERM_READ | PermName.PERM_WRITE); MessageExtBrokerInner inner = new MessageExtBrokerInner(); inner.setTopic(topicConfig.getTopicName()); inner.setBody(msgExt.getBody()); inner.setFlag(msgExt.getFlag()); MessageAccessor.setProperties(inner, msgExt.getProperties()); inner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); inner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgExt.getTags())); inner.setQueueId(0); inner.setSysFlag(msgExt.getSysFlag()); inner.setBornHost(msgExt.getBornHost()); inner.setBornTimestamp(msgExt.getBornTimestamp()); inner.setStoreHost(msgExt.getStoreHost()); inner.setReconsumeTimes(msgExt.getReconsumeTimes()); inner.setMsgId(msgExt.getMsgId()); inner.setWaitStoreMsgOK(false); return inner; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/GetResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.common.message.MessageExt; public class GetResult { private MessageExt msg; private PullResult pullResult; public MessageExt getMsg() { return msg; } public void setMsg(MessageExt msg) { this.msg = msg; } public PullResult getPullResult() { return pullResult; } public void setPullResult(PullResult pullResult) { this.pullResult = pullResult; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/MessageQueueOpContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; public class MessageQueueOpContext { private AtomicInteger totalSize = new AtomicInteger(0); private volatile long lastWriteTimestamp; private LinkedBlockingQueue contextQueue; public MessageQueueOpContext(long timestamp, int queueLength) { this.lastWriteTimestamp = timestamp; contextQueue = new LinkedBlockingQueue(queueLength); } public LinkedBlockingQueue getContextQueue() { return contextQueue; } public AtomicInteger getTotalSize() { return totalSize; } public long getLastWriteTimestamp() { return lastWriteTimestamp; } public void setLastWriteTimestamp(long lastWriteTimestamp) { this.lastWriteTimestamp = lastWriteTimestamp; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridge.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import io.opentelemetry.api.common.Attributes; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CONSUMER_GROUP; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; public class TransactionalMessageBridge { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private final ConcurrentHashMap opQueueMap = new ConcurrentHashMap<>(); private final BrokerController brokerController; private final MessageStore store; private final SocketAddress storeHost; public TransactionalMessageBridge(BrokerController brokerController, MessageStore store) { try { this.brokerController = brokerController; this.store = store; this.storeHost = new InetSocketAddress(brokerController.getBrokerConfig().getBrokerIP1(), brokerController.getNettyServerConfig().getListenPort()); } catch (Exception e) { LOGGER.error("Init TransactionBridge error", e); throw new RuntimeException(e); } } public long fetchConsumeOffset(MessageQueue mq) { long offset = brokerController.getConsumerOffsetManager().queryOffset(TransactionalMessageUtil.buildConsumerGroup(), mq.getTopic(), mq.getQueueId()); if (offset == -1) { offset = store.getMinOffsetInQueue(mq.getTopic(), mq.getQueueId()); } return offset; } public Set fetchMessageQueues(String topic) { Set mqSet = new HashSet<>(); TopicConfig topicConfig = selectTopicConfig(topic); if (topicConfig != null && topicConfig.getReadQueueNums() > 0) { for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { MessageQueue mq = new MessageQueue(); mq.setTopic(topic); mq.setBrokerName(brokerController.getBrokerConfig().getBrokerName()); mq.setQueueId(i); mqSet.add(mq); } } return mqSet; } public void updateConsumeOffset(MessageQueue mq, long offset) { this.brokerController.getConsumerOffsetManager().commitOffset( RemotingHelper.parseSocketAddressAddr(this.storeHost), TransactionalMessageUtil.buildConsumerGroup(), mq.getTopic(), mq.getQueueId(), offset); } public PullResult getHalfMessage(int queueId, long offset, int nums) { String group = TransactionalMessageUtil.buildConsumerGroup(); String topic = TransactionalMessageUtil.buildHalfTopic(); SubscriptionData sub = new SubscriptionData(topic, "*"); return getMessage(group, topic, queueId, offset, nums, sub); } public PullResult getOpMessage(int queueId, long offset, int nums) { String group = TransactionalMessageUtil.buildConsumerGroup(); String topic = TransactionalMessageUtil.buildOpTopic(); SubscriptionData sub = new SubscriptionData(topic, "*"); return getMessage(group, topic, queueId, offset, nums, sub); } private PullResult getMessage(String group, String topic, int queueId, long offset, int nums, SubscriptionData sub) { GetMessageResult getMessageResult = store.getMessage(group, topic, queueId, offset, nums, null); if (getMessageResult != null) { PullStatus pullStatus = PullStatus.NO_NEW_MSG; List foundList = null; switch (getMessageResult.getStatus()) { case FOUND: pullStatus = PullStatus.FOUND; foundList = decodeMsgList(getMessageResult); this.brokerController.getBrokerStatsManager().incGroupGetNums(group, topic, getMessageResult.getMessageCount()); this.brokerController.getBrokerStatsManager().incGroupGetSize(group, topic, getMessageResult.getBufferTotalSize()); this.brokerController.getBrokerStatsManager().incBrokerGetNums(topic, getMessageResult.getMessageCount()); if (foundList == null || foundList.size() == 0) { break; } this.brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId, this.brokerController.getMessageStore().now() - foundList.get(foundList.size() - 1) .getStoreTimestamp()); Attributes attributes = this.brokerController.getBrokerMetricsManager().newAttributesBuilder() .put(LABEL_TOPIC, topic) .put(LABEL_CONSUMER_GROUP, group) .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic) || MixAll.isSysConsumerGroup(group)) .build(); this.brokerController.getBrokerMetricsManager().getMessagesOutTotal().add(getMessageResult.getMessageCount(), attributes); this.brokerController.getBrokerMetricsManager().getThroughputOutTotal().add(getMessageResult.getBufferTotalSize(), attributes); break; case NO_MATCHED_MESSAGE: pullStatus = PullStatus.NO_MATCHED_MSG; LOGGER.warn("No matched message. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}", getMessageResult.getStatus(), topic, group, offset); break; case NO_MESSAGE_IN_QUEUE: case OFFSET_OVERFLOW_ONE: pullStatus = PullStatus.NO_NEW_MSG; LOGGER.warn("No new message. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}", getMessageResult.getStatus(), topic, group, offset); break; case MESSAGE_WAS_REMOVING: case NO_MATCHED_LOGIC_QUEUE: case OFFSET_FOUND_NULL: case OFFSET_OVERFLOW_BADLY: case OFFSET_TOO_SMALL: pullStatus = PullStatus.OFFSET_ILLEGAL; LOGGER.warn("Offset illegal. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}", getMessageResult.getStatus(), topic, group, offset); break; default: assert false; break; } return new PullResult(pullStatus, getMessageResult.getNextBeginOffset(), getMessageResult.getMinOffset(), getMessageResult.getMaxOffset(), foundList); } else { LOGGER.error("Get message from store return null. topic={}, groupId={}, requestOffset={}", topic, group, offset); return null; } } private List decodeMsgList(GetMessageResult getMessageResult) { List foundList = new ArrayList<>(); try { List messageBufferList = getMessageResult.getMessageBufferList(); for (ByteBuffer bb : messageBufferList) { MessageExt msgExt = MessageDecoder.decode(bb, true, false); if (msgExt != null) { foundList.add(msgExt); } } } finally { getMessageResult.release(); } return foundList; } public PutMessageResult putHalfMessage(MessageExtBrokerInner messageInner) { return store.putMessage(parseHalfMessageInner(messageInner)); } public CompletableFuture asyncPutHalfMessage(MessageExtBrokerInner messageInner) { return store.asyncPutMessage(parseHalfMessageInner(messageInner)); } private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) { String uniqId = msgInner.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (uniqId != null && !uniqId.isEmpty()) { MessageAccessor.putProperty(msgInner, TransactionalMessageUtil.TRANSACTION_ID, uniqId); } MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic()); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msgInner.getQueueId())); msgInner.setSysFlag( MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE)); if (null != store.getMessageStoreConfig() && store.getMessageStoreConfig().isTransRocksDBEnable() && !store.getMessageStoreConfig().isTransWriteOriginTransHalfEnable()) { msgInner.setTopic(TransactionalMessageUtil.buildHalfTopicForRocksDB()); } else { msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic()); } msgInner.setQueueId(0); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return msgInner; } public PutMessageResult putMessageReturnResult(MessageExtBrokerInner messageInner) { LOGGER.debug("[BUG-TO-FIX] Thread:{} msgID:{}", Thread.currentThread().getName(), messageInner.getMsgId()); PutMessageResult result = store.putMessage(messageInner); if (result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { this.brokerController.getBrokerStatsManager().incTopicPutNums(messageInner.getTopic()); this.brokerController.getBrokerStatsManager().incTopicPutSize(messageInner.getTopic(), result.getAppendMessageResult().getWroteBytes()); this.brokerController.getBrokerStatsManager().incBrokerPutNums(); } return result; } public boolean putMessage(MessageExtBrokerInner messageInner) { PutMessageResult putMessageResult = store.putMessage(messageInner); if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { return true; } else { LOGGER.error("Put message failed, topic: {}, queueId: {}, msgId: {}", messageInner.getTopic(), messageInner.getQueueId(), messageInner.getMsgId()); return false; } } public MessageExtBrokerInner renewImmunityHalfMessageInner(MessageExt msgExt) { MessageExtBrokerInner msgInner = renewHalfMessageInner(msgExt); String queueOffsetFromPrepare = msgExt.getUserProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); if (null != queueOffsetFromPrepare) { MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET, queueOffsetFromPrepare); } else { MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET, String.valueOf(msgExt.getQueueOffset())); } msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return msgInner; } public MessageExtBrokerInner renewHalfMessageInner(MessageExt msgExt) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(msgExt.getTopic()); msgInner.setBody(msgExt.getBody()); msgInner.setQueueId(msgExt.getQueueId()); msgInner.setMsgId(msgExt.getMsgId()); msgInner.setSysFlag(msgExt.getSysFlag()); msgInner.setTags(msgExt.getTags()); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgInner.getTags())); MessageAccessor.setProperties(msgInner, msgExt.getProperties()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setStoreHost(msgExt.getStoreHost()); msgInner.setWaitStoreMsgOK(false); return msgInner; } private MessageExtBrokerInner makeOpMessageInner(Message message, MessageQueue messageQueue) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(message.getTopic()); msgInner.setBody(message.getBody()); msgInner.setQueueId(messageQueue.getQueueId()); msgInner.setTags(message.getTags()); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgInner.getTags())); msgInner.setSysFlag(0); MessageAccessor.setProperties(msgInner, message.getProperties()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(message.getProperties())); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(this.storeHost); msgInner.setStoreHost(this.storeHost); msgInner.setWaitStoreMsgOK(false); MessageClientIDSetter.setUniqID(msgInner); return msgInner; } private TopicConfig selectTopicConfig(String topic) { TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic); if (topicConfig == null) { topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod( topic, 1, PermName.PERM_WRITE | PermName.PERM_READ, 0); } return topicConfig; } public boolean writeOp(Integer queueId,Message message) { MessageQueue opQueue = opQueueMap.get(queueId); if (opQueue == null) { opQueue = getOpQueueByHalf(queueId, this.brokerController.getBrokerConfig().getBrokerName()); MessageQueue oldQueue = opQueueMap.putIfAbsent(queueId, opQueue); if (oldQueue != null) { opQueue = oldQueue; } } PutMessageResult result = putMessageReturnResult(makeOpMessageInner(message, opQueue)); if (result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { return true; } return false; } private MessageQueue getOpQueueByHalf(Integer queueId, String brokerName) { MessageQueue opQueue = new MessageQueue(); opQueue.setTopic(TransactionalMessageUtil.buildOpTopic()); opQueue.setBrokerName(brokerName); opQueue.setQueueId(queueId); return opQueue; } public MessageExt lookMessageByOffset(final long commitLogOffset) { return this.store.lookMessageByOffset(commitLogOffset); } public BrokerController getBrokerController() { return brokerController; } public boolean escapeMessage(MessageExtBrokerInner messageInner) { PutMessageResult putMessageResult = this.brokerController.getEscapeBridge().putMessage(messageInner); if (putMessageResult != null && putMessageResult.isOk()) { return true; } else { LOGGER.error("Escaping message failed, topic: {}, queueId: {}, msgId: {}", messageInner.getTopic(), messageInner.getQueueId(), messageInner.getMsgId()); return false; } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.broker.transaction.OperationResult; import org.apache.rocketmq.broker.transaction.TransactionMetrics; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.BrokerRole; public class TransactionalMessageServiceImpl implements TransactionalMessageService { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private TransactionalMessageBridge transactionalMessageBridge; private static final int PULL_MSG_RETRY_NUMBER = 1; private static final int MAX_PROCESS_TIME_LIMIT = 60000; private static final int MAX_RETRY_TIMES_FOR_ESCAPE = 10; private static final int MAX_RETRY_COUNT_WHEN_HALF_NULL = 1; private static final int OP_MSG_PULL_NUMS = 32; private static final int SLEEP_WHILE_NO_OP = 1000; private final ConcurrentHashMap deleteContext = new ConcurrentHashMap<>(); private ServiceThread transactionalOpBatchService; private ConcurrentHashMap opQueueMap = new ConcurrentHashMap<>(); private TransactionMetrics transactionMetrics; public TransactionalMessageServiceImpl(TransactionalMessageBridge transactionBridge) { this.transactionalMessageBridge = transactionBridge; transactionalOpBatchService = new TransactionalOpBatchService(transactionalMessageBridge.getBrokerController(), this); transactionalOpBatchService.start(); transactionMetrics = new TransactionMetrics(BrokerPathConfigHelper.getTransactionMetricsPath( transactionalMessageBridge.getBrokerController().getMessageStoreConfig().getStorePathRootDir())); transactionMetrics.load(); } @Override public TransactionMetrics getTransactionMetrics() { return transactionMetrics; } @Override public void setTransactionMetrics(TransactionMetrics transactionMetrics) { this.transactionMetrics = transactionMetrics; } @Override public CompletableFuture asyncPrepareMessage(MessageExtBrokerInner messageInner) { return transactionalMessageBridge.asyncPutHalfMessage(messageInner); } @Override public PutMessageResult prepareMessage(MessageExtBrokerInner messageInner) { return transactionalMessageBridge.putHalfMessage(messageInner); } private boolean needDiscard(MessageExt msgExt, int transactionCheckMax) { String checkTimes = msgExt.getProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES); int checkTime = 1; if (null != checkTimes) { checkTime = getInt(checkTimes); if (checkTime >= transactionCheckMax) { return true; } else { checkTime++; } } msgExt.putUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, String.valueOf(checkTime)); return false; } private boolean needSkip(MessageExt msgExt) { long valueOfCurrentMinusBorn = System.currentTimeMillis() - msgExt.getBornTimestamp(); if (valueOfCurrentMinusBorn > transactionalMessageBridge.getBrokerController().getMessageStoreConfig().getFileReservedTime() * 3600L * 1000) { log.info("Half message exceed file reserved time ,so skip it.messageId {},bornTime {}", msgExt.getMsgId(), msgExt.getBornTimestamp()); return true; } return false; } private boolean putBackHalfMsgQueue(MessageExt msgExt, long offset) { PutMessageResult putMessageResult = putBackToHalfQueueReturnResult(msgExt); if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { msgExt.setQueueOffset( putMessageResult.getAppendMessageResult().getLogicsOffset()); msgExt.setCommitLogOffset( putMessageResult.getAppendMessageResult().getWroteOffset()); msgExt.setMsgId(putMessageResult.getAppendMessageResult().getMsgId()); log.debug( "Send check message, the offset={} restored in queueOffset={} " + "commitLogOffset={} " + "newMsgId={} realMsgId={} topic={}", offset, msgExt.getQueueOffset(), msgExt.getCommitLogOffset(), msgExt.getMsgId(), msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX), msgExt.getTopic()); return true; } else { log.error( "PutBackToHalfQueueReturnResult write failed, topic: {}, queueId: {}, " + "msgId: {}", msgExt.getTopic(), msgExt.getQueueId(), msgExt.getMsgId()); return false; } } @Override public void check(long transactionTimeout, int transactionCheckMax, AbstractTransactionalMessageCheckListener listener) { try { String topic = TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC; Set msgQueues = transactionalMessageBridge.fetchMessageQueues(topic); if (msgQueues == null || msgQueues.size() == 0) { log.warn("The queue of topic is empty :" + topic); return; } log.debug("Check topic={}, queues={}", topic, msgQueues); for (MessageQueue messageQueue : msgQueues) { long startTime = System.currentTimeMillis(); MessageQueue opQueue = getOpQueue(messageQueue); long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue); long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue); log.info("Before check, the queue={} msgOffset={} opOffset={}", messageQueue, halfOffset, opOffset); if (halfOffset < 0 || opOffset < 0) { log.error("MessageQueue: {} illegal offset read: {}, op offset: {},skip this queue", messageQueue, halfOffset, opOffset); continue; } List doneOpOffset = new ArrayList<>(); HashMap removeMap = new HashMap<>(); HashMap> opMsgMap = new HashMap>(); PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, opMsgMap, doneOpOffset); if (null == pullResult) { log.error("The queue={} check msgOffset={} with opOffset={} failed, pullResult is null", messageQueue, halfOffset, opOffset); continue; } // single thread int getMessageNullCount = 1; long newOffset = halfOffset; long i = halfOffset; long nextOpOffset = pullResult.getNextBeginOffset(); int putInQueueCount = 0; int escapeFailCnt = 0; while (true) { if (System.currentTimeMillis() - startTime > MAX_PROCESS_TIME_LIMIT) { log.info("Queue={} process time reach max={}", messageQueue, MAX_PROCESS_TIME_LIMIT); break; } Long removedOpOffset; if ((removedOpOffset = removeMap.remove(i)) != null) { log.debug("Half offset {} has been committed/rolled back", i); opMsgMap.get(removedOpOffset).remove(i); if (opMsgMap.get(removedOpOffset).size() == 0) { opMsgMap.remove(removedOpOffset); doneOpOffset.add(removedOpOffset); } } else { GetResult getResult = getHalfMsg(messageQueue, i); MessageExt msgExt = getResult.getMsg(); if (msgExt == null) { if (getMessageNullCount++ > MAX_RETRY_COUNT_WHEN_HALF_NULL) { break; } if (getResult.getPullResult().getPullStatus() == PullStatus.NO_NEW_MSG) { log.debug("No new msg, the miss offset={} in={}, continue check={}, pull result={}", i, messageQueue, getMessageNullCount, getResult.getPullResult()); break; } else { log.info("Illegal offset, the miss offset={} in={}, continue check={}, pull result={}", i, messageQueue, getMessageNullCount, getResult.getPullResult()); i = getResult.getPullResult().getNextBeginOffset(); newOffset = i; continue; } } if (this.transactionalMessageBridge.getBrokerController().getBrokerConfig().isEnableSlaveActingMaster() && this.transactionalMessageBridge.getBrokerController().getMinBrokerIdInGroup() == this.transactionalMessageBridge.getBrokerController().getBrokerIdentity().getBrokerId() && BrokerRole.SLAVE.equals(this.transactionalMessageBridge.getBrokerController().getMessageStoreConfig().getBrokerRole()) ) { final MessageExtBrokerInner msgInner = this.transactionalMessageBridge.renewHalfMessageInner(msgExt); final boolean isSuccess = this.transactionalMessageBridge.escapeMessage(msgInner); if (isSuccess) { escapeFailCnt = 0; newOffset = i + 1; i++; } else { log.warn("Escaping transactional message failed {} times! msgId(offsetId)={}, UNIQ_KEY(transactionId)={}", escapeFailCnt + 1, msgExt.getMsgId(), msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); if (escapeFailCnt < MAX_RETRY_TIMES_FOR_ESCAPE) { escapeFailCnt++; Thread.sleep(100L * (2 ^ escapeFailCnt)); } else { escapeFailCnt = 0; newOffset = i + 1; i++; } } continue; } if (needDiscard(msgExt, transactionCheckMax) || needSkip(msgExt)) { listener.resolveDiscardMsg(msgExt); newOffset = i + 1; i++; continue; } if (msgExt.getStoreTimestamp() >= startTime) { log.debug("Fresh stored. the miss offset={}, check it later, store={}", i, new Date(msgExt.getStoreTimestamp())); break; } long valueOfCurrentMinusBorn = System.currentTimeMillis() - msgExt.getBornTimestamp(); long checkImmunityTime = transactionTimeout; String checkImmunityTimeStr = msgExt.getUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS); if (null != checkImmunityTimeStr) { checkImmunityTime = getImmunityTime(checkImmunityTimeStr, transactionTimeout); if (valueOfCurrentMinusBorn <= checkImmunityTime) { if (checkPrepareQueueOffset(removeMap, doneOpOffset, msgExt, checkImmunityTimeStr)) { newOffset = i + 1; i++; continue; } } } else { if (0 <= valueOfCurrentMinusBorn && valueOfCurrentMinusBorn <= checkImmunityTime) { log.debug("New arrived, the miss offset={}, check it later checkImmunity={}, born={}", i, checkImmunityTime, new Date(msgExt.getBornTimestamp())); break; } } List opMsg = pullResult == null ? null : pullResult.getMsgFoundList(); boolean isNeedCheck = opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime || opMsg != null && opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout || valueOfCurrentMinusBorn <= -1; if (isNeedCheck) { if (!putBackHalfMsgQueue(msgExt, i)) { continue; } putInQueueCount++; log.info("Check transaction. real_topic={},uniqKey={},offset={},commitLogOffset={}", msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC), msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX), msgExt.getQueueOffset(), msgExt.getCommitLogOffset()); listener.resolveHalfMsg(msgExt); } else { nextOpOffset = pullResult != null ? pullResult.getNextBeginOffset() : nextOpOffset; pullResult = fillOpRemoveMap(removeMap, opQueue, nextOpOffset, halfOffset, opMsgMap, doneOpOffset); if (pullResult == null || pullResult.getPullStatus() == PullStatus.NO_NEW_MSG || pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL || pullResult.getPullStatus() == PullStatus.NO_MATCHED_MSG) { try { Thread.sleep(SLEEP_WHILE_NO_OP); } catch (Throwable ignored) { } } else { log.info("The miss message offset:{}, pullOffsetOfOp:{}, miniOffset:{} get more opMsg.", i, nextOpOffset, halfOffset); } continue; } } newOffset = i + 1; i++; } if (newOffset != halfOffset) { transactionalMessageBridge.updateConsumeOffset(messageQueue, newOffset); } long newOpOffset = calculateOpOffset(doneOpOffset, opOffset); if (newOpOffset != opOffset) { transactionalMessageBridge.updateConsumeOffset(opQueue, newOpOffset); } GetResult getResult = getHalfMsg(messageQueue, newOffset); pullResult = pullOpMsg(opQueue, newOpOffset, 1); long maxMsgOffset = getResult.getPullResult() == null ? newOffset : getResult.getPullResult().getMaxOffset(); long maxOpOffset = pullResult == null ? newOpOffset : pullResult.getMaxOffset(); long msgTime = getResult.getMsg() == null ? System.currentTimeMillis() : getResult.getMsg().getStoreTimestamp(); log.info("After check, {} opOffset={} opOffsetDiff={} msgOffset={} msgOffsetDiff={} msgTime={} msgTimeDelayInMs={} putInQueueCount={}", messageQueue, newOpOffset, maxOpOffset - newOpOffset, newOffset, maxMsgOffset - newOffset, new Date(msgTime), System.currentTimeMillis() - msgTime, putInQueueCount); } } catch (Throwable e) { log.error("Check error", e); } } private long getImmunityTime(String checkImmunityTimeStr, long transactionTimeout) { long checkImmunityTime; checkImmunityTime = getLong(checkImmunityTimeStr); if (-1 == checkImmunityTime) { checkImmunityTime = transactionTimeout; } else { checkImmunityTime *= 1000; } return checkImmunityTime; } /** * Read op message, parse op message, and fill removeMap * * @param removeMap Half message to be remove, key:halfOffset, value: opOffset. * @param opQueue Op message queue. * @param pullOffsetOfOp The begin offset of op message queue. * @param miniOffset The current minimum offset of half message queue. * @param opMsgMap Half message offset in op message * @param doneOpOffset Stored op messages that have been processed. * @return Op message result. */ private PullResult fillOpRemoveMap(HashMap removeMap, MessageQueue opQueue, long pullOffsetOfOp, long miniOffset, Map> opMsgMap, List doneOpOffset) { PullResult pullResult = pullOpMsg(opQueue, pullOffsetOfOp, OP_MSG_PULL_NUMS); if (null == pullResult) { return null; } if (pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL || pullResult.getPullStatus() == PullStatus.NO_MATCHED_MSG) { log.warn("The miss op offset={} in queue={} is illegal, pullResult={}", pullOffsetOfOp, opQueue, pullResult); transactionalMessageBridge.updateConsumeOffset(opQueue, pullResult.getNextBeginOffset()); return pullResult; } else if (pullResult.getPullStatus() == PullStatus.NO_NEW_MSG) { log.warn("The miss op offset={} in queue={} is NO_NEW_MSG, pullResult={}", pullOffsetOfOp, opQueue, pullResult); return pullResult; } List opMsg = pullResult.getMsgFoundList(); if (opMsg == null) { log.warn("The miss op offset={} in queue={} is empty, pullResult={}", pullOffsetOfOp, opQueue, pullResult); return pullResult; } for (MessageExt opMessageExt : opMsg) { if (opMessageExt.getBody() == null) { log.error("op message body is null. queueId={}, offset={}", opMessageExt.getQueueId(), opMessageExt.getQueueOffset()); doneOpOffset.add(opMessageExt.getQueueOffset()); continue; } HashSet set = new HashSet(); String queueOffsetBody = new String(opMessageExt.getBody(), TransactionalMessageUtil.CHARSET); log.debug("Topic: {} tags: {}, OpOffset: {}, HalfOffset: {}", opMessageExt.getTopic(), opMessageExt.getTags(), opMessageExt.getQueueOffset(), queueOffsetBody); if (TransactionalMessageUtil.REMOVE_TAG.equals(opMessageExt.getTags())) { String[] offsetArray = queueOffsetBody.split(TransactionalMessageUtil.OFFSET_SEPARATOR); for (String offset : offsetArray) { Long offsetValue = getLong(offset); if (offsetValue < miniOffset) { continue; } removeMap.put(offsetValue, opMessageExt.getQueueOffset()); set.add(offsetValue); } } else { log.error("Found a illegal tag in opMessageExt= {} ", opMessageExt); } if (set.size() > 0) { opMsgMap.put(opMessageExt.getQueueOffset(), set); } else { doneOpOffset.add(opMessageExt.getQueueOffset()); } } log.debug("Remove map: {}", removeMap); log.debug("Done op list: {}", doneOpOffset); log.debug("opMsg map: {}", opMsgMap); return pullResult; } /** * If return true, skip this msg * * @param removeMap Op message map to determine whether a half message was responded by producer. * @param doneOpOffset Op Message which has been checked. * @param msgExt Half message * @return Return true if put success, otherwise return false. */ private boolean checkPrepareQueueOffset(HashMap removeMap, List doneOpOffset, MessageExt msgExt, String checkImmunityTimeStr) { String prepareQueueOffsetStr = msgExt.getUserProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); if (null == prepareQueueOffsetStr) { return putImmunityMsgBackToHalfQueue(msgExt); } else { long prepareQueueOffset = getLong(prepareQueueOffsetStr); if (-1 == prepareQueueOffset) { return false; } else { Long tmpOpOffset; if ((tmpOpOffset = removeMap.remove(prepareQueueOffset)) != null) { doneOpOffset.add(tmpOpOffset); log.info("removeMap contain prepareQueueOffset. real_topic={},uniqKey={},immunityTime={},offset={}", msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC), msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX), checkImmunityTimeStr, msgExt.getQueueOffset()); return true; } else { return putImmunityMsgBackToHalfQueue(msgExt); } } } } /** * Write messageExt to Half topic again * * @param messageExt Message will be write back to queue * @return Put result can used to determine the specific results of storage. */ private PutMessageResult putBackToHalfQueueReturnResult(MessageExt messageExt) { PutMessageResult putMessageResult = null; try { MessageExtBrokerInner msgInner = transactionalMessageBridge.renewHalfMessageInner(messageExt); putMessageResult = transactionalMessageBridge.putMessageReturnResult(msgInner); } catch (Exception e) { log.warn("PutBackToHalfQueueReturnResult error", e); } return putMessageResult; } private boolean putImmunityMsgBackToHalfQueue(MessageExt messageExt) { MessageExtBrokerInner msgInner = transactionalMessageBridge.renewImmunityHalfMessageInner(messageExt); return transactionalMessageBridge.putMessage(msgInner); } /** * Read half message from Half Topic * * @param mq Target message queue, in this method, it means the half message queue. * @param offset Offset in the message queue. * @param nums Pull message number. * @return Messages pulled from half message queue. */ private PullResult pullHalfMsg(MessageQueue mq, long offset, int nums) { return transactionalMessageBridge.getHalfMessage(mq.getQueueId(), offset, nums); } /** * Read op message from Op Topic * * @param mq Target Message Queue * @param offset Offset in the message queue * @param nums Pull message number * @return Messages pulled from operate message queue. */ private PullResult pullOpMsg(MessageQueue mq, long offset, int nums) { return transactionalMessageBridge.getOpMessage(mq.getQueueId(), offset, nums); } private Long getLong(String s) { long v = -1; try { v = Long.parseLong(s); } catch (Exception e) { log.error("GetLong error", e); } return v; } private Integer getInt(String s) { int v = -1; try { v = Integer.parseInt(s); } catch (Exception e) { log.error("GetInt error", e); } return v; } private long calculateOpOffset(List doneOffset, long oldOffset) { Collections.sort(doneOffset); long newOffset = oldOffset; for (int i = 0; i < doneOffset.size(); i++) { if (doneOffset.get(i) == newOffset) { newOffset++; } else { break; } } return newOffset; } private MessageQueue getOpQueue(MessageQueue messageQueue) { MessageQueue opQueue = opQueueMap.get(messageQueue); if (opQueue == null) { opQueue = new MessageQueue(TransactionalMessageUtil.buildOpTopic(), messageQueue.getBrokerName(), messageQueue.getQueueId()); opQueueMap.put(messageQueue, opQueue); } return opQueue; } private GetResult getHalfMsg(MessageQueue messageQueue, long offset) { GetResult getResult = new GetResult(); PullResult result = pullHalfMsg(messageQueue, offset, PULL_MSG_RETRY_NUMBER); if (result != null) { getResult.setPullResult(result); List messageExts = result.getMsgFoundList(); if (messageExts == null || messageExts.size() == 0) { return getResult; } getResult.setMsg(messageExts.get(0)); } return getResult; } private OperationResult getHalfMessageByOffset(long commitLogOffset) { OperationResult response = new OperationResult(); MessageExt messageExt = this.transactionalMessageBridge.lookMessageByOffset(commitLogOffset); if (messageExt != null) { response.setPrepareMessage(messageExt); response.setResponseCode(ResponseCode.SUCCESS); } else { response.setResponseCode(ResponseCode.SYSTEM_ERROR); response.setResponseRemark("Find prepared transaction message failed"); } return response; } @Override public boolean deletePrepareMessage(MessageExt messageExt) { Integer queueId = messageExt.getQueueId(); MessageQueueOpContext mqContext = deleteContext.get(queueId); if (mqContext == null) { mqContext = new MessageQueueOpContext(System.currentTimeMillis(), 20000); MessageQueueOpContext old = deleteContext.putIfAbsent(queueId, mqContext); if (old != null) { mqContext = old; } } String data = messageExt.getQueueOffset() + TransactionalMessageUtil.OFFSET_SEPARATOR; try { boolean res = mqContext.getContextQueue().offer(data, 100, TimeUnit.MILLISECONDS); if (res) { int totalSize = mqContext.getTotalSize().addAndGet(data.length()); if (totalSize > transactionalMessageBridge.getBrokerController().getBrokerConfig().getTransactionOpMsgMaxSize()) { this.transactionalOpBatchService.wakeup(); } return true; } else { this.transactionalOpBatchService.wakeup(); } } catch (InterruptedException ignore) { } Message msg = getOpMessage(queueId, data); if (this.transactionalMessageBridge.writeOp(queueId, msg)) { log.warn("Force add remove op data. queueId={}", queueId); return true; } else { log.error("Transaction op message write failed. messageId is {}, queueId is {}", messageExt.getMsgId(), messageExt.getQueueId()); return false; } } @Override public OperationResult commitMessage(EndTransactionRequestHeader requestHeader) { return getHalfMessageByOffset(requestHeader.getCommitLogOffset()); } @Override public OperationResult rollbackMessage(EndTransactionRequestHeader requestHeader) { return getHalfMessageByOffset(requestHeader.getCommitLogOffset()); } @Override public boolean open() { return true; } @Override public void close() { if (this.transactionalOpBatchService != null) { this.transactionalOpBatchService.shutdown(); } this.getTransactionMetrics().persist(); } public Message getOpMessage(int queueId, String moreData) { String opTopic = TransactionalMessageUtil.buildOpTopic(); MessageQueueOpContext mqContext = deleteContext.get(queueId); int moreDataLength = moreData != null ? moreData.length() : 0; int length = moreDataLength; int maxSize = transactionalMessageBridge.getBrokerController().getBrokerConfig().getTransactionOpMsgMaxSize(); if (length < maxSize) { int sz = mqContext.getTotalSize().get(); if (sz > maxSize || length + sz > maxSize) { length = maxSize + 100; } else { length += sz; } } StringBuilder sb = new StringBuilder(length); if (moreData != null) { sb.append(moreData); } while (!mqContext.getContextQueue().isEmpty()) { if (sb.length() >= maxSize) { break; } String data = mqContext.getContextQueue().poll(); if (data != null) { sb.append(data); } } if (sb.length() == 0) { return null; } int l = sb.length() - moreDataLength; mqContext.getTotalSize().addAndGet(-l); mqContext.setLastWriteTimestamp(System.currentTimeMillis()); return new Message(opTopic, TransactionalMessageUtil.REMOVE_TAG, sb.toString().getBytes(TransactionalMessageUtil.CHARSET)); } public long batchSendOpMessage() { long startTime = System.currentTimeMillis(); try { long firstTimestamp = startTime; Map sendMap = null; long interval = transactionalMessageBridge.getBrokerController().getBrokerConfig().getTransactionOpBatchInterval(); int maxSize = transactionalMessageBridge.getBrokerController().getBrokerConfig().getTransactionOpMsgMaxSize(); boolean overSize = false; for (Map.Entry entry : deleteContext.entrySet()) { MessageQueueOpContext mqContext = entry.getValue(); //no msg in contextQueue if (mqContext.getTotalSize().get() <= 0 || mqContext.getContextQueue().size() == 0 || // wait for the interval mqContext.getTotalSize().get() < maxSize && startTime - mqContext.getLastWriteTimestamp() < interval) { continue; } if (sendMap == null) { sendMap = new HashMap<>(); } Message opMsg = getOpMessage(entry.getKey(), null); if (opMsg == null) { continue; } sendMap.put(entry.getKey(), opMsg); firstTimestamp = Math.min(firstTimestamp, mqContext.getLastWriteTimestamp()); if (mqContext.getTotalSize().get() >= maxSize) { overSize = true; } } if (sendMap != null) { for (Map.Entry entry : sendMap.entrySet()) { if (!this.transactionalMessageBridge.writeOp(entry.getKey(), entry.getValue())) { log.error("Transaction batch op message write failed. body is {}, queueId is {}", new String(entry.getValue().getBody(), TransactionalMessageUtil.CHARSET), entry.getKey()); } } } log.debug("Send op message queueIds={}", sendMap == null ? null : sendMap.keySet()); //wait for next batch remove long wakeupTimestamp = firstTimestamp + interval; if (!overSize && wakeupTimestamp > startTime) { return wakeupTimestamp; } } catch (Throwable t) { log.error("batchSendOp error.", t); } return 0L; } public Map getDeleteContext() { return this.deleteContext; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; public class TransactionalMessageUtil { public static final String REMOVE_TAG = "d"; public static final Charset CHARSET = StandardCharsets.UTF_8; public static final String OFFSET_SEPARATOR = ","; public static final String TRANSACTION_ID = "__transactionId__"; public static String buildOpTopic() { return TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC; } public static String buildOpTopicForRocksDB() { return TopicValidator.RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC; } public static String buildHalfTopic() { return TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC; } public static String buildHalfTopicForRocksDB() { return TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC; } public static String buildConsumerGroup() { return MixAll.CID_SYS_RMQ_TRANS; } public static MessageExtBrokerInner buildTransactionalMessageFromHalfMessage(MessageExt msgExt) { final MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setWaitStoreMsgOK(false); msgInner.setMsgId(msgExt.getMsgId()); msgInner.setTopic(msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgInner.setBody(msgExt.getBody()); final String realQueueIdStr = msgExt.getProperty(MessageConst.PROPERTY_REAL_QUEUE_ID); if (StringUtils.isNumeric(realQueueIdStr)) { msgInner.setQueueId(Integer.parseInt(realQueueIdStr)); } msgInner.setFlag(msgExt.getFlag()); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgInner.getTags())); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setTransactionId(msgExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); MessageAccessor.setProperties(msgInner, msgExt.getProperties()); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); int sysFlag = msgExt.getSysFlag(); sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE; msgInner.setSysFlag(sysFlag); return msgInner; } public static long getImmunityTime(String checkImmunityTimeStr, long transactionTimeout) { long checkImmunityTime = 0; try { checkImmunityTime = Long.parseLong(checkImmunityTimeStr) * 1000; } catch (Throwable ignored) { } //If a custom first check time is set, the minimum check time; //The default check protection period is transactionTimeout if (checkImmunityTime < transactionTimeout) { checkImmunityTime = transactionTimeout; } return checkImmunityTime; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalOpBatchService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class TransactionalOpBatchService extends ServiceThread { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private BrokerController brokerController; private TransactionalMessageServiceImpl transactionalMessageService; private long wakeupTimestamp = 0; public TransactionalOpBatchService(BrokerController brokerController, TransactionalMessageServiceImpl transactionalMessageService) { this.brokerController = brokerController; this.transactionalMessageService = transactionalMessageService; } @Override public String getServiceName() { return TransactionalOpBatchService.class.getSimpleName(); } @Override public void run() { LOGGER.info("Start transaction op batch thread!"); long checkInterval = brokerController.getBrokerConfig().getTransactionOpBatchInterval(); wakeupTimestamp = System.currentTimeMillis() + checkInterval; while (!this.isStopped()) { long interval = wakeupTimestamp - System.currentTimeMillis(); if (interval <= 0) { interval = 0; wakeup(); } this.waitForRunning(interval); } LOGGER.info("End transaction op batch thread!"); } @Override protected void onWaitEnd() { wakeupTimestamp = transactionalMessageService.batchSendOpMessage(); } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.rocksdb; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.TimeUnit; import io.netty.channel.Channel; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.transaction.TransRocksDBRecord; import org.apache.rocketmq.store.transaction.TransMessageRocksDBStore; import static org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage.TRANS_COLUMN_FAMILY; public class TransactionalMessageRocksDBService { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private static final int MAX_BATCH_SIZE_FROM_ROCKSDB = 2000; private static final int INITIAL = 0, RUNNING = 1, SHUTDOWN = 2; private volatile int state = INITIAL; private final MessageRocksDBStorage messageRocksDBStorage; private final TransMessageRocksDBStore transMessageRocksDBStore; private final MessageStore messageStore; private final BrokerController brokerController; private TransStatusCheckService transStatusService; private ExecutorService checkTranStatusTaskExecutor; public TransactionalMessageRocksDBService(final MessageStore messageStore, final BrokerController brokerController) { this.messageStore = messageStore; this.transMessageRocksDBStore = messageStore.getTransMessageRocksDBStore(); this.messageRocksDBStorage = transMessageRocksDBStore.getMessageRocksDBStorage(); this.brokerController = brokerController; } public void start() { if (this.state == RUNNING) { return; } initService(); this.transStatusService.start(); this.state = RUNNING; log.info("TransactionalMessageRocksDBService start success"); } private void initService() { this.transStatusService = new TransStatusCheckService(); this.checkTranStatusTaskExecutor = ThreadUtils.newThreadPoolExecutor( brokerController.getBrokerConfig().getTransactionCheckRocksdbCoreThreads(), brokerController.getBrokerConfig().getTransactionCheckRocksdbMaxThreads(), 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(brokerController.getBrokerConfig().getTransactionCheckRocksdbQueueCapacity()), new ThreadFactoryImpl("Transaction-rocksdb-msg-check-thread", brokerController.getBrokerIdentity()), new CallerRunsPolicy()); } public void shutdown() { if (this.state != RUNNING || this.state == SHUTDOWN) { return; } if (null != this.transStatusService) { this.transStatusService.shutdown(); } if (null != this.checkTranStatusTaskExecutor) { this.checkTranStatusTaskExecutor.shutdown(); } this.state = SHUTDOWN; log.info("TransactionalMessageRocksDBService shutdown success"); } private void checkTransStatus() { long count = 0; byte[] lastKey = null; while (true) { try { List trs = messageRocksDBStorage.scanRecordsForTrans(TRANS_COLUMN_FAMILY, MAX_BATCH_SIZE_FROM_ROCKSDB, lastKey); if (CollectionUtils.isEmpty(trs)) { log.info("TransactionalMessageRocksDBService checkTransStatus trs is empty"); break; } count += trs.size(); checkTransRecordsStatus(trs); lastKey = trs.size() >= MAX_BATCH_SIZE_FROM_ROCKSDB ? trs.get(trs.size() - 1).getKeyBytes() : null; if (null == lastKey) { break; } } catch (Exception e) { log.error("TransactionalMessageRocksDBService checkTransStatus error, error: {}, count: {}", e.getMessage(), count); break; } } log.info("TransactionalMessageRocksDBService checkTransStatus count: {}", count); } private void checkTransRecordsStatus(List trs) { if (CollectionUtils.isEmpty(trs)) { log.error("TransactionalMessageRocksDBService checkTransRecordsStatus, trs is empty"); return; } try { List updateList = new ArrayList<>(); for (TransRocksDBRecord halfRecord : trs) { if (null == halfRecord) { log.error("TransactionalMessageRocksDBService checkTransRecordsStatus, halfRecord is null"); continue; } try { if (halfRecord.getCheckTimes() > brokerController.getBrokerConfig().getTransactionCheckMax()) { halfRecord.setDelete(true); updateList.add(halfRecord); log.info("TransactionalMessageRocksDBService checkTransRecordsStatus checkTimes > {}, need delete, checkTimes: {}, msgId: {}", brokerController.getBrokerConfig().getTransactionCheckMax(), halfRecord.getCheckTimes(), halfRecord.getUniqKey()); continue; } MessageExt msgExt = transMessageRocksDBStore.getMessage(halfRecord.getOffsetPy(), halfRecord.getSizePy()); if (null == msgExt) { log.error("TransactionalMessageRocksDBService checkTransRecordsStatus, msgExt is null, offsetPy: {}, sizePy: {}", halfRecord.getOffsetPy(), halfRecord.getSizePy()); halfRecord.setDelete(true); updateList.add(halfRecord); continue; } if (!isImmunityTimeExpired(msgExt)) { continue; } resolveHalfMsg(msgExt); halfRecord.setCheckTimes(halfRecord.getCheckTimes() + 1); if (halfRecord.getCheckTimes() > brokerController.getBrokerConfig().getTransactionCheckMax()) { halfRecord.setDelete(true); log.info("TransactionalMessageRocksDBService checkTransRecordsStatus checkTimes > {}, need delete, checkTimes: {}, msgId: {}", brokerController.getBrokerConfig().getTransactionCheckMax(), halfRecord.getCheckTimes(), halfRecord.getUniqKey()); } updateList.add(halfRecord); } catch (Exception e) { log.error("TransactionalMessageRocksDBService checkTransRecordsStatus error : {}", e.getMessage()); } } if (!CollectionUtils.isEmpty(updateList)) { messageRocksDBStorage.updateRecordsForTrans(TRANS_COLUMN_FAMILY, updateList); } } catch (Exception e) { log.error("TransactionalMessageRocksDBService checkTransRecordsStatus error: {}", e.getMessage()); } } private boolean isImmunityTimeExpired(MessageExt msgExt) { String immunityTimeStr = msgExt.getUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS); long immunityTime = brokerController.getBrokerConfig().getTransactionTimeOut(); if (!StringUtils.isEmpty(immunityTimeStr)) { try { immunityTime = Long.parseLong(immunityTimeStr); immunityTime *= 1000; } catch (Exception e) { log.error("parse immunityTimesStr error: {}, msgId: {}", e.getMessage(), msgExt.getMsgId()); } } if ((System.currentTimeMillis() - msgExt.getBornTimestamp()) < immunityTime) { return false; } return true; } private String getServiceThreadName() { String brokerIdentifier = ""; if (TransactionalMessageRocksDBService.this.messageStore instanceof DefaultMessageStore) { DefaultMessageStore messageStore = (DefaultMessageStore) TransactionalMessageRocksDBService.this.messageStore; if (messageStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = messageStore.getBrokerConfig().getIdentifier(); } } return brokerIdentifier; } private void resolveHalfMsg(final MessageExt msgExt) { if (checkTranStatusTaskExecutor != null) { checkTranStatusTaskExecutor.execute(new Runnable() { @Override public void run() { try { sendCheckMessage(msgExt); } catch (Exception e) { log.error("TransactionalMessageRocksDBService Send check message error: {}, msgId: {}", e.getMessage(), msgExt.getMsgId()); } } }); } else { log.error("TransactionalMessageRocksDBService checkTranStatusTaskExecutor not init, msgId: {}", msgExt.getMsgId()); } } private void sendCheckMessage(MessageExt msgExt) { if (null == msgExt) { log.info("TransactionalMessageRocksDBService sendCheckMessage msgExt is null"); return; } try { CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader(); checkTransactionStateRequestHeader.setTopic(msgExt.getTopic()); checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset()); checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId()); checkTransactionStateRequestHeader.setMsgId(MessageClientIDSetter.getUniqID(msgExt)); checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId()); checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset()); checkTransactionStateRequestHeader.setBrokerName(brokerController.getBrokerConfig().getBrokerName()); msgExt.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgExt.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID))); msgExt.setStoreSize(0); String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); Channel channel = brokerController.getProducerManager().getAvailableChannel(groupId); if (channel != null) { brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt); } else { log.warn("TransactionalMessageRocksDBService checkProducerTransactionState failed, channel is null. groupId: {}, msgId: {}", groupId, msgExt.getMsgId()); } } catch (Exception e) { log.error("TransactionalMessageRocksDBService sendCheckMessage error: {}, msgId: {}", e.getMessage(), msgExt.getMsgId()); } } private class TransStatusCheckService extends ServiceThread { private final Logger log = TransactionalMessageRocksDBService.log; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { long begin = System.currentTimeMillis(); checkTransStatus(); log.info("TransactionalMessageRocksDBService ScanTransAndStatusCheckService check trans status, check cost: {}", System.currentTimeMillis() - begin); waitForRunning(brokerController.getBrokerConfig().getTransactionCheckInterval()); } catch (Exception e) { log.error("TransactionalMessageRocksDBService ScanTransAndStatusCheckService error: {}", e.getMessage()); } } log.info(this.getServiceName() + " service end"); } } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.util; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.schedule.ScheduleMessageService; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.timer.TimerMessageStore; public class HookUtils { protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final AtomicLong PRINT_TIMES = new AtomicLong(0); /** * On Linux: The maximum length for a file name is 255 bytes. * The maximum combined length of both the file name and path name is 4096 bytes. * This length matches the PATH_MAX that is supported by the operating system. * The Unicode representation of a character can occupy several bytes, * so the maximum number of characters that comprises a path and file name can vary. * The actual limitation is the number of bytes in the path and file components, * which might correspond to an equal number of characters. */ private static final Integer MAX_TOPIC_LENGTH = 255; public static PutMessageResult checkBeforePutMessage(BrokerController brokerController, final MessageExt msg) { if (brokerController.getMessageStore().isShutdown()) { LOG.warn("message store has shutdown, so putMessage is forbidden"); return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null); } if (!brokerController.getMessageStoreConfig().isDuplicationEnable() && BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) { long value = PRINT_TIMES.getAndIncrement(); if ((value % 50000) == 0) { LOG.warn("message store is in slave mode, so putMessage is forbidden "); } return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null); } if (!brokerController.getMessageStore().getRunningFlags().isWriteable()) { long value = PRINT_TIMES.getAndIncrement(); if ((value % 50000) == 0) { LOG.warn("message store is not writeable, so putMessage is forbidden " + brokerController.getMessageStore().getRunningFlags().getFlagBits()); } return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null); } else { PRINT_TIMES.set(0); } final byte[] topicData = msg.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); boolean retryTopic = msg.getTopic() != null && msg.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX); if (!retryTopic && topicData.length > Byte.MAX_VALUE) { LOG.warn("putMessage message topic[{}] length too long {}, but it is not supported by broker", msg.getTopic(), topicData.length); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } if (topicData.length > MAX_TOPIC_LENGTH) { LOG.warn("putMessage message topic[{}] length too long {}, but it is not supported by broker", msg.getTopic(), topicData.length); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } if (msg.getBody() == null) { LOG.warn("putMessage message topic[{}], but message body is null", msg.getTopic()); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } if (brokerController.getMessageStore().isOSPageCacheBusy()) { return new PutMessageResult(PutMessageStatus.OS_PAGE_CACHE_BUSY, null); } return null; } public static PutMessageResult checkInnerBatch(BrokerController brokerController, final MessageExt msg) { if (msg.getProperties().containsKey(MessageConst.PROPERTY_INNER_NUM) && !MessageSysFlag.check(msg.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG)) { LOG.warn("[BUG]The message had property {} but is not an inner batch", MessageConst.PROPERTY_INNER_NUM); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } if (MessageSysFlag.check(msg.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG)) { Optional topicConfig = Optional.ofNullable(brokerController.getTopicConfigManager().getTopicConfigTable().get(msg.getTopic())); if (!QueueTypeUtils.isBatchCq(topicConfig)) { LOG.error("[BUG]The message is an inner batch but cq type is not batch cq"); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } } return null; } public static PutMessageResult handleScheduleMessage(BrokerController brokerController, final MessageExtBrokerInner msg) { final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) { if (!isRolledTimerMessage(msg)) { if (checkIfTimerMessage(msg)) { if (!brokerController.getMessageStoreConfig().isTimerWheelEnable()) { //wheel timer is not enabled, reject the message return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_NOT_ENABLE, null); } PutMessageResult transformRes = transformTimerMessage(brokerController, msg); if (null != transformRes) { return transformRes; } } } // Delay Delivery if (msg.getDelayTimeLevel() > 0) { transformDelayLevelMessage(brokerController, msg); } } return null; } public static PutMessageResult handleLmqQuota(BrokerController brokerController, final MessageExtBrokerInner msg) { if (!brokerController.getMessageStoreConfig().isEnableLmqQuota() || !brokerController.getMessageStoreConfig().isEnableLmq() || !brokerController.getMessageStoreConfig().isEnableMultiDispatch() || !msg.needDispatchLMQ()) { return null; } ConsumeQueueStoreInterface cqStore = brokerController.getMessageStore().getQueueStore(); String[] queueNames = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH).split(MixAll.LMQ_DISPATCH_SEPARATOR); for (String queueName : queueNames) { if (!MixAll.isLmq(queueName)) { continue; } if (cqStore.getLmqNum() >= brokerController.getMessageStoreConfig().getMaxLmqConsumeQueueNum()) { if (!cqStore.isLmqExist(queueName)) { return new PutMessageResult(PutMessageStatus.LMQ_CONSUME_QUEUE_NUM_EXCEEDED, null); } } } return null; } private static boolean isRolledTimerMessage(MessageExtBrokerInner msg) { return TimerMessageStore.TIMER_TOPIC.equals(msg.getTopic()); } public static boolean checkIfTimerMessage(MessageExtBrokerInner msg) { if (msg.getDelayTimeLevel() > 0) { if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS)) { MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_TIMER_DELIVER_MS); } if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)) { MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_TIMER_DELAY_SEC); } if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS)) { MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_TIMER_DELAY_MS); } return false; //return this.defaultMessageStore.getMessageStoreConfig().isTimerInterceptDelayLevel(); } //double check if (TimerMessageStore.TIMER_TOPIC.equals(msg.getTopic()) || null != msg.getProperty(MessageConst.PROPERTY_TIMER_OUT_MS)) { return false; } return null != msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) || null != msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) || null != msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC); } private static PutMessageResult transformTimerMessage(BrokerController brokerController, MessageExtBrokerInner msg) { //do transform int delayLevel = msg.getDelayTimeLevel(); long deliverMs; try { if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) { deliverMs = System.currentTimeMillis() + Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)) * 1000; } else if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null) { deliverMs = System.currentTimeMillis() + Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS)); } else { deliverMs = Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS)); } } catch (Exception e) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null); } if (deliverMs > System.currentTimeMillis()) { if (delayLevel <= 0 && deliverMs - System.currentTimeMillis() > brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null); } int timerPrecisionMs = brokerController.getMessageStoreConfig().getTimerPrecisionMs(); if (deliverMs % timerPrecisionMs == 0) { deliverMs -= timerPrecisionMs; } else { deliverMs = deliverMs / timerPrecisionMs * timerPrecisionMs; } if (brokerController.getTimerMessageStore().isReject(deliverMs)) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_FLOW_CONTROL, null); } MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TIMER_OUT_MS, deliverMs + ""); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic()); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId())); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); msg.setTopic(TimerMessageStore.TIMER_TOPIC); msg.setQueueId(0); } else if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null); } return null; } public static void transformDelayLevelMessage(BrokerController brokerController, MessageExtBrokerInner msg) { if (msg.getDelayTimeLevel() > brokerController.getScheduleMessageService().getMaxDelayLevel()) { msg.setDelayTimeLevel(brokerController.getScheduleMessageService().getMaxDelayLevel()); } // Backup real topic, queueId MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic()); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId())); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); msg.setTopic(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC); msg.setQueueId(ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel())); } public static boolean sendMessageBack(BrokerController brokerController, List msgList, String brokerName, String brokerAddr) { try { Iterator it = msgList.iterator(); while (it.hasNext()) { MessageExt msg = it.next(); msg.setWaitStoreMsgOK(false); brokerController.getBrokerOuterAPI().sendMessageToSpecificBroker(brokerAddr, brokerName, msg, "InnerSendMessageBackGroup", 3000); it.remove(); } } catch (Exception e) { LOG.error("send message back to broker {} addr {} failed", brokerName, brokerAddr, e); return false; } return true; } } ================================================ FILE: broker/src/main/java/org/apache/rocketmq/broker/util/PositiveAtomicCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.util; import java.util.concurrent.atomic.AtomicInteger; public class PositiveAtomicCounter { private static final int MASK = 0x7FFFFFFF; private final AtomicInteger atom; public PositiveAtomicCounter() { atom = new AtomicInteger(0); } public final int incrementAndGet() { final int rt = atom.incrementAndGet(); return rt & MASK; } public int intValue() { return atom.intValue(); } } ================================================ FILE: broker/src/main/resources/rmq.broker.logback.xml ================================================ brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}broker_default.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}broker_default.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}broker.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}broker.%i.log.gz 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}protection.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}protection.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}watermark.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}watermark.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}rocksdb.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}rocksdb.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}store.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}store.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}tiered_store.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}tiered_store.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}broker_traffic.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}broker_traffic.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}remoting.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}remoting.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}storeerror.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}storeerror.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}transaction.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}transaction.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}lock.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}lock.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}filter.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}filter.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}stats.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}stats.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}commercial.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}commercial.%i.log.gz 1 10 500MB brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}pop.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}pop.%i.log 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}lite.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}lite.%i.log 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}/logs/rocketmqlogs/coldctr.log true ${user.home}/logs/rocketmqlogs/otherdays/coldctr.%i.log 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}broker_metrics.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}broker_metrics.%i.log.gz 1 3 512MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 brokerContainerLogDir ${file.separator} ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}auth_audit.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}auth_audit.%i.log.gz 1 3 512MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ================================================ FILE: broker/src/main/resources/transaction.sql ================================================ -- -- Licensed to the Apache Software Foundation (ASF) under one -- or more contributor license agreements. See the NOTICE file -- distributed with this work for additional information -- regarding copyright ownership. The ASF licenses this file -- to you under the Apache License, Version 2.0 (the -- "License"); you may not use this file except in compliance -- with the License. You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, -- software distributed under the License is distributed on an -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -- KIND, either express or implied. See the License for the -- specific language governing permissions and limitations -- under the License. -- CREATE TABLE t_transaction( offset NUMERIC(20) PRIMARY KEY, producerGroup VARCHAR(64) ) ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.io.File; import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.future.FutureTaskExt; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.auth.config.AuthConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class BrokerControllerTest { private MessageStoreConfig messageStoreConfig; private BrokerConfig brokerConfig; private NettyServerConfig nettyServerConfig; @Before public void setUp() { messageStoreConfig = new MessageStoreConfig(); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + UUID.randomUUID().toString(); messageStoreConfig.setStorePathRootDir(storePathRootDir); brokerConfig = new BrokerConfig(); nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(0); } @Test public void testBrokerRestart() throws Exception { BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); assertThat(brokerController.initialize()).isTrue(); brokerController.start(); brokerController.shutdown(); } @Test public void testBrokerMetricsManagerInitialization() throws Exception { BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); assertThat(brokerController.initialize()).isTrue(); // Verify that brokerMetricsManager is properly initialized and not null assertThat(brokerController.getBrokerMetricsManager()).isNotNull(); brokerController.shutdown(); } @After public void destroy() { UtilAll.deleteFile(new File(messageStoreConfig.getStorePathRootDir())); } @Test public void testHeadSlowTimeMills() throws Exception { BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); brokerController.initialize(); BlockingQueue queue = new LinkedBlockingQueue<>(); //create task is not instance of FutureTaskExt; Runnable runnable = new Runnable() { @Override public void run() { } }; RequestTask requestTask = new RequestTask(runnable, null, null); // the requestTask is not the head of queue; queue.add(new FutureTaskExt<>(requestTask, null)); long headSlowTimeMills = 100; TimeUnit.MILLISECONDS.sleep(headSlowTimeMills); assertThat(brokerController.headSlowTimeMills(queue)).isGreaterThanOrEqualTo(headSlowTimeMills); } @Test public void testCustomRemotingServer() throws CloneNotSupportedException { final RemotingServer mockRemotingServer = new NettyRemotingServer(nettyServerConfig); final String mockRemotingServerName = "MOCK_REMOTING_SERVER"; BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); brokerController.setRemotingServerByName(mockRemotingServerName, mockRemotingServer); brokerController.initializeRemotingServer(); final RPCHook rpcHook = new RPCHook() { @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } }; brokerController.registerServerRPCHook(rpcHook); // setRequestPipelineTest final RequestPipeline requestPipeline = (ctx, request) -> { }; brokerController.setRequestPipeline(requestPipeline); NettyRemotingAbstract tcpRemotingServer = (NettyRemotingAbstract) brokerController.getRemotingServer(); Assert.assertTrue(tcpRemotingServer.getRPCHook().contains(rpcHook)); NettyRemotingAbstract fastRemotingServer = (NettyRemotingAbstract) brokerController.getFastRemotingServer(); Assert.assertTrue(fastRemotingServer.getRPCHook().contains(rpcHook)); NettyRemotingAbstract mockRemotingServer1 = (NettyRemotingAbstract) brokerController.getRemotingServerByName(mockRemotingServerName); Assert.assertTrue(mockRemotingServer1.getRPCHook().contains(rpcHook)); Assert.assertSame(mockRemotingServer, mockRemotingServer1); } @Test public void testConfigContextMethods() throws Exception { // Test ConfigContext setter and getter methods BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); // Initially, ConfigContext should be null assertThat(brokerController.getConfigContext()).isNull(); // Create a test ConfigContext ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(brokerConfig) .messageStoreConfig(messageStoreConfig) .nettyServerConfig(nettyServerConfig) .nettyClientConfig(new NettyClientConfig()) .authConfig(new AuthConfig()) .build(); // Set the ConfigContext brokerController.setConfigContext(configContext); // Verify it was set correctly assertThat(brokerController.getConfigContext()).isNotNull(); assertThat(brokerController.getConfigContext()).isSameAs(configContext); assertThat(brokerController.getConfigContext().getBrokerConfig()).isSameAs(brokerConfig); assertThat(brokerController.getConfigContext().getMessageStoreConfig()).isSameAs(messageStoreConfig); assertThat(brokerController.getConfigContext().getNettyServerConfig()).isSameAs(nettyServerConfig); // Test setting null ConfigContext brokerController.setConfigContext(null); assertThat(brokerController.getConfigContext()).isNull(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import io.netty.channel.DefaultChannelPromise; import io.netty.util.concurrent.DefaultEventExecutor; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.QueryDataVersionResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.AdditionalMatchers.or; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest(NettyRemotingClient.class) public class BrokerOuterAPITest { @Mock private ChannelHandlerContext handlerContext; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; private String clusterName = "clusterName"; private String brokerName = "brokerName"; private String brokerAddr = "brokerAddr"; private long brokerId = 0L; private String nameserver1 = "127.0.0.1"; private String nameserver2 = "127.0.0.2"; private String nameserver3 = "127.0.0.3"; private int timeOut = 3000; @Mock private NettyRemotingClient nettyRemotingClient; private BrokerOuterAPI brokerOuterAPI; public void init() throws Exception { brokerOuterAPI = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(brokerOuterAPI, nettyRemotingClient); } @Test public void test_needRegister_normal() throws Exception { init(); brokerOuterAPI.start(); final RemotingCommand response = buildResponse(Boolean.TRUE); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); when(nettyRemotingClient.getNameServerAddressList()).thenReturn(Lists.asList(nameserver1, nameserver2, new String[] {nameserver3})); when(nettyRemotingClient.invokeSync(anyString(), any(RemotingCommand.class), anyLong())).thenReturn(response); List booleanList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigSerializeWrapper, timeOut, false); assertTrue(booleanList.size() > 0); assertFalse(booleanList.contains(Boolean.FALSE)); } @Test public void test_needRegister_timeout() throws Exception { if (MixAll.isMac()) { return; } init(); brokerOuterAPI.start(); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); when(nettyRemotingClient.getNameServerAddressList()).thenReturn(Lists.asList(nameserver1, nameserver2, new String[] {nameserver3})); when(nettyRemotingClient.invokeSync(anyString(), any(RemotingCommand.class), anyLong())).thenAnswer(new Answer() { @Override public RemotingCommand answer(InvocationOnMock invocation) throws Throwable { if (invocation.getArgument(0) == nameserver1) { return buildResponse(Boolean.TRUE); } else if (invocation.getArgument(0) == nameserver2) { return buildResponse(Boolean.FALSE); } else if (invocation.getArgument(0) == nameserver3) { TimeUnit.MILLISECONDS.sleep(timeOut + 100); // Increase sleep time to force timeout return buildResponse(Boolean.TRUE); } return buildResponse(Boolean.TRUE); } }); List booleanList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigSerializeWrapper, timeOut, false); assertEquals(2, booleanList.size()); boolean success = Iterables.any(booleanList, new Predicate() { public boolean apply(Boolean input) { return input; } }); assertTrue(success); } @Test public void test_register_normal() throws Exception { init(); brokerOuterAPI.start(); final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class); final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader(); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); when(nettyRemotingClient.getAvailableNameSrvList()).thenReturn(Lists.asList(nameserver1, nameserver2, new String[] {nameserver3})); when(nettyRemotingClient.invokeSync(anyString(), any(RemotingCommand.class), anyLong())).then(new Answer() { @Override public RemotingCommand answer(InvocationOnMock mock) throws Throwable { RemotingCommand request = mock.getArgument(1); return response; } }); List registerBrokerResultList = brokerOuterAPI.registerBrokerAll(clusterName, brokerAddr, brokerName, brokerId, "hasServerAddr", topicConfigSerializeWrapper, Lists.newArrayList(), false, timeOut, false, true, new BrokerIdentity()); assertTrue(registerBrokerResultList.size() > 0); } @Test public void test_register_timeout() throws Exception { init(); brokerOuterAPI.start(); final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); when(nettyRemotingClient.getAvailableNameSrvList()).thenReturn(Lists.asList(nameserver1, nameserver2, new String[] {nameserver3})); final ArgumentCaptor timeoutMillisCaptor = ArgumentCaptor.forClass(Long.class); when(nettyRemotingClient.invokeSync(or(ArgumentMatchers.eq(nameserver1), ArgumentMatchers.eq(nameserver2)), any(RemotingCommand.class), timeoutMillisCaptor.capture())).thenReturn(response); when(nettyRemotingClient.invokeSync(ArgumentMatchers.eq(nameserver3), any(RemotingCommand.class), anyLong())).thenThrow(RemotingTimeoutException.class); List registerBrokerResultList = brokerOuterAPI.registerBrokerAll(clusterName, brokerAddr, brokerName, brokerId, "hasServerAddr", topicConfigSerializeWrapper, Lists.newArrayList(), false, timeOut, false, true, new BrokerIdentity()); assertEquals(2, registerBrokerResultList.size()); } @Test public void testGetBrokerClusterInfo() throws Exception { init(); brokerOuterAPI.start(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); ClusterInfo want = new ClusterInfo(); want.setBrokerAddrTable(new HashMap<>(Collections.singletonMap("key", new BrokerData("cluster", "broker", new HashMap<>(Collections.singletonMap(MixAll.MASTER_ID, "127.0.0.1:10911")))))); response.setBody(RemotingSerializable.encode(want)); when(nettyRemotingClient.invokeSync(isNull(), argThat(argument -> argument.getCode() == RequestCode.GET_BROKER_CLUSTER_INFO), anyLong())).thenReturn(response); ClusterInfo got = brokerOuterAPI.getBrokerClusterInfo(); assertEquals(want, got); } private RemotingCommand buildResponse(Boolean changed) { final RemotingCommand response = RemotingCommand.createResponseCommand(QueryDataVersionResponseHeader.class); final QueryDataVersionResponseHeader responseHeader = (QueryDataVersionResponseHeader) response.readCustomHeader(); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); responseHeader.setChanged(changed); return response; } @Test public void testLookupAddressByDomain() throws Exception { init(); brokerOuterAPI.start(); Class clazz = BrokerOuterAPI.class; Method method = clazz.getDeclaredMethod("dnsLookupAddressByDomain", String.class); method.setAccessible(true); List addressList = (List) method.invoke(brokerOuterAPI, "localhost:6789"); AtomicBoolean result = new AtomicBoolean(false); addressList.forEach(s -> { if (s.contains("127.0.0.1:6789")) { result.set(true); } }); Assert.assertTrue(result.get()); } @Test public void testPullMessageFromSpecificBrokerAsync_createChannel_null() throws Exception { NettyRemotingClient mockClient = PowerMockito.spy(new NettyRemotingClient(new NettyClientConfig())); PowerMockito.when(mockClient, "getAndCreateChannelAsync", any()).thenReturn(null); BrokerOuterAPI api = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(api, mockClient); Triple rst = api.pullMessageFromSpecificBrokerAsync("", "", "", "", 1, 1, 1, 3000L).join(); Assert.assertNull(rst.getLeft()); Assert.assertTrue(rst.getMiddle().contains("connect")); Assert.assertTrue(rst.getRight()); // need retry } @Test public void testPullMessageFromSpecificBrokerAsync_createChannel_future_notSuccess() throws Exception { NettyRemotingClient mockClient = PowerMockito.spy(new NettyRemotingClient(new NettyClientConfig())); DefaultChannelPromise promise = PowerMockito.spy(new DefaultChannelPromise(PowerMockito.mock(Channel.class), new DefaultEventExecutor())); PowerMockito.when(mockClient, "getAndCreateChannelAsync", any()).thenReturn(promise); BrokerOuterAPI api = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(api, mockClient); promise.tryFailure(new Throwable()); Triple rst = api.pullMessageFromSpecificBrokerAsync("", "", "", "", 1, 1, 1, 3000L).join(); Assert.assertNull(rst.getLeft()); Assert.assertTrue(rst.getMiddle().contains("connect")); Assert.assertTrue(rst.getRight()); // need retry } // skip other future status test @Test public void testPullMessageFromSpecificBrokerAsync_timeout() throws Exception { Channel channel = Mockito.mock(Channel.class); when(channel.isActive()).thenReturn(true); NettyRemotingClient mockClient = PowerMockito.spy(new NettyRemotingClient(new NettyClientConfig())); DefaultChannelPromise promise = PowerMockito.spy(new DefaultChannelPromise(PowerMockito.mock(Channel.class), new DefaultEventExecutor())); PowerMockito.when(mockClient, "getAndCreateChannelAsync", any()).thenReturn(promise); when(promise.channel()).thenReturn(channel); BrokerOuterAPI api = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(api, mockClient); CompletableFuture future = new CompletableFuture<>(); doReturn(future).when(mockClient).invokeImpl(any(Channel.class), any(RemotingCommand.class), anyLong()); promise.trySuccess(null); future.completeExceptionally(new RemotingTimeoutException("wait response on the channel timeout")); Triple rst = api.pullMessageFromSpecificBrokerAsync("", "", "", "", 1, 1, 1, 3000L).join(); Assert.assertNull(rst.getLeft()); Assert.assertTrue(rst.getMiddle().contains("timeout")); Assert.assertTrue(rst.getRight()); // need retry } @Test public void testPullMessageFromSpecificBrokerAsync_brokerReturn_pullStatusCode() throws Exception { Channel channel = Mockito.mock(Channel.class); when(channel.isActive()).thenReturn(true); NettyRemotingClient mockClient = PowerMockito.spy(new NettyRemotingClient(new NettyClientConfig())); DefaultChannelPromise promise = PowerMockito.spy(new DefaultChannelPromise(PowerMockito.mock(Channel.class), new DefaultEventExecutor())); PowerMockito.when(mockClient, "getAndCreateChannelAsync", any()).thenReturn(promise); when(promise.channel()).thenReturn(channel); BrokerOuterAPI api = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(api, mockClient); int[] respCodes = new int[] {ResponseCode.SUCCESS, ResponseCode.PULL_NOT_FOUND, ResponseCode.PULL_RETRY_IMMEDIATELY, ResponseCode.PULL_OFFSET_MOVED}; PullStatus[] respStatus = new PullStatus[] {PullStatus.FOUND, PullStatus.NO_NEW_MSG, PullStatus.NO_MATCHED_MSG, PullStatus.OFFSET_ILLEGAL}; for (int i = 0; i < respCodes.length; i++) { CompletableFuture future = new CompletableFuture<>(); doReturn(future).when(mockClient).invokeImpl(any(Channel.class), any(RemotingCommand.class), anyLong()); RemotingCommand response = mockPullMessageResponse(respCodes[i]); ResponseFuture responseFuture = new ResponseFuture(channel, 0, null, 1000, resp -> { }, new SemaphoreReleaseOnlyOnce(new Semaphore(1))); responseFuture.setResponseCommand(response); promise.trySuccess(null); future.complete(responseFuture); Triple rst = api.pullMessageFromSpecificBrokerAsync("", "", "", "", 1, 1, 1, 3000L).join(); Assert.assertEquals(respStatus[i], rst.getLeft().getPullStatus()); if (ResponseCode.SUCCESS == respCodes[i]) { Assert.assertEquals(1, rst.getLeft().getMsgFoundList().size()); } else { Assert.assertNull(rst.getLeft().getMsgFoundList()); } Assert.assertEquals(respStatus[i].name(), rst.getMiddle()); Assert.assertFalse(rst.getRight()); // no retry } } @Test public void testPullMessageFromSpecificBrokerAsync_brokerReturn_allOtherResponseCode() throws Exception { Channel channel = Mockito.mock(Channel.class); when(channel.isActive()).thenReturn(true); NettyRemotingClient mockClient = PowerMockito.spy(new NettyRemotingClient(new NettyClientConfig())); DefaultChannelPromise promise = PowerMockito.spy(new DefaultChannelPromise(PowerMockito.mock(Channel.class), new DefaultEventExecutor())); PowerMockito.when(mockClient, "getAndCreateChannelAsync", any()).thenReturn(promise); when(promise.channel()).thenReturn(channel); BrokerOuterAPI api = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(api, mockClient); CompletableFuture future = new CompletableFuture<>(); doReturn(future).when(mockClient).invokeImpl(any(Channel.class), any(RemotingCommand.class), anyLong()); // test one code here, skip others RemotingCommand response = mockPullMessageResponse(ResponseCode.SUBSCRIPTION_NOT_EXIST); ResponseFuture responseFuture = new ResponseFuture(channel, 0, null, 1000, resp -> { }, new SemaphoreReleaseOnlyOnce(new Semaphore(1))); responseFuture.setResponseCommand(response); promise.trySuccess(null); future.complete(responseFuture); Triple rst = api.pullMessageFromSpecificBrokerAsync("", "", "", "", 1, 1, 1, 3000L).join(); Assert.assertNull(rst.getLeft()); Assert.assertTrue(rst.getMiddle().contains(ResponseCode.SUBSCRIPTION_NOT_EXIST + "")); Assert.assertTrue(rst.getRight()); // need retry } private RemotingCommand mockPullMessageResponse(int responseCode) throws Exception { RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class); response.setCode(responseCode); if (responseCode == ResponseCode.SUCCESS) { MessageExt msg = new MessageExt(); msg.setBody("HW".getBytes()); msg.setTopic("topic"); msg.setBornHost(new InetSocketAddress("127.0.0.1", 9000)); msg.setStoreHost(new InetSocketAddress("127.0.0.1", 9000)); byte[] encode = MessageDecoder.encode(msg, false); response.setBody(encode); } PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader(); responseHeader.setNextBeginOffset(0L); responseHeader.setMaxOffset(0L); responseHeader.setMinOffset(0L); responseHeader.setOffsetDelta(0L); responseHeader.setTopicSysFlag(0); responseHeader.setGroupSysFlag(0); responseHeader.setSuggestWhichBrokerId(0L); responseHeader.setForbiddenType(0); response.makeCustomHeaderToNet(); return response; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/BrokerPathConfigHelperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.io.File; import org.junit.Test; import static org.junit.Assert.assertEquals; public class BrokerPathConfigHelperTest { @Test public void testGetPath() { String lmqConsumerOffsetPath = BrokerPathConfigHelper.getLmqConsumerOffsetPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/lmqConsumerOffset.json".replace("/", File.separator), lmqConsumerOffsetPath); String consumerOffsetPath = BrokerPathConfigHelper.getConsumerOffsetPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/consumerOffset.json".replace("/", File.separator), consumerOffsetPath); String topicConfigPath = BrokerPathConfigHelper.getTopicConfigPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/topics.json".replace("/", File.separator), topicConfigPath); String subscriptionGroupPath = BrokerPathConfigHelper.getSubscriptionGroupPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/subscriptionGroup.json".replace("/", File.separator), subscriptionGroupPath); String topicQueueMappingPath = BrokerPathConfigHelper.getTopicQueueMappingPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/topicQueueMapping.json".replace("/", File.separator), topicQueueMappingPath); String consumerOrderInfoPath = BrokerPathConfigHelper.getConsumerOrderInfoPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/consumerOrderInfo.json".replace("/", File.separator), consumerOrderInfoPath); String timercheckPath = BrokerPathConfigHelper.getTimerCheckPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/timercheck".replace("/", File.separator), timercheckPath); String timermetricsPath = BrokerPathConfigHelper.getTimerMetricsPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/timermetrics".replace("/", File.separator), timermetricsPath); String transactionMetricsPath = BrokerPathConfigHelper.getTransactionMetricsPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/transactionMetrics".replace("/", File.separator), transactionMetricsPath); String consumerFilterPath = BrokerPathConfigHelper.getConsumerFilterPath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/consumerFilter.json".replace("/", File.separator), consumerFilterPath); String messageRequestModePath = BrokerPathConfigHelper.getMessageRequestModePath("/home/admin/store".replace("/", File.separator)); assertEquals("/home/admin/store/config/messageRequestMode.json".replace("/", File.separator), messageRequestModePath); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/BrokerShutdownTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.io.File; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.auth.config.AuthConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class BrokerShutdownTest { private MessageStoreConfig messageStoreConfig; private BrokerConfig brokerConfig; private NettyServerConfig nettyServerConfig; private AuthConfig authConfig; @Before public void setUp() { messageStoreConfig = new MessageStoreConfig(); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + UUID.randomUUID().toString(); messageStoreConfig.setStorePathRootDir(storePathRootDir); brokerConfig = new BrokerConfig(); nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(0); authConfig = new AuthConfig(); } @After public void destroy() { UtilAll.deleteFile(new File(messageStoreConfig.getStorePathRootDir())); } @Test public void testBrokerGracefulShutdown() throws Exception { // Test that broker shuts down gracefully with proper resource cleanup BrokerController brokerController = new BrokerController( brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig, authConfig); // Initialize and start the broker assertThat(brokerController.initialize()).isTrue(); brokerController.start(); // Verify broker is running assertThat(brokerController.getBrokerMetricsManager()).isNotNull(); // Test graceful shutdown long startTime = System.currentTimeMillis(); brokerController.shutdown(); long shutdownTime = System.currentTimeMillis() - startTime; // Shutdown should complete within reasonable time (10 seconds) assertThat(shutdownTime).isLessThan(40000); } @Test public void testChainedShutdownOrdering() throws Exception { // Test that shutdown components are called in proper order BrokerController brokerController = new BrokerController( brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig, authConfig); assertThat(brokerController.initialize()).isTrue(); // Track shutdown order using atomic flags AtomicBoolean metricsManagerShutdown = new AtomicBoolean(false); AtomicBoolean brokerStatsShutdown = new AtomicBoolean(false); // Start broker brokerController.start(); // Verify services are initialized assertThat(brokerController.getBrokerMetricsManager()).isNotNull(); assertThat(brokerController.getBrokerStatsManager()).isNotNull(); // Shutdown should not throw exceptions brokerController.shutdown(); // After shutdown, services should be properly cleaned up // (We can't easily verify the exact order without modifying the implementation, // but we can verify shutdown completes successfully) assertThat(true).isTrue(); // Placeholder for successful completion } @Test public void testShutdownWithConcurrentOperations() throws Exception { // Test shutdown behavior when concurrent operations are running BrokerController brokerController = new BrokerController( brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig, authConfig); assertThat(brokerController.initialize()).isTrue(); brokerController.start(); CountDownLatch shutdownLatch = new CountDownLatch(1); AtomicBoolean shutdownSuccess = new AtomicBoolean(false); // Simulate concurrent shutdown from another thread Thread shutdownThread = new Thread(() -> { try { brokerController.shutdown(); shutdownSuccess.set(true); } catch (Exception e) { // Should not happen in graceful shutdown } finally { shutdownLatch.countDown(); } }); shutdownThread.start(); // Wait for shutdown to complete assertThat(shutdownLatch.await(40, TimeUnit.SECONDS)).isTrue(); assertThat(shutdownSuccess.get()).isTrue(); } @Test public void testResourceCleanupDuringShutdown() throws Exception { // Test that resources are properly cleaned up during shutdown BrokerController brokerController = new BrokerController( brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig, authConfig); assertThat(brokerController.initialize()).isTrue(); // Verify essential components are initialized assertThat(brokerController.getBrokerMetricsManager()).isNotNull(); assertThat(brokerController.getBrokerStatsManager()).isNotNull(); assertThat(brokerController.getConsumerOffsetManager()).isNotNull(); assertThat(brokerController.getTopicConfigManager()).isNotNull(); brokerController.start(); // Shutdown should clean up all resources brokerController.shutdown(); // After shutdown, the broker should be in a clean state // We verify this by ensuring a second shutdown call doesn't cause issues brokerController.shutdown(); // Should be safe to call multiple times } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/BrokerStartupTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; import org.apache.rocketmq.common.MixAll; import org.junit.Assert; import org.junit.Test; public class BrokerStartupTest { private String storePathRootDir = "."; @Test public void testProperties2SystemEnv() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Properties properties = new Properties(); Class clazz = BrokerStartup.class; Method method = clazz.getDeclaredMethod("properties2SystemEnv", Properties.class); method.setAccessible(true); { properties.put("rmqAddressServerDomain", "value1"); properties.put("rmqAddressServerSubGroup", "value2"); method.invoke(null, properties); Assert.assertEquals("value1", System.getProperty("rocketmq.namesrv.domain")); Assert.assertEquals("value2", System.getProperty("rocketmq.namesrv.domain.subgroup")); } { properties.put("rmqAddressServerDomain", MixAll.WS_DOMAIN_NAME); properties.put("rmqAddressServerSubGroup", MixAll.WS_DOMAIN_SUBGROUP); method.invoke(null, properties); Assert.assertEquals(MixAll.WS_DOMAIN_NAME, System.getProperty("rocketmq.namesrv.domain")); Assert.assertEquals(MixAll.WS_DOMAIN_SUBGROUP, System.getProperty("rocketmq.namesrv.domain.subgroup")); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/RocksDBConfigManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker; import org.apache.rocketmq.broker.config.v1.RocksDBConfigManager; import org.apache.rocketmq.common.config.ConfigRocksDBStorage; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; public class RocksDBConfigManagerTest { private ConfigRocksDBStorage configRocksDBStorage; private RocksDBConfigManager rocksDBConfigManager; @Before public void setUp() throws IllegalAccessException { configRocksDBStorage = mock(ConfigRocksDBStorage.class); rocksDBConfigManager = spy(new RocksDBConfigManager("testPath", 1000L, null)); rocksDBConfigManager.configRocksDBStorage = configRocksDBStorage; } @Test public void testLoadDataVersion() throws Exception { DataVersion expected = new DataVersion(); expected.nextVersion(); when(rocksDBConfigManager.getKvDataVersion()).thenReturn(expected); boolean result = rocksDBConfigManager.loadDataVersion(); assertTrue(result); assertEquals(expected.getCounter().get(), rocksDBConfigManager.getKvDataVersion().getCounter().get()); assertEquals(expected.getTimestamp(), rocksDBConfigManager.getKvDataVersion().getTimestamp()); } @Test public void testUpdateKvDataVersion() throws Exception { rocksDBConfigManager.updateKvDataVersion(); verify(rocksDBConfigManager, times(1)).updateKvDataVersion(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ConsumerManagerScannerTest { private ConsumerManager consumerManager; private String group = "FooBar"; private String clientId = "clientId"; private ClientChannelInfo clientInfo; private Map> groupEventListMap = new HashMap<>(); @Mock private Channel channel; @Before public void init() { clientInfo = new ClientChannelInfo(channel, clientId, LanguageCode.JAVA, 0); consumerManager = new ConsumerManager(new ConsumerIdsChangeListener() { @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { groupEventListMap.compute(event, (eventKey, dataListVal) -> { if (dataListVal == null) { dataListVal = new ArrayList<>(); } dataListVal.add(new ConsumerIdsChangeListenerData(event, group, args)); return dataListVal; }); } @Override public void shutdown() { } }, 1000 * 120); } private static class ConsumerIdsChangeListenerData { private ConsumerGroupEvent event; private String group; private Object[] args; public ConsumerIdsChangeListenerData(ConsumerGroupEvent event, String group, Object[] args) { this.event = event; this.group = group; this.args = args; } } @Test public void testClientUnregisterEventInDoChannelCloseEvent() { assertThat(consumerManager.registerConsumer( group, clientInfo, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, new HashSet<>(), false )).isTrue(); consumerManager.doChannelCloseEvent("remoteAddr", channel); assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).size()).isEqualTo(1); assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]).isInstanceOf(ClientChannelInfo.class); ClientChannelInfo clientChannelInfo = (ClientChannelInfo) groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]; assertThat(clientChannelInfo).isSameAs(clientInfo); } @Test public void testClientUnregisterEventInUnregisterConsumer() { assertThat(consumerManager.registerConsumer( group, clientInfo, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, new HashSet<>(), false )).isTrue(); consumerManager.unregisterConsumer(group, clientInfo, false); assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).size()).isEqualTo(1); assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]).isInstanceOf(ClientChannelInfo.class); ClientChannelInfo clientChannelInfo = (ClientChannelInfo) groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]; assertThat(clientChannelInfo).isSameAs(clientInfo); } @Test public void testClientUnregisterEventInScanNotActiveChannel() { assertThat(consumerManager.registerConsumer( group, clientInfo, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, new HashSet<>(), false )).isTrue(); clientInfo.setLastUpdateTimestamp(0); when(channel.close()).thenReturn(mock(ChannelFuture.class)); consumerManager.scanNotActiveChannel(); assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).size()).isEqualTo(1); assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]).isInstanceOf(ClientChannelInfo.class); ClientChannelInfo clientChannelInfo = (ClientChannelInfo) groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]; assertThat(clientChannelInfo).isSameAs(clientInfo); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import com.google.common.collect.ImmutableSet; import io.netty.channel.Channel; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.net.Broker2Client; import org.apache.rocketmq.broker.filter.ConsumerFilterManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.HashSet; import java.util.Set; import static org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType.CONSUME_PASSIVELY; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ConsumerManagerTest { private ClientChannelInfo clientChannelInfo; @Mock private Channel channel; private ConsumerManager consumerManager; @Mock private BrokerController brokerController; private final BrokerConfig brokerConfig = new BrokerConfig(); private static final String GROUP = "DEFAULT_GROUP"; private static final String CLIENT_ID = "1"; private static final int VERSION = 1; private static final String TOPIC = "DEFAULT_TOPIC"; @Before public void before() { clientChannelInfo = new ClientChannelInfo(channel, CLIENT_ID, LanguageCode.JAVA, VERSION); DefaultConsumerIdsChangeListener defaultConsumerIdsChangeListener = new DefaultConsumerIdsChangeListener(brokerController); BrokerStatsManager brokerStatsManager = new BrokerStatsManager(brokerConfig); consumerManager = spy(new ConsumerManager(defaultConsumerIdsChangeListener, brokerStatsManager, brokerConfig)); ConsumerFilterManager consumerFilterManager = mock(ConsumerFilterManager.class); when(brokerController.getConsumerFilterManager()).thenReturn(consumerFilterManager); } @Test public void compensateBasicConsumerInfoTest() { ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); assertThat(consumerGroupInfo).isNull(); consumerManager.compensateBasicConsumerInfo(GROUP, ConsumeType.CONSUME_ACTIVELY, MessageModel.BROADCASTING); consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); assertThat(consumerGroupInfo).isNotNull(); assertThat(consumerGroupInfo.getConsumeType()).isEqualTo(ConsumeType.CONSUME_ACTIVELY); assertThat(consumerGroupInfo.getMessageModel()).isEqualTo(MessageModel.BROADCASTING); } @Test public void compensateSubscribeDataTest() { ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); assertThat(consumerGroupInfo).isNull(); consumerManager.compensateSubscribeData(GROUP, TOPIC, new SubscriptionData(TOPIC, SubscriptionData.SUB_ALL)); consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); assertThat(consumerGroupInfo).isNotNull(); assertThat(consumerGroupInfo.getSubscriptionTable().size()).isEqualTo(1); SubscriptionData subscriptionData = consumerGroupInfo.getSubscriptionTable().get(TOPIC); assertThat(subscriptionData).isNotNull(); assertThat(subscriptionData.getTopic()).isEqualTo(TOPIC); assertThat(subscriptionData.getSubString()).isEqualTo(SubscriptionData.SUB_ALL); } @Test public void registerConsumerTest() { register(); final Set subList = new HashSet<>(); SubscriptionData subscriptionData = new SubscriptionData(TOPIC, "*"); subList.add(subscriptionData); consumerManager.registerConsumer(GROUP, clientChannelInfo, CONSUME_PASSIVELY, MessageModel.BROADCASTING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET, subList, true); verify(consumerManager, never()).callConsumerIdsChangeListener(eq(ConsumerGroupEvent.CHANGE), any(), any()); assertThat(consumerManager.getConsumerTable().get(GROUP)).isNotNull(); } @Test public void unregisterConsumerTest() { // register register(); // unregister consumerManager.unregisterConsumer(GROUP, clientChannelInfo, true); verify(consumerManager, never()).callConsumerIdsChangeListener(eq(ConsumerGroupEvent.CHANGE), any(), any()); assertThat(consumerManager.getConsumerTable().get(GROUP)).isNull(); } @Test public void findChannelTest() { register(); final ClientChannelInfo consumerManagerChannel = consumerManager.findChannel(GROUP, CLIENT_ID); assertThat(consumerManagerChannel).isNotNull(); } @Test public void findSubscriptionDataTest() { register(); final SubscriptionData subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC); assertThat(subscriptionData).isNotNull(); } @Test public void findSubscriptionDataCountTest() { register(); final int count = consumerManager.findSubscriptionDataCount(GROUP); assertTrue(count > 0); } @Test public void findSubscriptionTest() { SubscriptionData subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC, true); assertThat(subscriptionData).isNull(); consumerManager.compensateSubscribeData(GROUP, TOPIC, new SubscriptionData(TOPIC, SubscriptionData.SUB_ALL)); subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC, true); assertThat(subscriptionData).isNotNull(); assertThat(subscriptionData.getTopic()).isEqualTo(TOPIC); assertThat(subscriptionData.getSubString()).isEqualTo(SubscriptionData.SUB_ALL); subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC, false); assertThat(subscriptionData).isNull(); } @Test public void scanNotActiveChannelTest() { clientChannelInfo.setLastUpdateTimestamp(System.currentTimeMillis() - brokerConfig.getChannelExpiredTimeout() * 2); consumerManager.scanNotActiveChannel(); assertThat(consumerManager.getConsumerTable().size()).isEqualTo(0); } @Test public void queryTopicConsumeByWhoTest() { register(); final HashSet consumeGroup = consumerManager.queryTopicConsumeByWho(TOPIC); assertFalse(consumeGroup.isEmpty()); assertThat(consumerManager.queryTopicConsumeByWho(TOPIC)).isEqualTo(ImmutableSet.of(GROUP)); } @Test public void doChannelCloseEventTest() { consumerManager.doChannelCloseEvent("127.0.0.1", channel); verify(consumerManager, never()).callConsumerIdsChangeListener(eq(ConsumerGroupEvent.CHANGE), any(), any()); assertEquals(0, consumerManager.getConsumerTable().size()); } private void register() { // register final Set subList = new HashSet<>(); SubscriptionData subscriptionData = new SubscriptionData(TOPIC, "*"); subList.add(subscriptionData); consumerManager.registerConsumer(GROUP, clientChannelInfo, CONSUME_PASSIVELY, MessageModel.BROADCASTING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET, subList, true); } @Test public void removeExpireConsumerGroupInfo() { SubscriptionData subscriptionData = new SubscriptionData(TOPIC, SubscriptionData.SUB_ALL); subscriptionData.setSubVersion(System.currentTimeMillis() - brokerConfig.getSubscriptionExpiredTimeout() * 2); consumerManager.compensateSubscribeData(GROUP, TOPIC, subscriptionData); consumerManager.compensateSubscribeData(GROUP, TOPIC + "_1", new SubscriptionData(TOPIC, SubscriptionData.SUB_ALL)); consumerManager.removeExpireConsumerGroupInfo(); assertThat(consumerManager.getConsumerGroupInfo(GROUP, true)).isNotNull(); assertThat(consumerManager.findSubscriptionData(GROUP, TOPIC)).isNull(); assertThat(consumerManager.findSubscriptionData(GROUP, TOPIC + "_1")).isNotNull(); } @Test public void testRegisterConsumerWithoutSub() { when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); Broker2Client broker2Client = mock(Broker2Client.class); when(brokerController.getBroker2Client()).thenReturn(broker2Client); ConsumerGroupInfo groupInfo = new ConsumerGroupInfo(GROUP, CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); SubscriptionData subscriptionData = new SubscriptionData(TOPIC, "*"); groupInfo.getSubscriptionTable().put(TOPIC, subscriptionData); consumerManager.getConsumerTable().put(GROUP, groupInfo); consumerManager.registerConsumerWithoutSub(GROUP, clientChannelInfo, CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET, true); Set actual = consumerManager.queryTopicConsumeByWho(TOPIC); assertThat(actual).contains(GROUP); assertThat(actual).doesNotContain(TOPIC); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import java.lang.reflect.Field; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ProducerManagerTest { private BrokerConfig brokerConfig; private ProducerManager producerManager; private String group = "FooBar"; private ClientChannelInfo clientInfo; @Mock private Channel channel; @Before public void init() { brokerConfig = new BrokerConfig(); producerManager = new ProducerManager(null, brokerConfig); clientInfo = new ClientChannelInfo(channel, "clientId", LanguageCode.JAVA, 0); } @Test public void scanNotActiveChannel() throws Exception { producerManager.registerProducer(group, clientInfo); AtomicReference groupRef = new AtomicReference<>(); AtomicReference clientChannelInfoRef = new AtomicReference<>(); producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> { switch (event) { case GROUP_UNREGISTER: groupRef.set(group); break; case CLIENT_UNREGISTER: clientChannelInfoRef.set(clientChannelInfo); break; default: break; } }); assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNotNull(); assertThat(producerManager.findChannel("clientId")).isNotNull(); Field field = ProducerManager.class.getDeclaredField("CHANNEL_EXPIRED_TIMEOUT"); field.setAccessible(true); long channelExpiredTimeout = field.getLong(producerManager); clientInfo.setLastUpdateTimestamp(System.currentTimeMillis() - channelExpiredTimeout - 10); when(channel.close()).thenReturn(mock(ChannelFuture.class)); producerManager.scanNotActiveChannel(); assertThat(producerManager.getGroupChannelTable().get(group)).isNull(); assertThat(groupRef.get()).isEqualTo(group); assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo); assertThat(producerManager.findChannel("clientId")).isNull(); } @Test public void scanNotActiveChannelWithSameClientId() throws Exception { producerManager.registerProducer(group, clientInfo); Channel channel1 = Mockito.mock(Channel.class); ClientChannelInfo clientInfo1 = new ClientChannelInfo(channel1, clientInfo.getClientId(), LanguageCode.JAVA, 0); producerManager.registerProducer(group, clientInfo1); AtomicReference groupRef = new AtomicReference<>(); AtomicReference clientChannelInfoRef = new AtomicReference<>(); producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> { switch (event) { case GROUP_UNREGISTER: groupRef.set(group); break; case CLIENT_UNREGISTER: clientChannelInfoRef.set(clientChannelInfo); break; default: break; } }); assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNotNull(); assertThat(producerManager.getGroupChannelTable().get(group).get(channel1)).isNotNull(); assertThat(producerManager.findChannel("clientId")).isNotNull(); Field field = ProducerManager.class.getDeclaredField("CHANNEL_EXPIRED_TIMEOUT"); field.setAccessible(true); long channelExpiredTimeout = field.getLong(producerManager); clientInfo.setLastUpdateTimestamp(System.currentTimeMillis() - channelExpiredTimeout - 10); when(channel.close()).thenReturn(mock(ChannelFuture.class)); producerManager.scanNotActiveChannel(); assertThat(producerManager.getGroupChannelTable().get(group).get(channel1)).isNotNull(); assertThat(producerManager.findChannel("clientId")).isNotNull(); } @Test public void doChannelCloseEvent() throws Exception { producerManager.registerProducer(group, clientInfo); AtomicReference groupRef = new AtomicReference<>(); AtomicReference clientChannelInfoRef = new AtomicReference<>(); producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> { switch (event) { case GROUP_UNREGISTER: groupRef.set(group); break; case CLIENT_UNREGISTER: clientChannelInfoRef.set(clientChannelInfo); break; default: break; } }); assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNotNull(); assertThat(producerManager.findChannel("clientId")).isNotNull(); producerManager.doChannelCloseEvent("127.0.0.1", channel); assertThat(producerManager.getGroupChannelTable().get(group)).isNull(); assertThat(groupRef.get()).isEqualTo(group); assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo); assertThat(producerManager.findChannel("clientId")).isNull(); } @Test public void testRegisterProducer() { brokerConfig.setEnableRegisterProducer(false); brokerConfig.setRejectTransactionMessage(true); producerManager.registerProducer(group, clientInfo); Map channelMap = producerManager.getGroupChannelTable().get(group); Channel channel1 = producerManager.findChannel("clientId"); assertThat(channelMap).isNull(); assertThat(channel1).isNull(); brokerConfig.setEnableRegisterProducer(true); brokerConfig.setRejectTransactionMessage(false); producerManager.registerProducer(group, clientInfo); channelMap = producerManager.getGroupChannelTable().get(group); channel1 = producerManager.findChannel("clientId"); assertThat(channelMap).isNotNull(); assertThat(channel1).isNotNull(); assertThat(channelMap.get(channel)).isEqualTo(clientInfo); assertThat(channel1).isEqualTo(channel); } @Test public void unregisterProducer() throws Exception { producerManager.registerProducer(group, clientInfo); AtomicReference groupRef = new AtomicReference<>(); AtomicReference clientChannelInfoRef = new AtomicReference<>(); producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> { switch (event) { case GROUP_UNREGISTER: groupRef.set(group); break; case CLIENT_UNREGISTER: clientChannelInfoRef.set(clientChannelInfo); break; default: break; } }); Map channelMap = producerManager.getGroupChannelTable().get(group); assertThat(channelMap).isNotNull(); assertThat(channelMap.get(channel)).isEqualTo(clientInfo); Channel channel1 = producerManager.findChannel("clientId"); assertThat(channel1).isNotNull(); assertThat(channel1).isEqualTo(channel); producerManager.unregisterProducer(group, clientInfo); channelMap = producerManager.getGroupChannelTable().get(group); channel1 = producerManager.findChannel("clientId"); assertThat(groupRef.get()).isEqualTo(group); assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo); assertThat(channelMap).isNull(); assertThat(channel1).isNull(); } @Test public void testGetGroupChannelTable() throws Exception { producerManager.registerProducer(group, clientInfo); Map oldMap = producerManager.getGroupChannelTable().get(group); producerManager.unregisterProducer(group, clientInfo); assertThat(oldMap.size()).isEqualTo(0); } @Test public void testGetAvailableChannel() { producerManager.registerProducer(group, clientInfo); when(channel.isActive()).thenReturn(true); when(channel.isWritable()).thenReturn(true); Channel c = producerManager.getAvailableChannel(group); assertThat(c).isSameAs(channel); when(channel.isWritable()).thenReturn(false); c = producerManager.getAvailableChannel(group); assertThat(c).isSameAs(channel); when(channel.isActive()).thenReturn(false); c = producerManager.getAvailableChannel(group); assertThat(c).isNull(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/client/net/Broker2ClientTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client.net; import io.netty.channel.Channel; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.GetConsumerStatusBody; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.store.MessageStore; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class Broker2ClientTest { @Mock private BrokerController brokerController; @Mock private RemotingServer remotingServer; @Mock private ConsumerManager consumerManager; @Mock private TopicConfigManager topicConfigManager; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private Channel channel; @Mock private ConsumerGroupInfo consumerGroupInfo; private Broker2Client broker2Client; private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; private final String defaultGroup = "defaultGroup"; private final long timestamp = System.currentTimeMillis(); private final boolean isForce = true; @Before public void init() { broker2Client = new Broker2Client(brokerController); when(brokerController.getRemotingServer()).thenReturn(remotingServer); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getConsumerManager()).thenReturn(consumerManager); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(brokerController.getBrokerConfig()).thenReturn(mock(BrokerConfig.class)); when(brokerController.getMessageStore()).thenReturn(mock(MessageStore.class)); when(consumerManager.getConsumerGroupInfo(any())).thenReturn(consumerGroupInfo); } @Test public void testCheckProducerTransactionState() throws Exception { CheckTransactionStateRequestHeader requestHeader = new CheckTransactionStateRequestHeader(); broker2Client.checkProducerTransactionState("group", channel, requestHeader, createMessageExt()); verify(remotingServer).invokeOneway(eq(channel), any(RemotingCommand.class), eq(10L)); } @Test public void testCheckProducerTransactionStateException() throws Exception { CheckTransactionStateRequestHeader requestHeader = new CheckTransactionStateRequestHeader(); MessageExt messageExt = createMessageExt(); doThrow(new RuntimeException("Test Exception")) .when(remotingServer) .invokeOneway(any(Channel.class), any(RemotingCommand.class), anyLong()); broker2Client.checkProducerTransactionState("group", channel, requestHeader, messageExt); verify(brokerController.getRemotingServer()).invokeOneway(eq(channel), any(RemotingCommand.class), eq(10L)); } @Test public void testResetOffsetNoTopicConfig() throws RemotingCommandException { when(topicConfigManager.selectTopicConfig(defaultTopic)).thenReturn(null); RemotingCommand response = broker2Client.resetOffset(defaultTopic, defaultGroup, timestamp, isForce); assertEquals(ResponseCode.TOPIC_NOT_EXIST, response.getCode()); } @Test public void testResetOffsetNoConsumerGroupInfo() throws RemotingCommandException { TopicConfig topicConfig = mock(TopicConfig.class); when(topicConfigManager.selectTopicConfig(defaultTopic)).thenReturn(topicConfig); when(topicConfig.getWriteQueueNums()).thenReturn(1); when(consumerOffsetManager.queryOffset(defaultGroup, defaultTopic, 0)).thenReturn(0L); RemotingCommand response = broker2Client.resetOffset(defaultTopic, defaultGroup, timestamp, isForce); assertEquals(ResponseCode.CONSUMER_NOT_ONLINE, response.getCode()); } @Test public void testResetOffset() throws RemotingCommandException { TopicConfig topicConfig = mock(TopicConfig.class); when(topicConfigManager.selectTopicConfig(defaultTopic)).thenReturn(topicConfig); when(topicConfig.getWriteQueueNums()).thenReturn(1); when(brokerController.getConsumerOffsetManager().queryOffset(defaultGroup, defaultTopic, 0)).thenReturn(0L); BrokerConfig brokerConfig = mock(BrokerConfig.class); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerConfig.getBrokerName()).thenReturn(defaultBroker); ConsumerGroupInfo consumerGroupInfo = mock(ConsumerGroupInfo.class); when(consumerManager.getConsumerGroupInfo(defaultGroup)).thenReturn(consumerGroupInfo); RemotingCommand response = broker2Client.resetOffset(defaultTopic, defaultGroup, timestamp, isForce); assertEquals(ResponseCode.CONSUMER_NOT_ONLINE, response.getCode()); } @Test public void testGetConsumeStatusNoConsumerOnline() { when(consumerGroupInfo.getChannelInfoTable()).thenReturn(new ConcurrentHashMap<>()); RemotingCommand response = broker2Client.getConsumeStatus(defaultTopic, defaultGroup, ""); assertEquals(ResponseCode.SYSTEM_ERROR, response.getCode()); } @Test public void testGetConsumeStatusClientDoesNotSupportFeature() { ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, "defaultClientId", null, MQVersion.Version.V3_0_6.ordinal()); ConcurrentMap channelInfoTable = new ConcurrentHashMap<>(); channelInfoTable.put(channel, clientChannelInfo); when(consumerGroupInfo.getChannelInfoTable()).thenReturn(channelInfoTable); RemotingCommand response = broker2Client.getConsumeStatus(defaultTopic, defaultGroup, ""); assertEquals(ResponseCode.SYSTEM_ERROR, response.getCode()); } @Test public void testGetConsumeStatus() throws Exception { ConcurrentMap channelInfoTable = new ConcurrentHashMap<>(); ClientChannelInfo clientChannelInfo = mock(ClientChannelInfo.class); when(clientChannelInfo.getVersion()).thenReturn(MQVersion.CURRENT_VERSION); channelInfoTable.put(channel, clientChannelInfo); when(consumerGroupInfo.getChannelInfoTable()).thenReturn(channelInfoTable); RemotingCommand responseMock = mock(RemotingCommand.class); when(responseMock.getCode()).thenReturn(ResponseCode.SUCCESS); when(responseMock.getBody()).thenReturn("{\"consumerTable\":{}}".getBytes(StandardCharsets.UTF_8)); when(remotingServer.invokeSync(any(Channel.class), any(RemotingCommand.class), anyLong())).thenReturn(responseMock); RemotingCommand response = broker2Client.getConsumeStatus(defaultTopic, defaultGroup, ""); assertEquals(ResponseCode.SUCCESS, response.getCode()); GetConsumerStatusBody body = RemotingSerializable.decode(response.getBody(), GetConsumerStatusBody.class); assertEquals(1, body.getConsumerTable().size()); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, defaultGroup); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); result.setKeys("keys"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setStoreHost(storeHost); result.setBornHost(bornHost); return result; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/client/rebalance/RebalanceLockManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.client.rebalance; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RebalanceLockManagerTest { @Mock private RebalanceLockManager.LockEntry lockEntry; private final RebalanceLockManager rebalanceLockManager = new RebalanceLockManager(); private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; private final String defaultGroup = "defaultGroup"; private final String defaultClientId = "defaultClientId"; @Test public void testIsLockAllExpiredGroupNotExist() { assertTrue(rebalanceLockManager.isLockAllExpired(defaultGroup)); } @Test public void testIsLockAllExpiredGroupExist() throws IllegalAccessException { FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); when(lockEntry.isExpired()).thenReturn(false); assertFalse(rebalanceLockManager.isLockAllExpired(defaultGroup)); } @Test public void testIsLockAllExpiredGroupExistSomeExpired() throws IllegalAccessException { FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); when(lockEntry.isExpired()).thenReturn(true).thenReturn(false); assertFalse(rebalanceLockManager.isLockAllExpired(defaultGroup)); } @Test public void testTryLockNotLocked() { assertTrue(rebalanceLockManager.tryLock(defaultGroup, createDefaultMessageQueue(), defaultClientId)); } @Test public void testTryLockSameClient() throws IllegalAccessException { when(lockEntry.isLocked(defaultClientId)).thenReturn(true); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); assertTrue(rebalanceLockManager.tryLock(defaultGroup, createDefaultMessageQueue(), defaultClientId)); } @Test public void testTryLockDifferentClient() throws Exception { when(lockEntry.isLocked(defaultClientId)).thenReturn(false); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); assertFalse(rebalanceLockManager.tryLock(defaultGroup, createDefaultMessageQueue(), defaultClientId)); } @Test public void testTryLockButExpired() throws IllegalAccessException { when(lockEntry.isExpired()).thenReturn(true); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); assertTrue(rebalanceLockManager.tryLock(defaultGroup, createDefaultMessageQueue(), defaultClientId)); } @Test public void testTryLockBatchAllLocked() { Set mqs = createMessageQueue(2); Set actual = rebalanceLockManager.tryLockBatch(defaultGroup, mqs, defaultClientId); assertEquals(mqs, actual); } @Test public void testTryLockBatchNoneLocked() throws IllegalAccessException { when(lockEntry.isLocked(defaultClientId)).thenReturn(false); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); Set actual = rebalanceLockManager.tryLockBatch(defaultGroup, createMessageQueue(2), defaultClientId); assertTrue(actual.isEmpty()); } @Test public void testTryLockBatchSomeLocked() throws IllegalAccessException { Set mqs = new HashSet<>(); MessageQueue mq1 = new MessageQueue(defaultTopic, defaultBroker, 0); MessageQueue mq2 = new MessageQueue(defaultTopic, defaultBroker, 1); mqs.add(mq1); mqs.add(mq2); when(lockEntry.isLocked(defaultClientId)).thenReturn(true).thenReturn(false); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", createMQLockTable(), true); Set actual = rebalanceLockManager.tryLockBatch(defaultGroup, mqs, defaultClientId); Set expected = new HashSet<>(); expected.add(mq2); assertEquals(expected, actual); } @Test public void testUnlockBatch() throws IllegalAccessException { when(lockEntry.getClientId()).thenReturn(defaultClientId); ConcurrentMap> mqLockTable = createMQLockTable(); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", mqLockTable, true); rebalanceLockManager.unlockBatch(defaultGroup, createMessageQueue(1), defaultClientId); assertEquals(1, mqLockTable.get(defaultGroup).values().size()); } @Test public void testUnlockBatchByOtherClient() throws IllegalAccessException { when(lockEntry.getClientId()).thenReturn("otherClientId"); ConcurrentMap> mqLockTable = createMQLockTable(); FieldUtils.writeDeclaredField(rebalanceLockManager, "mqLockTable", mqLockTable, true); rebalanceLockManager.unlockBatch(defaultGroup, createMessageQueue(1), defaultClientId); assertEquals(2, mqLockTable.get(defaultGroup).values().size()); } private MessageQueue createDefaultMessageQueue() { return createMessageQueue(1).iterator().next(); } private Set createMessageQueue(final int count) { Set result = new HashSet<>(); for (int i = 0; i < count; i++) { result.add(new MessageQueue(defaultTopic, defaultBroker, i)); } return result; } private ConcurrentMap> createMQLockTable() { MessageQueue messageQueue1 = new MessageQueue(defaultTopic, defaultBroker, 0); MessageQueue messageQueue2 = new MessageQueue(defaultTopic, defaultBroker, 1); ConcurrentHashMap lockEntryMap = new ConcurrentHashMap<>(); lockEntryMap.put(messageQueue1, lockEntry); lockEntryMap.put(messageQueue2, lockEntry); ConcurrentMap> result = new ConcurrentHashMap<>(); result.put(defaultGroup, lockEntryMap); return result; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/coldctr/ColdDataCgCtrServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.coldctr; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.coldctr.AccAndTimeStamp; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ColdDataCgCtrServiceTest { @Mock private BrokerController brokerController; @Mock private BrokerConfig brokerConfig; private ColdDataCgCtrService coldDataCgCtrService; @Before public void init() throws IllegalAccessException { when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); coldDataCgCtrService = new ColdDataCgCtrService(brokerController); FieldUtils.writeField(coldDataCgCtrService, "cgColdThresholdMapRuntime", createCgColdThresholdMapRuntime(), true); FieldUtils.writeField(coldDataCgCtrService, "cgColdThresholdMapConfig", createCgColdThresholdMapConfig(), true); } @Test public void testGetColdDataFlowCtrInfo() { String actual = coldDataCgCtrService.getColdDataFlowCtrInfo(); assertTrue(actual.contains("\"globalAcc\":0")); assertTrue(actual.contains("\"cgColdReadThreshold\":0")); assertTrue(actual.contains("\"globalColdReadThreshold\":0")); assertTrue(actual.contains("\"configTable\":{\"consumerGroup2\":2048}")); assertTrue(actual.contains("\"runtimeTable\":{\"consumerGroup1\":{\"coldAcc\":1,\"createTimeMills\":1,\"lastColdReadTimeMills\":1}}")); } private Map createCgColdThresholdMapRuntime() { Map result = new ConcurrentHashMap<>(); AccAndTimeStamp accAndTimeStamp = new AccAndTimeStamp(new AtomicLong(1L)); accAndTimeStamp.setCreateTimeMills(1L); accAndTimeStamp.setLastColdReadTimeMills(1L); result.put("consumerGroup1", accAndTimeStamp); return result; } private ConcurrentHashMap createCgColdThresholdMapConfig() { ConcurrentHashMap result = new ConcurrentHashMap<>(); result.put("consumerGroup2", 2048L); return result; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v1/RocksDBConsumerOffsetManagerMigrationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import java.io.File; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class RocksDBConsumerOffsetManagerMigrationTest { private static final String TEST_GROUP = "TestGroup"; private static final String TEST_TOPIC = "TestTopic"; private static final String TEST_KEY = TEST_TOPIC + "@" + TEST_GROUP; private BrokerController brokerController; private String storePath; private String separateRocksDBPath; private String unifiedRocksDBPath; @Before public void init() { brokerController = Mockito.mock(BrokerController.class); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); storePath = System.getProperty("java.io.tmpdir") + File.separator + "rocketmq-test-" + System.currentTimeMillis(); messageStoreConfig.setStorePathRootDir(storePath); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setConsumerOffsetUpdateVersionStep(1); Mockito.when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); separateRocksDBPath = storePath + File.separator + "config" + File.separator + "consumerOffsets" + File.separator; unifiedRocksDBPath = storePath + File.separator + "config" + File.separator + "metadata" + File.separator; // Create directories UtilAll.ensureDirOK(separateRocksDBPath); UtilAll.ensureDirOK(unifiedRocksDBPath); } @After public void destroy() { // Clean up test directories UtilAll.deleteFile(new File(storePath)); } @Test public void testMigrationFromSeparateToUnifiedRocksDB() { // First, create data in separate RocksDB mode RocksDBConsumerOffsetManager separateManager = new RocksDBConsumerOffsetManager(brokerController, false); separateManager.load(); // Add some consumer offsets separateManager.commitOffset("client", TEST_GROUP, TEST_TOPIC, 0, 100L); separateManager.commitOffset("client", TEST_GROUP, TEST_TOPIC, 1, 200L); separateManager.persist(); separateManager.stop(); // Now create unified RocksDB manager which should migrate data RocksDBConsumerOffsetManager unifiedManager = new RocksDBConsumerOffsetManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully", loaded); // Verify that data was migrated ConcurrentMap migratedOffsetMap = unifiedManager.getOffsetTable().get(TEST_KEY); Assert.assertNotNull("Consumer offset should be migrated", migratedOffsetMap); Assert.assertEquals("Offset for queue 0 should match", Long.valueOf(100L), migratedOffsetMap.get(0)); Assert.assertEquals("Offset for queue 1 should match", Long.valueOf(200L), migratedOffsetMap.get(1)); unifiedManager.commitOffset("client", TEST_GROUP, TEST_TOPIC, 0, 300L); unifiedManager.commitOffset("client", TEST_GROUP, TEST_TOPIC, 1, 400L); unifiedManager.persist(); unifiedManager.stop(); // reload unified RocksDB manager which should not migrate data unifiedManager = new RocksDBConsumerOffsetManager(brokerController, true); unifiedManager.load(); // Verify that data was new migratedOffsetMap = unifiedManager.getOffsetTable().get(TEST_KEY); Assert.assertEquals("Offset for queue 0 should match", Long.valueOf(300L), migratedOffsetMap.get(0)); Assert.assertEquals("Offset for queue 1 should match", Long.valueOf(400L), migratedOffsetMap.get(1)); unifiedManager.stop(); } @Test public void testMigrationWithNoSeparateRocksDB() { // Ensure separate RocksDB doesn't exist UtilAll.deleteFile(new File(separateRocksDBPath)); // Create unified RocksDB manager - should not fail even without separate DB RocksDBConsumerOffsetManager unifiedManager = new RocksDBConsumerOffsetManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully even without separate DB", loaded); unifiedManager.stop(); } @Test public void testNoMigrationWhenDisabled() { // Create data in separate RocksDB mode RocksDBConsumerOffsetManager separateManager = new RocksDBConsumerOffsetManager(brokerController, false); separateManager.load(); separateManager.commitOffset("client", TEST_GROUP, TEST_TOPIC, 0, 100L); separateManager.commitOffset("client", TEST_GROUP, TEST_TOPIC, 1, 200L); separateManager.persist(); separateManager.stop(); long version = separateManager.getDataVersion().getCounter().get(); Assert.assertEquals(2, version); // Create another separate manager - should not trigger migration RocksDBConsumerOffsetManager anotherSeparateManager = new RocksDBConsumerOffsetManager(brokerController, false); boolean loaded = anotherSeparateManager.load(); Assert.assertTrue("Separate manager should load successfully", loaded); anotherSeparateManager.loadDataVersion(); Assert.assertEquals(version, anotherSeparateManager.getDataVersion().getCounter().get()); anotherSeparateManager.stop(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v1/RocksDBSubscriptionGroupManagerMigrationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import java.io.File; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class RocksDBSubscriptionGroupManagerMigrationTest { private static final String TEST_GROUP = "TestGroup"; private BrokerController brokerController; private String storePath; private String separateRocksDBPath; private String unifiedRocksDBPath; @Before public void init() { brokerController = Mockito.mock(BrokerController.class); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); storePath = System.getProperty("java.io.tmpdir") + File.separator + "rocketmq-test-" + System.currentTimeMillis(); messageStoreConfig.setStorePathRootDir(storePath); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); separateRocksDBPath = storePath + File.separator + "config" + File.separator + "subscriptionGroups" + File.separator; unifiedRocksDBPath = storePath + File.separator + "config" + File.separator + "metadata" + File.separator; // Create directories UtilAll.ensureDirOK(separateRocksDBPath); UtilAll.ensureDirOK(unifiedRocksDBPath); } @After public void destroy() { // Clean up test directories UtilAll.deleteFile(new File(storePath)); } @Test public void testMigrationFromSeparateToUnifiedRocksDB() { // First, create data in separate RocksDB mode RocksDBSubscriptionGroupManager separateManager = new RocksDBSubscriptionGroupManager(brokerController, false); separateManager.load(); // Add some subscription groups SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName(TEST_GROUP); groupConfig.setConsumeEnable(true); groupConfig.setConsumeFromMinEnable(true); groupConfig.setRetryMaxTimes(3); separateManager.updateSubscriptionGroupConfig(groupConfig); separateManager.persist(); separateManager.stop(); { // Now create unified RocksDB manager which should migrate data RocksDBSubscriptionGroupManager unifiedManager = new RocksDBSubscriptionGroupManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully", loaded); // Verify that data was migrated SubscriptionGroupConfig migratedConfig = unifiedManager.findSubscriptionGroupConfig(TEST_GROUP); Assert.assertNotNull("Subscription group should be migrated", migratedConfig); Assert.assertEquals("Group name should match", TEST_GROUP, migratedConfig.getGroupName()); Assert.assertEquals("Retry max times should match", 3, migratedConfig.getRetryMaxTimes()); Assert.assertTrue("Consume enable should match", migratedConfig.isConsumeEnable()); Assert.assertTrue("Consume from min enable should match", migratedConfig.isConsumeFromMinEnable()); groupConfig.setRetryMaxTimes(4); unifiedManager.updateSubscriptionGroupConfig(groupConfig); unifiedManager.persist(); unifiedManager.stop(); } { // Now create unified RocksDB manager which should migrate data RocksDBSubscriptionGroupManager unifiedManager = new RocksDBSubscriptionGroupManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully", loaded); // Verify that data was migrated SubscriptionGroupConfig migratedConfig = unifiedManager.findSubscriptionGroupConfig(TEST_GROUP); Assert.assertNotNull("Subscription group should be migrated", migratedConfig); Assert.assertEquals("Group name should match", TEST_GROUP, migratedConfig.getGroupName()); Assert.assertEquals("Retry max times should match", 4, migratedConfig.getRetryMaxTimes()); Assert.assertTrue("Consume enable should match", migratedConfig.isConsumeEnable()); Assert.assertTrue("Consume from min enable should match", migratedConfig.isConsumeFromMinEnable()); unifiedManager.stop(); } } @Test public void testMigrationWithNoSeparateRocksDB() { // Ensure separate RocksDB doesn't exist UtilAll.deleteFile(new File(separateRocksDBPath)); // Create unified RocksDB manager - should not fail even without separate DB RocksDBSubscriptionGroupManager unifiedManager = new RocksDBSubscriptionGroupManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully even without separate DB", loaded); unifiedManager.stop(); } @Test public void testNoMigrationWhenDisabled() { // Create data in separate RocksDB mode RocksDBSubscriptionGroupManager separateManager = new RocksDBSubscriptionGroupManager(brokerController, false); separateManager.load(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName(TEST_GROUP); groupConfig.setConsumeEnable(true); groupConfig.setConsumeFromMinEnable(true); separateManager.putSubscriptionGroupConfig(groupConfig); separateManager.persist(); separateManager.stop(); // Create another separate manager - should not trigger migration RocksDBSubscriptionGroupManager anotherSeparateManager = new RocksDBSubscriptionGroupManager(brokerController, false); boolean loaded = anotherSeparateManager.load(); Assert.assertTrue("Separate manager should load successfully", loaded); anotherSeparateManager.stop(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v1/RocksDBSubscriptionGroupManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.nio.charset.StandardCharsets; import java.util.concurrent.ConcurrentMap; import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RocksDBSubscriptionGroupManagerTest { @Mock private BrokerController brokerController; @Mock private RocksDBConfigManager rocksDBConfigManager; private RocksDBSubscriptionGroupManager rocksDBSubscriptionGroupManager; @Mock private MessageStoreConfig messageStoreConfig; @Before public void init() throws IllegalAccessException { when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(messageStoreConfig.getMemTableFlushIntervalMs()).thenReturn(1000L); when(messageStoreConfig.getRocksdbCompressionType()).thenReturn("LZ4_COMPRESSION"); when(messageStoreConfig.getStorePathRootDir()).thenReturn("/"); BrokerConfig brokerConfig = mock(BrokerConfig.class); when(brokerConfig.isUseSingleRocksDBForAllConfigs()).thenReturn(true); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); rocksDBSubscriptionGroupManager = new RocksDBSubscriptionGroupManager(brokerController); FieldUtils.writeDeclaredField(rocksDBSubscriptionGroupManager, "rocksDBConfigManager", rocksDBConfigManager, true); } @Test public void testPutSubscriptionGroupConfig() { SubscriptionGroupConfig newConfig = new SubscriptionGroupConfig(); newConfig.setGroupName("group"); SubscriptionGroupConfig oldConfig = new SubscriptionGroupConfig(); oldConfig.setGroupName("group"); rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().put("group", oldConfig); assertEquals(oldConfig, rocksDBSubscriptionGroupManager.putSubscriptionGroupConfig(newConfig)); assertEquals(newConfig, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().get("group")); } @Test public void testPutSubscriptionGroupConfigIfAbsent() { SubscriptionGroupConfig newConfig = new SubscriptionGroupConfig(); newConfig.setGroupName("group"); SubscriptionGroupConfig oldConfig = new SubscriptionGroupConfig(); oldConfig.setGroupName("group"); assertNull(rocksDBSubscriptionGroupManager.putSubscriptionGroupConfigIfAbsent(newConfig)); assertEquals(newConfig, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().get("group")); } @Test public void testDecodeForbidden() { String forbiddenGroupName = "group"; String bodyJson = "{\"topic1\":1,\"topic2\":2}"; byte[] key = forbiddenGroupName.getBytes(StandardCharsets.UTF_8); byte[] body = bodyJson.getBytes(StandardCharsets.UTF_8); rocksDBSubscriptionGroupManager.decodeForbidden(key, body); ConcurrentMap> forbiddenTable = rocksDBSubscriptionGroupManager.getForbiddenTable(); assertTrue(forbiddenTable.containsKey(forbiddenGroupName)); ConcurrentMap forbiddenGroup = forbiddenTable.get(forbiddenGroupName); assertEquals(2, forbiddenGroup.size()); assertEquals(Integer.valueOf(1), forbiddenGroup.get("topic1")); assertEquals(Integer.valueOf(2), forbiddenGroup.get("topic2")); } @Test public void testDecodeSubscriptionGroup() { String groupName = "group"; String bodyJson = "{\"groupName\":\"group\",\"consumeEnable\":true}"; byte[] key = groupName.getBytes(StandardCharsets.UTF_8); byte[] body = bodyJson.getBytes(StandardCharsets.UTF_8); rocksDBSubscriptionGroupManager.decodeSubscriptionGroup(key, body); ConcurrentMap subscriptionGroupTable = rocksDBSubscriptionGroupManager.getSubscriptionGroupTable(); assertEquals(1, subscriptionGroupTable.size()); SubscriptionGroupConfig config = subscriptionGroupTable.get(groupName); assertEquals(groupName, config.getGroupName()); assertTrue(config.isConsumeEnable()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v1/RocksDBTopicConfigManagerMigrationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import java.io.File; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class RocksDBTopicConfigManagerMigrationTest { private static final String TEST_TOPIC = "TestTopic"; private BrokerController brokerController; private String storePath; private String separateRocksDBPath; private String unifiedRocksDBPath; @Before public void init() { brokerController = Mockito.mock(BrokerController.class); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); storePath = System.getProperty("java.io.tmpdir") + File.separator + "rocketmq-test-" + System.currentTimeMillis(); messageStoreConfig.setStorePathRootDir(storePath); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.when(brokerController.getBrokerConfig()).thenReturn(new BrokerConfig()); separateRocksDBPath = storePath + File.separator + "config" + File.separator + "topics" + File.separator; unifiedRocksDBPath = storePath + File.separator + "config" + File.separator + "metadata" + File.separator; // Create directories UtilAll.ensureDirOK(separateRocksDBPath); UtilAll.ensureDirOK(unifiedRocksDBPath); } @After public void destroy() { // Clean up test directories UtilAll.deleteFile(new File(storePath)); } @Test public void testMigrationFromSeparateToUnifiedRocksDB() { // First, create data in separate RocksDB mode RocksDBTopicConfigManager separateManager = new RocksDBTopicConfigManager(brokerController, false); separateManager.load(); // Add some topic configs TopicConfig topicConfig = new TopicConfig(TEST_TOPIC, 4, 4); separateManager.updateTopicConfig(topicConfig); separateManager.persist(); separateManager.stop(); { // Now create unified RocksDB manager which should migrate data RocksDBTopicConfigManager unifiedManager = new RocksDBTopicConfigManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully", loaded); // Verify that data was migrated TopicConfig migratedConfig = unifiedManager.selectTopicConfig(TEST_TOPIC); Assert.assertNotNull("Topic config should be migrated", migratedConfig); Assert.assertEquals("Topic name should match", TEST_TOPIC, migratedConfig.getTopicName()); Assert.assertEquals("Read queue num should match", 4, migratedConfig.getReadQueueNums()); Assert.assertEquals("Write queue num should match", 4, migratedConfig.getWriteQueueNums()); topicConfig.setReadQueueNums(8); topicConfig.setWriteQueueNums(8); unifiedManager.updateTopicConfig(topicConfig); unifiedManager.persist(); unifiedManager.stop(); } { // Now create unified RocksDB manager which should migrate data RocksDBTopicConfigManager unifiedManager = new RocksDBTopicConfigManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully", loaded); // Verify that data was migrated TopicConfig migratedConfig = unifiedManager.selectTopicConfig(TEST_TOPIC); Assert.assertNotNull("Topic config should be migrated", migratedConfig); Assert.assertEquals("Topic name should match", TEST_TOPIC, migratedConfig.getTopicName()); Assert.assertEquals("Read queue num should match", 8, migratedConfig.getReadQueueNums()); Assert.assertEquals("Write queue num should match", 8, migratedConfig.getWriteQueueNums()); unifiedManager.stop(); } } @Test public void testMigrationWithNoSeparateRocksDB() { // Ensure separate RocksDB doesn't exist UtilAll.deleteFile(new File(separateRocksDBPath)); // Create unified RocksDB manager - should not fail even without separate DB RocksDBTopicConfigManager unifiedManager = new RocksDBTopicConfigManager(brokerController, true); boolean loaded = unifiedManager.load(); Assert.assertTrue("Unified manager should load successfully even without separate DB", loaded); unifiedManager.stop(); } @Test public void testNoMigrationWhenDisabled() { // Create data in separate RocksDB mode RocksDBTopicConfigManager separateManager = new RocksDBTopicConfigManager(brokerController, false); separateManager.load(); TopicConfig topicConfig = new TopicConfig(TEST_TOPIC, 4, 4); separateManager.putTopicConfig(topicConfig); separateManager.persist(); separateManager.stop(); // Create another separate manager - should not trigger migration RocksDBTopicConfigManager anotherSeparateManager = new RocksDBTopicConfigManager(brokerController, false); boolean loaded = anotherSeparateManager.load(); Assert.assertTrue("Separate manager should load successfully", loaded); anotherSeparateManager.stop(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v1/RocksDBTopicConfigManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v1; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.nio.charset.StandardCharsets; import java.util.concurrent.ConcurrentMap; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RocksDBTopicConfigManagerTest { @Mock private BrokerController brokerController; @Mock private MessageStoreConfig messageStoreConfig; @Mock private RocksDBConfigManager rocksDBConfigManager; private RocksDBTopicConfigManager rocksDBTopicConfigManager; @Before public void init() throws Exception { when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(messageStoreConfig.getMemTableFlushIntervalMs()).thenReturn(1000L); when(messageStoreConfig.getRocksdbCompressionType()).thenReturn("LZ4_COMPRESSION"); when(messageStoreConfig.getStorePathRootDir()).thenReturn("/"); BrokerConfig brokerConfig = mock(BrokerConfig.class); when(brokerConfig.isUseSingleRocksDBForAllConfigs()).thenReturn(true); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); rocksDBTopicConfigManager = new RocksDBTopicConfigManager(brokerController); FieldUtils.writeDeclaredField(rocksDBTopicConfigManager, "rocksDBConfigManager", rocksDBConfigManager, true); } @Test public void testDecodeTopicConfig() { String topicName = "testTopic"; String topicConfigJson = "{\"topicName\":\"testTopic\",\"readQueueNums\":10,\"writeQueueNums\":10}"; byte[] key = topicName.getBytes(StandardCharsets.UTF_8); byte[] body = topicConfigJson.getBytes(StandardCharsets.UTF_8); rocksDBTopicConfigManager.decodeTopicConfig(key, body); ConcurrentMap topicConfigTable = rocksDBTopicConfigManager.getTopicConfigTable(); assertNotNull(topicConfigTable); assertEquals(1, topicConfigTable.size()); TopicConfig topicConfig = topicConfigTable.get(topicName); assertNotNull(topicConfig); assertEquals(topicName, topicConfig.getTopicName()); assertEquals(10, topicConfig.getReadQueueNums()); assertEquals(10, topicConfig.getWriteQueueNums()); } @Test public void testPutTopicConfig() throws Exception { TopicConfig newTopicConfig = new TopicConfig("newTopic"); newTopicConfig.setReadQueueNums(10); newTopicConfig.setWriteQueueNums(10); assertNull(rocksDBTopicConfigManager.putTopicConfig(newTopicConfig)); verify(rocksDBConfigManager, times(1)).put(any(byte[].class), any(byte[].class)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v2/ConsumerOffsetManagerV2Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import java.io.File; import java.io.IOException; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ConsumerOffsetManagerV2Test { private ConfigStorage configStorage; private ConsumerOffsetManagerV2 consumerOffsetManagerV2; @Mock private BrokerController controller; private MessageStoreConfig messageStoreConfig; @Rule public TemporaryFolder tf = new TemporaryFolder(); @After public void cleanUp() { if (null != configStorage) { configStorage.shutdown(); } } @Before public void setUp() throws IOException { BrokerConfig brokerConfig = new BrokerConfig(); Mockito.doReturn(brokerConfig).when(controller).getBrokerConfig(); File configStoreDir = tf.newFolder(); messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(configStoreDir.getAbsolutePath()); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); consumerOffsetManagerV2 = new ConsumerOffsetManagerV2(controller, configStorage); } /** * Verify consumer offset can survive restarts */ @Test public void testCommitOffset_Standard() { Assert.assertTrue(consumerOffsetManagerV2.load()); String clientHost = "localhost"; String topic = "T1"; String group = "G0"; int queueId = 1; long queueOffset = 100; consumerOffsetManagerV2.commitOffset(clientHost, group, topic, queueId, queueOffset); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); configStorage.shutdown(); consumerOffsetManagerV2.getOffsetTable().clear(); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); consumerOffsetManagerV2 = new ConsumerOffsetManagerV2(controller, configStorage); consumerOffsetManagerV2.load(); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); } /** * Verify commit offset can survive config store restart */ @Test public void testCommitOffset_LMQ() { Assert.assertTrue(consumerOffsetManagerV2.load()); String clientHost = "localhost"; String topic = MixAll.LMQ_PREFIX + "T1"; String group = "G0"; int queueId = 1; long queueOffset = 100; consumerOffsetManagerV2.commitOffset(clientHost, group, topic, queueId, queueOffset); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); configStorage.shutdown(); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); consumerOffsetManagerV2 = new ConsumerOffsetManagerV2(controller, configStorage); consumerOffsetManagerV2.load(); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); } /** * Verify commit offset can survive config store restart */ @Test public void testCommitPullOffset_LMQ() { Assert.assertTrue(consumerOffsetManagerV2.load()); String clientHost = "localhost"; String topic = MixAll.LMQ_PREFIX + "T1"; String group = "G0"; int queueId = 1; long queueOffset = 100; consumerOffsetManagerV2.commitPullOffset(clientHost, group, topic, queueId, queueOffset); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryPullOffset(group, topic, queueId)); configStorage.shutdown(); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); consumerOffsetManagerV2 = new ConsumerOffsetManagerV2(controller, configStorage); consumerOffsetManagerV2.load(); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryPullOffset(group, topic, queueId)); } /** * Verify commit offset can survive config store restart */ @Test public void testRemoveByTopicAtGroup() { Assert.assertTrue(consumerOffsetManagerV2.load()); String clientHost = "localhost"; String topic = MixAll.LMQ_PREFIX + "T1"; String topic2 = MixAll.LMQ_PREFIX + "T2"; String group = "G0"; int queueId = 1; long queueOffset = 100; consumerOffsetManagerV2.commitOffset(clientHost, group, topic, queueId, queueOffset); consumerOffsetManagerV2.commitOffset(clientHost, group, topic2, queueId, queueOffset); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic2, queueId)); consumerOffsetManagerV2.removeConsumerOffset(topic + ConsumerOffsetManager.TOPIC_GROUP_SEPARATOR + group); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic2, queueId)); configStorage.shutdown(); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); consumerOffsetManagerV2 = new ConsumerOffsetManagerV2(controller, configStorage); consumerOffsetManagerV2.load(); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic2, queueId)); } /** * Verify commit offset can survive config store restart */ @Test public void testRemoveByGroup() { Assert.assertTrue(consumerOffsetManagerV2.load()); String clientHost = "localhost"; String topic = MixAll.LMQ_PREFIX + "T1"; String topic2 = MixAll.LMQ_PREFIX + "T2"; String group = "G0"; int queueId = 1; long queueOffset = 100; consumerOffsetManagerV2.commitOffset(clientHost, group, topic, queueId, queueOffset); consumerOffsetManagerV2.commitOffset(clientHost, group, topic2, queueId, queueOffset); Assert.assertEquals(queueOffset, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); consumerOffsetManagerV2.removeOffset(group); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic2, queueId)); configStorage.shutdown(); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); consumerOffsetManagerV2 = new ConsumerOffsetManagerV2(controller, configStorage); consumerOffsetManagerV2.load(); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic, queueId)); Assert.assertEquals(-1L, consumerOffsetManagerV2.queryOffset(group, topic2, queueId)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v2/SubscriptionGroupManagerV2Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import java.io.File; import java.io.IOException; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.subscription.GroupRetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.GroupRetryPolicyType; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class SubscriptionGroupManagerV2Test { private MessageStoreConfig messageStoreConfig; private ConfigStorage configStorage; private SubscriptionGroupManagerV2 subscriptionGroupManagerV2; @Mock private BrokerController controller; @Mock private MessageStore messageStore; @Rule public TemporaryFolder tf = new TemporaryFolder(); @After public void cleanUp() { if (null != configStorage) { configStorage.shutdown(); } } @Before public void setUp() throws IOException { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setAutoCreateSubscriptionGroup(false); Mockito.doReturn(brokerConfig).when(controller).getBrokerConfig(); Mockito.doReturn(messageStore).when(controller).getMessageStore(); Mockito.doReturn(1L).when(messageStore).getStateMachineVersion(); File configStoreDir = tf.newFolder(); messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(configStoreDir.getAbsolutePath()); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); subscriptionGroupManagerV2 = new SubscriptionGroupManagerV2(controller, configStorage); } @Test public void testUpdateSubscriptionGroupConfig() { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName("G1"); subscriptionGroupConfig.setConsumeEnable(true); subscriptionGroupConfig.setRetryMaxTimes(16); subscriptionGroupConfig.setGroupSysFlag(1); GroupRetryPolicy retryPolicy = new GroupRetryPolicy(); retryPolicy.setType(GroupRetryPolicyType.EXPONENTIAL); subscriptionGroupConfig.setGroupRetryPolicy(retryPolicy); subscriptionGroupConfig.setBrokerId(1); subscriptionGroupConfig.setConsumeBroadcastEnable(true); subscriptionGroupConfig.setConsumeMessageOrderly(true); subscriptionGroupConfig.setConsumeTimeoutMinute(30); subscriptionGroupConfig.setConsumeFromMinEnable(true); subscriptionGroupConfig.setWhichBrokerWhenConsumeSlowly(1); subscriptionGroupConfig.setNotifyConsumerIdsChangedEnable(true); subscriptionGroupManagerV2.updateSubscriptionGroupConfig(subscriptionGroupConfig); SubscriptionGroupConfig found = subscriptionGroupManagerV2.findSubscriptionGroupConfig(subscriptionGroupConfig.getGroupName()); Assert.assertEquals(subscriptionGroupConfig, found); subscriptionGroupManagerV2.getSubscriptionGroupTable().clear(); configStorage.shutdown(); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); subscriptionGroupManagerV2 = new SubscriptionGroupManagerV2(controller, configStorage); subscriptionGroupManagerV2.load(); found = subscriptionGroupManagerV2.findSubscriptionGroupConfig(subscriptionGroupConfig.getGroupName()); Assert.assertEquals(subscriptionGroupConfig, found); } @Test public void testDeleteSubscriptionGroupConfig() { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName("G1"); subscriptionGroupConfig.setConsumeEnable(true); subscriptionGroupConfig.setRetryMaxTimes(16); subscriptionGroupConfig.setGroupSysFlag(1); GroupRetryPolicy retryPolicy = new GroupRetryPolicy(); retryPolicy.setType(GroupRetryPolicyType.EXPONENTIAL); subscriptionGroupConfig.setGroupRetryPolicy(retryPolicy); subscriptionGroupConfig.setBrokerId(1); subscriptionGroupConfig.setConsumeBroadcastEnable(true); subscriptionGroupConfig.setConsumeMessageOrderly(true); subscriptionGroupConfig.setConsumeTimeoutMinute(30); subscriptionGroupConfig.setConsumeFromMinEnable(true); subscriptionGroupConfig.setWhichBrokerWhenConsumeSlowly(1); subscriptionGroupConfig.setNotifyConsumerIdsChangedEnable(true); subscriptionGroupManagerV2.updateSubscriptionGroupConfig(subscriptionGroupConfig); SubscriptionGroupConfig found = subscriptionGroupManagerV2.findSubscriptionGroupConfig(subscriptionGroupConfig.getGroupName()); Assert.assertEquals(subscriptionGroupConfig, found); subscriptionGroupManagerV2.removeSubscriptionGroupConfig(subscriptionGroupConfig.getGroupName()); found = subscriptionGroupManagerV2.findSubscriptionGroupConfig(subscriptionGroupConfig.getGroupName()); Assert.assertNull(found); configStorage.shutdown(); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); subscriptionGroupManagerV2 = new SubscriptionGroupManagerV2(controller, configStorage); subscriptionGroupManagerV2.load(); found = subscriptionGroupManagerV2.findSubscriptionGroupConfig(subscriptionGroupConfig.getGroupName()); Assert.assertNull(found); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/config/v2/TopicConfigManagerV2Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.config.v2; import java.io.File; import java.io.IOException; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(value = MockitoJUnitRunner.class) public class TopicConfigManagerV2Test { private MessageStoreConfig messageStoreConfig; private ConfigStorage configStorage; @Mock private BrokerController controller; @Mock private MessageStore messageStore; @Rule public TemporaryFolder tf = new TemporaryFolder(); @After public void cleanUp() { if (null != configStorage) { configStorage.shutdown(); } } @Before public void setUp() throws IOException { BrokerConfig brokerConfig = new BrokerConfig(); Mockito.doReturn(brokerConfig).when(controller).getBrokerConfig(); messageStoreConfig = new MessageStoreConfig(); Mockito.doReturn(messageStoreConfig).when(controller).getMessageStoreConfig(); Mockito.doReturn(messageStore).when(controller).getMessageStore(); File configStoreDir = tf.newFolder(); messageStoreConfig.setStorePathRootDir(configStoreDir.getAbsolutePath()); configStorage = new ConfigStorage(messageStoreConfig); configStorage.start(); } @Test public void testUpdateTopicConfig() { TopicConfigManagerV2 topicConfigManagerV2 = new TopicConfigManagerV2(controller, configStorage); topicConfigManagerV2.load(); TopicConfig topicConfig = new TopicConfig(); String topicName = "T1"; topicConfig.setTopicName(topicName); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setWriteQueueNums(4); topicConfig.setOrder(true); topicConfig.setTopicSysFlag(4); topicConfigManagerV2.updateTopicConfig(topicConfig); Assert.assertTrue(configStorage.shutdown()); topicConfigManagerV2.getTopicConfigTable().clear(); configStorage = new ConfigStorage(messageStoreConfig); Assert.assertTrue(configStorage.start()); topicConfigManagerV2 = new TopicConfigManagerV2(controller, configStorage); Assert.assertTrue(topicConfigManagerV2.load()); TopicConfig loaded = topicConfigManagerV2.selectTopicConfig(topicName); Assert.assertNotNull(loaded); Assert.assertEquals(topicName, loaded.getTopicName()); Assert.assertEquals(6, loaded.getPerm()); Assert.assertEquals(8, loaded.getReadQueueNums()); Assert.assertEquals(4, loaded.getWriteQueueNums()); Assert.assertTrue(loaded.isOrder()); Assert.assertEquals(4, loaded.getTopicSysFlag()); Assert.assertTrue(topicConfigManagerV2.containsTopic(topicName)); } @Test public void testRemoveTopicConfig() { TopicConfig topicConfig = new TopicConfig(); String topicName = "T1"; topicConfig.setTopicName(topicName); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setWriteQueueNums(4); topicConfig.setOrder(true); topicConfig.setTopicSysFlag(4); TopicConfigManagerV2 topicConfigManagerV2 = new TopicConfigManagerV2(controller, configStorage); topicConfigManagerV2.updateTopicConfig(topicConfig); topicConfigManagerV2.removeTopicConfig(topicName); Assert.assertFalse(topicConfigManagerV2.containsTopic(topicName)); Assert.assertTrue(configStorage.shutdown()); configStorage = new ConfigStorage(messageStoreConfig); Assert.assertTrue(configStorage.start()); topicConfigManagerV2 = new TopicConfigManagerV2(controller, configStorage); Assert.assertTrue(topicConfigManagerV2.load()); Assert.assertFalse(topicConfigManagerV2.containsTopic(topicName)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerRegisterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.controller; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.slave.SlaveSynchronize; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; import org.apache.rocketmq.store.ha.autoswitch.BrokerMetadata; import org.apache.rocketmq.store.ha.autoswitch.TempBrokerMetadata; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.time.Duration; import java.util.Collections; import java.util.HashSet; import java.util.UUID; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ReplicasManagerRegisterTest { public static final String STORE_BASE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "ReplicasManagerRegisterTest"; public static final String STORE_PATH = STORE_BASE_PATH + File.separator + UUID.randomUUID(); public static final String BROKER_NAME = "default-broker"; public static final String CLUSTER_NAME = "default-cluster"; public static final String NAME_SRV_ADDR = "127.0.0.1:9999"; public static final String CONTROLLER_ADDR = "127.0.0.1:8888"; public static final BrokerConfig BROKER_CONFIG; private final HashSet syncStateSet = new HashSet<>(Collections.singletonList(1L)); @Mock private BrokerMetadata brokerMetadata; @Mock private TempBrokerMetadata tempBrokerMetadata; static { BROKER_CONFIG = new BrokerConfig(); BROKER_CONFIG.setListenPort(21030); BROKER_CONFIG.setNamesrvAddr(NAME_SRV_ADDR); BROKER_CONFIG.setControllerAddr(CONTROLLER_ADDR); BROKER_CONFIG.setSyncControllerMetadataPeriod(2 * 1000); BROKER_CONFIG.setEnableControllerMode(true); BROKER_CONFIG.setBrokerName(BROKER_NAME); BROKER_CONFIG.setBrokerClusterName(CLUSTER_NAME); } private MessageStoreConfig buildMessageStoreConfig(int id) { MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathRootDir(STORE_PATH + File.separator + id); config.setStorePathCommitLog(config.getStorePathRootDir() + File.separator + "commitLog"); config.setStorePathEpochFile(config.getStorePathRootDir() + File.separator + "epochFileCache"); config.setStorePathBrokerIdentity(config.getStorePathRootDir() + File.separator + "brokerIdentity"); return config; } private BrokerController mockedBrokerController; private DefaultMessageStore mockedMessageStore; private BrokerOuterAPI mockedBrokerOuterAPI; private AutoSwitchHAService mockedAutoSwitchHAService; private RunningFlags runningFlags = new RunningFlags(); @Before public void setUp() throws Exception { UtilAll.deleteFile(new File(STORE_BASE_PATH)); this.mockedBrokerController = Mockito.mock(BrokerController.class); this.mockedMessageStore = Mockito.mock(DefaultMessageStore.class); this.mockedBrokerOuterAPI = Mockito.mock(BrokerOuterAPI.class); this.mockedAutoSwitchHAService = Mockito.mock(AutoSwitchHAService.class); TopicConfigManager mockedTopicConfigManager = new TopicConfigManager(); when(mockedBrokerController.getBrokerOuterAPI()).thenReturn(mockedBrokerOuterAPI); when(mockedBrokerController.getMessageStore()).thenReturn(mockedMessageStore); when(mockedBrokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG); when(mockedBrokerController.getTopicConfigManager()).thenReturn(mockedTopicConfigManager); when(mockedMessageStore.getHaService()).thenReturn(mockedAutoSwitchHAService); when(mockedMessageStore.getRunningFlags()).thenReturn(runningFlags); when(mockedBrokerController.getSlaveSynchronize()).thenReturn(new SlaveSynchronize(mockedBrokerController)); when(mockedBrokerOuterAPI.getControllerMetaData(any())).thenReturn( new GetMetaDataResponseHeader("default-group", "dledger-a", CONTROLLER_ADDR, true, CONTROLLER_ADDR)); when(mockedBrokerOuterAPI.checkAddressReachable(any())).thenReturn(true); when(mockedBrokerController.getMessageStoreConfig()).thenReturn(buildMessageStoreConfig(0)); } @Test public void testBrokerRegisterSuccess() throws Exception { when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 1L)); when(mockedBrokerOuterAPI.applyBrokerId(any(), any(), anyLong(), any(), any())).thenReturn(new ApplyBrokerIdResponseHeader()); when(mockedBrokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenReturn(new Pair<>(new RegisterBrokerToControllerResponseHeader(), syncStateSet)); when(mockedBrokerOuterAPI.brokerElect(any(), any(), any(), anyLong())) .thenReturn(new Pair<>(new ElectMasterResponseHeader(1L, "127.0.0.1:13131", 1, 1), syncStateSet)); ReplicasManager replicasManager0 = new ReplicasManager(mockedBrokerController); replicasManager0.start(); await().atMost(Duration.ofMillis(1000)).until(() -> replicasManager0.getState() == ReplicasManager.State.RUNNING ); assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManager0.getRegisterState()); assertEquals(1L, replicasManager0.getBrokerControllerId().longValue()); checkMetadataFile(replicasManager0.getBrokerMetadata(), 1L); assertFalse(replicasManager0.getTempBrokerMetadata().isLoaded()); assertFalse(replicasManager0.getTempBrokerMetadata().fileExists()); replicasManager0.shutdown(); } @Test public void testBrokerRegisterSuccessAndRestartWithChangedBrokerConfig() throws Exception { when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 1L)); when(mockedBrokerOuterAPI.applyBrokerId(any(), any(), anyLong(), any(), any())).thenReturn(new ApplyBrokerIdResponseHeader()); when(mockedBrokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenReturn(new Pair<>(new RegisterBrokerToControllerResponseHeader(), syncStateSet)); when(mockedBrokerOuterAPI.brokerElect(any(), any(), any(), anyLong())).thenReturn(new Pair<>(new ElectMasterResponseHeader(1L, "127.0.0.1:13131", 1, 1), syncStateSet)); ReplicasManager replicasManager0 = new ReplicasManager(mockedBrokerController); replicasManager0.start(); await().atMost(Duration.ofMillis(1000)).until(() -> replicasManager0.getState() == ReplicasManager.State.RUNNING ); assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManager0.getRegisterState()); assertEquals(1L, replicasManager0.getBrokerControllerId().longValue()); checkMetadataFile(replicasManager0.getBrokerMetadata(), 1L); assertFalse(replicasManager0.getTempBrokerMetadata().isLoaded()); assertFalse(replicasManager0.getTempBrokerMetadata().fileExists()); replicasManager0.shutdown(); // change broker name in broker config mockedBrokerController.getBrokerConfig().setBrokerName(BROKER_NAME + "1"); ReplicasManager replicasManagerRestart = new ReplicasManager(mockedBrokerController); replicasManagerRestart.start(); assertEquals(ReplicasManager.RegisterState.CREATE_METADATA_FILE_DONE, replicasManagerRestart.getRegisterState()); mockedBrokerController.getBrokerConfig().setBrokerName(BROKER_NAME); replicasManagerRestart.shutdown(); // change cluster name in broker config mockedBrokerController.getBrokerConfig().setBrokerClusterName(CLUSTER_NAME + "1"); replicasManagerRestart = new ReplicasManager(mockedBrokerController); replicasManagerRestart.start(); assertEquals(ReplicasManager.RegisterState.CREATE_METADATA_FILE_DONE, replicasManagerRestart.getRegisterState()); mockedBrokerController.getBrokerConfig().setBrokerClusterName(CLUSTER_NAME); replicasManagerRestart.shutdown(); } @Test public void testRegisterFailedAtGetNextBrokerId() throws Exception { ReplicasManager replicasManager = new ReplicasManager(mockedBrokerController); when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenThrow(new RuntimeException()); replicasManager.start(); assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, replicasManager.getState()); assertEquals(ReplicasManager.RegisterState.INITIAL, replicasManager.getRegisterState()); assertFalse(replicasManager.getTempBrokerMetadata().fileExists()); assertFalse(replicasManager.getBrokerMetadata().fileExists()); assertNull(replicasManager.getBrokerControllerId()); replicasManager.shutdown(); } @Test public void testRegisterFailedAtCreateTempFile() throws Exception { ReplicasManager spyReplicasManager = new ReplicasManager(mockedBrokerController); when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 1L)); FieldUtils.writeDeclaredField(spyReplicasManager, "tempBrokerMetadata", tempBrokerMetadata, true); doThrow(new RuntimeException("Test exception")).when(tempBrokerMetadata).updateAndPersist(any(), any(), anyLong(), any()); spyReplicasManager.start(); assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, spyReplicasManager.getState()); assertEquals(ReplicasManager.RegisterState.INITIAL, spyReplicasManager.getRegisterState()); assertFalse(spyReplicasManager.getTempBrokerMetadata().fileExists()); assertFalse(spyReplicasManager.getBrokerMetadata().fileExists()); assertNull(spyReplicasManager.getBrokerControllerId()); spyReplicasManager.shutdown(); } @Test public void testRegisterFailedAtApplyBrokerIdFailed() throws Exception { ReplicasManager replicasManager = new ReplicasManager(mockedBrokerController); when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 1L)); when(mockedBrokerOuterAPI.applyBrokerId(any(), any(), anyLong(), any(), any())).thenThrow(new RuntimeException()); replicasManager.start(); assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, replicasManager.getState()); assertNotEquals(ReplicasManager.RegisterState.CREATE_METADATA_FILE_DONE, replicasManager.getRegisterState()); assertNotEquals(ReplicasManager.RegisterState.REGISTERED, replicasManager.getRegisterState()); replicasManager.shutdown(); assertFalse(replicasManager.getBrokerMetadata().fileExists()); assertNull(replicasManager.getBrokerControllerId()); } @Test public void testRegisterFailedAtCreateMetadataFileAndDeleteTemp() throws Exception { ReplicasManager spyReplicasManager = new ReplicasManager(mockedBrokerController); when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 1L)); when(mockedBrokerOuterAPI.applyBrokerId(any(), any(), anyLong(), any(), any())).thenReturn(new ApplyBrokerIdResponseHeader()); when(mockedBrokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenReturn(new Pair<>(new RegisterBrokerToControllerResponseHeader(), syncStateSet)); when(mockedBrokerOuterAPI.brokerElect(any(), any(), any(), anyLong())).thenReturn(new Pair<>(new ElectMasterResponseHeader(1L, "127.0.0.1:13131", 1, 1), syncStateSet)); FieldUtils.writeDeclaredField(spyReplicasManager, "brokerMetadata", brokerMetadata, true); doThrow(new RuntimeException("Test exception")).when(brokerMetadata).updateAndPersist(any(), any(), anyLong()); spyReplicasManager.start(); assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, spyReplicasManager.getState()); assertEquals(ReplicasManager.RegisterState.CREATE_TEMP_METADATA_FILE_DONE, spyReplicasManager.getRegisterState()); TempBrokerMetadata tempBrokerMetadata = spyReplicasManager.getTempBrokerMetadata(); assertTrue(tempBrokerMetadata.fileExists()); assertTrue(tempBrokerMetadata.isLoaded()); assertFalse(spyReplicasManager.getBrokerMetadata().fileExists()); assertNull(spyReplicasManager.getBrokerControllerId()); spyReplicasManager.shutdown(); // restart, we expect that this replicasManager still keep the tempMetadata and still try to finish its registering ReplicasManager replicasManagerNew = new ReplicasManager(mockedBrokerController); replicasManagerNew.start(); assertEquals(ReplicasManager.State.RUNNING, replicasManagerNew.getState()); assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManagerNew.getRegisterState()); // tempMetadata has been cleared assertFalse(replicasManagerNew.getTempBrokerMetadata().fileExists()); assertFalse(replicasManagerNew.getTempBrokerMetadata().isLoaded()); // metadata has been persisted assertTrue(replicasManagerNew.getBrokerMetadata().fileExists()); assertTrue(replicasManagerNew.getBrokerMetadata().isLoaded()); assertEquals(1L, replicasManagerNew.getBrokerMetadata().getBrokerId().longValue()); assertEquals(1L, replicasManagerNew.getBrokerControllerId().longValue()); replicasManagerNew.shutdown(); } @Test public void testRegisterFailedAtRegisterSuccess() throws Exception { ReplicasManager replicasManager = new ReplicasManager(mockedBrokerController); when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 1L)); when(mockedBrokerOuterAPI.applyBrokerId(any(), any(), anyLong(), any(), any())).thenReturn(new ApplyBrokerIdResponseHeader()); when(mockedBrokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenThrow(new RuntimeException()); when(mockedBrokerOuterAPI.brokerElect(any(), any(), any(), anyLong())).thenReturn(new Pair<>(new ElectMasterResponseHeader(1L, "127.0.0.1:13131", 1, 1), syncStateSet)); replicasManager.start(); assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, replicasManager.getState()); assertEquals(ReplicasManager.RegisterState.CREATE_METADATA_FILE_DONE, replicasManager.getRegisterState()); TempBrokerMetadata tempBrokerMetadata = replicasManager.getTempBrokerMetadata(); // temp metadata has been cleared assertFalse(tempBrokerMetadata.fileExists()); assertFalse(tempBrokerMetadata.isLoaded()); // metadata has been persisted assertTrue(replicasManager.getBrokerMetadata().fileExists()); assertTrue(replicasManager.getBrokerMetadata().isLoaded()); assertEquals(1L, replicasManager.getBrokerMetadata().getBrokerId().longValue()); assertEquals(1L, replicasManager.getBrokerControllerId().longValue()); replicasManager.shutdown(); Mockito.reset(mockedBrokerOuterAPI); when(mockedBrokerOuterAPI.brokerElect(any(), any(), any(), anyLong())) .thenReturn(new Pair<>(new ElectMasterResponseHeader(1L, "127.0.0.1:13131", 1, 1), syncStateSet)); when(mockedBrokerOuterAPI.getControllerMetaData(any())).thenReturn( new GetMetaDataResponseHeader("default-group", "dledger-a", CONTROLLER_ADDR, true, CONTROLLER_ADDR)); when(mockedBrokerOuterAPI.checkAddressReachable(any())).thenReturn(true); // restart, we expect that this replicasManager still keep the metadata and still try to finish its registering ReplicasManager replicasManagerNew = new ReplicasManager(mockedBrokerController); when(mockedBrokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenReturn(new Pair<>(new RegisterBrokerToControllerResponseHeader(), syncStateSet)); replicasManagerNew.start(); assertEquals(ReplicasManager.State.RUNNING, replicasManagerNew.getState()); assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManagerNew.getRegisterState()); // tempMetadata has been cleared assertFalse(replicasManagerNew.getTempBrokerMetadata().fileExists()); assertFalse(replicasManagerNew.getTempBrokerMetadata().isLoaded()); // metadata has been persisted assertTrue(replicasManagerNew.getBrokerMetadata().fileExists()); assertTrue(replicasManagerNew.getBrokerMetadata().isLoaded()); assertEquals(1L, replicasManagerNew.getBrokerMetadata().getBrokerId().longValue()); assertEquals(1L, replicasManagerNew.getBrokerControllerId().longValue()); replicasManagerNew.shutdown(); } private void checkMetadataFile(BrokerMetadata brokerMetadata0 ,Long brokerId) throws Exception { assertEquals(brokerId, brokerMetadata0.getBrokerId()); assertTrue(brokerMetadata0.fileExists()); BrokerMetadata brokerMetadata = new BrokerMetadata(brokerMetadata0.getFilePath()); brokerMetadata.readFromFile(); assertEquals(brokerMetadata0, brokerMetadata); } @After public void clear() { UtilAll.deleteFile(new File(STORE_BASE_PATH)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.controller; import com.google.common.collect.Lists; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.slave.SlaveSynchronize; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; import org.assertj.core.api.Assertions; import org.assertj.core.util.Sets; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ReplicasManagerTest { public static final String STORE_BASE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "ReplicasManagerTest"; public static final String STORE_PATH = STORE_BASE_PATH + File.separator + UUID.randomUUID(); @Mock private BrokerController brokerController; private ReplicasManager replicasManager; @Mock private DefaultMessageStore defaultMessageStore; private SlaveSynchronize slaveSynchronize; private AutoSwitchHAService autoSwitchHAService; private MessageStoreConfig messageStoreConfig; private GetMetaDataResponseHeader getMetaDataResponseHeader; private BrokerConfig brokerConfig; @Mock private BrokerOuterAPI brokerOuterAPI; private GetNextBrokerIdResponseHeader getNextBrokerIdResponseHeader; private ApplyBrokerIdResponseHeader applyBrokerIdResponseHeader; private RegisterBrokerToControllerResponseHeader registerBrokerToControllerResponseHeader; private ElectMasterResponseHeader brokerTryElectResponseHeader; private Pair result; private GetReplicaInfoResponseHeader getReplicaInfoResponseHeader; private SyncStateSet syncStateSet; private RunningFlags runningFlags = new RunningFlags(); private static final String OLD_MASTER_ADDRESS = "192.168.1.1"; private static final String NEW_MASTER_ADDRESS = "192.168.1.2"; private static final long BROKER_ID_1 = 1; private static final long BROKER_ID_2 = 2; private static final int OLD_MASTER_EPOCH = 2; private static final int NEW_MASTER_EPOCH = 3; private static final String GROUP = "DEFAULT_GROUP"; private static final String LEADER_ID = "leader-1"; private static final Boolean IS_LEADER = true; private static final String PEERS = "1.1.1.1"; private static final long SCHEDULE_SERVICE_EXEC_PERIOD = 5; private static final Long SYNC_STATE = 1L; private static final HashSet SYNC_STATE_SET_1 = new HashSet(Arrays.asList(BROKER_ID_1)); private static final HashSet SYNC_STATE_SET_2 = new HashSet(Arrays.asList(BROKER_ID_2)); @Before public void before() throws Exception { UtilAll.deleteFile(new File(STORE_BASE_PATH)); autoSwitchHAService = new AutoSwitchHAService(); messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(STORE_PATH); brokerConfig = new BrokerConfig(); slaveSynchronize = new SlaveSynchronize(brokerController); getMetaDataResponseHeader = new GetMetaDataResponseHeader(GROUP, LEADER_ID, OLD_MASTER_ADDRESS, IS_LEADER, PEERS); getNextBrokerIdResponseHeader = new GetNextBrokerIdResponseHeader(); getNextBrokerIdResponseHeader.setNextBrokerId(BROKER_ID_1); applyBrokerIdResponseHeader = new ApplyBrokerIdResponseHeader(); registerBrokerToControllerResponseHeader = new RegisterBrokerToControllerResponseHeader(); brokerTryElectResponseHeader = new ElectMasterResponseHeader(); brokerTryElectResponseHeader.setMasterBrokerId(BROKER_ID_1); brokerTryElectResponseHeader.setMasterAddress(OLD_MASTER_ADDRESS); brokerTryElectResponseHeader.setMasterEpoch(OLD_MASTER_EPOCH); brokerTryElectResponseHeader.setSyncStateSetEpoch(OLD_MASTER_EPOCH); getReplicaInfoResponseHeader = new GetReplicaInfoResponseHeader(); getReplicaInfoResponseHeader.setMasterAddress(OLD_MASTER_ADDRESS); getReplicaInfoResponseHeader.setMasterBrokerId(BROKER_ID_1); getReplicaInfoResponseHeader.setMasterEpoch(NEW_MASTER_EPOCH); syncStateSet = new SyncStateSet(Sets.newLinkedHashSet(SYNC_STATE), NEW_MASTER_EPOCH); result = new Pair<>(getReplicaInfoResponseHeader, syncStateSet); TopicConfigManager topicConfigManager = new TopicConfigManager(); when(defaultMessageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); when(brokerController.getMessageStore().getHaService()).thenReturn(autoSwitchHAService); when(brokerController.getMessageStore().getRunningFlags()).thenReturn(runningFlags); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getSlaveSynchronize()).thenReturn(slaveSynchronize); when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); when(brokerController.getBrokerAddr()).thenReturn(OLD_MASTER_ADDRESS); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerOuterAPI.getControllerMetaData(any())).thenReturn(getMetaDataResponseHeader); when(brokerOuterAPI.checkAddressReachable(any())).thenReturn(true); when(brokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(getNextBrokerIdResponseHeader); when(brokerOuterAPI.applyBrokerId(any(), any(), anyLong(), any(), any())).thenReturn(applyBrokerIdResponseHeader); when(brokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenReturn(new Pair<>(new RegisterBrokerToControllerResponseHeader(), SYNC_STATE_SET_1)); when(brokerOuterAPI.getReplicaInfo(any(), any())).thenReturn(result); when(brokerOuterAPI.brokerElect(any(), any(), any(), any())).thenReturn(new Pair<>(brokerTryElectResponseHeader, SYNC_STATE_SET_1)); replicasManager = new ReplicasManager(brokerController); autoSwitchHAService.init(defaultMessageStore); replicasManager.start(); // execute schedulingSyncBrokerMetadata() TimeUnit.SECONDS.sleep(SCHEDULE_SERVICE_EXEC_PERIOD); } @After public void after() { replicasManager.shutdown(); brokerController.shutdown(); UtilAll.deleteFile(new File(STORE_BASE_PATH)); } @Test public void changeBrokerRoleTest() { HashSet syncStateSetA = new HashSet<>(); syncStateSetA.add(BROKER_ID_1); HashSet syncStateSetB = new HashSet<>(); syncStateSetA.add(BROKER_ID_2); // not equal to localAddress Assertions.assertThatCode(() -> replicasManager.changeBrokerRole(BROKER_ID_2, NEW_MASTER_ADDRESS, NEW_MASTER_EPOCH, OLD_MASTER_EPOCH, syncStateSetB)) .doesNotThrowAnyException(); // equal to localAddress Assertions.assertThatCode(() -> replicasManager.changeBrokerRole(BROKER_ID_1, OLD_MASTER_ADDRESS, NEW_MASTER_EPOCH, OLD_MASTER_EPOCH, syncStateSetA)) .doesNotThrowAnyException(); } @Test public void changeToMasterTest() { HashSet syncStateSet = new HashSet<>(); syncStateSet.add(BROKER_ID_1); Assertions.assertThatCode(() -> replicasManager.changeToMaster(NEW_MASTER_EPOCH, OLD_MASTER_EPOCH, syncStateSet)).doesNotThrowAnyException(); } @Test public void changeToSlaveTest() { Assertions.assertThatCode(() -> replicasManager.changeToSlave(NEW_MASTER_ADDRESS, NEW_MASTER_EPOCH, BROKER_ID_2)) .doesNotThrowAnyException(); } @Test public void testUpdateControllerAddr() throws Exception { final String controllerAddr = "192.168.1.1"; brokerConfig.setFetchControllerAddrByDnsLookup(true); when(brokerOuterAPI.dnsLookupAddressByDomain(anyString())).thenReturn(Lists.newArrayList(controllerAddr)); Method method = ReplicasManager.class.getDeclaredMethod("updateControllerAddr"); method.setAccessible(true); method.invoke(replicasManager); List addresses = replicasManager.getControllerAddresses(); Assertions.assertThat(addresses).contains(controllerAddr); // Simulating dns resolution exceptions when(brokerOuterAPI.dnsLookupAddressByDomain(anyString())).thenReturn(new ArrayList<>()); method.invoke(replicasManager); addresses = replicasManager.getControllerAddresses(); Assertions.assertThat(addresses).contains(controllerAddr); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/failover/EscapeBridgeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.failover; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.topic.TopicRouteInfoManager; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.tieredstore.TieredMessageStore; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class EscapeBridgeTest { private EscapeBridge escapeBridge; @Mock private BrokerController brokerController; @Mock private MessageExtBrokerInner messageExtBrokerInner; private BrokerConfig brokerConfig; @Mock private DefaultMessageStore defaultMessageStore; @Mock private TieredMessageStore tieredMessageStore; private GetMessageResult getMessageResult; @Mock private DefaultMQProducer defaultMQProducer; @Mock private TopicRouteInfoManager topicRouteInfoManager; @Mock private BrokerOuterAPI brokerOuterAPI; private static final String BROKER_NAME = "broker_a"; private static final String TEST_TOPIC = "TEST_TOPIC"; private static final int DEFAULT_QUEUE_ID = 0; @Before public void before() throws Exception { brokerConfig = new BrokerConfig(); getMessageResult = new GetMessageResult(); brokerConfig.setBrokerName(BROKER_NAME); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); escapeBridge = new EscapeBridge(brokerController); messageExtBrokerInner = new MessageExtBrokerInner(); when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); when(defaultMessageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(CompletableFuture.completedFuture(getMessageResult)); when(brokerController.getTopicRouteInfoManager()).thenReturn(topicRouteInfoManager); when(topicRouteInfoManager.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(""); when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); brokerConfig.setEnableSlaveActingMaster(true); brokerConfig.setEnableRemoteEscape(true); escapeBridge.start(); defaultMQProducer.start(); } @After public void after() { escapeBridge.shutdown(); brokerController.shutdown(); defaultMQProducer.shutdown(); } @Test public void putMessageTest() { messageExtBrokerInner.setTopic(TEST_TOPIC); messageExtBrokerInner.setQueueId(DEFAULT_QUEUE_ID); messageExtBrokerInner.setBody("Hello World".getBytes(StandardCharsets.UTF_8)); // masterBroker is null final PutMessageResult result1 = escapeBridge.putMessage(messageExtBrokerInner); assert result1 != null; assert PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL.equals(result1.getPutMessageStatus()); // masterBroker is not null messageExtBrokerInner.setBody("Hello World2".getBytes(StandardCharsets.UTF_8)); when(brokerController.peekMasterBroker()).thenReturn(brokerController); Assertions.assertThatCode(() -> escapeBridge.putMessage(messageExtBrokerInner)).doesNotThrowAnyException(); when(brokerController.peekMasterBroker()).thenReturn(null); final PutMessageResult result3 = escapeBridge.putMessage(messageExtBrokerInner); assert result3 != null; assert PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL.equals(result3.getPutMessageStatus()); } @Test public void asyncPutMessageTest() { // masterBroker is null Assertions.assertThatCode(() -> escapeBridge.asyncPutMessage(messageExtBrokerInner)).doesNotThrowAnyException(); // masterBroker is not null when(brokerController.peekMasterBroker()).thenReturn(brokerController); Assertions.assertThatCode(() -> escapeBridge.asyncPutMessage(messageExtBrokerInner)).doesNotThrowAnyException(); when(brokerController.peekMasterBroker()).thenReturn(null); Assertions.assertThatCode(() -> escapeBridge.asyncPutMessage(messageExtBrokerInner)).doesNotThrowAnyException(); } @Test public void putMessageToSpecificQueueTest() { // masterBroker is null final PutMessageResult result1 = escapeBridge.putMessageToSpecificQueue(messageExtBrokerInner); assert result1 != null; assert PutMessageStatus.PUT_TO_REMOTE_BROKER_FAIL.equals(result1.getPutMessageStatus()); // masterBroker is not null when(brokerController.peekMasterBroker()).thenReturn(brokerController); Assertions.assertThatCode(() -> escapeBridge.putMessageToSpecificQueue(messageExtBrokerInner)).doesNotThrowAnyException(); } @Test public void getMessageTest() { when(brokerController.peekMasterBroker()).thenReturn(brokerController); when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(defaultMessageStore); Assertions.assertThatCode(() -> escapeBridge.putMessage(messageExtBrokerInner)).doesNotThrowAnyException(); Assertions.assertThatCode(() -> escapeBridge.getMessage(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false)).doesNotThrowAnyException(); } @Test public void getMessageAsyncTest() { when(brokerController.peekMasterBroker()).thenReturn(brokerController); when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(defaultMessageStore); Assertions.assertThatCode(() -> escapeBridge.putMessage(messageExtBrokerInner)).doesNotThrowAnyException(); Assertions.assertThatCode(() -> escapeBridge.getMessageAsync(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false)).doesNotThrowAnyException(); } @Test public void getMessageAsyncTest_localStore_getMessageAsync_null() { when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(defaultMessageStore); when(defaultMessageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(null)); Triple rst = escapeBridge.getMessageAsync(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("getMessageResult is null", rst.getMiddle()); Assert.assertFalse(rst.getRight()); // no retry } @Test public void getMessageAsyncTest_localStore_decodeNothing_DefaultMessageStore() throws Exception { when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(defaultMessageStore); for (GetMessageStatus status : GetMessageStatus.values()) { GetMessageResult getMessageResult = mockGetMessageResult(0, TEST_TOPIC, null); getMessageResult.setStatus(status); when(defaultMessageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(getMessageResult)); Triple rst = escapeBridge.getMessageAsync(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("Can not get msg", rst.getMiddle()); Assert.assertFalse(rst.getRight()); // DefaultMessageStore, no retry } } @Test public void getMessageAsyncTest_localStore_decodeNothing_TieredMessageStore() throws Exception { when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(tieredMessageStore); for (GetMessageStatus status : GetMessageStatus.values()) { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(status); when(tieredMessageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(getMessageResult)); Triple rst = escapeBridge.getMessageAsync(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("Can not get msg", rst.getMiddle()); if (GetMessageStatus.OFFSET_FOUND_NULL.equals(status)) { Assert.assertTrue(rst.getRight()); // TieredMessageStore returns OFFSET_FOUND_NULL, need retry } else { Assert.assertFalse(rst.getRight()); // other status, like DefaultMessageStore, no retry } } } @Test public void getMessageAsyncTest_localStore_message_found() throws Exception { when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(defaultMessageStore); when(defaultMessageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(mockGetMessageResult(2, TEST_TOPIC, "HW".getBytes()))); Triple rst = escapeBridge.getMessageAsync(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false).join(); Assert.assertNotNull(rst.getLeft()); Assert.assertEquals(0, rst.getLeft().getQueueOffset()); Assert.assertTrue(Arrays.equals("HW".getBytes(), rst.getLeft().getBody())); Assert.assertFalse(rst.getRight()); } @Test public void getMessageAsyncTest_remoteStore_addressNotFound() throws Exception { when(brokerController.getMessageStoreByBrokerName(any())).thenReturn(null); // just test address not found, since we have complete tests of getMessageFromRemoteAsync() when(topicRouteInfoManager.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(null); Triple rst = escapeBridge.getMessageAsync(TEST_TOPIC, 0, DEFAULT_QUEUE_ID, BROKER_NAME, false).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("brokerAddress not found", rst.getMiddle()); Assert.assertTrue(rst.getRight()); // need retry } @Test public void getMessageFromRemoteTest() { Assertions.assertThatCode(() -> escapeBridge.getMessageFromRemote(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME)).doesNotThrowAnyException(); } @Test public void getMessageFromRemoteAsyncTest() { Assertions.assertThatCode(() -> escapeBridge.getMessageFromRemoteAsync(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME)).doesNotThrowAnyException(); } @Test public void getMessageFromRemoteAsyncTest_exception_caught() throws Exception { when(brokerOuterAPI.pullMessageFromSpecificBrokerAsync(anyString(), anyString(), anyString(), anyString(), anyInt(), anyLong(), anyInt(), anyLong())) .thenThrow(new RemotingException("mock remoting exception")); Triple rst = escapeBridge.getMessageFromRemoteAsync(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("Get message from remote failed", rst.getMiddle()); Assert.assertTrue(rst.getRight()); // need retry } @Test public void getMessageFromRemoteAsyncTest_brokerAddressNotFound() throws Exception { when(topicRouteInfoManager.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(null); Triple rst = escapeBridge.getMessageFromRemoteAsync(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("brokerAddress not found", rst.getMiddle()); Assert.assertTrue(rst.getRight()); // need retry } @Test public void getMessageFromRemoteAsyncTest_message_found() throws Exception { PullResult pullResult = new PullResult(PullStatus.FOUND, 1, 1, 1, Arrays.asList(new MessageExt())); when(brokerOuterAPI.pullMessageFromSpecificBrokerAsync(anyString(), anyString(), anyString(), anyString(), anyInt(), anyLong(), anyInt(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Triple.of(pullResult, "", false))); // right value is ignored Triple rst = escapeBridge.getMessageFromRemoteAsync(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME).join(); Assert.assertNotNull(rst.getLeft()); Assert.assertTrue(StringUtils.isEmpty(rst.getMiddle())); Assert.assertFalse(rst.getRight()); // no retry } @Test public void getMessageFromRemoteAsyncTest_message_notFound() throws Exception { PullResult pullResult = new PullResult(PullStatus.NO_MATCHED_MSG, 1, 1, 1, null); when(brokerOuterAPI.pullMessageFromSpecificBrokerAsync(anyString(), anyString(), anyString(), anyString(), anyInt(), anyLong(), anyInt(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Triple.of(pullResult, "no msg", false))); Triple rst = escapeBridge.getMessageFromRemoteAsync(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("no msg", rst.getMiddle()); Assert.assertFalse(rst.getRight()); // no retry when(brokerOuterAPI.pullMessageFromSpecificBrokerAsync(anyString(), anyString(), anyString(), anyString(), anyInt(), anyLong(), anyInt(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Triple.of(null, "other resp code", true))); rst = escapeBridge.getMessageFromRemoteAsync(TEST_TOPIC, 1, DEFAULT_QUEUE_ID, BROKER_NAME).join(); Assert.assertNull(rst.getLeft()); Assert.assertEquals("other resp code", rst.getMiddle()); Assert.assertTrue(rst.getRight()); // need retry } @Test public void decodeMsgListTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(10); MappedFile mappedFile = new DefaultMappedFile(); SelectMappedBufferResult result = new SelectMappedBufferResult(0, byteBuffer, 10, mappedFile); getMessageResult.addMessage(result); Assertions.assertThatCode(() -> escapeBridge.decodeMsgList(getMessageResult, false)).doesNotThrowAnyException(); } @Test public void decodeMsgListTest_messageNotNull() throws Exception { MessageExt msg = new MessageExt(); msg.setBody("HW".getBytes()); msg.setTopic("topic"); msg.setBornHost(new InetSocketAddress("127.0.0.1", 9000)); msg.setStoreHost(new InetSocketAddress("127.0.0.1", 9000)); ByteBuffer byteBuffer = ByteBuffer.wrap(MessageDecoder.encode(msg, false)); SelectMappedBufferResult result = new SelectMappedBufferResult(0, byteBuffer, 10, new DefaultMappedFile()); getMessageResult.addMessage(result); getMessageResult.getMessageQueueOffset().add(0L); List list = escapeBridge.decodeMsgList(getMessageResult, false); // skip deCompressBody test Assert.assertEquals(1, list.size()); Assert.assertTrue(Arrays.equals(msg.getBody(), list.get(0).getBody())); } @Test public void testPutMessageToRemoteBroker_noSpecificBrokerName_hasRemoteBroker() throws Exception { MessageExtBrokerInner message = new MessageExtBrokerInner(); message.setTopic(TEST_TOPIC); String anotherBrokerName = "broker_b"; TopicPublishInfo publishInfo = mockTopicPublishInfo(BROKER_NAME, anotherBrokerName); when(topicRouteInfoManager.tryToFindTopicPublishInfo(anyString())).thenReturn(publishInfo); when(topicRouteInfoManager.findBrokerAddressInPublish(anotherBrokerName)).thenReturn("127.0.0.1"); escapeBridge.putMessageToRemoteBroker(message, null); verify(brokerOuterAPI).sendMessageToSpecificBroker(eq("127.0.0.1"), eq(anotherBrokerName), any(MessageExtBrokerInner.class), anyString(), anyLong()); } @Test public void testPutMessageToRemoteBroker_noSpecificBrokerName_noRemoteBroker() throws Exception { MessageExtBrokerInner message = new MessageExtBrokerInner(); message.setTopic(TEST_TOPIC); TopicPublishInfo publishInfo = mockTopicPublishInfo(BROKER_NAME); when(topicRouteInfoManager.tryToFindTopicPublishInfo(anyString())).thenReturn(publishInfo); escapeBridge.putMessageToRemoteBroker(message, null); verify(topicRouteInfoManager, times(0)).findBrokerAddressInPublish(anyString()); } @Test public void testPutMessageToRemoteBroker_specificBrokerName_equals() throws Exception { escapeBridge.putMessageToRemoteBroker(new MessageExtBrokerInner(), BROKER_NAME); verify(topicRouteInfoManager, times(0)).tryToFindTopicPublishInfo(anyString()); } @Test public void testPutMessageToRemoteBroker_specificBrokerName_addressNotFound() throws Exception { MessageExtBrokerInner message = new MessageExtBrokerInner(); message.setTopic(TEST_TOPIC); TopicPublishInfo publishInfo = mockTopicPublishInfo(BROKER_NAME); when(topicRouteInfoManager.tryToFindTopicPublishInfo(anyString())).thenReturn(publishInfo); escapeBridge.putMessageToRemoteBroker(message, "whatever"); verify(topicRouteInfoManager).findBrokerAddressInPublish(eq("whatever")); verify(brokerOuterAPI, times(0)).sendMessageToSpecificBroker(anyString(), anyString(), any(MessageExtBrokerInner.class), anyString(), anyLong()); } @Test public void testPutMessageToRemoteBroker_specificBrokerName_addressFound() throws Exception { MessageExtBrokerInner message = new MessageExtBrokerInner(); message.setTopic(TEST_TOPIC); String anotherBrokerName = "broker_b"; TopicPublishInfo publishInfo = mockTopicPublishInfo(BROKER_NAME, anotherBrokerName); when(topicRouteInfoManager.tryToFindTopicPublishInfo(anyString())).thenReturn(publishInfo); when(topicRouteInfoManager.findBrokerAddressInPublish(anotherBrokerName)).thenReturn("127.0.0.1"); escapeBridge.putMessageToRemoteBroker(message, anotherBrokerName); verify(brokerOuterAPI).sendMessageToSpecificBroker(eq("127.0.0.1"), eq(anotherBrokerName), any(MessageExtBrokerInner.class), anyString(), anyLong()); } private GetMessageResult mockGetMessageResult(int count, String topic, byte[] body) throws Exception { GetMessageResult result = new GetMessageResult(); for (int i = 0; i < count; i++) { MessageExt msg = new MessageExt(); msg.setBody(body); msg.setTopic(topic); msg.setBornHost(new InetSocketAddress("127.0.0.1", 9000)); msg.setStoreHost(new InetSocketAddress("127.0.0.1", 9000)); ByteBuffer byteBuffer = ByteBuffer.wrap(MessageDecoder.encode(msg, false)); SelectMappedBufferResult bufferResult = new SelectMappedBufferResult(0, byteBuffer, body.length, new DefaultMappedFile()); result.addMessage(bufferResult); result.getMessageQueueOffset().add(i + 0L); } return result; } private TopicPublishInfo mockTopicPublishInfo(String... brokerNames) { TopicPublishInfo topicPublishInfo = new TopicPublishInfo(); for (String brokerName : brokerNames) { topicPublishInfo.getMessageQueueList().add(new MessageQueue(TEST_TOPIC, brokerName, 0)); } return topicPublishInfo; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMapTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.filter.util.BitsArray; import org.apache.rocketmq.store.DispatchRequest; import org.junit.Test; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; public class CommitLogDispatcherCalcBitMapTest { @Test public void testDispatch_filterDataIllegal() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setEnableCalcFilterBitMap(true); ConsumerFilterManager filterManager = new ConsumerFilterManager(); filterManager.register("topic0", "CID_0", "a is not null and a >= 5", ExpressionType.SQL92, System.currentTimeMillis()); filterManager.register("topic0", "CID_1", "a is not null and a >= 15", ExpressionType.SQL92, System.currentTimeMillis()); ConsumerFilterData nullExpression = filterManager.get("topic0", "CID_0"); nullExpression.setExpression(null); nullExpression.setCompiledExpression(null); ConsumerFilterData nullBloomData = filterManager.get("topic0", "CID_1"); nullBloomData.setBloomFilterData(null); CommitLogDispatcherCalcBitMap calcBitMap = new CommitLogDispatcherCalcBitMap(brokerConfig, filterManager); for (int i = 0; i < 1; i++) { Map properties = new HashMap<>(4); properties.put("a", String.valueOf(i * 10 + 5)); String topic = "topic" + i; DispatchRequest dispatchRequest = new DispatchRequest( topic, 0, i * 100 + 123, 100, (long) ("tags" + i).hashCode(), System.currentTimeMillis(), i, null, UUID.randomUUID().toString(), 0, 0, properties ); calcBitMap.dispatch(dispatchRequest); assertThat(dispatchRequest.getBitMap()).isNotNull(); BitsArray bitsArray = BitsArray.create(dispatchRequest.getBitMap(), filterManager.getBloomFilter().getM()); for (int j = 0; j < bitsArray.bitLength(); j++) { assertThat(bitsArray.getBit(j)).isFalse(); } } } @Test public void testDispatch_blankFilterData() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setEnableCalcFilterBitMap(true); ConsumerFilterManager filterManager = new ConsumerFilterManager(); CommitLogDispatcherCalcBitMap calcBitMap = new CommitLogDispatcherCalcBitMap(brokerConfig, filterManager); for (int i = 0; i < 10; i++) { Map properties = new HashMap<>(4); properties.put("a", String.valueOf(i * 10 + 5)); String topic = "topic" + i; DispatchRequest dispatchRequest = new DispatchRequest( topic, 0, i * 100 + 123, 100, (long) ("tags" + i).hashCode(), System.currentTimeMillis(), i, null, UUID.randomUUID().toString(), 0, 0, properties ); calcBitMap.dispatch(dispatchRequest); assertThat(dispatchRequest.getBitMap()).isNull(); } } @Test public void testDispatch() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setEnableCalcFilterBitMap(true); ConsumerFilterManager filterManager = ConsumerFilterManagerTest.gen(10, 10); CommitLogDispatcherCalcBitMap calcBitMap = new CommitLogDispatcherCalcBitMap(brokerConfig, filterManager); for (int i = 0; i < 10; i++) { Map properties = new HashMap<>(4); properties.put("a", String.valueOf(i * 10 + 5)); String topic = "topic" + i; DispatchRequest dispatchRequest = new DispatchRequest( topic, 0, i * 100 + 123, 100, (long) ("tags" + i).hashCode(), System.currentTimeMillis(), i, null, UUID.randomUUID().toString(), 0, 0, properties ); calcBitMap.dispatch(dispatchRequest); assertThat(dispatchRequest.getBitMap()).isNotNull(); BitsArray bits = BitsArray.create(dispatchRequest.getBitMap()); Collection filterDatas = filterManager.get(topic); for (ConsumerFilterData filterData : filterDatas) { if (filterManager.getBloomFilter().isHit(filterData.getBloomFilterData(), bits)) { try { assertThat((Boolean) filterData.getCompiledExpression().evaluate( new MessageEvaluationContext(properties) )).isTrue(); } catch (Exception e) { e.printStackTrace(); assertThat(true).isFalse(); } } else { try { assertThat((Boolean) filterData.getCompiledExpression().evaluate( new MessageEvaluationContext(properties) )).isFalse(); } catch (Exception e) { e.printStackTrace(); assertThat(true).isFalse(); } } } } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/filter/ConsumerFilterManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ConsumerFilterManagerTest { public static ConsumerFilterManager gen(int topicCount, int consumerCount) { ConsumerFilterManager filterManager = new ConsumerFilterManager(); for (int i = 0; i < topicCount; i++) { String topic = "topic" + i; for (int j = 0; j < consumerCount; j++) { String consumer = "CID_" + j; filterManager.register(topic, consumer, expr(j), ExpressionType.SQL92, System.currentTimeMillis()); } } return filterManager; } public static String expr(int i) { return "a is not null and a > " + ((i - 1) * 10) + " and a < " + ((i + 1) * 10); } @Test public void testRegister_newExpressionCompileErrorAndRemoveOld() { ConsumerFilterManager filterManager = gen(10, 10); assertThat(filterManager.get("topic9", "CID_9")).isNotNull(); String newExpr = "a between 10,20"; assertThat(filterManager.register("topic9", "CID_9", newExpr, ExpressionType.SQL92, System.currentTimeMillis() + 1)) .isFalse(); assertThat(filterManager.get("topic9", "CID_9")).isNull(); newExpr = "a between 10 AND 20"; assertThat(filterManager.register("topic9", "CID_9", newExpr, ExpressionType.SQL92, System.currentTimeMillis() + 1)) .isTrue(); ConsumerFilterData filterData = filterManager.get("topic9", "CID_9"); assertThat(filterData).isNotNull(); assertThat(newExpr).isEqualTo(filterData.getExpression()); } @Test public void testRegister_change() { ConsumerFilterManager filterManager = gen(10, 10); ConsumerFilterData filterData; String newExpr = "a > 0 and a < 10"; filterManager.register("topic9", "CID_9", newExpr, ExpressionType.SQL92, System.currentTimeMillis() + 1); filterData = filterManager.get("topic9", "CID_9"); assertThat(newExpr).isEqualTo(filterData.getExpression()); } @Test public void testRegister() { ConsumerFilterManager filterManager = gen(10, 10); ConsumerFilterData filterData = filterManager.get("topic9", "CID_9"); assertThat(filterData).isNotNull(); assertThat(filterData.isDead()).isFalse(); // new version assertThat(filterManager.register( "topic9", "CID_9", "a is not null", ExpressionType.SQL92, System.currentTimeMillis() + 1000 )).isTrue(); ConsumerFilterData newFilter = filterManager.get("topic9", "CID_9"); assertThat(newFilter).isNotEqualTo(filterData); // same version assertThat(filterManager.register( "topic9", "CID_9", "a is null", ExpressionType.SQL92, newFilter.getClientVersion() )).isFalse(); ConsumerFilterData filterData1 = filterManager.get("topic9", "CID_9"); assertThat(newFilter).isEqualTo(filterData1); } @Test public void testRegister_reAlive() { ConsumerFilterManager filterManager = gen(10, 10); ConsumerFilterData filterData = filterManager.get("topic9", "CID_9"); assertThat(filterData).isNotNull(); assertThat(filterData.isDead()).isFalse(); //make dead filterManager.unRegister("CID_9"); //reAlive filterManager.register( filterData.getTopic(), filterData.getConsumerGroup(), filterData.getExpression(), filterData.getExpressionType(), System.currentTimeMillis() ); ConsumerFilterData newFilterData = filterManager.get("topic9", "CID_9"); assertThat(newFilterData).isNotNull(); assertThat(newFilterData.isDead()).isFalse(); } @Test public void testRegister_bySubscriptionData() { ConsumerFilterManager filterManager = new ConsumerFilterManager(); List subscriptionDatas = new ArrayList<>(); for (int i = 0; i < 10; i++) { try { subscriptionDatas.add( FilterAPI.build( "topic" + i, "a is not null and a > " + i, ExpressionType.SQL92 ) ); } catch (Exception e) { e.printStackTrace(); assertThat(true).isFalse(); } } filterManager.register("CID_0", subscriptionDatas); Collection filterDatas = filterManager.getByGroup("CID_0"); assertThat(filterDatas).isNotNull(); assertThat(filterDatas.size()).isEqualTo(10); Iterator iterator = filterDatas.iterator(); while (iterator.hasNext()) { ConsumerFilterData filterData = iterator.next(); assertThat(filterData).isNotNull(); assertThat(filterManager.getBloomFilter().isValid(filterData.getBloomFilterData())).isTrue(); } } @Test public void testRegister_tag() { ConsumerFilterManager filterManager = new ConsumerFilterManager(); assertThat(filterManager.register("topic0", "CID_0", "*", null, System.currentTimeMillis())).isFalse(); Collection filterDatas = filterManager.getByGroup("CID_0"); assertThat(filterDatas).isNullOrEmpty(); } @Test public void testUnregister() { ConsumerFilterManager filterManager = gen(10, 10); ConsumerFilterData filterData = filterManager.get("topic9", "CID_9"); assertThat(filterData).isNotNull(); assertThat(filterData.isDead()).isFalse(); filterManager.unRegister("CID_9"); assertThat(filterData.isDead()).isTrue(); } @Test public void testPersist() { ConsumerFilterManager filterManager = gen(10, 10); try { filterManager.persist(); ConsumerFilterData filterData = filterManager.get("topic9", "CID_9"); assertThat(filterData).isNotNull(); assertThat(filterData.isDead()).isFalse(); ConsumerFilterManager loadFilter = new ConsumerFilterManager(); assertThat(loadFilter.load()).isTrue(); filterData = loadFilter.get("topic9", "CID_9"); assertThat(filterData).isNotNull(); assertThat(filterData.isDead()).isTrue(); assertThat(filterData.getCompiledExpression()).isNotNull(); } finally { UtilAll.deleteFile(new File("./unit_test")); } } @Test public void testPersist_clean() { ConsumerFilterManager filterManager = gen(10, 10); String topic = "topic9"; for (int i = 0; i < 10; i++) { String cid = "CID_" + i; ConsumerFilterData filterData = filterManager.get(topic, cid); assertThat(filterData).isNotNull(); assertThat(filterData.isDead()).isFalse(); //make dead more than 24h filterData.setBornTime(System.currentTimeMillis() - 26 * 60 * 60 * 1000); filterData.setDeadTime(System.currentTimeMillis() - 25 * 60 * 60 * 1000); } try { filterManager.persist(); ConsumerFilterManager loadFilter = new ConsumerFilterManager(); assertThat(loadFilter.load()).isTrue(); ConsumerFilterData filterData = loadFilter.get(topic, "CID_9"); assertThat(filterData).isNull(); Collection topicData = loadFilter.get(topic); assertThat(topicData).isNullOrEmpty(); } finally { UtilAll.deleteFile(new File("./unit_test")); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.filter; import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.CommitLogDispatcher; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageArrivingListener; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.awaitility.core.ThrowingRunnable; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; public class MessageStoreWithFilterTest { private static final String MSG = "Once, there was a chance for me!"; private static final byte[] MSG_BODY = MSG.getBytes(); private static final String TOPIC = "topic"; private static final int QUEUE_ID = 0; private static final String STORE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "unit_test_store"; private static final int COMMIT_LOG_FILE_SIZE = 1024 * 1024 * 256; private static final int CQ_FILE_SIZE = 300000 * 20; private static final int CQ_EXT_FILE_SIZE = 300000 * 128; private static SocketAddress bornHost; private static SocketAddress storeHost; private DefaultMessageStore master; private ConsumerFilterManager filterManager; private int topicCount = 3; private int msgPerTopic = 30; static { try { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); } catch (UnknownHostException e) { } try { bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (UnknownHostException e) { } } @Before public void init() throws Exception { filterManager = ConsumerFilterManagerTest.gen(topicCount, msgPerTopic); master = gen(filterManager); } @After public void destroy() { if (master != null) { master.shutdown(); master.destroy(); } UtilAll.deleteFile(new File(STORE_PATH)); } public MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(TOPIC); msg.setTags(System.currentTimeMillis() + "TAG"); msg.setKeys("Hello"); msg.setBody(MSG_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); for (int i = 1; i < 3; i++) { msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); } msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } public MessageStoreConfig buildStoreConfig(int commitLogFileSize, int cqFileSize, boolean enableCqExt, int cqExtFileSize) { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(commitLogFileSize); messageStoreConfig.setMappedFileSizeConsumeQueue(cqFileSize); messageStoreConfig.setMappedFileSizeConsumeQueueExt(cqExtFileSize); messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(enableCqExt); messageStoreConfig.setStorePathRootDir(STORE_PATH); messageStoreConfig.setStorePathCommitLog(STORE_PATH + File.separator + "commitlog"); return messageStoreConfig; } protected DefaultMessageStore gen(ConsumerFilterManager filterManager) throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setEnableCalcFilterBitMap(true); brokerConfig.setMaxErrorRateOfBloomFilter(20); brokerConfig.setExpectConsumerNumUseFilter(64); DefaultMessageStore master = new DefaultMessageStore( messageStoreConfig, new BrokerStatsManager(brokerConfig.getBrokerClusterName(), brokerConfig.isEnableDetailStat()), new MessageArrivingListener() { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } , brokerConfig, new ConcurrentHashMap<>()); master.getDispatcherList().addFirst(new CommitLogDispatcher() { @Override public void dispatch(DispatchRequest request) { try { } catch (Throwable e) { e.printStackTrace(); } } }); master.getDispatcherList().addFirst(new CommitLogDispatcherCalcBitMap(brokerConfig, filterManager)); if (MixAll.isWindows()) { Assume.assumeTrue(master.load()); } else { assertThat(master.load()).isTrue(); } master.start(); return master; } protected List putMsg(DefaultMessageStore master, int topicCount, int msgCountPerTopic) throws Exception { List msgs = new ArrayList<>(); for (int i = 0; i < topicCount; i++) { String realTopic = TOPIC + i; for (int j = 0; j < msgCountPerTopic; j++) { MessageExtBrokerInner msg = buildMessage(); msg.setTopic(realTopic); msg.putUserProperty("a", String.valueOf(j * 10 + 5)); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); PutMessageResult result = master.putMessage(msg); msg.setMsgId(result.getAppendMessageResult().getMsgId()); msgs.add(msg); } } return msgs; } protected List filtered(List msgs, ConsumerFilterData filterData) { List filteredMsgs = new ArrayList<>(); for (MessageExtBrokerInner messageExtBrokerInner : msgs) { if (!messageExtBrokerInner.getTopic().equals(filterData.getTopic())) { continue; } try { Object evlRet = filterData.getCompiledExpression().evaluate(new MessageEvaluationContext(messageExtBrokerInner.getProperties())); if (evlRet == null || !(evlRet instanceof Boolean) || (Boolean) evlRet) { filteredMsgs.add(messageExtBrokerInner); } } catch (Exception e) { e.printStackTrace(); assertThat(true).isFalse(); } } return filteredMsgs; } @Test public void testGetMessage_withFilterBitMapAndConsumerChanged() throws Exception { List msgs = putMsg(master, topicCount, msgPerTopic); Thread.sleep(200); // reset consumer; String topic = "topic" + 0; String resetGroup = "CID_" + 2; String normalGroup = "CID_" + 3; { // reset CID_2@topic0 to get all messages. SubscriptionData resetSubData = new SubscriptionData(); resetSubData.setExpressionType(ExpressionType.SQL92); resetSubData.setTopic(topic); resetSubData.setClassFilterMode(false); resetSubData.setSubString("a is not null OR a is null"); ConsumerFilterData resetFilterData = ConsumerFilterManager.build(topic, resetGroup, resetSubData.getSubString(), resetSubData.getExpressionType(), System.currentTimeMillis()); GetMessageResult resetGetResult = master.getMessage(resetGroup, topic, QUEUE_ID, 0, 1000, new ExpressionMessageFilter(resetSubData, resetFilterData, filterManager)); try { assertThat(resetGetResult).isNotNull(); List filteredMsgs = filtered(msgs, resetFilterData); assertThat(resetGetResult.getMessageBufferList().size()).isEqualTo(filteredMsgs.size()); } finally { resetGetResult.release(); } } { ConsumerFilterData normalFilterData = filterManager.get(topic, normalGroup); assertThat(normalFilterData).isNotNull(); assertThat(normalFilterData.getBornTime()).isLessThan(System.currentTimeMillis()); SubscriptionData normalSubData = new SubscriptionData(); normalSubData.setExpressionType(normalFilterData.getExpressionType()); normalSubData.setTopic(topic); normalSubData.setClassFilterMode(false); normalSubData.setSubString(normalFilterData.getExpression()); List filteredMsgs = filtered(msgs, normalFilterData); GetMessageResult normalGetResult = master.getMessage(normalGroup, topic, QUEUE_ID, 0, 1000, new ExpressionMessageFilter(normalSubData, normalFilterData, filterManager)); try { assertThat(normalGetResult).isNotNull(); assertThat(normalGetResult.getMessageBufferList().size()).isEqualTo(filteredMsgs.size()); } finally { normalGetResult.release(); } } } @Test public void testGetMessage_withFilterBitMap() throws Exception { List msgs = putMsg(master, topicCount, msgPerTopic); Thread.sleep(100); for (int i = 0; i < topicCount; i++) { String realTopic = TOPIC + i; for (int j = 0; j < msgPerTopic; j++) { String group = "CID_" + j; ConsumerFilterData filterData = filterManager.get(realTopic, group); assertThat(filterData).isNotNull(); List filteredMsgs = filtered(msgs, filterData); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setExpressionType(filterData.getExpressionType()); subscriptionData.setTopic(filterData.getTopic()); subscriptionData.setClassFilterMode(false); subscriptionData.setSubString(filterData.getExpression()); GetMessageResult getMessageResult = master.getMessage(group, realTopic, QUEUE_ID, 0, 10000, new ExpressionMessageFilter(subscriptionData, filterData, filterManager)); String assertMsg = group + "-" + realTopic; try { assertThat(getMessageResult).isNotNull(); assertThat(GetMessageStatus.FOUND).isEqualTo(getMessageResult.getStatus()); assertThat(getMessageResult.getMessageBufferList()).isNotNull().isNotEmpty(); assertThat(getMessageResult.getMessageBufferList().size()).isEqualTo(filteredMsgs.size()); for (ByteBuffer buffer : getMessageResult.getMessageBufferList()) { MessageExt messageExt = MessageDecoder.decode(buffer.slice(), false); assertThat(messageExt).isNotNull(); Object evlRet = null; try { evlRet = filterData.getCompiledExpression().evaluate(new MessageEvaluationContext(messageExt.getProperties())); } catch (Exception e) { e.printStackTrace(); assertThat(true).isFalse(); } assertThat(evlRet).isNotNull().isEqualTo(Boolean.TRUE); // check boolean find = false; for (MessageExtBrokerInner messageExtBrokerInner : filteredMsgs) { if (messageExtBrokerInner.getMsgId().equals(messageExt.getMsgId())) { find = true; } } assertThat(find).isTrue(); } } finally { getMessageResult.release(); } } } } @Test public void testGetMessage_withFilter_checkTagsCode() throws Exception { putMsg(master, topicCount, msgPerTopic); await().atMost(3, TimeUnit.SECONDS).untilAsserted(new ThrowingRunnable() { @Override public void run() throws Throwable { for (int i = 0; i < topicCount; i++) { final String realTopic = TOPIC + i; GetMessageResult getMessageResult = master.getMessage("test", realTopic, QUEUE_ID, 0, 10000, new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { if (tagsCode != null && tagsCode <= ConsumeQueueExt.MAX_ADDR) { return false; } return true; } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return true; } }); assertThat(getMessageResult.getMessageCount()).isEqualTo(msgPerTopic); } } }); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/latency/BrokerFastFailureTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.latency; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.future.FutureTaskExt; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; public class BrokerFastFailureTest { private BrokerController brokerController; private final BrokerConfig brokerConfig = new BrokerConfig(); private MessageStore messageStore; @Before public void setUp() { brokerController = Mockito.mock(BrokerController.class); messageStore = Mockito.mock(DefaultMessageStore.class); BlockingQueue queue = new LinkedBlockingQueue<>(); Mockito.when(brokerController.getSendThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getPullThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getLitePullThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getHeartbeatThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getEndTransactionThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getAdminBrokerThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getAckThreadPoolQueue()).thenReturn(queue); Mockito.when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); Mockito.when(messageStore.isOSPageCacheBusy()).thenReturn(false); Mockito.when(brokerController.getMessageStore()).thenReturn(messageStore); } @Test public void testCleanExpiredRequestInQueue() throws Exception { BrokerFastFailure brokerFastFailure = new BrokerFastFailure(brokerController); BlockingQueue queue = new LinkedBlockingQueue<>(); brokerFastFailure.cleanExpiredRequestInQueue(queue, 1); assertThat(queue.size()).isZero(); //Normal Runnable Runnable runnable = new Runnable() { @Override public void run() { } }; queue.add(runnable); assertThat(queue.size()).isEqualTo(1); brokerFastFailure.cleanExpiredRequestInQueue(queue, 1); assertThat(queue.size()).isEqualTo(1); queue.clear(); //With expired request RequestTask expiredRequest = new RequestTask(runnable, null, null); queue.add(new FutureTaskExt<>(expiredRequest, null)); TimeUnit.MILLISECONDS.sleep(100); RequestTask requestTask = new RequestTask(runnable, null, null); queue.add(new FutureTaskExt<>(requestTask, null)); assertThat(queue.size()).isEqualTo(2); brokerFastFailure.cleanExpiredRequestInQueue(queue, 100); assertThat(queue.size()).isEqualTo(1); assertThat(((FutureTaskExt) queue.peek()).getRunnable()).isEqualTo(requestTask); } @Test public void testCleanExpiredCustomRequestInQueue() throws Exception { BrokerFastFailure brokerFastFailure = new BrokerFastFailure(brokerController); brokerFastFailure.start(); brokerConfig.setWaitTimeMillsInAckQueue(10); BlockingQueue customThreadPoolQueue = new LinkedBlockingQueue<>(); brokerFastFailure.addCleanExpiredRequestQueue(customThreadPoolQueue, () -> brokerConfig.getWaitTimeMillsInAckQueue()); Runnable runnable = new Runnable() { @Override public void run() { } }; RequestTask requestTask = new RequestTask(runnable, null, null); customThreadPoolQueue.add(new FutureTaskExt<>(requestTask, null)); Thread.sleep(2000); assertThat(customThreadPoolQueue.size()).isEqualTo(0); assertThat(requestTask.isStopRun()).isEqualTo(true); brokerConfig.setWaitTimeMillsInAckQueue(10000); RequestTask requestTask2 = new RequestTask(runnable, null, null); customThreadPoolQueue.add(new FutureTaskExt<>(requestTask2, null)); Thread.sleep(1000); assertThat(customThreadPoolQueue.size()).isEqualTo(1); assertThat(((FutureTaskExt) customThreadPoolQueue.peek()).getRunnable()).isEqualTo(requestTask2); brokerFastFailure.shutdown(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/AbstractLiteLifecycleManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBConsumerOffsetManager; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.processor.PopLiteMessageProcessor; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.MessageStore; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.apache.rocketmq.broker.offset.ConsumerOffsetManager.TOPIC_GROUP_SEPARATOR; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; import static org.mockito.Mockito.never; import static org.mockito.Mockito.atLeastOnce; @RunWith(MockitoJUnitRunner.class) public class AbstractLiteLifecycleManagerTest { private static final String PARENT_TOPIC = "parentTopic"; private static final String EXIST_LMQ_NAME = LiteUtil.toLmqName(PARENT_TOPIC, "HW"); private static final String GROUP = "group"; @Mock private BrokerController brokerController; @Mock private LiteSharding liteSharding; @Mock private MessageStore messageStore; @Mock private TopicConfigManager topicConfigManager; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private RocksDBConsumerOffsetManager consumerOffsetManager; @Mock private PopLiteMessageProcessor popLiteMessageProcessor; @Mock private ConsumerOrderInfoManager consumerOrderInfoManager; @Mock private LiteSubscriptionRegistry liteSubscriptionRegistry; private TestLiteLifecycleManager lifecycleManager; private BrokerConfig brokerConfig; private final TopicConfig topicConfig = new TopicConfig(PARENT_TOPIC, 1, 1); private final SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); private final ConcurrentMap> offsetTable = new ConcurrentHashMap<>(); @Before public void setUp() { brokerConfig = new BrokerConfig(); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(brokerController.getPopLiteMessageProcessor()).thenReturn(popLiteMessageProcessor); when(popLiteMessageProcessor.getConsumerOrderInfoManager()).thenReturn(consumerOrderInfoManager); when(brokerController.getLiteSubscriptionRegistry()).thenReturn(liteSubscriptionRegistry); topicConfig.getAttributes().put( TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), TopicMessageType.LITE.getValue()); ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); topicConfigTable.put(PARENT_TOPIC, topicConfig); when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigTable); when(topicConfigManager.selectTopicConfig(PARENT_TOPIC)).thenReturn(topicConfig); groupConfig.setGroupName(GROUP); groupConfig.setLiteBindTopic(PARENT_TOPIC); ConcurrentMap groupTable = new ConcurrentHashMap<>(); groupTable.put(GROUP, groupConfig); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(groupTable); when(consumerOffsetManager.getOffsetTable()).thenReturn(offsetTable); when(consumerOffsetManager.getPullOffsetTable()).thenReturn(offsetTable); TestLiteLifecycleManager testObject = new TestLiteLifecycleManager(brokerController, liteSharding); lifecycleManager = Mockito.spy(testObject); lifecycleManager.init(); } @After public void reset() { topicConfig.getAttributes().clear(); groupConfig.getAttributes().clear(); offsetTable.clear(); } @Test public void testIsSubscriptionActive() { when(liteSharding.shardingByLmqName(PARENT_TOPIC, EXIST_LMQ_NAME)).thenReturn(brokerConfig.getBrokerName()); Assert.assertTrue(lifecycleManager.isSubscriptionActive(PARENT_TOPIC, EXIST_LMQ_NAME)); Assert.assertFalse(lifecycleManager.isSubscriptionActive("whatever", "whatever")); when(liteSharding.shardingByLmqName(anyString(), anyString())).thenReturn(brokerConfig.getBrokerName()); Assert.assertTrue(lifecycleManager.isSubscriptionActive(PARENT_TOPIC, EXIST_LMQ_NAME)); Assert.assertTrue(lifecycleManager.isSubscriptionActive("whatever", "whatever")); when(liteSharding.shardingByLmqName(anyString(), anyString())).thenReturn("otherBrokerName"); Assert.assertTrue(lifecycleManager.isSubscriptionActive(PARENT_TOPIC, EXIST_LMQ_NAME)); Assert.assertFalse(lifecycleManager.isSubscriptionActive("whatever", "whatever")); } @Test public void testIsLmqExist() { Assert.assertTrue(lifecycleManager.isLmqExist(EXIST_LMQ_NAME)); Assert.assertFalse(lifecycleManager.isLmqExist("whatever")); } @Test public void testGetLiteTopicCount() { Assert.assertEquals(1, lifecycleManager.getLiteTopicCount(PARENT_TOPIC)); verify(lifecycleManager).collectByParentTopic(PARENT_TOPIC); Assert.assertEquals(0, lifecycleManager.getLiteTopicCount("whatever")); verify(lifecycleManager, never()).collectByParentTopic("whatever"); } @Test public void testIsLiteTopicExpired() { // not lite topic queue Assert.assertFalse(lifecycleManager.isLiteTopicExpired(PARENT_TOPIC, "whatever", 10L)); // maxOffset invalid Assert.assertFalse(lifecycleManager.isLiteTopicExpired(PARENT_TOPIC, EXIST_LMQ_NAME, 0L)); // less than minLiteTTl long mockStoreTime = System.currentTimeMillis(); when(messageStore.getMessageStoreTimeStamp(anyString(), anyInt(), anyLong())).thenReturn(mockStoreTime); Assert.assertFalse(lifecycleManager.isLiteTopicExpired(PARENT_TOPIC, EXIST_LMQ_NAME, 100L)); // topic ttl not found mockStoreTime = System.currentTimeMillis() - brokerConfig.getMinLiteTTl() - 2000; when(messageStore.getMessageStoreTimeStamp(anyString(), anyInt(), anyLong())).thenReturn(mockStoreTime); Assert.assertFalse(lifecycleManager.isLiteTopicExpired(PARENT_TOPIC, EXIST_LMQ_NAME, 100L)); // topic ttl no expiration topicConfig.getAttributes().put(TopicAttributes.LITE_EXPIRATION_ATTRIBUTE.getName(), "-1"); lifecycleManager.updateMetadata(); mockStoreTime = System.currentTimeMillis() - brokerConfig.getMinLiteTTl() - 2000; when(messageStore.getMessageStoreTimeStamp(anyString(), anyInt(), anyLong())).thenReturn(mockStoreTime); Assert.assertFalse(lifecycleManager.isLiteTopicExpired(PARENT_TOPIC, EXIST_LMQ_NAME, 100L)); // topic ttl expired topicConfig.getAttributes().put( TopicAttributes.LITE_EXPIRATION_ATTRIBUTE.getName(), "" + brokerConfig.getMinLiteTTl() / 1000 / 60); lifecycleManager.updateMetadata(); mockStoreTime = System.currentTimeMillis() - brokerConfig.getMinLiteTTl() - 2000; when(messageStore.getMessageStoreTimeStamp(anyString(), anyInt(), anyLong())).thenReturn(mockStoreTime); Assert.assertTrue(lifecycleManager.isLiteTopicExpired(PARENT_TOPIC, EXIST_LMQ_NAME, 100L)); } @Test public void testDeleteLmq() { lifecycleManager.updateMetadata(); String otherKey = "otherTopic@otherGroup"; String removeKey = EXIST_LMQ_NAME + TOPIC_GROUP_SEPARATOR + GROUP; offsetTable.put(otherKey, new ConcurrentHashMap<>()); offsetTable.put(removeKey, new ConcurrentHashMap<>()); // sharding to this broker when(liteSharding.shardingByLmqName(PARENT_TOPIC, EXIST_LMQ_NAME)).thenReturn(brokerConfig.getBrokerName()); lifecycleManager.deleteLmq(PARENT_TOPIC, EXIST_LMQ_NAME); Assert.assertTrue(offsetTable.containsKey(otherKey)); Assert.assertFalse(offsetTable.containsKey(removeKey)); verify(consumerOffsetManager).removeConsumerOffset(removeKey); verify(messageStore).deleteTopics(Collections.singleton(EXIST_LMQ_NAME)); verify(liteSubscriptionRegistry).cleanSubscription(EXIST_LMQ_NAME, false); verify(consumerOrderInfoManager, times(1)).remove(EXIST_LMQ_NAME, GROUP); // not sharding to this broker when(liteSharding.shardingByLmqName(PARENT_TOPIC, EXIST_LMQ_NAME)).thenReturn("otherBrokerName"); lifecycleManager.deleteLmq(PARENT_TOPIC, EXIST_LMQ_NAME); Assert.assertTrue(offsetTable.containsKey(otherKey)); Assert.assertFalse(offsetTable.containsKey(removeKey)); verify(consumerOffsetManager, times(2)).removeConsumerOffset(removeKey); verify(messageStore, times(2)).deleteTopics(Collections.singleton(EXIST_LMQ_NAME)); verify(liteSubscriptionRegistry, times(2)).cleanSubscription(EXIST_LMQ_NAME, false); } @Test public void testCleanExpiredLiteTopic() { String removeKey = EXIST_LMQ_NAME + TOPIC_GROUP_SEPARATOR + GROUP; when(liteSharding.shardingByLmqName(PARENT_TOPIC, EXIST_LMQ_NAME)).thenReturn(brokerConfig.getBrokerName()); lifecycleManager.cleanExpiredLiteTopic(); verify(consumerOffsetManager).removeConsumerOffset(removeKey); verify(messageStore).deleteTopics(Collections.singleton(EXIST_LMQ_NAME)); verify(liteSubscriptionRegistry).cleanSubscription(EXIST_LMQ_NAME, false); } @Test public void testCleanByParentTopic() { String removeKey = EXIST_LMQ_NAME + TOPIC_GROUP_SEPARATOR + GROUP; when(liteSharding.shardingByLmqName(PARENT_TOPIC, EXIST_LMQ_NAME)).thenReturn(brokerConfig.getBrokerName()); lifecycleManager.cleanByParentTopic(PARENT_TOPIC); verify(consumerOffsetManager).removeConsumerOffset(removeKey); verify(messageStore).deleteTopics(Collections.singleton(EXIST_LMQ_NAME)); verify(liteSubscriptionRegistry).cleanSubscription(EXIST_LMQ_NAME, false); lifecycleManager.cleanByParentTopic("whatever"); verify(lifecycleManager, never()).collectByParentTopic("whatever"); } @Test public void testRun() throws InterruptedException { brokerConfig.setLiteTtlCheckInterval(100L); when(liteSharding.shardingByLmqName(PARENT_TOPIC, EXIST_LMQ_NAME)).thenReturn(brokerConfig.getBrokerName()); lifecycleManager.start(); Thread.sleep(300); lifecycleManager.shutdown(); verify(consumerOffsetManager, atLeastOnce()).removeConsumerOffset(anyString()); verify(messageStore, atLeastOnce()).deleteTopics(Collections.singleton(EXIST_LMQ_NAME)); verify(liteSubscriptionRegistry, atLeastOnce()).cleanSubscription(EXIST_LMQ_NAME, false); } private static class TestLiteLifecycleManager extends AbstractLiteLifecycleManager { public TestLiteLifecycleManager(BrokerController brokerController, LiteSharding liteSharding) { super(brokerController, liteSharding); } @Override public long getMaxOffsetInQueue(String lmqName) { return EXIST_LMQ_NAME.equals(lmqName) ? 100 : -1; } @Override public List> collectExpiredLiteTopic() { return Collections.singletonList(new Pair<>(PARENT_TOPIC, EXIST_LMQ_NAME)); } @Override public List collectByParentTopic(String parentTopic) { return PARENT_TOPIC.equals(parentTopic) ? Collections.singletonList(EXIST_LMQ_NAME) : Collections.emptyList(); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/LiteEventDispatcherTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import com.google.common.cache.Cache; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.longpolling.PopLiteLongPollingService; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.processor.PopLiteMessageProcessor; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.LiteUtil; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListSet; import static org.apache.rocketmq.broker.lite.LiteEventDispatcher.COMPARATOR; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteEventDispatcherTest { @Mock private BrokerController brokerController; @Mock private LiteSubscriptionRegistry liteSubscriptionRegistry; @Mock private AbstractLiteLifecycleManager liteLifecycleManager; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private PopLiteMessageProcessor popLiteMessageProcessor; @Mock private PopLiteLongPollingService popLiteLongPollingService; @Mock private ConsumerOrderInfoManager consumerOrderInfoManager; @Mock private SubscriptionGroupManager subscriptionGroupManager; private BrokerConfig brokerConfig; private LiteEventDispatcher liteEventDispatcher; private ConcurrentMap clientEventMap; private Cache blacklist; @SuppressWarnings("unchecked") @Before public void setUp() throws IllegalAccessException { brokerConfig = new BrokerConfig(); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(brokerController.getPopLiteMessageProcessor()).thenReturn(popLiteMessageProcessor); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(popLiteMessageProcessor.getPopLiteLongPollingService()).thenReturn(popLiteLongPollingService); when(popLiteMessageProcessor.getConsumerOrderInfoManager()).thenReturn(consumerOrderInfoManager); LiteEventDispatcher testObject = new LiteEventDispatcher(brokerController, liteSubscriptionRegistry, liteLifecycleManager); liteEventDispatcher = Mockito.spy(testObject); liteEventDispatcher.init(); clientEventMap = (ConcurrentMap) FieldUtils.readDeclaredField(testObject, "clientEventMap", true); blacklist = (Cache) FieldUtils.readDeclaredField(testObject, "blacklist", true); } @After public void reset() { brokerConfig = new BrokerConfig(); clientEventMap.clear(); blacklist.invalidateAll(); } @Test public void testFullDispatchRequestComparator() { LiteEventDispatcher.FullDispatchRequest request1 = new LiteEventDispatcher.FullDispatchRequest("client1", "whatever", 1000); LiteEventDispatcher.FullDispatchRequest request2 = new LiteEventDispatcher.FullDispatchRequest("client2", "whatever", 2000); LiteEventDispatcher.FullDispatchRequest request3 = new LiteEventDispatcher.FullDispatchRequest("client1", "whatever", 1000); Assert.assertTrue(COMPARATOR.compare(request1, request2) < 0); Assert.assertTrue(COMPARATOR.compare(request2, request1) > 0); Assert.assertEquals(0, COMPARATOR.compare(request1, request3)); } @Test public void testFullDispatchSet() { ConcurrentSkipListSet set = new ConcurrentSkipListSet<>(COMPARATOR); LiteEventDispatcher.FullDispatchRequest request1 = new LiteEventDispatcher.FullDispatchRequest("client1", "whatever", 1000); LiteEventDispatcher.FullDispatchRequest request2 = new LiteEventDispatcher.FullDispatchRequest("client2", "whatever", 2000); LiteEventDispatcher.FullDispatchRequest request3 = new LiteEventDispatcher.FullDispatchRequest("client1", "whatever", 1000); LiteEventDispatcher.FullDispatchRequest request4 = new LiteEventDispatcher.FullDispatchRequest("client3", "whatever", 500); LiteEventDispatcher.FullDispatchRequest request5 = new LiteEventDispatcher.FullDispatchRequest("client4", "whatever", 1000); LiteEventDispatcher.FullDispatchRequest request6 = new LiteEventDispatcher.FullDispatchRequest(null, "whatever", 1000); set.add(request1); set.add(request3); set.add(request6); Assert.assertEquals(1, set.size()); Assert.assertEquals(request1, set.pollFirst()); set.clear(); set.add(request1); set.add(request2); set.add(request3); set.add(request4); set.add(request5); Assert.assertEquals(4, set.size()); Assert.assertEquals(request4, set.pollFirst()); Assert.assertEquals(request1, set.pollFirst()); Assert.assertEquals(request5, set.pollFirst()); Assert.assertEquals(request2, set.pollFirst()); } @Test public void testEventSetIterator() { LiteEventDispatcher.ClientEventSet clientEventSet = liteEventDispatcher.new ClientEventSet("group"); clientEventSet.offer("event1"); clientEventSet.offer("event2"); LiteEventDispatcher.EventSetIterator iterator = new LiteEventDispatcher.EventSetIterator(clientEventSet); Assert.assertTrue(iterator.hasNext()); Assert.assertEquals("event1", iterator.next()); Assert.assertTrue(iterator.hasNext()); Assert.assertEquals("event2", iterator.next()); Assert.assertFalse(iterator.hasNext()); } @Test public void testLiteSubscriptionIterator() { Iterator topicIterator = Arrays.asList("event1", "event2").iterator(); LiteEventDispatcher.LiteSubscriptionIterator iterator = new LiteEventDispatcher.LiteSubscriptionIterator("parentTopic", topicIterator); Assert.assertTrue(iterator.hasNext()); Assert.assertEquals("event1", iterator.next()); Assert.assertTrue(iterator.hasNext()); Assert.assertEquals("event2", iterator.next()); Assert.assertFalse(iterator.hasNext()); } @Test public void testClientEventSet_offerAndPoll() { brokerConfig.setMaxClientEventCount(3); LiteEventDispatcher.ClientEventSet clientEventSet = liteEventDispatcher.new ClientEventSet("group"); Assert.assertTrue(clientEventSet.offer("event1")); Assert.assertTrue(clientEventSet.offer("event2")); Assert.assertTrue(clientEventSet.offer("event1")); Assert.assertTrue(clientEventSet.offer("event3")); Assert.assertFalse(clientEventSet.offer("event4")); Assert.assertEquals(3, clientEventSet.size()); Assert.assertEquals("event1", clientEventSet.poll()); Assert.assertEquals("event2", clientEventSet.poll()); Assert.assertEquals("event3", clientEventSet.poll()); Assert.assertEquals(0, clientEventSet.size()); Assert.assertNull(clientEventSet.poll()); } @Test public void testClientEventSet_isLowWaterMark() { brokerConfig.setMaxClientEventCount(10); LiteEventDispatcher.ClientEventSet clientEventSet = liteEventDispatcher.new ClientEventSet("group"); Assert.assertTrue(clientEventSet.isLowWaterMark()); for (int i = 0; i < 4; i++) { clientEventSet.offer("event" + i); } Assert.assertFalse(clientEventSet.isLowWaterMark()); } @Test public void testClientEventSetMaybeBlock() throws Exception { LiteEventDispatcher.ClientEventSet clientEventSet = liteEventDispatcher.new ClientEventSet("group"); Assert.assertFalse(clientEventSet.maybeBlock()); clientEventSet.offer("event"); FieldUtils.writeDeclaredField(clientEventSet, "lastAccessTime", 0L, true); Assert.assertTrue(clientEventSet.maybeBlock()); clientEventSet.poll(); Assert.assertFalse(clientEventSet.maybeBlock()); } @Test public void testGetAllSubscriber_noSubscribers() { when(liteSubscriptionRegistry.getSubscriber("event")).thenReturn(null); Object result = liteEventDispatcher.getAllSubscriber("group", "event"); Assert.assertNull(result); } @Test @SuppressWarnings("unchecked") public void testGetAllSubscriber_singleSubscriber() { Set subscribers = new HashSet<>(); subscribers.add(new ClientGroup("clientId", "group")); when(liteSubscriptionRegistry.getSubscriber("event")).thenReturn(subscribers); Object result = liteEventDispatcher.getAllSubscriber("group", "event"); // specified Assert.assertTrue(result instanceof List); Assert.assertEquals(1, ((List) result).size()); Assert.assertEquals("clientId", ((List) result).get(0).clientId); result = liteEventDispatcher.getAllSubscriber(null, "event"); // not specified Assert.assertTrue(result instanceof List); Assert.assertEquals(1, ((List) result).size()); Assert.assertEquals("clientId", ((List) result).get(0).clientId); result = liteEventDispatcher.getAllSubscriber("otherGroup", "event"); // specified but not match Assert.assertNull(result); } @Test @SuppressWarnings("unchecked") public void testGetAllSubscriber_multipleSubscribers() { Set subscribers = new HashSet<>(); subscribers.add(new ClientGroup("clientId1", "group1")); subscribers.add(new ClientGroup("clientId2", "group1")); subscribers.add(new ClientGroup("clientId3", "group2")); when(liteSubscriptionRegistry.getSubscriber("event")).thenReturn(subscribers); Object result = liteEventDispatcher.getAllSubscriber("group1", "event"); // specified Assert.assertTrue(result instanceof List); Assert.assertEquals(2, ((List) result).size()); Assert.assertEquals("clientId1", ((List) result).get(0).clientId); result = liteEventDispatcher.getAllSubscriber("group2", "event"); // specified Assert.assertTrue(result instanceof List); Assert.assertEquals(1, ((List) result).size()); Assert.assertEquals("clientId3", ((List) result).get(0).clientId); result = liteEventDispatcher.getAllSubscriber("otherGroup", "event"); // specified but not match Assert.assertNull(result); result = liteEventDispatcher.getAllSubscriber(null, "event"); // not specified Assert.assertTrue(result instanceof Map); Assert.assertEquals(2, ((Map) result).size()); Assert.assertEquals(2, ((Map>) result).get("group1").size()); Assert.assertEquals(1, ((Map>) result).get("group2").size()); } @Test public void testTryDispatchToClient() { brokerConfig.setMaxClientEventCount(1); String clientId = "clientId"; boolean result = liteEventDispatcher.tryDispatchToClient("event1", clientId, "group"); Assert.assertTrue(result); // not in blacklist result = liteEventDispatcher.tryDispatchToClient("event2", clientId, "group"); Assert.assertFalse(result); verify(liteEventDispatcher).scheduleFullDispatch(clientId, "group", false); // in blacklist blacklist.put(clientId, Boolean.TRUE); result = liteEventDispatcher.tryDispatchToClient("event3", clientId, "group"); Assert.assertFalse(result); verify(liteEventDispatcher).scheduleFullDispatch(clientId, "group", true); blacklist.invalidate(clientId); result = liteEventDispatcher.tryDispatchToClient("event3", clientId, "group"); Assert.assertFalse(result); verify(liteEventDispatcher, times(2)).scheduleFullDispatch(clientId, "group", false); } @Test public void testSelectAndDispatch_empty_or_singleClient() { List clients = Collections.singletonList(new ClientGroup("client", "group")); // disable event mode brokerConfig.setEnableLiteEventMode(false); liteEventDispatcher.selectAndDispatch("event", clients, null); verify(liteEventDispatcher, never()).tryDispatchToClient(anyString(), anyString(), anyString()); // empty list liteEventDispatcher.selectAndDispatch("event", Collections.emptyList(), null); verify(liteEventDispatcher, never()).tryDispatchToClient(anyString(), anyString(), anyString()); // event mode brokerConfig.setMaxClientEventCount(2); brokerConfig.setEnableLiteEventMode(true); liteEventDispatcher.selectAndDispatch("event1", clients, null); liteEventDispatcher.selectAndDispatch("event2", clients, "client"); // exclude liteEventDispatcher.selectAndDispatch("event3", clients, null); verify(popLiteLongPollingService, times(2)).notifyMessageArriving("client", true, 0, "group"); } @Test public void testSelectAndDispatch_multipleClients() { brokerConfig.setMaxClientEventCount(2); String client1 = UUID.randomUUID().toString(); String client2 = UUID.randomUUID().toString(); List clients = Arrays.asList( new ClientGroup(client1, "group"), new ClientGroup(client2, "group")); // no fallback liteEventDispatcher.selectAndDispatch("event1", clients, client1); verify(popLiteLongPollingService).notifyMessageArriving(client2, true, 0, "group"); // no fallback liteEventDispatcher.selectAndDispatch("event2", clients, client2); verify(popLiteLongPollingService).notifyMessageArriving(client1, true, 0, "group"); // fallback blacklist.put(client1, Boolean.TRUE); liteEventDispatcher.selectAndDispatch("event3", clients, null); verify(popLiteLongPollingService, times(2)).notifyMessageArriving(client2, true, 0, "group"); // fallback blacklist.invalidate(client1); blacklist.put(client2, Boolean.TRUE); liteEventDispatcher.selectAndDispatch("event4", clients, null); verify(popLiteLongPollingService, times(2)).notifyMessageArriving(client1, true, 0, "group"); // queue all full liteEventDispatcher.selectAndDispatch("event5", clients, null); verify(popLiteLongPollingService, times(2)).notifyMessageArriving(client1, true, 0, "group"); verify(popLiteLongPollingService, times(2)).notifyMessageArriving(client2, true, 0, "group"); } @Test public void testDispatch() { // disable event mode brokerConfig.setEnableLiteEventMode(false); liteEventDispatcher.dispatch("group", "event", 0, 0, System.currentTimeMillis()); verify(liteEventDispatcher, never()).getAllSubscriber(anyString(), anyString()); // event mode brokerConfig.setEnableLiteEventMode(true); liteEventDispatcher.dispatch("group", "event", 1, 0, System.currentTimeMillis()); // queue id not match liteEventDispatcher.dispatch("group", "event", 0, 0, System.currentTimeMillis()); // queue name not match verify(liteEventDispatcher, never()).getAllSubscriber(anyString(), anyString()); // do dispatch liteEventDispatcher.dispatch("group", LiteUtil.toLmqName("p", "l"), 0, 0, System.currentTimeMillis()); verify(liteEventDispatcher).getAllSubscriber(anyString(), anyString()); } @Test public void testDoFullDispatch_disable_or_emptySubscription() { String clientId = "clientId"; String group = "group"; // disable event mode brokerConfig.setEnableLiteEventMode(false); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteSubscriptionRegistry, never()).getLiteSubscription(clientId); // empty subscription brokerConfig.setEnableLiteEventMode(true); when(liteSubscriptionRegistry.getLiteSubscription("clientId")).thenReturn(null); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteLifecycleManager, never()).getMaxOffsetInQueue(anyString()); } @Test public void testDoFullDispatch_maybeBlock() throws Exception { int num = 10; String clientId = "clientId"; String group = "group"; LiteSubscription subscription = new LiteSubscription(); subscription.setTopic("parentTopic"); for (int i = 0; i < num; i++) { subscription.addLiteTopic(LiteUtil.toLmqName(subscription.getTopic(), "l" + i)); } when(liteSubscriptionRegistry.getLiteSubscription(clientId)).thenReturn(subscription); // maybe block liteEventDispatcher.tryDispatchToClient("event", clientId, group); Assert.assertNotNull(clientEventMap.get(clientId)); FieldUtils.writeDeclaredField(clientEventMap.get(clientId), "lastAccessTime", 0L, true); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteEventDispatcher).scheduleFullDispatch(clientId, group, true); verify(liteLifecycleManager, never()).getMaxOffsetInQueue(anyString()); } @Test public void testDoFullDispatch_highWaterMark() throws Exception { int num = 10; String clientId = "clientId"; String group = "group"; LiteSubscription subscription = new LiteSubscription(); subscription.setTopic("parentTopic"); for (int i = 0; i < num; i++) { subscription.addLiteTopic(LiteUtil.toLmqName(subscription.getTopic(), "l" + i)); } when(liteSubscriptionRegistry.getLiteSubscription(clientId)).thenReturn(subscription); brokerConfig.setMaxClientEventCount(1); // active consuming liteEventDispatcher.tryDispatchToClient("event", clientId, group); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteEventDispatcher).scheduleFullDispatch(clientId, group, false); verify(liteLifecycleManager, never()).getMaxOffsetInQueue(anyString()); // not active consuming clientEventMap.clear(); liteEventDispatcher.tryDispatchToClient("event", clientId, group); FieldUtils.writeDeclaredField(clientEventMap.get(clientId), "lastAccessTime", System.currentTimeMillis() - 6000L, true); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteEventDispatcher).scheduleFullDispatch(clientId, group, true); verify(liteLifecycleManager, never()).getMaxOffsetInQueue(anyString()); } @Test public void testDoFullDispatch_multipleTopics() { String clientId = "clientId"; String group = "group"; String lmqName1 = "lmqName1"; String lmqName2 = "lmqName2"; String lmqName3 = "lmqName2"; LiteSubscription subscription = new LiteSubscription(); subscription.setTopic("parentTopic"); subscription.addLiteTopic(lmqName1); subscription.addLiteTopic(lmqName2); subscription.addLiteTopic(lmqName3); when(liteSubscriptionRegistry.getLiteSubscription(clientId)).thenReturn(subscription); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName1)).thenReturn(0L); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName2)).thenReturn(10L); when(consumerOffsetManager.queryOffset(group, lmqName2, 0)).thenReturn(10L); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName3)).thenReturn(10L); when(consumerOffsetManager.queryOffset(group, lmqName3, 0)).thenReturn(5L); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteLifecycleManager).getMaxOffsetInQueue(lmqName1); verify(liteLifecycleManager).getMaxOffsetInQueue(lmqName2); verify(liteLifecycleManager).getMaxOffsetInQueue(lmqName3); verify(consumerOffsetManager, never()).queryOffset(group, lmqName1, 0); verify(consumerOffsetManager).queryOffset(group, lmqName2, 0); verify(consumerOffsetManager).queryOffset(group, lmqName3, 0); verify(liteEventDispatcher, never()).scheduleFullDispatch(clientId, group, true); verify(popLiteLongPollingService, times(2)).notifyMessageArriving(clientId, true, 0, group); } @Test public void testDoFullDispatch_eventQueueFull() throws IllegalAccessException { brokerConfig.setMaxClientEventCount(2); String clientId = "clientId"; String group = "group"; String lmqName1 = "lmqName1"; String lmqName2 = "lmqName2"; String lmqName3 = "lmqName3"; LiteSubscription subscription = new LiteSubscription(); subscription.setTopic("parentTopic"); subscription.addLiteTopic(lmqName1); subscription.addLiteTopic(lmqName2); subscription.addLiteTopic(lmqName3); when(liteSubscriptionRegistry.getLiteSubscription(clientId)).thenReturn(subscription); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName1)).thenReturn(10L); when(consumerOffsetManager.queryOffset(group, lmqName1, 0)).thenReturn(5L); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName2)).thenReturn(10L); when(consumerOffsetManager.queryOffset(group, lmqName2, 0)).thenReturn(5L); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName3)).thenReturn(10L); when(consumerOffsetManager.queryOffset(group, lmqName3, 0)).thenReturn(5L); // active consuming liteEventDispatcher.doFullDispatch(clientId, group); verify(liteEventDispatcher).scheduleFullDispatch(clientId, group, false); verify(popLiteLongPollingService, times(2)).notifyMessageArriving(clientId, true, 0, group); Assert.assertNotNull(clientEventMap.get(clientId).poll()); Assert.assertNotNull(clientEventMap.get(clientId).poll()); // not active consuming FieldUtils.writeDeclaredField(clientEventMap.get(clientId), "lastAccessTime", System.currentTimeMillis() - 6000L, true); liteEventDispatcher.doFullDispatch(clientId, group); verify(liteEventDispatcher).scheduleFullDispatch(clientId, group, true); verify(popLiteLongPollingService, times(4)).notifyMessageArriving(clientId, true, 0, group); } @Test public void testDoFullDispatchByGroup() { String group = "group"; String clientId1 = "client1"; String clientId2 = "client2"; List clientIds = Arrays.asList(clientId1, clientId2); Mockito.when(liteSubscriptionRegistry.getAllClientIdByGroup(group)).thenReturn(clientIds); liteEventDispatcher.doFullDispatchByGroup(group); verify(liteSubscriptionRegistry, times(1)).getAllClientIdByGroup(group); verify(liteEventDispatcher, times(1)).doFullDispatch(clientId1, group); verify(liteEventDispatcher, times(1)).doFullDispatch(clientId2, group); } @Test public void testScan() throws Exception { String clientId = "clientId"; String group = "group"; String event = "event"; liteEventDispatcher.tryDispatchToClient(event, clientId, group); Assert.assertNotNull(clientEventMap.get(clientId)); FieldUtils.writeDeclaredField(clientEventMap.get(clientId), "lastAccessTime", 0L, true); liteEventDispatcher.scan(); verify(liteEventDispatcher).getAllSubscriber(group, event); } @Test public void testFullDispatchDeduplication() throws InterruptedException { String clientId1 = "clientId1"; String clientId2 = "clientId2"; String group = "group"; brokerConfig.setLiteEventFullDispatchDelayTime(10L); liteEventDispatcher.scheduleFullDispatch(clientId1, group, false); liteEventDispatcher.scheduleFullDispatch(clientId1, group, false); liteEventDispatcher.scheduleFullDispatch(clientId1, group, false); liteEventDispatcher.scheduleFullDispatch(clientId1, group, false); liteEventDispatcher.scheduleFullDispatch(clientId2, group, false); Thread.sleep(20L); liteEventDispatcher.scan(); verify(liteEventDispatcher, times(1)).doFullDispatch(clientId1, group); verify(liteEventDispatcher, times(1)).doFullDispatch(clientId2, group); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/LiteLifecycleManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.store.MessageStore; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import java.util.stream.IntStream; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteLifecycleManagerTest { private final static BrokerConfig BROKER_CONFIG = new BrokerConfig(); private final static ConcurrentMap TOPIC_CONFIG_TABLE = new ConcurrentHashMap<>(); private static String storePathRootDir; private static MessageStore messageStore; private static LiteLifecycleManager liteLifecycleManager; private static TopicConfig mockTopicConfig = new TopicConfig(); @BeforeClass public static void setUp() throws Exception { storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-lifecycleTest"; UtilAll.deleteFile(new File(storePathRootDir)); messageStore = LiteTestUtil.buildMessageStore(storePathRootDir, BROKER_CONFIG, TOPIC_CONFIG_TABLE, false); messageStore.load(); messageStore.start(); BrokerController brokerController = Mockito.mock(BrokerController.class); LiteSharding liteSharding = Mockito.mock(LiteSharding.class); TopicConfigManager topicConfigManager = Mockito.mock(TopicConfigManager.class); SubscriptionGroupManager subscriptionGroupManager = Mockito.mock(SubscriptionGroupManager.class); when(brokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(topicConfigManager.getTopicConfigTable()).thenReturn(TOPIC_CONFIG_TABLE); when(topicConfigManager.selectTopicConfig(anyString())).thenReturn(mockTopicConfig); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(new ConcurrentHashMap<>()); LiteLifecycleManager testObject = new LiteLifecycleManager(brokerController, liteSharding); liteLifecycleManager = Mockito.spy(testObject); liteLifecycleManager.init(); } @AfterClass public static void reset() { messageStore.shutdown(); messageStore.destroy(); UtilAll.deleteFile(new File(storePathRootDir)); } @Test public void testGetMaxOffsetInQueue() { int num = 3; String topic = UUID.randomUUID().toString(); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(topic, null)); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); Assert.assertEquals(num, liteLifecycleManager.getMaxOffsetInQueue(topic)); Assert.assertEquals(0, liteLifecycleManager.getMaxOffsetInQueue(UUID.randomUUID().toString())); } @Test public void testCollectByParentTopic() { int num = 3; String parentTopic = UUID.randomUUID().toString(); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, UUID.randomUUID().toString())); messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), UUID.randomUUID().toString())); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); List result = liteLifecycleManager.collectByParentTopic(parentTopic); Assert.assertEquals(num, result.size()); for (String lmqName : result) { Assert.assertTrue(LiteUtil.belongsTo(lmqName, parentTopic)); } result = liteLifecycleManager.collectByParentTopic(UUID.randomUUID().toString()); Assert.assertEquals(0, result.size()); } @Test public void testCollectExpiredLiteTopic() { int num = 3; String parentTopic = UUID.randomUUID().toString(); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, UUID.randomUUID().toString())); messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), null)); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); when(liteLifecycleManager.isLiteTopicExpired(anyString(), anyString(), anyLong())).thenReturn(false); List> result = liteLifecycleManager.collectExpiredLiteTopic(); Assert.assertEquals(0, result.size()); when(liteLifecycleManager.isLiteTopicExpired(eq(parentTopic), anyString(), anyLong())).thenReturn(true); result = liteLifecycleManager.collectExpiredLiteTopic(); Assert.assertEquals(num, result.size()); for (Pair pair : result) { Assert.assertEquals(parentTopic, pair.getObject1()); Assert.assertTrue(LiteUtil.belongsTo(pair.getObject2(), parentTopic)); } } @Ignore @Test public void testCleanExpiredLiteTopic() { int num = 3; String parentTopic = UUID.randomUUID().toString(); List liteTopics = IntStream.range(0, 3).mapToObj(i -> UUID.randomUUID().toString()).collect(Collectors.toList()); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, liteTopics.get(i))); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertTrue(messageStore.getQueueStore().getConsumeQueueTable().containsKey(lmqName)); } when(liteLifecycleManager.isLiteTopicExpired(eq(parentTopic), anyString(), anyLong())).thenReturn(true); liteLifecycleManager.cleanExpiredLiteTopic(); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertFalse(messageStore.getQueueStore().getConsumeQueueTable().containsKey(lmqName)); } } @Test public void testCleanByParentTopic() { int num = 3; String parentTopic = UUID.randomUUID().toString(); mockTopicConfig.getAttributes().put( TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), TopicMessageType.LITE.getValue()); List liteTopics = IntStream.range(0, 3).mapToObj(i -> UUID.randomUUID().toString()).collect(Collectors.toList()); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, liteTopics.get(i))); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertTrue(messageStore.getQueueStore().getConsumeQueueTable().containsKey(lmqName)); } liteLifecycleManager.cleanByParentTopic(parentTopic); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertFalse(messageStore.getQueueStore().getConsumeQueueTable().containsKey(lmqName)); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/LiteShardingImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import com.google.common.hash.Hashing; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.topic.TopicRouteInfoManager; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteShardingImplTest { @Mock private BrokerController brokerController; @Mock private TopicRouteInfoManager topicRouteInfoManager; private LiteShardingImpl liteSharding; @Before public void setUp() { liteSharding = new LiteShardingImpl(brokerController, topicRouteInfoManager); } /** * Test normal case: multiple MessageQueues, verify consistent hash selects correct brokerName */ @Test public void testShardingByLmqName_NormalCase() { // Prepare data String parentTopic = "TestTopic"; String liteTopic = "lite_topic"; String lmqName = LiteUtil.toLmqName(parentTopic, liteTopic); String brokerName1 = "BrokerA"; String brokerName2 = "BrokerB"; TopicPublishInfo topicPublishInfo = mock(TopicPublishInfo.class); List messageQueues = new ArrayList<>(); MessageQueue mq1 = mock(MessageQueue.class); MessageQueue mq2 = mock(MessageQueue.class); when(mq1.getBrokerName()).thenReturn(brokerName1); // when(mq2.getBrokerName()).thenReturn(brokerName2); messageQueues.add(mq1); messageQueues.add(mq2); when(topicPublishInfo.getMessageQueueList()).thenReturn(messageQueues); when(topicRouteInfoManager.tryToFindTopicPublishInfo(parentTopic)).thenReturn(topicPublishInfo); // Execute method String brokerName = liteSharding.shardingByLmqName(parentTopic, lmqName); // Verify consistent hash selected bucket int bucket = Hashing.consistentHash(liteTopic.hashCode(), messageQueues.size()); MessageQueue expectedMq = messageQueues.get(bucket); String expectedBrokerName = expectedMq.getBrokerName(); assertEquals(expectedBrokerName, brokerName); } /** * Test edge case: empty MessageQueue list should return current broker name */ @Test public void testShardingByLmqName_EmptyQueueList() { String parentTopic = "TestTopic"; String lmqName = "LmqName2"; String currentBrokerName = "CurrentBroker"; BrokerConfig brokerConfig = mock(BrokerConfig.class); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerConfig.getBrokerName()).thenReturn(currentBrokerName); TopicPublishInfo topicPublishInfo = mock(TopicPublishInfo.class); when(topicPublishInfo.getMessageQueueList()).thenReturn(new ArrayList<>()); when(topicRouteInfoManager.tryToFindTopicPublishInfo(parentTopic)).thenReturn(topicPublishInfo); String brokerName = liteSharding.shardingByLmqName(parentTopic, lmqName); assertEquals(currentBrokerName, brokerName); } /** * Test exception case: tryToFindTopicPublishInfo returns null, should return current broker name */ @Test public void testShardingByLmqName_NullTopicPublishInfo() { String parentTopic = "TestTopic"; String lmqName = "LmqName3"; String currentBrokerName = "CurrentBroker"; BrokerConfig brokerConfig = mock(BrokerConfig.class); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerConfig.getBrokerName()).thenReturn(currentBrokerName); when(topicRouteInfoManager.tryToFindTopicPublishInfo(parentTopic)).thenReturn(null); String brokerName = liteSharding.shardingByLmqName(parentTopic, lmqName); assertEquals(currentBrokerName, brokerName); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/LiteSubscriptionRegistryImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import io.netty.channel.Channel; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.orderly.QueueLevelConsumerManager; import org.apache.rocketmq.broker.processor.PopLiteMessageProcessor; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.attribute.LiteSubModel; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.OffsetOption; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_SUB_MODEL_ATTRIBUTE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class LiteSubscriptionRegistryImplTest { private LiteSubscriptionRegistryImpl registry; private LiteCtlListener mockListener; private AbstractLiteLifecycleManager mockLifecycleManager; private BrokerConfig mockBrokerConfig; private SubscriptionGroupManager mockSubscriptionGroupManager; private ConsumerOffsetManager mockConsumerOffsetManager; @Before public void setUp() { BrokerController mockBrokerController = mock(BrokerController.class); mockLifecycleManager = mock(AbstractLiteLifecycleManager.class); mockBrokerConfig = mock(BrokerConfig.class); mockSubscriptionGroupManager = mock(SubscriptionGroupManager.class); mockConsumerOffsetManager = mock(ConsumerOffsetManager.class); PopLiteMessageProcessor mockPopLiteMessageProcessor = mock(PopLiteMessageProcessor.class); QueueLevelConsumerManager mockConsumerOrderInfoManager = mock(QueueLevelConsumerManager.class); when(mockBrokerController.getBrokerConfig()).thenReturn(mockBrokerConfig); when(mockBrokerController.getSubscriptionGroupManager()).thenReturn(mockSubscriptionGroupManager); when(mockBrokerController.getConsumerOffsetManager()).thenReturn(mockConsumerOffsetManager); when(mockBrokerController.getPopLiteMessageProcessor()).thenReturn(mockPopLiteMessageProcessor); when(mockPopLiteMessageProcessor.getConsumerOrderInfoManager()).thenReturn(mockConsumerOrderInfoManager); when(mockConsumerOrderInfoManager.getTable()).thenReturn(new ConcurrentHashMap<>()); when(mockBrokerConfig.getMaxLiteSubscriptionCount()).thenReturn(1000L); when(mockBrokerConfig.getLiteSubscriptionCheckTimeoutMills()).thenReturn(60000L); when(mockBrokerConfig.getLiteSubscriptionCheckInterval()).thenReturn(10000L); registry = new LiteSubscriptionRegistryImpl(mockBrokerController, mockLifecycleManager); mockListener = mock(LiteCtlListener.class); registry.addListener(mockListener); } // Test addIncremental method @Test public void testAddPartialSubscription_BasicFunctionality() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add("lmq1"); liteTopicSet.add("lmq2"); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); LiteSubscription subscription = registry.getLiteSubscription(clientId); assertNotNull(subscription); assertEquals(group, subscription.getGroup()); assertEquals(topic, subscription.getTopic()); assertTrue(subscription.getLiteTopicSet().containsAll(liteTopicSet)); assertEquals(liteTopicSet.size(), registry.liteTopic2Group.size()); Set topicGroupSet = registry.liteTopic2Group.get("lmq1"); assertEquals(1, topicGroupSet.size()); ClientGroup registeredGroup = topicGroupSet.iterator().next(); assertEquals(clientId, registeredGroup.clientId); assertEquals(group, registeredGroup.group); verify(mockListener, times(2)).onRegister(eq(clientId), eq(group), anyString()); } @Test public void testAddPartialSubscription_ExclusiveMode() { String existingClientId = "existingClient"; String newClientId = "newClient"; String group = "group"; String topic = "topic"; String liteTopic = "lmq1"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Mock subscription group config for reset offset behavior SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); subscriptionGroupConfig.getAttributes().put(LITE_SUB_MODEL_ATTRIBUTE.getName(), LiteSubModel.Exclusive.name()); when(mockSubscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(subscriptionGroupConfig); // Add existing client registry.addPartialSubscription(existingClientId, group, topic, liteTopicSet, null); // Verify that the existing client is correctly registered LiteSubscription existingSubscription = registry.getLiteSubscription(existingClientId); assertNotNull(existingSubscription); assertTrue(existingSubscription.getLiteTopicSet().contains(liteTopic)); // Execute exclusive mode addition Set newLiteTopicSet = new HashSet<>(); newLiteTopicSet.add(liteTopic); registry.addPartialSubscription(newClientId, group, topic, newLiteTopicSet, null); // Verify that new client subscription has been added. LiteSubscription newSubscription = registry.getLiteSubscription(newClientId); assertNotNull(newSubscription); assertTrue(newSubscription.getLiteTopicSet().contains(liteTopic)); assertEquals(liteTopicSet.size(), registry.liteTopic2Group.size()); Set topicGroupSet = registry.liteTopic2Group.get(liteTopic); assertEquals(1, topicGroupSet.size()); ClientGroup registeredGroup = topicGroupSet.iterator().next(); assertEquals(newClientId, registeredGroup.clientId); assertEquals(group, registeredGroup.group); verify(mockListener).onRegister(existingClientId, group, liteTopic); verify(mockListener).onRegister(newClientId, group, liteTopic); verify(mockListener).onUnregister(existingClientId, group, liteTopic); } @Test public void testAddPartialSubscription_NonExclusiveMode() { // Add an existing client subscription first String existingClientId = "existingClient"; String newClientId = "newClient"; String group = "group1"; String topic = "topic1"; String liteTopic = "lmq1"; Set existingLiteTopicSet = new HashSet<>(); existingLiteTopicSet.add(liteTopic); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Mock subscription group config SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); when(mockSubscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(subscriptionGroupConfig); // Add existing client registry.addPartialSubscription(existingClientId, group, topic, existingLiteTopicSet, null); // Add new client in non-exclusive mode Set newLiteTopicSet = new HashSet<>(); newLiteTopicSet.add(liteTopic); registry.addPartialSubscription(newClientId, group, topic, newLiteTopicSet, null); // Verify both client subscriptions exist LiteSubscription existingSubscription = registry.getLiteSubscription(existingClientId); LiteSubscription newSubscription = registry.getLiteSubscription(newClientId); assertNotNull(existingSubscription); assertNotNull(newSubscription); assertTrue(existingSubscription.getLiteTopicSet().contains(liteTopic)); assertTrue(newSubscription.getLiteTopicSet().contains(liteTopic)); // Verify listener was only called for registration, not unregistration verify(mockListener, times(2)).onRegister(anyString(), eq(group), eq(liteTopic)); verify(mockListener, never()).onUnregister(anyString(), anyString(), anyString()); } @Test public void testAddPartialSubscription_WithEmptyLiteTopicSet() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; Set liteTopicSet = new HashSet<>(); registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); LiteSubscription subscription = registry.getLiteSubscription(clientId); assertNotNull(subscription); assertEquals(group, subscription.getGroup()); assertEquals(topic, subscription.getTopic()); assertTrue(subscription.getLiteTopicSet().isEmpty()); // Verify listener was not called verify(mockListener, never()).onRegister(anyString(), anyString(), anyString()); } @Test public void testAddPartialSubscription_InactiveSubscription() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; String inactiveLiteTopic = "inactive_lmq1"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(inactiveLiteTopic); // Mock inactive subscription when(mockLifecycleManager.isSubscriptionActive(topic, inactiveLiteTopic)).thenReturn(false); // Should not add inactive subscriptions registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); LiteSubscription subscription = registry.getLiteSubscription(clientId); assertNotNull(subscription); assertFalse(subscription.getLiteTopicSet().contains(inactiveLiteTopic)); assertEquals(0, registry.getActiveSubscriptionNum()); } @Test public void testAddPartialSubscription_ExclusiveModeDifferentGroups() { // Add two clients from different groups String client1 = "client1"; String group1 = "group1"; String client2 = "client2"; String group2 = "group2"; String topic = "topic1"; String liteTopic = "lmq1"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Mock subscription group configs SubscriptionGroupConfig subscriptionGroupConfig1 = new SubscriptionGroupConfig(); subscriptionGroupConfig1.setGroupName(group1); subscriptionGroupConfig1.getAttributes().put(LITE_SUB_MODEL_ATTRIBUTE.getName(), LiteSubModel.Exclusive.name()); when(mockSubscriptionGroupManager.findSubscriptionGroupConfig(group1)).thenReturn(subscriptionGroupConfig1); SubscriptionGroupConfig subscriptionGroupConfig2 = new SubscriptionGroupConfig(); subscriptionGroupConfig2.setGroupName(group2); subscriptionGroupConfig2.getAttributes().put(LITE_SUB_MODEL_ATTRIBUTE.getName(), LiteSubModel.Exclusive.name()); when(mockSubscriptionGroupManager.findSubscriptionGroupConfig(group2)).thenReturn(subscriptionGroupConfig2); // Add first client registry.addPartialSubscription(client1, group1, topic, liteTopicSet, null); // Add second client registry.addPartialSubscription(client2, group2, topic, liteTopicSet, null); // Verify both clients are registered for the same topic Set observers = registry.getSubscriber(liteTopic); assertEquals(2, observers.size()); // Add new client in exclusive mode from the same group as client1 String client3 = "client3"; registry.addPartialSubscription(client3, group1, topic, liteTopicSet, null); // Verify only client1 was removed (same group), client2 remains (different group) observers = registry.getSubscriber(liteTopic); assertEquals(2, observers.size()); // client2(group2) and client3(group1) boolean hasClient2 = false; boolean hasClient3 = false; for (ClientGroup cg : observers) { if (cg.clientId.equals(client2) && cg.group.equals(group2)) { hasClient2 = true; } if (cg.clientId.equals(client3) && cg.group.equals(group1)) { hasClient3 = true; } } assertTrue(hasClient2, "Client2 (group2) should still be registered"); assertTrue(hasClient3, "Client3 (group1) should be registered"); // Verify listener calls verify(mockListener).onUnregister(client1, group1, liteTopic); // Same group client1 removed verify(mockListener, never()).onUnregister(client2, group2, liteTopic); // Different group client2 retained } @Test public void testAddPartialSubscription_QuotaLimit() { // Set quota to 1 when(mockBrokerConfig.getMaxLiteSubscriptionCount()).thenReturn(1L); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Add first subscription String clientId1 = "client1"; String group1 = "group1"; String topic1 = "topic1"; Set liteTopicSet1 = new HashSet<>(); liteTopicSet1.add("lmq1"); registry.addPartialSubscription(clientId1, group1, topic1, liteTopicSet1, null); // Try to add second subscription, should throw exception String clientId2 = "client2"; String group2 = "group2"; String topic2 = "topic2"; Set liteTopicSet2 = new HashSet<>(); liteTopicSet2.add("lmq2"); assertThrows(LiteQuotaException.class, () -> { registry.addPartialSubscription(clientId2, group2, topic2, liteTopicSet2, null); }); } // Test removeIncremental method @Test public void testRemovePartialSubscription() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; String liteTopic1 = "lmq1"; String liteTopic2 = "lmq2"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic1); liteTopicSet.add(liteTopic2); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Add subscriptions first registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); // Verify subscriptions were added LiteSubscription subscription = registry.getLiteSubscription(clientId); assertTrue(subscription.getLiteTopicSet().contains(liteTopic1)); assertTrue(subscription.getLiteTopicSet().contains(liteTopic2)); // Remove some subscriptions Set toRemove = new HashSet<>(); toRemove.add(liteTopic1); registry.removePartialSubscription(clientId, group, topic, toRemove); // Verify removal was successful subscription = registry.getLiteSubscription(clientId); assertFalse(subscription.getLiteTopicSet().contains(liteTopic1)); assertTrue(subscription.getLiteTopicSet().contains(liteTopic2)); verify(mockListener).onUnregister(clientId, group, liteTopic1); verify(mockListener, never()).onUnregister(clientId, group, liteTopic2); } // Test addAll method @Test public void testAddCompleteSubscription() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; String liteTopic1 = "lmq1"; String liteTopic2 = "lmq2"; String liteTopic3 = "lmq3"; // Initial subscriptions Set initialSet = new HashSet<>(); initialSet.add(liteTopic1); initialSet.add(liteTopic2); // New full subscription set Set newFullSet = new HashSet<>(); newFullSet.add(liteTopic2); newFullSet.add(liteTopic3); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Add initial subscriptions registry.addPartialSubscription(clientId, group, topic, initialSet, null); // Reset mock to ignore previous interactions clearInvocations(mockListener); // Update with addAll registry.addCompleteSubscription(clientId, group, topic, newFullSet, 1L); // Verify update results LiteSubscription subscription = registry.getLiteSubscription(clientId); assertFalse(subscription.getLiteTopicSet().contains(liteTopic1)); // Should be removed assertTrue(subscription.getLiteTopicSet().contains(liteTopic2)); // Should be retained assertTrue(subscription.getLiteTopicSet().contains(liteTopic3)); // Should be added // Verify that liteTopic1 was unregistered (no longer in new set) verify(mockListener).onUnregister(clientId, group, liteTopic1); // Verify that liteTopic3 was registered (new in the set) verify(mockListener).onRegister(clientId, group, liteTopic3); // Verify that liteTopic2 was neither unregistered nor registered again // (it was already registered and remains in the new set) verify(mockListener, never()).onUnregister(clientId, group, liteTopic2); } // Test removeAll method @Test public void testRemoveCompleteSubscription() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; String liteTopic1 = "lmq1"; String liteTopic2 = "lmq2"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic1); liteTopicSet.add(liteTopic2); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Add subscriptions registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); // Verify subscriptions were added assertNotNull(registry.getLiteSubscription(clientId)); assertEquals(2, registry.getActiveSubscriptionNum()); // Remove all subscriptions registry.removeCompleteSubscription(clientId); // Verify all subscriptions were removed assertNull(registry.getLiteSubscription(clientId)); assertEquals(0, registry.getActiveSubscriptionNum()); verify(mockListener).onRemoveAll(clientId, group); } @Test public void testRemoveCompleteSubscription_NonExistentClient() { String nonExistentClientId = "nonexistent"; // Should not throw exception registry.removeCompleteSubscription(nonExistentClientId); // Verify no changes to registry state assertEquals(0, registry.getActiveSubscriptionNum()); assertNull(registry.getLiteSubscription(nonExistentClientId)); } // Test cleanSubscription method @Test public void testCleanSubscription() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; String liteTopic1 = "lmq1"; String liteTopic2 = "lmq2"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic1); liteTopicSet.add(liteTopic2); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Add subscription registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); assertEquals(2, registry.getActiveSubscriptionNum()); // Verify subscription was added LiteSubscription subscription = registry.getLiteSubscription(clientId); assertTrue(subscription.getLiteTopicSet().contains(liteTopic1)); assertTrue(subscription.getLiteTopicSet().contains(liteTopic2)); // Clean subscription registry.cleanSubscription(liteTopic1, true); registry.cleanSubscription(liteTopic2, false); // Verify subscription was cleaned subscription = registry.getLiteSubscription(clientId); assertFalse(subscription.getLiteTopicSet().contains(liteTopic1)); assertFalse(subscription.getLiteTopicSet().contains(liteTopic2)); assertNull(registry.getSubscriber(liteTopic1)); assertNull(registry.getSubscriber(liteTopic2)); assertEquals(0, registry.getActiveSubscriptionNum()); } // Test getSubscriber method @Test public void testGetSubscriber() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; String liteTopic = "lmq1"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); registry.addPartialSubscription(clientId, group, topic, liteTopicSet, null); Set observers = registry.getSubscriber(liteTopic); assertNotNull(observers); assertEquals(1, observers.size()); ClientGroup clientGroup = observers.iterator().next(); assertEquals(clientId, clientGroup.clientId); assertEquals(group, clientGroup.group); } @Test public void testGetSubscriber_NonExistentTopic() { String nonExistentTopic = "nonexistent_lmq"; Set result = registry.getSubscriber(nonExistentTopic); // Should return null for non-existent topic assertNull(result); } // Test updateClientChannel method @Test public void testUpdateClientChannel() { String clientId = "client1"; Channel mockChannel = mock(Channel.class); registry.updateClientChannel(clientId, mockChannel); // Verify channel was updated assertEquals(mockChannel, registry.clientChannels.get(clientId)); } // Test getActiveSubscriptionNum method @Test public void testGetActiveSubscriptionNum() { String clientId1 = "client1"; String clientId2 = "client2"; String group = "group1"; String topic = "topic1"; String liteTopic1 = "lmq1"; String liteTopic2 = "lmq2"; Set liteTopicSet1 = new HashSet<>(); liteTopicSet1.add(liteTopic1); Set liteTopicSet2 = new HashSet<>(); liteTopicSet2.add(liteTopic1); // Same topic liteTopicSet2.add(liteTopic2); // New topic when(mockLifecycleManager.isSubscriptionActive(anyString(), anyString())).thenReturn(true); // Initial state assertEquals(0, registry.getActiveSubscriptionNum()); // Add first client registry.addPartialSubscription(clientId1, group, topic, liteTopicSet1, null); assertEquals(1, registry.getActiveSubscriptionNum()); // Add second client registry.addPartialSubscription(clientId2, group, topic, liteTopicSet2, null); assertEquals(3, registry.getActiveSubscriptionNum()); // 3 references: client1->topic1, client2->topic1, client2->topic2 } // Test cleanupExpiredSubscriptions method @Test public void testCleanupExpiredSubscriptions_NoExpiredClients() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; Set liteTopics = new HashSet<>(); liteTopics.add("lmq1"); liteTopics.add("lmq2"); LiteSubscription subscription = new LiteSubscription(); subscription.setGroup(group); subscription.setTopic(topic); subscription.addLiteTopic(liteTopics); subscription.setUpdateTime(System.currentTimeMillis()); // Not expired Channel channel = mock(Channel.class); registry.client2Subscription.put(clientId, subscription); registry.clientChannels.put(clientId, channel); // Initialize liteTopic2Group for (String lmq : liteTopics) { registry.liteTopic2Group.computeIfAbsent(lmq, k -> ConcurrentHashMap.newKeySet()) .add(new ClientGroup(clientId, group)); } registry.activeNum.set(liteTopics.size()); // Perform cleanup with a timeout of 10 seconds registry.cleanupExpiredSubscriptions(10000); // Verify that the client has not been cleaned up assertNotNull(registry.client2Subscription.get(clientId)); assertNotNull(registry.clientChannels.get(clientId)); assertEquals(liteTopics.size(), registry.activeNum.get()); } @Test public void testCleanupExpiredSubscriptions_WithExpiredClients() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; Set liteTopics = new HashSet<>(); liteTopics.add("lmq1"); liteTopics.add("lmq2"); LiteSubscription subscription = new LiteSubscription(); subscription.setGroup(group); subscription.setTopic(topic); subscription.addLiteTopic(liteTopics); subscription.setUpdateTime(System.currentTimeMillis() - 20000); Channel channel = mock(Channel.class); registry.client2Subscription.put(clientId, subscription); registry.clientChannels.put(clientId, channel); // Initialize liteTopic2Group for (String lmq : liteTopics) { registry.liteTopic2Group.computeIfAbsent(lmq, k -> ConcurrentHashMap.newKeySet()) .add(new ClientGroup(clientId, group)); } registry.activeNum.set(liteTopics.size()); LiteCtlListener mockListener = mock(LiteCtlListener.class); registry.addListener(mockListener); // Perform cleanup with a timeout of 10 seconds registry.cleanupExpiredSubscriptions(10000); // Verify that the client has been cleaned up assertNull(registry.client2Subscription.get(clientId)); assertNull(registry.clientChannels.get(clientId)); assertEquals(0, registry.activeNum.get()); // Verify that the listener was called verify(mockListener, times(1)).onUnregister(eq(clientId), eq(group), eq("lmq1")); verify(mockListener, times(1)).onUnregister(eq(clientId), eq(group), eq("lmq2")); verify(mockListener, times(1)).onRemoveAll(eq(clientId), eq(group)); // Verify that topics in liteTopic2Group have been removed assertNull(registry.liteTopic2Group.get("lmq1")); assertNull(registry.liteTopic2Group.get("lmq2")); } @Test public void testCleanupExpiredSubscriptions_ExpiredClientWithNoSubscriptions() { String clientId = "client1"; String group = "group1"; String topic = "topic1"; Set liteTopics = new HashSet<>(); LiteSubscription subscription = new LiteSubscription(); subscription.setGroup(group); subscription.setTopic(topic); subscription.addLiteTopic(liteTopics); subscription.setUpdateTime(System.currentTimeMillis() - 20000); // Expired Channel channel = mock(Channel.class); registry.client2Subscription.put(clientId, subscription); registry.clientChannels.put(clientId, channel); registry.activeNum.set(0); LiteCtlListener mockListener = mock(LiteCtlListener.class); registry.addListener(mockListener); // Perform cleanup with 10 second timeout registry.cleanupExpiredSubscriptions(10000); // Verify that the client has been cleaned up assertNull(registry.client2Subscription.get(clientId)); assertNull(registry.clientChannels.get(clientId)); assertEquals(0, registry.activeNum.get()); // Verify that the listener was not called verify(mockListener, never()).onUnregister(anyString(), anyString(), anyString()); } // Test removeTopicGroup method @Test public void testRemoveTopicGroup_EmptyTopicGroupSet() { String clientId = "client1"; String group = "group1"; String liteTopic = "lmq1"; ClientGroup clientGroup = new ClientGroup(clientId, group); // Initialize with a single client Set topicGroupSet = ConcurrentHashMap.newKeySet(); topicGroupSet.add(clientGroup); registry.liteTopic2Group.put(liteTopic, topicGroupSet); registry.activeNum.set(1); // Remove the only client registry.removeTopicGroup(clientGroup, liteTopic, false); // Verify that the topic is completely removed from liteTopic2Group assertNull(registry.liteTopic2Group.get(liteTopic)); assertEquals(0, registry.getActiveSubscriptionNum()); } // Test excludeClientByLmqName method @Test public void testExcludeClientByLmqName_EmptyClientSet() { String newClientId = "newClient"; String group = "group1"; String lmqName = "lmq1"; // Ensure the liteTopic2Group map exists but is empty registry.liteTopic2Group.put(lmqName, ConcurrentHashMap.newKeySet()); // Should not throw any exception registry.excludeClientByLmqName(newClientId, group, lmqName); // Verify no changes assertTrue(registry.liteTopic2Group.get(lmqName).isEmpty()); } @Test public void testGetAllClientIdByGroup() { String group1 = "group1"; String group2 = "group2"; String clientId1 = "client1"; String clientId2 = "client2"; String clientId3 = "client3"; String topic = "parentTopic"; LiteSubscription sub1 = new LiteSubscription(); sub1.setGroup(group1); sub1.setTopic(topic); LiteSubscription sub2 = new LiteSubscription(); sub2.setGroup(group1); sub2.setTopic(topic); LiteSubscription sub3 = new LiteSubscription(); sub3.setGroup(group2); sub3.setTopic(topic); registry.client2Subscription.put(clientId1, sub1); registry.client2Subscription.put(clientId2, sub2); registry.client2Subscription.put(clientId3, sub3); List result; // group1 result = registry.getAllClientIdByGroup(group1); assertEquals(2, result.size()); assertTrue(result.contains(clientId1)); assertTrue(result.contains(clientId2)); // group2 result = registry.getAllClientIdByGroup(group2); assertEquals(1, result.size()); assertTrue(result.contains(clientId3)); // not exist result = registry.getAllClientIdByGroup("notExistGroup"); assertTrue(result.isEmpty()); // null result = registry.getAllClientIdByGroup(null); assertTrue(result.isEmpty()); } @Test public void testResetOffset_minOffset() { String lmqName = "lmq1"; String group = "group1"; String clientId = "client1"; when(mockConsumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(100L); OffsetOption offsetOption = new OffsetOption(OffsetOption.Type.POLICY, OffsetOption.POLICY_MIN_VALUE); registry.resetOffset(lmqName, group, clientId, offsetOption); verify(mockConsumerOffsetManager).assignResetOffset(lmqName, group, 0, 0L); } @Test public void testResetOffset_maxOffset() { String lmqName = "lmq1"; String group = "group1"; String clientId = "client1"; long maxOffset = 500L; when(mockConsumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(100L); when(mockLifecycleManager.getMaxOffsetInQueue(lmqName)).thenReturn(maxOffset); OffsetOption offsetOption = new OffsetOption(OffsetOption.Type.POLICY, OffsetOption.POLICY_MAX_VALUE); registry.resetOffset(lmqName, group, clientId, offsetOption); verify(mockConsumerOffsetManager).assignResetOffset(lmqName, group, 0, maxOffset); } @Test public void testResetOffset_absolute() { String lmqName = "lmq1"; String group = "group1"; String clientId = "client1"; long specifiedOffset = 250L; when(mockConsumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(100L); OffsetOption offsetOption = new OffsetOption(OffsetOption.Type.OFFSET, specifiedOffset); registry.resetOffset(lmqName, group, clientId, offsetOption); verify(mockConsumerOffsetManager).assignResetOffset(lmqName, group, 0, specifiedOffset); } @Test public void testResetOffset_LastN() { String lmqName = "lmq1"; String group1 = "group1"; String group2 = "group2"; String clientId = "client1"; long currentOffset = 100L; long lastN = 20L; long expectedTargetOffset = 80L; when(mockConsumerOffsetManager.queryOffset(group1, lmqName, 0)).thenReturn(currentOffset); when(mockConsumerOffsetManager.queryOffset(group2, lmqName, 0)).thenReturn(-1L); OffsetOption offsetOption = new OffsetOption(OffsetOption.Type.TAIL_N, lastN); registry.resetOffset(lmqName, group1, clientId, offsetOption); registry.resetOffset(lmqName, group2, clientId, offsetOption); verify(mockConsumerOffsetManager).assignResetOffset(lmqName, group1, 0, expectedTargetOffset); verify(mockConsumerOffsetManager, never()).assignResetOffset(lmqName, group2, 0, expectedTargetOffset); } @Test public void testResetOffset_timestamp_not_supported() { String lmqName = "lmq1"; String group = "group1"; String clientId = "client1"; long timestamp = System.currentTimeMillis(); when(mockConsumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(100L); OffsetOption offsetOption = new OffsetOption(OffsetOption.Type.TIMESTAMP, timestamp); registry.resetOffset(lmqName, group, clientId, offsetOption); verify(mockConsumerOffsetManager, never()).assignResetOffset(anyString(), anyString(), anyInt(), anyLong()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/LiteTestUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import java.net.InetSocketAddress; import java.util.concurrent.ConcurrentMap; public class LiteTestUtil { public static MessageStore buildMessageStore(String storePathRootDir, final BrokerConfig brokerConfig, final ConcurrentMap topicConfigTable, boolean isRocksDBStore) throws Exception { MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); storeConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); storeConfig.setMaxHashSlotNum(10000); storeConfig.setMaxIndexNum(100 * 100); storeConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); storeConfig.setFlushIntervalConsumeQueue(1); storeConfig.setHaListenPort(0); storeConfig.setEnableLmq(true); storeConfig.setEnableMultiDispatch(true); storeConfig.setStorePathRootDir(storePathRootDir); BrokerStatsManager brokerStatsManager = new BrokerStatsManager(brokerConfig); MessageStore messageStore; if (isRocksDBStore) { messageStore = new RocksDBMessageStore(storeConfig, brokerStatsManager, null, brokerConfig, topicConfigTable); } else { messageStore = new DefaultMessageStore(storeConfig, brokerStatsManager, null, brokerConfig, topicConfigTable); } return messageStore; } public static MessageExtBrokerInner buildMessage(String parentTopic, String liteTopic) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(parentTopic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody("HW".getBytes()); msg.setQueueId(0); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(new InetSocketAddress("localhost", 10911)); msg.setBornHost(new InetSocketAddress("localhost", 0)); if (StringUtils.isNotEmpty(liteTopic)) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopic); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqName); } msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/lite/RocksDBLiteLifecycleManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.lite; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.plugin.AbstractPluginMessageStore; import org.apache.rocketmq.store.plugin.MessageStorePluginContext; import org.apache.rocketmq.tieredstore.TieredMessageStore; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import java.util.stream.IntStream; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RocksDBLiteLifecycleManagerTest { private final static BrokerConfig BROKER_CONFIG = new BrokerConfig(); private final static ConcurrentMap TOPIC_CONFIG_TABLE = new ConcurrentHashMap<>(); private static String storePathRootDir; private static MessageStore messageStore; private static RocksDBLiteLifecycleManager liteLifecycleManager; private static TopicConfig mockTopicConfig = new TopicConfig(); @BeforeClass public static void setUp() throws Exception { storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-rocksDBLifecycleTest"; UtilAll.deleteFile(new File(storePathRootDir)); messageStore = LiteTestUtil.buildMessageStore(storePathRootDir, BROKER_CONFIG, TOPIC_CONFIG_TABLE, true); messageStore.load(); messageStore.start(); BrokerController brokerController = Mockito.mock(BrokerController.class); LiteSharding liteSharding = Mockito.mock(LiteSharding.class); TopicConfigManager topicConfigManager = Mockito.mock(TopicConfigManager.class); SubscriptionGroupManager subscriptionGroupManager = Mockito.mock(SubscriptionGroupManager.class); when(brokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(topicConfigManager.getTopicConfigTable()).thenReturn(TOPIC_CONFIG_TABLE); when(topicConfigManager.selectTopicConfig(anyString())).thenReturn(mockTopicConfig); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(new ConcurrentHashMap<>()); RocksDBLiteLifecycleManager testObject = new RocksDBLiteLifecycleManager(brokerController, liteSharding); liteLifecycleManager = Mockito.spy(testObject); liteLifecycleManager.init(); } @AfterClass public static void reset() { messageStore.shutdown(); messageStore.destroy(); UtilAll.deleteFile(new File(storePathRootDir)); mockTopicConfig = new TopicConfig(); } @Ignore @Test public void testInit_tieredStore() { BrokerController brokerController = Mockito.mock(BrokerController.class); LiteSharding liteSharding = Mockito.mock(LiteSharding.class); MessageStorePluginContext context = Mockito.mock(MessageStorePluginContext.class); TieredMessageStore tieredMessageStore = new TieredMessageStore(context, messageStore); when(brokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG); when(brokerController.getMessageStore()).thenReturn(tieredMessageStore); RocksDBLiteLifecycleManager manager = new RocksDBLiteLifecycleManager(brokerController, liteSharding); manager.init(); Assert.assertEquals(0, manager.getMaxOffsetInQueue(UUID.randomUUID().toString())); } @Test public void testInit_otherStore() { BrokerController brokerController = Mockito.mock(BrokerController.class); LiteSharding liteSharding = Mockito.mock(LiteSharding.class); AbstractPluginMessageStore pluginMessageStore = Mockito.mock(AbstractPluginMessageStore.class); when(brokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG); when(brokerController.getMessageStore()).thenReturn(pluginMessageStore); RocksDBLiteLifecycleManager manager = new RocksDBLiteLifecycleManager(brokerController, liteSharding); manager.init(); Assert.assertThrows(NullPointerException.class, () -> manager.getMaxOffsetInQueue("HW")); } @Test public void testGetMaxOffsetInQueue() { int num = 3; String topic = UUID.randomUUID().toString(); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(topic, null)); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); Assert.assertEquals(num, liteLifecycleManager.getMaxOffsetInQueue(topic)); Assert.assertEquals(0, liteLifecycleManager.getMaxOffsetInQueue(UUID.randomUUID().toString())); } @Test public void testCollectByParentTopic() { int num = 3; String parentTopic = UUID.randomUUID().toString(); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, UUID.randomUUID().toString())); messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), UUID.randomUUID().toString())); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); List result = liteLifecycleManager.collectByParentTopic(parentTopic); Assert.assertEquals(num, result.size()); for (String lmqName : result) { Assert.assertTrue(LiteUtil.belongsTo(lmqName, parentTopic)); } result = liteLifecycleManager.collectByParentTopic(UUID.randomUUID().toString()); Assert.assertEquals(0, result.size()); } @Test public void testCollectExpiredLiteTopic() { int num = 3; String parentTopic = UUID.randomUUID().toString(); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, UUID.randomUUID().toString())); messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), null)); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); when(liteLifecycleManager.isLiteTopicExpired(anyString(), anyString(), anyLong())).thenReturn(false); List> result = liteLifecycleManager.collectExpiredLiteTopic(); Assert.assertEquals(0, result.size()); when(liteLifecycleManager.isLiteTopicExpired(eq(parentTopic), anyString(), anyLong())).thenReturn(true); result = liteLifecycleManager.collectExpiredLiteTopic(); Assert.assertEquals(num, result.size()); for (Pair pair : result) { Assert.assertEquals(parentTopic, pair.getObject1()); Assert.assertTrue(LiteUtil.belongsTo(pair.getObject2(), parentTopic)); } } @Test public void testCleanExpiredLiteTopic() throws Exception { int num = 3; String parentTopic = UUID.randomUUID().toString(); List liteTopics = IntStream.range(0, 3).mapToObj(i -> UUID.randomUUID().toString()).collect(Collectors.toList()); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, liteTopics.get(i))); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertEquals(1, (long) messageStore.getQueueStore().getMaxOffset(lmqName, 0)); Assert.assertEquals(1, liteLifecycleManager.getMaxOffsetInQueue(lmqName)); } when(liteLifecycleManager.isLiteTopicExpired(eq(parentTopic), anyString(), anyLong())).thenReturn(true); liteLifecycleManager.cleanExpiredLiteTopic(); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertEquals(0, (long) messageStore.getQueueStore().getMaxOffset(lmqName, 0)); Assert.assertEquals(0, liteLifecycleManager.getMaxOffsetInQueue(lmqName)); } } @Test public void testCleanByParentTopic() throws Exception { int num = 3; String parentTopic = UUID.randomUUID().toString(); mockTopicConfig.getAttributes().put( TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), TopicMessageType.LITE.getValue()); List liteTopics = IntStream.range(0, 3).mapToObj(i -> UUID.randomUUID().toString()).collect(Collectors.toList()); for (int i = 0; i < num; i++) { messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, liteTopics.get(i))); } await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> messageStore.dispatchBehindBytes() <= 0); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertEquals(1, (long) messageStore.getQueueStore().getMaxOffset(lmqName, 0)); Assert.assertEquals(1, liteLifecycleManager.getMaxOffsetInQueue(lmqName)); } liteLifecycleManager.cleanByParentTopic(parentTopic); for (int i = 0; i < num; i++) { String lmqName = LiteUtil.toLmqName(parentTopic, liteTopics.get(i)); Assert.assertEquals(0, (long) messageStore.getQueueStore().getMaxOffset(lmqName, 0)); Assert.assertEquals(0, liteLifecycleManager.getMaxOffsetInQueue(lmqName)); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/longpolling/PopLiteLongPollingServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PopLiteLongPollingServiceTest { @Mock private BrokerController brokerController; @Mock private NettyRequestProcessor processor; @Mock private ChannelHandlerContext ctx; @Mock private ExecutorService pullMessageExecutor; private BrokerConfig brokerConfig; private PopLiteLongPollingService popLiteLongPollingService; private ConcurrentLinkedHashMap> pollingMap; private AtomicLong totalPollingNum; @SuppressWarnings("unchecked") @Before public void init() throws IllegalAccessException { brokerConfig = new BrokerConfig(); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getPullMessageExecutor()).thenReturn(pullMessageExecutor); popLiteLongPollingService = new PopLiteLongPollingService(brokerController, processor, true); pollingMap = (ConcurrentLinkedHashMap>) FieldUtils.readDeclaredField(popLiteLongPollingService, "pollingMap", true); totalPollingNum = (AtomicLong) FieldUtils.readDeclaredField(popLiteLongPollingService, "totalPollingNum", true); } @Test public void testNotifyMessageArriving_noRequest() { assertFalse(popLiteLongPollingService.notifyMessageArriving("clientId", true, 0, "group")); } @Test public void testNotifyMessageArriving_inactiveChannel() throws Exception { String clientId = "clientId"; String group = "group"; ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); Channel channel = mock(Channel.class); when(channel.isActive()).thenReturn(false); when(ctx.channel()).thenReturn(channel); PollingResult result = popLiteLongPollingService.polling( ctx, remotingCommand, System.currentTimeMillis(), 10000, clientId, group); assertEquals(PollingResult.POLLING_SUC, result); assertEquals(1, totalPollingNum.get()); assertFalse(popLiteLongPollingService.notifyMessageArriving(clientId, true, 0, group)); assertEquals(0, totalPollingNum.get()); } @Test public void testNotifyMessageArriving_success() throws Exception { String clientId = "clientId"; String group = "group"; ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand1 = mock(RemotingCommand.class); RemotingCommand remotingCommand2 = mock(RemotingCommand.class); Channel channel = mock(Channel.class); when(channel.isActive()).thenReturn(true); when(ctx.channel()).thenReturn(channel); PollingResult result1 = popLiteLongPollingService.polling( ctx, remotingCommand1, System.currentTimeMillis(), 10000, clientId, group); PollingResult result2 = popLiteLongPollingService.polling( ctx, remotingCommand2, System.currentTimeMillis(), 15000, clientId, group); assertEquals(PollingResult.POLLING_SUC, result1); assertEquals(PollingResult.POLLING_SUC, result2); assertEquals(2, totalPollingNum.get()); assertTrue(popLiteLongPollingService.notifyMessageArriving(clientId, true, 0, group)); assertEquals(1, totalPollingNum.get()); assertEquals(remotingCommand1, pollingMap.get(clientId).pollFirst().getRemotingCommand()); // notify last } @Test public void testWakeUp_nullRequest() { assertFalse(popLiteLongPollingService.wakeUp(null)); } @Test public void testWakeUp_completeRequest() { PopRequest request = mock(PopRequest.class); when(request.complete()).thenReturn(false); assertFalse(popLiteLongPollingService.wakeUp(request)); } @Test public void testWakeUp_inactiveChannel() { PopRequest request = mock(PopRequest.class); when(request.complete()).thenReturn(true); when(request.getCtx()).thenReturn(ctx); Channel channel = mock(Channel.class); when(ctx.channel()).thenReturn(channel); when(channel.isActive()).thenReturn(false); assertFalse(popLiteLongPollingService.wakeUp(request)); verify(pullMessageExecutor, never()).submit(any(Runnable.class)); } @Test public void testWakeUp_success() { PopRequest request = mock(PopRequest.class); when(request.complete()).thenReturn(true); when(request.getCtx()).thenReturn(ctx); Channel channel = mock(Channel.class); when(ctx.channel()).thenReturn(channel); when(channel.isActive()).thenReturn(true); assertTrue(popLiteLongPollingService.wakeUp(request)); verify(pullMessageExecutor).submit(any(Runnable.class)); } @Test public void testPolling_notPolling() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); PollingResult result = popLiteLongPollingService.polling(ctx, remotingCommand, 0, 0, "clientId", "group"); assertEquals(PollingResult.NOT_POLLING, result); } @Test public void testPolling_timeout() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); PollingResult result = popLiteLongPollingService.polling(ctx, remotingCommand, System.currentTimeMillis(), 40, "clientId", "group"); assertEquals(PollingResult.POLLING_TIMEOUT, result); } @Test public void testPolling_success() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); PollingResult result = popLiteLongPollingService.polling( ctx, remotingCommand, System.currentTimeMillis(), 10000, "clientId", "group"); assertEquals(PollingResult.POLLING_SUC, result); } @Test public void testPolling_totalPollingFull() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); totalPollingNum.set(brokerConfig.getMaxPopPollingSize() + 1); PollingResult result = popLiteLongPollingService.polling( ctx, remotingCommand, System.currentTimeMillis(), 10000, "clientId", "group"); assertEquals(PollingResult.POLLING_FULL, result); } @Test public void testPolling_singlePollingFull() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); brokerConfig.setPopPollingSize(-1); PollingResult result = popLiteLongPollingService.polling( ctx, remotingCommand, System.currentTimeMillis(), 10000, "clientId", "group"); assertEquals(PollingResult.POLLING_SUC, result); result = popLiteLongPollingService.polling( ctx, remotingCommand, System.currentTimeMillis(), 10000, "clientId", "group"); assertEquals(PollingResult.POLLING_FULL, result); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/longpolling/PopLongPollingServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.MessageFilter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PopLongPollingServiceTest { @Mock private BrokerController brokerController; @Mock private NettyRequestProcessor processor; @Mock private ChannelHandlerContext ctx; @Mock private ExecutorService pullMessageExecutor; private PopLongPollingService popLongPollingService; private final String defaultTopic = "defaultTopic"; @Before public void init() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setPopPollingMapSize(100); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); popLongPollingService = spy(new PopLongPollingService(brokerController, processor, true)); } @Test public void testNotifyMessageArrivingWithRetryTopic() { int queueId = 0; doNothing().when(popLongPollingService).notifyMessageArrivingWithRetryTopic(defaultTopic, queueId, -1L, null, 0L, null, null); popLongPollingService.notifyMessageArrivingWithRetryTopic(defaultTopic, queueId); verify(popLongPollingService, times(1)).notifyMessageArrivingWithRetryTopic(defaultTopic, queueId, -1L, null, 0L, null, null); } @Test public void testNotifyMessageArrivingFromRetry() { int queueId = -1; String group = "group"; String pullRetryTopic = MixAll.getRetryTopic(group); String popRetryTopicV1 = KeyBuilder.buildPopRetryTopic(defaultTopic, group, false); String popRetryTopicV2 = KeyBuilder.buildPopRetryTopic(defaultTopic, group, true); Map properties = new HashMap<>(); properties.putIfAbsent(MessageConst.PROPERTY_ORIGIN_GROUP, group); // pull retry popLongPollingService.notifyMessageArrivingWithRetryTopic(pullRetryTopic, queueId, queueId, -1L, 0L, null, properties); verify(popLongPollingService, times(0)).notifyMessageArriving(defaultTopic, queueId, group, true, -1L, 0L, null, properties, null); // pop retry v1 popLongPollingService.notifyMessageArrivingWithRetryTopic(popRetryTopicV1, queueId, queueId, -1L, 0L, null, properties); verify(popLongPollingService, times(1)).notifyMessageArriving(defaultTopic, queueId, group, true, -1L, 0L, null, properties, null); // pop retry v2 popLongPollingService.notifyMessageArrivingWithRetryTopic(popRetryTopicV2, queueId, queueId, -1L, 0L, null, properties); verify(popLongPollingService, times(2)).notifyMessageArriving(defaultTopic, queueId, group, true, -1L, 0L, null, properties, null); } @Test public void testNotifyMessageArriving() { int queueId = 0; Long tagsCode = 123L; long offset = 123L; long msgStoreTime = System.currentTimeMillis(); byte[] filterBitMap = new byte[] {0x01}; Map properties = new ConcurrentHashMap<>(); doNothing().when(popLongPollingService).notifyMessageArriving(defaultTopic, queueId, offset, tagsCode, msgStoreTime, filterBitMap, properties); popLongPollingService.notifyMessageArrivingWithRetryTopic(defaultTopic, queueId, offset, tagsCode, msgStoreTime, filterBitMap, properties); verify(popLongPollingService).notifyMessageArriving(defaultTopic, queueId, offset, tagsCode, msgStoreTime, filterBitMap, properties); } @Test public void testNotifyMessageArrivingValidRequest() throws Exception { String cid = "CID_1"; int queueId = 0; Cache> topicCidMap = Caffeine.newBuilder() .maximumSize(10) .expireAfterAccess(300, TimeUnit.SECONDS) .build(); ConcurrentHashMap cids = new ConcurrentHashMap<>(); cids.put(cid, (byte) 1); topicCidMap.put(defaultTopic, cids); popLongPollingService = new PopLongPollingService(brokerController, processor, true); Cache> pollingMap = Caffeine.newBuilder() .maximumSize(10) .expireAfterAccess(300, TimeUnit.SECONDS) .build(); Channel channel = mock(Channel.class); when(channel.isActive()).thenReturn(true); PopRequest popRequest = mock(PopRequest.class); MessageFilter messageFilter = mock(MessageFilter.class); SubscriptionData subscriptionData = mock(SubscriptionData.class); when(popRequest.getMessageFilter()).thenReturn(messageFilter); when(popRequest.getSubscriptionData()).thenReturn(subscriptionData); when(popRequest.getChannel()).thenReturn(channel); String pollingKey = KeyBuilder.buildPollingKey(defaultTopic, cid, queueId); ConcurrentSkipListSet popRequests = mock(ConcurrentSkipListSet.class); when(popRequests.pollLast()).thenReturn(popRequest); pollingMap.put(pollingKey, popRequests); FieldUtils.writeDeclaredField(popLongPollingService, "topicCidMap", topicCidMap, true); FieldUtils.writeDeclaredField(popLongPollingService, "pollingMap", pollingMap, true); boolean actual = popLongPollingService.notifyMessageArriving(defaultTopic, queueId, cid, null, 0, null, null); assertFalse(actual); } @Test public void testWakeUpNullRequest() { assertFalse(popLongPollingService.wakeUp(null)); } @Test public void testWakeUpIncompleteRequest() { PopRequest request = mock(PopRequest.class); when(request.complete()).thenReturn(false); assertFalse(popLongPollingService.wakeUp(request)); } @Test public void testWakeUpInactiveChannel() { PopRequest request = mock(PopRequest.class); when(request.complete()).thenReturn(true); when(request.getCtx()).thenReturn(ctx); Channel channel = mock(Channel.class); when(ctx.channel()).thenReturn(channel); when(channel.isActive()).thenReturn(true); when(brokerController.getPullMessageExecutor()).thenReturn(pullMessageExecutor); assertTrue(popLongPollingService.wakeUp(request)); } @Test public void testWakeUpValidRequestWithException() throws Exception { PopRequest request = mock(PopRequest.class); when(request.complete()).thenReturn(true); when(request.getCtx()).thenReturn(ctx); Channel channel = mock(Channel.class); when(ctx.channel()).thenReturn(channel); when(request.getChannel()).thenReturn(channel); when(channel.isActive()).thenReturn(true); when(brokerController.getPullMessageExecutor()).thenReturn(pullMessageExecutor); when(processor.processRequest(any(), any())).thenThrow(new RuntimeException("Test Exception")); assertTrue(popLongPollingService.wakeUp(request)); ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); verify(pullMessageExecutor).submit(captor.capture()); captor.getValue().run(); verify(processor).processRequest(any(), any()); } @Test public void testPollingNotPolling() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); PollingHeader requestHeader = mock(PollingHeader.class); SubscriptionData subscriptionData = mock(SubscriptionData.class); MessageFilter messageFilter = mock(MessageFilter.class); when(requestHeader.getPollTime()).thenReturn(0L); PollingResult result = popLongPollingService.polling(ctx, remotingCommand, requestHeader, subscriptionData, messageFilter); assertEquals(PollingResult.NOT_POLLING, result); } @Test public void testPollingServicePollingTimeout() throws IllegalAccessException { String cid = "CID_1"; popLongPollingService = new PopLongPollingService(brokerController, processor, true); popLongPollingService.shutdown(); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); PollingHeader requestHeader = mock(PollingHeader.class); SubscriptionData subscriptionData = mock(SubscriptionData.class); MessageFilter messageFilter = mock(MessageFilter.class); when(requestHeader.getPollTime()).thenReturn(1000L); when(requestHeader.getTopic()).thenReturn(defaultTopic); when(requestHeader.getConsumerGroup()).thenReturn("defaultGroup"); Cache> topicCidMap = Caffeine.newBuilder() .maximumSize(10) .expireAfterAccess(300, TimeUnit.SECONDS) .build(); ConcurrentHashMap cids = new ConcurrentHashMap<>(); cids.put(cid, (byte) 1); topicCidMap.put(defaultTopic, cids); FieldUtils.writeDeclaredField(popLongPollingService, "topicCidMap", topicCidMap, true); PollingResult result = popLongPollingService.polling(ctx, remotingCommand, requestHeader, subscriptionData, messageFilter); assertEquals(PollingResult.POLLING_TIMEOUT, result); } @Test public void testPollingPollingSuc() { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand remotingCommand = mock(RemotingCommand.class); PollingHeader requestHeader = mock(PollingHeader.class); SubscriptionData subscriptionData = mock(SubscriptionData.class); MessageFilter messageFilter = mock(MessageFilter.class); when(requestHeader.getPollTime()).thenReturn(1000L); when(requestHeader.getBornTime()).thenReturn(System.currentTimeMillis()); when(requestHeader.getTopic()).thenReturn("topic"); when(requestHeader.getConsumerGroup()).thenReturn("cid"); when(requestHeader.getQueueId()).thenReturn(0); PollingResult result = popLongPollingService.polling(ctx, remotingCommand, requestHeader, subscriptionData, messageFilter); assertEquals(PollingResult.POLLING_SUC, result); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.longpolling; import io.netty.channel.Channel; import java.util.HashMap; import java.util.concurrent.Executors; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.processor.PullMessageProcessor; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.DefaultMessageFilter; import org.apache.rocketmq.store.DefaultMessageStore; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PullRequestHoldServiceTest { @Mock private BrokerController brokerController; private PullRequestHoldService pullRequestHoldService; @Mock private PullRequest pullRequest; private BrokerConfig brokerConfig = new BrokerConfig(); @Mock private DefaultMessageStore defaultMessageStore; @Mock private DefaultMessageFilter defaultMessageFilter; @Mock private RemotingCommand remotingCommand; @Mock private Channel channel; private SubscriptionData subscriptionData; private static final String TEST_TOPIC = "TEST_TOPIC"; private static final int DEFAULT_QUEUE_ID = 0; private static final long MAX_OFFSET = 100L; @Before public void before() { when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getPullMessageProcessor()).thenReturn(new PullMessageProcessor(brokerController)); when(brokerController.getPullMessageExecutor()).thenReturn(Executors.newCachedThreadPool()); pullRequestHoldService = new PullRequestHoldService(brokerController); subscriptionData = new SubscriptionData(TEST_TOPIC, "*"); pullRequest = new PullRequest(remotingCommand, channel, 3000, 3000, 0L, subscriptionData, defaultMessageFilter); pullRequestHoldService.start(); } @After public void after() { pullRequestHoldService.shutdown(); } @Test public void suspendPullRequestTest() { Assertions.assertThatCode(() -> pullRequestHoldService.suspendPullRequest(TEST_TOPIC, DEFAULT_QUEUE_ID, pullRequest)).doesNotThrowAnyException(); } @Test public void getServiceNameTest() { final String name = pullRequestHoldService.getServiceName(); assert StringUtils.isNotEmpty(name); } @Test public void checkHoldRequestTest() { Assertions.assertThatCode(() -> pullRequestHoldService.checkHoldRequest()).doesNotThrowAnyException(); } @Test public void notifyMessageArrivingTest() { Assertions.assertThatCode(() -> pullRequestHoldService.notifyMessageArriving(TEST_TOPIC, DEFAULT_QUEUE_ID, MAX_OFFSET)).doesNotThrowAnyException(); Assertions.assertThatCode(() -> pullRequestHoldService.suspendPullRequest(TEST_TOPIC, DEFAULT_QUEUE_ID, pullRequest)).doesNotThrowAnyException(); Assertions.assertThatCode(() -> pullRequestHoldService.notifyMessageArriving(TEST_TOPIC, DEFAULT_QUEUE_ID, MAX_OFFSET, 1L, System.currentTimeMillis(), new byte[10], new HashMap<>())).doesNotThrowAnyException(); } @Test public void notifyMasterOnlineTest() { Assertions.assertThatCode(() -> pullRequestHoldService.suspendPullRequest(TEST_TOPIC, DEFAULT_QUEUE_ID, pullRequest)).doesNotThrowAnyException(); Assertions.assertThatCode(() -> pullRequestHoldService.notifyMasterOnline()).doesNotThrowAnyException(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.metrics.MetricsExporterType; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Test; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; public class BrokerMetricsManagerTest { private BrokerMetricsManager createTestBrokerMetricsManager() { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + UUID.randomUUID(); messageStoreConfig.setStorePathRootDir(storePathRootDir); BrokerConfig brokerConfig = new BrokerConfig(); NettyServerConfig nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(0); BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); return new BrokerMetricsManager(brokerController); } @Test public void testNewAttributesBuilder() { BrokerMetricsManager metricsManager = createTestBrokerMetricsManager(); Attributes attributes = metricsManager.newAttributesBuilder().put("a", "b") .build(); assertThat(attributes.get(AttributeKey.stringKey("a"))).isEqualTo("b"); } @Test public void testCustomizedAttributesBuilder() { BrokerMetricsManager metricsManager = createTestBrokerMetricsManager(); // Create a custom attributes builder supplier for testing metricsManager.setAttributesBuilderSupplier(() -> new AttributesBuilder() { private AttributesBuilder attributesBuilder = Attributes.builder(); @Override public Attributes build() { return attributesBuilder.put("customized", "value").build(); } @Override public AttributesBuilder put(AttributeKey key, int value) { attributesBuilder.put(key, value); return this; } @Override public AttributesBuilder put(AttributeKey key, T value) { attributesBuilder.put(key, value); return this; } @Override public AttributesBuilder putAll(Attributes attributes) { attributesBuilder.putAll(attributes); return this; } }); Attributes attributes = metricsManager.newAttributesBuilder().put("a", "b") .build(); assertThat(attributes.get(AttributeKey.stringKey("a"))).isEqualTo("b"); assertThat(attributes.get(AttributeKey.stringKey("customized"))).isEqualTo("value"); } @Test public void testIsRetryOrDlqTopicWithRetryTopic() { String topic = MixAll.RETRY_GROUP_TOPIC_PREFIX + "TestTopic"; boolean result = BrokerMetricsManager.isRetryOrDlqTopic(topic); assertThat(result).isTrue(); } @Test public void testIsRetryOrDlqTopicWithDlqTopic() { String topic = MixAll.DLQ_GROUP_TOPIC_PREFIX + "TestTopic"; boolean result = BrokerMetricsManager.isRetryOrDlqTopic(topic); assertThat(result).isTrue(); } @Test public void testIsRetryOrDlqTopicWithNonRetryOrDlqTopic() { String topic = "NormalTopic"; boolean result = BrokerMetricsManager.isRetryOrDlqTopic(topic); assertThat(result).isFalse(); } @Test public void testIsRetryOrDlqTopicWithEmptyTopic() { String topic = ""; boolean result = BrokerMetricsManager.isRetryOrDlqTopic(topic); assertThat(result).isFalse(); } @Test public void testIsRetryOrDlqTopicWithNullTopic() { String topic = null; boolean result = BrokerMetricsManager.isRetryOrDlqTopic(topic); assertThat(result).isFalse(); } @Test public void testIsSystemGroup_SystemGroup_ReturnsTrue() { String group = "FooGroup"; String systemGroup = MixAll.CID_RMQ_SYS_PREFIX + group; boolean result = BrokerMetricsManager.isSystemGroup(systemGroup); assertThat(result).isTrue(); } @Test public void testIsSystemGroup_NonSystemGroup_ReturnsFalse() { String group = "FooGroup"; boolean result = BrokerMetricsManager.isSystemGroup(group); assertThat(result).isFalse(); } @Test public void testIsSystemGroup_EmptyGroup_ReturnsFalse() { String group = ""; boolean result = BrokerMetricsManager.isSystemGroup(group); assertThat(result).isFalse(); } @Test public void testIsSystemGroup_NullGroup_ReturnsFalse() { String group = null; boolean result = BrokerMetricsManager.isSystemGroup(group); assertThat(result).isFalse(); } @Test public void testIsSystem_SystemTopicOrSystemGroup_ReturnsTrue() { String topic = "FooTopic"; String group = "FooGroup"; String systemTopic = TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC; String systemGroup = MixAll.CID_RMQ_SYS_PREFIX + group; boolean resultTopic = BrokerMetricsManager.isSystem(systemTopic, group); assertThat(resultTopic).isTrue(); boolean resultGroup = BrokerMetricsManager.isSystem(topic, systemGroup); assertThat(resultGroup).isTrue(); } @Test public void testIsSystem_NonSystemTopicAndGroup_ReturnsFalse() { String topic = "FooTopic"; String group = "FooGroup"; boolean result = BrokerMetricsManager.isSystem(topic, group); assertThat(result).isFalse(); } @Test public void testIsSystem_EmptyTopicAndGroup_ReturnsFalse() { String topic = ""; String group = ""; boolean result = BrokerMetricsManager.isSystem(topic, group); assertThat(result).isFalse(); } @Test public void testGetMessageTypeAsNormal() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setProperties(""); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.NORMAL).isEqualTo(result); } @Test public void testGetMessageTypeAsTransaction() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.TRANSACTION).isEqualTo(result); } @Test public void testGetMessageTypeAsFifo() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_SHARDING_KEY, "shardingKey"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.FIFO).isEqualTo(result); } @Test public void testGetMessageTypeAsDelayLevel() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "1"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.DELAY).isEqualTo(result); } @Test public void testGetMessageTypeAsDeliverMS() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_TIMER_DELIVER_MS, "10"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.DELAY).isEqualTo(result); } @Test public void testGetMessageTypeAsDelaySEC() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_TIMER_DELAY_SEC, "1"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.DELAY).isEqualTo(result); } @Test public void testGetMessageTypeAsDelayMS() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_TIMER_DELAY_MS, "10"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.DELAY).isEqualTo(result); } @Test public void testGetMessageTypeWithUnknownProperty() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put("unknownProperty", "unknownValue"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.NORMAL).isEqualTo(result); } @Test public void testGetMessageTypeWithMultipleProperties() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "1"); map.put(MessageConst.PROPERTY_SHARDING_KEY, "shardingKey"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.FIFO).isEqualTo(result); } @Test public void testGetMessageTypeWithTransactionFlagButOtherPropertiesPresent() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); map.put(MessageConst.PROPERTY_SHARDING_KEY, "shardingKey"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); TopicMessageType result = BrokerMetricsManager.getMessageType(requestHeader); assertThat(TopicMessageType.TRANSACTION).isEqualTo(result); } @Test public void testGetMessageTypeWithEmptyProperties() { TopicMessageType result = BrokerMetricsManager.getMessageType(new SendMessageRequestHeader()); assertThat(TopicMessageType.NORMAL).isEqualTo(result); } @Test public void testCreateMetricsManager() { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + UUID.randomUUID(); messageStoreConfig.setStorePathRootDir(storePathRootDir); BrokerConfig brokerConfig = new BrokerConfig(); NettyServerConfig nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(0); BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); BrokerMetricsManager metricsManager = new BrokerMetricsManager(brokerController); assertThat(metricsManager.getBrokerMeter()).isNull(); } @Test public void testCreateMetricsManagerLogType() throws CloneNotSupportedException { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setMetricsExporterType(MetricsExporterType.LOG); brokerConfig.setMetricsLabel("label1:value1;label2:value2"); brokerConfig.setMetricsOtelCardinalityLimit(1); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + UUID.randomUUID(); messageStoreConfig.setStorePathRootDir(storePathRootDir); NettyServerConfig nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(0); BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); brokerController.initialize(); BrokerMetricsManager metricsManager = new BrokerMetricsManager(brokerController); assertThat(metricsManager.getBrokerMeter()).isNotNull(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/metrics/LiteConsumerLagCalculatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.metrics; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.PriorityBlockingQueue; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.entity.TopicGroup; import org.apache.rocketmq.common.lite.LiteLagInfo; import org.apache.rocketmq.common.lite.LiteUtil; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteConsumerLagCalculatorTest { private LiteConsumerLagCalculator liteConsumerLagCalculator; @Mock private BrokerController brokerController; @Mock private ConsumerOffsetManager consumerOffsetManager; private final BrokerConfig brokerConfig = new BrokerConfig(); @Before public void setUp() { when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); liteConsumerLagCalculator = new LiteConsumerLagCalculator(brokerController); } @Test public void testUpdateLagInfo() { String group = "testGroup"; String topic = "testTopic"; String lmqName = LiteUtil.toLmqName(topic, "lmq1"); long storeTimestamp = System.currentTimeMillis(); liteConsumerLagCalculator.updateLagInfo(group, topic, lmqName, storeTimestamp); TopicGroup topicGroup = new TopicGroup(topic, group); PriorityBlockingQueue lagHeap = liteConsumerLagCalculator.topicGroupLagTimeMap.get(topicGroup); assertNotNull(lagHeap); assertEquals(1, lagHeap.size()); LiteConsumerLagCalculator.LagTimeInfo lagInfo = lagHeap.peek(); assertNotNull(lagInfo); assertEquals(lmqName, lagInfo.getLmqName()); assertEquals(storeTimestamp, lagInfo.getLagTimestamp()); } @Test public void testUpdateLagInfo_KeepSmallestWhenExceedsCapacity() { String group = "testGroup"; String topic = "testTopic"; // Set topK to 3, so the heap will retain at most 3 elements brokerConfig.setLiteLagLatencyTopK(3); // Add 5 elements with timestamps 1000, 2000, 3000, 4000, 5000 // Expected result is to retain the smallest 3: 1000, 2000, 3000 liteConsumerLagCalculator.updateLagInfo(group, topic, LiteUtil.toLmqName(topic, "lmq1"), 3000L); liteConsumerLagCalculator.updateLagInfo(group, topic, LiteUtil.toLmqName(topic, "lmq2"), 1000L); liteConsumerLagCalculator.updateLagInfo(group, topic, LiteUtil.toLmqName(topic, "lmq3"), 5000L); liteConsumerLagCalculator.updateLagInfo(group, topic, LiteUtil.toLmqName(topic, "lmq4"), 2000L); liteConsumerLagCalculator.updateLagInfo(group, topic, LiteUtil.toLmqName(topic, "lmq5"), 4000L); // Verify that the heap contains only 3 elements TopicGroup topicGroup = new TopicGroup(topic, group); PriorityBlockingQueue lagHeap = liteConsumerLagCalculator.topicGroupLagTimeMap.get(topicGroup); assertNotNull(lagHeap); assertEquals(3, lagHeap.size()); // Verify that the retained elements have the smallest timestamps: 1000, 2000, 3000 List timestamps = new ArrayList<>(); for (LiteConsumerLagCalculator.LagTimeInfo info : lagHeap) { timestamps.add(info.getLagTimestamp()); } Collections.sort(timestamps); assertEquals(3, timestamps.size()); assertEquals(1000L, timestamps.get(0).longValue()); assertEquals(2000L, timestamps.get(1).longValue()); assertEquals(3000L, timestamps.get(2).longValue()); } @Test public void testRemoveLagInfo() { String group = "testGroup"; String topic = "testTopic"; String lmqName = LiteUtil.toLmqName(topic, "lmq1"); long storeTimestamp = System.currentTimeMillis(); liteConsumerLagCalculator.updateLagInfo(group, topic, lmqName, storeTimestamp); liteConsumerLagCalculator.removeLagInfo(group, topic, lmqName); TopicGroup topicGroup = new TopicGroup(topic, group); PriorityBlockingQueue lagHeap = liteConsumerLagCalculator.topicGroupLagTimeMap.get(topicGroup); assertTrue(lagHeap.isEmpty()); } @Test public void testOffsetTableForEachByGroup() { String testTopic = "testTopic"; String liteTopic = "lmq1"; String testGroup = "testGroup"; String otherGroup = "otherGroup"; String lmqName = LiteUtil.toLmqName(testTopic, liteTopic); String key = lmqName + "@" + testGroup; // Prepare test data without thread-safe classes ConcurrentMap> offsetTable = new ConcurrentHashMap<>(); ConcurrentMap queueOffsetMap = new ConcurrentHashMap<>(); queueOffsetMap.put(0, 100L); offsetTable.put(key, queueOffsetMap); when(consumerOffsetManager.getOffsetTable()).thenReturn(offsetTable); // Test processing all groups final boolean[] processed = {false}; liteConsumerLagCalculator.offsetTableForEachByGroup(null, (topicGroup, offset) -> { processed[0] = true; assertEquals(lmqName, topicGroup.topic); assertEquals(testGroup, topicGroup.group); assertEquals(Long.valueOf(100L), offset); }); assertTrue(processed[0]); // Test processing specific group processed[0] = false; liteConsumerLagCalculator.offsetTableForEachByGroup(testGroup, (topicGroup, offset) -> { processed[0] = true; assertEquals(lmqName, topicGroup.topic); assertEquals(testGroup, topicGroup.group); assertEquals(Long.valueOf(100L), offset); }); assertTrue(processed[0]); // Test processing non-matching group processed[0] = false; liteConsumerLagCalculator.offsetTableForEachByGroup(otherGroup, (topicGroup, offset) -> processed[0] = true); assertFalse(processed[0]); } @Test public void testGetLagTimestampTopK_NormalCase() { // Prepare test data String group = "testGroup"; String parentTopic = "testParentTopic"; String lmq1 = LiteUtil.toLmqName(parentTopic, "lmq1"); String lmq2 = LiteUtil.toLmqName(parentTopic, "lmq2"); String lmq3 = LiteUtil.toLmqName(parentTopic, "lmq3"); long timestamp1 = 1000L; long timestamp2 = 2000L; long timestamp3 = 1500L; // Consumer offsets long consumerOffset1 = 50L; // long consumerOffset2 = 30L; long consumerOffset3 = 40L; // Max offsets long maxOffset1 = 100L; // long maxOffset2 = 80L; long maxOffset3 = 90L; // Create a spy of the calculator to allow partial mocking LiteConsumerLagCalculator spyCalculator = spy(liteConsumerLagCalculator); // Add lag info to the spy calculator spyCalculator.updateLagInfo(group, parentTopic, lmq1, timestamp1); spyCalculator.updateLagInfo(group, parentTopic, lmq2, timestamp2); spyCalculator.updateLagInfo(group, parentTopic, lmq3, timestamp3); // Mock getOffset and getMaxOffset methods on the spy doReturn(consumerOffset1).when(spyCalculator).getOffset(group, lmq1); // doReturn(consumerOffset2).when(spyCalculator).getOffset(group, lmq2); doReturn(consumerOffset3).when(spyCalculator).getOffset(group, lmq3); doReturn(maxOffset1).when(spyCalculator).getMaxOffset(lmq1); // doReturn(maxOffset2).when(spyCalculator).getMaxOffset(lmq2); doReturn(maxOffset3).when(spyCalculator).getMaxOffset(lmq3); // Test with topK = 2 Pair, Long> result = spyCalculator.getLagTimestampTopK(group, parentTopic, 2); // Verify results assertNotNull(result); assertEquals(2, result.getObject1().size()); // Should be sorted by timestamp in ascending order assertEquals(timestamp1, result.getObject1().get(0).getEarliestUnconsumedTimestamp()); assertEquals(timestamp3, result.getObject1().get(1).getEarliestUnconsumedTimestamp()); // Verify lag counts (maxOffset - consumerOffset) assertEquals(maxOffset1 - consumerOffset1, result.getObject1().get(0).getLagCount()); assertEquals(maxOffset3 - consumerOffset3, result.getObject1().get(1).getLagCount()); // Verify lite topics assertEquals("lmq1", result.getObject1().get(0).getLiteTopic()); assertEquals("lmq3", result.getObject1().get(1).getLiteTopic()); // Verify earliest timestamp assertEquals(timestamp1, result.getObject2().longValue()); } @Test public void testGetLagCountTopK_NormalCase() { String group = "testGroup"; String topic = "testTopic"; String lmqName1 = LiteUtil.toLmqName(topic, "lmq1"); String lmqName2 = LiteUtil.toLmqName(topic, "lmq2"); String lmqName3 = LiteUtil.toLmqName(topic, "lmq3"); // Prepare offset table data ConcurrentMap> offsetTable = new ConcurrentHashMap<>(); ConcurrentMap queueOffsetMap1 = new ConcurrentHashMap<>(); ConcurrentMap queueOffsetMap2 = new ConcurrentHashMap<>(); ConcurrentMap queueOffsetMap3 = new ConcurrentHashMap<>(); long consumerOffset1 = 50L; long consumerOffset2 = 30L; long consumerOffset3 = 70L; queueOffsetMap1.put(0, consumerOffset1); queueOffsetMap2.put(0, consumerOffset2); queueOffsetMap3.put(0, consumerOffset3); offsetTable.put(lmqName1 + "@" + group, queueOffsetMap1); offsetTable.put(lmqName2 + "@" + group, queueOffsetMap2); offsetTable.put(lmqName3 + "@" + group, queueOffsetMap3); when(consumerOffsetManager.getOffsetTable()).thenReturn(offsetTable); // Mock store timestamps long timestamp1 = 1000L; long timestamp2 = 2000L; long timestamp3 = 1500L; // Create a spy of the calculator to allow partial mocking LiteConsumerLagCalculator spyCalculator = spy(liteConsumerLagCalculator); // Mock getStoreTimestamp method on the spy doReturn(timestamp1).when(spyCalculator).getStoreTimestamp(lmqName1, consumerOffset1); doReturn(timestamp2).when(spyCalculator).getStoreTimestamp(lmqName2, consumerOffset2); doReturn(timestamp3).when(spyCalculator).getStoreTimestamp(lmqName3, consumerOffset3); // Mock getMaxOffset method on the spy doReturn(100L).when(spyCalculator).getMaxOffset(lmqName1); doReturn(80L).when(spyCalculator).getMaxOffset(lmqName2); doReturn(90L).when(spyCalculator).getMaxOffset(lmqName3); // Test with topK = 2 Pair, Long> result = spyCalculator.getLagCountTopK(group, 2); // Verify results assertNotNull(result); assertNotNull(result.getObject1()); assertEquals(2, result.getObject1().size()); // Should be sorted by lag count in descending order // lmq1: 100-50=50, lmq2: 80-30=50, lmq3: 90-70=20 // So order should be lmq1(50), lmq2(50) or lmq2(50), lmq1(50) (both have same lag count) LiteLagInfo first = result.getObject1().get(0); LiteLagInfo second = result.getObject1().get(1); // Verify lag counts assertEquals(50L, first.getLagCount()); assertEquals(50L, second.getLagCount()); // Verify lite topics assertTrue(first.getLiteTopic().equals("lmq1") || first.getLiteTopic().equals("lmq2")); assertTrue(second.getLiteTopic().equals("lmq1") || second.getLiteTopic().equals("lmq2")); // Verify timestamps assertTrue(first.getEarliestUnconsumedTimestamp() == timestamp1 || first.getEarliestUnconsumedTimestamp() == timestamp2); assertTrue(second.getEarliestUnconsumedTimestamp() == timestamp1 || second.getEarliestUnconsumedTimestamp() == timestamp2); // Verify total lag count assertEquals(120L, result.getObject2().longValue()); // 50 + 50 + 20 } @Test public void testCalculateLiteLagCount() { brokerConfig.setLiteLagCountMetricsEnable(true); String group = "testGroup"; String parentTopic = "testParentTopic"; String lmqName = LiteUtil.toLmqName(parentTopic, "lmq1"); ConcurrentMap> offsetTable = new ConcurrentHashMap<>(); ConcurrentMap queueOffsetMap = new ConcurrentHashMap<>(); queueOffsetMap.put(0, 50L); offsetTable.put(lmqName + "@" + group, queueOffsetMap); when(consumerOffsetManager.getOffsetTable()).thenReturn(offsetTable); LiteConsumerLagCalculator spyCalculator = spy(liteConsumerLagCalculator); doReturn(100L).when(spyCalculator).getMaxOffset(lmqName); final ConsumerLagCalculator.CalculateLagResult[] result = {null}; spyCalculator.calculateLiteLagCount(lagResult -> result[0] = lagResult); assertNotNull(result[0]); assertEquals(group, result[0].group); // The metrics of liteTopic are aggregated under its parent topic assertEquals(parentTopic, result[0].topic); assertEquals(50L, result[0].lag); } @Test public void testCalculateLiteLagLatency() { brokerConfig.setLiteLagLatencyMetricsEnable(true); String group = "testGroup"; String parentTopic = "testParentTopic"; String lmqName = LiteUtil.toLmqName(parentTopic, "lmq1"); long storeTimestamp = System.currentTimeMillis(); liteConsumerLagCalculator.updateLagInfo(group, parentTopic, lmqName, storeTimestamp); final ConsumerLagCalculator.CalculateLagResult[] result = {null}; liteConsumerLagCalculator.calculateLiteLagLatency(lagResult -> result[0] = lagResult); assertNotNull(result[0]); assertEquals(group, result[0].group); // The metrics of liteTopic are aggregated under its parent topic assertEquals(parentTopic, result[0].topic); assertEquals(storeTimestamp, result[0].earliestUnconsumedTimestamp); } @Test public void testUpdateLagInfoWithDuplicateElements() { String group = "testGroup"; String parentTopic = "testParentTopic"; String lmqName1 = "lmq1"; String lmqName2 = "lmq2"; String lmqName3 = "lmq3"; long storeTimestamp1 = 1000L; long storeTimestamp2 = 2000L; long storeTimestamp3 = 3000L; // Add three LMQs with different timestamps, each added three times for (int i = 0; i < 3; i++) { liteConsumerLagCalculator.updateLagInfo(group, parentTopic, lmqName1, storeTimestamp1 + i * 100); liteConsumerLagCalculator.updateLagInfo(group, parentTopic, lmqName2, storeTimestamp2 + i * 100); liteConsumerLagCalculator.updateLagInfo(group, parentTopic, lmqName3, storeTimestamp3 + i * 100); } // Verify that the heap contains exactly 3 elements PriorityBlockingQueue lagHeap = liteConsumerLagCalculator.topicGroupLagTimeMap .get(new TopicGroup(parentTopic, group)); assertNotNull(lagHeap); assertEquals(3, lagHeap.size()); // Verify that each LMQ is present with its latest timestamp assertTrue(lagHeap.contains(new LiteConsumerLagCalculator.LagTimeInfo(lmqName1, storeTimestamp1 + 200))); assertTrue(lagHeap.contains(new LiteConsumerLagCalculator.LagTimeInfo(lmqName2, storeTimestamp2 + 200))); assertTrue(lagHeap.contains(new LiteConsumerLagCalculator.LagTimeInfo(lmqName3, storeTimestamp3 + 200))); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.time.Duration; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.stubbing.Answer; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class BroadcastOffsetManagerTest { private final AtomicLong maxOffset = new AtomicLong(10L); private final AtomicLong commitOffset = new AtomicLong(-1); private final ConsumerOffsetManager consumerOffsetManager = mock(ConsumerOffsetManager.class); private final ConsumerManager consumerManager = mock(ConsumerManager.class); private final BrokerConfig brokerConfig = new BrokerConfig(); private final Set onlineClientIdSet = new HashSet<>(); private BroadcastOffsetManager broadcastOffsetManager; @Before public void before() throws ConsumeQueueException { brokerConfig.setEnableBroadcastOffsetStore(true); brokerConfig.setBroadcastOffsetExpireSecond(1); brokerConfig.setBroadcastOffsetExpireMaxSecond(5); BrokerController brokerController = mock(BrokerController.class); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getConsumerManager()).thenReturn(consumerManager); doAnswer((Answer) mock -> { String clientId = mock.getArgument(1); if (onlineClientIdSet.contains(clientId)) { return new ClientChannelInfo(null); } return null; }).when(consumerManager).findChannel(anyString(), anyString()); doAnswer((Answer) mock -> commitOffset.get()) .when(consumerOffsetManager).queryOffset(anyString(), anyString(), anyInt()); doAnswer((Answer) mock -> { commitOffset.set(mock.getArgument(4)); return null; }).when(consumerOffsetManager).commitOffset(anyString(), anyString(), anyString(), anyInt(), anyLong()); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); MessageStore messageStore = mock(MessageStore.class); doAnswer((Answer) mock -> maxOffset.get()) .when(messageStore).getMaxOffsetInQueue(anyString(), anyInt(), anyBoolean()); when(brokerController.getMessageStore()).thenReturn(messageStore); broadcastOffsetManager = new BroadcastOffsetManager(brokerController); } @Test public void testBroadcastOffsetSwitch() throws ConsumeQueueException { // client1 connect to broker onlineClientIdSet.add("client1"); long offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client1", 0, false); Assert.assertEquals(-1, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 10, "client1", false); offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client1", 11, false); Assert.assertEquals(-1, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 11, "client1", false); // client1 connect to proxy offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client1", -1, true); Assert.assertEquals(11, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 11, "client1", true); offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client1", 11, true); Assert.assertEquals(-1, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 12, "client1", true); broadcastOffsetManager.scanOffsetData(); Assert.assertEquals(12L, commitOffset.get()); // client2 connect to proxy onlineClientIdSet.add("client2"); offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client2", -1, true); Assert.assertEquals(12, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 12, "client2", true); offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client2", 11, true); Assert.assertEquals(-1, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 13, "client2", true); broadcastOffsetManager.scanOffsetData(); Assert.assertEquals(12L, commitOffset.get()); // client1 connect to broker offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client1", 20, false); Assert.assertEquals(12, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 12, "client1", false); offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client1", 12, false); Assert.assertEquals(-1, offset); onlineClientIdSet.clear(); maxOffset.set(30L); // client3 connect to broker onlineClientIdSet.add("client3"); offset = broadcastOffsetManager.queryInitOffset("group", "topic", 0, "client3", 30, false); Assert.assertEquals(-1, offset); broadcastOffsetManager.updateOffset("group", "topic", 0, 30, "client3", false); await().atMost(Duration.ofSeconds(brokerConfig.getBroadcastOffsetExpireSecond() + 1)).until(() -> { broadcastOffsetManager.scanOffsetData(); return commitOffset.get() == 30L; }); } @Test public void testBroadcastOffsetExpire() { onlineClientIdSet.add("client1"); broadcastOffsetManager.updateOffset( "group", "topic", 0, 10, "client1", false); onlineClientIdSet.clear(); await().atMost(Duration.ofSeconds(brokerConfig.getBroadcastOffsetExpireSecond() + 1)).until(() -> { broadcastOffsetManager.scanOffsetData(); return broadcastOffsetManager.offsetStoreMap.isEmpty(); }); onlineClientIdSet.add("client1"); broadcastOffsetManager.updateOffset( "group", "topic", 0, 10, "client1", false); await().atMost(Duration.ofSeconds(brokerConfig.getBroadcastOffsetExpireMaxSecond() + 1)).until(() -> { broadcastOffsetManager.scanOffsetData(); return broadcastOffsetManager.offsetStoreMap.isEmpty(); }); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import org.junit.Assert; import org.junit.Test; public class BroadcastOffsetStoreTest { @Test public void testBasicOffsetStore() { BroadcastOffsetStore offsetStore = new BroadcastOffsetStore(); offsetStore.updateOffset(0, 100L, false); offsetStore.updateOffset(1, 200L, false); Assert.assertEquals(100L, offsetStore.readOffset(0)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.mockito.Mockito; import static org.apache.rocketmq.broker.offset.ConsumerOffsetManager.TOPIC_GROUP_SEPARATOR; import static org.assertj.core.api.Assertions.assertThat; public class ConsumerOffsetManagerTest { private static final String KEY = "FooBar@FooBarGroup"; private BrokerController brokerController; private ConsumerOffsetManager consumerOffsetManager; @Before @SuppressWarnings("DoubleBraceInitialization") public void init() { brokerController = Mockito.mock(BrokerController.class); consumerOffsetManager = new ConsumerOffsetManager(brokerController); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); ConcurrentHashMap> offsetTable = new ConcurrentHashMap<>(512); offsetTable.put(KEY,new ConcurrentHashMap() {{ put(1,2L); put(2,3L); }}); consumerOffsetManager.setOffsetTable(offsetTable); } @Test public void cleanOffsetByTopic_NotExist() { consumerOffsetManager.cleanOffsetByTopic("InvalidTopic"); assertThat(consumerOffsetManager.getOffsetTable().containsKey(KEY)).isTrue(); } @Test public void cleanOffsetByTopic_Exist() { consumerOffsetManager.cleanOffsetByTopic("FooBar"); assertThat(!consumerOffsetManager.getOffsetTable().containsKey(KEY)).isTrue(); } @Test public void removeOffsetByGroupTest() { String topic = "TopicName"; String group = "GroupName"; Mockito.when(brokerController.getBrokerConfig()).thenReturn(new BrokerConfig()); consumerOffsetManager.commitOffset("Commit", group, topic, 0, 100); consumerOffsetManager.assignResetOffset(topic, group, 0, 100); consumerOffsetManager.commitPullOffset("Pull", group, topic, 0, 100); consumerOffsetManager.removeOffset(group); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(topic + TOPIC_GROUP_SEPARATOR + group)); consumerOffsetManager.commitPullOffset("Pull", group, topic, 0, 100); consumerOffsetManager.clearPullOffset(group, topic); Assert.assertEquals(-1L, consumerOffsetManager.queryPullOffset(group, topic, 0)); } @Test public void testOffsetPersistInMemory() { ConcurrentMap> offsetTable = consumerOffsetManager.getOffsetTable(); ConcurrentMap table = new ConcurrentHashMap<>(); table.put(0, 1L); table.put(1, 3L); String group = "G1"; offsetTable.put(group, table); consumerOffsetManager.persist(); ConsumerOffsetManager manager = new ConsumerOffsetManager(brokerController); manager.load(); ConcurrentMap offsetTableLoaded = manager.getOffsetTable().get(group); Assert.assertEquals(table, offsetTableLoaded); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.io.File; import java.util.Map; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.subscription.LmqSubscriptionGroupManager; import org.apache.rocketmq.broker.topic.LmqTopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Test; import org.mockito.Spy; import static org.assertj.core.api.Assertions.assertThat; public class LmqConsumerOffsetManagerTest { @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Test public void testOffsetManage() { LmqConsumerOffsetManager lmqConsumerOffsetManager = new LmqConsumerOffsetManager(brokerController); LmqTopicConfigManager lmqTopicConfigManager = new LmqTopicConfigManager(brokerController); LmqSubscriptionGroupManager lmqSubscriptionGroupManager = new LmqSubscriptionGroupManager(brokerController); String lmqTopicName = "%LMQ%1111"; TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(lmqTopicName); lmqTopicConfigManager.updateTopicConfig(topicConfig); TopicConfig topicConfig1 = lmqTopicConfigManager.selectTopicConfig(lmqTopicName); assertThat(topicConfig1.getTopicName()).isEqualTo(topicConfig.getTopicName()); String lmqGroupName = "%LMQ%GID_test"; SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(lmqGroupName); lmqSubscriptionGroupManager.updateSubscriptionGroupConfig(subscriptionGroupConfig); SubscriptionGroupConfig subscriptionGroupConfig1 = lmqSubscriptionGroupManager.findSubscriptionGroupConfig( lmqGroupName); assertThat(subscriptionGroupConfig1.getGroupName()).isEqualTo(subscriptionGroupConfig.getGroupName()); lmqConsumerOffsetManager.commitOffset("127.0.0.1", lmqGroupName, lmqTopicName, 0, 10L); Map integerLongMap = lmqConsumerOffsetManager.queryOffset(lmqGroupName, lmqTopicName); assertThat(integerLongMap.get(0)).isEqualTo(10L); long offset = lmqConsumerOffsetManager.queryOffset(lmqGroupName, lmqTopicName, 0); assertThat(offset).isEqualTo(10L); long offset1 = lmqConsumerOffsetManager.queryOffset(lmqGroupName, lmqTopicName + "test", 0); assertThat(offset1).isEqualTo(-1L); } @Test public void testOffsetManage1() { LmqConsumerOffsetManager lmqConsumerOffsetManager = new LmqConsumerOffsetManager(brokerController); String lmqTopicName = "%LMQ%1111"; String lmqGroupName = "%LMQ%GID_test"; lmqConsumerOffsetManager.commitOffset("127.0.0.1", lmqGroupName, lmqTopicName, 0, 10L); lmqTopicName = "%LMQ%1222"; lmqGroupName = "%LMQ%GID_test222"; lmqConsumerOffsetManager.commitOffset("127.0.0.1", lmqGroupName, lmqTopicName, 0, 10L); lmqConsumerOffsetManager.commitOffset("127.0.0.1","GID_test1", "MqttTest",0, 10L); String json = lmqConsumerOffsetManager.encode(true); LmqConsumerOffsetManager lmqConsumerOffsetManager1 = new LmqConsumerOffsetManager(brokerController); lmqConsumerOffsetManager1.decode(json); assertThat(lmqConsumerOffsetManager1.getOffsetTable().size()).isEqualTo(1); assertThat(lmqConsumerOffsetManager1.getLmqOffsetTable().size()).isEqualTo(2); } @After public void destroy() { UtilAll.deleteFile(new File(new MessageStoreConfig().getStorePathRootDir())); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/RocksDBConsumerOffsetManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.io.File; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import static org.apache.rocketmq.broker.offset.ConsumerOffsetManager.TOPIC_GROUP_SEPARATOR; import static org.assertj.core.api.Assertions.assertThat; public class RocksDBConsumerOffsetManagerTest { private static final String SKIP_MAC_KEY = "skipMac"; private static final String KEY = "FooBar@FooBarGroup"; private BrokerController brokerController; private ConsumerOffsetManager consumerOffsetManager; private BrokerConfig brokerConfig; @Before public void init() { // System.setProperty(SKIP_MAC_KEY, "false"); skipMacIfNecessary(); brokerController = Mockito.mock(BrokerController.class); brokerConfig = new BrokerConfig(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); consumerOffsetManager = new RocksDBConsumerOffsetManager(brokerController); consumerOffsetManager.load(); ConcurrentHashMap> offsetTable = new ConcurrentHashMap<>(512); ConcurrentHashMap innerMap = new ConcurrentHashMap<>(); innerMap.put(1, 2L); innerMap.put(2, 3L); offsetTable.put(KEY, innerMap); consumerOffsetManager.setOffsetTable(offsetTable); } @After public void destroy() { if (consumerOffsetManager != null) { consumerOffsetManager.stop(); File file = new File(((RocksDBConsumerOffsetManager) consumerOffsetManager).rocksdbConfigFilePath(null, false)); UtilAll.deleteFile(file); } } @Test public void cleanOffsetByTopic_NotExist() { consumerOffsetManager.cleanOffsetByTopic("InvalidTopic"); assertThat(consumerOffsetManager.getOffsetTable().containsKey(KEY)).isTrue(); } @Test public void cleanOffsetByTopic_Exist() { consumerOffsetManager.cleanOffsetByTopic("FooBar"); assertThat(!consumerOffsetManager.getOffsetTable().containsKey(KEY)).isTrue(); } @Test public void testOffsetPersistInMemory() { ConcurrentMap> offsetTable = consumerOffsetManager.getOffsetTable(); ConcurrentMap table = new ConcurrentHashMap<>(); table.put(0, 1L); table.put(1, 3L); String group = "G1"; offsetTable.put(group, table); consumerOffsetManager.persist(); consumerOffsetManager.stop(); consumerOffsetManager.load(); ConcurrentMap offsetTableLoaded = consumerOffsetManager.getOffsetTable().get(group); Assert.assertEquals(table, offsetTableLoaded); } @Test public void testCommitOffset_persist_periodically() { brokerConfig.setPersistConsumerOffsetIncrementally(false); String group = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); String key = topic + TOPIC_GROUP_SEPARATOR + group; // 1. commit but not persist Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.commitOffset("ClientID", group, topic, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key)); // not in kv // 2. commit and persist consumerOffsetManager.commitOffset("ClientID", group, topic, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.persist(); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key)); // load from kv } @Test public void testCommitOffset_persist_incrementally() { brokerConfig.setPersistConsumerOffsetIncrementally(true); String group = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); String key = topic + TOPIC_GROUP_SEPARATOR + group; // commit but not persist Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.commitOffset("ClientID", group, topic, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key)); // reload from kv } @Test public void testRemoveConsumerOffset() { String group = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); String key = topic + TOPIC_GROUP_SEPARATOR + group; // commit and persist Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.commitOffset("ClientID", group, topic, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key)); consumerOffsetManager.persist(); consumerOffsetManager.removeConsumerOffset(key); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key)); // removed from kv } @Test public void testRemoveOffset() { String group = UUID.randomUUID().toString(); String topic1 = UUID.randomUUID().toString(); String topic2 = UUID.randomUUID().toString(); String key1 = topic1 + TOPIC_GROUP_SEPARATOR + group; String key2 = topic2 + TOPIC_GROUP_SEPARATOR + group; // commit and persist Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.commitOffset("ClientID", group, topic1, 0, 1); consumerOffsetManager.commitOffset("ClientID", group, topic2, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.persist(); // remove all offsets by group consumerOffsetManager.removeOffset(group); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); // removed from kv Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); // removed from kv } @Test // similar to testRemoveOffset() public void testCleanOffset() { String group = UUID.randomUUID().toString(); String topic1 = UUID.randomUUID().toString(); String topic2 = UUID.randomUUID().toString(); String key1 = topic1 + TOPIC_GROUP_SEPARATOR + group; String key2 = topic2 + TOPIC_GROUP_SEPARATOR + group; // commit and persist Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.commitOffset("ClientID", group, topic1, 0, 1); consumerOffsetManager.commitOffset("ClientID", group, topic2, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.persist(); // remove all offsets by group consumerOffsetManager.cleanOffset(group); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); // removed from kv Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); // removed from kv } @Test public void testCleanOffsetByTopic() { String group1 = UUID.randomUUID().toString(); String group2 = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); String key1 = topic + TOPIC_GROUP_SEPARATOR + group1; String key2 = topic + TOPIC_GROUP_SEPARATOR + group2; // commit and persist Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.commitOffset("ClientID", group1, topic, 0, 1); consumerOffsetManager.commitOffset("ClientID", group2, topic, 0, 1); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertTrue(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.persist(); // remove all offsets by group consumerOffsetManager.cleanOffsetByTopic(topic); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); consumerOffsetManager.stop(); consumerOffsetManager.getOffsetTable().clear(); consumerOffsetManager.load(); Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key1)); // removed from kv Assert.assertFalse(consumerOffsetManager.getOffsetTable().containsKey(key2)); // removed from kv } @Test public void testUpdateDataVersion() { Assert.assertEquals(0, consumerOffsetManager.getDataVersion().getCounter().get()); for (int i = 0; i < 10; i++) { ((RocksDBConsumerOffsetManager) consumerOffsetManager).updateDataVersion(); } Assert.assertEquals(10, consumerOffsetManager.getDataVersion().getCounter().get()); } @Test public void testLoadDataVersion() { for (int i = 0; i < 10; i++) { ((RocksDBConsumerOffsetManager) consumerOffsetManager).updateDataVersion(); } consumerOffsetManager.stop(); consumerOffsetManager.load(); Assert.assertEquals(10, consumerOffsetManager.getDataVersion().getCounter().get()); } private static void skipMacIfNecessary() { boolean skipMac = Boolean.parseBoolean(System.getProperty(SKIP_MAC_KEY, "true")); Assume.assumeFalse(MixAll.isMac() && skipMac); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/RocksDBLmqConsumerOffsetManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; public class RocksDBLmqConsumerOffsetManagerTest { private static final String LMQ_GROUP = MixAll.LMQ_PREFIX + "FooBarGroup"; private static final String NON_LMQ_GROUP = "nonLmqGroup"; private static final String LMQ_TOPIC = MixAll.LMQ_PREFIX + "FooBarTopic"; private static final String NON_LMQ_TOPIC = "FooBarTopic"; private static final int QUEUE_ID = 0; private static final long OFFSET = 12345; private BrokerController brokerController; private RocksDBConsumerOffsetManager offsetManager; @Before public void setUp() { brokerController = Mockito.mock(BrokerController.class); when(brokerController.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); when(brokerController.getBrokerConfig()).thenReturn(new BrokerConfig()); offsetManager = new RocksDBConsumerOffsetManager(brokerController); } @Test public void testQueryOffsetForNonLmq() { long actualOffset = offsetManager.queryOffset(NON_LMQ_GROUP, NON_LMQ_TOPIC, QUEUE_ID); // Verify assertEquals("Offset should not be null.", -1, actualOffset); } @Test public void testQueryOffsetForLmqGroupWithExistingOffset() { offsetManager.commitOffset("127.0.0.1",LMQ_GROUP, LMQ_TOPIC, QUEUE_ID, OFFSET); // Act Map actualOffsets = offsetManager.queryOffset(LMQ_GROUP, LMQ_TOPIC); // Assert assertNotNull(actualOffsets); assertEquals(1, actualOffsets.size()); assertEquals(OFFSET, (long) actualOffsets.get(0)); } @Test public void testQueryOffsetForLmqGroupWithoutExistingOffset() { // Act Map actualOffsets = offsetManager.queryOffset(LMQ_GROUP, "nonExistingTopic"); // Assert assertNull(actualOffsets); } @Test public void testQueryOffsetForNonLmqGroup() { // Arrange Map mockOffsets = new HashMap<>(); mockOffsets.put(QUEUE_ID, OFFSET); offsetManager.commitOffset("clientHost", NON_LMQ_GROUP, NON_LMQ_TOPIC, QUEUE_ID, OFFSET); // Act Map actualOffsets = offsetManager.queryOffset(NON_LMQ_GROUP, NON_LMQ_TOPIC); // Assert assertNotNull(actualOffsets); assertEquals("Offsets should match the mocked return value for non-LMQ groups", mockOffsets, actualOffsets); } @Test public void testCommitOffsetForLmq() { // Execute offsetManager.commitOffset("clientHost", LMQ_GROUP, LMQ_TOPIC, QUEUE_ID, OFFSET); // Verify Long expectedOffset = offsetManager.getOffsetTable().get(getLMQKey()).get(QUEUE_ID); assertEquals("Offset should be updated correctly.", OFFSET, expectedOffset.longValue()); } private String getLMQKey() { return LMQ_TOPIC + "@" + LMQ_GROUP; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/RocksDBOffsetSerializeWrapperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.config.v1.RocksDBOffsetSerializeWrapper; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class RocksDBOffsetSerializeWrapperTest { private RocksDBOffsetSerializeWrapper wrapper; @Before public void setUp() { wrapper = new RocksDBOffsetSerializeWrapper(); } @Test public void testSetOffsetTable_ShouldSetTheOffsetTableCorrectly() { ConcurrentMap newOffsetTable = new ConcurrentHashMap<>(); wrapper.setOffsetTable(newOffsetTable); ConcurrentMap offsetTable = wrapper.getOffsetTable(); assertEquals("The offsetTable should be the same as the one set", newOffsetTable, offsetTable); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/offset/RocksdbTransferOffsetAndCqTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.offset; import java.io.IOException; import java.nio.file.Paths; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.MapUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.CheckRocksdbCqWriteResult; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.StoreType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.queue.CombineConsumeQueueStore; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStore; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.awaitility.Awaitility; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.rocksdb.RocksDBException; @RunWith(MockitoJUnitRunner.class) public class RocksdbTransferOffsetAndCqTest { private final String basePath = Paths.get(System.getProperty("user.home"), "unit-test-store", UUID.randomUUID().toString().substring(0, 16).toUpperCase()).toString(); private final String topic = "topic"; private final String group = "group"; private final String clientHost = "clientHost"; private final int queueId = 1; private RocksDBConsumerOffsetManager rocksdbConsumerOffsetManager; private ConsumerOffsetManager consumerOffsetManager; private DefaultMessageStore defaultMessageStore; @Mock private BrokerController brokerController; @Before public void init() throws IOException { if (notToBeExecuted()) { return; } BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setConsumerOffsetUpdateVersionStep(10); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(basePath); messageStoreConfig.setRocksdbCQDoubleWriteEnable(true); Mockito.lenient().when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); Mockito.lenient().when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); defaultMessageStore = new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("for-test", true), null, brokerConfig, new ConcurrentHashMap<>()); defaultMessageStore.loadCheckPoint(); consumerOffsetManager = new ConsumerOffsetManager(brokerController); consumerOffsetManager.load(); rocksdbConsumerOffsetManager = new RocksDBConsumerOffsetManager(brokerController); } @Test public void testTransferOffset() { if (notToBeExecuted()) { return; } for (int i = 0; i < 200; i++) { consumerOffsetManager.commitOffset(clientHost, group, topic, queueId, i); } ConcurrentMap> offsetTable = consumerOffsetManager.getOffsetTable(); ConcurrentMap map = offsetTable.get(topic + "@" + group); Assert.assertTrue(MapUtils.isNotEmpty(map)); Long offset = map.get(queueId); Assert.assertEquals(199L, (long) offset); long offsetDataVersion = consumerOffsetManager.getDataVersion().getCounter().get(); Assert.assertEquals(20L, offsetDataVersion); consumerOffsetManager.persist(); boolean loadResult = rocksdbConsumerOffsetManager.load(); Assert.assertTrue(loadResult); ConcurrentMap> rocksdbOffsetTable = rocksdbConsumerOffsetManager.getOffsetTable(); ConcurrentMap rocksdbMap = rocksdbOffsetTable.get(topic + "@" + group); Assert.assertTrue(MapUtils.isNotEmpty(rocksdbMap)); Long aLong1 = rocksdbMap.get(queueId); Assert.assertEquals(199L, (long) aLong1); long rocksdbOffset = rocksdbConsumerOffsetManager.getDataVersion().getCounter().get(); Assert.assertEquals(21L, rocksdbOffset); } @Test public void testRocksdbCqWrite() throws RocksDBException { if (notToBeExecuted()) { return; } long startTimestamp = System.currentTimeMillis(); ConsumeQueueStoreInterface combineConsumeQueueStore = defaultMessageStore.getQueueStore(); Assert.assertTrue(combineConsumeQueueStore instanceof CombineConsumeQueueStore); combineConsumeQueueStore.load(); combineConsumeQueueStore.recover(false); combineConsumeQueueStore.start(); RocksDBConsumeQueueStore rocksDBConsumeQueueStore = ((CombineConsumeQueueStore) combineConsumeQueueStore).getRocksDBConsumeQueueStore(); ConsumeQueueStore consumeQueueStore = ((CombineConsumeQueueStore) combineConsumeQueueStore).getConsumeQueueStore(); for (int i = 0; i < 200; i++) { DispatchRequest request = new DispatchRequest(topic, queueId, i, 200, 0, System.currentTimeMillis(), i, "", "", 0, 0, new HashMap<>()); combineConsumeQueueStore.putMessagePositionInfoWrapper(request); } ConsumeQueueInterface rocksdbCq = rocksDBConsumeQueueStore.findOrCreateConsumeQueue(topic, queueId); ConsumeQueueInterface fileCq = consumeQueueStore.findOrCreateConsumeQueue(topic, queueId); Awaitility.await() .pollInterval(100, TimeUnit.MILLISECONDS) .atMost(3, TimeUnit.SECONDS) .until(() -> rocksdbCq.getMaxOffsetInQueue() == 200); Pair unit = rocksdbCq.getCqUnitAndStoreTime(100); Pair unit1 = fileCq.getCqUnitAndStoreTime(100); Assert.assertEquals(unit.getObject1().getPos(), unit1.getObject1().getPos()); CheckRocksdbCqWriteResult result = ((CombineConsumeQueueStore) combineConsumeQueueStore).doCheckCqWriteProgress(topic, startTimestamp, StoreType.DEFAULT, StoreType.DEFAULT_ROCKSDB); Assert.assertEquals(CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue(), result.getCheckStatus()); } // /** // * No need to skip macOS platform. // * @return true if some platform is NOT a good fit for this test case. // */ private boolean notToBeExecuted() { return MixAll.isMac(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pagecache/ManyMessageTransferTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pagecache; import java.nio.ByteBuffer; import org.apache.rocketmq.store.GetMessageResult; import org.junit.Assert; import org.junit.Test; public class ManyMessageTransferTest { @Test public void ManyMessageTransferBuilderTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); ManyMessageTransfer manyMessageTransfer = new ManyMessageTransfer(byteBuffer,getMessageResult); } @Test public void ManyMessageTransferPosTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); ManyMessageTransfer manyMessageTransfer = new ManyMessageTransfer(byteBuffer,getMessageResult); Assert.assertEquals(manyMessageTransfer.position(),4); } @Test public void ManyMessageTransferCountTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); ManyMessageTransfer manyMessageTransfer = new ManyMessageTransfer(byteBuffer,getMessageResult); Assert.assertEquals(manyMessageTransfer.count(),20); } @Test public void ManyMessageTransferCloseTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); ManyMessageTransfer manyMessageTransfer = new ManyMessageTransfer(byteBuffer,getMessageResult); manyMessageTransfer.close(); manyMessageTransfer.deallocate(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pagecache/OneMessageTransferTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pagecache; import java.nio.ByteBuffer; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.junit.Assert; import org.junit.Test; public class OneMessageTransferTest { @Test public void OneMessageTransferTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new DefaultMappedFile()); OneMessageTransfer manyMessageTransfer = new OneMessageTransfer(byteBuffer,selectMappedBufferResult); } @Test public void OneMessageTransferCountTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new DefaultMappedFile()); OneMessageTransfer manyMessageTransfer = new OneMessageTransfer(byteBuffer,selectMappedBufferResult); Assert.assertEquals(manyMessageTransfer.count(),40); } @Test public void OneMessageTransferPosTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new DefaultMappedFile()); OneMessageTransfer manyMessageTransfer = new OneMessageTransfer(byteBuffer,selectMappedBufferResult); Assert.assertEquals(manyMessageTransfer.position(),8); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pagecache/QueryMessageTransferTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pagecache; import org.apache.rocketmq.store.QueryMessageResult; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class QueryMessageTransferTest { @Mock private WritableByteChannel writableByteChannel; @Mock private QueryMessageResult queryMessageResult; private QueryMessageTransfer queryMessageTransfer; private ByteBuffer byteBufferHeader; private ByteBuffer bb1; private ByteBuffer bb2; @Before public void init() { byteBufferHeader = ByteBuffer.allocate(4); byteBufferHeader.putInt(1); byteBufferHeader.flip(); bb1 = ByteBuffer.allocate(4); bb1.putInt(2); bb1.flip(); bb2 = ByteBuffer.allocate(4); bb2.putInt(3); bb2.flip(); when(queryMessageResult.getMessageBufferList()).thenReturn(Arrays.asList(bb1, bb2)); queryMessageTransfer = new QueryMessageTransfer(byteBufferHeader, queryMessageResult); } @Test public void testPosition_WithHeaderAndMessageBuffers() { byteBufferHeader.position(2); bb1.position(1); bb2.position(3); long actual = queryMessageTransfer.position(); long expected = byteBufferHeader.position() + bb1.position() + bb2.position(); assertEquals(expected, actual); } @Test public void testPosition_WithHeaderOnly() { byteBufferHeader.position(2); when(queryMessageResult.getMessageBufferList()).thenReturn(new ArrayList<>()); long actual = queryMessageTransfer.position(); long expected = byteBufferHeader.position(); assertEquals(expected, actual); } @Test public void testPosition_WithMessageBuffersOnly() { byteBufferHeader.clear(); byteBufferHeader.flip(); bb1.position(1); bb2.position(3); long actual = queryMessageTransfer.position(); long expected = bb1.position() + bb2.position(); assertEquals(expected, actual); } @Test public void testTransferTo_OnlyHeaderData() throws Exception { bb1.clear(); bb2.clear(); when(writableByteChannel.write(byteBufferHeader)).thenReturn(4); long actual = queryMessageTransfer.transferTo(writableByteChannel, 0); assertEquals(4, actual); verify(writableByteChannel, times(1)).write(byteBufferHeader); verify(writableByteChannel, never()).write(bb1); verify(writableByteChannel, never()).write(bb2); } @Test public void testTransferTo_OnlyMessageBuffersData() throws Exception { byteBufferHeader.clear(); byteBufferHeader.flip(); when(writableByteChannel.write(bb1)).thenReturn(4); long actual = queryMessageTransfer.transferTo(writableByteChannel, 0); assertEquals(4, actual); verify(writableByteChannel, never()).write(byteBufferHeader); verify(writableByteChannel, times(1)).write(bb1); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/PopConsumerCacheTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.util.Collections; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.common.BrokerConfig; import org.awaitility.Awaitility; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import static org.mockito.ArgumentMatchers.any; public class PopConsumerCacheTest { private final String attemptId = "attemptId"; private final String topicId = "TopicTest"; private final String groupId = "GroupTest"; private final int queueId = 2; @Test public void consumerRecordsTest() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setPopConsumerKVServiceLog(true); PopConsumerCache.ConsumerRecords consumerRecords = new PopConsumerCache.ConsumerRecords(brokerConfig, groupId, topicId, queueId); Assert.assertNotNull(consumerRecords.toString()); for (int i = 0; i < 5; i++) { consumerRecords.write(new PopConsumerRecord(i, groupId, topicId, queueId, 0, 20000, 100 + i, attemptId)); } Assert.assertEquals(100, consumerRecords.getMinOffsetInBuffer()); Assert.assertEquals(5, consumerRecords.getInFlightRecordCount()); for (int i = 0; i < 2; i++) { consumerRecords.delete(new PopConsumerRecord(i, groupId, topicId, queueId, 0, 20000, 100 + i, attemptId)); } Assert.assertEquals(102, consumerRecords.getMinOffsetInBuffer()); Assert.assertEquals(3, consumerRecords.getInFlightRecordCount()); long bufferTimeout = brokerConfig.getPopCkStayBufferTime(); consumerRecords.stageExpiredRecords(bufferTimeout + 2); Assert.assertEquals(1, consumerRecords.getRemoveTreeMap().size()); consumerRecords.clearStagedRecords(); consumerRecords.stageExpiredRecords(bufferTimeout + 4); Assert.assertEquals(2, consumerRecords.getRemoveTreeMap().size()); consumerRecords.clearStagedRecords(); } @Test public void consumerOffsetTest() throws IllegalAccessException { BrokerController brokerController = Mockito.mock(BrokerController.class); PopConsumerKVStore consumerKVStore = Mockito.mock(PopConsumerRocksdbStore.class); PopConsumerLockService consumerLockService = Mockito.mock(PopConsumerLockService.class); ConsumerOffsetManager consumerOffsetManager = Mockito.mock(ConsumerOffsetManager.class); Mockito.when(brokerController.getBrokerConfig()).thenReturn(new BrokerConfig()); Mockito.when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); Mockito.when(consumerLockService.tryLock(groupId, topicId)).thenReturn(true); PopConsumerCache consumerCache = new PopConsumerCache(brokerController, consumerKVStore, consumerLockService, null); consumerCache.commitOffset("CommitOffsetTest", groupId, topicId, queueId, 100L); consumerCache.removeRecords(groupId, topicId, queueId); AtomicInteger estimateCacheSize = (AtomicInteger) FieldUtils.readField( consumerCache, "estimateCacheSize", true); estimateCacheSize.set(2); consumerCache.start(); Awaitility.await().until(() -> estimateCacheSize.get() == 0); consumerCache.shutdown(); } @Test public void consumerCacheTest() { BrokerController brokerController = Mockito.mock(BrokerController.class); PopConsumerKVStore consumerKVStore = Mockito.mock(PopConsumerRocksdbStore.class); PopConsumerLockService consumerLockService = Mockito.mock(PopConsumerLockService.class); Mockito.when(brokerController.getBrokerConfig()).thenReturn(new BrokerConfig()); PopConsumerCache consumerCache = new PopConsumerCache(brokerController, consumerKVStore, consumerLockService, null); Assert.assertEquals(-1L, consumerCache.getMinOffsetInCache(groupId, topicId, queueId)); Assert.assertEquals(0, consumerCache.getPopInFlightMessageCount(groupId, topicId, queueId)); Assert.assertEquals(0, consumerCache.getCacheKeySize()); // write for (int i = 0; i < 3; i++) { PopConsumerRecord record = new PopConsumerRecord(2L, groupId, topicId, queueId, 0, 20000, 100 + i, attemptId); Assert.assertEquals(consumerCache.getKey(record), consumerCache.getKey(groupId, topicId, queueId)); consumerCache.writeRecords(Collections.singletonList(record)); } Assert.assertEquals(100, consumerCache.getMinOffsetInCache(groupId, topicId, queueId)); Assert.assertEquals(3, consumerCache.getPopInFlightMessageCount(groupId, topicId, queueId)); Assert.assertEquals(1, consumerCache.getCacheKeySize()); Assert.assertEquals(3, consumerCache.getCacheSize()); Assert.assertFalse(consumerCache.isCacheFull()); // delete PopConsumerRecord record = new PopConsumerRecord(2L, groupId, topicId, queueId, 0, 20000, 100, attemptId); Assert.assertEquals(0, consumerCache.deleteRecords(Collections.singletonList(record)).size()); Assert.assertEquals(101, consumerCache.getMinOffsetInCache(groupId, topicId, queueId)); Assert.assertEquals(2, consumerCache.getPopInFlightMessageCount(groupId, topicId, queueId)); Assert.assertEquals(2, consumerCache.getCacheSize()); record = new PopConsumerRecord(2L, groupId, topicId, queueId, 0, 20000, 104, attemptId); Assert.assertEquals(1, consumerCache.deleteRecords(Collections.singletonList(record)).size()); Assert.assertEquals(101, consumerCache.getMinOffsetInCache(groupId, topicId, queueId)); Assert.assertEquals(2, consumerCache.getPopInFlightMessageCount(groupId, topicId, queueId)); // clean expired records Queue consumerRecordList = new LinkedBlockingQueue<>(); consumerCache.cleanupRecords(consumerRecordList::add); Assert.assertEquals(2, consumerRecordList.size()); // clean all Mockito.when(consumerLockService.isLockTimeout(any(), any())).thenReturn(true); consumerRecordList.clear(); consumerCache.cleanupRecords(consumerRecordList::add); Assert.assertEquals(0, consumerRecordList.size()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/PopConsumerContextTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; public class PopConsumerContextTest { @Test public void consumerContextTest() { long popTime = System.currentTimeMillis(); PopConsumerContext context = new PopConsumerContext("127.0.0.1:6789", popTime, 20_000, "GroupId", true, ConsumeInitMode.MIN, "attemptId"); Assert.assertFalse(context.isFound()); Assert.assertEquals("127.0.0.1:6789", context.getClientHost()); Assert.assertEquals(popTime, context.getPopTime()); Assert.assertEquals(20_000, context.getInvisibleTime()); Assert.assertEquals("GroupId", context.getGroupId()); Assert.assertTrue(context.isFifo()); Assert.assertEquals("attemptId", context.getAttemptId()); Assert.assertEquals(0, context.getRestCount()); GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); getMessageResult.setMinOffset(10L); getMessageResult.setMaxOffset(20L); getMessageResult.setNextBeginOffset(15L); getMessageResult.addMessage(Mockito.mock(SelectMappedBufferResult.class), 10); getMessageResult.addMessage(Mockito.mock(SelectMappedBufferResult.class), 12); getMessageResult.addMessage(Mockito.mock(SelectMappedBufferResult.class), 13); context.addGetMessageResult(getMessageResult, "TopicId", 3, PopConsumerRecord.RetryType.NORMAL_TOPIC, 1); Assert.assertEquals(3, context.getMessageCount()); Assert.assertEquals( getMessageResult.getMaxOffset() - getMessageResult.getNextBeginOffset(), context.getRestCount()); // check header Assert.assertNotNull(context.toString()); Assert.assertEquals("0 3 1", context.getStartOffsetInfo()); Assert.assertEquals("0 3 10,12,13", context.getMsgOffsetInfo()); Assert.assertNotNull(context.getOrderCountInfoBuilder()); Assert.assertEquals("", context.getOrderCountInfo()); Assert.assertEquals(1, context.getGetMessageResultList().size()); Assert.assertEquals(3, context.getPopConsumerRecordList().size()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/PopConsumerLockServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.lang.reflect.Field; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.PopAckConstants; import org.junit.Assert; import org.junit.Test; public class PopConsumerLockServiceTest { @Test @SuppressWarnings("unchecked") public void consumerLockTest() throws NoSuchFieldException, IllegalAccessException { String groupId = "groupId"; String topicId = "topicId"; PopConsumerLockService lockService = new PopConsumerLockService(TimeUnit.MINUTES.toMillis(2)); Assert.assertTrue(lockService.tryLock(groupId, topicId)); Assert.assertFalse(lockService.tryLock(groupId, topicId)); lockService.unlock(groupId, topicId); Assert.assertTrue(lockService.tryLock(groupId, topicId)); Assert.assertFalse(lockService.tryLock(groupId, topicId)); Assert.assertFalse(lockService.isLockTimeout(groupId, topicId)); lockService.removeTimeout(); // set expired Field field = PopConsumerLockService.class.getDeclaredField("lockTable"); field.setAccessible(true); Map table = (Map) field.get(lockService); Field lockTime = PopConsumerLockService.TimedLock.class.getDeclaredField("lockTime"); lockTime.setAccessible(true); lockTime.set(table.get(groupId + PopAckConstants.SPLIT + topicId), System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(3)); lockService.removeTimeout(); Assert.assertEquals(0, table.size()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/PopConsumerRecordTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.util.UUID; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.junit.Assert; import org.junit.Test; public class PopConsumerRecordTest { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); @Test public void retryCodeTest() { Assert.assertEquals("NORMAL_TOPIC code should be 0", 0, PopConsumerRecord.RetryType.NORMAL_TOPIC.getCode()); Assert.assertEquals("RETRY_TOPIC code should be 1", 1, PopConsumerRecord.RetryType.RETRY_TOPIC_V1.getCode()); Assert.assertEquals("RETRY_TOPIC_V2 code should be 2", 2, PopConsumerRecord.RetryType.RETRY_TOPIC_V2.getCode()); } @Test public void deliveryRecordSerializeTest() { PopConsumerRecord consumerRecord = new PopConsumerRecord(); consumerRecord.setPopTime(System.currentTimeMillis()); consumerRecord.setGroupId("GroupId"); consumerRecord.setTopicId("TopicId"); consumerRecord.setQueueId(3); consumerRecord.setRetryFlag(PopConsumerRecord.RetryType.RETRY_TOPIC_V1.getCode()); consumerRecord.setInvisibleTime(20); consumerRecord.setOffset(100); consumerRecord.setAttemptTimes(2); consumerRecord.setAttemptId(UUID.randomUUID().toString().toUpperCase()); Assert.assertTrue(consumerRecord.isRetry()); Assert.assertEquals(consumerRecord.getPopTime() + consumerRecord.getInvisibleTime(), consumerRecord.getVisibilityTimeout()); Assert.assertEquals(8 + "GroupId".length() + 1 + "TopicId".length() + 1 + 4 + 1 + 8, consumerRecord.getKeyBytes().length); log.info("ConsumerRecord={}", consumerRecord.toString()); PopConsumerRecord decodeRecord = PopConsumerRecord.decode(consumerRecord.getValueBytes()); PopConsumerRecord consumerRecord2 = new PopConsumerRecord(consumerRecord.getPopTime(), consumerRecord.getGroupId(), consumerRecord.getTopicId(), consumerRecord.getQueueId(), consumerRecord.getRetryFlag(), consumerRecord.getInvisibleTime(), consumerRecord.getOffset(), consumerRecord.getAttemptId()); Assert.assertEquals(decodeRecord.getPopTime(), consumerRecord2.getPopTime()); Assert.assertEquals(decodeRecord.getGroupId(), consumerRecord2.getGroupId()); Assert.assertEquals(decodeRecord.getTopicId(), consumerRecord2.getTopicId()); Assert.assertEquals(decodeRecord.getQueueId(), consumerRecord2.getQueueId()); Assert.assertEquals(decodeRecord.getRetryFlag(), consumerRecord2.getRetryFlag()); Assert.assertEquals(decodeRecord.getInvisibleTime(), consumerRecord2.getInvisibleTime()); Assert.assertEquals(decodeRecord.getOffset(), consumerRecord2.getOffset()); Assert.assertEquals(0, consumerRecord2.getAttemptTimes()); Assert.assertEquals(decodeRecord.getAttemptId(), consumerRecord2.getAttemptId()); } @Test public void testSuspendFlagInitialization() { // Test constructor without suspend flag (should default to false) PopConsumerRecord record1 = new PopConsumerRecord( System.currentTimeMillis(), "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id"); Assert.assertFalse("Suspend flag should default to false", record1.isSuspend()); // Test constructor with suspend flag set to true PopConsumerRecord record2 = new PopConsumerRecord( System.currentTimeMillis(), "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id", true); Assert.assertTrue("Suspend flag should be true", record2.isSuspend()); // Test constructor with suspend flag set to false PopConsumerRecord record3 = new PopConsumerRecord( System.currentTimeMillis(), "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id", false); Assert.assertFalse("Suspend flag should be false", record3.isSuspend()); } @Test public void testSuspendFlagSerialization() { // Test serialization/deserialization with suspend flag PopConsumerRecord originalRecord = new PopConsumerRecord( 1234567890L, "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id", true); byte[] serialized = originalRecord.getValueBytes(); PopConsumerRecord deserialized = PopConsumerRecord.decode(serialized); Assert.assertTrue("Deserialized record should have suspend flag true", deserialized.isSuspend()); Assert.assertEquals("Other fields should match", originalRecord.getGroupId(), deserialized.getGroupId()); Assert.assertEquals("Other fields should match", originalRecord.getTopicId(), deserialized.getTopicId()); Assert.assertEquals("Other fields should match", originalRecord.getOffset(), deserialized.getOffset()); } @Test public void testSuspendFlagGetterSetter() { PopConsumerRecord record = new PopConsumerRecord(); // Test initial value Assert.assertFalse("Initial suspend value should be false", record.isSuspend()); // Test setter record.setSuspend(true); Assert.assertTrue("After setting to true, should be true", record.isSuspend()); record.setSuspend(false); Assert.assertFalse("After setting to false, should be false", record.isSuspend()); } @Test public void testSuspendInToString() { PopConsumerRecord record = new PopConsumerRecord( 1234567890L, "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id", true); String toString = record.toString(); Assert.assertTrue("toString should include suspend information", toString.contains("suspend=true")); PopConsumerRecord record2 = new PopConsumerRecord( 1234567890L, "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id", false); String toString2 = record2.toString(); Assert.assertTrue("toString should include suspend information", toString2.contains("suspend=false")); } @Test public void testSuspendFlagSerializationWithFalse() { // Test serialization/deserialization with suspend flag set to false PopConsumerRecord originalRecord = new PopConsumerRecord( 1234567890L, "test-group", "test-topic", 0, 0, 30000L, 100L, "attempt-id", false); byte[] serialized = originalRecord.getValueBytes(); PopConsumerRecord deserialized = PopConsumerRecord.decode(serialized); Assert.assertFalse("Deserialized record should have suspend flag false", deserialized.isSuspend()); Assert.assertEquals("GroupId should match", originalRecord.getGroupId(), deserialized.getGroupId()); Assert.assertEquals("TopicId should match", originalRecord.getTopicId(), deserialized.getTopicId()); Assert.assertEquals("Offset should match", originalRecord.getOffset(), deserialized.getOffset()); Assert.assertEquals("PopTime should match", originalRecord.getPopTime(), deserialized.getPopTime()); Assert.assertEquals("QueueId should match", originalRecord.getQueueId(), deserialized.getQueueId()); Assert.assertEquals("InvisibleTime should match", originalRecord.getInvisibleTime(), deserialized.getInvisibleTime()); Assert.assertEquals("RetryFlag should match", originalRecord.getRetryFlag(), deserialized.getRetryFlag()); Assert.assertEquals("AttemptId should match", originalRecord.getAttemptId(), deserialized.getAttemptId()); } @Test public void testSuspendFlagJSONSerializationCompleteness() { // Test complete serialization/deserialization with all fields including suspend long popTime = System.currentTimeMillis(); String groupId = "test-group"; String topicId = "test-topic"; int queueId = 1; int retryFlag = PopConsumerRecord.RetryType.RETRY_TOPIC_V2.getCode(); long invisibleTime = 30000L; long offset = 100L; String attemptId = UUID.randomUUID().toString().toUpperCase(); // Test with suspend = true PopConsumerRecord recordWithSuspend = new PopConsumerRecord( popTime, groupId, topicId, queueId, retryFlag, invisibleTime, offset, attemptId, true); recordWithSuspend.setAttemptTimes(3); byte[] serialized = recordWithSuspend.getValueBytes(); PopConsumerRecord deserialized = PopConsumerRecord.decode(serialized); Assert.assertTrue("Suspend flag should be true", deserialized.isSuspend()); Assert.assertEquals("PopTime should match", popTime, deserialized.getPopTime()); Assert.assertEquals("GroupId should match", groupId, deserialized.getGroupId()); Assert.assertEquals("TopicId should match", topicId, deserialized.getTopicId()); Assert.assertEquals("QueueId should match", queueId, deserialized.getQueueId()); Assert.assertEquals("RetryFlag should match", retryFlag, deserialized.getRetryFlag()); Assert.assertEquals("InvisibleTime should match", invisibleTime, deserialized.getInvisibleTime()); Assert.assertEquals("Offset should match", offset, deserialized.getOffset()); Assert.assertEquals("AttemptTimes should match", 3, deserialized.getAttemptTimes()); Assert.assertEquals("AttemptId should match", attemptId, deserialized.getAttemptId()); // Test with suspend = false PopConsumerRecord recordWithoutSuspend = new PopConsumerRecord( popTime, groupId, topicId, queueId, retryFlag, invisibleTime, offset, attemptId, false); recordWithoutSuspend.setAttemptTimes(3); serialized = recordWithoutSuspend.getValueBytes(); deserialized = PopConsumerRecord.decode(serialized); Assert.assertFalse("Suspend flag should be false", deserialized.isSuspend()); Assert.assertEquals("PopTime should match", popTime, deserialized.getPopTime()); Assert.assertEquals("GroupId should match", groupId, deserialized.getGroupId()); Assert.assertEquals("TopicId should match", topicId, deserialized.getTopicId()); Assert.assertEquals("QueueId should match", queueId, deserialized.getQueueId()); Assert.assertEquals("RetryFlag should match", retryFlag, deserialized.getRetryFlag()); Assert.assertEquals("InvisibleTime should match", invisibleTime, deserialized.getInvisibleTime()); Assert.assertEquals("Offset should match", offset, deserialized.getOffset()); Assert.assertEquals("AttemptTimes should match", 3, deserialized.getAttemptTimes()); Assert.assertEquals("AttemptId should match", attemptId, deserialized.getAttemptId()); } @Test public void testSuspendFlagDefaultValueInNoArgConstructor() { // Test that no-arg constructor defaults suspend to false PopConsumerRecord record = new PopConsumerRecord(); Assert.assertFalse("No-arg constructor should default suspend to false", record.isSuspend()); // Set all fields manually record.setPopTime(System.currentTimeMillis()); record.setGroupId("test-group"); record.setTopicId("test-topic"); record.setQueueId(0); record.setRetryFlag(0); record.setInvisibleTime(30000L); record.setOffset(100L); record.setAttemptId("attempt-id"); record.setSuspend(true); Assert.assertTrue("After setting suspend to true, should be true", record.isSuspend()); // Serialize and deserialize to verify byte[] serialized = record.getValueBytes(); PopConsumerRecord deserialized = PopConsumerRecord.decode(serialized); Assert.assertTrue("Deserialized record should preserve suspend=true", deserialized.isSuspend()); } @Test public void testSuspendFlagInDeliveryRecordSerializeTest() { // Enhance existing deliveryRecordSerializeTest to include suspend flag PopConsumerRecord consumerRecord = new PopConsumerRecord(); consumerRecord.setPopTime(System.currentTimeMillis()); consumerRecord.setGroupId("GroupId"); consumerRecord.setTopicId("TopicId"); consumerRecord.setQueueId(3); consumerRecord.setRetryFlag(PopConsumerRecord.RetryType.RETRY_TOPIC_V1.getCode()); consumerRecord.setInvisibleTime(20); consumerRecord.setOffset(100); consumerRecord.setAttemptTimes(2); consumerRecord.setAttemptId(UUID.randomUUID().toString().toUpperCase()); consumerRecord.setSuspend(true); PopConsumerRecord decodeRecord = PopConsumerRecord.decode(consumerRecord.getValueBytes()); Assert.assertTrue("Decoded record should preserve suspend flag", decodeRecord.isSuspend()); Assert.assertEquals("Suspend flag should match", consumerRecord.isSuspend(), decodeRecord.isSuspend()); // Test with suspend = false consumerRecord.setSuspend(false); decodeRecord = PopConsumerRecord.decode(consumerRecord.getValueBytes()); Assert.assertFalse("Decoded record should preserve suspend=false", decodeRecord.isSuspend()); Assert.assertEquals("Suspend flag should match", consumerRecord.isSuspend(), decodeRecord.isSuspend()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/PopConsumerRocksdbStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import com.google.common.base.Stopwatch; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.io.FileUtils; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.rocksdb.RocksDB; import org.rocksdb.RocksIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PopConsumerRocksdbStoreTest { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private static final String CONSUMER_STORE_PATH = "consumer_rocksdb"; public static String getRandomStorePath() { return Paths.get(System.getProperty("user.home"), "store_test", CONSUMER_STORE_PATH, UUID.randomUUID().toString().replace("-", "").toUpperCase().substring(0, 16)).toString(); } public static void deleteStoreDirectory(String storePath) { try { FileUtils.deleteDirectory(new File(storePath)); } catch (IOException e) { log.error("Delete store directory failed, filePath: {}", storePath, e); } } public static PopConsumerRecord getConsumerRecord() { return new PopConsumerRecord(1L, "GroupTest", "TopicTest", 2, PopConsumerRecord.RetryType.NORMAL_TOPIC.getCode(), TimeUnit.SECONDS.toMillis(20), 100L, "AttemptId"); } @Test public void rocksdbStoreWriteDeleteTest() { String filePath = getRandomStorePath(); PopConsumerKVStore consumerStore = new PopConsumerRocksdbStore(filePath); Assert.assertEquals(filePath, consumerStore.getFilePath()); consumerStore.start(); consumerStore.writeRecords(IntStream.range(0, 3).boxed() .flatMap(i -> IntStream.range(0, 5).mapToObj(j -> { PopConsumerRecord consumerRecord = getConsumerRecord(); consumerRecord.setPopTime(j); consumerRecord.setQueueId(i); consumerRecord.setOffset(100L + j); return consumerRecord; }) ) .collect(Collectors.toList())); consumerStore.deleteRecords(IntStream.range(0, 2).boxed() .flatMap(i -> IntStream.range(0, 5).mapToObj(j -> { PopConsumerRecord consumerRecord = getConsumerRecord(); consumerRecord.setPopTime(j); consumerRecord.setQueueId(i); consumerRecord.setOffset(100L + j); return consumerRecord; }) ) .collect(Collectors.toList())); List consumerRecords = consumerStore.scanExpiredRecords(0, 20002, 2); Assert.assertEquals(2, consumerRecords.size()); consumerStore.deleteRecords(consumerRecords); consumerRecords = consumerStore.scanExpiredRecords(0, 20003, 2); Assert.assertEquals(1, consumerRecords.size()); consumerStore.deleteRecords(consumerRecords); consumerRecords = consumerStore.scanExpiredRecords(0, 20005, 3); Assert.assertEquals(2, consumerRecords.size()); consumerStore.shutdown(); deleteStoreDirectory(filePath); } private long getDirectorySizeRecursive(File directory) { long size = 0; File[] files = directory.listFiles(); if (files != null) { for (File file : files) { if (file.isFile()) { size += file.length(); } else if (file.isDirectory()) { size += getDirectorySizeRecursive(file); } } } return size; } @Test @Ignore @SuppressWarnings("ConstantValue") public void tombstoneDeletionTest() throws IllegalAccessException, NoSuchFieldException { PopConsumerRocksdbStore rocksdbStore = new PopConsumerRocksdbStore(getRandomStorePath()); rocksdbStore.start(); int iterCount = 1000 * 1000; boolean useSeekFirstDelete = false; Field dbField = AbstractRocksDBStorage.class.getDeclaredField("db"); dbField.setAccessible(true); RocksDB rocksDB = (RocksDB) dbField.get(rocksdbStore); long currentTime = 0L; Stopwatch stopwatch = Stopwatch.createStarted(); for (int i = 0; i < iterCount; i++) { List records = new ArrayList<>(); for (int j = 0; j < 1000; j++) { PopConsumerRecord record = getConsumerRecord(); record.setPopTime((long) i * iterCount + j); record.setGroupId("GroupTest"); record.setTopicId("TopicTest"); record.setQueueId(i % 10); record.setRetryFlag(0); record.setInvisibleTime(TimeUnit.SECONDS.toMillis(30)); record.setOffset(i); records.add(record); } rocksdbStore.writeRecords(records); long start = stopwatch.elapsed(TimeUnit.MILLISECONDS); List deleteList = new ArrayList<>(); if (useSeekFirstDelete) { try (RocksIterator iterator = rocksDB.newIterator(rocksdbStore.columnFamilyHandle)) { iterator.seekToFirst(); if (i % 10 == 0) { long fileSize = getDirectorySizeRecursive(new File(rocksdbStore.getFilePath())); log.info("DirectorySize={}, Cost={}ms", MessageStoreUtil.toHumanReadable(fileSize), stopwatch.elapsed(TimeUnit.MILLISECONDS) - start); } while (iterator.isValid() && deleteList.size() < 1024) { deleteList.add(PopConsumerRecord.decode(iterator.value())); iterator.next(); } } } else { long upper = System.currentTimeMillis(); deleteList = rocksdbStore.scanExpiredRecords(currentTime, upper, 800); if (!deleteList.isEmpty()) { currentTime = deleteList.get(deleteList.size() - 1).getVisibilityTimeout(); } long scanCost = stopwatch.elapsed(TimeUnit.MILLISECONDS) - start; if (i % 100 == 0) { long fileSize = getDirectorySizeRecursive(new File(rocksdbStore.getFilePath())); long seekTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); try (RocksIterator iterator = rocksDB.newIterator(rocksdbStore.columnFamilyHandle)) { iterator.seekToFirst(); } log.info("DirectorySize={}, Cost={}ms, SeekFirstCost={}ms", MessageStoreUtil.toHumanReadable(fileSize), scanCost, stopwatch.elapsed(TimeUnit.MILLISECONDS) - seekTime); } } rocksdbStore.deleteRecords(deleteList); } rocksdbStore.shutdown(); deleteStoreDirectory(rocksdbStore.getFilePath()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/PopConsumerServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.longpolling.PopLongPollingService; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.processor.PopMessageProcessor; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; public class PopConsumerServiceTest { private final String clientHost = "127.0.0.1:8888"; private final String groupId = "groupId"; private final String topicId = "topicId"; private final int queueId = 2; private final String attemptId = UUID.randomUUID().toString().toUpperCase(); private final String filePath = PopConsumerRocksdbStoreTest.getRandomStorePath(); private BrokerController brokerController; private PopConsumerService consumerService; @Before public void init() throws IOException { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setEnablePopLog(true); brokerConfig.setEnablePopBufferMerge(true); brokerConfig.setEnablePopMessageThreshold(true); brokerConfig.setPopInflightMessageThreshold(100); brokerConfig.setPopConsumerKVServiceLog(true); brokerConfig.setEnableRetryTopicV2(true); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(filePath); TopicConfigManager topicConfigManager = Mockito.mock(TopicConfigManager.class); SubscriptionGroupManager subscriptionGroupManager = Mockito.mock(SubscriptionGroupManager.class); ConsumerOffsetManager consumerOffsetManager = Mockito.mock(ConsumerOffsetManager.class); PopMessageProcessor popMessageProcessor = Mockito.mock(PopMessageProcessor.class); PopLongPollingService popLongPollingService = Mockito.mock(PopLongPollingService.class); ConsumerOrderInfoManager consumerOrderInfoManager = Mockito.mock(ConsumerOrderInfoManager.class); brokerController = Mockito.mock(BrokerController.class); Mockito.when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); Mockito.when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); Mockito.when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); Mockito.when(brokerController.getPopMessageProcessor()).thenReturn(popMessageProcessor); Mockito.when(popMessageProcessor.getPopLongPollingService()).thenReturn(popLongPollingService); Mockito.when(brokerController.getConsumerOrderInfoManager()).thenReturn(consumerOrderInfoManager); consumerService = new PopConsumerService(brokerController); } @After public void shutdown() throws IOException { FileUtils.deleteDirectory(new File(filePath)); } public PopConsumerRecord getConsumerTestRecord() { PopConsumerRecord popConsumerRecord = new PopConsumerRecord(); popConsumerRecord.setPopTime(System.currentTimeMillis()); popConsumerRecord.setGroupId(groupId); popConsumerRecord.setTopicId(topicId); popConsumerRecord.setQueueId(queueId); popConsumerRecord.setRetryFlag(PopConsumerRecord.RetryType.NORMAL_TOPIC.getCode()); popConsumerRecord.setAttemptTimes(0); popConsumerRecord.setInvisibleTime(TimeUnit.SECONDS.toMillis(20)); popConsumerRecord.setAttemptId(UUID.randomUUID().toString().toUpperCase()); return popConsumerRecord; } @Test public void isPopShouldStopTest() throws IllegalAccessException { Assert.assertFalse(consumerService.isPopShouldStop(groupId, topicId, queueId)); PopConsumerCache consumerCache = (PopConsumerCache) FieldUtils.readField( consumerService, "popConsumerCache", true); for (int i = 0; i < 100; i++) { PopConsumerRecord record = getConsumerTestRecord(); record.setOffset(i); consumerCache.writeRecords(Collections.singletonList(record)); } Assert.assertTrue(consumerService.isPopShouldStop(groupId, topicId, queueId)); } @Test public void pendingFilterCountTest() throws ConsumeQueueException { MessageStore messageStore = Mockito.mock(MessageStore.class); Mockito.when(messageStore.getMaxOffsetInQueue(topicId, queueId)).thenReturn(100L); Mockito.when(brokerController.getMessageStore()).thenReturn(messageStore); ConsumerOffsetManager consumerOffsetManager = brokerController.getConsumerOffsetManager(); Mockito.when(consumerOffsetManager.queryOffset(groupId, topicId, queueId)).thenReturn(20L); Assert.assertEquals(consumerService.getPendingFilterCount(groupId, topicId, queueId), 80L); } private MessageExt getMessageExt() { MessageExt messageExt = new MessageExt(); messageExt.setTopic(topicId); messageExt.setQueueId(queueId); messageExt.setBody(new byte[128]); messageExt.setBornHost(new InetSocketAddress("127.0.0.1", 8080)); messageExt.setStoreHost(new InetSocketAddress("127.0.0.1", 8080)); messageExt.putUserProperty("Key", "Value"); return messageExt; } @Test public void recodeRetryMessageTest() throws Exception { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); // result is empty SelectMappedBufferResult bufferResult = new SelectMappedBufferResult( 0, ByteBuffer.allocate(10), 10, null); getMessageResult.addMessage(bufferResult); getMessageResult.getMessageMapedList().clear(); GetMessageResult result = consumerService.recodeRetryMessage( getMessageResult, topicId, 0, 100, 200); Assert.assertEquals(0, result.getMessageMapedList().size()); ByteBuffer buffer = ByteBuffer.wrap( MessageDecoder.encode(getMessageExt(), false)); getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); getMessageResult.addMessage(new SelectMappedBufferResult( 0, buffer, buffer.remaining(), null)); result = consumerService.recodeRetryMessage( getMessageResult, topicId, 0, 100, 200); Assert.assertNotNull(result); Assert.assertEquals(1, result.getMessageMapedList().size()); } @Test public void addGetMessageResultTest() { PopConsumerContext context = new PopConsumerContext( clientHost, System.currentTimeMillis(), 20000, groupId, false, ConsumeInitMode.MIN, attemptId); GetMessageResult result = new GetMessageResult(); result.setStatus(GetMessageStatus.FOUND); result.getMessageQueueOffset().add(100L); consumerService.handleGetMessageResult( context, result, topicId, queueId, PopConsumerRecord.RetryType.NORMAL_TOPIC, 100); Assert.assertEquals(1, context.getGetMessageResultList().size()); } @Test public void getMessageAsyncTest() throws Exception { MessageStore messageStore = Mockito.mock(MessageStore.class); Mockito.when(brokerController.getMessageStore()).thenReturn(messageStore); Mockito.when(messageStore.getMessageAsync(groupId, topicId, queueId, 0, 10, null)) .thenReturn(CompletableFuture.completedFuture(null)); GetMessageResult getMessageResult = consumerService.getMessageAsync( "127.0.0.1:8888", groupId, topicId, queueId, 0, 10, null).join(); Assert.assertNull(getMessageResult); // success when first get message GetMessageResult firstGetMessageResult = new GetMessageResult(); firstGetMessageResult.setStatus(GetMessageStatus.FOUND); Mockito.when(messageStore.getMessageAsync(groupId, topicId, queueId, 0, 10, null)) .thenReturn(CompletableFuture.completedFuture(firstGetMessageResult)); getMessageResult = consumerService.getMessageAsync( "127.0.0.1:8888", groupId, topicId, queueId, 0, 10, null).join(); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); // reset offset from server firstGetMessageResult.setStatus(GetMessageStatus.OFFSET_FOUND_NULL); firstGetMessageResult.setNextBeginOffset(25); GetMessageResult resetGetMessageResult = new GetMessageResult(); resetGetMessageResult.setStatus(GetMessageStatus.FOUND); Mockito.when(messageStore.getMessageAsync(groupId, topicId, queueId, 25, 10, null)) .thenReturn(CompletableFuture.completedFuture(resetGetMessageResult)); getMessageResult = consumerService.getMessageAsync( "127.0.0.1:8888", groupId, topicId, queueId, 0, 10, null).join(); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); // fifo block PopConsumerContext context = new PopConsumerContext( clientHost, System.currentTimeMillis(), 20000, groupId, false, ConsumeInitMode.MIN, attemptId); consumerService.setFifoBlocked(context, groupId, topicId, queueId, Collections.singletonList(100L), resetGetMessageResult); Mockito.when(brokerController.getConsumerOrderInfoManager() .checkBlock(anyString(), anyString(), anyString(), anyInt(), anyLong())).thenReturn(true); Assert.assertTrue(consumerService.isFifoBlocked(context, groupId, topicId, queueId)); // get message async normal CompletableFuture future = CompletableFuture.completedFuture(context); Assert.assertEquals(0L, consumerService.getMessageAsync(future, clientHost, groupId, topicId, queueId, 10, null, PopConsumerRecord.RetryType.NORMAL_TOPIC).join().getRestCount()); // get message result full, no need get again for (int i = 0; i < 10; i++) { ByteBuffer buffer = ByteBuffer.wrap(MessageDecoder.encode(getMessageExt(), false)); getMessageResult.addMessage(new SelectMappedBufferResult( 0, buffer, buffer.remaining(), null), i); } context.addGetMessageResult(getMessageResult, topicId, queueId, PopConsumerRecord.RetryType.NORMAL_TOPIC, 0); Mockito.when(brokerController.getMessageStore().getMaxOffsetInQueue(topicId, queueId)).thenReturn(100L); Mockito.when(brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, queueId)).thenReturn(0L); Assert.assertEquals(100L, consumerService.getMessageAsync(future, clientHost, groupId, topicId, queueId, 10, null, PopConsumerRecord.RetryType.NORMAL_TOPIC).join().getRestCount()); // fifo block test context = new PopConsumerContext( clientHost, System.currentTimeMillis(), 20000, groupId, true, ConsumeInitMode.MIN, attemptId); future = CompletableFuture.completedFuture(context); Assert.assertEquals(0L, consumerService.getMessageAsync(future, clientHost, groupId, topicId, queueId, 10, null, PopConsumerRecord.RetryType.NORMAL_TOPIC).join().getRestCount()); } @Test public void popAsyncTest() { PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); TopicConfigManager topicConfigManager = Mockito.mock(TopicConfigManager.class); Mockito.when(topicConfigManager.selectTopicConfig(topicId)).thenReturn(new TopicConfig( topicId, 2, 2, PermName.PERM_READ | PermName.PERM_WRITE, 0)); Mockito.when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); String[] retryTopic = new String[] { KeyBuilder.buildPopRetryTopicV1(topicId, groupId), KeyBuilder.buildPopRetryTopicV2(topicId, groupId) }; for (String retry : retryTopic) { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.NO_MATCHED_MESSAGE); getMessageResult.setMinOffset(0L); getMessageResult.setMaxOffset(1L); getMessageResult.setNextBeginOffset(1L); Mockito.doReturn(CompletableFuture.completedFuture(getMessageResult)) .when(consumerServiceSpy).getMessageAsync(clientHost, groupId, retry, 0, 0, 10, null); Mockito.doReturn(CompletableFuture.completedFuture(getMessageResult)) .when(consumerServiceSpy).getMessageAsync(clientHost, groupId, retry, 0, 0, 8, null); } for (int i = -1; i < 2; i++) { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); getMessageResult.setMinOffset(0L); getMessageResult.setMaxOffset(1L); getMessageResult.setNextBeginOffset(1L); getMessageResult.addMessage(Mockito.mock(SelectMappedBufferResult.class), 1L); Mockito.doReturn(CompletableFuture.completedFuture(getMessageResult)) .when(consumerServiceSpy).getMessageAsync(clientHost, groupId, topicId, i, 0, 8, null); Mockito.doReturn(CompletableFuture.completedFuture(getMessageResult)) .when(consumerServiceSpy).getMessageAsync(clientHost, groupId, topicId, i, 0, 9, null); Mockito.doReturn(CompletableFuture.completedFuture(getMessageResult)) .when(consumerServiceSpy).getMessageAsync(clientHost, groupId, topicId, i, 0, 10, null); } // pop broker consumerServiceSpy.popAsync(clientHost, System.currentTimeMillis(), 20000, groupId, topicId, -1, 10, false, attemptId, ConsumeInitMode.MIN, null).join(); } @Test public void ackAsyncTest() { long current = System.currentTimeMillis(); consumerService.getPopConsumerStore().start(); consumerService.ackAsync( current, 10, groupId, topicId, queueId, 100).join(); consumerService.changeInvisibilityDuration(current, 10, current + 100, 10, groupId, topicId, queueId, 100, false); consumerService.shutdown(); } @Test public void reviveSkipIfGroupAbsent() { String groupName = "PopGroupAbsent"; brokerController.getBrokerConfig().setPopReviveSkipIfGroupAbsent(true); PopConsumerRecord record = Mockito.mock(PopConsumerRecord.class); Mockito.when(record.getGroupId()).thenReturn(groupName); Mockito.when(brokerController.getSubscriptionGroupManager() .containsSubscriptionGroup(groupName)).thenReturn(false); CompletableFuture result = consumerService.revive(record); Assert.assertTrue(result.join()); } @Test public void reviveRetryTest() { Mockito.when(brokerController.getTopicConfigManager().selectTopicConfig(topicId)).thenReturn(null); Mockito.when(brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, 0)).thenReturn(-1L); consumerService.createRetryTopicIfNeeded(groupId, topicId); consumerService.clearCache(groupId, topicId, queueId); MessageExt messageExt = new MessageExt(); messageExt.setBody("body".getBytes()); messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setFlag(0); messageExt.setSysFlag(0); messageExt.setReconsumeTimes(1); messageExt.putUserProperty("key", "value"); PopConsumerRecord record = new PopConsumerRecord(); record.setTopicId("topic"); record.setGroupId("group"); Mockito.when(brokerController.getBrokerStatsManager()).thenReturn(Mockito.mock(BrokerStatsManager.class)); Mockito.when(brokerController.getEscapeBridge()).thenReturn(Mockito.mock(EscapeBridge.class)); Mockito.when(brokerController.getEscapeBridge().putMessageToSpecificQueue(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult( PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); Mockito.doNothing().when(consumerServiceSpy).createRetryTopicIfNeeded(any(), any()); Assert.assertTrue(consumerServiceSpy.reviveRetry(record, messageExt)); // write message error Mockito.when(brokerController.getEscapeBridge().putMessageToSpecificQueue(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); Assert.assertFalse(consumerServiceSpy.reviveRetry(record, messageExt)); // revive backoff consumerService.getPopConsumerStore().start(); List consumerRecordList = IntStream.range(0, 3) .mapToObj(i -> { PopConsumerRecord temp = new PopConsumerRecord(); temp.setPopTime(0); temp.setInvisibleTime(20 * 1000); temp.setTopicId("topic"); temp.setGroupId("group"); temp.setQueueId(2); temp.setOffset(i); return temp; }) .collect(Collectors.toList()); consumerService.getPopConsumerStore().writeRecords(consumerRecordList); Mockito.doReturn(CompletableFuture.completedFuture(null)) .when(consumerServiceSpy).getMessageAsync(any(PopConsumerRecord.class)); consumerServiceSpy.revive(new AtomicLong(20 * 1000), 1); Mockito.doReturn(CompletableFuture.completedFuture( Triple.of(null, "GetMessageResult is null", false))) .when(consumerServiceSpy).getMessageAsync(any(PopConsumerRecord.class)); consumerServiceSpy.revive(new AtomicLong(20 * 1000), 1); Mockito.doReturn(CompletableFuture.completedFuture( Triple.of(Mockito.mock(MessageExt.class), null, false))) .when(consumerServiceSpy).getMessageAsync(any(PopConsumerRecord.class)); consumerServiceSpy.revive(new AtomicLong(20 * 1000), 1); consumerService.shutdown(); } @Test public void reviveBackoffRetryTest() { Mockito.when(brokerController.getEscapeBridge()).thenReturn(Mockito.mock(EscapeBridge.class)); Mockito.when(brokerController.getSubscriptionGroupManager() .containsSubscriptionGroup(anyString())).thenReturn(true); PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); consumerService.getPopConsumerStore().start(); long popTime = 1000000000L; long invisibleTime = 60 * 1000L; PopConsumerRecord record = new PopConsumerRecord(); record.setPopTime(popTime); record.setInvisibleTime(invisibleTime); record.setTopicId("topic"); record.setGroupId("group"); record.setQueueId(0); record.setOffset(0); consumerService.getPopConsumerStore().writeRecords(Collections.singletonList(record)); Mockito.doReturn(CompletableFuture.completedFuture(Triple.of(Mockito.mock(MessageExt.class), "", false))) .when(consumerServiceSpy).getMessageAsync(any(PopConsumerRecord.class)); Mockito.when(brokerController.getEscapeBridge().putMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenReturn( new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)) ); long visibleTimestamp = popTime + invisibleTime; // revive fails Assert.assertEquals(1, consumerServiceSpy.revive(new AtomicLong(visibleTimestamp), 1)); // should be invisible now Assert.assertEquals(0, consumerService.getPopConsumerStore().scanExpiredRecords(0, visibleTimestamp, 1).size()); // will be visible again in 10 seconds Assert.assertEquals(1, consumerService.getPopConsumerStore().scanExpiredRecords(visibleTimestamp, System.currentTimeMillis() + visibleTimestamp + 10 * 1000, 1).size()); consumerService.shutdown(); } @Test public void transferToFsStoreTest() { Assert.assertNotNull(consumerService.getServiceName()); List consumerRecordList = IntStream.range(0, 3) .mapToObj(i -> { PopConsumerRecord temp = new PopConsumerRecord(); temp.setPopTime(0); temp.setInvisibleTime(20 * 1000); temp.setTopicId("topic"); temp.setGroupId("group"); temp.setQueueId(2); temp.setOffset(i); return temp; }) .collect(Collectors.toList()); Mockito.when(brokerController.getPopMessageProcessor().buildCkMsg(any(), anyInt())) .thenReturn(new MessageExtBrokerInner()); Mockito.when(brokerController.getMessageStore()).thenReturn(Mockito.mock(MessageStore.class)); Mockito.when(brokerController.getMessageStore().asyncPutMessage(any())) .thenReturn(CompletableFuture.completedFuture( new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); consumerService.start(); consumerService.getPopConsumerStore().writeRecords(consumerRecordList); consumerService.transferToFsStore(); consumerService.shutdown(); } @Test public void testChangeInvisibilityDurationWithSuspendTrue() { long current = System.currentTimeMillis(); long popTime = current - 1000; long invisibleTime = 10000; long changedPopTime = current; long changedInvisibleTime = 20000; long offset = 100L; consumerService.getPopConsumerStore().start(); Mockito.when(brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(groupId)).thenReturn(true); // Test with suspend = true consumerService.changeInvisibilityDuration(popTime, invisibleTime, changedPopTime, changedInvisibleTime, groupId, topicId, queueId, offset, true); // Verify that the record was written with suspend = true List records = consumerService.getPopConsumerStore() .scanExpiredRecords(0, changedPopTime + changedInvisibleTime + 1000, 10); Assert.assertFalse("Should have at least one record", records.isEmpty()); PopConsumerRecord ckRecord = records.stream() .filter(r -> r.getOffset() == offset && r.getPopTime() == changedPopTime) .findFirst() .orElse(null); Assert.assertNotNull("Should find the checkpoint record", ckRecord); Assert.assertTrue("Suspend flag should be true", ckRecord.isSuspend()); Assert.assertEquals("GroupId should match", groupId, ckRecord.getGroupId()); Assert.assertEquals("TopicId should match", topicId, ckRecord.getTopicId()); Assert.assertEquals("QueueId should match", queueId, ckRecord.getQueueId()); Assert.assertEquals("Offset should match", offset, ckRecord.getOffset()); consumerService.shutdown(); } @Test public void testChangeInvisibilityDurationWithSuspendFalse() { long current = System.currentTimeMillis(); long popTime = current - 1000; long invisibleTime = 10000; long changedPopTime = current; long changedInvisibleTime = 20000; long offset = 200L; consumerService.getPopConsumerStore().start(); Mockito.when(brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(groupId)).thenReturn(true); // Test with suspend = false consumerService.changeInvisibilityDuration(popTime, invisibleTime, changedPopTime, changedInvisibleTime, groupId, topicId, queueId, offset, false); // Verify that the record was written with suspend = false List records = consumerService.getPopConsumerStore() .scanExpiredRecords(0, changedPopTime + changedInvisibleTime + 1000, 10); Assert.assertFalse("Should have at least one record", records.isEmpty()); PopConsumerRecord ckRecord = records.stream() .filter(r -> r.getOffset() == offset && r.getPopTime() == changedPopTime) .findFirst() .orElse(null); Assert.assertNotNull("Should find the checkpoint record", ckRecord); Assert.assertFalse("Suspend flag should be false", ckRecord.isSuspend()); Assert.assertEquals("GroupId should match", groupId, ckRecord.getGroupId()); Assert.assertEquals("TopicId should match", topicId, ckRecord.getTopicId()); Assert.assertEquals("QueueId should match", queueId, ckRecord.getQueueId()); Assert.assertEquals("Offset should match", offset, ckRecord.getOffset()); consumerService.shutdown(); } @Test public void testReviveRetryWithSuspendTrue() { Mockito.when(brokerController.getTopicConfigManager().selectTopicConfig(topicId)).thenReturn(null); Mockito.when(brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, 0)).thenReturn(-1L); consumerService.createRetryTopicIfNeeded(groupId, topicId); consumerService.clearCache(groupId, topicId, queueId); // Create message with reconsumeTimes = 2 MessageExt messageExt = new MessageExt(); messageExt.setBody("body".getBytes()); messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setFlag(0); messageExt.setSysFlag(0); messageExt.setReconsumeTimes(2); messageExt.putUserProperty("key", "value"); // Create record with suspend = true PopConsumerRecord record = new PopConsumerRecord(); record.setTopicId(topicId); record.setGroupId(groupId); record.setQueueId(queueId); record.setPopTime(System.currentTimeMillis()); record.setInvisibleTime(30000); record.setOffset(100L); record.setSuspend(true); Mockito.when(brokerController.getBrokerStatsManager()).thenReturn(Mockito.mock(BrokerStatsManager.class)); EscapeBridge escapeBridge = Mockito.mock(EscapeBridge.class); Mockito.when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); // Capture the MessageExtBrokerInner to verify reconsumeTimes ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); Mockito.when(escapeBridge.putMessageToSpecificQueue(messageCaptor.capture())) .thenReturn(new PutMessageResult( PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); Mockito.doNothing().when(consumerServiceSpy).createRetryTopicIfNeeded(any(), any()); Assert.assertTrue("Revive should succeed", consumerServiceSpy.reviveRetry(record, messageExt)); // Verify that reconsumeTimes was NOT incremented (should remain 2) MessageExtBrokerInner capturedMessage = messageCaptor.getValue(); Assert.assertNotNull("Message should be captured", capturedMessage); Assert.assertEquals("ReconsumeTimes should remain 2 when suspend=true", 2, capturedMessage.getReconsumeTimes()); } @Test public void testReviveRetryWithSuspendFalse() { Mockito.when(brokerController.getTopicConfigManager().selectTopicConfig(topicId)).thenReturn(null); Mockito.when(brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, 0)).thenReturn(-1L); consumerService.createRetryTopicIfNeeded(groupId, topicId); consumerService.clearCache(groupId, topicId, queueId); // Create message with reconsumeTimes = 2 MessageExt messageExt = new MessageExt(); messageExt.setBody("body".getBytes()); messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setFlag(0); messageExt.setSysFlag(0); messageExt.setReconsumeTimes(2); messageExt.putUserProperty("key", "value"); // Create record with suspend = false PopConsumerRecord record = new PopConsumerRecord(); record.setTopicId(topicId); record.setGroupId(groupId); record.setQueueId(queueId); record.setPopTime(System.currentTimeMillis()); record.setInvisibleTime(30000); record.setOffset(200L); record.setSuspend(false); Mockito.when(brokerController.getBrokerStatsManager()).thenReturn(Mockito.mock(BrokerStatsManager.class)); EscapeBridge escapeBridge = Mockito.mock(EscapeBridge.class); Mockito.when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); // Capture the MessageExtBrokerInner to verify reconsumeTimes ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); Mockito.when(escapeBridge.putMessageToSpecificQueue(messageCaptor.capture())) .thenReturn(new PutMessageResult( PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); Mockito.doNothing().when(consumerServiceSpy).createRetryTopicIfNeeded(any(), any()); Assert.assertTrue("Revive should succeed", consumerServiceSpy.reviveRetry(record, messageExt)); // Verify that reconsumeTimes was incremented (should be 3) MessageExtBrokerInner capturedMessage = messageCaptor.getValue(); Assert.assertNotNull("Message should be captured", capturedMessage); Assert.assertEquals("ReconsumeTimes should be incremented to 3 when suspend=false", 3, capturedMessage.getReconsumeTimes()); } @Test public void testReviveRetryWithSuspendTrueMultipleTimes() { Mockito.when(brokerController.getTopicConfigManager().selectTopicConfig(topicId)).thenReturn(null); Mockito.when(brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, 0)).thenReturn(-1L); consumerService.createRetryTopicIfNeeded(groupId, topicId); consumerService.clearCache(groupId, topicId, queueId); // Create message with reconsumeTimes = 0 MessageExt messageExt = new MessageExt(); messageExt.setBody("body".getBytes()); messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setFlag(0); messageExt.setSysFlag(0); messageExt.setReconsumeTimes(0); messageExt.putUserProperty("key", "value"); Mockito.when(brokerController.getBrokerStatsManager()).thenReturn(Mockito.mock(BrokerStatsManager.class)); EscapeBridge escapeBridge = Mockito.mock(EscapeBridge.class); Mockito.when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); Mockito.doNothing().when(consumerServiceSpy).createRetryTopicIfNeeded(any(), any()); // Simulate multiple nacks with suspend = true for (int i = 0; i < 3; i++) { PopConsumerRecord record = new PopConsumerRecord(); record.setTopicId(topicId); record.setGroupId(groupId); record.setQueueId(queueId); record.setPopTime(System.currentTimeMillis()); record.setInvisibleTime(30000); record.setOffset(300L + i); record.setSuspend(true); // Capture the MessageExtBrokerInner to verify reconsumeTimes org.mockito.ArgumentCaptor messageCaptor = org.mockito.ArgumentCaptor.forClass(MessageExtBrokerInner.class); Mockito.when(escapeBridge.putMessageToSpecificQueue(messageCaptor.capture())) .thenReturn(new PutMessageResult( PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); Assert.assertTrue("Revive should succeed", consumerServiceSpy.reviveRetry(record, messageExt)); // Verify that reconsumeTimes remains 0 (not incremented) MessageExtBrokerInner capturedMessage = messageCaptor.getValue(); Assert.assertNotNull("Message should be captured", capturedMessage); Assert.assertEquals("ReconsumeTimes should remain 0 after " + (i + 1) + " nacks with suspend=true", 0, capturedMessage.getReconsumeTimes()); // Update messageExt for next iteration (simulate the message being re-consumed) messageExt.setReconsumeTimes(capturedMessage.getReconsumeTimes()); } } @Test public void testReviveRetryWithSuspendFalseMultipleTimes() { Mockito.when(brokerController.getTopicConfigManager().selectTopicConfig(topicId)).thenReturn(null); Mockito.when(brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, 0)).thenReturn(-1L); consumerService.createRetryTopicIfNeeded(groupId, topicId); consumerService.clearCache(groupId, topicId, queueId); // Create message with reconsumeTimes = 0 MessageExt messageExt = new MessageExt(); messageExt.setBody("body".getBytes()); messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setFlag(0); messageExt.setSysFlag(0); messageExt.setReconsumeTimes(0); messageExt.putUserProperty("key", "value"); Mockito.when(brokerController.getBrokerStatsManager()).thenReturn(Mockito.mock(BrokerStatsManager.class)); EscapeBridge escapeBridge = Mockito.mock(EscapeBridge.class); Mockito.when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); PopConsumerService consumerServiceSpy = Mockito.spy(consumerService); Mockito.doNothing().when(consumerServiceSpy).createRetryTopicIfNeeded(any(), any()); // Simulate multiple nacks with suspend = false for (int i = 0; i < 3; i++) { PopConsumerRecord record = new PopConsumerRecord(); record.setTopicId(topicId); record.setGroupId(groupId); record.setQueueId(queueId); record.setPopTime(System.currentTimeMillis()); record.setInvisibleTime(30000); record.setOffset(400L + i); record.setSuspend(false); // Capture the MessageExtBrokerInner to verify reconsumeTimes org.mockito.ArgumentCaptor messageCaptor = org.mockito.ArgumentCaptor.forClass(MessageExtBrokerInner.class); Mockito.when(escapeBridge.putMessageToSpecificQueue(messageCaptor.capture())) .thenReturn(new PutMessageResult( PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); Assert.assertTrue("Revive should succeed", consumerServiceSpy.reviveRetry(record, messageExt)); // Verify that reconsumeTimes is incremented each time MessageExtBrokerInner capturedMessage = messageCaptor.getValue(); Assert.assertNotNull("Message should be captured", capturedMessage); Assert.assertEquals("ReconsumeTimes should be " + (i + 1) + " after " + (i + 1) + " nacks with suspend=false", i + 1, capturedMessage.getReconsumeTimes()); // Update messageExt for next iteration (simulate the message being re-consumed) messageExt.setReconsumeTimes(capturedMessage.getReconsumeTimes()); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/orderly/ConsumerOrderInfoManagerLockFreeNotifyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop.orderly; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.processor.PopMessageProcessor; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import org.mockito.stubbing.Answer; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ConsumerOrderInfoManagerLockFreeNotifyTest { private static final String TOPIC = "topic"; private static final String GROUP = "group"; private static final int QUEUE_ID_0 = 0; private long popTime; private QueueLevelConsumerManager consumerOrderInfoManager; private AtomicBoolean notified; private final BrokerConfig brokerConfig = new BrokerConfig(); private final PopMessageProcessor popMessageProcessor = mock(PopMessageProcessor.class); private final BrokerController brokerController = mock(BrokerController.class); @Before public void before() throws ConsumeQueueException { notified = new AtomicBoolean(false); brokerConfig.setEnableNotifyAfterPopOrderLockRelease(true); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getPopMessageProcessor()).thenReturn(popMessageProcessor); doAnswer((Answer) mock -> { notified.set(true); return null; }).when(popMessageProcessor).notifyLongPollingRequestIfNeed(anyString(), anyString(), anyInt()); consumerOrderInfoManager = new QueueLevelConsumerManager(brokerController); popTime = System.currentTimeMillis(); } @Test public void testConsumeMessageThenNoAck() { consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L), new StringBuilder() ); await().atLeast(Duration.ofSeconds(2)).atMost(Duration.ofSeconds(4)).until(notified::get); assertTrue(consumerOrderInfoManager.getConsumerOrderInfoLockManager().getTimeoutMap().isEmpty()); } @Test public void testConsumeMessageThenAck() { consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L), new StringBuilder() ); consumerOrderInfoManager.commitAndNext( TOPIC, GROUP, QUEUE_ID_0, 1, popTime ); await().atMost(Duration.ofSeconds(1)).until(notified::get); assertTrue(consumerOrderInfoManager.getConsumerOrderInfoLockManager().getTimeoutMap().isEmpty()); } @Test public void testConsumeTheChangeInvisibleLonger() { consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L), new StringBuilder() ); consumerOrderInfoManager.updateNextVisibleTime( TOPIC, GROUP, QUEUE_ID_0, 1, popTime, popTime + 5000 ); await().atLeast(Duration.ofSeconds(4)).atMost(Duration.ofSeconds(6)).until(notified::get); assertTrue(consumerOrderInfoManager.getConsumerOrderInfoLockManager().getTimeoutMap().isEmpty()); } @Test public void testConsumeTheChangeInvisibleShorter() { consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L), new StringBuilder() ); consumerOrderInfoManager.updateNextVisibleTime( TOPIC, GROUP, QUEUE_ID_0, 1, popTime, popTime + 1000 ); await().atLeast(Duration.ofMillis(500)).atMost(Duration.ofSeconds(2)).until(notified::get); assertTrue(consumerOrderInfoManager.getConsumerOrderInfoLockManager().getTimeoutMap().isEmpty()); } @Test public void testRecover() { QueueLevelConsumerManager savedConsumerOrderInfoManager = new QueueLevelConsumerManager(); savedConsumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L), new StringBuilder() ); String encodedData = savedConsumerOrderInfoManager.encode(); consumerOrderInfoManager.decode(encodedData); await().atLeast(Duration.ofSeconds(2)).atMost(Duration.ofSeconds(4)).until(notified::get); assertTrue(consumerOrderInfoManager.getConsumerOrderInfoLockManager().getTimeoutMap().isEmpty()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/pop/orderly/ConsumerOrderInfoManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.pop.orderly; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ConsumerOrderInfoManagerTest { private static final String TOPIC = "topic"; private static final String GROUP = "group"; private static final int QUEUE_ID_0 = 0; private static final int QUEUE_ID_1 = 1; private long popTime; private QueueLevelConsumerManager consumerOrderInfoManager; @Before public void before() { consumerOrderInfoManager = new QueueLevelConsumerManager(); popTime = System.currentTimeMillis(); } @Test public void testCommitAndNext() { consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L), new StringBuilder() ); assertEncodeAndDecode(); assertEquals(-2, consumerOrderInfoManager.commitAndNext( TOPIC, GROUP, QUEUE_ID_0, 1L, popTime - 10 )); assertEncodeAndDecode(); assertTrue(consumerOrderInfoManager.checkBlock( null, TOPIC, GROUP, QUEUE_ID_0, TimeUnit.SECONDS.toMillis(3) )); assertEquals(2, consumerOrderInfoManager.commitAndNext( TOPIC, GROUP, QUEUE_ID_0, 1L, popTime )); assertEncodeAndDecode(); assertFalse(consumerOrderInfoManager.checkBlock( null, TOPIC, GROUP, QUEUE_ID_0, TimeUnit.SECONDS.toMillis(3) )); } @Test public void testConsumedCount() { { // consume three new messages StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L, 2L, 3L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(1, orderInfoMap.size()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); } { // reconsume same messages StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L, 2L, 3L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(4, orderInfoMap.size()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); for (int i = 1; i <= 3; i++) { assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, i)).intValue()); } } { // reconsume last two message StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(2L, 3L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(3, orderInfoMap.size()); assertEquals(2, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); for (int i = 2; i <= 3; i++) { assertEquals(2, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, i)).intValue()); } } { // consume a new message and reconsume last message StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(3L, 4L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(2, orderInfoMap.size()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); assertEquals(3, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, 3)).intValue()); } { // consume two new messages StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(5L, 6L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(1, orderInfoMap.size()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); } } @Test public void testConsumedCountForMultiQueue() { { // consume two new messages StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(0L), orderInfoBuilder ); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_1, popTime, 3000, Lists.newArrayList(0L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(2, orderInfoMap.size()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_1)).intValue()); } { // reconsume two message StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(0L), orderInfoBuilder ); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_1, popTime, 3000, Lists.newArrayList(0L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(4, orderInfoMap.size()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_1)).intValue()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, 0L)).intValue()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_1, 0L)).intValue()); } { // reconsume with a new message StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(0L, 1L), orderInfoBuilder ); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_1, popTime, 3000, Lists.newArrayList(0L), orderInfoBuilder ); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(4, orderInfoMap.size()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); assertEquals(2, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_1)).intValue()); assertEquals(2, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, 0L)).intValue()); assertNull(orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, 1L))); assertEquals(2, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_1, 0L)).intValue()); } } @Test public void testUpdateNextVisibleTime() { long invisibleTime = 3000; StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 1, Lists.newArrayList(1L, 2L, 3L), orderInfoBuilder ); consumerOrderInfoManager.updateNextVisibleTime(TOPIC, GROUP, QUEUE_ID_0, 2L, popTime, System.currentTimeMillis() + invisibleTime); assertEncodeAndDecode(); assertEquals(2, consumerOrderInfoManager.commitAndNext(TOPIC, GROUP, QUEUE_ID_0, 1L, popTime)); assertEncodeAndDecode(); assertEquals(2, consumerOrderInfoManager.commitAndNext(TOPIC, GROUP, QUEUE_ID_0, 3L, popTime)); assertEncodeAndDecode(); await().atMost(Duration.ofSeconds(invisibleTime + 1)).until(() -> !consumerOrderInfoManager.checkBlock(null, TOPIC, GROUP, QUEUE_ID_0, invisibleTime)); orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update( null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 1, Lists.newArrayList(2L, 3L, 4L), orderInfoBuilder ); consumerOrderInfoManager.updateNextVisibleTime(TOPIC, GROUP, QUEUE_ID_0, 2L, popTime, System.currentTimeMillis() + invisibleTime); assertEncodeAndDecode(); assertEquals(2, consumerOrderInfoManager.commitAndNext(TOPIC, GROUP, QUEUE_ID_0, 3L, popTime)); assertEncodeAndDecode(); assertEquals(2, consumerOrderInfoManager.commitAndNext(TOPIC, GROUP, QUEUE_ID_0, 4L, popTime)); assertEncodeAndDecode(); assertTrue(consumerOrderInfoManager.checkBlock(null, TOPIC, GROUP, QUEUE_ID_0, invisibleTime)); assertEquals(5L, consumerOrderInfoManager.commitAndNext(TOPIC, GROUP, QUEUE_ID_0, 2L, popTime)); assertEncodeAndDecode(); assertFalse(consumerOrderInfoManager.checkBlock(null, TOPIC, GROUP, QUEUE_ID_0, invisibleTime)); } @Test public void testAutoCleanAndEncode() { BrokerConfig brokerConfig = new BrokerConfig(); BrokerController brokerController = mock(BrokerController.class); TopicConfigManager topicConfigManager = mock(TopicConfigManager.class); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); SubscriptionGroupManager subscriptionGroupManager = mock(SubscriptionGroupManager.class); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(subscriptionGroupManager.containsSubscriptionGroup(GROUP)).thenReturn(true); TopicConfig topicConfig = new TopicConfig(TOPIC); when(topicConfigManager.selectTopicConfig(eq(TOPIC))).thenReturn(topicConfig); QueueLevelConsumerManager consumerOrderInfoManager = new QueueLevelConsumerManager(brokerController); { consumerOrderInfoManager.update(null, false, "errTopic", "errGroup", QUEUE_ID_0, popTime, 1, Lists.newArrayList(2L, 3L, 4L), new StringBuilder()); consumerOrderInfoManager.autoClean(); assertEquals(0, consumerOrderInfoManager.getTable().size()); } { consumerOrderInfoManager.update(null, false, TOPIC, "errGroup", QUEUE_ID_0, popTime, 1, Lists.newArrayList(2L, 3L, 4L), new StringBuilder()); consumerOrderInfoManager.autoClean(); assertEquals(0, consumerOrderInfoManager.getTable().size()); } { topicConfig.setReadQueueNums(0); consumerOrderInfoManager.update(null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 1, Lists.newArrayList(2L, 3L, 4L), new StringBuilder()); await().atMost(Duration.ofSeconds(1)).until(() -> { consumerOrderInfoManager.autoClean(); return consumerOrderInfoManager.getTable().size() == 0; }); } { topicConfig.setReadQueueNums(8); consumerOrderInfoManager.update(null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 1, Lists.newArrayList(2L, 3L, 4L), new StringBuilder()); consumerOrderInfoManager.autoClean(); assertEquals(1, consumerOrderInfoManager.getTable().size()); for (ConcurrentHashMap orderInfoMap : consumerOrderInfoManager.getTable().values()) { assertEquals(1, orderInfoMap.size()); assertNotNull(orderInfoMap.get(QUEUE_ID_0)); break; } } } private void assertEncodeAndDecode() { QueueLevelConsumerManager.OrderInfo prevOrderInfo = consumerOrderInfoManager.getTable().values().stream().findFirst() .get().get(QUEUE_ID_0); String dataEncoded = consumerOrderInfoManager.encode(); consumerOrderInfoManager.decode(dataEncoded); QueueLevelConsumerManager.OrderInfo newOrderInfo = consumerOrderInfoManager.getTable().values().stream().findFirst() .get().get(QUEUE_ID_0); assertNotSame(prevOrderInfo, newOrderInfo); assertEquals(prevOrderInfo.getPopTime(), newOrderInfo.getPopTime()); assertEquals(prevOrderInfo.getInvisibleTime(), newOrderInfo.getInvisibleTime()); assertEquals(prevOrderInfo.getOffsetList(), newOrderInfo.getOffsetList()); assertEquals(prevOrderInfo.getOffsetConsumedCount(), newOrderInfo.getOffsetConsumedCount()); assertEquals(prevOrderInfo.getOffsetNextVisibleTime(), newOrderInfo.getOffsetNextVisibleTime()); assertEquals(prevOrderInfo.getLastConsumeTimestamp(), newOrderInfo.getLastConsumeTimestamp()); assertEquals(prevOrderInfo.getCommitOffsetBit(), newOrderInfo.getCommitOffsetBit()); } @Test public void testLoadFromOldVersionOrderInfoData() { consumerOrderInfoManager.update(null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 1, Lists.newArrayList(2L, 3L, 4L), new StringBuilder()); QueueLevelConsumerManager.OrderInfo orderInfo = consumerOrderInfoManager.getTable().values().stream().findFirst() .get().get(QUEUE_ID_0); orderInfo.setInvisibleTime(null); orderInfo.setOffsetConsumedCount(null); orderInfo.setOffsetNextVisibleTime(null); String dataEncoded = consumerOrderInfoManager.encode(); consumerOrderInfoManager.decode(dataEncoded); assertTrue(consumerOrderInfoManager.checkBlock(null, TOPIC, GROUP, QUEUE_ID_0, 3000)); StringBuilder orderInfoBuilder = new StringBuilder(); consumerOrderInfoManager.update(null, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 1, Lists.newArrayList(3L, 4L, 5L), orderInfoBuilder); assertEncodeAndDecode(); Map orderInfoMap = ExtraInfoUtil.parseOrderCountInfo(orderInfoBuilder.toString()); assertEquals(3, orderInfoMap.size()); assertEquals(0, orderInfoMap.get(ExtraInfoUtil.getStartOffsetInfoMapKey(TOPIC, QUEUE_ID_0)).intValue()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, 3)).intValue()); assertEquals(1, orderInfoMap.get(ExtraInfoUtil.getQueueOffsetMapKey(TOPIC, QUEUE_ID_0, 4)).intValue()); } @Test public void testReentrant() { StringBuilder orderInfoBuilder = new StringBuilder(); String attemptId = UUID.randomUUID().toString(); consumerOrderInfoManager.update( attemptId, false, TOPIC, GROUP, QUEUE_ID_0, popTime, 3000, Lists.newArrayList(1L, 2L, 3L), orderInfoBuilder ); assertTrue(consumerOrderInfoManager.checkBlock(null, TOPIC, GROUP, QUEUE_ID_0, 3000)); assertFalse(consumerOrderInfoManager.checkBlock(attemptId, TOPIC, GROUP, QUEUE_ID_0, 3000)); } @Test public void testGetMaxLockFreeTimestamp() { QueueLevelConsumerManager.OrderInfo orderInfo = new QueueLevelConsumerManager.OrderInfo(); orderInfo.setOffsetList(new ArrayList<>()); assertNull(orderInfo.getMaxLockFreeTimestamp()); QueueLevelConsumerManager.OrderInfo nullOrderInfo = new QueueLevelConsumerManager.OrderInfo(); nullOrderInfo.setOffsetList(null); assertNull(nullOrderInfo.getMaxLockFreeTimestamp()); List offsetList = Arrays.asList(100L, 1L, 2L); QueueLevelConsumerManager.OrderInfo allAckOrderInfo = new QueueLevelConsumerManager.OrderInfo(); allAckOrderInfo.setOffsetList(offsetList); allAckOrderInfo.setCommitOffsetBit(7); allAckOrderInfo.setPopTime(System.currentTimeMillis()); allAckOrderInfo.setInvisibleTime(30000L); assertEquals(System.currentTimeMillis(), allAckOrderInfo.getMaxLockFreeTimestamp(), 1000L); QueueLevelConsumerManager.OrderInfo unackOrderInfo = new QueueLevelConsumerManager.OrderInfo(); unackOrderInfo.setOffsetList(offsetList); unackOrderInfo.setCommitOffsetBit(0); long popTime = System.currentTimeMillis(); unackOrderInfo.setPopTime(popTime); unackOrderInfo.setInvisibleTime(30000L); Long expectedTime = popTime + 30000L; assertEquals(expectedTime, unackOrderInfo.getMaxLockFreeTimestamp()); QueueLevelConsumerManager.OrderInfo hasVisibleButAckedOrderInfo = new QueueLevelConsumerManager.OrderInfo(); hasVisibleButAckedOrderInfo.setOffsetList(offsetList); hasVisibleButAckedOrderInfo.setCommitOffsetBit(1); hasVisibleButAckedOrderInfo.setPopTime(popTime); hasVisibleButAckedOrderInfo.setInvisibleTime(30000L); Map offsetNextVisibleTime = new HashMap<>(); offsetNextVisibleTime.put(100L, popTime + 60000L); hasVisibleButAckedOrderInfo.setOffsetNextVisibleTime(offsetNextVisibleTime); assertEquals(Long.valueOf(popTime + 30000L), hasVisibleButAckedOrderInfo.getMaxLockFreeTimestamp()); QueueLevelConsumerManager.OrderInfo multiUnackOrderInfo = new QueueLevelConsumerManager.OrderInfo(); multiUnackOrderInfo.setOffsetList(offsetList); multiUnackOrderInfo.setCommitOffsetBit(0); multiUnackOrderInfo.setPopTime(popTime); multiUnackOrderInfo.setInvisibleTime(30000L); Map multiOffsetNextVisibleTime = new HashMap<>(); multiOffsetNextVisibleTime.put(100L, popTime + 20000L); multiOffsetNextVisibleTime.put(101L, popTime + 40000L); multiOffsetNextVisibleTime.put(102L, popTime + 60000L); multiUnackOrderInfo.setOffsetNextVisibleTime(multiOffsetNextVisibleTime); assertEquals(Long.valueOf(popTime + 60000L), multiUnackOrderInfo.getMaxLockFreeTimestamp()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.net.Broker2Client; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BatchAck; import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import static org.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class AckMessageProcessorTest { private AckMessageProcessor ackMessageProcessor; @Mock private PopMessageProcessor popMessageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; @Mock private DefaultMessageStore messageStore; @Mock private Channel channel; private String topic = "FooBar"; private String group = "FooBarGroup"; private ClientChannelInfo clientInfo; @Mock private Broker2Client broker2Client; private static final long MIN_OFFSET_IN_QUEUE = 100; private static final long MAX_OFFSET_IN_QUEUE = 999; @Before public void init() throws IllegalAccessException, NoSuchFieldException, ConsumeQueueException { clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0); brokerController.setMessageStore(messageStore); Field field = BrokerController.class.getDeclaredField("broker2Client"); field.setAccessible(true); field.set(brokerController, broker2Client); EscapeBridge escapeBridge = new EscapeBridge(brokerController); Mockito.when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); // Initialize BrokerMetricsManager for tests Field bmmField = BrokerController.class.getDeclaredField("brokerMetricsManager"); bmmField.setAccessible(true); bmmField.set(brokerController, new BrokerMetricsManager(brokerController)); Channel mockChannel = mock(Channel.class); when(handlerContext.channel()).thenReturn(mockChannel); brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig()); ConsumerData consumerData = createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), false); ackMessageProcessor = new AckMessageProcessor(brokerController); when(messageStore.getMinOffsetInQueue(anyString(), anyInt())).thenReturn(MIN_OFFSET_IN_QUEUE); when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(MAX_OFFSET_IN_QUEUE); when(brokerController.getPopMessageProcessor()).thenReturn(popMessageProcessor); } @Test public void testProcessRequest_Success() throws RemotingCommandException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException { when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PopBufferMergeService popBufferMergeService = mock(PopBufferMergeService.class); when(popBufferMergeService.addAk(anyInt(), any())).thenReturn(false); when(popMessageProcessor.getPopBufferMergeService()).thenReturn(popBufferMergeService); int queueId = 0; long queueOffset = 0; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setOffset(MIN_OFFSET_IN_QUEUE + 1); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand responseToReturn = ackMessageProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque()); } @Test public void testProcessRequest_WrongRequestCode() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, null); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.MESSAGE_ILLEGAL); assertThat(response.getRemark()).isEqualTo("AckMessageProcessor failed to process RequestCode: " + RequestCode.SEND_MESSAGE); } @Test public void testSingleAck_TopicCheck() throws RemotingCommandException { AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic("wrongTopic"); requestHeader.setQueueId(0); requestHeader.setOffset(0L); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); assertThat(response.getRemark()).contains("not exist, apply first"); } @Test public void testSingleAck_QueueCheck() throws RemotingCommandException { { int qId = -1; AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(qId); requestHeader.setOffset(0L); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.MESSAGE_ILLEGAL); assertThat(response.getRemark()).contains("queueId[" + qId + "] is illegal"); } { int qId = 17; AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(qId); requestHeader.setOffset(0L); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.MESSAGE_ILLEGAL); assertThat(response.getRemark()).contains("queueId[" + qId + "] is illegal"); } } @Test public void testSingleAck_OffsetCheck() throws RemotingCommandException { { AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setOffset(MIN_OFFSET_IN_QUEUE - 1); //requestHeader.setOffset(maxOffsetInQueue + 1); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_MESSAGE); assertThat(response.getRemark()).contains("offset is illegal"); } { AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(0); //requestHeader.setOffset(minOffsetInQueue - 1); requestHeader.setOffset(MAX_OFFSET_IN_QUEUE + 1); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_MESSAGE); assertThat(response.getRemark()).contains("offset is illegal"); } } @Test public void testBatchAck_NoMessage() throws RemotingCommandException { { //reqBody == null RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_MESSAGE); } { //reqBody.getAcks() == null RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); BatchAckMessageRequestBody reqBody = new BatchAckMessageRequestBody(); request.setBody(reqBody.encode()); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_MESSAGE); } { //reqBody.getAcks().isEmpty() RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); BatchAckMessageRequestBody reqBody = new BatchAckMessageRequestBody(); reqBody.setAcks(new ArrayList<>()); request.setBody(reqBody.encode()); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_MESSAGE); } } @Test public void testSingleAck_appendAck() throws RemotingCommandException { { // buffer addAk OK PopBufferMergeService popBufferMergeService = mock(PopBufferMergeService.class); when(popBufferMergeService.addAk(anyInt(), any())).thenReturn(true); when(popMessageProcessor.getPopBufferMergeService()).thenReturn(popBufferMergeService); AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); long ackOffset = MIN_OFFSET_IN_QUEUE + 10; requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setOffset(ackOffset); requestHeader.setConsumerGroup(MixAll.DEFAULT_CONSUMER_GROUP); requestHeader.setExtraInfo("64 1666860736757 60000 4 0 broker-a 0 " + ackOffset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } { // buffer addAk fail PopBufferMergeService popBufferMergeService = mock(PopBufferMergeService.class); when(popBufferMergeService.addAk(anyInt(), any())).thenReturn(false); when(popMessageProcessor.getPopBufferMergeService()).thenReturn(popBufferMergeService); // store putMessage OK PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, null); when(messageStore.putMessage(any())).thenReturn(putMessageResult); AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); long ackOffset = MIN_OFFSET_IN_QUEUE + 10; requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setOffset(ackOffset); requestHeader.setConsumerGroup(MixAll.DEFAULT_CONSUMER_GROUP); requestHeader.setExtraInfo("64 1666860736757 60000 4 0 broker-a 0 " + ackOffset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } } @Test public void testBatchAck_appendAck() throws RemotingCommandException { { // buffer addAk OK PopBufferMergeService popBufferMergeService = mock(PopBufferMergeService.class); when(popBufferMergeService.addAk(anyInt(), any())).thenReturn(true); when(popMessageProcessor.getPopBufferMergeService()).thenReturn(popBufferMergeService); BatchAck bAck1 = new BatchAck(); bAck1.setConsumerGroup(MixAll.DEFAULT_CONSUMER_GROUP); bAck1.setTopic(topic); bAck1.setStartOffset(MIN_OFFSET_IN_QUEUE); bAck1.setBitSet(new BitSet()); bAck1.getBitSet().set(1); bAck1.setRetry("0"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); BatchAckMessageRequestBody reqBody = new BatchAckMessageRequestBody(); reqBody.setAcks(Collections.singletonList(bAck1)); request.setBody(reqBody.encode()); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } { // buffer addAk fail PopBufferMergeService popBufferMergeService = mock(PopBufferMergeService.class); when(popBufferMergeService.addAk(anyInt(), any())).thenReturn(false); when(popMessageProcessor.getPopBufferMergeService()).thenReturn(popBufferMergeService); // store putMessage OK PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, null); when(messageStore.putMessage(any())).thenReturn(putMessageResult); BatchAck bAck1 = new BatchAck(); bAck1.setConsumerGroup(MixAll.DEFAULT_CONSUMER_GROUP); bAck1.setTopic(topic); bAck1.setStartOffset(MIN_OFFSET_IN_QUEUE); bAck1.setBitSet(new BitSet()); bAck1.getBitSet().set(1); bAck1.setRetry("0"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); BatchAckMessageRequestBody reqBody = new BatchAckMessageRequestBody(); reqBody.setAcks(Arrays.asList(bAck1)); request.setBody(reqBody.encode()); request.makeCustomHeaderToNet(); RemotingCommand response = ackMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.enums.Decision; import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.authorization.model.Environment; import org.apache.rocketmq.auth.authorization.model.Resource; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.client.net.Broker2Client; import org.apache.rocketmq.broker.config.v1.RocksDBSubscriptionGroupManager; import org.apache.rocketmq.broker.config.v1.RocksDBTopicConfigManager; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.lite.LiteLifecycleManager; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.schedule.ScheduleMessageService; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.TopicQueueId; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.attribute.AttributeParser; import org.apache.rocketmq.common.constant.FIleReadaheadMode; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.AclInfo; import org.apache.rocketmq.remoting.protocol.body.CreateTopicListRequestBody; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.QueryCorrectionOffsetBody; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.header.CheckRocksdbCqWriteProgressRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerStatusRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetSubscriptionGroupConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ListAclsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ListUsersRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyMinBrokerIdChangeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryCorrectionOffsetHeader; import org.apache.rocketmq.remoting.protocol.header.QuerySubscriptionByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicConsumeByWhoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicsByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetMasterFlushOffsetHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.stats.BrokerStats; import org.apache.rocketmq.store.timer.TimerCheckpoint; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.TimerMetrics; import org.apache.rocketmq.store.util.LibC; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.LongAdder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class AdminBrokerProcessorTest { private AdminBrokerProcessor adminBrokerProcessor; @Mock private ChannelHandlerContext handlerContext; @Mock private Channel channel; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig(), null); @Mock private MessageStore messageStore; @Mock private SendMessageProcessor sendMessageProcessor; @Mock private ConcurrentMap inFlyWritingCounterMap; private Set systemTopicSet; private String topic; @Mock private SocketAddress socketAddress; @Mock private BrokerStats brokerStats; @Mock private TopicConfigManager topicConfigManager; @Mock private ConsumerManager consumerManager; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private DefaultMessageStore defaultMessageStore; @Mock private ScheduleMessageService scheduleMessageService; @Mock private AuthenticationMetadataManager authenticationMetadataManager; @Mock private AuthorizationMetadataManager authorizationMetadataManager; @Mock private TimerMessageStore timerMessageStore; @Mock private TimerMetrics timerMetrics; @Mock private MessageStoreConfig messageStoreConfig; @Mock private CommitLog commitLog; @Mock private Broker2Client broker2Client; @Mock private ClientChannelInfo clientChannelInfo; @Before public void init() throws Exception { brokerController.setMessageStore(messageStore); brokerController.setAuthenticationMetadataManager(authenticationMetadataManager); brokerController.setAuthorizationMetadataManager(authorizationMetadataManager); // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); Field field = BrokerController.class.getDeclaredField("broker2Client"); field.setAccessible(true); field.set(brokerController, broker2Client); //doReturn(sendMessageProcessor).when(brokerController).getSendMessageProcessor(); adminBrokerProcessor = new AdminBrokerProcessor(brokerController); systemTopicSet = Sets.newHashSet( TopicValidator.RMQ_SYS_SELF_TEST_TOPIC, TopicValidator.RMQ_SYS_BENCHMARK_TOPIC, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, TopicValidator.RMQ_SYS_OFFSET_MOVED_EVENT, TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC, this.brokerController.getBrokerConfig().getBrokerClusterName(), this.brokerController.getBrokerConfig().getBrokerClusterName() + "_" + MixAll.REPLY_TOPIC_POSTFIX); if (this.brokerController.getBrokerConfig().isTraceTopicEnable()) { systemTopicSet.add(this.brokerController.getBrokerConfig().getMsgTraceTopicName()); } when(handlerContext.channel()).thenReturn(channel); when(channel.remoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); topic = "FooBar" + System.nanoTime(); brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig(topic)); brokerController.getMessageStoreConfig().setTimerWheelEnable(false); when(this.brokerController.getMessageStore().getTimerMessageStore()).thenReturn(timerMessageStore); when(this.timerMessageStore.getTimerMetrics()).thenReturn(timerMetrics); } @After public void destroy() { if (notToBeExecuted()) { return; } if (brokerController.getSubscriptionGroupManager() != null) { brokerController.getSubscriptionGroupManager().stop(); } if (brokerController.getTopicConfigManager() != null) { brokerController.getTopicConfigManager().stop(); } if (brokerController.getConsumerOffsetManager() != null) { brokerController.getConsumerOffsetManager().stop(); } } private void initRocksdbTopicManager() { if (notToBeExecuted()) { return; } RocksDBTopicConfigManager rocksDBTopicConfigManager = new RocksDBTopicConfigManager(brokerController); brokerController.setTopicConfigManager(rocksDBTopicConfigManager); rocksDBTopicConfigManager.load(); } private void initRocksdbSubscriptionManager() { if (notToBeExecuted()) { return; } RocksDBSubscriptionGroupManager rocksDBSubscriptionGroupManager = new RocksDBSubscriptionGroupManager(brokerController); brokerController.setSubscriptionGroupManager(rocksDBSubscriptionGroupManager); rocksDBSubscriptionGroupManager.load(); } @Test public void testProcessRequest_success() throws RemotingCommandException, UnknownHostException { RemotingCommand request = createUpdateBrokerConfigCommand(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_fail() throws RemotingCommandException, UnknownHostException { RemotingCommand request = createResumeCheckHalfMessageCommand(); when(messageStore.selectOneMessageByOffset(any(Long.class))).thenReturn(createSelectMappedBufferResult()); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testUpdateAndCreateTopicInRocksdb() throws Exception { if (notToBeExecuted()) { return; } initRocksdbTopicManager(); testUpdateAndCreateTopic(); } @Test public void testUpdateAndCreateTopic() throws Exception { //test system topic for (String topic : systemTopicSet) { RemotingCommand request = buildCreateTopicRequest(topic); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); assertThat(response.getRemark()).isEqualTo("The topic[" + topic + "] is conflict with system topic."); } //test validate error topic String topic = ""; RemotingCommand request = buildCreateTopicRequest(topic); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); topic = "TEST_CREATE_TOPIC"; request = buildCreateTopicRequest(topic); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); // test deny MIXED topic type brokerController.getBrokerConfig().setEnableMixedMessageType(false); topic = "TEST_MIXED_TYPE"; Map attributes = new HashMap<>(); attributes.put("+message.type", "MIXED"); request = buildCreateTopicRequest(topic, attributes); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); // test allow MIXED topic type brokerController.getBrokerConfig().setEnableMixedMessageType(true); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUpdateAndCreateTopicList() throws RemotingCommandException { List systemTopicList = new ArrayList<>(systemTopicSet); RemotingCommand request = buildCreateTopicListRequest(systemTopicList); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); assertThat(response.getRemark()).isEqualTo("The topic[" + systemTopicList.get(0) + "] is conflict with system topic."); List inValidTopicList = new ArrayList<>(); inValidTopicList.add(""); request = buildCreateTopicListRequest(inValidTopicList); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); List topicList = new ArrayList<>(); topicList.add("TEST_CREATE_TOPIC"); topicList.add("TEST_CREATE_TOPIC1"); request = buildCreateTopicListRequest(topicList); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); //test no changes response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); // test deny MIXED topic type brokerController.getBrokerConfig().setEnableMixedMessageType(false); topicList.add("TEST_MIXED_TYPE"); topicList.add("TEST_MIXED_TYPE1"); Map attributes = new HashMap<>(); attributes.put("+message.type", "MIXED"); request = buildCreateTopicListRequest(topicList, attributes); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); // test allow MIXED topic type brokerController.getBrokerConfig().setEnableMixedMessageType(true); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testDeleteTopicInRocksdb() throws Exception { if (notToBeExecuted()) { return; } initRocksdbTopicManager(); testDeleteTopic(); } @Test public void testDeleteTopic() throws Exception { //test system topic for (String topic : systemTopicSet) { RemotingCommand request = buildDeleteTopicRequest(topic); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); assertThat(response.getRemark()).isEqualTo("The topic[" + topic + "] is conflict with system topic."); } String topic = "TEST_DELETE_TOPIC"; RemotingCommand request = buildDeleteTopicRequest(topic); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testDeleteWithPopRetryTopic() throws Exception { String topic = "topicA"; String anotherTopic = "another_topicA"; BrokerConfig brokerConfig = new BrokerConfig(); topicConfigManager = mock(TopicConfigManager.class); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); final ConcurrentHashMap topicConfigTable = new ConcurrentHashMap<>(); topicConfigTable.put(topic, new TopicConfig()); topicConfigTable.put(KeyBuilder.buildPopRetryTopic(topic, "cid1", brokerConfig.isEnableRetryTopicV2()), new TopicConfig()); topicConfigTable.put(anotherTopic, new TopicConfig()); topicConfigTable.put(KeyBuilder.buildPopRetryTopic(anotherTopic, "cid2", brokerConfig.isEnableRetryTopicV2()), new TopicConfig()); when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigTable); when(topicConfigManager.selectTopicConfig(anyString())).thenAnswer(invocation -> { final String selectTopic = invocation.getArgument(0); return topicConfigManager.getTopicConfigTable().get(selectTopic); }); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(consumerOffsetManager.whichGroupByTopic(topic)).thenReturn(Sets.newHashSet("cid1")); RemotingCommand request = buildDeleteTopicRequest(topic); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); verify(topicConfigManager).deleteTopicConfig(topic); verify(topicConfigManager).deleteTopicConfig(KeyBuilder.buildPopRetryTopic(topic, "cid1", brokerConfig.isEnableRetryTopicV2())); verify(messageStore, times(2)).deleteTopics(anySet()); } @Test public void testGetAllTopicConfigInRocksdb() throws Exception { if (notToBeExecuted()) { return; } initRocksdbTopicManager(); testGetAllTopicConfig(); } @Test public void testGetAllTopicConfig() throws Exception { GetAllTopicConfigRequestHeader getAllTopicConfigResponseHeader = new GetAllTopicConfigRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, getAllTopicConfigResponseHeader); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } private void getAllTopicConfig(boolean enableSplitMetadata) throws RemotingCommandException { brokerController.getBrokerConfig().setEnableSplitMetadata(enableSplitMetadata); // old client, request null RemotingCommand requestOldClient = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, null); RemotingCommand responseOldClient = adminBrokerProcessor.processRequest(handlerContext, requestOldClient); assertThat(responseOldClient.getCode()).isEqualTo(ResponseCode.SUCCESS); TopicConfigSerializeWrapper topicConfigSerializeWrapperOldClient = TopicConfigSerializeWrapper.decode(responseOldClient.getBody(), TopicConfigSerializeWrapper.class); assertThat(Maps.difference(topicConfigSerializeWrapperOldClient.getTopicConfigTable(), brokerController.getTopicConfigManager().getTopicConfigTable()).areEqual()).isTrue(); // new client, request seq from 0 AtomicBoolean dataVersionChanged = new AtomicBoolean(false); int topicSeq = 0; DataVersion dataVersion = null; int pageSize = ThreadLocalRandom.current().nextInt(500, brokerController.getBrokerConfig().getSplitMetadataSize()); ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); while (true) { GetAllTopicConfigRequestHeader requestHeader = new GetAllTopicConfigRequestHeader(); requestHeader.setTopicSeq(topicSeq); requestHeader.setMaxTopicNum(pageSize); requestHeader.setDataVersion(Optional.ofNullable(dataVersion).map(DataVersion::toJson).orElse(StringUtils.EMPTY)); RemotingCommand requestNewClient = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, requestHeader); requestNewClient.makeCustomHeaderToNet(); RemotingCommand responseNewClient = adminBrokerProcessor.processRequest(handlerContext, requestNewClient); GetAllTopicConfigResponseHeader responseHeader = (GetAllTopicConfigResponseHeader) responseNewClient.readCustomHeader(); assertThat(responseNewClient.getCode()).isEqualTo(ResponseCode.SUCCESS); TopicConfigSerializeWrapper topicConfigSerializeWrapper = TopicConfigSerializeWrapper.decode(responseNewClient.getBody(), TopicConfigSerializeWrapper.class); topicSeq += topicConfigSerializeWrapper.getTopicConfigTable().size(); assertThat(responseHeader.getTotalTopicNum()) .isEqualTo(brokerController.getTopicConfigManager().getTopicConfigTable().size()); assertThat(topicConfigSerializeWrapper.getDataVersion()) .isEqualTo(brokerController.getTopicConfigManager().getDataVersion()); DataVersion newDataVersion = topicConfigSerializeWrapper.getDataVersion(); if (dataVersion == null) { dataVersion = newDataVersion; } // mock server side data version changed if (topicSeq > responseHeader.getTotalTopicNum() / 2 && dataVersionChanged.compareAndSet(false, true)) { brokerController.getTopicConfigManager().getDataVersion().nextVersion(); } if (!Objects.equals(dataVersion, newDataVersion)) { dataVersion = newDataVersion; topicSeq = 0; // data version diff, from 0 topicConfigTable.clear(); continue; } topicConfigTable.putAll(topicConfigSerializeWrapper.getTopicConfigTable()); if (topicSeq >= responseHeader.getTotalTopicNum() - 1) { break; } else { assertThat(topicConfigSerializeWrapper.getTopicConfigTable().size()).isEqualTo(pageSize); } } assertThat(Maps.difference(topicConfigTable, brokerController.getTopicConfigManager().getTopicConfigTable()).areEqual()).isTrue(); } @Test public void testGetAllTopicConfigWithRequestHeader() throws RemotingCommandException { // from [0, 50000) fillTopicConfigTable(50000); getAllTopicConfig(true); getAllTopicConfig(false); // broker side disable split , will return all topic config } private void getAllSubscriptionGroup(boolean enableSplitMetadata) throws RemotingCommandException { brokerController.getBrokerConfig().setEnableSplitMetadata(enableSplitMetadata); // old client, request null RemotingCommand requestOldClient = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, null); RemotingCommand responseOldClient = adminBrokerProcessor.processRequest(handlerContext, requestOldClient); assertThat(responseOldClient.getCode()).isEqualTo(ResponseCode.SUCCESS); // new client, request from 0 AtomicBoolean dataVersionChanged = new AtomicBoolean(false); int groupSeq = 0; int pageSize = ThreadLocalRandom.current().nextInt(500, brokerController.getBrokerConfig().getSplitMetadataSize()); DataVersion dataVersion = null; ConcurrentMap subscriptionGroupTable = new ConcurrentHashMap<>(); ConcurrentMap> forbiddenTable = new ConcurrentHashMap<>(); while (true) { GetAllSubscriptionGroupRequestHeader requestHeader = new GetAllSubscriptionGroupRequestHeader(); requestHeader.setGroupSeq(groupSeq); requestHeader.setMaxGroupNum(pageSize); requestHeader.setDataVersion(Optional.ofNullable(dataVersion).map(DataVersion::toJson).orElse(StringUtils.EMPTY)); RemotingCommand requestNewClient = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, requestHeader); requestNewClient.makeCustomHeaderToNet(); RemotingCommand responseNewClient = adminBrokerProcessor.processRequest(handlerContext, requestNewClient); GetAllSubscriptionGroupResponseHeader responseHeader = (GetAllSubscriptionGroupResponseHeader) responseNewClient.readCustomHeader(); assertThat(responseNewClient.getCode()).isEqualTo(ResponseCode.SUCCESS); SubscriptionGroupWrapper subscriptionGroupWrapper = SubscriptionGroupWrapper.decode(responseNewClient.getBody(), SubscriptionGroupWrapper.class); groupSeq += subscriptionGroupWrapper.getSubscriptionGroupTable().size(); DataVersion newDataVersion = subscriptionGroupWrapper.getDataVersion(); assertThat(responseHeader.getTotalGroupNum()).isEqualTo( brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().size()); assertThat(newDataVersion).isEqualTo(brokerController.getSubscriptionGroupManager().getDataVersion()); if (dataVersion == null) { dataVersion = newDataVersion; } // mock server side data version changed if (groupSeq > responseHeader.getTotalGroupNum() / 2 && dataVersionChanged.compareAndSet(false, true)) { brokerController.getSubscriptionGroupManager().getDataVersion().nextVersion(); } if (!Objects.equals(dataVersion, newDataVersion)) { dataVersion = newDataVersion; groupSeq = 0; // data version diff, from 0 subscriptionGroupTable.clear(); forbiddenTable.clear(); continue; } subscriptionGroupTable.putAll(subscriptionGroupWrapper.getSubscriptionGroupTable()); forbiddenTable.putAll(subscriptionGroupWrapper.getForbiddenTable()); if (groupSeq >= responseHeader.getTotalGroupNum() - 1) { break; } else { assertThat(subscriptionGroupWrapper.getSubscriptionGroupTable().size()).isEqualTo(pageSize); } } assertThat(Maps.difference(subscriptionGroupTable, brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable()).areEqual()).isTrue(); assertThat(Maps.difference(forbiddenTable, brokerController.getSubscriptionGroupManager().getForbiddenTable()).areEqual()).isTrue(); } @Test public void testGetAllSubscriptionGroupWithRequestHeader() throws RemotingCommandException { fillSubscriptionGroupManager(50000); getAllSubscriptionGroup(true); getAllSubscriptionGroup(false); } @Test public void testUpdateBrokerConfig() throws Exception { handlerContext = mock(ChannelHandlerContext.class); channel = mock(Channel.class); when(handlerContext.channel()).thenReturn(channel); socketAddress = mock(SocketAddress.class); when(channel.remoteAddress()).thenReturn(socketAddress); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_BROKER_CONFIG, null); Map bodyMap = new HashMap<>(); bodyMap.put("key", "value"); request.setBody(bodyMap.toString().getBytes()); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetBrokerConfig() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CONFIG, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_UpdateConfigPath() throws RemotingCommandException { final RemotingCommand updateConfigRequest = RemotingCommand.createRequestCommand(RequestCode.UPDATE_BROKER_CONFIG, null); Properties properties = new Properties(); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); // Update allowed value properties.setProperty("allAckInSyncStateSet", "true"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); RemotingCommand response = adminBrokerProcessor.processRequest(ctx, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); //update disallowed value properties.clear(); properties.setProperty("brokerConfigPath", "test/path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = adminBrokerProcessor.processRequest(ctx, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list."); //update disallowed value properties.clear(); properties.setProperty("configBlackList", "test;path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = adminBrokerProcessor.processRequest(ctx, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list."); } @Test public void testSearchOffsetByTimestamp() throws Exception { messageStore = mock(MessageStore.class); when(messageStore.getOffsetInQueueByTime(anyString(), anyInt(), anyLong(), any(BoundaryType.class))).thenReturn(Long.MIN_VALUE); when(brokerController.getMessageStore()).thenReturn(messageStore); SearchOffsetRequestHeader searchOffsetRequestHeader = new SearchOffsetRequestHeader(); searchOffsetRequestHeader.setTopic("topic"); searchOffsetRequestHeader.setQueueId(0); searchOffsetRequestHeader.setTimestamp(System.currentTimeMillis()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, searchOffsetRequestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testSearchOffsetByTimestampWithLiteTopic() throws Exception { // Prepare test data String topic = "testTopic"; String liteTopic = "liteTestTopic"; long timestamp = System.currentTimeMillis(); long mockOffset = 100L; long mockMaxOffset = 500L; MessageStore messageStore = mock(MessageStore.class); LiteLifecycleManager liteLifecycleManager = mock(LiteLifecycleManager.class); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getLiteLifecycleManager()).thenReturn(liteLifecycleManager); when(liteLifecycleManager.getMaxOffsetInQueue(anyString())).thenReturn(mockMaxOffset); when(messageStore.getOffsetInQueueByTime(anyString(), anyInt(), anyLong(), any(BoundaryType.class))) .thenReturn(mockOffset); SearchOffsetRequestHeader requestHeader = new SearchOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setTimestamp(timestamp); requestHeader.setLiteTopic(liteTopic); requestHeader.setBoundaryType(BoundaryType.LOWER); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.readCustomHeader()).isInstanceOf(SearchOffsetResponseHeader.class); SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); assertThat(responseHeader.getOffset()).isEqualTo(mockOffset); // Verify that the LMQ conversion logic is correctly invoked // When maxOffset > 0, the offset query operation should be executed String expectedLmqTopic = LiteUtil.toLmqName(topic, liteTopic); verify(liteLifecycleManager).getMaxOffsetInQueue(expectedLmqTopic); verify(messageStore).getOffsetInQueueByTime(eq(expectedLmqTopic), eq(0), anyLong(), any(BoundaryType.class)); // Verify that queueId is correctly set to 0 (LMQ characteristic) verify(messageStore).getOffsetInQueueByTime(anyString(), eq(0), anyLong(), any(BoundaryType.class)); } @Test public void testGetMaxOffset() throws Exception { messageStore = mock(MessageStore.class); when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(Long.MIN_VALUE); when(brokerController.getMessageStore()).thenReturn(messageStore); GetMaxOffsetRequestHeader getMaxOffsetRequestHeader = new GetMaxOffsetRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MAX_OFFSET, getMaxOffsetRequestHeader); request.addExtField("topic", "topic"); request.addExtField("queueId", "0"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetMinOffset() throws Exception { messageStore = mock(MessageStore.class); when(messageStore.getMinOffsetInQueue(anyString(), anyInt())).thenReturn(Long.MIN_VALUE); when(brokerController.getMessageStore()).thenReturn(messageStore); GetMinOffsetRequestHeader getMinOffsetRequestHeader = new GetMinOffsetRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MIN_OFFSET, getMinOffsetRequestHeader); request.addExtField("topic", "topic"); request.addExtField("queueId", "0"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetEarliestMsgStoretime() throws Exception { messageStore = mock(MessageStore.class); when(brokerController.getMessageStore()).thenReturn(messageStore); GetEarliestMsgStoretimeRequestHeader getEarliestMsgStoretimeRequestHeader = new GetEarliestMsgStoretimeRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_EARLIEST_MSG_STORETIME, getEarliestMsgStoretimeRequestHeader); request.addExtField("topic", "topic"); request.addExtField("queueId", "0"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetBrokerRuntimeInfo() throws Exception { brokerStats = mock(BrokerStats.class); when(brokerController.getBrokerStats()).thenReturn(brokerStats); when(brokerStats.getMsgPutTotalYesterdayMorning()).thenReturn(Long.MIN_VALUE); when(brokerStats.getMsgPutTotalTodayMorning()).thenReturn(Long.MIN_VALUE); when(brokerStats.getMsgPutTotalTodayNow()).thenReturn(Long.MIN_VALUE); when(brokerStats.getMsgGetTotalTodayMorning()).thenReturn(Long.MIN_VALUE); when(brokerStats.getMsgGetTotalTodayNow()).thenReturn(Long.MIN_VALUE); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_RUNTIME_INFO, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testLockBatchMQ() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, null); LockBatchRequestBody lockBatchRequestBody = new LockBatchRequestBody(); lockBatchRequestBody.setClientId("1111"); lockBatchRequestBody.setConsumerGroup("group"); request.setBody(JSON.toJSON(lockBatchRequestBody).toString().getBytes()); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUnlockBatchMQ() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNLOCK_BATCH_MQ, null); UnlockBatchRequestBody unlockBatchRequestBody = new UnlockBatchRequestBody(); unlockBatchRequestBody.setClientId("11111"); unlockBatchRequestBody.setConsumerGroup("group"); request.setBody(JSON.toJSON(unlockBatchRequestBody).toString().getBytes()); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUpdateAndCreateSubscriptionGroupInRocksdb() throws Exception { initRocksdbSubscriptionManager(); testUpdateAndCreateSubscriptionGroup(); } @Test public void testUpdateAndCreateSubscriptionGroup() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setBrokerId(1); subscriptionGroupConfig.setGroupName("groupId"); subscriptionGroupConfig.setConsumeEnable(Boolean.TRUE); subscriptionGroupConfig.setConsumeBroadcastEnable(Boolean.TRUE); subscriptionGroupConfig.setRetryMaxTimes(111); subscriptionGroupConfig.setConsumeFromMinEnable(Boolean.TRUE); request.setBody(JSON.toJSON(subscriptionGroupConfig).toString().getBytes()); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetAllSubscriptionGroupInRocksdb() throws Exception { initRocksdbSubscriptionManager(); testGetAllSubscriptionGroup(); } @Test public void testGetAllSubscriptionGroup() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testDeleteSubscriptionGroupInRocksdb() throws Exception { initRocksdbSubscriptionManager(); testDeleteSubscriptionGroup(); } @Test public void testDeleteSubscriptionGroup() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_SUBSCRIPTIONGROUP, null); request.addExtField("groupName", "GID-Group-Name"); request.addExtField("removeOffset", "true"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetTopicStatsInfo() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_STATS_INFO, null); request.addExtField("topic", "topicTest"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); topicConfigManager = mock(TopicConfigManager.class); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("topicTest"); when(topicConfigManager.selectTopicConfig(anyString())).thenReturn(topicConfig); RemotingCommand responseSuccess = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(responseSuccess.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetConsumerConnectionList() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_CONNECTION_LIST, null); request.addExtField("consumerGroup", "GID-group-test"); consumerManager = mock(ConsumerManager.class); when(brokerController.getConsumerManager()).thenReturn(consumerManager); ConsumerGroupInfo consumerGroupInfo = new ConsumerGroupInfo("GID-group-test", ConsumeType.CONSUME_ACTIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); when(consumerManager.getConsumerGroupInfo(anyString())).thenReturn(consumerGroupInfo); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetProducerConnectionList() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_PRODUCER_CONNECTION_LIST, null); request.addExtField("producerGroup", "ProducerGroupId"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testGetAllProducerInfo() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_PRODUCER_INFO, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetConsumeStats() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUME_STATS, null); request.addExtField("topic", "topicTest"); request.addExtField("consumerGroup", "GID-test"); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetAllConsumerOffset() throws RemotingCommandException { consumerOffsetManager = mock(ConsumerOffsetManager.class); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); ConsumerOffsetManager consumerOffset = new ConsumerOffsetManager(); when(consumerOffsetManager.encode()).thenReturn(JSON.toJSONString(consumerOffset)); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_CONSUMER_OFFSET, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetAllDelayOffset() throws Exception { defaultMessageStore = mock(DefaultMessageStore.class); scheduleMessageService = mock(ScheduleMessageService.class); // when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); when(brokerController.getScheduleMessageService()).thenReturn(scheduleMessageService); when(scheduleMessageService.encode()).thenReturn("content"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_DELAY_OFFSET, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetTopicConfigInRocksdb() throws Exception { if (notToBeExecuted()) { return; } initRocksdbTopicManager(); testGetTopicConfig(); } @Test public void testGetTopicConfig() throws Exception { String topic = "foobar"; brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig(topic)); { GetTopicConfigRequestHeader requestHeader = new GetTopicConfigRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_CONFIG, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getBody()).isNotEmpty(); } { GetTopicConfigRequestHeader requestHeader = new GetTopicConfigRequestHeader(); requestHeader.setTopic("aaaaaaa"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_CONFIG, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); assertThat(response.getRemark()).contains("No topic in this broker."); } } @Test public void testCreateUser() throws RemotingCommandException { when(authenticationMetadataManager.createUser(any(User.class))) .thenReturn(CompletableFuture.completedFuture(null)); CreateUserRequestHeader createUserRequestHeader = new CreateUserRequestHeader(); createUserRequestHeader.setUsername("abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); UserInfo userInfo = UserInfo.of("abc", "123", UserType.NORMAL.getName()); request.setBody(JSON.toJSONBytes(userInfo)); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(true)); createUserRequestHeader = new CreateUserRequestHeader(); createUserRequestHeader.setUsername("super"); request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); userInfo = UserInfo.of("super", "123", UserType.SUPER.getName()); request.setBody(JSON.toJSONBytes(userInfo)); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(false)); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testUpdateUser() throws RemotingCommandException { when(authenticationMetadataManager.updateUser(any(User.class))) .thenReturn(CompletableFuture.completedFuture(null)); when(authenticationMetadataManager.getUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(User.of("abc", "123", UserType.NORMAL))); when(authenticationMetadataManager.getUser(eq("super"))).thenReturn(CompletableFuture.completedFuture(User.of("super", "123", UserType.SUPER))); UpdateUserRequestHeader updateUserRequestHeader = new UpdateUserRequestHeader(); updateUserRequestHeader.setUsername("abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_USER, updateUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); UserInfo userInfo = UserInfo.of("abc", "123", UserType.NORMAL.getName()); request.setBody(JSON.toJSONBytes(userInfo)); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(true)); updateUserRequestHeader = new UpdateUserRequestHeader(); updateUserRequestHeader.setUsername("super"); request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_USER, updateUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); userInfo = UserInfo.of("super", "123", UserType.SUPER.getName()); request.setBody(JSON.toJSONBytes(userInfo)); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(false)); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testDeleteUser() throws RemotingCommandException { when(authenticationMetadataManager.deleteUser(any(String.class))) .thenReturn(CompletableFuture.completedFuture(null)); when(authenticationMetadataManager.getUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(User.of("abc", "123", UserType.NORMAL))); when(authenticationMetadataManager.getUser(eq("super"))).thenReturn(CompletableFuture.completedFuture(User.of("super", "123", UserType.SUPER))); DeleteUserRequestHeader deleteUserRequestHeader = new DeleteUserRequestHeader(); deleteUserRequestHeader.setUsername("abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_USER, deleteUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(true)); deleteUserRequestHeader = new DeleteUserRequestHeader(); deleteUserRequestHeader.setUsername("super"); request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_USER, deleteUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(false)); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); } @Test public void testGetUser() throws RemotingCommandException { when(authenticationMetadataManager.getUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(User.of("abc", "123", UserType.NORMAL))); GetUserRequestHeader getUserRequestHeader = new GetUserRequestHeader(); getUserRequestHeader.setUsername("abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, getUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); UserInfo userInfo = JSON.parseObject(new String(response.getBody()), UserInfo.class); assertThat(userInfo.getUsername()).isEqualTo("abc"); assertThat(userInfo.getPassword()).isEqualTo("123"); assertThat(userInfo.getUserType()).isEqualTo("Normal"); } @Test public void testListUser() throws RemotingCommandException { when(authenticationMetadataManager.listUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(User.of("abc", "123", UserType.NORMAL)))); ListUsersRequestHeader listUserRequestHeader = new ListUsersRequestHeader(); listUserRequestHeader.setFilter("abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_USER, listUserRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); List userInfo = JSON.parseArray(new String(response.getBody()), UserInfo.class); assertThat(userInfo.get(0).getUsername()).isEqualTo("abc"); assertThat(userInfo.get(0).getPassword()).isEqualTo("123"); assertThat(userInfo.get(0).getUserType()).isEqualTo("Normal"); } @Test public void testCreateAcl() throws RemotingCommandException { when(authorizationMetadataManager.createAcl(any(Acl.class))) .thenReturn(CompletableFuture.completedFuture(null)); CreateAclRequestHeader createAclRequestHeader = new CreateAclRequestHeader(); createAclRequestHeader.setSubject("User:abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_ACL, createAclRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); AclInfo aclInfo = AclInfo.of("User:abc", Arrays.asList("Topic:*"), Arrays.asList("PUB"), Arrays.asList("192.168.0.1"), "Grant"); request.setBody(JSON.toJSONBytes(aclInfo)); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUpdateAcl() throws RemotingCommandException { when(authorizationMetadataManager.updateAcl(any(Acl.class))) .thenReturn(CompletableFuture.completedFuture(null)); UpdateAclRequestHeader updateAclRequestHeader = new UpdateAclRequestHeader(); updateAclRequestHeader.setSubject("User:abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_ACL, updateAclRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); AclInfo aclInfo = AclInfo.of("User:abc", Arrays.asList("Topic:*"), Arrays.asList("PUB"), Arrays.asList("192.168.0.1"), "Grant"); request.setBody(JSON.toJSONBytes(aclInfo)); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testDeleteAcl() throws RemotingCommandException { when(authorizationMetadataManager.deleteAcl(any(), any(), any())) .thenReturn(CompletableFuture.completedFuture(null)); DeleteAclRequestHeader deleteAclRequestHeader = new DeleteAclRequestHeader(); deleteAclRequestHeader.setSubject("User:abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_ACL, deleteAclRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetAcl() throws RemotingCommandException { Acl aclInfo = Acl.of(User.of("abc"), Arrays.asList(Resource.of("Topic:*")), Arrays.asList(Action.PUB), Environment.of("192.168.0.1"), Decision.ALLOW); when(authorizationMetadataManager.getAcl(any(Subject.class))).thenReturn(CompletableFuture.completedFuture(aclInfo)); GetAclRequestHeader getAclRequestHeader = new GetAclRequestHeader(); getAclRequestHeader.setSubject("User:abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_ACL, getAclRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); AclInfo aclInfoData = JSON.parseObject(new String(response.getBody()), AclInfo.class); assertThat(aclInfoData.getSubject()).isEqualTo("User:abc"); assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getResource()).isEqualTo("Topic:*"); assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getActions()).containsAll(Arrays.asList(Action.PUB.getName())); assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getSourceIps()).containsAll(Arrays.asList("192.168.0.1")); assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getDecision()).isEqualTo("Allow"); } @Test public void testListAcl() throws RemotingCommandException { Acl aclInfo = Acl.of(User.of("abc"), Arrays.asList(Resource.of("Topic:*")), Arrays.asList(Action.PUB), Environment.of("192.168.0.1"), Decision.ALLOW); when(authorizationMetadataManager.listAcl(any(), any())).thenReturn(CompletableFuture.completedFuture(Arrays.asList(aclInfo))); ListAclsRequestHeader listAclRequestHeader = new ListAclsRequestHeader(); listAclRequestHeader.setSubjectFilter("User:abc"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_ACL, listAclRequestHeader); request.setVersion(441); request.addExtField("AccessKey", "rocketmq"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); List aclInfoData = JSON.parseArray(new String(response.getBody()), AclInfo.class); assertThat(aclInfoData.get(0).getSubject()).isEqualTo("User:abc"); assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getResource()).isEqualTo("Topic:*"); assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getActions()).containsAll(Arrays.asList(Action.PUB.getName())); assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getSourceIps()).containsAll(Arrays.asList("192.168.0.1")); assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getDecision()).isEqualTo("Allow"); } @Test public void testGetTimeCheckPoint() throws RemotingCommandException { when(this.brokerController.getTimerCheckpoint()).thenReturn(null); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TIMER_CHECK_POINT, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); assertThat(response.getRemark()).isEqualTo("The checkpoint is null"); when(this.brokerController.getTimerCheckpoint()).thenReturn(new TimerCheckpoint()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetTimeMetrics() throws RemotingCommandException, IOException { when(this.brokerController.getMessageStore().getTimerMessageStore()).thenReturn(null); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TIMER_METRICS, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); when(this.brokerController.getMessageStore().getTimerMessageStore()).thenReturn(timerMessageStore); when(this.timerMessageStore.getTimerMetrics()).thenReturn(timerMetrics); when(this.timerMetrics.encode()).thenReturn(new TimerMetrics.TimerMetricsSerializeWrapper().toJson(false)); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUpdateColdDataFlowCtrGroupConfig() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_COLD_DATA_FLOW_CTR_CONFIG, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); request.setBody("consumerGroup1=1".getBytes()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); request.setBody("".getBytes()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testRemoveColdDataFlowCtrGroupConfig() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REMOVE_COLD_DATA_FLOW_CTR_CONFIG, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); request.setBody("consumerGroup1".getBytes()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetColdDataFlowCtrInfo() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_COLD_DATA_FLOW_CTR_INFO, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testSetCommitLogReadAheadMode() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SET_COMMITLOG_READ_MODE, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); HashMap extfields = new HashMap<>(); extfields.put(FIleReadaheadMode.READ_AHEAD_MODE, String.valueOf(LibC.MADV_DONTNEED)); request.setExtFields(extfields); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); extfields.clear(); extfields.put(FIleReadaheadMode.READ_AHEAD_MODE, String.valueOf(LibC.MADV_NORMAL)); request.setExtFields(extfields); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); this.brokerController.setMessageStore(defaultMessageStore); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); when(this.defaultMessageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(this.defaultMessageStore.getCommitLog()).thenReturn(commitLog); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetUnknownCmdResponse() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(10000, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.REQUEST_CODE_NOT_SUPPORTED); } @Test public void testGetAllMessageRequestMode() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_MESSAGE_REQUEST_MODE, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testResetOffset() throws RemotingCommandException { ResetOffsetRequestHeader requestHeader = createRequestHeader("topic","group",-1,false,-1,-1); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); this.brokerController.getTopicConfigManager().getTopicConfigTable().put("topic", new TopicConfig("topic")); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); this.brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().put("group", new SubscriptionGroupConfig()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); requestHeader.setQueueId(0); request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); request.makeCustomHeaderToNet(); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); requestHeader.setOffset(2L); request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); request.makeCustomHeaderToNet(); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testGetConsumerStatus() throws RemotingCommandException { GetConsumerStatusRequestHeader requestHeader = new GetConsumerStatusRequestHeader(); requestHeader.setGroup("group"); requestHeader.setTopic("topic"); requestHeader.setClientAddr(""); RemotingCommand request = RemotingCommand .createRequestCommand(RequestCode.INVOKE_BROKER_TO_GET_CONSUMER_STATUS, requestHeader); RemotingCommand responseCommand = RemotingCommand.createResponseCommand(null); responseCommand.setCode(ResponseCode.SUCCESS); when(broker2Client.getConsumeStatus(anyString(),anyString(),anyString())).thenReturn(responseCommand); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testQueryTopicConsumeByWho() throws RemotingCommandException { QueryTopicConsumeByWhoRequestHeader requestHeader = new QueryTopicConsumeByWhoRequestHeader(); requestHeader.setTopic("topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_TOPIC_CONSUME_BY_WHO, requestHeader); request.makeCustomHeaderToNet(); HashSet groups = new HashSet<>(); groups.add("group"); when(brokerController.getConsumerManager()).thenReturn(consumerManager); when(consumerManager.queryTopicConsumeByWho(anyString())).thenReturn(groups); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(RemotingSerializable.decode(response.getBody(), GroupList.class) .getGroupList().contains("group")) .isEqualTo(groups.contains("group")); } @Test public void testQueryTopicByConsumer() throws RemotingCommandException { QueryTopicsByConsumerRequestHeader requestHeader = new QueryTopicsByConsumerRequestHeader(); requestHeader.setGroup("group"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_TOPICS_BY_CONSUMER, requestHeader); request.makeCustomHeaderToNet(); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testQuerySubscriptionByConsumer() throws RemotingCommandException { QuerySubscriptionByConsumerRequestHeader requestHeader = new QuerySubscriptionByConsumerRequestHeader(); requestHeader.setGroup("group"); requestHeader.setTopic("topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_SUBSCRIPTION_BY_CONSUMER, requestHeader); request.makeCustomHeaderToNet(); when(brokerController.getConsumerManager()).thenReturn(consumerManager); when(consumerManager.findSubscriptionData(anyString(),anyString())).thenReturn(null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetSystemTopicListFromBroker() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_BROKER, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testCleanExpiredConsumeQueue() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CLEAN_EXPIRED_CONSUMEQUEUE, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testDeleteExpiredCommitLog() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_EXPIRED_COMMITLOG, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testCleanUnusedTopic() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CLEAN_UNUSED_TOPIC, null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetConsumerRunningInfo() throws RemotingCommandException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { when(brokerController.getConsumerManager()).thenReturn(consumerManager); when(consumerManager.findChannel(anyString(),anyString())).thenReturn(null); GetConsumerRunningInfoRequestHeader requestHeader = new GetConsumerRunningInfoRequestHeader(); requestHeader.setClientId("client"); requestHeader.setConsumerGroup("group"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_RUNNING_INFO, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); when(consumerManager.findChannel(anyString(),anyString())).thenReturn(clientChannelInfo); when(clientChannelInfo.getVersion()).thenReturn(MQVersion.Version.V3_0_0_SNAPSHOT.ordinal()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); when(clientChannelInfo.getVersion()).thenReturn(MQVersion.Version.V5_2_3.ordinal()); when(brokerController.getBroker2Client()).thenReturn(broker2Client); when(clientChannelInfo.getChannel()).thenReturn(channel); RemotingCommand responseCommand = RemotingCommand.createResponseCommand(null); responseCommand.setCode(ResponseCode.SUCCESS); when(broker2Client.callClient(any(Channel.class),any(RemotingCommand.class))).thenReturn(responseCommand); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(broker2Client.callClient(any(Channel.class),any(RemotingCommand.class))).thenThrow(new RemotingTimeoutException("timeout")); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.CONSUME_MSG_TIMEOUT); } @Test public void testQueryCorrectionOffset() throws RemotingCommandException { Map correctionOffsetMap = new HashMap<>(); correctionOffsetMap.put(0, 100L); correctionOffsetMap.put(1, 200L); Map compareOffsetMap = new HashMap<>(); compareOffsetMap.put(0, 80L); compareOffsetMap.put(1, 300L); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(consumerOffsetManager.queryMinOffsetInAllGroup(anyString(),anyString())).thenReturn(correctionOffsetMap); when(consumerOffsetManager.queryOffset(anyString(),anyString())).thenReturn(compareOffsetMap); QueryCorrectionOffsetHeader queryCorrectionOffsetHeader = new QueryCorrectionOffsetHeader(); queryCorrectionOffsetHeader.setTopic("topic"); queryCorrectionOffsetHeader.setCompareGroup("group"); queryCorrectionOffsetHeader.setFilterGroups(""); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CORRECTION_OFFSET, queryCorrectionOffsetHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); QueryCorrectionOffsetBody body = RemotingSerializable.decode(response.getBody(), QueryCorrectionOffsetBody.class); Map correctionOffsets = body.getCorrectionOffsets(); assertThat(correctionOffsets.get(0)).isEqualTo(Long.MAX_VALUE); assertThat(correctionOffsets.get(1)).isEqualTo(200L); } @Test public void testNotifyMinBrokerIdChange() throws RemotingCommandException { NotifyMinBrokerIdChangeRequestHeader requestHeader = new NotifyMinBrokerIdChangeRequestHeader(); requestHeader.setMinBrokerId(1L); requestHeader.setMinBrokerAddr("127.0.0.1:10912"); requestHeader.setOfflineBrokerAddr("127.0.0.1:10911"); requestHeader.setHaBrokerAddr(""); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFY_MIN_BROKER_ID_CHANGE, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUpdateBrokerHaInfo() throws RemotingCommandException { ExchangeHAInfoResponseHeader requestHeader = new ExchangeHAInfoResponseHeader(); requestHeader.setMasterAddress("127.0.0.1:10911"); requestHeader.setMasterFlushOffset(0L); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.EXCHANGE_BROKER_HA_INFO, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(brokerController.getMessageStore()).thenReturn(messageStore); requestHeader.setMasterHaAddress("127.0.0.1:10912"); request = RemotingCommand.createRequestCommand(RequestCode.EXCHANGE_BROKER_HA_INFO, requestHeader); request.makeCustomHeaderToNet(); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(messageStore.getMasterFlushedOffset()).thenReturn(0L); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetBrokerHaStatus() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_HA_STATUS,null); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); when(brokerController.getMessageStore()).thenReturn(messageStore); when(messageStore.getHARuntimeInfo()).thenReturn(new HARuntimeInfo()); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testResetMasterFlushOffset() throws RemotingCommandException { ResetMasterFlushOffsetHeader requestHeader = new ResetMasterFlushOffsetHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESET_MASTER_FLUSH_OFFSET,requestHeader); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); requestHeader.setMasterFlushOffset(0L); request.makeCustomHeaderToNet(); response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetSubscriptionGroup() throws RemotingCommandException { brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().put("group", new SubscriptionGroupConfig()); GetSubscriptionGroupConfigRequestHeader requestHeader = new GetSubscriptionGroupConfigRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_SUBSCRIPTIONGROUP_CONFIG, requestHeader); requestHeader.setGroup("group"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testCheckRocksdbCqWriteProgress() throws RemotingCommandException { CheckRocksdbCqWriteProgressRequestHeader requestHeader = new CheckRocksdbCqWriteProgressRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHECK_ROCKSDB_CQ_WRITE_PROGRESS, requestHeader); requestHeader.setTopic("topic"); request.makeCustomHeaderToNet(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testQueryConsumeQueue() throws RemotingCommandException { messageStore = mock(MessageStore.class); ConsumeQueueInterface consumeQueue = mock(ConsumeQueueInterface.class); when(consumeQueue.getMinOffsetInQueue()).thenReturn(0L); when(consumeQueue.getMaxOffsetInQueue()).thenReturn(1L); when(messageStore.getConsumeQueue(anyString(), anyInt())).thenReturn(consumeQueue); when(brokerController.getMessageStore()).thenReturn(messageStore); QueryConsumeQueueRequestHeader requestHeader = new QueryConsumeQueueRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUME_QUEUE, requestHeader); requestHeader.setTopic("topic"); requestHeader.setQueueId(0); requestHeader.setConsumerGroup("testGroup"); request.makeCustomHeaderToNet(); SubscriptionData subscriptionData = mock(SubscriptionData.class); when(brokerController.getConsumerManager()).thenReturn(consumerManager); when(consumerManager.findSubscriptionData(any(), any())).thenReturn(subscriptionData); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testProcessRequest_GetTopicConfig() throws Exception { GetTopicConfigRequestHeader requestHeader = new GetTopicConfigRequestHeader(); requestHeader.setTopic("testTopic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_CONFIG, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("testTopic"); TopicConfigManager topicConfigManager = mock(TopicConfigManager.class); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(topicConfigManager.selectTopicConfig("testTopic")) .thenReturn(topicConfig); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); String responseBody = new String(response.getBody(), StandardCharsets.UTF_8); TopicConfigAndQueueMapping result = JSONObject.parseObject(responseBody, TopicConfigAndQueueMapping.class); assertEquals("testTopic", result.getTopicName()); } private ResetOffsetRequestHeader createRequestHeader(String topic,String group,long timestamp,boolean force,long offset,int queueId) { ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); requestHeader.setTimestamp(timestamp); requestHeader.setForce(force); requestHeader.setOffset(offset); requestHeader.setQueueId(queueId); return requestHeader; } private RemotingCommand buildCreateTopicRequest(String topic) { return buildCreateTopicRequest(topic, null); } private RemotingCommand buildCreateTopicRequest(String topic, Map attributes) { CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader(); requestHeader.setTopic(topic); requestHeader.setTopicFilterType(TopicFilterType.SINGLE_TAG.name()); requestHeader.setReadQueueNums(8); requestHeader.setWriteQueueNums(8); requestHeader.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); if (attributes != null) { requestHeader.setAttributes(AttributeParser.parseToString(attributes)); } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, requestHeader); request.makeCustomHeaderToNet(); return request; } private RemotingCommand buildCreateTopicListRequest(List topicList) { return buildCreateTopicListRequest(topicList, null); } private RemotingCommand buildCreateTopicListRequest(List topicList, Map attributes) { List topicConfigList = new ArrayList<>(); for (String topic:topicList) { TopicConfig topicConfig = new TopicConfig(topic); topicConfig.setReadQueueNums(8); topicConfig.setWriteQueueNums(8); topicConfig.setTopicFilterType(TopicFilterType.SINGLE_TAG); topicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); topicConfig.setTopicSysFlag(0); topicConfig.setOrder(false); if (attributes != null) { topicConfig.setAttributes(new HashMap<>(attributes)); } topicConfigList.add(topicConfig); } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC_LIST, null); CreateTopicListRequestBody createTopicListRequestBody = new CreateTopicListRequestBody(topicConfigList); request.setBody(createTopicListRequestBody.encode()); return request; } private RemotingCommand buildDeleteTopicRequest(String topic) { DeleteTopicRequestHeader requestHeader = new DeleteTopicRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_TOPIC_IN_BROKER, requestHeader); request.makeCustomHeaderToNet(); return request; } private MessageExt createDefaultMessageExt() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("12345678"); messageExt.setQueueId(0); messageExt.setCommitLogOffset(123456789L); messageExt.setQueueOffset(1234); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_QUEUE_ID, "0"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_TOPIC, "testTopic"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, "15"); return messageExt; } private SelectMappedBufferResult createSelectMappedBufferResult() { SelectMappedBufferResult result = new SelectMappedBufferResult(0, ByteBuffer.allocate(1024), 0, new DefaultMappedFile()); return result; } private ResumeCheckHalfMessageRequestHeader createResumeCheckHalfMessageRequestHeader() { ResumeCheckHalfMessageRequestHeader header = new ResumeCheckHalfMessageRequestHeader(); header.setTopic("topic"); header.setMsgId("C0A803CA00002A9F0000000000031367"); return header; } private RemotingCommand createResumeCheckHalfMessageCommand() { ResumeCheckHalfMessageRequestHeader header = createResumeCheckHalfMessageRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESUME_CHECK_HALF_MESSAGE, header); request.makeCustomHeaderToNet(); return request; } private RemotingCommand createUpdateBrokerConfigCommand() { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_BROKER_CONFIG, null); request.makeCustomHeaderToNet(); return request; } private boolean notToBeExecuted() { return MixAll.isMac(); } private void fillTopicConfigTable(int num) { for (int i = num - 1; i >= 0; i--) { String topicName = String.format("topic%05d", i); TopicConfig topicConfig = new TopicConfig(topicName, 1, 1, PermName.PERM_READ | PermName.PERM_WRITE, 0); brokerController.getTopicConfigManager().getTopicConfigTable().put(topicName, topicConfig); } } private void fillSubscriptionGroupManager(int num) { for (int i = num - 1; i >= 0; i--) { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); String groupName = String.format("group-%05d", i); subscriptionGroupConfig.setGroupName(groupName); Map attr = ImmutableMap.of("+test", "true"); subscriptionGroupConfig.setAttributes(attr); brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().put(groupName, subscriptionGroupConfig); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.net.Broker2Client; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.metrics.PopMetricsManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import static org.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ChangeInvisibleTimeProcessorTest { private ChangeInvisibleTimeProcessor changeInvisibleTimeProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; @Mock private DefaultMessageStore messageStore; @Mock private Channel channel; private String topic = "FooBar"; private String group = "FooBarGroup"; private ClientChannelInfo clientInfo; @Mock private Broker2Client broker2Client; @Mock private EscapeBridge escapeBridge; @Before public void init() throws IllegalAccessException, NoSuchFieldException { // Inject BrokerMetricsManager if missing Field brokerMetricsManagerField = BrokerController.class.getDeclaredField("brokerMetricsManager"); brokerMetricsManagerField.setAccessible(true); if (brokerMetricsManagerField.get(brokerController) == null) { BrokerMetricsManager brokerMetricsManager = new BrokerMetricsManager(brokerController); brokerMetricsManagerField.set(brokerController, brokerMetricsManager); } // Mock necessary dependencies when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getEscapeBridge()).thenReturn(this.escapeBridge); Channel mockChannel = mock(Channel.class); when(handlerContext.channel()).thenReturn(mockChannel); // Mock TopicConfigManager TopicConfigManager topicConfigManager = mock(TopicConfigManager.class); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); ConcurrentHashMap topicConfigTable = new ConcurrentHashMap<>(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfigTable.put(topic, topicConfig); when(topicConfigManager.selectTopicConfig(topic)).thenReturn(topicConfig); // Mock BrokerStatsManager BrokerStatsManager brokerStatsManager = mock(BrokerStatsManager.class); when(brokerController.getBrokerStatsManager()).thenReturn(brokerStatsManager); // Mock PopMessageProcessor and PopBufferMergeService PopMessageProcessor popMessageProcessor = mock(PopMessageProcessor.class); PopBufferMergeService popBufferMergeService = mock(PopBufferMergeService.class); when(brokerController.getPopMessageProcessor()).thenReturn(popMessageProcessor); when(popMessageProcessor.getPopBufferMergeService()).thenReturn(popBufferMergeService); ConsumerData consumerData = createConsumerData(group, topic); clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), false); clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0); changeInvisibleTimeProcessor = new ChangeInvisibleTimeProcessor(brokerController); } @Test public void testProcessRequest_Success() throws RemotingCommandException, ConsumeQueueException { when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(2L); when(escapeBridge.asyncPutMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); int queueId = 0; long queueOffset = 0; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand responseToReturn = changeInvisibleTimeProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque()); } @Test public void testProcessRequest_NoMessage() throws RemotingCommandException, ConsumeQueueException { when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(2L); int queueId = 0; long queueOffset = 2; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand responseToReturn = changeInvisibleTimeProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.NO_MESSAGE); assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque()); } @Test public void testProcessRequestAsync_JsonParsing() throws Exception { Channel mockChannel = mock(Channel.class); RemotingCommand mockRequest = mock(RemotingCommand.class); BrokerController mockBrokerController = mock(BrokerController.class); TopicConfigManager mockTopicConfigManager = mock(TopicConfigManager.class); MessageStore mockMessageStore = mock(MessageStore.class); BrokerConfig mockBrokerConfig = mock(BrokerConfig.class); BrokerStatsManager mockBrokerStatsManager = mock(BrokerStatsManager.class); PopMessageProcessor mockPopMessageProcessor = mock(PopMessageProcessor.class); PopBufferMergeService mockPopBufferMergeService = mock(PopBufferMergeService.class); BrokerMetricsManager brokerMetricsManager = mock(BrokerMetricsManager.class); PopMetricsManager popMetricsManager = mock(PopMetricsManager.class); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); when(mockBrokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); doNothing().when(popMetricsManager).incPopReviveCkPutCount(any(), any()); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); when(mockBrokerController.getTopicConfigManager()).thenReturn(mockTopicConfigManager); when(mockBrokerController.getMessageStore()).thenReturn(mockMessageStore); when(mockBrokerController.getBrokerConfig()).thenReturn(mockBrokerConfig); when(mockBrokerController.getBrokerStatsManager()).thenReturn(mockBrokerStatsManager); when(mockBrokerController.getPopMessageProcessor()).thenReturn(mockPopMessageProcessor); when(mockPopMessageProcessor.getPopBufferMergeService()).thenReturn(mockPopBufferMergeService); when(mockPopBufferMergeService.addAk(anyInt(), any())).thenReturn(false); when(mockBrokerController.getEscapeBridge()).thenReturn(escapeBridge); PutMessageResult mockPutMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, null, true); when(mockBrokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(any())) .thenReturn(CompletableFuture.completedFuture(mockPutMessageResult)); TopicConfig topicConfig = new TopicConfig(); topicConfig.setReadQueueNums(4); when(mockTopicConfigManager.selectTopicConfig(anyString())).thenReturn(topicConfig); when(mockMessageStore.getMinOffsetInQueue(anyString(), anyInt())).thenReturn(0L); when(mockMessageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(10L); when(mockBrokerConfig.isPopConsumerKVServiceEnable()).thenReturn(false); ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic("TestTopic"); requestHeader.setQueueId(1); requestHeader.setOffset(5L); requestHeader.setConsumerGroup("TestGroup"); requestHeader.setExtraInfo("0 10000 10000 0 TestBroker 1"); requestHeader.setInvisibleTime(60000L); when(mockRequest.decodeCommandCustomHeader(ChangeInvisibleTimeRequestHeader.class)).thenReturn(requestHeader); ChangeInvisibleTimeProcessor processor = new ChangeInvisibleTimeProcessor(mockBrokerController); CompletableFuture futureResponse = processor.processRequestAsync(mockChannel, mockRequest, true); RemotingCommand response = futureResponse.get(); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testProcessRequestAsyncWithSuspendTrue() throws Exception { // Setup mocks Channel mockChannel = mock(Channel.class); RemotingCommand mockRequest = mock(RemotingCommand.class); BrokerController mockBrokerController = mock(BrokerController.class); TopicConfigManager mockTopicConfigManager = mock(TopicConfigManager.class); MessageStore mockMessageStore = mock(MessageStore.class); BrokerConfig mockBrokerConfig = mock(BrokerConfig.class); BrokerStatsManager mockBrokerStatsManager = mock(BrokerStatsManager.class); PopMessageProcessor mockPopMessageProcessor = mock(PopMessageProcessor.class); PopBufferMergeService mockPopBufferMergeService = mock(PopBufferMergeService.class); BrokerMetricsManager brokerMetricsManager = mock(BrokerMetricsManager.class); PopMetricsManager popMetricsManager = mock(PopMetricsManager.class); EscapeBridge mockEscapeBridge = mock(EscapeBridge.class); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); when(mockBrokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); doNothing().when(popMetricsManager).incPopReviveCkPutCount(any(), any()); when(mockBrokerController.getTopicConfigManager()).thenReturn(mockTopicConfigManager); when(mockBrokerController.getMessageStore()).thenReturn(mockMessageStore); when(mockBrokerController.getBrokerConfig()).thenReturn(mockBrokerConfig); when(mockBrokerController.getBrokerStatsManager()).thenReturn(mockBrokerStatsManager); when(mockBrokerController.getPopMessageProcessor()).thenReturn(mockPopMessageProcessor); when(mockPopMessageProcessor.getPopBufferMergeService()).thenReturn(mockPopBufferMergeService); when(mockPopBufferMergeService.addAk(anyInt(), any())).thenReturn(false); when(mockBrokerController.getEscapeBridge()).thenReturn(mockEscapeBridge); PutMessageResult mockPutMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, null, true); when(mockEscapeBridge.asyncPutMessageToSpecificQueue(any())) .thenReturn(CompletableFuture.completedFuture(mockPutMessageResult)); TopicConfig topicConfig = new TopicConfig(); topicConfig.setReadQueueNums(4); when(mockTopicConfigManager.selectTopicConfig(anyString())).thenReturn(topicConfig); when(mockMessageStore.getMinOffsetInQueue(anyString(), anyInt())).thenReturn(0L); when(mockMessageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(10L); when(mockBrokerConfig.isPopConsumerKVServiceEnable()).thenReturn(false); ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic("TestTopic"); requestHeader.setQueueId(1); requestHeader.setOffset(5L); requestHeader.setConsumerGroup("TestGroup"); requestHeader.setExtraInfo("0 10000 10000 0 TestBroker 1"); requestHeader.setInvisibleTime(60000L); requestHeader.setSuspend(true); // Test with suspend=true when(mockRequest.decodeCommandCustomHeader(ChangeInvisibleTimeRequestHeader.class)).thenReturn(requestHeader); ChangeInvisibleTimeProcessor processor = new ChangeInvisibleTimeProcessor(mockBrokerController); CompletableFuture futureResponse = processor.processRequestAsync(mockChannel, mockRequest, true); RemotingCommand response = futureResponse.get(); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testProcessRequestAsyncWithSuspendFalse() throws Exception { // Setup mocks Channel mockChannel = mock(Channel.class); RemotingCommand mockRequest = mock(RemotingCommand.class); BrokerController mockBrokerController = mock(BrokerController.class); TopicConfigManager mockTopicConfigManager = mock(TopicConfigManager.class); MessageStore mockMessageStore = mock(MessageStore.class); BrokerConfig mockBrokerConfig = mock(BrokerConfig.class); BrokerStatsManager mockBrokerStatsManager = mock(BrokerStatsManager.class); PopMessageProcessor mockPopMessageProcessor = mock(PopMessageProcessor.class); PopBufferMergeService mockPopBufferMergeService = mock(PopBufferMergeService.class); BrokerMetricsManager brokerMetricsManager = mock(BrokerMetricsManager.class); PopMetricsManager popMetricsManager = mock(PopMetricsManager.class); EscapeBridge mockEscapeBridge = mock(EscapeBridge.class); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); when(mockBrokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); doNothing().when(popMetricsManager).incPopReviveCkPutCount(any(), any()); when(mockBrokerController.getTopicConfigManager()).thenReturn(mockTopicConfigManager); when(mockBrokerController.getMessageStore()).thenReturn(mockMessageStore); when(mockBrokerController.getBrokerConfig()).thenReturn(mockBrokerConfig); when(mockBrokerController.getBrokerStatsManager()).thenReturn(mockBrokerStatsManager); when(mockBrokerController.getPopMessageProcessor()).thenReturn(mockPopMessageProcessor); when(mockPopMessageProcessor.getPopBufferMergeService()).thenReturn(mockPopBufferMergeService); when(mockPopBufferMergeService.addAk(anyInt(), any())).thenReturn(false); when(mockBrokerController.getEscapeBridge()).thenReturn(mockEscapeBridge); PutMessageResult mockPutMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, null, true); when(mockEscapeBridge.asyncPutMessageToSpecificQueue(any())) .thenReturn(CompletableFuture.completedFuture(mockPutMessageResult)); TopicConfig topicConfig = new TopicConfig(); topicConfig.setReadQueueNums(4); when(mockTopicConfigManager.selectTopicConfig(anyString())).thenReturn(topicConfig); when(mockMessageStore.getMinOffsetInQueue(anyString(), anyInt())).thenReturn(0L); when(mockMessageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(10L); when(mockBrokerConfig.isPopConsumerKVServiceEnable()).thenReturn(false); ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic("TestTopic"); requestHeader.setQueueId(1); requestHeader.setOffset(5L); requestHeader.setConsumerGroup("TestGroup"); requestHeader.setExtraInfo("0 10000 10000 0 TestBroker 1"); requestHeader.setInvisibleTime(60000L); requestHeader.setSuspend(false); // Test with suspend=false when(mockRequest.decodeCommandCustomHeader(ChangeInvisibleTimeRequestHeader.class)).thenReturn(requestHeader); ChangeInvisibleTimeProcessor processor = new ChangeInvisibleTimeProcessor(mockBrokerController); CompletableFuture futureResponse = processor.processRequestAsync(mockChannel, mockRequest, true); RemotingCommand response = futureResponse.get(); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testProcessRequestWithSuspendTrue() throws RemotingCommandException, ConsumeQueueException { when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(2L); when(escapeBridge.asyncPutMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); int queueId = 0; long queueOffset = 0; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setSuspend(true); // Set suspend to true final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand responseToReturn = changeInvisibleTimeProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque()); } @Test public void testProcessRequestWithSuspendFalse() throws RemotingCommandException, ConsumeQueueException { when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(2L); when(escapeBridge.asyncPutMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); int queueId = 0; long queueOffset = 0; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setSuspend(false); // Set suspend to false final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand responseToReturn = changeInvisibleTimeProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque()); } @Test public void testAppendCheckPointThenAckOriginWritesSuspendTrueInCheckpoint() throws Exception { when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(2L); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); when(escapeBridge.asyncPutMessageToSpecificQueue(msgCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); int queueId = 0; long queueOffset = 0; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setSuspend(true); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); request.makeCustomHeaderToNet(); changeInvisibleTimeProcessor.processRequest(handlerContext, request); List allValues = msgCaptor.getAllValues(); MessageExtBrokerInner ckMessage = allValues.stream() .filter(m -> PopAckConstants.CK_TAG.equals(m.getTags())) .findFirst() .orElseThrow(() -> new AssertionError("No CK message captured")); PopCheckPoint ck = JSON.parseObject(new String(ckMessage.getBody(), java.nio.charset.StandardCharsets.UTF_8), PopCheckPoint.class); assertThat(ck.isSuspend()).isTrue(); } @Test public void testAppendCheckPointThenAckOriginWritesSuspendFalseInCheckpoint() throws Exception { when(messageStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(2L); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); when(escapeBridge.asyncPutMessageToSpecificQueue(msgCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); int queueId = 0; long queueOffset = 0; long popTime = System.currentTimeMillis() - 1_000; long invisibleTime = 30_000; int reviveQid = 0; String brokerName = "test_broker"; String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset; ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(group); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setSuspend(false); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); request.makeCustomHeaderToNet(); changeInvisibleTimeProcessor.processRequest(handlerContext, request); List allValues = msgCaptor.getAllValues(); MessageExtBrokerInner ckMessage = allValues.stream() .filter(m -> PopAckConstants.CK_TAG.equals(m.getTags())) .findFirst() .orElseThrow(() -> new AssertionError("No CK message captured")); PopCheckPoint ck = JSON.parseObject(new String(ckMessage.getBody(), java.nio.charset.StandardCharsets.UTF_8), PopCheckPoint.class); assertThat(ck.isSuspend()).isFalse(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/ClientManageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.List; import java.util.ArrayList; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ClientManageProcessorTest { private ClientManageProcessor clientManageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; @Mock private Channel channel; private ClientChannelInfo clientChannelInfo; private String clientId = UUID.randomUUID().toString(); private String group = "FooBarGroup"; private String topic = "FooBar"; @Before public void init() { when(handlerContext.channel()).thenReturn(channel); clientManageProcessor = new ClientManageProcessor(brokerController); clientChannelInfo = new ClientChannelInfo(channel, clientId, LanguageCode.JAVA, 100); brokerController.getProducerManager().registerProducer(group, clientChannelInfo); ConsumerData consumerData = PullMessageProcessorTest.createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientChannelInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), false); } @Test public void processRequest_UnRegisterProducer() throws Exception { brokerController.getProducerManager().registerProducer(group, clientChannelInfo); Map channelMap = brokerController.getProducerManager().getGroupChannelTable().get(group); assertThat(channelMap).isNotNull(); assertThat(channelMap.get(channel)).isEqualTo(clientChannelInfo); RemotingCommand request = createUnRegisterProducerCommand(); RemotingCommand response = clientManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); channelMap = brokerController.getProducerManager().getGroupChannelTable().get(group); assertThat(channelMap).isNull(); } @Test public void processRequest_UnRegisterConsumer() throws RemotingCommandException { ConsumerGroupInfo consumerGroupInfo = brokerController.getConsumerManager().getConsumerGroupInfo(group); assertThat(consumerGroupInfo).isNotNull(); RemotingCommand request = createUnRegisterConsumerCommand(); RemotingCommand response = clientManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); consumerGroupInfo = brokerController.getConsumerManager().getConsumerGroupInfo(group); assertThat(consumerGroupInfo).isNull(); } @Test public void processRequest_heartbeat() throws RemotingCommandException { RemotingCommand request = createHeartbeatCommand(false, "topicA"); RemotingCommand response = clientManageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(Boolean.parseBoolean(response.getExtFields().get(MixAll.IS_SUB_CHANGE))).isFalse(); ConsumerGroupInfo consumerGroupInfo = brokerController.getConsumerManager().getConsumerGroupInfo(group); RemotingCommand requestSimple = createHeartbeatCommand(true, "topicA"); RemotingCommand responseSimple = clientManageProcessor.processRequest(handlerContext, requestSimple); assertThat(responseSimple.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(Boolean.parseBoolean(responseSimple.getExtFields().get(MixAll.IS_SUB_CHANGE))).isFalse(); ConsumerGroupInfo consumerGroupInfoSimple = brokerController.getConsumerManager().getConsumerGroupInfo(group); assertThat(consumerGroupInfoSimple).isEqualTo(consumerGroupInfo); request = createHeartbeatCommand(false, "topicB"); response = clientManageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(Boolean.parseBoolean(response.getExtFields().get(MixAll.IS_SUB_CHANGE))).isTrue(); consumerGroupInfo = brokerController.getConsumerManager().getConsumerGroupInfo(group); requestSimple = createHeartbeatCommand(true, "topicB"); responseSimple = clientManageProcessor.processRequest(handlerContext, requestSimple); assertThat(responseSimple.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(Boolean.parseBoolean(responseSimple.getExtFields().get(MixAll.IS_SUB_CHANGE))).isFalse(); consumerGroupInfoSimple = brokerController.getConsumerManager().getConsumerGroupInfo(group); assertThat(consumerGroupInfoSimple).isEqualTo(consumerGroupInfo); } @Test public void test_heartbeat_costTime() { String topic = "TOPIC_TEST"; List topicList = new ArrayList<>(); for (int i = 0; i < 500; i ++) { topicList.add(topic + i); } HeartbeatData heartbeatData = prepareHeartbeatData(false, topicList); long time = System.currentTimeMillis(); heartbeatData.computeHeartbeatFingerprint(); System.out.print("computeHeartbeatFingerprint cost time : " + (System.currentTimeMillis() - time) + " ms \n"); } private RemotingCommand createUnRegisterProducerCommand() { UnregisterClientRequestHeader requestHeader = new UnregisterClientRequestHeader(); requestHeader.setClientID(clientId); requestHeader.setProducerGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, requestHeader); request.setLanguage(LanguageCode.JAVA); request.setVersion(100); request.makeCustomHeaderToNet(); return request; } private RemotingCommand createUnRegisterConsumerCommand() { UnregisterClientRequestHeader requestHeader = new UnregisterClientRequestHeader(); requestHeader.setClientID(clientId); requestHeader.setConsumerGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, requestHeader); request.setLanguage(LanguageCode.JAVA); request.setVersion(100); request.makeCustomHeaderToNet(); return request; } private RemotingCommand createHeartbeatCommand(boolean isWithoutSub, String topic) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, null); request.setLanguage(LanguageCode.JAVA); HeartbeatData heartbeatDataWithSub = prepareHeartbeatData(false, topic); int heartbeatFingerprint = heartbeatDataWithSub.computeHeartbeatFingerprint(); HeartbeatData heartbeatData = prepareHeartbeatData(isWithoutSub, topic); heartbeatData.setHeartbeatFingerprint(heartbeatFingerprint); request.setBody(heartbeatData.encode()); return request; } private HeartbeatData prepareHeartbeatData(boolean isWithoutSub, String topic) { List list = new ArrayList<>(); list.add(topic); return prepareHeartbeatData(isWithoutSub, list); } private HeartbeatData prepareHeartbeatData(boolean isWithoutSub, List topicList) { HeartbeatData heartbeatData = new HeartbeatData(); heartbeatData.setClientID(this.clientId); ConsumerData consumerData = createConsumerData(group); if (!isWithoutSub) { Set subscriptionDataSet = new HashSet<>(); for (String topic : topicList) { SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setSubString("*"); subscriptionData.setSubVersion(100L); subscriptionDataSet.add(subscriptionData); } consumerData.getSubscriptionDataSet().addAll(subscriptionDataSet); } heartbeatData.getConsumerDataSet().add(consumerData); heartbeatData.setWithoutSub(isWithoutSub); return heartbeatData; } static ConsumerData createConsumerData(String group) { ConsumerData consumerData = new ConsumerData(); consumerData.setGroupName(group); consumerData.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumerData.setConsumeType(ConsumeType.CONSUME_PASSIVELY); consumerData.setMessageModel(MessageModel.CLUSTERING); return consumerData; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.broker.topic.TopicQueueMappingManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingContext; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.rpc.RpcClient; import org.apache.rocketmq.remoting.rpc.RpcException; import org.apache.rocketmq.remoting.rpc.RpcResponse; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ConsumerManageProcessorTest { private ConsumerManageProcessor consumerManageProcessor; @Mock private ChannelHandlerContext handlerContext; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; @Mock private Channel channel; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private BrokerOuterAPI brokerOuterAPI; @Mock private RpcClient rpcClient; @Mock private Future responseFuture; @Mock private TopicQueueMappingContext mappingContext; private String topic = "FooBar"; private String group = "FooBarGroup"; @Before public void init() throws RpcException { brokerController.setMessageStore(messageStore); TopicConfigManager topicConfigManager = new TopicConfigManager(brokerController); topicConfigManager.getTopicConfigTable().put(topic, new TopicConfig(topic)); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); SubscriptionGroupManager subscriptionGroupManager = new SubscriptionGroupManager(brokerController); subscriptionGroupManager.getSubscriptionGroupTable().put(group, new SubscriptionGroupConfig()); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); consumerManageProcessor = new ConsumerManageProcessor(brokerController); when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); when(brokerOuterAPI.getRpcClient()).thenReturn(rpcClient); when(rpcClient.invoke(any(),anyLong())).thenReturn(responseFuture); TopicQueueMappingDetail topicQueueMappingDetail = new TopicQueueMappingDetail(); topicQueueMappingDetail.setBname("BrokerA"); when(mappingContext.getMappingDetail()).thenReturn(topicQueueMappingDetail); } @Test public void testUpdateConsumerOffset_InvalidTopic() throws Exception { RemotingCommand request = buildUpdateConsumerOffsetRequest(group, "InvalidTopic", 0, 0); RemotingCommand response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); } @Test public void testUpdateConsumerOffset_GroupNotExist() throws Exception { RemotingCommand request = buildUpdateConsumerOffsetRequest("NotExistGroup", topic, 0, 0); RemotingCommand response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); } @Test public void testUpdateConsumerOffset() throws RemotingCommandException { when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(consumerOffsetManager.hasOffsetReset(anyString(),anyString(),anyInt())).thenReturn(true); RemotingCommand request = buildUpdateConsumerOffsetRequest(group, topic, 0, 0); RemotingCommand response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(consumerOffsetManager.hasOffsetReset(anyString(),anyString(),anyInt())).thenReturn(false); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetConsumerListByGroup() throws RemotingCommandException { GetConsumerListByGroupRequestHeader requestHeader = new GetConsumerListByGroupRequestHeader(); requestHeader.setConsumerGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, requestHeader); request.makeCustomHeaderToNet(); RemotingCommand response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); brokerController.getConsumerManager().getConsumerTable().put(group,new ConsumerGroupInfo(group)); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo( requestHeader.getConsumerGroup()); consumerGroupInfo.getChannelInfoTable().put(channel,new ClientChannelInfo(channel)); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testQueryConsumerOffset() throws RemotingCommandException, ExecutionException, InterruptedException { RemotingCommand request = buildQueryConsumerOffsetRequest(group, topic, 0, true); RemotingCommand response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.QUERY_NOT_FOUND); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(consumerOffsetManager.queryOffset(anyString(),anyString(),anyInt())).thenReturn(0L); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); when(consumerOffsetManager.queryOffset(anyString(),anyString(),anyInt())).thenReturn(-1L); when(messageStore.getMinOffsetInQueue(anyString(),anyInt())).thenReturn(-1L); when(messageStore.checkInMemByConsumeOffset(anyString(),anyInt(),anyLong(),anyInt())).thenReturn(true); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); TopicQueueMappingManager topicQueueMappingManager = mock(TopicQueueMappingManager.class); when(brokerController.getTopicQueueMappingManager()).thenReturn(topicQueueMappingManager); when(topicQueueMappingManager.buildTopicQueueMappingContext(any(QueryConsumerOffsetRequestHeader.class))).thenReturn(mappingContext); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NOT_LEADER_FOR_QUEUE); List items = new ArrayList<>(); LogicQueueMappingItem item1 = createLogicQueueMappingItem("BrokerC", 0, 0L, 0L); items.add(item1); when(mappingContext.getMappingItemList()).thenReturn(items); when(mappingContext.getLeaderItem()).thenReturn(item1); when(mappingContext.getCurrentItem()).thenReturn(item1); when(mappingContext.isLeader()).thenReturn(true); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); LogicQueueMappingItem item2 = createLogicQueueMappingItem("BrokerA", 0, 0L, 0L); items.add(item2); QueryConsumerOffsetResponseHeader queryConsumerOffsetResponseHeader = new QueryConsumerOffsetResponseHeader(); queryConsumerOffsetResponseHeader.setOffset(0L); RpcResponse rpcResponse = new RpcResponse(ResponseCode.SUCCESS,queryConsumerOffsetResponseHeader,null); when(responseFuture.get()).thenReturn(rpcResponse); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); queryConsumerOffsetResponseHeader.setOffset(-1L); rpcResponse = new RpcResponse(ResponseCode.SUCCESS,queryConsumerOffsetResponseHeader,null); when(responseFuture.get()).thenReturn(rpcResponse); response = consumerManageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.QUERY_NOT_FOUND); } @Test public void testRewriteRequestForStaticTopic() throws RpcException, ExecutionException, InterruptedException { UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(group); requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setCommitOffset(0L); RemotingCommand response = consumerManageProcessor.rewriteRequestForStaticTopic(requestHeader, mappingContext); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NOT_LEADER_FOR_QUEUE); List items = new ArrayList<>(); LogicQueueMappingItem item = createLogicQueueMappingItem("BrokerC", 0, 0L, 0L); items.add(item); when(mappingContext.getMappingItemList()).thenReturn(items); when(mappingContext.isLeader()).thenReturn(true); RpcResponse rpcResponse = new RpcResponse(ResponseCode.SUCCESS,new UpdateConsumerOffsetResponseHeader(),null); when(responseFuture.get()).thenReturn(rpcResponse); response = consumerManageProcessor.rewriteRequestForStaticTopic(requestHeader, mappingContext); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } public RemotingCommand buildQueryConsumerOffsetRequest(String group, String topic, int queueId,boolean setZeroIfNotFound) { QueryConsumerOffsetRequestHeader requestHeader = new QueryConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(group); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setSetZeroIfNotFound(setZeroIfNotFound); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader); request.makeCustomHeaderToNet(); return request; } public LogicQueueMappingItem createLogicQueueMappingItem(String brokerName, int queueId, long startOffset, long logicOffset) { LogicQueueMappingItem item = new LogicQueueMappingItem(); item.setBname(brokerName); item.setQueueId(queueId); item.setStartOffset(startOffset); item.setLogicOffset(logicOffset); return item; } private RemotingCommand buildUpdateConsumerOffsetRequest(String group, String topic, int queueId, long offset) { UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(group); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setCommitOffset(offset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader); request.makeCustomHeaderToNet(); return request; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.transaction.OperationResult; import org.apache.rocketmq.broker.transaction.TransactionMetrics; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.stats.Stats; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class EndTransactionProcessorTest { private static final String TOPIC = "trans_topic_test"; private EndTransactionProcessor endTransactionProcessor; @Mock private ChannelHandlerContext handlerContext; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig(), null); @Mock private MessageStore messageStore; @Mock private TransactionalMessageService transactionMsgService; @Mock private TransactionMetrics transactionMetrics; @Before public void init() { when(transactionMsgService.getTransactionMetrics()).thenReturn(transactionMetrics); brokerController.setMessageStore(messageStore); brokerController.setTransactionalMessageService(transactionMsgService); // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); endTransactionProcessor = new EndTransactionProcessor(brokerController); } private OperationResult createResponse(int status) { OperationResult response = new OperationResult(); response.setPrepareMessage(createDefaultMessageExt()); response.setResponseCode(status); response.setResponseRemark(null); return response; } @Test public void testProcessRequest() throws RemotingCommandException { when(transactionMsgService.commitMessage(any(EndTransactionRequestHeader.class))).thenReturn(createResponse(ResponseCode.SUCCESS)); when(messageStore.putMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, createAppendMessageResult(AppendMessageStatus.PUT_OK))); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_COMMIT_TYPE, false); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.BROKER_PUT_NUMS, brokerController.getBrokerConfig().getBrokerClusterName()).getValue().sum()).isEqualTo(1); assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.TOPIC_PUT_NUMS, TOPIC).getValue().sum()).isEqualTo(1L); assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.TOPIC_PUT_SIZE, TOPIC).getValue().sum()).isEqualTo(1L); } @Test public void testProcessRequest_CheckMessage() throws RemotingCommandException { when(transactionMsgService.commitMessage(any(EndTransactionRequestHeader.class))).thenReturn(createResponse(ResponseCode.SUCCESS)); when(messageStore.putMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, createAppendMessageResult(AppendMessageStatus.PUT_OK))); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_COMMIT_TYPE, true); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.BROKER_PUT_NUMS, brokerController.getBrokerConfig().getBrokerClusterName()).getValue().sum()).isEqualTo(1); assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.TOPIC_PUT_NUMS, TOPIC).getValue().sum()).isEqualTo(1L); assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.TOPIC_PUT_SIZE, TOPIC).getValue().sum()).isEqualTo(1L); } @Test public void testProcessRequest_NotType() throws RemotingCommandException { RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_NOT_TYPE, true); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response).isNull(); } @Test public void testProcessRequest_RollBack() throws RemotingCommandException { when(transactionMsgService.rollbackMessage(any(EndTransactionRequestHeader.class))).thenReturn(createResponse(ResponseCode.SUCCESS)); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE, true); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_RejectCommitMessage() throws RemotingCommandException { when(transactionMsgService.commitMessage(any(EndTransactionRequestHeader.class))).thenReturn(createRejectResponse()); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_COMMIT_TYPE, false); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.ILLEGAL_OPERATION); } @Test public void testProcessRequest_RejectRollBackMessage() throws RemotingCommandException { when(transactionMsgService.rollbackMessage(any(EndTransactionRequestHeader.class))).thenReturn(createRejectResponse()); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE, false); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.ILLEGAL_OPERATION); } private MessageExt createDefaultMessageExt() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("12345678"); messageExt.setQueueId(0); messageExt.setCommitLogOffset(123456789L); messageExt.setQueueOffset(1234); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_TOPIC, TOPIC); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_QUEUE_ID, "0"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_PRODUCER_GROUP, "testTransactionGroup"); return messageExt; } private EndTransactionRequestHeader createEndTransactionRequestHeader(int status, boolean isCheckMsg) { EndTransactionRequestHeader header = new EndTransactionRequestHeader(); header.setTopic("topic"); header.setCommitLogOffset(123456789L); header.setFromTransactionCheck(isCheckMsg); header.setCommitOrRollback(status); header.setMsgId("12345678"); header.setTransactionId("123"); header.setProducerGroup("testTransactionGroup"); header.setTranStateTableOffset(1234L); return header; } private RemotingCommand createEndTransactionMsgCommand(int status, boolean isCheckMsg) { EndTransactionRequestHeader header = createEndTransactionRequestHeader(status, isCheckMsg); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, header); request.makeCustomHeaderToNet(); return request; } private OperationResult createRejectResponse() { OperationResult response = new OperationResult(); response.setPrepareMessage(createRejectMessageExt()); response.setResponseCode(ResponseCode.SUCCESS); response.setResponseRemark(null); return response; } private MessageExt createRejectMessageExt() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("12345678"); messageExt.setQueueId(0); messageExt.setCommitLogOffset(123456789L); messageExt.setQueueOffset(1234); messageExt.setBody("body".getBytes(StandardCharsets.UTF_8)); messageExt.setBornTimestamp(System.currentTimeMillis() - 65 * 1000); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_QUEUE_ID, "0"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_PRODUCER_GROUP, "testTransactionGroup"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_TOPIC, "TEST"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS, "60"); return messageExt; } private AppendMessageResult createAppendMessageResult(AppendMessageStatus status) { AppendMessageResult result = new AppendMessageResult(status); result.setMsgId("12345678"); result.setMsgNum(1); result.setWroteBytes(1); return result; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/LiteManagerProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.AbstractLiteLifecycleManager; import org.apache.rocketmq.broker.lite.LiteEventDispatcher; import org.apache.rocketmq.broker.lite.LiteSharding; import org.apache.rocketmq.broker.lite.LiteSubscriptionRegistry; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.metrics.LiteConsumerLagCalculator; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.offset.MemoryConsumerOrderInfoManager; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.common.lite.LiteLagInfo; import org.apache.rocketmq.common.lite.LiteSubscription; import org.apache.rocketmq.common.lite.LiteUtil; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import org.apache.rocketmq.remoting.protocol.body.GetBrokerLiteInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteClientInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteGroupInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetParentTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetLiteClientInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteGroupInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetParentTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.TriggerLiteDispatchRequestHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteManagerProcessorTest { @Mock private BrokerController brokerController; @Mock private AbstractLiteLifecycleManager liteLifecycleManager; @Mock private LiteSharding liteSharding; @Mock private ChannelHandlerContext ctx; @Mock private MessageStoreConfig messageStoreConfig; @Mock private MessageStore messageStore; @Mock private ConsumeQueueStoreInterface consumeQueueStore; @Mock private TopicConfigManager topicConfigManager; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private LiteSubscriptionRegistry liteSubscriptionRegistry; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private BrokerMetricsManager brokerMetricsManager; @Mock private LiteConsumerLagCalculator liteConsumerLagCalculator; @Mock private LiteEventDispatcher liteEventDispatcher; @Mock private PopLiteMessageProcessor popLiteMessageProcessor; private LiteManagerProcessor processor; @Before public void setUp() { processor = new LiteManagerProcessor(brokerController, liteLifecycleManager, liteSharding); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(brokerController.getLiteSubscriptionRegistry()).thenReturn(liteSubscriptionRegistry); when(brokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); when(brokerController.getLiteEventDispatcher()).thenReturn(liteEventDispatcher); when(brokerController.getPopLiteMessageProcessor()).thenReturn(popLiteMessageProcessor); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); ConsumerOrderInfoManager consumerOrderInfoManager = new MemoryConsumerOrderInfoManager(brokerController); when(popLiteMessageProcessor.getConsumerOrderInfoManager()).thenReturn(consumerOrderInfoManager); when(messageStore.getQueueStore()).thenReturn(consumeQueueStore); when(consumeQueueStore.getConsumeQueueTable()).thenReturn(new ConcurrentHashMap<>()); when(brokerMetricsManager.getLiteConsumerLagCalculator()).thenReturn(liteConsumerLagCalculator); when(consumerOffsetManager.getOffsetTable()).thenReturn(new ConcurrentHashMap<>()); } @Test public void testProcessRequest_GetBrokerLiteInfo() throws Exception { RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.GET_BROKER_LITE_INFO); ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigTable); ConcurrentMap subscriptionGroupTable = new ConcurrentHashMap<>(); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(subscriptionGroupTable); RemotingCommand response = processor.processRequest(ctx, request); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); } @Test public void testProcessRequest_UnsupportedRequestCode() throws Exception { RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(99999); assertNull(processor.processRequest(ctx, request)); } @Test public void testGetBrokerLiteInfo() throws RemotingCommandException { when(messageStoreConfig.getStoreType()).thenReturn("RocksDB"); when(messageStoreConfig.getMaxLmqConsumeQueueNum()).thenReturn(10000); when(consumeQueueStore.getLmqNum()).thenReturn(100); when(liteSubscriptionRegistry.getActiveSubscriptionNum()).thenReturn(50); ConcurrentHashMap topicConfigMap = new ConcurrentHashMap<>(); topicConfigMap.put("SYSTEM_TOPIC", new TopicConfig("SYSTEM_TOPIC")); when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigMap); ConcurrentHashMap subscriptionGroupMap = new ConcurrentHashMap<>(); SubscriptionGroupConfig config = new SubscriptionGroupConfig(); config.setGroupName("test_group"); config.setLiteBindTopic("test_topic"); subscriptionGroupMap.put("test_group", config); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(subscriptionGroupMap); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_LITE_INFO, null); RemotingCommand response = processor.getBrokerLiteInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetBrokerLiteInfoResponseBody body = GetBrokerLiteInfoResponseBody.decode(response.getBody(), GetBrokerLiteInfoResponseBody.class); assertEquals("RocksDB", body.getStoreType()); assertEquals(10000, body.getMaxLmqNum()); assertEquals(100, body.getCurrentLmqNum()); assertEquals(50, body.getLiteSubscriptionCount()); assertNotNull(body.getTopicMeta()); assertNotNull(body.getGroupMeta()); } @Test public void testGetParentTopicInfo_TopicNotExist() throws RemotingCommandException { GetParentTopicInfoRequestHeader requestHeader = new GetParentTopicInfoRequestHeader(); requestHeader.setTopic("nonexistent_topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_PARENT_TOPIC_INFO, requestHeader); request.makeCustomHeaderToNet(); when(topicConfigManager.selectTopicConfig("nonexistent_topic")).thenReturn(null); RemotingCommand response = processor.getParentTopicInfo(ctx, request); assertEquals(ResponseCode.TOPIC_NOT_EXIST, response.getCode()); assertTrue(response.getRemark().contains("nonexistent_topic")); } @Test public void testGetParentTopicInfo_InvalidTopicType() throws RemotingCommandException { GetParentTopicInfoRequestHeader requestHeader = new GetParentTopicInfoRequestHeader(); requestHeader.setTopic("invalid_topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_PARENT_TOPIC_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("invalid_topic"); topicConfig.setTopicMessageType(TopicMessageType.NORMAL); when(topicConfigManager.selectTopicConfig("invalid_topic")).thenReturn(topicConfig); RemotingCommand response = processor.getParentTopicInfo(ctx, request); assertEquals(ResponseCode.INVALID_PARAMETER, response.getCode()); assertTrue(response.getRemark().contains("invalid_topic")); } @Test public void testGetParentTopicInfo_Success() throws RemotingCommandException { GetParentTopicInfoRequestHeader requestHeader = new GetParentTopicInfoRequestHeader(); requestHeader.setTopic("parent_topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_PARENT_TOPIC_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("parent_topic"); topicConfig.setTopicMessageType(TopicMessageType.LITE); topicConfig.setLiteTopicExpiration(3600); when(topicConfigManager.selectTopicConfig("parent_topic")).thenReturn(topicConfig); when(consumeQueueStore.getLmqNum()).thenReturn(200); when(liteLifecycleManager.getLiteTopicCount("parent_topic")).thenReturn(10); ConcurrentHashMap subscriptionGroupMap = new ConcurrentHashMap<>(); SubscriptionGroupConfig config = new SubscriptionGroupConfig(); config.setGroupName("test_group"); config.setLiteBindTopic("parent_topic"); subscriptionGroupMap.put("test_group", config); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(subscriptionGroupMap); RemotingCommand response = processor.getParentTopicInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetParentTopicInfoResponseBody body = GetParentTopicInfoResponseBody.decode(response.getBody(), GetParentTopicInfoResponseBody.class); assertEquals("parent_topic", body.getTopic()); assertEquals(3600, body.getTtl()); assertEquals(200, body.getLmqNum()); assertEquals(10, body.getLiteTopicCount()); assertTrue(body.getGroups().contains("test_group")); } @Test public void testGetLiteTopicInfo_ParentTopicNotExist() throws RemotingCommandException { GetLiteTopicInfoRequestHeader requestHeader = new GetLiteTopicInfoRequestHeader(); requestHeader.setParentTopic("nonexistent_parent"); requestHeader.setLiteTopic("lite_topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_TOPIC_INFO, requestHeader); request.makeCustomHeaderToNet(); when(topicConfigManager.selectTopicConfig("nonexistent_parent")).thenReturn(null); RemotingCommand response = processor.getLiteTopicInfo(ctx, request); assertEquals(ResponseCode.TOPIC_NOT_EXIST, response.getCode()); assertTrue(response.getRemark().contains("nonexistent_parent")); } @Test public void testGetLiteTopicInfo_InvalidParentTopicType() throws RemotingCommandException { GetLiteTopicInfoRequestHeader requestHeader = new GetLiteTopicInfoRequestHeader(); requestHeader.setParentTopic("invalid_parent"); requestHeader.setLiteTopic("lite_topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_TOPIC_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("invalid_parent"); topicConfig.setTopicMessageType(TopicMessageType.NORMAL); when(topicConfigManager.selectTopicConfig("invalid_parent")).thenReturn(topicConfig); RemotingCommand response = processor.getLiteTopicInfo(ctx, request); assertEquals(ResponseCode.INVALID_PARAMETER, response.getCode()); assertTrue(response.getRemark().contains("invalid_parent")); } @Test public void testGetLiteTopicInfo_Success() throws RemotingCommandException { GetLiteTopicInfoRequestHeader requestHeader = new GetLiteTopicInfoRequestHeader(); requestHeader.setParentTopic("parent_topic"); requestHeader.setLiteTopic("lite_topic"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_TOPIC_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("parent_topic"); topicConfig.setTopicMessageType(TopicMessageType.LITE); String lmqName = LiteUtil.toLmqName("parent_topic", "lite_topic"); long maxOffset = 100L; long minOffset = 10L; long lastUpdateTimestamp = System.currentTimeMillis(); when(topicConfigManager.selectTopicConfig("parent_topic")).thenReturn(topicConfig); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName)).thenReturn(maxOffset); when(messageStore.getMinOffsetInQueue(lmqName, 0)).thenReturn(minOffset); when(messageStore.getMessageStoreTimeStamp(lmqName, 0, maxOffset - 1)).thenReturn(lastUpdateTimestamp); Set subscribers = new HashSet<>(); subscribers.add(new ClientGroup("clientId1", "group1")); when(liteSubscriptionRegistry.getSubscriber(lmqName)).thenReturn(subscribers); when(brokerController.getBrokerConfig()).thenReturn(mock(BrokerConfig.class)); when(brokerController.getBrokerConfig().getBrokerName()).thenReturn("broker1"); when(liteSharding.shardingByLmqName("parent_topic", lmqName)).thenReturn("broker1"); RemotingCommand response = processor.getLiteTopicInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteTopicInfoResponseBody body = GetLiteTopicInfoResponseBody.decode(response.getBody(), GetLiteTopicInfoResponseBody.class); assertEquals("parent_topic", body.getParentTopic()); assertEquals("lite_topic", body.getLiteTopic()); assertEquals(subscribers, body.getSubscriber()); TopicOffset topicOffset = body.getTopicOffset(); assertEquals(minOffset, topicOffset.getMinOffset()); assertEquals(maxOffset, topicOffset.getMaxOffset()); assertEquals(lastUpdateTimestamp, topicOffset.getLastUpdateTimestamp()); assertTrue(body.isShardingToBroker()); } @Test public void testGetLiteClientInfo_ParentTopicNotExist() throws RemotingCommandException { GetLiteClientInfoRequestHeader requestHeader = new GetLiteClientInfoRequestHeader(); requestHeader.setParentTopic("nonexistent_parent"); requestHeader.setGroup("group1"); requestHeader.setClientId("client1"); requestHeader.setMaxCount(100); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_CLIENT_INFO, requestHeader); request.makeCustomHeaderToNet(); when(topicConfigManager.selectTopicConfig("nonexistent_parent")).thenReturn(null); RemotingCommand response = processor.getLiteClientInfo(ctx, request); assertEquals(ResponseCode.TOPIC_NOT_EXIST, response.getCode()); assertTrue(response.getRemark().contains("nonexistent_parent")); } @Test public void testGetLiteClientInfo_GroupNotExist() throws RemotingCommandException { GetLiteClientInfoRequestHeader requestHeader = new GetLiteClientInfoRequestHeader(); requestHeader.setParentTopic("parent_topic"); requestHeader.setGroup("nonexistent_group"); requestHeader.setClientId("client1"); requestHeader.setMaxCount(100); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_CLIENT_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("parent_topic"); topicConfig.setTopicMessageType(TopicMessageType.LITE); when(topicConfigManager.selectTopicConfig("parent_topic")).thenReturn(topicConfig); when(subscriptionGroupManager.findSubscriptionGroupConfig("nonexistent_group")).thenReturn(null); RemotingCommand response = processor.getLiteClientInfo(ctx, request); assertEquals(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST, response.getCode()); assertTrue(response.getRemark().contains("nonexistent_group")); } @Test public void testGetLiteClientInfo_NoSubscription() throws RemotingCommandException { GetLiteClientInfoRequestHeader requestHeader = new GetLiteClientInfoRequestHeader(); requestHeader.setParentTopic("parent_topic"); requestHeader.setGroup("group1"); requestHeader.setClientId("client1"); requestHeader.setMaxCount(100); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_CLIENT_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("parent_topic"); topicConfig.setTopicMessageType(TopicMessageType.LITE); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("group1"); groupConfig.setLiteBindTopic("parent_topic"); when(topicConfigManager.selectTopicConfig("parent_topic")).thenReturn(topicConfig); when(subscriptionGroupManager.findSubscriptionGroupConfig("group1")).thenReturn(groupConfig); when(liteSubscriptionRegistry.getLiteSubscription("client1")).thenReturn(null); RemotingCommand response = processor.getLiteClientInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteClientInfoResponseBody body = GetLiteClientInfoResponseBody.decode(response.getBody(), GetLiteClientInfoResponseBody.class); assertEquals("parent_topic", body.getParentTopic()); assertEquals("group1", body.getGroup()); assertEquals("client1", body.getClientId()); assertEquals(-1, body.getLiteTopicCount()); assertNull(body.getLiteTopicSet()); } @Test public void testGetLiteClientInfo_WithSubscription() throws RemotingCommandException { GetLiteClientInfoRequestHeader requestHeader = new GetLiteClientInfoRequestHeader(); requestHeader.setParentTopic("parent_topic"); requestHeader.setGroup("group1"); requestHeader.setClientId("client1"); requestHeader.setMaxCount(100); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_CLIENT_INFO, requestHeader); request.makeCustomHeaderToNet(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("parent_topic"); topicConfig.setTopicMessageType(TopicMessageType.LITE); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("group1"); groupConfig.setLiteBindTopic("parent_topic"); Set liteTopicSet = new HashSet<>(); liteTopicSet.add("lite_topic1"); liteTopicSet.add("lite_topic2"); LiteSubscription liteSubscription = new LiteSubscription(); liteSubscription.setLiteTopicSet(liteTopicSet); when(topicConfigManager.selectTopicConfig("parent_topic")).thenReturn(topicConfig); when(subscriptionGroupManager.findSubscriptionGroupConfig("group1")).thenReturn(groupConfig); when(liteSubscriptionRegistry.getLiteSubscription("client1")).thenReturn(liteSubscription); RemotingCommand response = processor.getLiteClientInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteClientInfoResponseBody body = GetLiteClientInfoResponseBody.decode(response.getBody(), GetLiteClientInfoResponseBody.class); assertEquals("parent_topic", body.getParentTopic()); assertEquals("group1", body.getGroup()); assertEquals("client1", body.getClientId()); assertEquals(2, body.getLiteTopicCount()); assertEquals(liteTopicSet, body.getLiteTopicSet()); } @Test public void testGetLiteGroupInfo_GroupNotExist() throws RemotingCommandException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup("nonexistent_group"); requestHeader.setLiteTopic(""); requestHeader.setTopK(10); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_GROUP_INFO, requestHeader); request.makeCustomHeaderToNet(); when(subscriptionGroupManager.findSubscriptionGroupConfig("nonexistent_group")).thenReturn(null); RemotingCommand response = processor.getLiteGroupInfo(ctx, request); assertEquals(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST, response.getCode()); assertTrue(response.getRemark().contains("nonexistent_group")); } @Test public void testGetLiteGroupInfo_NotLiteGroup() throws RemotingCommandException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup("normal_group"); requestHeader.setLiteTopic(""); requestHeader.setTopK(10); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_GROUP_INFO, requestHeader); request.makeCustomHeaderToNet(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("normal_group"); groupConfig.setLiteBindTopic(""); when(subscriptionGroupManager.findSubscriptionGroupConfig("normal_group")).thenReturn(groupConfig); RemotingCommand response = processor.getLiteGroupInfo(ctx, request); assertEquals(ResponseCode.INVALID_PARAMETER, response.getCode()); assertTrue(response.getRemark().contains("normal_group")); assertTrue(response.getRemark().contains("not a LITE group")); } @Test public void testGetLiteGroupInfo_GetTopKInfo() throws RemotingCommandException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup("lite_group"); requestHeader.setLiteTopic(""); requestHeader.setTopK(10); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_GROUP_INFO, requestHeader); request.makeCustomHeaderToNet(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("lite_group"); groupConfig.setLiteBindTopic("parent_topic"); List lagCountList = new ArrayList<>(); LiteLagInfo lagCountInfo = new LiteLagInfo(); lagCountInfo.setLiteTopic("topic1"); lagCountInfo.setLagCount(100L); lagCountList.add(lagCountInfo); Pair, Long> lagCountPair = new Pair<>(lagCountList, 100L); List lagTimeList = new ArrayList<>(); LiteLagInfo lagTimeInfo = new LiteLagInfo(); lagTimeInfo.setLiteTopic("topic1"); lagTimeInfo.setEarliestUnconsumedTimestamp(System.currentTimeMillis()); lagTimeList.add(lagTimeInfo); Pair, Long> lagTimePair = new Pair<>(lagTimeList, System.currentTimeMillis()); when(subscriptionGroupManager.findSubscriptionGroupConfig("lite_group")).thenReturn(groupConfig); when(liteConsumerLagCalculator.getLagCountTopK("lite_group", 10)).thenReturn(lagCountPair); when(liteConsumerLagCalculator.getLagTimestampTopK("lite_group", "parent_topic", 10)).thenReturn(lagTimePair); RemotingCommand response = processor.getLiteGroupInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteGroupInfoResponseBody body = GetLiteGroupInfoResponseBody.decode(response.getBody(), GetLiteGroupInfoResponseBody.class); assertEquals("lite_group", body.getGroup()); assertEquals("parent_topic", body.getParentTopic()); assertTrue(StringUtils.isEmpty(body.getLiteTopic())); List actualLagCountList = body.getLagCountTopK(); assertEquals(lagCountList.size(), actualLagCountList.size()); for (int i = 0; i < lagCountList.size(); i++) { LiteLagInfo expected = lagCountList.get(i); LiteLagInfo actual = actualLagCountList.get(i); assertEquals(expected.getLiteTopic(), actual.getLiteTopic()); assertEquals(expected.getLagCount(), actual.getLagCount()); } assertEquals(Long.valueOf(100L), Long.valueOf(body.getTotalLagCount())); List actualLagTimeList = body.getLagTimestampTopK(); assertEquals(lagTimeList.size(), actualLagTimeList.size()); for (int i = 0; i < lagTimeList.size(); i++) { LiteLagInfo expected = lagTimeList.get(i); LiteLagInfo actual = actualLagTimeList.get(i); assertEquals(expected.getLiteTopic(), actual.getLiteTopic()); assertEquals(expected.getEarliestUnconsumedTimestamp(), actual.getEarliestUnconsumedTimestamp()); } } @Test public void testGetLiteGroupInfo_SpecificLiteTopic_WithMessages() throws RemotingCommandException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup("lite_group"); requestHeader.setLiteTopic("specific_lite_topic"); requestHeader.setTopK(10); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_GROUP_INFO, requestHeader); request.makeCustomHeaderToNet(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("lite_group"); groupConfig.setLiteBindTopic("parent_topic"); String lmqName = LiteUtil.toLmqName("parent_topic", "specific_lite_topic"); long maxOffset = 100L; long commitOffset = 50L; long messageTimestamp = System.currentTimeMillis() - 10000; when(subscriptionGroupManager.findSubscriptionGroupConfig("lite_group")).thenReturn(groupConfig); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName)).thenReturn(maxOffset); when(consumerOffsetManager.queryOffset("lite_group", lmqName, 0)).thenReturn(commitOffset); when(messageStore.getMessageStoreTimeStamp(lmqName, 0, commitOffset)).thenReturn(messageTimestamp); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); RemotingCommand response = processor.getLiteGroupInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteGroupInfoResponseBody body = GetLiteGroupInfoResponseBody.decode(response.getBody(), GetLiteGroupInfoResponseBody.class); assertEquals("lite_group", body.getGroup()); assertEquals("parent_topic", body.getParentTopic()); assertEquals("specific_lite_topic", body.getLiteTopic()); assertEquals(maxOffset - commitOffset, body.getTotalLagCount()); assertEquals(messageTimestamp, body.getEarliestUnconsumedTimestamp()); } @Test public void testGetLiteGroupInfo_SpecificLiteTopic_WithoutMessages() throws RemotingCommandException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup("lite_group"); requestHeader.setLiteTopic("specific_lite_topic"); requestHeader.setTopK(10); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_GROUP_INFO, requestHeader); request.makeCustomHeaderToNet(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("lite_group"); groupConfig.setLiteBindTopic("parent_topic"); String lmqName = LiteUtil.toLmqName("parent_topic", "specific_lite_topic"); long maxOffset = 0L; when(subscriptionGroupManager.findSubscriptionGroupConfig("lite_group")).thenReturn(groupConfig); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName)).thenReturn(maxOffset); RemotingCommand response = processor.getLiteGroupInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteGroupInfoResponseBody body = GetLiteGroupInfoResponseBody.decode(response.getBody(), GetLiteGroupInfoResponseBody.class); assertEquals("lite_group", body.getGroup()); assertEquals("parent_topic", body.getParentTopic()); assertEquals("specific_lite_topic", body.getLiteTopic()); assertEquals(-1, body.getTotalLagCount()); assertEquals(-1L, body.getEarliestUnconsumedTimestamp()); } @Test public void testGetLiteGroupInfo_SpecificLiteTopic_ZeroCommitOffset() throws RemotingCommandException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup("lite_group"); requestHeader.setLiteTopic("specific_lite_topic"); requestHeader.setTopK(10); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_GROUP_INFO, requestHeader); request.makeCustomHeaderToNet(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName("lite_group"); groupConfig.setLiteBindTopic("parent_topic"); String lmqName = LiteUtil.toLmqName("parent_topic", "specific_lite_topic"); long maxOffset = 100L; long commitOffset = 0L; when(subscriptionGroupManager.findSubscriptionGroupConfig("lite_group")).thenReturn(groupConfig); when(liteLifecycleManager.getMaxOffsetInQueue(lmqName)).thenReturn(maxOffset); when(consumerOffsetManager.queryOffset("lite_group", lmqName, 0)).thenReturn(commitOffset); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); RemotingCommand response = processor.getLiteGroupInfo(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); GetLiteGroupInfoResponseBody body = GetLiteGroupInfoResponseBody.decode(response.getBody(), GetLiteGroupInfoResponseBody.class); assertEquals("lite_group", body.getGroup()); assertEquals("parent_topic", body.getParentTopic()); assertEquals("specific_lite_topic", body.getLiteTopic()); assertEquals(maxOffset - commitOffset, body.getTotalLagCount()); assertEquals(0, body.getEarliestUnconsumedTimestamp()); } @Test public void testTriggerLiteDispatch() throws Exception { String group = "group"; String clientId = "clientId"; TriggerLiteDispatchRequestHeader requestHeader; // with clientId requestHeader = new TriggerLiteDispatchRequestHeader(); requestHeader.setGroup(group); requestHeader.setClientId(clientId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.TRIGGER_LITE_DISPATCH, requestHeader); request.makeCustomHeaderToNet(); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setGroupName(group); groupConfig.setLiteBindTopic("parent_topic"); when(subscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(groupConfig); RemotingCommand response = processor.triggerLiteDispatch(ctx, request); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(liteEventDispatcher, times(1)).doFullDispatch(clientId, group); verify(liteEventDispatcher, never()).doFullDispatchByGroup(group); // without clientId requestHeader = new TriggerLiteDispatchRequestHeader(); requestHeader.setGroup(group); request = RemotingCommand.createRequestCommand(RequestCode.TRIGGER_LITE_DISPATCH, requestHeader); request.makeCustomHeaderToNet(); response = processor.triggerLiteDispatch(ctx, request); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(liteEventDispatcher, times(1)).doFullDispatch(clientId, group); verify(liteEventDispatcher, times(1)).doFullDispatchByGroup(group); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/LiteSubscriptionCtlProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.LiteSubscriptionRegistry; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.common.lite.LiteSubscriptionAction; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.LiteSubscriptionCtlRequestBody; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anySet; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteSubscriptionCtlProcessorTest { @Mock private BrokerController brokerController; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private LiteSubscriptionRegistry liteSubscriptionRegistry; @Mock private ChannelHandlerContext ctx; @Mock private Channel channel; @InjectMocks private LiteSubscriptionCtlProcessor processor; @Test public void testProcessRequest_BodyIsNull() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(0, null); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); } @Test public void testProcessRequest_SubscriptionSetIsEmpty() throws Exception { LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(Collections.emptySet()); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setBody(requestBody.encode()); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); } @Test public void testProcessRequest_ActionIsIncrementalAdd() throws Exception { String clientId = "clientId"; String group = "group"; String topic = "topic"; String liteTopic = "liteTopic"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); LiteSubscriptionDTO dto = new LiteSubscriptionDTO(); dto.setClientId(clientId); dto.setGroup(group); dto.setTopic(topic); dto.setLiteTopicSet(liteTopicSet); dto.setAction(LiteSubscriptionAction.PARTIAL_ADD); dto.setVersion(1L); Set subscriptionSet = new HashSet<>(); subscriptionSet.add(dto); LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(subscriptionSet); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setBody(requestBody.encode()); when(ctx.channel()).thenReturn(channel); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setConsumeEnable(true); when(subscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(groupConfig); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(liteSubscriptionRegistry).updateClientChannel(eq(clientId), eq(channel)); verify(liteSubscriptionRegistry).addPartialSubscription(eq(clientId), eq(group), eq(topic), anySet(), any()); } @Test public void testProcessRequest_ActionIsAllAdd() throws Exception { String clientId = "clientId"; String group = "group"; String topic = "topic"; String liteTopic = "liteTopic"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); LiteSubscriptionDTO dto = new LiteSubscriptionDTO(); dto.setClientId(clientId); dto.setGroup(group); dto.setTopic(topic); dto.setLiteTopicSet(liteTopicSet); dto.setAction(LiteSubscriptionAction.COMPLETE_ADD); dto.setVersion(1L); Set subscriptionSet = new HashSet<>(); subscriptionSet.add(dto); LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(subscriptionSet); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setBody(requestBody.encode()); when(ctx.channel()).thenReturn(channel); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setConsumeEnable(true); when(subscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(groupConfig); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(liteSubscriptionRegistry).updateClientChannel(eq(clientId), eq(channel)); verify(liteSubscriptionRegistry).addCompleteSubscription(eq(clientId), eq(group), eq(topic), anySet(), eq(1L)); } @Test public void testProcessRequest_ActionIsIncrementalRemove() throws Exception { String clientId = "clientId"; String group = "group"; String topic = "topic"; String liteTopic = "liteTopic"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); LiteSubscriptionDTO dto = new LiteSubscriptionDTO(); dto.setClientId(clientId); dto.setGroup(group); dto.setTopic(topic); dto.setLiteTopicSet(liteTopicSet); dto.setAction(LiteSubscriptionAction.PARTIAL_REMOVE); Set subscriptionSet = new HashSet<>(); subscriptionSet.add(dto); LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(subscriptionSet); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setBody(requestBody.encode()); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(liteSubscriptionRegistry).removePartialSubscription(eq(clientId), eq(group), eq(topic), anySet()); } @Test public void testProcessRequest_ActionIsAllRemove() throws Exception { String clientId = "clientId"; LiteSubscriptionDTO dto = new LiteSubscriptionDTO(); String group = "group"; String topic = "topic"; dto.setClientId(clientId); dto.setTopic(topic); dto.setGroup(group); dto.setAction(LiteSubscriptionAction.COMPLETE_REMOVE); Set subscriptionSet = new HashSet<>(); subscriptionSet.add(dto); LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(subscriptionSet); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setBody(requestBody.encode()); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(liteSubscriptionRegistry).removeCompleteSubscription(eq(clientId)); } @Test public void testProcessRequest_CheckConsumeEnableThrowsException() throws Exception { String clientId = "clientId"; String group = "group"; String topic = "topic"; String liteTopic = "liteTopic"; Set liteTopicSet = new HashSet<>(); liteTopicSet.add(liteTopic); LiteSubscriptionDTO dto = new LiteSubscriptionDTO(); dto.setClientId(clientId); dto.setGroup(group); dto.setTopic(topic); dto.setLiteTopicSet(liteTopicSet); dto.setAction(LiteSubscriptionAction.PARTIAL_ADD); Set subscriptionSet = new HashSet<>(); subscriptionSet.add(dto); LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(subscriptionSet); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setBody(requestBody.encode()); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); groupConfig.setConsumeEnable(false); when(subscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(groupConfig); RemotingCommand response = processor.processRequest(ctx, request); assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); assertTrue(response.getRemark().contains("Consumer group is not allowed to consume.")); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PeekMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PeekMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PeekMessageProcessorTest { private PeekMessageProcessor peekMessageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; @Mock private MessageStore messageStore; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private SubscriptionGroupConfig subscriptionGroupConfig; @Mock private Channel channel; private TopicConfigManager topicConfigManager; @Before public void init() { // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); peekMessageProcessor = new PeekMessageProcessor(brokerController); when(brokerController.getMessageStore()).thenReturn(messageStore); topicConfigManager = new TopicConfigManager(brokerController); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(subscriptionGroupManager.findSubscriptionGroupConfig(anyString())).thenReturn(subscriptionGroupConfig); when(subscriptionGroupConfig.isConsumeEnable()).thenReturn(true); topicConfigManager.getTopicConfigTable().put("topic", new TopicConfig("topic")); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(consumerOffsetManager.queryOffset(anyString(), anyString(), anyInt())).thenReturn(-1L); when(messageStore.getMinOffsetInQueue(anyString(),anyInt())).thenReturn(0L); when(handlerContext.channel()).thenReturn(channel); when(channel.remoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); } @Test public void testProcessRequest() throws RemotingCommandException { RemotingCommand request = createPeekMessageRequest("group","topic",0); GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); ByteBuffer bb = ByteBuffer.allocate(64); bb.putLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION, System.currentTimeMillis()); SelectMappedBufferResult mappedBufferResult1 = new SelectMappedBufferResult(0, bb, 64, null); for (int i = 0; i < 10;i++) { getMessageResult.addMessage(mappedBufferResult1); } when(messageStore.getMessage(anyString(),anyString(),anyInt(),anyLong(),anyInt(),any())).thenReturn(getMessageResult); RemotingCommand response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_NoPermission() throws RemotingCommandException { this.brokerController.getBrokerConfig().setBrokerPermission(PermName.PERM_WRITE); RemotingCommand request = createPeekMessageRequest("group","topic",0); RemotingCommand response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); this.brokerController.getBrokerConfig().setBrokerPermission(PermName.PERM_WRITE | PermName.PERM_READ); topicConfigManager.getTopicConfigTable().get("topic").setPerm(PermName.PERM_WRITE); response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); topicConfigManager.getTopicConfigTable().get("topic").setPerm(PermName.PERM_WRITE | PermName.PERM_READ); when(subscriptionGroupConfig.isConsumeEnable()).thenReturn(false); response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); } @Test public void testProcessRequest_TopicNotExist() throws RemotingCommandException { RemotingCommand request = createPeekMessageRequest("group1","topic1",0); RemotingCommand response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); } @Test public void testProcessRequest_SubscriptionGroupNotExist() throws RemotingCommandException { when(subscriptionGroupManager.findSubscriptionGroupConfig(anyString())).thenReturn(null); RemotingCommand request = createPeekMessageRequest("group","topic",0); RemotingCommand response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST); } @Test public void testProcessRequest_QueueIdError() throws RemotingCommandException { RemotingCommand request = createPeekMessageRequest("group","topic",17); RemotingCommand response = peekMessageProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.INVALID_PARAMETER); } private RemotingCommand createPeekMessageRequest(String group,String topic,int queueId) { PeekMessageRequestHeader peekMessageRequestHeader = new PeekMessageRequestHeader(); peekMessageRequestHeader.setConsumerGroup(group); peekMessageRequestHeader.setTopic(topic); peekMessageRequestHeader.setMaxMsgNums(10); peekMessageRequestHeader.setQueueId(queueId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PEEK_MESSAGE, peekMessageRequestHeader); request.makeCustomHeaderToNet(); return request; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.metrics.PopMetricsManager; import org.apache.rocketmq.broker.schedule.ScheduleMessageService; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class PopBufferMergeServiceTest { @Mock private BrokerController brokerController; private PopMessageProcessor popMessageProcessor; @Mock private ScheduleMessageService scheduleMessageService; @Mock private TopicConfigManager topicConfigManager; @Mock private ConsumerManager consumerManager; @Mock private DefaultMessageStore messageStore; @Mock private MessageStoreConfig messageStoreConfig; private String defaultGroup = "defaultGroup"; private String defaultTopic = "defaultTopic"; private PopBufferMergeService popBufferMergeService; @Mock private BrokerConfig brokerConfig; @Mock private EscapeBridge escapeBridge; @Before public void init() throws Exception { when(brokerConfig.getBrokerIP1()).thenReturn("127.0.0.1"); when(brokerConfig.isEnablePopBufferMerge()).thenReturn(true); when(brokerConfig.getPopCkStayBufferTime()).thenReturn(10 * 1000); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getScheduleMessageService()).thenReturn(scheduleMessageService); when(brokerController.getConsumerManager()).thenReturn(consumerManager); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); popMessageProcessor = new PopMessageProcessor(brokerController); popBufferMergeService = new PopBufferMergeService(brokerController, popMessageProcessor); FieldUtils.writeDeclaredField(popBufferMergeService, "brokerController", brokerController, true); ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); topicConfigTable.put(defaultTopic, new TopicConfig()); when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigTable); } @Test(timeout = 15_000) public void testBasic() throws Exception { // This test case fails on Windows in CI pipeline // Disable it for later fix Assume.assumeFalse(MixAll.isWindows()); PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); int msgCnt = 1; ck.setNum((byte) msgCnt); long popTime = System.currentTimeMillis() - 1000; ck.setPopTime(popTime); int invisibleTime = 30_000; ck.setInvisibleTime(invisibleTime); int offset = 100; ck.setStartOffset(offset); ck.setCId(defaultGroup); ck.setTopic(defaultTopic); int queueId = 0; ck.setQueueId(queueId); int reviveQid = 0; long nextBeginOffset = 101L; long ackOffset = offset; AckMsg ackMsg = new AckMsg(); ackMsg.setAckOffset(ackOffset); ackMsg.setStartOffset(offset); ackMsg.setConsumerGroup(defaultGroup); ackMsg.setTopic(defaultTopic); ackMsg.setQueueId(queueId); ackMsg.setPopTime(popTime); try { assertThat(popBufferMergeService.addCk(ck, reviveQid, ackOffset, nextBeginOffset)).isTrue(); assertThat(popBufferMergeService.getLatestOffset(defaultTopic, defaultGroup, queueId)).isEqualTo(nextBeginOffset); Thread.sleep(1000); // wait background threads of PopBufferMergeService run for some time assertThat(popBufferMergeService.addAk(reviveQid, ackMsg)).isTrue(); assertThat(popBufferMergeService.getLatestOffset(defaultTopic, defaultGroup, queueId)).isEqualTo(nextBeginOffset); } finally { popBufferMergeService.shutdown(true); } } @Test public void testAddCkJustOffset_MergeKeyConflict() { PopCheckPoint point = mock(PopCheckPoint.class); String mergeKey = "testMergeKey"; when(point.getTopic()).thenReturn(mergeKey); when(point.getCId()).thenReturn(""); when(point.getQueueId()).thenReturn(0); when(point.getStartOffset()).thenReturn(0L); when(point.getPopTime()).thenReturn(0L); when(point.getBrokerName()).thenReturn(""); popBufferMergeService.buffer.put(mergeKey + "000", mock(PopBufferMergeService.PopCheckPointWrapper.class)); assertFalse(popBufferMergeService.addCkJustOffset(point, 0, 0, 0)); } @Test public void testAddCkMock() { int queueId = 0; long startOffset = 100L; long invisibleTime = 30_000L; long popTime = System.currentTimeMillis(); int reviveQueueId = 0; long nextBeginOffset = 101L; String brokerName = "brokerName"; popBufferMergeService.addCkMock(defaultGroup, defaultTopic, queueId, startOffset, invisibleTime, popTime, reviveQueueId, nextBeginOffset, brokerName); verify(brokerConfig, times(1)).isEnablePopLog(); } @Test public void testPutAckToStore() throws Exception { PopCheckPoint point = new PopCheckPoint(); point.setStartOffset(100L); point.setCId("testGroup"); point.setTopic("testTopic"); point.setQueueId(1); point.setPopTime(System.currentTimeMillis()); point.setBrokerName("testBroker"); PopBufferMergeService.PopCheckPointWrapper pointWrapper = mock(PopBufferMergeService.PopCheckPointWrapper.class); when(pointWrapper.getCk()).thenReturn(point); when(pointWrapper.getReviveQueueId()).thenReturn(0); AtomicInteger toStoreBits = new AtomicInteger(0); when(pointWrapper.getToStoreBits()).thenReturn(toStoreBits); byte msgIndex = 0; AtomicInteger count = new AtomicInteger(0); EscapeBridge escapeBridge = mock(EscapeBridge.class); when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); when(brokerController.getBrokerConfig().isAppendAckAsync()).thenReturn(false); BrokerMetricsManager brokerMetricsManager = mock(BrokerMetricsManager.class); PopMetricsManager popMetricsManager = mock(PopMetricsManager.class); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); when(brokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); doNothing().when(popMetricsManager).incPopReviveCkPutCount(any(), any()); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); when(escapeBridge.putMessageToSpecificQueue(any())).thenAnswer(invocation -> { MessageExtBrokerInner capturedMessage = invocation.getArgument(0); AckMsg ackMsg = JSON.parseObject(capturedMessage.getBody(), AckMsg.class); assertEquals(point.ackOffsetByIndex(msgIndex), ackMsg.getAckOffset()); assertEquals(point.getStartOffset(), ackMsg.getStartOffset()); assertEquals(point.getCId(), ackMsg.getConsumerGroup()); assertEquals(point.getTopic(), ackMsg.getTopic()); assertEquals(point.getQueueId(), ackMsg.getQueueId()); assertEquals(point.getPopTime(), ackMsg.getPopTime()); assertEquals(point.getBrokerName(), ackMsg.getBrokerName()); PutMessageResult result = mock(PutMessageResult.class); when(result.getPutMessageStatus()).thenReturn(PutMessageStatus.PUT_OK); return result; }); Method method = PopBufferMergeService.class.getDeclaredMethod("putAckToStore", PopBufferMergeService.PopCheckPointWrapper.class, byte.class, AtomicInteger.class); method.setAccessible(true); method.invoke(popBufferMergeService, pointWrapper, msgIndex, count); verify(escapeBridge, times(1)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class PopInflightMessageCounterTest { @Test public void testNum() { BrokerController brokerController = mock(BrokerController.class); long brokerStartTime = System.currentTimeMillis(); when(brokerController.getShouldStartTime()).thenReturn(brokerStartTime); PopInflightMessageCounter counter = new PopInflightMessageCounter(brokerController); final String topic = "topic"; final String group = "group"; assertEquals(0, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.incrementInFlightMessageNum(topic, group, 0, 3); assertEquals(3, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.decrementInFlightMessageNum(topic, group, System.currentTimeMillis(), 0, 1); assertEquals(2, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.decrementInFlightMessageNum(topic, group, System.currentTimeMillis() - 1000, 0, 1); assertEquals(2, counter.getGroupPopInFlightMessageNum(topic, group, 0)); PopCheckPoint popCheckPoint = new PopCheckPoint(); popCheckPoint.setTopic(topic); popCheckPoint.setCId(group); popCheckPoint.setQueueId(0); popCheckPoint.setPopTime(System.currentTimeMillis()); counter.decrementInFlightMessageNum(popCheckPoint); assertEquals(1, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.decrementInFlightMessageNum(topic, group, System.currentTimeMillis(), 0 ,1); assertEquals(0, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.decrementInFlightMessageNum(topic, group, System.currentTimeMillis(), 0, 1); assertEquals(0, counter.getGroupPopInFlightMessageNum(topic, group, 0)); } @Test public void testClearInFlightMessageNum() { BrokerController brokerController = mock(BrokerController.class); long brokerStartTime = System.currentTimeMillis(); when(brokerController.getShouldStartTime()).thenReturn(brokerStartTime); PopInflightMessageCounter counter = new PopInflightMessageCounter(brokerController); final String topic = "topic"; final String group = "group"; assertEquals(0, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.incrementInFlightMessageNum(topic, group, 0, 3); assertEquals(3, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.clearInFlightMessageNumByTopicName("errorTopic"); assertEquals(3, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.clearInFlightMessageNumByTopicName(topic); assertEquals(0, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.incrementInFlightMessageNum(topic, group, 0, 3); assertEquals(3, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.clearInFlightMessageNumByGroupName("errorGroup"); assertEquals(3, counter.getGroupPopInFlightMessageNum(topic, group, 0)); counter.clearInFlightMessageNumByGroupName(group); assertEquals(0, counter.getGroupPopInFlightMessageNum(topic, group, 0)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PopLiteMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.lite.AbstractLiteLifecycleManager; import org.apache.rocketmq.broker.lite.LiteEventDispatcher; import org.apache.rocketmq.broker.longpolling.PopLiteLongPollingService; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.PopConsumerLockService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.doReturn; @RunWith(MockitoJUnitRunner.class) public class PopLiteMessageProcessorTest { @Mock private BrokerController brokerController; @Mock private MessageStore messageStore; @Mock private LiteEventDispatcher liteEventDispatcher; @Mock private PopLiteLongPollingService popLiteLongPollingService; @Mock private PopConsumerLockService lockService; @Mock private ConsumerOrderInfoManager consumerOrderInfoManager; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private PopMessageProcessor popMessageProcessor; @Mock private TopicConfigManager topicConfigManager; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private AbstractLiteLifecycleManager liteLifecycleManager; private BrokerConfig brokerConfig; private PopLiteMessageProcessor popLiteMessageProcessor; @Before public void setUp() throws Exception { brokerConfig = new BrokerConfig(); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(brokerController.getPopMessageProcessor()).thenReturn(popMessageProcessor); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(brokerController.getLiteLifecycleManager()).thenReturn(liteLifecycleManager); PopLiteMessageProcessor testObject = new PopLiteMessageProcessor(brokerController, liteEventDispatcher); FieldUtils.writeDeclaredField(testObject, "popLiteLongPollingService", popLiteLongPollingService, true); FieldUtils.writeDeclaredField(testObject, "lockService", lockService, true); FieldUtils.writeDeclaredField(testObject, "consumerOrderInfoManager", consumerOrderInfoManager, true); popLiteMessageProcessor = Mockito.spy(testObject); } @Test public void testRejectRequest() { assertFalse(popLiteMessageProcessor.rejectRequest()); } @Test public void testTransformOrderCountInfo_empty() { StringBuilder result = popLiteMessageProcessor.transformOrderCountInfo(new StringBuilder(), 3); assertEquals("0;0;0", result.toString()); } @Test public void testTransformOrderCountInfo_onlyQueueIdInfo() { StringBuilder input = new StringBuilder("0" + MessageConst.KEY_SEPARATOR + "0" + MessageConst.KEY_SEPARATOR + "2"); StringBuilder result = popLiteMessageProcessor.transformOrderCountInfo(input, 3); assertEquals("2;2;2", result.toString()); } @Test public void testTransformOrderCountInfo_consumeCountAndQueueIdInfo() { StringBuilder input = new StringBuilder("0 qo0%0 0;0 qo0%1 1;0 0 1"); StringBuilder result = popLiteMessageProcessor.transformOrderCountInfo(input, 2); assertEquals("0 qo0%0 0;0 qo0%1 1", result.toString()); } @Test public void testIsFifoBlocked() { when(consumerOrderInfoManager.checkBlock(anyString(), anyString(), anyString(), anyInt(), anyLong())) .thenReturn(true); assertTrue(popLiteMessageProcessor.isFifoBlocked("attemptId", "group", "lmqName", 1000L)); verify(consumerOrderInfoManager).checkBlock("attemptId", "lmqName", "group", 0, 1000L); } @Test public void testGetPopOffset_normal() throws ConsumeQueueException { String group = "group"; String lmqName = "lmqName"; long consumerOffset = 100L; // exist when(consumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(consumerOffset); when(consumerOffsetManager.queryThenEraseResetOffset(lmqName, group, 0)).thenReturn(null); assertEquals(consumerOffset, popLiteMessageProcessor.getPopOffset(group, lmqName)); // not exist, init mode long initOffset = 10L; when(consumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(-1L); when(popMessageProcessor.getInitOffset(lmqName, group, 0, 1, true)).thenReturn(initOffset); assertEquals(initOffset, popLiteMessageProcessor.getPopOffset(group, lmqName)); verify(consumerOffsetManager, times(2)).queryThenEraseResetOffset(lmqName, group, 0); verify(consumerOrderInfoManager, never()).clearBlock(anyString(), anyString(), anyInt()); verify(consumerOffsetManager, never()).commitOffset(anyString(), anyString(), anyString(), anyInt(), anyLong()); } @Test public void testGetPopOffset_resetOffset() { String group = "group"; String lmqName = "lmq"; long consumerOffset = 100L; long resetOffset = 50L; when(consumerOffsetManager.queryOffset(group, lmqName, 0)).thenReturn(consumerOffset); when(consumerOffsetManager.queryThenEraseResetOffset(lmqName, group, 0)).thenReturn(resetOffset); assertEquals(resetOffset, popLiteMessageProcessor.getPopOffset(group, lmqName)); verify(consumerOffsetManager).queryOffset(group, lmqName, 0); verify(consumerOffsetManager).queryThenEraseResetOffset(lmqName, group, 0); verify(consumerOrderInfoManager).clearBlock(lmqName, group, 0); verify(consumerOffsetManager).commitOffset("ResetOffset", group, lmqName, 0, resetOffset); } @SuppressWarnings("unchecked") @Test public void testPopByClientId_noEvent() { Iterator mockIterator = mock(Iterator.class); when(mockIterator.hasNext()).thenReturn(false); when(liteEventDispatcher.getEventIterator("clientId")).thenReturn(mockIterator); Pair result = popLiteMessageProcessor.popByClientId( "clientHost", "parentTopic", "group", "clientId", System.currentTimeMillis(), 6000L, 32, "attemptId"); assertEquals(0, result.getObject1().length()); assertEquals(0, result.getObject2().getMessageCount()); verify(liteEventDispatcher).getEventIterator("clientId"); } @SuppressWarnings("unchecked") @Test public void testPopByClientId_oneEvent() { String event = "lmqName"; int msgCount = 1; GetMessageResult mockResult = mockGetMessageResult(GetMessageStatus.FOUND, msgCount, 100L); long pollTime = System.currentTimeMillis(); Iterator mockIterator = mock(Iterator.class); when(mockIterator.hasNext()).thenReturn(true, false); when(mockIterator.next()).thenReturn(event); when(liteEventDispatcher.getEventIterator("clientId")).thenReturn(mockIterator); doReturn(new Pair<>(new StringBuilder("0"), mockResult)) .when(popLiteMessageProcessor) .popLiteTopic(anyString(), anyString(), anyString(), anyString(), anyLong(), anyLong(), anyLong(), anyString()); Pair result = popLiteMessageProcessor.popByClientId( "clientHost", "parentTopic", "group", "clientId", pollTime, 6000L, 32, "attemptId"); assertEquals(msgCount, result.getObject2().getMessageCount()); verify(mockIterator, times(2)).hasNext(); verify(popLiteMessageProcessor).popLiteTopic("parentTopic" ,"clientHost", "group", event, 32L, pollTime, 6000L, "attemptId"); } @SuppressWarnings("unchecked") @Test public void testPopByClientId_resultFull() { String event1 = "lmqName1"; String event2 = "lmqName2"; int msgCount = 1; GetMessageResult mockResult = mockGetMessageResult(GetMessageStatus.FOUND, msgCount, 100L); long pollTime = System.currentTimeMillis(); Iterator mockIterator = mock(Iterator.class); when(mockIterator.hasNext()).thenReturn(true, true, true, true, false); when(mockIterator.next()).thenReturn(event1, event2, "event3", "event4"); when(liteEventDispatcher.getEventIterator("clientId")).thenReturn(mockIterator); doReturn(new Pair<>(new StringBuilder("0"), mockResult)) .when(popLiteMessageProcessor) .popLiteTopic(anyString(), anyString(), anyString(), anyString(), anyLong(), anyLong(), anyLong(), anyString()); Pair result = popLiteMessageProcessor.popByClientId( "clientHost", "parentTopic", "group", "clientId", pollTime, 6000L, 2, "attemptId"); assertEquals(2, result.getObject2().getMessageCount()); assertEquals("0;0", result.getObject1().toString()); verify(mockIterator, times(2)).hasNext(); verify(popLiteMessageProcessor).popLiteTopic("parentTopic", "clientHost", "group", event1, 2L, pollTime, 6000L, "attemptId"); verify(popLiteMessageProcessor).popLiteTopic("parentTopic", "clientHost", "group", event2, 1L, pollTime, 6000L, "attemptId"); } @SuppressWarnings("unchecked") @Test public void testPopByClientId_duplicateEvent() { String event1 = "lmqName1"; String event2 = "lmqName2"; String event3 = "lmqName1"; int msgCount = 1; GetMessageResult mockResult = mockGetMessageResult(GetMessageStatus.FOUND, msgCount, 100L); long pollTime = System.currentTimeMillis(); Iterator mockIterator = mock(Iterator.class); when(mockIterator.hasNext()).thenReturn(true, true, true, false); when(mockIterator.next()).thenReturn(event1, event2, event3); when(liteEventDispatcher.getEventIterator("clientId")).thenReturn(mockIterator); doReturn(new Pair<>(new StringBuilder("0"), mockResult)) .when(popLiteMessageProcessor) .popLiteTopic(anyString(), anyString(), anyString(), anyString(), anyLong(), anyLong(), anyLong(), anyString()); Pair result = popLiteMessageProcessor.popByClientId( "clientHost", "parentTopic", "group", "clientId", pollTime, 6000L, 32, "attemptId"); assertEquals(2, result.getObject2().getMessageCount()); assertEquals("0;0", result.getObject1().toString()); verify(mockIterator, times(4)).hasNext(); verify(popLiteMessageProcessor).popLiteTopic("parentTopic", "clientHost", "group", event1, 32L, pollTime, 6000L, "attemptId"); verify(popLiteMessageProcessor).popLiteTopic("parentTopic", "clientHost", "group", event2, 31L, pollTime, 6000L, "attemptId"); } @Test public void testGetMessage_found() { String group = "group"; String lmqName = "lmqName"; String clientHost = "clientHost"; long offset = 50L; int batchSize = 16; GetMessageResult mockResult = mockGetMessageResult(GetMessageStatus.FOUND, 1, 100L); when(messageStore.getMessage(group, lmqName, 0, offset, batchSize, null)).thenReturn(mockResult); GetMessageResult getMessageResult = popLiteMessageProcessor.getMessage(clientHost, group, lmqName, offset, batchSize); assertEquals(mockResult, getMessageResult); verify(consumerOffsetManager, never()).commitOffset(clientHost, group, lmqName, 0, 100L); } @Test public void testGetMessage_notFound() { String group = "group"; String lmqName = "lmqName"; String clientHost = "clientHost"; long offset = 50L; long nextBeginOffset = 100L; int batchSize = 16; GetMessageResult firstResult = mockGetMessageResult(GetMessageStatus.MESSAGE_WAS_REMOVING, 0, nextBeginOffset); when(messageStore.getMessage(group, lmqName, 0, offset, batchSize, null)).thenReturn(firstResult); GetMessageResult secondResult = mockGetMessageResult(GetMessageStatus.FOUND, batchSize, nextBeginOffset + batchSize); when(messageStore.getMessage(group, lmqName, 0, nextBeginOffset, batchSize, null)).thenReturn(secondResult); GetMessageResult getMessageResult = popLiteMessageProcessor.getMessage(clientHost, group, lmqName, offset, batchSize); assertEquals(secondResult, getMessageResult); assertEquals(116, secondResult.getNextBeginOffset()); verify(consumerOffsetManager).commitOffset("CorrectOffset", group, lmqName, 0, nextBeginOffset); } @Test public void testHandleGetMessageResult_nullResult() { Pair result = popLiteMessageProcessor.handleGetMessageResult( null, "parentTopic", "group", "lmqName", System.currentTimeMillis(), 6000L, "attemptId"); assertNull(result); } @Test public void testHandleGetMessageResult_found() { int msgCount = 2; GetMessageResult getResult = mockGetMessageResult(GetMessageStatus.FOUND, msgCount, 100L); getResult.getMessageQueueOffset().add(0L); getResult.getMessageQueueOffset().add(1L); doNothing().when(popLiteMessageProcessor).recordPopLiteMetrics(any(), anyString(), anyString()); Pair result = popLiteMessageProcessor.handleGetMessageResult( getResult, "parentTopic", "group", "lmqName", System.currentTimeMillis(), 6000L, "attemptId"); assertNotNull(result); assertEquals(getResult, result.getObject2()); assertEquals("0;0", result.getObject1().toString()); } @Test public void testPopLiteTopic_lockFailed() { when(lockService.tryLock(anyString())).thenReturn(false); Pair result = popLiteMessageProcessor.popLiteTopic("parentTopic", "clientHost", "group", "lmqName", 32L, System.currentTimeMillis(), 6000L, "attemptId"); assertNull(result); verify(lockService).tryLock(anyString()); verify(lockService, never()).unlock(anyString()); } @Test public void testPopLiteTopic_fifoBlocked() { when(lockService.tryLock(anyString())).thenReturn(true); when(consumerOrderInfoManager.checkBlock(anyString(), anyString(), anyString(), anyInt(), anyLong())) .thenReturn(true); Pair result = popLiteMessageProcessor.popLiteTopic("parentTopic", "clientHost", "group", "lmqName", 32L, System.currentTimeMillis(), 6000L, "attemptId"); assertThat(result).isNull(); verify(lockService).tryLock(anyString()); verify(lockService).unlock(anyString()); } @Test public void testPopLiteTopic_lmqNotExist() { when(liteLifecycleManager.isLmqExist("lmqName")).thenReturn(false); brokerConfig.setEnableLiteEventMode(false); Pair result = popLiteMessageProcessor.popLiteTopic("parentTopic", "clientHost", "group", "lmqName", 32L, System.currentTimeMillis(), 6000L, "attemptId"); assertThat(result).isNull(); verify(lockService, never()).tryLock(anyString()); } @Test public void testPopLiteTopic_found() { when(lockService.tryLock(anyString())).thenReturn(true); when(consumerOrderInfoManager.checkBlock(anyString(), anyString(), anyString(), anyInt(), anyLong())) .thenReturn(false); GetMessageResult mockResult = mockGetMessageResult(GetMessageStatus.FOUND, 1, 100L); when(messageStore.getMessage("group", "lmqName", 0, 0, 32, null)).thenReturn(mockResult); Pair result = popLiteMessageProcessor.popLiteTopic("parentTopic", "clientHost", "group", "lmqName", 32L, System.currentTimeMillis(), 6000L, "attemptId"); assertEquals(mockResult, result.getObject2()); verify(lockService).tryLock(anyString()); verify(lockService).unlock(anyString()); } @Test public void testPreCheck() { final String parentTopic = "parentTopic"; final String group = "group"; final TopicConfig topicConfig = new TopicConfig(); final SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); final ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand response = RemotingCommand.createResponseCommand(PopLiteMessageResponseHeader.class); PopLiteMessageRequestHeader requestHeader = new PopLiteMessageRequestHeader(); when(topicConfigManager.selectTopicConfig(parentTopic)).thenReturn(topicConfig); when(subscriptionGroupManager.findSubscriptionGroupConfig(group)).thenReturn(groupConfig); // timeout too much requestHeader.setBornTime(System.currentTimeMillis() - 60000); requestHeader.setPollTime(30000); RemotingCommand result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.POLLING_TIMEOUT, result.getCode()); // not readable brokerConfig.setBrokerPermission(PermName.PERM_WRITE); requestHeader.setBornTime(System.currentTimeMillis()); requestHeader.setPollTime(30000); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.NO_PERMISSION, result.getCode()); brokerConfig.setBrokerPermission(PermName.PERM_READ | PermName.PERM_WRITE); // topic not exist requestHeader.setTopic("whatever"); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.TOPIC_NOT_EXIST, result.getCode()); // not lite topic type requestHeader.setTopic(parentTopic); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.INVALID_PARAMETER, result.getCode()); // group not exist topicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); topicConfig.setTopicMessageType(TopicMessageType.LITE); requestHeader.setConsumerGroup("whatever"); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST, result.getCode()); // group disable groupConfig.setConsumeEnable(false); requestHeader.setConsumerGroup(group); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.NO_PERMISSION, result.getCode()); groupConfig.setConsumeEnable(true); // bind topic not match groupConfig.setLiteBindTopic("otherTopic"); requestHeader.setMaxMsgNum(32); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertEquals(ResponseCode.INVALID_PARAMETER, result.getCode()); // normal groupConfig.setLiteBindTopic(parentTopic); result = popLiteMessageProcessor.preCheck(ctx, requestHeader, response); assertNull(result); } private GetMessageResult mockGetMessageResult(GetMessageStatus status, int messageCount, long nextBeginOffset) { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(status); getMessageResult.setMinOffset(0); getMessageResult.setMaxOffset(1024); getMessageResult.setNextBeginOffset(nextBeginOffset); if (GetMessageStatus.FOUND.equals(status)) { for (int i = 0; i < messageCount; i++) { getMessageResult.addMessage(Mockito.mock(SelectMappedBufferResult.class)); } } return getMessageResult; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.embedded.EmbeddedChannel; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import static org.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PopMessageProcessorTest { private PopMessageProcessor popMessageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; private final EmbeddedChannel embeddedChannel = new EmbeddedChannel(); @Mock private DefaultMessageStore messageStore; private ClientChannelInfo clientChannelInfo; private String group = "FooBarGroup"; private String topic = "FooBar"; @Before public void init() { brokerController.setMessageStore(messageStore); brokerController.getBrokerConfig().setEnablePopBufferMerge(true); // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); popMessageProcessor = new PopMessageProcessor(brokerController); when(handlerContext.channel()).thenReturn(embeddedChannel); brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig(topic)); clientChannelInfo = new ClientChannelInfo(embeddedChannel); ConsumerData consumerData = createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientChannelInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), false); } @Test public void testProcessRequest_TopicNotExist() throws RemotingCommandException { when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); brokerController.getTopicConfigManager().getTopicConfigTable().remove(topic); final RemotingCommand request = createPopMsgCommand(); RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); assertThat(response.getRemark()).contains("topic[" + topic + "] not exist"); } @Test public void testProcessRequest_Found() throws RemotingCommandException, InterruptedException { GetMessageResult getMessageResult = createGetMessageResult(1); when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPopMsgCommand(); popMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_MsgWasRemoving() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(1); getMessageResult.setStatus(GetMessageStatus.MESSAGE_WAS_REMOVING); when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPopMsgCommand(); popMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_NoMsgInQueue() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(0); getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPopMsgCommand(); RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNull(); } @Test public void testProcessRequest_whenTimerWheelIsFalse() throws RemotingCommandException { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setTimerWheelEnable(false); when(messageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig); final RemotingCommand request = createPopMsgCommand(); RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); assertThat(response.getRemark()).contains("pop message is forbidden because timerWheelEnable is false"); } @Test public void testGetInitOffset_retryTopic() throws RemotingCommandException { when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); String newGroup = group + "-" + System.currentTimeMillis(); String retryTopic = KeyBuilder.buildPopRetryTopic(topic, newGroup); long minOffset = 100L; when(messageStore.getMinOffsetInQueue(retryTopic, 0)).thenReturn(minOffset); brokerController.getTopicConfigManager().getTopicConfigTable().put(retryTopic, new TopicConfig(retryTopic, 1, 1)); GetMessageResult getMessageResult = createGetMessageResult(0); when(messageStore.getMessageAsync(eq(newGroup), anyString(), anyInt(), anyLong(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(getMessageResult)); long offset = brokerController.getConsumerOffsetManager().queryOffset(newGroup, retryTopic, 0); assertEquals(-1, offset); RemotingCommand request = createPopMsgCommand(newGroup, topic, 0, ConsumeInitMode.MAX); popMessageProcessor.processRequest(handlerContext, request); offset = brokerController.getConsumerOffsetManager().queryOffset(newGroup, retryTopic, 0); assertEquals(minOffset, offset); when(messageStore.getMinOffsetInQueue(retryTopic, 0)).thenReturn(minOffset * 2); popMessageProcessor.processRequest(handlerContext, request); offset = brokerController.getConsumerOffsetManager().queryOffset(newGroup, retryTopic, 0); assertEquals(minOffset, offset); // will not entry getInitOffset() again messageStore.getMinOffsetInQueue(retryTopic, 0); // prevent UnnecessaryStubbingException } @Test public void testGetInitOffset_normalTopic() throws RemotingCommandException, ConsumeQueueException { long maxOffset = 999L; when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); when(messageStore.getMaxOffsetInQueue(topic, 0)).thenReturn(maxOffset); String newGroup = group + "-" + System.currentTimeMillis(); GetMessageResult getMessageResult = createGetMessageResult(0); when(messageStore.getMessageAsync(eq(newGroup), anyString(), anyInt(), anyLong(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(getMessageResult)); long offset = brokerController.getConsumerOffsetManager().queryOffset(newGroup, topic, 0); assertEquals(-1, offset); RemotingCommand request = createPopMsgCommand(newGroup, topic, 0, ConsumeInitMode.MAX); popMessageProcessor.processRequest(handlerContext, request); offset = brokerController.getConsumerOffsetManager().queryOffset(newGroup, topic, 0); assertEquals(maxOffset - 1, offset); // checkInMem return false when(messageStore.getMaxOffsetInQueue(topic, 0)).thenReturn(maxOffset * 2); popMessageProcessor.processRequest(handlerContext, request); offset = brokerController.getConsumerOffsetManager().queryOffset(newGroup, topic, 0); assertEquals(maxOffset - 1, offset); // will not entry getInitOffset() again messageStore.getMaxOffsetInQueue(topic, 0); // prevent UnnecessaryStubbingException } @Test public void testBuildCkMsgJsonParsing() { PopCheckPoint ck = new PopCheckPoint(); ck.setTopic("TestTopic"); ck.setQueueId(1); ck.setStartOffset(100L); ck.setCId("TestConsumer"); ck.setPopTime(System.currentTimeMillis()); ck.setBrokerName("TestBroker"); int reviveQid = 0; PopMessageProcessor processor = new PopMessageProcessor(brokerController); MessageExtBrokerInner result = processor.buildCkMsg(ck, reviveQid); String jsonBody = new String(result.getBody(), StandardCharsets.UTF_8); PopCheckPoint actual = JSON.parseObject(jsonBody, PopCheckPoint.class); assertEquals(ck.getTopic(), actual.getTopic()); assertEquals(ck.getQueueId(), actual.getQueueId()); assertEquals(ck.getStartOffset(), actual.getStartOffset()); assertEquals(ck.getCId(), actual.getCId()); assertEquals(ck.getPopTime(), actual.getPopTime()); assertEquals(ck.getBrokerName(), actual.getBrokerName()); assertEquals(ck.getReviveTime(), actual.getReviveTime()); } private RemotingCommand createPopMsgCommand() { return createPopMsgCommand(group, topic, -1, ConsumeInitMode.MAX); } private RemotingCommand createPopMsgCommand(String group, String topic, int queueId, int initMode) { PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setConsumerGroup(group); requestHeader.setMaxMsgNums(30); requestHeader.setQueueId(queueId); requestHeader.setTopic(topic); requestHeader.setInvisibleTime(10_000); requestHeader.setInitMode(initMode); requestHeader.setOrder(false); requestHeader.setPollTime(15_000); requestHeader.setBornTime(System.currentTimeMillis()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); return request; } private GetMessageResult createGetMessageResult(int msgCnt) { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); getMessageResult.setMinOffset(100); getMessageResult.setMaxOffset(1024); getMessageResult.setNextBeginOffset(516); for (int i = 0; i < msgCnt; i++) { ByteBuffer bb = ByteBuffer.allocate(64); bb.putLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION, System.currentTimeMillis()); getMessageResult.addMessage(new SelectMappedBufferResult(200, bb, 64, new DefaultMappedFile())); } return getMessageResult; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson2.JSON; import org.apache.commons.lang3.tuple.Triple; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.metrics.PopMetricsManager; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.utils.DataConverter; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.BatchAckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class PopReviveServiceTest { private static final String CLUSTER_NAME = "test"; private static final String REVIVE_TOPIC = PopAckConstants.buildClusterReviveTopic(CLUSTER_NAME); private static final int REVIVE_QUEUE_ID = 0; private static final String GROUP = "group"; private static final String TOPIC = "topic"; private static final SocketAddress STORE_HOST = NetworkUtil.string2SocketAddress("127.0.0.1:8080"); private static final Long INVISIBLE_TIME = 1000L; @Mock private MessageStore messageStore; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private TopicConfigManager topicConfigManager; @Mock private TimerMessageStore timerMessageStore; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private BrokerController brokerController; @Mock private EscapeBridge escapeBridge; @Mock private BrokerMetricsManager brokerMetricsManager; @Mock private PopMetricsManager popMetricsManager; private PopMessageProcessor popMessageProcessor; private BrokerConfig brokerConfig; private PopReviveService popReviveService; @Before public void before() { brokerConfig = new BrokerConfig(); brokerConfig.setBrokerClusterName(CLUSTER_NAME); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); when(messageStore.getTimerMessageStore()).thenReturn(timerMessageStore); when(timerMessageStore.getDequeueBehind()).thenReturn(0L); when(timerMessageStore.getEnqueueBehind()).thenReturn(0L); when(topicConfigManager.selectTopicConfig(anyString())).thenReturn(new TopicConfig()); when(subscriptionGroupManager.findSubscriptionGroupConfig(anyString())).thenReturn(new SubscriptionGroupConfig()); // Initialize BrokerMetricsManager for tests when(brokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); when(brokerMetricsManager.getPopMetricsManager()).thenReturn(popMetricsManager); popMessageProcessor = new PopMessageProcessor(brokerController); // a real one, not mock when(brokerController.getPopMessageProcessor()).thenReturn(popMessageProcessor); popReviveService = spy(new PopReviveService(brokerController, REVIVE_TOPIC, REVIVE_QUEUE_ID)); popReviveService.setShouldRunPopRevive(true); } @Test public void testWhenAckMoreThanCk() throws Throwable { brokerConfig.setEnableSkipLongAwaitingAck(true); long maxReviveOffset = 4; when(consumerOffsetManager.queryOffset(PopAckConstants.REVIVE_GROUP, REVIVE_TOPIC, REVIVE_QUEUE_ID)) .thenReturn(0L); List reviveMessageExtList = new ArrayList<>(); long basePopTime = System.currentTimeMillis(); { // put a pair of ck and ack PopCheckPoint ck = buildPopCheckPoint(1, basePopTime, 1); reviveMessageExtList.add(buildCkMsg(ck)); reviveMessageExtList.add(buildAckMsg(buildAckMsg(1, basePopTime), ck.getReviveTime(), 1, basePopTime)); } { for (int i = 2; i <= maxReviveOffset; i++) { long popTime = basePopTime + i; PopCheckPoint ck = buildPopCheckPoint(i, popTime, i); reviveMessageExtList.add(buildAckMsg(buildAckMsg(i, popTime), ck.getReviveTime(), i, popTime)); } } doReturn(reviveMessageExtList, new ArrayList<>()).when(popReviveService).getReviveMessage(anyLong(), anyInt()); PopReviveService.ConsumeReviveObj consumeReviveObj = new PopReviveService.ConsumeReviveObj(); popReviveService.consumeReviveMessage(consumeReviveObj); assertEquals(1, consumeReviveObj.map.size()); ArgumentCaptor commitOffsetCaptor = ArgumentCaptor.forClass(Long.class); doNothing().when(consumerOffsetManager).commitOffset(anyString(), anyString(), anyString(), anyInt(), commitOffsetCaptor.capture()); popReviveService.mergeAndRevive(consumeReviveObj); assertEquals(1, commitOffsetCaptor.getValue().longValue()); } @Test public void testSkipLongWaiteAck() throws Throwable { brokerConfig.setEnableSkipLongAwaitingAck(true); brokerConfig.setReviveAckWaitMs(TimeUnit.SECONDS.toMillis(2)); long maxReviveOffset = 4; when(consumerOffsetManager.queryOffset(PopAckConstants.REVIVE_GROUP, REVIVE_TOPIC, REVIVE_QUEUE_ID)) .thenReturn(0L); List reviveMessageExtList = new ArrayList<>(); long basePopTime = System.currentTimeMillis() - brokerConfig.getReviveAckWaitMs() * 2; { // put a pair of ck and ack PopCheckPoint ck = buildPopCheckPoint(1, basePopTime, 1); reviveMessageExtList.add(buildCkMsg(ck)); reviveMessageExtList.add(buildAckMsg(buildAckMsg(1, basePopTime), ck.getReviveTime(), 1, basePopTime)); } { for (int i = 2; i <= maxReviveOffset; i++) { long popTime = basePopTime + i; PopCheckPoint ck = buildPopCheckPoint(i, popTime, i); reviveMessageExtList.add(buildAckMsg(buildAckMsg(i, popTime), ck.getReviveTime(), i, popTime)); } } doReturn(reviveMessageExtList, new ArrayList<>()).when(popReviveService).getReviveMessage(anyLong(), anyInt()); PopReviveService.ConsumeReviveObj consumeReviveObj = new PopReviveService.ConsumeReviveObj(); popReviveService.consumeReviveMessage(consumeReviveObj); assertEquals(4, consumeReviveObj.map.size()); ArgumentCaptor commitOffsetCaptor = ArgumentCaptor.forClass(Long.class); doNothing().when(consumerOffsetManager).commitOffset(anyString(), anyString(), anyString(), anyInt(), commitOffsetCaptor.capture()); popReviveService.mergeAndRevive(consumeReviveObj); assertEquals(maxReviveOffset, commitOffsetCaptor.getValue().longValue()); } @Test public void testSkipLongWaiteAckWithSameAck() throws Throwable { brokerConfig.setEnableSkipLongAwaitingAck(true); brokerConfig.setReviveAckWaitMs(TimeUnit.SECONDS.toMillis(2)); long maxReviveOffset = 4; when(consumerOffsetManager.queryOffset(PopAckConstants.REVIVE_GROUP, REVIVE_TOPIC, REVIVE_QUEUE_ID)) .thenReturn(0L); List reviveMessageExtList = new ArrayList<>(); long basePopTime = System.currentTimeMillis() - brokerConfig.getReviveAckWaitMs() * 2; { for (int i = 2; i <= maxReviveOffset; i++) { long popTime = basePopTime + i; PopCheckPoint ck = buildPopCheckPoint(0, basePopTime, i); reviveMessageExtList.add(buildAckMsg(buildAckMsg(0, basePopTime), ck.getReviveTime(), i, popTime)); } } doReturn(reviveMessageExtList, new ArrayList<>()).when(popReviveService).getReviveMessage(anyLong(), anyInt()); PopReviveService.ConsumeReviveObj consumeReviveObj = new PopReviveService.ConsumeReviveObj(); popReviveService.consumeReviveMessage(consumeReviveObj); assertEquals(1, consumeReviveObj.map.size()); ArgumentCaptor commitOffsetCaptor = ArgumentCaptor.forClass(Long.class); doNothing().when(consumerOffsetManager).commitOffset(anyString(), anyString(), anyString(), anyInt(), commitOffsetCaptor.capture()); popReviveService.mergeAndRevive(consumeReviveObj); assertEquals(maxReviveOffset, commitOffsetCaptor.getValue().longValue()); } @Test public void testReviveMsgFromCk_messageFound_writeRetryOK() throws Throwable { PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); StringBuilder actualRetryTopic = new StringBuilder(); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(new MessageExt(), "", false))); when(escapeBridge.putMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenAnswer(invocation -> { MessageExtBrokerInner msg = invocation.getArgument(0); actualRetryTopic.append(msg.getTopic()); return new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)); }); popReviveService.mergeAndRevive(reviveObj); Assert.assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, GROUP, false), actualRetryTopic.toString()); verify(escapeBridge, times(1)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(0)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageFound_writeRetryFailed_rewriteCK() throws Throwable { PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); StringBuilder actualRetryTopic = new StringBuilder(); StringBuilder actualReviveTopic = new StringBuilder(); AtomicLong actualInvisibleTime = new AtomicLong(0L); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(new MessageExt(), "", false))); when(escapeBridge.putMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenAnswer(invocation -> { MessageExtBrokerInner msg = invocation.getArgument(0); actualRetryTopic.append(msg.getTopic()); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED)); }); when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenAnswer(invocation -> { MessageExtBrokerInner msg = invocation.getArgument(0); actualReviveTopic.append(msg.getTopic()); PopCheckPoint rewriteCK = JSON.parseObject(msg.getBody(), PopCheckPoint.class); actualInvisibleTime.set(rewriteCK.getReviveTime()); return new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)); }); popReviveService.mergeAndRevive(reviveObj); // Wait for async operations to complete Thread.sleep(1000); Assert.assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, GROUP, false), actualRetryTopic.toString()); Assert.assertEquals(REVIVE_TOPIC, actualReviveTopic.toString()); Assert.assertEquals(INVISIBLE_TIME + 10 * 1000L, actualInvisibleTime.get()); // first interval is 10s verify(escapeBridge, times(1)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(1)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageFound_writeRetryFailed_rewriteCK_end() throws Throwable { brokerConfig.setSkipWhenCKRePutReachMaxTimes(true); PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); ck.setRePutTimes("17"); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); StringBuilder actualRetryTopic = new StringBuilder(); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(new MessageExt(), "", false))); when(escapeBridge.putMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenAnswer(invocation -> { MessageExtBrokerInner msg = invocation.getArgument(0); actualRetryTopic.append(msg.getTopic()); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED)); }); popReviveService.mergeAndRevive(reviveObj); Assert.assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, GROUP, false), actualRetryTopic.toString()); verify(escapeBridge, times(1)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(0)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageFound_writeRetryFailed_rewriteCK_noEnd() throws Throwable { brokerConfig.setSkipWhenCKRePutReachMaxTimes(false); PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); ck.setRePutTimes(Byte.MAX_VALUE + ""); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); StringBuilder actualRetryTopic = new StringBuilder(); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(new MessageExt(), "", false))); when(escapeBridge.putMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenAnswer(invocation -> { MessageExtBrokerInner msg = invocation.getArgument(0); actualRetryTopic.append(msg.getTopic()); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED)); }); popReviveService.mergeAndRevive(reviveObj); Assert.assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, GROUP, false), actualRetryTopic.toString()); verify(escapeBridge, times(1)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(1)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageNotFound_noRetry() throws Throwable { PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(null, "", false))); popReviveService.mergeAndRevive(reviveObj); verify(escapeBridge, times(0)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(0)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageNotFound_needRetry() throws Throwable { PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); StringBuilder actualReviveTopic = new StringBuilder(); AtomicLong actualInvisibleTime = new AtomicLong(0L); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(null, "", true))); when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenAnswer(invocation -> { MessageExtBrokerInner msg = invocation.getArgument(0); actualReviveTopic.append(msg.getTopic()); PopCheckPoint rewriteCK = JSON.parseObject(msg.getBody(), PopCheckPoint.class); actualInvisibleTime.set(rewriteCK.getReviveTime()); return new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)); }); popReviveService.mergeAndRevive(reviveObj); Assert.assertEquals(REVIVE_TOPIC, actualReviveTopic.toString()); Assert.assertEquals(INVISIBLE_TIME + 10 * 1000L, actualInvisibleTime.get()); // first interval is 10s verify(escapeBridge, times(0)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(1)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageNotFound_needRetry_end() throws Throwable { brokerConfig.setSkipWhenCKRePutReachMaxTimes(true); PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); ck.setRePutTimes("17"); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(null, "", true))); popReviveService.mergeAndRevive(reviveObj); verify(escapeBridge, times(0)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(0)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromCk_messageNotFound_needRetry_noEnd() throws Throwable { brokerConfig.setSkipWhenCKRePutReachMaxTimes(false); PopCheckPoint ck = buildPopCheckPoint(0, 0, 0); ck.setRePutTimes(Byte.MAX_VALUE + ""); PopReviveService.ConsumeReviveObj reviveObj = new PopReviveService.ConsumeReviveObj(); reviveObj.map.put("", ck); reviveObj.endTime = System.currentTimeMillis(); when(escapeBridge.getMessageAsync(anyString(), anyLong(), anyInt(), anyString(), anyBoolean())) .thenReturn(CompletableFuture.completedFuture(Triple.of(null, "", true))); popReviveService.mergeAndRevive(reviveObj); verify(escapeBridge, times(0)).putMessageToSpecificQueue(any(MessageExtBrokerInner.class)); // write retry verify(messageStore, times(1)).putMessage(any(MessageExtBrokerInner.class)); // rewrite CK } @Test public void testReviveMsgFromBatchAck() throws Throwable { brokerConfig.setEnableSkipLongAwaitingAck(true); when(consumerOffsetManager.queryOffset(PopAckConstants.REVIVE_GROUP, REVIVE_TOPIC, REVIVE_QUEUE_ID)).thenReturn(0L); List reviveMessageExtList = new ArrayList<>(); long basePopTime = System.currentTimeMillis(); reviveMessageExtList.add(buildBatchAckMsg(buildBatchAckMsg(Arrays.asList(1L, 2L, 3L), basePopTime), 1, 1, basePopTime)); doReturn(reviveMessageExtList, new ArrayList<>()).when(popReviveService).getReviveMessage(anyLong(), anyInt()); PopReviveService.ConsumeReviveObj consumeReviveObj = new PopReviveService.ConsumeReviveObj(); popReviveService.consumeReviveMessage(consumeReviveObj); assertEquals(1, consumeReviveObj.map.size()); ArgumentCaptor commitOffsetCaptor = ArgumentCaptor.forClass(Long.class); doNothing().when(consumerOffsetManager).commitOffset(anyString(), anyString(), anyString(), anyInt(), commitOffsetCaptor.capture()); popReviveService.mergeAndRevive(consumeReviveObj); assertEquals(1, commitOffsetCaptor.getValue().longValue()); } public static MessageExtBrokerInner buildBatchAckMsg(BatchAckMsg batchAckMsg, long deliverMs, long reviveOffset, long deliverTime) { MessageExtBrokerInner result = buildBatchAckInnerMessage(REVIVE_TOPIC, batchAckMsg, REVIVE_QUEUE_ID, STORE_HOST, deliverMs, PopMessageProcessor.genAckUniqueId(batchAckMsg)); result.setQueueOffset(reviveOffset); result.setDeliverTimeMs(deliverMs); result.setStoreTimestamp(deliverTime); return result; } public static BatchAckMsg buildBatchAckMsg(Collection offsets, long popTime) { BatchAckMsg result = new BatchAckMsg(); result.setConsumerGroup(GROUP); result.setTopic(TOPIC); result.setQueueId(0); result.setPopTime(popTime); result.setBrokerName("broker-a"); result.getAckOffsetList().addAll(offsets); return result; } public static MessageExtBrokerInner buildBatchAckInnerMessage(String reviveTopic, AckMsg ackMsg, int reviveQid, SocketAddress host, long deliverMs, String ackUniqueId) { MessageExtBrokerInner result = new MessageExtBrokerInner(); result.setTopic(reviveTopic); result.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8)); result.setQueueId(reviveQid); result.setTags(PopAckConstants.BATCH_ACK_TAG); result.setBornTimestamp(System.currentTimeMillis()); result.setBornHost(host); result.setStoreHost(host); result.setDeliverTimeMs(deliverMs); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, ackUniqueId); result.setPropertiesString(MessageDecoder.messageProperties2String(result.getProperties())); return result; } public static PopCheckPoint buildPopCheckPoint(long startOffset, long popTime, long reviveOffset) { PopCheckPoint ck = new PopCheckPoint(); ck.setStartOffset(startOffset); ck.setPopTime(popTime); ck.setQueueId(0); ck.setCId(GROUP); ck.setTopic(TOPIC); ck.setNum((byte) 1); ck.setBitMap(0); ck.setReviveOffset(reviveOffset); ck.setInvisibleTime(INVISIBLE_TIME); ck.setBrokerName("broker-a"); return ck; } public static AckMsg buildAckMsg(long offset, long popTime) { AckMsg ackMsg = new AckMsg(); ackMsg.setAckOffset(offset); ackMsg.setStartOffset(offset); ackMsg.setConsumerGroup(GROUP); ackMsg.setTopic(TOPIC); ackMsg.setQueueId(0); ackMsg.setPopTime(popTime); ackMsg.setBrokerName("broker-a"); return ackMsg; } public static MessageExtBrokerInner buildCkMsg(PopCheckPoint ck) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(REVIVE_TOPIC); msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.CHARSET_UTF8)); msgInner.setQueueId(REVIVE_QUEUE_ID); msgInner.setTags(PopAckConstants.CK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(STORE_HOST); msgInner.setStoreHost(STORE_HOST); msgInner.setDeliverTimeMs(ck.getReviveTime() - PopAckConstants.ackTimeInterval); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genCkUniqueId(ck)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); msgInner.setQueueOffset(ck.getReviveOffset()); return msgInner; } public static MessageExtBrokerInner buildAckMsg(AckMsg ackMsg, long deliverMs, long reviveOffset, long deliverTime) { MessageExtBrokerInner messageExtBrokerInner = buildAckInnerMessage( REVIVE_TOPIC, ackMsg, REVIVE_QUEUE_ID, STORE_HOST, deliverMs, PopMessageProcessor.genAckUniqueId(ackMsg) ); messageExtBrokerInner.setQueueOffset(reviveOffset); messageExtBrokerInner.setDeliverTimeMs(deliverMs); messageExtBrokerInner.setStoreTimestamp(deliverTime); return messageExtBrokerInner; } public static MessageExtBrokerInner buildAckInnerMessage(String reviveTopic, AckMsg ackMsg, int reviveQid, SocketAddress host, long deliverMs, String ackUniqueId) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(reviveTopic); msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8)); msgInner.setQueueId(reviveQid); msgInner.setTags(PopAckConstants.ACK_TAG); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(host); msgInner.setStoreHost(host); msgInner.setDeliverTimeMs(deliverMs); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, ackUniqueId); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return msgInner; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/PullMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.embedded.EmbeddedChannel; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageContext; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PullMessageProcessorTest { private PullMessageProcessor pullMessageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; private final EmbeddedChannel embeddedChannel = new EmbeddedChannel(); @Mock private MessageStore messageStore; private ClientChannelInfo clientChannelInfo; private String group = "FooBarGroup"; private String topic = "FooBar"; @Before public void init() { brokerController.setMessageStore(messageStore); // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); SubscriptionGroupManager subscriptionGroupManager = new SubscriptionGroupManager(brokerController); pullMessageProcessor = new PullMessageProcessor(brokerController); when(brokerController.getPullMessageProcessor()).thenReturn(pullMessageProcessor); when(handlerContext.channel()).thenReturn(embeddedChannel); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig()); clientChannelInfo = new ClientChannelInfo(embeddedChannel); ConsumerData consumerData = createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientChannelInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), false); } @Test public void testProcessRequest_TopicNotExist() throws RemotingCommandException { brokerController.getTopicConfigManager().getTopicConfigTable().remove(topic); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); assertThat(response.getRemark()).contains("topic[" + topic + "] not exist"); } @Test public void testProcessRequest_SubNotExist() throws RemotingCommandException { brokerController.getConsumerManager().unregisterConsumer(group, clientChannelInfo, false); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUBSCRIPTION_NOT_EXIST); assertThat(response.getRemark()).contains("consumer's group info not exist"); } @Test public void testProcessRequest_SubNotLatest() throws RemotingCommandException { final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); request.addExtField("subVersion", String.valueOf(101)); RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUBSCRIPTION_NOT_LATEST); assertThat(response.getRemark()).contains("subscription not latest"); } @Test public void testProcessRequest_Found() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); pullMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_FoundWithHook() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); List consumeMessageHookList = new ArrayList<>(); final ConsumeMessageContext[] messageContext = new ConsumeMessageContext[1]; ConsumeMessageHook consumeMessageHook = new ConsumeMessageHook() { @Override public String hookName() { return "TestHook"; } @Override public void consumeMessageBefore(ConsumeMessageContext context) { messageContext[0] = context; } @Override public void consumeMessageAfter(ConsumeMessageContext context) { } }; consumeMessageHookList.add(consumeMessageHook); pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); pullMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(messageContext[0]).isNotNull(); assertThat(messageContext[0].getConsumerGroup()).isEqualTo(group); assertThat(messageContext[0].getTopic()).isEqualTo(topic); assertThat(messageContext[0].getQueueId()).isEqualTo(1); } @Test public void testProcessRequest_MsgWasRemoving() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); getMessageResult.setStatus(GetMessageStatus.MESSAGE_WAS_REMOVING); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); pullMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.PULL_RETRY_IMMEDIATELY); } @Test public void testProcessRequest_NoMsgInQueue() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); pullMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.PULL_OFFSET_MOVED); } @Test public void test_LitePullRequestForbidden() throws Exception { brokerController.getBrokerConfig().setLitePullMessageEnable(false); RemotingCommand remotingCommand = createPullMsgCommand(RequestCode.LITE_PULL_MESSAGE); RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, remotingCommand); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); } @Test public void testIfBroadcast() throws Exception { Class clazz = pullMessageProcessor.getClass(); Method method = clazz.getDeclaredMethod("isBroadcast", boolean.class, ConsumerGroupInfo.class); method.setAccessible(true); ConsumerGroupInfo consumerGroupInfo = new ConsumerGroupInfo("GID-1", ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); Assert.assertTrue((Boolean) method.invoke(pullMessageProcessor, true, consumerGroupInfo)); ConsumerGroupInfo consumerGroupInfo2 = new ConsumerGroupInfo("GID-2", ConsumeType.CONSUME_ACTIVELY, MessageModel.BROADCASTING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); Assert.assertFalse((Boolean) method.invoke(pullMessageProcessor, false, consumerGroupInfo2)); ConsumerGroupInfo consumerGroupInfo3 = new ConsumerGroupInfo("GID-3", ConsumeType.CONSUME_PASSIVELY, MessageModel.BROADCASTING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); Assert.assertTrue((Boolean) method.invoke(pullMessageProcessor, false, consumerGroupInfo3)); } @Test public void testCommitPullOffset() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); pullMessageProcessor.processRequest(handlerContext, request); RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(this.brokerController.getConsumerOffsetManager().queryPullOffset(group, topic, 1)) .isEqualTo(getMessageResult.getNextBeginOffset()); } private RemotingCommand createPullMsgCommand(int requestCode) { PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setCommitOffset(123L); requestHeader.setConsumerGroup(group); requestHeader.setMaxMsgNums(100); requestHeader.setQueueId(1); requestHeader.setQueueOffset(456L); requestHeader.setSubscription("*"); requestHeader.setTopic(topic); requestHeader.setSysFlag(0); requestHeader.setSubVersion(100L); RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, requestHeader); request.makeCustomHeaderToNet(); return request; } static ConsumerData createConsumerData(String group, String topic) { ConsumerData consumerData = new ConsumerData(); consumerData.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumerData.setConsumeType(ConsumeType.CONSUME_PASSIVELY); consumerData.setGroupName(group); consumerData.setMessageModel(MessageModel.CLUSTERING); Set subscriptionDataSet = new HashSet<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setSubString("*"); subscriptionData.setSubVersion(100L); subscriptionDataSet.add(subscriptionData); consumerData.setSubscriptionDataSet(subscriptionDataSet); return consumerData; } private GetMessageResult createGetMessageResult() { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(GetMessageStatus.FOUND); getMessageResult.setMinOffset(100); getMessageResult.setMaxOffset(1024); getMessageResult.setNextBeginOffset(516); return getMessageResult; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import com.google.common.collect.ImmutableSet; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.topic.TopicRouteInfoManager; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragelyByCircle; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueConsistentHash; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentRequestBody; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentResponseBody; import org.apache.rocketmq.remoting.protocol.body.SetMessageRequestModeRequestBody; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class QueryAssignmentProcessorTest { private QueryAssignmentProcessor queryAssignmentProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private TopicRouteInfoManager topicRouteInfoManager; @Mock private ChannelHandlerContext handlerContext; @Mock private MessageStore messageStore; @Mock private Channel channel; private String broker = "defaultBroker"; private String topic = "FooBar"; private String group = "FooBarGroup"; private String clientId = "127.0.0.1"; private ClientChannelInfo clientInfo; @Before public void init() throws IllegalAccessException, NoSuchFieldException { clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0); brokerController.setMessageStore(messageStore); doReturn(topicRouteInfoManager).when(brokerController).getTopicRouteInfoManager(); when(topicRouteInfoManager.getTopicSubscribeInfo(topic)).thenReturn(ImmutableSet.of(new MessageQueue(topic, "broker-1", 0), new MessageQueue(topic, "broker-2", 1))); queryAssignmentProcessor = new QueryAssignmentProcessor(brokerController); brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig()); ConsumerData consumerData = createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientInfo, consumerData.getConsumeType(), consumerData.getMessageModel(), consumerData.getConsumeFromWhere(), consumerData.getSubscriptionDataSet(), false); } @Test public void testQueryAssignment() throws Exception { brokerController.getProducerManager().registerProducer(group, clientInfo); final RemotingCommand request = createQueryAssignmentRequest(); RemotingCommand responseToReturn = queryAssignmentProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(responseToReturn.getBody()).isNotNull(); QueryAssignmentResponseBody responseBody = QueryAssignmentResponseBody.decode(responseToReturn.getBody(), QueryAssignmentResponseBody.class); assertThat(responseBody.getMessageQueueAssignments()).size().isEqualTo(2); } @Test public void testSetMessageRequestMode_Success() throws Exception { brokerController.getProducerManager().registerProducer(group, clientInfo); final RemotingCommand request = createSetMessageRequestModeRequest(topic); RemotingCommand responseToReturn = queryAssignmentProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testSetMessageRequestMode_RetryTopic() throws Exception { brokerController.getProducerManager().registerProducer(group, clientInfo); final RemotingCommand request = createSetMessageRequestModeRequest(MixAll.RETRY_GROUP_TOPIC_PREFIX + topic); RemotingCommand responseToReturn = queryAssignmentProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); } @Test public void testDoLoadBalance() throws Exception { Method method = queryAssignmentProcessor.getClass() .getDeclaredMethod("doLoadBalance", String.class, String.class, String.class, MessageModel.class, String.class, SetMessageRequestModeRequestBody.class, ChannelHandlerContext.class); method.setAccessible(true); Set mqs1 = (Set) method.invoke( queryAssignmentProcessor, MixAll.LMQ_PREFIX + topic, group, "127.0.0.1", MessageModel.CLUSTERING, new AllocateMessageQueueAveragely().getName(), new SetMessageRequestModeRequestBody(), handlerContext); Set mqs2 = (Set) method.invoke( queryAssignmentProcessor, MixAll.LMQ_PREFIX + topic, group, "127.0.0.2", MessageModel.CLUSTERING, new AllocateMessageQueueAveragely().getName(), new SetMessageRequestModeRequestBody(), handlerContext); assertThat(mqs1).hasSize(1); assertThat(mqs2).isEmpty(); } @Test public void testAllocate4Pop() { testAllocate4Pop(new AllocateMessageQueueAveragely()); testAllocate4Pop(new AllocateMessageQueueAveragelyByCircle()); testAllocate4Pop(new AllocateMessageQueueConsistentHash()); } private void testAllocate4Pop(AllocateMessageQueueStrategy strategy) { int testNum = 16; List mqAll = new ArrayList<>(); for (int mqSize = 0; mqSize < testNum; mqSize++) { mqAll.add(new MessageQueue(topic, broker, mqSize)); List cidAll = new ArrayList<>(); for (int cidSize = 0; cidSize < testNum; cidSize++) { String clientId = String.valueOf(cidSize); cidAll.add(clientId); for (int popShareQueueNum = 0; popShareQueueNum < testNum; popShareQueueNum++) { List allocateResult = queryAssignmentProcessor.allocate4Pop(strategy, group, clientId, mqAll, cidAll, popShareQueueNum); Assert.assertTrue(checkAllocateResult(popShareQueueNum, mqAll.size(), cidAll.size(), allocateResult.size(), strategy)); } } } } private boolean checkAllocateResult(int popShareQueueNum, int mqSize, int cidSize, int allocateSize, AllocateMessageQueueStrategy strategy) { //The maximum size of allocations will not exceed mqSize. if (allocateSize > mqSize) { return false; } //It is not allowed that the client is not assigned to the consumeQueue. if (allocateSize <= 0) { return false; } if (popShareQueueNum <= 0 || popShareQueueNum >= cidSize - 1) { return allocateSize == mqSize; } else if (mqSize < cidSize) { return allocateSize == 1; } if (strategy instanceof AllocateMessageQueueAveragely || strategy instanceof AllocateMessageQueueAveragelyByCircle) { if (mqSize % cidSize == 0) { return allocateSize == (mqSize / cidSize) * (popShareQueueNum + 1); } else { int avgSize = mqSize / cidSize; return allocateSize >= avgSize * (popShareQueueNum + 1) && allocateSize <= (avgSize + 1) * (popShareQueueNum + 1); } } if (strategy instanceof AllocateMessageQueueConsistentHash) { //Just skip return true; } return false; } private RemotingCommand createQueryAssignmentRequest() { QueryAssignmentRequestBody requestBody = new QueryAssignmentRequestBody(); requestBody.setTopic(topic); requestBody.setConsumerGroup(group); requestBody.setClientId(clientId); requestBody.setMessageModel(MessageModel.CLUSTERING); requestBody.setStrategyName("AVG"); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_ASSIGNMENT, null); request.setBody(requestBody.encode()); return request; } private RemotingCommand createSetMessageRequestModeRequest(String topic) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SET_MESSAGE_REQUEST_MODE, null); SetMessageRequestModeRequestBody requestBody = new SetMessageRequestModeRequestBody(); requestBody.setTopic(topic); requestBody.setConsumerGroup(group); requestBody.setMode(MessageRequestMode.POP); requestBody.setPopShareQueueNum(0); request.setBody(requestBody.encode()); return request; } private RemotingCommand createResponse(int code, RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(code); response.setOpaque(request.getOpaque()); return response; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/QueryMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.QueryMessageResult; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.util.HashMap; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class QueryMessageProcessorTest { private QueryMessageProcessor queryMessageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; @Mock private ChannelHandlerContext handlerContext; @Mock private Channel channel; @Mock private ChannelFuture channelFuture; @Before public void init() { when(handlerContext.channel()).thenReturn(channel); queryMessageProcessor = new QueryMessageProcessor(brokerController); when(brokerController.getMessageStore()).thenReturn(messageStore); when(channel.writeAndFlush(any())).thenReturn(channelFuture); } @Test public void testQueryMessage() throws RemotingCommandException { QueryMessageResult result = new QueryMessageResult(); result.setIndexLastUpdateTimestamp(100); result.setIndexLastUpdatePhyoffset(0); result.addMessage(new SelectMappedBufferResult(0, null, 0, null)); when(messageStore.queryMessage(anyString(),anyString(),anyInt(),anyLong(),anyLong(),any(),any())).thenReturn(result); RemotingCommand request = createQueryMessageRequest("topic", "msgKey", 1, 100, 200,"false"); request.makeCustomHeaderToNet(); RemotingCommand response = queryMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(response.getCode(), ResponseCode.QUERY_NOT_FOUND); result.addMessage(new SelectMappedBufferResult(0, null, 1, null)); response = queryMessageProcessor.processRequest(handlerContext, request); Assert.assertNull(response); } @Test public void testViewMessageById() throws RemotingCommandException { ViewMessageRequestHeader viewMessageRequestHeader = new ViewMessageRequestHeader(); viewMessageRequestHeader.setTopic("topic"); viewMessageRequestHeader.setOffset(0L); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, viewMessageRequestHeader); request.makeCustomHeaderToNet(); request.setCode(RequestCode.VIEW_MESSAGE_BY_ID); when(messageStore.selectOneMessageByOffset(anyLong())).thenReturn(null); RemotingCommand response = queryMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(response.getCode(), ResponseCode.SYSTEM_ERROR); when(messageStore.selectOneMessageByOffset(anyLong())).thenReturn(new SelectMappedBufferResult(0, null, 0, null)); response = queryMessageProcessor.processRequest(handlerContext, request); Assert.assertNull(response); } private RemotingCommand createQueryMessageRequest(String topic, String key, int maxNum, long beginTimestamp, long endTimestamp,String flag) { QueryMessageRequestHeader requestHeader = new QueryMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setKey(key); requestHeader.setMaxNum(maxNum); requestHeader.setBeginTimestamp(beginTimestamp); requestHeader.setEndTimestamp(endTimestamp); HashMap extFields = new HashMap<>(); extFields.put(MixAll.UNIQUE_MSG_QUERY_FLAG, flag); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE, requestHeader); request.setExtFields(extFields); return request; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/RecallMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.List; import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class RecallMessageProcessorTest { private static final String TOPIC = "topic"; private static final String BROKER_NAME = "brokerName"; private RecallMessageProcessor recallMessageProcessor; @Mock private BrokerConfig brokerConfig; @Mock private BrokerController brokerController; @Mock private ChannelHandlerContext handlerContext; @Mock private MessageStoreConfig messageStoreConfig; @Mock private TopicConfigManager topicConfigManager; @Mock private MessageStore messageStore; @Mock private BrokerStatsManager brokerStatsManager; @Mock private Channel channel; @Before public void init() throws IllegalAccessException, NoSuchFieldException { when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(brokerConfig.getBrokerName()).thenReturn(BROKER_NAME); when(brokerConfig.isRecallMessageEnable()).thenReturn(true); when(brokerController.getBrokerStatsManager()).thenReturn(brokerStatsManager); when(handlerContext.channel()).thenReturn(channel); recallMessageProcessor = new RecallMessageProcessor(brokerController); } @Test public void testBuildMessage_withNamespace() { when(messageStoreConfig.isAppendTopicForTimerDeleteKey()).thenReturn(true); String timestampStr = String.valueOf(System.currentTimeMillis()); String id = "id"; RecallMessageHandle.HandleV1 handle = new RecallMessageHandle.HandleV1(TOPIC, "brokerName", timestampStr, id); MessageExtBrokerInner msg = recallMessageProcessor.buildMessage(handlerContext, new RecallMessageRequestHeader(), handle); Assert.assertEquals(TOPIC, msg.getTopic()); Map properties = MessageDecoder.string2messageProperties(msg.getPropertiesString()); Assert.assertEquals(timestampStr, properties.get(MessageConst.PROPERTY_TIMER_DELIVER_MS)); Assert.assertEquals(id, properties.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); Assert.assertEquals(TOPIC + "+" + id, properties.get(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)); } @Test public void testBuildMessage_withoutNamespace() { when(messageStoreConfig.isAppendTopicForTimerDeleteKey()).thenReturn(false); String timestampStr = String.valueOf(System.currentTimeMillis()); String id = "id"; RecallMessageHandle.HandleV1 handle = new RecallMessageHandle.HandleV1(TOPIC, "brokerName", timestampStr, id); MessageExtBrokerInner msg = recallMessageProcessor.buildMessage(handlerContext, new RecallMessageRequestHeader(), handle); Assert.assertEquals(TOPIC, msg.getTopic()); Map properties = MessageDecoder.string2messageProperties(msg.getPropertiesString()); Assert.assertEquals(timestampStr, properties.get(MessageConst.PROPERTY_TIMER_DELIVER_MS)); Assert.assertEquals(id, properties.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); Assert.assertEquals(id, properties.get(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)); } @Test public void testHandlePutMessageResult() { MessageExt message = new MessageExt(); MessageAccessor.putProperty(message, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "id"); RemotingCommand response = RemotingCommand.createResponseCommand(RecallMessageResponseHeader.class); recallMessageProcessor.handlePutMessageResult(null, null, response, message, handlerContext, 0L); Assert.assertEquals(ResponseCode.SYSTEM_ERROR, response.getCode()); List okStatus = Arrays.asList(PutMessageStatus.PUT_OK, PutMessageStatus.FLUSH_DISK_TIMEOUT, PutMessageStatus.FLUSH_SLAVE_TIMEOUT, PutMessageStatus.SLAVE_NOT_AVAILABLE); for (PutMessageStatus status : PutMessageStatus.values()) { PutMessageResult putMessageResult = new PutMessageResult(status, new AppendMessageResult(AppendMessageStatus.PUT_OK)); recallMessageProcessor.handlePutMessageResult(putMessageResult, null, response, message, handlerContext, 0L); if (okStatus.contains(status)) { Assert.assertEquals(ResponseCode.SUCCESS, response.getCode()); RecallMessageResponseHeader responseHeader = (RecallMessageResponseHeader) response.readCustomHeader(); Assert.assertEquals("id", responseHeader.getMsgId()); } else { Assert.assertEquals(ResponseCode.SYSTEM_ERROR, response.getCode()); } } } @Test public void testProcessRequest_notEnable() throws RemotingCommandException { when(brokerConfig.isRecallMessageEnable()).thenReturn(false); RemotingCommand request = mockRequest(0, TOPIC, TOPIC, "id", BROKER_NAME); RemotingCommand response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.NO_PERMISSION, response.getCode()); } @Test public void testProcessRequest_invalidStatus() throws RemotingCommandException { RemotingCommand request = mockRequest(0, TOPIC, TOPIC, "id", BROKER_NAME); RemotingCommand response; // role slave when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE); response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.SLAVE_NOT_AVAILABLE, response.getCode()); // not reach startTimestamp when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SYNC_MASTER); when(messageStore.now()).thenReturn(0L); when(brokerConfig.getStartAcceptSendRequestTimeStamp()).thenReturn(System.currentTimeMillis()); response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.SERVICE_NOT_AVAILABLE, response.getCode()); } @Test public void testProcessRequest_notWriteable() throws RemotingCommandException { when(brokerConfig.getBrokerPermission()).thenReturn(4); when(brokerConfig.isAllowRecallWhenBrokerNotWriteable()).thenReturn(false); RemotingCommand request = mockRequest(0, TOPIC, TOPIC, "id", BROKER_NAME); RemotingCommand response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.SERVICE_NOT_AVAILABLE, response.getCode()); } @Test public void testProcessRequest_topicNotFound_or_notMatch() throws RemotingCommandException { when(brokerConfig.getBrokerPermission()).thenReturn(6); RemotingCommand request; RemotingCommand response; // not found request = mockRequest(0, TOPIC, TOPIC, "id", BROKER_NAME); response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.TOPIC_NOT_EXIST, response.getCode()); // not match when(topicConfigManager.selectTopicConfig(TOPIC)).thenReturn(new TopicConfig(TOPIC)); request = mockRequest(0, TOPIC, "anotherTopic", "id", BROKER_NAME); response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); } @Test public void testProcessRequest_brokerNameNotMatch() throws RemotingCommandException { when(brokerConfig.getBrokerPermission()).thenReturn(6); when(topicConfigManager.selectTopicConfig(TOPIC)).thenReturn(new TopicConfig(TOPIC)); RemotingCommand request = mockRequest(0, TOPIC, "anotherTopic", "id", BROKER_NAME + "_other"); RemotingCommand response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); } @Test public void testProcessRequest_timestampInvalid() throws RemotingCommandException { when(brokerConfig.getBrokerPermission()).thenReturn(6); when(topicConfigManager.selectTopicConfig(TOPIC)).thenReturn(new TopicConfig(TOPIC)); RemotingCommand request; RemotingCommand response; // past timestamp request = mockRequest(0, TOPIC, TOPIC, "id", BROKER_NAME); response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); // timestamp overflow when(messageStoreConfig.getTimerMaxDelaySec()).thenReturn(86400); request = mockRequest(System.currentTimeMillis() + 86400 * 2 * 1000, TOPIC, TOPIC, "id", BROKER_NAME); response = recallMessageProcessor.processRequest(handlerContext, request); Assert.assertEquals(ResponseCode.ILLEGAL_OPERATION, response.getCode()); } @Test public void testProcessRequest_success() throws RemotingCommandException { when(brokerConfig.getBrokerPermission()).thenReturn(6); when(topicConfigManager.selectTopicConfig(TOPIC)).thenReturn(new TopicConfig(TOPIC)); when(messageStoreConfig.getTimerMaxDelaySec()).thenReturn(86400); when(messageStore.putMessage(any())).thenReturn( new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); String msgId = "msgId"; RemotingCommand request = mockRequest(System.currentTimeMillis() + 90 * 1000, TOPIC, TOPIC, msgId, BROKER_NAME); RemotingCommand response = recallMessageProcessor.processRequest(handlerContext, request); RecallMessageResponseHeader responseHeader = (RecallMessageResponseHeader) response.readCustomHeader(); Assert.assertEquals(ResponseCode.SUCCESS, response.getCode()); Assert.assertEquals(msgId, responseHeader.getMsgId()); verify(messageStore, times(1)).putMessage(any()); } private RemotingCommand mockRequest(long timestamp, String requestTopic, String handleTopic, String msgId, String brokerName) { String handle = RecallMessageHandle.HandleV1.buildHandle(handleTopic, brokerName, String.valueOf(timestamp), msgId); RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup("group"); requestHeader.setTopic(requestTopic); requestHeader.setRecallHandle(handle); requestHeader.setBrokerName(brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); return request; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.net.Broker2Client; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ReplyMessageProcessorTest { private ReplyMessageProcessor replyMessageProcessor; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private ChannelHandlerContext handlerContext; @Mock private MessageStore messageStore; @Mock private Channel channel; private String topic = "FooBar"; private String group = "FooBarGroup"; private ClientChannelInfo clientInfo; @Mock private Broker2Client broker2Client; @Before public void init() throws IllegalAccessException, NoSuchFieldException { clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0); brokerController.setMessageStore(messageStore); // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); Field field = BrokerController.class.getDeclaredField("broker2Client"); field.setAccessible(true); field.set(brokerController, broker2Client); when(messageStore.now()).thenReturn(System.currentTimeMillis()); Channel mockChannel = mock(Channel.class); when(mockChannel.remoteAddress()).thenReturn(new InetSocketAddress(1024)); when(handlerContext.channel()).thenReturn(mockChannel); replyMessageProcessor = new ReplyMessageProcessor(brokerController); } @Test public void testProcessRequest_Success() throws RemotingCommandException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException { when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); brokerController.getProducerManager().registerProducer(group, clientInfo); final RemotingCommand request = createSendMessageRequestHeaderCommand(RequestCode.SEND_REPLY_MESSAGE); when(brokerController.getBroker2Client().callClient(any(), any(RemotingCommand.class))).thenReturn(createResponse(ResponseCode.SUCCESS, request)); RemotingCommand responseToReturn = replyMessageProcessor.processRequest(handlerContext, request); assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque()); } private RemotingCommand createSendMessageRequestHeaderCommand(int requestCode) { SendMessageRequestHeader requestHeader = createSendMessageRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, requestHeader); request.setBody(new byte[] {'a'}); request.makeCustomHeaderToNet(); return request; } private SendMessageRequestHeader createSendMessageRequestHeader() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setProducerGroup(group); requestHeader.setTopic(topic); requestHeader.setDefaultTopic(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC); requestHeader.setDefaultTopicQueueNums(3); requestHeader.setQueueId(1); requestHeader.setSysFlag(0); requestHeader.setBornTimestamp(System.currentTimeMillis()); requestHeader.setFlag(124); requestHeader.setReconsumeTimes(0); Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT, "127.0.0.1"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); return requestHeader; } private RemotingCommand createResponse(int code, RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(code); response.setOpaque(request.getOpaque()); return response; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/processor/SendMessageProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.net.InetSocketAddress; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import org.apache.commons.codec.DecoderException; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageContext; import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook; import org.apache.rocketmq.broker.mqtrace.SendMessageContext; import org.apache.rocketmq.broker.mqtrace.SendMessageHook; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; @RunWith(MockitoJUnitRunner.class) public class SendMessageProcessorTest { private SendMessageProcessor sendMessageProcessor; @Mock private ChannelHandlerContext handlerContext; @Mock private Channel channel; @Spy private BrokerConfig brokerConfig; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; @Mock private TransactionalMessageService transactionMsgService; private String topic = "FooBar"; private String group = "FooBarGroup"; @Before public void init() { brokerController.setMessageStore(messageStore); // Initialize BrokerMetricsManager to prevent NPE in tests brokerController.setBrokerMetricsManager(new BrokerMetricsManager(brokerController)); TopicConfigManager topicConfigManager = new TopicConfigManager(brokerController); topicConfigManager.getTopicConfigTable().put(topic, new TopicConfig(topic)); SubscriptionGroupManager subscriptionGroupManager = new SubscriptionGroupManager(brokerController); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getPutMessageFutureExecutor()).thenReturn(Executors.newSingleThreadExecutor()); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); when(messageStore.now()).thenReturn(System.currentTimeMillis()); when(channel.remoteAddress()).thenReturn(new InetSocketAddress(1024)); when(handlerContext.channel()).thenReturn(channel); when(messageStore.lookMessageByOffset(anyLong())).thenReturn(new MessageExt()); sendMessageProcessor = new SendMessageProcessor(brokerController); } @Test public void testProcessRequest() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); assertPutResult(ResponseCode.SUCCESS); } @Test public void testProcessRequest_WithHook() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); List sendMessageHookList = new ArrayList<>(); final SendMessageContext[] sendMessageContext = new SendMessageContext[1]; SendMessageHook sendMessageHook = new SendMessageHook() { @Override public String hookName() { return null; } @Override public void sendMessageBefore(SendMessageContext context) { sendMessageContext[0] = context; } @Override public void sendMessageAfter(SendMessageContext context) { } }; sendMessageHookList.add(sendMessageHook); sendMessageProcessor.registerSendMessageHook(sendMessageHookList); assertPutResult(ResponseCode.SUCCESS); assertThat(sendMessageContext[0]).isNotNull(); assertThat(sendMessageContext[0].getTopic()).isEqualTo(topic); assertThat(sendMessageContext[0].getProducerGroup()).isEqualTo(group); } @Test public void testProcessRequest_FlushTimeOut() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.FLUSH_DISK_TIMEOUT, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.FLUSH_DISK_TIMEOUT); } @Test public void testProcessRequest_MessageIllegal() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.MESSAGE_ILLEGAL); } @Test public void testProcessRequest_CreateMappedFileFailed() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.SYSTEM_ERROR); } @Test public void testProcessRequest_FlushSlaveTimeout() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.FLUSH_SLAVE_TIMEOUT, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.FLUSH_SLAVE_TIMEOUT); } @Test public void testProcessRequest_PageCacheBusy() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.OS_PAGE_CACHE_BUSY, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.SYSTEM_BUSY); } @Test public void testProcessRequest_PropertiesTooLong() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PROPERTIES_SIZE_EXCEEDED, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.MESSAGE_ILLEGAL); } @Test public void testProcessRequest_ServiceNotAvailable() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.SERVICE_NOT_AVAILABLE); } @Test public void testProcessRequest_SlaveNotAvailable() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))). thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.SLAVE_NOT_AVAILABLE, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); assertPutResult(ResponseCode.SLAVE_NOT_AVAILABLE); } @Test public void testProcessRequest_WithMsgBack() throws Exception { when(messageStore.putMessage(any(MessageExtBrokerInner.class))). thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); final RemotingCommand request = createSendMsgBackCommand(RequestCode.CONSUMER_SEND_MSG_BACK); sendMessageProcessor = new SendMessageProcessor(brokerController); final RemotingCommand response = sendMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testProcessRequest_Transaction() throws RemotingCommandException { brokerController.setTransactionalMessageService(transactionMsgService); when(brokerController.getTransactionalMessageService().asyncPrepareMessage(any(MessageExtBrokerInner.class))) .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); RemotingCommand request = createSendTransactionMsgCommand(RequestCode.SEND_MESSAGE); final RemotingCommand[] response = new RemotingCommand[1]; doAnswer(invocation -> { response[0] = invocation.getArgument(0); return null; }).when(channel).writeAndFlush(any(Object.class)); await().atMost(Duration.ofSeconds(10)).until(() -> { RemotingCommand responseToReturn = sendMessageProcessor.processRequest(handlerContext, request); if (responseToReturn != null) { assertThat(response[0]).isNull(); response[0] = responseToReturn; } if (response[0] == null) { return false; } assertThat(response[0].getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response[0].getOpaque()).isEqualTo(request.getOpaque()); return true; }); } @Test public void testProcessRequest_WithAbortProcessSendMessageBeforeHook() throws Exception { List sendMessageHookList = new ArrayList<>(); final SendMessageContext[] sendMessageContext = new SendMessageContext[1]; SendMessageHook sendMessageHook = new SendMessageHook() { @Override public String hookName() { return null; } @Override public void sendMessageBefore(SendMessageContext context) { sendMessageContext[0] = context; throw new AbortProcessException(ResponseCode.FLOW_CONTROL, "flow control test"); } @Override public void sendMessageAfter(SendMessageContext context) { } }; sendMessageHookList.add(sendMessageHook); sendMessageProcessor.registerSendMessageHook(sendMessageHookList); assertPutResult(ResponseCode.FLOW_CONTROL); assertThat(sendMessageContext[0]).isNotNull(); assertThat(sendMessageContext[0].getTopic()).isEqualTo(topic); assertThat(sendMessageContext[0].getProducerGroup()).isEqualTo(group); } @Test public void testProcessRequest_WithMsgBackWithConsumeMessageAfterHook() throws Exception { when(messageStore.putMessage(any(MessageExtBrokerInner.class))). thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); final RemotingCommand request = createSendMsgBackCommand(RequestCode.CONSUMER_SEND_MSG_BACK); sendMessageProcessor = new SendMessageProcessor(brokerController); List consumeMessageHookList = new ArrayList<>(); final ConsumeMessageContext[] messageContext = new ConsumeMessageContext[1]; ConsumeMessageHook consumeMessageHook = new ConsumeMessageHook() { @Override public String hookName() { return "TestHook"; } @Override public void consumeMessageBefore(ConsumeMessageContext context) { } @Override public void consumeMessageAfter(ConsumeMessageContext context) { messageContext[0] = context; throw new AbortProcessException(ResponseCode.FLOW_CONTROL, "flow control test"); } }; consumeMessageHookList.add(consumeMessageHook); sendMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); final RemotingCommand response = sendMessageProcessor.processRequest(handlerContext, request); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testAttachRecallHandle_skip() { MessageExt message = new MessageExt(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_BATCH_MESSAGE, null); sendMessageProcessor.attachRecallHandle(request, message, new SendMessageResponseHeader()); request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, null); sendMessageProcessor.attachRecallHandle(request, message, new SendMessageResponseHeader()); request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, null); sendMessageProcessor.attachRecallHandle(request, message, new SendMessageResponseHeader()); request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, null); sendMessageProcessor.attachRecallHandle(request, message, new SendMessageResponseHeader()); verify(brokerConfig, times(0)).getBrokerName(); } @Test public void testAttachRecallHandle_doAttach() throws DecoderException { int[] precisionSet = {100, 200, 500, 1000}; SendMessageResponseHeader responseHeader = new SendMessageResponseHeader(); String id = MessageClientIDSetter.createUniqID(); long timestamp = System.currentTimeMillis(); for (int precisionMs : precisionSet) { long deliverMs = floor(timestamp, precisionMs); MessageExt message = new MessageExt(); MessageAccessor.putProperty(message, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, id); MessageAccessor.putProperty(message, MessageConst.PROPERTY_TIMER_OUT_MS, String.valueOf(deliverMs)); MessageAccessor.putProperty(message, MessageConst.PROPERTY_REAL_TOPIC, topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, null); sendMessageProcessor.attachRecallHandle(request, message, responseHeader); Assert.assertNotNull(responseHeader.getRecallHandle()); RecallMessageHandle.HandleV1 v1 = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(responseHeader.getRecallHandle()); Assert.assertEquals(id, v1.getMessageId()); Assert.assertEquals(topic, v1.getTopic()); Assert.assertEquals(deliverMs + 1, Long.parseLong(v1.getTimestampStr())); Assert.assertEquals(deliverMs, floor(Long.valueOf(v1.getTimestampStr()), precisionMs)); } } private long floor(long deliverMs, int precisionMs) { assert precisionMs > 0; if (deliverMs % precisionMs == 0) { deliverMs -= precisionMs; } else { deliverMs = deliverMs / precisionMs * precisionMs; } return deliverMs; } private RemotingCommand createSendTransactionMsgCommand(int requestCode) { SendMessageRequestHeader header = createSendMsgRequestHeader(); int sysFlag = header.getSysFlag(); Map oriProps = MessageDecoder.string2messageProperties(header.getProperties()); oriProps.put(MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); header.setProperties(MessageDecoder.messageProperties2String(oriProps)); sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE; header.setSysFlag(sysFlag); RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, header); request.setBody(new byte[] {'a'}); request.makeCustomHeaderToNet(); return request; } private SendMessageRequestHeader createSendMsgRequestHeader() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setProducerGroup(group); requestHeader.setTopic(topic); requestHeader.setDefaultTopic(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC); requestHeader.setDefaultTopicQueueNums(3); requestHeader.setQueueId(1); requestHeader.setSysFlag(0); requestHeader.setBornTimestamp(System.currentTimeMillis()); requestHeader.setFlag(124); requestHeader.setReconsumeTimes(0); return requestHeader; } private RemotingCommand createSendMsgCommand(int requestCode) { SendMessageRequestHeader requestHeader = createSendMsgRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, requestHeader); request.setBody(new byte[] {'a'}); request.makeCustomHeaderToNet(); return request; } private RemotingCommand createSendMsgBackCommand(int requestCode) { ConsumerSendMsgBackRequestHeader requestHeader = new ConsumerSendMsgBackRequestHeader(); requestHeader.setMaxReconsumeTimes(3); requestHeader.setDelayLevel(4); requestHeader.setGroup(group); requestHeader.setOffset(123L); RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, requestHeader); request.makeCustomHeaderToNet(); return request; } /** * We will explain the logic of this method so you can get a better feeling of how to use it: This method assumes * that if responseToReturn is not null, then there would be an error, which means the writeAndFlush are never * reached. If responseToReturn is null, means everything ok, so writeAndFlush should record the actual response. * * @param responseCode * @throws RemotingCommandException */ private void assertPutResult(int responseCode) throws RemotingCommandException { final RemotingCommand request = createSendMsgCommand(RequestCode.SEND_MESSAGE); final RemotingCommand[] response = new RemotingCommand[1]; doAnswer(invocation -> { response[0] = invocation.getArgument(0); return null; }).when(channel).writeAndFlush(any(Object.class)); await().atMost(Duration.ofSeconds(10)).until(() -> { RemotingCommand responseToReturn = sendMessageProcessor.processRequest(handlerContext, request); if (responseToReturn != null) { assertThat(response[0]).isNull(); response[0] = responseToReturn; } if (response[0] == null) { return false; } assertThat(response[0].getCode()).isEqualTo(responseCode); assertThat(response[0].getOpaque()).isEqualTo(request.getOpaque()); return true; }); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.schedule; import java.io.File; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.failover.EscapeBridge; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.broker.util.HookUtils; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageArrivingListener; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.common.metrics.NopLongCounter; import org.apache.rocketmq.common.metrics.NopLongHistogram; import io.opentelemetry.api.common.Attributes; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import static org.apache.rocketmq.common.stats.Stats.BROKER_PUT_NUMS; import static org.apache.rocketmq.common.stats.Stats.TOPIC_PUT_NUMS; import static org.apache.rocketmq.common.stats.Stats.TOPIC_PUT_SIZE; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; public class ScheduleMessageServiceTest { private BrokerController brokerController; private ScheduleMessageService scheduleMessageService; /** * t defaultMessageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h" */ String testMessageDelayLevel = "5s 8s"; /** * choose delay level */ int delayLevel = 3; private static final String STORE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "schedule_test#" + UUID.randomUUID(); private static final int COMMIT_LOG_FILE_SIZE = 1024; private static final int CQ_FILE_SIZE = 10; private static final int CQ_EXT_FILE_SIZE = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); private static SocketAddress bornHost; private static SocketAddress storeHost; private DefaultMessageStore messageStore; private MessageStoreConfig messageStoreConfig; private BrokerConfig brokerConfig; static String sendMessage = " ------- schedule message test -------"; static String topic = "schedule_topic_test"; static String messageGroup = "delayGroupTest"; private Random random = new Random(); static { try { bornHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); } catch (UnknownHostException e) { e.printStackTrace(); } try { storeHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (UnknownHostException e) { e.printStackTrace(); } } @Before public void setUp() throws Exception { messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMessageDelayLevel(testMessageDelayLevel); messageStoreConfig.setMappedFileSizeCommitLog(COMMIT_LOG_FILE_SIZE); messageStoreConfig.setMappedFileSizeConsumeQueue(CQ_FILE_SIZE); messageStoreConfig.setMappedFileSizeConsumeQueueExt(CQ_EXT_FILE_SIZE); messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(true); messageStoreConfig.setStorePathRootDir(STORE_PATH); messageStoreConfig.setStorePathCommitLog(STORE_PATH + File.separator + "commitlog"); // Let OS pick an available port messageStoreConfig.setHaListenPort(0); brokerConfig = new BrokerConfig(); BrokerStatsManager manager = new BrokerStatsManager(brokerConfig.getBrokerClusterName(), brokerConfig.isEnableDetailStat()); messageStore = new DefaultMessageStore(messageStoreConfig, manager, new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); assertThat(messageStore.load()).isTrue(); messageStore.start(); brokerController = Mockito.mock(BrokerController.class); Mockito.when(brokerController.getMessageStore()).thenReturn(messageStore); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); Mockito.when(brokerController.peekMasterBroker()).thenReturn(brokerController); Mockito.when(brokerController.getBrokerStatsManager()).thenReturn(manager); EscapeBridge escapeBridge = new EscapeBridge(brokerController); Mockito.when(brokerController.getEscapeBridge()).thenReturn(escapeBridge); // Initialize BrokerMetricsManager to prevent NPE in tests BrokerMetricsManager brokerMetricsManager = Mockito.mock(BrokerMetricsManager.class); // Mock newAttributesBuilder to return a valid AttributesBuilder instead of null Mockito.when(brokerMetricsManager.newAttributesBuilder()).thenReturn(Attributes.builder()); // Mock metrics getter methods to return Nop implementations to prevent NPE Mockito.when(brokerMetricsManager.getMessagesInTotal()).thenReturn(new NopLongCounter()); Mockito.when(brokerMetricsManager.getMessagesOutTotal()).thenReturn(new NopLongCounter()); Mockito.when(brokerMetricsManager.getThroughputInTotal()).thenReturn(new NopLongCounter()); Mockito.when(brokerMetricsManager.getThroughputOutTotal()).thenReturn(new NopLongCounter()); Mockito.when(brokerMetricsManager.getMessageSize()).thenReturn(new NopLongHistogram()); Mockito.when(brokerController.getBrokerMetricsManager()).thenReturn(brokerMetricsManager); scheduleMessageService = Mockito.spy(new ScheduleMessageService(brokerController)); // Mock ScheduleMessageService before it's used in HookUtils Mockito.when(brokerController.getScheduleMessageService()).thenReturn(scheduleMessageService); scheduleMessageService.load(); scheduleMessageService.start(); } @Test public void testLoad() { ConcurrentMap offsetTable = scheduleMessageService.getOffsetTable(); //offsetTable.put(0, 1L); offsetTable.put(1, 3L); offsetTable.put(2, 5L); scheduleMessageService.persist(); ScheduleMessageService controlInstance = new ScheduleMessageService(brokerController); assertTrue(controlInstance.load()); ConcurrentMap loaded = controlInstance.getOffsetTable(); for (long offset : loaded.values()) { assertEquals(0, offset); } } @Test public void testCorrectDelayOffset_whenInit() throws Exception { ConcurrentMap offsetTable = null; scheduleMessageService = new ScheduleMessageService(brokerController); scheduleMessageService.parseDelayLevel(); ConcurrentMap offsetTable1 = new ConcurrentHashMap<>(); for (int i = 1; i <= 2; i++) { offsetTable1.put(i, random.nextLong()); } Field field = scheduleMessageService.getClass().getDeclaredField("offsetTable"); field.setAccessible(true); field.set(scheduleMessageService, offsetTable1); String jsonStr = scheduleMessageService.encode(); scheduleMessageService.decode(jsonStr); offsetTable = (ConcurrentMap) field.get(scheduleMessageService); for (Map.Entry entry : offsetTable.entrySet()) { assertEquals(entry.getValue(), offsetTable1.get(entry.getKey())); } boolean success = scheduleMessageService.correctDelayOffset(); System.out.printf("correctDelayOffset %s", success); offsetTable = (ConcurrentMap) field.get(scheduleMessageService); for (long offset : offsetTable.values()) { assertEquals(0, offset); } } @Test public void testDeliverDelayedMessageTimerTask() throws Exception { assertThat(messageStore.getMessageStoreConfig().isEnableScheduleMessageStats()).isTrue(); assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_NUMS, topic)).isNull(); MessageExtBrokerInner msg = buildMessage(); int realQueueId = msg.getQueueId(); // set delayLevel,and send delay message msg.setDelayTimeLevel(delayLevel); HookUtils.handleScheduleMessage(brokerController, msg); PutMessageResult result = messageStore.putMessage(msg); assertThat(result.isOk()).isTrue(); // consumer message int delayQueueId = ScheduleMessageService.delayLevel2QueueId(delayLevel); assertThat(delayQueueId).isEqualTo(delayLevel - 1); Long offset = result.getAppendMessageResult().getLogicsOffset(); // now, no message in queue,must wait > delayTime GetMessageResult messageResult = getMessage(realQueueId, offset); assertThat(messageResult.getStatus()).isEqualTo(GetMessageStatus.NO_MESSAGE_IN_QUEUE); // timer run maybe delay, then consumer message again // and wait offsetTable TimeUnit.SECONDS.sleep(15); scheduleMessageService.buildRunningStats(new HashMap<>()); messageResult = getMessage(realQueueId, offset); // now,found the message assertThat(messageResult.getStatus()).isEqualTo(GetMessageStatus.FOUND); // get the stats change assertThat(messageStore.getBrokerStatsManager().getStatsItem(BROKER_PUT_NUMS, brokerConfig.getBrokerClusterName()).getValue().sum()).isEqualTo(1); assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_NUMS, topic).getValue().sum()).isEqualTo(1L); assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_SIZE, topic).getValue().sum()).isEqualTo(messageResult.getBufferTotalSize()); // get the message body ByteBuffer byteBuffer = ByteBuffer.allocate(messageResult.getBufferTotalSize()); List byteBufferList = messageResult.getMessageBufferList(); for (ByteBuffer bb : byteBufferList) { byteBuffer.put(bb); } // warp and decode the message byteBuffer = ByteBuffer.wrap(byteBuffer.array()); List msgList = MessageDecoder.decodes(byteBuffer); String retryMsg = new String(msgList.get(0).getBody()); assertThat(sendMessage).isEqualTo(retryMsg); // method will wait 10s,so I run it by myself scheduleMessageService.persist(); // add mapFile release messageResult.release(); } /** * add some [error/no use] code test */ @Test public void otherTest() { // the method no use ,why need ? int queueId = ScheduleMessageService.queueId2DelayLevel(delayLevel); assertThat(queueId).isEqualTo(delayLevel + 1); // error delayLevelTest Long time = scheduleMessageService.computeDeliverTimestamp(999, 0); assertThat(time).isEqualTo(1000); // just decode scheduleMessageService.decode(new DelayOffsetSerializeWrapper().toJson()); } private GetMessageResult getMessage(int queueId, Long offset) { return messageStore.getMessage(messageGroup, topic, queueId, offset, 1, null); } @After public void shutdown() throws InterruptedException { scheduleMessageService.shutdown(); messageStore.shutdown(); messageStore.destroy(); File file = new File(messageStoreConfig.getStorePathRootDir()); UtilAll.deleteFile(file); } public MessageExtBrokerInner buildMessage() { byte[] msgBody = sendMessage.getBytes(); MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("schedule_tag"); msg.setKeys("schedule_key"); msg.setBody(msgBody); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); return msg; } private class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/slave/SlaveSynchronizeAtomicTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.slave; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.loadbalance.MessageRequestModeManager; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.processor.QueryAssignmentProcessor; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.MessageRequestModeSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class SlaveSynchronizeAtomicTest { @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); private SlaveSynchronize slaveSynchronize; @Mock private BrokerOuterAPI brokerOuterAPI; @Mock private TopicConfigManager topicConfigManager; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private QueryAssignmentProcessor queryAssignmentProcessor; @Mock private MessageRequestModeManager messageRequestModeManager; private static final String BROKER_ADDR = "127.0.0.1:10911"; private final SubscriptionGroupWrapper subscriptionGroupWrapper = createSubscriptionGroupWrapper(); private final MessageRequestModeSerializeWrapper requestModeSerializeWrapper = createMessageRequestModeWrapper(); private final DataVersion dataVersion = new DataVersion(); @Before public void init() { for (int i = 0; i < 100000; i++) { subscriptionGroupWrapper.getSubscriptionGroupTable().put("group" + i, new SubscriptionGroupConfig()); } for (int i = 0; i < 100000; i++) { requestModeSerializeWrapper.getMessageRequestModeMap().put("topic" + i, new ConcurrentHashMap<>()); } when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(subscriptionGroupManager.getDataVersion()).thenReturn(dataVersion); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn( subscriptionGroupWrapper.getSubscriptionGroupTable()); slaveSynchronize = new SlaveSynchronize(brokerController); slaveSynchronize.setMasterAddr(BROKER_ADDR); } private SubscriptionGroupWrapper createSubscriptionGroupWrapper() { SubscriptionGroupWrapper wrapper = new SubscriptionGroupWrapper(); wrapper.setSubscriptionGroupTable(new ConcurrentHashMap<>()); DataVersion dataVersion = new DataVersion(); dataVersion.setStateVersion(1L); wrapper.setDataVersion(dataVersion); return wrapper; } private MessageRequestModeSerializeWrapper createMessageRequestModeWrapper() { MessageRequestModeSerializeWrapper wrapper = new MessageRequestModeSerializeWrapper(); wrapper.setMessageRequestModeMap(new ConcurrentHashMap<>()); return wrapper; } @Test public void testSyncAtomically() throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQBrokerException, InterruptedException, RemotingCommandException { when(brokerOuterAPI.getAllSubscriptionGroupConfig(anyString())).thenReturn(subscriptionGroupWrapper); when(brokerOuterAPI.getAllMessageRequestMode(anyString())).thenReturn(requestModeSerializeWrapper); CountDownLatch countDownLatch = new CountDownLatch(1); new Thread(() -> { while (countDownLatch.getCount() > 0) { dataVersion.nextVersion(); try { slaveSynchronize.syncAll(); } catch (Exception e) { e.printStackTrace(); } } }).start(); for (int i = 0; i < 10000000; i++) { Assert.assertTrue(subscriptionGroupWrapper.getSubscriptionGroupTable() .containsKey("group" + ThreadLocalRandom.current().nextInt(0, 100000))); Assert.assertTrue(requestModeSerializeWrapper.getMessageRequestModeMap() .containsKey("topic" + ThreadLocalRandom.current().nextInt(0, 100000))); } countDownLatch.countDown(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/slave/SlaveSynchronizeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.slave; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.loadbalance.MessageRequestModeManager; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.processor.QueryAssignmentProcessor; import org.apache.rocketmq.broker.schedule.ScheduleMessageService; import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.ConsumerOffsetSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.MessageRequestModeSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.timer.TimerCheckpoint; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.TimerMetrics; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.io.UnsupportedEncodingException; import java.util.concurrent.ConcurrentHashMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class SlaveSynchronizeTest { @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); private SlaveSynchronize slaveSynchronize; @Mock private BrokerOuterAPI brokerOuterAPI; @Mock private TopicConfigManager topicConfigManager; @Mock private ConsumerOffsetManager consumerOffsetManager; @Mock private MessageStoreConfig messageStoreConfig; @Mock private MessageStore messageStore; @Mock private ScheduleMessageService scheduleMessageService; @Mock private SubscriptionGroupManager subscriptionGroupManager; @Mock private QueryAssignmentProcessor queryAssignmentProcessor; @Mock private MessageRequestModeManager messageRequestModeManager; @Mock private TimerMessageStore timerMessageStore; @Mock private TimerMetrics timerMetrics; @Mock private TimerCheckpoint timerCheckpoint; private static final String BROKER_ADDR = "127.0.0.1:10911"; @Before public void init() { when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getScheduleMessageService()).thenReturn(scheduleMessageService); when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager); when(brokerController.getQueryAssignmentProcessor()).thenReturn(queryAssignmentProcessor); when(brokerController.getMessageStore()).thenReturn(messageStore); when(brokerController.getTimerMessageStore()).thenReturn(timerMessageStore); when(brokerController.getTimerCheckpoint()).thenReturn(timerCheckpoint); when(topicConfigManager.getDataVersion()).thenReturn(new DataVersion()); when(topicConfigManager.getTopicConfigTable()).thenReturn(new ConcurrentHashMap<>()); when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager); when(consumerOffsetManager.getOffsetTable()).thenReturn(new ConcurrentHashMap<>()); when(consumerOffsetManager.getDataVersion()).thenReturn(new DataVersion()); when(subscriptionGroupManager.getDataVersion()).thenReturn(new DataVersion()); when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(new ConcurrentHashMap<>()); when(queryAssignmentProcessor.getMessageRequestModeManager()).thenReturn(messageRequestModeManager); when(messageRequestModeManager.getMessageRequestModeMap()).thenReturn(new ConcurrentHashMap<>()); when(messageStoreConfig.isTimerWheelEnable()).thenReturn(true); when(messageStore.getTimerMessageStore()).thenReturn(timerMessageStore); when(timerMessageStore.isShouldRunningDequeue()).thenReturn(false); when(timerMessageStore.getTimerMetrics()).thenReturn(timerMetrics); when(timerMetrics.getDataVersion()).thenReturn(new DataVersion()); when(timerCheckpoint.getDataVersion()).thenReturn(new DataVersion()); slaveSynchronize = new SlaveSynchronize(brokerController); slaveSynchronize.setMasterAddr(BROKER_ADDR); } @Test public void testSyncAll() throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQBrokerException, InterruptedException, UnsupportedEncodingException, RemotingCommandException { TopicConfig newTopicConfig = new TopicConfig("NewTopic"); when(brokerOuterAPI.getAllTopicConfig(anyString())).thenReturn(createTopicConfigWrapper(newTopicConfig)); when(brokerOuterAPI.getAllConsumerOffset(anyString())).thenReturn(createConsumerOffsetWrapper()); when(brokerOuterAPI.getAllDelayOffset(anyString())).thenReturn(""); when(brokerOuterAPI.getAllSubscriptionGroupConfig(anyString())).thenReturn(createSubscriptionGroupWrapper()); when(brokerOuterAPI.getAllMessageRequestMode(anyString())).thenReturn(createMessageRequestModeWrapper()); when(brokerOuterAPI.getTimerMetrics(anyString())).thenReturn(createTimerMetricsWrapper()); slaveSynchronize.syncAll(); Assert.assertEquals(1, this.brokerController.getTopicConfigManager().getDataVersion().getStateVersion()); Assert.assertEquals(1, this.brokerController.getTopicQueueMappingManager().getDataVersion().getStateVersion()); Assert.assertEquals(1, consumerOffsetManager.getDataVersion().getStateVersion()); Assert.assertEquals(1, subscriptionGroupManager.getDataVersion().getStateVersion()); Assert.assertEquals(1, timerMetrics.getDataVersion().getStateVersion()); } @Test public void testGetMasterAddr() { Assert.assertEquals(BROKER_ADDR, slaveSynchronize.getMasterAddr()); } @Test public void testSyncTimerCheckPoint() throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQBrokerException, InterruptedException { when(brokerOuterAPI.getTimerCheckPoint(anyString())).thenReturn(timerCheckpoint); slaveSynchronize.syncTimerCheckPoint(); Assert.assertEquals(0, timerCheckpoint.getDataVersion().getStateVersion()); } private TopicConfigAndMappingSerializeWrapper createTopicConfigWrapper(TopicConfig topicConfig) { TopicConfigAndMappingSerializeWrapper wrapper = new TopicConfigAndMappingSerializeWrapper(); wrapper.setTopicConfigTable(new ConcurrentHashMap<>()); wrapper.getTopicConfigTable().put(topicConfig.getTopicName(), topicConfig); DataVersion dataVersion = new DataVersion(); dataVersion.setStateVersion(1L); wrapper.setDataVersion(dataVersion); wrapper.setMappingDataVersion(dataVersion); return wrapper; } private ConsumerOffsetSerializeWrapper createConsumerOffsetWrapper() { ConsumerOffsetSerializeWrapper wrapper = new ConsumerOffsetSerializeWrapper(); wrapper.setOffsetTable(new ConcurrentHashMap<>()); DataVersion dataVersion = new DataVersion(); dataVersion.setStateVersion(1L); wrapper.setDataVersion(dataVersion); return wrapper; } private SubscriptionGroupWrapper createSubscriptionGroupWrapper() { SubscriptionGroupWrapper wrapper = new SubscriptionGroupWrapper(); wrapper.setSubscriptionGroupTable(new ConcurrentHashMap<>()); DataVersion dataVersion = new DataVersion(); dataVersion.setStateVersion(1L); wrapper.setDataVersion(dataVersion); return wrapper; } private MessageRequestModeSerializeWrapper createMessageRequestModeWrapper() { MessageRequestModeSerializeWrapper wrapper = new MessageRequestModeSerializeWrapper(); wrapper.setMessageRequestModeMap(new ConcurrentHashMap<>()); return wrapper; } private TimerMetrics.TimerMetricsSerializeWrapper createTimerMetricsWrapper() { TimerMetrics.TimerMetricsSerializeWrapper wrapper = new TimerMetrics.TimerMetricsSerializeWrapper(); wrapper.setTimingCount(new ConcurrentHashMap<>()); DataVersion dataVersion = new DataVersion(); dataVersion.setStateVersion(1L); wrapper.setDataVersion(dataVersion); return wrapper; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/subscription/ForbiddenTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.subscription; import static org.junit.Assert.assertEquals; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Test; public class ForbiddenTest { @Test public void testBrokerRestart() throws Exception { SubscriptionGroupManager s = new SubscriptionGroupManager( new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig())); s.updateForbidden("g", "t", 0, true); assertEquals(1, s.getForbidden("g", "t")); assertEquals(true, s.getForbidden("g", "t", 0)); s.updateForbidden("g", "t", 1, true); assertEquals(3, s.getForbidden("g", "t")); assertEquals(true, s.getForbidden("g", "t", 1)); s.updateForbidden("g", "t", 2, true); assertEquals(7, s.getForbidden("g", "t")); assertEquals(true, s.getForbidden("g", "t", 2)); s.updateForbidden("g", "t", 1, false); assertEquals(5, s.getForbidden("g", "t")); assertEquals(false, s.getForbidden("g", "t", 1)); s.updateForbidden("g", "t", 1, false); assertEquals(5, s.getForbidden("g", "t")); assertEquals(false, s.getForbidden("g", "t", 1)); s.updateForbidden("g", "t", 0, false); assertEquals(4, s.getForbidden("g", "t")); assertEquals(false, s.getForbidden("g", "t", 0)); s.updateForbidden("g", "t", 2, false); assertEquals(0, s.getForbidden("g", "t")); assertEquals(false, s.getForbidden("g", "t", 2)); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/subscription/RocksdbGroupConfigTransferTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.subscription; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.stream.Stream; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBSubscriptionGroupManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RocksdbGroupConfigTransferTest { private final String basePath = Paths.get(System.getProperty("user.home"), "unit-test-store", UUID.randomUUID().toString().substring(0, 16).toUpperCase()).toString(); private RocksDBSubscriptionGroupManager rocksDBSubscriptionGroupManager; private SubscriptionGroupManager jsonSubscriptionGroupManager; @Mock private BrokerController brokerController; @Mock private DefaultMessageStore defaultMessageStore; @Before public void init() { if (notToBeExecuted()) { return; } BrokerConfig brokerConfig = new BrokerConfig(); Mockito.lenient().when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(basePath); Mockito.lenient().when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.lenient().when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); when(defaultMessageStore.getStateMachineVersion()).thenReturn(0L); } @After public void destroy() { if (notToBeExecuted()) { return; } if (rocksDBSubscriptionGroupManager != null) { rocksDBSubscriptionGroupManager.stop(); } Path root = Paths.get(basePath); if (Files.notExists(root)) { return; } try (Stream walk = Files.walk(root)) { walk.sorted(Comparator.reverseOrder()) .forEach(p -> { try { Files.deleteIfExists(p); } catch (IOException e) { // ignore } }); } catch (IOException e) { // ignore } } public void initRocksDBSubscriptionGroupManager() { if (rocksDBSubscriptionGroupManager == null) { rocksDBSubscriptionGroupManager = new RocksDBSubscriptionGroupManager(brokerController); rocksDBSubscriptionGroupManager.load(); } } public void initJsonSubscriptionGroupManager() { if (jsonSubscriptionGroupManager == null) { jsonSubscriptionGroupManager = new SubscriptionGroupManager(brokerController); jsonSubscriptionGroupManager.load(); } } @Test public void theFirstTimeLoadJsonSubscriptionGroupManager() { if (notToBeExecuted()) { return; } initJsonSubscriptionGroupManager(); DataVersion dataVersion = jsonSubscriptionGroupManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(0L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, jsonSubscriptionGroupManager.getSubscriptionGroupTable().size()); } @Test public void theFirstTimeLoadRocksDBSubscriptionGroupManager() { if (notToBeExecuted()) { return; } initRocksDBSubscriptionGroupManager(); DataVersion dataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(0L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size()); } @Test public void addGroupLoadJsonSubscriptionGroupManager() { if (notToBeExecuted()) { return; } initJsonSubscriptionGroupManager(); int beforeSize = jsonSubscriptionGroupManager.getSubscriptionGroupTable().size(); String groupName = "testAddGroupConfig-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(groupName); subscriptionGroupConfig.setAttributes(attributes); DataVersion beforeDataVersion = jsonSubscriptionGroupManager.getDataVersion(); long beforeDataVersionCounter = beforeDataVersion.getCounter().get(); long beforeTimestamp = beforeDataVersion.getTimestamp(); jsonSubscriptionGroupManager.updateSubscriptionGroupConfig(subscriptionGroupConfig); int afterSize = jsonSubscriptionGroupManager.getSubscriptionGroupTable().size(); DataVersion afterDataVersion = jsonSubscriptionGroupManager.getDataVersion(); long afterDataVersionCounter = afterDataVersion.getCounter().get(); long afterTimestamp = afterDataVersion.getTimestamp(); Assert.assertEquals(0, beforeDataVersionCounter); Assert.assertEquals(1, afterDataVersionCounter); Assert.assertEquals(1, afterSize - beforeSize); Assert.assertTrue(afterTimestamp >= beforeTimestamp); } @Test public void addForbiddenGroupLoadJsonSubscriptionGroupManager() { if (notToBeExecuted()) { return; } initJsonSubscriptionGroupManager(); int beforeSize = jsonSubscriptionGroupManager.getForbiddenTable().size(); String groupName = "testAddGroupConfig-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(groupName); subscriptionGroupConfig.setAttributes(attributes); DataVersion beforeDataVersion = jsonSubscriptionGroupManager.getDataVersion(); long beforeDataVersionCounter = beforeDataVersion.getCounter().get(); long beforeTimestamp = beforeDataVersion.getTimestamp(); jsonSubscriptionGroupManager.setForbidden(groupName, "topic", 0); int afterSize = jsonSubscriptionGroupManager.getForbiddenTable().size(); DataVersion afterDataVersion = jsonSubscriptionGroupManager.getDataVersion(); long afterDataVersionCounter = afterDataVersion.getCounter().get(); long afterTimestamp = afterDataVersion.getTimestamp(); Assert.assertEquals(1, afterDataVersionCounter - beforeDataVersionCounter); Assert.assertEquals(1, afterSize - beforeSize); Assert.assertTrue(afterTimestamp >= beforeTimestamp); } @Test public void addGroupLoadRocksdbSubscriptionGroupManager() { if (notToBeExecuted()) { return; } initRocksDBSubscriptionGroupManager(); int beforeSize = rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size(); String groupName = "testAddGroupConfig-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(groupName); subscriptionGroupConfig.setAttributes(attributes); DataVersion beforeDataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); long beforeDataVersionCounter = beforeDataVersion.getCounter().get(); long beforeTimestamp = beforeDataVersion.getTimestamp(); rocksDBSubscriptionGroupManager.updateSubscriptionGroupConfig(subscriptionGroupConfig); int afterSize = rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size(); DataVersion afterDataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); long afterDataVersionCounter = afterDataVersion.getCounter().get(); long afterTimestamp = afterDataVersion.getTimestamp(); Assert.assertEquals(1, afterDataVersionCounter); Assert.assertEquals(0, beforeDataVersionCounter); Assert.assertEquals(1, afterSize - beforeSize); Assert.assertTrue(afterTimestamp >= beforeTimestamp); } @Test public void addForbiddenLoadRocksdbSubscriptionGroupManager() { if (notToBeExecuted()) { return; } initRocksDBSubscriptionGroupManager(); int beforeSize = rocksDBSubscriptionGroupManager.getForbiddenTable().size(); String groupName = "testAddGroupConfig-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(groupName); subscriptionGroupConfig.setAttributes(attributes); DataVersion beforeDataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); long beforeDataVersionCounter = beforeDataVersion.getCounter().get(); long beforeTimestamp = beforeDataVersion.getTimestamp(); rocksDBSubscriptionGroupManager.updateForbidden(groupName, "topic", 0, true); int afterSize = rocksDBSubscriptionGroupManager.getForbiddenTable().size(); DataVersion afterDataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); long afterDataVersionCounter = afterDataVersion.getCounter().get(); long afterTimestamp = afterDataVersion.getTimestamp(); Assert.assertEquals(1, afterDataVersionCounter - beforeDataVersionCounter); Assert.assertEquals(1, afterSize - beforeSize); Assert.assertTrue(afterTimestamp >= beforeTimestamp); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size()); } @Test public void theSecondTimeLoadJsonSubscriptionGroupManager() { if (notToBeExecuted()) { return; } addGroupLoadJsonSubscriptionGroupManager(); jsonSubscriptionGroupManager.stop(); rocksDBSubscriptionGroupManager = null; addForbiddenGroupLoadJsonSubscriptionGroupManager(); jsonSubscriptionGroupManager.stop(); rocksDBSubscriptionGroupManager = null; jsonSubscriptionGroupManager = new SubscriptionGroupManager(brokerController); jsonSubscriptionGroupManager.load(); DataVersion dataVersion = jsonSubscriptionGroupManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(2L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, jsonSubscriptionGroupManager.getSubscriptionGroupTable().size()); Assert.assertNotEquals(0, jsonSubscriptionGroupManager.getForbiddenTable().size()); Assert.assertNotEquals(0, jsonSubscriptionGroupManager.getSubscriptionGroupTable().size()); } @Test public void theSecondTimeLoadRocksdbTopicConfigManager() { if (notToBeExecuted()) { return; } addGroupLoadRocksdbSubscriptionGroupManager(); rocksDBSubscriptionGroupManager.stop(); rocksDBSubscriptionGroupManager = null; addForbiddenLoadRocksdbSubscriptionGroupManager(); rocksDBSubscriptionGroupManager.stop(); rocksDBSubscriptionGroupManager = null; rocksDBSubscriptionGroupManager = new RocksDBSubscriptionGroupManager(brokerController); rocksDBSubscriptionGroupManager.load(); DataVersion dataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(2L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getForbiddenTable().size()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size()); } @Test public void jsonUpgradeToRocksdb() { if (notToBeExecuted()) { return; } addGroupLoadJsonSubscriptionGroupManager(); addForbiddenGroupLoadJsonSubscriptionGroupManager(); initRocksDBSubscriptionGroupManager(); DataVersion dataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(3L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getForbiddenTable().size()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size()); Assert.assertEquals(rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size(), jsonSubscriptionGroupManager.getSubscriptionGroupTable().size()); Assert.assertEquals(rocksDBSubscriptionGroupManager.getForbiddenTable().size(), jsonSubscriptionGroupManager.getForbiddenTable().size()); rocksDBSubscriptionGroupManager.stop(); rocksDBSubscriptionGroupManager = new RocksDBSubscriptionGroupManager(brokerController); rocksDBSubscriptionGroupManager.load(); dataVersion = rocksDBSubscriptionGroupManager.getDataVersion(); Assert.assertEquals(3L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getForbiddenTable().size()); Assert.assertNotEquals(0, rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size()); Assert.assertEquals(rocksDBSubscriptionGroupManager.getSubscriptionGroupTable().size(), jsonSubscriptionGroupManager.getSubscriptionGroupTable().size()); Assert.assertEquals(rocksDBSubscriptionGroupManager.getForbiddenTable().size(), jsonSubscriptionGroupManager.getForbiddenTable().size()); } private boolean notToBeExecuted() { return MixAll.isMac() || MixAll.isWindows(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.subscription; import com.google.common.collect.ImmutableMap; import java.nio.file.Paths; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.SubscriptionGroupAttributes; import org.apache.rocketmq.common.attribute.BooleanAttribute; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class SubscriptionGroupManagerTest { private String group = "group"; private final String basePath = Paths.get(System.getProperty("user.home"), "unit-test-store", UUID.randomUUID().toString().substring(0, 16).toUpperCase()).toString(); @Mock private BrokerController brokerControllerMock; private SubscriptionGroupManager subscriptionGroupManager; @Before public void before() { SubscriptionGroupAttributes.ALL.put("test", new BooleanAttribute( "test", false, false )); subscriptionGroupManager = spy(new SubscriptionGroupManager(brokerControllerMock)); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(basePath); Mockito.lenient().when(brokerControllerMock.getMessageStoreConfig()).thenReturn(messageStoreConfig); } @After public void destroy() { if (notToBeExecuted()) { return; } if (subscriptionGroupManager != null) { subscriptionGroupManager.stop(); } } @Test public void testUpdateAndCreateSubscriptionGroupInRocksdb() { if (notToBeExecuted()) { return; } group += System.currentTimeMillis(); updateSubscriptionGroupConfig(); } @Test public void updateSubscriptionGroupConfig() { if (notToBeExecuted()) { return; } SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); Map attr = ImmutableMap.of("+test", "true"); subscriptionGroupConfig.setAttributes(attr); SubscriptionGroupManager subscriptionGroupManager = new SubscriptionGroupManager(brokerControllerMock); subscriptionGroupManager.updateSubscriptionGroupConfig(subscriptionGroupConfig); SubscriptionGroupConfig result = subscriptionGroupManager.getSubscriptionGroupTable().get(group); assertThat(result).isNotNull(); assertThat(result.getGroupName()).isEqualTo(group); assertThat(result.getAttributes().get("test")).isEqualTo("true"); SubscriptionGroupConfig subscriptionGroupConfig1 = new SubscriptionGroupConfig(); subscriptionGroupConfig1.setGroupName(group); Map attrRemove = ImmutableMap.of("-test", ""); subscriptionGroupConfig1.setAttributes(attrRemove); assertThatThrownBy(() -> subscriptionGroupManager.updateSubscriptionGroupConfig(subscriptionGroupConfig1)) .isInstanceOf(RuntimeException.class).hasMessage("attempt to update an unchangeable attribute. key: test"); } private boolean notToBeExecuted() { return MixAll.isMac(); } @Test public void testUpdateSubscriptionGroupConfigList_NullConfigList() { if (notToBeExecuted()) { return; } subscriptionGroupManager.updateSubscriptionGroupConfigList(null); // Verifying that persist() is not called verify(subscriptionGroupManager, never()).persist(); } @Test public void testUpdateSubscriptionGroupConfigList_EmptyConfigList() { if (notToBeExecuted()) { return; } subscriptionGroupManager.updateSubscriptionGroupConfigList(Collections.emptyList()); // Verifying that persist() is not called verify(subscriptionGroupManager, never()).persist(); } @Test public void testUpdateSubscriptionGroupConfigList_ValidConfigList() { if (notToBeExecuted()) { return; } final List configList = new LinkedList<>(); final List groupNames = new LinkedList<>(); for (int i = 0; i < 10; i++) { SubscriptionGroupConfig config = new SubscriptionGroupConfig(); String groupName = String.format("group-%d", i); config.setGroupName(groupName); configList.add(config); groupNames.add(groupName); } SubscriptionGroupManager subscriptionGroupManager = new SubscriptionGroupManager(brokerControllerMock); subscriptionGroupManager.updateSubscriptionGroupConfigList(configList); groupNames.forEach(groupName -> assertThat(subscriptionGroupManager.getSubscriptionGroupTable().get(groupName)).isNotNull()); } @Test public void testSubGroupTable() { // Empty SubscriptionGroupManager subscriptionGroupManager.getSubscriptionGroupTable().clear(); Map result = subscriptionGroupManager.subGroupTable(subscriptionGroupManager.getDataVersion().toJson(), 0, 200); assertThat(result).isEmpty(); // fill SubscriptionGroupManager int totalGroupNum = 50000; fillSubscriptionGroupManager(totalGroupNum); // Null DataVersion int beginIndex = 0, maxNum = 200; int endIndex = beginIndex + maxNum - 1; result = subscriptionGroupManager.subGroupTable(null, beginIndex, maxNum); Assert.assertEquals(maxNum, result.size()); Assert.assertTrue(result.containsKey(String.format("group-%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("group-%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("group-%05d", endIndex + 1))); // Different DataVersion DataVersion differentVersion = new DataVersion(); differentVersion.setCounter(new AtomicLong(1000L)); // different counter differentVersion.setTimestamp(System.currentTimeMillis()); result = subscriptionGroupManager.subGroupTable(differentVersion.toJson(), 300, maxNum); Assert.assertEquals(maxNum, result.size()); Assert.assertTrue(result.containsKey(String.format("group-%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("group-%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("group-%05d", endIndex + 1))); // BeginIndexOutOfRange result = subscriptionGroupManager.subGroupTable(subscriptionGroupManager.getDataVersion().toJson(), totalGroupNum, 200); Assert.assertTrue(result.isEmpty()); // Normal Case beginIndex = 300; endIndex = beginIndex + maxNum - 1; result = subscriptionGroupManager.subGroupTable(subscriptionGroupManager.getDataVersion().toJson(), beginIndex, maxNum); Assert.assertEquals(maxNum, result.size()); Assert.assertTrue(result.containsKey(String.format("group-%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("group-%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("group-%05d", endIndex + 1))); // NotFullTopicConfigTable beginIndex = 49950; endIndex = Math.min(subscriptionGroupManager.getSubscriptionGroupTable().size() - 1, beginIndex + maxNum - 1); result = subscriptionGroupManager.subGroupTable(subscriptionGroupManager.getDataVersion().toJson(), beginIndex, maxNum); Assert.assertEquals(totalGroupNum - beginIndex, result.size()); Assert.assertTrue(result.containsKey(String.format("group-%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("group-%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("group-%05d", endIndex + 1))); } private void fillSubscriptionGroupManager(int num) { for (int i = num - 1; i >= 0; i--) { SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); String groupName = String.format("group-%05d", i); subscriptionGroupConfig.setGroupName(groupName); Map attr = ImmutableMap.of("+test", "true"); subscriptionGroupConfig.setAttributes(attr); subscriptionGroupManager.getSubscriptionGroupTable().put(groupName, subscriptionGroupConfig); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/topic/RocksdbTopicConfigManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBTopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.Attribute; import org.apache.rocketmq.common.attribute.BooleanAttribute; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.EnumAttribute; import org.apache.rocketmq.common.attribute.LongRangeAttribute; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static com.google.common.collect.Sets.newHashSet; import static java.util.Arrays.asList; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RocksdbTopicConfigManagerTest { private final String basePath = Paths.get(System.getProperty("user.home"), "unit-test-store", UUID.randomUUID().toString().substring(0, 16).toUpperCase()).toString(); private RocksDBTopicConfigManager topicConfigManager; @Mock private BrokerController brokerController; @Mock private DefaultMessageStore defaultMessageStore; @Before public void init() { if (notToBeExecuted()) { return; } BrokerConfig brokerConfig = new BrokerConfig(); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(basePath); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.lenient().when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); Mockito.lenient().when(defaultMessageStore.getStateMachineVersion()).thenReturn(0L); topicConfigManager = new RocksDBTopicConfigManager(brokerController); topicConfigManager.load(); } @After public void destroy() { if (notToBeExecuted()) { return; } if (topicConfigManager != null) { topicConfigManager.stop(); } } @Test public void testAddUnsupportedKeyOnCreating() { if (notToBeExecuted()) { return; } String unsupportedKey = "key4"; String topicName = "testAddUnsupportedKeyOnCreating-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+" + unsupportedKey, "value1"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("unsupported key: " + unsupportedKey, runtimeException.getMessage()); } @Test public void testAddWrongFormatKeyOnCreating() { if (notToBeExecuted()) { return; } String topicName = "testAddWrongFormatKeyOnCreating-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("++enum.key", "value1"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("kv string format wrong.", runtimeException.getMessage()); } @Test public void testDeleteKeyOnCreating() { if (notToBeExecuted()) { return; } String topicName = "testDeleteKeyOnCreating-" + System.currentTimeMillis(); String key = "enum.key"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("-" + key, ""); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("only add attribute is supported while creating topic. key: " + key, runtimeException.getMessage()); } @Test public void testAddWrongValueOnCreating() { if (notToBeExecuted()) { return; } String topicName = "testAddWrongValueOnCreating-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); attributes.put("+" + TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName(), "wrong-value"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("value is not in set: [SimpleCQ, BatchCQ]", runtimeException.getMessage()); } @Test public void testNormalAddKeyOnCreating() { if (notToBeExecuted()) { return; } String topic = "testNormalAddKeyOnCreating-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+long.range.key", "16"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); TopicConfig existingTopicConfig = topicConfigManager.getTopicConfigTable().get(topic); Assert.assertEquals("enum-2", existingTopicConfig.getAttributes().get("enum.key")); Assert.assertEquals("16", existingTopicConfig.getAttributes().get("long.range.key")); } @Test public void testAddDuplicatedKeyOnUpdating() { if (notToBeExecuted()) { return; } String duplicatedKey = "long.range.key"; String topicName = "testAddDuplicatedKeyOnUpdating-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-3"); attributes.put("+bool.key", "true"); attributes.put("+long.range.key", "12"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); attributes = new HashMap<>(); attributes.put("+" + duplicatedKey, "11"); attributes.put("-" + duplicatedKey, ""); TopicConfig duplicateTopicConfig = new TopicConfig(); duplicateTopicConfig.setTopicName(topicName); duplicateTopicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(duplicateTopicConfig)); Assert.assertEquals("alter duplication key. key: " + duplicatedKey, runtimeException.getMessage()); } @Test public void testDeleteNonexistentKeyOnUpdating() { if (notToBeExecuted()) { return; } String key = "nonexisting.key"; String topicName = "testDeleteNonexistentKeyOnUpdating-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+bool.key", "true"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); attributes = new HashMap<>(); attributes.clear(); attributes.put("-" + key, ""); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("attempt to delete a nonexistent key: " + key, runtimeException.getMessage()); } @Test public void testAlterTopicWithoutChangingAttributes() { if (notToBeExecuted()) { return; } String topic = "testAlterTopicWithoutChangingAttributes-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+bool.key", "true"); TopicConfig topicConfigInit = new TopicConfig(); topicConfigInit.setTopicName(topic); topicConfigInit.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfigInit); Assert.assertEquals("enum-2", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("enum.key")); Assert.assertEquals("true", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("bool.key")); TopicConfig topicConfigAlter = new TopicConfig(); topicConfigAlter.setTopicName(topic); topicConfigAlter.setReadQueueNums(10); topicConfigAlter.setWriteQueueNums(10); topicConfigManager.updateTopicConfig(topicConfigAlter); Assert.assertEquals("enum-2", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("enum.key")); Assert.assertEquals("true", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("bool.key")); } @Test public void testNormalUpdateUnchangeableKeyOnUpdating() { if (notToBeExecuted()) { return; } String topic = "testNormalUpdateUnchangeableKeyOnUpdating-" + System.currentTimeMillis(); supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", true, false), new LongRangeAttribute("long.range.key", false, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+long.range.key", "14"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); attributes.put("+long.range.key", "16"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("attempt to update an unchangeable attribute. key: long.range.key", runtimeException.getMessage()); } @Test public void testNormalQueryKeyOnGetting() { if (notToBeExecuted()) { return; } String topic = "testNormalQueryKeyOnGetting-" + System.currentTimeMillis(); String unchangeable = "bool.key"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+" + unchangeable, "true"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); TopicConfig topicConfigUpdated = topicConfigManager.getTopicConfigTable().get(topic); Assert.assertEquals(CQType.SimpleCQ, QueueTypeUtils.getCQType(Optional.of(topicConfigUpdated))); Assert.assertEquals("true", topicConfigUpdated.getAttributes().get(unchangeable)); } private void supportAttributes(List supportAttributes) { Map supportedAttributes = new HashMap<>(); for (Attribute supportAttribute : supportAttributes) { supportedAttributes.put(supportAttribute.getName(), supportAttribute); } TopicAttributes.ALL.putAll(supportedAttributes); } private boolean notToBeExecuted() { return MixAll.isMac(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/topic/RocksdbTopicConfigTransferTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.config.v1.RocksDBTopicConfigManager; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RocksdbTopicConfigTransferTest { private final String basePath = Paths.get(System.getProperty("user.home"), "unit-test-store", UUID.randomUUID().toString().substring(0, 16).toUpperCase()).toString(); private RocksDBTopicConfigManager rocksdbTopicConfigManager; private TopicConfigManager jsonTopicConfigManager; @Mock private BrokerController brokerController; @Mock private DefaultMessageStore defaultMessageStore; @Before public void init() { if (notToBeExecuted()) { return; } BrokerConfig brokerConfig = new BrokerConfig(); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(basePath); Mockito.lenient().when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); when(defaultMessageStore.getStateMachineVersion()).thenReturn(0L); } @After public void destroy() { if (notToBeExecuted()) { return; } Path pathToBeDeleted = Paths.get(basePath); try { Files.walk(pathToBeDeleted) .sorted(Comparator.reverseOrder()) .forEach(path -> { try { Files.delete(path); } catch (IOException e) { // ignore } }); } catch (IOException e) { // ignore } if (rocksdbTopicConfigManager != null) { rocksdbTopicConfigManager.stop(); } } public void initRocksdbTopicConfigManager() { if (rocksdbTopicConfigManager == null) { rocksdbTopicConfigManager = new RocksDBTopicConfigManager(brokerController); rocksdbTopicConfigManager.load(); } } public void initJsonTopicConfigManager() { if (jsonTopicConfigManager == null) { jsonTopicConfigManager = new TopicConfigManager(brokerController); jsonTopicConfigManager.load(); } } @Test public void theFirstTimeLoadJsonTopicConfigManager() { if (notToBeExecuted()) { return; } initJsonTopicConfigManager(); DataVersion dataVersion = jsonTopicConfigManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(0L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, jsonTopicConfigManager.getTopicConfigTable().size()); } @Test public void theFirstTimeLoadRocksdbTopicConfigManager() { if (notToBeExecuted()) { return; } initRocksdbTopicConfigManager(); DataVersion dataVersion = rocksdbTopicConfigManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(0L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksdbTopicConfigManager.getTopicConfigTable().size()); } @Test public void addTopicLoadJsonTopicConfigManager() { if (notToBeExecuted()) { return; } initJsonTopicConfigManager(); String topicName = "testAddTopicConfig-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); DataVersion beforeDataVersion = jsonTopicConfigManager.getDataVersion(); long beforeDataVersionCounter = beforeDataVersion.getCounter().get(); long beforeTimestamp = beforeDataVersion.getTimestamp(); jsonTopicConfigManager.updateTopicConfig(topicConfig); DataVersion afterDataVersion = jsonTopicConfigManager.getDataVersion(); long afterDataVersionCounter = afterDataVersion.getCounter().get(); long afterTimestamp = afterDataVersion.getTimestamp(); Assert.assertEquals(0, beforeDataVersionCounter); Assert.assertEquals(1, afterDataVersionCounter); Assert.assertTrue(afterTimestamp >= beforeTimestamp); } @Test public void addTopicLoadRocksdbTopicConfigManager() { if (notToBeExecuted()) { return; } initRocksdbTopicConfigManager(); String topicName = "testAddTopicConfig-" + System.currentTimeMillis(); Map attributes = new HashMap<>(); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setAttributes(attributes); DataVersion beforeDataVersion = rocksdbTopicConfigManager.getDataVersion(); long beforeDataVersionCounter = beforeDataVersion.getCounter().get(); long beforeTimestamp = beforeDataVersion.getTimestamp(); rocksdbTopicConfigManager.updateTopicConfig(topicConfig); DataVersion afterDataVersion = rocksdbTopicConfigManager.getDataVersion(); long afterDataVersionCounter = afterDataVersion.getCounter().get(); long afterTimestamp = afterDataVersion.getTimestamp(); Assert.assertEquals(0, beforeDataVersionCounter); Assert.assertEquals(1, afterDataVersionCounter); Assert.assertTrue(afterTimestamp >= beforeTimestamp); } @Test public void theSecondTimeLoadJsonTopicConfigManager() { if (notToBeExecuted()) { return; } addTopicLoadJsonTopicConfigManager(); jsonTopicConfigManager.stop(); jsonTopicConfigManager = new TopicConfigManager(brokerController); jsonTopicConfigManager.load(); DataVersion dataVersion = jsonTopicConfigManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(1L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, jsonTopicConfigManager.getTopicConfigTable().size()); } @Test public void theSecondTimeLoadRocksdbTopicConfigManager() { if (notToBeExecuted()) { return; } addTopicLoadRocksdbTopicConfigManager(); rocksdbTopicConfigManager.stop(); rocksdbTopicConfigManager = null; rocksdbTopicConfigManager = new RocksDBTopicConfigManager(brokerController); rocksdbTopicConfigManager.load(); DataVersion dataVersion = rocksdbTopicConfigManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(1L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksdbTopicConfigManager.getTopicConfigTable().size()); } @Test public void jsonUpgradeToRocksdb() { if (notToBeExecuted()) { return; } addTopicLoadJsonTopicConfigManager(); initRocksdbTopicConfigManager(); DataVersion dataVersion = rocksdbTopicConfigManager.getDataVersion(); Assert.assertNotNull(dataVersion); Assert.assertEquals(2L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksdbTopicConfigManager.getTopicConfigTable().size()); Assert.assertEquals(rocksdbTopicConfigManager.getTopicConfigTable().size(), jsonTopicConfigManager.getTopicConfigTable().size()); rocksdbTopicConfigManager.stop(); rocksdbTopicConfigManager = new RocksDBTopicConfigManager(brokerController); rocksdbTopicConfigManager.load(); dataVersion = rocksdbTopicConfigManager.getDataVersion(); Assert.assertEquals(2L, dataVersion.getCounter().get()); Assert.assertEquals(0L, dataVersion.getStateVersion()); Assert.assertNotEquals(0, rocksdbTopicConfigManager.getTopicConfigTable().size()); Assert.assertEquals(rocksdbTopicConfigManager.getTopicConfigTable().size(), rocksdbTopicConfigManager.getTopicConfigTable().size()); } private boolean notToBeExecuted() { return MixAll.isMac(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/topic/TopicConfigManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.Attribute; import org.apache.rocketmq.common.attribute.BooleanAttribute; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.EnumAttribute; import org.apache.rocketmq.common.attribute.LongRangeAttribute; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static com.google.common.collect.Sets.newHashSet; import static java.util.Arrays.asList; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TopicConfigManagerTest { private final String basePath = Paths.get(System.getProperty("user.home"), "unit-test-store", UUID.randomUUID().toString().substring(0, 16).toUpperCase()).toString(); private TopicConfigManager topicConfigManager; @Mock private BrokerController brokerController; @Mock private DefaultMessageStore defaultMessageStore; @Before public void init() { BrokerConfig brokerConfig = new BrokerConfig(); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(basePath); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.lenient().when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); when(defaultMessageStore.getStateMachineVersion()).thenReturn(0L); topicConfigManager = new TopicConfigManager(brokerController); } @Test public void testAddUnsupportedKeyOnCreating() { String unsupportedKey = "key4"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+" + unsupportedKey, "value1"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("unsupported key: " + unsupportedKey, runtimeException.getMessage()); } @Test public void testAddWrongFormatKeyOnCreating() { supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("++enum.key", "value1"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("kv string format wrong.", runtimeException.getMessage()); } @Test public void testDeleteKeyOnCreating() { String key = "enum.key"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("-" + key, ""); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("only add attribute is supported while creating topic. key: " + key, runtimeException.getMessage()); } @Test public void testAddWrongValueOnCreating() { Map attributes = new HashMap<>(); attributes.put("+" + TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName(), "wrong-value"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("value is not in set: [SimpleCQ, BatchCQ]", runtimeException.getMessage()); } @Test public void testNormalAddKeyOnCreating() { String topic = "new-topic"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+long.range.key", "16"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); TopicConfig existingTopicConfig = topicConfigManager.getTopicConfigTable().get(topic); Assert.assertEquals("enum-2", existingTopicConfig.getAttributes().get("enum.key")); Assert.assertEquals("16", existingTopicConfig.getAttributes().get("long.range.key")); // assert file } @Test public void testAddDuplicatedKeyOnUpdating() { String duplicatedKey = "long.range.key"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); createTopic(); Map attributes = new HashMap<>(); attributes.put("+" + duplicatedKey, "11"); attributes.put("-" + duplicatedKey, ""); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("alter duplication key. key: " + duplicatedKey, runtimeException.getMessage()); } private void createTopic() { Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-3"); attributes.put("+bool.key", "true"); attributes.put("+long.range.key", "12"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); } @Test public void testDeleteNonexistentKeyOnUpdating() { String key = "nonexisting.key"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+bool.key", "true"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("new-topic"); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); attributes = new HashMap<>(); attributes.clear(); attributes.put("-" + key, ""); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("attempt to delete a nonexistent key: " + key, runtimeException.getMessage()); } @Test public void testAlterTopicWithoutChangingAttributes() { String topic = "new-topic"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+enum.key", "enum-2"); attributes.put("+bool.key", "true"); TopicConfig topicConfigInit = new TopicConfig(); topicConfigInit.setTopicName(topic); topicConfigInit.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfigInit); Assert.assertEquals("enum-2", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("enum.key")); Assert.assertEquals("true", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("bool.key")); TopicConfig topicConfigAlter = new TopicConfig(); topicConfigAlter.setTopicName(topic); topicConfigAlter.setReadQueueNums(10); topicConfigAlter.setWriteQueueNums(10); topicConfigManager.updateTopicConfig(topicConfigAlter); Assert.assertEquals("enum-2", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("enum.key")); Assert.assertEquals("true", topicConfigManager.getTopicConfigTable().get(topic).getAttributes().get("bool.key")); } @Test public void testNormalUpdateUnchangeableKeyOnUpdating() { String topic = "exist-topic"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", true, false), new LongRangeAttribute("long.range.key", false, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+long.range.key", "14"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); attributes.put("+long.range.key", "16"); topicConfig.setAttributes(attributes); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> topicConfigManager.updateTopicConfig(topicConfig)); Assert.assertEquals("attempt to update an unchangeable attribute. key: long.range.key", runtimeException.getMessage()); } @Test public void testNormalQueryKeyOnGetting() { String topic = "exist-topic"; String unchangeable = "bool.key"; supportAttributes(asList( new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"), new BooleanAttribute("bool.key", false, false), new LongRangeAttribute("long.range.key", true, 10, 20, 15) )); Map attributes = new HashMap<>(); attributes.put("+" + unchangeable, "true"); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setAttributes(attributes); topicConfigManager.updateTopicConfig(topicConfig); TopicConfig topicConfigUpdated = topicConfigManager.getTopicConfigTable().get(topic); Assert.assertEquals(CQType.SimpleCQ, QueueTypeUtils.getCQType(Optional.of(topicConfigUpdated))); Assert.assertEquals("true", topicConfigUpdated.getAttributes().get(unchangeable)); } private void supportAttributes(List supportAttributes) { Map supportedAttributes = new HashMap<>(); for (Attribute supportAttribute : supportAttributes) { supportedAttributes.put(supportAttribute.getName(), supportAttribute); } TopicAttributes.ALL.putAll(supportedAttributes); } private void fillTopicConfigTable(int num) { ConcurrentHashMap topicConfigTable = new ConcurrentHashMap<>(); for (int i = num - 1; i >= 0; i--) { String topicName = String.format("topic%05d", i); TopicConfig topicConfig = new TopicConfig(topicName, 1, 1, PermName.PERM_READ | PermName.PERM_WRITE, 0); topicConfigTable.put(topicName, topicConfig); } topicConfigManager.setTopicConfigTable(topicConfigTable); } @Test public void testSubTopicConfigTable() { // Empty TopicConfigTable topicConfigManager.getTopicConfigTable().clear(); Map result = topicConfigManager.subTopicConfigTable(topicConfigManager.getDataVersion().toJson(), 0, 200); Assert.assertTrue(result.isEmpty()); // init table, topic range [0, N) final int totalTopicNum = 50000; fillTopicConfigTable(totalTopicNum); // Null DataVersion int beginIndex = 0, maxNum = 200; int endIndex = beginIndex + maxNum - 1; result = topicConfigManager.subTopicConfigTable(null, beginIndex, maxNum); Assert.assertEquals(maxNum, result.size()); Assert.assertTrue(result.containsKey(String.format("topic%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("topic%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("topic%05d", endIndex + 1))); // Different DataVersion DataVersion differentVersion = new DataVersion(); differentVersion.setCounter(new AtomicLong(1000L)); // different counter differentVersion.setTimestamp(System.currentTimeMillis()); result = topicConfigManager.subTopicConfigTable(differentVersion.toJson(), 300, maxNum); Assert.assertEquals(maxNum, result.size()); Assert.assertTrue(result.containsKey(String.format("topic%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("topic%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("topic%05d", endIndex + 1))); // BeginIndexOutOfRange result = topicConfigManager.subTopicConfigTable(topicConfigManager.getDataVersion().toJson(), totalTopicNum, 200); Assert.assertTrue(result.isEmpty()); // Normal Case beginIndex = 300; endIndex = beginIndex + maxNum - 1; result = topicConfigManager.subTopicConfigTable(topicConfigManager.getDataVersion().toJson(), beginIndex, maxNum); Assert.assertEquals(maxNum, result.size()); Assert.assertTrue(result.containsKey(String.format("topic%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("topic%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("topic%05d", endIndex + 1))); // NotFullTopicConfigTable beginIndex = 49950; endIndex = Math.min(topicConfigManager.getTopicConfigTable().size() - 1, beginIndex + maxNum - 1); result = topicConfigManager.subTopicConfigTable(topicConfigManager.getDataVersion().toJson(), beginIndex, maxNum); Assert.assertEquals(totalTopicNum - beginIndex, result.size()); Assert.assertTrue(result.containsKey(String.format("topic%05d", ThreadLocalRandom.current().nextInt(beginIndex, endIndex)))); Assert.assertFalse(result.containsKey(String.format("topic%05d", beginIndex - 1))); Assert.assertFalse(result.containsKey(String.format("topic%05d", endIndex + 1))); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/topic/TopicQueueMappingCleanServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.rpc.RpcClient; import org.apache.rocketmq.remoting.rpc.RpcRequest; import org.apache.rocketmq.remoting.rpc.RpcResponse; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TopicQueueMappingCleanServiceTest { @Mock private BrokerController brokerController; @Mock private TopicQueueMappingManager topicQueueMappingManager; @Mock private RpcClient rpcClient; @Mock private MessageStoreConfig messageStoreConfig; @Mock private BrokerConfig brokerConfig; @Mock private BrokerOuterAPI brokerOuterAPI; private TopicQueueMappingCleanService topicQueueMappingCleanService; private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; private final String deleteWhen = "00;01;02;03;04;05;06;07;08;09;10;11;12;13;14;15;16;17;18;19;20;21;22;23"; @Before public void init() { when(brokerOuterAPI.getRpcClient()).thenReturn(rpcClient); when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); when(brokerController.getTopicQueueMappingManager()).thenReturn(topicQueueMappingManager); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); topicQueueMappingCleanService = new TopicQueueMappingCleanService(brokerController); } @Test public void testCleanItemExpiredNoChange() throws Exception { when(messageStoreConfig.getDeleteWhen()).thenReturn("04"); topicQueueMappingCleanService.cleanItemExpired(); verify(topicQueueMappingManager, never()).updateTopicQueueMapping(any(), anyBoolean(), anyBoolean(), anyBoolean()); } @Test public void testCleanItemExpiredWithChange() throws Exception { when(messageStoreConfig.getDeleteWhen()).thenReturn(deleteWhen); TopicQueueMappingDetail mappingDetail = new TopicQueueMappingDetail(defaultTopic, 2, defaultBroker, 1); mappingDetail.getHostedQueues().put(0, Arrays.asList(new LogicQueueMappingItem(0, 0, defaultBroker, 0, 0, 100, 0, 0), new LogicQueueMappingItem(0, 1, defaultBroker, 1, 100, 200, 0, 0))); when(topicQueueMappingManager.getTopicQueueMappingTable()).thenReturn(new ConcurrentHashMap<>(Collections.singletonMap(defaultTopic, mappingDetail))); when(brokerConfig.getBrokerName()).thenReturn(defaultBroker); TopicStatsTable topicStatsTable = mock(TopicStatsTable.class); Map offsetTable = new ConcurrentHashMap<>(); TopicOffset topicOffset = new TopicOffset(); topicOffset.setMinOffset(0); topicOffset.setMaxOffset(0); MessageQueue messageQueue = new MessageQueue(defaultTopic, defaultBroker, 0); offsetTable.put(messageQueue, topicOffset); when(topicStatsTable.getOffsetTable()).thenReturn(offsetTable); when(rpcClient.invoke(any(RpcRequest.class), anyLong())).thenReturn(CompletableFuture.completedFuture(new RpcResponse(0, null, topicStatsTable))); DataVersion dataVersion = mock(DataVersion.class); when(topicQueueMappingManager.getDataVersion()).thenReturn(dataVersion); topicQueueMappingCleanService.cleanItemExpired(); verify(topicQueueMappingManager, times(1)).updateTopicQueueMapping(any(), anyBoolean(), anyBoolean(), anyBoolean()); } @Test public void testCleanItemListMoreThanSecondGen() throws Exception { when(brokerConfig.getBrokerName()).thenReturn(defaultBroker); when(messageStoreConfig.getDeleteWhen()).thenReturn(deleteWhen); TopicQueueMappingDetail mappingDetail = new TopicQueueMappingDetail(defaultTopic, 1, defaultBroker, 1); mappingDetail.setHostedQueues(new ConcurrentHashMap<>()); LogicQueueMappingItem logicQueueMappingItem = mock(LogicQueueMappingItem.class); when(logicQueueMappingItem.getBname()).thenReturn("broker"); mappingDetail.getHostedQueues().put(0, Collections.singletonList(logicQueueMappingItem)); ConcurrentMap topicQueueMappingTable = new ConcurrentHashMap<>(); topicQueueMappingTable.put(defaultBroker, mappingDetail); when(topicQueueMappingManager.getTopicQueueMappingTable()).thenReturn(topicQueueMappingTable); TopicRouteData topicRouteData = new TopicRouteData(); when(brokerOuterAPI.getTopicRouteInfoFromNameServer(any(), anyLong())).thenReturn(topicRouteData); topicQueueMappingCleanService.cleanItemListMoreThanSecondGen(); verify(brokerOuterAPI, times(1)).getTopicRouteInfoFromNameServer(any(), anyLong()); } @Test public void testCleanItemListMoreThanSecondGenNoChange() throws Exception { when(messageStoreConfig.getDeleteWhen()).thenReturn("04"); topicQueueMappingCleanService.cleanItemListMoreThanSecondGen(); verify(brokerOuterAPI, never()).getTopicRouteInfoFromNameServer(anyString(), anyLong()); verify(rpcClient, never()).invoke(any(RpcRequest.class), anyLong()); } @Test public void testCleanItemListMoreThanSecondGenException() throws Exception { when(brokerConfig.getBrokerName()).thenReturn(defaultBroker); when(messageStoreConfig.getDeleteWhen()).thenReturn(deleteWhen); TopicQueueMappingDetail mappingDetail = new TopicQueueMappingDetail(defaultTopic, 1, defaultBroker, 1); mappingDetail.setHostedQueues(new ConcurrentHashMap<>()); LogicQueueMappingItem logicQueueMappingItem = mock(LogicQueueMappingItem.class); when(logicQueueMappingItem.getBname()).thenReturn("broker"); mappingDetail.getHostedQueues().put(0, Collections.singletonList(logicQueueMappingItem)); ConcurrentMap topicQueueMappingTable = new ConcurrentHashMap<>(); topicQueueMappingTable.put(defaultBroker, mappingDetail); when(topicQueueMappingManager.getTopicQueueMappingTable()).thenReturn(topicQueueMappingTable); when(brokerOuterAPI.getTopicRouteInfoFromNameServer(any(), anyLong())).thenThrow(new RemotingException("Test exception")); topicQueueMappingCleanService.cleanItemListMoreThanSecondGen(); verify(brokerOuterAPI, times(1)).getTopicRouteInfoFromNameServer(any(), anyLong()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.topic; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.body.TopicQueueMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.protocol.statictopic.TopicRemappingDetailWrapper; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TopicQueueMappingManagerTest { @Mock private BrokerController brokerController; private static final String BROKER1_NAME = "broker1"; @Before public void before() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerName(BROKER1_NAME); when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir")); messageStoreConfig.setDeleteWhen("01;02;03;04;05;06;07;08;09;10;11;12;13;14;15;16;17;18;19;20;21;22;23;00"); when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); } private void delete(TopicQueueMappingManager topicQueueMappingManager) throws Exception { if (topicQueueMappingManager == null) { return; } Files.deleteIfExists(Paths.get(topicQueueMappingManager.configFilePath())); Files.deleteIfExists(Paths.get(topicQueueMappingManager.configFilePath() + ".bak")); } @Test public void testEncodeDecode() throws Exception { Map mappingDetailMap = new HashMap<>(); TopicQueueMappingManager topicQueueMappingManager = null; Set brokers = new HashSet<>(); brokers.add(BROKER1_NAME); { for (int i = 0; i < 10; i++) { String topic = UUID.randomUUID().toString(); int queueNum = 10; TopicRemappingDetailWrapper topicRemappingDetailWrapper = TopicQueueMappingUtils.createTopicConfigMapping(topic, queueNum, brokers, new HashMap<>()); assertEquals(1, topicRemappingDetailWrapper.getBrokerConfigMap().size()); TopicQueueMappingDetail topicQueueMappingDetail = topicRemappingDetailWrapper.getBrokerConfigMap().values().iterator().next().getMappingDetail(); assertEquals(queueNum, topicQueueMappingDetail.getHostedQueues().size()); mappingDetailMap.put(topic, topicQueueMappingDetail); } } { topicQueueMappingManager = new TopicQueueMappingManager(brokerController); Assert.assertTrue(topicQueueMappingManager.load()); assertEquals(0, topicQueueMappingManager.getTopicQueueMappingTable().size()); for (TopicQueueMappingDetail mappingDetail : mappingDetailMap.values()) { for (int i = 0; i < 10; i++) { topicQueueMappingManager.updateTopicQueueMapping(mappingDetail, false, false, true); } } topicQueueMappingManager.persist(); } { topicQueueMappingManager = new TopicQueueMappingManager(brokerController); Assert.assertTrue(topicQueueMappingManager.load()); assertEquals(mappingDetailMap.size(), topicQueueMappingManager.getTopicQueueMappingTable().size()); for (TopicQueueMappingDetail topicQueueMappingDetail: topicQueueMappingManager.getTopicQueueMappingTable().values()) { assertEquals(topicQueueMappingDetail, mappingDetailMap.get(topicQueueMappingDetail.getTopic())); } } delete(topicQueueMappingManager); } @Test public void testEncodePretty() { TopicQueueMappingManager topicQueueMappingManager = new TopicQueueMappingManager(null); TopicQueueMappingDetail detail = new TopicQueueMappingDetail(); detail.setTopic("testTopic"); detail.setBname("testBroker"); topicQueueMappingManager.getTopicQueueMappingTable().put("testTopic", detail); topicQueueMappingManager.getDataVersion().nextVersion(); String actual = topicQueueMappingManager.encode(true); TopicQueueMappingSerializeWrapper expectedWrapper = new TopicQueueMappingSerializeWrapper(); expectedWrapper.setTopicQueueMappingInfoMap(new ConcurrentHashMap<>(topicQueueMappingManager.getTopicQueueMappingTable())); expectedWrapper.setDataVersion(topicQueueMappingManager.getDataVersion()); String expected = JSON.toJSONString(expectedWrapper, JSONWriter.Feature.PrettyFormat); assertEquals(expected, actual); } @Test public void testEncodeNonPretty() { TopicQueueMappingManager topicQueueMappingManager = new TopicQueueMappingManager(null); TopicQueueMappingDetail detail = new TopicQueueMappingDetail(); detail.setTopic("testTopic"); detail.setBname("testBroker"); topicQueueMappingManager.getTopicQueueMappingTable().put("testTopic", detail); topicQueueMappingManager.getDataVersion().nextVersion(); String actual = topicQueueMappingManager.encode(false); TopicQueueMappingSerializeWrapper expectedWrapper = new TopicQueueMappingSerializeWrapper(); expectedWrapper.setTopicQueueMappingInfoMap(new ConcurrentHashMap<>(topicQueueMappingManager.getTopicQueueMappingTable())); expectedWrapper.setDataVersion(topicQueueMappingManager.getDataVersion()); String expected = JSON.toJSONString(expectedWrapper); assertEquals(expected, actual); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import java.net.InetSocketAddress; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class DefaultTransactionalMessageCheckListenerTest { private DefaultTransactionalMessageCheckListener listener; @Mock private MessageStore messageStore; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Before public void init() throws Exception { listener = new DefaultTransactionalMessageCheckListener(); listener.setBrokerController(brokerController); brokerController.setMessageStore(messageStore); } @After public void destroy() { // brokerController.shutdown(); } @Test public void testResolveHalfMsg() { listener.resolveHalfMsg(createMessageExt()); } @Test public void testSendCheckMessage() throws Exception { MessageExt messageExt = createMessageExt(); listener.sendCheckMessage(messageExt); } @Test public void sendCheckMessage() { listener.resolveDiscardMsg(createMessageExt()); } private MessageExtBrokerInner createMessageExt() { MessageExtBrokerInner inner = new MessageExtBrokerInner(); MessageAccessor.putProperty(inner, MessageConst.PROPERTY_REAL_QUEUE_ID, "1"); MessageAccessor.putProperty(inner, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "1234255"); MessageAccessor.putProperty(inner, MessageConst.PROPERTY_REAL_TOPIC, "realTopic"); inner.setTransactionId(inner.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); inner.setBody("check".getBytes()); inner.setMsgId("12344567890"); inner.setQueueId(0); return inner; } @Test public void testResolveDiscardMsg() { MessageExt messageExt = new MessageExt(); messageExt.setTopic(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC); messageExt.setQueueId(0); messageExt.setBody("test resolve discard msg".getBytes()); messageExt.setStoreHost(new InetSocketAddress("127.0.0.1", 10911)); messageExt.setBornHost(new InetSocketAddress("127.0.0.1", 54270)); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_TOPIC, "test_topic"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_PRODUCER_GROUP, "PID_TEST_DISCARD_MSG"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, "15"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_REAL_QUEUE_ID, "2"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_TAGS, "test_discard_msg"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "AC14157E4F1C18B4AAC27EB1A0F30000"); listener.resolveDiscardMsg(messageExt); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionMetricsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import org.apache.rocketmq.broker.transaction.TransactionMetrics; import org.apache.rocketmq.broker.transaction.TransactionMetrics.Metric; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class TransactionMetricsTest { private TransactionMetrics transactionMetrics; private String configPath; private Path path; @Before public void before() throws Exception { configPath = createBaseDir(); path = Paths.get(configPath); transactionMetrics = spy(new TransactionMetrics(configPath)); } @After public void after() throws Exception { deleteFile(configPath); assertFalse(path.toFile().exists()); } /** * test addAndGet method */ @Test public void testAddAndGet() { String topic = "testAddAndGet"; int value = 10; long result = transactionMetrics.addAndGet(topic, value); assert result == value; } @Test public void testGetTopicPair() { String topic = "getTopicPair"; Metric result = transactionMetrics.getTopicPair(topic); assert result != null; } @Test public void testGetTransactionCount() { String topicExist = "topicExist"; String topicNotExist = "topicNotExist"; transactionMetrics.addAndGet(topicExist, 10); assert transactionMetrics.getTransactionCount(topicExist) == 10; assert transactionMetrics.getTransactionCount(topicNotExist) == 0; } /** * test clean metrics */ @Test public void testCleanMetrics() { String topic = "testCleanMetrics"; int value = 10; assert transactionMetrics.addAndGet(topic, value) == value; transactionMetrics.cleanMetrics(Collections.singleton(topic)); assert transactionMetrics.getTransactionCount(topic) == 0; } @Test public void testPersist() { assertFalse(path.toFile().exists()); transactionMetrics.persist(); assertTrue(path.toFile().exists()); verify(transactionMetrics).persist(); } private String createBaseDir() { String baseDir = System.getProperty("java.io.tmpdir") + File.separator + "unitteststore-" + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { System.exit(1); } return baseDir; } private void deleteFile(String fileName) { deleteFile(new File(fileName)); } private void deleteFile(File file) { if (!file.exists()) { return; } if (file.isFile()) { file.delete(); } else if (file.isDirectory()) { File[] files = file.listFiles(); for (File file1 : files) { deleteFile(file1); } file.delete(); } } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridgeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TransactionalMessageBridgeTest { private TransactionalMessageBridge transactionBridge; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; @Before public void init() { brokerController.setMessageStore(messageStore); transactionBridge = new TransactionalMessageBridge(brokerController, messageStore); } @Test public void testPutOpMessage() { when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn( new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); boolean isSuccess = transactionBridge.writeOp(0, createMessageBrokerInner()); assertThat(isSuccess).isTrue(); } @Test public void testPutHalfMessage() { when(messageStore.putMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PutMessageResult result = transactionBridge.putHalfMessage(createMessageBrokerInner()); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); } @Test public void testAsyncPutHalfMessage() throws Exception { when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); CompletableFuture result = transactionBridge.asyncPutHalfMessage(createMessageBrokerInner()); assertThat(result.get().getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); } @Test public void testFetchMessageQueues() { Set messageQueues = transactionBridge.fetchMessageQueues(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC); assertThat(messageQueues.size()).isEqualTo(1); } @Test public void testFetchConsumeOffset() { MessageQueue mq = new MessageQueue(TransactionalMessageUtil.buildOpTopic(), this.brokerController.getBrokerConfig().getBrokerName(), 0); long offset = transactionBridge.fetchConsumeOffset(mq); assertThat(offset).isGreaterThan(-1); } @Test public void updateConsumeOffset() { MessageQueue mq = new MessageQueue(TransactionalMessageUtil.buildOpTopic(), this.brokerController.getBrokerConfig().getBrokerName(), 0); transactionBridge.updateConsumeOffset(mq, 0); } @Test public void testGetHalfMessage() { when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), ArgumentMatchers.nullable(MessageFilter.class))).thenReturn(createGetMessageResult(GetMessageStatus.NO_MESSAGE_IN_QUEUE)); PullResult result = transactionBridge.getHalfMessage(0, 0, 1); assertThat(result.getPullStatus()).isEqualTo(PullStatus.NO_NEW_MSG); } @Test public void testGetOpMessage() { when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), ArgumentMatchers.nullable(MessageFilter.class))).thenReturn(createGetMessageResult(GetMessageStatus.NO_MESSAGE_IN_QUEUE)); PullResult result = transactionBridge.getOpMessage(0, 0, 1); assertThat(result.getPullStatus()).isEqualTo(PullStatus.NO_NEW_MSG); } @Test public void testPutMessageReturnResult() { when(messageStore.putMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PutMessageResult result = transactionBridge.putMessageReturnResult(createMessageBrokerInner()); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); } @Test public void testPutMessage() { when(messageStore.putMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); Boolean success = transactionBridge.putMessage(createMessageBrokerInner()); assertThat(success).isEqualTo(true); } @Test public void testRenewImmunityHalfMessageInner() { MessageExt messageExt = createMessageBrokerInner(); final String offset = "123456789"; MessageExtBrokerInner msgInner = transactionBridge.renewImmunityHalfMessageInner(messageExt); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET,offset); assertThat(msgInner).isNotNull(); Map properties = msgInner.getProperties(); assertThat(properties).isNotNull(); String resOffset = properties.get(MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); assertThat(resOffset).isEqualTo(offset); } @Test public void testRenewHalfMessageInner() { MessageExt messageExt = new MessageExt(); long bornTimeStamp = messageExt.getBornTimestamp(); MessageExt messageExtRes = transactionBridge.renewHalfMessageInner(messageExt); assertThat(messageExtRes.getBornTimestamp()).isEqualTo(bornTimeStamp); } @Test public void testLookMessageByOffset() { when(messageStore.lookMessageByOffset(anyLong())).thenReturn(new MessageExt()); MessageExt messageExt = transactionBridge.lookMessageByOffset(123); assertThat(messageExt).isNotNull(); } @Test public void testGetHalfMessageStatusFound() { when(messageStore .getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), ArgumentMatchers.nullable(MessageFilter.class))) .thenReturn(createGetMessageResult(GetMessageStatus.FOUND)); PullResult result = transactionBridge.getHalfMessage(0, 0, 1); assertThat(result.getPullStatus()).isEqualTo(PullStatus.FOUND); } @Test public void testGetHalfMessageNull() { when(messageStore .getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), ArgumentMatchers.nullable(MessageFilter.class))) .thenReturn(null); PullResult result = transactionBridge.getHalfMessage(0, 0, 1); assertThat(result).isNull(); } private GetMessageResult createGetMessageResult(GetMessageStatus status) { GetMessageResult getMessageResult = new GetMessageResult(); getMessageResult.setStatus(status); getMessageResult.setMinOffset(100); getMessageResult.setMaxOffset(1024); getMessageResult.setNextBeginOffset(516); return getMessageResult; } private MessageExtBrokerInner createMessageBrokerInner() { MessageExtBrokerInner inner = new MessageExtBrokerInner(); inner.setTransactionId("12342123444"); inner.setBornTimestamp(System.currentTimeMillis()); inner.setBody("prepare".getBytes()); inner.setMsgId("123456-123"); inner.setQueueId(0); inner.setTopic("hello"); return inner; } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.broker.transaction.OperationResult; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TransactionalMessageServiceImplTest { private TransactionalMessageService queueTransactionMsgService; @Mock private TransactionalMessageBridge bridge; @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig(), null); @Mock private AbstractTransactionalMessageCheckListener listener; @Before public void init() { when(bridge.getBrokerController()).thenReturn(brokerController); listener.setBrokerController(brokerController); queueTransactionMsgService = new TransactionalMessageServiceImpl(bridge); } @Test public void testPrepareMessage() { MessageExtBrokerInner inner = createMessageBrokerInner(); when(bridge.putHalfMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); PutMessageResult result = queueTransactionMsgService.prepareMessage(inner); assert result.isOk(); } @Test public void testCommitMessage() { when(bridge.lookMessageByOffset(anyLong())).thenReturn(createMessageBrokerInner()); OperationResult result = queueTransactionMsgService.commitMessage(createEndTransactionRequestHeader(MessageSysFlag.TRANSACTION_COMMIT_TYPE)); assertThat(result.getResponseCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testRollbackMessage() { when(bridge.lookMessageByOffset(anyLong())).thenReturn(createMessageBrokerInner()); OperationResult result = queueTransactionMsgService.commitMessage(createEndTransactionRequestHeader(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE)); assertThat(result.getResponseCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testCheck_withDiscard() { when(bridge.fetchMessageQueues(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC)).thenReturn(createMessageQueueSet(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC)); when(bridge.getHalfMessage(0, 0, 1)).thenReturn(createDiscardPullResult(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC, 5, "hellp", 1)); when(bridge.getHalfMessage(0, 1, 1)).thenReturn(createPullResult(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC, 6, "hellp", 0)); when(bridge.getOpMessage(anyInt(), anyLong(), anyInt())).thenReturn(createOpPulResult(TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC, 1, "10", 1)); when(bridge.getBrokerController()).thenReturn(this.brokerController); long timeOut = this.brokerController.getBrokerConfig().getTransactionTimeOut(); int checkMax = this.brokerController.getBrokerConfig().getTransactionCheckMax(); final AtomicInteger checkMessage = new AtomicInteger(0); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { checkMessage.addAndGet(1); return null; } }).when(listener).resolveDiscardMsg(any(MessageExt.class)); queueTransactionMsgService.check(timeOut, checkMax, listener); assertThat(checkMessage.get()).isEqualTo(1); } @Test public void testCheck_withCheck() { when(bridge.fetchMessageQueues(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC)).thenReturn(createMessageQueueSet(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC)); when(bridge.getHalfMessage(0, 0, 1)).thenReturn(createPullResult(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC, 5, "hello", 1)); when(bridge.getHalfMessage(0, 1, 1)).thenReturn(createPullResult(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC, 6, "hellp", 0)); when(bridge.getOpMessage(anyInt(), anyLong(), anyInt())).thenReturn(createPullResult(TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC, 1, "5", 0)); when(bridge.getBrokerController()).thenReturn(this.brokerController); when(bridge.renewHalfMessageInner(any(MessageExtBrokerInner.class))).thenReturn(createMessageBrokerInner()); when(bridge.putMessageReturnResult(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); long timeOut = this.brokerController.getBrokerConfig().getTransactionTimeOut(); final int checkMax = this.brokerController.getBrokerConfig().getTransactionCheckMax(); final AtomicInteger checkMessage = new AtomicInteger(0); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { checkMessage.addAndGet(1); return checkMessage; } }).when(listener).resolveHalfMsg(any(MessageExt.class)); queueTransactionMsgService.check(timeOut, checkMax, listener); assertThat(checkMessage.get()).isEqualTo(1); } @Test public void testDeletePrepareMessage_queueFull() throws InterruptedException { ((TransactionalMessageServiceImpl)queueTransactionMsgService).getDeleteContext().put(0, new MessageQueueOpContext(0, 1)); boolean res = queueTransactionMsgService.deletePrepareMessage(createMessageBrokerInner()); assertThat(res).isTrue(); when(bridge.writeOp(any(Integer.class), any(Message.class))).thenReturn(false); res = queueTransactionMsgService.deletePrepareMessage(createMessageBrokerInner()); assertThat(res).isFalse(); } @Test public void testDeletePrepareMessage_maxSize() throws InterruptedException { brokerController.getBrokerConfig().setTransactionOpMsgMaxSize(1); brokerController.getBrokerConfig().setTransactionOpBatchInterval(3000); queueTransactionMsgService.open(); boolean res = queueTransactionMsgService.deletePrepareMessage(createMessageBrokerInner(1000, "test", "testHello")); assertThat(res).isTrue(); verify(bridge, timeout(50)).writeOp(any(Integer.class), any(Message.class)); queueTransactionMsgService.close(); } @Test public void testOpen() { boolean isOpen = queueTransactionMsgService.open(); assertThat(isOpen).isTrue(); } private PullResult createDiscardPullResult(String topic, long queueOffset, String body, int size) { PullResult result = createPullResult(topic, queueOffset, body, size); List msgs = result.getMsgFoundList(); for (MessageExt msg : msgs) { msg.putUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, "100000"); } return result; } private PullResult createPullResult(String topic, long queueOffset, String body, int size) { PullResult result = null; if (0 == size) { result = new PullResult(PullStatus.NO_NEW_MSG, 1, 0, 1, null); } else { result = new PullResult(PullStatus.FOUND, 1, 0, 1, getMessageList(queueOffset, topic, body, 1)); return result; } return result; } private PullResult createOpPulResult(String topic, long queueOffset, String body, int size) { PullResult result = createPullResult(topic, queueOffset, body, size); List msgs = result.getMsgFoundList(); for (MessageExt msg : msgs) { msg.setTags(TransactionalMessageUtil.REMOVE_TAG); } return result; } private PullResult createImmunityPulResult(String topic, long queueOffset, String body, int size) { PullResult result = createPullResult(topic, queueOffset, body, size); List msgs = result.getMsgFoundList(); for (MessageExt msg : msgs) { msg.putUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS, "0"); } return result; } private List getMessageList(long queueOffset, String topic, String body, int size) { List msgs = new ArrayList<>(); for (int i = 0; i < size; i++) { MessageExt messageExt = createMessageBrokerInner(queueOffset, topic, body); msgs.add(messageExt); } return msgs; } private Set createMessageQueueSet(String topic) { Set messageQueues = new HashSet<>(); MessageQueue messageQueue = new MessageQueue(topic, "DefaultCluster", 0); messageQueues.add(messageQueue); return messageQueues; } private EndTransactionRequestHeader createEndTransactionRequestHeader(int status) { EndTransactionRequestHeader header = new EndTransactionRequestHeader(); header.setTopic("topic"); header.setCommitLogOffset(123456789L); header.setCommitOrRollback(status); header.setMsgId("12345678"); header.setTransactionId("123"); header.setProducerGroup("testTransactionGroup"); header.setTranStateTableOffset(1234L); return header; } private MessageExtBrokerInner createMessageBrokerInner(long queueOffset, String topic, String body) { MessageExtBrokerInner inner = new MessageExtBrokerInner(); inner.setBornTimestamp(System.currentTimeMillis() - 80000); inner.setTransactionId("123456123"); inner.setTopic(topic); inner.setQueueOffset(queueOffset); inner.setBody(body.getBytes()); inner.setMsgId("123456123"); inner.setQueueId(0); inner.setTopic("hello"); return inner; } private MessageExtBrokerInner createMessageBrokerInner() { return createMessageBrokerInner(1, "testTopic", "hello world"); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.transaction.queue; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class TransactionalMessageUtilTest { @Test public void testBuildTransactionalMessageFromHalfMessage() { MessageExt halfMessage = new MessageExt(); halfMessage.setTopic(TransactionalMessageUtil.buildHalfTopic()); MessageAccessor.putProperty(halfMessage, MessageConst.PROPERTY_REAL_TOPIC, "real-topic"); halfMessage.setMsgId("msgId"); halfMessage.setTransactionId("tranId"); MessageAccessor.putProperty(halfMessage, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "tranId"); MessageAccessor.putProperty(halfMessage, MessageConst.PROPERTY_PRODUCER_GROUP, "trans-producer-grp"); MessageExtBrokerInner msgExtInner = TransactionalMessageUtil.buildTransactionalMessageFromHalfMessage(halfMessage); assertEquals("real-topic", msgExtInner.getTopic()); assertEquals("true", msgExtInner.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED)); assertEquals(msgExtInner.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX), halfMessage.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); assertEquals(msgExtInner.getMsgId(), halfMessage.getMsgId()); assertTrue(MessageSysFlag.check(msgExtInner.getSysFlag(), MessageSysFlag.TRANSACTION_PREPARED_TYPE)); assertEquals(msgExtInner.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP), halfMessage.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP)); } @Test public void testGetImmunityTime() { long transactionTimeout = 6 * 1000; String checkImmunityTimeStr = "1"; long immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(6 * 1000, immunityTime); checkImmunityTimeStr = "5"; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(6 * 1000, immunityTime); checkImmunityTimeStr = "7"; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(7 * 1000, immunityTime); checkImmunityTimeStr = null; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(6 * 1000, immunityTime); checkImmunityTimeStr = "-1"; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(6 * 1000, immunityTime); checkImmunityTimeStr = "60"; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(60 * 1000, immunityTime); checkImmunityTimeStr = "100"; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(100 * 1000, immunityTime); checkImmunityTimeStr = "100.5"; immunityTime = TransactionalMessageUtil.getImmunityTime(checkImmunityTimeStr, transactionTimeout); Assert.assertEquals(6 * 1000, immunityTime); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.util; import java.util.Objects; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; public class HookUtilsTest { @Test public void testCheckBeforePutMessage() { BrokerController brokerController = Mockito.mock(BrokerController.class); MessageStore messageStore = Mockito.mock(MessageStore.class); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); RunningFlags runningFlags = Mockito.mock(RunningFlags.class); Mockito.when(brokerController.getMessageStore()).thenReturn(messageStore); Mockito.when(brokerController.getMessageStore().isShutdown()).thenReturn(false); Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); Mockito.when(messageStore.getRunningFlags()).thenReturn(runningFlags); Mockito.when(messageStore.getRunningFlags().isWriteable()).thenReturn(true); MessageExt messageExt = new MessageExt(); messageExt.setTopic(RandomStringUtils.randomAlphabetic(Byte.MAX_VALUE).toUpperCase()); messageExt.setBody(RandomStringUtils.randomAlphabetic(Byte.MAX_VALUE).toUpperCase().getBytes()); Assert.assertNull(HookUtils.checkBeforePutMessage(brokerController, messageExt)); messageExt.setTopic(RandomStringUtils.randomAlphabetic(Byte.MAX_VALUE + 1).toUpperCase()); Assert.assertEquals(PutMessageStatus.MESSAGE_ILLEGAL, Objects.requireNonNull( HookUtils.checkBeforePutMessage(brokerController, messageExt)).getPutMessageStatus()); messageExt.setTopic(MixAll.RETRY_GROUP_TOPIC_PREFIX + RandomStringUtils.randomAlphabetic(Byte.MAX_VALUE + 1).toUpperCase()); Assert.assertNull(HookUtils.checkBeforePutMessage(brokerController, messageExt)); messageExt.setTopic(MixAll.RETRY_GROUP_TOPIC_PREFIX + RandomStringUtils.randomAlphabetic(255 - MixAll.RETRY_GROUP_TOPIC_PREFIX.length()).toUpperCase()); Assert.assertNull(HookUtils.checkBeforePutMessage(brokerController, messageExt)); messageExt.setTopic(MixAll.RETRY_GROUP_TOPIC_PREFIX + RandomStringUtils.randomAlphabetic(256 - MixAll.RETRY_GROUP_TOPIC_PREFIX.length()).toUpperCase()); Assert.assertEquals(PutMessageStatus.MESSAGE_ILLEGAL, Objects.requireNonNull( HookUtils.checkBeforePutMessage(brokerController, messageExt)).getPutMessageStatus()); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/util/LogTransactionalMessageCheckListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.util; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.common.message.MessageExt; public class LogTransactionalMessageCheckListener extends AbstractTransactionalMessageCheckListener { @Override public void resolveDiscardMsg(MessageExt msgExt) { } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/util/ServiceProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.util; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.common.utils.ServiceProvider; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ServiceProviderTest { @Test public void loadTransactionMsgServiceTest() { TransactionalMessageService transactionService = ServiceProvider.loadClass(TransactionalMessageService.class); assertThat(transactionService).isNotNull(); } @Test public void loadAbstractTransactionListenerTest() { AbstractTransactionalMessageCheckListener listener = ServiceProvider.loadClass( AbstractTransactionalMessageCheckListener.class); assertThat(listener).isNotNull(); } } ================================================ FILE: broker/src/test/java/org/apache/rocketmq/broker/util/TransactionalMessageServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.broker.util; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener; import org.apache.rocketmq.broker.transaction.OperationResult; import org.apache.rocketmq.broker.transaction.TransactionMetrics; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.PutMessageResult; public class TransactionalMessageServiceImpl implements TransactionalMessageService { private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); @Override public PutMessageResult prepareMessage(MessageExtBrokerInner messageInner) { return null; } @Override public CompletableFuture asyncPrepareMessage(MessageExtBrokerInner messageInner) { return null; } @Override public boolean deletePrepareMessage(MessageExt messageExt) { return false; } @Override public OperationResult commitMessage(EndTransactionRequestHeader requestHeader) { return null; } @Override public OperationResult rollbackMessage(EndTransactionRequestHeader requestHeader) { return null; } @Override public void check(long transactionTimeout, int transactionCheckMax, AbstractTransactionalMessageCheckListener listener) { log.warn("check check!"); } @Override public boolean open() { return true; } @Override public void close() { } @Override public TransactionMetrics getTransactionMetrics() { return null; } @Override public void setTransactionMetrics(TransactionMetrics transactionMetrics) { } } ================================================ FILE: broker/src/test/resources/META-INF/service/org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener ================================================ org.apache.rocketmq.broker.util.LogTransactionalMessageCheckListener ================================================ FILE: broker/src/test/resources/META-INF/service/org.apache.rocketmq.broker.transaction.TransactionalMessageService ================================================ org.apache.rocketmq.broker.util.TransactionalMessageServiceImpl ================================================ FILE: broker/src/test/resources/rmq.logback-test.xml ================================================ ${CONSOLE_LOG_PATTERN} ================================================ FILE: client/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "client", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "//remoting", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_validator_commons_validator", "@maven//:com_github_luben_zstd_jni", "@maven//:org_lz4_lz4_java", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:io_netty_netty_all", "@maven//:io_opentracing_opentracing_api", "@maven//:commons_collections_commons_collections", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:com_google_guava_guava", "@maven//:commons_codec_commons_codec", "@maven//:org_yaml_snakeyaml", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":client", "//remoting", "//common", "//:test_deps", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_netty_netty_all", "@maven//:io_opentracing_opentracing_api", "@maven//:io_opentracing_opentracing_mock", "@maven//:org_awaitility_awaitility", "@maven//:org_mockito_mockito_junit_jupiter", "@maven//:com_alibaba_fastjson2_fastjson2", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) + glob(["src/test/resources/**/*.yml"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], exclude_tests = [ "src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest", "src/test/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPITest", ], ) ================================================ FILE: client/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-client rocketmq-client ${project.version} ${basedir}/.. ${project.groupId} rocketmq-remoting io.netty netty-tcnative org.apache.commons commons-lang3 io.opentracing opentracing-api io.opentracing opentracing-mock io.github.aliyunmq rocketmq-slf4j-api io.github.aliyunmq rocketmq-logback-classic org.yaml snakeyaml ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; public class AclClientRPCHook implements RPCHook { private final SessionCredentials sessionCredentials; public AclClientRPCHook(SessionCredentials sessionCredentials) { this.sessionCredentials = sessionCredentials; } @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { // Add AccessKey and SecurityToken into signature calculating. request.addExtField(SessionCredentials.ACCESS_KEY, sessionCredentials.getAccessKey()); // The SecurityToken value is unnecessary,user can choose this one. if (sessionCredentials.getSecurityToken() != null) { request.addExtField(SessionCredentials.SECURITY_TOKEN, sessionCredentials.getSecurityToken()); } byte[] total = AclUtils.combineRequestContent(request, parseRequestContent(request)); String signature = AclUtils.calSignature(total, sessionCredentials.getSecretKey()); request.addExtField(SessionCredentials.SIGNATURE, signature); } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } protected SortedMap parseRequestContent(RemotingCommand request) { request.makeCustomHeaderToNet(); Map extFields = request.getExtFields(); // Sort property return new TreeMap<>(extFields); } public SessionCredentials getSessionCredentials() { return sessionCredentials; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/AclConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; public class AclConstants { public static final String CONFIG_ACCESS_KEY = "accessKey"; public static final String CONFIG_SECRET_KEY = "secretKey"; public static final String PUB = "PUB"; public static final String SUB = "SUB"; public static final String DENY = "DENY"; public static final String PUB_SUB = "PUB|SUB"; public static final String SUB_PUB = "SUB|PUB"; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/AclException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; public class AclException extends RuntimeException { private static final long serialVersionUID = -7256002576788700354L; private String status; private int code; public AclException(String status, int code) { super(); this.status = status; this.code = code; } public AclException(String status, int code, String message) { super(message); this.status = status; this.code = code; } public AclException(String message) { super(message); } public AclException(String message, Throwable throwable) { super(message, throwable); } public AclException(String status, int code, String message, Throwable throwable) { super(message, throwable); this.status = status; this.code = code; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/AclSigner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.apache.commons.codec.binary.Base64; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public class AclSigner { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; public static final SigningAlgorithm DEFAULT_ALGORITHM = SigningAlgorithm.HmacSHA1; private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTHORIZE_LOGGER_NAME); private static final int CAL_SIGNATURE_FAILED = 10015; private static final String CAL_SIGNATURE_FAILED_MSG = "[%s:signature-failed] unable to calculate a request signature. error=%s"; public static String calSignature(String data, String key) throws AclException { return calSignature(data, key, DEFAULT_ALGORITHM, DEFAULT_CHARSET); } public static String calSignature(String data, String key, SigningAlgorithm algorithm, Charset charset) throws AclException { return signAndBase64Encode(data, key, algorithm, charset); } private static String signAndBase64Encode(String data, String key, SigningAlgorithm algorithm, Charset charset) throws AclException { try { byte[] signature = sign(data.getBytes(charset), key.getBytes(charset), algorithm); return new String(Base64.encodeBase64(signature), DEFAULT_CHARSET); } catch (Exception e) { String message = String.format(CAL_SIGNATURE_FAILED_MSG, CAL_SIGNATURE_FAILED, e.getMessage()); log.error(message, e); throw new AclException("CAL_SIGNATURE_FAILED", CAL_SIGNATURE_FAILED, message, e); } } private static byte[] sign(byte[] data, byte[] key, SigningAlgorithm algorithm) throws AclException { try { Mac mac = Mac.getInstance(algorithm.toString()); mac.init(new SecretKeySpec(key, algorithm.toString())); return mac.doFinal(data); } catch (Exception e) { String message = String.format(CAL_SIGNATURE_FAILED_MSG, CAL_SIGNATURE_FAILED, e.getMessage()); log.error(message, e); throw new AclException("CAL_SIGNATURE_FAILED", CAL_SIGNATURE_FAILED, message, e); } } public static String calSignature(byte[] data, String key) throws AclException { return calSignature(data, key, DEFAULT_ALGORITHM, DEFAULT_CHARSET); } public static String calSignature(byte[] data, String key, SigningAlgorithm algorithm, Charset charset) throws AclException { return signAndBase64Encode(data, key, algorithm, charset); } private static String signAndBase64Encode(byte[] data, String key, SigningAlgorithm algorithm, Charset charset) throws AclException { try { byte[] signature = sign(data, key.getBytes(charset), algorithm); return new String(Base64.encodeBase64(signature), DEFAULT_CHARSET); } catch (Exception e) { String message = String.format(CAL_SIGNATURE_FAILED_MSG, CAL_SIGNATURE_FAILED, e.getMessage()); log.error(message, e); throw new AclException("CAL_SIGNATURE_FAILED", CAL_SIGNATURE_FAILED, message, e); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import com.alibaba.fastjson2.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.yaml.snakeyaml.Yaml; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Map; import java.util.SortedMap; import static org.apache.rocketmq.acl.common.SessionCredentials.CHARSET; public class AclUtils { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static byte[] combineRequestContent(RemotingCommand request, SortedMap fieldsMap) { try { StringBuilder sb = new StringBuilder(); for (Map.Entry entry : fieldsMap.entrySet()) { if (!SessionCredentials.SIGNATURE.equals(entry.getKey())) { sb.append(entry.getValue()); } } return AclUtils.combineBytes(sb.toString().getBytes(CHARSET), request.getBody()); } catch (Exception e) { throw new RuntimeException("Incompatible exception.", e); } } public static byte[] combineBytes(byte[] b1, byte[] b2) { if (b1 == null || b1.length == 0) return b2; if (b2 == null || b2.length == 0) return b1; byte[] total = new byte[b1.length + b2.length]; System.arraycopy(b1, 0, total, 0, b1.length); System.arraycopy(b2, 0, total, b1.length, b2.length); return total; } public static String calSignature(byte[] data, String secretKey) { return AclSigner.calSignature(data, secretKey); } public static void IPv6AddressCheck(String netAddress) { if (isAsterisk(netAddress) || isMinus(netAddress)) { int asterisk = netAddress.indexOf("*"); int minus = netAddress.indexOf("-"); // '*' must be the end of netAddress if it exists if (asterisk > -1 && asterisk != netAddress.length() - 1) { throw new AclException(String.format("NetAddress examine scope Exception netAddress is %s", netAddress)); } // format like "2::ac5:78:1-200:*" or "2::ac5:78:1-200" is legal if (minus > -1) { if (asterisk == -1) { if (minus <= netAddress.lastIndexOf(":")) { throw new AclException(String.format("NetAddress examine scope Exception netAddress is %s", netAddress)); } } else { if (minus <= netAddress.lastIndexOf(":", netAddress.lastIndexOf(":") - 1)) { throw new AclException(String.format("NetAddress examine scope Exception netAddress is %s", netAddress)); } } } } } public static String v6ipProcess(String netAddress) { int part; String subAddress; boolean isAsterisk = isAsterisk(netAddress); boolean isMinus = isMinus(netAddress); if (isAsterisk && isMinus) { part = 6; int lastColon = netAddress.lastIndexOf(':'); int secondLastColon = netAddress.substring(0, lastColon).lastIndexOf(':'); subAddress = netAddress.substring(0, secondLastColon); } else if (!isAsterisk && !isMinus) { part = 8; subAddress = netAddress; } else { part = 7; subAddress = netAddress.substring(0, netAddress.lastIndexOf(':')); } return expandIP(subAddress, part); } public static void verify(String netAddress, int index) { if (!AclUtils.isScope(netAddress, index)) { throw new AclException(String.format("NetAddress examine scope Exception netAddress is %s", netAddress)); } } public static String[] getAddresses(String netAddress, String partialAddress) { String[] parAddStrArray = StringUtils.split(partialAddress.substring(1, partialAddress.length() - 1), ","); String address = netAddress.substring(0, netAddress.indexOf("{")); String[] addressStrArray = new String[parAddStrArray.length]; for (int i = 0; i < parAddStrArray.length; i++) { addressStrArray[i] = address + parAddStrArray[i]; } return addressStrArray; } public static boolean isScope(String netAddress, int index) { // IPv6 Address if (isColon(netAddress)) { netAddress = expandIP(netAddress, 8); String[] strArray = StringUtils.split(netAddress, ":"); return isIPv6Scope(strArray, index); } String[] strArray = StringUtils.split(netAddress, "."); if (strArray.length != 4) { return false; } return isScope(strArray, index); } public static boolean isScope(String[] num, int index) { for (int i = 0; i < index; i++) { if (!isScope(num[i])) { return false; } } return true; } public static boolean isColon(String netAddress) { return netAddress.indexOf(':') > -1; } public static boolean isScope(String num) { return isScope(Integer.parseInt(num.trim())); } public static boolean isScope(int num) { return num >= 0 && num <= 255; } public static boolean isAsterisk(String asterisk) { return asterisk.indexOf('*') > -1; } public static boolean isComma(String colon) { return colon.indexOf(',') > -1; } public static boolean isMinus(String minus) { return minus.indexOf('-') > -1; } public static boolean isIPv6Scope(String[] num, int index) { for (int i = 0; i < index; i++) { int value; try { value = Integer.parseInt(num[i], 16); } catch (NumberFormatException e) { return false; } if (!isIPv6Scope(value)) { return false; } } return true; } public static boolean isIPv6Scope(int num) { int min = Integer.parseInt("0", 16); int max = Integer.parseInt("ffff", 16); return num >= min && num <= max; } public static String expandIP(String netAddress, int part) { netAddress = netAddress.toUpperCase(); // expand netAddress int separatorCount = StringUtils.countMatches(netAddress, ":"); int padCount = part - separatorCount; if (padCount > 0) { StringBuilder padStr = new StringBuilder(":"); for (int i = 0; i < padCount; i++) { padStr.append(":"); } netAddress = StringUtils.replace(netAddress, "::", padStr.toString()); } // pad netAddress String[] strArray = StringUtils.splitPreserveAllTokens(netAddress, ":"); for (int i = 0; i < strArray.length; i++) { if (strArray[i].length() < 4) { strArray[i] = StringUtils.leftPad(strArray[i], 4, '0'); } } // output StringBuilder sb = new StringBuilder(); for (int i = 0; i < strArray.length; i++) { sb.append(strArray[i]); if (i != strArray.length - 1) { sb.append(":"); } } return sb.toString(); } public static T getYamlDataObject(String path, Class clazz) { try (FileInputStream fis = new FileInputStream(path)) { return getYamlDataObject(fis, clazz); } catch (FileNotFoundException ignore) { return null; } catch (Exception e) { throw new AclException(e.getMessage(), e); } } public static T getYamlDataObject(InputStream fis, Class clazz) { Yaml yaml = new Yaml(); try { return yaml.loadAs(fis, clazz); } catch (Exception e) { throw new AclException(e.getMessage(), e); } } public static RPCHook getAclRPCHook(String fileName) { JSONObject yamlDataObject; try { yamlDataObject = AclUtils.getYamlDataObject(fileName, JSONObject.class); } catch (Exception e) { log.error("Convert yaml file to data object error, ", e); return null; } return buildRpcHook(yamlDataObject); } public static RPCHook getAclRPCHook(InputStream inputStream) { JSONObject yamlDataObject = null; try { yamlDataObject = AclUtils.getYamlDataObject(inputStream, JSONObject.class); } catch (Exception e) { log.error("Convert yaml file to data object error, ", e); return null; } return buildRpcHook(yamlDataObject); } private static RPCHook buildRpcHook(JSONObject yamlDataObject) { if (yamlDataObject == null || yamlDataObject.isEmpty()) { log.warn("Failed to parse configuration to enable ACL."); return null; } String accessKey = yamlDataObject.getString(AclConstants.CONFIG_ACCESS_KEY); String secretKey = yamlDataObject.getString(AclConstants.CONFIG_SECRET_KEY); if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(secretKey)) { log.warn("Failed to enable ACL. Either AccessKey or secretKey is blank"); return null; } return new AclClientRPCHook(new SessionCredentials(accessKey, secretKey)); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/Permission.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; public class Permission { public static final byte DENY = 1; public static final byte ANY = 1 << 1; public static final byte PUB = 1 << 2; public static final byte SUB = 1 << 3; public static byte parsePermFromString(String permString) { if (permString == null) { return Permission.DENY; } switch (permString.trim()) { case AclConstants.PUB: return Permission.PUB; case AclConstants.SUB: return Permission.SUB; case AclConstants.PUB_SUB: case AclConstants.SUB_PUB: return Permission.PUB | Permission.SUB; case AclConstants.DENY: return Permission.DENY; default: return Permission.DENY; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/SessionCredentials.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.apache.rocketmq.common.MixAll; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Properties; public class SessionCredentials { public static final Charset CHARSET = StandardCharsets.UTF_8; public static final String ACCESS_KEY = "AccessKey"; public static final String SECRET_KEY = "SecretKey"; public static final String SIGNATURE = "Signature"; public static final String SECURITY_TOKEN = "SecurityToken"; public static final String KEY_FILE = System.getProperty("rocketmq.client.keyFile", System.getProperty("user.home") + File.separator + "key"); private String accessKey; private String secretKey; private String securityToken; private String signature; public SessionCredentials() { String keyContent = null; try { keyContent = MixAll.file2String(KEY_FILE); } catch (IOException ignore) { } if (keyContent != null) { Properties prop = MixAll.string2Properties(keyContent); if (prop != null) { this.updateContent(prop); } } } public SessionCredentials(String accessKey, String secretKey) { this.accessKey = accessKey; this.secretKey = secretKey; } public SessionCredentials(String accessKey, String secretKey, String securityToken) { this(accessKey, secretKey); this.securityToken = securityToken; } public void updateContent(Properties prop) { { String value = prop.getProperty(ACCESS_KEY); if (value != null) { this.accessKey = value.trim(); } } { String value = prop.getProperty(SECRET_KEY); if (value != null) { this.secretKey = value.trim(); } } { String value = prop.getProperty(SECURITY_TOKEN); if (value != null) { this.securityToken = value.trim(); } } } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } public String getSecurityToken() { return securityToken; } public void setSecurityToken(final String securityToken) { this.securityToken = securityToken; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((accessKey == null) ? 0 : accessKey.hashCode()); result = prime * result + ((secretKey == null) ? 0 : secretKey.hashCode()); result = prime * result + ((signature == null) ? 0 : signature.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SessionCredentials other = (SessionCredentials) obj; if (accessKey == null) { if (other.accessKey != null) return false; } else if (!accessKey.equals(other.accessKey)) return false; if (secretKey == null) { if (other.secretKey != null) return false; } else if (!secretKey.equals(other.secretKey)) return false; if (signature == null) { return other.signature == null; } else return signature.equals(other.signature); } @Override public String toString() { return "SessionCredentials [accessKey=" + accessKey + ", secretKey=" + secretKey + ", signature=" + signature + ", SecurityToken=" + securityToken + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/acl/common/SigningAlgorithm.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; public enum SigningAlgorithm { HmacSHA1, HmacSHA256, HmacMD5; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/AccessChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; /** * Used for set access channel, if need migrate the rocketmq service to cloud, it is We recommend set the value with * "CLOUD". otherwise set with "LOCAL", especially used the message trace feature. */ public enum AccessChannel { /** * Means connect to private IDC cluster. */ LOCAL, /** * Means connect to Cloud service. */ CLOUD, } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/ClientConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.NameServerAddressUtils; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RequestType; /** * Client Common configuration */ public class ClientConfig { public static final String SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY = "com.rocketmq.sendMessageWithVIPChannel"; public static final String SOCKS_PROXY_CONFIG = "com.rocketmq.socks.proxy.config"; public static final String DECODE_READ_BODY = "com.rocketmq.read.body"; public static final String DECODE_DECOMPRESS_BODY = "com.rocketmq.decompress.body"; public static final String SEND_LATENCY_ENABLE = "com.rocketmq.sendLatencyEnable"; public static final String START_DETECTOR_ENABLE = "com.rocketmq.startDetectorEnable"; public static final String HEART_BEAT_V2 = "com.rocketmq.heartbeat.v2"; private String namesrvAddr = NameServerAddressUtils.getNameServerAddresses(); private String clientIP = NetworkUtil.getLocalAddress(); private String instanceName = System.getProperty("rocketmq.client.name", "DEFAULT"); private int clientCallbackExecutorThreads = Runtime.getRuntime().availableProcessors(); @Deprecated protected String namespace; private boolean namespaceInitialized = false; protected String namespaceV2; protected AccessChannel accessChannel = AccessChannel.LOCAL; /** * Pulling topic information interval from the named server */ private int pollNameServerInterval = 1000 * 30; /** * Heartbeat interval in microseconds with message broker */ private int heartbeatBrokerInterval = 1000 * 30; /** * Offset persistent interval for consumer */ private int persistConsumerOffsetInterval = 1000 * 5; private long pullTimeDelayMillsWhenException = 1000; private int traceMsgBatchNum = 10; private boolean unitMode = false; private String unitName; private boolean decodeReadBody = Boolean.parseBoolean(System.getProperty(DECODE_READ_BODY, "true")); private boolean decodeDecompressBody = Boolean.parseBoolean(System.getProperty(DECODE_DECOMPRESS_BODY, "true")); private boolean vipChannelEnabled = Boolean.parseBoolean(System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "false")); private boolean useHeartbeatV2 = Boolean.parseBoolean(System.getProperty(HEART_BEAT_V2, "false")); private boolean useTLS = TlsSystemConfig.tlsEnable; private String socksProxyConfig = System.getProperty(SOCKS_PROXY_CONFIG, "{}"); private int mqClientApiTimeout = 3 * 1000; private int detectTimeout = 200; private int detectInterval = 2 * 1000; private LanguageCode language = LanguageCode.JAVA; /** * Enable stream request type will inject a RPCHook to add corresponding request type to remoting layer. * And it will also generate a different client id to prevent unexpected reuses of MQClientInstance. */ protected boolean enableStreamRequestType = false; /** * Enable the fault tolerance mechanism of the client sending process. * DO NOT OPEN when ORDER messages are required. * Turning on will interfere with the queue selection functionality, * possibly conflicting with the order message. */ private boolean sendLatencyEnable = Boolean.parseBoolean(System.getProperty(SEND_LATENCY_ENABLE, "false")); private boolean startDetectorEnable = Boolean.parseBoolean(System.getProperty(START_DETECTOR_ENABLE, "false")); private boolean enableHeartbeatChannelEventListener = true; private boolean enableConcurrentHeartbeat = false; private int concurrentHeartbeatThreadPoolSize = Runtime.getRuntime().availableProcessors(); /** * The switch for message trace */ protected boolean enableTrace = false; /** * The name value of message trace topic. If not set, the default trace topic name will be used. */ protected String traceTopic; protected int maxPageSizeInGetMetadata = 2000; public String buildMQClientId() { StringBuilder sb = new StringBuilder(); sb.append(this.getClientIP()); sb.append("@"); sb.append(this.getInstanceName()); if (!UtilAll.isBlank(this.unitName)) { sb.append("@"); sb.append(this.unitName); } if (enableStreamRequestType) { sb.append("@"); sb.append(RequestType.STREAM); } return sb.toString(); } public int getTraceMsgBatchNum() { return traceMsgBatchNum; } public void setTraceMsgBatchNum(int traceMsgBatchNum) { this.traceMsgBatchNum = traceMsgBatchNum; } public String getClientIP() { return clientIP; } public void setClientIP(String clientIP) { this.clientIP = clientIP; } public String getInstanceName() { return instanceName; } public void setInstanceName(String instanceName) { this.instanceName = instanceName; } public void changeInstanceNameToPID() { if (this.instanceName.equals("DEFAULT")) { this.instanceName = UtilAll.getPid() + "#" + System.nanoTime(); } } @Deprecated public String withNamespace(String resource) { return NamespaceUtil.wrapNamespace(this.getNamespace(), resource); } @Deprecated public Set withNamespace(Set resourceSet) { Set resourceWithNamespace = new HashSet<>(); for (String resource : resourceSet) { resourceWithNamespace.add(withNamespace(resource)); } return resourceWithNamespace; } @Deprecated public String withoutNamespace(String resource) { return NamespaceUtil.withoutNamespace(resource, this.getNamespace()); } @Deprecated public Set withoutNamespace(Set resourceSet) { Set resourceWithoutNamespace = new HashSet<>(); for (String resource : resourceSet) { resourceWithoutNamespace.add(withoutNamespace(resource)); } return resourceWithoutNamespace; } @Deprecated public MessageQueue queueWithNamespace(MessageQueue queue) { if (StringUtils.isEmpty(this.getNamespace())) { return queue; } return new MessageQueue(withNamespace(queue.getTopic()), queue.getBrokerName(), queue.getQueueId()); } @Deprecated public Collection queuesWithNamespace(Collection queues) { if (StringUtils.isEmpty(this.getNamespace())) { return queues; } Iterator iter = queues.iterator(); while (iter.hasNext()) { MessageQueue queue = iter.next(); queue.setTopic(withNamespace(queue.getTopic())); } return queues; } public void resetClientConfig(final ClientConfig cc) { this.namesrvAddr = cc.namesrvAddr; this.clientIP = cc.clientIP; this.instanceName = cc.instanceName; this.clientCallbackExecutorThreads = cc.clientCallbackExecutorThreads; this.pollNameServerInterval = cc.pollNameServerInterval; this.heartbeatBrokerInterval = cc.heartbeatBrokerInterval; this.persistConsumerOffsetInterval = cc.persistConsumerOffsetInterval; this.pullTimeDelayMillsWhenException = cc.pullTimeDelayMillsWhenException; this.unitMode = cc.unitMode; this.unitName = cc.unitName; this.vipChannelEnabled = cc.vipChannelEnabled; this.useTLS = cc.useTLS; this.socksProxyConfig = cc.socksProxyConfig; this.namespace = cc.namespace; this.language = cc.language; this.mqClientApiTimeout = cc.mqClientApiTimeout; this.decodeReadBody = cc.decodeReadBody; this.decodeDecompressBody = cc.decodeDecompressBody; this.enableStreamRequestType = cc.enableStreamRequestType; this.useHeartbeatV2 = cc.useHeartbeatV2; this.startDetectorEnable = cc.startDetectorEnable; this.sendLatencyEnable = cc.sendLatencyEnable; this.enableHeartbeatChannelEventListener = cc.enableHeartbeatChannelEventListener; this.detectInterval = cc.detectInterval; this.detectTimeout = cc.detectTimeout; this.namespaceV2 = cc.namespaceV2; this.enableTrace = cc.enableTrace; this.traceTopic = cc.traceTopic; this.enableConcurrentHeartbeat = cc.enableConcurrentHeartbeat; this.concurrentHeartbeatThreadPoolSize = cc.concurrentHeartbeatThreadPoolSize; } public ClientConfig cloneClientConfig() { ClientConfig cc = new ClientConfig(); cc.namesrvAddr = namesrvAddr; cc.clientIP = clientIP; cc.instanceName = instanceName; cc.clientCallbackExecutorThreads = clientCallbackExecutorThreads; cc.pollNameServerInterval = pollNameServerInterval; cc.heartbeatBrokerInterval = heartbeatBrokerInterval; cc.persistConsumerOffsetInterval = persistConsumerOffsetInterval; cc.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException; cc.unitMode = unitMode; cc.unitName = unitName; cc.vipChannelEnabled = vipChannelEnabled; cc.useTLS = useTLS; cc.socksProxyConfig = socksProxyConfig; cc.namespace = namespace; cc.language = language; cc.mqClientApiTimeout = mqClientApiTimeout; cc.decodeReadBody = decodeReadBody; cc.decodeDecompressBody = decodeDecompressBody; cc.enableStreamRequestType = enableStreamRequestType; cc.useHeartbeatV2 = useHeartbeatV2; cc.startDetectorEnable = startDetectorEnable; cc.enableHeartbeatChannelEventListener = enableHeartbeatChannelEventListener; cc.sendLatencyEnable = sendLatencyEnable; cc.detectInterval = detectInterval; cc.detectTimeout = detectTimeout; cc.namespaceV2 = namespaceV2; cc.enableTrace = enableTrace; cc.traceTopic = traceTopic; cc.enableConcurrentHeartbeat = enableConcurrentHeartbeat; cc.concurrentHeartbeatThreadPoolSize = concurrentHeartbeatThreadPoolSize; return cc; } public String getNamesrvAddr() { if (StringUtils.isNotEmpty(namesrvAddr) && NameServerAddressUtils.NAMESRV_ENDPOINT_PATTERN.matcher(namesrvAddr.trim()).matches()) { return NameServerAddressUtils.getNameSrvAddrFromNamesrvEndpoint(namesrvAddr); } return namesrvAddr; } /** * Domain name mode access way does not support the delimiter(;), and only one domain name can be set. * * @param namesrvAddr name server address */ public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; this.namespaceInitialized = false; } public int getClientCallbackExecutorThreads() { return clientCallbackExecutorThreads; } public void setClientCallbackExecutorThreads(int clientCallbackExecutorThreads) { this.clientCallbackExecutorThreads = clientCallbackExecutorThreads; } public int getPollNameServerInterval() { return pollNameServerInterval; } public void setPollNameServerInterval(int pollNameServerInterval) { this.pollNameServerInterval = pollNameServerInterval; } public int getHeartbeatBrokerInterval() { return heartbeatBrokerInterval; } public void setHeartbeatBrokerInterval(int heartbeatBrokerInterval) { this.heartbeatBrokerInterval = heartbeatBrokerInterval; } public int getPersistConsumerOffsetInterval() { return persistConsumerOffsetInterval; } public void setPersistConsumerOffsetInterval(int persistConsumerOffsetInterval) { this.persistConsumerOffsetInterval = persistConsumerOffsetInterval; } public long getPullTimeDelayMillsWhenException() { return pullTimeDelayMillsWhenException; } public void setPullTimeDelayMillsWhenException(long pullTimeDelayMillsWhenException) { this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException; } public String getUnitName() { return unitName; } public void setUnitName(String unitName) { this.unitName = unitName; } public boolean isUnitMode() { return unitMode; } public void setUnitMode(boolean unitMode) { this.unitMode = unitMode; } public boolean isVipChannelEnabled() { return vipChannelEnabled; } public void setVipChannelEnabled(final boolean vipChannelEnabled) { this.vipChannelEnabled = vipChannelEnabled; } public boolean isUseTLS() { return useTLS; } public void setUseTLS(boolean useTLS) { this.useTLS = useTLS; } public String getSocksProxyConfig() { return socksProxyConfig; } public void setSocksProxyConfig(String socksProxyConfig) { this.socksProxyConfig = socksProxyConfig; } public LanguageCode getLanguage() { return language; } public void setLanguage(LanguageCode language) { this.language = language; } public boolean isDecodeReadBody() { return decodeReadBody; } public void setDecodeReadBody(boolean decodeReadBody) { this.decodeReadBody = decodeReadBody; } public boolean isDecodeDecompressBody() { return decodeDecompressBody; } public void setDecodeDecompressBody(boolean decodeDecompressBody) { this.decodeDecompressBody = decodeDecompressBody; } @Deprecated public String getNamespace() { if (namespaceInitialized) { return namespace; } if (StringUtils.isNotEmpty(namespace)) { return namespace; } if (StringUtils.isNotEmpty(this.namesrvAddr)) { if (NameServerAddressUtils.validateInstanceEndpoint(namesrvAddr)) { namespace = NameServerAddressUtils.parseInstanceIdFromEndpoint(namesrvAddr); } } namespaceInitialized = true; return namespace; } @Deprecated public void setNamespace(String namespace) { this.namespace = namespace; this.namespaceInitialized = true; } public String getNamespaceV2() { return namespaceV2; } public void setNamespaceV2(String namespaceV2) { this.namespaceV2 = namespaceV2; } public AccessChannel getAccessChannel() { return this.accessChannel; } public void setAccessChannel(AccessChannel accessChannel) { this.accessChannel = accessChannel; } public int getMqClientApiTimeout() { return mqClientApiTimeout; } public void setMqClientApiTimeout(int mqClientApiTimeout) { this.mqClientApiTimeout = mqClientApiTimeout; } public boolean isEnableStreamRequestType() { return enableStreamRequestType; } public void setEnableStreamRequestType(boolean enableStreamRequestType) { this.enableStreamRequestType = enableStreamRequestType; } public boolean isSendLatencyEnable() { return sendLatencyEnable; } public void setSendLatencyEnable(boolean sendLatencyEnable) { this.sendLatencyEnable = sendLatencyEnable; } public boolean isStartDetectorEnable() { return startDetectorEnable; } public void setStartDetectorEnable(boolean startDetectorEnable) { this.startDetectorEnable = startDetectorEnable; } public boolean isEnableHeartbeatChannelEventListener() { return enableHeartbeatChannelEventListener; } public void setEnableHeartbeatChannelEventListener(boolean enableHeartbeatChannelEventListener) { this.enableHeartbeatChannelEventListener = enableHeartbeatChannelEventListener; } public int getDetectTimeout() { return this.detectTimeout; } public void setDetectTimeout(int detectTimeout) { this.detectTimeout = detectTimeout; } public int getDetectInterval() { return this.detectInterval; } public void setDetectInterval(int detectInterval) { this.detectInterval = detectInterval; } public boolean isUseHeartbeatV2() { return useHeartbeatV2; } public void setUseHeartbeatV2(boolean useHeartbeatV2) { this.useHeartbeatV2 = useHeartbeatV2; } public boolean isEnableTrace() { return enableTrace; } public void setEnableTrace(boolean enableTrace) { this.enableTrace = enableTrace; } public String getTraceTopic() { return traceTopic; } public void setTraceTopic(String traceTopic) { this.traceTopic = traceTopic; } public int getMaxPageSizeInGetMetadata() { return maxPageSizeInGetMetadata; } public void setMaxPageSizeInGetMetadata(int maxPageSizeInGetMetadata) { this.maxPageSizeInGetMetadata = maxPageSizeInGetMetadata; } public boolean isEnableConcurrentHeartbeat() { return this.enableConcurrentHeartbeat; } public void setEnableConcurrentHeartbeat(boolean enableConcurrentHeartbeat) { this.enableConcurrentHeartbeat = enableConcurrentHeartbeat; } public int getConcurrentHeartbeatThreadPoolSize() { return concurrentHeartbeatThreadPoolSize; } public void setConcurrentHeartbeatThreadPoolSize(int concurrentHeartbeatThreadPoolSize) { this.concurrentHeartbeatThreadPoolSize = concurrentHeartbeatThreadPoolSize; } @Override public String toString() { return "ClientConfig{" + "namesrvAddr='" + namesrvAddr + '\'' + ", clientIP='" + clientIP + '\'' + ", instanceName='" + instanceName + '\'' + ", clientCallbackExecutorThreads=" + clientCallbackExecutorThreads + ", namespace='" + namespace + '\'' + ", namespaceInitialized=" + namespaceInitialized + ", namespaceV2='" + namespaceV2 + '\'' + ", accessChannel=" + accessChannel + ", pollNameServerInterval=" + pollNameServerInterval + ", heartbeatBrokerInterval=" + heartbeatBrokerInterval + ", persistConsumerOffsetInterval=" + persistConsumerOffsetInterval + ", pullTimeDelayMillsWhenException=" + pullTimeDelayMillsWhenException + ", unitMode=" + unitMode + ", unitName='" + unitName + '\'' + ", decodeReadBody=" + decodeReadBody + ", decodeDecompressBody=" + decodeDecompressBody + ", vipChannelEnabled=" + vipChannelEnabled + ", useHeartbeatV2=" + useHeartbeatV2 + ", useTLS=" + useTLS + ", socksProxyConfig='" + socksProxyConfig + '\'' + ", mqClientApiTimeout=" + mqClientApiTimeout + ", detectTimeout=" + detectTimeout + ", detectInterval=" + detectInterval + ", language=" + language + ", enableStreamRequestType=" + enableStreamRequestType + ", sendLatencyEnable=" + sendLatencyEnable + ", startDetectorEnable=" + startDetectorEnable + ", enableHeartbeatChannelEventListener=" + enableHeartbeatChannelEventListener + ", enableTrace=" + enableTrace + ", traceTopic='" + traceTopic + '\'' + ", enableConcurrentHeartbeat=" + enableConcurrentHeartbeat + ", concurrentHeartbeatThreadPoolSize=" + concurrentHeartbeatThreadPoolSize + '}'; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/MQAdmin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import java.util.Map; /** * Base interface for MQ management */ public interface MQAdmin { /** * Creates a topic * @param key accessKey * @param newTopic topic name * @param queueNum topic's queue number * @param attributes */ void createTopic(final String key, final String newTopic, final int queueNum, Map attributes) throws MQClientException; /** * Creates a topic * @param key accessKey * @param newTopic topic name * @param queueNum topic's queue number * @param topicSysFlag topic system flag * @param attributes */ void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException; /** * Gets the message queue offset according to some time in milliseconds
* be cautious to call because of more IO overhead * * @param mq Instance of MessageQueue * @param timestamp from when in milliseconds. * @return offset */ long searchOffset(final MessageQueue mq, final long timestamp) throws MQClientException; /** * Gets the max offset * * @param mq Instance of MessageQueue * @return the max offset */ long maxOffset(final MessageQueue mq) throws MQClientException; /** * Gets the minimum offset * * @param mq Instance of MessageQueue * @return the minimum offset */ long minOffset(final MessageQueue mq) throws MQClientException; /** * Gets the earliest stored message time * * @param mq Instance of MessageQueue * @return the time in microseconds */ long earliestMsgStoreTime(final MessageQueue mq) throws MQClientException; /** * Query messages * * @param topic message topic * @param key message key index word * @param maxNum max message number * @param begin from when * @param end to when * @return Instance of QueryResult */ QueryResult queryMessage(final String topic, final String key, final int maxNum, final long begin, final long end) throws MQClientException, InterruptedException; /** * @return The {@code MessageExt} of given msgId */ MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/MQHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import java.util.Set; import java.util.TreeSet; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MQHelper { private static final Logger log = LoggerFactory.getLogger(MQHelper.class); @Deprecated public static void resetOffsetByTimestamp( final MessageModel messageModel, final String consumerGroup, final String topic, final long timestamp) throws Exception { resetOffsetByTimestamp(messageModel, "DEFAULT", consumerGroup, topic, timestamp); } /** * Reset consumer topic offset according to time * * @param messageModel which model * @param instanceName which instance * @param consumerGroup consumer group * @param topic topic * @param timestamp time */ public static void resetOffsetByTimestamp( final MessageModel messageModel, final String instanceName, final String consumerGroup, final String topic, final long timestamp) throws Exception { DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(consumerGroup); consumer.setInstanceName(instanceName); consumer.setMessageModel(messageModel); consumer.start(); Set mqs = null; try { mqs = consumer.fetchSubscribeMessageQueues(topic); if (mqs != null && !mqs.isEmpty()) { TreeSet mqsNew = new TreeSet<>(mqs); for (MessageQueue mq : mqsNew) { long offset = consumer.searchOffset(mq, timestamp); if (offset >= 0) { consumer.updateConsumeOffset(mq, offset); log.info("resetOffsetByTimestamp updateConsumeOffset success, {} {} {}", consumerGroup, offset, mq); } } } } catch (Exception e) { log.warn("resetOffsetByTimestamp Exception", e); throw e; } finally { if (mqs != null) { consumer.getDefaultMQPullConsumerImpl().getOffsetStore().persistAll(mqs); } consumer.shutdown(); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/MqClientAdmin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeTimeSpanRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QuerySubscriptionByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicConsumeByWhoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicsByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public interface MqClientAdmin { CompletableFuture> queryMessage(String address, boolean uniqueKeyFlag, boolean decompressBody, QueryMessageRequestHeader requestHeader, long timeoutMillis); CompletableFuture getTopicStatsInfo(String address, GetTopicStatsInfoRequestHeader requestHeader, long timeoutMillis); CompletableFuture> queryConsumeTimeSpan(String address, QueryConsumeTimeSpanRequestHeader requestHeader, long timeoutMillis); CompletableFuture updateOrCreateTopic(String address, CreateTopicRequestHeader requestHeader, long timeoutMillis); CompletableFuture updateOrCreateSubscriptionGroup(String address, SubscriptionGroupConfig config, long timeoutMillis); CompletableFuture deleteTopicInBroker(String address, DeleteTopicRequestHeader requestHeader, long timeoutMillis); CompletableFuture deleteTopicInNameserver(String address, DeleteTopicFromNamesrvRequestHeader requestHeader, long timeoutMillis); CompletableFuture deleteKvConfig(String address, DeleteKVConfigRequestHeader requestHeader, long timeoutMillis); CompletableFuture deleteSubscriptionGroup(String address, DeleteSubscriptionGroupRequestHeader requestHeader, long timeoutMillis); CompletableFuture> invokeBrokerToResetOffset(String address, ResetOffsetRequestHeader requestHeader, long timeoutMillis); CompletableFuture viewMessage(String address, ViewMessageRequestHeader requestHeader, long timeoutMillis); CompletableFuture getBrokerClusterInfo(String address, long timeoutMillis); CompletableFuture getConsumerConnectionList(String address, GetConsumerConnectionListRequestHeader requestHeader, long timeoutMillis); CompletableFuture queryTopicsByConsumer(String address, QueryTopicsByConsumerRequestHeader requestHeader, long timeoutMillis); CompletableFuture querySubscriptionByConsumer(String address, QuerySubscriptionByConsumerRequestHeader requestHeader, long timeoutMillis); CompletableFuture getConsumeStats(String address, GetConsumeStatsRequestHeader requestHeader, long timeoutMillis); CompletableFuture queryTopicConsumeByWho(String address, QueryTopicConsumeByWhoRequestHeader requestHeader, long timeoutMillis); CompletableFuture getConsumerRunningInfo(String address, GetConsumerRunningInfoRequestHeader requestHeader, long timeoutMillis); CompletableFuture consumeMessageDirectly(String address, ConsumeMessageDirectlyResultRequestHeader requestHeader, long timeoutMillis); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/QueryResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; public class QueryResult { private final long indexLastUpdateTimestamp; private final List messageList; public QueryResult(long indexLastUpdateTimestamp, List messageList) { this.indexLastUpdateTimestamp = indexLastUpdateTimestamp; this.messageList = messageList; } public long getIndexLastUpdateTimestamp() { return indexLastUpdateTimestamp; } public List getMessageList() { return messageList; } @Override public String toString() { return "QueryResult [indexLastUpdateTimestamp=" + indexLastUpdateTimestamp + ", messageList=" + messageList + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/Validators.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import java.io.File; import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.protocol.ResponseCode; import static org.apache.rocketmq.common.topic.TopicValidator.isTopicOrGroupIllegal; /** * Common Validator */ public class Validators { public static final int CHARACTER_MAX_LENGTH = 255; public static final int TOPIC_MAX_LENGTH = 127; /* * Group name max length is 120, for it will be used to make up retry and DLQ topic, * like pull retry: %RETRY%group_topic and pop retry: %RETRY%group_topic. */ public static final int GROUP_MAX_LENGTH = 120; /** * Validate group */ public static void checkGroup(String group) throws MQClientException { if (UtilAll.isBlank(group)) { throw new MQClientException("the specified group is blank", null); } if (group.length() > GROUP_MAX_LENGTH) { throw new MQClientException(String.format("the specified group[%s] is longer than group max length: %s.", group, GROUP_MAX_LENGTH), null); } if (isTopicOrGroupIllegal(group)) { throw new MQClientException(String.format( "the specified group[%s] contains illegal characters, allowing only %s", group, "^[%|a-zA-Z0-9_-]+$"), null); } } public static void checkMessage(Message msg, DefaultMQProducer defaultMQProducer) throws MQClientException { if (null == msg) { throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message is null"); } // topic Validators.checkTopic(msg.getTopic()); Validators.isNotAllowedSendTopic(msg.getTopic()); // body if (null == msg.getBody()) { throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body is null"); } if (0 == msg.getBody().length) { throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body length is zero"); } if (msg.getBody().length > defaultMQProducer.getMaxMessageSize()) { throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body size over max value, MAX: " + defaultMQProducer.getMaxMessageSize()); } String lmqPath = msg.getUserProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); if (StringUtils.contains(lmqPath, File.separator)) { throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "INNER_MULTI_DISPATCH " + lmqPath + " can not contains " + File.separator + " character"); } } public static void checkTopic(String topic) throws MQClientException { if (UtilAll.isBlank(topic)) { throw new MQClientException("The specified topic is blank", null); } if (topic.length() > TOPIC_MAX_LENGTH) { throw new MQClientException( String.format("The specified topic is longer than topic max length %d.", TOPIC_MAX_LENGTH), null); } if (isTopicOrGroupIllegal(topic)) { throw new MQClientException(String.format( "The specified topic[%s] contains illegal characters, allowing only %s", topic, "^[%|a-zA-Z0-9_-]+$"), null); } } public static void isSystemTopic(String topic) throws MQClientException { if (TopicValidator.isSystemTopic(topic)) { throw new MQClientException( String.format("The topic[%s] is conflict with system topic.", topic), null); } } public static void isNotAllowedSendTopic(String topic) throws MQClientException { if (TopicValidator.isNotAllowedSendTopic(topic)) { throw new MQClientException( String.format("Sending message to topic[%s] is forbidden.", topic), null); } } public static void checkTopicConfig(final TopicConfig topicConfig) throws MQClientException { if (!PermName.isValid(topicConfig.getPerm())) { throw new MQClientException(ResponseCode.NO_PERMISSION, String.format("topicPermission value: %s is invalid.", topicConfig.getPerm())); } } public static void checkBrokerConfig(final Properties brokerConfig) throws MQClientException { String brokerPermission = brokerConfig.getProperty("brokerPermission"); if (brokerPermission != null && !PermName.isValid(brokerPermission)) { throw new MQClientException(ResponseCode.NO_PERMISSION, String.format("brokerPermission value: %s is invalid.", brokerPermission)); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/admin/MQAdminExtInner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.admin; public interface MQAdminExtInner { } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/common/ClientErrorCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.common; public class ClientErrorCode { public static final int CONNECT_BROKER_EXCEPTION = 10001; public static final int ACCESS_BROKER_TIMEOUT = 10002; public static final int BROKER_NOT_EXIST_EXCEPTION = 10003; public static final int NO_NAME_SERVER_EXCEPTION = 10004; public static final int NOT_FOUND_TOPIC_EXCEPTION = 10005; public static final int REQUEST_TIMEOUT_EXCEPTION = 10006; public static final int CREATE_REPLY_MESSAGE_EXCEPTION = 10007; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/common/NameserverAccessConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.common; public class NameserverAccessConfig { private String namesrvAddr; private String namesrvDomain; private String namesrvDomainSubgroup; public NameserverAccessConfig(String namesrvAddr, String namesrvDomain, String namesrvDomainSubgroup) { this.namesrvAddr = namesrvAddr; this.namesrvDomain = namesrvDomain; this.namesrvDomainSubgroup = namesrvDomainSubgroup; } public String getNamesrvAddr() { return namesrvAddr; } public String getNamesrvDomain() { return namesrvDomain; } public String getNamesrvDomainSubgroup() { return namesrvDomainSubgroup; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.common; import java.util.Random; public class ThreadLocalIndex { private final ThreadLocal threadLocalIndex = new ThreadLocal<>(); private final Random random = new Random(); private final static int POSITIVE_MASK = 0x7FFFFFFF; public int incrementAndGet() { Integer index = this.threadLocalIndex.get(); if (null == index) { index = random.nextInt(); } this.threadLocalIndex.set(++index); return index & POSITIVE_MASK; } public void reset() { int index = Math.abs(random.nextInt(Integer.MAX_VALUE)); this.threadLocalIndex.set(index); } @Override public String toString() { return "ThreadLocalIndex{" + "threadLocalIndex=" + threadLocalIndex.get() + '}'; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public interface AckCallback { void onSuccess(final AckResult ackResult); void onException(final Throwable e); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public class AckResult { private AckStatus status; private String extraInfo; private long popTime; public void setPopTime(long popTime) { this.popTime = popTime; } public long getPopTime() { return popTime; } public AckStatus getStatus() { return status; } public void setStatus(AckStatus status) { this.status = status; } public void setExtraInfo(String extraInfo) { this.extraInfo = extraInfo; } public String getExtraInfo() { return extraInfo; } @Override public String toString() { return "AckResult [AckStatus=" + status + ",extraInfo=" + extraInfo + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public enum AckStatus { /** * ack success */ OK, /** * msg not exist */ NO_EXIST, } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/AllocateMessageQueueStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; /** * Strategy Algorithm for message allocating between consumers */ public interface AllocateMessageQueueStrategy { /** * Allocating by consumer id * * @param consumerGroup current consumer group * @param currentCID current consumer id * @param mqAll message queue set in current topic * @param cidAll consumer set in current consumer group * @return The allocate result of given strategy */ List allocate( final String consumerGroup, final String currentCID, final List mqAll, final List cidAll ); /** * Algorithm name * * @return The strategy name */ String getName(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.ConsumeMessageTraceHookImpl; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import static org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData.SUB_ALL; public class DefaultLitePullConsumer extends ClientConfig implements LitePullConsumer { private static final Logger log = LoggerFactory.getLogger(DefaultLitePullConsumer.class); private final DefaultLitePullConsumerImpl defaultLitePullConsumerImpl; /** * Consumers belonging to the same consumer group share a group id. The consumers in a group then divides the topic * as fairly amongst themselves as possible by establishing that each queue is only consumed by a single consumer * from the group. If all consumers are from the same group, it functions as a traditional message queue. Each * message would be consumed by one consumer of the group only. When multiple consumer groups exist, the flow of the * data consumption model aligns with the traditional publish-subscribe model. The messages are broadcast to all * consumer groups. */ private String consumerGroup; /** * Long polling mode, the Consumer connection max suspend time, it is not recommended to modify */ private long brokerSuspendMaxTimeMillis = 1000 * 20; /** * Long polling mode, the Consumer connection timeout(must greater than brokerSuspendMaxTimeMillis), it is not * recommended to modify */ private long consumerTimeoutMillisWhenSuspend = 1000 * 30; /** * The socket timeout in milliseconds */ private long consumerPullTimeoutMillis = 1000 * 10; /** * Consumption pattern,default is clustering */ private MessageModel messageModel = MessageModel.CLUSTERING; /** * Message queue listener */ private MessageQueueListener messageQueueListener; /** * Offset Storage */ private OffsetStore offsetStore; /** * Queue allocation algorithm */ private AllocateMessageQueueStrategy allocateMessageQueueStrategy = new AllocateMessageQueueAveragely(); /** * Whether the unit of subscription group */ private boolean unitMode = false; /** * The flag for auto commit offset */ private boolean autoCommit = true; /** * Pull thread number */ private int pullThreadNums = 20; /** * Minimum commit offset interval time in milliseconds. */ private static final long MIN_AUTOCOMMIT_INTERVAL_MILLIS = 1000; /** * Maximum commit offset interval time in milliseconds. */ private long autoCommitIntervalMillis = 5 * 1000; /** * Maximum number of messages pulled each time. */ private int pullBatchSize = 10; /** * Flow control threshold for consume request, each consumer will cache at most 10000 consume requests by default. * Consider the {@code pullBatchSize}, the instantaneous value may exceed the limit */ private long pullThresholdForAll = 10000; /** * Consume max span offset. */ private int consumeMaxSpan = 2000; /** * Flow control threshold on queue level, each message queue will cache at most 1000 messages by default, Consider * the {@code pullBatchSize}, the instantaneous value may exceed the limit */ private int pullThresholdForQueue = 1000; /** * Limit the cached message size on queue level, each message queue will cache at most 100 MiB messages by default, * Consider the {@code pullBatchSize}, the instantaneous value may exceed the limit * *

* The size of a message only measured by message body, so it's not accurate */ private int pullThresholdSizeForQueue = 100; /** * The poll timeout in milliseconds */ private long pollTimeoutMillis = 1000 * 5; /** * Interval time in in milliseconds for checking changes in topic metadata. */ private long topicMetadataCheckIntervalMillis = 30 * 1000; private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET; /** * Backtracking consumption time with second precision. Time format is 20131223171201
Implying Seventeen twelve * and 01 seconds on December 23, 2013 year
Default backtracking consumption time Half an hour ago. */ private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30)); /** * Interface of asynchronous transfer data */ private TraceDispatcher traceDispatcher = null; private RPCHook rpcHook; private final Set subscriptionsForHeartbeat = new HashSet<>(); /** * Default constructor. */ public DefaultLitePullConsumer() { this(MixAll.DEFAULT_CONSUMER_GROUP, null); } /** * Constructor specifying consumer group. * * @param consumerGroup Consumer group. */ public DefaultLitePullConsumer(final String consumerGroup) { this(consumerGroup, null); } /** * Constructor specifying RPC hook. * * @param rpcHook RPC hook to execute before each remoting command. */ public DefaultLitePullConsumer(RPCHook rpcHook) { this(MixAll.DEFAULT_CONSUMER_GROUP, rpcHook); } /** * Constructor specifying consumer group, RPC hook * * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. */ public DefaultLitePullConsumer(final String consumerGroup, RPCHook rpcHook) { this.consumerGroup = consumerGroup; this.rpcHook = rpcHook; this.enableStreamRequestType = true; defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook); } /** * Constructor specifying namespace, consumer group and RPC hook. * * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. */ @Deprecated public DefaultLitePullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) { this.namespace = namespace; this.consumerGroup = consumerGroup; this.rpcHook = rpcHook; this.enableStreamRequestType = true; defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook); } @Override public void start() throws MQClientException { setTraceDispatcher(); setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); this.defaultLitePullConsumerImpl.start(); if (null != traceDispatcher) { try { traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); } catch (MQClientException e) { log.warn("trace dispatcher start failed ", e); } } } @Override public void shutdown() { this.defaultLitePullConsumerImpl.shutdown(); if (null != traceDispatcher) { traceDispatcher.shutdown(); } } @Override public boolean isRunning() { return this.defaultLitePullConsumerImpl.isRunning(); } @Override public void subscribe(String topic) throws MQClientException { this.subscribe(topic, SUB_ALL); } @Override public void subscribe(String topic, String subExpression) throws MQClientException { this.defaultLitePullConsumerImpl.subscribe(withNamespace(topic), subExpression); } @Override public void subscribe(String topic, MessageSelector messageSelector) throws MQClientException { this.defaultLitePullConsumerImpl.subscribe(withNamespace(topic), messageSelector); } @Override public void unsubscribe(String topic) { this.defaultLitePullConsumerImpl.unsubscribe(withNamespace(topic)); } @Override public void assign(Collection messageQueues) { defaultLitePullConsumerImpl.assign(queuesWithNamespace(messageQueues)); } @Override public void setSubExpressionForAssign(final String topic, final String subExpresion) { defaultLitePullConsumerImpl.setSubExpressionForAssign(withNamespace(topic), subExpresion); } @Override public List poll() { return defaultLitePullConsumerImpl.poll(this.getPollTimeoutMillis()); } @Override public List poll(long timeout) { return defaultLitePullConsumerImpl.poll(timeout); } @Override public void seek(MessageQueue messageQueue, long offset) throws MQClientException { this.defaultLitePullConsumerImpl.seek(queueWithNamespace(messageQueue), offset); } @Override public void pause(Collection messageQueues) { this.defaultLitePullConsumerImpl.pause(queuesWithNamespace(messageQueues)); } @Override public void resume(Collection messageQueues) { this.defaultLitePullConsumerImpl.resume(queuesWithNamespace(messageQueues)); } @Override public Collection fetchMessageQueues(String topic) throws MQClientException { return this.defaultLitePullConsumerImpl.fetchMessageQueues(withNamespace(topic)); } @Override public Long offsetForTimestamp(MessageQueue messageQueue, Long timestamp) throws MQClientException { return this.defaultLitePullConsumerImpl.searchOffset(queueWithNamespace(messageQueue), timestamp); } @Override public void registerTopicMessageQueueChangeListener(String topic, TopicMessageQueueChangeListener topicMessageQueueChangeListener) throws MQClientException { this.defaultLitePullConsumerImpl.registerTopicMessageQueueChangeListener(withNamespace(topic), topicMessageQueueChangeListener); } @Deprecated @Override public void commitSync() { this.defaultLitePullConsumerImpl.commitAll(); } @Deprecated @Override public void commitSync(Map offsetMap, boolean persist) { this.defaultLitePullConsumerImpl.commit(offsetMap, persist); } @Override public void commit() { this.defaultLitePullConsumerImpl.commitAll(); } @Override public void commit(Map offsetMap, boolean persist) { this.defaultLitePullConsumerImpl.commit(offsetMap, persist); } /** * Get the MessageQueue assigned in subscribe mode * * @return * @throws MQClientException */ @Override public Set assignment() throws MQClientException { return this.defaultLitePullConsumerImpl.assignment(); } /** * Subscribe some topic with subExpression and messageQueueListener * * @param topic * @param subExpression * @param messageQueueListener */ @Override public void subscribe(String topic, String subExpression, MessageQueueListener messageQueueListener) throws MQClientException { this.defaultLitePullConsumerImpl.subscribe(withNamespace(topic), subExpression, messageQueueListener); } @Override public void commit(final Set messageQueues, boolean persist) { this.defaultLitePullConsumerImpl.commit(messageQueues, persist); } @Override public Long committed(MessageQueue messageQueue) throws MQClientException { return this.defaultLitePullConsumerImpl.committed(queueWithNamespace(messageQueue)); } @Override public void updateNameServerAddress(String nameServerAddress) { this.defaultLitePullConsumerImpl.updateNameServerAddr(nameServerAddress); } @Override public void seekToBegin(MessageQueue messageQueue) throws MQClientException { this.defaultLitePullConsumerImpl.seekToBegin(queueWithNamespace(messageQueue)); } @Override public void seekToEnd(MessageQueue messageQueue) throws MQClientException { this.defaultLitePullConsumerImpl.seekToEnd(queueWithNamespace(messageQueue)); } @Override public boolean isAutoCommit() { return autoCommit; } @Override public void setAutoCommit(boolean autoCommit) { this.autoCommit = autoCommit; } public boolean isConnectBrokerByUser() { return this.defaultLitePullConsumerImpl.getPullAPIWrapper().isConnectBrokerByUser(); } public void setConnectBrokerByUser(boolean connectBrokerByUser) { this.defaultLitePullConsumerImpl.getPullAPIWrapper().setConnectBrokerByUser(connectBrokerByUser); } public long getDefaultBrokerId() { return this.defaultLitePullConsumerImpl.getPullAPIWrapper().getDefaultBrokerId(); } public void setDefaultBrokerId(long defaultBrokerId) { this.defaultLitePullConsumerImpl.getPullAPIWrapper().setDefaultBrokerId(defaultBrokerId); } public int getPullThreadNums() { return pullThreadNums; } public void setPullThreadNums(int pullThreadNums) { this.pullThreadNums = pullThreadNums; } public long getAutoCommitIntervalMillis() { return autoCommitIntervalMillis; } public void setAutoCommitIntervalMillis(long autoCommitIntervalMillis) { if (autoCommitIntervalMillis >= MIN_AUTOCOMMIT_INTERVAL_MILLIS) { this.autoCommitIntervalMillis = autoCommitIntervalMillis; } } public int getPullBatchSize() { return pullBatchSize; } public void setPullBatchSize(int pullBatchSize) { this.pullBatchSize = pullBatchSize; } public long getPullThresholdForAll() { return pullThresholdForAll; } public void setPullThresholdForAll(long pullThresholdForAll) { this.pullThresholdForAll = pullThresholdForAll; } public int getConsumeMaxSpan() { return consumeMaxSpan; } public void setConsumeMaxSpan(int consumeMaxSpan) { this.consumeMaxSpan = consumeMaxSpan; } public int getPullThresholdForQueue() { return pullThresholdForQueue; } public void setPullThresholdForQueue(int pullThresholdForQueue) { this.pullThresholdForQueue = pullThresholdForQueue; } public int getPullThresholdSizeForQueue() { return pullThresholdSizeForQueue; } public void setPullThresholdSizeForQueue(int pullThresholdSizeForQueue) { this.pullThresholdSizeForQueue = pullThresholdSizeForQueue; } public AllocateMessageQueueStrategy getAllocateMessageQueueStrategy() { return allocateMessageQueueStrategy; } public void setAllocateMessageQueueStrategy(AllocateMessageQueueStrategy allocateMessageQueueStrategy) { this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; } public long getBrokerSuspendMaxTimeMillis() { return brokerSuspendMaxTimeMillis; } public long getPollTimeoutMillis() { return pollTimeoutMillis; } public void setPollTimeoutMillis(long pollTimeoutMillis) { this.pollTimeoutMillis = pollTimeoutMillis; } public OffsetStore getOffsetStore() { return offsetStore; } public void setOffsetStore(OffsetStore offsetStore) { this.offsetStore = offsetStore; } @Override public boolean isUnitMode() { return unitMode; } @Override public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public String getConsumerGroup() { return consumerGroup; } public MessageQueueListener getMessageQueueListener() { return messageQueueListener; } public void setMessageQueueListener(MessageQueueListener messageQueueListener) { this.messageQueueListener = messageQueueListener; } public long getConsumerPullTimeoutMillis() { return consumerPullTimeoutMillis; } public void setConsumerPullTimeoutMillis(long consumerPullTimeoutMillis) { this.consumerPullTimeoutMillis = consumerPullTimeoutMillis; } public long getConsumerTimeoutMillisWhenSuspend() { return consumerTimeoutMillisWhenSuspend; } public void setConsumerTimeoutMillisWhenSuspend(long consumerTimeoutMillisWhenSuspend) { this.consumerTimeoutMillisWhenSuspend = consumerTimeoutMillisWhenSuspend; } public long getTopicMetadataCheckIntervalMillis() { return topicMetadataCheckIntervalMillis; } public void setTopicMetadataCheckIntervalMillis(long topicMetadataCheckIntervalMillis) { this.topicMetadataCheckIntervalMillis = topicMetadataCheckIntervalMillis; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public void setConsumeFromWhere(ConsumeFromWhere consumeFromWhere) { if (consumeFromWhere != ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET && consumeFromWhere != ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET && consumeFromWhere != ConsumeFromWhere.CONSUME_FROM_TIMESTAMP) { throw new RuntimeException("Invalid ConsumeFromWhere Value", null); } this.consumeFromWhere = consumeFromWhere; } public String getConsumeTimestamp() { return consumeTimestamp; } public void setConsumeTimestamp(String consumeTimestamp) { this.consumeTimestamp = consumeTimestamp; } public TraceDispatcher getTraceDispatcher() { return traceDispatcher; } private void setTraceDispatcher() { if (enableTrace) { try { AsyncTraceDispatcher traceDispatcher = new AsyncTraceDispatcher(consumerGroup, TraceDispatcher.Type.CONSUME, getTraceMsgBatchNum(), traceTopic, rpcHook); traceDispatcher.getTraceProducer().setUseTLS(this.isUseTLS()); traceDispatcher.setNamespaceV2(namespaceV2); this.traceDispatcher = traceDispatcher; this.defaultLitePullConsumerImpl.registerConsumeMessageHook( new ConsumeMessageTraceHookImpl(traceDispatcher)); } catch (Throwable e) { log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); } } } public String getCustomizedTraceTopic() { return traceTopic; } public void setCustomizedTraceTopic(String customizedTraceTopic) { this.traceTopic = customizedTraceTopic; } public boolean isEnableMsgTrace() { return enableTrace; } public void setEnableMsgTrace(boolean enableMsgTrace) { this.enableTrace = enableMsgTrace; } public Set getSubscriptionsForHeartbeat() { return this.subscriptionsForHeartbeat; } public synchronized void buildSubscriptionsForHeartbeat(Map messageSelectorMap) throws Exception { this.subscriptionsForHeartbeat.clear(); for (Map.Entry entry : messageSelectorMap.entrySet()) { SubscriptionData subscriptionData = FilterAPI.build(entry.getKey(), entry.getValue().getExpression(), entry.getValue().getExpressionType()); this.subscriptionsForHeartbeat.add(subscriptionData); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.consumer.DefaultMQPullConsumerImpl; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; /** * @deprecated Default pulling consumer. This class will be removed in 2022, and a better implementation * {@link DefaultLitePullConsumer} is recommend to use in the scenario of actively pulling messages. */ @Deprecated public class DefaultMQPullConsumer extends ClientConfig implements MQPullConsumer { protected final transient DefaultMQPullConsumerImpl defaultMQPullConsumerImpl; /** * Do the same thing for the same Group, the application must be set,and guarantee Globally unique */ private String consumerGroup; /** * Long polling mode, the Consumer connection max suspend time, it is not recommended to modify */ private long brokerSuspendMaxTimeMillis = 1000 * 20; /** * Long polling mode, the Consumer connection timeout(must greater than brokerSuspendMaxTimeMillis), it is not * recommended to modify */ private long consumerTimeoutMillisWhenSuspend = 1000 * 30; /** * The socket timeout in milliseconds */ private long consumerPullTimeoutMillis = 1000 * 10; /** * Consumption pattern,default is clustering */ private MessageModel messageModel = MessageModel.CLUSTERING; /** * Message queue listener */ private MessageQueueListener messageQueueListener; /** * Offset Storage */ private OffsetStore offsetStore; /** * Topic set you want to register */ private Set registerTopics = new HashSet<>(); private final Set registerSubscriptions = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * Queue allocation algorithm */ private AllocateMessageQueueStrategy allocateMessageQueueStrategy = new AllocateMessageQueueAveragely(); /** * Whether the unit of subscription group */ private boolean unitMode = false; private int maxReconsumeTimes = 16; private boolean enableRebalance = true; public DefaultMQPullConsumer() { this(MixAll.DEFAULT_CONSUMER_GROUP, null); } public DefaultMQPullConsumer(final String consumerGroup) { this(consumerGroup, null); } public DefaultMQPullConsumer(RPCHook rpcHook) { this(MixAll.DEFAULT_CONSUMER_GROUP, rpcHook); } public DefaultMQPullConsumer(final String consumerGroup, RPCHook rpcHook) { this.consumerGroup = consumerGroup; this.enableStreamRequestType = true; defaultMQPullConsumerImpl = new DefaultMQPullConsumerImpl(this, rpcHook); } /** * Constructor specifying namespace, consumer group and RPC hook. * * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. */ public DefaultMQPullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) { this.namespace = namespace; this.consumerGroup = consumerGroup; this.enableStreamRequestType = true; defaultMQPullConsumerImpl = new DefaultMQPullConsumerImpl(this, rpcHook); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public void createTopic(String key, String newTopic, int queueNum, Map attributes) throws MQClientException { createTopic(key, withNamespace(newTopic), queueNum, 0, null); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException { this.defaultMQPullConsumerImpl.createTopic(key, withNamespace(newTopic), queueNum, topicSysFlag); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { return this.defaultMQPullConsumerImpl.searchOffset(queueWithNamespace(mq), timestamp); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long maxOffset(MessageQueue mq) throws MQClientException { return this.defaultMQPullConsumerImpl.maxOffset(queueWithNamespace(mq)); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long minOffset(MessageQueue mq) throws MQClientException { return this.defaultMQPullConsumerImpl.minOffset(queueWithNamespace(mq)); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { return this.defaultMQPullConsumerImpl.earliestMsgStoreTime(queueWithNamespace(mq)); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return this.defaultMQPullConsumerImpl.queryMessage(withNamespace(topic), key, maxNum, begin, end); } public AllocateMessageQueueStrategy getAllocateMessageQueueStrategy() { return allocateMessageQueueStrategy; } public void setAllocateMessageQueueStrategy(AllocateMessageQueueStrategy allocateMessageQueueStrategy) { this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; } public long getBrokerSuspendMaxTimeMillis() { return brokerSuspendMaxTimeMillis; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public void setBrokerSuspendMaxTimeMillis(long brokerSuspendMaxTimeMillis) { this.brokerSuspendMaxTimeMillis = brokerSuspendMaxTimeMillis; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public long getConsumerPullTimeoutMillis() { return consumerPullTimeoutMillis; } public void setConsumerPullTimeoutMillis(long consumerPullTimeoutMillis) { this.consumerPullTimeoutMillis = consumerPullTimeoutMillis; } public long getConsumerTimeoutMillisWhenSuspend() { return consumerTimeoutMillisWhenSuspend; } public void setConsumerTimeoutMillisWhenSuspend(long consumerTimeoutMillisWhenSuspend) { this.consumerTimeoutMillisWhenSuspend = consumerTimeoutMillisWhenSuspend; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public MessageQueueListener getMessageQueueListener() { return messageQueueListener; } public void setMessageQueueListener(MessageQueueListener messageQueueListener) { this.messageQueueListener = messageQueueListener; } public Set getRegisterTopics() { return registerTopics; } public void setRegisterTopics(Set registerTopics) { this.registerTopics = withNamespace(registerTopics); } public Set getRegisterSubscriptions() { return registerSubscriptions; } public void addRegisterSubscriptions(String topic, MessageSelector messageSelector) throws MQClientException { try { if (messageSelector == null) { messageSelector = MessageSelector.byTag(SubscriptionData.SUB_ALL); } SubscriptionData subscriptionData = FilterAPI.build(withNamespace(topic), messageSelector.getExpression(), messageSelector.getExpressionType()); this.registerSubscriptions.add(subscriptionData); } catch (Exception e) { throw new MQClientException("add subscription exception", e); } } public void clearRegisterSubscriptions() { this.registerSubscriptions.clear(); } /** * This method will be removed or it's visibility will be changed in a certain version after April 5, 2020, so * please do not use this method. */ @Deprecated @Override public void sendMessageBack(MessageExt msg, int delayLevel) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQPullConsumerImpl.sendMessageBack(msg, delayLevel, msg.getBrokerName()); } /** * This method will be removed or it's visibility will be changed in a certain version after April 5, 2020, so * please do not use this method. */ @Deprecated @Override public void sendMessageBack(MessageExt msg, int delayLevel, String brokerName) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQPullConsumerImpl.sendMessageBack(msg, delayLevel, brokerName); } @Override public Set fetchSubscribeMessageQueues(String topic) throws MQClientException { return this.defaultMQPullConsumerImpl.fetchSubscribeMessageQueues(withNamespace(topic)); } @Override public void start() throws MQClientException { this.setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); this.defaultMQPullConsumerImpl.start(); } @Override public void shutdown() { this.defaultMQPullConsumerImpl.shutdown(); } @Override public void registerMessageQueueListener(String topic, MessageQueueListener listener) { synchronized (this.registerTopics) { this.registerTopics.add(withNamespace(topic)); if (listener != null) { this.messageQueueListener = listener; } } } @Override public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums); } @Override public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, timeout); } @Override public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums); } @Override public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, timeout); } @Override public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback); } @Override public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback, timeout); } @Override public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, int maxSize, PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pull(mq, subExpression, offset, maxNums, maxSize, pullCallback, timeout); } @Override public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, pullCallback); } @Override public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, pullCallback, timeout); } @Override public PullResult pullBlockIfNotFound(MessageQueue mq, String subExpression, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQPullConsumerImpl.pullBlockIfNotFound(queueWithNamespace(mq), subExpression, offset, maxNums); } @Override public void pullBlockIfNotFound(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pullBlockIfNotFound(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback); } @Override public void pullBlockIfNotFoundWithMessageSelector(MessageQueue mq, MessageSelector selector, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { this.defaultMQPullConsumerImpl.pullBlockIfNotFoundWithMessageSelector(mq, selector, offset, maxNums, pullCallback); } @Override public PullResult pullBlockIfNotFoundWithMessageSelector(MessageQueue mq, MessageSelector selector, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQPullConsumerImpl.pullBlockIfNotFoundWithMessageSelector(mq, selector, offset, maxNums); } @Override public void updateConsumeOffset(MessageQueue mq, long offset) throws MQClientException { this.defaultMQPullConsumerImpl.updateConsumeOffset(queueWithNamespace(mq), offset); } @Override public long fetchConsumeOffset(MessageQueue mq, boolean fromStore) throws MQClientException { return this.defaultMQPullConsumerImpl.fetchConsumeOffset(queueWithNamespace(mq), fromStore); } @Override public Set fetchMessageQueuesInBalance(String topic) throws MQClientException { return this.defaultMQPullConsumerImpl.fetchMessageQueuesInBalance(withNamespace(topic)); } @Override public MessageExt viewMessage(String topic, String uniqKey) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { try { MessageDecoder.decodeMessageId(uniqKey); return this.defaultMQPullConsumerImpl.viewMessage(topic, uniqKey); } catch (Exception e) { // Ignore } return this.defaultMQPullConsumerImpl.queryMessageByUniqKey(withNamespace(topic), uniqKey); } @Override public void sendMessageBack(MessageExt msg, int delayLevel, String brokerName, String consumerGroup) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQPullConsumerImpl.sendMessageBack(msg, delayLevel, brokerName, consumerGroup); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public OffsetStore getOffsetStore() { return offsetStore; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public void setOffsetStore(OffsetStore offsetStore) { this.offsetStore = offsetStore; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public DefaultMQPullConsumerImpl getDefaultMQPullConsumerImpl() { return defaultMQPullConsumerImpl; } @Override public boolean isUnitMode() { return unitMode; } @Override public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } public int getMaxReconsumeTimes() { return maxReconsumeTimes; } public void setMaxReconsumeTimes(final int maxReconsumeTimes) { this.maxReconsumeTimes = maxReconsumeTimes; } public void persist(MessageQueue mq) { this.getOffsetStore().persist(queueWithNamespace(mq)); } public boolean isEnableRebalance() { return enableRebalance; } public void setEnableRebalance(boolean enableRebalance) { this.enableRebalance = enableRebalance; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.consumer.listener.MessageListener; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.ConsumeMessageTraceHookImpl; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * In most scenarios, this is the mostly recommended class to consume messages. *

* Technically speaking, this push client is virtually a wrapper of the underlying pull service. Specifically, on * arrival of messages pulled from brokers, it roughly invokes the registered callback handler to feed the messages. *

* See quickstart/Consumer in the example module for a typical usage. *

* *

* Thread Safety: After initialization, the instance can be regarded as thread-safe. *

*/ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsumer { private final Logger log = LoggerFactory.getLogger(DefaultMQPushConsumer.class); /** * Internal implementation. Most of the functions herein are delegated to it. */ protected final transient DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; /** * Consumers of the same role is required to have exactly same subscriptions and consumerGroup to correctly achieve * load balance. It's required and needs to be globally unique. *

* See here for further discussion. */ private String consumerGroup; /** * Message model defines the way how messages are delivered to each consumer clients. *

* RocketMQ supports two message models: clustering and broadcasting. If clustering is set, consumer clients with * the same {@link #consumerGroup} would only consume shards of the messages subscribed, which achieves load * balances; Conversely, if the broadcasting is set, each consumer client will consume all subscribed messages * separately. *

* This field defaults to clustering. */ private MessageModel messageModel = MessageModel.CLUSTERING; /** * Consuming point on consumer booting. *

* There are three consuming points: *
    *
  • * CONSUME_FROM_LAST_OFFSET: consumer clients pick up where it stopped previously. * If it were a newly booting up consumer client, according aging of the consumer group, there are two * cases: *
      *
    1. * if the consumer group is created so recently that the earliest message being subscribed has yet * expired, which means the consumer group represents a lately launched business, consuming will * start from the very beginning; *
    2. *
    3. * if the earliest message being subscribed has expired, consuming will start from the latest * messages, meaning messages born prior to the booting timestamp would be ignored. *
    4. *
    *
  • *
  • * CONSUME_FROM_FIRST_OFFSET: Consumer client will start from earliest messages available. *
  • *
  • * CONSUME_FROM_TIMESTAMP: Consumer client will start from specified timestamp, which means * messages born prior to {@link #consumeTimestamp} will be ignored *
  • *
*/ private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET; /** * Backtracking consumption time with second precision. Time format is * 20131223171201
* Implying Seventeen twelve and 01 seconds on December 23, 2013 year
* Default backtracking consumption time Half an hour ago. */ private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30)); /** * Queue allocation algorithm specifying how message queues are allocated to each consumer clients. */ private AllocateMessageQueueStrategy allocateMessageQueueStrategy; /** * Subscription relationship */ private Map subscription = new HashMap<>(); /** * Message listener */ private MessageListener messageListener; /** * Listener to call if message queue assignment is changed. */ private MessageQueueListener messageQueueListener; /** * Offset Storage */ private OffsetStore offsetStore; /** * Minimum consumer thread number */ private int consumeThreadMin = 20; /** * Max consumer thread number */ private int consumeThreadMax = 20; /** * Threshold for dynamic adjustment of the number of thread pool */ private long adjustThreadPoolNumsThreshold = 100000; /** * Concurrently max span offset.it has no effect on sequential consumption */ private int consumeConcurrentlyMaxSpan = 2000; /** * Flow control threshold on queue level, each message queue will cache at most 1000 messages by default, * Consider the {@code pullBatchSize}, the instantaneous value may exceed the limit */ private int pullThresholdForQueue = 1000; /** * Flow control threshold on queue level, means max num of messages waiting to ack. * in contrast with pull threshold, once a message is popped, it's considered the beginning of consumption. */ private int popThresholdForQueue = 96; /** * Limit the cached message size on queue level, each message queue will cache at most 100 MiB messages by default, * Consider the {@code pullBatchSize}, the instantaneous value may exceed the limit * *

* The size(MB) of a message only measured by message body, so it's not accurate */ private int pullThresholdSizeForQueue = 100; /** * Flow control threshold on topic level, default value is -1(Unlimited) *

* The value of {@code pullThresholdForQueue} will be overwritten and calculated based on * {@code pullThresholdForTopic} if it isn't unlimited *

* For example, if the value of pullThresholdForTopic is 1000 and 10 message queues are assigned to this consumer, * then pullThresholdForQueue will be set to 100 */ private int pullThresholdForTopic = -1; /** * Limit the cached message size on topic level, default value is -1 MiB(Unlimited) *

* The value of {@code pullThresholdSizeForQueue} will be overwritten and calculated based on * {@code pullThresholdSizeForTopic} if it isn't unlimited *

* For example, if the value of pullThresholdSizeForTopic is 1000 MiB and 10 message queues are * assigned to this consumer, then pullThresholdSizeForQueue will be set to 100 MiB */ private int pullThresholdSizeForTopic = -1; /** * Message pull Interval */ private long pullInterval = 0; /** * Batch consumption size */ private int consumeMessageBatchMaxSize = 1; /** * Batch pull size */ private int pullBatchSize = 32; private int pullBatchSizeInBytes = 256 * 1024; /** * Whether update subscription relationship when every pull */ private boolean postSubscriptionWhenPull = false; /** * Whether the unit of subscription group */ private boolean unitMode = false; /** * Max re-consume times. * In concurrently mode, -1 means 16; * In orderly mode, -1 means Integer.MAX_VALUE. * If messages are re-consumed more than {@link #maxReconsumeTimes} before success. */ private int maxReconsumeTimes = -1; /** * Suspending pulling time for cases requiring slow pulling like flow-control scenario. */ private long suspendCurrentQueueTimeMillis = 1000; /** * Maximum amount of time in minutes a message may block the consuming thread. */ private long consumeTimeout = 15; /** * Maximum amount of invisible time in millisecond of a message, rang is [5000, 300000] */ private long popInvisibleTime = 60000; /** * Batch pop size. range is [1, 32] */ private int popBatchNums = 32; /** * Maximum time to await message consuming when shutdown consumer, 0 indicates no await. */ private long awaitTerminationMillisWhenShutdown = 0; /** * Interface of asynchronous transfer data */ private TraceDispatcher traceDispatcher = null; // force to use client rebalance private boolean clientRebalance = true; private RPCHook rpcHook = null; /** * Default constructor. */ public DefaultMQPushConsumer() { this(MixAll.DEFAULT_CONSUMER_GROUP, null, new AllocateMessageQueueAveragely()); } /** * Constructor specifying consumer group. * * @param consumerGroup Consumer group. */ public DefaultMQPushConsumer(final String consumerGroup) { this(consumerGroup, null, new AllocateMessageQueueAveragely()); } /** * Constructor specifying RPC hook. * * @param rpcHook RPC hook to execute before each remoting command. */ public DefaultMQPushConsumer(RPCHook rpcHook) { this(MixAll.DEFAULT_CONSUMER_GROUP, rpcHook, new AllocateMessageQueueAveragely()); } /** * Constructor specifying consumer group, RPC hook. * * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. */ public DefaultMQPushConsumer(final String consumerGroup, RPCHook rpcHook) { this(consumerGroup, rpcHook, new AllocateMessageQueueAveragely()); } /** * Constructor specifying consumer group, enabled msg trace flag and customized trace topic name. * * @param consumerGroup Consumer group. * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default trace topic name. */ public DefaultMQPushConsumer(final String consumerGroup, boolean enableMsgTrace, final String customizedTraceTopic) { this(consumerGroup, null, new AllocateMessageQueueAveragely(), enableMsgTrace, customizedTraceTopic); } /** * Constructor specifying consumer group, RPC hook and message queue allocating algorithm. * * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. * @param allocateMessageQueueStrategy Message queue allocating algorithm. */ public DefaultMQPushConsumer(final String consumerGroup, RPCHook rpcHook, AllocateMessageQueueStrategy allocateMessageQueueStrategy) { this(consumerGroup, rpcHook, allocateMessageQueueStrategy, false, null); } /** * Constructor specifying consumer group, RPC hook, message queue allocating algorithm, enabled msg trace flag and customized trace topic name. * * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. * @param allocateMessageQueueStrategy message queue allocating algorithm. * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default trace topic name. */ public DefaultMQPushConsumer(final String consumerGroup, RPCHook rpcHook, AllocateMessageQueueStrategy allocateMessageQueueStrategy, boolean enableMsgTrace, final String customizedTraceTopic) { this.consumerGroup = consumerGroup; this.rpcHook = rpcHook; this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(this, rpcHook); this.enableTrace = enableMsgTrace; this.traceTopic = customizedTraceTopic; } /** * Constructor specifying namespace and consumer group. * * @param namespace Namespace for this MQ Producer instance. * @param consumerGroup Consumer group. */ @Deprecated public DefaultMQPushConsumer(final String namespace, final String consumerGroup) { this(namespace, consumerGroup, null, new AllocateMessageQueueAveragely()); } /** * Constructor specifying namespace, consumer group and RPC hook . * * @param namespace Namespace for this MQ Producer instance. * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. */ @Deprecated public DefaultMQPushConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) { this(namespace, consumerGroup, rpcHook, new AllocateMessageQueueAveragely()); } /** * Constructor specifying namespace, consumer group, RPC hook and message queue allocating algorithm. * * @param namespace Namespace for this MQ Producer instance. * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. * @param allocateMessageQueueStrategy Message queue allocating algorithm. */ @Deprecated public DefaultMQPushConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook, AllocateMessageQueueStrategy allocateMessageQueueStrategy) { this.consumerGroup = consumerGroup; this.namespace = namespace; this.rpcHook = rpcHook; this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(this, rpcHook); } /** * Constructor specifying namespace, consumer group, RPC hook, message queue allocating algorithm, enabled msg trace flag and customized trace topic name. * * @param namespace Namespace for this MQ Producer instance. * @param consumerGroup Consumer group. * @param rpcHook RPC hook to execute before each remoting command. * @param allocateMessageQueueStrategy message queue allocating algorithm. * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default trace topic name. */ @Deprecated public DefaultMQPushConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook, AllocateMessageQueueStrategy allocateMessageQueueStrategy, boolean enableMsgTrace, final String customizedTraceTopic) { this.consumerGroup = consumerGroup; this.namespace = namespace; this.rpcHook = rpcHook; this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(this, rpcHook); this.enableTrace = enableMsgTrace; this.traceTopic = customizedTraceTopic; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public void createTopic(String key, String newTopic, int queueNum, Map attributes) throws MQClientException { createTopic(key, withNamespace(newTopic), queueNum, 0, null); } @Override public void setUseTLS(boolean useTLS) { super.setUseTLS(useTLS); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException { this.defaultMQPushConsumerImpl.createTopic(key, withNamespace(newTopic), queueNum, topicSysFlag); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { return this.defaultMQPushConsumerImpl.searchOffset(queueWithNamespace(mq), timestamp); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long maxOffset(MessageQueue mq) throws MQClientException { return this.defaultMQPushConsumerImpl.maxOffset(queueWithNamespace(mq)); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long minOffset(MessageQueue mq) throws MQClientException { return this.defaultMQPushConsumerImpl.minOffset(queueWithNamespace(mq)); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { return this.defaultMQPushConsumerImpl.earliestMsgStoreTime(queueWithNamespace(mq)); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return this.defaultMQPushConsumerImpl.queryMessage(withNamespace(topic), key, maxNum, begin, end); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated @Override public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { try { MessageDecoder.decodeMessageId(msgId); return this.defaultMQPushConsumerImpl.viewMessage(withNamespace(topic), msgId); } catch (Exception e) { // Ignore } return this.defaultMQPushConsumerImpl.queryMessageByUniqKey(withNamespace(topic), msgId); } public AllocateMessageQueueStrategy getAllocateMessageQueueStrategy() { return allocateMessageQueueStrategy; } public void setAllocateMessageQueueStrategy(AllocateMessageQueueStrategy allocateMessageQueueStrategy) { this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; } public int getConsumeConcurrentlyMaxSpan() { return consumeConcurrentlyMaxSpan; } public void setConsumeConcurrentlyMaxSpan(int consumeConcurrentlyMaxSpan) { this.consumeConcurrentlyMaxSpan = consumeConcurrentlyMaxSpan; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public void setConsumeFromWhere(ConsumeFromWhere consumeFromWhere) { this.consumeFromWhere = consumeFromWhere; } public int getConsumeMessageBatchMaxSize() { return consumeMessageBatchMaxSize; } public void setConsumeMessageBatchMaxSize(int consumeMessageBatchMaxSize) { this.consumeMessageBatchMaxSize = consumeMessageBatchMaxSize; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public int getConsumeThreadMax() { return consumeThreadMax; } public void setConsumeThreadMax(int consumeThreadMax) { this.consumeThreadMax = consumeThreadMax; } public int getConsumeThreadMin() { return consumeThreadMin; } public void setConsumeThreadMin(int consumeThreadMin) { this.consumeThreadMin = consumeThreadMin; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public DefaultMQPushConsumerImpl getDefaultMQPushConsumerImpl() { return defaultMQPushConsumerImpl; } public MessageListener getMessageListener() { return messageListener; } public void setMessageListener(MessageListener messageListener) { this.messageListener = messageListener; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public int getPullBatchSize() { return pullBatchSize; } public void setPullBatchSize(int pullBatchSize) { this.pullBatchSize = pullBatchSize; } public long getPullInterval() { return pullInterval; } public void setPullInterval(long pullInterval) { this.pullInterval = pullInterval; } public int getPullThresholdForQueue() { return pullThresholdForQueue; } public void setPullThresholdForQueue(int pullThresholdForQueue) { this.pullThresholdForQueue = pullThresholdForQueue; } public int getPopThresholdForQueue() { return popThresholdForQueue; } public void setPopThresholdForQueue(int popThresholdForQueue) { this.popThresholdForQueue = popThresholdForQueue; } public int getPullThresholdForTopic() { return pullThresholdForTopic; } public void setPullThresholdForTopic(final int pullThresholdForTopic) { this.pullThresholdForTopic = pullThresholdForTopic; } public int getPullThresholdSizeForQueue() { return pullThresholdSizeForQueue; } public void setPullThresholdSizeForQueue(final int pullThresholdSizeForQueue) { this.pullThresholdSizeForQueue = pullThresholdSizeForQueue; } public int getPullThresholdSizeForTopic() { return pullThresholdSizeForTopic; } public void setPullThresholdSizeForTopic(final int pullThresholdSizeForTopic) { this.pullThresholdSizeForTopic = pullThresholdSizeForTopic; } public Map getSubscription() { return subscription; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public void setSubscription(Map subscription) { Map subscriptionWithNamespace = new HashMap<>(subscription.size(), 1); for (Entry topicEntry : subscription.entrySet()) { subscriptionWithNamespace.put(withNamespace(topicEntry.getKey()), topicEntry.getValue()); } this.subscription = subscriptionWithNamespace; } /** * Send message back to broker which will be re-delivered in future. *

* This method will be removed or it's visibility will be changed in a certain version after April 5, 2020, so * please do not use this method. * * @param msg Message to send back. * @param delayLevel delay level. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any broker error. * @throws InterruptedException if the thread is interrupted. * @throws MQClientException if there is any client error. */ @Deprecated @Override public void sendMessageBack(MessageExt msg, int delayLevel) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, msg.getBrokerName()); } /** * Send message back to the broker whose name is brokerName and the message will be re-delivered in * future. *

* This method will be removed or it's visibility will be changed in a certain version after April 5, 2020, so * please do not use this method. * * @param msg Message to send back. * @param delayLevel delay level. * @param brokerName broker name. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any broker error. * @throws InterruptedException if the thread is interrupted. * @throws MQClientException if there is any client error. */ @Deprecated @Override public void sendMessageBack(MessageExt msg, int delayLevel, String brokerName) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, brokerName); } @Override public Set fetchSubscribeMessageQueues(String topic) throws MQClientException { return this.defaultMQPushConsumerImpl.fetchSubscribeMessageQueues(withNamespace(topic)); } /** * This method gets internal infrastructure readily to serve. Instances must call this method after configuration. * * @throws MQClientException if there is any client error. */ @Override public void start() throws MQClientException { setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); this.defaultMQPushConsumerImpl.start(); if (enableTrace) { try { AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(consumerGroup, TraceDispatcher.Type.CONSUME, getTraceMsgBatchNum(), traceTopic, rpcHook); dispatcher.setHostConsumer(this.defaultMQPushConsumerImpl); dispatcher.setNamespaceV2(namespaceV2); traceDispatcher = dispatcher; this.defaultMQPushConsumerImpl.registerConsumeMessageHook(new ConsumeMessageTraceHookImpl(traceDispatcher)); } catch (Throwable e) { log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); } } if (null != traceDispatcher) { if (traceDispatcher instanceof AsyncTraceDispatcher) { ((AsyncTraceDispatcher) traceDispatcher).getTraceProducer().setUseTLS(isUseTLS()); } try { traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); } catch (MQClientException e) { log.warn("trace dispatcher start failed ", e); } } } /** * Shut down this client and releasing underlying resources. */ @Override public void shutdown() { this.defaultMQPushConsumerImpl.shutdown(awaitTerminationMillisWhenShutdown); if (null != traceDispatcher) { traceDispatcher.shutdown(); } } @Override @Deprecated public void registerMessageListener(MessageListener messageListener) { this.messageListener = messageListener; this.defaultMQPushConsumerImpl.registerMessageListener(messageListener); } /** * Register a callback to execute on message arrival for concurrent consuming. * * @param messageListener message handling callback. */ @Override public void registerMessageListener(MessageListenerConcurrently messageListener) { this.messageListener = messageListener; this.defaultMQPushConsumerImpl.registerMessageListener(messageListener); } /** * Register a callback to execute on message arrival for orderly consuming. * * @param messageListener message handling callback. */ @Override public void registerMessageListener(MessageListenerOrderly messageListener) { this.messageListener = messageListener; this.defaultMQPushConsumerImpl.registerMessageListener(messageListener); } /** * Subscribe a topic to consuming subscription. * * @param topic topic to subscribe. * @param subExpression subscription expression.it only support or operation such as "tag1 || tag2 || tag3"
* if null or * expression,meaning subscribe all * @throws MQClientException if there is any client error. */ @Override public void subscribe(String topic, String subExpression) throws MQClientException { this.defaultMQPushConsumerImpl.subscribe(withNamespace(topic), subExpression); } /** * Subscribe a topic to consuming subscription. * * @param topic topic to consume. * @param fullClassName full class name,must extend org.apache.rocketmq.common.filter. MessageFilter * @param filterClassSource class source code,used UTF-8 file encoding,must be responsible for your code safety */ @Override public void subscribe(String topic, String fullClassName, String filterClassSource) throws MQClientException { this.defaultMQPushConsumerImpl.subscribe(withNamespace(topic), fullClassName, filterClassSource); } /** * Subscribe a topic by message selector. * * @param topic topic to consume. * @param messageSelector {@link org.apache.rocketmq.client.consumer.MessageSelector} * @see org.apache.rocketmq.client.consumer.MessageSelector#bySql * @see org.apache.rocketmq.client.consumer.MessageSelector#byTag */ @Override public void subscribe(final String topic, final MessageSelector messageSelector) throws MQClientException { this.defaultMQPushConsumerImpl.subscribe(withNamespace(topic), messageSelector); } /** * Un-subscribe the specified topic from subscription. * * @param topic message topic */ @Override public void unsubscribe(String topic) { this.defaultMQPushConsumerImpl.unsubscribe(topic); } /** * Update the message consuming thread core pool size. * * @param corePoolSize new core pool size. */ @Override public void updateCorePoolSize(int corePoolSize) { this.defaultMQPushConsumerImpl.updateCorePoolSize(corePoolSize); } /** * Suspend pulling new messages. */ @Override public void suspend() { this.defaultMQPushConsumerImpl.suspend(); } /** * Resume pulling. */ @Override public void resume() { this.defaultMQPushConsumerImpl.resume(); } public boolean isPause() { return this.defaultMQPushConsumerImpl.isPause(); } public boolean isConsumeOrderly() { return this.defaultMQPushConsumerImpl.isConsumeOrderly(); } public void registerConsumeMessageHook(final ConsumeMessageHook hook) { this.defaultMQPushConsumerImpl.registerConsumeMessageHook(hook); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public OffsetStore getOffsetStore() { return offsetStore; } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. */ @Deprecated public void setOffsetStore(OffsetStore offsetStore) { this.offsetStore = offsetStore; } public String getConsumeTimestamp() { return consumeTimestamp; } public void setConsumeTimestamp(String consumeTimestamp) { this.consumeTimestamp = consumeTimestamp; } public boolean isPostSubscriptionWhenPull() { return postSubscriptionWhenPull; } public void setPostSubscriptionWhenPull(boolean postSubscriptionWhenPull) { this.postSubscriptionWhenPull = postSubscriptionWhenPull; } @Override public boolean isUnitMode() { return unitMode; } @Override public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } public long getAdjustThreadPoolNumsThreshold() { return adjustThreadPoolNumsThreshold; } public void setAdjustThreadPoolNumsThreshold(long adjustThreadPoolNumsThreshold) { this.adjustThreadPoolNumsThreshold = adjustThreadPoolNumsThreshold; } public int getMaxReconsumeTimes() { return maxReconsumeTimes; } public void setMaxReconsumeTimes(final int maxReconsumeTimes) { this.maxReconsumeTimes = maxReconsumeTimes; } public long getSuspendCurrentQueueTimeMillis() { return suspendCurrentQueueTimeMillis; } public void setSuspendCurrentQueueTimeMillis(final long suspendCurrentQueueTimeMillis) { this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis; } public long getConsumeTimeout() { return consumeTimeout; } public void setConsumeTimeout(final long consumeTimeout) { this.consumeTimeout = consumeTimeout; } public long getPopInvisibleTime() { return popInvisibleTime; } public void setPopInvisibleTime(long popInvisibleTime) { this.popInvisibleTime = popInvisibleTime; } public long getAwaitTerminationMillisWhenShutdown() { return awaitTerminationMillisWhenShutdown; } public void setAwaitTerminationMillisWhenShutdown(long awaitTerminationMillisWhenShutdown) { this.awaitTerminationMillisWhenShutdown = awaitTerminationMillisWhenShutdown; } public int getPullBatchSizeInBytes() { return pullBatchSizeInBytes; } public void setPullBatchSizeInBytes(int pullBatchSizeInBytes) { this.pullBatchSizeInBytes = pullBatchSizeInBytes; } public TraceDispatcher getTraceDispatcher() { return traceDispatcher; } public int getPopBatchNums() { return popBatchNums; } public void setPopBatchNums(int popBatchNums) { this.popBatchNums = popBatchNums; } public boolean isClientRebalance() { return clientRebalance; } public void setClientRebalance(boolean clientRebalance) { this.clientRebalance = clientRebalance; } public MessageQueueListener getMessageQueueListener() { return messageQueueListener; } public void setMessageQueueListener(MessageQueueListener messageQueueListener) { this.messageQueueListener = messageQueueListener; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/LitePullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public interface LitePullConsumer { /** * Start the consumer */ void start() throws MQClientException; /** * Shutdown the consumer */ void shutdown(); /** * This consumer is still running * * @return true if consumer is still running */ boolean isRunning(); /** * Subscribe some topic with all tags * @throws MQClientException if there is any client error. */ void subscribe(final String topic) throws MQClientException; /** * Subscribe some topic with subExpression * * @param subExpression subscription expression.it only support or operation such as "tag1 || tag2 || tag3"
if * null or * expression,meaning subscribe all * @throws MQClientException if there is any client error. */ void subscribe(final String topic, final String subExpression) throws MQClientException; /** * Subscribe some topic with subExpression and messageQueueListener * @param topic * @param subExpression * @param messageQueueListener */ void subscribe(final String topic, final String subExpression, final MessageQueueListener messageQueueListener) throws MQClientException; /** * Subscribe some topic with selector. * * @param selector message selector({@link MessageSelector}), can be null. * @throws MQClientException if there is any client error. */ void subscribe(final String topic, final MessageSelector selector) throws MQClientException; /** * Unsubscribe consumption some topic * * @param topic Message topic that needs to be unsubscribe. */ void unsubscribe(final String topic); /** * subscribe mode, get assigned MessageQueue * @return * @throws MQClientException */ Set assignment() throws MQClientException; /** * Manually assign a list of message queues to this consumer. This interface does not allow for incremental * assignment and will replace the previous assignment (if there is one). * * @param messageQueues Message queues that needs to be assigned. */ void assign(Collection messageQueues); /** * Set topic subExpression for assign mode. This interface does not allow be call after start(). Default value is * if not set. * assignment and will replace the previous assignment (if there is one). * * @param subExpression subscription expression.it only support or operation such as "tag1 || tag2 || tag3"
if * * null or * expression,meaning subscribe all */ void setSubExpressionForAssign(final String topic, final String subExpression); void buildSubscriptionsForHeartbeat(Map subExpressionMap) throws Exception; /** * Fetch data for the topics or partitions specified using assign API * * @return list of message, can be null. */ List poll(); /** * Fetch data for the topics or partitions specified using assign API * * @param timeout The amount time, in milliseconds, spent waiting in poll if data is not available. Must not be * negative * @return list of message, can be null. */ List poll(long timeout); /** * Overrides the fetch offsets that the consumer will use on the next poll. If this API is invoked for the same * message queue more than once, the latest offset will be used on the next poll(). Note that you may lose data if * this API is arbitrarily used in the middle of consumption. * * @param messageQueue * @param offset */ void seek(MessageQueue messageQueue, long offset) throws MQClientException; /** * Suspend pulling from the requested message queues. * * Because of the implementation of pre-pull, fetch data in {@link #poll()} will not stop immediately until the * messages of the requested message queues drain. * * Note that this method does not affect message queue subscription. In particular, it does not cause a group * rebalance. * * @param messageQueues Message queues that needs to be paused. */ void pause(Collection messageQueues); /** * Resume specified message queues which have been paused with {@link #pause(Collection)}. * * @param messageQueues Message queues that needs to be resumed. */ void resume(Collection messageQueues); /** * Whether to enable auto-commit consume offset. * * @return true if enable auto-commit, false if disable auto-commit. */ boolean isAutoCommit(); /** * Set whether to enable auto-commit consume offset. * * @param autoCommit Whether to enable auto-commit. */ void setAutoCommit(boolean autoCommit); /** * Get metadata about the message queues for a given topic. * * @param topic The topic that need to get metadata. * @return collection of message queues * @throws MQClientException if there is any client error. */ Collection fetchMessageQueues(String topic) throws MQClientException; /** * Look up the offsets for the given message queue by timestamp. The returned offset for each message queue is the * earliest offset whose timestamp is greater than or equal to the given timestamp in the corresponding message * queue. * * @param messageQueue Message queues that needs to get offset by timestamp. * @param timestamp * @return offset * @throws MQClientException if there is any client error. */ Long offsetForTimestamp(MessageQueue messageQueue, Long timestamp) throws MQClientException; @Deprecated /** * The method is deprecated because its name is ambiguous, this method relies on the background thread commit consumerOffset rather than the synchronous commit offset. * The method is expected to be removed after version 5.1.0. It is recommended to use the {@link #commit()} method. * * Manually commit consume offset saved by the system. */ void commitSync(); @Deprecated /** * The method is deprecated because its name is ambiguous, this method relies on the background thread commit consumerOffset rather than the synchronous commit offset. * The method is expected to be removed after version 5.1.0. It is recommended to use the {@link #commit(java.util.Map, boolean)} method. * * @param offsetMap Offset specified by batch commit */ void commitSync(Map offsetMap, boolean persist); /** * Manually commit consume offset saved by the system. This is a non-blocking method. */ void commit(); /** * Offset specified by batch commit * * @param offsetMap Offset specified by batch commit * @param persist Whether to persist to the broker */ void commit(Map offsetMap, boolean persist); /** * Manually commit consume offset saved by the system. * * @param messageQueues Message queues that need to submit consumer offset * @param persist hether to persist to the broker */ void commit(final Set messageQueues, boolean persist); /** * Get the last committed offset for the given message queue. * * @param messageQueue * @return offset, if offset equals -1 means no offset in broker. * @throws MQClientException if there is any client error. */ Long committed(MessageQueue messageQueue) throws MQClientException; /** * Register a callback for sensing topic metadata changes. * * @param topic The topic that need to monitor. * @param topicMessageQueueChangeListener Callback when topic metadata changes, refer {@link * TopicMessageQueueChangeListener} * @throws MQClientException if there is any client error. */ void registerTopicMessageQueueChangeListener(String topic, TopicMessageQueueChangeListener topicMessageQueueChangeListener) throws MQClientException; /** * Update name server addresses. */ void updateNameServerAddress(String nameServerAddress); /** * Overrides the fetch offsets with the begin offset that the consumer will use on the next poll. If this API is * invoked for the same message queue more than once, the latest offset will be used on the next poll(). Note that * you may lose data if this API is arbitrarily used in the middle of consumption. * * @param messageQueue */ void seekToBegin(MessageQueue messageQueue)throws MQClientException; /** * Overrides the fetch offsets with the end offset that the consumer will use on the next poll. If this API is * invoked for the same message queue more than once, the latest offset will be used on the next poll(). Note that * you may lose data if this API is arbitrarily used in the middle of consumption. * * @param messageQueue */ void seekToEnd(MessageQueue messageQueue)throws MQClientException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/MQConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Set; import org.apache.rocketmq.client.MQAdmin; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; /** * Message queue consumer interface */ public interface MQConsumer extends MQAdmin { /** * If consuming of messages failed, they will be sent back to the brokers for another delivery attempt after * interval specified in delay level. */ @Deprecated void sendMessageBack(final MessageExt msg, final int delayLevel) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; /** * If consuming of messages failed, they will be sent back to the brokers for another delivery attempt after * interval specified in delay level. */ void sendMessageBack(final MessageExt msg, final int delayLevel, final String brokerName) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; /** * Fetch message queues from consumer cache pertaining to the given topic. * * @param topic message topic * @return queue set */ Set fetchSubscribeMessageQueues(final String topic) throws MQClientException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Set; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; /** * Pulling consumer interface */ public interface MQPullConsumer extends MQConsumer { /** * Start the consumer */ void start() throws MQClientException; /** * Shutdown the consumer */ void shutdown(); /** * Register the message queue listener */ void registerMessageQueueListener(final String topic, final MessageQueueListener listener); /** * Pulling the messages,not blocking * * @param mq from which message queue * @param subExpression subscription expression.it only support or operation such as "tag1 || tag2 || tag3"
if * null or * expression,meaning subscribe all * @param offset from where to pull * @param maxNums max pulling numbers * @return The resulting {@code PullRequest} */ PullResult pull(final MessageQueue mq, final String subExpression, final long offset, final int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; /** * Pulling the messages in the specified timeout * * @return The resulting {@code PullRequest} */ PullResult pull(final MessageQueue mq, final String subExpression, final long offset, final int maxNums, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; /** * Pulling the messages, not blocking *

* support other message selection, such as {@link org.apache.rocketmq.common.filter.ExpressionType#SQL92} *

* * @param mq from which message queue * @param selector message selector({@link MessageSelector}), can be null. * @param offset from where to pull * @param maxNums max pulling numbers * @return The resulting {@code PullRequest} */ PullResult pull(final MessageQueue mq, final MessageSelector selector, final long offset, final int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; /** * Pulling the messages in the specified timeout *

* support other message selection, such as {@link org.apache.rocketmq.common.filter.ExpressionType#SQL92} *

* * @param mq from which message queue * @param selector message selector({@link MessageSelector}), can be null. * @param offset from where to pull * @param maxNums max pulling numbers * @param timeout Pulling the messages in the specified timeout * @return The resulting {@code PullRequest} */ PullResult pull(final MessageQueue mq, final MessageSelector selector, final long offset, final int maxNums, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; /** * Pulling the messages in a async. way */ void pull(final MessageQueue mq, final String subExpression, final long offset, final int maxNums, final PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages in a async. way */ void pull(final MessageQueue mq, final String subExpression, final long offset, final int maxNums, final PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages in a async. way */ void pull(final MessageQueue mq, final String subExpression, final long offset, final int maxNums, final int maxSize, final PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages in a async way. Support message selection */ void pull(final MessageQueue mq, final MessageSelector selector, final long offset, final int maxNums, final PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages in a async. way. Support message selection */ void pull(final MessageQueue mq, final MessageSelector selector, final long offset, final int maxNums, final PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages,if no message arrival,blocking some time * * @return The resulting {@code PullRequest} */ PullResult pullBlockIfNotFound(final MessageQueue mq, final String subExpression, final long offset, final int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; /** * Pulling the messages through callback function,if no message arrival,blocking. */ void pullBlockIfNotFound(final MessageQueue mq, final String subExpression, final long offset, final int maxNums, final PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages through callback function,if no message arrival,blocking. Support message selection */ void pullBlockIfNotFoundWithMessageSelector(final MessageQueue mq, final MessageSelector selector, final long offset, final int maxNums, final PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException; /** * Pulling the messages,if no message arrival,blocking some time. Support message selection * * @return The resulting {@code PullRequest} */ PullResult pullBlockIfNotFoundWithMessageSelector(final MessageQueue mq, final MessageSelector selector, final long offset, final int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; /** * Update the offset */ void updateConsumeOffset(final MessageQueue mq, final long offset) throws MQClientException; /** * Fetch the offset * * @return The fetched offset of given queue */ long fetchConsumeOffset(final MessageQueue mq, final boolean fromStore) throws MQClientException; /** * Fetch the message queues according to the topic * * @param topic message topic * @return message queue set */ Set fetchMessageQueuesInBalance(final String topic) throws MQClientException; /** * If consuming failure,message will be send back to the broker,and delay consuming in some time later.
* Mind! message can only be consumed in the same group. */ void sendMessageBack(MessageExt msg, int delayLevel, String brokerName, String consumerGroup) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumerScheduleService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * Schedule service for pull consumer. * This Consumer will be removed in 2022, and a better implementation {@link * DefaultLitePullConsumer} is recommend to use in the scenario of actively pulling messages. */ public class MQPullConsumerScheduleService { private final Logger log = LoggerFactory.getLogger(MQPullConsumerScheduleService.class); private final MessageQueueListener messageQueueListener = new MessageQueueListenerImpl(); private final ConcurrentMap taskTable = new ConcurrentHashMap<>(); private DefaultMQPullConsumer defaultMQPullConsumer; private int pullThreadNums = 20; private ConcurrentMap callbackTable = new ConcurrentHashMap<>(); private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; public MQPullConsumerScheduleService(final String consumerGroup) { this.defaultMQPullConsumer = new DefaultMQPullConsumer(consumerGroup); this.defaultMQPullConsumer.setMessageModel(MessageModel.CLUSTERING); } public MQPullConsumerScheduleService(final String consumerGroup, final RPCHook rpcHook) { this.defaultMQPullConsumer = new DefaultMQPullConsumer(consumerGroup, rpcHook); this.defaultMQPullConsumer.setMessageModel(MessageModel.CLUSTERING); } public void putTask(String topic, Set mqNewSet) { Iterator> it = this.taskTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getKey().getTopic().equals(topic)) { if (!mqNewSet.contains(next.getKey())) { next.getValue().setCancelled(true); it.remove(); } } } for (MessageQueue mq : mqNewSet) { if (!this.taskTable.containsKey(mq)) { PullTaskImpl command = new PullTaskImpl(mq); this.taskTable.put(mq, command); this.scheduledThreadPoolExecutor.schedule(command, 0, TimeUnit.MILLISECONDS); } } } public void start() throws MQClientException { final String group = this.defaultMQPullConsumer.getConsumerGroup(); this.scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor( this.pullThreadNums, new ThreadFactoryImpl("PullMsgThread-" + group) ); this.defaultMQPullConsumer.setMessageQueueListener(this.messageQueueListener); this.defaultMQPullConsumer.start(); log.info("MQPullConsumerScheduleService start OK, {} {}", this.defaultMQPullConsumer.getConsumerGroup(), this.callbackTable); } public void registerPullTaskCallback(final String topic, final PullTaskCallback callback) { this.callbackTable.put(NamespaceUtil.wrapNamespace(this.defaultMQPullConsumer.getNamespace(), topic), callback); this.defaultMQPullConsumer.registerMessageQueueListener(topic, null); } public void shutdown() { if (this.scheduledThreadPoolExecutor != null) { this.scheduledThreadPoolExecutor.shutdown(); } if (this.defaultMQPullConsumer != null) { this.defaultMQPullConsumer.shutdown(); } } public ConcurrentMap getCallbackTable() { return callbackTable; } public void setCallbackTable(ConcurrentHashMap callbackTable) { this.callbackTable = callbackTable; } public int getPullThreadNums() { return pullThreadNums; } public void setPullThreadNums(int pullThreadNums) { this.pullThreadNums = pullThreadNums; } public DefaultMQPullConsumer getDefaultMQPullConsumer() { return defaultMQPullConsumer; } public void setDefaultMQPullConsumer(DefaultMQPullConsumer defaultMQPullConsumer) { this.defaultMQPullConsumer = defaultMQPullConsumer; } public MessageModel getMessageModel() { return this.defaultMQPullConsumer.getMessageModel(); } public void setMessageModel(MessageModel messageModel) { this.defaultMQPullConsumer.setMessageModel(messageModel); } class MessageQueueListenerImpl implements MessageQueueListener { @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { MessageModel messageModel = MQPullConsumerScheduleService.this.defaultMQPullConsumer.getMessageModel(); switch (messageModel) { case BROADCASTING: MQPullConsumerScheduleService.this.putTask(topic, mqAll); break; case CLUSTERING: MQPullConsumerScheduleService.this.putTask(topic, mqDivided); break; default: break; } } } public class PullTaskImpl implements Runnable { private final MessageQueue messageQueue; private volatile boolean cancelled = false; public PullTaskImpl(final MessageQueue messageQueue) { this.messageQueue = messageQueue; } @Override public void run() { String topic = this.messageQueue.getTopic(); if (!this.isCancelled()) { PullTaskCallback pullTaskCallback = MQPullConsumerScheduleService.this.callbackTable.get(topic); if (pullTaskCallback != null) { final PullTaskContext context = new PullTaskContext(); context.setPullConsumer(MQPullConsumerScheduleService.this.defaultMQPullConsumer); try { pullTaskCallback.doPullTask(this.messageQueue, context); } catch (Throwable e) { context.setPullNextDelayTimeMillis(1000); log.error("doPullTask Exception", e); } if (!this.isCancelled()) { MQPullConsumerScheduleService.this.scheduledThreadPoolExecutor.schedule(this, context.getPullNextDelayTimeMillis(), TimeUnit.MILLISECONDS); } else { log.warn("The Pull Task is cancelled after doPullTask, {}", messageQueue); } } else { log.warn("Pull Task Callback not exist , {}", topic); } } else { log.warn("The Pull Task is cancelled, {}", messageQueue); } } public boolean isCancelled() { return cancelled; } public void setCancelled(boolean cancelled) { this.cancelled = cancelled; } public MessageQueue getMessageQueue() { return messageQueue; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/MQPushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import org.apache.rocketmq.client.consumer.listener.MessageListener; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.exception.MQClientException; /** * Push consumer */ public interface MQPushConsumer extends MQConsumer { /** * Start the consumer */ void start() throws MQClientException; /** * Shutdown the consumer */ void shutdown(); /** * Register the message listener */ @Deprecated void registerMessageListener(MessageListener messageListener); void registerMessageListener(final MessageListenerConcurrently messageListener); void registerMessageListener(final MessageListenerOrderly messageListener); /** * Subscribe some topic * * @param subExpression subscription expression.it only support or operation such as "tag1 || tag2 || tag3"
if * null or * expression,meaning subscribe * all */ void subscribe(final String topic, final String subExpression) throws MQClientException; /** * This method will be removed in the version 5.0.0,because filterServer was removed,and method subscribe(final String topic, final MessageSelector messageSelector) * is recommended. * * Subscribe some topic * * @param fullClassName full class name,must extend org.apache.rocketmq.common.filter. MessageFilter * @param filterClassSource class source code,used UTF-8 file encoding,must be responsible for your code safety */ @Deprecated void subscribe(final String topic, final String fullClassName, final String filterClassSource) throws MQClientException; /** * Subscribe some topic with selector. *

* This interface also has the ability of {@link #subscribe(String, String)}, * and, support other message selection, such as {@link org.apache.rocketmq.common.filter.ExpressionType#SQL92}. *

*

*

* Choose Tag: {@link MessageSelector#byTag(java.lang.String)} *

*

*

* Choose SQL92: {@link MessageSelector#bySql(java.lang.String)} *

* * @param selector message selector({@link MessageSelector}), can be null. */ void subscribe(final String topic, final MessageSelector selector) throws MQClientException; /** * Unsubscribe consumption some topic * * @param topic message topic */ void unsubscribe(final String topic); /** * Update the consumer thread pool size Dynamically */ void updateCorePoolSize(int corePoolSize); /** * Suspend the consumption */ void suspend(); /** * Resume the consumption */ void resume(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/MessageQueueListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; /** * A MessageQueueListener is implemented by the application and may be specified when a message queue changed */ public interface MessageQueueListener { /** * @param topic message topic * @param mqAll all queues in this message topic * @param mqAssigned collection of queues, assigned to the current consumer */ void messageQueueChanged(final String topic, final Set mqAll, final Set mqAssigned); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/MessageSelector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import org.apache.rocketmq.common.filter.ExpressionType; /** * Message selector: select message at server. *

* Now, support: *

  • Tag: {@link org.apache.rocketmq.common.filter.ExpressionType#TAG} *
  • *
  • SQL92: {@link org.apache.rocketmq.common.filter.ExpressionType#SQL92} *
  • *

    */ public class MessageSelector { /** * @see org.apache.rocketmq.common.filter.ExpressionType */ private String type; /** * expression content. */ private String expression; private MessageSelector(String type, String expression) { this.type = type; this.expression = expression; } /** * Use SQL92 to select message. * * @param sql if null or empty, will be treated as select all message. */ public static MessageSelector bySql(String sql) { return new MessageSelector(ExpressionType.SQL92, sql); } /** * Use tag to select message. * * @param tag if null or empty or "*", will be treated as select all message. */ public static MessageSelector byTag(String tag) { return new MessageSelector(ExpressionType.TAG, tag); } public String getExpressionType() { return type; } public String getExpression() { return expression; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/NotifyResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public class NotifyResult { private boolean hasMsg; private boolean pollingFull; public boolean isHasMsg() { return hasMsg; } public boolean isPollingFull() { return pollingFull; } public void setHasMsg(boolean hasMsg) { this.hasMsg = hasMsg; } public void setPollingFull(boolean pollingFull) { this.pollingFull = pollingFull; } @Override public String toString() { return "NotifyResult{" + "hasMsg=" + hasMsg + ", pollingFull=" + pollingFull + '}'; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; /** * Async message pop interface */ public interface PopCallback { void onSuccess(final PopResult popResult); void onException(final Throwable e); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; public class PopResult { private List msgFoundList; private PopStatus popStatus; private long popTime; private long invisibleTime; private long restNum; public PopResult(PopStatus popStatus, List msgFoundList) { this.popStatus = popStatus; this.msgFoundList = msgFoundList; } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public long getRestNum() { return restNum; } public void setRestNum(long restNum) { this.restNum = restNum; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public void setPopStatus(PopStatus popStatus) { this.popStatus = popStatus; } public PopStatus getPopStatus() { return popStatus; } public List getMsgFoundList() { return msgFoundList; } public void setMsgFoundList(List msgFoundList) { this.msgFoundList = msgFoundList; } @Override public String toString() { return "PopResult [popStatus=" + popStatus + ",msgFoundList=" + (msgFoundList == null ? 0 : msgFoundList.size()) + ",restNum=" + restNum + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public enum PopStatus { /** * Founded */ FOUND, /** * No new message can be pull after polling time out * delete after next release */ NO_NEW_MSG, /** * polling pool is full, do not try again immediately. */ POLLING_FULL, /** * polling time out but no message find */ POLLING_NOT_FOUND } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PullCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; /** * Async message pulling interface */ public interface PullCallback { void onSuccess(final PullResult pullResult); void onException(final Throwable e); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PullResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; public class PullResult { private final PullStatus pullStatus; private final long nextBeginOffset; private final long minOffset; private final long maxOffset; private List msgFoundList; public PullResult(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset, List msgFoundList) { super(); this.pullStatus = pullStatus; this.nextBeginOffset = nextBeginOffset; this.minOffset = minOffset; this.maxOffset = maxOffset; this.msgFoundList = msgFoundList; } public PullStatus getPullStatus() { return pullStatus; } public long getNextBeginOffset() { return nextBeginOffset; } public long getMinOffset() { return minOffset; } public long getMaxOffset() { return maxOffset; } public List getMsgFoundList() { return msgFoundList; } public void setMsgFoundList(List msgFoundList) { this.msgFoundList = msgFoundList; } @Override public String toString() { return "PullResult [pullStatus=" + pullStatus + ", nextBeginOffset=" + nextBeginOffset + ", minOffset=" + minOffset + ", maxOffset=" + maxOffset + ", msgFoundList=" + (msgFoundList == null ? 0 : msgFoundList.size()) + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PullStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public enum PullStatus { /** * Founded */ FOUND, /** * No new message can be pull */ NO_NEW_MSG, /** * Filtering results can not match */ NO_MATCHED_MSG, /** * Illegal offset,may be too big or too small */ OFFSET_ILLEGAL } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PullTaskCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import org.apache.rocketmq.common.message.MessageQueue; public interface PullTaskCallback { void doPullTask(final MessageQueue mq, final PullTaskContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/PullTaskContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; public class PullTaskContext { private int pullNextDelayTimeMillis = 200; private MQPullConsumer pullConsumer; public int getPullNextDelayTimeMillis() { return pullNextDelayTimeMillis; } public void setPullNextDelayTimeMillis(int pullNextDelayTimeMillis) { this.pullNextDelayTimeMillis = pullNextDelayTimeMillis; } public MQPullConsumer getPullConsumer() { return pullConsumer; } public void setPullConsumer(MQPullConsumer pullConsumer) { this.pullConsumer = pullConsumer; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/TopicMessageQueueChangeListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; public interface TopicMessageQueueChangeListener { /** * This method will be invoked in the condition of queue numbers changed, These scenarios occur when the topic is * expanded or shrunk. * * @param messageQueues */ void onChanged(String topic, Set messageQueues); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/ConsumeConcurrentlyContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; import org.apache.rocketmq.common.message.MessageQueue; /** * Consumer concurrent consumption context */ public class ConsumeConcurrentlyContext { private final MessageQueue messageQueue; /** * Message consume retry strategy
    * -1,no retry,put into DLQ directly
    * 0,broker control retry frequency
    * >0,client control retry frequency */ private int delayLevelWhenNextConsume = 0; private int ackIndex = Integer.MAX_VALUE; public ConsumeConcurrentlyContext(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public int getDelayLevelWhenNextConsume() { return delayLevelWhenNextConsume; } public void setDelayLevelWhenNextConsume(int delayLevelWhenNextConsume) { this.delayLevelWhenNextConsume = delayLevelWhenNextConsume; } public MessageQueue getMessageQueue() { return messageQueue; } public int getAckIndex() { return ackIndex; } public void setAckIndex(int ackIndex) { this.ackIndex = ackIndex; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/ConsumeConcurrentlyStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; public enum ConsumeConcurrentlyStatus { /** * Success consumption */ CONSUME_SUCCESS, /** * Failure consumption,later try to consume */ RECONSUME_LATER; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/ConsumeOrderlyContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; import org.apache.rocketmq.common.message.MessageQueue; /** * Consumer Orderly consumption context */ public class ConsumeOrderlyContext { private final MessageQueue messageQueue; private boolean autoCommit = true; private long suspendCurrentQueueTimeMillis = -1; public ConsumeOrderlyContext(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public boolean isAutoCommit() { return autoCommit; } public void setAutoCommit(boolean autoCommit) { this.autoCommit = autoCommit; } public MessageQueue getMessageQueue() { return messageQueue; } public long getSuspendCurrentQueueTimeMillis() { return suspendCurrentQueueTimeMillis; } public void setSuspendCurrentQueueTimeMillis(long suspendCurrentQueueTimeMillis) { this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/ConsumeOrderlyStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; public enum ConsumeOrderlyStatus { /** * Success consumption */ SUCCESS, /** * Rollback consumption(only for binlog consumption) */ @Deprecated ROLLBACK, /** * Commit offset(only for binlog consumption) */ @Deprecated COMMIT, /** * Suspend current queue a moment */ SUSPEND_CURRENT_QUEUE_A_MOMENT; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/ConsumeReturnType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; public enum ConsumeReturnType { /** * consume return success */ SUCCESS, /** * consume timeout ,even if success */ TIME_OUT, /** * consume throw exception */ EXCEPTION, /** * consume return null */ RETURNNULL, /** * consume return failed */ FAILED } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; /** * A MessageListener object is used to receive asynchronously delivered messages. */ public interface MessageListener { } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListenerConcurrently.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; /** * A MessageListenerConcurrently object is used to receive asynchronously delivered messages concurrently */ public interface MessageListenerConcurrently extends MessageListener { /** * It is not recommend to throw exception,rather than returning ConsumeConcurrentlyStatus.RECONSUME_LATER if * consumption failure * * @param msgs msgs.size() >= 1
    DefaultMQPushConsumer.consumeMessageBatchMaxSize=1,you can modify here * @return The consume status */ ConsumeConcurrentlyStatus consumeMessage(final List msgs, final ConsumeConcurrentlyContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListenerOrderly.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.listener; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; /** * A MessageListenerOrderly object is used to receive messages orderly. One queue by one thread */ public interface MessageListenerOrderly extends MessageListener { /** * It is not recommend to throw exception,rather than returning ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT * if consumption failure * * @param msgs msgs.size() >= 1
    DefaultMQPushConsumer.consumeMessageBatchMaxSize=1,you can modify here * @return The consume status */ ConsumeOrderlyStatus consumeMessage(final List msgs, final ConsumeOrderlyContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AbstractAllocateMessageQueueStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class AbstractAllocateMessageQueueStrategy implements AllocateMessageQueueStrategy { private static final Logger log = LoggerFactory.getLogger(AbstractAllocateMessageQueueStrategy.class); public boolean check(String consumerGroup, String currentCID, List mqAll, List cidAll) { if (StringUtils.isEmpty(currentCID)) { throw new IllegalArgumentException("currentCID is empty"); } if (CollectionUtils.isEmpty(mqAll)) { throw new IllegalArgumentException("mqAll is null or mqAll empty"); } if (CollectionUtils.isEmpty(cidAll)) { throw new IllegalArgumentException("cidAll is null or cidAll empty"); } if (!cidAll.contains(currentCID)) { log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}", consumerGroup, currentCID, cidAll); return false; } return true; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearby.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.common.message.MessageQueue; /** * An allocate strategy proxy for based on machine room nearside priority. An actual allocate strategy can be * specified. * * If any consumer is alive in a machine room, the message queue of the broker which is deployed in the same machine * should only be allocated to those. Otherwise, those message queues can be shared along all consumers since there are * no alive consumer to monopolize them. */ public class AllocateMachineRoomNearby extends AbstractAllocateMessageQueueStrategy { private final AllocateMessageQueueStrategy allocateMessageQueueStrategy;//actual allocate strategy private final MachineRoomResolver machineRoomResolver; public AllocateMachineRoomNearby(AllocateMessageQueueStrategy allocateMessageQueueStrategy, MachineRoomResolver machineRoomResolver) throws NullPointerException { if (allocateMessageQueueStrategy == null) { throw new NullPointerException("allocateMessageQueueStrategy is null"); } if (machineRoomResolver == null) { throw new NullPointerException("machineRoomResolver is null"); } this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; this.machineRoomResolver = machineRoomResolver; } @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { List result = new ArrayList<>(); if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } //group mq by machine room Map> mr2Mq = new TreeMap<>(); for (MessageQueue mq : mqAll) { String brokerMachineRoom = machineRoomResolver.brokerDeployIn(mq); if (StringUtils.isNoneEmpty(brokerMachineRoom)) { if (mr2Mq.get(brokerMachineRoom) == null) { mr2Mq.put(brokerMachineRoom, new ArrayList<>()); } mr2Mq.get(brokerMachineRoom).add(mq); } else { throw new IllegalArgumentException("Machine room is null for mq " + mq); } } //group consumer by machine room Map> mr2c = new TreeMap<>(); for (String cid : cidAll) { String consumerMachineRoom = machineRoomResolver.consumerDeployIn(cid); if (StringUtils.isNoneEmpty(consumerMachineRoom)) { if (mr2c.get(consumerMachineRoom) == null) { mr2c.put(consumerMachineRoom, new ArrayList<>()); } mr2c.get(consumerMachineRoom).add(cid); } else { throw new IllegalArgumentException("Machine room is null for consumer id " + cid); } } List allocateResults = new ArrayList<>(); //1.allocate the mq that deploy in the same machine room with the current consumer String currentMachineRoom = machineRoomResolver.consumerDeployIn(currentCID); List mqInThisMachineRoom = mr2Mq.remove(currentMachineRoom); List consumerInThisMachineRoom = mr2c.get(currentMachineRoom); if (mqInThisMachineRoom != null && !mqInThisMachineRoom.isEmpty()) { allocateResults.addAll(allocateMessageQueueStrategy.allocate(consumerGroup, currentCID, mqInThisMachineRoom, consumerInThisMachineRoom)); } //2.allocate the rest mq to each machine room if there are no consumer alive in that machine room for (Entry> machineRoomEntry : mr2Mq.entrySet()) { if (!mr2c.containsKey(machineRoomEntry.getKey())) { // no alive consumer in the corresponding machine room, so all consumers share these queues allocateResults.addAll(allocateMessageQueueStrategy.allocate(consumerGroup, currentCID, machineRoomEntry.getValue(), cidAll)); } } return allocateResults; } @Override public String getName() { return "MACHINE_ROOM_NEARBY" + "-" + allocateMessageQueueStrategy.getName(); } /** * A resolver object to determine which machine room do the message queues or clients are deployed in. * * AllocateMachineRoomNearby will use the results to group the message queues and clients by machine room. * * The result returned from the implemented method CANNOT be null. */ public interface MachineRoomResolver { String brokerDeployIn(MessageQueue messageQueue); String consumerDeployIn(String clientID); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; /** * Average Hashing queue algorithm */ public class AllocateMessageQueueAveragely extends AbstractAllocateMessageQueueStrategy { @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { List result = new ArrayList<>(); if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } int index = cidAll.indexOf(currentCID); int mod = mqAll.size() % cidAll.size(); int averageSize = mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size() + 1 : mqAll.size() / cidAll.size()); int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod; int range = Math.min(averageSize, mqAll.size() - startIndex); for (int i = 0; i < range; i++) { result.add(mqAll.get(startIndex + i)); } return result; } @Override public String getName() { return "AVG"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; /** * Cycle average Hashing queue algorithm */ public class AllocateMessageQueueAveragelyByCircle extends AbstractAllocateMessageQueueStrategy { @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { List result = new ArrayList<>(); if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } int index = cidAll.indexOf(currentCID); for (int i = index; i < mqAll.size(); i++) { if (i % cidAll.size() == index) { result.add(mqAll.get(i)); } } return result; } @Override public String getName() { return "AVG_BY_CIRCLE"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; public class AllocateMessageQueueByConfig extends AbstractAllocateMessageQueueStrategy { private List messageQueueList; @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { return this.messageQueueList; } @Override public String getName() { return "CONFIG"; } public List getMessageQueueList() { return messageQueueList; } public void setMessageQueueList(List messageQueueList) { this.messageQueueList = messageQueueList; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoom.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; /** * Computer room Hashing queue algorithm, such as Alipay logic room */ public class AllocateMessageQueueByMachineRoom extends AbstractAllocateMessageQueueStrategy { private Set consumeridcs; @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { List result = new ArrayList<>(); if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } int currentIndex = cidAll.indexOf(currentCID); if (currentIndex < 0) { return result; } List premqAll = new ArrayList<>(); for (MessageQueue mq : mqAll) { String[] temp = mq.getBrokerName().split("@"); if (temp.length == 2 && consumeridcs.contains(temp[0])) { premqAll.add(mq); } } int mod = premqAll.size() / cidAll.size(); int rem = premqAll.size() % cidAll.size(); int startIndex = mod * currentIndex; int endIndex = startIndex + mod; for (int i = startIndex; i < endIndex; i++) { result.add(premqAll.get(i)); } if (rem > currentIndex) { result.add(premqAll.get(currentIndex + mod * cidAll.size())); } return result; } @Override public String getName() { return "MACHINE_ROOM"; } public Set getConsumeridcs() { return consumeridcs; } public void setConsumeridcs(Set consumeridcs) { this.consumeridcs = consumeridcs; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsistentHash.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.rocketmq.common.consistenthash.ConsistentHashRouter; import org.apache.rocketmq.common.consistenthash.HashFunction; import org.apache.rocketmq.common.consistenthash.Node; import org.apache.rocketmq.common.message.MessageQueue; /** * Consistent Hashing queue algorithm */ public class AllocateMessageQueueConsistentHash extends AbstractAllocateMessageQueueStrategy { private final int virtualNodeCnt; private final HashFunction customHashFunction; public AllocateMessageQueueConsistentHash() { this(10); } public AllocateMessageQueueConsistentHash(int virtualNodeCnt) { this(virtualNodeCnt, null); } public AllocateMessageQueueConsistentHash(int virtualNodeCnt, HashFunction customHashFunction) { if (virtualNodeCnt < 0) { throw new IllegalArgumentException("illegal virtualNodeCnt :" + virtualNodeCnt); } this.virtualNodeCnt = virtualNodeCnt; this.customHashFunction = customHashFunction; } @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { List result = new ArrayList<>(); if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } Collection cidNodes = new ArrayList<>(); for (String cid : cidAll) { cidNodes.add(new ClientNode(cid)); } final ConsistentHashRouter router; //for building hash ring if (customHashFunction != null) { router = new ConsistentHashRouter<>(cidNodes, virtualNodeCnt, customHashFunction); } else { router = new ConsistentHashRouter<>(cidNodes, virtualNodeCnt); } List results = new ArrayList<>(); for (MessageQueue mq : mqAll) { ClientNode clientNode = router.routeNode(mq.toString()); if (clientNode != null && currentCID.equals(clientNode.getKey())) { results.add(mq); } } return results; } @Override public String getName() { return "CONSISTENT_HASH"; } private static class ClientNode implements Node { private final String clientID; public ClientNode(String clientID) { this.clientID = clientID; } @Override public String getKey() { return clientID; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/store/ControllableOffset.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.util.concurrent.atomic.AtomicLong; /** * The ControllableOffset class encapsulates a thread-safe offset value that can be * updated atomically. Additionally, this class allows for the offset to be "frozen," * which prevents further updates after the freeze operation has been performed. *

    * Concurrency Scenarios: * If {@code updateAndFreeze} is called before any {@code update} operations, it sets * {@code allowToUpdate} to false and updates the offset to the target value specified. * After this operation, further invocations of {@code update} will not affect the offset, * as it is considered frozen. *

    * If {@code update} is in progress while {@code updateAndFreeze} is invoked concurrently, * the final outcome depends on the sequence of operations: * 1. If {@code update}'s atomic update operation completes before {@code updateAndFreeze}, * the latter will overwrite the offset and set {@code allowToUpdate} to false, * preventing any further updates. * 2. If {@code updateAndFreeze} executes before the {@code update} finalizes its operation, * the ongoing {@code update} will not proceed with its changes. The {@link AtomicLong#getAndUpdate} * method used in both operations ensures atomicity and respects the final state imposed by * {@code updateAndFreeze}, even if the {@code update} function has already begun. *

    * In essence, once the {@code updateAndFreeze} operation is executed, the offset value remains * immutable to any subsequent {@code update} calls due to the immediate visibility of the * {@code allowToUpdate} state change, courtesy of its volatile nature. *

    * The combination of an AtomicLong for the offset value and a volatile boolean flag for update * control provides a reliable mechanism for managing offset values in concurrent environments. */ public class ControllableOffset { // Holds the current offset value in an atomic way. private final AtomicLong value; // Controls whether updates to the offset are allowed. private volatile boolean allowToUpdate; public ControllableOffset(long value) { this.value = new AtomicLong(value); this.allowToUpdate = true; } /** * Attempts to update the offset to the target value. If increaseOnly is true, * the offset will not be decreased. The update operation is atomic and thread-safe. * The operation will respect the current allowToUpdate state, and if the offset * has been frozen by a previous call to {@link #updateAndFreeze(long)}, * this method will not update the offset. * * @param target the new target offset value. * @param increaseOnly if true, the offset will only be updated if the target value * is greater than the current value. */ public void update(long target, boolean increaseOnly) { if (allowToUpdate) { value.getAndUpdate(val -> { if (allowToUpdate) { if (increaseOnly) { return Math.max(target, val); } else { return target; } } else { return val; } }); } } /** * Overloaded method for updating the offset value unconditionally. * * @param target The new target value for the offset. */ public void update(long target) { update(target, false); } /** * Freezes the offset at the target value provided. Once frozen, the offset * cannot be updated by subsequent calls to {@link #update(long, boolean)}. * This method will set allowToUpdate to false and then update the offset, * ensuring the new value is the final state of the offset. * * @param target the new target offset value to freeze at. */ public void updateAndFreeze(long target) { value.getAndUpdate(val -> { allowToUpdate = false; return target; }); } public long getOffset() { return value.get(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * Local storage implementation */ public class LocalFileOffsetStore implements OffsetStore { public final static String LOCAL_OFFSET_STORE_DIR = System.getProperty( "rocketmq.client.localOffsetStoreDir", System.getProperty("user.home") + File.separator + ".rocketmq_offsets"); private final static Logger log = LoggerFactory.getLogger(LocalFileOffsetStore.class); private final MQClientInstance mQClientFactory; private final String groupName; private final String storePath; private ConcurrentMap offsetTable = new ConcurrentHashMap<>(); public LocalFileOffsetStore(MQClientInstance mQClientFactory, String groupName) { this.mQClientFactory = mQClientFactory; this.groupName = groupName; this.storePath = LOCAL_OFFSET_STORE_DIR + File.separator + this.mQClientFactory.getClientId() + File.separator + this.groupName + File.separator + "offsets.json"; } @Override public void load() throws MQClientException { OffsetSerializeWrapper offsetSerializeWrapper = this.readLocalOffset(); if (offsetSerializeWrapper != null && offsetSerializeWrapper.getOffsetTable() != null) { for (Entry mqEntry : offsetSerializeWrapper.getOffsetTable().entrySet()) { AtomicLong offset = mqEntry.getValue(); offsetTable.put(mqEntry.getKey(), new ControllableOffset(offset.get())); log.info("load consumer's offset, {} {} {}", this.groupName, mqEntry.getKey(), offset.get()); } } } @Override public void updateOffset(MessageQueue mq, long offset, boolean increaseOnly) { if (mq != null) { ControllableOffset offsetOld = this.offsetTable.get(mq); if (null == offsetOld) { offsetOld = this.offsetTable.putIfAbsent(mq, new ControllableOffset(offset)); } if (null != offsetOld) { if (increaseOnly) { offsetOld.update(offset, true); } else { offsetOld.update(offset); } } } } @Override public void updateAndFreezeOffset(MessageQueue mq, long offset) { if (mq != null) { this.offsetTable.computeIfAbsent(mq, k -> new ControllableOffset(offset)) .updateAndFreeze(offset); } } @Override public long readOffset(final MessageQueue mq, final ReadOffsetType type) { if (mq != null) { switch (type) { case MEMORY_FIRST_THEN_STORE: case READ_FROM_MEMORY: { ControllableOffset offset = this.offsetTable.get(mq); if (offset != null) { return offset.getOffset(); } else if (ReadOffsetType.READ_FROM_MEMORY == type) { return -1; } } case READ_FROM_STORE: { OffsetSerializeWrapper offsetSerializeWrapper; try { offsetSerializeWrapper = this.readLocalOffset(); } catch (MQClientException e) { return -1; } if (offsetSerializeWrapper != null && offsetSerializeWrapper.getOffsetTable() != null) { AtomicLong offset = offsetSerializeWrapper.getOffsetTable().get(mq); if (offset != null) { this.updateOffset(mq, offset.get(), false); return offset.get(); } } } default: break; } } return -1; } @Override public void persistAll(Set mqs) { if (null == mqs || mqs.isEmpty()) { return; } OffsetSerializeWrapper offsetSerializeWrapper = null; try { offsetSerializeWrapper = readLocalOffset(); } catch (MQClientException e) { log.error("readLocalOffset exception", e); return; } if (offsetSerializeWrapper == null) { offsetSerializeWrapper = new OffsetSerializeWrapper(); } for (Map.Entry entry : this.offsetTable.entrySet()) { if (mqs.contains(entry.getKey())) { AtomicLong offset = new AtomicLong(entry.getValue().getOffset()); offsetSerializeWrapper.getOffsetTable().put(entry.getKey(), offset); } } String jsonString = offsetSerializeWrapper.toJson(true); if (jsonString != null) { try { MixAll.string2File(jsonString, this.storePath); } catch (IOException e) { log.error("persistAll consumer offset Exception, " + this.storePath, e); } } } @Override public void persist(MessageQueue mq) { if (mq == null) { return; } ControllableOffset offset = this.offsetTable.get(mq); if (offset != null) { OffsetSerializeWrapper offsetSerializeWrapper = null; try { offsetSerializeWrapper = readLocalOffset(); } catch (MQClientException e) { log.error("readLocalOffset exception", e); return; } if (offsetSerializeWrapper == null) { offsetSerializeWrapper = new OffsetSerializeWrapper(); } offsetSerializeWrapper.getOffsetTable().put(mq, new AtomicLong(offset.getOffset())); String jsonString = offsetSerializeWrapper.toJson(true); if (jsonString != null) { try { MixAll.string2File(jsonString, this.storePath); } catch (IOException e) { log.error("persist consumer offset exception, " + this.storePath, e); } } } } @Override public void removeOffset(MessageQueue mq) { if (mq != null) { this.offsetTable.remove(mq); log.info("remove unnecessary messageQueue offset. group={}, mq={}, offsetTableSize={}", this.groupName, mq, offsetTable.size()); } } @Override public void updateConsumeOffsetToBroker(final MessageQueue mq, final long offset, final boolean isOneway) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { } @Override public Map cloneOffsetTable(String topic) { Map cloneOffsetTable = new HashMap<>(this.offsetTable.size(), 1); for (Map.Entry entry : this.offsetTable.entrySet()) { MessageQueue mq = entry.getKey(); if (!UtilAll.isBlank(topic) && !topic.equals(mq.getTopic())) { continue; } cloneOffsetTable.put(mq, entry.getValue().getOffset()); } return cloneOffsetTable; } private OffsetSerializeWrapper readLocalOffset() throws MQClientException { String content = null; try { content = MixAll.file2String(this.storePath); } catch (IOException e) { log.warn("Load local offset store file exception", e); } if (null == content || content.length() == 0) { return this.readLocalOffsetBak(); } else { OffsetSerializeWrapper offsetSerializeWrapper = null; try { offsetSerializeWrapper = OffsetSerializeWrapper.fromJson(content, OffsetSerializeWrapper.class); } catch (Exception e) { log.warn("readLocalOffset Exception, and try to correct", e); return this.readLocalOffsetBak(); } return offsetSerializeWrapper; } } private OffsetSerializeWrapper readLocalOffsetBak() throws MQClientException { String content = null; try { content = MixAll.file2String(this.storePath + ".bak"); } catch (IOException e) { log.warn("Load local offset store bak file exception", e); } if (content != null && content.length() > 0) { OffsetSerializeWrapper offsetSerializeWrapper = null; try { offsetSerializeWrapper = OffsetSerializeWrapper.fromJson(content, OffsetSerializeWrapper.class); } catch (Exception e) { log.warn("readLocalOffset Exception", e); throw new MQClientException("readLocalOffset Exception, maybe fastjson version too low" + FAQUrl.suggestTodo(FAQUrl.LOAD_JSON_EXCEPTION), e); } return offsetSerializeWrapper; } return null; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/store/OffsetSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; /** * Wrapper class for offset serialization */ public class OffsetSerializeWrapper extends RemotingSerializable { private ConcurrentMap offsetTable = new ConcurrentHashMap<>(); public ConcurrentMap getOffsetTable() { return offsetTable; } public void setOffsetTable(ConcurrentMap offsetTable) { this.offsetTable = offsetTable; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/store/OffsetStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; /** * Offset store interface */ public interface OffsetStore { /** * Load */ void load() throws MQClientException; /** * Update the offset,store it in memory */ void updateOffset(final MessageQueue mq, final long offset, final boolean increaseOnly); /** * Update and freeze the message queue to prevent concurrent update action * * @param mq target message queue * @param offset expect update offset */ void updateAndFreezeOffset(final MessageQueue mq, final long offset); /** * Get offset from local storage * * @return The fetched offset */ long readOffset(final MessageQueue mq, final ReadOffsetType type); /** * Persist all offsets,may be in local storage or remote name server */ void persistAll(final Set mqs); /** * Persist the offset,may be in local storage or remote name server */ void persist(final MessageQueue mq); /** * Remove offset */ void removeOffset(MessageQueue mq); /** * @return The cloned offset table of given topic */ Map cloneOffsetTable(String topic); /** * @param mq * @param offset * @param isOneway */ void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/store/ReadOffsetType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; public enum ReadOffsetType { /** * From memory */ READ_FROM_MEMORY, /** * From storage */ READ_FROM_STORE, /** * From memory,then from storage */ MEMORY_FIRST_THEN_STORE; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.OffsetNotFoundException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; /** * Remote storage implementation */ public class RemoteBrokerOffsetStore implements OffsetStore { private final static Logger log = LoggerFactory.getLogger(RemoteBrokerOffsetStore.class); private final MQClientInstance mQClientFactory; private final String groupName; private ConcurrentMap offsetTable = new ConcurrentHashMap<>(); public RemoteBrokerOffsetStore(MQClientInstance mQClientFactory, String groupName) { this.mQClientFactory = mQClientFactory; this.groupName = groupName; } @Override public void load() { } @Override public void updateOffset(MessageQueue mq, long offset, boolean increaseOnly) { if (mq != null) { ControllableOffset offsetOld = this.offsetTable.get(mq); if (null == offsetOld) { offsetOld = this.offsetTable.putIfAbsent(mq, new ControllableOffset(offset)); } if (null != offsetOld) { if (increaseOnly) { offsetOld.update(offset, true); } else { offsetOld.update(offset); } } } } @Override public void updateAndFreezeOffset(MessageQueue mq, long offset) { if (mq != null) { this.offsetTable.computeIfAbsent(mq, k -> new ControllableOffset(offset)) .updateAndFreeze(offset); } } @Override public long readOffset(final MessageQueue mq, final ReadOffsetType type) { if (mq != null) { switch (type) { case MEMORY_FIRST_THEN_STORE: case READ_FROM_MEMORY: { ControllableOffset offset = this.offsetTable.get(mq); if (offset != null) { return offset.getOffset(); } else if (ReadOffsetType.READ_FROM_MEMORY == type) { return -1; } } case READ_FROM_STORE: { try { long brokerOffset = this.fetchConsumeOffsetFromBroker(mq); this.updateOffset(mq, brokerOffset, false); return brokerOffset; } // No offset in broker catch (OffsetNotFoundException e) { return -1; } //Other exceptions catch (Exception e) { log.warn("fetchConsumeOffsetFromBroker exception, " + mq, e); return -2; } } default: break; } } return -3; } @Override public void persistAll(Set mqs) { if (null == mqs || mqs.isEmpty()) return; final HashSet unusedMQ = new HashSet<>(); for (Map.Entry entry : this.offsetTable.entrySet()) { MessageQueue mq = entry.getKey(); ControllableOffset offset = entry.getValue(); if (offset != null) { if (mqs.contains(mq)) { try { this.updateConsumeOffsetToBroker(mq, offset.getOffset()); log.info("[persistAll] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}", this.groupName, this.mQClientFactory.getClientId(), mq, offset.getOffset()); } catch (Exception e) { log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e); } } else { unusedMQ.add(mq); } } } if (!unusedMQ.isEmpty()) { for (MessageQueue mq : unusedMQ) { this.offsetTable.remove(mq); log.info("remove unused mq, {}, {}", mq, this.groupName); } } } @Override public void persist(MessageQueue mq) { ControllableOffset offset = this.offsetTable.get(mq); if (offset != null) { try { this.updateConsumeOffsetToBroker(mq, offset.getOffset()); log.info("[persist] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}", this.groupName, this.mQClientFactory.getClientId(), mq, offset.getOffset()); } catch (Exception e) { log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e); } } } public void removeOffset(MessageQueue mq) { if (mq != null) { this.offsetTable.remove(mq); log.info("remove unnecessary messageQueue offset. group={}, mq={}, offsetTableSize={}", this.groupName, mq, offsetTable.size()); } } @Override public Map cloneOffsetTable(String topic) { Map cloneOffsetTable = new HashMap<>(this.offsetTable.size(), 1); for (Map.Entry entry : this.offsetTable.entrySet()) { MessageQueue mq = entry.getKey(); if (!UtilAll.isBlank(topic) && !topic.equals(mq.getTopic())) { continue; } cloneOffsetTable.put(mq, entry.getValue().getOffset()); } return cloneOffsetTable; } /** * Update the Consumer Offset in one way, once the Master is off, updated to Slave, here need to be optimized. */ private void updateConsumeOffsetToBroker(MessageQueue mq, long offset) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { updateConsumeOffsetToBroker(mq, offset, true); } /** * Update the Consumer Offset synchronously, once the Master is off, updated to Slave, here need to be optimized. */ @Override public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, false); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, false); } if (findBrokerResult != null) { UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader(); requestHeader.setTopic(mq.getTopic()); requestHeader.setConsumerGroup(this.groupName); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setCommitOffset(offset); requestHeader.setBrokerName(mq.getBrokerName()); if (isOneway) { this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffsetOneway( findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5); } else { this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffset( findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5); } } else { throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } } private long fetchConsumeOffsetFromBroker(MessageQueue mq) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, true); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, false); } if (findBrokerResult != null) { QueryConsumerOffsetRequestHeader requestHeader = new QueryConsumerOffsetRequestHeader(); requestHeader.setTopic(mq.getTopic()); requestHeader.setConsumerGroup(this.groupName); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setBrokerName(mq.getBrokerName()); return this.mQClientFactory.getMQClientAPIImpl().queryConsumerOffset( findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5); } else { throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/exception/MQBrokerException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.exception; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.help.FAQUrl; public class MQBrokerException extends Exception { private static final long serialVersionUID = 5975020272601250368L; private final int responseCode; private final String errorMessage; private final String brokerAddr; MQBrokerException() { this.responseCode = 0; this.errorMessage = null; this.brokerAddr = null; } public MQBrokerException(int responseCode, String errorMessage) { super(FAQUrl.attachDefaultURL("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: " + errorMessage)); this.responseCode = responseCode; this.errorMessage = errorMessage; this.brokerAddr = null; } public MQBrokerException(int responseCode, String errorMessage, String brokerAddr) { super(FAQUrl.attachDefaultURL("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: " + errorMessage + (brokerAddr != null ? " BROKER: " + brokerAddr : ""))); this.responseCode = responseCode; this.errorMessage = errorMessage; this.brokerAddr = brokerAddr; } public int getResponseCode() { return responseCode; } public String getErrorMessage() { return errorMessage; } public String getBrokerAddr() { return brokerAddr; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.exception; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.help.FAQUrl; public class MQClientException extends Exception { private static final long serialVersionUID = -5758410930844185841L; private int responseCode; private String errorMessage; public MQClientException(String errorMessage, Throwable cause) { super(FAQUrl.attachDefaultURL(errorMessage), cause); this.responseCode = -1; this.errorMessage = errorMessage; } public MQClientException(int responseCode, String errorMessage) { super(FAQUrl.attachDefaultURL("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: " + errorMessage)); this.responseCode = responseCode; this.errorMessage = errorMessage; } public MQClientException(int responseCode, String errorMessage, Throwable cause) { super(FAQUrl.attachDefaultURL("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: " + errorMessage), cause); this.responseCode = responseCode; this.errorMessage = errorMessage; } public int getResponseCode() { return responseCode; } public MQClientException setResponseCode(final int responseCode) { this.responseCode = responseCode; return this; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(final String errorMessage) { this.errorMessage = errorMessage; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/exception/OffsetNotFoundException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.exception; public class OffsetNotFoundException extends MQBrokerException { public OffsetNotFoundException() { } public OffsetNotFoundException(int responseCode, String errorMessage) { super(responseCode, errorMessage); } public OffsetNotFoundException(int responseCode, String errorMessage, String brokerAddr) { super(responseCode, errorMessage, brokerAddr); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/exception/RequestTimeoutException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.exception; import org.apache.rocketmq.common.UtilAll; public class RequestTimeoutException extends Exception { private static final long serialVersionUID = -5758410930844185841L; private int responseCode; private String errorMessage; public RequestTimeoutException(String errorMessage, Throwable cause) { super(errorMessage, cause); this.responseCode = -1; this.errorMessage = errorMessage; } public RequestTimeoutException(int responseCode, String errorMessage) { super("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: " + errorMessage); this.responseCode = responseCode; this.errorMessage = errorMessage; } public int getResponseCode() { return responseCode; } public RequestTimeoutException setResponseCode(final int responseCode) { this.responseCode = responseCode; return this; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(final String errorMessage) { this.errorMessage = errorMessage; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/CheckForbiddenContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; public class CheckForbiddenContext { private String nameSrvAddr; private String group; private Message message; private MessageQueue mq; private String brokerAddr; private CommunicationMode communicationMode; private SendResult sendResult; private Exception exception; private Object arg; private boolean unitMode = false; public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public Message getMessage() { return message; } public void setMessage(Message message) { this.message = message; } public MessageQueue getMq() { return mq; } public void setMq(MessageQueue mq) { this.mq = mq; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public CommunicationMode getCommunicationMode() { return communicationMode; } public void setCommunicationMode(CommunicationMode communicationMode) { this.communicationMode = communicationMode; } public SendResult getSendResult() { return sendResult; } public void setSendResult(SendResult sendResult) { this.sendResult = sendResult; } public Exception getException() { return exception; } public void setException(Exception exception) { this.exception = exception; } public Object getArg() { return arg; } public void setArg(Object arg) { this.arg = arg; } public boolean isUnitMode() { return unitMode; } public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } public String getNameSrvAddr() { return nameSrvAddr; } public void setNameSrvAddr(String nameSrvAddr) { this.nameSrvAddr = nameSrvAddr; } @Override public String toString() { return "SendMessageContext [nameSrvAddr=" + nameSrvAddr + ", group=" + group + ", message=" + message + ", mq=" + mq + ", brokerAddr=" + brokerAddr + ", communicationMode=" + communicationMode + ", sendResult=" + sendResult + ", exception=" + exception + ", unitMode=" + unitMode + ", arg=" + arg + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/CheckForbiddenHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; import org.apache.rocketmq.client.exception.MQClientException; public interface CheckForbiddenHook { String hookName(); void checkForbidden(final CheckForbiddenContext context) throws MQClientException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/ConsumeMessageContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; import java.util.List; import java.util.Map; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class ConsumeMessageContext { private String consumerGroup; private List msgList; private MessageQueue mq; private boolean success; private String status; private Object mqTraceContext; private Map props; private String namespace; private AccessChannel accessChannel; public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public List getMsgList() { return msgList; } public void setMsgList(List msgList) { this.msgList = msgList; } public MessageQueue getMq() { return mq; } public void setMq(MessageQueue mq) { this.mq = mq; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public Object getMqTraceContext() { return mqTraceContext; } public void setMqTraceContext(Object mqTraceContext) { this.mqTraceContext = mqTraceContext; } public Map getProps() { return props; } public void setProps(Map props) { this.props = props; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public AccessChannel getAccessChannel() { return accessChannel; } public void setAccessChannel(AccessChannel accessChannel) { this.accessChannel = accessChannel; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/ConsumeMessageHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; public interface ConsumeMessageHook { String hookName(); void consumeMessageBefore(final ConsumeMessageContext context); void consumeMessageAfter(final ConsumeMessageContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/EndTransactionContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.common.message.Message; public class EndTransactionContext { private String producerGroup; private Message message; private String brokerAddr; private String msgId; private String transactionId; private LocalTransactionState transactionState; private boolean fromTransactionCheck; public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public Message getMessage() { return message; } public void setMessage(Message message) { this.message = message; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } public LocalTransactionState getTransactionState() { return transactionState; } public void setTransactionState(LocalTransactionState transactionState) { this.transactionState = transactionState; } public boolean isFromTransactionCheck() { return fromTransactionCheck; } public void setFromTransactionCheck(boolean fromTransactionCheck) { this.fromTransactionCheck = fromTransactionCheck; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/EndTransactionHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; public interface EndTransactionHook { String hookName(); void endTransaction(final EndTransactionContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/FilterMessageContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class FilterMessageContext { private String consumerGroup; private List msgList; private MessageQueue mq; private Object arg; private boolean unitMode; public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public List getMsgList() { return msgList; } public void setMsgList(List msgList) { this.msgList = msgList; } public MessageQueue getMq() { return mq; } public void setMq(MessageQueue mq) { this.mq = mq; } public Object getArg() { return arg; } public void setArg(Object arg) { this.arg = arg; } public boolean isUnitMode() { return unitMode; } public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } @Override public String toString() { return "ConsumeMessageContext [consumerGroup=" + consumerGroup + ", msgList=" + msgList + ", mq=" + mq + ", arg=" + arg + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/FilterMessageHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; public interface FilterMessageHook { String hookName(); void filterMessage(final FilterMessageContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/SendMessageContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; import java.util.Map; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageType; public class SendMessageContext { private String producerGroup; private Message message; private MessageQueue mq; private String brokerAddr; private String bornHost; private CommunicationMode communicationMode; private SendResult sendResult; private Exception exception; private Object mqTraceContext; private Map props; private DefaultMQProducerImpl producer; private MessageType msgType = MessageType.Normal_Msg; private String namespace; public MessageType getMsgType() { return msgType; } public void setMsgType(final MessageType msgType) { this.msgType = msgType; } public DefaultMQProducerImpl getProducer() { return producer; } public void setProducer(final DefaultMQProducerImpl producer) { this.producer = producer; } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public Message getMessage() { return message; } public void setMessage(Message message) { this.message = message; } public MessageQueue getMq() { return mq; } public void setMq(MessageQueue mq) { this.mq = mq; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public CommunicationMode getCommunicationMode() { return communicationMode; } public void setCommunicationMode(CommunicationMode communicationMode) { this.communicationMode = communicationMode; } public SendResult getSendResult() { return sendResult; } public void setSendResult(SendResult sendResult) { this.sendResult = sendResult; } public Exception getException() { return exception; } public void setException(Exception exception) { this.exception = exception; } public Object getMqTraceContext() { return mqTraceContext; } public void setMqTraceContext(Object mqTraceContext) { this.mqTraceContext = mqTraceContext; } public Map getProps() { return props; } public void setProps(Map props) { this.props = props; } public String getBornHost() { return bornHost; } public void setBornHost(String bornHost) { this.bornHost = bornHost; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/hook/SendMessageHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.hook; public interface SendMessageHook { String hookName(); void sendMessageBefore(final SendMessageContext context); void sendMessageAfter(final SendMessageContext context); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import io.netty.channel.ChannelHandlerContext; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.MQProducerInner; import org.apache.rocketmq.client.producer.RequestFutureHolder; import org.apache.rocketmq.client.producer.RequestResponseFuture; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.compression.Compressor; import org.apache.rocketmq.common.compression.CompressorFactory; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.GetConsumerStatusBody; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerStatusRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyConsumerIdsChangedRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ReplyMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ClientRemotingProcessor implements NettyRequestProcessor { private final Logger logger = LoggerFactory.getLogger(ClientRemotingProcessor.class); private final MQClientInstance mqClientFactory; public ClientRemotingProcessor(final MQClientInstance mqClientFactory) { this.mqClientFactory = mqClientFactory; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { switch (request.getCode()) { case RequestCode.CHECK_TRANSACTION_STATE: return this.checkTransactionState(ctx, request); case RequestCode.NOTIFY_CONSUMER_IDS_CHANGED: return this.notifyConsumerIdsChanged(ctx, request); case RequestCode.RESET_CONSUMER_CLIENT_OFFSET: return this.resetOffset(ctx, request); case RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT: return this.getConsumeStatus(ctx, request); case RequestCode.GET_CONSUMER_RUNNING_INFO: return this.getConsumerRunningInfo(ctx, request); case RequestCode.CONSUME_MESSAGE_DIRECTLY: return this.consumeMessageDirectly(ctx, request); case RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT: return this.receiveReplyMessage(ctx, request); default: break; } return null; } @Override public boolean rejectRequest() { return false; } public RemotingCommand checkTransactionState(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final CheckTransactionStateRequestHeader requestHeader = (CheckTransactionStateRequestHeader) request.decodeCommandCustomHeader(CheckTransactionStateRequestHeader.class); final ByteBuffer byteBuffer = ByteBuffer.wrap(request.getBody()); final MessageExt messageExt = MessageDecoder.decode(byteBuffer); if (messageExt != null) { if (StringUtils.isNotEmpty(this.mqClientFactory.getClientConfig().getNamespace())) { messageExt.setTopic(NamespaceUtil .withoutNamespace(messageExt.getTopic(), this.mqClientFactory.getClientConfig().getNamespace())); } String transactionId = messageExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (null != transactionId && !"".equals(transactionId)) { messageExt.setTransactionId(transactionId); } final String group = messageExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); if (group != null) { MQProducerInner producer = this.mqClientFactory.selectProducer(group); if (producer != null) { final String addr = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); producer.checkTransactionState(addr, messageExt, requestHeader); } else { logger.debug("checkTransactionState, pick producer by group[{}] failed", group); } } else { logger.warn("checkTransactionState, pick producer group failed"); } } else { logger.warn("checkTransactionState, decode message failed"); } return null; } public RemotingCommand notifyConsumerIdsChanged(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { try { final NotifyConsumerIdsChangedRequestHeader requestHeader = (NotifyConsumerIdsChangedRequestHeader) request.decodeCommandCustomHeader(NotifyConsumerIdsChangedRequestHeader.class); logger.info("receive broker's notification[{}], the consumer group: {} changed, rebalance immediately", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getConsumerGroup()); this.mqClientFactory.rebalanceImmediately(); } catch (Exception e) { logger.error("notifyConsumerIdsChanged exception", UtilAll.exceptionSimpleDesc(e)); } return null; } public RemotingCommand resetOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ResetOffsetRequestHeader requestHeader = (ResetOffsetRequestHeader) request.decodeCommandCustomHeader(ResetOffsetRequestHeader.class); logger.info("invoke reset offset operation from broker. brokerAddr={}, topic={}, group={}, timestamp={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getTopic(), requestHeader.getGroup(), requestHeader.getTimestamp()); Map offsetTable = new HashMap<>(); if (request.getBody() != null) { ResetOffsetBody body = ResetOffsetBody.decode(request.getBody(), ResetOffsetBody.class); offsetTable = body.getOffsetTable(); } this.mqClientFactory.resetOffset(requestHeader.getTopic(), requestHeader.getGroup(), offsetTable); return null; } @Deprecated public RemotingCommand getConsumeStatus(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetConsumerStatusRequestHeader requestHeader = (GetConsumerStatusRequestHeader) request.decodeCommandCustomHeader(GetConsumerStatusRequestHeader.class); Map offsetTable = this.mqClientFactory.getConsumerStatus(requestHeader.getTopic(), requestHeader.getGroup()); GetConsumerStatusBody body = new GetConsumerStatusBody(); body.setMessageQueueTable(offsetTable); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); return response; } private RemotingCommand getConsumerRunningInfo(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetConsumerRunningInfoRequestHeader requestHeader = (GetConsumerRunningInfoRequestHeader) request.decodeCommandCustomHeader(GetConsumerRunningInfoRequestHeader.class); ConsumerRunningInfo consumerRunningInfo = this.mqClientFactory.consumerRunningInfo(requestHeader.getConsumerGroup()); if (null != consumerRunningInfo) { if (requestHeader.isJstackEnable()) { Map map = Thread.getAllStackTraces(); String jstack = UtilAll.jstack(map); consumerRunningInfo.setJstack(jstack); } response.setCode(ResponseCode.SUCCESS); response.setBody(consumerRunningInfo.encode()); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("The Consumer Group <%s> not exist in this consumer", requestHeader.getConsumerGroup())); } return response; } private RemotingCommand consumeMessageDirectly(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final ConsumeMessageDirectlyResultRequestHeader requestHeader = (ConsumeMessageDirectlyResultRequestHeader) request .decodeCommandCustomHeader(ConsumeMessageDirectlyResultRequestHeader.class); final MessageExt msg = MessageDecoder.clientDecode(ByteBuffer.wrap(request.getBody()), true); ConsumeMessageDirectlyResult result = this.mqClientFactory.consumeMessageDirectly(msg, requestHeader.getConsumerGroup(), requestHeader.getBrokerName()); if (null != result) { response.setCode(ResponseCode.SUCCESS); response.setBody(result.encode()); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("The Consumer Group <%s> not exist in this consumer", requestHeader.getConsumerGroup())); } return response; } private RemotingCommand receiveReplyMessage(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); long receiveTime = System.currentTimeMillis(); ReplyMessageRequestHeader requestHeader = (ReplyMessageRequestHeader) request.decodeCommandCustomHeader(ReplyMessageRequestHeader.class); try { MessageExt msg = new MessageExt(); msg.setTopic(requestHeader.getTopic()); msg.setQueueId(requestHeader.getQueueId()); msg.setStoreTimestamp(requestHeader.getStoreTimestamp()); if (requestHeader.getBornHost() != null) { msg.setBornHost(NetworkUtil.string2SocketAddress(requestHeader.getBornHost())); } if (requestHeader.getStoreHost() != null) { msg.setStoreHost(NetworkUtil.string2SocketAddress(requestHeader.getStoreHost())); } byte[] body = request.getBody(); int sysFlag = requestHeader.getSysFlag(); if ((sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { try { Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(sysFlag)); body = compressor.decompress(body); } catch (IOException e) { logger.warn("err when uncompress constant", e); } } msg.setBody(body); msg.setFlag(requestHeader.getFlag()); MessageAccessor.setProperties(msg, MessageDecoder.string2messageProperties(requestHeader.getProperties())); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REPLY_MESSAGE_ARRIVE_TIME, String.valueOf(receiveTime)); msg.setBornTimestamp(requestHeader.getBornTimestamp()); msg.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes()); logger.debug("receive reply message :{}", msg); processReplyMessage(msg); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } catch (Exception e) { logger.warn("unknown err when receiveReplyMsg", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("process reply message fail"); } return response; } private void processReplyMessage(MessageExt replyMsg) { final String correlationId = replyMsg.getUserProperty(MessageConst.PROPERTY_CORRELATION_ID); final RequestResponseFuture requestResponseFuture = RequestFutureHolder.getInstance().getRequestFutureTable().get(correlationId); if (requestResponseFuture != null) { requestResponseFuture.putResponseMessage(replyMsg); RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId); if (requestResponseFuture.getRequestCallback() != null) { requestResponseFuture.getRequestCallback().onSuccess(replyMsg); } } else { String bornHost = replyMsg.getBornHostString(); logger.warn("receive reply message, but not matched any request, CorrelationId: {} , reply from host: {}", correlationId, bornHost); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/CommunicationMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; public enum CommunicationMode { SYNC, ASYNC, ONEWAY, } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/FindBrokerResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; public class FindBrokerResult { private final String brokerAddr; private final boolean slave; private final int brokerVersion; public FindBrokerResult(String brokerAddr, boolean slave) { this.brokerAddr = brokerAddr; this.slave = slave; this.brokerVersion = 0; } public FindBrokerResult(String brokerAddr, boolean slave, int brokerVersion) { this.brokerAddr = brokerAddr; this.slave = slave; this.brokerVersion = brokerVersion; } public String getBrokerAddr() { return brokerAddr; } public boolean isSlave() { return slave; } public int getBrokerVersion() { return brokerVersion; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageId; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class MQAdminImpl { private static final Logger log = LoggerFactory.getLogger(MQAdminImpl.class); private final MQClientInstance mQClientFactory; private long timeoutMillis = 6000; public MQAdminImpl(MQClientInstance mQClientFactory) { this.mQClientFactory = mQClientFactory; } public long getTimeoutMillis() { return timeoutMillis; } public void setTimeoutMillis(long timeoutMillis) { this.timeoutMillis = timeoutMillis; } public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { createTopic(key, newTopic, queueNum, 0, null); } public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException { try { Validators.checkTopic(newTopic); Validators.isSystemTopic(newTopic); TopicRouteData topicRouteData = this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(key, timeoutMillis); List brokerDataList = topicRouteData.getBrokerDatas(); if (brokerDataList != null && !brokerDataList.isEmpty()) { Collections.sort(brokerDataList); boolean createOKAtLeastOnce = false; MQClientException exception = null; StringBuilder orderTopicString = new StringBuilder(); for (BrokerData brokerData : brokerDataList) { String addr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID); if (addr != null) { TopicConfig topicConfig = new TopicConfig(newTopic); topicConfig.setReadQueueNums(queueNum); topicConfig.setWriteQueueNums(queueNum); topicConfig.setTopicSysFlag(topicSysFlag); topicConfig.setAttributes(attributes); boolean createOK = false; for (int i = 0; i < 5; i++) { try { this.mQClientFactory.getMQClientAPIImpl().createTopic(addr, key, topicConfig, timeoutMillis); createOK = true; createOKAtLeastOnce = true; break; } catch (Exception e) { if (4 == i) { exception = new MQClientException("create topic to broker exception", e); } } } if (createOK) { orderTopicString.append(brokerData.getBrokerName()); orderTopicString.append(":"); orderTopicString.append(queueNum); orderTopicString.append(";"); } } } if (exception != null && !createOKAtLeastOnce) { throw exception; } } else { throw new MQClientException("Not found broker, maybe key is wrong", null); } } catch (Exception e) { throw new MQClientException("create new topic failed", e); } } public List fetchPublishMessageQueues(String topic) throws MQClientException { try { TopicRouteData topicRouteData = this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(topic, timeoutMillis); if (topicRouteData != null) { TopicPublishInfo topicPublishInfo = MQClientInstance.topicRouteData2TopicPublishInfo(topic, topicRouteData); if (topicPublishInfo != null && topicPublishInfo.ok()) { return parsePublishMessageQueues(topicPublishInfo.getMessageQueueList()); } } } catch (Exception e) { throw new MQClientException("Can not find Message Queue for this topic, " + topic, e); } throw new MQClientException("Unknown why, Can not find Message Queue for this topic, " + topic, null); } public List parsePublishMessageQueues(List messageQueueList) { List resultQueues = new ArrayList<>(); for (MessageQueue queue : messageQueueList) { String userTopic = NamespaceUtil.withoutNamespace(queue.getTopic(), this.mQClientFactory.getClientConfig().getNamespace()); resultQueues.add(new MessageQueue(userTopic, queue.getBrokerName(), queue.getQueueId())); } return resultQueues; } public Set fetchSubscribeMessageQueues(String topic) throws MQClientException { try { TopicRouteData topicRouteData = this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(topic, timeoutMillis); if (topicRouteData != null) { Set mqList = MQClientInstance.topicRouteData2TopicSubscribeInfo(topic, topicRouteData); if (!mqList.isEmpty()) { return mqList; } else { throw new MQClientException("Can not find Message Queue for this topic, " + topic + " Namesrv return empty", null); } } } catch (Exception e) { throw new MQClientException( "Can not find Message Queue for this topic, " + topic + FAQUrl.suggestTodo(FAQUrl.MQLIST_NOT_EXIST), e); } throw new MQClientException("Unknown why, Can not find Message Queue for this topic, " + topic, null); } public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { // default return lower boundary offset when there are more than one offsets. return searchOffset(mq, timestamp, BoundaryType.LOWER); } public long searchOffset(MessageQueue mq, long timestamp, BoundaryType boundaryType) throws MQClientException { String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { return this.mQClientFactory.getMQClientAPIImpl().searchOffset(brokerAddr, mq, timestamp, boundaryType, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } public long maxOffset(MessageQueue mq) throws MQClientException { String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { return this.mQClientFactory.getMQClientAPIImpl().getMaxOffset(brokerAddr, mq, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } public long minOffset(MessageQueue mq) throws MQClientException { String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { return this.mQClientFactory.getMQClientAPIImpl().getMinOffset(brokerAddr, mq, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { return this.mQClientFactory.getMQClientAPIImpl().getEarliestMsgStoretime(brokerAddr, mq, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { MessageId messageId; try { messageId = MessageDecoder.decodeMessageId(msgId); } catch (Exception e) { throw new MQClientException(ResponseCode.NO_MESSAGE, "query message by id finished, but no message."); } return this.mQClientFactory.getMQClientAPIImpl().viewMessage(NetworkUtil.socketAddress2String(messageId.getAddress()), topic, messageId.getOffset(), timeoutMillis); } public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return queryMessage(null, topic, key, maxNum, begin, end, false, MessageConst.INDEX_KEY_TYPE, null); } public QueryResult queryMessageByUniqKey(String topic, String uniqKey, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return queryMessage(null, topic, uniqKey, maxNum, begin, end, true, MessageConst.INDEX_UNIQUE_TYPE, null); } public QueryResult queryMessageByUniqKey(String clusterName, String topic, String uniqKey, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return queryMessage(clusterName, topic, uniqKey, maxNum, begin, end, true, MessageConst.INDEX_UNIQUE_TYPE, null); } public MessageExt queryMessageByUniqKey(String topic, String uniqKey) throws InterruptedException, MQClientException { return queryMessageByUniqKey(topic, uniqKey, System.currentTimeMillis() - 3L * 24 * 60L * 60L * 1000L, Long.MAX_VALUE); } public MessageExt queryMessageByUniqKey(String clusterName, String topic, String uniqKey) throws InterruptedException, MQClientException { return queryMessageByUniqKey(clusterName, topic, uniqKey, System.currentTimeMillis() - 3L * 24 * 60L * 60L * 1000L, Long.MAX_VALUE); } public MessageExt queryMessageByUniqKey(String topic, String uniqKey, long begin, long end) throws InterruptedException, MQClientException { return queryMessageByUniqKey(null, topic, uniqKey, begin, end); } public MessageExt queryMessageByUniqKey(String clusterName, String topic, String uniqKey, long begin, long end) throws InterruptedException, MQClientException { QueryResult qr = this.queryMessage(clusterName, topic, uniqKey, 32, begin, end, true, MessageConst.INDEX_UNIQUE_TYPE, null); if (qr != null && qr.getMessageList() != null && qr.getMessageList().size() > 0) { return qr.getMessageList().get(0); } else { return null; } } public QueryResult queryMessage(String clusterName, String topic, String key, int maxNum, long begin, long end, boolean isUniqKey) throws MQClientException, InterruptedException { return queryMessage(clusterName, topic, key, maxNum, begin, end, isUniqKey, null, null); } public QueryResult queryMessage(String clusterName, String topic, String key, int maxNum, long begin, long end, boolean isUniqKey, String indexType, String lastKey) throws MQClientException, InterruptedException { boolean isLmq = MixAll.isLmq(topic); String routeTopic = topic; // if topic is lmq ,then use clusterName as lmq parent topic // Use clusterName or lmq parent topic to get topic route for lmq or rmq_sys_wheel_timer if (!StringUtils.isEmpty(topic) && (isLmq || topic.equals(TopicValidator.SYSTEM_TOPIC_PREFIX + "wheel_timer")) && !StringUtils.isEmpty(clusterName)) { routeTopic = clusterName; } TopicRouteData topicRouteData = this.mQClientFactory.getAnExistTopicRouteData(routeTopic); if (null == topicRouteData) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(routeTopic); topicRouteData = this.mQClientFactory.getAnExistTopicRouteData(routeTopic); } if (topicRouteData != null) { List brokerAddrs = new LinkedList<>(); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { if (!isLmq && clusterName != null && !clusterName.isEmpty() && !clusterName.equals(brokerData.getCluster())) { continue; } String addr = brokerData.selectBrokerAddr(); if (addr != null) { brokerAddrs.add(addr); } } if (!brokerAddrs.isEmpty()) { final CountDownLatch countDownLatch = new CountDownLatch(brokerAddrs.size()); final List queryResultList = new LinkedList<>(); final ReadWriteLock lock = new ReentrantReadWriteLock(false); for (String addr : brokerAddrs) { try { QueryMessageRequestHeader requestHeader = new QueryMessageRequestHeader(); if (isLmq) { requestHeader.setTopic(clusterName); } else { requestHeader.setTopic(topic); } requestHeader.setKey(key); requestHeader.setMaxNum(maxNum); requestHeader.setBeginTimestamp(begin); requestHeader.setEndTimestamp(end); requestHeader.setIndexType(indexType); requestHeader.setLastKey(lastKey); this.mQClientFactory.getMQClientAPIImpl().queryMessage(addr, requestHeader, timeoutMillis * 3, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { switch (response.getCode()) { case ResponseCode.SUCCESS: { QueryMessageResponseHeader responseHeader = null; try { responseHeader = (QueryMessageResponseHeader) response .decodeCommandCustomHeader(QueryMessageResponseHeader.class); } catch (RemotingCommandException e) { log.error("decodeCommandCustomHeader exception", e); return; } List wrappers = MessageDecoder.decodes(ByteBuffer.wrap(response.getBody()), true); QueryResult qr = new QueryResult(responseHeader.getIndexLastUpdateTimestamp(), wrappers); try { lock.writeLock().lock(); queryResultList.add(qr); } finally { lock.writeLock().unlock(); } break; } default: log.warn("getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); break; } } finally { countDownLatch.countDown(); } } @Override public void operationFail(Throwable throwable) { log.error("queryMessage error, requestHeader={}", requestHeader); countDownLatch.countDown(); } }, isUniqKey); } catch (Exception e) { log.warn("queryMessage exception", e); } } boolean ok = countDownLatch.await(timeoutMillis * 4, TimeUnit.MILLISECONDS); if (!ok) { log.warn("queryMessage, maybe some broker failed"); } long indexLastUpdateTimestamp = 0; List messageList = new LinkedList<>(); for (QueryResult qr : queryResultList) { if (qr.getIndexLastUpdateTimestamp() > indexLastUpdateTimestamp) { indexLastUpdateTimestamp = qr.getIndexLastUpdateTimestamp(); } for (MessageExt msgExt : qr.getMessageList()) { if (isUniqKey) { if (msgExt.getMsgId().equals(key)) { messageList.add(msgExt); } else { log.warn("queryMessage by uniqKey, find message key not matched, maybe hash duplicate {}", msgExt.toString()); } } else if (!StringUtils.isEmpty(indexType) && MessageConst.INDEX_KEY_TYPE.equals(indexType)) { String keys = msgExt.getKeys(); String msgTopic = msgExt.getTopic(); if (keys != null) { boolean matched = false; String[] keyArray = keys.split(MessageConst.KEY_SEPARATOR); for (String k : keyArray) { // both topic and key must be equal at the same time if (Objects.equals(key, k) && (isLmq || Objects.equals(topic, msgTopic))) { matched = true; break; } } if (matched) { messageList.add(msgExt); } else { log.warn("queryMessage, find message key not matched, maybe hash duplicate {}", msgExt.toString()); } } } else if (!StringUtils.isEmpty(indexType) && MessageConst.INDEX_TAG_TYPE.equals(indexType)) { String tags = msgExt.getTags(); String msgTopic = msgExt.getTopic(); boolean matched = false; if (tags != null) { if (Objects.equals(key, tags) && (isLmq || Objects.equals(topic, msgTopic))) { matched = true; } } if (matched) { messageList.add(msgExt); } else { log.warn("queryMessage, find message key not matched, maybe hash duplicate {}", msgExt.toString()); } } } } //If namespace not null , reset Topic without namespace. if (null != this.mQClientFactory.getClientConfig().getNamespace()) { for (MessageExt messageExt : messageList) { messageExt.setTopic(NamespaceUtil.withoutNamespace(messageExt.getTopic(), this.mQClientFactory.getClientConfig().getNamespace())); } } if (!messageList.isEmpty()) { return new QueryResult(indexLastUpdateTimestamp, messageList); } else { throw new MQClientException(ResponseCode.NO_MESSAGE, "query message by key finished, but no message."); } } } throw new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "The topic[" + topic + "] not matched route info"); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import com.alibaba.fastjson2.JSON; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.OffsetNotFoundException; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.rpchook.NamespaceRpcHook; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.CheckRocksdbCqWriteResult; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ObjectCreator; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.AttributeParser; import org.apache.rocketmq.common.constant.FIleReadaheadMode; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.namesrv.DefaultTopAddressing; import org.apache.rocketmq.common.namesrv.NameServerUpdateCallback; import org.apache.rocketmq.common.namesrv.TopAddressing; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.common.HeartbeatV2Result; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.AclInfo; import org.apache.rocketmq.remoting.protocol.body.BatchAck; import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo; import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData; import org.apache.rocketmq.remoting.protocol.body.CheckClientRequestBody; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatsList; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.CreateTopicListRequestBody; import org.apache.rocketmq.remoting.protocol.body.EpochEntryCache; import org.apache.rocketmq.remoting.protocol.body.GetBrokerLiteInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetConsumerStatusBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteClientInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteGroupInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetLiteTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GetParentTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.remoting.protocol.body.KVTable; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.LockBatchResponseBody; import org.apache.rocketmq.remoting.protocol.body.ProducerConnection; import org.apache.rocketmq.remoting.protocol.body.ProducerTableInfo; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentRequestBody; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeQueueResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeTimeSpanBody; import org.apache.rocketmq.remoting.protocol.body.QueryCorrectionOffsetBody; import org.apache.rocketmq.remoting.protocol.body.QuerySubscriptionResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.body.SetMessageRequestModeRequestBody; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupList; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.AddBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.CheckRocksdbCqWriteProgressRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CloneGroupOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExportRocksDBConfigToJsonRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.header.GetAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllProducerInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllSubscriptionGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsInBrokerHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerStatusRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteClientInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteGroupInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetLiteTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetParentTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetProducerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetSubscriptionGroupConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicsByClusterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ListAclsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ListUsersRequestHeader; import org.apache.rocketmq.remoting.protocol.header.LockBatchMqRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeTimeSpanRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryCorrectionOffsetHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QuerySubscriptionByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicConsumeByWhoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicsByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.RemoveBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetMasterFlushOffsetHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.TriggerLiteDispatchRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UnlockBatchMqRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateAclRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateUserRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewBrokerStatsDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVListByNamespaceRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.PutKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.subscription.GroupForbidden; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.rpchook.DynamicalExtFieldRPCHook; import org.apache.rocketmq.remoting.rpchook.StreamTypeRPCHook; import static org.apache.rocketmq.common.message.MessageConst.TIMER_ENGINE_TYPE; import static org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode.SUCCESS; public class MQClientAPIImpl implements NameServerUpdateCallback, StartAndShutdown { private final static Logger log = LoggerFactory.getLogger(MQClientAPIImpl.class); private static boolean sendSmartMsg = Boolean.parseBoolean(System.getProperty("org.apache.rocketmq.client.sendSmartMsg", "true")); static { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); } private final RemotingClient remotingClient; private final TopAddressing topAddressing; private final ClientRemotingProcessor clientRemotingProcessor; private String nameSrvAddr = null; private ClientConfig clientConfig; public MQClientAPIImpl( final NettyClientConfig nettyClientConfig, final ClientRemotingProcessor clientRemotingProcessor, final RPCHook rpcHook, final ClientConfig clientConfig ) { this(nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig, null); } public MQClientAPIImpl( final NettyClientConfig nettyClientConfig, final ClientRemotingProcessor clientRemotingProcessor, final RPCHook rpcHook, final ClientConfig clientConfig, final ChannelEventListener channelEventListener ) { this( nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig, channelEventListener, null ); } public MQClientAPIImpl(final NettyClientConfig nettyClientConfig, final ClientRemotingProcessor clientRemotingProcessor, RPCHook rpcHook, final ClientConfig clientConfig, final ChannelEventListener channelEventListener, final ObjectCreator remotingClientCreator) { this.clientConfig = clientConfig; topAddressing = new DefaultTopAddressing(MixAll.getWSAddr(), clientConfig.getUnitName()); topAddressing.registerChangeCallBack(this); this.remotingClient = remotingClientCreator != null ? remotingClientCreator.create(nettyClientConfig, channelEventListener) : new NettyRemotingClient(nettyClientConfig, channelEventListener); this.clientRemotingProcessor = clientRemotingProcessor; this.remotingClient.registerRPCHook(new NamespaceRpcHook(clientConfig)); // Inject stream rpc hook first to make reserve field signature if (clientConfig.isEnableStreamRequestType()) { this.remotingClient.registerRPCHook(new StreamTypeRPCHook()); } this.remotingClient.registerRPCHook(rpcHook); this.remotingClient.registerRPCHook(new DynamicalExtFieldRPCHook()); this.remotingClient.registerProcessor(RequestCode.CHECK_TRANSACTION_STATE, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.NOTIFY_UNSUBSCRIBE_LITE, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.RESET_CONSUMER_CLIENT_OFFSET, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.GET_CONSUMER_RUNNING_INFO, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.CONSUME_MESSAGE_DIRECTLY, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT, this.clientRemotingProcessor, null); } public List getNameServerAddressList() { return this.remotingClient.getNameServerAddressList(); } public RemotingClient getRemotingClient() { return remotingClient; } public String fetchNameServerAddr() { try { String addrs = this.topAddressing.fetchNSAddr(); if (!UtilAll.isBlank(addrs)) { if (!addrs.equals(this.nameSrvAddr)) { log.info("name server address changed, old=" + this.nameSrvAddr + ", new=" + addrs); this.updateNameServerAddressList(addrs); this.nameSrvAddr = addrs; return nameSrvAddr; } } } catch (Exception e) { log.error("fetchNameServerAddr Exception", e); } return nameSrvAddr; } @Override public String onNameServerAddressChange(String namesrvAddress) { if (namesrvAddress != null) { if (!namesrvAddress.equals(this.nameSrvAddr)) { log.info("name server address changed, old=" + this.nameSrvAddr + ", new=" + namesrvAddress); this.updateNameServerAddressList(namesrvAddress); this.nameSrvAddr = namesrvAddress; return nameSrvAddr; } } return nameSrvAddr; } public void updateNameServerAddressList(final String addrs) { String[] addrArray = addrs.split(";"); List list = Arrays.asList(addrArray); this.remotingClient.updateNameServerAddressList(list); } public void start() { this.remotingClient.start(); } public void shutdown() { this.remotingClient.shutdown(); } public Set queryAssignment(final String addr, final String topic, final String consumerGroup, final String clientId, final String strategyName, final MessageModel messageModel, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { QueryAssignmentRequestBody requestBody = new QueryAssignmentRequestBody(); requestBody.setTopic(topic); requestBody.setConsumerGroup(consumerGroup); requestBody.setClientId(clientId); requestBody.setMessageModel(messageModel); requestBody.setStrategyName(strategyName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_ASSIGNMENT, null); request.setBody(requestBody.encode()); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { QueryAssignmentResponseBody queryAssignmentResponseBody = QueryAssignmentResponseBody.decode(response.getBody(), QueryAssignmentResponseBody.class); return queryAssignmentResponseBody.getMessageQueueAssignments(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void createSubscriptionGroup(final String addr, final SubscriptionGroupConfig config, final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null); byte[] body = RemotingSerializable.encode(config); request.setBody(body); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void createSubscriptionGroupList(final String address, final List configs, final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP_LIST, null); SubscriptionGroupList requestBody = new SubscriptionGroupList(configs); request.setBody(requestBody.encode()); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), address), request, timeoutMillis); assert response != null; if (response.getCode() == ResponseCode.SUCCESS) { return; } throw new MQClientException(response.getCode(), response.getRemark()); } public void createTopic(final String addr, final String defaultTopic, final TopicConfig topicConfig, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { Validators.checkTopicConfig(topicConfig); CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader(); requestHeader.setTopic(topicConfig.getTopicName()); requestHeader.setDefaultTopic(defaultTopic); requestHeader.setReadQueueNums(topicConfig.getReadQueueNums()); requestHeader.setWriteQueueNums(topicConfig.getWriteQueueNums()); requestHeader.setPerm(topicConfig.getPerm()); requestHeader.setTopicFilterType(topicConfig.getTopicFilterType().name()); requestHeader.setTopicSysFlag(topicConfig.getTopicSysFlag()); requestHeader.setOrder(topicConfig.isOrder()); requestHeader.setAttributes(AttributeParser.parseToString(topicConfig.getAttributes())); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void createTopicList(final String address, final List topicConfigList, final long timeoutMillis) throws InterruptedException, RemotingException, MQClientException { CreateTopicListRequestHeader requestHeader = new CreateTopicListRequestHeader(); CreateTopicListRequestBody requestBody = new CreateTopicListRequestBody(topicConfigList); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC_LIST, requestHeader); request.setBody(requestBody.encode()); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), address), request, timeoutMillis); assert response != null; if (response.getCode() == ResponseCode.SUCCESS) { return; } throw new MQClientException(response.getCode(), response.getRemark()); } public SendResult sendMessage( final String addr, final String brokerName, final Message msg, final SendMessageRequestHeader requestHeader, final long timeoutMillis, final CommunicationMode communicationMode, final SendMessageContext context, final DefaultMQProducerImpl producer ) throws RemotingException, MQBrokerException, InterruptedException { return sendMessage(addr, brokerName, msg, requestHeader, timeoutMillis, communicationMode, null, null, null, 0, context, producer); } public SendResult sendMessage( final String addr, final String brokerName, final Message msg, final SendMessageRequestHeader requestHeader, final long timeoutMillis, final CommunicationMode communicationMode, final SendCallback sendCallback, final TopicPublishInfo topicPublishInfo, final MQClientInstance instance, final int retryTimesWhenSendFailed, final SendMessageContext context, final DefaultMQProducerImpl producer ) throws RemotingException, MQBrokerException, InterruptedException { long beginStartTime = System.currentTimeMillis(); RemotingCommand request = null; String msgType = msg.getProperty(MessageConst.PROPERTY_MESSAGE_TYPE); boolean isReply = msgType != null && msgType.equals(MixAll.REPLY_MESSAGE_FLAG); if (isReply) { if (sendSmartMsg) { SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader); request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE_V2, requestHeaderV2); } else { request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE, requestHeader); } } else { if (sendSmartMsg || msg instanceof MessageBatch) { SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader); request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2); } else { request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader); } } request.setBody(msg.getBody()); switch (communicationMode) { case ONEWAY: this.remotingClient.invokeOneway(addr, request, timeoutMillis); return null; case ASYNC: final AtomicInteger times = new AtomicInteger(); long costTimeAsync = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTimeAsync) { throw new RemotingTooMuchRequestException("sendMessage call timeout"); } this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance, retryTimesWhenSendFailed, times, context, producer); return null; case SYNC: long costTimeSync = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTimeSync) { throw new RemotingTooMuchRequestException("sendMessage call timeout"); } return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request); default: assert false; break; } return null; } private SendResult sendMessageSync( final String addr, final String brokerName, final Message msg, final long timeoutMillis, final RemotingCommand request ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; return this.processSendResponse(brokerName, msg, response, addr); } void execRpcHooksAfterRequest(ResponseFuture responseFuture) { if (this.remotingClient instanceof NettyRemotingClient) { NettyRemotingClient remotingClient = (NettyRemotingClient) this.remotingClient; RemotingCommand response = responseFuture.getResponseCommand(); remotingClient.doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(responseFuture.getChannel()), responseFuture.getRequestCommand(), response); } } private void sendMessageAsync( final String addr, final String brokerName, final Message msg, final long timeoutMillis, final RemotingCommand request, final SendCallback sendCallback, final TopicPublishInfo topicPublishInfo, final MQClientInstance instance, final int retryTimesWhenSendFailed, final AtomicInteger times, final SendMessageContext context, final DefaultMQProducerImpl producer ) { final long beginStartTime = System.currentTimeMillis(); try { this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { long cost = System.currentTimeMillis() - beginStartTime; if (null == sendCallback) { try { SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response, addr); if (context != null && sendResult != null) { context.setSendResult(sendResult); context.getProducer().executeSendMessageHookAfter(context); } } catch (Throwable e) { } producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, false, true); return; } try { SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response, addr); assert sendResult != null; if (context != null) { context.setSendResult(sendResult); context.getProducer().executeSendMessageHookAfter(context); } try { sendCallback.onSuccess(sendResult); } catch (Throwable e) { } producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, false, true); } catch (Exception e) { producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, true, true); onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance, retryTimesWhenSendFailed, times, e, context, false, producer); } } @Override public void operationFail(Throwable throwable) { producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, true, true); long cost = System.currentTimeMillis() - beginStartTime; if (throwable instanceof RemotingSendRequestException) { MQClientException ex = new MQClientException("send request failed", throwable); onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance, retryTimesWhenSendFailed, times, ex, context, true, producer); } else if (throwable instanceof RemotingTimeoutException) { MQClientException ex = new MQClientException("wait response timeout, cost=" + cost, throwable); onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance, retryTimesWhenSendFailed, times, ex, context, true, producer); } else { MQClientException ex = new MQClientException("unknown reason", throwable); boolean needRetry = !(throwable instanceof RemotingTooMuchRequestException); onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance, retryTimesWhenSendFailed, times, ex, context, needRetry, producer); } } }); } catch (Exception ex) { long cost = System.currentTimeMillis() - beginStartTime; producer.updateFaultItem(brokerName, cost, true, false); onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance, retryTimesWhenSendFailed, times, ex, context, true, producer); } } private void onExceptionImpl(final String brokerName, final Message msg, final long timeoutMillis, final RemotingCommand request, final SendCallback sendCallback, final TopicPublishInfo topicPublishInfo, final MQClientInstance instance, final int timesTotal, final AtomicInteger curTimes, final Exception e, final SendMessageContext context, final boolean needRetry, final DefaultMQProducerImpl producer ) { int tmp = curTimes.incrementAndGet(); if (needRetry && tmp <= timesTotal && timeoutMillis > 0) { String retryBrokerName = brokerName;//by default, it will send to the same broker if (topicPublishInfo != null) { //select one message queue accordingly, in order to determine which broker to send MessageQueue mqChosen = producer.selectOneMessageQueue(topicPublishInfo, brokerName, false); retryBrokerName = instance.getBrokerNameFromMessageQueue(mqChosen); } String addr = instance.findBrokerAddressInPublish(retryBrokerName); log.warn("async send msg by retry {} times. topic={}, brokerAddr={}, brokerName={}", tmp, msg.getTopic(), addr, retryBrokerName, e); request.setOpaque(RemotingCommand.createNewRequestId()); sendMessageAsync(addr, retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, context, producer); } else { if (context != null) { context.setException(e); context.getProducer().executeSendMessageHookAfter(context); } try { sendCallback.onException(e); } catch (Exception ignored) { } } } protected SendResult processSendResponse( final String brokerName, final Message msg, final RemotingCommand response, final String addr ) throws MQBrokerException, RemotingCommandException { SendStatus sendStatus; switch (response.getCode()) { case ResponseCode.FLUSH_DISK_TIMEOUT: { sendStatus = SendStatus.FLUSH_DISK_TIMEOUT; break; } case ResponseCode.FLUSH_SLAVE_TIMEOUT: { sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT; break; } case ResponseCode.SLAVE_NOT_AVAILABLE: { sendStatus = SendStatus.SLAVE_NOT_AVAILABLE; break; } case ResponseCode.SUCCESS: { sendStatus = SendStatus.SEND_OK; break; } default: { throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class); //If namespace not null , reset Topic without namespace. String topic = msg.getTopic(); if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) { topic = NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace()); } MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId()); String uniqMsgId = MessageClientIDSetter.getUniqID(msg); if (msg instanceof MessageBatch && responseHeader.getBatchUniqId() == null) { // This means it is not an inner batch StringBuilder sb = new StringBuilder(); for (Message message : (MessageBatch) msg) { sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(message)); } uniqMsgId = sb.toString(); } SendResult sendResult = new SendResult(sendStatus, uniqMsgId, responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset()); sendResult.setTransactionId(responseHeader.getTransactionId()); sendResult.setRecallHandle(responseHeader.getRecallHandle()); String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION); if (regionId == null || regionId.isEmpty()) { regionId = MixAll.DEFAULT_TRACE_REGION_ID; } sendResult.setRegionId(regionId); String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH); sendResult.setTraceOn(!Boolean.FALSE.toString().equals(traceOn)); return sendResult; } public PullResult pullMessage( final String addr, final PullMessageRequestHeader requestHeader, final long timeoutMillis, final CommunicationMode communicationMode, final PullCallback pullCallback ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request; if (PullSysFlag.hasLitePullFlag(requestHeader.getSysFlag())) { request = RemotingCommand.createRequestCommand(RequestCode.LITE_PULL_MESSAGE, requestHeader); } else { request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); } switch (communicationMode) { case ONEWAY: assert false; return null; case ASYNC: this.pullMessageAsync(addr, request, timeoutMillis, pullCallback); return null; case SYNC: return this.pullMessageSync(addr, request, timeoutMillis); default: assert false; break; } return null; } public void popMessageAsync( final String brokerName, final String addr, final PopMessageRequestHeader requestHeader, final long timeoutMillis, final PopCallback popCallback ) throws RemotingException, InterruptedException { final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader); this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { PopResult popResult = MQClientAPIImpl.this.processPopResponse(brokerName, response, requestHeader.getTopic(), requestHeader); popCallback.onSuccess(popResult); } catch (Exception e) { popCallback.onException(e); } } @Override public void operationFail(Throwable throwable) { popCallback.onException(throwable); } }); } public void popLiteMessageAsync( final String brokerName, final String addr, final PopLiteMessageRequestHeader requestHeader, final long timeoutMillis, final PopCallback popCallback ) throws RemotingException, InterruptedException { final String bindTopic = requestHeader.getTopic(); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.POP_LITE_MESSAGE, requestHeader); this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { PopResult popResult = MQClientAPIImpl.this.processPopLiteResponse(brokerName, response, bindTopic, requestHeader); popCallback.onSuccess(popResult); } catch (Exception e) { popCallback.onException(e); } } @Override public void operationFail(Throwable throwable) { popCallback.onException(throwable); } }); } public void ackMessageAsync( final String addr, final long timeOut, final AckCallback ackCallback, final AckMessageRequestHeader requestHeader ) throws RemotingException, MQBrokerException, InterruptedException { ackMessageAsync(addr, timeOut, ackCallback, requestHeader, null); } public void ackLiteMessageAsync( final String addr, final long timeout, final AckCallback ackCallback, final AckMessageRequestHeader requestHeader ) throws RemotingException, MQBrokerException, InterruptedException { ackMessageAsync(addr, timeout, ackCallback, requestHeader, null); } public void batchAckMessageAsync( final String addr, final long timeOut, final AckCallback ackCallback, final String topic, final String consumerGroup, final List extraInfoList ) throws RemotingException, MQBrokerException, InterruptedException { String brokerName = null; Map batchAckMap = new HashMap<>(); for (String extraInfo : extraInfoList) { String[] extraInfoData = ExtraInfoUtil.split(extraInfo); if (brokerName == null) { brokerName = ExtraInfoUtil.getBrokerName(extraInfoData); } String mergeKey = ExtraInfoUtil.getRetry(extraInfoData) + "@" + ExtraInfoUtil.getQueueId(extraInfoData) + "@" + ExtraInfoUtil.getCkQueueOffset(extraInfoData) + "@" + ExtraInfoUtil.getPopTime(extraInfoData); BatchAck bAck = batchAckMap.computeIfAbsent(mergeKey, k -> { BatchAck newBatchAck = new BatchAck(); newBatchAck.setConsumerGroup(consumerGroup); newBatchAck.setTopic(topic); newBatchAck.setRetry(ExtraInfoUtil.getRetry(extraInfoData)); newBatchAck.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfoData)); newBatchAck.setQueueId(ExtraInfoUtil.getQueueId(extraInfoData)); newBatchAck.setReviveQueueId(ExtraInfoUtil.getReviveQid(extraInfoData)); newBatchAck.setPopTime(ExtraInfoUtil.getPopTime(extraInfoData)); newBatchAck.setInvisibleTime(ExtraInfoUtil.getInvisibleTime(extraInfoData)); newBatchAck.setBitSet(new BitSet()); return newBatchAck; }); bAck.getBitSet().set((int) (ExtraInfoUtil.getQueueOffset(extraInfoData) - ExtraInfoUtil.getCkQueueOffset(extraInfoData))); } BatchAckMessageRequestBody requestBody = new BatchAckMessageRequestBody(); requestBody.setBrokerName(brokerName); requestBody.setAcks(new ArrayList<>(batchAckMap.values())); batchAckMessageAsync(addr, timeOut, ackCallback, requestBody); } public void batchAckMessageAsync( final String addr, final long timeOut, final AckCallback ackCallback, final BatchAckMessageRequestBody requestBody ) throws RemotingException, MQBrokerException, InterruptedException { ackMessageAsync(addr, timeOut, ackCallback, null, requestBody); } protected void ackMessageAsync( final String addr, final long timeOut, final AckCallback ackCallback, final AckMessageRequestHeader requestHeader, final BatchAckMessageRequestBody requestBody ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request; if (requestHeader != null) { request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); } else { request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); if (requestBody != null) { request.setBody(requestBody.encode()); } } this.remotingClient.invokeAsync(addr, request, timeOut, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { AckResult ackResult = new AckResult(); if (ResponseCode.SUCCESS == response.getCode()) { ackResult.setStatus(AckStatus.OK); } else { ackResult.setStatus(AckStatus.NO_EXIST); } ackCallback.onSuccess(ackResult); } @Override public void operationFail(Throwable throwable) { ackCallback.onException(throwable); } }); } public void changeInvisibleTimeAsync(// final String brokerName, final String addr, // final ChangeInvisibleTimeRequestHeader requestHeader,// final long timeoutMillis, final AckCallback ackCallback ) throws RemotingException, MQBrokerException, InterruptedException { final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader); this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.decodeCommandCustomHeader(ChangeInvisibleTimeResponseHeader.class); AckResult ackResult = new AckResult(); if (ResponseCode.SUCCESS == response.getCode()) { ackResult.setStatus(AckStatus.OK); ackResult.setPopTime(responseHeader.getPopTime()); ackResult.setExtraInfo(ExtraInfoUtil .buildExtraInfo(requestHeader.getOffset(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), requestHeader.getTopic(), brokerName, requestHeader.getQueueId()) + MessageConst.KEY_SEPARATOR + requestHeader.getOffset()); } else { ackResult.setStatus(AckStatus.NO_EXIST); } ackCallback.onSuccess(ackResult); } catch (Exception e) { ackCallback.onException(e); } } @Override public void operationFail(Throwable throwable) { ackCallback.onException(throwable); } }); } private void pullMessageAsync( final String addr, final RemotingCommand request, final long timeoutMillis, final PullCallback pullCallback ) throws RemotingException, InterruptedException { this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr); pullCallback.onSuccess(pullResult); } catch (Exception e) { pullCallback.onException(e); } } @Override public void operationFail(Throwable throwable) { pullCallback.onException(throwable); } }); } private PullResult pullMessageSync( final String addr, final RemotingCommand request, final long timeoutMillis ) throws RemotingException, InterruptedException, MQBrokerException { RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; return this.processPullResponse(response, addr); } private PullResult processPullResponse( final RemotingCommand response, final String addr) throws MQBrokerException, RemotingCommandException { PullStatus pullStatus = PullStatus.NO_NEW_MSG; switch (response.getCode()) { case ResponseCode.SUCCESS: pullStatus = PullStatus.FOUND; break; case ResponseCode.PULL_NOT_FOUND: pullStatus = PullStatus.NO_NEW_MSG; break; case ResponseCode.PULL_RETRY_IMMEDIATELY: pullStatus = PullStatus.NO_MATCHED_MSG; break; case ResponseCode.PULL_OFFSET_MOVED: pullStatus = PullStatus.OFFSET_ILLEGAL; break; default: throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class); return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(), responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody(), responseHeader.getOffsetDelta()); } private PopResult processPopResponse(final String brokerName, final RemotingCommand response, String topic, CommandCustomHeader requestHeader) throws MQBrokerException, RemotingCommandException { PopStatus popStatus = PopStatus.NO_NEW_MSG; List msgFoundList = null; switch (response.getCode()) { case ResponseCode.SUCCESS: popStatus = PopStatus.FOUND; ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBody()); msgFoundList = MessageDecoder.decodesBatch( byteBuffer, clientConfig.isDecodeReadBody(), clientConfig.isDecodeDecompressBody(), true); break; case ResponseCode.POLLING_FULL: popStatus = PopStatus.POLLING_FULL; break; case ResponseCode.POLLING_TIMEOUT: popStatus = PopStatus.POLLING_NOT_FOUND; break; case ResponseCode.PULL_NOT_FOUND: popStatus = PopStatus.POLLING_NOT_FOUND; break; default: throw new MQBrokerException(response.getCode(), response.getRemark()); } PopResult popResult = new PopResult(popStatus, msgFoundList); PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.decodeCommandCustomHeader(PopMessageResponseHeader.class); popResult.setRestNum(responseHeader.getRestNum()); if (popStatus != PopStatus.FOUND) { return popResult; } // it is a pop command if pop time greater than 0, we should set the check point info to extraInfo field Map startOffsetInfo = null; Map> msgOffsetInfo = null; Map orderCountInfo = null; if (requestHeader instanceof PopMessageRequestHeader) { popResult.setInvisibleTime(responseHeader.getInvisibleTime()); popResult.setPopTime(responseHeader.getPopTime()); startOffsetInfo = ExtraInfoUtil.parseStartOffsetInfo(responseHeader.getStartOffsetInfo()); msgOffsetInfo = ExtraInfoUtil.parseMsgOffsetInfo(responseHeader.getMsgOffsetInfo()); orderCountInfo = ExtraInfoUtil.parseOrderCountInfo(responseHeader.getOrderCountInfo()); } Map/*msg queueOffset*/> sortMap = buildQueueOffsetSortedMap(topic, msgFoundList); Map map = new HashMap<>(5); for (MessageExt messageExt : msgFoundList) { if (requestHeader instanceof PopMessageRequestHeader) { if (startOffsetInfo == null) { // we should set the check point info to extraInfo field , if the command is popMsg // find pop ck offset String key = messageExt.getTopic() + messageExt.getQueueId(); if (!map.containsKey(messageExt.getTopic() + messageExt.getQueueId())) { map.put(key, ExtraInfoUtil.buildExtraInfo(messageExt.getQueueOffset(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), messageExt.getTopic(), brokerName, messageExt.getQueueId())); } messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, map.get(key) + MessageConst.KEY_SEPARATOR + messageExt.getQueueOffset()); } else { if (messageExt.getProperty(MessageConst.PROPERTY_POP_CK) == null) { final String queueIdKey; final String queueOffsetKey; final int index; final Long msgQueueOffset; if (MixAll.isLmq(topic) && messageExt.getReconsumeTimes() == 0 && StringUtils.isNotEmpty( messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH))) { // process LMQ String[] queues = messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH) .split(MixAll.LMQ_DISPATCH_SEPARATOR); String[] queueOffsets = messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET) .split(MixAll.LMQ_DISPATCH_SEPARATOR); long offset = Long.parseLong(queueOffsets[ArrayUtils.indexOf(queues, topic)]); // LMQ topic has only 1 queue, which queue id is 0 queueIdKey = ExtraInfoUtil.getStartOffsetInfoMapKey(topic, MixAll.LMQ_QUEUE_ID); queueOffsetKey = ExtraInfoUtil.getQueueOffsetMapKey(topic, MixAll.LMQ_QUEUE_ID, offset); index = sortMap.get(queueIdKey).indexOf(offset); msgQueueOffset = msgOffsetInfo.get(queueIdKey).get(index); if (msgQueueOffset != offset) { log.warn("Queue offset[{}] of msg is strange, not equal to the stored in msg, {}", msgQueueOffset, messageExt); } messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, ExtraInfoUtil.buildExtraInfo(startOffsetInfo.get(queueIdKey), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), topic, brokerName, 0, msgQueueOffset) ); } else { queueIdKey = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getQueueId()); queueOffsetKey = ExtraInfoUtil.getQueueOffsetMapKey(messageExt.getTopic(), messageExt.getQueueId(), messageExt.getQueueOffset()); index = sortMap.get(queueIdKey).indexOf(messageExt.getQueueOffset()); msgQueueOffset = msgOffsetInfo.get(queueIdKey).get(index); if (msgQueueOffset != messageExt.getQueueOffset()) { log.warn("Queue offset[{}] of msg is strange, not equal to the stored in msg, {}", msgQueueOffset, messageExt); } messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, ExtraInfoUtil.buildExtraInfo(startOffsetInfo.get(queueIdKey), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), messageExt.getTopic(), brokerName, messageExt.getQueueId(), msgQueueOffset) ); } if (((PopMessageRequestHeader) requestHeader).isOrder() && orderCountInfo != null) { Integer count = orderCountInfo.get(queueOffsetKey); if (count == null) { count = orderCountInfo.get(queueIdKey); } if (count != null && count > 0) { messageExt.setReconsumeTimes(count); } } } } messageExt.getProperties().computeIfAbsent( MessageConst.PROPERTY_FIRST_POP_TIME, k -> String.valueOf(responseHeader.getPopTime())); } messageExt.setBrokerName(brokerName); messageExt.setTopic(NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace())); } return popResult; } private PopResult processPopLiteResponse(final String brokerName, final RemotingCommand response, String topic, CommandCustomHeader requestHeader) throws MQBrokerException, RemotingCommandException { PopStatus popStatus; List msgFoundList = null; switch (response.getCode()) { case ResponseCode.SUCCESS: popStatus = PopStatus.FOUND; ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBody()); msgFoundList = MessageDecoder.decodesBatch( byteBuffer, clientConfig.isDecodeReadBody(), clientConfig.isDecodeDecompressBody(), true); break; case ResponseCode.POLLING_FULL: popStatus = PopStatus.POLLING_FULL; break; case ResponseCode.POLLING_TIMEOUT: popStatus = PopStatus.POLLING_NOT_FOUND; break; case ResponseCode.PULL_NOT_FOUND: popStatus = PopStatus.POLLING_NOT_FOUND; break; default: throw new MQBrokerException(response.getCode(), response.getRemark()); } PopResult popResult = new PopResult(popStatus, msgFoundList); PopLiteMessageResponseHeader responseHeader = response.decodeCommandCustomHeader(PopLiteMessageResponseHeader.class); if (popStatus != PopStatus.FOUND) { return popResult; } List orderCountList = ExtraInfoUtil.parseLiteOrderCountInfo(responseHeader.getOrderCountInfo(), msgFoundList.size()); for (int i = 0; i < msgFoundList.size(); i++) { MessageExt messageExt = msgFoundList.get(i); String[] queues = StringUtils.split( messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH), MixAll.LMQ_DISPATCH_SEPARATOR); String[] queueOffsets = StringUtils.split( messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET), MixAll.LMQ_DISPATCH_SEPARATOR); if (null == queues || null == queueOffsets || queues.length != 1 || queues.length != queueOffsets.length) { continue; } messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, ExtraInfoUtil.buildExtraInfo(0, responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), topic, brokerName, 0, Long.parseLong(queueOffsets[0]))); messageExt.getProperties().computeIfAbsent( MessageConst.PROPERTY_FIRST_POP_TIME, k -> String.valueOf(responseHeader.getPopTime())); messageExt.setBrokerName(brokerName); messageExt.setReconsumeTimes(orderCountList != null ? orderCountList.get(i) : 0); messageExt.setQueueOffset(Long.parseLong(queueOffsets[0])); } return popResult; } /** * Build queue offset sorted map * * @param topic pop consumer topic * @param msgFoundList popped message list * @return sorted map, key is topicMark@queueId, value is sorted msg queueOffset list */ private static Map> buildQueueOffsetSortedMap(String topic, List msgFoundList) { Map/*msg queueOffset*/> sortMap = new HashMap<>(16); for (MessageExt messageExt : msgFoundList) { final String key; if (MixAll.isLmq(topic) && messageExt.getReconsumeTimes() == 0 && StringUtils.isNotEmpty(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH))) { // process LMQ String[] queues = messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH) .split(MixAll.LMQ_DISPATCH_SEPARATOR); String[] queueOffsets = messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET) .split(MixAll.LMQ_DISPATCH_SEPARATOR); // LMQ topic has only 1 queue, which queue id is 0 key = ExtraInfoUtil.getStartOffsetInfoMapKey(topic, MixAll.LMQ_QUEUE_ID); sortMap.putIfAbsent(key, new ArrayList<>(4)); sortMap.get(key).add(Long.parseLong(queueOffsets[ArrayUtils.indexOf(queues, topic)])); continue; } // Value of POP_CK is used to determine whether it is a pop retry, // cause topic could be rewritten by broker. key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getProperty(MessageConst.PROPERTY_POP_CK), messageExt.getQueueId()); if (!sortMap.containsKey(key)) { sortMap.put(key, new ArrayList<>(4)); } sortMap.get(key).add(messageExt.getQueueOffset()); } return sortMap; } public MessageExt viewMessage(final String addr, final String topic, final long phyoffset, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { ViewMessageRequestHeader requestHeader = new ViewMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setOffset(phyoffset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBody()); MessageExt messageExt = MessageDecoder.clientDecode(byteBuffer, true); //If namespace not null , reset Topic without namespace. if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) { messageExt.setTopic(NamespaceUtil.withoutNamespace(messageExt.getTopic(), this.clientConfig.getNamespace())); } return messageExt; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } @Deprecated public long searchOffset(final String addr, final String topic, final int queueId, final long timestamp, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { SearchOffsetRequestHeader requestHeader = new SearchOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setTimestamp(timestamp); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.decodeCommandCustomHeader(SearchOffsetResponseHeader.class); return responseHeader.getOffset(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public long searchOffset(final String addr, final MessageQueue messageQueue, final long timestamp, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { // default return lower boundary offset when there are more than one offsets. return searchOffset(addr, messageQueue, timestamp, BoundaryType.LOWER, timeoutMillis); } public long searchOffset(final String addr, final MessageQueue messageQueue, final long timestamp, final BoundaryType boundaryType, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { SearchOffsetRequestHeader requestHeader = new SearchOffsetRequestHeader(); requestHeader.setTopic(messageQueue.getTopic()); requestHeader.setQueueId(messageQueue.getQueueId()); requestHeader.setBrokerName(messageQueue.getBrokerName()); requestHeader.setTimestamp(timestamp); requestHeader.setBoundaryType(boundaryType); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.decodeCommandCustomHeader(SearchOffsetResponseHeader.class); return responseHeader.getOffset(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public long getMaxOffset(final String addr, final MessageQueue messageQueue, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader(); requestHeader.setTopic(messageQueue.getTopic()); requestHeader.setQueueId(messageQueue.getQueueId()); requestHeader.setBrokerName(messageQueue.getBrokerName()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MAX_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.decodeCommandCustomHeader(GetMaxOffsetResponseHeader.class); return responseHeader.getOffset(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public List getConsumerIdListByGroup( final String addr, final String consumerGroup, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQBrokerException, InterruptedException { GetConsumerListByGroupRequestHeader requestHeader = new GetConsumerListByGroupRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getBody() != null) { GetConsumerListByGroupResponseBody body = GetConsumerListByGroupResponseBody.decode(response.getBody(), GetConsumerListByGroupResponseBody.class); return body.getConsumerIdList(); } } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public long getMinOffset(final String addr, final MessageQueue messageQueue, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetMinOffsetRequestHeader requestHeader = new GetMinOffsetRequestHeader(); requestHeader.setTopic(messageQueue.getTopic()); requestHeader.setQueueId(messageQueue.getQueueId()); requestHeader.setBrokerName(messageQueue.getBrokerName()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MIN_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) response.decodeCommandCustomHeader(GetMinOffsetResponseHeader.class); return responseHeader.getOffset(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public long getEarliestMsgStoretime(final String addr, final MessageQueue mq, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetEarliestMsgStoretimeRequestHeader requestHeader = new GetEarliestMsgStoretimeRequestHeader(); requestHeader.setTopic(mq.getTopic()); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setBrokerName(mq.getBrokerName()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_EARLIEST_MSG_STORETIME, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { GetEarliestMsgStoretimeResponseHeader responseHeader = (GetEarliestMsgStoretimeResponseHeader) response.decodeCommandCustomHeader(GetEarliestMsgStoretimeResponseHeader.class); return responseHeader.getTimestamp(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public long queryConsumerOffset( final String addr, final QueryConsumerOffsetRequestHeader requestHeader, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { QueryConsumerOffsetResponseHeader responseHeader = (QueryConsumerOffsetResponseHeader) response.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class); return responseHeader.getOffset(); } case ResponseCode.QUERY_NOT_FOUND: { throw new OffsetNotFoundException(response.getCode(), response.getRemark(), addr); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void updateConsumerOffset( final String addr, final UpdateConsumerOffsetRequestHeader requestHeader, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void updateConsumerOffsetOneway( final String addr, final UpdateConsumerOffsetRequestHeader requestHeader, final long timeoutMillis ) throws RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader); this.remotingClient.invokeOneway(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); } public int sendHeartbeat( final String addr, final HeartbeatData heartbeatData, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, new HeartbeatRequestHeader()); request.setLanguage(clientConfig.getLanguage()); request.setBody(heartbeatData.encode()); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return response.getVersion(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public HeartbeatV2Result sendHeartbeatV2( final String addr, final HeartbeatData heartbeatData, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, new HeartbeatRequestHeader()); request.setLanguage(clientConfig.getLanguage()); request.setBody(heartbeatData.encode()); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getExtFields() != null) { return new HeartbeatV2Result(response.getVersion(), Boolean.parseBoolean(response.getExtFields().get(MixAll.IS_SUB_CHANGE)), Boolean.parseBoolean(response.getExtFields().get(MixAll.IS_SUPPORT_HEART_BEAT_V2))); } return new HeartbeatV2Result(response.getVersion(), false, false); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void unregisterClient( final String addr, final String clientID, final String producerGroup, final String consumerGroup, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { final UnregisterClientRequestHeader requestHeader = new UnregisterClientRequestHeader(); requestHeader.setClientID(clientID); requestHeader.setProducerGroup(producerGroup); requestHeader.setConsumerGroup(consumerGroup); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void endTransactionOneway( final String addr, final EndTransactionRequestHeader requestHeader, final String remark, final long timeoutMillis ) throws RemotingException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader); request.setRemark(remark); this.remotingClient.invokeOneway(addr, request, timeoutMillis); } public void queryMessage( final String addr, final QueryMessageRequestHeader requestHeader, final long timeoutMillis, final InvokeCallback invokeCallback, final Boolean isUniqueKey ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE, requestHeader); request.addExtField(MixAll.UNIQUE_MSG_QUERY_FLAG, isUniqueKey.toString()); this.remotingClient.invokeAsync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis, invokeCallback); } public boolean registerClient(final String addr, final HeartbeatData heartbeat, final long timeoutMillis) throws RemotingException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, new HeartbeatRequestHeader()); request.setBody(heartbeat.encode()); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); return response.getCode() == ResponseCode.SUCCESS; } public void consumerSendMessageBack( final String addr, final String brokerName, final MessageExt msg, final String consumerGroup, final int delayLevel, final long timeoutMillis, final int maxConsumeRetryTimes ) throws RemotingException, MQBrokerException, InterruptedException { ConsumerSendMsgBackRequestHeader requestHeader = new ConsumerSendMsgBackRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, requestHeader); requestHeader.setGroup(consumerGroup); requestHeader.setOriginTopic(msg.getTopic()); requestHeader.setOffset(msg.getCommitLogOffset()); requestHeader.setDelayLevel(delayLevel); requestHeader.setOriginMsgId(msg.getMsgId()); requestHeader.setMaxReconsumeTimes(maxConsumeRetryTimes); requestHeader.setBrokerName(brokerName); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public Set lockBatchMQ( final String addr, final LockBatchRequestBody requestBody, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, new LockBatchMqRequestHeader()); request.setBody(requestBody.encode()); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class); Set messageQueues = responseBody.getLockOKMQSet(); return messageQueues; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void unlockBatchMQ( final String addr, final UnlockBatchRequestBody requestBody, final long timeoutMillis, final boolean oneway ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNLOCK_BATCH_MQ, new UnlockBatchMqRequestHeader()); request.setBody(requestBody.encode()); if (oneway) { this.remotingClient.invokeOneway(addr, request, timeoutMillis); } else { RemotingCommand response = this.remotingClient .invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } public TopicStatsTable getTopicStatsInfo(final String addr, final String topic, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { GetTopicStatsInfoRequestHeader requestHeader = new GetTopicStatsInfoRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_STATS_INFO, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { TopicStatsTable topicStatsTable = TopicStatsTable.decode(response.getBody(), TopicStatsTable.class); return topicStatsTable; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public ConsumeStats getConsumeStats(final String addr, final String consumerGroup, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { return getConsumeStats(addr, consumerGroup, null, null, timeoutMillis); } public ConsumeStats getConsumeStats(final String addr, final String consumerGroup, final List topicList, final long timeoutMillis) throws RemotingSendRequestException, RemotingConnectException, RemotingTimeoutException, MQBrokerException, InterruptedException { return getConsumeStats(addr, consumerGroup, null, topicList, timeoutMillis); } public ConsumeStats getConsumeStats(final String addr, final String consumerGroup, final String topic, final long timeoutMillis) throws RemotingSendRequestException, RemotingConnectException, RemotingTimeoutException, MQBrokerException, InterruptedException { return getConsumeStats(addr, consumerGroup, topic, null, timeoutMillis); } public ConsumeStats getConsumeStats(final String addr, final String consumerGroup, final String topic, final List topicList, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { GetConsumeStatsRequestHeader requestHeader = new GetConsumeStatsRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(topic); requestHeader.updateTopicList(topicList); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUME_STATS, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { ConsumeStats consumeStats = ConsumeStats.decode(response.getBody(), ConsumeStats.class); return consumeStats; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public ProducerConnection getProducerConnectionList(final String addr, final String producerGroup, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { GetProducerConnectionListRequestHeader requestHeader = new GetProducerConnectionListRequestHeader(); requestHeader.setProducerGroup(producerGroup); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_PRODUCER_CONNECTION_LIST, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return ProducerConnection.decode(response.getBody(), ProducerConnection.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public ProducerTableInfo getAllProducerInfo(final String addr, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { GetAllProducerInfoRequestHeader requestHeader = new GetAllProducerInfoRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_PRODUCER_INFO, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return ProducerTableInfo.decode(response.getBody(), ProducerTableInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public ConsumerConnection getConsumerConnectionList(final String addr, final String consumerGroup, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { GetConsumerConnectionListRequestHeader requestHeader = new GetConsumerConnectionListRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_CONNECTION_LIST, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return ConsumerConnection.decode(response.getBody(), ConsumerConnection.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public KVTable getBrokerRuntimeInfo(final String addr, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_RUNTIME_INFO, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return KVTable.decode(response.getBody(), KVTable.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void addBroker(final String addr, final String brokerConfigPath, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { AddBrokerRequestHeader requestHeader = new AddBrokerRequestHeader(); requestHeader.setConfigPath(brokerConfigPath); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ADD_BROKER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: return; default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void removeBroker(final String addr, String clusterName, String brokerName, long brokerId, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { RemoveBrokerRequestHeader requestHeader = new RemoveBrokerRequestHeader(); requestHeader.setBrokerClusterName(clusterName); requestHeader.setBrokerName(brokerName); requestHeader.setBrokerId(brokerId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REMOVE_BROKER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: return; default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void updateBrokerConfig(final String addr, final Properties properties, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, MQClientException, UnsupportedEncodingException { Validators.checkBrokerConfig(properties); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_BROKER_CONFIG, null); String str = MixAll.properties2String(properties); if (str != null && str.length() > 0) { request.setBody(str.getBytes(MixAll.DEFAULT_CHARSET)); RemotingCommand response = this.remotingClient .invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } public Properties getBrokerConfig(final String addr, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, UnsupportedEncodingException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CONFIG, null); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return MixAll.string2Properties(new String(response.getBody(), MixAll.DEFAULT_CHARSET)); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void updateColdDataFlowCtrGroupConfig(final String addr, final Properties properties, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, UnsupportedEncodingException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_COLD_DATA_FLOW_CTR_CONFIG, null); String str = MixAll.properties2String(properties); if (str != null && str.length() > 0) { request.setBody(str.getBytes(MixAll.DEFAULT_CHARSET)); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } } public void removeColdDataFlowCtrGroupConfig(final String addr, final String consumerGroup, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, UnsupportedEncodingException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REMOVE_COLD_DATA_FLOW_CTR_CONFIG, null); if (consumerGroup != null && consumerGroup.length() > 0) { request.setBody(consumerGroup.getBytes(MixAll.DEFAULT_CHARSET)); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } } public String getColdDataFlowCtrInfo(final String addr, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, UnsupportedEncodingException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_COLD_DATA_FLOW_CTR_INFO, null); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (null != response.getBody() && response.getBody().length > 0) { return new String(response.getBody(), MixAll.DEFAULT_CHARSET); } return null; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public String setCommitLogReadAheadMode(final String addr, final String mode, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SET_COMMITLOG_READ_MODE, null); HashMap extFields = new HashMap<>(); extFields.put(FIleReadaheadMode.READ_AHEAD_MODE, mode); request.setExtFields(extFields); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (null != response.getRemark() && response.getRemark().length() > 0) { return response.getRemark(); } return null; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public ClusterInfo getBrokerClusterInfo( final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CLUSTER_INFO, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return ClusterInfo.decode(response.getBody(), ClusterInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public TopicRouteData getDefaultTopicRouteInfoFromNameServer(final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { return getTopicRouteInfoFromNameServer(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC, timeoutMillis, false); } public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { return getTopicRouteInfoFromNameServer(topic, timeoutMillis, true); } public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis, boolean allowTopicNotExist) throws MQClientException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.TOPIC_NOT_EXIST: { if (allowTopicNotExist) { log.warn("get Topic [{}] RouteInfoFromNameServer is not exist value", topic); } break; } case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { return TopicRouteData.decode(body, TopicRouteData.class); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public TopicList getTopicListFromNameServer(final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { return TopicList.decode(body, TopicList.class); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public int wipeWritePermOfBroker(final String namesrvAddr, String brokerName, final long timeoutMillis) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException { WipeWritePermOfBrokerRequestHeader requestHeader = new WipeWritePermOfBrokerRequestHeader(); requestHeader.setBrokerName(brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.WIPE_WRITE_PERM_OF_BROKER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { WipeWritePermOfBrokerResponseHeader responseHeader = (WipeWritePermOfBrokerResponseHeader) response.decodeCommandCustomHeader(WipeWritePermOfBrokerResponseHeader.class); return responseHeader.getWipeTopicCount(); } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public int addWritePermOfBroker(final String nameSrvAddr, String brokerName, final long timeoutMillis) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException { AddWritePermOfBrokerRequestHeader requestHeader = new AddWritePermOfBrokerRequestHeader(); requestHeader.setBrokerName(brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ADD_WRITE_PERM_OF_BROKER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(nameSrvAddr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { AddWritePermOfBrokerResponseHeader responseHeader = (AddWritePermOfBrokerResponseHeader) response.decodeCommandCustomHeader(AddWritePermOfBrokerResponseHeader.class); return responseHeader.getAddTopicCount(); } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void deleteTopicInBroker(final String addr, final String topic, final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException { DeleteTopicRequestHeader requestHeader = new DeleteTopicRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_TOPIC_IN_BROKER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void deleteTopicInNameServer(final String addr, final String topic, final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException { DeleteTopicFromNamesrvRequestHeader requestHeader = new DeleteTopicFromNamesrvRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_TOPIC_IN_NAMESRV, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void deleteTopicInNameServer(final String addr, final String clusterName, final String topic, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { DeleteTopicFromNamesrvRequestHeader requestHeader = new DeleteTopicFromNamesrvRequestHeader(); requestHeader.setTopic(topic); requestHeader.setClusterName(clusterName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_TOPIC_IN_NAMESRV, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void deleteSubscriptionGroup(final String addr, final String groupName, final boolean removeOffset, final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException { DeleteSubscriptionGroupRequestHeader requestHeader = new DeleteSubscriptionGroupRequestHeader(); requestHeader.setGroupName(groupName); requestHeader.setCleanOffset(removeOffset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_SUBSCRIPTIONGROUP, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public String getKVConfigValue(final String namespace, final String key, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { GetKVConfigRequestHeader requestHeader = new GetKVConfigRequestHeader(); requestHeader.setNamespace(namespace); requestHeader.setKey(key); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_KV_CONFIG, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { GetKVConfigResponseHeader responseHeader = (GetKVConfigResponseHeader) response.decodeCommandCustomHeader(GetKVConfigResponseHeader.class); return responseHeader.getValue(); } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void putKVConfigValue(final String namespace, final String key, final String value, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { PutKVConfigRequestHeader requestHeader = new PutKVConfigRequestHeader(); requestHeader.setNamespace(namespace); requestHeader.setKey(key); requestHeader.setValue(value); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PUT_KV_CONFIG, requestHeader); List nameServerAddressList = this.remotingClient.getNameServerAddressList(); if (nameServerAddressList != null) { RemotingCommand errResponse = null; for (String namesrvAddr : nameServerAddressList) { RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { break; } default: errResponse = response; } } if (errResponse != null) { throw new MQClientException(errResponse.getCode(), errResponse.getRemark()); } } } public void deleteKVConfigValue(final String namespace, final String key, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { DeleteKVConfigRequestHeader requestHeader = new DeleteKVConfigRequestHeader(); requestHeader.setNamespace(namespace); requestHeader.setKey(key); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_KV_CONFIG, requestHeader); List nameServerAddressList = this.remotingClient.getNameServerAddressList(); if (nameServerAddressList != null) { RemotingCommand errResponse = null; for (String namesrvAddr : nameServerAddressList) { RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { break; } default: errResponse = response; } } if (errResponse != null) { throw new MQClientException(errResponse.getCode(), errResponse.getRemark()); } } } public KVTable getKVListByNamespace(final String namespace, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { GetKVListByNamespaceRequestHeader requestHeader = new GetKVListByNamespaceRequestHeader(); requestHeader.setNamespace(namespace); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_KVLIST_BY_NAMESPACE, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return KVTable.decode(response.getBody(), KVTable.class); } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public Map invokeBrokerToResetOffset(final String addr, final String topic, final String group, final long timestamp, final boolean isForce, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { return invokeBrokerToResetOffset(addr, topic, group, timestamp, isForce, timeoutMillis, false); } public Map invokeBrokerToResetOffset(final String addr, final String topic, final String group, final long timestamp, int queueId, Long offset, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); requestHeader.setQueueId(queueId); requestHeader.setTimestamp(timestamp); requestHeader.setOffset(offset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); RemotingCommand response = remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { if (null != response.getBody()) { return ResetOffsetBody.decode(response.getBody(), ResetOffsetBody.class).getOffsetTable(); } break; } case ResponseCode.TOPIC_NOT_EXIST: case ResponseCode.SUBSCRIPTION_NOT_EXIST: case ResponseCode.SYSTEM_ERROR: log.warn("Invoke broker to reset offset error code={}, remark={}", response.getCode(), response.getRemark()); break; default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public Map invokeBrokerToResetOffset(final String addr, final String topic, final String group, final long timestamp, final boolean isForce, final long timeoutMillis, boolean isC) throws RemotingException, MQClientException, InterruptedException { ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); requestHeader.setTimestamp(timestamp); requestHeader.setForce(isForce); // offset is -1 means offset is null requestHeader.setOffset(-1L); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); if (isC) { request.setLanguage(LanguageCode.CPP); } RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getBody() != null) { ResetOffsetBody body = ResetOffsetBody.decode(response.getBody(), ResetOffsetBody.class); return body.getOffsetTable(); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public Map> invokeBrokerToGetConsumerStatus(final String addr, final String topic, final String group, final String clientAddr, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { GetConsumerStatusRequestHeader requestHeader = new GetConsumerStatusRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); requestHeader.setClientAddr(clientAddr); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_GET_CONSUMER_STATUS, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getBody() != null) { GetConsumerStatusBody body = GetConsumerStatusBody.decode(response.getBody(), GetConsumerStatusBody.class); return body.getConsumerTable(); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public GroupList queryTopicConsumeByWho(final String addr, final String topic, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { QueryTopicConsumeByWhoRequestHeader requestHeader = new QueryTopicConsumeByWhoRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_TOPIC_CONSUME_BY_WHO, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { GroupList groupList = GroupList.decode(response.getBody(), GroupList.class); return groupList; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public TopicList queryTopicsByConsumer(final String addr, final String group, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { QueryTopicsByConsumerRequestHeader requestHeader = new QueryTopicsByConsumerRequestHeader(); requestHeader.setGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_TOPICS_BY_CONSUMER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { TopicList topicList = TopicList.decode(response.getBody(), TopicList.class); return topicList; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public SubscriptionData querySubscriptionByConsumer(final String addr, final String group, final String topic, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { QuerySubscriptionByConsumerRequestHeader requestHeader = new QuerySubscriptionByConsumerRequestHeader(); requestHeader.setGroup(group); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_SUBSCRIPTION_BY_CONSUMER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { QuerySubscriptionResponseBody subscriptionResponseBody = QuerySubscriptionResponseBody.decode(response.getBody(), QuerySubscriptionResponseBody.class); return subscriptionResponseBody.getSubscriptionData(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public List queryConsumeTimeSpan(final String addr, final String topic, final String group, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { QueryConsumeTimeSpanRequestHeader requestHeader = new QueryConsumeTimeSpanRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUME_TIME_SPAN, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { QueryConsumeTimeSpanBody consumeTimeSpanBody = GroupList.decode(response.getBody(), QueryConsumeTimeSpanBody.class); return consumeTimeSpanBody.getConsumeTimeSpanSet(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public TopicList getTopicsByCluster(final String cluster, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { GetTopicsByClusterRequestHeader requestHeader = new GetTopicsByClusterRequestHeader(); requestHeader.setCluster(cluster); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPICS_BY_CLUSTER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicList topicList = TopicList.decode(body, TopicList.class); return topicList; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public TopicList getSystemTopicList( final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicList topicList = TopicList.decode(response.getBody(), TopicList.class); if (topicList.getTopicList() != null && !topicList.getTopicList().isEmpty() && !UtilAll.isBlank(topicList.getBrokerAddr())) { TopicList tmp = getSystemTopicListFromBroker(topicList.getBrokerAddr(), timeoutMillis); if (tmp.getTopicList() != null && !tmp.getTopicList().isEmpty()) { topicList.getTopicList().addAll(tmp.getTopicList()); } } return topicList; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public TopicList getSystemTopicListFromBroker(final String addr, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_BROKER, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicList topicList = TopicList.decode(body, TopicList.class); return topicList; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public boolean cleanExpiredConsumeQueue(final String addr, long timeoutMillis) throws MQClientException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CLEAN_EXPIRED_CONSUMEQUEUE, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return true; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public boolean deleteExpiredCommitLog(final String addr, long timeoutMillis) throws MQClientException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_EXPIRED_COMMITLOG, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return true; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public boolean cleanUnusedTopicByAddr(final String addr, long timeoutMillis) throws MQClientException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CLEAN_UNUSED_TOPIC, null); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { return true; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public ConsumerRunningInfo getConsumerRunningInfo(final String addr, String consumerGroup, String clientId, boolean jstack, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { GetConsumerRunningInfoRequestHeader requestHeader = new GetConsumerRunningInfoRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setClientId(clientId); requestHeader.setJstackEnable(jstack); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_RUNNING_INFO, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { ConsumerRunningInfo info = ConsumerRunningInfo.decode(body, ConsumerRunningInfo.class); return info; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public ConsumeMessageDirectlyResult consumeMessageDirectly(final String addr, String consumerGroup, String clientId, String topic, String msgId, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { ConsumeMessageDirectlyResultRequestHeader requestHeader = new ConsumeMessageDirectlyResultRequestHeader(); requestHeader.setTopic(topic); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setClientId(clientId); requestHeader.setMsgId(msgId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONSUME_MESSAGE_DIRECTLY, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { ConsumeMessageDirectlyResult info = ConsumeMessageDirectlyResult.decode(body, ConsumeMessageDirectlyResult.class); return info; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public Map queryCorrectionOffset(final String addr, final String topic, final String group, Set filterGroup, long timeoutMillis) throws MQClientException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { QueryCorrectionOffsetHeader requestHeader = new QueryCorrectionOffsetHeader(); requestHeader.setCompareGroup(group); requestHeader.setTopic(topic); if (filterGroup != null) { StringBuilder sb = new StringBuilder(); String splitor = ""; for (String s : filterGroup) { sb.append(splitor).append(s); splitor = ","; } requestHeader.setFilterGroups(sb.toString()); } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CORRECTION_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getBody() != null) { QueryCorrectionOffsetBody body = QueryCorrectionOffsetBody.decode(response.getBody(), QueryCorrectionOffsetBody.class); return body.getCorrectionOffsets(); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public TopicList getUnitTopicList(final boolean containRetry, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_UNIT_TOPIC_LIST, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicList topicList = TopicList.decode(response.getBody(), TopicList.class); if (!containRetry) { Iterator it = topicList.getTopicList().iterator(); while (it.hasNext()) { String topic = it.next(); if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { it.remove(); } } } return topicList; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public TopicList getHasUnitSubTopicList(final boolean containRetry, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicList topicList = TopicList.decode(response.getBody(), TopicList.class); if (!containRetry) { Iterator it = topicList.getTopicList().iterator(); while (it.hasNext()) { String topic = it.next(); if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { it.remove(); } } } return topicList; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public TopicList getHasUnitSubUnUnitTopicList(final boolean containRetry, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST, null); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { TopicList topicList = TopicList.decode(response.getBody(), TopicList.class); if (!containRetry) { Iterator it = topicList.getTopicList().iterator(); while (it.hasNext()) { String topic = it.next(); if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { it.remove(); } } } return topicList; } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public void cloneGroupOffset(final String addr, final String srcGroup, final String destGroup, final String topic, final boolean isOffline, final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { CloneGroupOffsetRequestHeader requestHeader = new CloneGroupOffsetRequestHeader(); requestHeader.setSrcGroup(srcGroup); requestHeader.setDestGroup(destGroup); requestHeader.setTopic(topic); requestHeader.setOffline(isOffline); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CLONE_GROUP_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public BrokerStatsData viewBrokerStatsData(String brokerAddr, String statsName, String statsKey, long timeoutMillis) throws MQClientException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { ViewBrokerStatsDataRequestHeader requestHeader = new ViewBrokerStatsDataRequestHeader(); requestHeader.setStatsName(statsName); requestHeader.setStatsKey(statsKey); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.VIEW_BROKER_STATS_DATA, requestHeader); RemotingCommand response = this.remotingClient .invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { return BrokerStatsData.decode(body, BrokerStatsData.class); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public Set getClusterList(String topic, long timeoutMillis) { return Collections.EMPTY_SET; } public ConsumeStatsList fetchConsumeStatsInBroker(String brokerAddr, boolean isOrder, long timeoutMillis) throws MQClientException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { GetConsumeStatsInBrokerHeader requestHeader = new GetConsumeStatsInBrokerHeader(); requestHeader.setIsOrder(isOrder); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CONSUME_STATS, requestHeader); RemotingCommand response = this.remotingClient .invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { return ConsumeStatsList.decode(body, ConsumeStatsList.class); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } public SubscriptionGroupWrapper getAllSubscriptionGroup(final String brokerAddr, long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException, RemotingCommandException { DataVersion currentDataVersion = null; int groupSeq = 0; ConcurrentMap subscriptionGroupTable = new ConcurrentHashMap<>(); ConcurrentMap> forbiddenTable = new ConcurrentHashMap<>(); long beginTime = System.nanoTime(); while (true) { long leftTime = timeoutMillis - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginTime); if (leftTime < 0) { throw new RemotingTimeoutException("invokeSync call timeout"); } GetAllSubscriptionGroupRequestHeader requestHeader = new GetAllSubscriptionGroupRequestHeader(); requestHeader.setGroupSeq(groupSeq); requestHeader.setMaxGroupNum(clientConfig.getMaxPageSizeInGetMetadata()); requestHeader.setDataVersion(Optional.ofNullable(currentDataVersion) .map(DataVersion::toJson).orElse(StringUtils.EMPTY)); log.info("getAllSubscriptionGroup from seq {}, max {}, dataVersion {}", groupSeq, requestHeader.getMaxGroupNum(), requestHeader.getDataVersion()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, requestHeader); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, leftTime); assert response != null; if (response.getCode() == SUCCESS) { SubscriptionGroupWrapper subscriptionGroupWrapper = SubscriptionGroupWrapper.decode(response.getBody(), SubscriptionGroupWrapper.class); subscriptionGroupTable.putAll(subscriptionGroupWrapper.getSubscriptionGroupTable()); forbiddenTable.putAll(subscriptionGroupWrapper.getForbiddenTable()); DataVersion newDataVersion = subscriptionGroupWrapper.getDataVersion(); if (currentDataVersion == null) { // fill dataVersion before break the loop to compatible with old version server currentDataVersion = newDataVersion; } groupSeq += subscriptionGroupWrapper.getSubscriptionGroupTable().size(); GetAllSubscriptionGroupResponseHeader responseHeader = response.decodeCommandCustomHeader(GetAllSubscriptionGroupResponseHeader.class); Integer totalGroupNum = Optional.ofNullable(responseHeader) .map(GetAllSubscriptionGroupResponseHeader::getTotalGroupNum).orElse(null); if (Objects.isNull(totalGroupNum)) { // the server side don't support totalGroupNum, all data is returned break; } if (!Objects.equals(currentDataVersion, newDataVersion)) { log.error("dataVersion changed, currentDataVersion: {}, newDataVersion: {}", currentDataVersion, newDataVersion); currentDataVersion = newDataVersion; groupSeq = 0; subscriptionGroupTable.clear(); forbiddenTable.clear(); continue; } if (groupSeq >= totalGroupNum - 1) { log.info("get all subscription group config, totalGroupNum: {}", totalGroupNum); break; } } else { throw new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr); } } SubscriptionGroupWrapper allSubscriptionGroup = new SubscriptionGroupWrapper(); allSubscriptionGroup.setSubscriptionGroupTable(subscriptionGroupTable); allSubscriptionGroup.setForbiddenTable(forbiddenTable); allSubscriptionGroup.setDataVersion(currentDataVersion); return allSubscriptionGroup; } public SubscriptionGroupConfig getSubscriptionGroupConfig(final String brokerAddr, String group, long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { GetSubscriptionGroupConfigRequestHeader header = new GetSubscriptionGroupConfigRequestHeader(); header.setGroup(group); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_SUBSCRIPTIONGROUP_CONFIG, header); RemotingCommand response = this.remotingClient .invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), SubscriptionGroupConfig.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr); } public TopicConfigSerializeWrapper getAllTopicConfig(final String addr, long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, RemotingCommandException { DataVersion currentDataVersion = null; int topicSeq = 0; ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); long beginTime = System.nanoTime(); while (true) { long leftTime = timeoutMillis - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginTime); if (leftTime <= 0) { throw new RemotingTimeoutException("invokeSync call timeout"); } GetAllTopicConfigRequestHeader requestHeader = new GetAllTopicConfigRequestHeader(); requestHeader.setTopicSeq(topicSeq); requestHeader.setMaxTopicNum(clientConfig.getMaxPageSizeInGetMetadata()); requestHeader.setDataVersion(Optional.ofNullable(currentDataVersion). map(DataVersion::toJson).orElse(StringUtils.EMPTY)); log.info("getAllTopicConfig from seq {}, max {}, dataVersion {}", topicSeq, requestHeader.getMaxTopicNum(), requestHeader.getDataVersion()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, requestHeader); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, leftTime); assert response != null; if (response.getCode() == SUCCESS) { TopicConfigSerializeWrapper topicConfigSerializeWrapper = TopicConfigSerializeWrapper.decode(response.getBody(), TopicConfigSerializeWrapper.class); topicConfigTable.putAll(topicConfigSerializeWrapper.getTopicConfigTable()); topicSeq += topicConfigSerializeWrapper.getTopicConfigTable().size(); DataVersion newDataVersion = topicConfigSerializeWrapper.getDataVersion(); if (currentDataVersion == null) { // fill dataVersion before break the loop to compatible with old version server currentDataVersion = newDataVersion; } GetAllTopicConfigResponseHeader responseHeader = response.decodeCommandCustomHeader(GetAllTopicConfigResponseHeader.class); Integer totalTopicNum = Optional.ofNullable(responseHeader) .map(GetAllTopicConfigResponseHeader::getTotalTopicNum).orElse(null); if (Objects.isNull(totalTopicNum)) { // compatible with old version server // the server side don't support totalTopicNum, all data is returned break; } if (!Objects.equals(currentDataVersion, newDataVersion)) { log.error("dataVersion changed, currentDataVersion: {}, newDataVersion: {}", currentDataVersion, newDataVersion); currentDataVersion = newDataVersion; topicSeq = 0; topicConfigTable.clear(); continue; } if (topicSeq >= totalTopicNum - 1) { log.info("get all topic config, totalTopicNum: {}", totalTopicNum); break; } } else { throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(currentDataVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigTable); return topicConfigSerializeWrapper; } public void updateNameServerConfig(final Properties properties, final List nameServers, long timeoutMillis) throws UnsupportedEncodingException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { String str = MixAll.properties2String(properties); if (str == null || str.length() < 1) { return; } List invokeNameServers = (nameServers == null || nameServers.isEmpty()) ? this.remotingClient.getNameServerAddressList() : nameServers; if (invokeNameServers == null || invokeNameServers.isEmpty()) { return; } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_NAMESRV_CONFIG, null); request.setBody(str.getBytes(MixAll.DEFAULT_CHARSET)); RemotingCommand errResponse = null; for (String nameServer : invokeNameServers) { RemotingCommand response = this.remotingClient.invokeSync(nameServer, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { break; } default: errResponse = response; } } if (errResponse != null) { throw new MQClientException(errResponse.getCode(), errResponse.getRemark()); } } public Map getNameServerConfig(final List nameServers, long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException { List invokeNameServers = (nameServers == null || nameServers.isEmpty()) ? this.remotingClient.getNameServerAddressList() : nameServers; if (invokeNameServers == null || invokeNameServers.isEmpty()) { return null; } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_NAMESRV_CONFIG, null); Map configMap = new HashMap<>(4); for (String nameServer : invokeNameServers) { RemotingCommand response = this.remotingClient.invokeSync(nameServer, request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS == response.getCode()) { configMap.put(nameServer, MixAll.string2Properties(new String(response.getBody(), MixAll.DEFAULT_CHARSET))); } else { throw new MQClientException(response.getCode(), response.getRemark()); } } return configMap; } public QueryConsumeQueueResponseBody queryConsumeQueue(final String brokerAddr, final String topic, final int queueId, final long index, final int count, final String consumerGroup, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { QueryConsumeQueueRequestHeader requestHeader = new QueryConsumeQueueRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setIndex(index); requestHeader.setCount(count); requestHeader.setConsumerGroup(consumerGroup); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUME_QUEUE, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS == response.getCode()) { return QueryConsumeQueueResponseBody.decode(response.getBody(), QueryConsumeQueueResponseBody.class); } throw new MQClientException(response.getCode(), response.getRemark()); } public CheckRocksdbCqWriteResult checkRocksdbCqWriteProgress(final String brokerAddr, final String topic, final long checkStoreTime, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { CheckRocksdbCqWriteProgressRequestHeader header = new CheckRocksdbCqWriteProgressRequestHeader(); header.setTopic(topic); header.setCheckStoreTime(checkStoreTime); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHECK_ROCKSDB_CQ_WRITE_PROGRESS, header); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS == response.getCode()) { return JSON.parseObject(response.getBody(), CheckRocksdbCqWriteResult.class); } throw new MQClientException(response.getCode(), response.getRemark()); } public void exportRocksDBConfigToJson(final String brokerAddr, final List configType, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { ExportRocksDBConfigToJsonRequestHeader header = new ExportRocksDBConfigToJsonRequestHeader(); header.updateConfigType(configType); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.EXPORT_ROCKSDB_CONFIG_TO_JSON, header); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS != response.getCode()) { throw new MQClientException(response.getCode(), response.getRemark()); } } public void checkClientInBroker(final String brokerAddr, final String consumerGroup, final String clientId, final SubscriptionData subscriptionData, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHECK_CLIENT_CONFIG, null); CheckClientRequestBody requestBody = new CheckClientRequestBody(); requestBody.setClientId(clientId); requestBody.setGroup(consumerGroup); requestBody.setSubscriptionData(subscriptionData); request.setBody(requestBody.encode()); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS != response.getCode()) { throw new MQClientException(response.getCode(), response.getRemark()); } } public boolean resumeCheckHalfMessage(final String addr, String topic, String msgId, final long timeoutMillis) throws RemotingException, InterruptedException { ResumeCheckHalfMessageRequestHeader requestHeader = new ResumeCheckHalfMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setMsgId(msgId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESUME_CHECK_HALF_MESSAGE, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return true; } default: log.error("Failed to resume half message check logic. Remark={}", response.getRemark()); return false; } } public void setMessageRequestMode(final String brokerAddr, final String topic, final String consumerGroup, final MessageRequestMode mode, final int popShareQueueNum, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SET_MESSAGE_REQUEST_MODE, null); SetMessageRequestModeRequestBody requestBody = new SetMessageRequestModeRequestBody(); requestBody.setTopic(topic); requestBody.setConsumerGroup(consumerGroup); requestBody.setMode(mode); requestBody.setPopShareQueueNum(popShareQueueNum); request.setBody(requestBody.encode()); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS != response.getCode()) { throw new MQClientException(response.getCode(), response.getRemark()); } } public TopicConfigAndQueueMapping getTopicConfig(final String brokerAddr, String topic, long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { GetTopicConfigRequestHeader header = new GetTopicConfigRequestHeader(); header.setTopic(topic); header.setLo(true); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_CONFIG, header); RemotingCommand response = this.remotingClient .invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), TopicConfigAndQueueMapping.class); } //should check the exist case ResponseCode.TOPIC_NOT_EXIST: { //should return null? break; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void createStaticTopic(final String addr, final String defaultTopic, final TopicConfig topicConfig, final TopicQueueMappingDetail topicQueueMappingDetail, boolean force, final long timeoutMillis) throws RemotingException, InterruptedException, MQBrokerException { CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader(); requestHeader.setTopic(topicConfig.getTopicName()); requestHeader.setDefaultTopic(defaultTopic); requestHeader.setReadQueueNums(topicConfig.getReadQueueNums()); requestHeader.setWriteQueueNums(topicConfig.getWriteQueueNums()); requestHeader.setPerm(topicConfig.getPerm()); requestHeader.setTopicFilterType(topicConfig.getTopicFilterType().name()); requestHeader.setTopicSysFlag(topicConfig.getTopicSysFlag()); requestHeader.setOrder(topicConfig.isOrder()); requestHeader.setForce(force); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_STATIC_TOPIC, requestHeader); request.setBody(topicQueueMappingDetail.encode()); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } /** * @param addr * @param requestHeader * @param timeoutMillis * @throws InterruptedException * @throws RemotingTimeoutException * @throws RemotingSendRequestException * @throws RemotingConnectException * @throws MQBrokerException */ public GroupForbidden updateAndGetGroupForbidden(String addr, UpdateGroupForbiddenRequestHeader requestHeader, long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_GET_GROUP_FORBIDDEN, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), GroupForbidden.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void resetMasterFlushOffset(final String brokerAddr, final long masterFlushOffset) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { ResetMasterFlushOffsetHeader requestHeader = new ResetMasterFlushOffsetHeader(); requestHeader.setMasterFlushOffset(masterFlushOffset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESET_MASTER_FLUSH_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr); } public HARuntimeInfo getBrokerHAStatus(final String brokerAddr, final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_HA_STATUS, null); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return HARuntimeInfo.decode(response.getBody(), HARuntimeInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public GetMetaDataResponseHeader getControllerMetaData( final String controllerAddress) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, RemotingCommandException, MQBrokerException { final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_METADATA_INFO, null); final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000); assert response != null; if (response.getCode() == SUCCESS) { return (GetMetaDataResponseHeader) response.decodeCommandCustomHeader(GetMetaDataResponseHeader.class); } throw new MQBrokerException(response.getCode(), response.getRemark()); } public BrokerReplicasInfo getInSyncStateData(final String controllerAddress, final List brokers) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, RemotingCommandException { // Get controller leader address. final GetMetaDataResponseHeader controllerMetaData = getControllerMetaData(controllerAddress); assert controllerMetaData != null; assert controllerMetaData.getControllerLeaderAddress() != null; final String leaderAddress = controllerMetaData.getControllerLeaderAddress(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_SYNC_STATE_DATA, null); final byte[] body = RemotingSerializable.encode(brokers); request.setBody(body); RemotingCommand response = this.remotingClient.invokeSync(leaderAddress, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), BrokerReplicasInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public EpochEntryCache getBrokerEpochCache( String brokerAddr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_EPOCH_CACHE, null); final RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), EpochEntryCache.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public Map getControllerConfig(final List controllerServers, final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException { List invokeControllerServers = (controllerServers == null || controllerServers.isEmpty()) ? this.remotingClient.getNameServerAddressList() : controllerServers; if (invokeControllerServers == null || invokeControllerServers.isEmpty()) { return null; } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONTROLLER_CONFIG, null); Map configMap = new HashMap<>(4); for (String controller : invokeControllerServers) { RemotingCommand response = this.remotingClient.invokeSync(controller, request, timeoutMillis); assert response != null; if (ResponseCode.SUCCESS == response.getCode()) { configMap.put(controller, MixAll.string2Properties(new String(response.getBody(), MixAll.DEFAULT_CHARSET))); } else { throw new MQClientException(response.getCode(), response.getRemark()); } } return configMap; } public void updateControllerConfig(final Properties properties, final List controllers, final long timeoutMillis) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException { String str = MixAll.properties2String(properties); if (str.length() < 1 || controllers == null || controllers.isEmpty()) { return; } RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONTROLLER_CONFIG, null); request.setBody(str.getBytes(MixAll.DEFAULT_CHARSET)); RemotingCommand errResponse = null; for (String controller : controllers) { RemotingCommand response = this.remotingClient.invokeSync(controller, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { break; } default: errResponse = response; } } if (errResponse != null) { throw new MQClientException(errResponse.getCode(), errResponse.getRemark()); } } public Pair electMaster(String controllerAddr, String clusterName, String brokerName, Long brokerId) throws MQBrokerException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, RemotingCommandException { //get controller leader address final GetMetaDataResponseHeader controllerMetaData = this.getControllerMetaData(controllerAddr); assert controllerMetaData != null; assert controllerMetaData.getControllerLeaderAddress() != null; final String leaderAddress = controllerMetaData.getControllerLeaderAddress(); ElectMasterRequestHeader electRequestHeader = ElectMasterRequestHeader.ofAdminTrigger(clusterName, brokerName, brokerId); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ELECT_MASTER, electRequestHeader); final RemotingCommand response = this.remotingClient.invokeSync(leaderAddress, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { BrokerMemberGroup brokerMemberGroup = RemotingSerializable.decode(response.getBody(), BrokerMemberGroup.class); ElectMasterResponseHeader responseHeader = (ElectMasterResponseHeader) response.decodeCommandCustomHeader(ElectMasterResponseHeader.class); return new Pair<>(responseHeader, brokerMemberGroup); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void cleanControllerBrokerData(String controllerAddr, String clusterName, String brokerName, String brokerControllerIdsToClean, boolean isCleanLivingBroker) throws RemotingException, InterruptedException, MQBrokerException { //get controller leader address final GetMetaDataResponseHeader controllerMetaData = this.getControllerMetaData(controllerAddr); assert controllerMetaData != null; assert controllerMetaData.getControllerLeaderAddress() != null; final String leaderAddress = controllerMetaData.getControllerLeaderAddress(); CleanControllerBrokerDataRequestHeader cleanHeader = new CleanControllerBrokerDataRequestHeader(clusterName, brokerName, brokerControllerIdsToClean, isCleanLivingBroker); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CLEAN_BROKER_DATA, cleanHeader); final RemotingCommand response = this.remotingClient.invokeSync(leaderAddress, request, 3000); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void createUser(String addr, UserInfo userInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { CreateUserRequestHeader requestHeader = new CreateUserRequestHeader(userInfo.getUsername()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, requestHeader); request.setBody(RemotingSerializable.encode(userInfo)); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void updateUser(String addr, UserInfo userInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { UpdateUserRequestHeader requestHeader = new UpdateUserRequestHeader(userInfo.getUsername()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_USER, requestHeader); request.setBody(RemotingSerializable.encode(userInfo)); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void deleteUser(String addr, String username, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { DeleteUserRequestHeader requestHeader = new DeleteUserRequestHeader(username); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_USER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public UserInfo getUser(String addr, String username, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { GetUserRequestHeader requestHeader = new GetUserRequestHeader(username); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), UserInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public List listUser(String addr, String filter, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { ListUsersRequestHeader requestHeader = new ListUsersRequestHeader(filter); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_USER, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decodeList(response.getBody(), UserInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void createAcl(String addr, AclInfo aclInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { CreateAclRequestHeader requestHeader = new CreateAclRequestHeader(aclInfo.getSubject()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_ACL, requestHeader); request.setBody(RemotingSerializable.encode(aclInfo)); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void updateAcl(String addr, AclInfo aclInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { UpdateAclRequestHeader requestHeader = new UpdateAclRequestHeader(aclInfo.getSubject()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_ACL, requestHeader); request.setBody(RemotingSerializable.encode(aclInfo)); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void deleteAcl(String addr, String subject, String resource, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { DeleteAclRequestHeader requestHeader = new DeleteAclRequestHeader(subject, resource); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_ACL, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return; } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public AclInfo getAcl(String addr, String subject, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { GetAclRequestHeader requestHeader = new GetAclRequestHeader(subject); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_ACL, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decode(response.getBody(), AclInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public List listAcl(String addr, String subjectFilter, String resourceFilter, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { ListAclsRequestHeader requestHeader = new ListAclsRequestHeader(subjectFilter, resourceFilter); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_ACL, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis); assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { return RemotingSerializable.decodeList(response.getBody(), AclInfo.class); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public String recallMessage( final String addr, RecallMessageRequestHeader requestHeader, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); switch (response.getCode()) { case ResponseCode.SUCCESS: { RecallMessageResponseHeader responseHeader = response.decodeCommandCustomHeader(RecallMessageResponseHeader.class); return responseHeader.getMsgId(); } default: break; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } public void recallMessageAsync( final String addr, final RecallMessageRequestHeader requestHeader, final long timeoutMillis, final InvokeCallback invokeCallback ) throws RemotingException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, requestHeader); this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { invokeCallback.operationSucceed(response); } @Override public void operationFail(Throwable throwable) { invokeCallback.operationFail(throwable); } }); } public void exportPopRecord(String brokerAddr, long timeout) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand( RequestCode.POP_ROLLBACK, null); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, timeout); assert response != null; if (response.getCode() == SUCCESS) { return; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public void switchTimerEngine(String brokerAddr, String engineType, long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SWITCH_TIMER_ENGINE, null); request.addExtField(TIMER_ENGINE_TYPE, engineType); RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, timeoutMillis); assert response != null; if (response.getCode() == SUCCESS) { return; } throw new MQBrokerException(response.getCode(), response.getRemark()); } public GetBrokerLiteInfoResponseBody getBrokerLiteInfo(String addr, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { return invokeBrokerMethod(addr, RequestCode.GET_BROKER_LITE_INFO, null, GetBrokerLiteInfoResponseBody.class, timeoutMillis); } public GetParentTopicInfoResponseBody getParentTopicInfo(String addr, String topic, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetParentTopicInfoRequestHeader requestHeader = new GetParentTopicInfoRequestHeader(); requestHeader.setTopic(topic); return invokeBrokerMethod(addr, RequestCode.GET_PARENT_TOPIC_INFO, requestHeader, GetParentTopicInfoResponseBody.class, timeoutMillis); } public GetLiteTopicInfoResponseBody getLiteTopicInfo(String addr, String parentTopic, String liteTopic, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetLiteTopicInfoRequestHeader requestHeader = new GetLiteTopicInfoRequestHeader(); requestHeader.setParentTopic(parentTopic); requestHeader.setLiteTopic(liteTopic); return invokeBrokerMethod(addr, RequestCode.GET_LITE_TOPIC_INFO, requestHeader, GetLiteTopicInfoResponseBody.class, timeoutMillis); } public GetLiteClientInfoResponseBody getLiteClientInfo(String addr, String parentTopic, String group, String clientId, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetLiteClientInfoRequestHeader requestHeader = new GetLiteClientInfoRequestHeader(); requestHeader.setParentTopic(parentTopic); requestHeader.setGroup(group); requestHeader.setClientId(clientId); return invokeBrokerMethod(addr, RequestCode.GET_LITE_CLIENT_INFO, requestHeader, GetLiteClientInfoResponseBody.class, timeoutMillis); } public GetLiteGroupInfoResponseBody getLiteGroupInfo(String addr, String group, String liteTopic, int topK, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetLiteGroupInfoRequestHeader requestHeader = new GetLiteGroupInfoRequestHeader(); requestHeader.setGroup(group); requestHeader.setTopK(topK); requestHeader.setLiteTopic(liteTopic); return invokeBrokerMethod(addr, RequestCode.GET_LITE_GROUP_INFO, requestHeader, GetLiteGroupInfoResponseBody.class, timeoutMillis); } public void triggerLiteDispatch(String addr, String group, String clientId, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { TriggerLiteDispatchRequestHeader requestHeader = new TriggerLiteDispatchRequestHeader(); requestHeader.setGroup(group); requestHeader.setClientId(clientId); invokeBrokerMethod(addr, RequestCode.TRIGGER_LITE_DISPATCH, requestHeader, null, timeoutMillis); } private R invokeBrokerMethod( final String addr, final int requestCode, final T requestHeader, final Class responseClass, final long timeoutMillis ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, requestHeader); RemotingCommand response = this.remotingClient.invokeSync( MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis ); if (response.getCode() == SUCCESS) { if (response.getBody() != null) { return RemotingSerializable.decode(response.getBody(), responseClass); } return null; } throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/MQClientManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.producer.ProduceAccumulator; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MQClientManager { private final static Logger log = LoggerFactory.getLogger(MQClientManager.class); private static MQClientManager instance = new MQClientManager(); private AtomicInteger factoryIndexGenerator = new AtomicInteger(); private ConcurrentMap factoryTable = new ConcurrentHashMap<>(); private ConcurrentMap accumulatorTable = new ConcurrentHashMap(); private MQClientManager() { } public static MQClientManager getInstance() { return instance; } public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig) { return getOrCreateMQClientInstance(clientConfig, null); } public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) { String clientId = clientConfig.buildMQClientId(); MQClientInstance instance = this.factoryTable.get(clientId); if (null == instance) { instance = new MQClientInstance(clientConfig.cloneClientConfig(), this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook); MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance); if (prev != null) { instance = prev; log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId); } else { log.info("Created new MQClientInstance for clientId:[{}]", clientId); } } return instance; } public ProduceAccumulator getOrCreateProduceAccumulator(final ClientConfig clientConfig) { String clientId = clientConfig.buildMQClientId(); ProduceAccumulator accumulator = this.accumulatorTable.get(clientId); if (null == accumulator) { accumulator = new ProduceAccumulator(clientId); ProduceAccumulator prev = this.accumulatorTable.putIfAbsent(clientId, accumulator); if (prev != null) { accumulator = prev; log.warn("Returned Previous ProduceAccumulator for clientId:[{}]", clientId); } else { log.info("Created new ProduceAccumulator for clientId:[{}]", clientId); } } return accumulator; } public void removeClientFactory(final String clientId) { this.factoryTable.remove(clientId); } public ConcurrentMap getFactoryTable() { return factoryTable; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/admin/MqClientAdminImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.admin; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.MqClientAdmin; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeTimeSpanBody; import org.apache.rocketmq.remoting.protocol.body.QuerySubscriptionResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeTimeSpanRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QuerySubscriptionByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicConsumeByWhoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicsByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class MqClientAdminImpl implements MqClientAdmin { private final static Logger log = LoggerFactory.getLogger(MqClientAdminImpl.class); private final RemotingClient remotingClient; public MqClientAdminImpl(RemotingClient remotingClient) { this.remotingClient = remotingClient; } @Override public CompletableFuture> queryMessage(String address, boolean uniqueKeyFlag, boolean decompressBody, QueryMessageRequestHeader requestHeader, long timeoutMillis) { CompletableFuture> future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE, requestHeader); request.addExtField(MixAll.UNIQUE_MSG_QUERY_FLAG, String.valueOf(uniqueKeyFlag)); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { List wrappers = MessageDecoder.decodesBatch(ByteBuffer.wrap(response.getBody()), true, decompressBody, true); future.complete(filterMessages(wrappers, requestHeader.getTopic(), requestHeader.getKey(), uniqueKeyFlag)); } else if (response.getCode() == ResponseCode.QUERY_NOT_FOUND) { List wrappers = new ArrayList<>(); future.complete(wrappers); } else { log.warn("queryMessage getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture getTopicStatsInfo(String address, GetTopicStatsInfoRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_STATS_INFO, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { TopicStatsTable topicStatsTable = TopicStatsTable.decode(response.getBody(), TopicStatsTable.class); future.complete(topicStatsTable); } else { log.warn("getTopicStatsInfo getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture> queryConsumeTimeSpan(String address, QueryConsumeTimeSpanRequestHeader requestHeader, long timeoutMillis) { CompletableFuture> future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUME_TIME_SPAN, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { QueryConsumeTimeSpanBody consumeTimeSpanBody = GroupList.decode(response.getBody(), QueryConsumeTimeSpanBody.class); future.complete(consumeTimeSpanBody.getConsumeTimeSpanSet()); } else { log.warn("queryConsumerTimeSpan getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture updateOrCreateTopic(String address, CreateTopicRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { future.complete(null); } else { log.warn("updateOrCreateTopic getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture updateOrCreateSubscriptionGroup(String address, SubscriptionGroupConfig config, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null); byte[] body = RemotingSerializable.encode(config); request.setBody(body); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { future.complete(null); } else { log.warn("updateOrCreateSubscriptionGroup getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), config); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture deleteTopicInBroker(String address, DeleteTopicRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_TOPIC_IN_BROKER, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { future.complete(null); } else { log.warn("deleteTopicInBroker getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture deleteTopicInNameserver(String address, DeleteTopicFromNamesrvRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_TOPIC_IN_NAMESRV, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { future.complete(null); } else { log.warn("deleteTopicInNameserver getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture deleteKvConfig(String address, DeleteKVConfigRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_KV_CONFIG, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { future.complete(null); } else { log.warn("deleteKvConfig getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture deleteSubscriptionGroup(String address, DeleteSubscriptionGroupRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_SUBSCRIPTIONGROUP, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { future.complete(null); } else { log.warn("deleteSubscriptionGroup getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture> invokeBrokerToResetOffset(String address, ResetOffsetRequestHeader requestHeader, long timeoutMillis) { CompletableFuture> future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS && null != response.getBody()) { Map offsetTable = ResetOffsetBody.decode(response.getBody(), ResetOffsetBody.class).getOffsetTable(); future.complete(offsetTable); log.info("Invoke broker to reset offset success. address:{}, header:{}, offsetTable:{}", address, requestHeader, offsetTable); } else { log.warn("invokeBrokerToResetOffset getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture viewMessage(String address, ViewMessageRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBody()); MessageExt messageExt = MessageDecoder.clientDecode(byteBuffer, true); future.complete(messageExt); } else { log.warn("viewMessage getResponseCommand failed, {} {}, header={}", response.getCode(), response.getRemark(), requestHeader); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture getBrokerClusterInfo(String address, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CLUSTER_INFO, null); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ClusterInfo clusterInfo = ClusterInfo.decode(response.getBody(), ClusterInfo.class); future.complete(clusterInfo); } else { log.warn("getBrokerClusterInfo getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture getConsumerConnectionList(String address, GetConsumerConnectionListRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_CONNECTION_LIST, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ConsumerConnection consumerConnection = ConsumerConnection.decode(response.getBody(), ConsumerConnection.class); future.complete(consumerConnection); } else { log.warn("getConsumerConnectionList getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture queryTopicsByConsumer(String address, QueryTopicsByConsumerRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_TOPICS_BY_CONSUMER, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { TopicList topicList = TopicList.decode(response.getBody(), TopicList.class); future.complete(topicList); } else { log.warn("queryTopicsByConsumer getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture querySubscriptionByConsumer(String address, QuerySubscriptionByConsumerRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_SUBSCRIPTION_BY_CONSUMER, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { QuerySubscriptionResponseBody subscriptionResponseBody = QuerySubscriptionResponseBody.decode(response.getBody(), QuerySubscriptionResponseBody.class); future.complete(subscriptionResponseBody.getSubscriptionData()); } else { log.warn("querySubscriptionByConsumer getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture getConsumeStats(String address, GetConsumeStatsRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUME_STATS, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ConsumeStats consumeStats = ConsumeStats.decode(response.getBody(), ConsumeStats.class); future.complete(consumeStats); } else { log.warn("getConsumeStats getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture queryTopicConsumeByWho(String address, QueryTopicConsumeByWhoRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_TOPIC_CONSUME_BY_WHO, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { GroupList groupList = GroupList.decode(response.getBody(), GroupList.class); future.complete(groupList); } else { log.warn("queryTopicConsumeByWho getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture getConsumerRunningInfo(String address, GetConsumerRunningInfoRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_RUNNING_INFO, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ConsumerRunningInfo info = ConsumerRunningInfo.decode(response.getBody(), ConsumerRunningInfo.class); future.complete(info); } else { log.warn("getConsumerRunningInfo getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } @Override public CompletableFuture consumeMessageDirectly(String address, ConsumeMessageDirectlyResultRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONSUME_MESSAGE_DIRECTLY, requestHeader); remotingClient.invoke(address, request, timeoutMillis).thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ConsumeMessageDirectlyResult info = ConsumeMessageDirectlyResult.decode(response.getBody(), ConsumeMessageDirectlyResult.class); future.complete(info); } else { log.warn("consumeMessageDirectly getResponseCommand failed, {} {}", response.getCode(), response.getRemark()); future.completeExceptionally(new MQClientException(response.getCode(), response.getRemark())); } }); return future; } private List filterMessages(List messageFoundList, String topic, String key, boolean uniqueKeyFlag) { List matchedMessages = new ArrayList<>(); if (uniqueKeyFlag) { matchedMessages.addAll(messageFoundList.stream() .filter(msg -> topic.equals(msg.getTopic())) .filter(msg -> key.equals(msg.getMsgId())) .collect(Collectors.toList()) ); } else { matchedMessages.addAll(messageFoundList.stream() .filter(msg -> topic.equals(msg.getTopic())) .filter(msg -> { boolean matched = false; if (StringUtils.isNotBlank(msg.getKeys())) { String[] keyArray = msg.getKeys().split(MessageConst.KEY_SEPARATOR); for (String s : keyArray) { if (key.equals(s)) { matched = true; break; } } } return matched; }).collect(Collectors.toList())); } return matchedMessages; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/AssignedMessageQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.message.MessageQueue; public class AssignedMessageQueue { private final ConcurrentHashMap assignedMessageQueueState; private RebalanceImpl rebalanceImpl; public AssignedMessageQueue() { assignedMessageQueueState = new ConcurrentHashMap<>(); } public void setRebalanceImpl(RebalanceImpl rebalanceImpl) { this.rebalanceImpl = rebalanceImpl; } public boolean isPaused(MessageQueue messageQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { return messageQueueState.isPaused(); } return true; } public void pause(Collection messageQueues) { for (MessageQueue messageQueue : messageQueues) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (assignedMessageQueueState.get(messageQueue) != null) { messageQueueState.setPaused(true); } } } public void resume(Collection messageQueueCollection) { for (MessageQueue messageQueue : messageQueueCollection) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (assignedMessageQueueState.get(messageQueue) != null) { messageQueueState.setPaused(false); } } } public ProcessQueue getProcessQueue(MessageQueue messageQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { return messageQueueState.getProcessQueue(); } return null; } public long getPullOffset(MessageQueue messageQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { return messageQueueState.getPullOffset(); } return -1; } public void updatePullOffset(MessageQueue messageQueue, long offset, ProcessQueue processQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { if (messageQueueState.getProcessQueue() != processQueue) { return; } messageQueueState.setPullOffset(offset); } } public long getConsumerOffset(MessageQueue messageQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { return messageQueueState.getConsumeOffset(); } return -1; } public void updateConsumeOffset(MessageQueue messageQueue, long offset) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { messageQueueState.setConsumeOffset(offset); } } public void setSeekOffset(MessageQueue messageQueue, long offset) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { messageQueueState.setSeekOffset(offset); } } public long getSeekOffset(MessageQueue messageQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { return messageQueueState.getSeekOffset(); } return -1; } public void updateAssignedMessageQueue(String topic, Collection assigned) { synchronized (this.assignedMessageQueueState) { Iterator> it = this.assignedMessageQueueState.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); if (next.getKey().getTopic().equals(topic)) { if (!assigned.contains(next.getKey())) { next.getValue().getProcessQueue().setDropped(true); it.remove(); } } } addAssignedMessageQueue(assigned); } } public void updateAssignedMessageQueue(Collection assigned) { synchronized (this.assignedMessageQueueState) { Iterator> it = this.assignedMessageQueueState.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); if (!assigned.contains(next.getKey())) { next.getValue().getProcessQueue().setDropped(true); it.remove(); } } addAssignedMessageQueue(assigned); } } private void addAssignedMessageQueue(Collection assigned) { for (MessageQueue messageQueue : assigned) { if (!this.assignedMessageQueueState.containsKey(messageQueue)) { MessageQueueState messageQueueState; if (rebalanceImpl != null && rebalanceImpl.getProcessQueueTable().get(messageQueue) != null) { messageQueueState = new MessageQueueState(messageQueue, rebalanceImpl.getProcessQueueTable().get(messageQueue)); } else { ProcessQueue processQueue = new ProcessQueue(); messageQueueState = new MessageQueueState(messageQueue, processQueue); } this.assignedMessageQueueState.put(messageQueue, messageQueueState); } } } public void removeAssignedMessageQueue(String topic) { synchronized (this.assignedMessageQueueState) { Iterator> it = this.assignedMessageQueueState.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); if (next.getKey().getTopic().equals(topic)) { it.remove(); } } } } public Set getAssignedMessageQueues() { return this.assignedMessageQueueState.keySet(); } private class MessageQueueState { private MessageQueue messageQueue; private ProcessQueue processQueue; private volatile boolean paused = false; private volatile long pullOffset = -1; private volatile long consumeOffset = -1; private volatile long seekOffset = -1; private MessageQueueState(MessageQueue messageQueue, ProcessQueue processQueue) { this.messageQueue = messageQueue; this.processQueue = processQueue; } public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public boolean isPaused() { return paused; } public void setPaused(boolean paused) { this.paused = paused; } public long getPullOffset() { return pullOffset; } public void setPullOffset(long pullOffset) { this.pullOffset = pullOffset; } public ProcessQueue getProcessQueue() { return processQueue; } public void setProcessQueue(ProcessQueue processQueue) { this.processQueue = processQueue; } public long getConsumeOffset() { return consumeOffset; } public void setConsumeOffset(long consumeOffset) { this.consumeOffset = consumeOffset; } public long getSeekOffset() { return seekOffset; } public void setSeekOffset(long seekOffset) { this.seekOffset = seekOffset; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeReturnType; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ConsumeMessageConcurrentlyService implements ConsumeMessageService { private static final Logger log = LoggerFactory.getLogger(ConsumeMessageConcurrentlyService.class); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; private final DefaultMQPushConsumer defaultMQPushConsumer; private final MessageListenerConcurrently messageListener; private final BlockingQueue consumeRequestQueue; private final ThreadPoolExecutor consumeExecutor; private final String consumerGroup; private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService cleanExpireMsgExecutors; public ConsumeMessageConcurrentlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl, MessageListenerConcurrently messageListener) { this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl; this.messageListener = messageListener; this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer(); this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup(); this.consumeRequestQueue = new LinkedBlockingQueue<>(); String consumerGroupTag = (consumerGroup.length() > 100 ? consumerGroup.substring(0, 100) : consumerGroup) + "_"; this.consumeExecutor = new ThreadPoolExecutor( this.defaultMQPushConsumer.getConsumeThreadMin(), this.defaultMQPushConsumer.getConsumeThreadMax(), 1000 * 60, TimeUnit.MILLISECONDS, this.consumeRequestQueue, new ThreadFactoryImpl("ConsumeMessageThread_" + consumerGroupTag)); this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_" + consumerGroupTag)); this.cleanExpireMsgExecutors = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CleanExpireMsgScheduledThread_" + consumerGroupTag)); } public void start() { this.cleanExpireMsgExecutors.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { cleanExpireMsg(); } catch (Throwable e) { log.error("scheduleAtFixedRate cleanExpireMsg exception", e); } } }, this.defaultMQPushConsumer.getConsumeTimeout(), this.defaultMQPushConsumer.getConsumeTimeout(), TimeUnit.MINUTES); } public void shutdown(long awaitTerminateMillis) { this.scheduledExecutorService.shutdown(); ThreadUtils.shutdownGracefully(this.consumeExecutor, awaitTerminateMillis, TimeUnit.MILLISECONDS); this.cleanExpireMsgExecutors.shutdown(); } @Override public void updateCorePoolSize(int corePoolSize) { if (corePoolSize > 0 && corePoolSize <= Short.MAX_VALUE && corePoolSize < this.defaultMQPushConsumer.getConsumeThreadMax()) { this.consumeExecutor.setCorePoolSize(corePoolSize); } } @Override public void incCorePoolSize() { } @Override public void decCorePoolSize() { } @Override public int getCorePoolSize() { return this.consumeExecutor.getCorePoolSize(); } @Override public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) { ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); result.setOrder(false); result.setAutoCommit(true); msg.setBrokerName(brokerName); List msgs = new ArrayList<>(); msgs.add(msg); MessageQueue mq = new MessageQueue(); mq.setBrokerName(brokerName); mq.setTopic(msg.getTopic()); mq.setQueueId(msg.getQueueId()); ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(mq); this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, this.consumerGroup); final long beginTime = System.currentTimeMillis(); log.info("consumeMessageDirectly receive new message: {}", msg); try { ConsumeConcurrentlyStatus status = this.messageListener.consumeMessage(msgs, context); if (status != null) { switch (status) { case CONSUME_SUCCESS: result.setConsumeResult(CMResult.CR_SUCCESS); break; case RECONSUME_LATER: result.setConsumeResult(CMResult.CR_LATER); break; default: break; } } else { result.setConsumeResult(CMResult.CR_RETURN_NULL); } } catch (Throwable e) { result.setConsumeResult(CMResult.CR_THROW_EXCEPTION); result.setRemark(UtilAll.exceptionSimpleDesc(e)); log.warn("consumeMessageDirectly exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessageConcurrentlyService.this.consumerGroup, msgs, mq, e); } result.setSpentTimeMills(System.currentTimeMillis() - beginTime); log.info("consumeMessageDirectly Result: {}", result); return result; } @Override public void submitConsumeRequest( final List msgs, final ProcessQueue processQueue, final MessageQueue messageQueue, final boolean dispatchToConsume) { final int consumeBatchSize = this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize(); if (msgs.size() <= consumeBatchSize) { ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue); try { this.consumeExecutor.submit(consumeRequest); } catch (RejectedExecutionException e) { this.submitConsumeRequestLater(consumeRequest); } } else { for (int total = 0; total < msgs.size(); ) { List msgThis = new ArrayList<>(consumeBatchSize); for (int i = 0; i < consumeBatchSize; i++, total++) { if (total < msgs.size()) { msgThis.add(msgs.get(total)); } else { break; } } ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue); try { this.consumeExecutor.submit(consumeRequest); } catch (RejectedExecutionException e) { for (; total < msgs.size(); total++) { msgThis.add(msgs.get(total)); } this.submitConsumeRequestLater(consumeRequest); } } } } @Override public void submitPopConsumeRequest(final List msgs, final PopProcessQueue processQueue, final MessageQueue messageQueue) { throw new UnsupportedOperationException(); } private void cleanExpireMsg() { Iterator> it = this.defaultMQPushConsumerImpl.getRebalanceImpl().getProcessQueueTable().entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); ProcessQueue pq = next.getValue(); pq.cleanExpiredMsg(this.defaultMQPushConsumer); } } public void processConsumeResult( final ConsumeConcurrentlyStatus status, final ConsumeConcurrentlyContext context, final ConsumeRequest consumeRequest ) { int ackIndex = context.getAckIndex(); if (consumeRequest.getMsgs().isEmpty()) return; switch (status) { case CONSUME_SUCCESS: if (ackIndex >= consumeRequest.getMsgs().size()) { ackIndex = consumeRequest.getMsgs().size() - 1; } int ok = ackIndex + 1; int failed = consumeRequest.getMsgs().size() - ok; this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), ok); this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), failed); break; case RECONSUME_LATER: ackIndex = -1; this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), consumeRequest.getMsgs().size()); break; default: break; } switch (this.defaultMQPushConsumer.getMessageModel()) { case BROADCASTING: for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) { MessageExt msg = consumeRequest.getMsgs().get(i); log.warn("BROADCASTING, the message consume failed, drop it, {}", msg.toString()); } break; case CLUSTERING: List msgBackFailed = new ArrayList<>(consumeRequest.getMsgs().size()); for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) { MessageExt msg = consumeRequest.getMsgs().get(i); // Maybe message is expired and cleaned, just ignore it. if (!consumeRequest.getProcessQueue().containsMessage(msg)) { log.info("Message is not found in its process queue; skip send-back-procedure, topic={}, " + "brokerName={}, queueId={}, queueOffset={}", msg.getTopic(), msg.getBrokerName(), msg.getQueueId(), msg.getQueueOffset()); continue; } boolean result = this.sendMessageBack(msg, context); if (!result) { msg.setReconsumeTimes(msg.getReconsumeTimes() + 1); msgBackFailed.add(msg); } } if (!msgBackFailed.isEmpty()) { consumeRequest.getMsgs().removeAll(msgBackFailed); this.submitConsumeRequestLater(msgBackFailed, consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue()); } break; default: break; } long offset = consumeRequest.getProcessQueue().removeMessage(consumeRequest.getMsgs()); if (offset >= 0 && !consumeRequest.getProcessQueue().isDropped()) { this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), offset, true); } } public ConsumerStatsManager getConsumerStatsManager() { return this.defaultMQPushConsumerImpl.getConsumerStatsManager(); } public boolean sendMessageBack(final MessageExt msg, final ConsumeConcurrentlyContext context) { int delayLevel = context.getDelayLevelWhenNextConsume(); // Wrap topic with namespace before sending back message. msg.setTopic(this.defaultMQPushConsumer.withNamespace(msg.getTopic())); try { this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, this.defaultMQPushConsumer.queueWithNamespace(context.getMessageQueue())); return true; } catch (Exception e) { log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg, e); } return false; } private void submitConsumeRequestLater( final List msgs, final ProcessQueue processQueue, final MessageQueue messageQueue ) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { ConsumeMessageConcurrentlyService.this.submitConsumeRequest(msgs, processQueue, messageQueue, true); } }, 5000, TimeUnit.MILLISECONDS); } private void submitConsumeRequestLater(final ConsumeRequest consumeRequest ) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { ConsumeMessageConcurrentlyService.this.consumeExecutor.submit(consumeRequest); } }, 5000, TimeUnit.MILLISECONDS); } class ConsumeRequest implements Runnable { private final List msgs; private final ProcessQueue processQueue; private final MessageQueue messageQueue; public ConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue) { this.msgs = msgs; this.processQueue = processQueue; this.messageQueue = messageQueue; } public List getMsgs() { return msgs; } public ProcessQueue getProcessQueue() { return processQueue; } @Override public void run() { if (this.processQueue.isDropped()) { log.info("the message queue not be able to consume, because it's dropped. group={} {}", ConsumeMessageConcurrentlyService.this.consumerGroup, this.messageQueue); return; } MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener; ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue); ConsumeConcurrentlyStatus status = null; defaultMQPushConsumerImpl.tryResetPopRetryTopic(msgs, consumerGroup); defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup()); ConsumeMessageContext consumeMessageContext = null; if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext = new ConsumeMessageContext(); consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace()); consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup()); consumeMessageContext.setProps(new HashMap<>()); consumeMessageContext.setMq(messageQueue); consumeMessageContext.setMsgList(msgs); consumeMessageContext.setSuccess(false); ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext); } long beginTimestamp = System.currentTimeMillis(); boolean hasException = false; ConsumeReturnType returnType = ConsumeReturnType.SUCCESS; try { if (msgs != null && !msgs.isEmpty()) { for (MessageExt msg : msgs) { MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis())); } } status = listener.consumeMessage(Collections.unmodifiableList(msgs), context); } catch (Throwable e) { log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessageConcurrentlyService.this.consumerGroup, msgs, messageQueue, e); hasException = true; } long consumeRT = System.currentTimeMillis() - beginTimestamp; if (null == status) { if (hasException) { returnType = ConsumeReturnType.EXCEPTION; } else { returnType = ConsumeReturnType.RETURNNULL; } } else if (consumeRT >= defaultMQPushConsumer.getConsumeTimeout() * 60 * 1000) { returnType = ConsumeReturnType.TIME_OUT; } else if (ConsumeConcurrentlyStatus.RECONSUME_LATER == status) { returnType = ConsumeReturnType.FAILED; } else if (ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status) { returnType = ConsumeReturnType.SUCCESS; } if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name()); } if (null == status) { log.warn("consumeMessage return null, Group: {} Msgs: {} MQ: {}", ConsumeMessageConcurrentlyService.this.consumerGroup, msgs, messageQueue); status = ConsumeConcurrentlyStatus.RECONSUME_LATER; } if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext.setStatus(status.toString()); consumeMessageContext.setSuccess(ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status); consumeMessageContext.setAccessChannel(defaultMQPushConsumer.getAccessChannel()); ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext); } ConsumeMessageConcurrentlyService.this.getConsumerStatsManager() .incConsumeRT(ConsumeMessageConcurrentlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT); if (!processQueue.isDropped()) { ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this); } else { log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs); } } public MessageQueue getMessageQueue() { return messageQueue; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeReturnType; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ConsumeMessageOrderlyService implements ConsumeMessageService { private static final Logger log = LoggerFactory.getLogger(ConsumeMessageOrderlyService.class); private final static long MAX_TIME_CONSUME_CONTINUOUSLY = Long.parseLong(System.getProperty("rocketmq.client.maxTimeConsumeContinuously", "60000")); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; private final DefaultMQPushConsumer defaultMQPushConsumer; private final MessageListenerOrderly messageListener; private final BlockingQueue consumeRequestQueue; private final ThreadPoolExecutor consumeExecutor; private final String consumerGroup; private final MessageQueueLock messageQueueLock = new MessageQueueLock(); private final ScheduledExecutorService scheduledExecutorService; private volatile boolean stopped = false; public ConsumeMessageOrderlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl, MessageListenerOrderly messageListener) { this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl; this.messageListener = messageListener; this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer(); this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup(); this.consumeRequestQueue = new LinkedBlockingQueue<>(); String consumerGroupTag = (consumerGroup.length() > 100 ? consumerGroup.substring(0, 100) : consumerGroup) + "_"; this.consumeExecutor = new ThreadPoolExecutor( this.defaultMQPushConsumer.getConsumeThreadMin(), this.defaultMQPushConsumer.getConsumeThreadMax(), 1000 * 60, TimeUnit.MILLISECONDS, this.consumeRequestQueue, new ThreadFactoryImpl("ConsumeMessageThread_" + consumerGroupTag)); this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_" + consumerGroupTag)); } @Override public void start() { if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { ConsumeMessageOrderlyService.this.lockMQPeriodically(); } catch (Throwable e) { log.error("scheduleAtFixedRate lockMQPeriodically exception", e); } } }, 1000, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS); } } @Override public void shutdown(long awaitTerminateMillis) { this.stopped = true; this.scheduledExecutorService.shutdown(); ThreadUtils.shutdownGracefully(this.consumeExecutor, awaitTerminateMillis, TimeUnit.MILLISECONDS); if (MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) { this.unlockAllMQ(); } } public synchronized void unlockAllMQ() { this.defaultMQPushConsumerImpl.getRebalanceImpl().unlockAll(false); } @Override public void updateCorePoolSize(int corePoolSize) { if (corePoolSize > 0 && corePoolSize <= Short.MAX_VALUE && corePoolSize < this.defaultMQPushConsumer.getConsumeThreadMax()) { this.consumeExecutor.setCorePoolSize(corePoolSize); } } @Override public void incCorePoolSize() { } @Override public void decCorePoolSize() { } @Override public int getCorePoolSize() { return this.consumeExecutor.getCorePoolSize(); } @Override public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) { ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); result.setOrder(true); List msgs = new ArrayList<>(); msgs.add(msg); MessageQueue mq = new MessageQueue(); mq.setBrokerName(brokerName); mq.setTopic(msg.getTopic()); mq.setQueueId(msg.getQueueId()); ConsumeOrderlyContext context = new ConsumeOrderlyContext(mq); this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, this.consumerGroup); final long beginTime = System.currentTimeMillis(); log.info("consumeMessageDirectly receive new message: {}", msg); try { ConsumeOrderlyStatus status = this.messageListener.consumeMessage(msgs, context); if (status != null) { switch (status) { case COMMIT: result.setConsumeResult(CMResult.CR_COMMIT); break; case ROLLBACK: result.setConsumeResult(CMResult.CR_ROLLBACK); break; case SUCCESS: result.setConsumeResult(CMResult.CR_SUCCESS); break; case SUSPEND_CURRENT_QUEUE_A_MOMENT: result.setConsumeResult(CMResult.CR_LATER); break; default: break; } } else { result.setConsumeResult(CMResult.CR_RETURN_NULL); } } catch (Throwable e) { result.setConsumeResult(CMResult.CR_THROW_EXCEPTION); result.setRemark(UtilAll.exceptionSimpleDesc(e)); log.warn("consumeMessageDirectly exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessageOrderlyService.this.consumerGroup, msgs, mq, e); } result.setAutoCommit(context.isAutoCommit()); result.setSpentTimeMills(System.currentTimeMillis() - beginTime); log.info("consumeMessageDirectly Result: {}", result); return result; } @Override public void submitConsumeRequest( final List msgs, final ProcessQueue processQueue, final MessageQueue messageQueue, final boolean dispatchToConsume) { if (dispatchToConsume) { ConsumeRequest consumeRequest = new ConsumeRequest(processQueue, messageQueue); this.consumeExecutor.submit(consumeRequest); } } @Override public void submitPopConsumeRequest(final List msgs, final PopProcessQueue processQueue, final MessageQueue messageQueue) { throw new UnsupportedOperationException(); } public synchronized void lockMQPeriodically() { if (!this.stopped) { this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll(); } } public void tryLockLaterAndReconsume(final MessageQueue mq, final ProcessQueue processQueue, final long delayMills) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { boolean lockOK = ConsumeMessageOrderlyService.this.lockOneMQ(mq); if (lockOK) { ConsumeMessageOrderlyService.this.submitConsumeRequestLater(processQueue, mq, 10); } else { ConsumeMessageOrderlyService.this.submitConsumeRequestLater(processQueue, mq, 3000); } } }, delayMills, TimeUnit.MILLISECONDS); } public synchronized boolean lockOneMQ(final MessageQueue mq) { if (!this.stopped) { return this.defaultMQPushConsumerImpl.getRebalanceImpl().lock(mq); } return false; } private void submitConsumeRequestLater( final ProcessQueue processQueue, final MessageQueue messageQueue, final long suspendTimeMillis ) { long timeMillis = suspendTimeMillis; if (timeMillis == -1) { timeMillis = this.defaultMQPushConsumer.getSuspendCurrentQueueTimeMillis(); } if (timeMillis < 10) { timeMillis = 10; } else if (timeMillis > 30000) { timeMillis = 30000; } this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { ConsumeMessageOrderlyService.this.submitConsumeRequest(null, processQueue, messageQueue, true); } }, timeMillis, TimeUnit.MILLISECONDS); } public boolean processConsumeResult( final List msgs, final ConsumeOrderlyStatus status, final ConsumeOrderlyContext context, final ConsumeRequest consumeRequest ) { boolean continueConsume = true; long commitOffset = -1L; if (context.isAutoCommit()) { switch (status) { case COMMIT: case ROLLBACK: log.warn("the message queue consume result is illegal, we think you want to ack these message {}", consumeRequest.getMessageQueue()); case SUCCESS: commitOffset = consumeRequest.getProcessQueue().commit(); this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size()); break; case SUSPEND_CURRENT_QUEUE_A_MOMENT: this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size()); if (checkReconsumeTimes(msgs)) { consumeRequest.getProcessQueue().makeMessageToConsumeAgain(msgs); this.submitConsumeRequestLater( consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue(), context.getSuspendCurrentQueueTimeMillis()); continueConsume = false; } else { commitOffset = consumeRequest.getProcessQueue().commit(); } break; default: break; } } else { switch (status) { case SUCCESS: this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size()); break; case COMMIT: commitOffset = consumeRequest.getProcessQueue().commit(); break; case ROLLBACK: consumeRequest.getProcessQueue().rollback(); this.submitConsumeRequestLater( consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue(), context.getSuspendCurrentQueueTimeMillis()); continueConsume = false; break; case SUSPEND_CURRENT_QUEUE_A_MOMENT: this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size()); if (checkReconsumeTimes(msgs)) { consumeRequest.getProcessQueue().makeMessageToConsumeAgain(msgs); this.submitConsumeRequestLater( consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue(), context.getSuspendCurrentQueueTimeMillis()); continueConsume = false; } break; default: break; } } if (commitOffset >= 0 && !consumeRequest.getProcessQueue().isDropped()) { this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), commitOffset, false); } return continueConsume; } public ConsumerStatsManager getConsumerStatsManager() { return this.defaultMQPushConsumerImpl.getConsumerStatsManager(); } private int getMaxReconsumeTimes() { // default reconsume times: Integer.MAX_VALUE if (this.defaultMQPushConsumer.getMaxReconsumeTimes() == -1) { return Integer.MAX_VALUE; } else { return this.defaultMQPushConsumer.getMaxReconsumeTimes(); } } private boolean checkReconsumeTimes(List msgs) { boolean suspend = false; if (msgs != null && !msgs.isEmpty()) { for (MessageExt msg : msgs) { if (msg.getReconsumeTimes() >= getMaxReconsumeTimes()) { MessageAccessor.setReconsumeTime(msg, String.valueOf(msg.getReconsumeTimes())); if (!sendMessageBack(msg)) { suspend = true; msg.setReconsumeTimes(msg.getReconsumeTimes() + 1); } } else { suspend = true; msg.setReconsumeTimes(msg.getReconsumeTimes() + 1); } } } return suspend; } public boolean sendMessageBack(final MessageExt msg) { try { // max reconsume times exceeded then send to dead letter queue. Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody()); MessageAccessor.setProperties(newMsg, msg.getProperties()); String originMsgId = MessageAccessor.getOriginMessageId(msg); MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId); newMsg.setFlag(msg.getFlag()); MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic()); MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes() + 1)); MessageAccessor.setMaxReconsumeTimes(newMsg, String.valueOf(getMaxReconsumeTimes())); MessageAccessor.clearProperty(newMsg, MessageConst.PROPERTY_TRANSACTION_PREPARED); newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes()); this.defaultMQPushConsumerImpl.getmQClientFactory().getDefaultMQProducer().send(newMsg); return true; } catch (Exception e) { log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e); } return false; } public void resetNamespace(final List msgs) { for (MessageExt msg : msgs) { if (StringUtils.isNotEmpty(this.defaultMQPushConsumer.getNamespace())) { msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace())); } } } class ConsumeRequest implements Runnable { private final ProcessQueue processQueue; private final MessageQueue messageQueue; public ConsumeRequest(ProcessQueue processQueue, MessageQueue messageQueue) { this.processQueue = processQueue; this.messageQueue = messageQueue; } public ProcessQueue getProcessQueue() { return processQueue; } public MessageQueue getMessageQueue() { return messageQueue; } @Override public void run() { if (this.processQueue.isDropped()) { log.warn("run, the message queue not be able to consume, because it's dropped. {}", this.messageQueue); return; } final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue); synchronized (objLock) { if (MessageModel.BROADCASTING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel()) || this.processQueue.isLocked() && !this.processQueue.isLockExpired()) { final long beginTime = System.currentTimeMillis(); for (boolean continueConsume = true; continueConsume; ) { if (this.processQueue.isDropped()) { log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue); break; } if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel()) && !this.processQueue.isLocked()) { log.warn("the message queue not locked, so consume later, {}", this.messageQueue); ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 10); break; } if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel()) && this.processQueue.isLockExpired()) { log.warn("the message queue lock expired, so consume later, {}", this.messageQueue); ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 10); break; } long interval = System.currentTimeMillis() - beginTime; if (interval > MAX_TIME_CONSUME_CONTINUOUSLY) { ConsumeMessageOrderlyService.this.submitConsumeRequestLater(processQueue, messageQueue, 10); break; } final int consumeBatchSize = ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize(); List msgs = this.processQueue.takeMessages(consumeBatchSize); defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup()); if (!msgs.isEmpty()) { final ConsumeOrderlyContext context = new ConsumeOrderlyContext(this.messageQueue); ConsumeOrderlyStatus status = null; ConsumeMessageContext consumeMessageContext = null; if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext = new ConsumeMessageContext(); consumeMessageContext .setConsumerGroup(ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumerGroup()); consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace()); consumeMessageContext.setMq(messageQueue); consumeMessageContext.setMsgList(msgs); consumeMessageContext.setSuccess(false); // init the consume context type consumeMessageContext.setProps(new HashMap<>()); ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext); } long beginTimestamp = System.currentTimeMillis(); ConsumeReturnType returnType = ConsumeReturnType.SUCCESS; boolean hasException = false; try { this.processQueue.getConsumeLock().readLock().lock(); if (this.processQueue.isDropped()) { log.warn("consumeMessage, the message queue not be able to consume, because it's dropped. {}", this.messageQueue); break; } status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context); } catch (Throwable e) { log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessageOrderlyService.this.consumerGroup, msgs, messageQueue, e); hasException = true; } finally { this.processQueue.getConsumeLock().readLock().unlock(); } if (null == status || ConsumeOrderlyStatus.ROLLBACK == status || ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) { log.warn("consumeMessage Orderly return not OK, Group: {} Msgs: {} MQ: {}", ConsumeMessageOrderlyService.this.consumerGroup, msgs, messageQueue); } long consumeRT = System.currentTimeMillis() - beginTimestamp; if (null == status) { if (hasException) { returnType = ConsumeReturnType.EXCEPTION; } else { returnType = ConsumeReturnType.RETURNNULL; } } else if (consumeRT >= defaultMQPushConsumer.getConsumeTimeout() * 60 * 1000) { returnType = ConsumeReturnType.TIME_OUT; } else if (ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) { returnType = ConsumeReturnType.FAILED; } else if (ConsumeOrderlyStatus.SUCCESS == status) { returnType = ConsumeReturnType.SUCCESS; } if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name()); } if (null == status) { status = ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext.setStatus(status.toString()); consumeMessageContext .setSuccess(ConsumeOrderlyStatus.SUCCESS == status || ConsumeOrderlyStatus.COMMIT == status); consumeMessageContext.setAccessChannel(defaultMQPushConsumer.getAccessChannel()); ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext); } ConsumeMessageOrderlyService.this.getConsumerStatsManager() .incConsumeRT(ConsumeMessageOrderlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT); continueConsume = ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status, context, this); } else { continueConsume = false; } } } else { if (this.processQueue.isDropped()) { log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue); return; } ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 100); } } } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeReturnType; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ConsumeMessagePopConcurrentlyService implements ConsumeMessageService { private static final Logger log = LoggerFactory.getLogger(ConsumeMessagePopConcurrentlyService.class); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; private final DefaultMQPushConsumer defaultMQPushConsumer; private final MessageListenerConcurrently messageListener; private final BlockingQueue consumeRequestQueue; private final ThreadPoolExecutor consumeExecutor; private final String consumerGroup; private final ScheduledExecutorService scheduledExecutorService; public ConsumeMessagePopConcurrentlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl, MessageListenerConcurrently messageListener) { this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl; this.messageListener = messageListener; this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer(); this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup(); this.consumeRequestQueue = new LinkedBlockingQueue<>(); this.consumeExecutor = new ThreadPoolExecutor( this.defaultMQPushConsumer.getConsumeThreadMin(), this.defaultMQPushConsumer.getConsumeThreadMax(), 1000 * 60, TimeUnit.MILLISECONDS, this.consumeRequestQueue, new ThreadFactoryImpl("ConsumeMessageThread_")); this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_")); } public void start() { } public void shutdown(long awaitTerminateMillis) { this.scheduledExecutorService.shutdown(); ThreadUtils.shutdownGracefully(this.consumeExecutor, awaitTerminateMillis, TimeUnit.MILLISECONDS); } @Override public void updateCorePoolSize(int corePoolSize) { if (corePoolSize > 0 && corePoolSize <= Short.MAX_VALUE && corePoolSize < this.defaultMQPushConsumer.getConsumeThreadMax()) { this.consumeExecutor.setCorePoolSize(corePoolSize); } } @Override public void incCorePoolSize() { } @Override public void decCorePoolSize() { } @Override public int getCorePoolSize() { return this.consumeExecutor.getCorePoolSize(); } @Override public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) { ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); result.setOrder(false); result.setAutoCommit(true); List msgs = new ArrayList<>(); msgs.add(msg); MessageQueue mq = new MessageQueue(); mq.setBrokerName(brokerName); mq.setTopic(msg.getTopic()); mq.setQueueId(msg.getQueueId()); ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(mq); this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, this.consumerGroup); final long beginTime = System.currentTimeMillis(); log.info("consumeMessageDirectly receive new message: {}", msg); try { ConsumeConcurrentlyStatus status = this.messageListener.consumeMessage(msgs, context); if (status != null) { switch (status) { case CONSUME_SUCCESS: result.setConsumeResult(CMResult.CR_SUCCESS); break; case RECONSUME_LATER: result.setConsumeResult(CMResult.CR_LATER); break; default: break; } } else { result.setConsumeResult(CMResult.CR_RETURN_NULL); } } catch (Throwable e) { result.setConsumeResult(CMResult.CR_THROW_EXCEPTION); result.setRemark(UtilAll.exceptionSimpleDesc(e)); log.warn("consumeMessageDirectly exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessagePopConcurrentlyService.this.consumerGroup, msgs, mq, e); } result.setSpentTimeMills(System.currentTimeMillis() - beginTime); log.info("consumeMessageDirectly Result: {}", result); return result; } @Override public void submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume) { throw new UnsupportedOperationException(); } @Override public void submitPopConsumeRequest( final List msgs, final PopProcessQueue processQueue, final MessageQueue messageQueue) { final int consumeBatchSize = this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize(); if (msgs.size() <= consumeBatchSize) { ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue); try { this.consumeExecutor.submit(consumeRequest); } catch (RejectedExecutionException e) { this.submitConsumeRequestLater(consumeRequest); } } else { for (int total = 0; total < msgs.size(); ) { List msgThis = new ArrayList<>(consumeBatchSize); for (int i = 0; i < consumeBatchSize; i++, total++) { if (total < msgs.size()) { msgThis.add(msgs.get(total)); } else { break; } } ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue); try { this.consumeExecutor.submit(consumeRequest); } catch (RejectedExecutionException e) { for (; total < msgs.size(); total++) { msgThis.add(msgs.get(total)); } this.submitConsumeRequestLater(consumeRequest); } } } } public void processConsumeResult( final ConsumeConcurrentlyStatus status, final ConsumeConcurrentlyContext context, final ConsumeRequest consumeRequest) { if (consumeRequest.getMsgs().isEmpty()) { return; } int ackIndex = context.getAckIndex(); String topic = consumeRequest.getMessageQueue().getTopic(); switch (status) { case CONSUME_SUCCESS: if (ackIndex >= consumeRequest.getMsgs().size()) { ackIndex = consumeRequest.getMsgs().size() - 1; } int ok = ackIndex + 1; int failed = consumeRequest.getMsgs().size() - ok; this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, topic, ok); this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, topic, failed); break; case RECONSUME_LATER: ackIndex = -1; this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, topic, consumeRequest.getMsgs().size()); break; default: break; } //ack if consume success for (int i = 0; i <= ackIndex; i++) { this.defaultMQPushConsumerImpl.ackAsync(consumeRequest.getMsgs().get(i), consumerGroup); consumeRequest.getPopProcessQueue().ack(); } //consume later if consume fail for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) { MessageExt msgExt = consumeRequest.getMsgs().get(i); consumeRequest.getPopProcessQueue().ack(); if (msgExt.getReconsumeTimes() >= this.defaultMQPushConsumerImpl.getMaxReconsumeTimes()) { checkNeedAckOrDelay(msgExt); continue; } int delayLevel = context.getDelayLevelWhenNextConsume(); changePopInvisibleTime(consumeRequest.getMsgs().get(i), consumerGroup, delayLevel); } } private void checkNeedAckOrDelay(MessageExt msgExt) { int[] delayLevelTable = this.defaultMQPushConsumerImpl.getPopDelayLevel(); long msgDelaytime = System.currentTimeMillis() - msgExt.getBornTimestamp(); if (msgDelaytime > delayLevelTable[delayLevelTable.length - 1] * 1000 * 2) { log.warn("Consume too many times, ack message async. message {}", msgExt.toString()); this.defaultMQPushConsumerImpl.ackAsync(msgExt, consumerGroup); } else { int delayLevel = delayLevelTable.length - 1; for (; delayLevel >= 0; delayLevel--) { if (msgDelaytime >= delayLevelTable[delayLevel] * 1000) { delayLevel++; break; } } changePopInvisibleTime(msgExt, consumerGroup, delayLevel); log.warn("Consume too many times, but delay time {} not enough. changePopInvisibleTime to delayLevel {} . message key:{}", msgDelaytime, delayLevel, msgExt.getKeys()); } } private void changePopInvisibleTime(final MessageExt msg, String consumerGroup, int delayLevel) { if (0 == delayLevel) { delayLevel = msg.getReconsumeTimes(); } int[] delayLevelTable = this.defaultMQPushConsumerImpl.getPopDelayLevel(); int delaySecond = delayLevel >= delayLevelTable.length ? delayLevelTable[delayLevelTable.length - 1] : delayLevelTable[delayLevel]; String extraInfo = msg.getProperty(MessageConst.PROPERTY_POP_CK); try { this.defaultMQPushConsumerImpl.changePopInvisibleTimeAsync(msg.getTopic(), consumerGroup, extraInfo, delaySecond * 1000L, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { } @Override public void onException(Throwable e) { log.error("changePopInvisibleTimeAsync fail. msg:{} error info: {}", msg.toString(), e.toString()); } }); } catch (Throwable t) { log.error("changePopInvisibleTimeAsync fail, group:{} msg:{} errorInfo:{}", consumerGroup, msg.toString(), t.toString()); } } public ConsumerStatsManager getConsumerStatsManager() { return this.defaultMQPushConsumerImpl.getConsumerStatsManager(); } private void submitConsumeRequestLater( final List msgs, final PopProcessQueue processQueue, final MessageQueue messageQueue ) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { ConsumeMessagePopConcurrentlyService.this.submitPopConsumeRequest(msgs, processQueue, messageQueue); } }, 5000, TimeUnit.MILLISECONDS); } private void submitConsumeRequestLater(final ConsumeRequest consumeRequest ) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { ConsumeMessagePopConcurrentlyService.this.consumeExecutor.submit(consumeRequest); } }, 5000, TimeUnit.MILLISECONDS); } class ConsumeRequest implements Runnable { private final List msgs; private final PopProcessQueue processQueue; private final MessageQueue messageQueue; private long popTime = 0; private long invisibleTime = 0; public ConsumeRequest(List msgs, PopProcessQueue processQueue, MessageQueue messageQueue) { this.msgs = msgs; this.processQueue = processQueue; this.messageQueue = messageQueue; try { String extraInfo = msgs.get(0).getProperty(MessageConst.PROPERTY_POP_CK); String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo); popTime = ExtraInfoUtil.getPopTime(extraInfoStrs); invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfoStrs); } catch (Throwable t) { log.error("parse extra info error. msg:" + msgs.get(0), t); } } public boolean isPopTimeout() { if (msgs.size() == 0 || popTime <= 0 || invisibleTime <= 0) { return true; } long current = System.currentTimeMillis(); return current - popTime >= invisibleTime; } public List getMsgs() { return msgs; } public PopProcessQueue getPopProcessQueue() { return processQueue; } @Override public void run() { if (this.processQueue.isDropped()) { log.info("the message queue not be able to consume, because it's dropped(pop). group={} {}", ConsumeMessagePopConcurrentlyService.this.consumerGroup, this.messageQueue); return; } if (isPopTimeout()) { log.info("the pop message time out so abort consume. popTime={} invisibleTime={}, group={} {}", popTime, invisibleTime, ConsumeMessagePopConcurrentlyService.this.consumerGroup, this.messageQueue); processQueue.decFoundMsg(-msgs.size()); return; } MessageListenerConcurrently listener = ConsumeMessagePopConcurrentlyService.this.messageListener; ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue); ConsumeConcurrentlyStatus status = null; defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup()); ConsumeMessageContext consumeMessageContext = null; if (ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext = new ConsumeMessageContext(); consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace()); consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup()); consumeMessageContext.setProps(new HashMap<>()); consumeMessageContext.setMq(messageQueue); consumeMessageContext.setMsgList(msgs); consumeMessageContext.setSuccess(false); ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext); } long beginTimestamp = System.currentTimeMillis(); boolean hasException = false; ConsumeReturnType returnType = ConsumeReturnType.SUCCESS; try { if (msgs != null && !msgs.isEmpty()) { for (MessageExt msg : msgs) { MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis())); } } status = listener.consumeMessage(Collections.unmodifiableList(msgs), context); } catch (Throwable e) { log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessagePopConcurrentlyService.this.consumerGroup, msgs, messageQueue); hasException = true; } long consumeRT = System.currentTimeMillis() - beginTimestamp; if (null == status) { if (hasException) { returnType = ConsumeReturnType.EXCEPTION; } else { returnType = ConsumeReturnType.RETURNNULL; } } else if (consumeRT >= invisibleTime * 1000) { returnType = ConsumeReturnType.TIME_OUT; } else if (ConsumeConcurrentlyStatus.RECONSUME_LATER == status) { returnType = ConsumeReturnType.FAILED; } else if (ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status) { returnType = ConsumeReturnType.SUCCESS; } if (null == status) { log.warn("consumeMessage return null, Group: {} Msgs: {} MQ: {}", ConsumeMessagePopConcurrentlyService.this.consumerGroup, msgs, messageQueue); status = ConsumeConcurrentlyStatus.RECONSUME_LATER; } if (ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name()); consumeMessageContext.setStatus(status.toString()); consumeMessageContext.setSuccess(ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status); consumeMessageContext.setAccessChannel(defaultMQPushConsumer.getAccessChannel()); ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext); } ConsumeMessagePopConcurrentlyService.this.getConsumerStatsManager() .incConsumeRT(ConsumeMessagePopConcurrentlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT); if (!processQueue.isDropped() && !isPopTimeout()) { ConsumeMessagePopConcurrentlyService.this.processConsumeResult(status, context, this); } else { if (msgs != null) { processQueue.decFoundMsg(-msgs.size()); } log.warn("processQueue invalid or popTimeout. isDropped={}, isPopTimeout={}, messageQueue={}, msgs={}", processQueue.isDropped(), isPopTimeout(), messageQueue, msgs); } } public MessageQueue getMessageQueue() { return messageQueue; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import io.netty.util.internal.ConcurrentSet; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ConsumeMessagePopOrderlyService implements ConsumeMessageService { private static final Logger log = LoggerFactory.getLogger(ConsumeMessagePopOrderlyService.class); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; private final DefaultMQPushConsumer defaultMQPushConsumer; private final MessageListenerOrderly messageListener; private final BlockingQueue consumeRequestQueue; private final ConcurrentSet consumeRequestSet = new ConcurrentSet<>(); private final ThreadPoolExecutor consumeExecutor; private final String consumerGroup; private final MessageQueueLock messageQueueLock = new MessageQueueLock(); private final MessageQueueLock consumeRequestLock = new MessageQueueLock(); private final ScheduledExecutorService scheduledExecutorService; private volatile boolean stopped = false; public ConsumeMessagePopOrderlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl, MessageListenerOrderly messageListener) { this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl; this.messageListener = messageListener; this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer(); this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup(); this.consumeRequestQueue = new LinkedBlockingQueue<>(); this.consumeExecutor = new ThreadPoolExecutor( this.defaultMQPushConsumer.getConsumeThreadMin(), this.defaultMQPushConsumer.getConsumeThreadMax(), 1000 * 60, TimeUnit.MILLISECONDS, this.consumeRequestQueue, new ThreadFactoryImpl("ConsumeMessageThread_")); this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_")); } @Override public void start() { if (MessageModel.CLUSTERING.equals(ConsumeMessagePopOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { ConsumeMessagePopOrderlyService.this.lockMQPeriodically(); } }, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS); } } @Override public void shutdown(long awaitTerminateMillis) { this.stopped = true; this.scheduledExecutorService.shutdown(); ThreadUtils.shutdownGracefully(this.consumeExecutor, awaitTerminateMillis, TimeUnit.MILLISECONDS); if (MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) { this.unlockAllMessageQueues(); } } public synchronized void unlockAllMessageQueues() { this.defaultMQPushConsumerImpl.getRebalanceImpl().unlockAll(false); } @Override public void updateCorePoolSize(int corePoolSize) { if (corePoolSize > 0 && corePoolSize <= Short.MAX_VALUE && corePoolSize < this.defaultMQPushConsumer.getConsumeThreadMax()) { this.consumeExecutor.setCorePoolSize(corePoolSize); } } @Override public void incCorePoolSize() { } @Override public void decCorePoolSize() { } @Override public int getCorePoolSize() { return this.consumeExecutor.getCorePoolSize(); } @Override public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) { ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); result.setOrder(true); List msgs = new ArrayList<>(); msgs.add(msg); MessageQueue mq = new MessageQueue(); mq.setBrokerName(brokerName); mq.setTopic(msg.getTopic()); mq.setQueueId(msg.getQueueId()); ConsumeOrderlyContext context = new ConsumeOrderlyContext(mq); this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, this.consumerGroup); final long beginTime = System.currentTimeMillis(); log.info("consumeMessageDirectly receive new message: {}", msg); try { ConsumeOrderlyStatus status = this.messageListener.consumeMessage(msgs, context); if (status != null) { switch (status) { case COMMIT: result.setConsumeResult(CMResult.CR_COMMIT); break; case ROLLBACK: result.setConsumeResult(CMResult.CR_ROLLBACK); break; case SUCCESS: result.setConsumeResult(CMResult.CR_SUCCESS); break; case SUSPEND_CURRENT_QUEUE_A_MOMENT: result.setConsumeResult(CMResult.CR_LATER); break; default: break; } } else { result.setConsumeResult(CMResult.CR_RETURN_NULL); } } catch (Throwable e) { result.setConsumeResult(CMResult.CR_THROW_EXCEPTION); result.setRemark(UtilAll.exceptionSimpleDesc(e)); log.warn("consumeMessageDirectly exception: {} Group: {} Msgs: {} MQ: {}", UtilAll.exceptionSimpleDesc(e), ConsumeMessagePopOrderlyService.this.consumerGroup, msgs, mq, e); } result.setAutoCommit(context.isAutoCommit()); result.setSpentTimeMills(System.currentTimeMillis() - beginTime); log.info("consumeMessageDirectly Result: {}", result); return result; } @Override public void submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume) { throw new UnsupportedOperationException(); } @Override public void submitPopConsumeRequest(final List msgs, final PopProcessQueue processQueue, final MessageQueue messageQueue) { ConsumeRequest req = new ConsumeRequest(processQueue, messageQueue); submitConsumeRequest(req, false); } public synchronized void lockMQPeriodically() { if (!this.stopped) { this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll(); } } private void removeConsumeRequest(final ConsumeRequest consumeRequest) { consumeRequestSet.remove(consumeRequest); } private void submitConsumeRequest(final ConsumeRequest consumeRequest, boolean force) { Object lock = consumeRequestLock.fetchLockObject(consumeRequest.getMessageQueue(), consumeRequest.shardingKeyIndex); synchronized (lock) { boolean isNewReq = consumeRequestSet.add(consumeRequest); if (force || isNewReq) { try { consumeExecutor.submit(consumeRequest); } catch (Exception e) { log.error("error submit consume request: {}, mq: {}, shardingKeyIndex: {}", e.toString(), consumeRequest.getMessageQueue(), consumeRequest.getShardingKeyIndex()); } } } } private void submitConsumeRequestLater(final ConsumeRequest consumeRequest, final long suspendTimeMillis) { long timeMillis = suspendTimeMillis; if (timeMillis == -1) { timeMillis = this.defaultMQPushConsumer.getSuspendCurrentQueueTimeMillis(); } if (timeMillis < 10) { timeMillis = 10; } else if (timeMillis > 30000) { timeMillis = 30000; } this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { submitConsumeRequest(consumeRequest, true); } }, timeMillis, TimeUnit.MILLISECONDS); } public boolean processConsumeResult( final List msgs, final ConsumeOrderlyStatus status, final ConsumeOrderlyContext context, final ConsumeRequest consumeRequest ) { return true; } public ConsumerStatsManager getConsumerStatsManager() { return this.defaultMQPushConsumerImpl.getConsumerStatsManager(); } private int getMaxReconsumeTimes() { // default reconsume times: Integer.MAX_VALUE if (this.defaultMQPushConsumer.getMaxReconsumeTimes() == -1) { return Integer.MAX_VALUE; } else { return this.defaultMQPushConsumer.getMaxReconsumeTimes(); } } private boolean checkReconsumeTimes(List msgs) { boolean suspend = false; if (msgs != null && !msgs.isEmpty()) { for (MessageExt msg : msgs) { if (msg.getReconsumeTimes() >= getMaxReconsumeTimes()) { MessageAccessor.setReconsumeTime(msg, String.valueOf(msg.getReconsumeTimes())); if (!sendMessageBack(msg)) { suspend = true; msg.setReconsumeTimes(msg.getReconsumeTimes() + 1); } } else { suspend = true; msg.setReconsumeTimes(msg.getReconsumeTimes() + 1); } } } return suspend; } public boolean sendMessageBack(final MessageExt msg) { try { // max reconsume times exceeded then send to dead letter queue. Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody()); MessageAccessor.setProperties(newMsg, msg.getProperties()); String originMsgId = MessageAccessor.getOriginMessageId(msg); MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId); newMsg.setFlag(msg.getFlag()); MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic()); MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes())); MessageAccessor.setMaxReconsumeTimes(newMsg, String.valueOf(getMaxReconsumeTimes())); newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes()); this.defaultMQPushConsumerImpl.getmQClientFactory().getDefaultMQProducer().send(newMsg); return true; } catch (Exception e) { log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e); } return false; } public void resetNamespace(final List msgs) { for (MessageExt msg : msgs) { if (StringUtils.isNotEmpty(this.defaultMQPushConsumer.getNamespace())) { msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace())); } } } class ConsumeRequest implements Runnable { private final PopProcessQueue processQueue; private final MessageQueue messageQueue; private int shardingKeyIndex = 0; public ConsumeRequest(PopProcessQueue processQueue, MessageQueue messageQueue) { this.processQueue = processQueue; this.messageQueue = messageQueue; this.shardingKeyIndex = 0; } public ConsumeRequest(PopProcessQueue processQueue, MessageQueue messageQueue, int shardingKeyIndex) { this.processQueue = processQueue; this.messageQueue = messageQueue; this.shardingKeyIndex = shardingKeyIndex; } public PopProcessQueue getProcessQueue() { return processQueue; } public MessageQueue getMessageQueue() { return messageQueue; } public int getShardingKeyIndex() { return shardingKeyIndex; } @Override public void run() { if (this.processQueue.isDropped()) { log.warn("run, message queue not be able to consume, because it's dropped. {}", this.messageQueue); ConsumeMessagePopOrderlyService.this.removeConsumeRequest(this); return; } // lock on sharding key index final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue, shardingKeyIndex); } @Override public int hashCode() { int hash = shardingKeyIndex; if (processQueue != null) { hash += processQueue.hashCode() * 31; } if (messageQueue != null) { hash += messageQueue.hashCode() * 31; } return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ConsumeRequest other = (ConsumeRequest) obj; if (shardingKeyIndex != other.shardingKeyIndex) { return false; } if (processQueue != other.processQueue) { return false; } if (messageQueue == other.messageQueue) { return true; } if (messageQueue != null && messageQueue.equals(other.messageQueue)) { return true; } return false; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; public interface ConsumeMessageService { void start(); void shutdown(long awaitTerminateMillis); void updateCorePoolSize(int corePoolSize); void incCorePoolSize(); void decCorePoolSize(); int getCorePoolSize(); ConsumeMessageDirectlyResult consumeMessageDirectly(final MessageExt msg, final String brokerName); void submitConsumeRequest( final List msgs, final ProcessQueue processQueue, final MessageQueue messageQueue, final boolean dispathToConsume); void submitPopConsumeRequest( final List msgs, final PopProcessQueue processQueue, final MessageQueue messageQueue); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.consumer.MessageQueueListener; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.TopicMessageQueueChangeListener; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.ProcessQueueInfo; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class DefaultLitePullConsumerImpl implements MQConsumerInner { private static final Logger log = LoggerFactory.getLogger(DefaultLitePullConsumerImpl.class); private final long consumerStartTimestamp = System.currentTimeMillis(); private final RPCHook rpcHook; private final ArrayList filterMessageHookList = new ArrayList<>(); private volatile ServiceState serviceState = ServiceState.CREATE_JUST; protected MQClientInstance mQClientFactory; private PullAPIWrapper pullAPIWrapper; private OffsetStore offsetStore; private RebalanceImpl rebalanceImpl = new RebalanceLitePullImpl(this); private enum SubscriptionType { NONE, SUBSCRIBE, ASSIGN } private static final String NOT_RUNNING_EXCEPTION_MESSAGE = "The consumer not running, please start it first."; private static final String SUBSCRIPTION_CONFLICT_EXCEPTION_MESSAGE = "Subscribe and assign are mutually exclusive."; /** * the type of subscription */ private SubscriptionType subscriptionType = SubscriptionType.NONE; /** * Delay some time when exception occur */ private long pullTimeDelayMillsWhenException = 1000; /** * Flow control interval when message cache is full */ private static final long PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL = 50; /** * Flow control interval when broker return flow control */ private static final long PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL = 20; /** * Delay some time when suspend pull service */ private static final long PULL_TIME_DELAY_MILLS_WHEN_PAUSE = 1000; private static final long PULL_TIME_DELAY_MILLS_ON_EXCEPTION = 3 * 1000; private ConcurrentHashMap topicToSubExpression = new ConcurrentHashMap<>(); private DefaultLitePullConsumer defaultLitePullConsumer; private final ConcurrentMap taskTable = new ConcurrentHashMap<>(); private AssignedMessageQueue assignedMessageQueue = new AssignedMessageQueue(); private final BlockingQueue consumeRequestCache = new LinkedBlockingQueue<>(); private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; private final ScheduledExecutorService scheduledExecutorService; private Map topicMessageQueueChangeListenerMap = new HashMap<>(); private Map> messageQueuesForTopic = new HashMap<>(); private long consumeRequestFlowControlTimes = 0L; private long queueFlowControlTimes = 0L; private long queueMaxSpanFlowControlTimes = 0L; private long nextAutoCommitDeadline = -1L; private final MessageQueueLock messageQueueLock = new MessageQueueLock(); private final ArrayList consumeMessageHookList = new ArrayList<>(); // only for test purpose, will be modified by reflection in unit test. @SuppressWarnings("FieldMayBeFinal") private static boolean doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged = false; public DefaultLitePullConsumerImpl(final DefaultLitePullConsumer defaultLitePullConsumer, final RPCHook rpcHook) { this.defaultLitePullConsumer = defaultLitePullConsumer; this.rpcHook = rpcHook; this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("MonitorMessageQueueChangeThread")); this.pullTimeDelayMillsWhenException = defaultLitePullConsumer.getPullTimeDelayMillsWhenException(); } public void registerConsumeMessageHook(final ConsumeMessageHook hook) { this.consumeMessageHookList.add(hook); log.info("register consumeMessageHook Hook, {}", hook.hookName()); } public void executeHookBefore(final ConsumeMessageContext context) { if (!this.consumeMessageHookList.isEmpty()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageBefore(context); } catch (Throwable e) { log.error("consumeMessageHook {} executeHookBefore exception", hook.hookName(), e); } } } } public void executeHookAfter(final ConsumeMessageContext context) { if (!this.consumeMessageHookList.isEmpty()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageAfter(context); } catch (Throwable e) { log.error("consumeMessageHook {} executeHookAfter exception", hook.hookName(), e); } } } } private void checkServiceState() { if (this.serviceState != ServiceState.RUNNING) { throw new IllegalStateException(NOT_RUNNING_EXCEPTION_MESSAGE); } } public void updateNameServerAddr(String newAddresses) { this.mQClientFactory.getMQClientAPIImpl().updateNameServerAddressList(newAddresses); } private synchronized void setSubscriptionType(SubscriptionType type) { if (this.subscriptionType == SubscriptionType.NONE) { this.subscriptionType = type; } else if (this.subscriptionType != type) { throw new IllegalStateException(SUBSCRIPTION_CONFLICT_EXCEPTION_MESSAGE); } } private void updateAssignedMessageQueue(String topic, Set assignedMessageQueue) { this.assignedMessageQueue.updateAssignedMessageQueue(topic, assignedMessageQueue); } private void updatePullTask(String topic, Set mqNewSet) { Iterator> it = this.taskTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); if (next.getKey().getTopic().equals(topic)) { if (!mqNewSet.contains(next.getKey())) { next.getValue().setCancelled(true); it.remove(); } } } startPullTask(mqNewSet); } class MessageQueueListenerImpl implements MessageQueueListener { @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { updateAssignQueueAndStartPullTask(topic, mqAll, mqDivided); } } public void updateAssignQueueAndStartPullTask(String topic, Set mqAll, Set mqDivided) { MessageModel messageModel = defaultLitePullConsumer.getMessageModel(); switch (messageModel) { case BROADCASTING: updateAssignedMessageQueue(topic, mqAll); updatePullTask(topic, mqAll); break; case CLUSTERING: updateAssignedMessageQueue(topic, mqDivided); updatePullTask(topic, mqDivided); break; default: break; } } public synchronized void shutdown() { switch (this.serviceState) { case CREATE_JUST: break; case RUNNING: persistConsumerOffset(); this.mQClientFactory.unregisterConsumer(this.defaultLitePullConsumer.getConsumerGroup()); scheduledThreadPoolExecutor.shutdown(); scheduledExecutorService.shutdown(); this.mQClientFactory.shutdown(); this.serviceState = ServiceState.SHUTDOWN_ALREADY; log.info("the consumer [{}] shutdown OK", this.defaultLitePullConsumer.getConsumerGroup()); break; default: break; } } public synchronized boolean isRunning() { return this.serviceState == ServiceState.RUNNING; } public synchronized void start() throws MQClientException { switch (this.serviceState) { case CREATE_JUST: this.serviceState = ServiceState.START_FAILED; this.checkConfig(); if (this.defaultLitePullConsumer.getMessageModel() == MessageModel.CLUSTERING) { this.defaultLitePullConsumer.changeInstanceNameToPID(); } initScheduledThreadPoolExecutor(); initMQClientFactory(); initRebalanceImpl(); initPullAPIWrapper(); initOffsetStore(); mQClientFactory.start(); startScheduleTask(); this.serviceState = ServiceState.RUNNING; log.info("the consumer [{}] start OK", this.defaultLitePullConsumer.getConsumerGroup()); try { operateAfterRunning(); } catch (Exception e) { shutdown(); throw e; } break; case RUNNING: case START_FAILED: case SHUTDOWN_ALREADY: throw new MQClientException("The PullConsumer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); default: break; } } private void initScheduledThreadPoolExecutor() { this.scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor( this.defaultLitePullConsumer.getPullThreadNums(), new ThreadFactoryImpl("PullMsgThread-" + this.defaultLitePullConsumer.getConsumerGroup()) ); } private void initMQClientFactory() throws MQClientException { this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultLitePullConsumer, this.rpcHook); boolean registerOK = mQClientFactory.registerConsumer(this.defaultLitePullConsumer.getConsumerGroup(), this); if (!registerOK) { this.serviceState = ServiceState.CREATE_JUST; throw new MQClientException("The consumer group[" + this.defaultLitePullConsumer.getConsumerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null); } } private void initRebalanceImpl() { this.rebalanceImpl.setConsumerGroup(this.defaultLitePullConsumer.getConsumerGroup()); this.rebalanceImpl.setMessageModel(this.defaultLitePullConsumer.getMessageModel()); this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultLitePullConsumer.getAllocateMessageQueueStrategy()); this.rebalanceImpl.setmQClientFactory(this.mQClientFactory); } private void initPullAPIWrapper() { this.pullAPIWrapper = new PullAPIWrapper( mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup(), isUnitMode()); this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList); } private void initOffsetStore() throws MQClientException { if (this.defaultLitePullConsumer.getOffsetStore() != null) { this.offsetStore = this.defaultLitePullConsumer.getOffsetStore(); } else { switch (this.defaultLitePullConsumer.getMessageModel()) { case BROADCASTING: this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup()); break; case CLUSTERING: this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup()); break; default: break; } this.defaultLitePullConsumer.setOffsetStore(this.offsetStore); } this.offsetStore.load(); } private void startScheduleTask() { scheduledExecutorService.scheduleAtFixedRate( new Runnable() { @Override public void run() { try { fetchTopicMessageQueuesAndCompare(); } catch (Exception e) { log.error("ScheduledTask fetchMessageQueuesAndCompare exception", e); } } }, 1000 * 10, this.getDefaultLitePullConsumer().getTopicMetadataCheckIntervalMillis(), TimeUnit.MILLISECONDS); } private void operateAfterRunning() throws MQClientException { // If subscribe function invoke before start function, then update topic subscribe info after initialization. if (subscriptionType == SubscriptionType.SUBSCRIBE) { updateTopicSubscribeInfoWhenSubscriptionChanged(); } // If assign function invoke before start function, then update pull task after initialization. if (subscriptionType == SubscriptionType.ASSIGN) { updateAssignPullTask(assignedMessageQueue.getAssignedMessageQueues()); } for (String topic : topicMessageQueueChangeListenerMap.keySet()) { Set messageQueues = fetchMessageQueues(topic); messageQueuesForTopic.put(topic, messageQueues); } this.mQClientFactory.checkClientInBroker(); } private void checkConfig() throws MQClientException { // Check consumerGroup Validators.checkGroup(this.defaultLitePullConsumer.getConsumerGroup()); // Check consumerGroup name is not equal default consumer group name. if (this.defaultLitePullConsumer.getConsumerGroup().equals(MixAll.DEFAULT_CONSUMER_GROUP)) { throw new MQClientException( "consumerGroup can not equal " + MixAll.DEFAULT_CONSUMER_GROUP + ", please specify another one." + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // Check messageModel is not null. if (null == this.defaultLitePullConsumer.getMessageModel()) { throw new MQClientException( "messageModel is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // Check allocateMessageQueueStrategy is not null if (null == this.defaultLitePullConsumer.getAllocateMessageQueueStrategy()) { throw new MQClientException( "allocateMessageQueueStrategy is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } if (this.defaultLitePullConsumer.getConsumerTimeoutMillisWhenSuspend() < this.defaultLitePullConsumer.getBrokerSuspendMaxTimeMillis()) { throw new MQClientException( "Long polling mode, the consumer consumerTimeoutMillisWhenSuspend must greater than brokerSuspendMaxTimeMillis" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } } public PullAPIWrapper getPullAPIWrapper() { return pullAPIWrapper; } private void startPullTask(Collection mqSet) { for (MessageQueue messageQueue : mqSet) { if (!this.taskTable.containsKey(messageQueue)) { PullTaskImpl pullTask = new PullTaskImpl(messageQueue); this.taskTable.put(messageQueue, pullTask); this.scheduledThreadPoolExecutor.schedule(pullTask, 0, TimeUnit.MILLISECONDS); } } } private void updateAssignPullTask(Collection mqNewSet) { Iterator> it = this.taskTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); if (!mqNewSet.contains(next.getKey())) { next.getValue().setCancelled(true); it.remove(); } } startPullTask(mqNewSet); } private void updateTopicSubscribeInfoWhenSubscriptionChanged() { if (doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged) { return; } Map subTable = rebalanceImpl.getSubscriptionInner(); if (subTable != null) { for (final Map.Entry entry : subTable.entrySet()) { final String topic = entry.getKey(); this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); } } } /** * subscribe data by customizing messageQueueListener * * @param topic * @param subExpression * @param messageQueueListener * @throws MQClientException */ public synchronized void subscribe(String topic, String subExpression, MessageQueueListener messageQueueListener) throws MQClientException { try { if (StringUtils.isEmpty(topic)) { throw new IllegalArgumentException("Topic can not be null or empty."); } setSubscriptionType(SubscriptionType.SUBSCRIBE); SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); this.defaultLitePullConsumer.setMessageQueueListener(new MessageQueueListener() { @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { // First, update the assign queue updateAssignQueueAndStartPullTask(topic, mqAll, mqDivided); // run custom listener messageQueueListener.messageQueueChanged(topic, mqAll, mqDivided); } }); assignedMessageQueue.setRebalanceImpl(this.rebalanceImpl); if (serviceState == ServiceState.RUNNING) { this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); updateTopicSubscribeInfoWhenSubscriptionChanged(); } } catch (Exception e) { throw new MQClientException("subscribe exception", e); } } public synchronized void subscribe(String topic, String subExpression) throws MQClientException { try { if (topic == null || "".equals(topic)) { throw new IllegalArgumentException("Topic can not be null or empty."); } setSubscriptionType(SubscriptionType.SUBSCRIBE); SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); this.defaultLitePullConsumer.setMessageQueueListener(new MessageQueueListenerImpl()); assignedMessageQueue.setRebalanceImpl(this.rebalanceImpl); if (serviceState == ServiceState.RUNNING) { this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); updateTopicSubscribeInfoWhenSubscriptionChanged(); } } catch (Exception e) { throw new MQClientException("subscribe exception", e); } } public synchronized void subscribe(String topic, MessageSelector messageSelector) throws MQClientException { try { if (topic == null || "".equals(topic)) { throw new IllegalArgumentException("Topic can not be null or empty."); } setSubscriptionType(SubscriptionType.SUBSCRIBE); if (messageSelector == null) { subscribe(topic, SubscriptionData.SUB_ALL); return; } SubscriptionData subscriptionData = FilterAPI.build(topic, messageSelector.getExpression(), messageSelector.getExpressionType()); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); this.defaultLitePullConsumer.setMessageQueueListener(new MessageQueueListenerImpl()); assignedMessageQueue.setRebalanceImpl(this.rebalanceImpl); if (serviceState == ServiceState.RUNNING) { this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); updateTopicSubscribeInfoWhenSubscriptionChanged(); } } catch (Exception e) { throw new MQClientException("subscribe exception", e); } } public synchronized void unsubscribe(final String topic) { this.rebalanceImpl.getSubscriptionInner().remove(topic); removePullTaskCallback(topic); assignedMessageQueue.removeAssignedMessageQueue(topic); } public synchronized void assign(Collection messageQueues) { if (messageQueues == null || messageQueues.isEmpty()) { throw new IllegalArgumentException("Message queues can not be null or empty."); } setSubscriptionType(SubscriptionType.ASSIGN); assignedMessageQueue.updateAssignedMessageQueue(messageQueues); if (serviceState == ServiceState.RUNNING) { updateAssignPullTask(messageQueues); } } public synchronized void setSubExpressionForAssign(final String topic, final String subExpression) { if (StringUtils.isBlank(subExpression)) { throw new IllegalArgumentException("subExpression can not be null or empty."); } if (serviceState != ServiceState.CREATE_JUST) { throw new IllegalStateException("setAssignTag only can be called before start."); } setSubscriptionType(SubscriptionType.ASSIGN); topicToSubExpression.put(topic, subExpression); } private void maybeAutoCommit() { long now = System.currentTimeMillis(); if (now >= nextAutoCommitDeadline) { commitAll(); nextAutoCommitDeadline = now + defaultLitePullConsumer.getAutoCommitIntervalMillis(); } } public synchronized List poll(long timeout) { try { checkServiceState(); if (timeout < 0) { throw new IllegalArgumentException("Timeout must not be negative"); } if (defaultLitePullConsumer.isAutoCommit()) { maybeAutoCommit(); } long endTime = System.currentTimeMillis() + timeout; ConsumeRequest consumeRequest = consumeRequestCache.poll(endTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); if (endTime - System.currentTimeMillis() > 0) { while (consumeRequest != null && consumeRequest.getProcessQueue().isDropped()) { consumeRequest = consumeRequestCache.poll(endTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); if (endTime - System.currentTimeMillis() <= 0) { break; } } } if (consumeRequest != null && !consumeRequest.getProcessQueue().isDropped()) { List messages = consumeRequest.getMessageExts(); long offset = consumeRequest.getProcessQueue().removeMessage(messages); assignedMessageQueue.updateConsumeOffset(consumeRequest.getMessageQueue(), offset); //If namespace not null , reset Topic without namespace. this.resetTopic(messages); if (!this.consumeMessageHookList.isEmpty()) { ConsumeMessageContext consumeMessageContext = new ConsumeMessageContext(); consumeMessageContext.setNamespace(defaultLitePullConsumer.getNamespace()); consumeMessageContext.setConsumerGroup(this.groupName()); consumeMessageContext.setMq(consumeRequest.getMessageQueue()); consumeMessageContext.setMsgList(messages); consumeMessageContext.setSuccess(false); this.executeHookBefore(consumeMessageContext); consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString()); consumeMessageContext.setSuccess(true); consumeMessageContext.setAccessChannel(defaultLitePullConsumer.getAccessChannel()); this.executeHookAfter(consumeMessageContext); } consumeRequest.getProcessQueue().setLastConsumeTimestamp(System.currentTimeMillis()); return messages; } } catch (InterruptedException ignore) { } return Collections.emptyList(); } public void pause(Collection messageQueues) { assignedMessageQueue.pause(messageQueues); } public void resume(Collection messageQueues) { assignedMessageQueue.resume(messageQueues); } public synchronized void seek(MessageQueue messageQueue, long offset) throws MQClientException { if (!assignedMessageQueue.getAssignedMessageQueues().contains(messageQueue)) { if (subscriptionType == SubscriptionType.SUBSCRIBE) { throw new MQClientException("The message queue is not in assigned list, may be rebalancing, message queue: " + messageQueue, null); } else { throw new MQClientException("The message queue is not in assigned list, message queue: " + messageQueue, null); } } long minOffset = minOffset(messageQueue); long maxOffset = maxOffset(messageQueue); if (offset < minOffset || offset > maxOffset) { throw new MQClientException("Seek offset illegal, seek offset = " + offset + ", min offset = " + minOffset + ", max offset = " + maxOffset, null); } final Object objLock = messageQueueLock.fetchLockObject(messageQueue); synchronized (objLock) { clearMessageQueueInCache(messageQueue); PullTaskImpl oldPullTaskImpl = this.taskTable.get(messageQueue); if (oldPullTaskImpl != null) { oldPullTaskImpl.tryInterrupt(); this.taskTable.remove(messageQueue); } assignedMessageQueue.setSeekOffset(messageQueue, offset); if (!this.taskTable.containsKey(messageQueue)) { PullTaskImpl pullTask = new PullTaskImpl(messageQueue); this.taskTable.put(messageQueue, pullTask); this.scheduledThreadPoolExecutor.schedule(pullTask, 0, TimeUnit.MILLISECONDS); } } } public void seekToBegin(MessageQueue messageQueue) throws MQClientException { long begin = minOffset(messageQueue); this.seek(messageQueue, begin); } public void seekToEnd(MessageQueue messageQueue) throws MQClientException { long end = maxOffset(messageQueue); this.seek(messageQueue, end); } private long maxOffset(MessageQueue messageQueue) throws MQClientException { checkServiceState(); return this.mQClientFactory.getMQAdminImpl().maxOffset(messageQueue); } private long minOffset(MessageQueue messageQueue) throws MQClientException { checkServiceState(); return this.mQClientFactory.getMQAdminImpl().minOffset(messageQueue); } private void removePullTaskCallback(final String topic) { removePullTask(topic); } private void removePullTask(final String topic) { Iterator> it = this.taskTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); if (next.getKey().getTopic().equals(topic)) { next.getValue().setCancelled(true); it.remove(); } } } public synchronized void commitAll() { for (MessageQueue messageQueue : assignedMessageQueue.getAssignedMessageQueues()) { try { commit(messageQueue); } catch (Exception e) { log.error("An error occurred when update consume offset Automatically."); } } } /** * Specify offset commit * * @param messageQueues * @param persist */ public synchronized void commit(final Map messageQueues, boolean persist) { if (messageQueues == null || messageQueues.size() == 0) { log.warn("MessageQueues is empty, Ignore this commit "); return; } for (Map.Entry messageQueueEntry : messageQueues.entrySet()) { MessageQueue messageQueue = messageQueueEntry.getKey(); long offset = messageQueueEntry.getValue(); if (offset != -1) { ProcessQueue processQueue = assignedMessageQueue.getProcessQueue(messageQueue); if (processQueue != null && !processQueue.isDropped()) { updateConsumeOffset(messageQueue, offset); } } else { log.error("consumerOffset is -1 in messageQueue [" + messageQueue + "]."); } } if (persist) { this.offsetStore.persistAll(messageQueues.keySet()); } } /** * Get the queue assigned in subscribe mode * * @return */ public synchronized Set assignment() { return assignedMessageQueue.getAssignedMessageQueues(); } public synchronized void commit(final Set messageQueues, boolean persist) { if (messageQueues == null || messageQueues.size() == 0) { return; } for (MessageQueue messageQueue : messageQueues) { commit(messageQueue); } if (persist) { this.offsetStore.persistAll(messageQueues); } } private synchronized void commit(MessageQueue messageQueue) { long consumerOffset = assignedMessageQueue.getConsumerOffset(messageQueue); if (consumerOffset != -1) { ProcessQueue processQueue = assignedMessageQueue.getProcessQueue(messageQueue); if (processQueue != null && !processQueue.isDropped()) { updateConsumeOffset(messageQueue, consumerOffset); } } else { log.error("consumerOffset is -1 in messageQueue [" + messageQueue + "]."); } } private void updatePullOffset(MessageQueue messageQueue, long nextPullOffset, ProcessQueue processQueue) { if (assignedMessageQueue.getSeekOffset(messageQueue) == -1) { assignedMessageQueue.updatePullOffset(messageQueue, nextPullOffset, processQueue); } } private void submitConsumeRequest(ConsumeRequest consumeRequest) { try { consumeRequestCache.put(consumeRequest); } catch (InterruptedException e) { log.error("Submit consumeRequest error", e); } } private long fetchConsumeOffset(MessageQueue messageQueue) throws MQClientException { checkServiceState(); long offset = this.rebalanceImpl.computePullFromWhereWithException(messageQueue); return offset; } public long committed(MessageQueue messageQueue) throws MQClientException { checkServiceState(); long offset = this.offsetStore.readOffset(messageQueue, ReadOffsetType.MEMORY_FIRST_THEN_STORE); if (offset == -2) { throw new MQClientException("Fetch consume offset from broker exception", null); } return offset; } private void clearMessageQueueInCache(MessageQueue messageQueue) { ProcessQueue processQueue = assignedMessageQueue.getProcessQueue(messageQueue); if (processQueue != null) { processQueue.clear(); } Iterator iter = consumeRequestCache.iterator(); while (iter.hasNext()) { if (iter.next().getMessageQueue().equals(messageQueue)) { iter.remove(); } } } private long nextPullOffset(MessageQueue messageQueue) throws MQClientException { long offset = -1; long seekOffset = assignedMessageQueue.getSeekOffset(messageQueue); if (seekOffset != -1) { offset = seekOffset; assignedMessageQueue.updateConsumeOffset(messageQueue, offset); assignedMessageQueue.setSeekOffset(messageQueue, -1); } else { offset = assignedMessageQueue.getPullOffset(messageQueue); if (offset == -1) { offset = fetchConsumeOffset(messageQueue); } } return offset; } public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { checkServiceState(); return this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } public class PullTaskImpl implements Runnable { private final MessageQueue messageQueue; private volatile boolean cancelled = false; private Thread currentThread; public PullTaskImpl(final MessageQueue messageQueue) { this.messageQueue = messageQueue; } public void tryInterrupt() { setCancelled(true); if (currentThread == null) { return; } if (!currentThread.isInterrupted()) { currentThread.interrupt(); } } @Override public void run() { if (!this.isCancelled()) { this.currentThread = Thread.currentThread(); if (assignedMessageQueue.isPaused(messageQueue)) { scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_PAUSE, TimeUnit.MILLISECONDS); log.debug("Message Queue: {} has been paused!", messageQueue); return; } ProcessQueue processQueue = assignedMessageQueue.getProcessQueue(messageQueue); if (null == processQueue || processQueue.isDropped()) { log.info("The message queue not be able to poll, because it's dropped. group={}, messageQueue={}", defaultLitePullConsumer.getConsumerGroup(), this.messageQueue); return; } processQueue.setLastPullTimestamp(System.currentTimeMillis()); if ((long) consumeRequestCache.size() * defaultLitePullConsumer.getPullBatchSize() > defaultLitePullConsumer.getPullThresholdForAll()) { scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL, TimeUnit.MILLISECONDS); if ((consumeRequestFlowControlTimes++ % 1000) == 0) { log.warn("The consume request count exceeds threshold {}, so do flow control, consume request count={}, flowControlTimes={}", (int)Math.ceil((double)defaultLitePullConsumer.getPullThresholdForAll() / defaultLitePullConsumer.getPullBatchSize()), consumeRequestCache.size(), consumeRequestFlowControlTimes); } return; } long cachedMessageCount = processQueue.getMsgCount().get(); long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024); if (cachedMessageCount > defaultLitePullConsumer.getPullThresholdForQueue()) { scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL, TimeUnit.MILLISECONDS); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn( "The cached message count exceeds the threshold {}, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, flowControlTimes={}", defaultLitePullConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, queueFlowControlTimes); } return; } if (cachedMessageSizeInMiB > defaultLitePullConsumer.getPullThresholdSizeForQueue()) { scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL, TimeUnit.MILLISECONDS); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn( "The cached message size exceeds the threshold {} MiB, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, flowControlTimes={}", defaultLitePullConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, queueFlowControlTimes); } return; } if (processQueue.getMaxSpan() > defaultLitePullConsumer.getConsumeMaxSpan()) { scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL, TimeUnit.MILLISECONDS); if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) { log.warn( "The queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, flowControlTimes={}", processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(), queueMaxSpanFlowControlTimes); } return; } long offset = 0L; try { offset = nextPullOffset(messageQueue); } catch (Exception e) { log.error("Failed to get next pull offset", e); scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_ON_EXCEPTION, TimeUnit.MILLISECONDS); return; } if (this.isCancelled() || processQueue.isDropped()) { return; } long pullDelayTimeMills = 0; try { SubscriptionData subscriptionData; String topic = this.messageQueue.getTopic(); if (subscriptionType == SubscriptionType.SUBSCRIBE) { subscriptionData = rebalanceImpl.getSubscriptionInner().get(topic); } else { String subExpression4Assign = topicToSubExpression.get(topic); subExpression4Assign = subExpression4Assign == null ? SubscriptionData.SUB_ALL : subExpression4Assign; subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression4Assign); } PullResult pullResult = pull(messageQueue, subscriptionData, offset, defaultLitePullConsumer.getPullBatchSize()); if (this.isCancelled() || processQueue.isDropped()) { return; } switch (pullResult.getPullStatus()) { case FOUND: final Object objLock = messageQueueLock.fetchLockObject(messageQueue); synchronized (objLock) { if (pullResult.getMsgFoundList() != null && !pullResult.getMsgFoundList().isEmpty() && assignedMessageQueue.getSeekOffset(messageQueue) == -1) { processQueue.putMessage(pullResult.getMsgFoundList()); submitConsumeRequest(new ConsumeRequest(pullResult.getMsgFoundList(), messageQueue, processQueue)); } } break; case OFFSET_ILLEGAL: log.warn("The pull request offset illegal, {}", pullResult.toString()); break; default: break; } updatePullOffset(messageQueue, pullResult.getNextBeginOffset(), processQueue); } catch (InterruptedException interruptedException) { log.warn("Polling thread was interrupted.", interruptedException); } catch (Throwable e) { if (e instanceof MQBrokerException && ((MQBrokerException) e).getResponseCode() == ResponseCode.FLOW_CONTROL) { pullDelayTimeMills = PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL; } else { pullDelayTimeMills = pullTimeDelayMillsWhenException; } log.error("An error occurred in pull message process.", e); } if (!this.isCancelled()) { scheduledThreadPoolExecutor.schedule(this, pullDelayTimeMills, TimeUnit.MILLISECONDS); } else { log.warn("The Pull Task is cancelled after doPullTask, {}", messageQueue); } } } public boolean isCancelled() { return cancelled; } public void setCancelled(boolean cancelled) { this.cancelled = cancelled; } public MessageQueue getMessageQueue() { return messageQueue; } } private PullResult pull(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return pull(mq, subscriptionData, offset, maxNums, this.defaultLitePullConsumer.getConsumerPullTimeoutMillis()); } private PullResult pull(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, true, timeout); } private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { if (null == mq) { throw new MQClientException("mq is null", null); } if (offset < 0) { throw new MQClientException("offset < 0", null); } if (maxNums <= 0) { throw new MQClientException("maxNums <= 0", null); } int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false, true); long timeoutMillis = block ? this.defaultLitePullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout; boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType()); PullResult pullResult = this.pullAPIWrapper.pullKernelImpl( mq, subscriptionData.getSubString(), subscriptionData.getExpressionType(), isTagType ? 0L : subscriptionData.getSubVersion(), offset, maxNums, sysFlag, 0, this.defaultLitePullConsumer.getBrokerSuspendMaxTimeMillis(), timeoutMillis, CommunicationMode.SYNC, null ); this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData); return pullResult; } private void resetTopic(List msgList) { if (null == msgList || msgList.size() == 0) { return; } //If namespace not null , reset Topic without namespace. String namespace = this.defaultLitePullConsumer.getNamespace(); if (namespace != null) { for (MessageExt messageExt : msgList) { messageExt.setTopic(NamespaceUtil.withoutNamespace(messageExt.getTopic(), namespace)); } } } public void updateConsumeOffset(MessageQueue mq, long offset) { checkServiceState(); this.offsetStore.updateOffset(mq, offset, false); } @Override public String groupName() { return this.defaultLitePullConsumer.getConsumerGroup(); } @Override public MessageModel messageModel() { return this.defaultLitePullConsumer.getMessageModel(); } @Override public ConsumeType consumeType() { return ConsumeType.CONSUME_ACTIVELY; } @Override public ConsumeFromWhere consumeFromWhere() { return ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET; } @Override public Set subscriptions() { Set subSet = new HashSet<>(); subSet.addAll(this.rebalanceImpl.getSubscriptionInner().values()); subSet.addAll(this.defaultLitePullConsumer.getSubscriptionsForHeartbeat()); return subSet; } @Override public void doRebalance() { if (this.rebalanceImpl != null) { this.rebalanceImpl.doRebalance(false); } } @Override public boolean tryRebalance() { if (this.rebalanceImpl != null) { return this.rebalanceImpl.doRebalance(false); } return false; } @Override public void persistConsumerOffset() { try { checkServiceState(); Set mqs = new HashSet<>(); if (this.subscriptionType == SubscriptionType.SUBSCRIBE) { Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet(); mqs.addAll(allocateMq); } else if (this.subscriptionType == SubscriptionType.ASSIGN) { Set assignedMessageQueue = this.assignedMessageQueue.getAssignedMessageQueues(); mqs.addAll(assignedMessageQueue); } this.offsetStore.persistAll(mqs); } catch (Exception e) { log.error("Persist consumer offset error for group: {} ", this.defaultLitePullConsumer.getConsumerGroup(), e); } } @Override public void updateTopicSubscribeInfo(String topic, Set info) { Map subTable = this.rebalanceImpl.getSubscriptionInner(); if (subTable != null) { if (subTable.containsKey(topic)) { this.rebalanceImpl.getTopicSubscribeInfoTable().put(topic, info); } } } @Override public boolean isSubscribeTopicNeedUpdate(String topic) { Map subTable = this.rebalanceImpl.getSubscriptionInner(); if (subTable != null) { if (subTable.containsKey(topic)) { return !this.rebalanceImpl.topicSubscribeInfoTable.containsKey(topic); } } return false; } @Override public boolean isUnitMode() { return this.defaultLitePullConsumer.isUnitMode(); } @Override public ConsumerRunningInfo consumerRunningInfo() { ConsumerRunningInfo info = new ConsumerRunningInfo(); Properties prop = MixAll.object2Properties(this.defaultLitePullConsumer); prop.put(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP, String.valueOf(this.consumerStartTimestamp)); info.setProperties(prop); info.getSubscriptionSet().addAll(this.subscriptions()); for (MessageQueue mq : this.assignedMessageQueue.getAssignedMessageQueues()) { ProcessQueue pq = this.assignedMessageQueue.getProcessQueue(mq); ProcessQueueInfo pqInfo = new ProcessQueueInfo(); pqInfo.setCommitOffset(this.offsetStore.readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE)); pq.fillProcessQueueInfo(pqInfo); info.getMqTable().put(mq, pqInfo); } return info; } private void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.offsetStore.updateConsumeOffsetToBroker(mq, offset, isOneway); } public OffsetStore getOffsetStore() { return offsetStore; } public DefaultLitePullConsumer getDefaultLitePullConsumer() { return defaultLitePullConsumer; } public Set fetchMessageQueues(String topic) throws MQClientException { checkServiceState(); Set result = this.mQClientFactory.getMQAdminImpl().fetchSubscribeMessageQueues(topic); return parseMessageQueues(result); } private synchronized void fetchTopicMessageQueuesAndCompare() throws MQClientException { for (Map.Entry entry : topicMessageQueueChangeListenerMap.entrySet()) { String topic = entry.getKey(); TopicMessageQueueChangeListener topicMessageQueueChangeListener = entry.getValue(); Set oldMessageQueues = messageQueuesForTopic.get(topic); Set newMessageQueues = fetchMessageQueues(topic); boolean isChanged = !isSetEqual(newMessageQueues, oldMessageQueues); if (isChanged) { messageQueuesForTopic.put(topic, newMessageQueues); if (topicMessageQueueChangeListener != null) { topicMessageQueueChangeListener.onChanged(topic, newMessageQueues); } } } } private boolean isSetEqual(Set set1, Set set2) { if (set1 == null && set2 == null) { return true; } if (set1 == null || set2 == null || set1.size() != set2.size()) { return false; } for (MessageQueue messageQueue : set2) { if (!set1.contains(messageQueue)) { return false; } } return true; } public AssignedMessageQueue getAssignedMessageQueue() { return assignedMessageQueue; } public synchronized void registerTopicMessageQueueChangeListener(String topic, TopicMessageQueueChangeListener listener) throws MQClientException { if (topic == null || listener == null) { throw new MQClientException("Topic or listener is null", null); } if (topicMessageQueueChangeListenerMap.containsKey(topic)) { log.warn("Topic {} had been registered, new listener will overwrite the old one", topic); } topicMessageQueueChangeListenerMap.put(topic, listener); if (this.serviceState == ServiceState.RUNNING) { Set messageQueues = fetchMessageQueues(topic); messageQueuesForTopic.put(topic, messageQueues); } } private Set parseMessageQueues(Set queueSet) { Set resultQueues = new HashSet<>(); for (MessageQueue messageQueue : queueSet) { String userTopic = NamespaceUtil.withoutNamespace(messageQueue.getTopic(), this.defaultLitePullConsumer.getNamespace()); resultQueues.add(new MessageQueue(userTopic, messageQueue.getBrokerName(), messageQueue.getQueueId())); } return resultQueues; } public class ConsumeRequest { private final List messageExts; private final MessageQueue messageQueue; private final ProcessQueue processQueue; public ConsumeRequest(final List messageExts, final MessageQueue messageQueue, final ProcessQueue processQueue) { this.messageExts = messageExts; this.messageQueue = messageQueue; this.processQueue = processQueue; } public List getMessageExts() { return messageExts; } public MessageQueue getMessageQueue() { return messageQueue; } public ProcessQueue getProcessQueue() { return processQueue; } } public void setPullTimeDelayMillsWhenException(long pullTimeDelayMillsWhenException) { this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPullConsumerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; /** * This class will be removed in 2022, and a better implementation {@link DefaultLitePullConsumerImpl} is recommend to use * in the scenario of actively pulling messages. */ @Deprecated public class DefaultMQPullConsumerImpl implements MQConsumerInner { private static final Logger log = LoggerFactory.getLogger(DefaultMQPullConsumerImpl.class); private final DefaultMQPullConsumer defaultMQPullConsumer; private final long consumerStartTimestamp = System.currentTimeMillis(); private final RPCHook rpcHook; private final ArrayList consumeMessageHookList = new ArrayList<>(); private final ArrayList filterMessageHookList = new ArrayList<>(); private volatile ServiceState serviceState = ServiceState.CREATE_JUST; protected MQClientInstance mQClientFactory; private PullAPIWrapper pullAPIWrapper; private OffsetStore offsetStore; private RebalanceImpl rebalanceImpl = new RebalancePullImpl(this); public DefaultMQPullConsumerImpl(final DefaultMQPullConsumer defaultMQPullConsumer, final RPCHook rpcHook) { this.defaultMQPullConsumer = defaultMQPullConsumer; this.rpcHook = rpcHook; } public void registerConsumeMessageHook(final ConsumeMessageHook hook) { this.consumeMessageHookList.add(hook); log.info("register consumeMessageHook Hook, {}", hook.hookName()); } public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { createTopic(key, newTopic, queueNum, 0); } public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { this.isRunning(); this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag, null); } private void isRunning() throws MQClientException { if (this.serviceState != ServiceState.RUNNING) { throw new MQClientException("The consumer is not in running status, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); } } public long fetchConsumeOffset(MessageQueue mq, boolean fromStore) throws MQClientException { this.isRunning(); return this.offsetStore.readOffset(mq, fromStore ? ReadOffsetType.READ_FROM_STORE : ReadOffsetType.MEMORY_FIRST_THEN_STORE); } public Set fetchMessageQueuesInBalance(String topic) throws MQClientException { this.isRunning(); if (null == topic) { throw new IllegalArgumentException("topic is null"); } ConcurrentMap mqTable = this.rebalanceImpl.getProcessQueueTable(); Set mqResult = new HashSet<>(); for (MessageQueue mq : mqTable.keySet()) { if (mq.getTopic().equals(topic)) { mqResult.add(mq); } } return parseSubscribeMessageQueues(mqResult); } public List fetchPublishMessageQueues(String topic) throws MQClientException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().fetchPublishMessageQueues(topic); } public Set fetchSubscribeMessageQueues(String topic) throws MQClientException { this.isRunning(); // check if has info in memory, otherwise invoke api. Set result = this.rebalanceImpl.getTopicSubscribeInfoTable().get(topic); if (null == result) { result = this.mQClientFactory.getMQAdminImpl().fetchSubscribeMessageQueues(topic); } return parseSubscribeMessageQueues(result); } public Set parseSubscribeMessageQueues(Set queueSet) { Set resultQueues = new HashSet<>(); for (MessageQueue messageQueue : queueSet) { String userTopic = NamespaceUtil.withoutNamespace(messageQueue.getTopic(), this.defaultMQPullConsumer.getNamespace()); resultQueues.add(new MessageQueue(userTopic, messageQueue.getBrokerName(), messageQueue.getQueueId())); } return resultQueues; } public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().earliestMsgStoreTime(mq); } public long maxOffset(MessageQueue mq) throws MQClientException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } public long minOffset(MessageQueue mq) throws MQClientException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().minOffset(mq); } public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return pull(mq, subExpression, offset, maxNums, this.defaultMQPullConsumer.getConsumerPullTimeoutMillis()); } public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression); return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, false, timeout); } public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return pull(mq, messageSelector, offset, maxNums, this.defaultMQPullConsumer.getConsumerPullTimeoutMillis()); } public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, messageSelector); return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, false, timeout); } private SubscriptionData getSubscriptionData(MessageQueue mq, String subExpression) throws MQClientException { if (null == mq) { throw new MQClientException("mq is null", null); } try { return FilterAPI.buildSubscriptionData(mq.getTopic(), subExpression); } catch (Exception e) { throw new MQClientException("parse subscription error", e); } } private SubscriptionData getSubscriptionData(MessageQueue mq, MessageSelector messageSelector) throws MQClientException { if (null == mq) { throw new MQClientException("mq is null", null); } try { return FilterAPI.build(mq.getTopic(), messageSelector.getExpression(), messageSelector.getExpressionType()); } catch (Exception e) { throw new MQClientException("parse subscription error", e); } } private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { this.isRunning(); if (null == mq) { throw new MQClientException("mq is null", null); } if (offset < 0) { throw new MQClientException("offset < 0", null); } if (maxNums <= 0) { throw new MQClientException("maxNums <= 0", null); } this.subscriptionAutomatically(mq.getTopic()); int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false); long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout; boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType()); PullResult pullResult = this.pullAPIWrapper.pullKernelImpl( mq, subscriptionData.getSubString(), subscriptionData.getExpressionType(), isTagType ? 0L : subscriptionData.getSubVersion(), offset, maxNums, sysFlag, 0, this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(), timeoutMillis, CommunicationMode.SYNC, null ); this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData); //If namespace is not null , reset Topic without namespace. this.resetTopic(pullResult.getMsgFoundList()); if (!this.consumeMessageHookList.isEmpty()) { ConsumeMessageContext consumeMessageContext = null; consumeMessageContext = new ConsumeMessageContext(); consumeMessageContext.setNamespace(defaultMQPullConsumer.getNamespace()); consumeMessageContext.setConsumerGroup(this.groupName()); consumeMessageContext.setMq(mq); consumeMessageContext.setMsgList(pullResult.getMsgFoundList()); consumeMessageContext.setSuccess(false); this.executeHookBefore(consumeMessageContext); consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString()); consumeMessageContext.setSuccess(true); consumeMessageContext.setAccessChannel(defaultMQPullConsumer.getAccessChannel()); this.executeHookAfter(consumeMessageContext); } return pullResult; } public void resetTopic(List msgList) { if (null == msgList || msgList.size() == 0) { return; } //If namespace not null , reset Topic without namespace. String namespace = this.getDefaultMQPullConsumer().getNamespace(); if (namespace != null) { for (MessageExt messageExt : msgList) { messageExt.setTopic(NamespaceUtil.withoutNamespace(messageExt.getTopic(), namespace)); } } } public void subscriptionAutomatically(final String topic) { if (!this.rebalanceImpl.getSubscriptionInner().containsKey(topic)) { try { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, SubscriptionData.SUB_ALL); this.rebalanceImpl.subscriptionInner.putIfAbsent(topic, subscriptionData); } catch (Exception ignore) { } } } public void unsubscribe(String topic) { this.rebalanceImpl.getSubscriptionInner().remove(topic); } @Override public String groupName() { return this.defaultMQPullConsumer.getConsumerGroup(); } public void executeHookBefore(final ConsumeMessageContext context) { if (!this.consumeMessageHookList.isEmpty()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageBefore(context); } catch (Throwable ignored) { } } } } public void executeHookAfter(final ConsumeMessageContext context) { if (!this.consumeMessageHookList.isEmpty()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageAfter(context); } catch (Throwable ignored) { } } } } @Override public MessageModel messageModel() { return this.defaultMQPullConsumer.getMessageModel(); } @Override public ConsumeType consumeType() { return ConsumeType.CONSUME_ACTIVELY; } @Override public ConsumeFromWhere consumeFromWhere() { return ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET; } @Override public Set subscriptions() { Set registerSubscriptions = defaultMQPullConsumer.getRegisterSubscriptions(); if (registerSubscriptions != null && !registerSubscriptions.isEmpty()) { return registerSubscriptions; } Set result = new HashSet<>(); Set topics = this.defaultMQPullConsumer.getRegisterTopics(); if (topics != null) { synchronized (topics) { for (String t : topics) { SubscriptionData ms = null; try { ms = FilterAPI.buildSubscriptionData(t, SubscriptionData.SUB_ALL); } catch (Exception e) { log.error("parse subscription error", e); } if (ms != null) { ms.setSubVersion(0L); result.add(ms); } } } } return result; } @Override public void doRebalance() { if (!defaultMQPullConsumer.isEnableRebalance()) { return; } if (this.rebalanceImpl != null) { this.rebalanceImpl.doRebalance(false); } } @Override public boolean tryRebalance() { if (!defaultMQPullConsumer.isEnableRebalance()) { return true; } if (this.rebalanceImpl != null) { return this.rebalanceImpl.doRebalance(false); } return false; } @Override public void persistConsumerOffset() { try { this.isRunning(); Set mqs = new HashSet<>(); Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet(); mqs.addAll(allocateMq); this.offsetStore.persistAll(mqs); } catch (Exception e) { log.error("group: " + this.defaultMQPullConsumer.getConsumerGroup() + " persistConsumerOffset exception", e); } } @Override public void updateTopicSubscribeInfo(String topic, Set info) { Map subTable = this.rebalanceImpl.getSubscriptionInner(); if (subTable != null) { if (subTable.containsKey(topic)) { this.rebalanceImpl.getTopicSubscribeInfoTable().put(topic, info); } } } @Override public boolean isSubscribeTopicNeedUpdate(String topic) { Map subTable = this.rebalanceImpl.getSubscriptionInner(); if (subTable != null) { if (subTable.containsKey(topic)) { return !this.rebalanceImpl.topicSubscribeInfoTable.containsKey(topic); } } return false; } @Override public boolean isUnitMode() { return this.defaultMQPullConsumer.isUnitMode(); } @Override public ConsumerRunningInfo consumerRunningInfo() { ConsumerRunningInfo info = new ConsumerRunningInfo(); Properties prop = MixAll.object2Properties(this.defaultMQPullConsumer); prop.put(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP, String.valueOf(this.consumerStartTimestamp)); info.setProperties(prop); info.getSubscriptionSet().addAll(this.subscriptions()); return info; } public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { pull(mq, subExpression, offset, maxNums, pullCallback, this.defaultMQPullConsumer.getConsumerPullTimeoutMillis()); } public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression); this.pullAsyncImpl(mq, subscriptionData, offset, maxNums, pullCallback, false, timeout); } public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, int maxSize, PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression); this.pullAsyncImpl(mq, subscriptionData, offset, maxNums, maxSize, pullCallback, false, timeout); } public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { pull(mq, messageSelector, offset, maxNums, pullCallback, this.defaultMQPullConsumer.getConsumerPullTimeoutMillis()); } public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, PullCallback pullCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, messageSelector); this.pullAsyncImpl(mq, subscriptionData, offset, maxNums, pullCallback, false, timeout); } private void pullAsyncImpl( final MessageQueue mq, final SubscriptionData subscriptionData, final long offset, final int maxNums, final int maxSizeInBytes, final PullCallback pullCallback, final boolean block, final long timeout) throws MQClientException, RemotingException, InterruptedException { this.isRunning(); if (null == mq) { throw new MQClientException("mq is null", null); } if (offset < 0) { throw new MQClientException("offset < 0", null); } if (maxNums <= 0) { throw new MQClientException("maxNums <= 0", null); } if (maxSizeInBytes <= 0) { throw new MQClientException("maxSizeInBytes <= 0", null); } if (null == pullCallback) { throw new MQClientException("pullCallback is null", null); } this.subscriptionAutomatically(mq.getTopic()); try { int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false); long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout; boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType()); this.pullAPIWrapper.pullKernelImpl( mq, subscriptionData.getSubString(), subscriptionData.getExpressionType(), isTagType ? 0L : subscriptionData.getSubVersion(), offset, maxNums, maxSizeInBytes, sysFlag, 0, this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(), timeoutMillis, CommunicationMode.ASYNC, new PullCallback() { @Override public void onSuccess(PullResult pullResult) { PullResult userPullResult = DefaultMQPullConsumerImpl.this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData); resetTopic(userPullResult.getMsgFoundList()); pullCallback.onSuccess(userPullResult); } @Override public void onException(Throwable e) { pullCallback.onException(e); } }); } catch (MQBrokerException e) { throw new MQClientException("pullAsync unknown exception", e); } } private void pullAsyncImpl( final MessageQueue mq, final SubscriptionData subscriptionData, final long offset, final int maxNums, final PullCallback pullCallback, final boolean block, final long timeout) throws MQClientException, RemotingException, InterruptedException { pullAsyncImpl( mq, subscriptionData, offset, maxNums, Integer.MAX_VALUE, pullCallback, block, timeout ); } public PullResult pullBlockIfNotFound(MessageQueue mq, String subExpression, long offset, int maxNums) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression); return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, true, this.getDefaultMQPullConsumer().getConsumerPullTimeoutMillis()); } public DefaultMQPullConsumer getDefaultMQPullConsumer() { return defaultMQPullConsumer; } public void pullBlockIfNotFound(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression); this.pullAsyncImpl(mq, subscriptionData, offset, maxNums, pullCallback, true, this.getDefaultMQPullConsumer().getConsumerPullTimeoutMillis()); } public void pullBlockIfNotFoundWithMessageSelector(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, PullCallback pullCallback) throws MQClientException, RemotingException, InterruptedException { SubscriptionData subscriptionData = getSubscriptionData(mq, messageSelector); this.pullAsyncImpl(mq, subscriptionData, offset, maxNums, pullCallback, true, this.getDefaultMQPullConsumer().getConsumerPullTimeoutMillis()); } public PullResult pullBlockIfNotFoundWithMessageSelector(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { SubscriptionData subscriptionData = getSubscriptionData(mq, messageSelector); return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, true, this.getDefaultMQPullConsumer().getConsumerPullTimeoutMillis()); } public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().queryMessage(topic, key, maxNum, begin, end); } public MessageExt queryMessageByUniqKey(String topic, String uniqKey) throws MQClientException, InterruptedException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().queryMessageByUniqKey(topic, uniqKey); } public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } public void sendMessageBack(MessageExt msg, int delayLevel, final String brokerName) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { sendMessageBack(msg, delayLevel, brokerName, this.defaultMQPullConsumer.getConsumerGroup()); } public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.offsetStore.updateConsumeOffsetToBroker(mq, offset, isOneway); } @Deprecated public void sendMessageBack(MessageExt msg, int delayLevel, final String brokerName, String consumerGroup) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { try { String destBrokerName = brokerName; if (destBrokerName != null && destBrokerName.startsWith(MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX)) { destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(this.defaultMQPullConsumer.queueWithNamespace(new MessageQueue(msg.getTopic(), msg.getBrokerName(), msg.getQueueId()))); } String brokerAddr = (null != destBrokerName) ? this.mQClientFactory.findBrokerAddressInPublish(destBrokerName) : RemotingHelper.parseSocketAddressAddr(msg.getStoreHost()); if (UtilAll.isBlank(brokerAddr)) { throw new MQClientException("Broker[" + destBrokerName + "] master node does not exist", null); } if (UtilAll.isBlank(consumerGroup)) { consumerGroup = this.defaultMQPullConsumer.getConsumerGroup(); } this.mQClientFactory.getMQClientAPIImpl().consumerSendMessageBack(brokerAddr, brokerName, msg, consumerGroup, delayLevel, 3000, this.defaultMQPullConsumer.getMaxReconsumeTimes()); } catch (Exception e) { log.error("sendMessageBack Exception, " + this.defaultMQPullConsumer.getConsumerGroup(), e); Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPullConsumer.getConsumerGroup()), msg.getBody()); String originMsgId = MessageAccessor.getOriginMessageId(msg); MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId); newMsg.setFlag(msg.getFlag()); MessageAccessor.setProperties(newMsg, msg.getProperties()); MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic()); MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes() + 1)); MessageAccessor.setMaxReconsumeTimes(newMsg, String.valueOf(this.defaultMQPullConsumer.getMaxReconsumeTimes())); newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes()); this.mQClientFactory.getDefaultMQProducer().send(newMsg); } finally { msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPullConsumer.getNamespace())); } } public synchronized void shutdown() { switch (this.serviceState) { case CREATE_JUST: break; case RUNNING: this.persistConsumerOffset(); this.mQClientFactory.unregisterConsumer(this.defaultMQPullConsumer.getConsumerGroup()); this.mQClientFactory.shutdown(); log.info("the consumer [{}] shutdown OK", this.defaultMQPullConsumer.getConsumerGroup()); this.serviceState = ServiceState.SHUTDOWN_ALREADY; break; case SHUTDOWN_ALREADY: break; default: break; } } public synchronized void start() throws MQClientException { switch (this.serviceState) { case CREATE_JUST: this.serviceState = ServiceState.START_FAILED; this.checkConfig(); this.copySubscription(); if (this.defaultMQPullConsumer.getMessageModel() == MessageModel.CLUSTERING) { this.defaultMQPullConsumer.changeInstanceNameToPID(); } this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPullConsumer, this.rpcHook); this.rebalanceImpl.setConsumerGroup(this.defaultMQPullConsumer.getConsumerGroup()); this.rebalanceImpl.setMessageModel(this.defaultMQPullConsumer.getMessageModel()); this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPullConsumer.getAllocateMessageQueueStrategy()); this.rebalanceImpl.setmQClientFactory(this.mQClientFactory); this.pullAPIWrapper = new PullAPIWrapper( mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup(), isUnitMode()); this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList); if (this.defaultMQPullConsumer.getOffsetStore() != null) { this.offsetStore = this.defaultMQPullConsumer.getOffsetStore(); } else { switch (this.defaultMQPullConsumer.getMessageModel()) { case BROADCASTING: this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup()); break; case CLUSTERING: this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup()); break; default: break; } this.defaultMQPullConsumer.setOffsetStore(this.offsetStore); } this.offsetStore.load(); boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPullConsumer.getConsumerGroup(), this); if (!registerOK) { this.serviceState = ServiceState.CREATE_JUST; throw new MQClientException("The consumer group[" + this.defaultMQPullConsumer.getConsumerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null); } mQClientFactory.start(); log.info("the consumer [{}] start OK", this.defaultMQPullConsumer.getConsumerGroup()); this.serviceState = ServiceState.RUNNING; break; case RUNNING: case START_FAILED: case SHUTDOWN_ALREADY: throw new MQClientException("The PullConsumer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); default: break; } } private void checkConfig() throws MQClientException { // check consumerGroup Validators.checkGroup(this.defaultMQPullConsumer.getConsumerGroup()); // consumerGroup if (null == this.defaultMQPullConsumer.getConsumerGroup()) { throw new MQClientException( "consumerGroup is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // consumerGroup if (this.defaultMQPullConsumer.getConsumerGroup().equals(MixAll.DEFAULT_CONSUMER_GROUP)) { throw new MQClientException( "consumerGroup can not equal " + MixAll.DEFAULT_CONSUMER_GROUP + ", please specify another one." + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // messageModel if (null == this.defaultMQPullConsumer.getMessageModel()) { throw new MQClientException( "messageModel is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // allocateMessageQueueStrategy if (null == this.defaultMQPullConsumer.getAllocateMessageQueueStrategy()) { throw new MQClientException( "allocateMessageQueueStrategy is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // allocateMessageQueueStrategy if (this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() < this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis()) { throw new MQClientException( "Long polling mode, the consumer consumerTimeoutMillisWhenSuspend must greater than brokerSuspendMaxTimeMillis" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } } private void copySubscription() throws MQClientException { try { Set registerTopics = this.defaultMQPullConsumer.getRegisterTopics(); if (registerTopics != null) { for (final String topic : registerTopics) { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, SubscriptionData.SUB_ALL); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); } } } catch (Exception e) { throw new MQClientException("subscription exception", e); } } public void updateConsumeOffset(MessageQueue mq, long offset) throws MQClientException { this.isRunning(); this.offsetStore.updateOffset(mq, offset, false); } public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.isRunning(); return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId); } public void registerFilterMessageHook(final FilterMessageHook hook) { this.filterMessageHookList.add(hook); log.info("register FilterMessageHook Hook, {}", hook.hookName()); } public OffsetStore getOffsetStore() { return offsetStore; } public void setOffsetStore(OffsetStore offsetStore) { this.offsetStore = offsetStore; } public PullAPIWrapper getPullAPIWrapper() { return pullAPIWrapper; } public void setPullAPIWrapper(PullAPIWrapper pullAPIWrapper) { this.pullAPIWrapper = pullAPIWrapper; } public ServiceState getServiceState() { return serviceState; } //Don't use this deprecated setter, which will be removed soon. @Deprecated public void setServiceState(ServiceState serviceState) { this.serviceState = serviceState; } public long getConsumerStartTimestamp() { return consumerStartTimestamp; } public RebalanceImpl getRebalanceImpl() { return rebalanceImpl; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentMap; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageQueueListener; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.listener.MessageListener; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.hook.FilterMessageContext; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatus; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.PopProcessQueueInfo; import org.apache.rocketmq.remoting.protocol.body.ProcessQueueInfo; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class DefaultMQPushConsumerImpl implements MQConsumerInner { /** * Delay some time when exception occur */ private long pullTimeDelayMillsWhenException = 3000; /** * Flow control interval when message cache is full */ private static final long PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL = 50; /** * Flow control interval when broker return flow control */ private static final long PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL = 20; /** * Delay some time when suspend pull service */ private static final long PULL_TIME_DELAY_MILLS_WHEN_SUSPEND = 1000; private static final long BROKER_SUSPEND_MAX_TIME_MILLIS = 1000 * 15; private static final long CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND = 1000 * 30; private static final Logger log = LoggerFactory.getLogger(DefaultMQPushConsumerImpl.class); private final DefaultMQPushConsumer defaultMQPushConsumer; private final RebalanceImpl rebalanceImpl = new RebalancePushImpl(this); private final ArrayList filterMessageHookList = new ArrayList<>(); private final long consumerStartTimestamp = System.currentTimeMillis(); private final ArrayList consumeMessageHookList = new ArrayList<>(); private final RPCHook rpcHook; private volatile ServiceState serviceState = ServiceState.CREATE_JUST; private MQClientInstance mQClientFactory; private PullAPIWrapper pullAPIWrapper; private volatile boolean pause = false; private boolean consumeOrderly = false; private MessageListener messageListenerInner; private OffsetStore offsetStore; private ConsumeMessageService consumeMessageService; private ConsumeMessageService consumeMessagePopService; private long queueFlowControlTimes = 0; private long queueMaxSpanFlowControlTimes = 0; //10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h private final int[] popDelayLevel = new int[] {10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200}; private static final int MAX_POP_INVISIBLE_TIME = 300000; private static final int MIN_POP_INVISIBLE_TIME = 5000; private static final int ASYNC_TIMEOUT = 3000; // only for test purpose, will be modified by reflection in unit test. @SuppressWarnings("FieldMayBeFinal") private static boolean doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged = false; public DefaultMQPushConsumerImpl(DefaultMQPushConsumer defaultMQPushConsumer, RPCHook rpcHook) { this.defaultMQPushConsumer = defaultMQPushConsumer; this.rpcHook = rpcHook; this.pullTimeDelayMillsWhenException = defaultMQPushConsumer.getPullTimeDelayMillsWhenException(); } public void registerFilterMessageHook(final FilterMessageHook hook) { this.filterMessageHookList.add(hook); log.info("register FilterMessageHook Hook, {}", hook.hookName()); } public boolean hasHook() { return !this.consumeMessageHookList.isEmpty(); } public void registerConsumeMessageHook(final ConsumeMessageHook hook) { this.consumeMessageHookList.add(hook); log.info("register consumeMessageHook Hook, {}", hook.hookName()); } public void executeHookBefore(final ConsumeMessageContext context) { if (!this.consumeMessageHookList.isEmpty()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageBefore(context); } catch (Throwable e) { log.warn("consumeMessageHook {} executeHookBefore exception", hook.hookName(), e); } } } } public void executeHookAfter(final ConsumeMessageContext context) { if (!this.consumeMessageHookList.isEmpty()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageAfter(context); } catch (Throwable e) { log.warn("consumeMessageHook {} executeHookAfter exception", hook.hookName(), e); } } } } public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { createTopic(key, newTopic, queueNum, 0); } public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag, null); } public Set fetchSubscribeMessageQueues(String topic) throws MQClientException { Set result = this.rebalanceImpl.getTopicSubscribeInfoTable().get(topic); if (null == result) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); result = this.rebalanceImpl.getTopicSubscribeInfoTable().get(topic); } if (null == result) { throw new MQClientException("The topic[" + topic + "] not exist", null); } return parseSubscribeMessageQueues(result); } public Set parseSubscribeMessageQueues(Set messageQueueList) { Set resultQueues = new HashSet<>(); for (MessageQueue queue : messageQueueList) { String userTopic = NamespaceUtil.withoutNamespace(queue.getTopic(), this.defaultMQPushConsumer.getNamespace()); resultQueues.add(new MessageQueue(userTopic, queue.getBrokerName(), queue.getQueueId())); } return resultQueues; } public DefaultMQPushConsumer getDefaultMQPushConsumer() { return defaultMQPushConsumer; } public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().earliestMsgStoreTime(mq); } public long maxOffset(MessageQueue mq) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } public long minOffset(MessageQueue mq) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().minOffset(mq); } public OffsetStore getOffsetStore() { return offsetStore; } public void setOffsetStore(OffsetStore offsetStore) { this.offsetStore = offsetStore; } public void pullMessage(final PullRequest pullRequest) { final ProcessQueue processQueue = pullRequest.getProcessQueue(); if (processQueue.isDropped()) { log.info("the pull request[{}] is dropped.", pullRequest.toString()); return; } pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis()); try { this.makeSureStateOK(); } catch (MQClientException e) { log.warn("pullMessage exception, consumer state not ok", e); this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); return; } if (this.isPause()) { log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup()); this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND); return; } long cachedMessageCount = processQueue.getMsgCount().get(); long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024); if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) { this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn( "the cached message count exceeds the threshold {}, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}", this.defaultMQPushConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes); } return; } if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) { this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn( "the cached message size exceeds the threshold {} MiB, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}", this.defaultMQPushConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes); } return; } if (!this.consumeOrderly) { if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) { this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL); if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) { log.warn( "the queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, pullRequest={}, flowControlTimes={}", processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(), pullRequest, queueMaxSpanFlowControlTimes); } return; } } else { if (processQueue.isLocked()) { if (!pullRequest.isPreviouslyLocked()) { long offset = -1L; try { offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue()); if (offset < 0) { throw new MQClientException(ResponseCode.SYSTEM_ERROR, "Unexpected offset " + offset); } } catch (Exception e) { this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); log.error("Failed to compute pull offset, pullResult: {}", pullRequest, e); return; } boolean brokerBusy = offset < pullRequest.getNextOffset(); log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}", pullRequest, offset, brokerBusy); if (brokerBusy) { log.info("[NOTIFYME]the first time to pull message, but pull request offset larger than broker consume offset. pullRequest: {} NewOffset: {}", pullRequest, offset); } pullRequest.setPreviouslyLocked(true); pullRequest.setNextOffset(offset); } } else { this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); log.info("pull message later because not locked in broker, {}", pullRequest); return; } } final MessageQueue messageQueue = pullRequest.getMessageQueue(); final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(messageQueue.getTopic()); if (null == subscriptionData) { this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); log.warn("find the consumer's subscription failed, {}", pullRequest); return; } final long beginTimestamp = System.currentTimeMillis(); PullCallback pullCallback = new PullCallback() { @Override public void onSuccess(PullResult pullResult) { if (pullResult != null) { pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult, subscriptionData); switch (pullResult.getPullStatus()) { case FOUND: long prevRequestOffset = pullRequest.getNextOffset(); pullRequest.setNextOffset(pullResult.getNextBeginOffset()); long pullRT = System.currentTimeMillis() - beginTimestamp; DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(pullRequest.getConsumerGroup(), pullRequest.getMessageQueue().getTopic(), pullRT); long firstMsgOffset = Long.MAX_VALUE; if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) { DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); } else { firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset(); DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(), pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size()); boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList()); DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest( pullResult.getMsgFoundList(), processQueue, pullRequest.getMessageQueue(), dispatchToConsume); if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) { DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval()); } else { DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); } } if (pullResult.getNextBeginOffset() < prevRequestOffset || firstMsgOffset < prevRequestOffset) { log.warn( "[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}", pullResult.getNextBeginOffset(), firstMsgOffset, prevRequestOffset); } break; case NO_NEW_MSG: case NO_MATCHED_MSG: pullRequest.setNextOffset(pullResult.getNextBeginOffset()); DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest); DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); break; case OFFSET_ILLEGAL: log.warn("the pull request offset illegal, {} {}", pullRequest.toString(), pullResult.toString()); pullRequest.setNextOffset(pullResult.getNextBeginOffset()); pullRequest.getProcessQueue().setDropped(true); DefaultMQPushConsumerImpl.this.executeTask(new Runnable() { @Override public void run() { try { DefaultMQPushConsumerImpl.this.offsetStore.updateAndFreezeOffset(pullRequest.getMessageQueue(), pullRequest.getNextOffset()); DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue()); // removeProcessQueue will also remove offset to cancel the frozen status. DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue()); DefaultMQPushConsumerImpl.this.rebalanceImpl.getmQClientFactory().rebalanceImmediately(); log.warn("fix the pull request offset, {}", pullRequest); } catch (Throwable e) { log.error("executeTaskLater Exception", e); } } }); break; default: break; } } } @Override public void onException(Throwable e) { if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { if (e instanceof MQBrokerException && ((MQBrokerException) e).getResponseCode() == ResponseCode.SUBSCRIPTION_NOT_LATEST) { log.warn("the subscription is not latest, group={}, messageQueue={}", groupName(), messageQueue); } else { log.warn("execute the pull request exception, group={}, messageQueue={}", groupName(), messageQueue, e); } } if (e instanceof MQBrokerException && ((MQBrokerException) e).getResponseCode() == ResponseCode.FLOW_CONTROL) { DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL); } else { DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); } } }; boolean commitOffsetEnable = false; long commitOffsetValue = 0L; if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) { commitOffsetValue = this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY); if (commitOffsetValue > 0) { commitOffsetEnable = true; } } String subExpression = null; boolean classFilter = false; SubscriptionData sd = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic()); if (sd != null) { if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) { subExpression = sd.getSubString(); } classFilter = sd.isClassFilterMode(); } int sysFlag = PullSysFlag.buildSysFlag( commitOffsetEnable, // commitOffset true, // suspend subExpression != null, // subscription classFilter // class filter ); try { this.pullAPIWrapper.pullKernelImpl( pullRequest.getMessageQueue(), subExpression, subscriptionData.getExpressionType(), subscriptionData.getSubVersion(), pullRequest.getNextOffset(), this.defaultMQPushConsumer.getPullBatchSize(), this.defaultMQPushConsumer.getPullBatchSizeInBytes(), sysFlag, commitOffsetValue, BROKER_SUSPEND_MAX_TIME_MILLIS, CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND, CommunicationMode.ASYNC, pullCallback ); } catch (Exception e) { log.error("pullKernelImpl exception", e); this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); } } void popMessage(final PopRequest popRequest) { final PopProcessQueue processQueue = popRequest.getPopProcessQueue(); if (processQueue.isDropped()) { log.info("the pop request[{}] is dropped.", popRequest.toString()); return; } processQueue.setLastPopTimestamp(System.currentTimeMillis()); try { this.makeSureStateOK(); } catch (MQClientException e) { log.warn("popMessage exception, consumer state not ok", e); this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException); return; } if (this.isPause()) { log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup()); this.executePopPullRequestLater(popRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND); return; } if (processQueue.getWaiAckMsgCount() > this.defaultMQPushConsumer.getPopThresholdForQueue()) { this.executePopPullRequestLater(popRequest, PULL_TIME_DELAY_MILLS_WHEN_CACHE_FLOW_CONTROL); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn("the messages waiting to ack exceeds the threshold {}, so do flow control, popRequest={}, flowControlTimes={}, wait count={}", this.defaultMQPushConsumer.getPopThresholdForQueue(), popRequest, queueFlowControlTimes, processQueue.getWaiAckMsgCount()); } return; } //POPTODO think of pop mode orderly implementation later. final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(popRequest.getMessageQueue().getTopic()); if (null == subscriptionData) { this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException); log.warn("find the consumer's subscription failed, {}", popRequest); return; } final long beginTimestamp = System.currentTimeMillis(); PopCallback popCallback = new PopCallback() { @Override public void onSuccess(PopResult popResult) { if (popResult == null) { log.error("pop callback popResult is null"); DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest); return; } processPopResult(popResult, subscriptionData); switch (popResult.getPopStatus()) { case FOUND: long pullRT = System.currentTimeMillis() - beginTimestamp; DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(popRequest.getConsumerGroup(), popRequest.getMessageQueue().getTopic(), pullRT); if (popResult.getMsgFoundList() == null || popResult.getMsgFoundList().isEmpty()) { DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest); } else { DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(popRequest.getConsumerGroup(), popRequest.getMessageQueue().getTopic(), popResult.getMsgFoundList().size()); popRequest.getPopProcessQueue().incFoundMsg(popResult.getMsgFoundList().size()); DefaultMQPushConsumerImpl.this.consumeMessagePopService.submitPopConsumeRequest( popResult.getMsgFoundList(), processQueue, popRequest.getMessageQueue()); if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) { DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval()); } else { DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest); } } break; case NO_NEW_MSG: case POLLING_NOT_FOUND: DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest); break; case POLLING_FULL: default: DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException); break; } } @Override public void onException(Throwable e) { if (!popRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { log.warn("execute the pull request exception: {}", e); } if (e instanceof MQBrokerException && ((MQBrokerException) e).getResponseCode() == ResponseCode.FLOW_CONTROL) { DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL); } else { DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException); } } }; try { long invisibleTime = this.defaultMQPushConsumer.getPopInvisibleTime(); if (invisibleTime < MIN_POP_INVISIBLE_TIME || invisibleTime > MAX_POP_INVISIBLE_TIME) { invisibleTime = 60000; } this.pullAPIWrapper.popAsync(popRequest.getMessageQueue(), invisibleTime, this.defaultMQPushConsumer.getPopBatchNums(), popRequest.getConsumerGroup(), BROKER_SUSPEND_MAX_TIME_MILLIS, popCallback, true, popRequest.getInitMode(), false, subscriptionData.getExpressionType(), subscriptionData.getSubString()); } catch (Exception e) { log.error("popAsync exception", e); this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException); } } private PopResult processPopResult(final PopResult popResult, final SubscriptionData subscriptionData) { if (PopStatus.FOUND == popResult.getPopStatus()) { List msgFoundList = popResult.getMsgFoundList(); List msgListFilterAgain = new ArrayList<>(popResult.getMsgFoundList().size()); if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode() && popResult.getMsgFoundList().size() > 0) { for (MessageExt msg : popResult.getMsgFoundList()) { if (msg.getTags() != null) { if (subscriptionData.getTagsSet().contains(msg.getTags())) { msgListFilterAgain.add(msg); } } } } else { msgListFilterAgain.addAll(msgFoundList); } if (!this.filterMessageHookList.isEmpty()) { FilterMessageContext filterMessageContext = new FilterMessageContext(); filterMessageContext.setUnitMode(this.defaultMQPushConsumer.isUnitMode()); filterMessageContext.setMsgList(msgListFilterAgain); if (!this.filterMessageHookList.isEmpty()) { for (FilterMessageHook hook : this.filterMessageHookList) { try { hook.filterMessage(filterMessageContext); } catch (Throwable e) { log.error("execute hook error. hookName={}", hook.hookName()); } } } } Iterator iterator = msgListFilterAgain.iterator(); while (iterator.hasNext()) { MessageExt msg = iterator.next(); if (msg.getReconsumeTimes() > getMaxReconsumeTimes()) { iterator.remove(); log.info("Reconsume times has reached {}, so ack msg={}", msg.getReconsumeTimes(), msg); } } if (msgFoundList.size() != msgListFilterAgain.size()) { for (MessageExt msg : msgFoundList) { if (!msgListFilterAgain.contains(msg)) { ackAsync(msg, this.groupName()); } } } popResult.setMsgFoundList(msgListFilterAgain); } return popResult; } private void makeSureStateOK() throws MQClientException { if (this.serviceState != ServiceState.RUNNING) { throw new MQClientException("The consumer service state not OK, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); } } void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) { this.mQClientFactory.getPullMessageService().executePullRequestLater(pullRequest, timeDelay); } public boolean isPause() { return pause; } public void setPause(boolean pause) { this.pause = pause; } public ConsumerStatsManager getConsumerStatsManager() { return this.mQClientFactory.getConsumerStatsManager(); } public void executePullRequestImmediately(final PullRequest pullRequest) { this.mQClientFactory.getPullMessageService().executePullRequestImmediately(pullRequest); } void executePopPullRequestLater(final PopRequest pullRequest, final long timeDelay) { this.mQClientFactory.getPullMessageService().executePopPullRequestLater(pullRequest, timeDelay); } void executePopPullRequestImmediately(final PopRequest pullRequest) { this.mQClientFactory.getPullMessageService().executePopPullRequestImmediately(pullRequest); } private void correctTagsOffset(final PullRequest pullRequest) { if (0L == pullRequest.getProcessQueue().getMsgCount().get()) { this.offsetStore.updateOffset(pullRequest.getMessageQueue(), pullRequest.getNextOffset(), true); } } public void executeTaskLater(final Runnable r, final long timeDelay) { this.mQClientFactory.getPullMessageService().executeTaskLater(r, timeDelay); } public void executeTask(final Runnable r) { this.mQClientFactory.getPullMessageService().executeTask(r); } public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return this.mQClientFactory.getMQAdminImpl().queryMessage(topic, key, maxNum, begin, end); } public MessageExt queryMessageByUniqKey(String topic, String uniqKey) throws MQClientException, InterruptedException { return this.mQClientFactory.getMQAdminImpl().queryMessageByUniqKey(topic, uniqKey); } public void registerMessageListener(MessageListener messageListener) { this.messageListenerInner = messageListener; } public void resume() { this.pause = false; doRebalance(); log.info("resume this consumer, {}", this.defaultMQPushConsumer.getConsumerGroup()); } @Deprecated public void sendMessageBack(MessageExt msg, int delayLevel, final String brokerName) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { sendMessageBack(msg, delayLevel, brokerName, null); } public void sendMessageBack(MessageExt msg, int delayLevel, final MessageQueue mq) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { sendMessageBack(msg, delayLevel, msg.getBrokerName(), mq); } private void sendMessageBack(MessageExt msg, int delayLevel, final String brokerName, final MessageQueue mq) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { boolean needRetry = true; try { if (brokerName != null && brokerName.startsWith(MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX) || mq != null && mq.getBrokerName().startsWith(MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX)) { needRetry = false; sendMessageBackAsNormalMessage(msg); } else { String brokerAddr = (null != brokerName) ? this.mQClientFactory.findBrokerAddressInPublish(brokerName) : RemotingHelper.parseSocketAddressAddr(msg.getStoreHost()); if (UtilAll.isBlank(brokerAddr)) { throw new MQClientException("Broker[" + brokerName + "] master node does not exist", null); } this.mQClientFactory.getMQClientAPIImpl().consumerSendMessageBack(brokerAddr, brokerName, msg, this.defaultMQPushConsumer.getConsumerGroup(), delayLevel, 5000, getMaxReconsumeTimes()); } } catch (Throwable t) { log.error("Failed to send message back, consumerGroup={}, brokerName={}, mq={}, message={}", this.defaultMQPushConsumer.getConsumerGroup(), brokerName, mq, msg, t); if (needRetry) { sendMessageBackAsNormalMessage(msg); } } finally { msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace())); } } private void sendMessageBackAsNormalMessage(MessageExt msg) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody()); MessageAccessor.setProperties(newMsg, msg.getProperties()); String originMsgId = MessageAccessor.getOriginMessageId(msg); MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId); newMsg.setFlag(msg.getFlag()); MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic()); MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes() + 1)); MessageAccessor.setMaxReconsumeTimes(newMsg, String.valueOf(getMaxReconsumeTimes())); MessageAccessor.clearProperty(newMsg, MessageConst.PROPERTY_TRANSACTION_PREPARED); newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes()); this.mQClientFactory.getDefaultMQProducer().send(newMsg); } void ackAsync(MessageExt message, String consumerGroup) { final String extraInfo = message.getProperty(MessageConst.PROPERTY_POP_CK); try { String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo); String brokerName = ExtraInfoUtil.getBrokerName(extraInfoStrs); int queueId = ExtraInfoUtil.getQueueId(extraInfoStrs); long queueOffset = ExtraInfoUtil.getQueueOffset(extraInfoStrs); String topic = message.getTopic(); String desBrokerName = brokerName; if (brokerName != null && brokerName.startsWith(MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX)) { desBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(this.defaultMQPushConsumer.queueWithNamespace(new MessageQueue(topic, brokerName, queueId))); } FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(desBrokerName, MixAll.MASTER_ID, true); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(desBrokerName, MixAll.MASTER_ID, true); } if (findBrokerResult == null) { log.error("The broker[" + desBrokerName + "] not exist"); return; } AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(ExtraInfoUtil.getRealTopic(extraInfoStrs, topic, consumerGroup)); requestHeader.setQueueId(queueId); requestHeader.setOffset(queueOffset); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setExtraInfo(extraInfo); requestHeader.setBrokerName(brokerName); this.mQClientFactory.getMQClientAPIImpl().ackMessageAsync(findBrokerResult.getBrokerAddr(), ASYNC_TIMEOUT, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { if (ackResult != null && !AckStatus.OK.equals(ackResult.getStatus())) { log.warn("Ack message fail. ackResult: {}, extraInfo: {}", ackResult, extraInfo); } } @Override public void onException(Throwable e) { log.warn("Ack message fail. extraInfo: {} error message: {}", extraInfo, e.toString()); } }, requestHeader); } catch (Throwable t) { log.error("ack async error.", t); } } void changePopInvisibleTimeAsync(String topic, String consumerGroup, String extraInfo, long invisibleTime, AckCallback callback) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo); String brokerName = ExtraInfoUtil.getBrokerName(extraInfoStrs); int queueId = ExtraInfoUtil.getQueueId(extraInfoStrs); String desBrokerName = brokerName; if (brokerName != null && brokerName.startsWith(MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX)) { desBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(this.defaultMQPushConsumer.queueWithNamespace(new MessageQueue(topic, brokerName, queueId))); } FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(desBrokerName, MixAll.MASTER_ID, true); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(desBrokerName, MixAll.MASTER_ID, true); } if (findBrokerResult != null) { ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(ExtraInfoUtil.getRealTopic(extraInfoStrs, topic, consumerGroup)); requestHeader.setQueueId(queueId); requestHeader.setOffset(ExtraInfoUtil.getQueueOffset(extraInfoStrs)); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setBrokerName(brokerName); //here the broker should be polished this.mQClientFactory.getMQClientAPIImpl().changeInvisibleTimeAsync(brokerName, findBrokerResult.getBrokerAddr(), requestHeader, ASYNC_TIMEOUT, callback); return; } throw new MQClientException("The broker[" + desBrokerName + "] not exist", null); } public int getMaxReconsumeTimes() { // default reconsume times: 16 if (this.defaultMQPushConsumer.getMaxReconsumeTimes() == -1) { return 16; } else { return this.defaultMQPushConsumer.getMaxReconsumeTimes(); } } public void shutdown() { shutdown(0); } public synchronized void shutdown(long awaitTerminateMillis) { switch (this.serviceState) { case CREATE_JUST: break; case RUNNING: this.consumeMessageService.shutdown(awaitTerminateMillis); this.persistConsumerOffset(); this.mQClientFactory.unregisterConsumer(this.defaultMQPushConsumer.getConsumerGroup()); this.mQClientFactory.shutdown(); log.info("the consumer [{}] shutdown OK", this.defaultMQPushConsumer.getConsumerGroup()); this.rebalanceImpl.destroy(); this.serviceState = ServiceState.SHUTDOWN_ALREADY; break; case SHUTDOWN_ALREADY: break; default: break; } } public synchronized void start() throws MQClientException { switch (this.serviceState) { case CREATE_JUST: log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(), this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode()); this.serviceState = ServiceState.START_FAILED; this.checkConfig(); this.copySubscription(); if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) { this.defaultMQPushConsumer.changeInstanceNameToPID(); } this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook); this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup()); this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel()); this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy()); this.rebalanceImpl.setmQClientFactory(this.mQClientFactory); if (this.pullAPIWrapper == null) { this.pullAPIWrapper = new PullAPIWrapper( mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode()); } this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList); if (this.defaultMQPushConsumer.getOffsetStore() != null) { this.offsetStore = this.defaultMQPushConsumer.getOffsetStore(); } else { switch (this.defaultMQPushConsumer.getMessageModel()) { case BROADCASTING: this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup()); break; case CLUSTERING: this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup()); break; default: break; } this.defaultMQPushConsumer.setOffsetStore(this.offsetStore); } this.offsetStore.load(); if (this.getMessageListenerInner() instanceof MessageListenerOrderly) { this.consumeOrderly = true; this.consumeMessageService = new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner()); //POPTODO reuse Executor ? this.consumeMessagePopService = new ConsumeMessagePopOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner()); } else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) { this.consumeOrderly = false; this.consumeMessageService = new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner()); //POPTODO reuse Executor ? this.consumeMessagePopService = new ConsumeMessagePopConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner()); } this.consumeMessageService.start(); // POPTODO this.consumeMessagePopService.start(); boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this); if (!registerOK) { this.serviceState = ServiceState.CREATE_JUST; this.consumeMessageService.shutdown(defaultMQPushConsumer.getAwaitTerminationMillisWhenShutdown()); throw new MQClientException("The consumer group[" + this.defaultMQPushConsumer.getConsumerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null); } mQClientFactory.start(); log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup()); this.serviceState = ServiceState.RUNNING; break; case RUNNING: case START_FAILED: case SHUTDOWN_ALREADY: throw new MQClientException("The PushConsumer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); default: break; } try { this.updateTopicSubscribeInfoWhenSubscriptionChanged(); this.mQClientFactory.checkClientInBroker(); if (this.mQClientFactory.sendHeartbeatToAllBrokerWithLock()) { this.mQClientFactory.rebalanceImmediately(); } } catch (Exception e) { log.warn("Start the consumer {} fail.", this.defaultMQPushConsumer.getConsumerGroup(), e); shutdown(); throw e; } } private void checkConfig() throws MQClientException { Validators.checkGroup(this.defaultMQPushConsumer.getConsumerGroup()); if (null == this.defaultMQPushConsumer.getConsumerGroup()) { throw new MQClientException( "consumerGroup is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } if (this.defaultMQPushConsumer.getConsumerGroup().equals(MixAll.DEFAULT_CONSUMER_GROUP)) { throw new MQClientException( "consumerGroup can not equal " + MixAll.DEFAULT_CONSUMER_GROUP + ", please specify another one." + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } if (null == this.defaultMQPushConsumer.getMessageModel()) { throw new MQClientException( "messageModel is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } if (null == this.defaultMQPushConsumer.getConsumeFromWhere()) { throw new MQClientException( "consumeFromWhere is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } Date dt = UtilAll.parseDate(this.defaultMQPushConsumer.getConsumeTimestamp(), UtilAll.YYYYMMDDHHMMSS); if (null == dt) { throw new MQClientException( "consumeTimestamp is invalid, the valid format is yyyyMMddHHmmss,but received " + this.defaultMQPushConsumer.getConsumeTimestamp() + " " + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // allocateMessageQueueStrategy if (null == this.defaultMQPushConsumer.getAllocateMessageQueueStrategy()) { throw new MQClientException( "allocateMessageQueueStrategy is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // subscription if (null == this.defaultMQPushConsumer.getSubscription()) { throw new MQClientException( "subscription is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // messageListener if (null == this.defaultMQPushConsumer.getMessageListener()) { throw new MQClientException( "messageListener is null" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } boolean orderly = this.defaultMQPushConsumer.getMessageListener() instanceof MessageListenerOrderly; boolean concurrently = this.defaultMQPushConsumer.getMessageListener() instanceof MessageListenerConcurrently; if (!orderly && !concurrently) { throw new MQClientException( "messageListener must be instanceof MessageListenerOrderly or MessageListenerConcurrently" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // consumeThreadMin if (this.defaultMQPushConsumer.getConsumeThreadMin() < 1 || this.defaultMQPushConsumer.getConsumeThreadMin() > 1000) { throw new MQClientException( "consumeThreadMin Out of range [1, 1000]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // consumeThreadMax if (this.defaultMQPushConsumer.getConsumeThreadMax() < 1 || this.defaultMQPushConsumer.getConsumeThreadMax() > 1000) { throw new MQClientException( "consumeThreadMax Out of range [1, 1000]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // consumeThreadMin can't be larger than consumeThreadMax if (this.defaultMQPushConsumer.getConsumeThreadMin() > this.defaultMQPushConsumer.getConsumeThreadMax()) { throw new MQClientException( "consumeThreadMin (" + this.defaultMQPushConsumer.getConsumeThreadMin() + ") " + "is larger than consumeThreadMax (" + this.defaultMQPushConsumer.getConsumeThreadMax() + ")", null); } // consumeConcurrentlyMaxSpan if (this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan() < 1 || this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan() > 65535) { throw new MQClientException( "consumeConcurrentlyMaxSpan Out of range [1, 65535]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // pullThresholdForQueue if (this.defaultMQPushConsumer.getPullThresholdForQueue() < 1 || this.defaultMQPushConsumer.getPullThresholdForQueue() > 65535) { throw new MQClientException( "pullThresholdForQueue Out of range [1, 65535]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // pullThresholdForTopic if (this.defaultMQPushConsumer.getPullThresholdForTopic() != -1) { if (this.defaultMQPushConsumer.getPullThresholdForTopic() < 1 || this.defaultMQPushConsumer.getPullThresholdForTopic() > 6553500) { throw new MQClientException( "pullThresholdForTopic Out of range [1, 6553500]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } } // pullThresholdSizeForQueue if (this.defaultMQPushConsumer.getPullThresholdSizeForQueue() < 1 || this.defaultMQPushConsumer.getPullThresholdSizeForQueue() > 1024) { throw new MQClientException( "pullThresholdSizeForQueue Out of range [1, 1024]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } if (this.defaultMQPushConsumer.getPullThresholdSizeForTopic() != -1) { // pullThresholdSizeForTopic if (this.defaultMQPushConsumer.getPullThresholdSizeForTopic() < 1 || this.defaultMQPushConsumer.getPullThresholdSizeForTopic() > 102400) { throw new MQClientException( "pullThresholdSizeForTopic Out of range [1, 102400]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } } // pullInterval if (this.defaultMQPushConsumer.getPullInterval() < 0 || this.defaultMQPushConsumer.getPullInterval() > 65535) { throw new MQClientException( "pullInterval Out of range [0, 65535]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // consumeMessageBatchMaxSize if (this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize() < 1 || this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize() > 1024) { throw new MQClientException( "consumeMessageBatchMaxSize Out of range [1, 1024]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // pullBatchSize if (this.defaultMQPushConsumer.getPullBatchSize() < 1 || this.defaultMQPushConsumer.getPullBatchSize() > 1024) { throw new MQClientException( "pullBatchSize Out of range [1, 1024]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // popInvisibleTime if (this.defaultMQPushConsumer.getPopInvisibleTime() < MIN_POP_INVISIBLE_TIME || this.defaultMQPushConsumer.getPopInvisibleTime() > MAX_POP_INVISIBLE_TIME) { throw new MQClientException( "popInvisibleTime Out of range [" + MIN_POP_INVISIBLE_TIME + ", " + MAX_POP_INVISIBLE_TIME + "]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } // popBatchNums if (this.defaultMQPushConsumer.getPopBatchNums() <= 0 || this.defaultMQPushConsumer.getPopBatchNums() > 32) { throw new MQClientException( "popBatchNums Out of range [1, 32]" + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); } } private void copySubscription() throws MQClientException { try { Map sub = this.defaultMQPushConsumer.getSubscription(); if (sub != null) { for (final Map.Entry entry : sub.entrySet()) { final String topic = entry.getKey(); final String subString = entry.getValue(); SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subString); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); } } if (null == this.messageListenerInner) { this.messageListenerInner = this.defaultMQPushConsumer.getMessageListener(); } switch (this.defaultMQPushConsumer.getMessageModel()) { case BROADCASTING: break; case CLUSTERING: final String retryTopic = MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()); SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(retryTopic, SubscriptionData.SUB_ALL); this.rebalanceImpl.getSubscriptionInner().put(retryTopic, subscriptionData); break; default: break; } } catch (Exception e) { throw new MQClientException("subscription exception", e); } } public MessageListener getMessageListenerInner() { return messageListenerInner; } private void updateTopicSubscribeInfoWhenSubscriptionChanged() { if (doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged) { return; } Map subTable = this.getSubscriptionInner(); if (subTable != null) { for (final Map.Entry entry : subTable.entrySet()) { final String topic = entry.getKey(); this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); } } } public ConcurrentMap getSubscriptionInner() { return this.rebalanceImpl.getSubscriptionInner(); } public void subscribe(String topic, String subExpression) throws MQClientException { try { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); if (this.mQClientFactory != null) { this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); } } catch (Exception e) { throw new MQClientException("subscription exception", e); } } public void subscribe(String topic, String fullClassName, String filterClassSource) throws MQClientException { try { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, SubscriptionData.SUB_ALL); subscriptionData.setSubString(fullClassName); subscriptionData.setClassFilterMode(true); subscriptionData.setFilterClassSource(filterClassSource); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); if (this.mQClientFactory != null) { this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); } } catch (Exception e) { throw new MQClientException("subscription exception", e); } } public void subscribe(final String topic, final MessageSelector messageSelector) throws MQClientException { try { if (messageSelector == null) { subscribe(topic, SubscriptionData.SUB_ALL); return; } SubscriptionData subscriptionData = FilterAPI.build(topic, messageSelector.getExpression(), messageSelector.getExpressionType()); this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); if (this.mQClientFactory != null) { this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); } } catch (Exception e) { throw new MQClientException("subscription exception", e); } } public void suspend() { this.pause = true; log.info("suspend this consumer, {}", this.defaultMQPushConsumer.getConsumerGroup()); } public void unsubscribe(String topic) { this.rebalanceImpl.getSubscriptionInner().remove(topic); } public void updateConsumeOffset(MessageQueue mq, long offset) { this.offsetStore.updateOffset(mq, offset, false); } public void updateCorePoolSize(int corePoolSize) { this.consumeMessageService.updateCorePoolSize(corePoolSize); } public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId); } public RebalanceImpl getRebalanceImpl() { return rebalanceImpl; } public boolean isConsumeOrderly() { return consumeOrderly; } public void setConsumeOrderly(boolean consumeOrderly) { this.consumeOrderly = consumeOrderly; } public void resetOffsetByTimeStamp(long timeStamp) throws MQClientException { for (String topic : rebalanceImpl.getSubscriptionInner().keySet()) { Set mqs = rebalanceImpl.getTopicSubscribeInfoTable().get(topic); if (CollectionUtils.isNotEmpty(mqs)) { Map offsetTable = new HashMap<>(mqs.size(), 1); for (MessageQueue mq : mqs) { long offset = searchOffset(mq, timeStamp); offsetTable.put(mq, offset); } this.mQClientFactory.resetOffset(topic, groupName(), offsetTable); } } } public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } @Override public String groupName() { return this.defaultMQPushConsumer.getConsumerGroup(); } @Override public MessageModel messageModel() { return this.defaultMQPushConsumer.getMessageModel(); } @Override public ConsumeType consumeType() { return ConsumeType.CONSUME_PASSIVELY; } @Override public ConsumeFromWhere consumeFromWhere() { return this.defaultMQPushConsumer.getConsumeFromWhere(); } @Override public Set subscriptions() { return new HashSet<>(this.rebalanceImpl.getSubscriptionInner().values()); } @Override public void doRebalance() { if (!this.pause) { this.rebalanceImpl.doRebalance(this.isConsumeOrderly()); } } @Override public boolean tryRebalance() { if (!this.pause) { return this.rebalanceImpl.doRebalance(this.isConsumeOrderly()); } return false; } @Override public void persistConsumerOffset() { try { this.makeSureStateOK(); Set mqs = new HashSet<>(); Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet(); mqs.addAll(allocateMq); this.offsetStore.persistAll(mqs); } catch (Exception e) { log.error("group: " + this.defaultMQPushConsumer.getConsumerGroup() + " persistConsumerOffset exception", e); } } @Override public void updateTopicSubscribeInfo(String topic, Set info) { Map subTable = this.getSubscriptionInner(); if (subTable != null) { if (subTable.containsKey(topic)) { this.rebalanceImpl.topicSubscribeInfoTable.put(topic, info); } } } @Override public boolean isSubscribeTopicNeedUpdate(String topic) { Map subTable = this.getSubscriptionInner(); if (subTable != null) { if (subTable.containsKey(topic)) { return !this.rebalanceImpl.topicSubscribeInfoTable.containsKey(topic); } } return false; } @Override public boolean isUnitMode() { return this.defaultMQPushConsumer.isUnitMode(); } @Override public ConsumerRunningInfo consumerRunningInfo() { ConsumerRunningInfo info = new ConsumerRunningInfo(); Properties prop = MixAll.object2Properties(this.defaultMQPushConsumer); prop.put(ConsumerRunningInfo.PROP_CONSUME_ORDERLY, String.valueOf(this.consumeOrderly)); prop.put(ConsumerRunningInfo.PROP_THREADPOOL_CORE_SIZE, String.valueOf(this.consumeMessageService.getCorePoolSize())); prop.put(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP, String.valueOf(this.consumerStartTimestamp)); info.setProperties(prop); Set subSet = this.subscriptions(); info.getSubscriptionSet().addAll(subSet); Iterator> it = this.rebalanceImpl.getProcessQueueTable().entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); MessageQueue mq = next.getKey(); ProcessQueue pq = next.getValue(); ProcessQueueInfo pqinfo = new ProcessQueueInfo(); pqinfo.setCommitOffset(this.offsetStore.readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE)); pq.fillProcessQueueInfo(pqinfo); info.getMqTable().put(mq, pqinfo); } Iterator> popIt = this.rebalanceImpl.getPopProcessQueueTable().entrySet().iterator(); while (popIt.hasNext()) { Entry next = popIt.next(); MessageQueue mq = next.getKey(); PopProcessQueue pq = next.getValue(); PopProcessQueueInfo pqinfo = new PopProcessQueueInfo(); pq.fillPopProcessQueueInfo(pqinfo); info.getMqPopTable().put(mq, pqinfo); } for (SubscriptionData sd : subSet) { ConsumeStatus consumeStatus = this.mQClientFactory.getConsumerStatsManager().consumeStatus(this.groupName(), sd.getTopic()); info.getStatusTable().put(sd.getTopic(), consumeStatus); } return info; } public MQClientInstance getmQClientFactory() { return mQClientFactory; } public void setmQClientFactory(MQClientInstance mQClientFactory) { this.mQClientFactory = mQClientFactory; } public ServiceState getServiceState() { return serviceState; } //Don't use this deprecated setter, which will be removed soon. @Deprecated public synchronized void setServiceState(ServiceState serviceState) { this.serviceState = serviceState; } public void adjustThreadPool() { long computeAccTotal = this.computeAccumulationTotal(); long adjustThreadPoolNumsThreshold = this.defaultMQPushConsumer.getAdjustThreadPoolNumsThreshold(); long incThreshold = (long) (adjustThreadPoolNumsThreshold * 1.0); long decThreshold = (long) (adjustThreadPoolNumsThreshold * 0.8); if (computeAccTotal >= incThreshold) { this.consumeMessageService.incCorePoolSize(); } if (computeAccTotal < decThreshold) { this.consumeMessageService.decCorePoolSize(); } } private long computeAccumulationTotal() { long msgAccTotal = 0; ConcurrentMap processQueueTable = this.rebalanceImpl.getProcessQueueTable(); Iterator> it = processQueueTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); ProcessQueue value = next.getValue(); msgAccTotal += value.getMsgAccCnt(); } return msgAccTotal; } public List queryConsumeTimeSpan(final String topic) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { List queueTimeSpan = new ArrayList<>(); TopicRouteData routeData = this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(topic, 3000); for (BrokerData brokerData : routeData.getBrokerDatas()) { String addr = brokerData.selectBrokerAddr(); queueTimeSpan.addAll(this.mQClientFactory.getMQClientAPIImpl().queryConsumeTimeSpan(addr, topic, groupName(), 3000)); } return queueTimeSpan; } public void tryResetPopRetryTopic(final List msgs, String consumerGroup) { String popRetryPrefix = MixAll.RETRY_GROUP_TOPIC_PREFIX + consumerGroup + "_"; for (MessageExt msg : msgs) { if (msg.getTopic().startsWith(popRetryPrefix)) { String normalTopic = KeyBuilder.parseNormalTopic(msg.getTopic(), consumerGroup); if (normalTopic != null && !normalTopic.isEmpty()) { msg.setTopic(normalTopic); } } } } public void resetRetryAndNamespace(final List msgs, String consumerGroup) { final String groupTopic = MixAll.getRetryTopic(consumerGroup); for (MessageExt msg : msgs) { String retryTopic = msg.getProperty(MessageConst.PROPERTY_RETRY_TOPIC); if (retryTopic != null && groupTopic.equals(msg.getTopic())) { msg.setTopic(retryTopic); } if (StringUtils.isNotEmpty(this.defaultMQPushConsumer.getNamespace())) { msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace())); } } } public ConsumeMessageService getConsumeMessageService() { return consumeMessageService; } public void setConsumeMessageService(ConsumeMessageService consumeMessageService) { this.consumeMessageService = consumeMessageService; } public void setPullTimeDelayMillsWhenException(long pullTimeDelayMillsWhenException) { this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException; } int[] getPopDelayLevel() { return popDelayLevel; } public MessageQueueListener getMessageQueueListener() { if (null == defaultMQPushConsumer) { return null; } return defaultMQPushConsumer.getMessageQueueListener(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/MQConsumerInner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.Set; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; /** * Consumer inner interface */ public interface MQConsumerInner { String groupName(); MessageModel messageModel(); ConsumeType consumeType(); ConsumeFromWhere consumeFromWhere(); Set subscriptions(); void doRebalance(); boolean tryRebalance(); void persistConsumerOffset(); void updateTopicSubscribeInfo(final String topic, final Set info); boolean isSubscribeTopicNeedUpdate(final String topic); boolean isUnitMode(); ConsumerRunningInfo consumerRunningInfo(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.message.MessageQueue; /** * Message lock,strictly ensure the single queue only one thread at a time consuming */ public class MessageQueueLock { private ConcurrentMap> mqLockTable = new ConcurrentHashMap<>(32); public Object fetchLockObject(final MessageQueue mq) { return fetchLockObject(mq, -1); } public Object fetchLockObject(final MessageQueue mq, final int shardingKeyIndex) { ConcurrentMap objMap = this.mqLockTable.get(mq); if (null == objMap) { objMap = new ConcurrentHashMap<>(32); ConcurrentMap prevObjMap = this.mqLockTable.putIfAbsent(mq, objMap); if (prevObjMap != null) { objMap = prevObjMap; } } Object lock = objMap.get(shardingKeyIndex); if (null == lock) { lock = new Object(); Object prevLock = objMap.putIfAbsent(shardingKeyIndex, lock); if (prevLock != null) { lock = prevLock; } } return lock; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.common.message.MessageRequestMode; public interface MessageRequest { MessageRequestMode getMessageRequestMode(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.remoting.protocol.body.PopProcessQueueInfo; /** * Queue consumption snapshot */ public class PopProcessQueue { private final static long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000")); private long lastPopTimestamp = System.currentTimeMillis(); private AtomicInteger waitAckCounter = new AtomicInteger(0); private volatile boolean dropped = false; public long getLastPopTimestamp() { return lastPopTimestamp; } public void setLastPopTimestamp(long lastPopTimestamp) { this.lastPopTimestamp = lastPopTimestamp; } public void incFoundMsg(int count) { this.waitAckCounter.getAndAdd(count); } /** * @return the value before decrement. */ public int ack() { return this.waitAckCounter.getAndDecrement(); } public void decFoundMsg(int count) { this.waitAckCounter.addAndGet(count); } public int getWaiAckMsgCount() { return this.waitAckCounter.get(); } public boolean isDropped() { return dropped; } public void setDropped(boolean dropped) { this.dropped = dropped; } public void fillPopProcessQueueInfo(final PopProcessQueueInfo info) { info.setWaitAckCount(getWaiAckMsgCount()); info.setDroped(isDropped()); info.setLastPopTimestamp(getLastPopTimestamp()); } public boolean isPullExpired() { return (System.currentTimeMillis() - this.lastPopTimestamp) > PULL_MAX_IDLE_TIME; } @Override public String toString() { return "PopProcessQueue[waitAckCounter:" + this.waitAckCounter.get() + ", lastPopTimestamp:" + getLastPopTimestamp() + ", drop:" + dropped + "]"; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageRequestMode; public class PopRequest implements MessageRequest { private String topic; private String consumerGroup; private MessageQueue messageQueue; private PopProcessQueue popProcessQueue; private boolean lockedFirst = false; private int initMode = ConsumeInitMode.MAX; public boolean isLockedFirst() { return lockedFirst; } public void setLockedFirst(boolean lockedFirst) { this.lockedFirst = lockedFirst; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public PopProcessQueue getPopProcessQueue() { return popProcessQueue; } public void setPopProcessQueue(PopProcessQueue popProcessQueue) { this.popProcessQueue = popProcessQueue; } public int getInitMode() { return initMode; } public void setInitMode(int initMode) { this.initMode = initMode; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((topic == null) ? 0 : topic.hashCode()); result = prime * result + ((consumerGroup == null) ? 0 : consumerGroup.hashCode()); result = prime * result + ((messageQueue == null) ? 0 : messageQueue.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PopRequest other = (PopRequest) obj; if (topic == null) { if (other.topic != null) return false; } else if (!topic.equals(other.topic)) { return false; } if (consumerGroup == null) { if (other.consumerGroup != null) return false; } else if (!consumerGroup.equals(other.consumerGroup)) return false; if (messageQueue == null) { if (other.messageQueue != null) return false; } else if (!messageQueue.equals(other.messageQueue)) { return false; } return true; } @Override public String toString() { return "PopRequest [topic=" + topic + ", consumerGroup=" + consumerGroup + ", messageQueue=" + messageQueue + "]"; } @Override public MessageRequestMode getMessageRequestMode() { return MessageRequestMode.POP; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.body.ProcessQueueInfo; /** * Queue consumption snapshot */ public class ProcessQueue { public final static long REBALANCE_LOCK_MAX_LIVE_TIME = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockMaxLiveTime", "30000")); public final static long REBALANCE_LOCK_INTERVAL = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockInterval", "20000")); private final static long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000")); private final Logger log = LoggerFactory.getLogger(ProcessQueue.class); private final ReadWriteLock treeMapLock = new ReentrantReadWriteLock(); private final TreeMap msgTreeMap = new TreeMap<>(); private final AtomicLong msgCount = new AtomicLong(); private final AtomicLong msgSize = new AtomicLong(); private final ReadWriteLock consumeLock = new ReentrantReadWriteLock(); /** * A subset of msgTreeMap, will only be used when orderly consume */ private final TreeMap consumingMsgOrderlyTreeMap = new TreeMap<>(); private final AtomicLong tryUnlockTimes = new AtomicLong(0); private volatile long queueOffsetMax = 0L; private volatile boolean dropped = false; private volatile long lastPullTimestamp = System.currentTimeMillis(); private volatile long lastConsumeTimestamp = System.currentTimeMillis(); private volatile boolean locked = false; private volatile long lastLockTimestamp = System.currentTimeMillis(); private volatile boolean consuming = false; private volatile long msgAccCnt = 0; public boolean isLockExpired() { return (System.currentTimeMillis() - this.lastLockTimestamp) > REBALANCE_LOCK_MAX_LIVE_TIME; } public boolean isPullExpired() { return (System.currentTimeMillis() - this.lastPullTimestamp) > PULL_MAX_IDLE_TIME; } /** * @param pushConsumer */ public void cleanExpiredMsg(DefaultMQPushConsumer pushConsumer) { if (pushConsumer.isConsumeOrderly()) { return; } int loop = Math.min(msgTreeMap.size(), 16); for (int i = 0; i < loop; i++) { MessageExt msg = null; try { this.treeMapLock.readLock().lockInterruptibly(); try { if (!msgTreeMap.isEmpty()) { String consumeStartTimeStamp = MessageAccessor.getConsumeStartTimeStamp(msgTreeMap.firstEntry().getValue()); if (StringUtils.isNotEmpty(consumeStartTimeStamp) && System.currentTimeMillis() - Long.parseLong(consumeStartTimeStamp) > pushConsumer.getConsumeTimeout() * 60 * 1000) { msg = msgTreeMap.firstEntry().getValue(); } } } finally { this.treeMapLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getExpiredMsg exception", e); } if (msg == null) { break; } try { pushConsumer.sendMessageBack(msg, 3); log.info("send expire msg back. topic={}, msgId={}, storeHost={}, queueId={}, queueOffset={}", msg.getTopic(), msg.getMsgId(), msg.getStoreHost(), msg.getQueueId(), msg.getQueueOffset()); try { this.treeMapLock.writeLock().lockInterruptibly(); try { if (!msgTreeMap.isEmpty() && msg.getQueueOffset() == msgTreeMap.firstKey()) { try { removeMessage(Collections.singletonList(msg)); } catch (Exception e) { log.error("send expired msg exception", e); } } } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("getExpiredMsg exception", e); } } catch (Exception e) { log.error("send expired msg exception", e); } } } public boolean putMessage(final List msgs) { boolean dispatchToConsume = false; try { this.treeMapLock.writeLock().lockInterruptibly(); try { int validMsgCnt = 0; for (MessageExt msg : msgs) { MessageExt old = msgTreeMap.put(msg.getQueueOffset(), msg); if (null == old) { validMsgCnt++; this.queueOffsetMax = msg.getQueueOffset(); msgSize.addAndGet(null == msg.getBody() ? 0 : msg.getBody().length); } } msgCount.addAndGet(validMsgCnt); if (!msgTreeMap.isEmpty() && !this.consuming) { dispatchToConsume = true; this.consuming = true; } if (!msgs.isEmpty()) { MessageExt messageExt = msgs.get(msgs.size() - 1); String property = messageExt.getProperty(MessageConst.PROPERTY_MAX_OFFSET); if (property != null) { long accTotal = Long.parseLong(property) - messageExt.getQueueOffset(); if (accTotal > 0) { this.msgAccCnt = accTotal; } } } } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("putMessage exception", e); } return dispatchToConsume; } public long getMaxSpan() { try { this.treeMapLock.readLock().lockInterruptibly(); try { if (!this.msgTreeMap.isEmpty()) { return this.msgTreeMap.lastKey() - this.msgTreeMap.firstKey(); } } finally { this.treeMapLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getMaxSpan exception", e); } return 0; } public long removeMessage(final List msgs) { long result = -1; final long now = System.currentTimeMillis(); try { this.treeMapLock.writeLock().lockInterruptibly(); this.lastConsumeTimestamp = now; try { if (!msgTreeMap.isEmpty()) { result = this.queueOffsetMax + 1; int removedCnt = 0; for (MessageExt msg : msgs) { MessageExt prev = msgTreeMap.remove(msg.getQueueOffset()); if (prev != null) { removedCnt--; long bodySize = null == msg.getBody() ? 0 : msg.getBody().length; if (bodySize > 0) { msgSize.addAndGet(-bodySize); } } } if (msgCount.addAndGet(removedCnt) == 0) { msgSize.set(0); } if (!msgTreeMap.isEmpty()) { result = msgTreeMap.firstKey(); } } } finally { this.treeMapLock.writeLock().unlock(); } } catch (Throwable t) { log.error("removeMessage exception", t); } return result; } public TreeMap getMsgTreeMap() { return msgTreeMap; } public AtomicLong getMsgCount() { return msgCount; } public AtomicLong getMsgSize() { return msgSize; } public boolean isDropped() { return dropped; } public void setDropped(boolean dropped) { this.dropped = dropped; } public boolean isLocked() { return locked; } public void setLocked(boolean locked) { this.locked = locked; } public void rollback() { try { this.treeMapLock.writeLock().lockInterruptibly(); try { this.msgTreeMap.putAll(this.consumingMsgOrderlyTreeMap); this.consumingMsgOrderlyTreeMap.clear(); } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("rollback exception", e); } } public long commit() { try { this.treeMapLock.writeLock().lockInterruptibly(); try { Long offset = this.consumingMsgOrderlyTreeMap.lastKey(); if (msgCount.addAndGet(-this.consumingMsgOrderlyTreeMap.size()) == 0) { msgSize.set(0); } else { for (MessageExt msg : this.consumingMsgOrderlyTreeMap.values()) { int bodySize = null == msg.getBody() ? 0 : msg.getBody().length; if (bodySize > 0) { msgSize.addAndGet(-bodySize); } } } this.consumingMsgOrderlyTreeMap.clear(); if (offset != null) { return offset + 1; } } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("commit exception", e); } return -1; } public void makeMessageToConsumeAgain(List msgs) { try { this.treeMapLock.writeLock().lockInterruptibly(); try { for (MessageExt msg : msgs) { this.consumingMsgOrderlyTreeMap.remove(msg.getQueueOffset()); this.msgTreeMap.put(msg.getQueueOffset(), msg); } } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("makeMessageToCosumeAgain exception", e); } } public List takeMessages(final int batchSize) { List result = new ArrayList<>(batchSize); final long now = System.currentTimeMillis(); try { this.treeMapLock.writeLock().lockInterruptibly(); this.lastConsumeTimestamp = now; try { if (!this.msgTreeMap.isEmpty()) { for (int i = 0; i < batchSize; i++) { Map.Entry entry = this.msgTreeMap.pollFirstEntry(); if (entry != null) { result.add(entry.getValue()); consumingMsgOrderlyTreeMap.put(entry.getKey(), entry.getValue()); } else { break; } } } if (result.isEmpty()) { consuming = false; } } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("take Messages exception", e); } return result; } /** * Return the result that whether current message is exist in the process queue or not. */ public boolean containsMessage(MessageExt message) { if (message == null) { // should never reach here. return false; } try { this.treeMapLock.readLock().lockInterruptibly(); try { return this.msgTreeMap.containsKey(message.getQueueOffset()); } finally { this.treeMapLock.readLock().unlock(); } } catch (Throwable t) { log.error("Failed to check message's existence in process queue, message={}", message, t); } return false; } public boolean hasTempMessage() { try { this.treeMapLock.readLock().lockInterruptibly(); try { return !this.msgTreeMap.isEmpty(); } finally { this.treeMapLock.readLock().unlock(); } } catch (InterruptedException e) { } return true; } public void clear() { try { this.treeMapLock.writeLock().lockInterruptibly(); try { this.msgTreeMap.clear(); this.consumingMsgOrderlyTreeMap.clear(); this.msgCount.set(0); this.msgSize.set(0); this.queueOffsetMax = 0L; } finally { this.treeMapLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("rollback exception", e); } } public long getLastLockTimestamp() { return lastLockTimestamp; } public void setLastLockTimestamp(long lastLockTimestamp) { this.lastLockTimestamp = lastLockTimestamp; } public ReadWriteLock getConsumeLock() { return consumeLock; } public long getLastPullTimestamp() { return lastPullTimestamp; } public void setLastPullTimestamp(long lastPullTimestamp) { this.lastPullTimestamp = lastPullTimestamp; } public long getMsgAccCnt() { return msgAccCnt; } public void setMsgAccCnt(long msgAccCnt) { this.msgAccCnt = msgAccCnt; } public long getTryUnlockTimes() { return this.tryUnlockTimes.get(); } public void incTryUnlockTimes() { this.tryUnlockTimes.incrementAndGet(); } public void fillProcessQueueInfo(final ProcessQueueInfo info) { try { this.treeMapLock.readLock().lockInterruptibly(); if (!this.msgTreeMap.isEmpty()) { info.setCachedMsgMinOffset(this.msgTreeMap.firstKey()); info.setCachedMsgMaxOffset(this.msgTreeMap.lastKey()); info.setCachedMsgCount(this.msgTreeMap.size()); } info.setCachedMsgSizeInMiB((int) (this.msgSize.get() / (1024 * 1024))); if (!this.consumingMsgOrderlyTreeMap.isEmpty()) { info.setTransactionMsgMinOffset(this.consumingMsgOrderlyTreeMap.firstKey()); info.setTransactionMsgMaxOffset(this.consumingMsgOrderlyTreeMap.lastKey()); info.setTransactionMsgCount(this.consumingMsgOrderlyTreeMap.size()); } info.setLocked(this.locked); info.setTryUnlockTimes(this.tryUnlockTimes.get()); info.setLastLockTimestamp(this.lastLockTimestamp); info.setDroped(this.dropped); info.setLastPullTimestamp(this.lastPullTimestamp); info.setLastConsumeTimestamp(this.lastConsumeTimestamp); } catch (Exception e) { } finally { this.treeMapLock.readLock().unlock(); } } public long getLastConsumeTimestamp() { return lastConsumeTimestamp; } public void setLastConsumeTimestamp(long lastConsumeTimestamp) { this.lastConsumeTimestamp = lastConsumeTimestamp; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.FilterMessageContext; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class PullAPIWrapper { private static final Logger log = LoggerFactory.getLogger(PullAPIWrapper.class); private final MQClientInstance mQClientFactory; private final String consumerGroup; private final boolean unitMode; private ConcurrentMap pullFromWhichNodeTable = new ConcurrentHashMap<>(32); private volatile boolean connectBrokerByUser = false; private volatile long defaultBrokerId = MixAll.MASTER_ID; private Random random = new Random(System.nanoTime()); private ArrayList filterMessageHookList = new ArrayList<>(); public PullAPIWrapper(MQClientInstance mQClientFactory, String consumerGroup, boolean unitMode) { this.mQClientFactory = mQClientFactory; this.consumerGroup = consumerGroup; this.unitMode = unitMode; } public PullResult processPullResult(final MessageQueue mq, final PullResult pullResult, final SubscriptionData subscriptionData) { PullResultExt pullResultExt = (PullResultExt) pullResult; this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId()); if (PullStatus.FOUND == pullResult.getPullStatus()) { ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary()); List msgList = MessageDecoder.decodesBatch( byteBuffer, this.mQClientFactory.getClientConfig().isDecodeReadBody(), this.mQClientFactory.getClientConfig().isDecodeDecompressBody(), true ); boolean needDecodeInnerMessage = false; for (MessageExt messageExt: msgList) { if (MessageSysFlag.check(messageExt.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG) && MessageSysFlag.check(messageExt.getSysFlag(), MessageSysFlag.NEED_UNWRAP_FLAG)) { needDecodeInnerMessage = true; break; } } if (needDecodeInnerMessage) { List innerMsgList = new ArrayList<>(); try { for (MessageExt messageExt: msgList) { if (MessageSysFlag.check(messageExt.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG) && MessageSysFlag.check(messageExt.getSysFlag(), MessageSysFlag.NEED_UNWRAP_FLAG)) { MessageDecoder.decodeMessage(messageExt, innerMsgList); } else { innerMsgList.add(messageExt); } } msgList = innerMsgList; } catch (Throwable t) { log.error("Try to decode the inner batch failed for {}", pullResult.toString(), t); } } List msgListFilterAgain = msgList; if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()) { msgListFilterAgain = new ArrayList<>(msgList.size()); for (MessageExt msg : msgList) { if (msg.getTags() != null) { if (subscriptionData.getTagsSet().contains(msg.getTags())) { msgListFilterAgain.add(msg); } } } } if (this.hasHook()) { FilterMessageContext filterMessageContext = new FilterMessageContext(); filterMessageContext.setUnitMode(unitMode); filterMessageContext.setMsgList(msgListFilterAgain); this.executeHook(filterMessageContext); } for (MessageExt msg : msgListFilterAgain) { String traFlag = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED); if (Boolean.parseBoolean(traFlag)) { msg.setTransactionId(msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); } MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET, Long.toString(pullResult.getMinOffset())); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET, Long.toString(pullResult.getMaxOffset())); msg.setBrokerName(mq.getBrokerName()); msg.setQueueId(mq.getQueueId()); if (pullResultExt.getOffsetDelta() != null) { msg.setQueueOffset(pullResultExt.getOffsetDelta() + msg.getQueueOffset()); } } pullResultExt.setMsgFoundList(msgListFilterAgain); } pullResultExt.setMessageBinary(null); return pullResult; } public void updatePullFromWhichNode(final MessageQueue mq, final long brokerId) { AtomicLong suggest = this.pullFromWhichNodeTable.get(mq); if (null == suggest) { this.pullFromWhichNodeTable.put(mq, new AtomicLong(brokerId)); } else { suggest.set(brokerId); } } public boolean hasHook() { return !this.filterMessageHookList.isEmpty(); } public void executeHook(final FilterMessageContext context) { if (!this.filterMessageHookList.isEmpty()) { for (FilterMessageHook hook : this.filterMessageHookList) { try { hook.filterMessage(context); } catch (Throwable e) { log.error("execute hook error. hookName={}", hook.hookName()); } } } } public PullResult pullKernelImpl( final MessageQueue mq, final String subExpression, final String expressionType, final long subVersion, final long offset, final int maxNums, final int maxSizeInBytes, final int sysFlag, final long commitOffset, final long brokerSuspendMaxTimeMillis, final long timeoutMillis, final CommunicationMode communicationMode, final PullCallback pullCallback ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), this.recalculatePullFromWhichNode(mq), false); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), this.recalculatePullFromWhichNode(mq), false); } if (findBrokerResult != null) { { // check version if (!ExpressionType.isTagType(expressionType) && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) { throw new MQClientException("The broker[" + mq.getBrokerName() + ", " + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null); } } int sysFlagInner = sysFlag; if (findBrokerResult.isSlave()) { sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner); } PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup(this.consumerGroup); requestHeader.setTopic(mq.getTopic()); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setQueueOffset(offset); requestHeader.setMaxMsgNums(maxNums); requestHeader.setSysFlag(sysFlagInner); requestHeader.setCommitOffset(commitOffset); requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis); requestHeader.setSubscription(subExpression); requestHeader.setSubVersion(subVersion); requestHeader.setMaxMsgBytes(maxSizeInBytes); requestHeader.setExpressionType(expressionType); requestHeader.setBrokerName(mq.getBrokerName()); String brokerAddr = findBrokerResult.getBrokerAddr(); if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) { brokerAddr = computePullFromWhichFilterServer(mq.getTopic(), brokerAddr); } PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage( brokerAddr, requestHeader, timeoutMillis, communicationMode, pullCallback); return pullResult; } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } public PullResult pullKernelImpl( MessageQueue mq, final String subExpression, final String expressionType, final long subVersion, long offset, final int maxNums, final int sysFlag, long commitOffset, final long brokerSuspendMaxTimeMillis, final long timeoutMillis, final CommunicationMode communicationMode, PullCallback pullCallback ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return pullKernelImpl( mq, subExpression, expressionType, subVersion, offset, maxNums, Integer.MAX_VALUE, sysFlag, commitOffset, brokerSuspendMaxTimeMillis, timeoutMillis, communicationMode, pullCallback ); } public long recalculatePullFromWhichNode(final MessageQueue mq) { if (this.isConnectBrokerByUser()) { return this.defaultBrokerId; } AtomicLong suggest = this.pullFromWhichNodeTable.get(mq); if (suggest != null) { return suggest.get(); } return MixAll.MASTER_ID; } private String computePullFromWhichFilterServer(final String topic, final String brokerAddr) throws MQClientException { ConcurrentMap topicRouteTable = this.mQClientFactory.getTopicRouteTable(); if (topicRouteTable != null) { TopicRouteData topicRouteData = topicRouteTable.get(topic); List list = topicRouteData.getFilterServerTable().get(brokerAddr); if (list != null && !list.isEmpty()) { return list.get(randomNum() % list.size()); } } throw new MQClientException("Find Filter Server Failed, Broker Addr: " + brokerAddr + " topic: " + topic, null); } public boolean isConnectBrokerByUser() { return connectBrokerByUser; } public void setConnectBrokerByUser(boolean connectBrokerByUser) { this.connectBrokerByUser = connectBrokerByUser; } public int randomNum() { int value = random.nextInt(); if (value < 0) { value = Math.abs(value); if (value < 0) value = 0; } return value; } public void registerFilterMessageHook(ArrayList filterMessageHookList) { this.filterMessageHookList = filterMessageHookList; } public long getDefaultBrokerId() { return defaultBrokerId; } public void setDefaultBrokerId(long defaultBrokerId) { this.defaultBrokerId = defaultBrokerId; } /** * * @param mq * @param invisibleTime * @param maxNums * @param consumerGroup * @param timeout * @param popCallback * @param poll * @param initMode // * @param expressionType // * @param expression * @param order * @throws MQClientException * @throws RemotingException * @throws InterruptedException */ public void popAsync(MessageQueue mq, long invisibleTime, int maxNums, String consumerGroup, long timeout, PopCallback popCallback, boolean poll, int initMode, boolean order, String expressionType, String expression) throws MQClientException, RemotingException, InterruptedException { FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true); } if (findBrokerResult != null) { PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(mq.getTopic()); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setMaxMsgNums(maxNums); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setInitMode(initMode); requestHeader.setExpType(expressionType); requestHeader.setExp(expression); requestHeader.setOrder(order); requestHeader.setBrokerName(mq.getBrokerName()); //give 1000 ms for server response if (poll) { requestHeader.setPollTime(timeout); requestHeader.setBornTime(System.currentTimeMillis()); // timeout + 10s, fix the too earlier timeout of client when long polling. timeout += 10 * 1000; } String brokerAddr = findBrokerResult.getBrokerAddr(); this.mQClientFactory.getMQClientAPIImpl().popMessageAsync(mq.getBrokerName(), brokerAddr, requestHeader, timeout, popCallback); return; } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class PullMessageService extends ServiceThread { private final Logger logger = LoggerFactory.getLogger(PullMessageService.class); private final LinkedBlockingQueue messageRequestQueue = new LinkedBlockingQueue<>(); private final MQClientInstance mQClientFactory; private final ScheduledExecutorService scheduledExecutorService = Executors .newSingleThreadScheduledExecutor(new ThreadFactoryImpl("PullMessageServiceScheduledThread")); public PullMessageService(MQClientInstance mQClientFactory) { this.mQClientFactory = mQClientFactory; } public void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) { if (!isStopped()) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { PullMessageService.this.executePullRequestImmediately(pullRequest); } }, timeDelay, TimeUnit.MILLISECONDS); } else { logger.warn("PullMessageServiceScheduledThread has shutdown"); } } public void executePullRequestImmediately(final PullRequest pullRequest) { try { this.messageRequestQueue.put(pullRequest); } catch (InterruptedException e) { logger.error("executePullRequestImmediately pullRequestQueue.put", e); } } public void executePopPullRequestLater(final PopRequest popRequest, final long timeDelay) { if (!isStopped()) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { PullMessageService.this.executePopPullRequestImmediately(popRequest); } }, timeDelay, TimeUnit.MILLISECONDS); } else { logger.warn("PullMessageServiceScheduledThread has shutdown"); } } public void executePopPullRequestImmediately(final PopRequest popRequest) { try { this.messageRequestQueue.put(popRequest); } catch (InterruptedException e) { logger.error("executePullRequestImmediately pullRequestQueue.put", e); } } public void executeTaskLater(final Runnable r, final long timeDelay) { if (!isStopped()) { this.scheduledExecutorService.schedule(r, timeDelay, TimeUnit.MILLISECONDS); } else { logger.warn("PullMessageServiceScheduledThread has shutdown"); } } public void executeTask(final Runnable r) { if (!isStopped()) { this.scheduledExecutorService.execute(r); } else { logger.warn("PullMessageServiceScheduledThread has shutdown"); } } public ScheduledExecutorService getScheduledExecutorService() { return scheduledExecutorService; } private void pullMessage(final PullRequest pullRequest) { final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup()); if (consumer != null) { DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer; impl.pullMessage(pullRequest); } else { logger.warn("No matched consumer for the PullRequest {}, drop it", pullRequest); } } private void popMessage(final PopRequest popRequest) { final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(popRequest.getConsumerGroup()); if (consumer != null) { DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer; impl.popMessage(popRequest); } else { logger.warn("No matched consumer for the PopRequest {}, drop it", popRequest); } } @Override public void run() { logger.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { MessageRequest messageRequest = this.messageRequestQueue.take(); if (messageRequest.getMessageRequestMode() == MessageRequestMode.POP) { this.popMessage((PopRequest) messageRequest); } else { this.pullMessage((PullRequest) messageRequest); } } catch (InterruptedException ignored) { } catch (Throwable e) { logger.error("Pull Message Service Run Method exception", e); } } logger.info(this.getServiceName() + " service end"); } @Override public void shutdown(boolean interrupt) { super.shutdown(interrupt); ThreadUtils.shutdownGracefully(this.scheduledExecutorService, 1000, TimeUnit.MILLISECONDS); } @Override public String getServiceName() { return PullMessageService.class.getSimpleName(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageRequestMode; public class PullRequest implements MessageRequest { private String consumerGroup; private MessageQueue messageQueue; private ProcessQueue processQueue; private long nextOffset; private boolean previouslyLocked = false; public boolean isPreviouslyLocked() { return previouslyLocked; } public void setPreviouslyLocked(boolean previouslyLocked) { this.previouslyLocked = previouslyLocked; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public long getNextOffset() { return nextOffset; } public void setNextOffset(long nextOffset) { this.nextOffset = nextOffset; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((consumerGroup == null) ? 0 : consumerGroup.hashCode()); result = prime * result + ((messageQueue == null) ? 0 : messageQueue.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PullRequest other = (PullRequest) obj; if (consumerGroup == null) { if (other.consumerGroup != null) return false; } else if (!consumerGroup.equals(other.consumerGroup)) return false; if (messageQueue == null) { if (other.messageQueue != null) return false; } else if (!messageQueue.equals(other.messageQueue)) return false; return true; } @Override public String toString() { return "PullRequest [consumerGroup=" + consumerGroup + ", messageQueue=" + messageQueue + ", nextOffset=" + nextOffset + "]"; } public ProcessQueue getProcessQueue() { return processQueue; } public void setProcessQueue(ProcessQueue processQueue) { this.processQueue = processQueue; } @Override public MessageRequestMode getMessageRequestMode() { return MessageRequestMode.PULL; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullResultExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.List; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.message.MessageExt; public class PullResultExt extends PullResult { private final long suggestWhichBrokerId; private byte[] messageBinary; private final Long offsetDelta; public PullResultExt(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset, List msgFoundList, final long suggestWhichBrokerId, final byte[] messageBinary) { this(pullStatus, nextBeginOffset, minOffset, maxOffset, msgFoundList, suggestWhichBrokerId, messageBinary, 0L); } public PullResultExt(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset, List msgFoundList, final long suggestWhichBrokerId, final byte[] messageBinary, final Long offsetDelta) { super(pullStatus, nextBeginOffset, minOffset, maxOffset, msgFoundList); this.suggestWhichBrokerId = suggestWhichBrokerId; this.messageBinary = messageBinary; this.offsetDelta = offsetDelta; } public Long getOffsetDelta() { return offsetDelta; } public byte[] getMessageBinary() { return messageBinary; } public void setMessageBinary(byte[] messageBinary) { this.messageBinary = messageBinary; } public long getSuggestWhichBrokerId() { return suggestWhichBrokerId; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class RebalanceImpl { protected static final Logger log = LoggerFactory.getLogger(RebalanceImpl.class); protected final ConcurrentMap processQueueTable = new ConcurrentHashMap<>(64); protected final ConcurrentMap popProcessQueueTable = new ConcurrentHashMap<>(64); protected final ConcurrentMap> topicSubscribeInfoTable = new ConcurrentHashMap<>(); protected final ConcurrentMap subscriptionInner = new ConcurrentHashMap<>(); protected String consumerGroup; protected MessageModel messageModel; protected AllocateMessageQueueStrategy allocateMessageQueueStrategy; protected MQClientInstance mQClientFactory; private static final int QUERY_ASSIGNMENT_TIMEOUT = 3000; public RebalanceImpl(String consumerGroup, MessageModel messageModel, AllocateMessageQueueStrategy allocateMessageQueueStrategy, MQClientInstance mQClientFactory) { this.consumerGroup = consumerGroup; this.messageModel = messageModel; this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; this.mQClientFactory = mQClientFactory; } public void unlock(final MessageQueue mq, final boolean oneway) { FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, true); if (findBrokerResult != null) { UnlockBatchRequestBody requestBody = new UnlockBatchRequestBody(); requestBody.setConsumerGroup(this.consumerGroup); requestBody.setClientId(this.mQClientFactory.getClientId()); requestBody.getMqSet().add(mq); try { this.mQClientFactory.getMQClientAPIImpl().unlockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000, oneway); log.warn("unlock messageQueue. group:{}, clientId:{}, mq:{}", this.consumerGroup, this.mQClientFactory.getClientId(), mq); } catch (Exception e) { log.error("unlockBatchMQ exception, " + mq, e); } } } public void unlockAll(final boolean oneway) { HashMap> brokerMqs = this.buildProcessQueueTableByBrokerName(); for (final Map.Entry> entry : brokerMqs.entrySet()) { final String brokerName = entry.getKey(); final Set mqs = entry.getValue(); if (mqs.isEmpty()) { continue; } FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true); if (findBrokerResult != null) { UnlockBatchRequestBody requestBody = new UnlockBatchRequestBody(); requestBody.setConsumerGroup(this.consumerGroup); requestBody.setClientId(this.mQClientFactory.getClientId()); requestBody.setMqSet(mqs); try { this.mQClientFactory.getMQClientAPIImpl().unlockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000, oneway); for (MessageQueue mq : mqs) { ProcessQueue processQueue = this.processQueueTable.get(mq); if (processQueue != null) { processQueue.setLocked(false); log.info("the message queue unlock OK, Group: {} {}", this.consumerGroup, mq); } } } catch (Exception e) { log.error("unlockBatchMQ exception, " + mqs, e); } } } } private HashMap> buildProcessQueueTableByBrokerName() { HashMap> result = new HashMap<>(); for (Map.Entry entry : this.processQueueTable.entrySet()) { MessageQueue mq = entry.getKey(); ProcessQueue pq = entry.getValue(); if (pq.isDropped()) { continue; } String destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq); Set mqs = result.get(destBrokerName); if (null == mqs) { mqs = new HashSet<>(); result.put(mq.getBrokerName(), mqs); } mqs.add(mq); } return result; } public boolean lock(final MessageQueue mq) { FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, true); if (findBrokerResult != null) { LockBatchRequestBody requestBody = new LockBatchRequestBody(); requestBody.setConsumerGroup(this.consumerGroup); requestBody.setClientId(this.mQClientFactory.getClientId()); requestBody.getMqSet().add(mq); try { Set lockedMq = this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000); for (MessageQueue mmqq : lockedMq) { ProcessQueue processQueue = this.processQueueTable.get(mmqq); if (processQueue != null) { processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); } } boolean lockOK = lockedMq.contains(mq); log.info("message queue lock {}, {} {}", lockOK ? "OK" : "Failed", this.consumerGroup, mq); return lockOK; } catch (Exception e) { log.error("lockBatchMQ exception, " + mq, e); } } return false; } public void lockAll() { HashMap> brokerMqs = this.buildProcessQueueTableByBrokerName(); Iterator>> it = brokerMqs.entrySet().iterator(); while (it.hasNext()) { Entry> entry = it.next(); final String brokerName = entry.getKey(); final Set mqs = entry.getValue(); if (mqs.isEmpty()) { continue; } FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true); if (findBrokerResult != null) { LockBatchRequestBody requestBody = new LockBatchRequestBody(); requestBody.setConsumerGroup(this.consumerGroup); requestBody.setClientId(this.mQClientFactory.getClientId()); requestBody.setMqSet(mqs); try { Set lockOKMQSet = this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000); for (MessageQueue mq : mqs) { ProcessQueue processQueue = this.processQueueTable.get(mq); if (processQueue != null) { if (lockOKMQSet.contains(mq)) { if (!processQueue.isLocked()) { log.info("the message queue locked OK, Group: {} {}", this.consumerGroup, mq); } processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); } else { processQueue.setLocked(false); log.warn("the message queue locked Failed, Group: {} {}", this.consumerGroup, mq); } } } } catch (Exception e) { log.error("lockBatchMQ exception, " + mqs, e); } } } } public boolean clientRebalance(String topic) { return true; } public boolean doRebalance(final boolean isOrder) { boolean balanced = true; Map subTable = this.getSubscriptionInner(); if (subTable != null) { for (final Map.Entry entry : subTable.entrySet()) { final String topic = entry.getKey(); try { if (!clientRebalance(topic)) { boolean result = this.getRebalanceResultFromBroker(topic, isOrder); if (!result) { balanced = false; } } else { boolean result = this.rebalanceByTopic(topic, isOrder); if (!result) { balanced = false; } } } catch (Throwable e) { if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { log.warn("rebalance Exception", e); balanced = false; } } } } this.truncateMessageQueueNotMyTopic(); return balanced; } public ConcurrentMap getSubscriptionInner() { return subscriptionInner; } private boolean rebalanceByTopic(final String topic, final boolean isOrder) { boolean balanced = true; switch (messageModel) { case BROADCASTING: { Set mqSet = this.topicSubscribeInfoTable.get(topic); if (mqSet != null) { boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, false); if (changed) { this.messageQueueChanged(topic, mqSet, mqSet); log.info("messageQueueChanged {} {} {} {}", consumerGroup, topic, mqSet, mqSet); } balanced = mqSet.equals(getWorkingMessageQueue(topic)); } else { this.messageQueueChanged(topic, Collections.emptySet(), Collections.emptySet()); log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic); } break; } case CLUSTERING: { Set mqSet = this.topicSubscribeInfoTable.get(topic); if (null == mqSet || mqSet.isEmpty()) { if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { this.messageQueueChanged(topic, Collections.emptySet(), Collections.emptySet()); log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic); } break; } List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup); if (null == cidAll || cidAll.isEmpty()) { log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic); } else { List mqAll = new ArrayList<>(mqSet); Collections.sort(mqAll); Collections.sort(cidAll); AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy; List allocateResult = null; try { allocateResult = strategy.allocate( this.consumerGroup, this.mQClientFactory.getClientId(), mqAll, cidAll); } catch (Throwable e) { log.error("allocate message queue exception. strategy name: {}, ex: {}", strategy.getName(), e); return false; } Set allocateResultSet = new HashSet<>(); if (allocateResult != null) { allocateResultSet.addAll(allocateResult); } boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder); if (changed) { log.info( "client rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}", strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(), allocateResultSet.size(), allocateResultSet); this.messageQueueChanged(topic, mqSet, allocateResultSet); } balanced = allocateResultSet.equals(getWorkingMessageQueue(topic)); } break; } default: break; } return balanced; } private boolean getRebalanceResultFromBroker(final String topic, final boolean isOrder) { String strategyName = this.allocateMessageQueueStrategy.getName(); Set messageQueueAssignments; try { messageQueueAssignments = this.mQClientFactory.queryAssignment(topic, consumerGroup, strategyName, messageModel, QUERY_ASSIGNMENT_TIMEOUT); } catch (Exception e) { log.error("allocate message queue exception. strategy name: {}, ex: {}", strategyName, e); return false; } // null means invalid result, we should skip the update logic if (messageQueueAssignments == null) { return false; } Set mqSet = new HashSet<>(); for (MessageQueueAssignment messageQueueAssignment : messageQueueAssignments) { if (messageQueueAssignment.getMessageQueue() != null) { mqSet.add(messageQueueAssignment.getMessageQueue()); } } Set mqAll = null; boolean changed = this.updateMessageQueueAssignment(topic, messageQueueAssignments, isOrder); if (changed) { log.info("broker rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, assignmentSet={}", strategyName, consumerGroup, topic, this.mQClientFactory.getClientId(), messageQueueAssignments); this.messageQueueChanged(topic, mqAll, mqSet); } return mqSet.equals(getWorkingMessageQueue(topic)); } private Set getWorkingMessageQueue(String topic) { Set queueSet = new HashSet<>(); for (Entry entry : this.processQueueTable.entrySet()) { MessageQueue mq = entry.getKey(); ProcessQueue pq = entry.getValue(); if (mq.getTopic().equals(topic) && !pq.isDropped()) { queueSet.add(mq); } } for (Entry entry : this.popProcessQueueTable.entrySet()) { MessageQueue mq = entry.getKey(); PopProcessQueue pq = entry.getValue(); if (mq.getTopic().equals(topic) && !pq.isDropped()) { queueSet.add(mq); } } return queueSet; } private void truncateMessageQueueNotMyTopic() { Map subTable = this.getSubscriptionInner(); for (MessageQueue mq : this.processQueueTable.keySet()) { if (!subTable.containsKey(mq.getTopic())) { ProcessQueue pq = this.processQueueTable.remove(mq); if (pq != null) { pq.setDropped(true); log.info("doRebalance, {}, truncateMessageQueueNotMyTopic remove unnecessary mq, {}", consumerGroup, mq); } } } for (MessageQueue mq : this.popProcessQueueTable.keySet()) { if (!subTable.containsKey(mq.getTopic())) { PopProcessQueue pq = this.popProcessQueueTable.remove(mq); if (pq != null) { pq.setDropped(true); log.info("doRebalance, {}, truncateMessageQueueNotMyTopic remove unnecessary pop mq, {}", consumerGroup, mq); } } } } private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet, final boolean needLockMq) { boolean changed = false; // drop process queues no longer belong me HashMap removeQueueMap = new HashMap<>(this.processQueueTable.size()); Iterator> it = this.processQueueTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); MessageQueue mq = next.getKey(); ProcessQueue pq = next.getValue(); if (mq.getTopic().equals(topic)) { if (!mqSet.contains(mq)) { pq.setDropped(true); removeQueueMap.put(mq, pq); } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) { pq.setDropped(true); removeQueueMap.put(mq, pq); log.error("[BUG]doRebalance, {}, try remove unnecessary mq, {}, because pull is pause, so try to fixed it", consumerGroup, mq); } } } // remove message queues no longer belong me for (Entry entry : removeQueueMap.entrySet()) { MessageQueue mq = entry.getKey(); ProcessQueue pq = entry.getValue(); if (this.removeUnnecessaryMessageQueue(mq, pq)) { this.processQueueTable.remove(mq); changed = true; log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq); } } // add new message queue boolean allMQLocked = true; List pullRequestList = new ArrayList<>(); for (MessageQueue mq : mqSet) { if (!this.processQueueTable.containsKey(mq)) { if (needLockMq && !this.lock(mq)) { log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq); allMQLocked = false; continue; } this.removeDirtyOffset(mq); ProcessQueue pq = createProcessQueue(); pq.setLocked(true); long nextOffset = this.computePullFromWhere(mq); if (nextOffset >= 0) { ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq); if (pre != null) { log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq); } else { log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq); PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(nextOffset); pullRequest.setMessageQueue(mq); pullRequest.setProcessQueue(pq); pullRequestList.add(pullRequest); changed = true; } } else { log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq); } } } if (!allMQLocked) { mQClientFactory.rebalanceLater(500); } this.dispatchPullRequest(pullRequestList, 500); return changed; } private boolean updateMessageQueueAssignment(final String topic, final Set assignments, final boolean isOrder) { boolean changed = false; Map mq2PushAssignment = new HashMap<>(); Map mq2PopAssignment = new HashMap<>(); for (MessageQueueAssignment assignment : assignments) { MessageQueue messageQueue = assignment.getMessageQueue(); if (messageQueue == null) { continue; } if (MessageRequestMode.POP == assignment.getMode()) { mq2PopAssignment.put(messageQueue, assignment); } else { mq2PushAssignment.put(messageQueue, assignment); } } if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { if (mq2PopAssignment.isEmpty() && !mq2PushAssignment.isEmpty()) { //pop switch to push //subscribe pop retry topic try { final String retryTopic = KeyBuilder.buildPopRetryTopic(topic, getConsumerGroup()); SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(retryTopic, SubscriptionData.SUB_ALL); getSubscriptionInner().put(retryTopic, subscriptionData); } catch (Exception ignored) { } } else if (!mq2PopAssignment.isEmpty() && mq2PushAssignment.isEmpty()) { //push switch to pop //unsubscribe pop retry topic try { final String retryTopic = KeyBuilder.buildPopRetryTopic(topic, getConsumerGroup()); getSubscriptionInner().remove(retryTopic); } catch (Exception ignored) { } } } { // drop process queues no longer belong me HashMap removeQueueMap = new HashMap<>(this.processQueueTable.size()); Iterator> it = this.processQueueTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); MessageQueue mq = next.getKey(); ProcessQueue pq = next.getValue(); if (mq.getTopic().equals(topic)) { if (!mq2PushAssignment.containsKey(mq)) { pq.setDropped(true); removeQueueMap.put(mq, pq); } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) { pq.setDropped(true); removeQueueMap.put(mq, pq); log.error("[BUG]doRebalance, {}, try remove unnecessary mq, {}, because pull is pause, so try to fixed it", consumerGroup, mq); } } } // remove message queues no longer belong me for (Entry entry : removeQueueMap.entrySet()) { MessageQueue mq = entry.getKey(); ProcessQueue pq = entry.getValue(); if (this.removeUnnecessaryMessageQueue(mq, pq)) { this.processQueueTable.remove(mq); changed = true; log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq); } } } { HashMap removeQueueMap = new HashMap<>(this.popProcessQueueTable.size()); Iterator> it = this.popProcessQueueTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); MessageQueue mq = next.getKey(); PopProcessQueue pq = next.getValue(); if (mq.getTopic().equals(topic)) { if (!mq2PopAssignment.containsKey(mq)) { //the queue is no longer your assignment pq.setDropped(true); removeQueueMap.put(mq, pq); } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) { pq.setDropped(true); removeQueueMap.put(mq, pq); log.error("[BUG]doRebalance, {}, try remove unnecessary pop mq, {}, because pop is pause, so try to fixed it", consumerGroup, mq); } } } // remove message queues no longer belong me for (Entry entry : removeQueueMap.entrySet()) { MessageQueue mq = entry.getKey(); PopProcessQueue pq = entry.getValue(); if (this.removeUnnecessaryPopMessageQueue(mq, pq)) { this.popProcessQueueTable.remove(mq); changed = true; log.info("doRebalance, {}, remove unnecessary pop mq, {}", consumerGroup, mq); } } } { // add new message queue boolean allMQLocked = true; List pullRequestList = new ArrayList<>(); for (MessageQueue mq : mq2PushAssignment.keySet()) { if (!this.processQueueTable.containsKey(mq)) { if (isOrder && !this.lock(mq)) { log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq); allMQLocked = false; continue; } this.removeDirtyOffset(mq); ProcessQueue pq = createProcessQueue(); pq.setLocked(true); long nextOffset = -1L; try { nextOffset = this.computePullFromWhereWithException(mq); } catch (Exception e) { log.info("doRebalance, {}, compute offset failed, {}", consumerGroup, mq); continue; } if (nextOffset >= 0) { ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq); if (pre != null) { log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq); } else { log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq); PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(nextOffset); pullRequest.setMessageQueue(mq); pullRequest.setProcessQueue(pq); pullRequestList.add(pullRequest); changed = true; } } else { log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq); } } } if (!allMQLocked) { mQClientFactory.rebalanceLater(500); } this.dispatchPullRequest(pullRequestList, 500); } { // add new message queue List popRequestList = new ArrayList<>(); for (MessageQueue mq : mq2PopAssignment.keySet()) { if (!this.popProcessQueueTable.containsKey(mq)) { PopProcessQueue pq = createPopProcessQueue(); PopProcessQueue pre = this.popProcessQueueTable.putIfAbsent(mq, pq); if (pre != null) { log.info("doRebalance, {}, mq pop already exists, {}", consumerGroup, mq); } else { log.info("doRebalance, {}, add a new pop mq, {}", consumerGroup, mq); PopRequest popRequest = new PopRequest(); popRequest.setTopic(topic); popRequest.setConsumerGroup(consumerGroup); popRequest.setMessageQueue(mq); popRequest.setPopProcessQueue(pq); popRequest.setInitMode(getConsumeInitMode()); popRequestList.add(popRequest); changed = true; } } } this.dispatchPopPullRequest(popRequestList, 500); } return changed; } public abstract void messageQueueChanged(final String topic, final Set mqAll, final Set mqDivided); public abstract boolean removeUnnecessaryMessageQueue(final MessageQueue mq, final ProcessQueue pq); public boolean removeUnnecessaryPopMessageQueue(final MessageQueue mq, final PopProcessQueue pq) { return true; } public abstract ConsumeType consumeType(); public abstract void removeDirtyOffset(final MessageQueue mq); /** * When the network is unstable, using this interface may return wrong offset. * It is recommended to use computePullFromWhereWithException instead. * @param mq * @return offset */ @Deprecated public abstract long computePullFromWhere(final MessageQueue mq); public abstract long computePullFromWhereWithException(final MessageQueue mq) throws MQClientException; public abstract int getConsumeInitMode(); public abstract void dispatchPullRequest(final List pullRequestList, final long delay); public abstract void dispatchPopPullRequest(final List pullRequestList, final long delay); public abstract ProcessQueue createProcessQueue(); public abstract PopProcessQueue createPopProcessQueue(); public void removeProcessQueue(final MessageQueue mq) { ProcessQueue prev = this.processQueueTable.remove(mq); if (prev != null) { boolean droped = prev.isDropped(); prev.setDropped(true); this.removeUnnecessaryMessageQueue(mq, prev); log.info("Fix Offset, {}, remove unnecessary mq, {} Droped: {}", consumerGroup, mq, droped); } } public ConcurrentMap getProcessQueueTable() { return processQueueTable; } public ConcurrentMap getPopProcessQueueTable() { return popProcessQueueTable; } public ConcurrentMap> getTopicSubscribeInfoTable() { return topicSubscribeInfoTable; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public AllocateMessageQueueStrategy getAllocateMessageQueueStrategy() { return allocateMessageQueueStrategy; } public void setAllocateMessageQueueStrategy(AllocateMessageQueueStrategy allocateMessageQueueStrategy) { this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; } public MQClientInstance getmQClientFactory() { return mQClientFactory; } public void setmQClientFactory(MQClientInstance mQClientFactory) { this.mQClientFactory = mQClientFactory; } public void destroy() { Iterator> it = this.processQueueTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().setDropped(true); } this.processQueueTable.clear(); Iterator> popIt = this.popProcessQueueTable.entrySet().iterator(); while (popIt.hasNext()) { Entry next = popIt.next(); next.getValue().setDropped(true); } this.popProcessQueueTable.clear(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.List; import java.util.Set; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.MessageQueueListener; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class RebalanceLitePullImpl extends RebalanceImpl { private final DefaultLitePullConsumerImpl litePullConsumerImpl; public RebalanceLitePullImpl(DefaultLitePullConsumerImpl litePullConsumerImpl) { this(null, null, null, null, litePullConsumerImpl); } public RebalanceLitePullImpl(String consumerGroup, MessageModel messageModel, AllocateMessageQueueStrategy allocateMessageQueueStrategy, MQClientInstance mQClientFactory, DefaultLitePullConsumerImpl litePullConsumerImpl) { super(consumerGroup, messageModel, allocateMessageQueueStrategy, mQClientFactory); this.litePullConsumerImpl = litePullConsumerImpl; } @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { MessageQueueListener messageQueueListener = this.litePullConsumerImpl.getDefaultLitePullConsumer().getMessageQueueListener(); if (messageQueueListener != null) { try { messageQueueListener.messageQueueChanged(topic, mqAll, mqDivided); } catch (Throwable e) { log.error("messageQueueChanged exception", e); } } } @Override public boolean removeUnnecessaryMessageQueue(MessageQueue mq, ProcessQueue pq) { this.litePullConsumerImpl.getOffsetStore().persist(mq); this.litePullConsumerImpl.getOffsetStore().removeOffset(mq); return true; } @Override public ConsumeType consumeType() { return ConsumeType.CONSUME_ACTIVELY; } @Override public void removeDirtyOffset(final MessageQueue mq) { this.litePullConsumerImpl.getOffsetStore().removeOffset(mq); } @Deprecated @Override public long computePullFromWhere(MessageQueue mq) { long result = -1L; try { result = computePullFromWhereWithException(mq); } catch (MQClientException e) { log.warn("Compute consume offset exception, mq={}", mq); } return result; } @Override public long computePullFromWhereWithException(MessageQueue mq) throws MQClientException { ConsumeFromWhere consumeFromWhere = litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeFromWhere(); long result = -1; switch (consumeFromWhere) { case CONSUME_FROM_LAST_OFFSET: { long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { // First start, no offset result = 0L; } else { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); throw e; } } } else { result = -1; } break; } case CONSUME_FROM_FIRST_OFFSET: { long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { result = 0L; } else { result = -1; } break; } case CONSUME_FROM_TIMESTAMP: { long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); throw e; } } else { try { long timestamp = UtilAll.parseDate(this.litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeTimestamp(), UtilAll.YYYYMMDDHHMMSS).getTime(); result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } catch (MQClientException e) { log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); throw e; } } } else { result = -1; } break; } } return result; } @Override public int getConsumeInitMode() { throw new UnsupportedOperationException("no initMode for Pull"); } @Override public void dispatchPullRequest(final List pullRequestList, final long delay) { } @Override public void dispatchPopPullRequest(List pullRequestList, long delay) { } @Override public ProcessQueue createProcessQueue() { return new ProcessQueue(); } @Override public PopProcessQueue createPopProcessQueue() { return null; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.List; import java.util.Set; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.MessageQueueListener; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class RebalancePullImpl extends RebalanceImpl { private final DefaultMQPullConsumerImpl defaultMQPullConsumerImpl; public RebalancePullImpl(DefaultMQPullConsumerImpl defaultMQPullConsumerImpl) { this(null, null, null, null, defaultMQPullConsumerImpl); } public RebalancePullImpl(String consumerGroup, MessageModel messageModel, AllocateMessageQueueStrategy allocateMessageQueueStrategy, MQClientInstance mQClientFactory, DefaultMQPullConsumerImpl defaultMQPullConsumerImpl) { super(consumerGroup, messageModel, allocateMessageQueueStrategy, mQClientFactory); this.defaultMQPullConsumerImpl = defaultMQPullConsumerImpl; } @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { MessageQueueListener messageQueueListener = this.defaultMQPullConsumerImpl.getDefaultMQPullConsumer().getMessageQueueListener(); if (messageQueueListener != null) { try { messageQueueListener.messageQueueChanged(topic, mqAll, mqDivided); } catch (Throwable e) { log.error("messageQueueChanged exception", e); } } } @Override public boolean removeUnnecessaryMessageQueue(MessageQueue mq, ProcessQueue pq) { this.defaultMQPullConsumerImpl.getOffsetStore().persist(mq); this.defaultMQPullConsumerImpl.getOffsetStore().removeOffset(mq); return true; } @Override public ConsumeType consumeType() { return ConsumeType.CONSUME_ACTIVELY; } @Override public void removeDirtyOffset(final MessageQueue mq) { this.defaultMQPullConsumerImpl.getOffsetStore().removeOffset(mq); } @Deprecated @Override public long computePullFromWhere(MessageQueue mq) { return 0; } @Override public long computePullFromWhereWithException(MessageQueue mq) throws MQClientException { return 0; } @Override public int getConsumeInitMode() { throw new UnsupportedOperationException("no initMode for Pull"); } @Override public void dispatchPullRequest(final List pullRequestList, final long delay) { } @Override public void dispatchPopPullRequest(final List pullRequestList, final long delay) { } @Override public ProcessQueue createProcessQueue() { return new ProcessQueue(); } @Override public PopProcessQueue createPopProcessQueue() { return null; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.MessageQueueListener; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class RebalancePushImpl extends RebalanceImpl { private final static long UNLOCK_DELAY_TIME_MILLS = Long.parseLong(System.getProperty("rocketmq.client.unlockDelayTimeMills", "20000")); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; public RebalancePushImpl(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl) { this(null, null, null, null, defaultMQPushConsumerImpl); } public RebalancePushImpl(String consumerGroup, MessageModel messageModel, AllocateMessageQueueStrategy allocateMessageQueueStrategy, MQClientInstance mQClientFactory, DefaultMQPushConsumerImpl defaultMQPushConsumerImpl) { super(consumerGroup, messageModel, allocateMessageQueueStrategy, mQClientFactory); this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl; } @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { /* * When rebalance result changed, should update subscription's version to notify broker. * Fix: inconsistency subscription may lead to consumer miss messages. */ SubscriptionData subscriptionData = this.subscriptionInner.get(topic); long newVersion = System.currentTimeMillis(); log.info("{} Rebalance changed, also update version: {}, {}", topic, subscriptionData.getSubVersion(), newVersion); subscriptionData.setSubVersion(newVersion); int currentQueueCount = this.processQueueTable.size(); if (currentQueueCount != 0) { int pullThresholdForTopic = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getPullThresholdForTopic(); if (pullThresholdForTopic != -1) { int newVal = Math.max(1, pullThresholdForTopic / currentQueueCount); log.info("The pullThresholdForQueue is changed from {} to {}", this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getPullThresholdForQueue(), newVal); this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().setPullThresholdForQueue(newVal); } int pullThresholdSizeForTopic = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getPullThresholdSizeForTopic(); if (pullThresholdSizeForTopic != -1) { int newVal = Math.max(1, pullThresholdSizeForTopic / currentQueueCount); log.info("The pullThresholdSizeForQueue is changed from {} to {}", this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getPullThresholdSizeForQueue(), newVal); this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().setPullThresholdSizeForQueue(newVal); } } // notify broker this.getmQClientFactory().sendHeartbeatToAllBrokerWithLockV2(true); MessageQueueListener messageQueueListener = defaultMQPushConsumerImpl.getMessageQueueListener(); if (null != messageQueueListener) { messageQueueListener.messageQueueChanged(topic, mqAll, mqDivided); } } @Override public boolean removeUnnecessaryMessageQueue(final MessageQueue mq, final ProcessQueue pq) { if (this.defaultMQPushConsumerImpl.isConsumeOrderly() && MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) { // commit offset immediately this.defaultMQPushConsumerImpl.getOffsetStore().persist(mq); // remove order message queue: unlock & remove return tryRemoveOrderMessageQueue(mq, pq); } else { this.defaultMQPushConsumerImpl.getOffsetStore().persist(mq); this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq); return true; } } private boolean tryRemoveOrderMessageQueue(final MessageQueue mq, final ProcessQueue pq) { try { // unlock & remove when no message is consuming or UNLOCK_DELAY_TIME_MILLS timeout (Backwards compatibility) boolean forceUnlock = pq.isDropped() && System.currentTimeMillis() > pq.getLastLockTimestamp() + UNLOCK_DELAY_TIME_MILLS; if (forceUnlock || pq.getConsumeLock().writeLock().tryLock(500, TimeUnit.MILLISECONDS)) { try { RebalancePushImpl.this.defaultMQPushConsumerImpl.getOffsetStore().persist(mq); RebalancePushImpl.this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq); pq.setLocked(false); RebalancePushImpl.this.unlock(mq, true); return true; } finally { if (!forceUnlock) { pq.getConsumeLock().writeLock().unlock(); } } } else { pq.incTryUnlockTimes(); } } catch (Exception e) { pq.incTryUnlockTimes(); } return false; } @Override public boolean clientRebalance(String topic) { // POPTODO order pop consume not implement yet return defaultMQPushConsumerImpl.getDefaultMQPushConsumer().isClientRebalance() || defaultMQPushConsumerImpl.isConsumeOrderly() || MessageModel.BROADCASTING.equals(messageModel); } @Override public ConsumeType consumeType() { return ConsumeType.CONSUME_PASSIVELY; } @Override public void removeDirtyOffset(final MessageQueue mq) { this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq); } @Deprecated @Override public long computePullFromWhere(MessageQueue mq) { long result = -1L; try { result = computePullFromWhereWithException(mq); } catch (MQClientException e) { log.warn("Compute consume offset exception, mq={}", mq); } return result; } @Override public long computePullFromWhereWithException(MessageQueue mq) throws MQClientException { long result = -1; final ConsumeFromWhere consumeFromWhere = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere(); final OffsetStore offsetStore = this.defaultMQPushConsumerImpl.getOffsetStore(); switch (consumeFromWhere) { case CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST: case CONSUME_FROM_MIN_OFFSET: case CONSUME_FROM_MAX_OFFSET: case CONSUME_FROM_LAST_OFFSET: { long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE); if (lastOffset >= 0) { result = lastOffset; } // First start,no offset else if (-1 == lastOffset) { if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { result = 0L; } else { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); throw e; } } } else { throw new MQClientException(ResponseCode.QUERY_NOT_FOUND, "Failed to query consume offset from " + "offset store"); } break; } case CONSUME_FROM_FIRST_OFFSET: { long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { //the offset will be fixed by the OFFSET_ILLEGAL process result = 0L; } else { throw new MQClientException(ResponseCode.QUERY_NOT_FOUND, "Failed to query offset from offset " + "store"); } break; } case CONSUME_FROM_TIMESTAMP: { long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); throw e; } } else { try { long timestamp = UtilAll.parseDate(this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeTimestamp(), UtilAll.YYYYMMDDHHMMSS).getTime(); result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } catch (MQClientException e) { log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); throw e; } } } else { throw new MQClientException(ResponseCode.QUERY_NOT_FOUND, "Failed to query offset from offset " + "store"); } break; } default: break; } if (result < 0) { throw new MQClientException(ResponseCode.SYSTEM_ERROR, "Found unexpected result " + result); } return result; } @Override public int getConsumeInitMode() { final ConsumeFromWhere consumeFromWhere = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere(); if (ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET == consumeFromWhere) { return ConsumeInitMode.MIN; } else { return ConsumeInitMode.MAX; } } @Override public void dispatchPullRequest(final List pullRequestList, final long delay) { for (PullRequest pullRequest : pullRequestList) { if (delay <= 0) { this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest); } else { this.defaultMQPushConsumerImpl.executePullRequestLater(pullRequest, delay); } } } @Override public void dispatchPopPullRequest(final List pullRequestList, final long delay) { for (PopRequest pullRequest : pullRequestList) { if (delay <= 0) { this.defaultMQPushConsumerImpl.executePopPullRequestImmediately(pullRequest); } else { this.defaultMQPushConsumerImpl.executePopPullRequestLater(pullRequest, delay); } } } @Override public ProcessQueue createProcessQueue() { return new ProcessQueue(); } @Override public PopProcessQueue createPopProcessQueue() { return new PopProcessQueue(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class RebalanceService extends ServiceThread { private static long waitInterval = Long.parseLong(System.getProperty( "rocketmq.client.rebalance.waitInterval", "20000")); private static long minInterval = Long.parseLong(System.getProperty( "rocketmq.client.rebalance.minInterval", "1000")); private final Logger log = LoggerFactory.getLogger(RebalanceService.class); private final MQClientInstance mqClientFactory; private long lastRebalanceTimestamp = System.currentTimeMillis(); public RebalanceService(MQClientInstance mqClientFactory) { this.mqClientFactory = mqClientFactory; } @Override public void run() { log.info(this.getServiceName() + " service started"); long realWaitInterval = waitInterval; while (!this.isStopped()) { this.waitForRunning(realWaitInterval); long interval = System.currentTimeMillis() - lastRebalanceTimestamp; if (interval < minInterval) { realWaitInterval = minInterval - interval; } else { boolean balanced = this.mqClientFactory.doRebalance(); realWaitInterval = balanced ? waitInterval : minInterval; lastRebalanceTimestamp = System.currentTimeMillis(); } } log.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { return RebalanceService.class.getSimpleName(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.factory; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.admin.MQAdminExtInner; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.DefaultMQPullConsumerImpl; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.MQConsumerInner; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.RebalanceService; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.MQProducerInner; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.common.HeartbeatV2Result; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.ProducerData; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static org.apache.rocketmq.remoting.rpc.ClientMetadata.topicRouteData2EndpointsForStaticTopic; public class MQClientInstance { private final static long LOCK_TIMEOUT_MILLIS = 3000; private final static Logger log = LoggerFactory.getLogger(MQClientInstance.class); private final ClientConfig clientConfig; private final String clientId; private final long bootTimestamp = System.currentTimeMillis(); /** * The container of the producer in the current client. The key is the name of producerGroup. */ private final ConcurrentMap producerTable = new ConcurrentHashMap<>(); /** * The container of the consumer in the current client. The key is the name of consumerGroup. */ private final ConcurrentMap consumerTable = new ConcurrentHashMap<>(); /** * The container of the adminExt in the current client. The key is the name of adminExtGroup. */ private final ConcurrentMap adminExtTable = new ConcurrentHashMap<>(); private final NettyClientConfig nettyClientConfig; private final MQClientAPIImpl mQClientAPIImpl; private final MQAdminImpl mQAdminImpl; private final ConcurrentMap topicRouteTable = new ConcurrentHashMap<>(); private final ConcurrentMap> topicEndPointsTable = new ConcurrentHashMap<>(); private final Lock lockNamesrv = new ReentrantLock(); private final Lock lockHeartbeat = new ReentrantLock(); /** * The container which stores the brokerClusterInfo. The key of the map is the broker name. * And the value is the broker instance list that belongs to the broker cluster. * For the sub map, the key is the id of single broker instance, and the value is the address. */ private final ConcurrentMap> brokerAddrTable = new ConcurrentHashMap<>(); private final ConcurrentMap> brokerVersionTable = new ConcurrentHashMap<>(); private final Set brokerSupportV2HeartbeatSet = new HashSet<>(); private final ConcurrentMap brokerAddrHeartbeatFingerprintTable = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "MQClientFactoryScheduledThread")); private final ScheduledExecutorService fetchRemoteConfigExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "MQClientFactoryFetchRemoteConfigScheduledThread"); } }); private final PullMessageService pullMessageService; private final RebalanceService rebalanceService; private final DefaultMQProducer defaultMQProducer; private final ConsumerStatsManager consumerStatsManager; private final AtomicLong sendHeartbeatTimesTotal = new AtomicLong(0); private ServiceState serviceState = ServiceState.CREATE_JUST; private final Random random = new Random(); private ExecutorService concurrentHeartbeatExecutor; public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId) { this(clientConfig, instanceIndex, clientId, null); } public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) { this.clientConfig = clientConfig; this.nettyClientConfig = new NettyClientConfig(); this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads()); this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS()); this.nettyClientConfig.setSocksProxyConfig(clientConfig.getSocksProxyConfig()); this.nettyClientConfig.setScanAvailableNameSrv(false); ClientRemotingProcessor clientRemotingProcessor = new ClientRemotingProcessor(this); ChannelEventListener channelEventListener; if (clientConfig.isEnableHeartbeatChannelEventListener()) { channelEventListener = new ChannelEventListener() { private final ConcurrentMap> brokerAddrTable = MQClientInstance.this.brokerAddrTable; @Override public void onChannelConnect(String remoteAddr, Channel channel) { } @Override public void onChannelClose(String remoteAddr, Channel channel) { } @Override public void onChannelException(String remoteAddr, Channel channel) { } @Override public void onChannelIdle(String remoteAddr, Channel channel) { } @Override public void onChannelActive(String remoteAddr, Channel channel) { for (Map.Entry> addressEntry : brokerAddrTable.entrySet()) { for (Map.Entry entry : addressEntry.getValue().entrySet()) { String addr = entry.getValue(); if (addr.equals(remoteAddr)) { long id = entry.getKey(); String brokerName = addressEntry.getKey(); if (sendHeartbeatToBroker(id, brokerName, addr, false)) { rebalanceImmediately(); } break; } } } } }; } else { channelEventListener = null; } this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig, channelEventListener); if (this.clientConfig.getNamesrvAddr() != null) { this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr()); log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr()); } this.clientId = clientId; this.mQAdminImpl = new MQAdminImpl(this); this.pullMessageService = new PullMessageService(this); this.rebalanceService = new RebalanceService(this); this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP); this.defaultMQProducer.resetClientConfig(clientConfig); this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService); if (this.clientConfig.isEnableConcurrentHeartbeat()) { this.concurrentHeartbeatExecutor = Executors.newFixedThreadPool( clientConfig.getConcurrentHeartbeatThreadPoolSize(), new ThreadFactoryImpl("MQClientConcurrentHeartbeatThread_", true)); } log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}", instanceIndex, this.clientId, this.clientConfig, MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer()); } public static TopicPublishInfo topicRouteData2TopicPublishInfo(final String topic, final TopicRouteData route) { TopicPublishInfo info = new TopicPublishInfo(); // TO DO should check the usage of raw route, it is better to remove such field info.setTopicRouteData(route); if (route.getOrderTopicConf() != null && route.getOrderTopicConf().length() > 0) { String[] brokers = route.getOrderTopicConf().split(";"); for (String broker : brokers) { String[] item = broker.split(":"); int nums = Integer.parseInt(item[1]); for (int i = 0; i < nums; i++) { MessageQueue mq = new MessageQueue(topic, item[0], i); info.getMessageQueueList().add(mq); } } info.setOrderTopic(true); } else if (route.getOrderTopicConf() == null && route.getTopicQueueMappingByBroker() != null && !route.getTopicQueueMappingByBroker().isEmpty()) { info.setOrderTopic(false); ConcurrentMap mqEndPoints = topicRouteData2EndpointsForStaticTopic(topic, route); info.getMessageQueueList().addAll(mqEndPoints.keySet()); info.getMessageQueueList().sort((mq1, mq2) -> MixAll.compareInteger(mq1.getQueueId(), mq2.getQueueId())); } else { List qds = route.getQueueDatas(); Collections.sort(qds); for (QueueData qd : qds) { if (PermName.isWriteable(qd.getPerm())) { BrokerData brokerData = null; for (BrokerData bd : route.getBrokerDatas()) { if (bd.getBrokerName().equals(qd.getBrokerName())) { brokerData = bd; break; } } if (null == brokerData) { continue; } if (!brokerData.getBrokerAddrs().containsKey(MixAll.MASTER_ID)) { continue; } for (int i = 0; i < qd.getWriteQueueNums(); i++) { MessageQueue mq = new MessageQueue(topic, qd.getBrokerName(), i); info.getMessageQueueList().add(mq); } } } info.setOrderTopic(false); } return info; } public static Set topicRouteData2TopicSubscribeInfo(final String topic, final TopicRouteData route) { Set mqList = new HashSet<>(); if (route.getTopicQueueMappingByBroker() != null && !route.getTopicQueueMappingByBroker().isEmpty()) { ConcurrentMap mqEndPoints = topicRouteData2EndpointsForStaticTopic(topic, route); return mqEndPoints.keySet(); } List qds = route.getQueueDatas(); for (QueueData qd : qds) { if (PermName.isReadable(qd.getPerm())) { for (int i = 0; i < qd.getReadQueueNums(); i++) { MessageQueue mq = new MessageQueue(topic, qd.getBrokerName(), i); mqList.add(mq); } } } return mqList; } public void start() throws MQClientException { synchronized (this) { switch (this.serviceState) { case CREATE_JUST: this.serviceState = ServiceState.START_FAILED; // If not specified,looking address from name server if (null == this.clientConfig.getNamesrvAddr()) { this.mQClientAPIImpl.fetchNameServerAddr(); } // Start request-response channel this.mQClientAPIImpl.start(); // Start various schedule tasks this.startScheduledTask(); // Start pull service this.pullMessageService.start(); // Start rebalance service this.rebalanceService.start(); // Start push service this.defaultMQProducer.getDefaultMQProducerImpl().start(false); log.info("the client factory [{}] start OK", this.clientId); this.serviceState = ServiceState.RUNNING; break; case START_FAILED: throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null); default: break; } } } private void startScheduledTask() { if (null == this.clientConfig.getNamesrvAddr()) { this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr(); } catch (Throwable t) { log.error("ScheduledTask fetchNameServerAddr exception", t); } }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); } this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { MQClientInstance.this.updateTopicRouteInfoFromNameServer(); } catch (Throwable t) { log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", t); } }, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { MQClientInstance.this.cleanOfflineBroker(); MQClientInstance.this.sendHeartbeatToAllBrokerWithLock(); } catch (Throwable t) { log.error("ScheduledTask sendHeartbeatToAllBroker exception", t); } }, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { MQClientInstance.this.persistAllConsumerOffset(); } catch (Throwable t) { log.error("ScheduledTask persistAllConsumerOffset exception", t); } }, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { MQClientInstance.this.adjustThreadPool(); } catch (Throwable t) { log.error("ScheduledTask adjustThreadPool exception", t); } }, 1, 1, TimeUnit.MINUTES); } public String getClientId() { return clientId; } public void updateTopicRouteInfoFromNameServer() { Set topicList = new HashSet<>(); // Consumer { for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { Set subList = impl.subscriptions(); if (subList != null) { for (SubscriptionData subData : subList) { topicList.add(subData.getTopic()); } } } } } // Producer { for (Entry entry : this.producerTable.entrySet()) { MQProducerInner impl = entry.getValue(); if (impl != null) { Set lst = impl.getPublishTopicList(); topicList.addAll(lst); } } } for (String topic : topicList) { this.updateTopicRouteInfoFromNameServer(topic); } } public Map parseOffsetTableFromBroker(Map offsetTable, String namespace) { HashMap newOffsetTable = new HashMap<>(offsetTable.size(), 1); if (StringUtils.isNotEmpty(namespace)) { for (Entry entry : offsetTable.entrySet()) { MessageQueue queue = entry.getKey(); queue.setTopic(NamespaceUtil.withoutNamespace(queue.getTopic(), namespace)); newOffsetTable.put(queue, entry.getValue()); } } else { newOffsetTable.putAll(offsetTable); } return newOffsetTable; } /** * Remove offline broker */ private void cleanOfflineBroker() { try { if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) try { ConcurrentHashMap> updatedTable = new ConcurrentHashMap<>(this.brokerAddrTable.size(), 1); Iterator>> itBrokerTable = this.brokerAddrTable.entrySet().iterator(); while (itBrokerTable.hasNext()) { Entry> entry = itBrokerTable.next(); String brokerName = entry.getKey(); HashMap oneTable = entry.getValue(); HashMap cloneAddrTable = new HashMap<>(oneTable.size(), 1); cloneAddrTable.putAll(oneTable); Iterator> it = cloneAddrTable.entrySet().iterator(); while (it.hasNext()) { Entry ee = it.next(); String addr = ee.getValue(); if (!this.isBrokerAddrExistInTopicRouteTable(addr)) { it.remove(); log.info("the broker addr[{} {}] is offline, remove it", brokerName, addr); } } if (cloneAddrTable.isEmpty()) { itBrokerTable.remove(); log.info("the broker[{}] name's host is offline, remove it", brokerName); } else { updatedTable.put(brokerName, cloneAddrTable); } } if (!updatedTable.isEmpty()) { this.brokerAddrTable.putAll(updatedTable); } } finally { this.lockNamesrv.unlock(); } } catch (InterruptedException e) { log.warn("cleanOfflineBroker Exception", e); } } public void checkClientInBroker() throws MQClientException { for (Entry entry : this.consumerTable.entrySet()) { Set subscriptionInner = entry.getValue().subscriptions(); if (subscriptionInner == null || subscriptionInner.isEmpty()) { return; } for (SubscriptionData subscriptionData : subscriptionInner) { if (ExpressionType.isTagType(subscriptionData.getExpressionType())) { continue; } // may need to check one broker every cluster... // assume that the configs of every broker in cluster are the same. String addr = findBrokerAddrByTopic(subscriptionData.getTopic()); if (addr != null) { try { this.getMQClientAPIImpl().checkClientInBroker( addr, entry.getKey(), this.clientId, subscriptionData, clientConfig.getMqClientApiTimeout() ); } catch (Exception e) { if (e instanceof MQClientException) { throw (MQClientException) e; } else { throw new MQClientException("Check client in broker error, maybe because you use " + subscriptionData.getExpressionType() + " to filter message, but server has not been upgraded to support!" + "This error would not affect the launch of consumer, but may has impact on message receiving if you " + "have use the new features which are not supported by server, please check the log!", e); } } } } } } public boolean sendHeartbeatToAllBrokerWithLockV2(boolean isRebalance) { if (this.lockHeartbeat.tryLock()) { try { if (clientConfig.isUseHeartbeatV2()) { return this.sendHeartbeatToAllBrokerV2(isRebalance); } else { return this.sendHeartbeatToAllBroker(); } } catch (final Exception e) { log.error("sendHeartbeatToAllBrokerWithLockV2 exception", e); } finally { this.lockHeartbeat.unlock(); } } else { log.warn("sendHeartbeatToAllBrokerWithLockV2 lock heartBeat, but failed."); } return false; } public boolean sendHeartbeatToAllBrokerWithLock() { if (this.lockHeartbeat.tryLock()) { try { if (clientConfig.isUseHeartbeatV2()) { return this.sendHeartbeatToAllBrokerV2(false); } else if (clientConfig.isEnableConcurrentHeartbeat()) { return this.sendHeartbeatToAllBrokerConcurrently(); } else { return this.sendHeartbeatToAllBroker(); } } catch (final Exception e) { log.error("sendHeartbeatToAllBroker exception", e); } finally { this.lockHeartbeat.unlock(); } } else { log.warn("lock heartBeat, but failed. [{}]", this.clientId); } return false; } private void persistAllConsumerOffset() { for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); impl.persistConsumerOffset(); } } public void adjustThreadPool() { for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { try { if (impl instanceof DefaultMQPushConsumerImpl) { DefaultMQPushConsumerImpl dmq = (DefaultMQPushConsumerImpl) impl; dmq.adjustThreadPool(); } } catch (Exception ignored) { } } } } public boolean updateTopicRouteInfoFromNameServer(final String topic) { return updateTopicRouteInfoFromNameServer(topic, false, null); } private boolean isBrokerAddrExistInTopicRouteTable(final String addr) { for (Entry entry : this.topicRouteTable.entrySet()) { TopicRouteData topicRouteData = entry.getValue(); List bds = topicRouteData.getBrokerDatas(); for (BrokerData bd : bds) { if (bd.getBrokerAddrs() != null) { boolean exist = bd.getBrokerAddrs().containsValue(addr); if (exist) return true; } } } return false; } public boolean sendHeartbeatToBroker(long id, String brokerName, String addr) { return sendHeartbeatToBroker(id, brokerName, addr, true); } /** * @param id * @param brokerName * @param addr * @param strictLockMode When the connection is initially established, sending a heartbeat will simultaneously trigger the onChannelActive event to acquire the lock again, causing an exception. Therefore, * the exception that occurs when sending the heartbeat during the initial onChannelActive event can be ignored. * @return */ public boolean sendHeartbeatToBroker(long id, String brokerName, String addr, boolean strictLockMode) { if (this.lockHeartbeat.tryLock()) { try { final HeartbeatData heartbeatDataWithSub = this.prepareHeartbeatData(false); final boolean producerEmpty = heartbeatDataWithSub.getProducerDataSet().isEmpty(); final boolean consumerEmpty = heartbeatDataWithSub.getConsumerDataSet().isEmpty(); if (producerEmpty && consumerEmpty) { log.warn("sendHeartbeatToBroker sending heartbeat, but no consumer and no producer. [{}]", this.clientId); return false; } if (clientConfig.isUseHeartbeatV2()) { int currentHeartbeatFingerprint = heartbeatDataWithSub.computeHeartbeatFingerprint(); heartbeatDataWithSub.setHeartbeatFingerprint(currentHeartbeatFingerprint); HeartbeatData heartbeatDataWithoutSub = this.prepareHeartbeatData(true); heartbeatDataWithoutSub.setHeartbeatFingerprint(currentHeartbeatFingerprint); return this.sendHeartbeatToBrokerV2(id, brokerName, addr, heartbeatDataWithSub, heartbeatDataWithoutSub, currentHeartbeatFingerprint); } else { return this.sendHeartbeatToBroker(id, brokerName, addr, heartbeatDataWithSub); } } catch (final Exception e) { log.error("sendHeartbeatToAllBroker exception", e); } finally { this.lockHeartbeat.unlock(); } } else { if (strictLockMode) { log.warn("lock heartBeat, but failed. [{}]", this.clientId); } } return false; } private boolean sendHeartbeatToBroker(long id, String brokerName, String addr, HeartbeatData heartbeatData) { try { int version = this.mQClientAPIImpl.sendHeartbeat(addr, heartbeatData, clientConfig.getMqClientApiTimeout()); if (!this.brokerVersionTable.containsKey(brokerName)) { this.brokerVersionTable.put(brokerName, new ConcurrentHashMap<>(4)); } this.brokerVersionTable.get(brokerName).put(addr, version); long times = this.sendHeartbeatTimesTotal.getAndIncrement(); if (times % 20 == 0) { log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr); log.info(heartbeatData.toString()); } return true; } catch (Exception e) { if (this.isBrokerInNameServer(addr)) { log.warn("send heart beat to broker[{} {} {}] failed", brokerName, id, addr, e); } else { log.warn("send heart beat to broker[{} {} {}] exception, because the broker not up, forget it", brokerName, id, addr, e); } } return false; } private boolean sendHeartbeatToAllBroker() { final HeartbeatData heartbeatData = this.prepareHeartbeatData(false); final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty(); final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty(); if (producerEmpty && consumerEmpty) { log.warn("sending heartbeat, but no consumer and no producer. [{}]", this.clientId); return false; } if (this.brokerAddrTable.isEmpty()) { return false; } for (Entry> brokerClusterInfo : this.brokerAddrTable.entrySet()) { String brokerName = brokerClusterInfo.getKey(); HashMap oneTable = brokerClusterInfo.getValue(); if (oneTable == null) { continue; } for (Entry singleBrokerInstance : oneTable.entrySet()) { Long id = singleBrokerInstance.getKey(); String addr = singleBrokerInstance.getValue(); if (addr == null) { continue; } if (consumerEmpty && MixAll.MASTER_ID != id) { continue; } sendHeartbeatToBroker(id, brokerName, addr, heartbeatData); } } return true; } private boolean sendHeartbeatToBrokerV2(long id, String brokerName, String addr, HeartbeatData heartbeatDataWithSub, HeartbeatData heartbeatDataWithoutSub, int currentHeartbeatFingerprint) { try { int version = 0; boolean isBrokerSupportV2 = brokerSupportV2HeartbeatSet.contains(addr); HeartbeatV2Result heartbeatV2Result = null; if (isBrokerSupportV2 && null != brokerAddrHeartbeatFingerprintTable.get(addr) && brokerAddrHeartbeatFingerprintTable.get(addr) == currentHeartbeatFingerprint) { heartbeatV2Result = this.mQClientAPIImpl.sendHeartbeatV2(addr, heartbeatDataWithoutSub, clientConfig.getMqClientApiTimeout()); if (heartbeatV2Result.isSubChange()) { brokerAddrHeartbeatFingerprintTable.remove(addr); } log.info("sendHeartbeatToAllBrokerV2 simple brokerName: {} subChange: {} brokerAddrHeartbeatFingerprintTable: {}", brokerName, heartbeatV2Result.isSubChange(), JSON.toJSONString(brokerAddrHeartbeatFingerprintTable)); } else { heartbeatV2Result = this.mQClientAPIImpl.sendHeartbeatV2(addr, heartbeatDataWithSub, clientConfig.getMqClientApiTimeout()); if (heartbeatV2Result.isSupportV2()) { brokerSupportV2HeartbeatSet.add(addr); if (heartbeatV2Result.isSubChange()) { brokerAddrHeartbeatFingerprintTable.remove(addr); } else if (!brokerAddrHeartbeatFingerprintTable.containsKey(addr) || brokerAddrHeartbeatFingerprintTable.get(addr) != currentHeartbeatFingerprint) { brokerAddrHeartbeatFingerprintTable.put(addr, currentHeartbeatFingerprint); } } log.info("sendHeartbeatToAllBrokerV2 normal brokerName: {} subChange: {} brokerAddrHeartbeatFingerprintTable: {}", brokerName, heartbeatV2Result.isSubChange(), JSON.toJSONString(brokerAddrHeartbeatFingerprintTable)); } version = heartbeatV2Result.getVersion(); if (!this.brokerVersionTable.containsKey(brokerName)) { this.brokerVersionTable.put(brokerName, new ConcurrentHashMap<>(4)); } this.brokerVersionTable.get(brokerName).put(addr, version); long times = this.sendHeartbeatTimesTotal.getAndIncrement(); if (times % 20 == 0) { log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr); log.info(heartbeatDataWithSub.toString()); } return true; } catch (Exception e) { if (this.isBrokerInNameServer(addr)) { log.warn("sendHeartbeatToAllBrokerV2 send heart beat to broker[{} {} {}] failed", brokerName, id, addr, e); } else { log.warn("sendHeartbeatToAllBrokerV2 send heart beat to broker[{} {} {}] exception, because the broker not up, forget it", brokerName, id, addr, e); } } return false; } private boolean sendHeartbeatToAllBrokerV2(boolean isRebalance) { final HeartbeatData heartbeatDataWithSub = this.prepareHeartbeatData(false); final boolean producerEmpty = heartbeatDataWithSub.getProducerDataSet().isEmpty(); final boolean consumerEmpty = heartbeatDataWithSub.getConsumerDataSet().isEmpty(); if (producerEmpty && consumerEmpty) { log.warn("sendHeartbeatToAllBrokerV2 sending heartbeat, but no consumer and no producer. [{}]", this.clientId); return false; } if (this.brokerAddrTable.isEmpty()) { return false; } if (isRebalance) { resetBrokerAddrHeartbeatFingerprintMap(); } int currentHeartbeatFingerprint = heartbeatDataWithSub.computeHeartbeatFingerprint(); heartbeatDataWithSub.setHeartbeatFingerprint(currentHeartbeatFingerprint); HeartbeatData heartbeatDataWithoutSub = this.prepareHeartbeatData(true); heartbeatDataWithoutSub.setHeartbeatFingerprint(currentHeartbeatFingerprint); for (Entry> brokerClusterInfo : this.brokerAddrTable.entrySet()) { String brokerName = brokerClusterInfo.getKey(); HashMap oneTable = brokerClusterInfo.getValue(); if (oneTable == null) { continue; } for (Entry singleBrokerInstance : oneTable.entrySet()) { Long id = singleBrokerInstance.getKey(); String addr = singleBrokerInstance.getValue(); if (addr == null) { continue; } if (consumerEmpty && MixAll.MASTER_ID != id) { continue; } sendHeartbeatToBrokerV2(id, brokerName, addr, heartbeatDataWithSub, heartbeatDataWithoutSub, currentHeartbeatFingerprint); } } return true; } private class ClientHeartBeatTask { private final String brokerName; private final Long brokerId; private final String brokerAddr; private final HeartbeatData heartbeatData; public ClientHeartBeatTask(String brokerName, Long brokerId, String brokerAddr, HeartbeatData heartbeatData) { this.brokerName = brokerName; this.brokerId = brokerId; this.brokerAddr = brokerAddr; this.heartbeatData = heartbeatData; } public void execute() throws Exception { int version = MQClientInstance.this.mQClientAPIImpl.sendHeartbeat( brokerAddr, heartbeatData, MQClientInstance.this.clientConfig.getMqClientApiTimeout()); ConcurrentHashMap inner = MQClientInstance.this.brokerVersionTable .computeIfAbsent(brokerName, k -> new ConcurrentHashMap<>(4)); inner.put(brokerAddr, version); } } private boolean sendHeartbeatToAllBrokerConcurrently() { final HeartbeatData heartbeatData = this.prepareHeartbeatData(false); final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty(); final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty(); if (producerEmpty && consumerEmpty) { log.warn("sending heartbeat, but no consumer and no producer. [{}]", this.clientId); return false; } if (this.brokerAddrTable.isEmpty()) { return false; } long times = this.sendHeartbeatTimesTotal.getAndIncrement(); List tasks = new ArrayList<>(); for (Entry> entry : this.brokerAddrTable.entrySet()) { String brokerName = entry.getKey(); HashMap oneTable = entry.getValue(); if (oneTable != null) { for (Map.Entry entry1 : oneTable.entrySet()) { Long id = entry1.getKey(); String addr = entry1.getValue(); if (addr == null) continue; if (consumerEmpty && id != MixAll.MASTER_ID) continue; tasks.add(new ClientHeartBeatTask(brokerName, id, addr, heartbeatData)); } } } if (tasks.isEmpty()) { return false; } final CountDownLatch latch = new CountDownLatch(tasks.size()); for (ClientHeartBeatTask task : tasks) { try { this.concurrentHeartbeatExecutor.execute(() -> { try { task.execute(); if (times % 20 == 0) { log.info("send heart beat to broker[{} {} {}] success", task.brokerName, task.brokerId, task.brokerAddr); } } catch (Exception e) { if (MQClientInstance.this.isBrokerInNameServer(task.brokerAddr)) { log.warn("send heart beat to broker[{} {} {}] failed", task.brokerName, task.brokerId, task.brokerAddr, e); } else { log.warn("send heart beat to broker[{} {} {}] exception, because the broker not up, forget it", task.brokerName, task.brokerId, task.brokerAddr, e); } } finally { latch.countDown(); } }); } catch (RejectedExecutionException rex) { log.warn("heartbeat submission rejected for broker[{} {} {}], will skip this round", task.brokerName, task.brokerId, task.brokerAddr, rex); latch.countDown(); } } try { // wait all tasks finish latch.await(); } catch (InterruptedException ie) { log.warn("Interrupted while waiting for broker heartbeat tasks to complete", ie); Thread.currentThread().interrupt(); } return true; } public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault, DefaultMQProducer defaultMQProducer) { try { if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { TopicRouteData topicRouteData; if (isDefault && defaultMQProducer != null) { topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(clientConfig.getMqClientApiTimeout()); if (topicRouteData != null) { for (QueueData data : topicRouteData.getQueueDatas()) { int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums()); data.setReadQueueNums(queueNums); data.setWriteQueueNums(queueNums); } } } else { topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, clientConfig.getMqClientApiTimeout()); } if (topicRouteData != null) { TopicRouteData old = this.topicRouteTable.get(topic); boolean changed = topicRouteData.topicRouteDataChanged(old); if (!changed) { changed = this.isNeedUpdateTopicRouteInfo(topic); } else { log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData); } if (changed) { for (BrokerData bd : topicRouteData.getBrokerDatas()) { this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs()); } // Update endpoint map { ConcurrentMap mqEndPoints = topicRouteData2EndpointsForStaticTopic(topic, topicRouteData); if (!mqEndPoints.isEmpty()) { topicEndPointsTable.put(topic, mqEndPoints); } } // Update Pub info { TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData); publishInfo.setHaveTopicRouterInfo(true); for (Entry entry : this.producerTable.entrySet()) { MQProducerInner impl = entry.getValue(); if (impl != null) { impl.updateTopicPublishInfo(topic, publishInfo); } } } // Update sub info if (!consumerTable.isEmpty()) { Set subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData); for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { impl.updateTopicSubscribeInfo(topic, subscribeInfo); } } } TopicRouteData cloneTopicRouteData = new TopicRouteData(topicRouteData); log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData); this.topicRouteTable.put(topic, cloneTopicRouteData); return true; } } else { log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}. [{}]", topic, this.clientId); } } catch (MQClientException e) { if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && !topic.equals(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC)) { log.warn("updateTopicRouteInfoFromNameServer Exception", e); } } catch (RemotingException e) { log.error("updateTopicRouteInfoFromNameServer Exception", e); throw new IllegalStateException(e); } finally { this.lockNamesrv.unlock(); } } else { log.warn("updateTopicRouteInfoFromNameServer tryLock timeout {}ms. [{}]", LOCK_TIMEOUT_MILLIS, this.clientId); } } catch (InterruptedException e) { log.warn("updateTopicRouteInfoFromNameServer Exception", e); } return false; } private HeartbeatData prepareHeartbeatData(boolean isWithoutSub) { HeartbeatData heartbeatData = new HeartbeatData(); // clientID heartbeatData.setClientID(this.clientId); // Consumer for (Map.Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { ConsumerData consumerData = new ConsumerData(); consumerData.setGroupName(impl.groupName()); consumerData.setConsumeType(impl.consumeType()); consumerData.setMessageModel(impl.messageModel()); consumerData.setConsumeFromWhere(impl.consumeFromWhere()); consumerData.setUnitMode(impl.isUnitMode()); if (!isWithoutSub) { consumerData.getSubscriptionDataSet().addAll(impl.subscriptions()); } heartbeatData.getConsumerDataSet().add(consumerData); } } // Producer for (Map.Entry entry : this.producerTable.entrySet()) { MQProducerInner impl = entry.getValue(); if (impl != null) { ProducerData producerData = new ProducerData(); producerData.setGroupName(entry.getKey()); heartbeatData.getProducerDataSet().add(producerData); } } heartbeatData.setWithoutSub(isWithoutSub); return heartbeatData; } private boolean isBrokerInNameServer(final String brokerAddr) { for (Entry itNext : this.topicRouteTable.entrySet()) { List brokerDatas = itNext.getValue().getBrokerDatas(); for (BrokerData bd : brokerDatas) { boolean contain = bd.getBrokerAddrs().containsValue(brokerAddr); if (contain) return true; } } return false; } private boolean isNeedUpdateTopicRouteInfo(final String topic) { boolean result = false; Iterator> producerIterator = this.producerTable.entrySet().iterator(); while (producerIterator.hasNext() && !result) { Entry entry = producerIterator.next(); MQProducerInner impl = entry.getValue(); if (impl != null) { result = impl.isPublishTopicNeedUpdate(topic); } } if (result) { return true; } Iterator> consumerIterator = this.consumerTable.entrySet().iterator(); while (consumerIterator.hasNext() && !result) { Entry entry = consumerIterator.next(); MQConsumerInner impl = entry.getValue(); if (impl != null) { result = impl.isSubscribeTopicNeedUpdate(topic); } } return result; } public void shutdown() { // Consumer if (!this.consumerTable.isEmpty()) return; // AdminExt if (!this.adminExtTable.isEmpty()) return; // Producer if (this.producerTable.size() > 1) return; synchronized (this) { switch (this.serviceState) { case RUNNING: this.defaultMQProducer.getDefaultMQProducerImpl().shutdown(false); this.serviceState = ServiceState.SHUTDOWN_ALREADY; this.pullMessageService.shutdown(true); this.scheduledExecutorService.shutdown(); this.mQClientAPIImpl.shutdown(); this.rebalanceService.shutdown(); if (concurrentHeartbeatExecutor != null) { this.concurrentHeartbeatExecutor.shutdown(); } MQClientManager.getInstance().removeClientFactory(this.clientId); log.info("the client factory [{}] shutdown OK", this.clientId); break; case CREATE_JUST: case SHUTDOWN_ALREADY: default: break; } } } public synchronized boolean registerConsumer(final String group, final MQConsumerInner consumer) { if (null == group || null == consumer) { return false; } MQConsumerInner prev = this.consumerTable.putIfAbsent(group, consumer); if (prev != null) { log.warn("the consumer group[" + group + "] exist already."); return false; } return true; } public synchronized void unregisterConsumer(final String group) { this.consumerTable.remove(group); this.unregisterClient(null, group); } private void unregisterClient(final String producerGroup, final String consumerGroup) { for (Entry> brokerClusterInfo : this.brokerAddrTable.entrySet()) { String brokerName = brokerClusterInfo.getKey(); HashMap oneTable = brokerClusterInfo.getValue(); if (oneTable == null) { continue; } for (Entry singleBrokerInstance : oneTable.entrySet()) { String addr = singleBrokerInstance.getValue(); if (addr != null) { try { this.mQClientAPIImpl.unregisterClient(addr, this.clientId, producerGroup, consumerGroup, clientConfig.getMqClientApiTimeout()); log.info("unregister client[Producer: {} Consumer: {}] from broker[{} {} {}] success", producerGroup, consumerGroup, brokerName, singleBrokerInstance.getKey(), addr); } catch (RemotingException e) { log.warn("unregister client RemotingException from broker: {}, {}", addr, e.getMessage()); } catch (InterruptedException e) { log.warn("unregister client InterruptedException from broker: {}, {}", addr, e.getMessage()); } catch (MQBrokerException e) { log.warn("unregister client MQBrokerException from broker: {}, {}", addr, e.getMessage()); } } } } } public synchronized boolean registerProducer(final String group, final DefaultMQProducerImpl producer) { if (null == group || null == producer) { return false; } MQProducerInner prev = this.producerTable.putIfAbsent(group, producer); if (prev != null) { log.warn("the producer group[{}] exist already.", group); return false; } return true; } public synchronized void unregisterProducer(final String group) { this.producerTable.remove(group); this.unregisterClient(group, null); } public boolean registerAdminExt(final String group, final MQAdminExtInner admin) { if (null == group || null == admin) { return false; } MQAdminExtInner prev = this.adminExtTable.putIfAbsent(group, admin); if (prev != null) { log.warn("the admin group[{}] exist already.", group); return false; } return true; } public void unregisterAdminExt(final String group) { this.adminExtTable.remove(group); } public void rebalanceLater(long delayMillis) { if (delayMillis <= 0) { this.rebalanceService.wakeup(); } else { this.scheduledExecutorService.schedule(MQClientInstance.this.rebalanceService::wakeup, delayMillis, TimeUnit.MILLISECONDS); } } public void rebalanceImmediately() { this.rebalanceService.wakeup(); } public boolean doRebalance() { boolean balanced = true; for (Map.Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { try { if (!impl.tryRebalance()) { balanced = false; } } catch (Throwable e) { log.error("doRebalance for consumer group [{}] exception", entry.getKey(), e); } } } return balanced; } public MQProducerInner selectProducer(final String group) { return this.producerTable.get(group); } public MQConsumerInner selectConsumer(final String group) { return this.consumerTable.get(group); } public String getBrokerNameFromMessageQueue(final MessageQueue mq) { if (topicEndPointsTable.get(mq.getTopic()) != null && !topicEndPointsTable.get(mq.getTopic()).isEmpty()) { return topicEndPointsTable.get(mq.getTopic()).get(mq); } return mq.getBrokerName(); } public FindBrokerResult findBrokerAddressInAdmin(final String brokerName) { if (brokerName == null) { return null; } String brokerAddr = null; boolean slave = false; boolean found = false; HashMap map = this.brokerAddrTable.get(brokerName); if (map != null && !map.isEmpty()) { for (Map.Entry entry : map.entrySet()) { Long id = entry.getKey(); brokerAddr = entry.getValue(); if (brokerAddr != null) { found = true; slave = MixAll.MASTER_ID != id; break; } } // end of for } if (found) { return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr)); } return null; } public String findBrokerAddressInPublish(final String brokerName) { if (brokerName == null) { return null; } HashMap map = this.brokerAddrTable.get(brokerName); if (map != null && !map.isEmpty()) { return map.get(MixAll.MASTER_ID); } return null; } public FindBrokerResult findBrokerAddressInSubscribe( final String brokerName, final long brokerId, final boolean onlyThisBroker ) { if (brokerName == null) { return null; } String brokerAddr = null; boolean slave = false; boolean found = false; HashMap map = this.brokerAddrTable.get(brokerName); if (map != null && !map.isEmpty()) { brokerAddr = map.get(brokerId); slave = brokerId != MixAll.MASTER_ID; found = brokerAddr != null; if (!found && slave) { brokerAddr = map.get(brokerId + 1); found = brokerAddr != null; } if (!found && !onlyThisBroker) { Entry entry = map.entrySet().iterator().next(); brokerAddr = entry.getValue(); slave = entry.getKey() != MixAll.MASTER_ID; found = brokerAddr != null; } } if (found) { return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr)); } return null; } private int findBrokerVersion(String brokerName, String brokerAddr) { if (this.brokerVersionTable.containsKey(brokerName)) { if (this.brokerVersionTable.get(brokerName).containsKey(brokerAddr)) { return this.brokerVersionTable.get(brokerName).get(brokerAddr); } } //To do need to fresh the version return 0; } public List findConsumerIdList(final String topic, final String group) { String brokerAddr = this.findBrokerAddrByTopic(topic); if (null == brokerAddr) { this.updateTopicRouteInfoFromNameServer(topic); brokerAddr = this.findBrokerAddrByTopic(topic); } if (null != brokerAddr) { try { return this.mQClientAPIImpl.getConsumerIdListByGroup(brokerAddr, group, clientConfig.getMqClientApiTimeout()); } catch (Exception e) { log.warn("getConsumerIdListByGroup exception, " + brokerAddr + " " + group, e); } } return null; } public Set queryAssignment(final String topic, final String consumerGroup, final String strategyName, final MessageModel messageModel, int timeout) throws RemotingException, InterruptedException, MQBrokerException { String brokerAddr = this.findBrokerAddrByTopic(topic); if (null == brokerAddr) { this.updateTopicRouteInfoFromNameServer(topic); brokerAddr = this.findBrokerAddrByTopic(topic); } if (null != brokerAddr) { return this.mQClientAPIImpl.queryAssignment(brokerAddr, topic, consumerGroup, clientId, strategyName, messageModel, timeout); } return null; } public String findBrokerAddrByTopic(final String topic) { TopicRouteData topicRouteData = this.topicRouteTable.get(topic); if (topicRouteData != null) { List brokers = topicRouteData.getBrokerDatas(); if (!brokers.isEmpty()) { BrokerData bd = brokers.get(random.nextInt(brokers.size())); return bd.selectBrokerAddr(); } } return null; } public synchronized void resetOffset(String topic, String group, Map offsetTable) { DefaultMQPushConsumerImpl consumer = null; try { MQConsumerInner impl = this.consumerTable.get(group); if (impl instanceof DefaultMQPushConsumerImpl) { consumer = (DefaultMQPushConsumerImpl) impl; } else { log.info("[reset-offset] consumer does not exist. group={}", group); return; } consumer.suspend(); ConcurrentMap processQueueTable = consumer.getRebalanceImpl().getProcessQueueTable(); for (Map.Entry entry : processQueueTable.entrySet()) { MessageQueue mq = entry.getKey(); if (topic.equals(mq.getTopic()) && offsetTable.containsKey(mq)) { ProcessQueue pq = entry.getValue(); pq.setDropped(true); pq.clear(); } } try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException ignored) { } Iterator iterator = processQueueTable.keySet().iterator(); while (iterator.hasNext()) { MessageQueue mq = iterator.next(); Long offset = offsetTable.get(mq); if (topic.equals(mq.getTopic()) && offset != null) { try { consumer.updateConsumeOffset(mq, offset); consumer.getRebalanceImpl().removeUnnecessaryMessageQueue(mq, processQueueTable.get(mq)); iterator.remove(); } catch (Exception e) { log.warn("reset offset failed. group={}, {}", group, mq, e); } } } } finally { if (consumer != null) { consumer.resume(); } } } @SuppressWarnings("unchecked") public Map getConsumerStatus(String topic, String group) { MQConsumerInner impl = this.consumerTable.get(group); if (impl instanceof DefaultMQPushConsumerImpl) { DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) impl; return consumer.getOffsetStore().cloneOffsetTable(topic); } else if (impl instanceof DefaultMQPullConsumerImpl) { DefaultMQPullConsumerImpl consumer = (DefaultMQPullConsumerImpl) impl; return consumer.getOffsetStore().cloneOffsetTable(topic); } else { return Collections.EMPTY_MAP; } } public TopicRouteData getAnExistTopicRouteData(final String topic) { return this.topicRouteTable.get(topic); } public MQClientAPIImpl getMQClientAPIImpl() { return mQClientAPIImpl; } public MQAdminImpl getMQAdminImpl() { return mQAdminImpl; } public long getBootTimestamp() { return bootTimestamp; } public ScheduledExecutorService getScheduledExecutorService() { return scheduledExecutorService; } public PullMessageService getPullMessageService() { return pullMessageService; } public DefaultMQProducer getDefaultMQProducer() { return defaultMQProducer; } public ConcurrentMap getTopicRouteTable() { return topicRouteTable; } public ConsumeMessageDirectlyResult consumeMessageDirectly(final MessageExt msg, final String consumerGroup, final String brokerName) { MQConsumerInner mqConsumerInner = this.consumerTable.get(consumerGroup); if (mqConsumerInner instanceof DefaultMQPushConsumerImpl) { DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) mqConsumerInner; return consumer.getConsumeMessageService().consumeMessageDirectly(msg, brokerName); } return null; } public ConsumerRunningInfo consumerRunningInfo(final String consumerGroup) { MQConsumerInner mqConsumerInner = this.consumerTable.get(consumerGroup); if (mqConsumerInner == null) { return null; } ConsumerRunningInfo consumerRunningInfo = mqConsumerInner.consumerRunningInfo(); List nsList = this.mQClientAPIImpl.getRemotingClient().getNameServerAddressList(); StringBuilder strBuilder = new StringBuilder(); if (nsList != null) { for (String addr : nsList) { strBuilder.append(addr).append(";"); } } String nsAddr = strBuilder.toString(); consumerRunningInfo.getProperties().put(ConsumerRunningInfo.PROP_NAMESERVER_ADDR, nsAddr); consumerRunningInfo.getProperties().put(ConsumerRunningInfo.PROP_CONSUME_TYPE, mqConsumerInner.consumeType().name()); consumerRunningInfo.getProperties().put(ConsumerRunningInfo.PROP_CLIENT_VERSION, MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION)); return consumerRunningInfo; } private void resetBrokerAddrHeartbeatFingerprintMap() { brokerAddrHeartbeatFingerprintTable.clear(); } public ConsumerStatsManager getConsumerStatsManager() { return consumerStatsManager; } public NettyClientConfig getNettyClientConfig() { return nettyClientConfig; } public ClientConfig getClientConfig() { return clientConfig; } public ConcurrentMap getProducerTable() { return producerTable; } public ConcurrentMap getConsumerTable() { return consumerTable; } public TopicRouteData queryTopicRouteData(String topic) { TopicRouteData data = this.getAnExistTopicRouteData(topic); if (data == null) { this.updateTopicRouteInfoFromNameServer(topic); data = this.getAnExistTopicRouteData(topic); } return data; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/mqclient/DoNothingClientRemotingProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.mqclient; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class DoNothingClientRemotingProcessor extends ClientRemotingProcessor { public DoNothingClientRemotingProcessor(MQClientInstance mqClientFactory) { super(mqClientFactory); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) { return null; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.mqclient; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.NotifyResult; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.OffsetNotFoundException; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.admin.MqClientAdminImpl; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.ObjectCreator; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.GetLiteTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.body.LiteSubscriptionCtlRequestBody; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.LockBatchResponseBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetLiteTopicInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader; import org.apache.rocketmq.remoting.protocol.header.LiteSubscriptionCtlRequestHeader; import org.apache.rocketmq.remoting.protocol.header.LockBatchMqRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotificationRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotificationResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2; import org.apache.rocketmq.remoting.protocol.header.UnlockBatchMqRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; public class MQClientAPIExt extends MQClientAPIImpl { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final ClientConfig clientConfig; private final MqClientAdminImpl mqClientAdmin; public MQClientAPIExt( ClientConfig clientConfig, NettyClientConfig nettyClientConfig, ClientRemotingProcessor clientRemotingProcessor, RPCHook rpcHook ) { this(clientConfig, nettyClientConfig, clientRemotingProcessor, rpcHook, null); } public MQClientAPIExt( ClientConfig clientConfig, NettyClientConfig nettyClientConfig, ClientRemotingProcessor clientRemotingProcessor, RPCHook rpcHook, ObjectCreator remotingClientCreator ) { super(nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig, null, remotingClientCreator); this.clientConfig = clientConfig; this.mqClientAdmin = new MqClientAdminImpl(getRemotingClient()); } public boolean updateNameServerAddressList() { if (this.clientConfig.getNamesrvAddr() != null) { this.updateNameServerAddressList(this.clientConfig.getNamesrvAddr()); log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr()); return true; } return false; } public CompletableFuture sendHeartbeatOneway( String brokerAddr, HeartbeatData heartbeatData, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, new HeartbeatRequestHeader()); request.setLanguage(clientConfig.getLanguage()); request.setBody(heartbeatData.encode()); this.getRemotingClient().invokeOneway(brokerAddr, request, timeoutMillis); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture sendHeartbeatAsync( String brokerAddr, HeartbeatData heartbeatData, long timeoutMillis ) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, new HeartbeatRequestHeader()); request.setLanguage(clientConfig.getLanguage()); request.setBody(heartbeatData.encode()); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future0 = new CompletableFuture<>(); if (ResponseCode.SUCCESS == response.getCode()) { future0.complete(response.getVersion()); } else { future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr)); } return future0; }); } public CompletableFuture sendMessageAsync( String brokerAddr, String brokerName, Message msg, SendMessageRequestHeader requestHeader, long timeoutMillis ) { SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, requestHeaderV2); request.setBody(msg.getBody()); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future0 = new CompletableFuture<>(); try { future0.complete(this.processSendResponse(brokerName, msg, response, brokerAddr)); } catch (Exception e) { future0.completeExceptionally(e); } return future0; }); } public CompletableFuture sendMessageAsync( String brokerAddr, String brokerName, List msgList, SendMessageRequestHeader requestHeader, long timeoutMillis ) { SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_BATCH_MESSAGE, requestHeaderV2); CompletableFuture future = new CompletableFuture<>(); try { requestHeader.setBatch(true); MessageBatch msgBatch = MessageBatch.generateFromList(msgList); MessageClientIDSetter.setUniqID(msgBatch); byte[] body = msgBatch.encode(); msgBatch.setBody(body); request.setBody(body); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future0 = new CompletableFuture<>(); try { future0.complete(processSendResponse(brokerName, msgBatch, response, brokerAddr)); } catch (Exception e) { future0.completeExceptionally(e); } return future0; }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture sendMessageBackAsync( String brokerAddr, ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis ) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, requestHeader); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis); } public CompletableFuture popMessageAsync( String brokerAddr, String brokerName, PopMessageRequestHeader requestHeader, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.popMessageAsync(brokerName, brokerAddr, requestHeader, timeoutMillis, new PopCallback() { @Override public void onSuccess(PopResult popResult) { future.complete(popResult); } @Override public void onException(Throwable t) { future.completeExceptionally(t); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture popLiteMessageAsync( String brokerAddr, String brokerName, PopLiteMessageRequestHeader requestHeader, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.popLiteMessageAsync(brokerName, brokerAddr, requestHeader, timeoutMillis, new PopCallback() { @Override public void onSuccess(PopResult popResult) { future.complete(popResult); } @Override public void onException(Throwable t) { future.completeExceptionally(t); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture ackMessageAsync( String brokerAddr, AckMessageRequestHeader requestHeader, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.ackMessageAsync(brokerAddr, timeoutMillis, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { future.complete(ackResult); } @Override public void onException(Throwable t) { future.completeExceptionally(t); } }, requestHeader); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture batchAckMessageAsync( String brokerAddr, String topic, String consumerGroup, List extraInfoList, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.batchAckMessageAsync(brokerAddr, timeoutMillis, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { future.complete(ackResult); } @Override public void onException(Throwable t) { future.completeExceptionally(t); } }, topic, consumerGroup, extraInfoList); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture changeInvisibleTimeAsync( String brokerAddr, String brokerName, ChangeInvisibleTimeRequestHeader requestHeader, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.changeInvisibleTimeAsync(brokerName, brokerAddr, requestHeader, timeoutMillis, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { future.complete(ackResult); } @Override public void onException(Throwable t) { future.completeExceptionally(t); } } ); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture pullMessageAsync( String brokerAddr, PullMessageRequestHeader requestHeader, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.pullMessage(brokerAddr, requestHeader, timeoutMillis, CommunicationMode.ASYNC, new PullCallback() { @Override public void onSuccess(PullResult pullResult) { if (pullResult instanceof PullResultExt) { PullResultExt pullResultExt = (PullResultExt) pullResult; if (PullStatus.FOUND.equals(pullResult.getPullStatus())) { List messageExtList = MessageDecoder.decodesBatch( ByteBuffer.wrap(pullResultExt.getMessageBinary()), true, false, true ); pullResult.setMsgFoundList(messageExtList); } } future.complete(pullResult); } @Override public void onException(Throwable t) { future.completeExceptionally(t); } } ); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture queryConsumerOffsetWithFuture( String brokerAddr, QueryConsumerOffsetRequestHeader requestHeader, long timeoutMillis ) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future0 = new CompletableFuture<>(); switch (response.getCode()) { case ResponseCode.SUCCESS: { try { QueryConsumerOffsetResponseHeader responseHeader = (QueryConsumerOffsetResponseHeader) response.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class); future0.complete(responseHeader.getOffset()); } catch (RemotingCommandException e) { future0.completeExceptionally(e); } break; } case ResponseCode.QUERY_NOT_FOUND: { future0.completeExceptionally(new OffsetNotFoundException(response.getCode(), response.getRemark(), brokerAddr)); break; } default: { future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); break; } } return future0; }); } public CompletableFuture updateConsumerOffsetOneWay( String brokerAddr, UpdateConsumerOffsetRequestHeader header, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, header); this.getRemotingClient().invokeOneway(brokerAddr, request, timeoutMillis); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture updateConsumerOffsetAsync( String brokerAddr, UpdateConsumerOffsetRequestHeader header, long timeoutMillis ) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, header); CompletableFuture future = new CompletableFuture<>(); invoke(brokerAddr, request, timeoutMillis).whenComplete((response, t) -> { if (t != null) { log.error("updateConsumerOffsetAsync failed, brokerAddr={}, requestHeader={}", brokerAddr, header, t); future.completeExceptionally(t); return; } switch (response.getCode()) { case ResponseCode.SUCCESS: future.complete(null); break; default: future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr)); break; } }); return future; } public CompletableFuture> getConsumerListByGroupAsync( String brokerAddr, GetConsumerListByGroupRequestHeader requestHeader, long timeoutMillis ) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, requestHeader); CompletableFuture> future = new CompletableFuture<>(); try { this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { switch (response.getCode()) { case ResponseCode.SUCCESS: { if (response.getBody() != null) { GetConsumerListByGroupResponseBody body = GetConsumerListByGroupResponseBody.decode(response.getBody(), GetConsumerListByGroupResponseBody.class); future.complete(body.getConsumerIdList()); return; } } /* @see org.apache.rocketmq.broker.processor.ConsumerManageProcessor#getConsumerListByGroup, * broker will return {@link ResponseCode.SYSTEM_ERROR} if there is no consumer. */ case ResponseCode.SYSTEM_ERROR: { future.complete(Collections.emptyList()); return; } default: break; } future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); } @Override public void operationFail(Throwable throwable) { future.completeExceptionally(throwable); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture getMaxOffset(String brokerAddr, GetMaxOffsetRequestHeader requestHeader, long timeoutMillis) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MAX_OFFSET, requestHeader); CompletableFuture future = new CompletableFuture<>(); try { this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { if (ResponseCode.SUCCESS == response.getCode()) { try { GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.decodeCommandCustomHeader(GetMaxOffsetResponseHeader.class); future.complete(responseHeader.getOffset()); } catch (Throwable t) { future.completeExceptionally(t); } } future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); } @Override public void operationFail(Throwable throwable) { future.completeExceptionally(throwable); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture getMinOffset(String brokerAddr, GetMinOffsetRequestHeader requestHeader, long timeoutMillis) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MIN_OFFSET, requestHeader); CompletableFuture future = new CompletableFuture<>(); try { this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { if (ResponseCode.SUCCESS == response.getCode()) { try { GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) response.decodeCommandCustomHeader(GetMinOffsetResponseHeader.class); future.complete(responseHeader.getOffset()); } catch (Throwable t) { future.completeExceptionally(t); } } future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); } @Override public void operationFail(Throwable throwable) { future.completeExceptionally(throwable); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture searchOffset(String brokerAddr, SearchOffsetRequestHeader requestHeader, long timeoutMillis) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future0 = new CompletableFuture<>(); if (response.getCode() == ResponseCode.SUCCESS) { try { SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.decodeCommandCustomHeader(SearchOffsetResponseHeader.class); future0.complete(responseHeader.getOffset()); } catch (Throwable t) { future0.completeExceptionally(t); } } else { future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); } return future0; }); } public CompletableFuture> lockBatchMQWithFuture(String brokerAddr, LockBatchRequestBody requestBody, long timeoutMillis) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, new LockBatchMqRequestHeader()); request.setBody(requestBody.encode()); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture> future0 = new CompletableFuture<>(); if (response.getCode() == ResponseCode.SUCCESS) { try { LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class); Set messageQueues = responseBody.getLockOKMQSet(); future0.complete(messageQueues); } catch (Throwable t) { future0.completeExceptionally(t); } } else { future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); } return future0; }); } public CompletableFuture unlockBatchMQOneway(String brokerAddr, UnlockBatchRequestBody requestBody, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNLOCK_BATCH_MQ, new UnlockBatchMqRequestHeader()); request.setBody(requestBody.encode()); try { this.getRemotingClient().invokeOneway(brokerAddr, request, timeoutMillis); future.complete(null); } catch (Exception e) { future.completeExceptionally(e); } return future; } public CompletableFuture notification(String brokerAddr, NotificationRequestHeader requestHeader, long timeoutMillis) { return notificationWithPollingStats(brokerAddr, requestHeader, timeoutMillis).thenApply(NotifyResult::isHasMsg); } public CompletableFuture notificationWithPollingStats(String brokerAddr, NotificationRequestHeader requestHeader, long timeoutMillis) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFICATION, requestHeader); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future0 = new CompletableFuture<>(); if (response.getCode() == ResponseCode.SUCCESS) { try { NotificationResponseHeader responseHeader = (NotificationResponseHeader) response.decodeCommandCustomHeader(NotificationResponseHeader.class); NotifyResult notifyResult = new NotifyResult(); notifyResult.setHasMsg(responseHeader.isHasMsg()); notifyResult.setPollingFull(responseHeader.isPollingFull()); future0.complete(notifyResult); } catch (Throwable t) { future0.completeExceptionally(t); } } else { future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); } return future0; }); } public CompletableFuture recallMessageAsync(String brokerAddr, RecallMessageRequestHeader requestHeader, long timeoutMillis) { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, requestHeader); return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> { CompletableFuture future = new CompletableFuture<>(); if (ResponseCode.SUCCESS == response.getCode()) { try { RecallMessageResponseHeader responseHeader = response.decodeCommandCustomHeader(RecallMessageResponseHeader.class); future.complete(responseHeader.getMsgId()); } catch (Throwable t) { future.completeExceptionally(t); } } else { future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr)); } return future; }); } public CompletableFuture syncLiteSubscriptionAsync( String brokerAddr, LiteSubscriptionDTO liteSubscriptionDTO, long timeoutMillis ) { LiteSubscriptionCtlRequestBody requestBody = new LiteSubscriptionCtlRequestBody(); requestBody.setSubscriptionSet(Collections.singleton(liteSubscriptionDTO)); RemotingCommand request = RemotingCommand .createRequestCommand(RequestCode.LITE_SUBSCRIPTION_CTL, new LiteSubscriptionCtlRequestHeader()); request.setBody(requestBody.encode()); return getRemotingClient() .invoke(brokerAddr, request, timeoutMillis) .thenCompose(response -> { if (ResponseCode.SUCCESS == response.getCode()) { return CompletableFuture.completedFuture(null); } else { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally( new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr) ); return future; } }); } public CompletableFuture getLiteTopicInfoAsync( String addr, String parentTopic, String liteTopic, long timeoutMillis ) { GetLiteTopicInfoRequestHeader requestHeader = new GetLiteTopicInfoRequestHeader(); requestHeader.setParentTopic(parentTopic); requestHeader.setLiteTopic(liteTopic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_LITE_TOPIC_INFO, requestHeader); return this.getRemotingClient() .invoke(addr, request, timeoutMillis) .thenApply(response -> { if (ResponseCode.SUCCESS == response.getCode()) { try { return GetLiteTopicInfoResponseBody.decode(response.getBody(), GetLiteTopicInfoResponseBody.class); } catch (Exception e) { throw new CompletionException(e); } } else { throw new CompletionException(new MQBrokerException(response.getCode(), response.getRemark())); } }); } public CompletableFuture invoke(String brokerAddr, RemotingCommand request, long timeoutMillis) { return getRemotingClient().invoke(brokerAddr, request, timeoutMillis); } public CompletableFuture invokeOneway(String brokerAddr, RemotingCommand request, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { this.getRemotingClient().invokeOneway(brokerAddr, request, timeoutMillis); future.complete(null); } catch (Exception e) { future.completeExceptionally(e); } return future; } public MqClientAdminImpl getMqClientAdmin() { return mqClientAdmin; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.mqclient; import com.google.common.base.Strings; import java.time.Duration; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.common.NameserverAccessConfig; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ObjectCreator; import org.apache.rocketmq.common.utils.AsyncShutdownHelper; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.netty.NettyClientConfig; public class MQClientAPIFactory implements StartAndShutdown { private MQClientAPIExt[] clients; private final String namePrefix; private final int clientNum; private final ClientRemotingProcessor clientRemotingProcessor; private final RPCHook rpcHook; private final ScheduledExecutorService scheduledExecutorService; private final NameserverAccessConfig nameserverAccessConfig; private final ObjectCreator remotingClientCreator; public MQClientAPIFactory( NameserverAccessConfig nameserverAccessConfig, String namePrefix, int clientNum, ClientRemotingProcessor clientRemotingProcessor, RPCHook rpcHook, ScheduledExecutorService scheduledExecutorService ) { this(nameserverAccessConfig, namePrefix, clientNum, clientRemotingProcessor, rpcHook, scheduledExecutorService, null); } public MQClientAPIFactory( NameserverAccessConfig nameserverAccessConfig, String namePrefix, int clientNum, ClientRemotingProcessor clientRemotingProcessor, RPCHook rpcHook, ScheduledExecutorService scheduledExecutorService, ObjectCreator remotingClientCreator ) { this.nameserverAccessConfig = nameserverAccessConfig; this.namePrefix = namePrefix; this.clientNum = clientNum; this.clientRemotingProcessor = clientRemotingProcessor; this.rpcHook = rpcHook; this.scheduledExecutorService = scheduledExecutorService; this.remotingClientCreator = remotingClientCreator; this.init(); } protected void init() { System.setProperty(ClientConfig.SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "false"); if (StringUtils.isEmpty(nameserverAccessConfig.getNamesrvDomain())) { if (Strings.isNullOrEmpty(nameserverAccessConfig.getNamesrvAddr())) { throw new RuntimeException("The configuration item NamesrvAddr is not configured"); } System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, nameserverAccessConfig.getNamesrvAddr()); } else { System.setProperty("rocketmq.namesrv.domain", nameserverAccessConfig.getNamesrvDomain()); System.setProperty("rocketmq.namesrv.domain.subgroup", nameserverAccessConfig.getNamesrvDomainSubgroup()); } } public MQClientAPIExt getClient() { if (clients.length == 1) { return this.clients[0]; } int index = ThreadLocalRandom.current().nextInt(this.clients.length); return this.clients[index]; } @Override public void start() throws Exception { this.clients = new MQClientAPIExt[this.clientNum]; for (int i = 0; i < this.clientNum; i++) { clients[i] = createAndStart(this.namePrefix + "N_" + i); } } @Override public void shutdown() throws Exception { AsyncShutdownHelper helper = new AsyncShutdownHelper(); for (int i = 0; i < this.clientNum; i++) { helper.addTarget(clients[i]); } helper.shutdown().await(Integer.MAX_VALUE, TimeUnit.SECONDS); } protected MQClientAPIExt createAndStart(String instanceName) { ClientConfig clientConfig = new ClientConfig(); clientConfig.setInstanceName(instanceName); clientConfig.setDecodeReadBody(true); clientConfig.setDecodeDecompressBody(false); NettyClientConfig nettyClientConfig = new NettyClientConfig(); nettyClientConfig.setDisableCallbackExecutor(true); MQClientAPIExt mqClientAPIExt = new MQClientAPIExt( clientConfig, nettyClientConfig, clientRemotingProcessor, rpcHook, remotingClientCreator ); if (StringUtils.isEmpty(nameserverAccessConfig.getNamesrvDomain())) { mqClientAPIExt.updateNameServerAddressList(nameserverAccessConfig.getNamesrvAddr()); } else { mqClientAPIExt.fetchNameServerAddr(); this.scheduledExecutorService.scheduleAtFixedRate( mqClientAPIExt::fetchNameServerAddr, Duration.ofSeconds(10).toMillis(), Duration.ofMinutes(2).toMillis(), TimeUnit.MILLISECONDS ); } mqClientAPIExt.start(); return mqClientAPIExt; } public void onNameServerAddressChange(String namesrvAddress) { for (MQClientAPIExt client : clients) { client.onNameServerAddressChange(namesrvAddress); } } public MQClientAPIExt[] getClients() { return clients; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.producer; import java.io.IOException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.Optional; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.common.ClientErrorCode; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.client.hook.CheckForbiddenContext; import org.apache.rocketmq.client.hook.CheckForbiddenHook; import org.apache.rocketmq.client.hook.EndTransactionContext; import org.apache.rocketmq.client.hook.EndTransactionHook; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.latency.MQFaultStrategy; import org.apache.rocketmq.client.latency.Resolver; import org.apache.rocketmq.client.latency.ServiceDetector; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.RequestCallback; import org.apache.rocketmq.client.producer.RequestFutureHolder; import org.apache.rocketmq.client.producer.RequestResponseFuture; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.producer.TransactionCheckListener; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.client.producer.TransactionSendResult; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageId; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageType; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.CorrelationIdUtil; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class DefaultMQProducerImpl implements MQProducerInner { private final Logger log = LoggerFactory.getLogger(DefaultMQProducerImpl.class); private final Random random = new Random(); private final DefaultMQProducer defaultMQProducer; private final ConcurrentMap topicPublishInfoTable = new ConcurrentHashMap<>(); private final ArrayList sendMessageHookList = new ArrayList<>(); private final ArrayList endTransactionHookList = new ArrayList<>(); private final RPCHook rpcHook; private final BlockingQueue asyncSenderThreadPoolQueue; private final ExecutorService defaultAsyncSenderExecutor; protected BlockingQueue checkRequestQueue; protected ExecutorService checkExecutor; private ServiceState serviceState = ServiceState.CREATE_JUST; private MQClientInstance mQClientFactory; private ArrayList checkForbiddenHookList = new ArrayList<>(); private MQFaultStrategy mqFaultStrategy; private ExecutorService asyncSenderExecutor; // backpressure related private Semaphore semaphoreAsyncSendNum; private Semaphore semaphoreAsyncSendSize; public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer) { this(defaultMQProducer, null); } public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) { this.defaultMQProducer = defaultMQProducer; this.rpcHook = rpcHook; this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000); this.defaultAsyncSenderExecutor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 1000 * 60, TimeUnit.MILLISECONDS, this.asyncSenderThreadPoolQueue, new ThreadFactoryImpl("AsyncSenderExecutor_")); if (defaultMQProducer.getBackPressureForAsyncSendNum() > 10) { semaphoreAsyncSendNum = new Semaphore(Math.max(defaultMQProducer.getBackPressureForAsyncSendNum(), 10), true); } else { semaphoreAsyncSendNum = new Semaphore(10, true); log.info("semaphoreAsyncSendNum can not be smaller than 10."); } if (defaultMQProducer.getBackPressureForAsyncSendSize() > 1024 * 1024) { semaphoreAsyncSendSize = new Semaphore(Math.max(defaultMQProducer.getBackPressureForAsyncSendSize(), 1024 * 1024), true); } else { semaphoreAsyncSendSize = new Semaphore(1024 * 1024, true); log.info("semaphoreAsyncSendSize can not be smaller than 1M."); } ServiceDetector serviceDetector = new ServiceDetector() { @Override public boolean detect(String endpoint, long timeoutMillis) { Optional candidateTopic = pickTopic(); if (!candidateTopic.isPresent()) { return false; } try { MessageQueue mq = new MessageQueue(candidateTopic.get(), null, 0); mQClientFactory.getMQClientAPIImpl() .getMaxOffset(endpoint, mq, timeoutMillis); return true; } catch (Exception e) { return false; } } }; this.mqFaultStrategy = new MQFaultStrategy(defaultMQProducer.cloneClientConfig(), new Resolver() { @Override public String resolve(String name) { return DefaultMQProducerImpl.this.mQClientFactory.findBrokerAddressInPublish(name); } }, serviceDetector); } private Optional pickTopic() { if (topicPublishInfoTable.isEmpty()) { return Optional.empty(); } return Optional.of(topicPublishInfoTable.keySet().iterator().next()); } public void registerCheckForbiddenHook(CheckForbiddenHook checkForbiddenHook) { this.checkForbiddenHookList.add(checkForbiddenHook); log.info("register a new checkForbiddenHook. hookName={}, allHookSize={}", checkForbiddenHook.hookName(), checkForbiddenHookList.size()); } public void setSemaphoreAsyncSendNum(int num) { semaphoreAsyncSendNum = new Semaphore(num, true); } public void setSemaphoreAsyncSendSize(int size) { semaphoreAsyncSendSize = new Semaphore(size, true); } public int getSemaphoreAsyncSendNumAvailablePermits() { return semaphoreAsyncSendNum == null ? 0 : semaphoreAsyncSendNum.availablePermits(); } public int getSemaphoreAsyncSendSizeAvailablePermits() { return semaphoreAsyncSendSize == null ? 0 : semaphoreAsyncSendSize.availablePermits(); } public void initTransactionEnv() { TransactionMQProducer producer = (TransactionMQProducer) this.defaultMQProducer; if (producer.getExecutorService() != null) { this.checkExecutor = producer.getExecutorService(); } else { this.checkRequestQueue = new LinkedBlockingQueue<>(producer.getCheckRequestHoldMax()); this.checkExecutor = new ThreadPoolExecutor( producer.getCheckThreadPoolMinSize(), producer.getCheckThreadPoolMaxSize(), 1000 * 60, TimeUnit.MILLISECONDS, this.checkRequestQueue); } } public void destroyTransactionEnv() { if (this.checkExecutor != null) { this.checkExecutor.shutdown(); } } public void registerSendMessageHook(final SendMessageHook hook) { this.sendMessageHookList.add(hook); log.info("register sendMessage Hook, {}", hook.hookName()); } public void registerEndTransactionHook(final EndTransactionHook hook) { this.endTransactionHookList.add(hook); log.info("register endTransaction Hook, {}", hook.hookName()); } public void start() throws MQClientException { this.start(true); } public void start(final boolean startFactory) throws MQClientException { switch (this.serviceState) { case CREATE_JUST: this.serviceState = ServiceState.START_FAILED; this.checkConfig(); if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) { this.defaultMQProducer.changeInstanceNameToPID(); } this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook); defaultMQProducer.initProduceAccumulator(); boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this); if (!registerOK) { this.serviceState = ServiceState.CREATE_JUST; throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null); } if (startFactory) { mQClientFactory.start(); } this.initTopicRoute(); this.mqFaultStrategy.startDetector(); log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(), this.defaultMQProducer.isSendMessageWithVIPChannel()); this.serviceState = ServiceState.RUNNING; break; case RUNNING: case START_FAILED: case SHUTDOWN_ALREADY: throw new MQClientException("The producer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); default: break; } this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); RequestFutureHolder.getInstance().startScheduledTask(this); } private void checkConfig() throws MQClientException { Validators.checkGroup(this.defaultMQProducer.getProducerGroup()); if (this.defaultMQProducer.getProducerGroup().equals(MixAll.DEFAULT_PRODUCER_GROUP)) { throw new MQClientException("producerGroup can not equal " + MixAll.DEFAULT_PRODUCER_GROUP + ", please specify another one.", null); } } public void shutdown() { this.shutdown(true); } public void shutdown(final boolean shutdownFactory) { switch (this.serviceState) { case CREATE_JUST: break; case RUNNING: this.mQClientFactory.unregisterProducer(this.defaultMQProducer.getProducerGroup()); this.defaultAsyncSenderExecutor.shutdown(); if (shutdownFactory) { this.mQClientFactory.shutdown(); } this.mqFaultStrategy.shutdown(); RequestFutureHolder.getInstance().shutdown(this); log.info("the producer [{}] shutdown OK", this.defaultMQProducer.getProducerGroup()); this.serviceState = ServiceState.SHUTDOWN_ALREADY; break; case SHUTDOWN_ALREADY: break; default: break; } } @Override public Set getPublishTopicList() { return new HashSet<>(this.topicPublishInfoTable.keySet()); } @Override public boolean isPublishTopicNeedUpdate(String topic) { TopicPublishInfo prev = this.topicPublishInfoTable.get(topic); return null == prev || !prev.ok(); } /** * @deprecated This method will be removed in the version 5.0.0 and {@link DefaultMQProducerImpl#getCheckListener} is recommended. */ @Override @Deprecated public TransactionCheckListener checkListener() { if (this.defaultMQProducer instanceof TransactionMQProducer) { TransactionMQProducer producer = (TransactionMQProducer) defaultMQProducer; return producer.getTransactionCheckListener(); } return null; } @Override public TransactionListener getCheckListener() { if (this.defaultMQProducer instanceof TransactionMQProducer) { TransactionMQProducer producer = (TransactionMQProducer) defaultMQProducer; return producer.getTransactionListener(); } return null; } @Override public void checkTransactionState(final String addr, final MessageExt msg, final CheckTransactionStateRequestHeader header) { Runnable request = new Runnable() { private final String brokerAddr = addr; private final MessageExt message = msg; private final CheckTransactionStateRequestHeader checkRequestHeader = header; private final String group = DefaultMQProducerImpl.this.defaultMQProducer.getProducerGroup(); @Override public void run() { TransactionCheckListener transactionCheckListener = DefaultMQProducerImpl.this.checkListener(); TransactionListener transactionListener = getCheckListener(); if (transactionCheckListener != null || transactionListener != null) { LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW; Throwable exception = null; try { if (transactionCheckListener != null) { localTransactionState = transactionCheckListener.checkLocalTransactionState(message); } else { log.debug("TransactionCheckListener is null, used new check API, producerGroup={}", group); localTransactionState = transactionListener.checkLocalTransaction(message); } } catch (Throwable e) { log.error("Broker call checkTransactionState, but checkLocalTransactionState exception", e); exception = e; } this.processTransactionState( checkRequestHeader.getTopic(), localTransactionState, group, exception); } else { log.warn("CheckTransactionState, pick transactionCheckListener by group[{}] failed", group); } } private void processTransactionState( final String topic, final LocalTransactionState localTransactionState, final String producerGroup, final Throwable exception) { final EndTransactionRequestHeader thisHeader = new EndTransactionRequestHeader(); thisHeader.setTopic(topic); thisHeader.setCommitLogOffset(checkRequestHeader.getCommitLogOffset()); thisHeader.setProducerGroup(producerGroup); thisHeader.setTranStateTableOffset(checkRequestHeader.getTranStateTableOffset()); thisHeader.setFromTransactionCheck(true); thisHeader.setBrokerName(checkRequestHeader.getBrokerName()); String uniqueKey = message.getProperties().get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (uniqueKey == null) { uniqueKey = message.getMsgId(); } thisHeader.setMsgId(uniqueKey); thisHeader.setTransactionId(checkRequestHeader.getTransactionId()); switch (localTransactionState) { case COMMIT_MESSAGE: thisHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE); break; case ROLLBACK_MESSAGE: thisHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE); log.warn("when broker check, client rollback this transaction, {}", thisHeader); break; case UNKNOW: thisHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE); log.warn("when broker check, client does not know this transaction state, {}", thisHeader); break; default: break; } String remark = null; if (exception != null) { remark = "checkLocalTransactionState Exception: " + UtilAll.exceptionSimpleDesc(exception); } doExecuteEndTransactionHook(msg, uniqueKey, brokerAddr, localTransactionState, true); try { DefaultMQProducerImpl.this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, thisHeader, remark, 3000); } catch (Exception e) { log.error("endTransactionOneway exception", e); } } }; this.checkExecutor.submit(request); } @Override public void updateTopicPublishInfo(final String topic, final TopicPublishInfo info) { if (info != null && topic != null) { TopicPublishInfo prev = this.topicPublishInfoTable.put(topic, info); if (prev != null) { log.info("updateTopicPublishInfo prev is not null, " + prev); } } } @Override public boolean isUnitMode() { return this.defaultMQProducer.isUnitMode(); } public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { createTopic(key, newTopic, queueNum, 0); } public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { this.makeSureStateOK(); Validators.checkTopic(newTopic); Validators.isSystemTopic(newTopic); this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag, null); } private void makeSureStateOK() throws MQClientException { if (this.serviceState != ServiceState.RUNNING) { throw new MQClientException("The producer service state not OK, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); } } public List fetchPublishMessageQueues(String topic) throws MQClientException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().fetchPublishMessageQueues(topic); } public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } public long maxOffset(MessageQueue mq) throws MQClientException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } public long minOffset(MessageQueue mq) throws MQClientException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().minOffset(mq); } public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().earliestMsgStoreTime(mq); } public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId); } public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().queryMessage(topic, key, maxNum, begin, end); } public MessageExt queryMessageByUniqKey(String topic, String uniqKey) throws MQClientException, InterruptedException { this.makeSureStateOK(); return this.mQClientFactory.getMQAdminImpl().queryMessageByUniqKey(topic, uniqKey); } /** * DEFAULT ASYNC ------------------------------------------------------- */ public void send(Message msg, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException { send(msg, sendCallback, this.defaultMQProducer.getSendMsgTimeout()); } /** * @param msg * @param sendCallback * @param timeout the sendCallback will be invoked at most time * @throws RejectedExecutionException * @deprecated It will be removed at 4.4.0 cause for exception handling and the wrong Semantics of timeout. A new one will be * provided in next version */ @Deprecated public void send(final Message msg, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException { BackpressureSendCallBack newCallBack = new BackpressureSendCallBack(sendCallback); final long beginStartTime = System.currentTimeMillis(); Runnable runnable = new Runnable() { @Override public void run() { long costTime = System.currentTimeMillis() - beginStartTime; if (timeout > costTime) { try { sendDefaultImpl(msg, CommunicationMode.ASYNC, newCallBack, timeout - costTime); } catch (Exception e) { newCallBack.onException(e); } } else { newCallBack.onException( new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout")); } } }; executeAsyncMessageSend(runnable, msg, newCallBack, timeout, beginStartTime); } class BackpressureSendCallBack implements SendCallback { public boolean isSemaphoreAsyncSizeAcquired = false; public boolean isSemaphoreAsyncNumAcquired = false; public int msgLen; private final SendCallback sendCallback; public BackpressureSendCallBack(final SendCallback sendCallback) { this.sendCallback = sendCallback; } @Override public void onSuccess(SendResult sendResult) { semaphoreProcessor(); sendCallback.onSuccess(sendResult); } @Override public void onException(Throwable e) { semaphoreProcessor(); sendCallback.onException(e); } public void semaphoreProcessor() { if (isSemaphoreAsyncSizeAcquired) { defaultMQProducer.acquireBackPressureForAsyncSendSizeLock(); semaphoreAsyncSendSize.release(msgLen); defaultMQProducer.releaseBackPressureForAsyncSendSizeLock(); } if (isSemaphoreAsyncNumAcquired) { defaultMQProducer.acquireBackPressureForAsyncSendNumLock(); semaphoreAsyncSendNum.release(); defaultMQProducer.releaseBackPressureForAsyncSendNumLock(); } } public void semaphoreAsyncAdjust(int semaphoreAsyncNum, int semaphoreAsyncSize) throws InterruptedException { defaultMQProducer.acquireBackPressureForAsyncSendNumLock(); if (semaphoreAsyncNum > 0) { semaphoreAsyncSendNum.release(semaphoreAsyncNum); } else { semaphoreAsyncSendNum.acquire(- semaphoreAsyncNum); } defaultMQProducer.setBackPressureForAsyncSendNumInsideAdjust(defaultMQProducer.getBackPressureForAsyncSendNum() + semaphoreAsyncNum); defaultMQProducer.releaseBackPressureForAsyncSendNumLock(); defaultMQProducer.acquireBackPressureForAsyncSendSizeLock(); if (semaphoreAsyncSize > 0) { semaphoreAsyncSendSize.release(semaphoreAsyncSize); } else { semaphoreAsyncSendSize.acquire(- semaphoreAsyncSize); } defaultMQProducer.setBackPressureForAsyncSendSizeInsideAdjust(defaultMQProducer.getBackPressureForAsyncSendSize() + semaphoreAsyncSize); defaultMQProducer.releaseBackPressureForAsyncSendSizeLock(); } } public void executeAsyncMessageSend(Runnable runnable, final Message msg, final BackpressureSendCallBack sendCallback, final long timeout, final long beginStartTime) throws MQClientException, InterruptedException { ExecutorService executor = this.getAsyncSenderExecutor(); boolean isEnableBackpressureForAsyncMode = this.getDefaultMQProducer().isEnableBackpressureForAsyncMode(); boolean isSemaphoreAsyncNumAcquired = false; boolean isSemaphoreAsyncSizeAcquired = false; int msgLen = msg.getBody() == null ? 1 : msg.getBody().length; sendCallback.msgLen = msgLen; try { if (isEnableBackpressureForAsyncMode) { defaultMQProducer.acquireBackPressureForAsyncSendNumLock(); long costTime = System.currentTimeMillis() - beginStartTime; isSemaphoreAsyncNumAcquired = timeout - costTime > 0 && semaphoreAsyncSendNum.tryAcquire(timeout - costTime, TimeUnit.MILLISECONDS); sendCallback.isSemaphoreAsyncNumAcquired = isSemaphoreAsyncNumAcquired; defaultMQProducer.releaseBackPressureForAsyncSendNumLock(); if (!isSemaphoreAsyncNumAcquired) { sendCallback.onException( new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncNum timeout")); return; } defaultMQProducer.acquireBackPressureForAsyncSendSizeLock(); costTime = System.currentTimeMillis() - beginStartTime; isSemaphoreAsyncSizeAcquired = timeout - costTime > 0 && semaphoreAsyncSendSize.tryAcquire(msgLen, timeout - costTime, TimeUnit.MILLISECONDS); sendCallback.isSemaphoreAsyncSizeAcquired = isSemaphoreAsyncSizeAcquired; defaultMQProducer.releaseBackPressureForAsyncSendSizeLock(); if (!isSemaphoreAsyncSizeAcquired) { sendCallback.onException( new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncSize timeout")); return; } } executor.submit(runnable); } catch (RejectedExecutionException e) { if (isEnableBackpressureForAsyncMode) { runnable.run(); } else { throw new MQClientException("executor rejected ", e); } } } public MessageQueue invokeMessageQueueSelector(Message msg, MessageQueueSelector selector, Object arg, final long timeout) throws MQClientException, RemotingTooMuchRequestException { long beginStartTime = System.currentTimeMillis(); this.makeSureStateOK(); Validators.checkMessage(msg, this.defaultMQProducer); TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic()); if (topicPublishInfo != null && topicPublishInfo.ok()) { MessageQueue mq = null; try { List messageQueueList = mQClientFactory.getMQAdminImpl().parsePublishMessageQueues(topicPublishInfo.getMessageQueueList()); Message userMessage = MessageAccessor.cloneMessage(msg); String userTopic = NamespaceUtil.withoutNamespace(userMessage.getTopic(), mQClientFactory.getClientConfig().getNamespace()); userMessage.setTopic(userTopic); mq = mQClientFactory.getClientConfig().queueWithNamespace(selector.select(messageQueueList, userMessage, arg)); } catch (Throwable e) { throw new MQClientException("select message queue threw exception.", e); } long costTime = System.currentTimeMillis() - beginStartTime; if (timeout < costTime) { throw new RemotingTooMuchRequestException("sendSelectImpl call timeout"); } if (mq != null) { return mq; } else { throw new MQClientException("select message queue return null.", null); } } validateNameServerSetting(); throw new MQClientException("No route info for this topic, " + msg.getTopic(), null); } public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName, final boolean resetIndex) { return this.mqFaultStrategy.selectOneMessageQueue(tpInfo, lastBrokerName, resetIndex); } public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation, boolean reachable) { this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation, reachable); } private void validateNameServerSetting() throws MQClientException { List nsList = this.getMqClientFactory().getMQClientAPIImpl().getNameServerAddressList(); if (null == nsList || nsList.isEmpty()) { throw new MQClientException( "No name server address, please set it." + FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL), null).setResponseCode(ClientErrorCode.NO_NAME_SERVER_EXCEPTION); } } private SendResult sendDefaultImpl( Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { this.makeSureStateOK(); Validators.checkMessage(msg, this.defaultMQProducer); final long invokeID = random.nextLong(); long beginTimestampFirst = System.currentTimeMillis(); long beginTimestampPrev = beginTimestampFirst; long endTimestamp = beginTimestampFirst; TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic()); if (topicPublishInfo != null && topicPublishInfo.ok()) { boolean callTimeout = false; MessageQueue mq = null; Exception exception = null; SendResult sendResult = null; int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1; int times = 0; String[] brokersSent = new String[timesTotal]; boolean resetIndex = false; for (; times < timesTotal; times++) { String lastBrokerName = null == mq ? null : mq.getBrokerName(); if (times > 0) { resetIndex = true; } MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName, resetIndex); if (mqSelected != null) { mq = mqSelected; brokersSent[times] = mq.getBrokerName(); try { beginTimestampPrev = System.currentTimeMillis(); if (times > 0) { //Reset topic with namespace during resend. msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic())); } long costTime = beginTimestampPrev - beginTimestampFirst; if (timeout < costTime) { callTimeout = true; break; } long curTimeout = timeout - costTime; // Get the maximum timeout allowed per request long maxSendTimeoutPerRequest = defaultMQProducer.getSendMsgMaxTimeoutPerRequest(); // Determine if retries are still possible boolean canRetryAgain = times + 1 < timesTotal; // If retries are possible, and the current timeout exceeds the max allowed timeout, set the current timeout to the max allowed if (maxSendTimeoutPerRequest > -1 && canRetryAgain && curTimeout > maxSendTimeoutPerRequest) { curTimeout = maxSendTimeoutPerRequest; } sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, curTimeout); endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true); switch (communicationMode) { case ASYNC: return null; case ONEWAY: return null; case SYNC: if (sendResult.getSendStatus() != SendStatus.SEND_OK) { if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) { continue; } } return sendResult; default: break; } } catch (MQClientException e) { endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true); log.warn("sendKernelImpl exception, resend at once, InvokeID: {}, RT: {}ms, Broker: {}", invokeID, endTimestamp - beginTimestampPrev, mq, e); if (log.isDebugEnabled()) { log.debug(msg.toString()); } exception = e; continue; } catch (RemotingException e) { endTimestamp = System.currentTimeMillis(); // Set this broker unreachable when detecting schedule task is running for RemotingException. // Otherwise, isolate this broker. this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true, !this.mqFaultStrategy.isStartDetectorEnable()); log.warn("sendKernelImpl exception, resend at once, InvokeID: {}, RT: {}ms, Broker: {}", invokeID, endTimestamp - beginTimestampPrev, mq, e); if (log.isDebugEnabled()) { log.debug(msg.toString()); } exception = e; continue; } catch (MQBrokerException e) { endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true, false); log.warn("sendKernelImpl exception, resend at once, InvokeID: {}, RT: {}ms, Broker: {}", invokeID, endTimestamp - beginTimestampPrev, mq, e); if (log.isDebugEnabled()) { log.debug(msg.toString()); } exception = e; if (this.defaultMQProducer.getRetryResponseCodes().contains(e.getResponseCode())) { continue; } else { if (sendResult != null) { return sendResult; } throw e; } } catch (InterruptedException e) { endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true); log.warn("sendKernelImpl exception, throw exception, InvokeID: {}, RT: {}ms, Broker: {}", invokeID, endTimestamp - beginTimestampPrev, mq, e); if (log.isDebugEnabled()) { log.debug(msg.toString()); } throw e; } } else { break; } } if (sendResult != null) { return sendResult; } String info = String.format("Send [%d] times, still failed, cost [%d]ms, Topic: %s, BrokersSent: %s", times, System.currentTimeMillis() - beginTimestampFirst, msg.getTopic(), Arrays.toString(brokersSent)); info += FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED); MQClientException mqClientException = new MQClientException(info, exception); if (callTimeout) { throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout"); } if (exception instanceof MQBrokerException) { mqClientException.setResponseCode(((MQBrokerException) exception).getResponseCode()); } else if (exception instanceof RemotingConnectException) { mqClientException.setResponseCode(ClientErrorCode.CONNECT_BROKER_EXCEPTION); } else if (exception instanceof RemotingTimeoutException) { mqClientException.setResponseCode(ClientErrorCode.ACCESS_BROKER_TIMEOUT); } else if (exception instanceof MQClientException) { mqClientException.setResponseCode(ClientErrorCode.BROKER_NOT_EXIST_EXCEPTION); } throw mqClientException; } validateNameServerSetting(); throw new MQClientException("No route info of this topic: " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO), null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION); } private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) { TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic); if (null == topicPublishInfo || !topicPublishInfo.ok()) { this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo()); this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); topicPublishInfo = this.topicPublishInfoTable.get(topic); } if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) { return topicPublishInfo; } else { this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer); topicPublishInfo = this.topicPublishInfoTable.get(topic); return topicPublishInfo; } } private SendResult sendKernelImpl(final Message msg, final MessageQueue mq, final CommunicationMode communicationMode, final SendCallback sendCallback, final TopicPublishInfo topicPublishInfo, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { long beginStartTime = System.currentTimeMillis(); String brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq); String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName); if (null == brokerAddr) { tryToFindTopicPublishInfo(mq.getTopic()); brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq); brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName); } SendMessageContext context = null; if (brokerAddr != null) { brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr); byte[] prevBody = msg.getBody(); try { //for MessageBatch,ID has been set in the generating process if (!(msg instanceof MessageBatch)) { MessageClientIDSetter.setUniqID(msg); } boolean topicWithNamespace = false; if (null != this.mQClientFactory.getClientConfig().getNamespace()) { msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace()); topicWithNamespace = true; } int sysFlag = 0; boolean msgBodyCompressed = false; if (this.tryToCompressMessage(msg)) { sysFlag |= MessageSysFlag.COMPRESSED_FLAG; sysFlag |= this.defaultMQProducer.getCompressType().getCompressionFlag(); msgBodyCompressed = true; } final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED); if (Boolean.parseBoolean(tranMsg)) { sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE; } if (hasCheckForbiddenHook()) { CheckForbiddenContext checkForbiddenContext = new CheckForbiddenContext(); checkForbiddenContext.setNameSrvAddr(this.defaultMQProducer.getNamesrvAddr()); checkForbiddenContext.setGroup(this.defaultMQProducer.getProducerGroup()); checkForbiddenContext.setCommunicationMode(communicationMode); checkForbiddenContext.setBrokerAddr(brokerAddr); checkForbiddenContext.setMessage(msg); checkForbiddenContext.setMq(mq); checkForbiddenContext.setUnitMode(this.isUnitMode()); this.executeCheckForbiddenHook(checkForbiddenContext); } if (this.hasSendMessageHook()) { context = new SendMessageContext(); context.setProducer(this); context.setProducerGroup(this.defaultMQProducer.getProducerGroup()); context.setCommunicationMode(communicationMode); context.setBornHost(this.defaultMQProducer.getClientIP()); context.setBrokerAddr(brokerAddr); context.setMessage(msg); context.setMq(mq); context.setNamespace(this.defaultMQProducer.getNamespace()); String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED); if (isTrans != null && isTrans.equals("true")) { context.setMsgType(MessageType.Trans_Msg_Half); } if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null || msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null || msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null || msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null) { context.setMsgType(MessageType.Delay_Msg); } this.executeSendMessageHookBefore(context); } SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup()); requestHeader.setTopic(msg.getTopic()); requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey()); requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums()); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setSysFlag(sysFlag); requestHeader.setBornTimestamp(System.currentTimeMillis()); requestHeader.setFlag(msg.getFlag()); requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties())); requestHeader.setReconsumeTimes(0); requestHeader.setUnitMode(this.isUnitMode()); requestHeader.setBatch(msg instanceof MessageBatch); requestHeader.setBrokerName(brokerName); if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { String reconsumeTimes = MessageAccessor.getReconsumeTime(msg); if (reconsumeTimes != null) { requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes)); MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME); } String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg); if (maxReconsumeTimes != null) { requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes)); MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES); } } SendResult sendResult = null; switch (communicationMode) { case ASYNC: Message tmpMessage = msg; boolean messageCloned = false; if (msgBodyCompressed) { //If msg body was compressed, msgbody should be reset using prevBody. //Clone new message using compressed message body and recover origin massage. //Fix bug:https://github.com/apache/rocketmq-externals/issues/66 tmpMessage = MessageAccessor.cloneMessage(msg); messageCloned = true; msg.setBody(prevBody); } if (topicWithNamespace) { if (!messageCloned) { tmpMessage = MessageAccessor.cloneMessage(msg); messageCloned = true; } msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace())); } long costTimeAsync = System.currentTimeMillis() - beginStartTime; if (timeout < costTimeAsync) { throw new RemotingTooMuchRequestException("sendKernelImpl call timeout"); } sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage( brokerAddr, brokerName, tmpMessage, requestHeader, timeout - costTimeAsync, communicationMode, sendCallback, topicPublishInfo, this.mQClientFactory, this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(), context, this); break; case ONEWAY: case SYNC: long costTimeSync = System.currentTimeMillis() - beginStartTime; if (timeout < costTimeSync) { throw new RemotingTooMuchRequestException("sendKernelImpl call timeout"); } sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage( brokerAddr, brokerName, msg, requestHeader, timeout - costTimeSync, communicationMode, context, this); break; default: assert false; break; } if (this.hasSendMessageHook()) { context.setSendResult(sendResult); this.executeSendMessageHookAfter(context); } return sendResult; } catch (RemotingException | InterruptedException | MQBrokerException e) { if (this.hasSendMessageHook()) { context.setException(e); this.executeSendMessageHookAfter(context); } throw e; } finally { msg.setBody(prevBody); msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace())); } } throw new MQClientException("The broker[" + brokerName + "] not exist", null); } public MQClientInstance getMqClientFactory() { return mQClientFactory; } @Deprecated public MQClientInstance getmQClientFactory() { return mQClientFactory; } private boolean tryToCompressMessage(final Message msg) { if (msg instanceof MessageBatch) { //batch does not support compressing right now return false; } byte[] body = msg.getBody(); if (body != null) { if (body.length >= this.defaultMQProducer.getCompressMsgBodyOverHowmuch()) { try { byte[] data = this.defaultMQProducer.getCompressor().compress(body, this.defaultMQProducer.getCompressLevel()); if (data != null) { msg.setBody(data); return true; } } catch (IOException e) { log.error("tryToCompressMessage exception", e); if (log.isDebugEnabled()) { log.debug(msg.toString()); } } } } return false; } public boolean hasCheckForbiddenHook() { return !checkForbiddenHookList.isEmpty(); } public void executeCheckForbiddenHook(final CheckForbiddenContext context) throws MQClientException { if (hasCheckForbiddenHook()) { for (CheckForbiddenHook hook : checkForbiddenHookList) { hook.checkForbidden(context); } } } public boolean hasSendMessageHook() { return !this.sendMessageHookList.isEmpty(); } public void executeSendMessageHookBefore(final SendMessageContext context) { if (!this.sendMessageHookList.isEmpty()) { for (SendMessageHook hook : this.sendMessageHookList) { try { hook.sendMessageBefore(context); } catch (Throwable e) { log.warn("failed to executeSendMessageHookBefore", e); } } } } public void executeSendMessageHookAfter(final SendMessageContext context) { if (!this.sendMessageHookList.isEmpty()) { for (SendMessageHook hook : this.sendMessageHookList) { try { hook.sendMessageAfter(context); } catch (Throwable e) { log.warn("failed to executeSendMessageHookAfter", e); } } } } public boolean hasEndTransactionHook() { return !this.endTransactionHookList.isEmpty(); } public void executeEndTransactionHook(final EndTransactionContext context) { if (!this.endTransactionHookList.isEmpty()) { for (EndTransactionHook hook : this.endTransactionHookList) { try { hook.endTransaction(context); } catch (Throwable e) { log.warn("failed to executeEndTransactionHook", e); } } } } public void doExecuteEndTransactionHook(Message msg, String msgId, String brokerAddr, LocalTransactionState state, boolean fromTransactionCheck) { if (hasEndTransactionHook()) { EndTransactionContext context = new EndTransactionContext(); context.setProducerGroup(defaultMQProducer.getProducerGroup()); context.setBrokerAddr(brokerAddr); context.setMessage(msg); context.setMsgId(msgId); context.setTransactionId(msg.getTransactionId()); context.setTransactionState(state); context.setFromTransactionCheck(fromTransactionCheck); executeEndTransactionHook(context); } } /** * DEFAULT ONEWAY ------------------------------------------------------- */ public void sendOneway(Message msg) throws MQClientException, RemotingException, InterruptedException { try { this.sendDefaultImpl(msg, CommunicationMode.ONEWAY, null, this.defaultMQProducer.getSendMsgTimeout()); } catch (MQBrokerException e) { throw new MQClientException("unknown exception", e); } } /** * KERNEL SYNC ------------------------------------------------------- */ public SendResult send(Message msg, MessageQueue mq) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return send(msg, mq, this.defaultMQProducer.getSendMsgTimeout()); } public SendResult send(Message msg, MessageQueue mq, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { long beginStartTime = System.currentTimeMillis(); this.makeSureStateOK(); Validators.checkMessage(msg, this.defaultMQProducer); if (!msg.getTopic().equals(mq.getTopic())) { throw new MQClientException("message's topic not equal mq's topic", null); } long costTime = System.currentTimeMillis() - beginStartTime; if (timeout < costTime) { throw new RemotingTooMuchRequestException("call timeout"); } return this.sendKernelImpl(msg, mq, CommunicationMode.SYNC, null, null, timeout); } /** * KERNEL ASYNC ------------------------------------------------------- */ public void send(Message msg, MessageQueue mq, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException { send(msg, mq, sendCallback, this.defaultMQProducer.getSendMsgTimeout()); } /** * @param msg * @param mq * @param sendCallback * @param timeout the sendCallback will be invoked at most time * @throws MQClientException * @throws RemotingException * @throws InterruptedException * @deprecated It will be removed at 4.4.0 cause for exception handling and the wrong Semantics of timeout. A new one will be * provided in next version */ @Deprecated public void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException { BackpressureSendCallBack newCallBack = new BackpressureSendCallBack(sendCallback); final long beginStartTime = System.currentTimeMillis(); Runnable runnable = new Runnable() { @Override public void run() { try { makeSureStateOK(); Validators.checkMessage(msg, defaultMQProducer); if (!msg.getTopic().equals(mq.getTopic())) { throw new MQClientException("Topic of the message does not match its target message queue", null); } long costTime = System.currentTimeMillis() - beginStartTime; if (timeout > costTime) { try { sendKernelImpl(msg, mq, CommunicationMode.ASYNC, newCallBack, null, timeout - costTime); } catch (MQBrokerException e) { throw new MQClientException("unknown exception", e); } } else { newCallBack.onException(new RemotingTooMuchRequestException("call timeout")); } } catch (Exception e) { newCallBack.onException(e); } } }; executeAsyncMessageSend(runnable, msg, newCallBack, timeout, beginStartTime); } /** * KERNEL ONEWAY ------------------------------------------------------- */ public void sendOneway(Message msg, MessageQueue mq) throws MQClientException, RemotingException, InterruptedException { this.makeSureStateOK(); Validators.checkMessage(msg, this.defaultMQProducer); try { this.sendKernelImpl(msg, mq, CommunicationMode.ONEWAY, null, null, this.defaultMQProducer.getSendMsgTimeout()); } catch (MQBrokerException e) { throw new MQClientException("unknown exception", e); } } /** * SELECT SYNC ------------------------------------------------------- */ public SendResult send(Message msg, MessageQueueSelector selector, Object arg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return send(msg, selector, arg, this.defaultMQProducer.getSendMsgTimeout()); } public SendResult send(Message msg, MessageQueueSelector selector, Object arg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.sendSelectImpl(msg, selector, arg, CommunicationMode.SYNC, null, timeout); } private SendResult sendSelectImpl( Message msg, MessageQueueSelector selector, Object arg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { long beginStartTime = System.currentTimeMillis(); this.makeSureStateOK(); Validators.checkMessage(msg, this.defaultMQProducer); TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic()); if (topicPublishInfo != null && topicPublishInfo.ok()) { MessageQueue mq = null; try { List messageQueueList = mQClientFactory.getMQAdminImpl().parsePublishMessageQueues(topicPublishInfo.getMessageQueueList()); Message userMessage = MessageAccessor.cloneMessage(msg); String userTopic = NamespaceUtil.withoutNamespace(userMessage.getTopic(), mQClientFactory.getClientConfig().getNamespace()); userMessage.setTopic(userTopic); mq = mQClientFactory.getClientConfig().queueWithNamespace(selector.select(messageQueueList, userMessage, arg)); } catch (Throwable e) { throw new MQClientException("select message queue threw exception.", e); } long costTime = System.currentTimeMillis() - beginStartTime; if (timeout < costTime) { throw new RemotingTooMuchRequestException("sendSelectImpl call timeout"); } if (mq != null) { return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, null, timeout - costTime); } else { throw new MQClientException("select message queue return null.", null); } } validateNameServerSetting(); throw new MQClientException("No route info for this topic, " + msg.getTopic(), null); } /** * SELECT ASYNC ------------------------------------------------------- */ public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException { send(msg, selector, arg, sendCallback, this.defaultMQProducer.getSendMsgTimeout()); } /** * It will be removed at 4.4.0 cause for exception handling and the wrong Semantics of timeout. A new one will be * provided in next version * * @param msg * @param selector * @param arg * @param sendCallback * @param timeout the sendCallback will be invoked at most time * @throws MQClientException * @throws RemotingException * @throws InterruptedException */ @Deprecated public void send(final Message msg, final MessageQueueSelector selector, final Object arg, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException { BackpressureSendCallBack newCallBack = new BackpressureSendCallBack(sendCallback); final long beginStartTime = System.currentTimeMillis(); Runnable runnable = new Runnable() { @Override public void run() { long costTime = System.currentTimeMillis() - beginStartTime; if (timeout > costTime) { try { try { sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, newCallBack, timeout - costTime); } catch (MQBrokerException e) { throw new MQClientException("unknown exception", e); } } catch (Exception e) { newCallBack.onException(e); } } else { newCallBack.onException(new RemotingTooMuchRequestException("call timeout")); } } }; executeAsyncMessageSend(runnable, msg, newCallBack, timeout, beginStartTime); } /** * SELECT ONEWAY ------------------------------------------------------- */ public void sendOneway(Message msg, MessageQueueSelector selector, Object arg) throws MQClientException, RemotingException, InterruptedException { try { this.sendSelectImpl(msg, selector, arg, CommunicationMode.ONEWAY, null, this.defaultMQProducer.getSendMsgTimeout()); } catch (MQBrokerException e) { throw new MQClientException("unknown exception", e); } } public TransactionSendResult sendMessageInTransaction(final Message msg, final TransactionListener localTransactionListener, final Object arg) throws MQClientException { TransactionListener transactionListener = getCheckListener(); if (null == localTransactionListener && null == transactionListener) { throw new MQClientException("tranExecutor is null", null); } ensureNotDelayedForTransactional(msg); Validators.checkMessage(msg, this.defaultMQProducer); SendResult sendResult = null; MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup()); try { sendResult = this.send(msg); } catch (Exception e) { throw new MQClientException("send message Exception", e); } LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW; Throwable localException = null; switch (sendResult.getSendStatus()) { case SEND_OK: { try { if (sendResult.getTransactionId() != null) { msg.putUserProperty("__transactionId__", sendResult.getTransactionId()); } String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (null != transactionId && !"".equals(transactionId)) { msg.setTransactionId(transactionId); } if (null != localTransactionListener) { localTransactionState = localTransactionListener.executeLocalTransaction(msg, arg); } else { log.debug("Used new transaction API"); localTransactionState = transactionListener.executeLocalTransaction(msg, arg); } if (null == localTransactionState) { localTransactionState = LocalTransactionState.UNKNOW; } if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) { log.info("executeLocalTransactionBranch return: {} messageTopic: {} transactionId: {} tag: {} key: {}", localTransactionState, msg.getTopic(), msg.getTransactionId(), msg.getTags(), msg.getKeys()); } } catch (Throwable e) { log.error("executeLocalTransactionBranch exception, messageTopic: {} transactionId: {} tag: {} key: {}", msg.getTopic(), msg.getTransactionId(), msg.getTags(), msg.getKeys(), e); localException = e; } } break; case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case SLAVE_NOT_AVAILABLE: localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE; break; default: break; } try { this.endTransaction(msg, sendResult, localTransactionState, localException); } catch (Exception e) { log.warn("local transaction execute {}, but end broker transaction failed", localTransactionState, e); } TransactionSendResult transactionSendResult = new TransactionSendResult(); transactionSendResult.setSendStatus(sendResult.getSendStatus()); transactionSendResult.setMessageQueue(sendResult.getMessageQueue()); transactionSendResult.setMsgId(sendResult.getMsgId()); transactionSendResult.setQueueOffset(sendResult.getQueueOffset()); transactionSendResult.setTransactionId(sendResult.getTransactionId()); transactionSendResult.setLocalTransactionState(localTransactionState); return transactionSendResult; } private void ensureNotDelayedForTransactional(final Message msg) throws MQClientException { if (msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null || msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null || msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null || msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null) { throw new MQClientException("Transactional messages do not support delayed delivery", null); } } /** * DEFAULT SYNC ------------------------------------------------------- */ public SendResult send( Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return send(msg, this.defaultMQProducer.getSendMsgTimeout()); } public void endTransaction( final Message msg, final SendResult sendResult, final LocalTransactionState localTransactionState, final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException { final MessageId id; if (sendResult.getOffsetMsgId() != null) { id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId()); } else { id = MessageDecoder.decodeMessageId(sendResult.getMsgId()); } String transactionId = sendResult.getTransactionId(); final String destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(defaultMQProducer.queueWithNamespace(sendResult.getMessageQueue())); final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(destBrokerName); EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader(); requestHeader.setTopic(msg.getTopic()); requestHeader.setTransactionId(transactionId); requestHeader.setCommitLogOffset(id.getOffset()); requestHeader.setBrokerName(destBrokerName); switch (localTransactionState) { case COMMIT_MESSAGE: requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE); break; case ROLLBACK_MESSAGE: requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE); break; case UNKNOW: requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE); break; default: break; } doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false); requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup()); requestHeader.setTranStateTableOffset(sendResult.getQueueOffset()); requestHeader.setMsgId(sendResult.getMsgId()); String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null; this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark, this.defaultMQProducer.getSendMsgTimeout()); } public String recallMessage( String topic, String recallHandle) throws RemotingException, MQClientException, MQBrokerException, InterruptedException { makeSureStateOK(); Validators.checkTopic(topic); if (NamespaceUtil.isRetryTopic(topic) || NamespaceUtil.isDLQTopic(topic)) { throw new MQClientException("topic is not supported", null); } RecallMessageHandle.HandleV1 handleEntity; try { handleEntity = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(recallHandle); } catch (Exception e) { throw new MQClientException(e.getMessage(), null); } tryToFindTopicPublishInfo(topic); String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(handleEntity.getBrokerName()); brokerAddr = StringUtils.isNotEmpty(brokerAddr) ? // find another address to support multi proxy endpoints, // may cause failure request in proxy-less mode when the broker is temporarily unavailable brokerAddr : this.mQClientFactory.findBrokerAddrByTopic(topic); if (StringUtils.isEmpty(brokerAddr)) { log.warn("can't find broker service address. {}", handleEntity.getBrokerName()); throw new MQClientException("The broker service address not found", null); } RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup()); requestHeader.setTopic(topic); requestHeader.setRecallHandle(recallHandle); requestHeader.setBrokerName(handleEntity.getBrokerName()); return this.mQClientFactory.getMQClientAPIImpl().recallMessage(brokerAddr, requestHeader, this.defaultMQProducer.getSendMsgTimeout()); } public void setCallbackExecutor(final ExecutorService callbackExecutor) { this.mQClientFactory.getMQClientAPIImpl().getRemotingClient().setCallbackExecutor(callbackExecutor); } public ExecutorService getAsyncSenderExecutor() { return null == asyncSenderExecutor ? defaultAsyncSenderExecutor : asyncSenderExecutor; } public void setAsyncSenderExecutor(ExecutorService asyncSenderExecutor) { this.asyncSenderExecutor = asyncSenderExecutor; } public SendResult send(Message msg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout); } public Message request(final Message msg, long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID); try { final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, null); RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture); long cost = System.currentTimeMillis() - beginTimestamp; this.sendDefaultImpl(msg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { requestResponseFuture.setSendRequestOk(false); requestResponseFuture.putResponseMessage(null); requestResponseFuture.setCause(e); } }, timeout - cost); return waitResponse(msg, timeout, requestResponseFuture, cost); } finally { RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId); } } public void request(Message msg, final RequestCallback requestCallback, long timeout) throws RemotingException, InterruptedException, MQClientException, MQBrokerException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID); final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, requestCallback); RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture); long cost = System.currentTimeMillis() - beginTimestamp; this.sendDefaultImpl(msg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); requestResponseFuture.executeRequestCallback(); } @Override public void onException(Throwable e) { requestResponseFuture.setCause(e); requestFail(correlationId); } }, timeout - cost); } public Message request(final Message msg, final MessageQueueSelector selector, final Object arg, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, RequestTimeoutException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID); try { final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, null); RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture); long cost = System.currentTimeMillis() - beginTimestamp; this.sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { requestResponseFuture.setSendRequestOk(false); requestResponseFuture.putResponseMessage(null); requestResponseFuture.setCause(e); } }, timeout - cost); return waitResponse(msg, timeout, requestResponseFuture, cost); } finally { RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId); } } public void request(final Message msg, final MessageQueueSelector selector, final Object arg, final RequestCallback requestCallback, final long timeout) throws RemotingException, InterruptedException, MQClientException, MQBrokerException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID); final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, requestCallback); RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture); long cost = System.currentTimeMillis() - beginTimestamp; this.sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { requestResponseFuture.setCause(e); requestFail(correlationId); } }, timeout - cost); } public Message request(final Message msg, final MessageQueue mq, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, RequestTimeoutException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID); try { final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, null); RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture); long cost = System.currentTimeMillis() - beginTimestamp; this.sendKernelImpl(msg, mq, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { requestResponseFuture.setSendRequestOk(false); requestResponseFuture.putResponseMessage(null); requestResponseFuture.setCause(e); } }, null, timeout - cost); return waitResponse(msg, timeout, requestResponseFuture, cost); } finally { RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId); } } private Message waitResponse(Message msg, long timeout, RequestResponseFuture requestResponseFuture, long cost) throws InterruptedException, RequestTimeoutException, MQClientException { Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost); if (responseMessage == null) { if (requestResponseFuture.isSendRequestOk()) { throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION, "send request message to <" + msg.getTopic() + "> OK, but wait reply message timeout, " + timeout + " ms."); } else { throw new MQClientException("send request message to <" + msg.getTopic() + "> fail", requestResponseFuture.getCause()); } } return responseMessage; } public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) throws RemotingException, InterruptedException, MQClientException, MQBrokerException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID); final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, requestCallback); RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture); long cost = System.currentTimeMillis() - beginTimestamp; this.sendKernelImpl(msg, mq, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { requestResponseFuture.setCause(e); requestFail(correlationId); } }, null, timeout - cost); } private void requestFail(final String correlationId) { RequestResponseFuture responseFuture = RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId); if (responseFuture != null) { responseFuture.setSendRequestOk(false); responseFuture.putResponseMessage(null); try { responseFuture.executeRequestCallback(); } catch (Exception e) { log.warn("execute requestCallback in requestFail, and callback throw", e); } } } private void prepareSendRequest(final Message msg, long timeout) { String correlationId = CorrelationIdUtil.createCorrelationId(); String requestClientId = this.getMqClientFactory().getClientId(); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_CORRELATION_ID, correlationId); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT, requestClientId); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MESSAGE_TTL, String.valueOf(timeout)); boolean hasRouteData = this.getMqClientFactory().getTopicRouteTable().containsKey(msg.getTopic()); if (!hasRouteData) { long beginTimestamp = System.currentTimeMillis(); this.tryToFindTopicPublishInfo(msg.getTopic()); this.getMqClientFactory().sendHeartbeatToAllBrokerWithLock(); long cost = System.currentTimeMillis() - beginTimestamp; if (cost > 500) { log.warn("prepare send request for <{}> cost {} ms", msg.getTopic(), cost); } } } private void initTopicRoute() { List topics = this.defaultMQProducer.getTopics(); if (topics != null && topics.size() > 0) { topics.forEach(topic -> { String newTopic = NamespaceUtil.wrapNamespace(this.defaultMQProducer.getNamespace(), topic); TopicPublishInfo topicPublishInfo = tryToFindTopicPublishInfo(newTopic); if (topicPublishInfo == null || !topicPublishInfo.ok()) { log.warn("No route info of this topic: " + newTopic + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO)); } }); } } public ConcurrentMap getTopicPublishInfoTable() { return topicPublishInfoTable; } public ServiceState getServiceState() { return serviceState; } public void setServiceState(ServiceState serviceState) { this.serviceState = serviceState; } public long[] getNotAvailableDuration() { return this.mqFaultStrategy.getNotAvailableDuration(); } public void setNotAvailableDuration(final long[] notAvailableDuration) { this.mqFaultStrategy.setNotAvailableDuration(notAvailableDuration); } public long[] getLatencyMax() { return this.mqFaultStrategy.getLatencyMax(); } public void setLatencyMax(final long[] latencyMax) { this.mqFaultStrategy.setLatencyMax(latencyMax); } public boolean isSendLatencyFaultEnable() { return this.mqFaultStrategy.isSendLatencyFaultEnable(); } public void setSendLatencyFaultEnable(final boolean sendLatencyFaultEnable) { this.mqFaultStrategy.setSendLatencyFaultEnable(sendLatencyFaultEnable); } public DefaultMQProducer getDefaultMQProducer() { return defaultMQProducer; } public MQFaultStrategy getMqFaultStrategy() { return mqFaultStrategy; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/producer/MQProducerInner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.producer; import java.util.Set; import org.apache.rocketmq.client.producer.TransactionCheckListener; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; public interface MQProducerInner { Set getPublishTopicList(); boolean isPublishTopicNeedUpdate(final String topic); TransactionCheckListener checkListener(); TransactionListener getCheckListener(); void checkTransactionState( final String addr, final MessageExt msg, final CheckTransactionStateRequestHeader checkRequestHeader); void updateTopicPublishInfo(final String topic, final TopicPublishInfo info); boolean isUnitMode(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.producer; import java.util.ArrayList; import java.util.List; import com.google.common.base.Preconditions; import org.apache.rocketmq.client.common.ThreadLocalIndex; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class TopicPublishInfo { private boolean orderTopic = false; private boolean haveTopicRouterInfo = false; private List messageQueueList = new ArrayList<>(); private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex(); private TopicRouteData topicRouteData; public interface QueueFilter { boolean filter(MessageQueue mq); } public boolean isOrderTopic() { return orderTopic; } public void setOrderTopic(boolean orderTopic) { this.orderTopic = orderTopic; } public boolean ok() { return null != this.messageQueueList && !this.messageQueueList.isEmpty(); } public List getMessageQueueList() { return messageQueueList; } public void setMessageQueueList(List messageQueueList) { this.messageQueueList = messageQueueList; } public ThreadLocalIndex getSendWhichQueue() { return sendWhichQueue; } public void setSendWhichQueue(ThreadLocalIndex sendWhichQueue) { this.sendWhichQueue = sendWhichQueue; } public boolean isHaveTopicRouterInfo() { return haveTopicRouterInfo; } public void setHaveTopicRouterInfo(boolean haveTopicRouterInfo) { this.haveTopicRouterInfo = haveTopicRouterInfo; } public MessageQueue selectOneMessageQueue(QueueFilter ...filter) { return selectOneMessageQueue(this.messageQueueList, this.sendWhichQueue, filter); } private MessageQueue selectOneMessageQueue(List messageQueueList, ThreadLocalIndex sendQueue, QueueFilter ...filter) { if (messageQueueList == null || messageQueueList.isEmpty()) { return null; } if (filter != null && filter.length != 0) { for (int i = 0; i < messageQueueList.size(); i++) { int index = Math.abs(sendQueue.incrementAndGet() % messageQueueList.size()); MessageQueue mq = messageQueueList.get(index); boolean filterResult = true; for (QueueFilter f: filter) { Preconditions.checkNotNull(f); filterResult &= f.filter(mq); } if (filterResult) { return mq; } } return null; } int index = Math.abs(sendQueue.incrementAndGet() % messageQueueList.size()); return messageQueueList.get(index); } public void resetIndex() { this.sendWhichQueue.reset(); } public MessageQueue selectOneMessageQueue(final String lastBrokerName) { if (lastBrokerName == null) { return selectOneMessageQueue(); } else { for (int i = 0; i < this.messageQueueList.size(); i++) { MessageQueue mq = selectOneMessageQueue(); if (!mq.getBrokerName().equals(lastBrokerName)) { return mq; } } return selectOneMessageQueue(); } } public MessageQueue selectOneMessageQueue() { int index = this.sendWhichQueue.incrementAndGet(); int pos = index % this.messageQueueList.size(); return this.messageQueueList.get(pos); } public int getWriteQueueNumsByBroker(final String brokerName) { for (int i = 0; i < topicRouteData.getQueueDatas().size(); i++) { final QueueData queueData = this.topicRouteData.getQueueDatas().get(i); if (queueData.getBrokerName().equals(brokerName)) { return queueData.getWriteQueueNums(); } } return -1; } @Override public String toString() { return "TopicPublishInfo [orderTopic=" + orderTopic + ", messageQueueList=" + messageQueueList + ", sendWhichQueue=" + sendWhichQueue + ", haveTopicRouterInfo=" + haveTopicRouterInfo + "]"; } public TopicRouteData getTopicRouteData() { return topicRouteData; } public void setTopicRouteData(final TopicRouteData topicRouteData) { this.topicRouteData = topicRouteData; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.latency; public interface LatencyFaultTolerance { /** * Update brokers' states, to decide if they are good or not. * * @param name Broker's name. * @param currentLatency Current message sending process's latency. * @param notAvailableDuration Corresponding not available time, ms. The broker will be not available until it * spends such time. * @param reachable To decide if this broker is reachable or not. */ void updateFaultItem(final T name, final long currentLatency, final long notAvailableDuration, final boolean reachable); /** * To check if this broker is available. * * @param name Broker's name. * @return boolean variable, if this is true, then the broker is available. */ boolean isAvailable(final T name); /** * To check if this broker is reachable. * * @param name Broker's name. * @return boolean variable, if this is true, then the broker is reachable. */ boolean isReachable(final T name); /** * Remove the broker in this fault item table. * * @param name broker's name. */ void remove(final T name); /** * The worst situation, no broker can be available. Then choose random one. * * @return A random mq will be returned. */ T pickOneAtLeast(); /** * Start a new thread, to detect the broker's reachable tag. */ void startDetector(); /** * Shutdown threads that started by LatencyFaultTolerance. */ void shutdown(); /** * A function reserved, just detect by once, won't create a new thread. */ void detectByOneRound(); /** * Use it to set the detect timeout bound. * * @param detectTimeout timeout bound */ void setDetectTimeout(final int detectTimeout); /** * Use it to set the detector's detector interval for each broker (each broker will be detected once during this * time) * * @param detectInterval each broker's detecting interval */ void setDetectInterval(final int detectInterval); /** * Use it to set the detector work or not. * * @param startDetectorEnable set the detector's work status */ void setStartDetectorEnable(final boolean startDetectorEnable); /** * Use it to judge if the detector enabled. * * @return is the detector should be started. */ boolean isStartDetectorEnable(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.latency; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.common.ThreadLocalIndex; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class LatencyFaultToleranceImpl implements LatencyFaultTolerance { private final static Logger log = LoggerFactory.getLogger(MQFaultStrategy.class); private final ConcurrentHashMap faultItemTable = new ConcurrentHashMap(16); private int detectTimeout = 200; private int detectInterval = 2000; private final ThreadLocalIndex whichItemWorst = new ThreadLocalIndex(); private volatile boolean startDetectorEnable = false; private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "LatencyFaultToleranceScheduledThread"); } }); private final Resolver resolver; private final ServiceDetector serviceDetector; public LatencyFaultToleranceImpl(Resolver resolver, ServiceDetector serviceDetector) { this.resolver = resolver; this.serviceDetector = serviceDetector; } @Override public void detectByOneRound() { for (Map.Entry item : this.faultItemTable.entrySet()) { FaultItem brokerItem = item.getValue(); if (System.currentTimeMillis() - brokerItem.checkStamp >= 0) { brokerItem.checkStamp = System.currentTimeMillis() + this.detectInterval; String brokerAddr = resolver.resolve(brokerItem.getName()); if (brokerAddr == null) { faultItemTable.remove(item.getKey()); continue; } if (null == serviceDetector) { continue; } boolean serviceOK = serviceDetector.detect(brokerAddr, detectTimeout); if (serviceOK && !brokerItem.reachableFlag) { log.info(brokerItem.name + " is reachable now, then it can be used."); brokerItem.reachableFlag = true; } } } } @Override public void startDetector() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { if (startDetectorEnable) { detectByOneRound(); } } catch (Exception e) { log.warn("Unexpected exception raised while detecting service reachability", e); } } }, 3, 3, TimeUnit.SECONDS); } @Override public void shutdown() { this.scheduledExecutorService.shutdown(); } @Override public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration, final boolean reachable) { FaultItem old = this.faultItemTable.get(name); if (null == old) { final FaultItem faultItem = new FaultItem(name); faultItem.setCurrentLatency(currentLatency); faultItem.updateNotAvailableDuration(notAvailableDuration); faultItem.setReachable(reachable); old = this.faultItemTable.putIfAbsent(name, faultItem); } if (null != old) { old.setCurrentLatency(currentLatency); old.updateNotAvailableDuration(notAvailableDuration); old.setReachable(reachable); } if (!reachable) { log.info(name + " is unreachable, it will not be used until it's reachable"); } } @Override public boolean isAvailable(final String name) { final FaultItem faultItem = this.faultItemTable.get(name); if (faultItem != null) { return faultItem.isAvailable(); } return true; } @Override public boolean isReachable(final String name) { final FaultItem faultItem = this.faultItemTable.get(name); if (faultItem != null) { return faultItem.isReachable(); } return true; } @Override public void remove(final String name) { this.faultItemTable.remove(name); } @Override public boolean isStartDetectorEnable() { return startDetectorEnable; } @Override public void setStartDetectorEnable(boolean startDetectorEnable) { this.startDetectorEnable = startDetectorEnable; } @Override public String pickOneAtLeast() { final Enumeration elements = this.faultItemTable.elements(); List tmpList = new LinkedList(); while (elements.hasMoreElements()) { final FaultItem faultItem = elements.nextElement(); tmpList.add(faultItem); } if (!tmpList.isEmpty()) { Collections.shuffle(tmpList); for (FaultItem faultItem : tmpList) { if (faultItem.reachableFlag) { return faultItem.name; } } } return null; } @Override public String toString() { return "LatencyFaultToleranceImpl{" + "faultItemTable=" + faultItemTable + ", whichItemWorst=" + whichItemWorst + '}'; } @Override public void setDetectTimeout(final int detectTimeout) { this.detectTimeout = detectTimeout; } @Override public void setDetectInterval(final int detectInterval) { this.detectInterval = detectInterval; } public class FaultItem implements Comparable { private final String name; private volatile long currentLatency; private volatile long startTimestamp; private volatile long checkStamp; private volatile boolean reachableFlag; public FaultItem(final String name) { this.name = name; } public void updateNotAvailableDuration(long notAvailableDuration) { if (notAvailableDuration > 0 && System.currentTimeMillis() + notAvailableDuration > this.startTimestamp) { this.startTimestamp = System.currentTimeMillis() + notAvailableDuration; log.info(name + " will be isolated for " + notAvailableDuration + " ms."); } } @Override public int compareTo(final FaultItem other) { if (this.isAvailable() != other.isAvailable()) { if (this.isAvailable()) { return -1; } if (other.isAvailable()) { return 1; } } if (this.currentLatency < other.currentLatency) { return -1; } else if (this.currentLatency > other.currentLatency) { return 1; } if (this.startTimestamp < other.startTimestamp) { return -1; } else if (this.startTimestamp > other.startTimestamp) { return 1; } return 0; } public void setReachable(boolean reachableFlag) { this.reachableFlag = reachableFlag; } public void setCheckStamp(long checkStamp) { this.checkStamp = checkStamp; } public boolean isAvailable() { return System.currentTimeMillis() >= startTimestamp; } public boolean isReachable() { return reachableFlag; } @Override public int hashCode() { int result = getName() != null ? getName().hashCode() : 0; result = 31 * result + (int) (getCurrentLatency() ^ (getCurrentLatency() >>> 32)); result = 31 * result + (int) (getStartTimestamp() ^ (getStartTimestamp() >>> 32)); return result; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof FaultItem)) { return false; } final FaultItem faultItem = (FaultItem) o; if (getCurrentLatency() != faultItem.getCurrentLatency()) { return false; } if (getStartTimestamp() != faultItem.getStartTimestamp()) { return false; } return getName() != null ? getName().equals(faultItem.getName()) : faultItem.getName() == null; } @Override public String toString() { return "FaultItem{" + "name='" + name + '\'' + ", currentLatency=" + currentLatency + ", startTimestamp=" + startTimestamp + ", reachableFlag=" + reachableFlag + '}'; } public String getName() { return name; } public long getCurrentLatency() { return currentLatency; } public void setCurrentLatency(final long currentLatency) { this.currentLatency = currentLatency; } public long getStartTimestamp() { return startTimestamp; } public void setStartTimestamp(final long startTimestamp) { this.startTimestamp = startTimestamp; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.latency; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo.QueueFilter; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.StartAndShutdown; public class MQFaultStrategy implements StartAndShutdown { private LatencyFaultTolerance latencyFaultTolerance; private volatile boolean sendLatencyFaultEnable; private volatile boolean startDetectorEnable; private long[] latencyMax = {50L, 100L, 550L, 1800L, 3000L, 5000L, 15000L}; private long[] notAvailableDuration = {0L, 0L, 2000L, 5000L, 6000L, 10000L, 30000L}; public static class BrokerFilter implements QueueFilter { private String lastBrokerName; public void setLastBrokerName(String lastBrokerName) { this.lastBrokerName = lastBrokerName; } @Override public boolean filter(MessageQueue mq) { if (lastBrokerName != null) { return !mq.getBrokerName().equals(lastBrokerName); } return true; } } private ThreadLocal threadBrokerFilter = new ThreadLocal() { @Override protected BrokerFilter initialValue() { return new BrokerFilter(); } }; private QueueFilter reachableFilter = new QueueFilter() { @Override public boolean filter(MessageQueue mq) { return latencyFaultTolerance.isReachable(mq.getBrokerName()); } }; private QueueFilter availableFilter = new QueueFilter() { @Override public boolean filter(MessageQueue mq) { return latencyFaultTolerance.isAvailable(mq.getBrokerName()); } }; public MQFaultStrategy(ClientConfig cc, Resolver fetcher, ServiceDetector serviceDetector) { this.latencyFaultTolerance = new LatencyFaultToleranceImpl(fetcher, serviceDetector); this.latencyFaultTolerance.setDetectInterval(cc.getDetectInterval()); this.latencyFaultTolerance.setDetectTimeout(cc.getDetectTimeout()); this.setStartDetectorEnable(cc.isStartDetectorEnable()); this.setSendLatencyFaultEnable(cc.isSendLatencyEnable()); } // For unit test. public MQFaultStrategy(ClientConfig cc, LatencyFaultTolerance tolerance) { this.setStartDetectorEnable(cc.isStartDetectorEnable()); this.setSendLatencyFaultEnable(cc.isSendLatencyEnable()); this.latencyFaultTolerance = tolerance; this.latencyFaultTolerance.setDetectInterval(cc.getDetectInterval()); this.latencyFaultTolerance.setDetectTimeout(cc.getDetectTimeout()); } public long[] getNotAvailableDuration() { return notAvailableDuration; } public QueueFilter getAvailableFilter() { return availableFilter; } public QueueFilter getReachableFilter() { return reachableFilter; } public ThreadLocal getThreadBrokerFilter() { return threadBrokerFilter; } public void setNotAvailableDuration(final long[] notAvailableDuration) { this.notAvailableDuration = notAvailableDuration; } public long[] getLatencyMax() { return latencyMax; } public void setLatencyMax(final long[] latencyMax) { this.latencyMax = latencyMax; } public boolean isSendLatencyFaultEnable() { return sendLatencyFaultEnable; } public void setSendLatencyFaultEnable(final boolean sendLatencyFaultEnable) { this.sendLatencyFaultEnable = sendLatencyFaultEnable; } public boolean isStartDetectorEnable() { return startDetectorEnable; } public void setStartDetectorEnable(boolean startDetectorEnable) { this.startDetectorEnable = startDetectorEnable; this.latencyFaultTolerance.setStartDetectorEnable(startDetectorEnable); } public void startDetector() { this.latencyFaultTolerance.startDetector(); } @Override public void start() throws Exception { this.startDetector(); } public void shutdown() { this.latencyFaultTolerance.shutdown(); } public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName, final boolean resetIndex) { BrokerFilter brokerFilter = threadBrokerFilter.get(); brokerFilter.setLastBrokerName(lastBrokerName); if (this.sendLatencyFaultEnable) { if (resetIndex) { tpInfo.resetIndex(); } MessageQueue mq = tpInfo.selectOneMessageQueue(availableFilter, brokerFilter); if (mq != null) { return mq; } mq = tpInfo.selectOneMessageQueue(reachableFilter, brokerFilter); if (mq != null) { return mq; } return tpInfo.selectOneMessageQueue(); } MessageQueue mq = tpInfo.selectOneMessageQueue(brokerFilter); if (mq != null) { return mq; } return tpInfo.selectOneMessageQueue(); } public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation, final boolean reachable) { if (this.sendLatencyFaultEnable) { long duration = computeNotAvailableDuration(isolation ? 10000 : currentLatency); this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration, reachable); } } private long computeNotAvailableDuration(final long currentLatency) { for (int i = latencyMax.length - 1; i >= 0; i--) { if (currentLatency >= latencyMax[i]) { return this.notAvailableDuration[i]; } } return 0; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/latency/Resolver.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.latency; public interface Resolver { String resolve(String name); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/latency/ServiceDetector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.latency; /** * Detect whether the remote service state is normal. */ public interface ServiceDetector { /** * Check if the remote service is normal. * @param endpoint Service endpoint to check against * @return true if the service is back to normal; false otherwise. */ boolean detect(String endpoint, long timeoutMillis); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/lock/ReadWriteCASLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.lock; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class ReadWriteCASLock { //true : can lock ; false : not lock private final AtomicBoolean writeLock = new AtomicBoolean(true); private final AtomicInteger readLock = new AtomicInteger(0); public void acquireWriteLock() { boolean isLock = false; do { isLock = writeLock.compareAndSet(true, false); } while (!isLock); do { isLock = readLock.get() == 0; } while (!isLock); } public void releaseWriteLock() { this.writeLock.compareAndSet(false, true); } public void acquireReadLock() { boolean isLock = false; do { isLock = writeLock.get(); } while (!isLock); readLock.getAndIncrement(); } public void releaseReadLock() { this.readLock.getAndDecrement(); } public boolean getWriteLock() { return this.writeLock.get() && this.readLock.get() == 0; } public boolean getReadLock() { return this.writeLock.get(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/DefaultMQProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.lock.ReadWriteCASLock; import org.apache.rocketmq.client.trace.hook.DefaultRecallMessageTraceHook; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.EndTransactionTraceHookImpl; import org.apache.rocketmq.client.trace.hook.SendMessageTraceHookImpl; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.compression.CompressionType; import org.apache.rocketmq.common.compression.Compressor; import org.apache.rocketmq.common.compression.CompressorFactory; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.ResponseCode; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; /** * This class is the entry point for applications intending to send messages.

    *

    * It's fine to tune fields which exposes getter/setter methods, but keep in mind, all of them should work well out of * box for most scenarios.

    *

    * This class aggregates various send methods to deliver messages to broker(s). Each of them has pros and * cons; you'd better understand strengths and weakness of them before actually coding.

    * *

    Thread Safety: After configuring and starting process, this class can be regarded as thread-safe * and used among multiple threads context.

    */ public class DefaultMQProducer extends ClientConfig implements MQProducer { /** * Wrapping internal implementations for virtually all methods presented in this class. */ protected final transient DefaultMQProducerImpl defaultMQProducerImpl; private final Logger logger = LoggerFactory.getLogger(DefaultMQProducer.class); private final Set retryResponseCodes = new CopyOnWriteArraySet<>(Arrays.asList( ResponseCode.TOPIC_NOT_EXIST, ResponseCode.SERVICE_NOT_AVAILABLE, ResponseCode.SYSTEM_ERROR, ResponseCode.SYSTEM_BUSY, ResponseCode.NO_PERMISSION, ResponseCode.NO_BUYER_ID, ResponseCode.NOT_IN_CURRENT_UNIT, ResponseCode.GO_AWAY )); /** * Producer group conceptually aggregates all producer instances of exactly same role, which is particularly * important when transactional messages are involved.

    *

    * For non-transactional messages, it does not matter as long as it's unique per process.

    *

    * See core concepts for more discussion. */ private String producerGroup; /** * Topics that need to be initialized for transaction producer */ private List topics; /** * Just for testing or demo program */ private String createTopicKey = TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC; /** * Number of queues to create per default topic. */ private volatile int defaultTopicQueueNums = 4; /** * Timeout for sending messages. */ private int sendMsgTimeout = 3000; /** * Max timeout for sending messages per request. */ private int sendMsgMaxTimeoutPerRequest = -1; /** * Compress message body threshold, namely, message body larger than 4k will be compressed on default. */ private int compressMsgBodyOverHowmuch = 1024 * 4; /** * Maximum number of retry to perform internally before claiming sending failure in synchronous mode.

    *

    * This may potentially cause message duplication which is up to application developers to resolve. */ private int retryTimesWhenSendFailed = 2; /** * Maximum number of retry to perform internally before claiming sending failure in asynchronous mode.

    *

    * This may potentially cause message duplication which is up to application developers to resolve. */ private int retryTimesWhenSendAsyncFailed = 2; /** * Indicate whether to retry another broker on sending failure internally. */ private boolean retryAnotherBrokerWhenNotStoreOK = false; /** * Maximum allowed message body size in bytes. */ private int maxMessageSize = 1024 * 1024 * 4; // 4M /** * Interface of asynchronous transfer data */ private TraceDispatcher traceDispatcher = null; /** * Switch flag instance for automatic batch message */ private boolean autoBatch = false; /** * Instance for batching message automatically */ private ProduceAccumulator produceAccumulator = null; /** * Indicate whether to block message when asynchronous sending traffic is too heavy. */ private boolean enableBackpressureForAsyncMode = false; /** * on BackpressureForAsyncMode, limit maximum number of on-going sending async messages * default is 1024 */ private int backPressureForAsyncSendNum = 1024; /** * on BackpressureForAsyncMode, limit maximum message size of on-going sending async messages * default is 100M */ private int backPressureForAsyncSendSize = 100 * 1024 * 1024; /** * Maximum hold time of accumulator. */ private int batchMaxDelayMs = -1; /** * Maximum accumulation message body size for a single messageAccumulation. */ private long batchMaxBytes = -1; /** * Maximum message body size for produceAccumulator. */ private long totalBatchMaxBytes = -1; private RPCHook rpcHook = null; /** * backPressureForAsyncSendNum is guaranteed to be modified at runtime and no new requests are allowed */ private final ReadWriteCASLock backPressureForAsyncSendNumLock = new ReadWriteCASLock(); /** * backPressureForAsyncSendSize is guaranteed to be modified at runtime and no new requests are allowed */ private final ReadWriteCASLock backPressureForAsyncSendSizeLock = new ReadWriteCASLock(); /** * Compress level of compress algorithm. */ private int compressLevel = Integer.parseInt(System.getProperty(MixAll.MESSAGE_COMPRESS_LEVEL, "5")); /** * Compress type of compress algorithm, default using ZLIB. */ private CompressionType compressType = CompressionType.of(System.getProperty(MixAll.MESSAGE_COMPRESS_TYPE, "ZLIB")); /** * Compressor of compress algorithm. */ private Compressor compressor = CompressorFactory.getCompressor(compressType); /** * Default constructor. */ public DefaultMQProducer() { this(MixAll.DEFAULT_PRODUCER_GROUP); } /** * Constructor specifying the RPC hook. * * @param rpcHook RPC hook to execute per each remoting command execution. */ public DefaultMQProducer(RPCHook rpcHook) { this(MixAll.DEFAULT_PRODUCER_GROUP, rpcHook); } /** * Constructor specifying producer group. * * @param producerGroup Producer group, see the name-sake field. */ public DefaultMQProducer(final String producerGroup) { this(producerGroup, (RPCHook) null); } /** * Constructor specifying both producer group and RPC hook. * * @param producerGroup Producer group, see the name-sake field. * @param rpcHook RPC hook to execute per each remoting command execution. */ public DefaultMQProducer(final String producerGroup, RPCHook rpcHook) { this(producerGroup, rpcHook, null); } /** * Constructor specifying namespace, producer group, topics and RPC hook. * * @param producerGroup Producer group, see the name-sake field. * @param rpcHook RPC hook to execute per each remoting command execution. * @param topics Topic that needs to be initialized for routing */ public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, final List topics) { this(producerGroup, rpcHook, topics, false, null); } /** * Constructor specifying producer group, enabled msgTrace flag and customized trace topic name. * * @param producerGroup Producer group, see the name-sake field. * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default * trace topic name. */ public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic) { this(producerGroup, null, enableMsgTrace, customizedTraceTopic); } /** * Constructor specifying producer group. * * @param producerGroup Producer group, see the name-sake field. * @param rpcHook RPC hook to execute per each remoting command execution. * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default * trace topic name. */ public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace, final String customizedTraceTopic) { this(producerGroup, rpcHook, null, enableMsgTrace, customizedTraceTopic); } /** * Constructor specifying namespace, producer group, topics, RPC hook, enabled msgTrace flag and customized trace topic * name. * * @param producerGroup Producer group, see the name-sake field. * @param rpcHook RPC hook to execute per each remoting command execution. * @param topics Topic that needs to be initialized for routing * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default * trace topic name. */ public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, final List topics, boolean enableMsgTrace, final String customizedTraceTopic) { this.producerGroup = producerGroup; this.rpcHook = rpcHook; this.topics = topics; this.enableTrace = enableMsgTrace; this.traceTopic = customizedTraceTopic; defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook); } /** * Constructor specifying producer group. * * @param namespace Namespace for this MQ Producer instance. * @param producerGroup Producer group, see the name-sake field. */ @Deprecated public DefaultMQProducer(final String namespace, final String producerGroup) { this(namespace, producerGroup, null); } /** * Constructor specifying namespace, producer group and RPC hook. * * @param namespace Namespace for this MQ Producer instance. * @param producerGroup Producer group, see the name-sake field. * @param rpcHook RPC hook to execute per each remoting command execution. */ @Deprecated public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) { this.namespace = namespace; this.producerGroup = producerGroup; this.rpcHook = rpcHook; defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook); } /** * Constructor specifying namespace, producer group, RPC hook, enabled msgTrace flag and customized trace topic * name. * * @param namespace Namespace for this MQ Producer instance. * @param producerGroup Producer group, see the name-sake field. * @param rpcHook RPC hook to execute per each remoting command execution. * @param enableMsgTrace Switch flag instance for message trace. * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default * trace topic name. */ @Deprecated public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace, final String customizedTraceTopic) { this(namespace, producerGroup, rpcHook); //if client open the message trace feature this.enableTrace = enableMsgTrace; this.traceTopic = customizedTraceTopic; } /** * Start this producer instance.

    * * Much internal initializing procedures are carried out to make this instance prepared, thus, it's a must * to invoke this method before sending or querying messages.

    * * @throws MQClientException if there is any unexpected error. */ @Override public void start() throws MQClientException { this.setProducerGroup(withNamespace(this.producerGroup)); this.defaultMQProducerImpl.start(); if (this.produceAccumulator != null) { this.produceAccumulator.start(); } if (enableTrace) { try { AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(producerGroup, TraceDispatcher.Type.PRODUCE, getTraceMsgBatchNum(), traceTopic, rpcHook); dispatcher.setHostProducer(this.defaultMQProducerImpl); dispatcher.setNamespaceV2(this.namespaceV2); traceDispatcher = dispatcher; this.defaultMQProducerImpl.registerSendMessageHook( new SendMessageTraceHookImpl(traceDispatcher)); this.defaultMQProducerImpl.registerEndTransactionHook( new EndTransactionTraceHookImpl(traceDispatcher)); this.defaultMQProducerImpl.getMqClientFactory().getMQClientAPIImpl().getRemotingClient() .registerRPCHook(new DefaultRecallMessageTraceHook(traceDispatcher)); } catch (Throwable e) { logger.error("system mqtrace hook init failed ,maybe can't send msg trace data"); } } if (null != traceDispatcher) { if (traceDispatcher instanceof AsyncTraceDispatcher) { ((AsyncTraceDispatcher) traceDispatcher).getTraceProducer().setUseTLS(isUseTLS()); } try { traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); } catch (MQClientException e) { logger.warn("trace dispatcher start failed ", e); } } } /** * This method shuts down this producer instance and releases related resources. */ @Override public void shutdown() { this.defaultMQProducerImpl.shutdown(); if (this.produceAccumulator != null) { this.produceAccumulator.shutdown(); } if (null != traceDispatcher) { traceDispatcher.shutdown(); } } /** * Fetch message queues of topic topic, to which we may send/publish messages. * * @param topic Topic to fetch. * @return List of message queues readily to send messages to * @throws MQClientException if there is any client error. */ @Override public List fetchPublishMessageQueues(String topic) throws MQClientException { return this.defaultMQProducerImpl.fetchPublishMessageQueues(withNamespace(topic)); } private boolean canBatch(Message msg) { // produceAccumulator is full if (!produceAccumulator.tryAddMessage(msg)) { return false; } // delay message do not support batch processing if (msg.getDelayTimeLevel() > 0 || msg.getDelayTimeMs() > 0 || msg.getDelayTimeSec() > 0 || msg.getDeliverTimeMs() > 0) { return false; } // retry message do not support batch processing if (msg.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { return false; } // message which have been assigned to producer group do not support batch processing if (msg.getProperties().containsKey(MessageConst.PROPERTY_PRODUCER_GROUP)) { return false; } return true; } /** * Send message in synchronous mode. This method returns only when the sending procedure totally completes.

    * * Warn: this method has internal retry-mechanism, that is, internal implementation will retry * {@link #retryTimesWhenSendFailed} times before claiming failure. As a result, multiple messages may be potentially * delivered to broker(s). It's up to the application developers to resolve potential duplication issue. * * @param msg Message to send. * @return {@link SendResult} instance to inform senders details of the deliverable, say Message ID of the message, * {@link SendStatus} indicating broker storage/replication status, message queue sent to, etc. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any error with broker. * @throws InterruptedException if the sending thread is interrupted. */ @Override public SendResult send( Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); if (this.getAutoBatch() && !(msg instanceof MessageBatch)) { return sendByAccumulator(msg, null, null); } else { return sendDirect(msg, null, null); } } /** * Same to {@link #send(Message)} with send timeout specified in addition. * * @param msg Message to send. * @param timeout send timeout. * @return {@link SendResult} instance to inform senders details of the deliverable, say Message ID of the message, * {@link SendStatus} indicating broker storage/replication status, message queue sent to, etc. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any error with broker. * @throws InterruptedException if the sending thread is interrupted. */ @Override public SendResult send(Message msg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); return this.defaultMQProducerImpl.send(msg, timeout); } /** * Send message to broker asynchronously.

    *

    * This method returns immediately. On sending completion, sendCallback will be executed.

    *

    * Similar to {@link #send(Message)}, internal implementation would potentially retry up to {@link * #retryTimesWhenSendAsyncFailed} times before claiming sending failure, which may yield message duplication and * application developers are the one to resolve this potential issue. * * @param msg Message to send. * @param sendCallback Callback to execute on sending completed, either successful or unsuccessful. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void send(Message msg, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); try { if (this.getAutoBatch() && !(msg instanceof MessageBatch)) { sendByAccumulator(msg, null, sendCallback); } else { sendDirect(msg, null, sendCallback); } } catch (Throwable e) { sendCallback.onException(e); } } /** * Same to {@link #send(Message, SendCallback)} with send timeout specified in addition. * * @param msg message to send. * @param sendCallback Callback to execute. * @param timeout send timeout. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void send(Message msg, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.send(msg, sendCallback, timeout); } /** * Similar to UDP, this method won't wait for * acknowledgement from broker before return. Obviously, it has maximums throughput yet potentials of message loss. * * @param msg Message to send. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void sendOneway(Message msg) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.sendOneway(msg); } /** * Same to {@link #send(Message)} with target message queue specified in addition. * * @param msg Message to send. * @param mq Target message queue. * @return {@link SendResult} instance to inform senders details of the deliverable, say Message ID of the message, * {@link SendStatus} indicating broker storage/replication status, message queue sent to, etc. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any error with broker. * @throws InterruptedException if the sending thread is interrupted. */ @Override public SendResult send(Message msg, MessageQueue mq) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); mq = queueWithNamespace(mq); if (this.getAutoBatch() && !(msg instanceof MessageBatch)) { return sendByAccumulator(msg, mq, null); } else { return sendDirect(msg, mq, null); } } /** * Same to {@link #send(Message)} with target message queue and send timeout specified. * * @param msg Message to send. * @param mq Target message queue. * @param timeout send timeout. * @return {@link SendResult} instance to inform senders details of the deliverable, say Message ID of the message, * {@link SendStatus} indicating broker storage/replication status, message queue sent to, etc. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any error with broker. * @throws InterruptedException if the sending thread is interrupted. */ @Override public SendResult send(Message msg, MessageQueue mq, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); return this.defaultMQProducerImpl.send(msg, queueWithNamespace(mq), timeout); } /** * Same to {@link #send(Message, SendCallback)} with target message queue specified. * * @param msg Message to send. * @param mq Target message queue. * @param sendCallback Callback to execute on sending completed, either successful or unsuccessful. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void send(Message msg, MessageQueue mq, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); mq = queueWithNamespace(mq); try { if (this.getAutoBatch() && !(msg instanceof MessageBatch)) { sendByAccumulator(msg, mq, sendCallback); } else { sendDirect(msg, mq, sendCallback); } } catch (MQBrokerException e) { // ignore } } /** * Same to {@link #send(Message, SendCallback)} with target message queue and send timeout specified. * * @param msg Message to send. * @param mq Target message queue. * @param sendCallback Callback to execute on sending completed, either successful or unsuccessful. * @param timeout Send timeout. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void send(Message msg, MessageQueue mq, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.send(msg, queueWithNamespace(mq), sendCallback, timeout); } /** * Same to {@link #sendOneway(Message)} with target message queue specified. * * @param msg Message to send. * @param mq Target message queue. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void sendOneway(Message msg, MessageQueue mq) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.sendOneway(msg, queueWithNamespace(mq)); } /** * Same to {@link #send(Message)} with message queue selector specified. * * @param msg Message to send. * @param selector Message queue selector, through which we get target message queue to deliver message to. * @param arg Argument to work along with message queue selector. * @return {@link SendResult} instance to inform senders details of the deliverable, say Message ID of the message, * {@link SendStatus} indicating broker storage/replication status, message queue sent to, etc. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any error with broker. * @throws InterruptedException if the sending thread is interrupted. */ @Override public SendResult send(Message msg, MessageQueueSelector selector, Object arg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); MessageQueue mq = this.defaultMQProducerImpl.invokeMessageQueueSelector(msg, selector, arg, this.getSendMsgTimeout()); mq = queueWithNamespace(mq); if (this.getAutoBatch() && !(msg instanceof MessageBatch)) { return sendByAccumulator(msg, mq, null); } else { return sendDirect(msg, mq, null); } } /** * Same to {@link #send(Message, MessageQueueSelector, Object)} with send timeout specified. * * @param msg Message to send. * @param selector Message queue selector, through which we get target message queue to deliver message to. * @param arg Argument to work along with message queue selector. * @param timeout Send timeout. * @return {@link SendResult} instance to inform senders details of the deliverable, say Message ID of the message, * {@link SendStatus} indicating broker storage/replication status, message queue sent to, etc. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any error with broker. * @throws InterruptedException if the sending thread is interrupted. */ @Override public SendResult send(Message msg, MessageQueueSelector selector, Object arg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); return this.defaultMQProducerImpl.send(msg, selector, arg, timeout); } /** * Same to {@link #send(Message, SendCallback)} with message queue selector specified. * * @param msg Message to send. * @param selector Message selector through which to get target message queue. * @param arg Argument used along with message queue selector. * @param sendCallback callback to execute on sending completion. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); try { MessageQueue mq = this.defaultMQProducerImpl.invokeMessageQueueSelector(msg, selector, arg, this.getSendMsgTimeout()); mq = queueWithNamespace(mq); if (this.getAutoBatch() && !(msg instanceof MessageBatch)) { sendByAccumulator(msg, mq, sendCallback); } else { sendDirect(msg, mq, sendCallback); } } catch (Throwable e) { sendCallback.onException(e); } } /** * Same to {@link #send(Message, MessageQueueSelector, Object, SendCallback)} with timeout specified. * * @param msg Message to send. * @param selector Message selector through which to get target message queue. * @param arg Argument used along with message queue selector. * @param sendCallback callback to execute on sending completion. * @param timeout Send timeout. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.send(msg, selector, arg, sendCallback, timeout); } public SendResult sendDirect(Message msg, MessageQueue mq, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { // send in sync mode if (sendCallback == null) { if (mq == null) { return this.defaultMQProducerImpl.send(msg); } else { return this.defaultMQProducerImpl.send(msg, mq); } } else { if (mq == null) { this.defaultMQProducerImpl.send(msg, sendCallback); } else { this.defaultMQProducerImpl.send(msg, mq, sendCallback); } return null; } } public SendResult sendByAccumulator(Message msg, MessageQueue mq, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { // check whether it can batch if (!canBatch(msg)) { return sendDirect(msg, mq, sendCallback); } else { Validators.checkMessage(msg, this); MessageClientIDSetter.setUniqID(msg); if (sendCallback == null) { return this.produceAccumulator.send(msg, mq, this); } else { this.produceAccumulator.send(msg, mq, sendCallback, this); return null; } } } /** * Send request message in synchronous mode. This method returns only when the consumer consume the request message and reply a message.

    * * Warn: this method has internal retry-mechanism, that is, internal implementation will retry * {@link #retryTimesWhenSendFailed} times before claiming failure. As a result, multiple messages may be potentially * delivered to broker(s). It's up to the application developers to resolve potential duplication issue. * * @param msg request message to send * @param timeout request timeout * @return reply message * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any broker error. * @throws InterruptedException if the thread is interrupted. * @throws RequestTimeoutException if request timeout. */ @Override public Message request(final Message msg, final long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); return this.defaultMQProducerImpl.request(msg, timeout); } /** * Request asynchronously.

    * This method returns immediately. On receiving reply message, requestCallback will be executed.

    *

    * Similar to {@link #request(Message, long)}, internal implementation would potentially retry up to {@link * #retryTimesWhenSendAsyncFailed} times before claiming sending failure, which may yield message duplication and * application developers are the one to resolve this potential issue. * * @param msg request message to send * @param requestCallback callback to execute on request completion. * @param timeout request timeout * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the thread is interrupted. * @throws MQBrokerException if there is any broker error. */ @Override public void request(final Message msg, final RequestCallback requestCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.request(msg, requestCallback, timeout); } /** * Same to {@link #request(Message, long)} with message queue selector specified. * * @param msg request message to send * @param selector message queue selector, through which we get target message queue to deliver message to. * @param arg argument to work along with message queue selector. * @param timeout timeout of request. * @return reply message * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any broker error. * @throws InterruptedException if the thread is interrupted. * @throws RequestTimeoutException if request timeout. */ @Override public Message request(final Message msg, final MessageQueueSelector selector, final Object arg, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, RequestTimeoutException { msg.setTopic(withNamespace(msg.getTopic())); return this.defaultMQProducerImpl.request(msg, selector, arg, timeout); } /** * Same to {@link #request(Message, RequestCallback, long)} with target message selector specified. * * @param msg request message to send * @param selector message queue selector, through which we get target message queue to deliver message to. * @param arg argument to work along with message queue selector. * @param requestCallback callback to execute on request completion. * @param timeout timeout of request. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the thread is interrupted. * @throws MQBrokerException if there is any broker error. */ @Override public void request(final Message msg, final MessageQueueSelector selector, final Object arg, final RequestCallback requestCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.request(msg, selector, arg, requestCallback, timeout); } /** * Same to {@link #request(Message, long)} with target message queue specified in addition. * * @param msg request message to send * @param mq target message queue. * @param timeout request timeout * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws MQBrokerException if there is any broker error. * @throws InterruptedException if the thread is interrupted. * @throws RequestTimeoutException if request timeout. */ @Override public Message request(final Message msg, final MessageQueue mq, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, RequestTimeoutException { msg.setTopic(withNamespace(msg.getTopic())); return this.defaultMQProducerImpl.request(msg, mq, timeout); } /** * Same to {@link #request(Message, RequestCallback, long)} with target message queue specified. * * @param msg request message to send * @param mq target message queue. * @param requestCallback callback to execute on request completion. * @param timeout timeout of request. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the thread is interrupted. * @throws MQBrokerException if there is any broker error. */ @Override public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) throws MQClientException, RemotingException, InterruptedException, MQBrokerException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.request(msg, mq, requestCallback, timeout); } /** * Same to {@link #sendOneway(Message)} with message queue selector specified. * * @param msg Message to send. * @param selector Message queue selector, through which to determine target message queue to deliver message * @param arg Argument used along with message queue selector. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Override public void sendOneway(Message msg, MessageQueueSelector selector, Object arg) throws MQClientException, RemotingException, InterruptedException { msg.setTopic(withNamespace(msg.getTopic())); this.defaultMQProducerImpl.sendOneway(msg, selector, arg); } /** * This method is used to send transactional messages. * * @param msg Transactional message to send. * @param arg Argument used along with local transaction executor. * @return Transaction result. * @throws MQClientException */ @Override public TransactionSendResult sendMessageInTransaction(Message msg, Object arg) throws MQClientException { throw new RuntimeException("sendMessageInTransaction not implement, please use TransactionMQProducer class"); } /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * * @param key accessKey * @param newTopic topic name * @param queueNum topic's queue number * @param attributes * @throws MQClientException if there is any client error. */ @Deprecated @Override public void createTopic(String key, String newTopic, int queueNum, Map attributes) throws MQClientException { createTopic(key, withNamespace(newTopic), queueNum, 0, null); } /** * Create a topic on broker. This method will be removed in a certain version after April 5, 2020, so please do not * use this method. * * @param key accessKey * @param newTopic topic name * @param queueNum topic's queue number * @param topicSysFlag topic system flag * @param attributes * @throws MQClientException if there is any client error. */ @Deprecated @Override public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException { this.defaultMQProducerImpl.createTopic(key, withNamespace(newTopic), queueNum, topicSysFlag); } /** * Search consume queue offset of the given time stamp. * * @param mq Instance of MessageQueue * @param timestamp from when in milliseconds. * @return Consume queue offset. * @throws MQClientException if there is any client error. */ @Override public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { return this.defaultMQProducerImpl.searchOffset(queueWithNamespace(mq), timestamp); } /** * Query maximum offset of the given message queue. *

    * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * * @param mq Instance of MessageQueue * @return maximum offset of the given consume queue. * @throws MQClientException if there is any client error. */ @Deprecated @Override public long maxOffset(MessageQueue mq) throws MQClientException { return this.defaultMQProducerImpl.maxOffset(queueWithNamespace(mq)); } /** * Query minimum offset of the given message queue. *

    * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * * @param mq Instance of MessageQueue * @return minimum offset of the given message queue. * @throws MQClientException if there is any client error. */ @Deprecated @Override public long minOffset(MessageQueue mq) throws MQClientException { return this.defaultMQProducerImpl.minOffset(queueWithNamespace(mq)); } /** * Query the earliest message store time. *

    * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * * @param mq Instance of MessageQueue * @return earliest message store time. * @throws MQClientException if there is any client error. */ @Deprecated @Override public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { return this.defaultMQProducerImpl.earliestMsgStoreTime(queueWithNamespace(mq)); } /** * Query message by key. *

    * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * * @param topic message topic * @param key message key index word * @param maxNum max message number * @param begin from when * @param end to when * @return QueryResult instance contains matched messages. * @throws MQClientException if there is any client error. * @throws InterruptedException if the thread is interrupted. */ @Deprecated @Override public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return this.defaultMQProducerImpl.queryMessage(withNamespace(topic), key, maxNum, begin, end); } /** * Query message of the given message ID. *

    * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * * @param topic Topic * @param msgId Message ID * @return Message specified. * @throws MQBrokerException if there is any broker error. * @throws MQClientException if there is any client error. * @throws RemotingException if there is any network-tier error. * @throws InterruptedException if the sending thread is interrupted. */ @Deprecated @Override public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { try { return this.defaultMQProducerImpl.viewMessage(topic, msgId); } catch (Exception ignored) { } return this.defaultMQProducerImpl.queryMessageByUniqKey(withNamespace(topic), msgId); } @Override public SendResult send( Collection msgs) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQProducerImpl.send(batch(msgs)); } @Override public SendResult send(Collection msgs, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQProducerImpl.send(batch(msgs), timeout); } @Override public SendResult send(Collection msgs, MessageQueue messageQueue) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQProducerImpl.send(batch(msgs), messageQueue); } @Override public SendResult send(Collection msgs, MessageQueue messageQueue, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQProducerImpl.send(batch(msgs), messageQueue, timeout); } @Override public void send(Collection msgs, SendCallback sendCallback) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { this.defaultMQProducerImpl.send(batch(msgs), sendCallback); } @Override public void send(Collection msgs, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { this.defaultMQProducerImpl.send(batch(msgs), sendCallback, timeout); } @Override public void send(Collection msgs, MessageQueue mq, SendCallback sendCallback) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { this.defaultMQProducerImpl.send(batch(msgs), queueWithNamespace(mq), sendCallback); } @Override public void send(Collection msgs, MessageQueue mq, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { this.defaultMQProducerImpl.send(batch(msgs), queueWithNamespace(mq), sendCallback, timeout); } @Override public String recallMessage(String topic, String recallHandle) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { return this.defaultMQProducerImpl.recallMessage(withNamespace(topic), recallHandle); } /** * Sets an Executor to be used for executing callback methods. * * @param callbackExecutor the instance of Executor */ public void setCallbackExecutor(final ExecutorService callbackExecutor) { this.defaultMQProducerImpl.setCallbackExecutor(callbackExecutor); } /** * Sets an Executor to be used for executing asynchronous send. * * @param asyncSenderExecutor the instance of Executor */ public void setAsyncSenderExecutor(final ExecutorService asyncSenderExecutor) { this.defaultMQProducerImpl.setAsyncSenderExecutor(asyncSenderExecutor); } /** * Add response code for retrying. * * @param responseCode response code, {@link ResponseCode} */ public void addRetryResponseCode(int responseCode) { this.retryResponseCodes.add(responseCode); } private MessageBatch batch(Collection msgs) throws MQClientException { MessageBatch msgBatch; try { msgBatch = MessageBatch.generateFromList(msgs); for (Message message : msgBatch) { Validators.checkMessage(message, this); MessageClientIDSetter.setUniqID(message); message.setTopic(withNamespace(message.getTopic())); } MessageClientIDSetter.setUniqID(msgBatch); msgBatch.setBody(msgBatch.encode()); } catch (Exception e) { throw new MQClientException("Failed to initiate the MessageBatch", e); } msgBatch.setTopic(withNamespace(msgBatch.getTopic())); return msgBatch; } public int getBatchMaxDelayMs() { if (this.produceAccumulator == null) { return 0; } return produceAccumulator.getBatchMaxDelayMs(); } public void batchMaxDelayMs(int holdMs) { this.batchMaxDelayMs = holdMs; if (this.produceAccumulator != null) { this.produceAccumulator.batchMaxDelayMs(holdMs); } } public long getBatchMaxBytes() { if (this.produceAccumulator == null) { return 0; } return produceAccumulator.getBatchMaxBytes(); } public void batchMaxBytes(long holdSize) { this.batchMaxBytes = holdSize; if (this.produceAccumulator != null) { this.produceAccumulator.batchMaxBytes(holdSize); } } public long getTotalBatchMaxBytes() { if (this.produceAccumulator == null) { return 0; } return produceAccumulator.getTotalBatchMaxBytes(); } public void totalBatchMaxBytes(long totalHoldSize) { this.totalBatchMaxBytes = totalHoldSize; if (this.produceAccumulator != null) { this.produceAccumulator.totalBatchMaxBytes(totalHoldSize); } } public boolean getAutoBatch() { if (this.produceAccumulator == null) { return false; } return this.autoBatch; } public void setAutoBatch(boolean autoBatch) { this.autoBatch = autoBatch; } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public String getCreateTopicKey() { return createTopicKey; } public void setCreateTopicKey(String createTopicKey) { this.createTopicKey = createTopicKey; } public int getSendMsgTimeout() { return sendMsgTimeout; } public void setSendMsgTimeout(int sendMsgTimeout) { this.sendMsgTimeout = sendMsgTimeout; } public int getSendMsgMaxTimeoutPerRequest() { return sendMsgMaxTimeoutPerRequest; } public void setSendMsgMaxTimeoutPerRequest(int sendMsgMaxTimeoutPerRequest) { this.sendMsgMaxTimeoutPerRequest = sendMsgMaxTimeoutPerRequest; } public int getCompressMsgBodyOverHowmuch() { return compressMsgBodyOverHowmuch; } public void setCompressMsgBodyOverHowmuch(int compressMsgBodyOverHowmuch) { this.compressMsgBodyOverHowmuch = compressMsgBodyOverHowmuch; } @Deprecated public DefaultMQProducerImpl getDefaultMQProducerImpl() { return defaultMQProducerImpl; } public boolean isRetryAnotherBrokerWhenNotStoreOK() { return retryAnotherBrokerWhenNotStoreOK; } public void setRetryAnotherBrokerWhenNotStoreOK(boolean retryAnotherBrokerWhenNotStoreOK) { this.retryAnotherBrokerWhenNotStoreOK = retryAnotherBrokerWhenNotStoreOK; } public int getMaxMessageSize() { return maxMessageSize; } public void setMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; } public int getDefaultTopicQueueNums() { return defaultTopicQueueNums; } public void setDefaultTopicQueueNums(int defaultTopicQueueNums) { this.defaultTopicQueueNums = defaultTopicQueueNums; } public int getRetryTimesWhenSendFailed() { return retryTimesWhenSendFailed; } public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed) { this.retryTimesWhenSendFailed = retryTimesWhenSendFailed; } public boolean isSendMessageWithVIPChannel() { return isVipChannelEnabled(); } public void setSendMessageWithVIPChannel(final boolean sendMessageWithVIPChannel) { this.setVipChannelEnabled(sendMessageWithVIPChannel); } public long[] getNotAvailableDuration() { return this.defaultMQProducerImpl.getNotAvailableDuration(); } public void setNotAvailableDuration(final long[] notAvailableDuration) { this.defaultMQProducerImpl.setNotAvailableDuration(notAvailableDuration); } public long[] getLatencyMax() { return this.defaultMQProducerImpl.getLatencyMax(); } public void setLatencyMax(final long[] latencyMax) { this.defaultMQProducerImpl.setLatencyMax(latencyMax); } public boolean isSendLatencyFaultEnable() { return this.defaultMQProducerImpl.isSendLatencyFaultEnable(); } public void setSendLatencyFaultEnable(final boolean sendLatencyFaultEnable) { this.defaultMQProducerImpl.setSendLatencyFaultEnable(sendLatencyFaultEnable); } public int getRetryTimesWhenSendAsyncFailed() { return retryTimesWhenSendAsyncFailed; } public void setRetryTimesWhenSendAsyncFailed(final int retryTimesWhenSendAsyncFailed) { this.retryTimesWhenSendAsyncFailed = retryTimesWhenSendAsyncFailed; } public TraceDispatcher getTraceDispatcher() { return traceDispatcher; } public Set getRetryResponseCodes() { return retryResponseCodes; } public boolean isEnableBackpressureForAsyncMode() { return enableBackpressureForAsyncMode; } public void setEnableBackpressureForAsyncMode(boolean enableBackpressureForAsyncMode) { this.enableBackpressureForAsyncMode = enableBackpressureForAsyncMode; } public int getBackPressureForAsyncSendNum() { return backPressureForAsyncSendNum; } /** * For user modify backPressureForAsyncSendNum at runtime */ public void setBackPressureForAsyncSendNum(int backPressureForAsyncSendNum) { this.backPressureForAsyncSendNumLock.acquireWriteLock(); backPressureForAsyncSendNum = Math.max(backPressureForAsyncSendNum, 10); int acquiredBackPressureForAsyncSendNum = this.backPressureForAsyncSendNum - defaultMQProducerImpl.getSemaphoreAsyncSendNumAvailablePermits(); this.backPressureForAsyncSendNum = backPressureForAsyncSendNum; defaultMQProducerImpl.setSemaphoreAsyncSendNum(backPressureForAsyncSendNum - acquiredBackPressureForAsyncSendNum); this.backPressureForAsyncSendNumLock.releaseWriteLock(); } public int getBackPressureForAsyncSendSize() { return backPressureForAsyncSendSize; } /** * For user modify backPressureForAsyncSendSize at runtime */ public void setBackPressureForAsyncSendSize(int backPressureForAsyncSendSize) { this.backPressureForAsyncSendSizeLock.acquireWriteLock(); backPressureForAsyncSendSize = Math.max(backPressureForAsyncSendSize, 1024 * 1024); int acquiredBackPressureForAsyncSendSize = this.backPressureForAsyncSendSize - defaultMQProducerImpl.getSemaphoreAsyncSendSizeAvailablePermits(); this.backPressureForAsyncSendSize = backPressureForAsyncSendSize; defaultMQProducerImpl.setSemaphoreAsyncSendSize(backPressureForAsyncSendSize - acquiredBackPressureForAsyncSendSize); this.backPressureForAsyncSendSizeLock.releaseWriteLock(); } /** * Used for system internal adjust backPressureForAsyncSendSize */ public void setBackPressureForAsyncSendSizeInsideAdjust(int backPressureForAsyncSendSize) { this.backPressureForAsyncSendSize = backPressureForAsyncSendSize; } /** * Used for system internal adjust backPressureForAsyncSendNum */ public void setBackPressureForAsyncSendNumInsideAdjust(int backPressureForAsyncSendNum) { this.backPressureForAsyncSendNum = backPressureForAsyncSendNum; } public void acquireBackPressureForAsyncSendSizeLock() { this.backPressureForAsyncSendSizeLock.acquireReadLock(); } public void releaseBackPressureForAsyncSendSizeLock() { this.backPressureForAsyncSendSizeLock.releaseReadLock(); } public void acquireBackPressureForAsyncSendNumLock() { this.backPressureForAsyncSendNumLock.acquireReadLock(); } public void releaseBackPressureForAsyncSendNumLock() { this.backPressureForAsyncSendNumLock.releaseReadLock(); } public List getTopics() { return topics; } public void setTopics(List topics) { this.topics = topics; } @Override public void setStartDetectorEnable(boolean startDetectorEnable) { super.setStartDetectorEnable(startDetectorEnable); this.defaultMQProducerImpl.getMqFaultStrategy().setStartDetectorEnable(startDetectorEnable); } public int getCompressLevel() { return compressLevel; } public void setCompressLevel(int compressLevel) { this.compressLevel = compressLevel; } public CompressionType getCompressType() { return compressType; } public void setCompressType(CompressionType compressType) { this.compressType = compressType; this.compressor = CompressorFactory.getCompressor(compressType); } public Compressor getCompressor() { return compressor; } public void initProduceAccumulator() { this.produceAccumulator = MQClientManager.getInstance().getOrCreateProduceAccumulator(this); if (this.batchMaxDelayMs > -1) { this.produceAccumulator.batchMaxDelayMs(this.batchMaxDelayMs); } if (this.batchMaxBytes > -1) { this.produceAccumulator.batchMaxBytes(this.batchMaxBytes); } if (this.totalBatchMaxBytes > -1) { this.produceAccumulator.totalBatchMaxBytes(this.totalBatchMaxBytes); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/LocalTransactionState.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; public enum LocalTransactionState { COMMIT_MESSAGE, ROLLBACK_MESSAGE, UNKNOW, } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/MQProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.Collection; import java.util.List; import org.apache.rocketmq.client.MQAdmin; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; public interface MQProducer extends MQAdmin { void start() throws MQClientException; void shutdown(); List fetchPublishMessageQueues(final String topic) throws MQClientException; SendResult send(final Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; SendResult send(final Message msg, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Message msg, final SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException, MQBrokerException; void send(final Message msg, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException; void sendOneway(final Message msg) throws MQClientException, RemotingException, InterruptedException; SendResult send(final Message msg, final MessageQueue mq) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; SendResult send(final Message msg, final MessageQueue mq, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException; void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, InterruptedException; void sendOneway(final Message msg, final MessageQueue mq) throws MQClientException, RemotingException, InterruptedException; SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Message msg, final MessageQueueSelector selector, final Object arg, final SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException; void send(final Message msg, final MessageQueueSelector selector, final Object arg, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException; void sendOneway(final Message msg, final MessageQueueSelector selector, final Object arg) throws MQClientException, RemotingException, InterruptedException; TransactionSendResult sendMessageInTransaction(final Message msg, final Object arg) throws MQClientException; //for batch SendResult send(final Collection msgs) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; SendResult send(final Collection msgs, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; SendResult send(final Collection msgs, final MessageQueue mq) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; SendResult send(final Collection msgs, final MessageQueue mq, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Collection msgs, final SendCallback sendCallback) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Collection msgs, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Collection msgs, final MessageQueue mq, final SendCallback sendCallback) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; void send(final Collection msgs, final MessageQueue mq, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; String recallMessage(String topic, String recallHandle) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; //for rpc Message request(final Message msg, final long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException; void request(final Message msg, final RequestCallback requestCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException, MQBrokerException; Message request(final Message msg, final MessageQueueSelector selector, final Object arg, final long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException; void request(final Message msg, final MessageQueueSelector selector, final Object arg, final RequestCallback requestCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException, MQBrokerException; Message request(final Message msg, final MessageQueue mq, final long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException; void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/MessageQueueSelector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.List; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; public interface MessageQueueSelector { MessageQueue select(final List mqs, final Message msg, final Object arg); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/ProduceAccumulator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingException; public class ProduceAccumulator { // totalHoldSize normal value private long totalHoldSize = 32 * 1024 * 1024; // holdSize normal value private long holdSize = 32 * 1024; // holdMs normal value private int holdMs = 10; private final Logger log = LoggerFactory.getLogger(DefaultMQProducer.class); private final GuardForSyncSendService guardThreadForSyncSend; private final GuardForAsyncSendService guardThreadForAsyncSend; private final Map syncSendBatchs = new ConcurrentHashMap(); private final Map asyncSendBatchs = new ConcurrentHashMap(); private final AtomicLong currentlyHoldSize = new AtomicLong(0); private final String instanceName; public ProduceAccumulator(String instanceName) { this.instanceName = instanceName; this.guardThreadForSyncSend = new GuardForSyncSendService(this.instanceName); this.guardThreadForAsyncSend = new GuardForAsyncSendService(this.instanceName); } private class GuardForSyncSendService extends ServiceThread { private final String serviceName; public GuardForSyncSendService(String clientInstanceName) { serviceName = String.format("Client_%s_GuardForSyncSend", clientInstanceName); } @Override public String getServiceName() { return serviceName; } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.doWork(); } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info(this.getServiceName() + " service end"); } private void doWork() throws InterruptedException { Collection values = syncSendBatchs.values(); final int sleepTime = Math.max(1, holdMs / 2); for (MessageAccumulation v : values) { v.wakeup(); synchronized (v) { synchronized (v.closed) { if (v.messagesSize.get() == 0) { v.closed.set(true); syncSendBatchs.remove(v.aggregateKey, v); } else { v.notify(); } } } } Thread.sleep(sleepTime); } } private class GuardForAsyncSendService extends ServiceThread { private final String serviceName; public GuardForAsyncSendService(String clientInstanceName) { serviceName = String.format("Client_%s_GuardForAsyncSend", clientInstanceName); } @Override public String getServiceName() { return serviceName; } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.doWork(); } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info(this.getServiceName() + " service end"); } private void doWork() throws Exception { Collection values = asyncSendBatchs.values(); final int sleepTime = Math.max(1, holdMs / 2); for (MessageAccumulation v : values) { if (v.readyToSend()) { v.send(null); } synchronized (v.closed) { if (v.messagesSize.get() == 0) { v.closed.set(true); asyncSendBatchs.remove(v.aggregateKey, v); } } } Thread.sleep(sleepTime); } } void start() { guardThreadForSyncSend.start(); guardThreadForAsyncSend.start(); } void shutdown() { guardThreadForSyncSend.shutdown(); guardThreadForAsyncSend.shutdown(); } int getBatchMaxDelayMs() { return holdMs; } void batchMaxDelayMs(int holdMs) { if (holdMs <= 0 || holdMs > 30 * 1000) { throw new IllegalArgumentException(String.format("batchMaxDelayMs expect between 1ms and 30s, but get %d!", holdMs)); } this.holdMs = holdMs; } long getBatchMaxBytes() { return holdSize; } void batchMaxBytes(long holdSize) { if (holdSize <= 0 || holdSize > 2 * 1024 * 1024) { throw new IllegalArgumentException(String.format("batchMaxBytes expect between 1B and 2MB, but get %d!", holdSize)); } this.holdSize = holdSize; } long getTotalBatchMaxBytes() { return holdSize; } void totalBatchMaxBytes(long totalHoldSize) { if (totalHoldSize <= 0) { throw new IllegalArgumentException(String.format("totalBatchMaxBytes must bigger then 0, but get %d!", totalHoldSize)); } this.totalHoldSize = totalHoldSize; } private MessageAccumulation getOrCreateSyncSendBatch(AggregateKey aggregateKey, DefaultMQProducer defaultMQProducer) { MessageAccumulation batch = syncSendBatchs.get(aggregateKey); if (batch != null) { return batch; } batch = new MessageAccumulation(aggregateKey, defaultMQProducer); MessageAccumulation previous = syncSendBatchs.putIfAbsent(aggregateKey, batch); return previous == null ? batch : previous; } private MessageAccumulation getOrCreateAsyncSendBatch(AggregateKey aggregateKey, DefaultMQProducer defaultMQProducer) { MessageAccumulation batch = asyncSendBatchs.get(aggregateKey); if (batch != null) { return batch; } batch = new MessageAccumulation(aggregateKey, defaultMQProducer); MessageAccumulation previous = asyncSendBatchs.putIfAbsent(aggregateKey, batch); return previous == null ? batch : previous; } SendResult send(Message msg, DefaultMQProducer defaultMQProducer) throws InterruptedException, MQBrokerException, RemotingException, MQClientException { AggregateKey partitionKey = new AggregateKey(msg); while (true) { MessageAccumulation batch = getOrCreateSyncSendBatch(partitionKey, defaultMQProducer); int index = batch.add(msg); if (index == -1) { syncSendBatchs.remove(partitionKey, batch); } else { return batch.sendResults[index]; } } } SendResult send(Message msg, MessageQueue mq, DefaultMQProducer defaultMQProducer) throws InterruptedException, MQBrokerException, RemotingException, MQClientException { AggregateKey partitionKey = new AggregateKey(msg, mq); while (true) { MessageAccumulation batch = getOrCreateSyncSendBatch(partitionKey, defaultMQProducer); int index = batch.add(msg); if (index == -1) { syncSendBatchs.remove(partitionKey, batch); } else { return batch.sendResults[index]; } } } void send(Message msg, SendCallback sendCallback, DefaultMQProducer defaultMQProducer) throws InterruptedException, RemotingException, MQClientException { AggregateKey partitionKey = new AggregateKey(msg); while (true) { MessageAccumulation batch = getOrCreateAsyncSendBatch(partitionKey, defaultMQProducer); if (!batch.add(msg, sendCallback)) { asyncSendBatchs.remove(partitionKey, batch); } else { return; } } } void send(Message msg, MessageQueue mq, SendCallback sendCallback, DefaultMQProducer defaultMQProducer) throws InterruptedException, RemotingException, MQClientException { AggregateKey partitionKey = new AggregateKey(msg, mq); while (true) { MessageAccumulation batch = getOrCreateAsyncSendBatch(partitionKey, defaultMQProducer); if (!batch.add(msg, sendCallback)) { asyncSendBatchs.remove(partitionKey, batch); } else { return; } } } boolean tryAddMessage(Message message) { synchronized (currentlyHoldSize) { if (currentlyHoldSize.get() < totalHoldSize) { int bodySize = null == message.getBody() ? 0 : message.getBody().length; if (bodySize > 0) { currentlyHoldSize.addAndGet(bodySize); } return true; } else { return false; } } } private class AggregateKey { public String topic = null; public MessageQueue mq = null; public boolean waitStoreMsgOK = false; public String tag = null; public AggregateKey(Message message) { this(message.getTopic(), null, message.isWaitStoreMsgOK(), message.getTags()); } public AggregateKey(Message message, MessageQueue mq) { this(message.getTopic(), mq, message.isWaitStoreMsgOK(), message.getTags()); } public AggregateKey(String topic, MessageQueue mq, boolean waitStoreMsgOK, String tag) { this.topic = topic; this.mq = mq; this.waitStoreMsgOK = waitStoreMsgOK; this.tag = tag; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AggregateKey key = (AggregateKey) o; return waitStoreMsgOK == key.waitStoreMsgOK && topic.equals(key.topic) && Objects.equals(mq, key.mq) && Objects.equals(tag, key.tag); } @Override public int hashCode() { return Objects.hash(topic, mq, waitStoreMsgOK, tag); } } private class MessageAccumulation { private final DefaultMQProducer defaultMQProducer; private LinkedList messages; private LinkedList sendCallbacks; private Set keys; private final AtomicBoolean closed; private SendResult[] sendResults; private AggregateKey aggregateKey; private AtomicInteger messagesSize; private int count; private long createTime; public MessageAccumulation(AggregateKey aggregateKey, DefaultMQProducer defaultMQProducer) { this.defaultMQProducer = defaultMQProducer; this.messages = new LinkedList(); this.sendCallbacks = new LinkedList(); this.keys = new HashSet(); this.closed = new AtomicBoolean(false); this.messagesSize = new AtomicInteger(0); this.aggregateKey = aggregateKey; this.count = 0; this.createTime = System.currentTimeMillis(); } private boolean readyToSend() { if (this.messagesSize.get() > holdSize || System.currentTimeMillis() >= this.createTime + holdMs) { return true; } return false; } public int add(Message msg) throws InterruptedException, MQBrokerException, RemotingException, MQClientException { int ret = -1; synchronized (this.closed) { if (this.closed.get()) { return ret; } ret = this.count++; this.messages.add(msg); int bodySize = null == msg.getBody() ? 0 : msg.getBody().length; if (bodySize > 0) { messagesSize.addAndGet(bodySize); } String msgKeys = msg.getKeys(); if (msgKeys != null) { this.keys.addAll(Arrays.asList(msgKeys.split(MessageConst.KEY_SEPARATOR))); } } synchronized (this) { while (!this.closed.get()) { if (readyToSend()) { this.send(); break; } else { this.wait(); } } return ret; } } public boolean add(Message msg, SendCallback sendCallback) throws InterruptedException, RemotingException, MQClientException { synchronized (this.closed) { if (this.closed.get()) { return false; } this.count++; this.messages.add(msg); this.sendCallbacks.add(sendCallback); int bodySize = null == msg.getBody() ? 0 : msg.getBody().length; if (bodySize > 0) { messagesSize.addAndGet(bodySize); } } if (readyToSend()) { this.send(sendCallback); } return true; } public synchronized void wakeup() { if (this.closed.get()) { return; } this.notify(); } private MessageBatch batch() { MessageBatch messageBatch = new MessageBatch(this.messages); messageBatch.setTopic(this.aggregateKey.topic); messageBatch.setWaitStoreMsgOK(this.aggregateKey.waitStoreMsgOK); messageBatch.setKeys(this.keys); messageBatch.setTags(this.aggregateKey.tag); MessageClientIDSetter.setUniqID(messageBatch); messageBatch.setBody(MessageDecoder.encodeMessages(this.messages)); return messageBatch; } private void splitSendResults(SendResult sendResult) { if (sendResult == null) { throw new IllegalArgumentException("sendResult is null"); } boolean isBatchConsumerQueue = !sendResult.getMsgId().contains(","); this.sendResults = new SendResult[this.count]; if (!isBatchConsumerQueue) { String[] msgIds = sendResult.getMsgId().split(","); String[] offsetMsgIds = sendResult.getOffsetMsgId().split(","); if (offsetMsgIds.length != this.count || msgIds.length != this.count) { throw new IllegalArgumentException("sendResult is illegal"); } for (int i = 0; i < this.count; i++) { this.sendResults[i] = new SendResult(sendResult.getSendStatus(), msgIds[i], sendResult.getMessageQueue(), sendResult.getQueueOffset() + i, sendResult.getTransactionId(), offsetMsgIds[i], sendResult.getRegionId()); } } else { for (int i = 0; i < this.count; i++) { this.sendResults[i] = sendResult; } } } private void send() throws InterruptedException, MQClientException, MQBrokerException, RemotingException { synchronized (this.closed) { if (this.closed.getAndSet(true)) { return; } } MessageBatch messageBatch = this.batch(); SendResult sendResult = null; try { if (defaultMQProducer != null) { sendResult = defaultMQProducer.sendDirect(messageBatch, aggregateKey.mq, null); this.splitSendResults(sendResult); } else { throw new IllegalArgumentException("defaultMQProducer is null, can not send message"); } } finally { currentlyHoldSize.addAndGet(-messagesSize.get()); this.notifyAll(); } } private void send(SendCallback sendCallback) { synchronized (this.closed) { if (this.closed.getAndSet(true)) { return; } } MessageBatch messageBatch = this.batch(); SendResult sendResult = null; try { if (defaultMQProducer != null) { final int size = messagesSize.get(); defaultMQProducer.sendDirect(messageBatch, aggregateKey.mq, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { try { splitSendResults(sendResult); int i = 0; Iterator it = sendCallbacks.iterator(); while (it.hasNext()) { SendCallback v = it.next(); v.onSuccess(sendResults[i++]); } if (i != count) { throw new IllegalArgumentException("sendResult is illegal"); } currentlyHoldSize.addAndGet(-size); } catch (Exception e) { onException(e); } } @Override public void onException(Throwable e) { for (SendCallback v : sendCallbacks) { v.onException(e); } currentlyHoldSize.addAndGet(-size); } }); } else { throw new IllegalArgumentException("defaultMQProducer is null, can not send message"); } } catch (Exception e) { for (SendCallback v : sendCallbacks) { v.onException(e); } } } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/RequestCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import org.apache.rocketmq.common.message.Message; public interface RequestCallback { void onSuccess(final Message message); void onException(final Throwable e); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/RequestFutureHolder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.common.ClientErrorCode; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class RequestFutureHolder { private static final Logger log = LoggerFactory.getLogger(RequestFutureHolder.class); private static final RequestFutureHolder INSTANCE = new RequestFutureHolder(); private ConcurrentHashMap requestFutureTable = new ConcurrentHashMap<>(); private final Set producerSet = new HashSet<>(); private ScheduledExecutorService scheduledExecutorService = null; public ConcurrentHashMap getRequestFutureTable() { return requestFutureTable; } private void scanExpiredRequest() { final List rfList = new LinkedList<>(); Iterator> it = requestFutureTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); RequestResponseFuture rep = next.getValue(); if (rep.isTimeout()) { it.remove(); rfList.add(rep); log.warn("remove timeout request, CorrelationId={}" + rep.getCorrelationId()); } } for (RequestResponseFuture rf : rfList) { try { Throwable cause = new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION, "request timeout, no reply message."); rf.setCause(cause); rf.executeRequestCallback(); } catch (Throwable e) { log.warn("scanResponseTable, operationComplete Exception", e); } } } public synchronized void startScheduledTask(DefaultMQProducerImpl producer) { this.producerSet.add(producer); if (null == scheduledExecutorService) { this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RequestHouseKeepingService")); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { RequestFutureHolder.getInstance().scanExpiredRequest(); } catch (Throwable e) { log.error("scan RequestFutureTable exception", e); } } }, 1000 * 3, 1000, TimeUnit.MILLISECONDS); } } public synchronized void shutdown(DefaultMQProducerImpl producer) { this.producerSet.remove(producer); if (this.producerSet.size() <= 0 && null != this.scheduledExecutorService) { ScheduledExecutorService executorService = this.scheduledExecutorService; this.scheduledExecutorService = null; executorService.shutdown(); } } private RequestFutureHolder() {} public static RequestFutureHolder getInstance() { return INSTANCE; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/RequestResponseFuture.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.message.Message; public class RequestResponseFuture { private final String correlationId; private final RequestCallback requestCallback; private final long beginTimestamp = System.currentTimeMillis(); private final Message requestMsg = null; private long timeoutMillis; private CountDownLatch countDownLatch = new CountDownLatch(1); private volatile Message responseMsg = null; private volatile boolean sendRequestOk = true; private volatile Throwable cause = null; public RequestResponseFuture(String correlationId, long timeoutMillis, RequestCallback requestCallback) { this.correlationId = correlationId; this.timeoutMillis = timeoutMillis; this.requestCallback = requestCallback; } public void executeRequestCallback() { if (requestCallback != null) { if (sendRequestOk && cause == null) { requestCallback.onSuccess(responseMsg); } else { requestCallback.onException(cause); } } } public boolean isTimeout() { long diff = System.currentTimeMillis() - this.beginTimestamp; return diff > this.timeoutMillis; } public Message waitResponseMessage(final long timeout) throws InterruptedException { this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS); return this.responseMsg; } public void putResponseMessage(final Message responseMsg) { this.responseMsg = responseMsg; this.countDownLatch.countDown(); } public String getCorrelationId() { return correlationId; } public long getTimeoutMillis() { return timeoutMillis; } public void setTimeoutMillis(long timeoutMillis) { this.timeoutMillis = timeoutMillis; } public RequestCallback getRequestCallback() { return requestCallback; } public long getBeginTimestamp() { return beginTimestamp; } public CountDownLatch getCountDownLatch() { return countDownLatch; } public void setCountDownLatch(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } public Message getResponseMsg() { return responseMsg; } public void setResponseMsg(Message responseMsg) { this.responseMsg = responseMsg; } public boolean isSendRequestOk() { return sendRequestOk; } public void setSendRequestOk(boolean sendRequestOk) { this.sendRequestOk = sendRequestOk; } public Message getRequestMsg() { return requestMsg; } public Throwable getCause() { return cause; } public void setCause(Throwable cause) { this.cause = cause; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/SendCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; public interface SendCallback { void onSuccess(final SendResult sendResult); void onException(final Throwable e); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/SendResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.common.message.MessageQueue; public class SendResult { private SendStatus sendStatus; private String msgId; private MessageQueue messageQueue; private long queueOffset; private String transactionId; private String offsetMsgId; private String regionId; private boolean traceOn = true; private byte[] rawRespBody; private String recallHandle; public SendResult() { } public SendResult(SendStatus sendStatus, String msgId, String offsetMsgId, MessageQueue messageQueue, long queueOffset) { this.sendStatus = sendStatus; this.msgId = msgId; this.offsetMsgId = offsetMsgId; this.messageQueue = messageQueue; this.queueOffset = queueOffset; } public SendResult(final SendStatus sendStatus, final String msgId, final MessageQueue messageQueue, final long queueOffset, final String transactionId, final String offsetMsgId, final String regionId) { this.sendStatus = sendStatus; this.msgId = msgId; this.messageQueue = messageQueue; this.queueOffset = queueOffset; this.transactionId = transactionId; this.offsetMsgId = offsetMsgId; this.regionId = regionId; } public static String encoderSendResultToJson(final Object obj) { return JSON.toJSONString(obj); } public static SendResult decoderSendResultFromJson(String json) { return JSON.parseObject(json, SendResult.class); } public boolean isTraceOn() { return traceOn; } public void setTraceOn(final boolean traceOn) { this.traceOn = traceOn; } public String getRegionId() { return regionId; } public void setRegionId(final String regionId) { this.regionId = regionId; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public SendStatus getSendStatus() { return sendStatus; } public void setSendStatus(SendStatus sendStatus) { this.sendStatus = sendStatus; } public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public long getQueueOffset() { return queueOffset; } public void setQueueOffset(long queueOffset) { this.queueOffset = queueOffset; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } public String getOffsetMsgId() { return offsetMsgId; } public void setOffsetMsgId(String offsetMsgId) { this.offsetMsgId = offsetMsgId; } public String getRecallHandle() { return recallHandle; } public void setRecallHandle(String recallHandle) { this.recallHandle = recallHandle; } @Override public String toString() { return "SendResult [sendStatus=" + sendStatus + ", msgId=" + msgId + ", offsetMsgId=" + offsetMsgId + ", messageQueue=" + messageQueue + ", queueOffset=" + queueOffset + ", recallHandle=" + recallHandle + "]"; } public void setRawRespBody(byte[] body) { this.rawRespBody = body; } public byte[] getRawRespBody() { return rawRespBody; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/SendStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; public enum SendStatus { SEND_OK, FLUSH_DISK_TIMEOUT, FLUSH_SLAVE_TIMEOUT, SLAVE_NOT_AVAILABLE, } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/TransactionCheckListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import org.apache.rocketmq.common.message.MessageExt; /** * @deprecated This interface will be removed in the version 5.0.0, interface {@link TransactionListener} is recommended. */ @Deprecated public interface TransactionCheckListener { LocalTransactionState checkLocalTransactionState(final MessageExt msg); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/TransactionListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; public interface TransactionListener { /** * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction. * * @param msg Half(prepare) message * @param arg Custom business parameter * @return Transaction state */ LocalTransactionState executeLocalTransaction(final Message msg, final Object arg); /** * When no response to prepare(half) message. broker will send check message to check the transaction status, and this * method will be invoked to get local transaction status. * * @param msg Check message * @return Transaction state */ LocalTransactionState checkLocalTransaction(final MessageExt msg); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/TransactionMQProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.List; import java.util.concurrent.ExecutorService; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class TransactionMQProducer extends DefaultMQProducer { private TransactionCheckListener transactionCheckListener; private int checkThreadPoolMinSize = 1; private int checkThreadPoolMaxSize = 1; private int checkRequestHoldMax = 2000; private ExecutorService executorService; private TransactionListener transactionListener; public TransactionMQProducer() { } public TransactionMQProducer(final String producerGroup) { super(producerGroup); } public TransactionMQProducer(final String producerGroup, final List topics) { super(producerGroup, null, topics); } public TransactionMQProducer(final String producerGroup, RPCHook rpcHook) { super(producerGroup, rpcHook, null); } public TransactionMQProducer(final String producerGroup, RPCHook rpcHook, final List topics) { super(producerGroup, rpcHook, topics); } public TransactionMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace, final String customizedTraceTopic) { super(producerGroup, rpcHook, enableMsgTrace, customizedTraceTopic); } @Deprecated public TransactionMQProducer(final String namespace, final String producerGroup) { super(namespace, producerGroup); } @Deprecated public TransactionMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace, final String customizedTraceTopic) { super(namespace, producerGroup, rpcHook, enableMsgTrace, customizedTraceTopic); } @Override public void start() throws MQClientException { this.defaultMQProducerImpl.initTransactionEnv(); super.start(); } @Override public void shutdown() { super.shutdown(); this.defaultMQProducerImpl.destroyTransactionEnv(); } @Override public TransactionSendResult sendMessageInTransaction(final Message msg, final Object arg) throws MQClientException { if (null == this.transactionListener) { throw new MQClientException("TransactionListener is null", null); } msg.setTopic(NamespaceUtil.wrapNamespace(this.getNamespace(), msg.getTopic())); return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg); } public TransactionCheckListener getTransactionCheckListener() { return transactionCheckListener; } /** * This method will be removed in the version 5.0.0 and set a custom thread pool is recommended. */ @Deprecated public void setTransactionCheckListener(TransactionCheckListener transactionCheckListener) { this.transactionCheckListener = transactionCheckListener; } public int getCheckThreadPoolMinSize() { return checkThreadPoolMinSize; } /** * This method will be removed in the version 5.0.0 and set a custom thread pool is recommended. */ @Deprecated public void setCheckThreadPoolMinSize(int checkThreadPoolMinSize) { this.checkThreadPoolMinSize = checkThreadPoolMinSize; } public int getCheckThreadPoolMaxSize() { return checkThreadPoolMaxSize; } /** * This method will be removed in the version 5.0.0 and set a custom thread pool is recommended. */ @Deprecated public void setCheckThreadPoolMaxSize(int checkThreadPoolMaxSize) { this.checkThreadPoolMaxSize = checkThreadPoolMaxSize; } public int getCheckRequestHoldMax() { return checkRequestHoldMax; } /** * This method will be removed in the version 5.0.0 and set a custom thread pool is recommended. */ @Deprecated public void setCheckRequestHoldMax(int checkRequestHoldMax) { this.checkRequestHoldMax = checkRequestHoldMax; } public ExecutorService getExecutorService() { return executorService; } public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } public TransactionListener getTransactionListener() { return transactionListener; } public void setTransactionListener(TransactionListener transactionListener) { this.transactionListener = transactionListener; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/TransactionSendResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; public class TransactionSendResult extends SendResult { private LocalTransactionState localTransactionState; public TransactionSendResult() { } public LocalTransactionState getLocalTransactionState() { return localTransactionState; } public void setLocalTransactionState(LocalTransactionState localTransactionState) { this.localTransactionState = localTransactionState; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByHash.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import java.util.List; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; public class SelectMessageQueueByHash implements MessageQueueSelector { @Override public MessageQueue select(List mqs, Message msg, Object arg) { int value = arg.hashCode() % mqs.size(); if (value < 0) { value = Math.abs(value); } return mqs.get(value); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByMachineRoom.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import java.util.List; import java.util.Set; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; public class SelectMessageQueueByMachineRoom implements MessageQueueSelector { private Set consumeridcs; @Override public MessageQueue select(List mqs, Message msg, Object arg) { return null; } public Set getConsumeridcs() { return consumeridcs; } public void setConsumeridcs(Set consumeridcs) { this.consumeridcs = consumeridcs; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByRandom.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import java.util.List; import java.util.Random; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; public class SelectMessageQueueByRandom implements MessageQueueSelector { private Random random = new Random(System.currentTimeMillis()); @Override public MessageQueue select(List mqs, Message msg, Object arg) { int value = random.nextInt(mqs.size()); return mqs.get(value); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/rpchook/NamespaceRpcHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.rpchook; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class NamespaceRpcHook implements RPCHook { private final ClientConfig clientConfig; public NamespaceRpcHook(ClientConfig clientConfig) { this.clientConfig = clientConfig; } @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { if (StringUtils.isNotEmpty(clientConfig.getNamespaceV2())) { request.addExtField(MixAll.RPC_REQUEST_HEADER_NAMESPACED_FIELD, "true"); request.addExtField(MixAll.RPC_REQUEST_HEADER_NAMESPACE_FIELD, clientConfig.getNamespaceV2()); } } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/stat/ConsumerStatsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.stat; import java.util.concurrent.ScheduledExecutorService; import org.apache.rocketmq.common.stats.StatsItemSet; import org.apache.rocketmq.common.stats.StatsSnapshot; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatus; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ConsumerStatsManager { private static final Logger log = LoggerFactory.getLogger(ConsumerStatsManager.class); private static final String TOPIC_AND_GROUP_CONSUME_OK_TPS = "CONSUME_OK_TPS"; private static final String TOPIC_AND_GROUP_CONSUME_FAILED_TPS = "CONSUME_FAILED_TPS"; private static final String TOPIC_AND_GROUP_CONSUME_RT = "CONSUME_RT"; private static final String TOPIC_AND_GROUP_PULL_TPS = "PULL_TPS"; private static final String TOPIC_AND_GROUP_PULL_RT = "PULL_RT"; private final StatsItemSet topicAndGroupConsumeOKTPS; private final StatsItemSet topicAndGroupConsumeRT; private final StatsItemSet topicAndGroupConsumeFailedTPS; private final StatsItemSet topicAndGroupPullTPS; private final StatsItemSet topicAndGroupPullRT; public ConsumerStatsManager(final ScheduledExecutorService scheduledExecutorService) { this.topicAndGroupConsumeOKTPS = new StatsItemSet(TOPIC_AND_GROUP_CONSUME_OK_TPS, scheduledExecutorService, log); this.topicAndGroupConsumeRT = new StatsItemSet(TOPIC_AND_GROUP_CONSUME_RT, scheduledExecutorService, log); this.topicAndGroupConsumeFailedTPS = new StatsItemSet(TOPIC_AND_GROUP_CONSUME_FAILED_TPS, scheduledExecutorService, log); this.topicAndGroupPullTPS = new StatsItemSet(TOPIC_AND_GROUP_PULL_TPS, scheduledExecutorService, log); this.topicAndGroupPullRT = new StatsItemSet(TOPIC_AND_GROUP_PULL_RT, scheduledExecutorService, log); } public void start() { } public void shutdown() { } public void incPullRT(final String group, final String topic, final long rt) { this.topicAndGroupPullRT.addRTValue(topic + "@" + group, (int) rt, 1); } public void incPullTPS(final String group, final String topic, final long msgs) { this.topicAndGroupPullTPS.addValue(topic + "@" + group, (int) msgs, 1); } public void incConsumeRT(final String group, final String topic, final long rt) { this.topicAndGroupConsumeRT.addRTValue(topic + "@" + group, (int) rt, 1); } public void incConsumeOKTPS(final String group, final String topic, final long msgs) { this.topicAndGroupConsumeOKTPS.addValue(topic + "@" + group, (int) msgs, 1); } public void incConsumeFailedTPS(final String group, final String topic, final long msgs) { this.topicAndGroupConsumeFailedTPS.addValue(topic + "@" + group, (int) msgs, 1); } public ConsumeStatus consumeStatus(final String group, final String topic) { ConsumeStatus cs = new ConsumeStatus(); { StatsSnapshot ss = this.getPullRT(group, topic); if (ss != null) { cs.setPullRT(ss.getAvgpt()); } } { StatsSnapshot ss = this.getPullTPS(group, topic); if (ss != null) { cs.setPullTPS(ss.getTps()); } } { StatsSnapshot ss = this.getConsumeRT(group, topic); if (ss != null) { cs.setConsumeRT(ss.getAvgpt()); } } { StatsSnapshot ss = this.getConsumeOKTPS(group, topic); if (ss != null) { cs.setConsumeOKTPS(ss.getTps()); } } { StatsSnapshot ss = this.getConsumeFailedTPS(group, topic); if (ss != null) { cs.setConsumeFailedTPS(ss.getTps()); } } { StatsSnapshot ss = this.topicAndGroupConsumeFailedTPS.getStatsDataInHour(topic + "@" + group); if (ss != null) { cs.setConsumeFailedMsgs(ss.getSum()); } } return cs; } private StatsSnapshot getPullRT(final String group, final String topic) { return this.topicAndGroupPullRT.getStatsDataInMinute(topic + "@" + group); } private StatsSnapshot getPullTPS(final String group, final String topic) { return this.topicAndGroupPullTPS.getStatsDataInMinute(topic + "@" + group); } private StatsSnapshot getConsumeRT(final String group, final String topic) { StatsSnapshot statsData = this.topicAndGroupConsumeRT.getStatsDataInMinute(topic + "@" + group); if (0 == statsData.getSum()) { statsData = this.topicAndGroupConsumeRT.getStatsDataInHour(topic + "@" + group); } return statsData; } private StatsSnapshot getConsumeOKTPS(final String group, final String topic) { return this.topicAndGroupConsumeOKTPS.getStatsDataInMinute(topic + "@" + group); } private StatsSnapshot getConsumeFailedTPS(final String group, final String topic) { return this.topicAndGroupConsumeFailedTPS.getStatsDataInMinute(topic + "@" + group); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/AsyncTraceDispatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.common.ThreadLocalIndex; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RPCHook; import static org.apache.rocketmq.client.trace.TraceConstants.TRACE_INSTANCE_NAME; public class AsyncTraceDispatcher implements TraceDispatcher { private static final Logger log = LoggerFactory.getLogger(AsyncTraceDispatcher.class); private static final AtomicInteger COUNTER = new AtomicInteger(); private static final AtomicInteger INSTANCE_NUM = new AtomicInteger(0); private static final long WAIT_FOR_SHUTDOWN = 5000L; private volatile boolean stopped = false; private final int traceInstanceId = INSTANCE_NUM.getAndIncrement(); private final int batchNum; private final int maxMsgSize; private final DefaultMQProducer traceProducer; private AtomicLong discardCount; private Thread worker; private final ThreadPoolExecutor traceExecutor; private final ArrayBlockingQueue traceContextQueue; private final ArrayBlockingQueue appenderQueue; private volatile Thread shutDownHook; private DefaultMQProducerImpl hostProducer; private DefaultMQPushConsumerImpl hostConsumer; private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex(); private volatile String traceTopicName; private AtomicBoolean isStarted = new AtomicBoolean(false); private volatile AccessChannel accessChannel = AccessChannel.LOCAL; private String group; private Type type; private String namespaceV2; private final int flushTraceInterval = 5000; private long lastFlushTime = System.currentTimeMillis(); public AsyncTraceDispatcher(String group, Type type, int batchNum, String traceTopicName, RPCHook rpcHook) { this.batchNum = Math.min(batchNum, 20);/* max value 20*/ this.maxMsgSize = 128000; this.discardCount = new AtomicLong(0L); this.traceContextQueue = new ArrayBlockingQueue<>(2048); this.group = group; this.type = type; this.appenderQueue = new ArrayBlockingQueue<>(2048); if (!UtilAll.isBlank(traceTopicName)) { this.traceTopicName = traceTopicName; } else { this.traceTopicName = TopicValidator.RMQ_SYS_TRACE_TOPIC; } this.traceExecutor = new ThreadPoolExecutor(// 2, // 4, // 1000 * 60, // TimeUnit.MILLISECONDS, // this.appenderQueue, // new ThreadFactoryImpl("MQTraceSendThread_" + traceInstanceId + "_")); traceProducer = getAndCreateTraceProducer(rpcHook); } public AccessChannel getAccessChannel() { return accessChannel; } public void setAccessChannel(AccessChannel accessChannel) { this.accessChannel = accessChannel; } public String getTraceTopicName() { return traceTopicName; } public void setTraceTopicName(String traceTopicName) { this.traceTopicName = traceTopicName; } public DefaultMQProducer getTraceProducer() { return traceProducer; } public DefaultMQProducerImpl getHostProducer() { return hostProducer; } public void setHostProducer(DefaultMQProducerImpl hostProducer) { this.hostProducer = hostProducer; } public DefaultMQPushConsumerImpl getHostConsumer() { return hostConsumer; } public void setHostConsumer(DefaultMQPushConsumerImpl hostConsumer) { this.hostConsumer = hostConsumer; } public String getNamespaceV2() { return namespaceV2; } public void setNamespaceV2(String namespaceV2) { this.namespaceV2 = namespaceV2; } public void start(String nameSrvAddr, AccessChannel accessChannel) throws MQClientException { if (isStarted.compareAndSet(false, true)) { traceProducer.setNamesrvAddr(nameSrvAddr); traceProducer.setInstanceName(TRACE_INSTANCE_NAME + "_" + nameSrvAddr); traceProducer.setNamespaceV2(namespaceV2); traceProducer.setEnableTrace(false); traceProducer.start(); } this.accessChannel = accessChannel; this.worker = new ThreadFactoryImpl("MQ-AsyncArrayDispatcher-Thread" + traceInstanceId, true) .newThread(new AsyncRunnable()); this.worker.setDaemon(true); this.worker.start(); this.registerShutDownHook(); } private DefaultMQProducer getAndCreateTraceProducer(RPCHook rpcHook) { DefaultMQProducer traceProducerInstance = this.traceProducer; if (traceProducerInstance == null) { traceProducerInstance = new DefaultMQProducer(rpcHook); traceProducerInstance.setProducerGroup(genGroupNameForTrace()); traceProducerInstance.setSendMsgTimeout(5000); traceProducerInstance.setVipChannelEnabled(false); // The max size of message is 128K traceProducerInstance.setMaxMessageSize(maxMsgSize); } return traceProducerInstance; } private String genGroupNameForTrace() { return TraceConstants.GROUP_NAME_PREFIX + "-" + this.group + "-" + this.type + "-" + COUNTER.incrementAndGet(); } @Override public boolean append(final Object ctx) { boolean result = traceContextQueue.offer((TraceContext) ctx); if (!result) { log.info("buffer full" + discardCount.incrementAndGet() + " ,context is " + ctx); } return result; } @Override public void flush() { while (traceContextQueue.size() > 0) { try { flushTraceContext(true); } catch (Throwable throwable) { log.error("flushTraceContext error", throwable); } } } @Override public void shutdown() { flush(); ThreadUtils.shutdownGracefully(this.traceExecutor, WAIT_FOR_SHUTDOWN, TimeUnit.MILLISECONDS); if (isStarted.get()) { traceProducer.shutdown(); } this.removeShutdownHook(); stopped = true; } public void registerShutDownHook() { if (shutDownHook == null) { shutDownHook = new Thread(new Runnable() { private volatile boolean hasShutdown = false; @Override public void run() { synchronized (this) { if (!this.hasShutdown) { flush(); } } } }, "ShutdownHookMQTrace"); try { Runtime.getRuntime().addShutdownHook(shutDownHook); } catch (IllegalStateException e) { // ignore - VM is already shutting down } } } public void removeShutdownHook() { if (shutDownHook != null) { try { Runtime.getRuntime().removeShutdownHook(shutDownHook); } catch (IllegalStateException e) { // ignore - VM is already shutting down } } } class AsyncRunnable implements Runnable { private volatile boolean stopped = false; @Override public void run() { while (!stopped) { try { flushTraceContext(false); } catch (Throwable e) { log.error("flushTraceContext error", e); } if (AsyncTraceDispatcher.this.stopped) { this.stopped = true; } } } } private void flushTraceContext(boolean forceFlush) throws InterruptedException { List contextList = new ArrayList<>(batchNum); int size = traceContextQueue.size(); if (size != 0) { if (forceFlush || size >= batchNum || System.currentTimeMillis() - lastFlushTime > flushTraceInterval) { for (int i = 0; i < batchNum; i++) { TraceContext context = traceContextQueue.poll(); if (context != null) { contextList.add(context); } else { break; } } asyncSendTraceMessage(contextList); return; } } // To prevent an infinite loop, add a wait time between each two task executions Thread.sleep(5); } private void asyncSendTraceMessage(List contextList) { AsyncDataSendTask request = new AsyncDataSendTask(contextList); traceExecutor.submit(request); lastFlushTime = System.currentTimeMillis(); } class AsyncDataSendTask implements Runnable { private final List contextList; public AsyncDataSendTask(List contextList) { this.contextList = contextList; } @Override public void run() { sendTraceData(contextList); } public void sendTraceData(List contextList) { Map> transBeanMap = new HashMap<>(16); String traceTopic; for (TraceContext context : contextList) { AccessChannel accessChannel = context.getAccessChannel(); if (accessChannel == null) { accessChannel = AsyncTraceDispatcher.this.accessChannel; } String currentRegionId = context.getRegionId(); if (currentRegionId == null || context.getTraceBeans().isEmpty()) { continue; } if (AccessChannel.CLOUD == accessChannel) { traceTopic = TraceConstants.TRACE_TOPIC_PREFIX + currentRegionId; } else { traceTopic = traceTopicName; } String topic = context.getTraceBeans().get(0).getTopic(); String key = topic + TraceConstants.CONTENT_SPLITOR + traceTopic; List transBeanList = transBeanMap.computeIfAbsent(key, k -> new ArrayList<>()); TraceTransferBean traceData = TraceDataEncoder.encoderFromContextBean(context); transBeanList.add(traceData); } for (Map.Entry> entry : transBeanMap.entrySet()) { String[] key = entry.getKey().split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); flushData(entry.getValue(), key[0], key[1]); } } private void flushData(List transBeanList, String topic, String traceTopic) { if (transBeanList.size() == 0) { return; } StringBuilder buffer = new StringBuilder(1024); int count = 0; Set keySet = new HashSet(); for (TraceTransferBean bean : transBeanList) { keySet.addAll(bean.getTransKey()); buffer.append(bean.getTransData()); count++; if (buffer.length() >= traceProducer.getMaxMessageSize()) { sendTraceDataByMQ(keySet, buffer.toString(), traceTopic); buffer.delete(0, buffer.length()); keySet.clear(); count = 0; } } if (count > 0) { sendTraceDataByMQ(keySet, buffer.toString(), traceTopic); } transBeanList.clear(); } /** * Send message trace data * * @param keySet the keyset in this batch(including msgId in original message not offsetMsgId) * @param data the message trace data in this batch * @param traceTopic the topic which message trace data will send to */ private void sendTraceDataByMQ(Set keySet, final String data, String traceTopic) { final Message message = new Message(traceTopic, data.getBytes(StandardCharsets.UTF_8)); // Keyset of message trace includes msgId of or original message message.setKeys(keySet); try { Set traceBrokerSet = tryGetMessageQueueBrokerSet(traceProducer.getDefaultMQProducerImpl(), traceTopic); SendCallback callback = new SendCallback() { @Override public void onSuccess(SendResult sendResult) { } @Override public void onException(Throwable e) { log.error("send trace data failed, the traceData is {}", data, e); } }; if (traceBrokerSet.isEmpty()) { // No cross set traceProducer.send(message, callback, 5000); } else { traceProducer.send(message, new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { Set brokerSet = (Set) arg; List filterMqs = new ArrayList<>(); for (MessageQueue queue : mqs) { if (brokerSet.contains(queue.getBrokerName())) { filterMqs.add(queue); } } int index = sendWhichQueue.incrementAndGet(); int pos = index % filterMqs.size(); return filterMqs.get(pos); } }, traceBrokerSet, callback); } } catch (Exception e) { log.error("send trace data failed, the traceData is {}", data, e); } } private Set tryGetMessageQueueBrokerSet(DefaultMQProducerImpl producer, String topic) { Set brokerSet = new HashSet<>(); TopicPublishInfo topicPublishInfo = producer.getTopicPublishInfoTable().get(topic); if (null == topicPublishInfo || !topicPublishInfo.ok()) { producer.getTopicPublishInfoTable().putIfAbsent(topic, new TopicPublishInfo()); producer.getMqClientFactory().updateTopicRouteInfoFromNameServer(topic); topicPublishInfo = producer.getTopicPublishInfoTable().get(topic); } if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) { for (MessageQueue queue : topicPublishInfo.getMessageQueueList()) { brokerSet.add(queue.getBrokerName()); } } return brokerSet; } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceBean.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageType; public class TraceBean { private static final String LOCAL_ADDRESS; private String topic = ""; private String msgId = ""; private String offsetMsgId = ""; private String tags = ""; private String keys = ""; private String storeHost = LOCAL_ADDRESS; private String clientHost = LOCAL_ADDRESS; private long storeTime; private int retryTimes; private int bodyLength; private MessageType msgType; private LocalTransactionState transactionState; private String transactionId; private boolean fromTransactionCheck; static { byte[] ip = UtilAll.getIP(); if (ip.length == 4) { LOCAL_ADDRESS = UtilAll.ipToIPv4Str(ip); } else { LOCAL_ADDRESS = UtilAll.ipToIPv6Str(ip); } } public MessageType getMsgType() { return msgType; } public void setMsgType(final MessageType msgType) { this.msgType = msgType; } public String getOffsetMsgId() { return offsetMsgId; } public void setOffsetMsgId(final String offsetMsgId) { this.offsetMsgId = offsetMsgId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getTags() { return tags; } public void setTags(String tags) { this.tags = tags; } public String getKeys() { return keys; } public void setKeys(String keys) { this.keys = keys; } public String getStoreHost() { return storeHost; } public void setStoreHost(String storeHost) { this.storeHost = storeHost; } public String getClientHost() { return clientHost; } public void setClientHost(String clientHost) { this.clientHost = clientHost; } public long getStoreTime() { return storeTime; } public void setStoreTime(long storeTime) { this.storeTime = storeTime; } public int getRetryTimes() { return retryTimes; } public void setRetryTimes(int retryTimes) { this.retryTimes = retryTimes; } public int getBodyLength() { return bodyLength; } public void setBodyLength(int bodyLength) { this.bodyLength = bodyLength; } public LocalTransactionState getTransactionState() { return transactionState; } public void setTransactionState(LocalTransactionState transactionState) { this.transactionState = transactionState; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } public boolean isFromTransactionCheck() { return fromTransactionCheck; } public void setFromTransactionCheck(boolean fromTransactionCheck) { this.fromTransactionCheck = fromTransactionCheck; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.common.topic.TopicValidator; public class TraceConstants { public static final String GROUP_NAME_PREFIX = "_INNER_TRACE_PRODUCER"; public static final char CONTENT_SPLITOR = (char) 1; public static final char FIELD_SPLITOR = (char) 2; public static final String TRACE_INSTANCE_NAME = "PID_CLIENT_INNER_TRACE_PRODUCER"; public static final String TRACE_TOPIC_PREFIX = TopicValidator.SYSTEM_TOPIC_PREFIX + "TRACE_DATA_"; public static final String TO_PREFIX = "To_"; public static final String FROM_PREFIX = "From_"; public static final String END_TRANSACTION = "EndTransaction"; public static final String ROCKETMQ_SERVICE = "rocketmq"; public static final String ROCKETMQ_SUCCESS = "rocketmq.success"; public static final String ROCKETMQ_TAGS = "rocketmq.tags"; public static final String ROCKETMQ_KEYS = "rocketmq.keys"; public static final String ROCKETMQ_STORE_HOST = "rocketmq.store_host"; public static final String ROCKETMQ_BODY_LENGTH = "rocketmq.body_length"; public static final String ROCKETMQ_MSG_ID = "rocketmq.mgs_id"; public static final String ROCKETMQ_MSG_TYPE = "rocketmq.mgs_type"; public static final String ROCKETMQ_REGION_ID = "rocketmq.region_id"; public static final String ROCKETMQ_TRANSACTION_ID = "rocketmq.transaction_id"; public static final String ROCKETMQ_TRANSACTION_STATE = "rocketmq.transaction_state"; public static final String ROCKETMQ_IS_FROM_TRANSACTION_CHECK = "rocketmq.is_from_transaction_check"; public static final String ROCKETMQ_RETRY_TIMERS = "rocketmq.retry_times"; } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.common.message.MessageClientIDSetter; import java.util.List; /** * The context of Trace */ public class TraceContext implements Comparable { private TraceType traceType; private long timeStamp = System.currentTimeMillis(); private String regionId = ""; private String regionName = ""; private String groupName = ""; private int costTime = 0; private boolean isSuccess = true; private String requestId = MessageClientIDSetter.createUniqID(); private int contextCode = 0; private AccessChannel accessChannel; private List traceBeans; public int getContextCode() { return contextCode; } public void setContextCode(final int contextCode) { this.contextCode = contextCode; } public List getTraceBeans() { return traceBeans; } public void setTraceBeans(List traceBeans) { this.traceBeans = traceBeans; } public String getRegionId() { return regionId; } public void setRegionId(String regionId) { this.regionId = regionId; } public TraceType getTraceType() { return traceType; } public void setTraceType(TraceType traceType) { this.traceType = traceType; } public long getTimeStamp() { return timeStamp; } public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public int getCostTime() { return costTime; } public void setCostTime(int costTime) { this.costTime = costTime; } public boolean isSuccess() { return isSuccess; } public void setSuccess(boolean success) { isSuccess = success; } public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public String getRegionName() { return regionName; } public void setRegionName(String regionName) { this.regionName = regionName; } public AccessChannel getAccessChannel() { return accessChannel; } public void setAccessChannel(AccessChannel accessChannel) { this.accessChannel = accessChannel; } @Override public int compareTo(TraceContext o) { return Long.compare(this.timeStamp, o.getTimeStamp()); } @Override public String toString() { StringBuilder sb = new StringBuilder(1024); sb.append("TraceContext{").append(traceType).append("_").append(groupName).append("_") .append(regionId).append("_").append(isSuccess).append("_"); if (traceBeans != null && traceBeans.size() > 0) { for (TraceBean bean : traceBeans) { sb.append(bean.getMsgId()).append("_").append(bean.getTopic()).append("_"); } } sb.append('}'); return sb.toString(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageType; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Encode/decode for Trace Data */ public class TraceDataEncoder { /** * Resolving traceContext list From trace data String * * @param traceData * @return */ public static List decoderFromTraceDataString(String traceData) { List resList = new ArrayList<>(); if (traceData == null || traceData.length() <= 0) { return resList; } String[] contextList = traceData.split(String.valueOf(TraceConstants.FIELD_SPLITOR)); for (String context : contextList) { String[] line = context.split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); if (line[0].equals(TraceType.Pub.name())) { TraceContext pubContext = new TraceContext(); pubContext.setTraceType(TraceType.Pub); pubContext.setTimeStamp(Long.parseLong(line[1])); pubContext.setRegionId(line[2]); pubContext.setGroupName(line[3]); TraceBean bean = new TraceBean(); bean.setTopic(line[4]); bean.setMsgId(line[5]); bean.setTags(line[6]); bean.setKeys(line[7]); bean.setStoreHost(line[8]); bean.setBodyLength(Integer.parseInt(line[9])); pubContext.setCostTime(Integer.parseInt(line[10])); bean.setMsgType(MessageType.values()[Integer.parseInt(line[11])]); if (line.length == 13) { pubContext.setSuccess(Boolean.parseBoolean(line[12])); } else if (line.length == 14) { bean.setOffsetMsgId(line[12]); pubContext.setSuccess(Boolean.parseBoolean(line[13])); } // compatible with the old version if (line.length >= 15) { bean.setOffsetMsgId(line[12]); pubContext.setSuccess(Boolean.parseBoolean(line[13])); bean.setClientHost(line[14]); } pubContext.setTraceBeans(new ArrayList<>(1)); pubContext.getTraceBeans().add(bean); resList.add(pubContext); } else if (line[0].equals(TraceType.SubBefore.name())) { TraceContext subBeforeContext = new TraceContext(); subBeforeContext.setTraceType(TraceType.SubBefore); subBeforeContext.setTimeStamp(Long.parseLong(line[1])); subBeforeContext.setRegionId(line[2]); subBeforeContext.setGroupName(line[3]); subBeforeContext.setRequestId(line[4]); TraceBean bean = new TraceBean(); bean.setMsgId(line[5]); bean.setRetryTimes(Integer.parseInt(line[6])); bean.setKeys(line[7]); subBeforeContext.setTraceBeans(new ArrayList<>(1)); subBeforeContext.getTraceBeans().add(bean); resList.add(subBeforeContext); } else if (line[0].equals(TraceType.SubAfter.name())) { TraceContext subAfterContext = new TraceContext(); subAfterContext.setTraceType(TraceType.SubAfter); subAfterContext.setRequestId(line[1]); TraceBean bean = new TraceBean(); bean.setMsgId(line[2]); bean.setKeys(line[5]); subAfterContext.setTraceBeans(new ArrayList<>(1)); subAfterContext.getTraceBeans().add(bean); subAfterContext.setCostTime(Integer.parseInt(line[3])); subAfterContext.setSuccess(Boolean.parseBoolean(line[4])); if (line.length >= 7) { // add the context type subAfterContext.setContextCode(Integer.parseInt(line[6])); } // compatible with the old version if (line.length >= 9) { subAfterContext.setTimeStamp(Long.parseLong(line[7])); subAfterContext.setGroupName(line[8]); } resList.add(subAfterContext); } else if (line[0].equals(TraceType.EndTransaction.name())) { TraceContext endTransactionContext = new TraceContext(); endTransactionContext.setTraceType(TraceType.EndTransaction); endTransactionContext.setTimeStamp(Long.parseLong(line[1])); endTransactionContext.setRegionId(line[2]); endTransactionContext.setGroupName(line[3]); TraceBean bean = new TraceBean(); bean.setTopic(line[4]); bean.setMsgId(line[5]); bean.setTags(line[6]); bean.setKeys(line[7]); bean.setStoreHost(line[8]); bean.setMsgType(MessageType.values()[Integer.parseInt(line[9])]); bean.setTransactionId(line[10]); bean.setTransactionState(LocalTransactionState.valueOf(line[11])); bean.setFromTransactionCheck(Boolean.parseBoolean(line[12])); endTransactionContext.setTraceBeans(new ArrayList<>(1)); endTransactionContext.getTraceBeans().add(bean); resList.add(endTransactionContext); } else if (line[0].equals(TraceType.Recall.name())) { TraceContext recallContext = new TraceContext(); recallContext.setTraceType(TraceType.Recall); recallContext.setTimeStamp(Long.parseLong(line[1])); recallContext.setRegionId(line[2]); recallContext.setGroupName(line[3]); TraceBean bean = new TraceBean(); bean.setTopic(line[4]); bean.setMsgId(line[5]); recallContext.setSuccess(Boolean.parseBoolean(line[6])); recallContext.setTraceBeans(new ArrayList<>(1)); recallContext.getTraceBeans().add(bean); resList.add(recallContext); } } return resList; } /** * Encoding the trace context into data strings and keyset sets * * @param ctx * @return */ public static TraceTransferBean encoderFromContextBean(TraceContext ctx) { if (ctx == null) { return null; } //build message trace of the transferring entity content bean TraceTransferBean transferBean = new TraceTransferBean(); StringBuilder sb = new StringBuilder(256); switch (ctx.getTraceType()) { case Pub: { TraceBean bean = ctx.getTraceBeans().get(0); //append the content of context and traceBean to transferBean's TransData sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTopic()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTags()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getStoreHost()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getBodyLength()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgType().ordinal()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getOffsetMsgId()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.isSuccess()).append(TraceConstants.FIELD_SPLITOR);// } break; case SubBefore: { for (TraceBean bean : ctx.getTraceBeans()) { sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getRetryTimes()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getKeys()).append(TraceConstants.FIELD_SPLITOR);// } } break; case SubAfter: { for (TraceBean bean : ctx.getTraceBeans()) { sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.isSuccess()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getContextCode()).append(TraceConstants.CONTENT_SPLITOR); if (!ctx.getAccessChannel().equals(AccessChannel.CLOUD)) { sb.append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR); sb.append(ctx.getGroupName()); } sb.append(TraceConstants.FIELD_SPLITOR); } } break; case EndTransaction: { TraceBean bean = ctx.getTraceBeans().get(0); sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)// .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTopic()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTags()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getStoreHost()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgType().ordinal()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTransactionId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTransactionState().name()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.isFromTransactionCheck()).append(TraceConstants.FIELD_SPLITOR); } break; case Recall: { TraceBean bean = ctx.getTraceBeans().get(0); sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR) .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR) .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR) .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR) .append(bean.getTopic()).append(TraceConstants.CONTENT_SPLITOR) .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR) .append(ctx.isSuccess()).append(TraceConstants.FIELD_SPLITOR);// } break; default: } transferBean.setTransData(sb.toString()); for (TraceBean bean : ctx.getTraceBeans()) { transferBean.getTransKey().add(bean.getMsgId()); if (bean.getKeys() != null && bean.getKeys().length() > 0) { String[] keys = bean.getKeys().split(MessageConst.KEY_SEPARATOR); transferBean.getTransKey().addAll(Arrays.asList(keys)); } } return transferBean; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceDispatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.exception.MQClientException; import java.io.IOException; /** * Interface of asynchronous transfer data */ public interface TraceDispatcher { enum Type { PRODUCE, CONSUME } /** * Initialize asynchronous transfer data module */ void start(String nameSrvAddr, AccessChannel accessChannel) throws MQClientException; /** * Append the transferring data * @param ctx data information * @return */ boolean append(Object ctx); /** * Write flush action * * @throws IOException */ void flush() throws IOException; /** * Close the trace Hook */ void shutdown(); } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceDispatcherType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; public enum TraceDispatcherType { PRODUCER, CONSUMER } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceTransferBean.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.util.HashSet; import java.util.Set; /** * Trace transferring bean */ public class TraceTransferBean { private String transData; private Set transKey = new HashSet<>(); public String getTransData() { return transData; } public void setTransData(String transData) { this.transData = transData; } public Set getTransKey() { return transKey; } public void setTransKey(Set transKey) { this.transKey = transKey; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; public enum TraceType { Pub, Recall, SubBefore, SubAfter, EndTransaction, } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; public class TraceView { private String msgId; private String tags; private String keys; private String storeHost; private String clientHost; private int costTime; private String msgType; private String offSetMsgId; private long timeStamp; private long bornTime; private String topic; private String groupName; private String status; public static List decodeFromTraceTransData(String key, MessageExt messageExt) { List messageTraceViewList = new ArrayList<>(); String messageBody = new String(messageExt.getBody(), StandardCharsets.UTF_8); if (messageBody == null || messageBody.length() <= 0) { return messageTraceViewList; } List traceContextList = TraceDataEncoder.decoderFromTraceDataString(messageBody); for (TraceContext context : traceContextList) { TraceView messageTraceView = new TraceView(); TraceBean traceBean = context.getTraceBeans().get(0); if (!traceBean.getMsgId().equals(key)) { continue; } messageTraceView.setCostTime(context.getCostTime()); messageTraceView.setGroupName(context.getGroupName()); if (context.isSuccess()) { messageTraceView.setStatus("success"); } else { messageTraceView.setStatus("failed"); } messageTraceView.setKeys(traceBean.getKeys()); messageTraceView.setMsgId(traceBean.getMsgId()); messageTraceView.setTags(traceBean.getTags()); messageTraceView.setTopic(traceBean.getTopic()); messageTraceView.setMsgType(context.getTraceType().name()); messageTraceView.setOffSetMsgId(traceBean.getOffsetMsgId()); messageTraceView.setTimeStamp(context.getTimeStamp()); messageTraceView.setStoreHost(traceBean.getStoreHost()); messageTraceView.setClientHost(messageExt.getBornHostString()); messageTraceViewList.add(messageTraceView); } return messageTraceViewList; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getTags() { return tags; } public void setTags(String tags) { this.tags = tags; } public String getKeys() { return keys; } public void setKeys(String keys) { this.keys = keys; } public String getStoreHost() { return storeHost; } public void setStoreHost(String storeHost) { this.storeHost = storeHost; } public String getClientHost() { return clientHost; } public void setClientHost(String clientHost) { this.clientHost = clientHost; } public int getCostTime() { return costTime; } public void setCostTime(int costTime) { this.costTime = costTime; } public String getMsgType() { return msgType; } public void setMsgType(String msgType) { this.msgType = msgType; } public String getOffSetMsgId() { return offSetMsgId; } public void setOffSetMsgId(String offSetMsgId) { this.offSetMsgId = offSetMsgId; } public long getTimeStamp() { return timeStamp; } public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } public long getBornTime() { return bornTime; } public void setBornTime(long bornTime) { this.bornTime = bornTime; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageOpenTracingHookImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMapAdapter; import io.opentracing.tag.Tags; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.trace.TraceConstants; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class ConsumeMessageOpenTracingHookImpl implements ConsumeMessageHook { private Tracer tracer; public ConsumeMessageOpenTracingHookImpl(Tracer tracer) { this.tracer = tracer; } @Override public String hookName() { return "ConsumeMessageOpenTracingHook"; } @Override public void consumeMessageBefore(ConsumeMessageContext context) { if (context == null || context.getMsgList() == null || context.getMsgList().isEmpty()) { return; } List spanList = new ArrayList<>(); for (MessageExt msg : context.getMsgList()) { if (msg == null) { continue; } Tracer.SpanBuilder spanBuilder = tracer .buildSpan(TraceConstants.FROM_PREFIX + msg.getTopic()) .withTag(Tags.SPAN_KIND, Tags.SPAN_KIND_CONSUMER); SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(msg.getProperties())); if (spanContext != null) { spanBuilder.asChildOf(spanContext); } Span span = spanBuilder.start(); span.setTag(Tags.PEER_SERVICE, TraceConstants.ROCKETMQ_SERVICE); span.setTag(Tags.MESSAGE_BUS_DESTINATION, NamespaceUtil.withoutNamespace(msg.getTopic())); span.setTag(TraceConstants.ROCKETMQ_MSG_ID, msg.getMsgId()); span.setTag(TraceConstants.ROCKETMQ_TAGS, msg.getTags()); span.setTag(TraceConstants.ROCKETMQ_KEYS, msg.getKeys()); span.setTag(TraceConstants.ROCKETMQ_BODY_LENGTH, msg.getStoreSize()); span.setTag(TraceConstants.ROCKETMQ_RETRY_TIMERS, msg.getReconsumeTimes()); span.setTag(TraceConstants.ROCKETMQ_REGION_ID, msg.getProperty(MessageConst.PROPERTY_MSG_REGION)); spanList.add(span); } context.setMqTraceContext(spanList); } @Override public void consumeMessageAfter(ConsumeMessageContext context) { if (context == null || context.getMsgList() == null || context.getMsgList().isEmpty()) { return; } List spanList = (List) context.getMqTraceContext(); if (spanList == null) { return; } for (Span span : spanList) { span.setTag(TraceConstants.ROCKETMQ_SUCCESS, context.isSuccess()); span.finish(); } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.rocketmq.client.consumer.listener.ConsumeReturnType; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.trace.TraceBean; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.TraceType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class ConsumeMessageTraceHookImpl implements ConsumeMessageHook { private TraceDispatcher localDispatcher; public ConsumeMessageTraceHookImpl(TraceDispatcher localDispatcher) { this.localDispatcher = localDispatcher; } @Override public String hookName() { return "ConsumeMessageTraceHook"; } @Override public void consumeMessageBefore(ConsumeMessageContext context) { if (context == null || context.getMsgList() == null || context.getMsgList().isEmpty()) { return; } TraceContext traceContext = new TraceContext(); context.setMqTraceContext(traceContext); traceContext.setTraceType(TraceType.SubBefore); traceContext.setGroupName(NamespaceUtil.withoutNamespace(context.getConsumerGroup())); List beans = new ArrayList<>(); for (MessageExt msg : context.getMsgList()) { if (msg == null) { continue; } String regionId = msg.getProperty(MessageConst.PROPERTY_MSG_REGION); String traceOn = msg.getProperty(MessageConst.PROPERTY_TRACE_SWITCH); if (traceOn != null && traceOn.equals("false")) { // If trace switch is false ,skip it continue; } TraceBean traceBean = new TraceBean(); traceBean.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic())); traceBean.setMsgId(msg.getMsgId()); traceBean.setTags(msg.getTags()); traceBean.setKeys(msg.getKeys()); traceBean.setStoreTime(msg.getStoreTimestamp()); traceBean.setBodyLength(msg.getStoreSize()); traceBean.setRetryTimes(msg.getReconsumeTimes()); traceContext.setRegionId(regionId); beans.add(traceBean); } if (beans.size() > 0) { traceContext.setTraceBeans(beans); traceContext.setTimeStamp(System.currentTimeMillis()); localDispatcher.append(traceContext); } } @Override public void consumeMessageAfter(ConsumeMessageContext context) { if (context == null || context.getMsgList() == null || context.getMsgList().isEmpty()) { return; } TraceContext subBeforeContext = (TraceContext) context.getMqTraceContext(); if (subBeforeContext.getTraceBeans() == null || subBeforeContext.getTraceBeans().size() < 1) { // If subBefore bean is null ,skip it return; } TraceContext subAfterContext = new TraceContext(); subAfterContext.setTraceType(TraceType.SubAfter); subAfterContext.setRegionId(subBeforeContext.getRegionId()); subAfterContext.setGroupName(NamespaceUtil.withoutNamespace(subBeforeContext.getGroupName())); subAfterContext.setRequestId(subBeforeContext.getRequestId()); subAfterContext.setAccessChannel(context.getAccessChannel()); subAfterContext.setSuccess(context.isSuccess()); // Calculate the cost time for processing messages int costTime = (int) ((System.currentTimeMillis() - subBeforeContext.getTimeStamp()) / context.getMsgList().size()); subAfterContext.setCostTime(costTime); subAfterContext.setTraceBeans(subBeforeContext.getTraceBeans()); Map props = context.getProps(); if (props != null) { String contextType = props.get(MixAll.CONSUME_CONTEXT_TYPE); if (contextType != null) { subAfterContext.setContextCode(ConsumeReturnType.valueOf(contextType).ordinal()); } } localDispatcher.append(subAfterContext); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/DefaultRecallMessageTraceHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import org.apache.rocketmq.client.trace.TraceBean; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.TraceType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import java.util.ArrayList; public class DefaultRecallMessageTraceHook implements RPCHook { private static final String RECALL_TRACE_ENABLE_KEY = "com.rocketmq.recall.default.trace.enable"; private boolean enableDefaultTrace = Boolean.parseBoolean(System.getProperty(RECALL_TRACE_ENABLE_KEY, "false")); private TraceDispatcher traceDispatcher; public DefaultRecallMessageTraceHook(TraceDispatcher traceDispatcher) { this.traceDispatcher = traceDispatcher; } @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { if (request.getCode() != RequestCode.RECALL_MESSAGE || !enableDefaultTrace || null == response.getExtFields() || null == response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION) || null == traceDispatcher) { return; } try { String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION); RecallMessageRequestHeader requestHeader = request.decodeCommandCustomHeader(RecallMessageRequestHeader.class); String topic = NamespaceUtil.withoutNamespace(requestHeader.getTopic()); String group = NamespaceUtil.withoutNamespace(requestHeader.getProducerGroup()); String recallHandle = requestHeader.getRecallHandle(); RecallMessageHandle.HandleV1 handleV1 = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(recallHandle); TraceBean traceBean = new TraceBean(); traceBean.setTopic(topic); traceBean.setMsgId(handleV1.getMessageId()); TraceContext traceContext = new TraceContext(); traceContext.setRegionId(regionId); traceContext.setTraceBeans(new ArrayList<>(1)); traceContext.setTraceType(TraceType.Recall); traceContext.setGroupName(group); traceContext.getTraceBeans().add(traceBean); traceContext.setSuccess(ResponseCode.SUCCESS == response.getCode()); traceDispatcher.append(traceContext); } catch (Exception e) { } } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/EndTransactionOpenTracingHookImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMapAdapter; import io.opentracing.tag.Tags; import org.apache.rocketmq.client.hook.EndTransactionContext; import org.apache.rocketmq.client.hook.EndTransactionHook; import org.apache.rocketmq.client.trace.TraceConstants; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageType; public class EndTransactionOpenTracingHookImpl implements EndTransactionHook { private Tracer tracer; public EndTransactionOpenTracingHookImpl(Tracer tracer) { this.tracer = tracer; } @Override public String hookName() { return "EndTransactionOpenTracingHook"; } @Override public void endTransaction(EndTransactionContext context) { if (context == null) { return; } Message msg = context.getMessage(); Tracer.SpanBuilder spanBuilder = tracer .buildSpan(TraceConstants.END_TRANSACTION) .withTag(Tags.SPAN_KIND, Tags.SPAN_KIND_PRODUCER); SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(msg.getProperties())); if (spanContext != null) { spanBuilder.asChildOf(spanContext); } Span span = spanBuilder.start(); span.setTag(Tags.PEER_SERVICE, TraceConstants.ROCKETMQ_SERVICE); span.setTag(Tags.MESSAGE_BUS_DESTINATION, msg.getTopic()); span.setTag(TraceConstants.ROCKETMQ_TAGS, msg.getTags()); span.setTag(TraceConstants.ROCKETMQ_KEYS, msg.getKeys()); span.setTag(TraceConstants.ROCKETMQ_STORE_HOST, context.getBrokerAddr()); span.setTag(TraceConstants.ROCKETMQ_MSG_ID, context.getMsgId()); span.setTag(TraceConstants.ROCKETMQ_MSG_TYPE, MessageType.Trans_msg_Commit.name()); span.setTag(TraceConstants.ROCKETMQ_TRANSACTION_ID, context.getTransactionId()); span.setTag(TraceConstants.ROCKETMQ_TRANSACTION_STATE, context.getTransactionState().name()); span.setTag(TraceConstants.ROCKETMQ_IS_FROM_TRANSACTION_CHECK, context.isFromTransactionCheck()); span.finish(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/EndTransactionTraceHookImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import java.util.ArrayList; import org.apache.rocketmq.client.hook.EndTransactionContext; import org.apache.rocketmq.client.hook.EndTransactionHook; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceBean; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.TraceType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageType; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class EndTransactionTraceHookImpl implements EndTransactionHook { private TraceDispatcher localDispatcher; public EndTransactionTraceHookImpl(TraceDispatcher localDispatcher) { this.localDispatcher = localDispatcher; } @Override public String hookName() { return "EndTransactionTraceHook"; } @Override public void endTransaction(EndTransactionContext context) { //if it is message trace data,then it doesn't recorded if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())) { return; } Message msg = context.getMessage(); //build the context content of TuxeTraceContext TraceContext tuxeContext = new TraceContext(); tuxeContext.setTraceBeans(new ArrayList<>(1)); tuxeContext.setTraceType(TraceType.EndTransaction); tuxeContext.setGroupName(NamespaceUtil.withoutNamespace(context.getProducerGroup())); //build the data bean object of message trace TraceBean traceBean = new TraceBean(); traceBean.setTopic(NamespaceUtil.withoutNamespace(context.getMessage().getTopic())); traceBean.setTags(context.getMessage().getTags()); traceBean.setKeys(context.getMessage().getKeys()); traceBean.setStoreHost(context.getBrokerAddr()); traceBean.setMsgType(MessageType.Trans_msg_Commit); traceBean.setClientHost(((AsyncTraceDispatcher)localDispatcher).getHostProducer().getMqClientFactory().getClientId()); traceBean.setMsgId(context.getMsgId()); traceBean.setTransactionState(context.getTransactionState()); traceBean.setTransactionId(context.getTransactionId()); traceBean.setFromTransactionCheck(context.isFromTransactionCheck()); String regionId = msg.getProperty(MessageConst.PROPERTY_MSG_REGION); if (regionId == null || regionId.isEmpty()) { regionId = MixAll.DEFAULT_TRACE_REGION_ID; } tuxeContext.setRegionId(regionId); tuxeContext.getTraceBeans().add(traceBean); tuxeContext.setTimeStamp(System.currentTimeMillis()); localDispatcher.append(tuxeContext); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageOpenTracingHookImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMapAdapter; import io.opentracing.tag.Tags; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.trace.TraceConstants; import org.apache.rocketmq.common.message.Message; public class SendMessageOpenTracingHookImpl implements SendMessageHook { private Tracer tracer; public SendMessageOpenTracingHookImpl(Tracer tracer) { this.tracer = tracer; } @Override public String hookName() { return "SendMessageOpenTracingHook"; } @Override public void sendMessageBefore(SendMessageContext context) { if (context == null) { return; } Message msg = context.getMessage(); Tracer.SpanBuilder spanBuilder = tracer .buildSpan(TraceConstants.TO_PREFIX + msg.getTopic()) .withTag(Tags.SPAN_KIND, Tags.SPAN_KIND_PRODUCER); SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(msg.getProperties())); if (spanContext != null) { spanBuilder.asChildOf(spanContext); } Span span = spanBuilder.start(); tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMapAdapter(msg.getProperties())); span.setTag(Tags.PEER_SERVICE, TraceConstants.ROCKETMQ_SERVICE); span.setTag(Tags.MESSAGE_BUS_DESTINATION, msg.getTopic()); span.setTag(TraceConstants.ROCKETMQ_TAGS, msg.getTags()); span.setTag(TraceConstants.ROCKETMQ_KEYS, msg.getKeys()); span.setTag(TraceConstants.ROCKETMQ_STORE_HOST, context.getBrokerAddr()); span.setTag(TraceConstants.ROCKETMQ_MSG_TYPE, context.getMsgType().name()); span.setTag(TraceConstants.ROCKETMQ_BODY_LENGTH, null == msg.getBody() ? 0 : msg.getBody().length); context.setMqTraceContext(span); } @Override public void sendMessageAfter(SendMessageContext context) { if (context == null || context.getMqTraceContext() == null) { return; } if (context.getSendResult() == null) { return; } if (context.getSendResult().getRegionId() == null) { return; } Span span = (Span) context.getMqTraceContext(); span.setTag(TraceConstants.ROCKETMQ_SUCCESS, context.getSendResult().getSendStatus().equals(SendStatus.SEND_OK)); span.setTag(TraceConstants.ROCKETMQ_MSG_ID, context.getSendResult().getMsgId()); span.setTag(TraceConstants.ROCKETMQ_REGION_ID, context.getSendResult().getRegionId()); span.finish(); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace.hook; import java.util.ArrayList; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceBean; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.TraceType; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class SendMessageTraceHookImpl implements SendMessageHook { private TraceDispatcher localDispatcher; public SendMessageTraceHookImpl(TraceDispatcher localDispatcher) { this.localDispatcher = localDispatcher; } @Override public String hookName() { return "SendMessageTraceHook"; } @Override public void sendMessageBefore(SendMessageContext context) { //if it is message trace data,then it doesn't recorded if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())) { return; } //build the context content of TraceContext TraceContext traceContext = new TraceContext(); traceContext.setTraceBeans(new ArrayList<>(1)); context.setMqTraceContext(traceContext); traceContext.setTraceType(TraceType.Pub); traceContext.setGroupName(NamespaceUtil.withoutNamespace(context.getProducerGroup())); //build the data bean object of message trace TraceBean traceBean = new TraceBean(); traceBean.setTopic(NamespaceUtil.withoutNamespace(context.getMessage().getTopic())); traceBean.setTags(context.getMessage().getTags()); traceBean.setKeys(context.getMessage().getKeys()); traceBean.setStoreHost(context.getBrokerAddr()); int bodyLength = null == context.getMessage().getBody() ? 0 : context.getMessage().getBody().length; traceBean.setBodyLength(bodyLength); traceBean.setMsgType(context.getMsgType()); traceContext.getTraceBeans().add(traceBean); } @Override public void sendMessageAfter(SendMessageContext context) { //if it is message trace data,then it doesn't recorded if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName()) || context.getMqTraceContext() == null) { return; } if (context.getSendResult() == null) { return; } if (context.getSendResult().getRegionId() == null || !context.getSendResult().isTraceOn()) { // if switch is false,skip it return; } TraceContext traceContext = (TraceContext) context.getMqTraceContext(); TraceBean traceBean = traceContext.getTraceBeans().get(0); int costTime = (int) ((System.currentTimeMillis() - traceContext.getTimeStamp()) / traceContext.getTraceBeans().size()); traceContext.setCostTime(costTime); if (context.getSendResult().getSendStatus().equals(SendStatus.SEND_OK)) { traceContext.setSuccess(true); } else { traceContext.setSuccess(false); } traceContext.setRegionId(context.getSendResult().getRegionId()); traceBean.setMsgId(context.getSendResult().getMsgId()); traceBean.setOffsetMsgId(context.getSendResult().getOffsetMsgId()); traceBean.setStoreTime(traceContext.getTimeStamp() + costTime / 2); localDispatcher.append(traceContext); } } ================================================ FILE: client/src/main/java/org/apache/rocketmq/client/utils/MessageUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.utils; import org.apache.rocketmq.client.common.ClientErrorCode; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; public class MessageUtil { public static Message createReplyMessage(final Message requestMessage, final byte[] body) throws MQClientException { if (requestMessage != null) { Message replyMessage = new Message(); String cluster = requestMessage.getProperty(MessageConst.PROPERTY_CLUSTER); String replyTo = requestMessage.getProperty(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT); String correlationId = requestMessage.getProperty(MessageConst.PROPERTY_CORRELATION_ID); String ttl = requestMessage.getProperty(MessageConst.PROPERTY_MESSAGE_TTL); replyMessage.setBody(body); if (cluster != null) { String replyTopic = MixAll.getReplyTopic(cluster); replyMessage.setTopic(replyTopic); MessageAccessor.putProperty(replyMessage, MessageConst.PROPERTY_MESSAGE_TYPE, MixAll.REPLY_MESSAGE_FLAG); MessageAccessor.putProperty(replyMessage, MessageConst.PROPERTY_CORRELATION_ID, correlationId); MessageAccessor.putProperty(replyMessage, MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT, replyTo); MessageAccessor.putProperty(replyMessage, MessageConst.PROPERTY_MESSAGE_TTL, ttl); return replyMessage; } else { throw new MQClientException(ClientErrorCode.CREATE_REPLY_MESSAGE_EXCEPTION, "create reply message fail, requestMessage error, property[" + MessageConst.PROPERTY_CLUSTER + "] is null."); } } throw new MQClientException(ClientErrorCode.CREATE_REPLY_MESSAGE_EXCEPTION, "create reply message fail, requestMessage cannot be null."); } public static String getReplyToClient(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT); } } ================================================ FILE: client/src/main/resources/rmq.client.logback.xml ================================================ %yellow(%d{yyy-MM-dd HH:mm:ss.SSS,GMT+8}) %highlight(%-5p) %boldWhite([%pid]) %magenta([%t]) %boldGreen([%logger{12}#%M:%L]) - %m%n UTF-8 true ${rocketmq.log.root:-${user.home}${file.separator}logs${file.separator}rocketmqlogs}${file.separator}rocketmq_client.log ${rocketmq.log.root:-${user.home}${file.separator}logs${file.separator}rocketmqlogs}${file.separator}other_days${file.separator}rocketmq_client-%i.log.gz 1 ${rocketmq.log.file.maxIndex:-10} 64MB %d{yyy-MM-dd HH:mm:ss.SSS,GMT+8} %-5p [%pid] [%t] [%logger{12}#%M:%L] - %m%n UTF-8 ================================================ FILE: client/src/test/java/org/apache/rocketmq/acl/common/AclClientRPCHookTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.RequestType; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.Test; import java.lang.reflect.Field; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import static org.apache.rocketmq.acl.common.SessionCredentials.ACCESS_KEY; import static org.apache.rocketmq.acl.common.SessionCredentials.SECURITY_TOKEN; import static org.assertj.core.api.Assertions.assertThat; public class AclClientRPCHookTest { protected ConcurrentHashMap, Field[]> fieldCache = new ConcurrentHashMap<>(); private AclClientRPCHook aclClientRPCHook = new AclClientRPCHook(null); @Test public void testParseRequestContent() { PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup("group"); requestHeader.setTopic("topic"); requestHeader.setQueueId(1); requestHeader.setQueueOffset(2L); requestHeader.setMaxMsgNums(32); requestHeader.setSysFlag(0); requestHeader.setCommitOffset(0L); requestHeader.setSuspendTimeoutMillis(15000L); requestHeader.setSubVersion(0L); RemotingCommand testPullRemotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); SortedMap oldContent = oldVersionParseRequestContent(testPullRemotingCommand, "ak", null); byte[] oldBytes = AclUtils.combineRequestContent(testPullRemotingCommand, oldContent); testPullRemotingCommand.addExtField(ACCESS_KEY, "ak"); SortedMap content = aclClientRPCHook.parseRequestContent(testPullRemotingCommand); byte[] newBytes = AclUtils.combineRequestContent(testPullRemotingCommand, content); assertThat(newBytes).isEqualTo(oldBytes); } @Test public void testParseRequestContentWithStreamRequestType() { PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup("group"); requestHeader.setTopic("topic"); requestHeader.setQueueId(1); requestHeader.setQueueOffset(2L); requestHeader.setMaxMsgNums(32); requestHeader.setSysFlag(0); requestHeader.setCommitOffset(0L); requestHeader.setSuspendTimeoutMillis(15000L); requestHeader.setSubVersion(0L); RemotingCommand testPullRemotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); testPullRemotingCommand.addExtField(MixAll.REQ_T, String.valueOf(RequestType.STREAM.getCode())); testPullRemotingCommand.addExtField(ACCESS_KEY, "ak"); SortedMap content = aclClientRPCHook.parseRequestContent(testPullRemotingCommand); assertThat(content.get(MixAll.REQ_T)).isEqualTo(String.valueOf(RequestType.STREAM.getCode())); } private SortedMap oldVersionParseRequestContent(RemotingCommand request, String ak, String securityToken) { CommandCustomHeader header = request.readCustomHeader(); // Sort property SortedMap map = new TreeMap<>(); map.put(ACCESS_KEY, ak); if (securityToken != null) { map.put(SECURITY_TOKEN, securityToken); } try { // Add header properties if (null != header) { Field[] fields = fieldCache.get(header.getClass()); if (null == fields) { fields = header.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); } Field[] tmp = fieldCache.putIfAbsent(header.getClass(), fields); if (null != tmp) { fields = tmp; } } for (Field field : fields) { Object value = field.get(header); if (null != value && !field.isSynthetic()) { map.put(field.getName(), value.toString()); } } } return map; } catch (Exception e) { throw new RuntimeException("incompatible exception.", e); } } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.junit.Assert; import org.junit.Test; public class AclSignerTest { @Test(expected = Exception.class) public void calSignatureExceptionTest() { AclSigner.calSignature(new byte[]{},""); } @Test public void calSignatureTest() { String expectedSignature = "IUc8rrO/0gDch8CjObLQsW2rsiA="; Assert.assertEquals(expectedSignature, AclSigner.calSignature("RocketMQ", "12345678")); Assert.assertEquals(expectedSignature, AclSigner.calSignature("RocketMQ".getBytes(), "12345678")); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import com.alibaba.fastjson2.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.remoting.RPCHook; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.UUID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class AclUtilsTest { @Test public void testGetAddresses() { String address = "1.1.1.{1,2,3,4}"; String[] addressArray = AclUtils.getAddresses(address, "{1,2,3,4}"); List newAddressList = new ArrayList<>(Arrays.asList(addressArray)); List addressList = new ArrayList<>(); addressList.add("1.1.1.1"); addressList.add("1.1.1.2"); addressList.add("1.1.1.3"); addressList.add("1.1.1.4"); Assert.assertEquals(newAddressList, addressList); // IPv6 test String ipv6Address = "1:ac41:9987::bb22:666:{1,2,3,4}"; String[] ipv6AddressArray = AclUtils.getAddresses(ipv6Address, "{1,2,3,4}"); List newIPv6AddressList = new ArrayList<>(); Collections.addAll(newIPv6AddressList, ipv6AddressArray); List ipv6AddressList = new ArrayList<>(); ipv6AddressList.add("1:ac41:9987::bb22:666:1"); ipv6AddressList.add("1:ac41:9987::bb22:666:2"); ipv6AddressList.add("1:ac41:9987::bb22:666:3"); ipv6AddressList.add("1:ac41:9987::bb22:666:4"); Assert.assertEquals(newIPv6AddressList, ipv6AddressList); } @Test public void testIsScope_StringArray() { String address = "12"; for (int i = 0; i < 6; i++) { boolean isScope = AclUtils.isScope(address, 4); if (i == 3) { Assert.assertTrue(isScope); } else { Assert.assertFalse(isScope); } address = address + ".12"; } } @Test public void testIsScope_Array() { String[] address = StringUtils.split("12.12.12.12", "."); boolean isScope = AclUtils.isScope(address, 4); Assert.assertTrue(isScope); isScope = AclUtils.isScope(address, 3); Assert.assertTrue(isScope); address = StringUtils.split("12.12.1222.1222", "."); isScope = AclUtils.isScope(address, 4); Assert.assertFalse(isScope); isScope = AclUtils.isScope(address, 3); Assert.assertFalse(isScope); // IPv6 test address = StringUtils.split("1050:0000:0000:0000:0005:0600:300c:326b", ":"); isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertTrue(isScope); isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); address = StringUtils.split("1050:9876:0000:0000:0005:akkg:300c:326b", ":"); isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertFalse(isScope); isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); address = StringUtils.split(AclUtils.expandIP("1050::0005:akkg:300c:326b", 8), ":"); isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertFalse(isScope); isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); } @Test public void testIsScope_String() { for (int i = 0; i < 256; i++) { boolean isScope = AclUtils.isScope(i + ""); Assert.assertTrue(isScope); } boolean isScope = AclUtils.isScope("-1"); Assert.assertFalse(isScope); isScope = AclUtils.isScope("256"); Assert.assertFalse(isScope); } @Test public void testIsScope_Integral() { for (int i = 0; i < 256; i++) { boolean isScope = AclUtils.isScope(i); Assert.assertTrue(isScope); } boolean isScope = AclUtils.isScope(-1); Assert.assertFalse(isScope); isScope = AclUtils.isScope(256); Assert.assertFalse(isScope); // IPv6 test int min = Integer.parseInt("0", 16); int max = Integer.parseInt("ffff", 16); for (int i = min; i < max + 1; i++) { isScope = AclUtils.isIPv6Scope(i); Assert.assertTrue(isScope); } isScope = AclUtils.isIPv6Scope(-1); Assert.assertFalse(isScope); isScope = AclUtils.isIPv6Scope(max + 1); Assert.assertFalse(isScope); } @Test public void testIsAsterisk() { boolean isAsterisk = AclUtils.isAsterisk("*"); Assert.assertTrue(isAsterisk); isAsterisk = AclUtils.isAsterisk(","); Assert.assertFalse(isAsterisk); } @Test public void testIsComma() { boolean isColon = AclUtils.isComma(","); Assert.assertTrue(isColon); isColon = AclUtils.isComma("-"); Assert.assertFalse(isColon); } @Test public void testIsMinus() { boolean isMinus = AclUtils.isMinus("-"); Assert.assertTrue(isMinus); isMinus = AclUtils.isMinus("*"); Assert.assertFalse(isMinus); } @Test public void testV6ipProcess() { String remoteAddr = "5::7:6:1-200:*"; Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0007:0006"); remoteAddr = "5::7:6:1-200"; Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0000:0007:0006"); remoteAddr = "5::7:6:*"; Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0000:0007:0006"); remoteAddr = "5:7:6:*"; Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0007:0006"); } @Test public void testExpandIP() { Assert.assertEquals(AclUtils.expandIP("::", 8), "0000:0000:0000:0000:0000:0000:0000:0000"); Assert.assertEquals(AclUtils.expandIP("::1", 8), "0000:0000:0000:0000:0000:0000:0000:0001"); Assert.assertEquals(AclUtils.expandIP("3::", 8), "0003:0000:0000:0000:0000:0000:0000:0000"); Assert.assertEquals(AclUtils.expandIP("2::2", 8), "0002:0000:0000:0000:0000:0000:0000:0002"); Assert.assertEquals(AclUtils.expandIP("4::aac4:92", 8), "0004:0000:0000:0000:0000:0000:AAC4:0092"); Assert.assertEquals(AclUtils.expandIP("ab23:56:901a::cc6:765:bb:9011", 8), "AB23:0056:901A:0000:0CC6:0765:00BB:9011"); Assert.assertEquals(AclUtils.expandIP("ab23:56:901a:1:cc6:765:bb:9011", 8), "AB23:0056:901A:0001:0CC6:0765:00BB:9011"); Assert.assertEquals(AclUtils.expandIP("5::7:6", 6), "0005:0000:0000:0000:0007:0006"); } private static String randomTmpFile() { String tmpFileName = System.getProperty("java.io.tmpdir"); // https://rationalpi.wordpress.com/2007/01/26/javaiotmpdir-inconsitency/ if (!tmpFileName.endsWith(File.separator)) { tmpFileName += File.separator; } tmpFileName += UUID.randomUUID() + ".yml"; return tmpFileName; } @Test public void getYamlDataIgnoreFileNotFoundExceptionTest() { JSONObject yamlDataObject = AclUtils.getYamlDataObject("plain_acl.yml", JSONObject.class); Assert.assertNull(yamlDataObject); } @Test public void getAclRPCHookTest() throws IOException { try (InputStream is = AclUtilsTest.class.getClassLoader().getResourceAsStream("conf/plain_acl_incomplete.yml")) { RPCHook incompleteContRPCHook = AclUtils.getAclRPCHook(is); Assert.assertNull(incompleteContRPCHook); } } @Test public void testGetAclRPCHookByFileName() { // Skip this test if running in Bazel, as the resource path is a path inside the JAR. Assume.assumeTrue(System.getProperty("build.bazel") == null); RPCHook actual = AclUtils.getAclRPCHook(Objects.requireNonNull(AclUtilsTest.class.getResource("/acl_hook/plain_acl.yml")).getPath()); assertNotNull(actual); assertTrue(actual instanceof AclClientRPCHook); assertAclClientRPCHook((AclClientRPCHook) actual); } @Test public void testGetAclRPCHookByInputStream() { RPCHook actual = AclUtils.getAclRPCHook(Objects.requireNonNull(AclUtilsTest.class.getResourceAsStream("/acl_hook/plain_acl.yml"))); assertNotNull(actual); assertTrue(actual instanceof AclClientRPCHook); assertAclClientRPCHook((AclClientRPCHook) actual); } private void assertAclClientRPCHook(final AclClientRPCHook actual) { assertEquals("rocketmq2", actual.getSessionCredentials().getAccessKey()); assertEquals("12345678", actual.getSessionCredentials().getSecretKey()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.junit.Assert; import org.junit.Test; public class PermissionTest { @Test public void fromStringGetPermissionTest() { byte perm = Permission.parsePermFromString("PUB"); Assert.assertEquals(perm, Permission.PUB); perm = Permission.parsePermFromString("SUB"); Assert.assertEquals(perm, Permission.SUB); perm = Permission.parsePermFromString("PUB|SUB"); Assert.assertEquals(perm, Permission.PUB | Permission.SUB); perm = Permission.parsePermFromString("SUB|PUB"); Assert.assertEquals(perm, Permission.PUB | Permission.SUB); perm = Permission.parsePermFromString("DENY"); Assert.assertEquals(perm, Permission.DENY); perm = Permission.parsePermFromString("1"); Assert.assertEquals(perm, Permission.DENY); perm = Permission.parsePermFromString(null); Assert.assertEquals(perm, Permission.DENY); } @Test public void AclExceptionTest() { AclException aclException = new AclException("CAL_SIGNATURE_FAILED",10015); AclException aclExceptionWithMessage = new AclException("CAL_SIGNATURE_FAILED",10015,"CAL_SIGNATURE_FAILED Exception"); Assert.assertEquals(aclException.getCode(),10015); Assert.assertEquals(aclExceptionWithMessage.getStatus(),"CAL_SIGNATURE_FAILED"); aclException.setCode(10016); Assert.assertEquals(aclException.getCode(),10016); aclException.setStatus("netAddress examine scope Exception netAddress"); Assert.assertEquals(aclException.getStatus(),"netAddress examine scope Exception netAddress"); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.acl.common; import org.junit.Assert; import org.junit.Test; import java.util.Properties; public class SessionCredentialsTest { @Test public void equalsTest() { SessionCredentials sessionCredentials = new SessionCredentials("RocketMQ","12345678"); sessionCredentials.setSecurityToken("abcd"); SessionCredentials other = new SessionCredentials("RocketMQ","12345678","abcd"); Assert.assertTrue(sessionCredentials.equals(other)); } @Test public void updateContentTest() { SessionCredentials sessionCredentials = new SessionCredentials(); Properties properties = new Properties(); properties.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ"); properties.setProperty(SessionCredentials.SECRET_KEY,"12345678"); properties.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd"); sessionCredentials.updateContent(properties); } @Test public void SessionCredentialHashCodeTest() { SessionCredentials sessionCredentials = new SessionCredentials(); Properties properties = new Properties(); properties.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ"); properties.setProperty(SessionCredentials.SECRET_KEY,"12345678"); properties.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd"); sessionCredentials.updateContent(properties); Assert.assertEquals(sessionCredentials.hashCode(),353652211); } @Test public void SessionCredentialEqualsTest() { SessionCredentials sessionCredential1 = new SessionCredentials(); Properties properties1 = new Properties(); properties1.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ"); properties1.setProperty(SessionCredentials.SECRET_KEY,"12345678"); properties1.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd"); sessionCredential1.updateContent(properties1); SessionCredentials sessionCredential2 = new SessionCredentials(); Properties properties2 = new Properties(); properties2.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ"); properties2.setProperty(SessionCredentials.SECRET_KEY,"12345678"); properties2.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd"); sessionCredential2.updateContent(properties2); Assert.assertTrue(sessionCredential2.equals(sessionCredential1)); sessionCredential2.setSecretKey("1234567899"); sessionCredential2.setSignature("1234567899"); Assert.assertFalse(sessionCredential2.equals(sessionCredential1)); } @Test public void SessionCredentialToStringTest() { SessionCredentials sessionCredential1 = new SessionCredentials(); Properties properties1 = new Properties(); properties1.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ"); properties1.setProperty(SessionCredentials.SECRET_KEY,"12345678"); properties1.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd"); sessionCredential1.updateContent(properties1); Assert.assertEquals(sessionCredential1.toString(), "SessionCredentials [accessKey=RocketMQ, secretKey=12345678, signature=null, SecurityToken=abcd]"); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/ClientConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.util.Collection; import java.util.Collections; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class ClientConfigTest { private ClientConfig clientConfig; private final String resource = "resource"; @Before public void init() { clientConfig = createClientConfig(); } @Test public void testWithNamespace() { Set resources = clientConfig.withNamespace(Collections.singleton(resource)); assertTrue(resources.contains("lmq%resource")); } @Test public void testWithoutNamespace() { String actual = clientConfig.withoutNamespace(resource); assertEquals(resource, actual); Set resources = clientConfig.withoutNamespace(Collections.singleton(resource)); assertTrue(resources.contains(resource)); } @Test public void testQueuesWithNamespace() { MessageQueue messageQueue = new MessageQueue(); messageQueue.setTopic("defaultTopic"); Collection messageQueues = clientConfig.queuesWithNamespace(Collections.singleton(messageQueue)); assertTrue(messageQueues.contains(messageQueue)); assertEquals("lmq%defaultTopic", messageQueues.iterator().next().getTopic()); } private ClientConfig createClientConfig() { ClientConfig result = new ClientConfig(); result.setUnitName("unitName"); result.setClientIP("127.0.0.1"); result.setClientCallbackExecutorThreads(1); result.setPollNameServerInterval(1000 * 30); result.setHeartbeatBrokerInterval(1000 * 30); result.setPersistConsumerOffsetInterval(1000 * 5); result.setPullTimeDelayMillsWhenException(1000); result.setUnitMode(true); result.setSocksProxyConfig("{}"); result.setLanguage(LanguageCode.JAVA); result.setDecodeReadBody(true); result.setDecodeDecompressBody(true); result.setAccessChannel(AccessChannel.LOCAL); result.setMqClientApiTimeout(1000 * 3); result.setEnableStreamRequestType(true); result.setSendLatencyEnable(true); result.setEnableHeartbeatChannelEventListener(true); result.setDetectTimeout(200); result.setDetectInterval(1000 * 2); result.setUseHeartbeatV2(false); result.buildMQClientId(); result.setNamespace("lmq"); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/ValidatorsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client; import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.junit.Assert.fail; public class ValidatorsTest { @Test public void testGroupNameBlank() { try { Validators.checkGroup(null); fail("excepted MQClientException for group name is blank"); } catch (MQClientException e) { assertThat(e.getErrorMessage()).isEqualTo("the specified group is blank"); } } @Test public void testCheckTopic_Success() throws MQClientException { Validators.checkTopic("Hello"); Validators.checkTopic("%RETRY%Hello"); Validators.checkTopic("_%RETRY%Hello"); Validators.checkTopic("-%RETRY%Hello"); Validators.checkTopic("223-%RETRY%Hello"); } @Test public void testCheckTopic_HasIllegalCharacters() { String illegalTopic = "TOPIC&*^"; try { Validators.checkTopic(illegalTopic); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageStartingWith(String.format("The specified topic[%s] contains illegal characters, allowing only %s", illegalTopic, "^[%|a-zA-Z0-9_-]+$")); } } @Test public void testCheckTopic_BlankTopic() { String blankTopic = ""; try { Validators.checkTopic(blankTopic); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageStartingWith("The specified topic is blank"); } } @Test public void testCheckTopic_TooLongTopic() { String tooLongTopic = StringUtils.rightPad("TooLongTopic", Validators.TOPIC_MAX_LENGTH + 1, "_"); assertThat(tooLongTopic.length()).isGreaterThan(Validators.TOPIC_MAX_LENGTH); try { Validators.checkTopic(tooLongTopic); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageStartingWith("The specified topic is longer than topic max length"); } } @Test public void testIsSystemTopic() { for (String topic : TopicValidator.getSystemTopicSet()) { try { Validators.isSystemTopic(topic); fail("excepted MQClientException for system topic"); } catch (MQClientException e) { assertThat(e.getResponseCode()).isEqualTo(-1); assertThat(e.getErrorMessage()).isEqualTo(String.format("The topic[%s] is conflict with system topic.", topic)); } } } @Test public void testIsNotAllowedSendTopic() { for (String topic : TopicValidator.getNotAllowedSendTopicSet()) { try { Validators.isNotAllowedSendTopic(topic); fail("excepted MQClientException for blacklist topic"); } catch (MQClientException e) { assertThat(e.getResponseCode()).isEqualTo(-1); assertThat(e.getErrorMessage()).isEqualTo(String.format("Sending message to topic[%s] is forbidden.", topic)); } } } @Test public void testTopicConfigValid() throws MQClientException { TopicConfig topicConfig = new TopicConfig(); topicConfig.setPerm(PermName.PERM_INHERIT | PermName.PERM_WRITE | PermName.PERM_READ); Validators.checkTopicConfig(topicConfig); topicConfig.setPerm(PermName.PERM_WRITE | PermName.PERM_READ); Validators.checkTopicConfig(topicConfig); topicConfig.setPerm(PermName.PERM_READ); Validators.checkTopicConfig(topicConfig); try { topicConfig.setPerm(PermName.PERM_PRIORITY); Validators.checkTopicConfig(topicConfig); } catch (MQClientException e) { assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(e.getErrorMessage()).isEqualTo(String.format("topicPermission value: %s is invalid.", topicConfig.getPerm())); } try { topicConfig.setPerm(PermName.PERM_PRIORITY | PermName.PERM_WRITE); Validators.checkTopicConfig(topicConfig); } catch (MQClientException e) { assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(e.getErrorMessage()).isEqualTo(String.format("topicPermission value: %s is invalid.", topicConfig.getPerm())); } } @Test public void testBrokerConfigValid() throws MQClientException { Properties brokerConfig = new Properties(); brokerConfig.setProperty("brokerPermission", String.valueOf(PermName.PERM_INHERIT | PermName.PERM_WRITE | PermName.PERM_READ)); Validators.checkBrokerConfig(brokerConfig); brokerConfig.setProperty("brokerPermission", String.valueOf(PermName.PERM_WRITE | PermName.PERM_READ)); Validators.checkBrokerConfig(brokerConfig); brokerConfig.setProperty("brokerPermission", String.valueOf(PermName.PERM_READ)); Validators.checkBrokerConfig(brokerConfig); try { brokerConfig.setProperty("brokerPermission", String.valueOf(PermName.PERM_PRIORITY)); Validators.checkBrokerConfig(brokerConfig); } catch (MQClientException e) { assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(e.getErrorMessage()).isEqualTo(String.format("brokerPermission value: %s is invalid.", brokerConfig.getProperty("brokerPermission"))); } try { brokerConfig.setProperty("brokerPermission", String.valueOf(PermName.PERM_PRIORITY | PermName.PERM_INHERIT)); Validators.checkBrokerConfig(brokerConfig); } catch (MQClientException e) { assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(e.getErrorMessage()).isEqualTo(String.format("brokerPermission value: %s is invalid.", brokerConfig.getProperty("brokerPermission"))); } } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/common/ThreadLocalIndexTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.common; import java.lang.reflect.Field; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ThreadLocalIndexTest { @Test public void testIncrementAndGet() throws Exception { ThreadLocalIndex localIndex = new ThreadLocalIndex(); int initialVal = localIndex.incrementAndGet(); assertThat(localIndex.incrementAndGet()).isEqualTo(initialVal + 1); } @Test public void testIncrementAndGet2() throws Exception { ThreadLocalIndex localIndex = new ThreadLocalIndex(); int initialVal = localIndex.incrementAndGet(); assertThat(initialVal >= 0).isTrue(); } @Test public void testIncrementAndGet3() throws Exception { ThreadLocalIndex localIndex = new ThreadLocalIndex(); Field threadLocalIndexField = ThreadLocalIndex.class.getDeclaredField("threadLocalIndex"); ThreadLocal mockThreadLocal = new ThreadLocal<>(); mockThreadLocal.set(Integer.MAX_VALUE); threadLocalIndexField.setAccessible(true); threadLocalIndexField.set(localIndex, mockThreadLocal); int initialVal = localIndex.incrementAndGet(); assertThat(initialVal >= 0).isTrue(); } @Test public void testResultOfResetIsGreaterThanOrEqualToZero() { ThreadLocalIndex localIndex = new ThreadLocalIndex(); localIndex.reset(); assertThat(localIndex.incrementAndGet() > 0).isTrue(); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.AssignedMessageQueue; import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl; import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; import org.apache.rocketmq.client.impl.consumer.RebalanceService; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class DefaultLitePullConsumerTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); private MQClientInstance mqClientInstance; @Mock private MQClientAPIImpl mQClientAPIImpl; @Mock private MQAdminImpl mQAdminImpl; @Mock private AssignedMessageQueue assignedMQ; private RebalanceImpl rebalanceImpl; private OffsetStore offsetStore; private DefaultLitePullConsumerImpl litePullConsumerImpl; private String consumerGroup = "LitePullConsumerGroup"; private String topic = "LitePullConsumerTest"; private String brokerName = "BrokerA"; private boolean flag = false; @BeforeClass public static void setEnv() { System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); } @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); factoryTable.forEach((s, instance) -> instance.shutdown()); factoryTable.clear(); Field field = MQClientInstance.class.getDeclaredField("rebalanceService"); field.setAccessible(true); RebalanceService rebalanceService = (RebalanceService) field.get(mQClientFactory); field = RebalanceService.class.getDeclaredField("waitInterval"); field.setAccessible(true); field.set(rebalanceService, 100); field = DefaultLitePullConsumerImpl.class.getDeclaredField("doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged"); field.setAccessible(true); field.set(null, true); } @After public void destroy() { if (mqClientInstance != null) { mqClientInstance.unregisterConsumer(litePullConsumerImpl.groupName()); mqClientInstance.shutdown(); } } @Test public void testAssign_PollMessageSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.assign(Collections.singletonList(messageQueue)); List result = litePullConsumer.poll(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testSubscribeWithListener_PollMessageSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createSubscribeLitePullConsumerWithListener(); try { Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); List result = litePullConsumer.poll(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); Set assignment = litePullConsumer.assignment(); assertThat(assignment.stream().findFirst().get()).isEqualTo(messageQueueSet.stream().findFirst().get()); } finally { litePullConsumer.shutdown(); } } @Test public void testAssign_PollMessageWithTagSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumerWithTag(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.assign(Collections.singletonList(messageQueue)); List result = litePullConsumer.poll(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getTags()).isEqualTo("tagA"); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testConsumerCommitSyncWithMQOffset() throws Exception { DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { RemoteBrokerOffsetStore store = new RemoteBrokerOffsetStore(mQClientFactory, consumerGroup); litePullConsumer.setOffsetStore(store); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); //replace with real offsetStore. Field offsetStore = litePullConsumerImpl.getClass().getDeclaredField("offsetStore"); offsetStore.setAccessible(true); offsetStore.set(litePullConsumerImpl, store); MessageQueue messageQueue = createMessageQueue(); HashSet set = new HashSet<>(); set.add(messageQueue); //mock assign and reset offset litePullConsumer.assign(set); litePullConsumer.seek(messageQueue, 0); await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(litePullConsumer.committed(messageQueue)).isEqualTo(0)); //commit offset 1 Map commitOffset = new HashMap<>(); commitOffset.put(messageQueue, 1L); litePullConsumer.commit(commitOffset, true); assertThat(litePullConsumer.committed(messageQueue)).isEqualTo(1); } finally { litePullConsumer.shutdown(); } } @Test public void testSubscribe_PollMessageSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createSubscribeLitePullConsumer(); try { Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); List result = litePullConsumer.poll(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testSubscribe_BroadcastPollMessageSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createBroadcastLitePullConsumer(); try { Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); List result = litePullConsumer.poll(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testSubscriptionType_AssignAndSubscribeExclusive() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); try { litePullConsumer.subscribe(topic, "*"); litePullConsumer.assign(Collections.singletonList(createMessageQueue())); failBecauseExceptionWasNotThrown(IllegalStateException.class); } catch (IllegalStateException e) { assertThat(e).hasMessageContaining("Subscribe and assign are mutually exclusive."); } finally { litePullConsumer.shutdown(); } } @Test public void testFetchMessageQueues_FetchMessageQueuesBeforeStart() throws Exception { DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { litePullConsumer.fetchMessageQueues(topic); failBecauseExceptionWasNotThrown(IllegalStateException.class); } catch (IllegalStateException e) { assertThat(e).hasMessageContaining("The consumer not running, please start it first."); } finally { litePullConsumer.shutdown(); } } @Test public void testSeek_SeekOffsetSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); when(mQAdminImpl.minOffset(any(MessageQueue.class))).thenReturn(0L); when(mQAdminImpl.maxOffset(any(MessageQueue.class))).thenReturn(500L); MessageQueue messageQueue = createMessageQueue(); List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); litePullConsumer.pause(Collections.singletonList(messageQueue)); long offset = litePullConsumer.committed(messageQueue); litePullConsumer.seek(messageQueue, offset); Field field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); AssignedMessageQueue assignedMessageQueue = (AssignedMessageQueue) field.get(litePullConsumerImpl); assertEquals(assignedMessageQueue.getSeekOffset(messageQueue), offset); litePullConsumer.shutdown(); } @Test public void testSeek_SeekToBegin() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); when(mQAdminImpl.minOffset(any(MessageQueue.class))).thenReturn(0L); when(mQAdminImpl.maxOffset(any(MessageQueue.class))).thenReturn(500L); MessageQueue messageQueue = createMessageQueue(); List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); litePullConsumer.pause(Collections.singletonList(messageQueue)); litePullConsumer.seekToBegin(messageQueue); Field field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); AssignedMessageQueue assignedMessageQueue = (AssignedMessageQueue) field.get(litePullConsumerImpl); assertEquals(assignedMessageQueue.getSeekOffset(messageQueue), 0L); litePullConsumer.shutdown(); } @Test public void testSeek_SeekToEnd() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); when(mQAdminImpl.minOffset(any(MessageQueue.class))).thenReturn(0L); when(mQAdminImpl.maxOffset(any(MessageQueue.class))).thenReturn(500L); MessageQueue messageQueue = createMessageQueue(); List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); litePullConsumer.pause(Collections.singletonList(messageQueue)); litePullConsumer.seekToEnd(messageQueue); Field field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); AssignedMessageQueue assignedMessageQueue = (AssignedMessageQueue) field.get(litePullConsumerImpl); assertEquals(assignedMessageQueue.getSeekOffset(messageQueue), 500L); litePullConsumer.shutdown(); } @Test public void testSeek_SeekOffsetIllegal() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); when(mQAdminImpl.minOffset(any(MessageQueue.class))).thenReturn(0L); when(mQAdminImpl.maxOffset(any(MessageQueue.class))).thenReturn(100L); MessageQueue messageQueue = createMessageQueue(); List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); litePullConsumer.pause(Collections.singletonList(messageQueue)); try { litePullConsumer.seek(messageQueue, -1); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("min offset = 0"); } try { litePullConsumer.seek(messageQueue, 1000); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("max offset = 100"); } litePullConsumer.shutdown(); } @Test public void testSeek_MessageQueueNotInAssignList() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); try { litePullConsumer.seek(createMessageQueue(), 0); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("The message queue is not in assigned list"); } finally { litePullConsumer.shutdown(); } litePullConsumer = createSubscribeLitePullConsumer(); try { litePullConsumer.seek(createMessageQueue(), 0); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("The message queue is not in assigned list, may be rebalancing"); } finally { litePullConsumer.shutdown(); } } @Test public void testOffsetForTimestamp_FailedAndSuccess() throws Exception { MessageQueue messageQueue = createMessageQueue(); DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { litePullConsumer.offsetForTimestamp(messageQueue, 123456L); failBecauseExceptionWasNotThrown(IllegalStateException.class); } catch (IllegalStateException e) { assertThat(e).hasMessageContaining("The consumer not running, please start it first."); } finally { litePullConsumer.shutdown(); } doReturn(123L).when(mQAdminImpl).searchOffset(any(MessageQueue.class), anyLong()); litePullConsumer = createStartLitePullConsumer(); try { long offset = litePullConsumer.offsetForTimestamp(messageQueue, 123456L); assertThat(offset).isEqualTo(123L); } finally { litePullConsumer.shutdown(); } } @Test public void testPauseAndResume_Success() throws Exception { DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.assign(Collections.singletonList(messageQueue)); litePullConsumer.pause(Collections.singletonList(messageQueue)); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); List result = litePullConsumer.poll(); assertThat(result.isEmpty()).isTrue(); litePullConsumer.resume(Collections.singletonList(messageQueue)); result = litePullConsumer.poll(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testPullTaskImpl_ProcessQueueNull() throws Exception { DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.assign(Collections.singletonList(messageQueue)); Field field = DefaultLitePullConsumer.class.getDeclaredField("defaultLitePullConsumerImpl"); field.setAccessible(true); // set ProcessQueue dropped = true DefaultLitePullConsumerImpl localLitePullConsumerImpl = (DefaultLitePullConsumerImpl) field.get(litePullConsumer); field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); when(assignedMQ.isPaused(any(MessageQueue.class))).thenReturn(false); when(assignedMQ.getProcessQueue(any(MessageQueue.class))).thenReturn(null); litePullConsumer.start(); field.set(localLitePullConsumerImpl, assignedMQ); List result = litePullConsumer.poll(100); assertThat(result.isEmpty()).isTrue(); } finally { litePullConsumer.shutdown(); } } @Test public void testPullTaskImpl_ProcessQueueDropped() throws Exception { DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.assign(Collections.singletonList(messageQueue)); Field field = DefaultLitePullConsumer.class.getDeclaredField("defaultLitePullConsumerImpl"); field.setAccessible(true); // set ProcessQueue dropped = true DefaultLitePullConsumerImpl localLitePullConsumerImpl = (DefaultLitePullConsumerImpl) field.get(litePullConsumer); field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); AssignedMessageQueue assignedMessageQueue = (AssignedMessageQueue) field.get(localLitePullConsumerImpl); assignedMessageQueue.getProcessQueue(messageQueue).setDropped(true); litePullConsumer.start(); List result = litePullConsumer.poll(100); assertThat(result.isEmpty()).isTrue(); } finally { litePullConsumer.shutdown(); } } @Test public void testRegisterTopicMessageQueueChangeListener_Success() throws Exception { flag = false; DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); try { doReturn(Collections.emptySet()).when(mQAdminImpl).fetchSubscribeMessageQueues(anyString()); litePullConsumer.setTopicMetadataCheckIntervalMillis(10); litePullConsumer.registerTopicMessageQueueChangeListener(topic, new TopicMessageQueueChangeListener() { @Override public void onChanged(String topic, Set messageQueues) { flag = true; } }); Set set = new HashSet<>(); set.add(createMessageQueue()); doReturn(set).when(mQAdminImpl).fetchSubscribeMessageQueues(anyString()); Thread.sleep(11 * 1000); assertThat(flag).isTrue(); } finally { litePullConsumer.shutdown(); } } @Test public void testFlowControl_Success() throws Exception { DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.setPullThresholdForAll(-1); litePullConsumer.assign(Collections.singletonList(messageQueue)); litePullConsumer.setPollTimeoutMillis(500); List result = litePullConsumer.poll(); assertThat(result).isEmpty(); } finally { litePullConsumer.shutdown(); } litePullConsumer = createStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.setPullThresholdForQueue(-1); litePullConsumer.assign(Collections.singletonList(messageQueue)); litePullConsumer.setPollTimeoutMillis(500); List result = litePullConsumer.poll(); assertThat(result).isEmpty(); } finally { litePullConsumer.shutdown(); } litePullConsumer = createStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.setPullThresholdSizeForQueue(-1); litePullConsumer.assign(Collections.singletonList(messageQueue)); litePullConsumer.setPollTimeoutMillis(500); List result = litePullConsumer.poll(); assertThat(result).isEmpty(); } finally { litePullConsumer.shutdown(); } litePullConsumer = createStartLitePullConsumer(); try { MessageQueue messageQueue = createMessageQueue(); litePullConsumer.setConsumeMaxSpan(-1); litePullConsumer.assign(Collections.singletonList(messageQueue)); litePullConsumer.setPollTimeoutMillis(500); List result = litePullConsumer.poll(); assertThat(result).isEmpty(); } finally { litePullConsumer.shutdown(); } } @Test public void testCheckConfig_Exception() { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(MixAll.DEFAULT_CONSUMER_GROUP); try { litePullConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("consumerGroup can not equal"); } finally { litePullConsumer.shutdown(); } litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setMessageModel(null); try { litePullConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("messageModel is null"); } finally { litePullConsumer.shutdown(); } litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setAllocateMessageQueueStrategy(null); try { litePullConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("allocateMessageQueueStrategy is null"); } finally { litePullConsumer.shutdown(); } litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setConsumerTimeoutMillisWhenSuspend(1); try { litePullConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("Long polling mode, the consumer consumerTimeoutMillisWhenSuspend must greater than brokerSuspendMaxTimeMillis"); } finally { litePullConsumer.shutdown(); } } @Test public void testComputePullFromWhereReturnedNotFound() throws Exception { DefaultLitePullConsumer defaultLitePullConsumer = createStartLitePullConsumer(); try { defaultLitePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); MessageQueue messageQueue = createMessageQueue(); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); long offset = rebalanceImpl.computePullFromWhere(messageQueue); assertThat(offset).isEqualTo(0); } finally { defaultLitePullConsumer.shutdown(); } } @Test public void testComputePullFromWhereReturned() throws Exception { DefaultLitePullConsumer defaultLitePullConsumer = createStartLitePullConsumer(); defaultLitePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); MessageQueue messageQueue = createMessageQueue(); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(100L); long offset = rebalanceImpl.computePullFromWhere(messageQueue); assertThat(offset).isEqualTo(100); defaultLitePullConsumer.shutdown(); } @Test public void testComputePullFromLast() throws Exception { DefaultLitePullConsumer defaultLitePullConsumer = createStartLitePullConsumer(); defaultLitePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); MessageQueue messageQueue = createMessageQueue(); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); when(mQClientFactory.getMQAdminImpl().maxOffset(any(MessageQueue.class))).thenReturn(100L); long offset = rebalanceImpl.computePullFromWhere(messageQueue); assertThat(offset).isEqualTo(100); defaultLitePullConsumer.shutdown(); } @Test public void testComputePullByTimeStamp() throws Exception { DefaultLitePullConsumer defaultLitePullConsumer = createStartLitePullConsumer(); try { defaultLitePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); defaultLitePullConsumer.setConsumeTimestamp("20191024171201"); MessageQueue messageQueue = createMessageQueue(); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); when(mQClientFactory.getMQAdminImpl().searchOffset(any(MessageQueue.class), anyLong())).thenReturn(100L); long offset = rebalanceImpl.computePullFromWhere(messageQueue); assertThat(offset).isEqualTo(100); } finally { defaultLitePullConsumer.shutdown(); } } @Test public void testConsumerAfterShutdown() throws Exception { DefaultLitePullConsumer defaultLitePullConsumer = createSubscribeLitePullConsumer(); new AsyncConsumer().executeAsync(defaultLitePullConsumer); Thread.sleep(100); defaultLitePullConsumer.shutdown(); assertThat(defaultLitePullConsumer.isRunning()).isFalse(); } @Test public void testConsumerCommitWithMQ() throws Exception { DefaultLitePullConsumer litePullConsumer = createNotStartLitePullConsumer(); try { RemoteBrokerOffsetStore store = new RemoteBrokerOffsetStore(mQClientFactory, consumerGroup); litePullConsumer.setOffsetStore(store); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); //replace with real offsetStore. Field offsetStore = litePullConsumerImpl.getClass().getDeclaredField("offsetStore"); offsetStore.setAccessible(true); offsetStore.set(litePullConsumerImpl, store); MessageQueue messageQueue = createMessageQueue(); HashSet set = new HashSet<>(); set.add(messageQueue); //mock assign and reset offset litePullConsumer.assign(set); litePullConsumer.seek(messageQueue, 0); //commit litePullConsumer.commit(set, true); assertThat(litePullConsumer.committed(messageQueue)).isEqualTo(0); } finally { litePullConsumer.shutdown(); } } static class AsyncConsumer { public void executeAsync(final DefaultLitePullConsumer consumer) { new Thread(() -> { while (consumer.isRunning()) { consumer.poll(2 * 1000); } }).start(); } } private void initDefaultLitePullConsumer(DefaultLitePullConsumer litePullConsumer) throws Exception { Field field = DefaultLitePullConsumer.class.getDeclaredField("defaultLitePullConsumerImpl"); field.setAccessible(true); litePullConsumerImpl = (DefaultLitePullConsumerImpl) field.get(litePullConsumer); field = DefaultLitePullConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); mqClientInstance = (MQClientInstance) field.get(litePullConsumerImpl); field.set(litePullConsumerImpl, mQClientFactory); PullAPIWrapper pullAPIWrapper = litePullConsumerImpl.getPullAPIWrapper(); field = PullAPIWrapper.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pullAPIWrapper, mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); field = MQClientInstance.class.getDeclaredField("mQAdminImpl"); field.setAccessible(true); field.set(mQClientFactory, mQAdminImpl); field = DefaultLitePullConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); rebalanceImpl = (RebalanceImpl) field.get(litePullConsumerImpl); field = RebalanceImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(rebalanceImpl, mQClientFactory); offsetStore = spy(litePullConsumerImpl.getOffsetStore()); field = DefaultLitePullConsumerImpl.class.getDeclaredField("offsetStore"); field.setAccessible(true); field.set(litePullConsumerImpl, offsetStore); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); return pullResult; } }); doAnswer(x -> new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); doReturn(Collections.singletonList(mQClientFactory.getClientId())).when(mQClientFactory).findConsumerIdList(anyString(), anyString()); doReturn(123L).when(offsetStore).readOffset(any(MessageQueue.class), any(ReadOffsetType.class)); } private void initDefaultLitePullConsumerWithTag(DefaultLitePullConsumer litePullConsumer) throws Exception { Field field = DefaultLitePullConsumer.class.getDeclaredField("defaultLitePullConsumerImpl"); field.setAccessible(true); litePullConsumerImpl = (DefaultLitePullConsumerImpl) field.get(litePullConsumer); field = DefaultLitePullConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(litePullConsumerImpl, mQClientFactory); PullAPIWrapper pullAPIWrapper = litePullConsumerImpl.getPullAPIWrapper(); field = PullAPIWrapper.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pullAPIWrapper, mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); field = MQClientInstance.class.getDeclaredField("mQAdminImpl"); field.setAccessible(true); field.set(mQClientFactory, mQAdminImpl); field = DefaultLitePullConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); rebalanceImpl = (RebalanceImpl) field.get(litePullConsumerImpl); field = RebalanceImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(rebalanceImpl, mQClientFactory); offsetStore = spy(litePullConsumerImpl.getOffsetStore()); field = DefaultLitePullConsumerImpl.class.getDeclaredField("offsetStore"); field.setAccessible(true); field.set(litePullConsumerImpl, offsetStore); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setTags("tagA"); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); return pullResult; } }); when(mQClientFactory.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(new FindBrokerResult("127.0.0.1:10911", false)); doReturn(123L).when(offsetStore).readOffset(any(MessageQueue.class), any(ReadOffsetType.class)); } private DefaultLitePullConsumer createSubscribeLitePullConsumer() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.subscribe(topic, "*"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); return litePullConsumer; } private DefaultLitePullConsumer createSubscribeLitePullConsumerWithListener() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.subscribe(topic, "*", new MessageQueueListener() { @Override public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) { assertThat(mqAll.stream().findFirst().get().getTopic()).isEqualTo(mqDivided.stream().findFirst().get().getTopic()); assertThat(mqAll.stream().findFirst().get().getBrokerName()).isEqualTo(mqDivided.stream().findFirst().get().getBrokerName()); assertThat(mqAll.stream().findFirst().get().getQueueId()).isEqualTo(mqDivided.stream().findFirst().get().getQueueId()); } }); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); return litePullConsumer; } private DefaultLitePullConsumer createStartLitePullConsumer() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); return litePullConsumer; } private DefaultLitePullConsumer createStartLitePullConsumerWithTag() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.setSubExpressionForAssign(topic, "tagA"); litePullConsumer.start(); initDefaultLitePullConsumerWithTag(litePullConsumer); return litePullConsumer; } private DefaultLitePullConsumer createNotStartLitePullConsumer() { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); return litePullConsumer; } private DefaultLitePullConsumer createBroadcastLitePullConsumer() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.setMessageModel(MessageModel.BROADCASTING); litePullConsumer.subscribe(topic, "*"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); return litePullConsumer; } private MessageQueue createMessageQueue() { MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); return messageQueue; } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } private void suppressUpdateTopicRouteInfoFromNameServer( DefaultLitePullConsumer litePullConsumer) throws IllegalAccessException { if (litePullConsumer.getMessageModel() == MessageModel.CLUSTERING) { litePullConsumer.changeInstanceNameToPID(); } ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); factoryTable.put(litePullConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); doNothing().when(mQClientFactory).updateTopicRouteInfoFromNameServer(); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQPullConsumerTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); @Mock private MQClientAPIImpl mQClientAPIImpl; private DefaultMQPullConsumer pullConsumer; private String consumerGroup = "FooBarGroup"; private String topic = "FooBar"; private String brokerName = "BrokerA"; @Before public void init() throws Exception { pullConsumer = new DefaultMQPullConsumer(consumerGroup); pullConsumer.setNamesrvAddr("127.0.0.1:9876"); pullConsumer.start(); PullAPIWrapper pullAPIWrapper = pullConsumer.getDefaultMQPullConsumerImpl().getPullAPIWrapper(); Field field = PullAPIWrapper.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pullAPIWrapper, mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); when(mQClientFactory.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(new FindBrokerResult("127.0.0.1:10911", false)); } @After public void terminate() { pullConsumer.shutdown(); } @Test public void testStart_OffsetShouldNotNUllAfterStart() { Assert.assertNotNull(pullConsumer.getOffsetStore()); } @Test public void testPullMessage_Success() throws Exception { doAnswer(new Answer() { @Override public Object answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); return createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(new MessageExt())); } }).when(mQClientAPIImpl).pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class)); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 0); PullResult pullResult = pullConsumer.pull(messageQueue, "*", 1024, 3); assertThat(pullResult).isNotNull(); assertThat(pullResult.getPullStatus()).isEqualTo(PullStatus.FOUND); assertThat(pullResult.getNextBeginOffset()).isEqualTo(1024 + 1); assertThat(pullResult.getMinOffset()).isEqualTo(123); assertThat(pullResult.getMaxOffset()).isEqualTo(2048); assertThat(pullResult.getMsgFoundList()).isEqualTo(new ArrayList<>()); } @Test public void testPullMessage_NotFound() throws Exception { doAnswer(new Answer() { @Override public Object answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); return createPullResult(requestHeader, PullStatus.NO_NEW_MSG, new ArrayList<>()); } }).when(mQClientAPIImpl).pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class)); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 0); PullResult pullResult = pullConsumer.pull(messageQueue, "*", 1024, 3); assertThat(pullResult.getPullStatus()).isEqualTo(PullStatus.NO_NEW_MSG); } @Test public void testPullMessageAsync_Success() throws Exception { doAnswer(new Answer() { @Override public Object answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(new MessageExt())); PullCallback pullCallback = mock.getArgument(4); pullCallback.onSuccess(pullResult); return null; } }).when(mQClientAPIImpl).pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class)); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 0); pullConsumer.pull(messageQueue, "*", 1024, 3, new PullCallback() { @Override public void onSuccess(PullResult pullResult) { assertThat(pullResult).isNotNull(); assertThat(pullResult.getPullStatus()).isEqualTo(PullStatus.FOUND); assertThat(pullResult.getNextBeginOffset()).isEqualTo(1024 + 1); assertThat(pullResult.getMinOffset()).isEqualTo(123); assertThat(pullResult.getMaxOffset()).isEqualTo(2048); assertThat(pullResult.getMsgFoundList()).isEqualTo(new ArrayList<>()); } @Override public void onException(Throwable e) { } }); } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, new byte[] {}); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragelyByCircle; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.PullRequest; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class DefaultMQPushConsumerTest { private String consumerGroup; private String topic = "FooBar"; private String brokerName = "BrokerA"; private MQClientInstance mQClientFactory; private final byte[] msgBody = Long.toString(System.currentTimeMillis()).getBytes(); @Mock private MQClientAPIImpl mQClientAPIImpl; private PullAPIWrapper pullAPIWrapper; private RebalanceImpl rebalanceImpl; private static DefaultMQPushConsumer pushConsumer; private AtomicLong queueOffset = new AtomicLong(1024); @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); for (Map.Entry entry : factoryTable.entrySet()) { entry.getValue().shutdown(); } factoryTable.clear(); when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); return pullResult; } }); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setPullInterval(60 * 1000); pushConsumer.setClientRebalance(false); pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return null; } }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); FieldUtils.writeDeclaredField(mQClientFactory, "mQClientAPIImpl", mQClientAPIImpl, true); mQClientFactory = spy(mQClientFactory); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); doReturn(null).when(mQClientFactory).queryAssignment(anyString(), anyString(), anyString(), any(MessageModel.class), anyInt()); doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); rebalanceImpl = spy(pushConsumerImpl.getRebalanceImpl()); doReturn(123L).when(rebalanceImpl).computePullFromWhereWithException(any(MessageQueue.class)); Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); field.set(pushConsumerImpl, rebalanceImpl); field = DefaultMQPushConsumerImpl.class.getDeclaredField("doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged"); field.setAccessible(true); field.set(null, true); Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); pushConsumerImpl.setmQClientFactory(mQClientFactory); pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); FieldUtils.writeDeclaredField(pushConsumerImpl, "pullAPIWrapper", pullAPIWrapper, true); when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setQueueOffset(queueOffset.getAndIncrement()); messageClientExt.setMsgId("1024"); messageClientExt.setBody(msgBody); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); return pullResult; } }); pushConsumer.subscribe(topic, "*"); pushConsumer.start(); mQClientFactory.registerConsumer(consumerGroup, pushConsumerImpl); } @AfterClass public static void terminate() { if (pushConsumer != null) { pushConsumer.shutdown(); } } @Test public void testStart_OffsetShouldNotNUllAfterStart() { assertNotNull(pushConsumer.getOffsetStore()); } @Test public void testPullMessage_Success() throws InterruptedException, RemotingException, MQBrokerException { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference messageAtomic = new AtomicReference<>(); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return null; } })); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(10, TimeUnit.SECONDS); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(msgBody); } @Test(timeout = 20000) public void testPullMessage_SuccessWithOrderlyService() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference messageAtomic = new AtomicReference<>(); MessageListenerOrderly listenerOrderly = new MessageListenerOrderly() { @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return null; } }; pushConsumer.registerMessageListener(listenerOrderly); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageOrderlyService(pushConsumer.getDefaultMQPushConsumerImpl(), listenerOrderly)); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeOrderly(true); pushConsumer.getDefaultMQPushConsumerImpl().doRebalance(); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestLater(createPullRequest(), 100); countDownLatch.await(); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(msgBody); } @Test public void testCheckConfig() { DefaultMQPushConsumer pushConsumer = createPushConsumer(); pushConsumer.setPullThresholdForQueue(65535 + 1); try { pushConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("pullThresholdForQueue Out of range [1, 65535]"); } pushConsumer = createPushConsumer(); pushConsumer.setPullThresholdForTopic(65535 * 100 + 1); try { pushConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("pullThresholdForTopic Out of range [1, 6553500]"); } pushConsumer = createPushConsumer(); pushConsumer.setPullThresholdSizeForQueue(1024 + 1); try { pushConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("pullThresholdSizeForQueue Out of range [1, 1024]"); } pushConsumer = createPushConsumer(); pushConsumer.setPullThresholdSizeForTopic(1024 * 100 + 1); try { pushConsumer.start(); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("pullThresholdSizeForTopic Out of range [1, 102400]"); } } @Test(timeout = 20000) public void testGracefulShutdown() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); pushConsumer.setAwaitTerminationMillisWhenShutdown(2000); final AtomicBoolean messageConsumedFlag = new AtomicBoolean(false); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { assertThat(msgs.get(0).getBody()).isEqualTo(msgBody); countDownLatch.countDown(); try { Thread.sleep(1000); messageConsumedFlag.set(true); } catch (InterruptedException e) { } return null; } })); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue(); pushConsumer.shutdown(); assertThat(messageConsumedFlag.get()).isTrue(); } private DefaultMQPushConsumer createPushConsumer() { DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return null; } }); return pushConsumer; } private PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(queueOffset.get()); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); pullRequest.setMessageQueue(messageQueue); ProcessQueue processQueue = new ProcessQueue(); processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); pullRequest.setProcessQueue(processQueue); return pullRequest; } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } @Test public void testPullMessage_ExceptionOccursWhenComputePullFromWhere() throws MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); final MessageExt[] messageExts = new MessageExt[1]; pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService( new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { messageExts[0] = msgs.get(0); return null; } })); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeOrderly(true); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); assertThat(messageExts[0]).isNull(); } @Test public void assertCreatePushConsumer() { DefaultMQPushConsumer pushConsumer1 = new DefaultMQPushConsumer(consumerGroup, mock(RPCHook.class)); assertNotNull(pushConsumer1); assertEquals(consumerGroup, pushConsumer1.getConsumerGroup()); assertTrue(pushConsumer1.getAllocateMessageQueueStrategy() instanceof AllocateMessageQueueAveragely); assertNotNull(pushConsumer1.defaultMQPushConsumerImpl); assertFalse(pushConsumer1.isEnableTrace()); assertTrue(UtilAll.isBlank(pushConsumer1.getTraceTopic())); DefaultMQPushConsumer pushConsumer2 = new DefaultMQPushConsumer(consumerGroup, mock(RPCHook.class), new AllocateMessageQueueAveragelyByCircle()); assertNotNull(pushConsumer2); assertEquals(consumerGroup, pushConsumer2.getConsumerGroup()); assertTrue(pushConsumer2.getAllocateMessageQueueStrategy() instanceof AllocateMessageQueueAveragelyByCircle); assertNotNull(pushConsumer2.defaultMQPushConsumerImpl); assertFalse(pushConsumer2.isEnableTrace()); assertTrue(UtilAll.isBlank(pushConsumer2.getTraceTopic())); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearByTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class AllocateMachineRoomNearByTest { private static final String CID_PREFIX = "CID-"; private final String topic = "topic_test"; private final AllocateMachineRoomNearby.MachineRoomResolver machineRoomResolver = new AllocateMachineRoomNearby.MachineRoomResolver() { @Override public String brokerDeployIn(MessageQueue messageQueue) { return messageQueue.getBrokerName().split("-")[0]; } @Override public String consumerDeployIn(String clientID) { return clientID.split("-")[0]; } }; private final AllocateMessageQueueStrategy allocateMessageQueueStrategy = new AllocateMachineRoomNearby(new AllocateMessageQueueAveragely(), machineRoomResolver); @Before public void init() { } @Test public void test1() { testWhenIDCSizeEquals(5,20,10); testWhenIDCSizeEquals(5,20,20); testWhenIDCSizeEquals(5,20,30); testWhenIDCSizeEquals(5,20,0); } @Test public void test2() { testWhenConsumerIDCIsMore(5,1,10, 10, false); testWhenConsumerIDCIsMore(5,1,10, 5, false); testWhenConsumerIDCIsMore(5,1,10, 20, false); testWhenConsumerIDCIsMore(5,1,10, 0, false); } @Test public void test3() { testWhenConsumerIDCIsLess(5,2,10, 10, false); testWhenConsumerIDCIsLess(5,2,10, 5, false); testWhenConsumerIDCIsLess(5,2,10, 20, false); testWhenConsumerIDCIsLess(5,2,10, 0, false); } @Test public void testRun10RandomCase() { for (int i = 0; i < 10; i++) { int consumerSize = new Random().nextInt(200) + 1;//1-200 int queueSize = new Random().nextInt(100) + 1;//1-100 int brokerIDCSize = new Random().nextInt(10) + 1;//1-10 int consumerIDCSize = new Random().nextInt(10) + 1;//1-10 if (brokerIDCSize == consumerIDCSize) { testWhenIDCSizeEquals(brokerIDCSize,queueSize,consumerSize); } else if (brokerIDCSize > consumerIDCSize) { testWhenConsumerIDCIsLess(brokerIDCSize,brokerIDCSize - consumerIDCSize, queueSize, consumerSize, false); } else { testWhenConsumerIDCIsMore(brokerIDCSize, consumerIDCSize - brokerIDCSize, queueSize, consumerSize, false); } } } public void testWhenIDCSizeEquals(int idcSize, int queueSize, int consumerSize) { List cidAll = prepareConsumer(idcSize, consumerSize); List mqAll = prepareMQ(idcSize, queueSize); List resAll = new ArrayList<>(); for (String currentID : cidAll) { List res = allocateMessageQueueStrategy.allocate("Test-C-G",currentID,mqAll,cidAll); for (MessageQueue mq : res) { Assert.assertTrue(machineRoomResolver.brokerDeployIn(mq).equals(machineRoomResolver.consumerDeployIn(currentID))); } resAll.addAll(res); } Assert.assertTrue(hasAllocateAllQ(cidAll,mqAll,resAll)); } public void testWhenConsumerIDCIsMore(int brokerIDCSize, int consumerMore, int queueSize, int consumerSize, boolean print) { Set brokerIDCWithConsumer = new TreeSet<>(); List cidAll = prepareConsumer(brokerIDCSize + consumerMore, consumerSize); List mqAll = prepareMQ(brokerIDCSize, queueSize); for (MessageQueue mq : mqAll) { brokerIDCWithConsumer.add(machineRoomResolver.brokerDeployIn(mq)); } List resAll = new ArrayList<>(); for (String currentID : cidAll) { List res = allocateMessageQueueStrategy.allocate("Test-C-G",currentID,mqAll,cidAll); for (MessageQueue mq : res) { if (brokerIDCWithConsumer.contains(machineRoomResolver.brokerDeployIn(mq))) { //healthy idc, so only consumer in this idc should be allocated Assert.assertTrue(machineRoomResolver.brokerDeployIn(mq).equals(machineRoomResolver.consumerDeployIn(currentID))); } } resAll.addAll(res); } Assert.assertTrue(hasAllocateAllQ(cidAll,mqAll,resAll)); } public void testWhenConsumerIDCIsLess(int brokerIDCSize, int consumerIDCLess, int queueSize, int consumerSize, boolean print) { Set healthyIDC = new TreeSet<>(); List cidAll = prepareConsumer(brokerIDCSize - consumerIDCLess, consumerSize); List mqAll = prepareMQ(brokerIDCSize, queueSize); for (String cid : cidAll) { healthyIDC.add(machineRoomResolver.consumerDeployIn(cid)); } List resAll = new ArrayList<>(); Map> idc2Res = new TreeMap<>(); for (String currentID : cidAll) { String currentIDC = machineRoomResolver.consumerDeployIn(currentID); List res = allocateMessageQueueStrategy.allocate("Test-C-G",currentID,mqAll,cidAll); if (!idc2Res.containsKey(currentIDC)) { idc2Res.put(currentIDC, new ArrayList<>()); } idc2Res.get(currentIDC).addAll(res); resAll.addAll(res); } for (String consumerIDC : healthyIDC) { List resInOneIDC = idc2Res.get(consumerIDC); List mqInThisIDC = createMessageQueueList(consumerIDC,queueSize); Assert.assertTrue(resInOneIDC.containsAll(mqInThisIDC)); } Assert.assertTrue(hasAllocateAllQ(cidAll,mqAll,resAll)); } private boolean hasAllocateAllQ(List cidAll,List mqAll, List allocatedResAll) { if (cidAll.isEmpty()) { return allocatedResAll.isEmpty(); } return mqAll.containsAll(allocatedResAll) && allocatedResAll.containsAll(mqAll) && mqAll.size() == allocatedResAll.size(); } private List createConsumerIdList(String machineRoom, int size) { List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add(machineRoom + "-" + CID_PREFIX + String.valueOf(i)); } return consumerIdList; } private List createMessageQueueList(String machineRoom, int size) { List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue(topic, machineRoom + "-brokerName", i); messageQueueList.add(mq); } return messageQueueList; } private List prepareMQ(int brokerIDCSize, int queueSize) { List mqAll = new ArrayList<>(); for (int i = 1; i <= brokerIDCSize; i++) { mqAll.addAll(createMessageQueueList("IDC" + i, queueSize)); } return mqAll; } private List prepareConsumer(int idcSize, int consumerSize) { List cidAll = new ArrayList<>(); for (int i = 1; i <= idcSize; i++) { cidAll.addAll(createConsumerIdList("IDC" + i, consumerSize)); } return cidAll; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircleTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.TestCase; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; public class AllocateMessageQueueAveragelyByCircleTest extends TestCase { public void testAllocateMessageQueueAveragelyByCircle() { List consumerIdList = createConsumerIdList(4); List messageQueueList = createMessageQueueList(10); // the consumerId not in cidAll List allocateQueues = new AllocateMessageQueueAveragelyByCircle().allocate("", "CID_PREFIX", messageQueueList, consumerIdList); Assert.assertEquals(0, allocateQueues.size()); Map consumerAllocateQueue = new HashMap<>(consumerIdList.size()); for (String consumerId : consumerIdList) { List queues = new AllocateMessageQueueAveragelyByCircle().allocate("", consumerId, messageQueueList, consumerIdList); int[] queueIds = new int[queues.size()]; for (int i = 0; i < queues.size(); i++) { queueIds[i] = queues.get(i).getQueueId(); } consumerAllocateQueue.put(consumerId, queueIds); } Assert.assertArrayEquals(new int[] {0, 4, 8}, consumerAllocateQueue.get("CID_PREFIX0")); Assert.assertArrayEquals(new int[] {1, 5, 9}, consumerAllocateQueue.get("CID_PREFIX1")); Assert.assertArrayEquals(new int[] {2, 6}, consumerAllocateQueue.get("CID_PREFIX2")); Assert.assertArrayEquals(new int[] {3, 7}, consumerAllocateQueue.get("CID_PREFIX3")); } private List createConsumerIdList(int size) { List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add("CID_PREFIX" + i); } return consumerIdList; } private List createMessageQueueList(int size) { List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue("topic", "brokerName", i); messageQueueList.add(mq); } return messageQueueList; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import junit.framework.TestCase; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; import java.util.ArrayList; import java.util.List; public class AllocateMessageQueueAveragelyTest extends TestCase { public void testAllocateMessageQueueAveragely() { List consumerIdList = createConsumerIdList(4); List messageQueueList = createMessageQueueList(10); int[] results = new int[consumerIdList.size()]; for (int i = 0; i < consumerIdList.size(); i++) { List result = new AllocateMessageQueueAveragely().allocate("", consumerIdList.get(i), messageQueueList, consumerIdList); results[i] = result.size(); } Assert.assertArrayEquals(new int[]{3, 3, 2, 2}, results); } private List createConsumerIdList(int size) { List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add("CID_PREFIX" + i); } return consumerIdList; } private List createMessageQueueList(int size) { List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue("topic", "brokerName", i); messageQueueList.add(mq); } return messageQueueList; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.TestCase; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; public class AllocateMessageQueueByConfigTest extends TestCase { public void testAllocateMessageQueueByConfig() { List consumerIdList = createConsumerIdList(2); List messageQueueList = createMessageQueueList(4); AllocateMessageQueueByConfig allocateStrategy = new AllocateMessageQueueByConfig(); allocateStrategy.setMessageQueueList(messageQueueList); Map consumerAllocateQueue = new HashMap<>(consumerIdList.size()); for (String consumerId : consumerIdList) { List queues = allocateStrategy.allocate("", consumerId, messageQueueList, consumerIdList); int[] queueIds = new int[queues.size()]; for (int i = 0; i < queues.size(); i++) { queueIds[i] = queues.get(i).getQueueId(); } consumerAllocateQueue.put(consumerId, queueIds); } Assert.assertArrayEquals(new int[] {0, 1, 2, 3}, consumerAllocateQueue.get("CID_PREFIX0")); Assert.assertArrayEquals(new int[] {0, 1, 2, 3}, consumerAllocateQueue.get("CID_PREFIX1")); } private List createConsumerIdList(int size) { List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add("CID_PREFIX" + i); } return consumerIdList; } private List createMessageQueueList(int size) { List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue("topic", "brokerName", i); messageQueueList.add(mq); } return messageQueueList; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoomTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.TestCase; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; public class AllocateMessageQueueByMachineRoomTest extends TestCase { public void testAllocateMessageQueueByMachineRoom() { List consumerIdList = createConsumerIdList(2); List messageQueueList = createMessageQueueList(10); Set consumeridcs = new HashSet<>(); consumeridcs.add("room1"); AllocateMessageQueueByMachineRoom allocateStrategy = new AllocateMessageQueueByMachineRoom(); allocateStrategy.setConsumeridcs(consumeridcs); // mqAll is null or mqAll empty try { allocateStrategy.allocate("", consumerIdList.get(0), new ArrayList<>(), consumerIdList); } catch (Exception e) { assert e instanceof IllegalArgumentException; Assert.assertEquals("mqAll is null or mqAll empty", e.getMessage()); } Map consumerAllocateQueue = new HashMap<>(consumerIdList.size()); for (String consumerId : consumerIdList) { List queues = allocateStrategy.allocate("", consumerId, messageQueueList, consumerIdList); int[] queueIds = new int[queues.size()]; for (int i = 0; i < queues.size(); i++) { queueIds[i] = queues.get(i).getQueueId(); } consumerAllocateQueue.put(consumerId, queueIds); } Assert.assertArrayEquals(new int[] {0, 1, 4}, consumerAllocateQueue.get("CID_PREFIX0")); Assert.assertArrayEquals(new int[] {2, 3}, consumerAllocateQueue.get("CID_PREFIX1")); } private List createConsumerIdList(int size) { List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add("CID_PREFIX" + i); } return consumerIdList; } private List createMessageQueueList(int size) { List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq; if (i < size / 2) { mq = new MessageQueue("topic", "room1@broker-a", i); } else { mq = new MessageQueue("topic", "room2@broker-b", i); } messageQueueList.add(mq); } return messageQueueList; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsitentHashTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.rebalance; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.TreeMap; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class AllocateMessageQueueConsitentHashTest { private String topic; private static final String CID_PREFIX = "CID-"; @Before public void init() { topic = "topic_test"; } @Test public void testCurrentCIDNotExists() { String currentCID = String.valueOf(Integer.MAX_VALUE); List consumerIdList = createConsumerIdList(2); List messageQueueList = createMessageQueueList(6); List result = new AllocateMessageQueueConsistentHash().allocate("", currentCID, messageQueueList, consumerIdList); Assert.assertEquals(result.size(), 0); } @Test(expected = IllegalArgumentException.class) public void testCurrentCIDIllegalArgument() { List consumerIdList = createConsumerIdList(2); List messageQueueList = createMessageQueueList(6); new AllocateMessageQueueConsistentHash().allocate("", "", messageQueueList, consumerIdList); } @Test(expected = IllegalArgumentException.class) public void testMessageQueueIllegalArgument() { String currentCID = "0"; List consumerIdList = createConsumerIdList(2); new AllocateMessageQueueConsistentHash().allocate("", currentCID, null, consumerIdList); } @Test(expected = IllegalArgumentException.class) public void testConsumerIdIllegalArgument() { String currentCID = "0"; List messageQueueList = createMessageQueueList(6); new AllocateMessageQueueConsistentHash().allocate("", currentCID, messageQueueList, null); } @Test public void testAllocate1() { testAllocate(20, 10); } @Test public void testAllocate2() { testAllocate(10, 20); } @Test public void testRun100RandomCase() { for (int i = 0; i < 10; i++) { int consumerSize = new Random().nextInt(20) + 1;//1-20 int queueSize = new Random().nextInt(20) + 1;//1-20 testAllocate(queueSize, consumerSize); try { Thread.sleep(1); } catch (InterruptedException e) { } } } public void testAllocate(int queueSize, int consumerSize) { AllocateMessageQueueStrategy allocateMessageQueueConsistentHash = new AllocateMessageQueueConsistentHash(3); List mqAll = createMessageQueueList(queueSize); List cidAll = createConsumerIdList(consumerSize); List allocatedResAll = new ArrayList<>(); Map allocateToAllOrigin = new TreeMap<>(); //test allocate all { List cidBegin = new ArrayList<>(cidAll); for (String cid : cidBegin) { List rs = allocateMessageQueueConsistentHash.allocate("testConsumerGroup", cid, mqAll, cidBegin); for (MessageQueue mq : rs) { allocateToAllOrigin.put(mq, cid); } allocatedResAll.addAll(rs); } Assert.assertTrue( verifyAllocateAll(cidBegin, mqAll, allocatedResAll)); } Map allocateToAllAfterRemoveOne = new TreeMap<>(); List cidAfterRemoveOne = new ArrayList<>(cidAll); //test allocate remove one cid { String removeCID = cidAfterRemoveOne.remove(0); List mqShouldOnlyChanged = new ArrayList<>(); Iterator> it = allocateToAllOrigin.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (entry.getValue().equals(removeCID)) { mqShouldOnlyChanged.add(entry.getKey()); } } List allocatedResAllAfterRemove = new ArrayList<>(); for (String cid : cidAfterRemoveOne) { List rs = allocateMessageQueueConsistentHash.allocate("testConsumerGroup", cid, mqAll, cidAfterRemoveOne); allocatedResAllAfterRemove.addAll(rs); for (MessageQueue mq : rs) { allocateToAllAfterRemoveOne.put(mq, cid); } } Assert.assertTrue("queueSize" + queueSize + "consumerSize:" + consumerSize + "\nmqAll:" + mqAll + "\nallocatedResAllAfterRemove" + allocatedResAllAfterRemove, verifyAllocateAll(cidAfterRemoveOne, mqAll, allocatedResAllAfterRemove)); verifyAfterRemove(allocateToAllOrigin, allocateToAllAfterRemoveOne, removeCID); } List cidAfterAdd = new ArrayList<>(cidAfterRemoveOne); //test allocate add one more cid { String newCid = CID_PREFIX + "NEW"; cidAfterAdd.add(newCid); List mqShouldOnlyChanged = new ArrayList<>(); List allocatedResAllAfterAdd = new ArrayList<>(); Map allocateToAll3 = new TreeMap<>(); for (String cid : cidAfterAdd) { List rs = allocateMessageQueueConsistentHash.allocate("testConsumerGroup", cid, mqAll, cidAfterAdd); allocatedResAllAfterAdd.addAll(rs); for (MessageQueue mq : rs) { allocateToAll3.put(mq, cid); if (cid.equals(newCid)) { mqShouldOnlyChanged.add(mq); } } } Assert.assertTrue( verifyAllocateAll(cidAfterAdd, mqAll, allocatedResAllAfterAdd)); verifyAfterAdd(allocateToAllAfterRemoveOne, allocateToAll3, newCid); } } private boolean verifyAllocateAll(List cidAll, List mqAll, List allocatedResAll) { if (cidAll.isEmpty()) { return allocatedResAll.isEmpty(); } return mqAll.containsAll(allocatedResAll) && allocatedResAll.containsAll(mqAll); } private void verifyAfterRemove(Map allocateToBefore, Map allocateAfter, String removeCID) { for (MessageQueue mq : allocateToBefore.keySet()) { String allocateToOrigin = allocateToBefore.get(mq); if (allocateToOrigin.equals(removeCID)) { } else { //the rest queue should be the same Assert.assertTrue(allocateAfter.get(mq).equals(allocateToOrigin));//should be the same } } } private void verifyAfterAdd(Map allocateBefore, Map allocateAfter, String newCID) { for (MessageQueue mq : allocateAfter.keySet()) { String allocateToOrigin = allocateBefore.get(mq); String allocateToAfter = allocateAfter.get(mq); if (allocateToAfter.equals(newCID)) { } else { //the rest queue should be the same Assert.assertTrue("it was allocated to " + allocateToOrigin + ". Now, it is to " + allocateAfter.get(mq) + " mq:" + mq, allocateAfter.get(mq).equals(allocateToOrigin));//should be the same } } } private List createConsumerIdList(int size) { List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add(CID_PREFIX + String.valueOf(i)); } return consumerIdList; } private List createMessageQueueList(int size) { List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue(topic, "brokerName", i); messageQueueList.add(mq); } return messageQueueList; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/store/ControllableOffsetTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class ControllableOffsetTest { private ControllableOffset controllableOffset; @Before public void setUp() { controllableOffset = new ControllableOffset(0); } @Test public void testUpdateAndFreeze_ShouldFreezeOffsetAtTargetValue() { controllableOffset.updateAndFreeze(100); assertEquals(100, controllableOffset.getOffset()); controllableOffset.update(200); assertEquals(100, controllableOffset.getOffset()); } @Test public void testUpdate_ShouldUpdateOffsetWhenNotFrozen() { controllableOffset.update(200); assertEquals(200, controllableOffset.getOffset()); } @Test public void testUpdate_ShouldNotUpdateOffsetWhenFrozen() { controllableOffset.updateAndFreeze(100); controllableOffset.update(200); assertEquals(100, controllableOffset.getOffset()); } @Test public void testUpdate_ShouldNotDecreaseOffsetWhenIncreaseOnly() { controllableOffset.update(200); controllableOffset.update(100, true); assertEquals(200, controllableOffset.getOffset()); } @Test public void testUpdate_ShouldUpdateOffsetToGreaterValueWhenIncreaseOnly() { controllableOffset.update(100); controllableOffset.update(200, true); assertEquals(200, controllableOffset.getOffset()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LocalFileOffsetStoreTest { @Mock private MQClientInstance mQClientFactory; private String group = "FooBarGroup"; private String topic = "FooBar"; private String brokerName = "DefaultBrokerName"; @Before public void init() { System.setProperty("rocketmq.client.localOffsetStoreDir", System.getProperty("java.io.tmpdir") + File.separator + ".rocketmq_offsets"); String clientId = new ClientConfig().buildMQClientId() + "#TestNamespace" + System.currentTimeMillis(); when(mQClientFactory.getClientId()).thenReturn(clientId); } @Test public void testUpdateOffset() throws Exception { OffsetStore offsetStore = new LocalFileOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 1); offsetStore.updateOffset(messageQueue, 1024, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.updateOffset(messageQueue, 1023, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1023); offsetStore.updateOffset(messageQueue, 1022, true); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1023); } @Test public void testReadOffset_FromStore() throws Exception { OffsetStore offsetStore = new LocalFileOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 2); offsetStore.updateOffset(messageQueue, 1024, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(-1); offsetStore.persistAll(new HashSet<>(Collections.singletonList(messageQueue))); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1024); } @Test public void testCloneOffset() throws Exception { OffsetStore offsetStore = new LocalFileOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 3); offsetStore.updateOffset(messageQueue, 1024, false); Map cloneOffsetTable = offsetStore.cloneOffsetTable(topic); assertThat(cloneOffsetTable.size()).isEqualTo(1); assertThat(cloneOffsetTable.get(messageQueue)).isEqualTo(1024); } @Test public void testPersist() throws Exception { OffsetStore offsetStore = new LocalFileOffsetStore(mQClientFactory, group); MessageQueue messageQueue0 = new MessageQueue(topic, brokerName, 0); offsetStore.updateOffset(messageQueue0, 1024, false); offsetStore.persist(messageQueue0); assertThat(offsetStore.readOffset(messageQueue0, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1024); MessageQueue messageQueue1 = new MessageQueue(topic, brokerName, 1); assertThat(offsetStore.readOffset(messageQueue1, ReadOffsetType.READ_FROM_STORE)).isEqualTo(-1); } @Test public void testPersistAll() throws Exception { OffsetStore offsetStore = new LocalFileOffsetStore(mQClientFactory, group); MessageQueue messageQueue0 = new MessageQueue(topic, brokerName, 0); offsetStore.updateOffset(messageQueue0, 1024, false); offsetStore.persistAll(new HashSet(Collections.singletonList(messageQueue0))); assertThat(offsetStore.readOffset(messageQueue0, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1024); MessageQueue messageQueue1 = new MessageQueue(topic, brokerName, 1); MessageQueue messageQueue2 = new MessageQueue(topic, brokerName, 2); offsetStore.updateOffset(messageQueue1, 1025, false); offsetStore.updateOffset(messageQueue2, 1026, false); offsetStore.persistAll(new HashSet(Arrays.asList(messageQueue1, messageQueue2))); assertThat(offsetStore.readOffset(messageQueue0, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1024); assertThat(offsetStore.readOffset(messageQueue1, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1025); assertThat(offsetStore.readOffset(messageQueue2, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1026); } @Test public void testRemoveOffset() throws Exception { OffsetStore offsetStore = new LocalFileOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 0); offsetStore.updateOffset(messageQueue, 1024, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.removeOffset(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(-1); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.consumer.store; import java.util.Collections; import java.util.HashSet; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.OffsetNotFoundException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RemoteBrokerOffsetStoreTest { @Mock private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mqClientAPI; private String group = "FooBarGroup"; private String topic = "FooBar"; private String brokerName = "DefaultBrokerName"; @Before public void init() { System.setProperty("rocketmq.client.localOffsetStoreDir", System.getProperty("java.io.tmpdir") + ".rocketmq_offsets"); String clientId = new ClientConfig().buildMQClientId() + "#TestNamespace" + System.currentTimeMillis(); when(mQClientFactory.getClientId()).thenReturn(clientId); when(mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, false)).thenReturn(new FindBrokerResult("127.0.0.1", false)); when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mqClientAPI); when(mQClientFactory.getBrokerNameFromMessageQueue(any())).thenReturn(brokerName); } @Test public void testUpdateOffset() throws Exception { OffsetStore offsetStore = new RemoteBrokerOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 1); offsetStore.updateOffset(messageQueue, 1024, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.updateOffset(messageQueue, 1023, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1023); offsetStore.updateOffset(messageQueue, 1022, true); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1023); } @Test public void testUpdateAndFreezeOffset() throws Exception { OffsetStore offsetStore = new RemoteBrokerOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 1); offsetStore.updateAndFreezeOffset(messageQueue, 1024); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.updateOffset(messageQueue, 1023, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.updateOffset(messageQueue, 1022, true); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); } @Test public void testUpdateAndFreezeOffsetWithRemove() throws Exception { OffsetStore offsetStore = new RemoteBrokerOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 1); offsetStore.updateAndFreezeOffset(messageQueue, 1024); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.updateOffset(messageQueue, 1023, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.removeOffset(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(-1); offsetStore.updateOffset(messageQueue, 1023, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1023); } @Test public void testReadOffset_WithException() throws Exception { OffsetStore offsetStore = new RemoteBrokerOffsetStore(mQClientFactory, group); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 2); offsetStore.updateOffset(messageQueue, 1024, false); doThrow(new OffsetNotFoundException(ResponseCode.QUERY_NOT_FOUND, "", null)) .when(mqClientAPI).queryConsumerOffset(anyString(), any(QueryConsumerOffsetRequestHeader.class), anyLong()); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(-1); doThrow(new MQBrokerException(-1, "", null)) .when(mqClientAPI).queryConsumerOffset(anyString(), any(QueryConsumerOffsetRequestHeader.class), anyLong()); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(-2); doThrow(new RemotingException("", null)) .when(mqClientAPI).queryConsumerOffset(anyString(), any(QueryConsumerOffsetRequestHeader.class), anyLong()); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(-2); } @Test public void testReadOffset_Success() throws Exception { OffsetStore offsetStore = new RemoteBrokerOffsetStore(mQClientFactory, group); final MessageQueue messageQueue = new MessageQueue(topic, brokerName, 3); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock mock) throws Throwable { UpdateConsumerOffsetRequestHeader updateRequestHeader = mock.getArgument(1); when(mqClientAPI.queryConsumerOffset(anyString(), any(QueryConsumerOffsetRequestHeader.class), anyLong())).thenReturn(updateRequestHeader.getCommitOffset()); return null; } }).when(mqClientAPI).updateConsumerOffsetOneway(any(String.class), any(UpdateConsumerOffsetRequestHeader.class), any(Long.class)); offsetStore.updateOffset(messageQueue, 1024, false); offsetStore.persist(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1024); offsetStore.updateOffset(messageQueue, 1023, false); offsetStore.persist(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1023); offsetStore.updateOffset(messageQueue, 1022, true); offsetStore.persist(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1023); offsetStore.updateOffset(messageQueue, 1025, false); offsetStore.persistAll(new HashSet<>(Collections.singletonList(messageQueue))); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1025); } @Test public void testRemoveOffset() throws Exception { OffsetStore offsetStore = new RemoteBrokerOffsetStore(mQClientFactory, group); final MessageQueue messageQueue = new MessageQueue(topic, brokerName, 4); offsetStore.updateOffset(messageQueue, 1024, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(1024); offsetStore.removeOffset(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(-1); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/ClientRemotingProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.MQProducerInner; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerStatusRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyConsumerIdsChangedRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ReplyMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import static org.apache.rocketmq.common.message.MessageDecoder.NAME_VALUE_SEPARATOR; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ClientRemotingProcessorTest { @Mock private MQClientInstance mQClientFactory; private ClientRemotingProcessor processor; private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; private final String defaultGroup = "defaultGroup"; @Before public void init() throws RemotingException, InterruptedException, MQClientException { processor = new ClientRemotingProcessor(mQClientFactory); ClientConfig clientConfig = mock(ClientConfig.class); when(clientConfig.getNamespace()).thenReturn("namespace"); when(mQClientFactory.getClientConfig()).thenReturn(clientConfig); MQProducerInner producerInner = mock(MQProducerInner.class); when(mQClientFactory.selectProducer(defaultGroup)).thenReturn(producerInner); } @Test public void testCheckTransactionState() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.CHECK_TRANSACTION_STATE); when(request.getBody()).thenReturn(getMessageResult()); CheckTransactionStateRequestHeader requestHeader = new CheckTransactionStateRequestHeader(); when(request.decodeCommandCustomHeader(CheckTransactionStateRequestHeader.class)).thenReturn(requestHeader); assertNull(processor.processRequest(ctx, request)); } @Test public void testNotifyConsumerIdsChanged() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED); NotifyConsumerIdsChangedRequestHeader requestHeader = new NotifyConsumerIdsChangedRequestHeader(); when(request.decodeCommandCustomHeader(NotifyConsumerIdsChangedRequestHeader.class)).thenReturn(requestHeader); assertNull(processor.processRequest(ctx, request)); } @Test public void testResetOffset() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.RESET_CONSUMER_CLIENT_OFFSET); ResetOffsetBody offsetBody = new ResetOffsetBody(); when(request.getBody()).thenReturn(RemotingSerializable.encode(offsetBody)); ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); when(request.decodeCommandCustomHeader(ResetOffsetRequestHeader.class)).thenReturn(requestHeader); assertNull(processor.processRequest(ctx, request)); } @Test public void testGetConsumeStatus() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT); GetConsumerStatusRequestHeader requestHeader = new GetConsumerStatusRequestHeader(); when(request.decodeCommandCustomHeader(GetConsumerStatusRequestHeader.class)).thenReturn(requestHeader); assertNotNull(processor.processRequest(ctx, request)); } @Test public void testGetConsumerRunningInfo() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.GET_CONSUMER_RUNNING_INFO); ConsumerRunningInfo consumerRunningInfo = new ConsumerRunningInfo(); consumerRunningInfo.setJstack("jstack"); when(mQClientFactory.consumerRunningInfo(anyString())).thenReturn(consumerRunningInfo); GetConsumerRunningInfoRequestHeader requestHeader = new GetConsumerRunningInfoRequestHeader(); requestHeader.setJstackEnable(true); requestHeader.setConsumerGroup(defaultGroup); when(request.decodeCommandCustomHeader(GetConsumerRunningInfoRequestHeader.class)).thenReturn(requestHeader); RemotingCommand command = processor.processRequest(ctx, request); assertNotNull(command); assertEquals(ResponseCode.SUCCESS, command.getCode()); } @Test public void testConsumeMessageDirectly() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.CONSUME_MESSAGE_DIRECTLY); when(request.getBody()).thenReturn(getMessageResult()); ConsumeMessageDirectlyResult directlyResult = mock(ConsumeMessageDirectlyResult.class); when(mQClientFactory.consumeMessageDirectly(any(MessageExt.class), anyString(), anyString())).thenReturn(directlyResult); ConsumeMessageDirectlyResultRequestHeader requestHeader = new ConsumeMessageDirectlyResultRequestHeader(); requestHeader.setConsumerGroup(defaultGroup); requestHeader.setBrokerName(defaultBroker); when(request.decodeCommandCustomHeader(ConsumeMessageDirectlyResultRequestHeader.class)).thenReturn(requestHeader); RemotingCommand command = processor.processRequest(ctx, request); assertNotNull(command); assertEquals(ResponseCode.SUCCESS, command.getCode()); } @Test public void testReceiveReplyMessage() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); RemotingCommand request = mock(RemotingCommand.class); when(request.getCode()).thenReturn(RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT); when(request.getBody()).thenReturn(getMessageResult()); when(request.decodeCommandCustomHeader(ReplyMessageRequestHeader.class)).thenReturn(createReplyMessageRequestHeader()); when(request.getBody()).thenReturn(new byte[1]); RemotingCommand command = processor.processRequest(ctx, request); assertNotNull(command); assertEquals(ResponseCode.SUCCESS, command.getCode()); } private ReplyMessageRequestHeader createReplyMessageRequestHeader() { ReplyMessageRequestHeader result = new ReplyMessageRequestHeader(); result.setTopic(defaultTopic); result.setQueueId(0); result.setStoreTimestamp(System.currentTimeMillis()); result.setBornTimestamp(System.currentTimeMillis()); result.setReconsumeTimes(1); result.setBornHost("127.0.0.1:12911"); result.setStoreHost("127.0.0.1:10911"); result.setSysFlag(1); result.setFlag(1); result.setProperties("CORRELATION_ID" + NAME_VALUE_SEPARATOR + "1"); return result; } private byte[] getMessageResult() throws Exception { byte[] bytes = MessageDecoder.encode(createMessageExt(), false); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes); return byteBuffer.array(); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, defaultGroup); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); result.setKeys("keys"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/MQAdminImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.QueryMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class MQAdminImplTest { @Mock private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mQClientAPIImpl; private MQAdminImpl mqAdminImpl; private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; private final String defaultCluster = "defaultCluster"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final long defaultTimeout = 3000L; @Before public void init() throws RemotingException, InterruptedException, MQClientException { when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mQClientAPIImpl); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createRouteData()); ClientConfig clientConfig = mock(ClientConfig.class); when(clientConfig.getNamespace()).thenReturn("namespace"); when(mQClientFactory.getClientConfig()).thenReturn(clientConfig); when(mQClientFactory.findBrokerAddressInPublish(any())).thenReturn(defaultBrokerAddr); when(mQClientFactory.getAnExistTopicRouteData(any())).thenReturn(createRouteData()); mqAdminImpl = new MQAdminImpl(mQClientFactory); } @Test public void assertTimeoutMillis() { assertEquals(6000L, mqAdminImpl.getTimeoutMillis()); mqAdminImpl.setTimeoutMillis(defaultTimeout); assertEquals(defaultTimeout, mqAdminImpl.getTimeoutMillis()); } @Test public void testCreateTopic() throws MQClientException { mqAdminImpl.createTopic("", defaultTopic, 6); } @Test public void assertFetchPublishMessageQueues() throws MQClientException { List queueList = mqAdminImpl.fetchPublishMessageQueues(defaultTopic); assertNotNull(queueList); assertEquals(6, queueList.size()); for (MessageQueue each : queueList) { assertEquals(defaultTopic, each.getTopic()); assertEquals(defaultBroker, each.getBrokerName()); } } @Test public void assertFetchSubscribeMessageQueues() throws MQClientException { Set queueList = mqAdminImpl.fetchSubscribeMessageQueues(defaultTopic); assertNotNull(queueList); assertEquals(6, queueList.size()); for (MessageQueue each : queueList) { assertEquals(defaultTopic, each.getTopic()); assertEquals(defaultBroker, each.getBrokerName()); } } @Test public void assertSearchOffset() throws MQClientException { assertEquals(0, mqAdminImpl.searchOffset(new MessageQueue(), defaultTimeout)); } @Test public void assertMaxOffset() throws MQClientException { assertEquals(0, mqAdminImpl.maxOffset(new MessageQueue())); } @Test public void assertMinOffset() throws MQClientException { assertEquals(0, mqAdminImpl.minOffset(new MessageQueue())); } @Test public void assertEarliestMsgStoreTime() throws MQClientException { assertEquals(0, mqAdminImpl.earliestMsgStoreTime(new MessageQueue())); } @Test(expected = MQClientException.class) public void assertViewMessage() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { MessageExt actual = mqAdminImpl.viewMessage(defaultTopic, "1"); assertNotNull(actual); } @Test public void assertQueryMessage() throws InterruptedException, MQClientException, MQBrokerException, RemotingException { doAnswer(invocation -> { InvokeCallback callback = invocation.getArgument(3); QueryMessageResponseHeader responseHeader = new QueryMessageResponseHeader(); responseHeader.setIndexLastUpdatePhyoffset(1L); responseHeader.setIndexLastUpdateTimestamp(System.currentTimeMillis()); RemotingCommand response = mock(RemotingCommand.class); when(response.decodeCommandCustomHeader(QueryMessageResponseHeader.class)).thenReturn(responseHeader); when(response.getBody()).thenReturn(getMessageResult()); when(response.getCode()).thenReturn(ResponseCode.SUCCESS); callback.operationSucceed(response); return null; }).when(mQClientAPIImpl).queryMessage(anyString(), any(), anyLong(), any(InvokeCallback.class), any()); QueryResult actual = mqAdminImpl.queryMessage(defaultTopic, "keys", 100, 1L, 50L); assertNotNull(actual); assertEquals(1, actual.getMessageList().size()); assertEquals(defaultTopic, actual.getMessageList().get(0).getTopic()); } @Test public void assertQueryMessageByUniqKey() throws InterruptedException, MQClientException, MQBrokerException, RemotingException { doAnswer(invocation -> { InvokeCallback callback = invocation.getArgument(3); QueryMessageResponseHeader responseHeader = new QueryMessageResponseHeader(); responseHeader.setIndexLastUpdatePhyoffset(1L); responseHeader.setIndexLastUpdateTimestamp(System.currentTimeMillis()); RemotingCommand response = mock(RemotingCommand.class); when(response.decodeCommandCustomHeader(QueryMessageResponseHeader.class)).thenReturn(responseHeader); when(response.getBody()).thenReturn(getMessageResult()); when(response.getCode()).thenReturn(ResponseCode.SUCCESS); callback.operationSucceed(response); return null; }).when(mQClientAPIImpl).queryMessage(anyString(), any(), anyLong(), any(InvokeCallback.class), any()); String msgId = buildMsgId(); MessageExt actual = mqAdminImpl.queryMessageByUniqKey(defaultTopic, msgId); assertNotNull(actual); assertEquals(msgId, actual.getMsgId()); assertEquals(defaultTopic, actual.getTopic()); actual = mqAdminImpl.queryMessageByUniqKey(defaultCluster, defaultTopic, msgId); assertNotNull(actual); assertEquals(msgId, actual.getMsgId()); assertEquals(defaultTopic, actual.getTopic()); QueryResult queryResult = mqAdminImpl.queryMessageByUniqKey(defaultTopic, msgId, 1, 0L, 1L); assertNotNull(queryResult); assertEquals(1, queryResult.getMessageList().size()); assertEquals(defaultTopic, queryResult.getMessageList().get(0).getTopic()); } private String buildMsgId() { MessageExt msgExt = createMessageExt(); int storeHostIPLength = (msgExt.getFlag() & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 : 16; int msgIDLength = storeHostIPLength + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); return MessageDecoder.createMessageId(byteBufferMsgId, msgExt.getStoreHostBytes(), msgExt.getCommitLogOffset()); } private TopicRouteData createRouteData() { TopicRouteData result = new TopicRouteData(); result.setBrokerDatas(createBrokerData()); result.setQueueDatas(createQueueData()); return result; } private List createBrokerData() { HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, defaultBrokerAddr); return Collections.singletonList(new BrokerData(defaultCluster, defaultBroker, brokerAddrs)); } private List createQueueData() { QueueData queueData = new QueueData(); queueData.setPerm(6); queueData.setBrokerName(defaultBroker); queueData.setReadQueueNums(6); queueData.setWriteQueueNums(6); return Collections.singletonList(queueData); } private byte[] getMessageResult() throws Exception { byte[] bytes = MessageDecoder.encode(createMessageExt(), false); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes); return byteBuffer.array(); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.setKeys("keys"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.CheckRocksdbCqWriteResult; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ObjectCreator; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.namesrv.TopAddressing; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.common.HeartbeatV2Result; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.AclInfo; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo; import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData; import org.apache.rocketmq.remoting.protocol.body.BrokerStatsItem; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.Connection; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeQueueData; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatsList; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.EpochEntryCache; import org.apache.rocketmq.remoting.protocol.body.GetConsumerStatusBody; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.remoting.protocol.body.KVTable; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.ProducerConnection; import org.apache.rocketmq.remoting.protocol.body.ProducerInfo; import org.apache.rocketmq.remoting.protocol.body.ProducerTableInfo; import org.apache.rocketmq.remoting.protocol.body.QueryAssignmentResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeQueueResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeTimeSpanBody; import org.apache.rocketmq.remoting.protocol.body.QueryCorrectionOffsetBody; import org.apache.rocketmq.remoting.protocol.body.QuerySubscriptionResponseBody; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import org.apache.rocketmq.remoting.protocol.subscription.GroupForbidden; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.assertj.core.api.Assertions; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.CountDownLatch; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class MQClientAPIImplTest { private MQClientAPIImpl mqClientAPI = new MQClientAPIImpl(new NettyClientConfig(), null, null, new ClientConfig()); @Mock private RemotingClient remotingClient; @Mock private DefaultMQProducerImpl defaultMQProducerImpl; @Mock private RemotingCommand response; private final String brokerAddr = "127.0.0.1"; private final String brokerName = "DefaultBroker"; private final String clusterName = "DefaultCluster"; private final String group = "FooBarGroup"; private final String topic = "FooBar"; private final Message msg = new Message("FooBar", new byte[]{}); private final String clientId = "127.0.0.2@UnitTest"; private final String defaultTopic = "defaultTopic"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final String defaultNsAddr = "127.0.0.1:9876"; private final long defaultTimeout = 3000L; @Before public void init() throws Exception { Field field = MQClientAPIImpl.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(mqClientAPI, remotingClient); } @Test public void testSendMessageOneWay_Success() throws RemotingException, InterruptedException, MQBrokerException { doNothing().when(remotingClient).invokeOneway(anyString(), any(RemotingCommand.class), anyLong()); SendResult sendResult = mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ONEWAY, new SendMessageContext(), defaultMQProducerImpl); assertThat(sendResult).isNull(); } @Test public void testSendMessageOneWay_WithException() throws RemotingException, InterruptedException, MQBrokerException { doThrow(new RemotingTimeoutException("Remoting Exception in Test")).when(remotingClient).invokeOneway(anyString(), any(RemotingCommand.class), anyLong()); try { mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ONEWAY, new SendMessageContext(), defaultMQProducerImpl); failBecauseExceptionWasNotThrown(RemotingException.class); } catch (RemotingException e) { assertThat(e).hasMessage("Remoting Exception in Test"); } doThrow(new InterruptedException("Interrupted Exception in Test")).when(remotingClient).invokeOneway(anyString(), any(RemotingCommand.class), anyLong()); try { mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ONEWAY, new SendMessageContext(), defaultMQProducerImpl); failBecauseExceptionWasNotThrown(InterruptedException.class); } catch (InterruptedException e) { assertThat(e).hasMessage("Interrupted Exception in Test"); } } @Test public void testSendMessageSync_Success() throws InterruptedException, RemotingException, MQBrokerException { doAnswer(mock -> { RemotingCommand request = mock.getArgument(1); return createSendMessageSuccessResponse(request); }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); SendMessageRequestHeader requestHeader = createSendMessageRequestHeader(); SendResult sendResult = mqClientAPI.sendMessage(brokerAddr, brokerName, msg, requestHeader, 3 * 1000, CommunicationMode.SYNC, new SendMessageContext(), defaultMQProducerImpl); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(123L); assertThat(sendResult.getMessageQueue().getQueueId()).isEqualTo(1); } @Test public void testSendMessageSync_WithException() throws InterruptedException, RemotingException { doAnswer(mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(ResponseCode.SYSTEM_ERROR); response.setOpaque(request.getOpaque()); response.setRemark("Broker is broken."); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); SendMessageRequestHeader requestHeader = createSendMessageRequestHeader(); try { mqClientAPI.sendMessage(brokerAddr, brokerName, msg, requestHeader, 3 * 1000, CommunicationMode.SYNC, new SendMessageContext(), defaultMQProducerImpl); failBecauseExceptionWasNotThrown(MQBrokerException.class); } catch (MQBrokerException e) { assertThat(e).hasMessageContaining("Broker is broken."); } } @Test public void testSendMessageAsync_Success() throws RemotingException, InterruptedException, MQBrokerException { doNothing().when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); SendResult sendResult = mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ASYNC, new SendMessageContext(), defaultMQProducerImpl); assertThat(sendResult).isNull(); doAnswer(mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); responseFuture.setResponseCommand(createSendMessageSuccessResponse(request)); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); SendMessageContext sendMessageContext = new SendMessageContext(); sendMessageContext.setProducer(new DefaultMQProducerImpl(new DefaultMQProducer())); mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(123L); assertThat(sendResult.getMessageQueue().getQueueId()).isEqualTo(1); } @Override public void onException(Throwable e) { } }, null, null, 0, sendMessageContext, defaultMQProducerImpl); } @Test public void testSendMessageAsync_WithException() throws RemotingException, InterruptedException, MQBrokerException { doThrow(new RemotingTimeoutException("Remoting Exception in Test")).when(remotingClient) .invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); SendMessageContext sendMessageContext = new SendMessageContext(); sendMessageContext.setProducer(new DefaultMQProducerImpl(new DefaultMQProducer())); mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { } @Override public void onException(Throwable e) { assertThat(e).hasMessage("Remoting Exception in Test"); } }, null, null, 0, sendMessageContext, defaultMQProducerImpl); doThrow(new InterruptedException("Interrupted Exception in Test")).when(remotingClient) .invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { } @Override public void onException(Throwable e) { assertThat(e).hasMessage("Interrupted Exception in Test"); } }, null, null, 0, sendMessageContext, defaultMQProducerImpl); } @Test public void testResumeCheckHalfMessage_WithException() throws RemotingException, InterruptedException { doAnswer(mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(ResponseCode.SYSTEM_ERROR); response.setOpaque(request.getOpaque()); response.setRemark("Put message back to RMQ_SYS_TRANS_HALF_TOPIC failed."); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); boolean result = mqClientAPI.resumeCheckHalfMessage(brokerAddr, "topic,", "test", 3000); assertThat(result).isEqualTo(false); } @Test public void testResumeCheckHalfMessage_Success() throws InterruptedException, RemotingException { doAnswer(mock -> { RemotingCommand request = mock.getArgument(1); return createResumeSuccessResponse(request); }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); boolean result = mqClientAPI.resumeCheckHalfMessage(brokerAddr, "topic", "test", 3000); assertThat(result).isEqualTo(true); } @Test public void testSendMessageTypeofReply() throws Exception { doAnswer(mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); responseFuture.setResponseCommand(createSendMessageSuccessResponse(request)); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(ArgumentMatchers.anyString(), ArgumentMatchers.any(RemotingCommand.class), ArgumentMatchers.anyLong(), ArgumentMatchers.any(InvokeCallback.class)); SendMessageContext sendMessageContext = new SendMessageContext(); sendMessageContext.setProducer(new DefaultMQProducerImpl(new DefaultMQProducer())); msg.getProperties().put("MSG_TYPE", "reply"); mqClientAPI.sendMessage(brokerAddr, brokerName, msg, new SendMessageRequestHeader(), 3 * 1000, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(123L); assertThat(sendResult.getMessageQueue().getQueueId()).isEqualTo(1); } @Override public void onException(Throwable e) { } }, null, null, 0, sendMessageContext, defaultMQProducerImpl); } @Test public void testQueryAssignment_Success() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); QueryAssignmentResponseBody b = new QueryAssignmentResponseBody(); b.setMessageQueueAssignments(Collections.singleton(new MessageQueueAssignment())); response.setBody(b.encode()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); Set assignments = mqClientAPI.queryAssignment(brokerAddr, topic, group, clientId, null, MessageModel.CLUSTERING, 10 * 1000); assertThat(assignments).size().isEqualTo(1); } @Test public void testPopMessageAsync_Success() throws Exception { final long popTime = System.currentTimeMillis(); final int invisibleTime = 10 * 1000; doAnswer((Answer) mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); responseHeader.setInvisibleTime(invisibleTime); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(0); responseHeader.setRestNum(1); StringBuilder startOffsetInfo = new StringBuilder(64); ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, topic, 0, 0L); responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); StringBuilder msgOffsetInfo = new StringBuilder(64); ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, topic, 0, Collections.singletonList(0L)); responseHeader.setMsgOffsetInfo(msgOffsetInfo.toString()); response.setRemark("FOUND"); response.makeCustomHeaderToNet(); MessageExt message = new MessageExt(); message.setQueueId(0); message.setFlag(12); message.setQueueOffset(0L); message.setCommitLogOffset(100L); message.setSysFlag(0); message.setBornTimestamp(System.currentTimeMillis()); message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); message.setStoreTimestamp(System.currentTimeMillis()); message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); message.setBody("body".getBytes()); message.setTopic(topic); message.putUserProperty("key", "value"); response.setBody(MessageDecoder.encode(message, false)); responseFuture.setResponseCommand(response); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); final CountDownLatch done = new CountDownLatch(1); mqClientAPI.popMessageAsync(brokerName, brokerAddr, new PopMessageRequestHeader(), 10 * 1000, new PopCallback() { @Override public void onSuccess(PopResult popResult) { assertThat(popResult.getPopStatus()).isEqualTo(PopStatus.FOUND); assertThat(popResult.getRestNum()).isEqualTo(1); assertThat(popResult.getInvisibleTime()).isEqualTo(invisibleTime); assertThat(popResult.getPopTime()).isEqualTo(popTime); assertThat(popResult.getMsgFoundList()).size().isEqualTo(1); done.countDown(); } @Override public void onException(Throwable e) { Assertions.fail("want no exception but got one", e); done.countDown(); } }); done.await(); } @Test public void testPopLmqMessage_async() throws Exception { final long popTime = System.currentTimeMillis(); final int invisibleTime = 10 * 1000; final String lmqTopic = MixAll.LMQ_PREFIX + "lmq1"; doAnswer((Answer) mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); responseHeader.setInvisibleTime(invisibleTime); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(0); responseHeader.setRestNum(1); StringBuilder startOffsetInfo = new StringBuilder(64); ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, topic, 0, 0L); responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); StringBuilder msgOffsetInfo = new StringBuilder(64); ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, topic, 0, Collections.singletonList(0L)); responseHeader.setMsgOffsetInfo(msgOffsetInfo.toString()); response.setRemark("FOUND"); response.makeCustomHeaderToNet(); MessageExt message = new MessageExt(); message.setQueueId(3); message.setFlag(0); message.setQueueOffset(5L); message.setCommitLogOffset(11111L); message.setSysFlag(0); message.setBornTimestamp(System.currentTimeMillis()); message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); message.setStoreTimestamp(System.currentTimeMillis()); message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); message.setBody("body".getBytes()); message.setTopic(topic); message.putUserProperty("key", "value"); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqTopic); message.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, String.valueOf(0)); response.setBody(MessageDecoder.encode(message, false)); responseFuture.setResponseCommand(response); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); final CountDownLatch done = new CountDownLatch(1); final PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setTopic(lmqTopic); mqClientAPI.popMessageAsync(brokerName, brokerAddr, requestHeader, 10 * 1000, new PopCallback() { @Override public void onSuccess(PopResult popResult) { assertThat(popResult.getPopStatus()).isEqualTo(PopStatus.FOUND); assertThat(popResult.getRestNum()).isEqualTo(1); assertThat(popResult.getInvisibleTime()).isEqualTo(invisibleTime); assertThat(popResult.getPopTime()).isEqualTo(popTime); assertThat(popResult.getMsgFoundList()).size().isEqualTo(1); assertThat(popResult.getMsgFoundList().get(0).getTopic()).isEqualTo(lmqTopic); assertThat(popResult.getMsgFoundList().get(0).getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)) .isEqualTo(lmqTopic); done.countDown(); } @Override public void onException(Throwable e) { Assertions.fail("want no exception but got one", e); done.countDown(); } }); done.await(); } @Test public void testPopMultiLmqMessage_async() throws Exception { final long popTime = System.currentTimeMillis(); final int invisibleTime = 10 * 1000; final String lmqTopic = MixAll.LMQ_PREFIX + "lmq1"; final String lmqTopic2 = MixAll.LMQ_PREFIX + "lmq2"; final String multiDispatch = String.join(MixAll.LMQ_DISPATCH_SEPARATOR, lmqTopic, lmqTopic2); final String multiOffset = String.join(MixAll.LMQ_DISPATCH_SEPARATOR, "0", "0"); doAnswer((Answer) mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); responseHeader.setInvisibleTime(invisibleTime); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(0); responseHeader.setRestNum(1); StringBuilder startOffsetInfo = new StringBuilder(64); ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, topic, 0, 0L); responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); StringBuilder msgOffsetInfo = new StringBuilder(64); ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, topic, 0, Collections.singletonList(0L)); responseHeader.setMsgOffsetInfo(msgOffsetInfo.toString()); response.setRemark("FOUND"); response.makeCustomHeaderToNet(); MessageExt message = new MessageExt(); message.setQueueId(0); message.setFlag(0); message.setQueueOffset(10L); message.setCommitLogOffset(10000L); message.setSysFlag(0); message.setBornTimestamp(System.currentTimeMillis()); message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); message.setStoreTimestamp(System.currentTimeMillis()); message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); message.setBody("body".getBytes()); message.setTopic(topic); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, multiDispatch); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, multiOffset); response.setBody(MessageDecoder.encode(message, false)); responseFuture.setResponseCommand(response); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); final CountDownLatch done = new CountDownLatch(1); final PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setTopic(lmqTopic); mqClientAPI.popMessageAsync(brokerName, brokerAddr, requestHeader, 10 * 1000, new PopCallback() { @Override public void onSuccess(PopResult popResult) { assertThat(popResult.getPopStatus()).isEqualTo(PopStatus.FOUND); assertThat(popResult.getRestNum()).isEqualTo(1); assertThat(popResult.getInvisibleTime()).isEqualTo(invisibleTime); assertThat(popResult.getPopTime()).isEqualTo(popTime); assertThat(popResult.getMsgFoundList()).size().isEqualTo(1); assertThat(popResult.getMsgFoundList().get(0).getTopic()).isEqualTo(lmqTopic); assertThat(popResult.getMsgFoundList().get(0).getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)) .isEqualTo(multiDispatch); assertThat(popResult.getMsgFoundList().get(0).getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET)) .isEqualTo(multiOffset); done.countDown(); } @Override public void onException(Throwable e) { Assertions.fail("want no exception but got one", e); done.countDown(); } }); done.await(); } @Test public void testAckMessageAsync_Success() throws Exception { doAnswer((Answer) mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); response.setOpaque(request.getOpaque()); response.setCode(ResponseCode.SUCCESS); responseFuture.setResponseCommand(response); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); final CountDownLatch done = new CountDownLatch(1); mqClientAPI.ackMessageAsync(brokerAddr, 10 * 1000, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { assertThat(ackResult.getStatus()).isEqualTo(AckStatus.OK); done.countDown(); } @Override public void onException(Throwable e) { Assertions.fail("want no exception but got one", e); done.countDown(); } }, new AckMessageRequestHeader()); done.await(); } @Test public void testChangeInvisibleTimeAsync_Success() throws Exception { doAnswer((Answer) mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); response.setOpaque(request.getOpaque()); response.setCode(ResponseCode.SUCCESS); ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.readCustomHeader(); responseHeader.setPopTime(System.currentTimeMillis()); responseHeader.setInvisibleTime(10 * 1000L); responseFuture.setResponseCommand(response); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); final CountDownLatch done = new CountDownLatch(1); ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(0); requestHeader.setOffset(0L); requestHeader.setInvisibleTime(10 * 1000L); mqClientAPI.changeInvisibleTimeAsync(brokerName, brokerAddr, requestHeader, 10 * 1000, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { assertThat(ackResult.getStatus()).isEqualTo(AckStatus.OK); done.countDown(); } @Override public void onException(Throwable e) { Assertions.fail("want no exception but got one", e); done.countDown(); } }); done.await(); } @Test public void testSetMessageRequestMode_Success() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); mqClientAPI.setMessageRequestMode(brokerAddr, topic, group, MessageRequestMode.POP, 8, 10 * 1000L); } @Test public void testCreateSubscriptionGroup_Success() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); mqClientAPI.createSubscriptionGroup(brokerAddr, new SubscriptionGroupConfig(), 10000); } @Test public void testCreateTopic_Success() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); mqClientAPI.createTopic(brokerAddr, topic, new TopicConfig(), 10000); } @Test public void testViewMessage() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(null); MessageExt message = new MessageExt(); message.setQueueId(0); message.setFlag(12); message.setQueueOffset(0L); message.setCommitLogOffset(100L); message.setSysFlag(0); message.setBornTimestamp(System.currentTimeMillis()); message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); message.setStoreTimestamp(System.currentTimeMillis()); message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); message.setBody("body".getBytes()); message.setTopic(topic); message.putUserProperty("key", "value"); response.setBody(MessageDecoder.encode(message, false)); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); MessageExt messageExt = mqClientAPI.viewMessage(brokerAddr, "topic", 100L, 10000); assertThat(messageExt.getTopic()).isEqualTo(topic); } @Test public void testSearchOffset() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class); final SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(100L); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); long offset = mqClientAPI.searchOffset(brokerAddr, topic, 0, System.currentTimeMillis() - 1000, 10000); assertThat(offset).isEqualTo(100L); } @Test public void testGetMaxOffset() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(GetMaxOffsetResponseHeader.class); final GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(100L); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); long offset = mqClientAPI.getMaxOffset(brokerAddr, new MessageQueue(topic, brokerName, 0), 10000); assertThat(offset).isEqualTo(100L); } @Test public void testGetMinOffset() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(GetMinOffsetResponseHeader.class); final GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(100L); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); long offset = mqClientAPI.getMinOffset(brokerAddr, new MessageQueue(topic, brokerName, 0), 10000); assertThat(offset).isEqualTo(100L); } @Test public void testGetEarliestMsgStoretime() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(GetEarliestMsgStoretimeResponseHeader.class); final GetEarliestMsgStoretimeResponseHeader responseHeader = (GetEarliestMsgStoretimeResponseHeader) response.readCustomHeader(); responseHeader.setTimestamp(100L); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); long t = mqClientAPI.getEarliestMsgStoretime(brokerAddr, new MessageQueue(topic, brokerName, 0), 10000); assertThat(t).isEqualTo(100L); } @Test public void testQueryConsumerOffset() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(QueryConsumerOffsetResponseHeader.class); final QueryConsumerOffsetResponseHeader responseHeader = (QueryConsumerOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(100L); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); long t = mqClientAPI.queryConsumerOffset(brokerAddr, new QueryConsumerOffsetRequestHeader(), 1000); assertThat(t).isEqualTo(100L); } @Test public void testUpdateConsumerOffset() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(UpdateConsumerOffsetResponseHeader.class); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); mqClientAPI.updateConsumerOffset(brokerAddr, new UpdateConsumerOffsetRequestHeader(), 1000); } @Test public void testGetConsumerIdListByGroup() throws Exception { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); final RemotingCommand response = RemotingCommand.createResponseCommand(GetConsumerListByGroupResponseHeader.class); GetConsumerListByGroupResponseBody body = new GetConsumerListByGroupResponseBody(); body.setConsumerIdList(Collections.singletonList("consumer1")); response.setBody(body.encode()); response.makeCustomHeaderToNet(); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); List consumerIdList = mqClientAPI.getConsumerIdListByGroup(brokerAddr, group, 10000); assertThat(consumerIdList).size().isGreaterThan(0); } private RemotingCommand createResumeSuccessResponse(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; } private RemotingCommand createSendMessageSuccessResponse(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader(); responseHeader.setMsgId("123"); responseHeader.setQueueId(1); responseHeader.setQueueOffset(123L); response.addExtField(MessageConst.PROPERTY_MSG_REGION, "RegionHZ"); response.addExtField(MessageConst.PROPERTY_TRACE_SWITCH, "true"); response.addExtField("queueId", String.valueOf(responseHeader.getQueueId())); response.addExtField("msgId", responseHeader.getMsgId()); response.addExtField("queueOffset", String.valueOf(responseHeader.getQueueOffset())); return response; } private RemotingCommand createSuccessResponse4UpdateAclConfig(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); response.markResponseType(); response.setRemark(null); return response; } private RemotingCommand createSuccessResponse4DeleteAclConfig(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); response.markResponseType(); response.setRemark(null); return response; } private SendMessageRequestHeader createSendMessageRequestHeader() { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setBornTimestamp(System.currentTimeMillis()); requestHeader.setTopic(topic); requestHeader.setProducerGroup(group); requestHeader.setQueueId(1); requestHeader.setMaxReconsumeTimes(10); return requestHeader; } @Test public void testAddWritePermOfBroker() throws Exception { doAnswer(invocationOnMock -> { RemotingCommand request = invocationOnMock.getArgument(1); if (request.getCode() != RequestCode.ADD_WRITE_PERM_OF_BROKER) { return null; } RemotingCommand response = RemotingCommand.createResponseCommand(AddWritePermOfBrokerResponseHeader.class); AddWritePermOfBrokerResponseHeader responseHeader = (AddWritePermOfBrokerResponseHeader) response.readCustomHeader(); response.setCode(ResponseCode.SUCCESS); responseHeader.setAddTopicCount(7); response.addExtField("addTopicCount", String.valueOf(responseHeader.getAddTopicCount())); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); int topicCnt = mqClientAPI.addWritePermOfBroker("127.0.0.1", "default-broker", 1000); assertThat(topicCnt).isEqualTo(7); } @Test public void testCreateTopicList_Success() throws RemotingException, InterruptedException, MQClientException { doAnswer((Answer) mock -> { RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); return response; }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); final List topicConfigList = new LinkedList<>(); for (int i = 0; i < 16; i++) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName("Topic" + i); topicConfigList.add(topicConfig); } mqClientAPI.createTopicList(brokerAddr, topicConfigList, 10000); } @Test public void assertFetchNameServerAddr() throws NoSuchFieldException, IllegalAccessException { setTopAddressing(); assertEquals(defaultNsAddr, mqClientAPI.fetchNameServerAddr()); } @Test public void assertOnNameServerAddressChange() { assertEquals(defaultNsAddr, mqClientAPI.onNameServerAddressChange(defaultNsAddr)); } @Test public void assertPullMessage() throws MQBrokerException, RemotingException, InterruptedException { PullMessageRequestHeader requestHeader = mock(PullMessageRequestHeader.class); mockInvokeSync(); PullCallback callback = mock(PullCallback.class); PullMessageResponseHeader responseHeader = mock(PullMessageResponseHeader.class); setResponseHeader(responseHeader); when(responseHeader.getNextBeginOffset()).thenReturn(1L); when(responseHeader.getMinOffset()).thenReturn(1L); when(responseHeader.getMaxOffset()).thenReturn(10L); when(responseHeader.getSuggestWhichBrokerId()).thenReturn(MixAll.MASTER_ID); PullResult actual = mqClientAPI.pullMessage(defaultBrokerAddr, requestHeader, defaultTimeout, CommunicationMode.SYNC, callback); assertNotNull(actual); assertEquals(1L, actual.getNextBeginOffset()); assertEquals(1L, actual.getMinOffset()); assertEquals(10L, actual.getMaxOffset()); assertEquals(PullStatus.FOUND, actual.getPullStatus()); assertNull(actual.getMsgFoundList()); } @Test public void testBatchAckMessageAsync() throws MQBrokerException, RemotingException, InterruptedException { AckCallback callback = mock(AckCallback.class); List extraInfoList = new ArrayList<>(); extraInfoList.add(String.format("%s %s %s %s %s %s %d %d", "1", "2", "3", "4", "5", brokerName, 7, 8)); mqClientAPI.batchAckMessageAsync(defaultBrokerAddr, defaultTimeout, callback, defaultTopic, "", extraInfoList); } @Test public void assertSearchOffset() throws MQBrokerException, RemotingException, InterruptedException { mockInvokeSync(); SearchOffsetResponseHeader responseHeader = mock(SearchOffsetResponseHeader.class); when(responseHeader.getOffset()).thenReturn(1L); setResponseHeader(responseHeader); assertEquals(1L, mqClientAPI.searchOffset(defaultBrokerAddr, new MessageQueue(), System.currentTimeMillis(), defaultTimeout)); } @Test public void testUpdateConsumerOffsetOneway() throws RemotingException, InterruptedException { UpdateConsumerOffsetRequestHeader requestHeader = mock(UpdateConsumerOffsetRequestHeader.class); mqClientAPI.updateConsumerOffsetOneway(defaultBrokerAddr, requestHeader, defaultTimeout); } @Test public void assertSendHeartbeat() throws MQBrokerException, RemotingException, InterruptedException { mockInvokeSync(); HeartbeatData heartbeatData = new HeartbeatData(); assertEquals(1, mqClientAPI.sendHeartbeat(defaultBrokerAddr, heartbeatData, defaultTimeout)); } @Test public void assertSendHeartbeatV2() throws MQBrokerException, RemotingException, InterruptedException { mockInvokeSync(); HeartbeatData heartbeatData = new HeartbeatData(); HeartbeatV2Result actual = mqClientAPI.sendHeartbeatV2(defaultBrokerAddr, heartbeatData, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getVersion()); assertFalse(actual.isSubChange()); assertFalse(actual.isSupportV2()); } @Test public void testUnregisterClient() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.unregisterClient(defaultBrokerAddr, "", "", "", defaultTimeout); } @Test public void testEndTransactionOneway() throws RemotingException, InterruptedException { mockInvokeSync(); EndTransactionRequestHeader requestHeader = mock(EndTransactionRequestHeader.class); mqClientAPI.endTransactionOneway(defaultBrokerAddr, requestHeader, "", defaultTimeout); } @Test public void testQueryMessage() throws MQBrokerException, RemotingException, InterruptedException { QueryMessageRequestHeader requestHeader = mock(QueryMessageRequestHeader.class); InvokeCallback callback = mock(InvokeCallback.class); mqClientAPI.queryMessage(defaultBrokerAddr, requestHeader, defaultTimeout, callback, false); } @Test public void testRegisterClient() throws RemotingException, InterruptedException { mockInvokeSync(); HeartbeatData heartbeatData = new HeartbeatData(); assertTrue(mqClientAPI.registerClient(defaultBrokerAddr, heartbeatData, defaultTimeout)); } @Test public void testConsumerSendMessageBack() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); MessageExt messageExt = mock(MessageExt.class); mqClientAPI.consumerSendMessageBack(defaultBrokerAddr, brokerName, messageExt, "", 1, defaultTimeout, 1000); } @Test public void assertLockBatchMQ() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); LockBatchRequestBody responseBody = new LockBatchRequestBody(); setResponseBody(responseBody); Set actual = mqClientAPI.lockBatchMQ(defaultBrokerAddr, responseBody, defaultTimeout); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void testUnlockBatchMQ() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); UnlockBatchRequestBody unlockBatchRequestBody = new UnlockBatchRequestBody(); mqClientAPI.unlockBatchMQ(defaultBrokerAddr, unlockBatchRequestBody, defaultTimeout, false); } @Test public void assertGetTopicStatsInfo() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); TopicStatsTable responseBody = new TopicStatsTable(); MessageQueue messageQueue = new MessageQueue(); TopicOffset topicOffset = new TopicOffset(); responseBody.getOffsetTable().put(messageQueue, topicOffset); setResponseBody(responseBody); TopicStatsTable actual = mqClientAPI.getTopicStatsInfo(defaultBrokerAddr, defaultTopic, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getOffsetTable().size()); } @Test public void assertGetConsumeStats() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); ConsumeStats responseBody = new ConsumeStats(); responseBody.setConsumeTps(1000); setResponseBody(responseBody); ConsumeStats actual = mqClientAPI.getConsumeStats(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals(1000, actual.getConsumeTps(), 0.0); } @Test public void assertGetProducerConnectionList() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); ProducerConnection responseBody = new ProducerConnection(); responseBody.getConnectionSet().add(new Connection()); setResponseBody(responseBody); ProducerConnection actual = mqClientAPI.getProducerConnectionList(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getConnectionSet().size()); } @Test public void assertGetAllProducerInfo() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); Map> data = new HashMap<>(); data.put("key", Collections.emptyList()); ProducerTableInfo responseBody = new ProducerTableInfo(data); setResponseBody(responseBody); ProducerTableInfo actual = mqClientAPI.getAllProducerInfo(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getData().size()); } @Test public void assertGetConsumerConnectionList() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); ConsumerConnection responseBody = new ConsumerConnection(); responseBody.setConsumeType(ConsumeType.CONSUME_ACTIVELY); responseBody.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); responseBody.setMessageModel(MessageModel.CLUSTERING); setResponseBody(responseBody); ConsumerConnection actual = mqClientAPI.getConsumerConnectionList(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals(ConsumeType.CONSUME_ACTIVELY, actual.getConsumeType()); assertEquals(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, actual.getConsumeFromWhere()); assertEquals(MessageModel.CLUSTERING, actual.getMessageModel()); } @Test public void assertGetBrokerRuntimeInfo() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); KVTable responseBody = new KVTable(); responseBody.getTable().put("key", "value"); setResponseBody(responseBody); KVTable actual = mqClientAPI.getBrokerRuntimeInfo(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTable().size()); } @Test public void testAddBroker() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.addBroker(defaultBrokerAddr, "", defaultTimeout); } @Test public void testRemoveBroker() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.removeBroker(defaultBrokerAddr, clusterName, brokerName, MixAll.MASTER_ID, defaultTimeout); } @Test public void testUpdateBrokerConfig() throws RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException, MQClientException { mockInvokeSync(); mqClientAPI.updateBrokerConfig(defaultBrokerAddr, createProperties(), defaultTimeout); } @Test public void assertGetBrokerConfig() throws RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException { mockInvokeSync(); setResponseBody("{\"key\":\"value\"}"); Properties actual = mqClientAPI.getBrokerConfig(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); } @Test public void testUpdateColdDataFlowCtrGroupConfig() throws RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException { mockInvokeSync(); Properties props = new Properties(); mqClientAPI.updateColdDataFlowCtrGroupConfig(defaultBrokerAddr, props, defaultTimeout); } @Test public void testRemoveColdDataFlowCtrGroupConfig() throws RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException { mockInvokeSync(); mqClientAPI.removeColdDataFlowCtrGroupConfig(defaultBrokerAddr, "", defaultTimeout); } @Test public void assertGetColdDataFlowCtrInfo() throws RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException { mockInvokeSync(); setResponseBody("{\"key\":\"value\"}"); String actual = mqClientAPI.getColdDataFlowCtrInfo(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals("\"{\\\"key\\\":\\\"value\\\"}\"", actual); } @Test public void assertSetCommitLogReadAheadMode() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); when(response.getRemark()).thenReturn("remark"); String actual = mqClientAPI.setCommitLogReadAheadMode(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals("remark", actual); } @Test public void assertGetBrokerClusterInfo() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); ClusterInfo responseBody = new ClusterInfo(); Map> clusterAddrTable = new HashMap<>(); clusterAddrTable.put(clusterName, new HashSet<>()); Map brokerAddrTable = new HashMap<>(); brokerAddrTable.put(brokerName, new BrokerData()); responseBody.setClusterAddrTable(clusterAddrTable); responseBody.setBrokerAddrTable(brokerAddrTable); setResponseBody(responseBody); ClusterInfo actual = mqClientAPI.getBrokerClusterInfo(defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getClusterAddrTable().size()); assertEquals(1, actual.getBrokerAddrTable().size()); } @Test public void assertGetDefaultTopicRouteInfoFromNameServer() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicRouteData responseBody = new TopicRouteData(); responseBody.getQueueDatas().add(new QueueData()); responseBody.getBrokerDatas().add(new BrokerData()); responseBody.getFilterServerTable().put("key", Collections.emptyList()); Map topicQueueMappingByBroker = new HashMap<>(); topicQueueMappingByBroker.put("key", new TopicQueueMappingInfo()); responseBody.setTopicQueueMappingByBroker(topicQueueMappingByBroker); setResponseBody(responseBody); TopicRouteData actual = mqClientAPI.getDefaultTopicRouteInfoFromNameServer(defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getQueueDatas().size()); assertEquals(1, actual.getBrokerDatas().size()); assertEquals(1, actual.getFilterServerTable().size()); assertEquals(1, actual.getTopicQueueMappingByBroker().size()); } @Test public void assertGetTopicRouteInfoFromNameServer() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicRouteData responseBody = new TopicRouteData(); responseBody.getQueueDatas().add(new QueueData()); responseBody.getBrokerDatas().add(new BrokerData()); responseBody.getFilterServerTable().put("key", Collections.emptyList()); Map topicQueueMappingByBroker = new HashMap<>(); topicQueueMappingByBroker.put("key", new TopicQueueMappingInfo()); responseBody.setTopicQueueMappingByBroker(topicQueueMappingByBroker); setResponseBody(responseBody); TopicRouteData actual = mqClientAPI.getTopicRouteInfoFromNameServer(defaultTopic, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getQueueDatas().size()); assertEquals(1, actual.getBrokerDatas().size()); assertEquals(1, actual.getFilterServerTable().size()); assertEquals(1, actual.getTopicQueueMappingByBroker().size()); } @Test public void assertGetTopicListFromNameServer() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.setBrokerAddr(defaultBrokerAddr); responseBody.getTopicList().add(defaultTopic); setResponseBody(responseBody); TopicList actual = mqClientAPI.getTopicListFromNameServer(defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTopicList().size()); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); } @Test public void assertWipeWritePermOfBroker() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); WipeWritePermOfBrokerResponseHeader responseHeader = mock(WipeWritePermOfBrokerResponseHeader.class); when(responseHeader.getWipeTopicCount()).thenReturn(1); setResponseHeader(responseHeader); assertEquals(1, mqClientAPI.wipeWritePermOfBroker(defaultNsAddr, brokerName, defaultTimeout)); } @Test public void testDeleteTopicInBroker() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); mqClientAPI.deleteTopicInBroker(defaultBrokerAddr, defaultTopic, defaultTimeout); } @Test public void testDeleteTopicInNameServer() throws RemotingException, InterruptedException, MQClientException, MQBrokerException { mockInvokeSync(); mqClientAPI.deleteTopicInNameServer(defaultNsAddr, defaultTopic, defaultTimeout); mqClientAPI.deleteTopicInNameServer(defaultNsAddr, clusterName, defaultTopic, defaultTimeout); } @Test public void testDeleteSubscriptionGroup() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); mqClientAPI.deleteSubscriptionGroup(defaultBrokerAddr, "", true, defaultTimeout); } @Test public void assertGetKVConfigValue() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); GetKVConfigResponseHeader responseHeader = mock(GetKVConfigResponseHeader.class); when(responseHeader.getValue()).thenReturn("value"); setResponseHeader(responseHeader); assertEquals("value", mqClientAPI.getKVConfigValue("", "", defaultTimeout)); } @Test public void testPutKVConfigValue() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); mqClientAPI.putKVConfigValue("", "", "", defaultTimeout); } @Test public void testDeleteKVConfigValue() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); mqClientAPI.deleteKVConfigValue("", "", defaultTimeout); } @Test public void assertGetKVListByNamespace() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); KVTable responseBody = new KVTable(); responseBody.getTable().put("key", "value"); setResponseBody(responseBody); KVTable actual = mqClientAPI.getKVListByNamespace("", defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTable().size()); } @Test public void assertInvokeBrokerToResetOffset() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); ResetOffsetBody responseBody = new ResetOffsetBody(); responseBody.getOffsetTable().put(new MessageQueue(), 1L); setResponseBody(responseBody); Map actual = mqClientAPI.invokeBrokerToResetOffset(defaultBrokerAddr, defaultTopic, "", System.currentTimeMillis(), false, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); actual = mqClientAPI.invokeBrokerToResetOffset(defaultBrokerAddr, defaultTopic, "", System.currentTimeMillis(), 1, 1L, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); } @Test public void assertInvokeBrokerToGetConsumerStatus() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); GetConsumerStatusBody responseBody = new GetConsumerStatusBody(); responseBody.getConsumerTable().put("key", new HashMap<>()); responseBody.getMessageQueueTable().put(new MessageQueue(), 1L); setResponseBody(responseBody); Map> actual = mqClientAPI.invokeBrokerToGetConsumerStatus(defaultBrokerAddr, defaultTopic, "", "", defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); } @Test public void assertQueryTopicConsumeByWho() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); GroupList responseBody = new GroupList(); responseBody.getGroupList().add(""); setResponseBody(responseBody); GroupList actual = mqClientAPI.queryTopicConsumeByWho(defaultBrokerAddr, defaultTopic, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getGroupList().size()); } @Test public void assertQueryTopicsByConsumer() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.getTopicList().add(defaultTopic); responseBody.setBrokerAddr(defaultBrokerAddr); setResponseBody(responseBody); TopicList actual = mqClientAPI.queryTopicsByConsumer(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTopicList().size()); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); } @Test public void assertQuerySubscriptionByConsumer() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); QuerySubscriptionResponseBody responseBody = new QuerySubscriptionResponseBody(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(defaultTopic); responseBody.setSubscriptionData(subscriptionData); setResponseBody(responseBody); SubscriptionData actual = mqClientAPI.querySubscriptionByConsumer(defaultBrokerAddr, group, defaultTopic, defaultTimeout); assertNotNull(actual); assertEquals(defaultTopic, actual.getTopic()); } @Test public void assertQueryConsumeTimeSpan() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); QueryConsumeTimeSpanBody responseBody = new QueryConsumeTimeSpanBody(); responseBody.getConsumeTimeSpanSet().add(new QueueTimeSpan()); setResponseBody(responseBody); List actual = mqClientAPI.queryConsumeTimeSpan(defaultBrokerAddr, defaultTopic, group, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); } @Test public void assertGetTopicsByCluster() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.setBrokerAddr(defaultBrokerAddr); responseBody.setTopicList(Collections.singleton(defaultTopic)); setResponseBody(responseBody); TopicList actual = mqClientAPI.getTopicsByCluster(clusterName, defaultTimeout); assertNotNull(actual); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); assertEquals(1, actual.getTopicList().size()); assertTrue(actual.getTopicList().contains(defaultTopic)); } @Test public void assertGetSystemTopicList() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.setBrokerAddr(defaultBrokerAddr); responseBody.setTopicList(Collections.singleton(defaultTopic)); setResponseBody(responseBody); TopicList actual = mqClientAPI.getSystemTopicList(defaultTimeout); assertNotNull(actual); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); assertEquals(1, actual.getTopicList().size()); assertTrue(actual.getTopicList().contains(defaultTopic)); } @Test public void assertGetSystemTopicListFromBroker() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.setBrokerAddr(defaultBrokerAddr); responseBody.setTopicList(Collections.singleton(defaultTopic)); setResponseBody(responseBody); TopicList actual = mqClientAPI.getSystemTopicListFromBroker(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); assertEquals(1, actual.getTopicList().size()); assertTrue(actual.getTopicList().contains(defaultTopic)); } @Test public void assertCleanExpiredConsumeQueue() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); assertTrue(mqClientAPI.cleanExpiredConsumeQueue(defaultBrokerAddr, defaultTimeout)); } @Test public void assertDeleteExpiredCommitLog() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); assertTrue(mqClientAPI.deleteExpiredCommitLog(defaultBrokerAddr, defaultTimeout)); } @Test public void assertCleanUnusedTopicByAddr() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); assertTrue(mqClientAPI.cleanUnusedTopicByAddr(defaultBrokerAddr, defaultTimeout)); } @Test public void assertGetConsumerRunningInfo() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); ConsumerRunningInfo responseBody = new ConsumerRunningInfo(); responseBody.setJstack("jstack"); responseBody.getUserConsumerInfo().put("key", "value"); setResponseBody(responseBody); ConsumerRunningInfo actual = mqClientAPI.getConsumerRunningInfo(defaultBrokerAddr, group, clientId, false, defaultTimeout); assertNotNull(actual); assertEquals("jstack", actual.getJstack()); assertEquals(1, actual.getUserConsumerInfo().size()); } @Test public void assertConsumeMessageDirectly() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); ConsumeMessageDirectlyResult responseBody = new ConsumeMessageDirectlyResult(); responseBody.setAutoCommit(true); responseBody.setRemark("remark"); setResponseBody(responseBody); ConsumeMessageDirectlyResult actual = mqClientAPI.consumeMessageDirectly(defaultBrokerAddr, group, clientId, topic, "", defaultTimeout); assertNotNull(actual); assertEquals("remark", actual.getRemark()); assertTrue(actual.isAutoCommit()); } @Test public void assertQueryCorrectionOffset() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); QueryCorrectionOffsetBody responseBody = new QueryCorrectionOffsetBody(); responseBody.getCorrectionOffsets().put(1, 1L); setResponseBody(responseBody); Map actual = mqClientAPI.queryCorrectionOffset(defaultBrokerAddr, topic, group, new HashSet<>(), defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); assertTrue(actual.containsKey(1)); assertTrue(actual.containsValue(1L)); } @Test public void assertGetUnitTopicList() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.getTopicList().add(defaultTopic); setResponseBody(responseBody); TopicList actual = mqClientAPI.getUnitTopicList(false, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTopicList().size()); } @Test public void assertGetHasUnitSubTopicList() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.getTopicList().add(defaultTopic); setResponseBody(responseBody); TopicList actual = mqClientAPI.getHasUnitSubTopicList(false, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTopicList().size()); } @Test public void assertGetHasUnitSubUnUnitTopicList() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); TopicList responseBody = new TopicList(); responseBody.getTopicList().add(defaultTopic); setResponseBody(responseBody); TopicList actual = mqClientAPI.getHasUnitSubUnUnitTopicList(false, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTopicList().size()); } @Test public void testCloneGroupOffset() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); mqClientAPI.cloneGroupOffset(defaultBrokerAddr, "", "", defaultTopic, false, defaultTimeout); } @Test public void assertViewBrokerStatsData() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); BrokerStatsData responseBody = new BrokerStatsData(); responseBody.setStatsDay(new BrokerStatsItem()); setResponseBody(responseBody); BrokerStatsData actual = mqClientAPI.viewBrokerStatsData(defaultBrokerAddr, "", "", defaultTimeout); assertNotNull(actual); assertNotNull(actual.getStatsDay()); } @Test public void assertGetClusterList() { Set actual = mqClientAPI.getClusterList(topic, defaultTimeout); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void assertFetchConsumeStatsInBroker() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); ConsumeStatsList responseBody = new ConsumeStatsList(); responseBody.setBrokerAddr(defaultBrokerAddr); responseBody.getConsumeStatsList().add(new HashMap<>()); setResponseBody(responseBody); ConsumeStatsList actual = mqClientAPI.fetchConsumeStatsInBroker(defaultBrokerAddr, false, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getConsumeStatsList().size()); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); } @Test public void assertGetAllSubscriptionGroupForSubscriptionGroupWrapper() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); SubscriptionGroupWrapper responseBody = new SubscriptionGroupWrapper(); responseBody.getSubscriptionGroupTable().put("key", new SubscriptionGroupConfig()); setResponseBody(responseBody); SubscriptionGroupWrapper actual = mqClientAPI.getAllSubscriptionGroup(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getSubscriptionGroupTable().size()); assertNotNull(actual.getDataVersion()); assertEquals(0, actual.getDataVersion().getStateVersion()); } @Test public void assertGetAllSubscriptionGroupForSubscriptionGroupConfig() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); SubscriptionGroupConfig responseBody = new SubscriptionGroupConfig(); responseBody.setGroupName(group); responseBody.setBrokerId(MixAll.MASTER_ID); setResponseBody(responseBody); SubscriptionGroupConfig actual = mqClientAPI.getSubscriptionGroupConfig(defaultBrokerAddr, group, defaultTimeout); assertNotNull(actual); assertEquals(group, actual.getGroupName()); assertEquals(MixAll.MASTER_ID, actual.getBrokerId()); } @Test public void assertGetAllTopicConfig() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); TopicConfigSerializeWrapper responseBody = new TopicConfigSerializeWrapper(); responseBody.getTopicConfigTable().put("key", new TopicConfig()); setResponseBody(responseBody); TopicConfigSerializeWrapper actual = mqClientAPI.getAllTopicConfig(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getTopicConfigTable().size()); assertNotNull(actual.getDataVersion()); assertEquals(0, actual.getDataVersion().getStateVersion()); } @Test public void testUpdateNameServerConfig() throws RemotingException, InterruptedException, MQClientException, UnsupportedEncodingException { mockInvokeSync(); mqClientAPI.updateNameServerConfig(createProperties(), Collections.singletonList(defaultNsAddr), defaultTimeout); } @Test public void assertGetNameServerConfig() throws RemotingException, InterruptedException, UnsupportedEncodingException, MQClientException { mockInvokeSync(); setResponseBody("{\"key\":\"value\"}"); Map actual = mqClientAPI.getNameServerConfig(Collections.singletonList(defaultNsAddr), defaultTimeout); assertNotNull(actual); assertEquals(1, actual.size()); assertTrue(actual.containsKey(defaultNsAddr)); } @Test public void assertQueryConsumeQueue() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); QueryConsumeQueueResponseBody responseBody = new QueryConsumeQueueResponseBody(); responseBody.setQueueData(Collections.singletonList(new ConsumeQueueData())); setResponseBody(responseBody); QueryConsumeQueueResponseBody actual = mqClientAPI.queryConsumeQueue(defaultBrokerAddr, defaultTopic, 1, 1, 1, group, defaultTimeout); assertNotNull(actual); assertEquals(1, actual.getQueueData().size()); } @Test public void testCheckClientInBroker() throws RemotingException, InterruptedException, MQClientException { mockInvokeSync(); mqClientAPI.checkClientInBroker(defaultBrokerAddr, group, clientId, new SubscriptionData(), defaultTimeout); } @Test public void assertGetTopicConfig() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); TopicConfigAndQueueMapping responseBody = new TopicConfigAndQueueMapping(new TopicConfig(), new TopicQueueMappingDetail()); setResponseBody(responseBody); TopicConfigAndQueueMapping actual = mqClientAPI.getTopicConfig(defaultBrokerAddr, defaultTopic, defaultTimeout); assertNotNull(actual); assertNotNull(actual.getMappingDetail()); } @Test public void testCreateStaticTopic() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.createStaticTopic(defaultBrokerAddr, defaultTopic, new TopicConfig(), new TopicQueueMappingDetail(), false, defaultTimeout); } @Test public void assertUpdateAndGetGroupForbidden() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); GroupForbidden responseBody = new GroupForbidden(); responseBody.setGroup(group); responseBody.setTopic(defaultTopic); setResponseBody(responseBody); GroupForbidden actual = mqClientAPI.updateAndGetGroupForbidden(defaultBrokerAddr, new UpdateGroupForbiddenRequestHeader(), defaultTimeout); assertNotNull(actual); assertEquals(group, actual.getGroup()); assertEquals(defaultTopic, actual.getTopic()); } @Test public void testResetMasterFlushOffset() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.resetMasterFlushOffset(defaultBrokerAddr, 1L); } @Test public void assertGetBrokerHAStatus() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); HARuntimeInfo responseBody = new HARuntimeInfo(); responseBody.setMaster(true); responseBody.setMasterCommitLogMaxOffset(1L); setResponseBody(responseBody); HARuntimeInfo actual = mqClientAPI.getBrokerHAStatus(defaultBrokerAddr, defaultTimeout); assertNotNull(actual); assertEquals(1L, actual.getMasterCommitLogMaxOffset()); assertTrue(actual.isMaster()); } @Test public void assertGetControllerMetaData() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); GetMetaDataResponseHeader responseHeader = new GetMetaDataResponseHeader(); responseHeader.setGroup(group); responseHeader.setIsLeader(true); setResponseHeader(responseHeader); GetMetaDataResponseHeader actual = mqClientAPI.getControllerMetaData(defaultBrokerAddr); assertNotNull(actual); assertEquals(group, actual.getGroup()); assertTrue(actual.isLeader()); } @Test public void assertGetInSyncStateData() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); BrokerReplicasInfo responseBody = new BrokerReplicasInfo(); BrokerReplicasInfo.ReplicasInfo replicasInfo = new BrokerReplicasInfo.ReplicasInfo(MixAll.MASTER_ID, defaultBrokerAddr, 1, 1, Collections.emptyList(), Collections.emptyList()); responseBody.getReplicasInfoTable().put("key", replicasInfo); GetMetaDataResponseHeader responseHeader = new GetMetaDataResponseHeader(); responseHeader.setControllerLeaderAddress(defaultBrokerAddr); setResponseHeader(responseHeader); setResponseBody(responseBody); BrokerReplicasInfo actual = mqClientAPI.getInSyncStateData(defaultBrokerAddr, Collections.singletonList(defaultBrokerAddr)); assertNotNull(actual); assertEquals(1L, actual.getReplicasInfoTable().size()); } @Test public void assertGetBrokerEpochCache() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); EpochEntryCache responseBody = new EpochEntryCache(clusterName, brokerName, MixAll.MASTER_ID, Collections.emptyList(), 1); setResponseBody(responseBody); EpochEntryCache actual = mqClientAPI.getBrokerEpochCache(defaultBrokerAddr); assertNotNull(actual); assertEquals(1L, actual.getMaxOffset()); assertEquals(MixAll.MASTER_ID, actual.getBrokerId()); assertEquals(brokerName, actual.getBrokerName()); assertEquals(clusterName, actual.getClusterName()); } @Test public void assertGetControllerConfig() throws RemotingException, InterruptedException, UnsupportedEncodingException, MQClientException { mockInvokeSync(); setResponseBody("{\"key\":\"value\"}"); Map actual = mqClientAPI.getControllerConfig(Collections.singletonList(defaultBrokerAddr), defaultTimeout); assertNotNull(actual); assertEquals(1L, actual.size()); } @Test public void testUpdateControllerConfig() throws RemotingException, InterruptedException, UnsupportedEncodingException, MQClientException { mockInvokeSync(); mqClientAPI.updateControllerConfig(createProperties(), Collections.singletonList(defaultBrokerAddr), defaultTimeout); } @Test public void assertElectMaster() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); BrokerMemberGroup responseBody = new BrokerMemberGroup(); setResponseBody(responseBody); GetMetaDataResponseHeader getMetaDataResponseHeader = new GetMetaDataResponseHeader(); getMetaDataResponseHeader.setControllerLeaderAddress(defaultBrokerAddr); when(response.decodeCommandCustomHeader(GetMetaDataResponseHeader.class)).thenReturn(getMetaDataResponseHeader); ElectMasterResponseHeader responseHeader = new ElectMasterResponseHeader(); when(response.decodeCommandCustomHeader(ElectMasterResponseHeader.class)).thenReturn(responseHeader); Pair actual = mqClientAPI.electMaster(defaultBrokerAddr, clusterName, brokerName, MixAll.MASTER_ID); assertNotNull(actual); assertEquals(responseHeader, actual.getObject1()); assertEquals(responseBody, actual.getObject2()); } @Test public void testCleanControllerBrokerData() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); GetMetaDataResponseHeader responseHeader = new GetMetaDataResponseHeader(); responseHeader.setControllerLeaderAddress(defaultBrokerAddr); setResponseHeader(responseHeader); mqClientAPI.cleanControllerBrokerData(defaultBrokerAddr, clusterName, brokerName, "", false); } @Test public void testCreateUser() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.createUser(defaultBrokerAddr, new UserInfo(), defaultTimeout); } @Test public void testUpdateUser() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.updateUser(defaultBrokerAddr, new UserInfo(), defaultTimeout); } @Test public void testDeleteUser() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.deleteUser(defaultBrokerAddr, "", defaultTimeout); } @Test public void assertGetUser() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); setResponseBody(createUserInfo()); UserInfo actual = mqClientAPI.getUser(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals("username", actual.getUsername()); assertEquals("password", actual.getPassword()); assertEquals("userStatus", actual.getUserStatus()); assertEquals("userType", actual.getUserType()); } @Test public void assertListUser() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); setResponseBody(Collections.singletonList(createUserInfo())); List actual = mqClientAPI.listUser(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals("username", actual.get(0).getUsername()); assertEquals("password", actual.get(0).getPassword()); assertEquals("userStatus", actual.get(0).getUserStatus()); assertEquals("userType", actual.get(0).getUserType()); } @Test public void testCreateAcl() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.createAcl(defaultBrokerAddr, new AclInfo(), defaultTimeout); } @Test public void testUpdateAcl() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.updateAcl(defaultBrokerAddr, new AclInfo(), defaultTimeout); } @Test public void testDeleteAcl() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); mqClientAPI.deleteAcl(defaultBrokerAddr, "", "", defaultTimeout); } @Test public void assertGetAcl() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); setResponseBody(createAclInfo()); AclInfo actual = mqClientAPI.getAcl(defaultBrokerAddr, "", defaultTimeout); assertNotNull(actual); assertEquals("subject", actual.getSubject()); assertEquals(1, actual.getPolicies().size()); } @Test public void assertListAcl() throws RemotingException, InterruptedException, MQBrokerException { mockInvokeSync(); setResponseBody(Collections.singletonList(createAclInfo())); List actual = mqClientAPI.listAcl(defaultBrokerAddr, "", "", defaultTimeout); assertNotNull(actual); assertEquals("subject", actual.get(0).getSubject()); assertEquals(1, actual.get(0).getPolicies().size()); } @Test public void testRecallMessage() throws RemotingException, InterruptedException, MQBrokerException { RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup(group); requestHeader.setTopic(topic); requestHeader.setRecallHandle("handle"); requestHeader.setBrokerName(brokerName); // success mockInvokeSync(); String msgId = MessageClientIDSetter.createUniqID(); RecallMessageResponseHeader responseHeader = new RecallMessageResponseHeader(); responseHeader.setMsgId(msgId); setResponseHeader(responseHeader); String result = mqClientAPI.recallMessage(defaultBrokerAddr, requestHeader, defaultTimeout); assertEquals(msgId, result); // error when(response.getCode()).thenReturn(ResponseCode.SYSTEM_ERROR); when(response.getRemark()).thenReturn("error"); MQBrokerException e = assertThrows(MQBrokerException.class, () -> { mqClientAPI.recallMessage(defaultBrokerAddr, requestHeader, defaultTimeout); }); assertEquals(ResponseCode.SYSTEM_ERROR, e.getResponseCode()); assertEquals("error", e.getErrorMessage()); assertEquals(defaultBrokerAddr, e.getBrokerAddr()); } @Test public void testRecallMessageAsync() throws RemotingException, InterruptedException { RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup(group); requestHeader.setTopic(topic); requestHeader.setRecallHandle("handle"); requestHeader.setBrokerName(brokerName); String msgId = "msgId"; doAnswer((Answer) mock -> { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(RecallMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); RecallMessageResponseHeader responseHeader = (RecallMessageResponseHeader) response.readCustomHeader(); responseHeader.setMsgId(msgId); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); responseFuture.setResponseCommand(response); callback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); final CountDownLatch done = new CountDownLatch(1); mqClientAPI.recallMessageAsync(defaultBrokerAddr, requestHeader, defaultTimeout, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { RecallMessageResponseHeader responseHeader = (RecallMessageResponseHeader) response.readCustomHeader(); Assert.assertEquals(msgId, responseHeader.getMsgId()); done.countDown(); } @Override public void operationFail(Throwable throwable) { } }); done.await(); } @Test public void testMQClientAPIImplWithoutObjectCreator() { MQClientAPIImpl clientAPI = new MQClientAPIImpl( new NettyClientConfig(), null, null, new ClientConfig(), null, null ); RemotingClient remotingClient1 = clientAPI.getRemotingClient(); Assert.assertTrue(remotingClient1 instanceof NettyRemotingClient); } @Test public void testMQClientAPIImplWithObjectCreator() { ObjectCreator clientObjectCreator = args -> new MockRemotingClientTest((NettyClientConfig) args[0]); final NettyClientConfig nettyClientConfig = new NettyClientConfig(); MQClientAPIImpl clientAPI = new MQClientAPIImpl( nettyClientConfig, null, null, new ClientConfig(), null, clientObjectCreator ); RemotingClient remotingClient1 = clientAPI.getRemotingClient(); Assert.assertTrue(remotingClient1 instanceof MockRemotingClientTest); MockRemotingClientTest remotingClientTest = (MockRemotingClientTest) remotingClient1; Assert.assertSame(remotingClientTest.getNettyClientConfig(), nettyClientConfig); } private static class MockRemotingClientTest extends NettyRemotingClient { public MockRemotingClientTest(NettyClientConfig nettyClientConfig) { super(nettyClientConfig); } public NettyClientConfig getNettyClientConfig() { return nettyClientConfig; } } @Test public void testCheckRocksdbCqWriteProgress() throws Exception { RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success"); CheckRocksdbCqWriteResult expectedResult = new CheckRocksdbCqWriteResult(); expectedResult.setCheckStatus(CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue()); response.setBody(JSON.toJSONString(expectedResult).getBytes()); when(remotingClient.invokeSync(any(String.class), any(RemotingCommand.class), any(Long.class))) .thenReturn(response); CheckRocksdbCqWriteResult result = mqClientAPI.checkRocksdbCqWriteProgress( "brokerAddr", "testTopic", 12345L, 3000L); assertEquals(CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue(), result.getCheckStatus()); } private Properties createProperties() { Properties result = new Properties(); result.put("key", "value"); return result; } private AclInfo createAclInfo() { return AclInfo.of("subject", Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), ""); } private UserInfo createUserInfo() { UserInfo result = new UserInfo(); result.setUsername("username"); result.setPassword("password"); result.setUserStatus("userStatus"); result.setUserType("userType"); return result; } private void setResponseHeader(CommandCustomHeader responseHeader) throws RemotingCommandException { when(response.decodeCommandCustomHeader(any())).thenReturn(responseHeader); } private void setResponseBody(Object responseBody) { when(response.getBody()).thenReturn(RemotingSerializable.encode(responseBody)); } private void mockInvokeSync() throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { when(response.getCode()).thenReturn(ResponseCode.SUCCESS); when(response.getVersion()).thenReturn(1); when(remotingClient.invokeSync(any(), any(), anyLong())).thenReturn(response); when(remotingClient.getNameServerAddressList()).thenReturn(Collections.singletonList(defaultNsAddr)); } private void setTopAddressing() throws NoSuchFieldException, IllegalAccessException { TopAddressing topAddressing = mock(TopAddressing.class); setField(mqClientAPI, "topAddressing", topAddressing); when(topAddressing.fetchNSAddr()).thenReturn(defaultNsAddr); } private void setField(final Object target, final String fieldName, final Object newValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = target.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(target, newValue); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/admin/MqClientAdminImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.admin; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeTimeSpanBody; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.body.ResetOffsetBody; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumeTimeSpanRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QuerySubscriptionByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicConsumeByWhoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryTopicsByConsumerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class MqClientAdminImplTest { @Mock private RemotingClient remotingClient; @Mock private RemotingCommand response; private MqClientAdminImpl mqClientAdminImpl; private final String defaultTopic = "defaultTopic"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final long defaultTimeout = 3000L; @Before public void init() throws RemotingException, InterruptedException, MQClientException { mqClientAdminImpl = new MqClientAdminImpl(remotingClient); when(remotingClient.invoke(any(String.class), any(RemotingCommand.class), any(Long.class))).thenReturn(CompletableFuture.completedFuture(response)); } @Test public void assertQueryMessageWithSuccess() throws Exception { setResponseSuccess(getMessageResult()); QueryMessageRequestHeader requestHeader = mock(QueryMessageRequestHeader.class); when(requestHeader.getTopic()).thenReturn(defaultTopic); when(requestHeader.getKey()).thenReturn("keys"); CompletableFuture> actual = mqClientAdminImpl.queryMessage(defaultBrokerAddr, false, false, requestHeader, defaultTimeout); List messageExtList = actual.get(); assertNotNull(messageExtList); assertEquals(1, messageExtList.size()); } @Test public void assertQueryMessageWithNotFound() throws Exception { when(response.getCode()).thenReturn(ResponseCode.QUERY_NOT_FOUND); QueryMessageRequestHeader requestHeader = mock(QueryMessageRequestHeader.class); CompletableFuture> actual = mqClientAdminImpl.queryMessage(defaultBrokerAddr, false, false, requestHeader, defaultTimeout); List messageExtList = actual.get(); assertNotNull(messageExtList); assertEquals(0, messageExtList.size()); } @Test public void assertQueryMessageWithError() { setResponseError(); QueryMessageRequestHeader requestHeader = mock(QueryMessageRequestHeader.class); CompletableFuture> actual = mqClientAdminImpl.queryMessage(defaultBrokerAddr, false, false, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertGetTopicStatsInfoWithSuccess() throws Exception { TopicStatsTable responseBody = new TopicStatsTable(); setResponseSuccess(RemotingSerializable.encode(responseBody)); GetTopicStatsInfoRequestHeader requestHeader = mock(GetTopicStatsInfoRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getTopicStatsInfo(defaultBrokerAddr, requestHeader, defaultTimeout); TopicStatsTable topicStatsTable = actual.get(); assertNotNull(topicStatsTable); assertEquals(0, topicStatsTable.getOffsetTable().size()); } @Test public void assertGetTopicStatsInfoWithError() { setResponseError(); GetTopicStatsInfoRequestHeader requestHeader = mock(GetTopicStatsInfoRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getTopicStatsInfo(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertQueryConsumeTimeSpanWithSuccess() throws Exception { QueryConsumeTimeSpanBody responseBody = new QueryConsumeTimeSpanBody(); setResponseSuccess(RemotingSerializable.encode(responseBody)); QueryConsumeTimeSpanRequestHeader requestHeader = mock(QueryConsumeTimeSpanRequestHeader.class); CompletableFuture> actual = mqClientAdminImpl.queryConsumeTimeSpan(defaultBrokerAddr, requestHeader, defaultTimeout); List queueTimeSpans = actual.get(); assertNotNull(queueTimeSpans); assertEquals(0, queueTimeSpans.size()); } @Test public void assertQueryConsumeTimeSpanWithError() { setResponseError(); QueryConsumeTimeSpanRequestHeader requestHeader = mock(QueryConsumeTimeSpanRequestHeader.class); CompletableFuture> actual = mqClientAdminImpl.queryConsumeTimeSpan(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertUpdateOrCreateTopicWithSuccess() throws Exception { setResponseSuccess(null); CreateTopicRequestHeader requestHeader = mock(CreateTopicRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.updateOrCreateTopic(defaultBrokerAddr, requestHeader, defaultTimeout); assertNull(actual.get()); } @Test public void assertUpdateOrCreateTopicWithError() { setResponseError(); CreateTopicRequestHeader requestHeader = mock(CreateTopicRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.updateOrCreateTopic(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertUpdateOrCreateSubscriptionGroupWithSuccess() throws Exception { setResponseSuccess(null); SubscriptionGroupConfig config = new SubscriptionGroupConfig(); CompletableFuture actual = mqClientAdminImpl.updateOrCreateSubscriptionGroup(defaultBrokerAddr, config, defaultTimeout); assertNull(actual.get()); } @Test public void assertUpdateOrCreateSubscriptionGroupWithError() { setResponseError(); SubscriptionGroupConfig config = new SubscriptionGroupConfig(); CompletableFuture actual = mqClientAdminImpl.updateOrCreateSubscriptionGroup(defaultBrokerAddr, config, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertDeleteTopicInBrokerWithSuccess() throws Exception { setResponseSuccess(null); DeleteTopicRequestHeader requestHeader = mock(DeleteTopicRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteTopicInBroker(defaultBrokerAddr, requestHeader, defaultTimeout); assertNull(actual.get()); } @Test public void assertDeleteTopicInBrokerWithError() { setResponseError(); DeleteTopicRequestHeader requestHeader = mock(DeleteTopicRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteTopicInBroker(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertDeleteTopicInNameserverWithSuccess() throws Exception { setResponseSuccess(null); DeleteTopicFromNamesrvRequestHeader requestHeader = mock(DeleteTopicFromNamesrvRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteTopicInNameserver(defaultBrokerAddr, requestHeader, defaultTimeout); assertNull(actual.get()); } @Test public void assertDeleteTopicInNameserverWithError() { setResponseError(); DeleteTopicFromNamesrvRequestHeader requestHeader = mock(DeleteTopicFromNamesrvRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteTopicInNameserver(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertDeleteKvConfigWithSuccess() throws Exception { setResponseSuccess(null); DeleteKVConfigRequestHeader requestHeader = mock(DeleteKVConfigRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteKvConfig(defaultBrokerAddr, requestHeader, defaultTimeout); assertNull(actual.get()); } @Test public void assertDeleteKvConfigWithError() { setResponseError(); DeleteKVConfigRequestHeader requestHeader = mock(DeleteKVConfigRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteKvConfig(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertDeleteSubscriptionGroupWithSuccess() throws Exception { setResponseSuccess(null); DeleteSubscriptionGroupRequestHeader requestHeader = mock(DeleteSubscriptionGroupRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteSubscriptionGroup(defaultBrokerAddr, requestHeader, defaultTimeout); assertNull(actual.get()); } @Test public void assertDeleteSubscriptionGroupWithError() { setResponseError(); DeleteSubscriptionGroupRequestHeader requestHeader = mock(DeleteSubscriptionGroupRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.deleteSubscriptionGroup(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertInvokeBrokerToResetOffsetWithSuccess() throws Exception { ResetOffsetBody responseBody = new ResetOffsetBody(); setResponseSuccess(RemotingSerializable.encode(responseBody)); ResetOffsetRequestHeader requestHeader = mock(ResetOffsetRequestHeader.class); CompletableFuture> actual = mqClientAdminImpl.invokeBrokerToResetOffset(defaultBrokerAddr, requestHeader, defaultTimeout); assertEquals(0, actual.get().size()); } @Test public void assertInvokeBrokerToResetOffsetWithError() { setResponseError(); ResetOffsetRequestHeader requestHeader = mock(ResetOffsetRequestHeader.class); CompletableFuture> actual = mqClientAdminImpl.invokeBrokerToResetOffset(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertViewMessageWithSuccess() throws Exception { setResponseSuccess(getMessageResult()); ViewMessageRequestHeader requestHeader = mock(ViewMessageRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.viewMessage(defaultBrokerAddr, requestHeader, defaultTimeout); MessageExt result = actual.get(); assertNotNull(result); assertEquals(defaultTopic, result.getTopic()); } @Test public void assertViewMessageWithError() { setResponseError(); ViewMessageRequestHeader requestHeader = mock(ViewMessageRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.viewMessage(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertGetBrokerClusterInfoWithSuccess() throws Exception { ClusterInfo responseBody = new ClusterInfo(); setResponseSuccess(RemotingSerializable.encode(responseBody)); CompletableFuture actual = mqClientAdminImpl.getBrokerClusterInfo(defaultBrokerAddr, defaultTimeout); ClusterInfo result = actual.get(); assertNotNull(result); } @Test public void assertGetBrokerClusterInfoWithError() { setResponseError(); CompletableFuture actual = mqClientAdminImpl.getBrokerClusterInfo(defaultBrokerAddr, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertGetConsumerConnectionListWithSuccess() throws Exception { ConsumerConnection responseBody = new ConsumerConnection(); setResponseSuccess(RemotingSerializable.encode(responseBody)); GetConsumerConnectionListRequestHeader requestHeader = mock(GetConsumerConnectionListRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getConsumerConnectionList(defaultBrokerAddr, requestHeader, defaultTimeout); ConsumerConnection result = actual.get(); assertNotNull(result); assertEquals(0, result.getConnectionSet().size()); } @Test public void assertGetConsumerConnectionListWithError() { setResponseError(); GetConsumerConnectionListRequestHeader requestHeader = mock(GetConsumerConnectionListRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getConsumerConnectionList(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertQueryTopicsByConsumerWithSuccess() throws Exception { TopicList responseBody = new TopicList(); setResponseSuccess(RemotingSerializable.encode(responseBody)); QueryTopicsByConsumerRequestHeader requestHeader = mock(QueryTopicsByConsumerRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.queryTopicsByConsumer(defaultBrokerAddr, requestHeader, defaultTimeout); TopicList result = actual.get(); assertNotNull(result); assertEquals(0, result.getTopicList().size()); } @Test public void assertQueryTopicsByConsumerWithError() { setResponseError(); QueryTopicsByConsumerRequestHeader requestHeader = mock(QueryTopicsByConsumerRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.queryTopicsByConsumer(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertQuerySubscriptionByConsumerWithSuccess() throws Exception { SubscriptionData responseBody = new SubscriptionData(); setResponseSuccess(RemotingSerializable.encode(responseBody)); QuerySubscriptionByConsumerRequestHeader requestHeader = mock(QuerySubscriptionByConsumerRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.querySubscriptionByConsumer(defaultBrokerAddr, requestHeader, defaultTimeout); assertNull(actual.get()); } @Test public void assertQuerySubscriptionByConsumerWithError() { setResponseError(); QuerySubscriptionByConsumerRequestHeader requestHeader = mock(QuerySubscriptionByConsumerRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.querySubscriptionByConsumer(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertGetConsumeStatsWithSuccess() throws Exception { ConsumeStats responseBody = new ConsumeStats(); setResponseSuccess(RemotingSerializable.encode(responseBody)); GetConsumeStatsRequestHeader requestHeader = mock(GetConsumeStatsRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getConsumeStats(defaultBrokerAddr, requestHeader, defaultTimeout); ConsumeStats result = actual.get(); assertNotNull(result); assertEquals(0, result.getOffsetTable().size()); } @Test public void assertGetConsumeStatsWithError() { setResponseError(); GetConsumeStatsRequestHeader requestHeader = mock(GetConsumeStatsRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getConsumeStats(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertQueryTopicConsumeByWhoWithSuccess() throws Exception { GroupList responseBody = new GroupList(); setResponseSuccess(RemotingSerializable.encode(responseBody)); QueryTopicConsumeByWhoRequestHeader requestHeader = mock(QueryTopicConsumeByWhoRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.queryTopicConsumeByWho(defaultBrokerAddr, requestHeader, defaultTimeout); GroupList result = actual.get(); assertNotNull(result); assertEquals(0, result.getGroupList().size()); } @Test public void assertQueryTopicConsumeByWhoWithError() { setResponseError(); QueryTopicConsumeByWhoRequestHeader requestHeader = mock(QueryTopicConsumeByWhoRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.queryTopicConsumeByWho(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertGetConsumerRunningInfoWithSuccess() throws Exception { ConsumerRunningInfo responseBody = new ConsumerRunningInfo(); setResponseSuccess(RemotingSerializable.encode(responseBody)); GetConsumerRunningInfoRequestHeader requestHeader = mock(GetConsumerRunningInfoRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getConsumerRunningInfo(defaultBrokerAddr, requestHeader, defaultTimeout); ConsumerRunningInfo result = actual.get(); assertNotNull(result); assertEquals(0, result.getProperties().size()); } @Test public void assertGetConsumerRunningInfoWithError() { setResponseError(); GetConsumerRunningInfoRequestHeader requestHeader = mock(GetConsumerRunningInfoRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.getConsumerRunningInfo(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } @Test public void assertConsumeMessageDirectlyWithSuccess() throws Exception { ConsumeMessageDirectlyResult responseBody = new ConsumeMessageDirectlyResult(); setResponseSuccess(RemotingSerializable.encode(responseBody)); ConsumeMessageDirectlyResultRequestHeader requestHeader = mock(ConsumeMessageDirectlyResultRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.consumeMessageDirectly(defaultBrokerAddr, requestHeader, defaultTimeout); ConsumeMessageDirectlyResult result = actual.get(); assertNotNull(result); assertTrue(result.isAutoCommit()); } @Test public void assertConsumeMessageDirectlyWithError() { setResponseError(); ConsumeMessageDirectlyResultRequestHeader requestHeader = mock(ConsumeMessageDirectlyResultRequestHeader.class); CompletableFuture actual = mqClientAdminImpl.consumeMessageDirectly(defaultBrokerAddr, requestHeader, defaultTimeout); Throwable thrown = assertThrows(ExecutionException.class, actual::get); assertTrue(thrown.getCause() instanceof MQClientException); MQClientException mqException = (MQClientException) thrown.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, mqException.getResponseCode()); assertTrue(mqException.getMessage().contains("CODE: 1 DESC: null")); } private byte[] getMessageResult() throws Exception { byte[] bytes = MessageDecoder.encode(createMessageExt(), false); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes); return byteBuffer.array(); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName("defaultBroker"); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, "defaultGroup"); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); result.setKeys("keys"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } private void setResponseSuccess(byte[] body) { when(response.getCode()).thenReturn(ResponseCode.SUCCESS); when(response.getBody()).thenReturn(body); } private void setResponseError() { when(response.getCode()).thenReturn(ResponseCode.SYSTEM_ERROR); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.stats.StatsItem; import org.apache.rocketmq.common.stats.StatsItemSet; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatus; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import org.mockito.Mockito; @RunWith(MockitoJUnitRunner.class) public class ConsumeMessageConcurrentlyServiceTest { private static String consumerGroup; private static String topic = "FooBar"; private static String brokerName = "BrokerA"; private static MQClientInstance mQClientFactory; @Mock private static MQClientAPIImpl mQClientAPIImpl; private static PullAPIWrapper pullAPIWrapper; private static RebalancePushImpl rebalancePushImpl; private static DefaultMQPushConsumer pushConsumer; @BeforeClass public static void init() throws Exception { mQClientAPIImpl = Mockito.mock(MQClientAPIImpl.class); ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); Collection instances = factoryTable.values(); for (MQClientInstance instance : instances) { instance.shutdown(); } factoryTable.clear(); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setPullInterval(60 * 1000); pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); field.set(pushConsumerImpl, rebalancePushImpl); pushConsumer.subscribe(topic, "*"); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); mQClientFactory = spy(mQClientFactory); field = DefaultMQPushConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pushConsumerImpl, mQClientFactory); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); field = DefaultMQPushConsumerImpl.class.getDeclaredField("pullAPIWrapper"); field.setAccessible(true); field.set(pushConsumerImpl, pullAPIWrapper); pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl().setmQClientFactory(mQClientFactory); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))).thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] { 'a' }); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); return pullResult; } }); doReturn(new FindBrokerResult("127.0.0.1:10912", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); pushConsumer.start(); } @Test public void testPullMessage_ConsumeSuccess() throws InterruptedException, RemotingException, MQBrokerException, NoSuchFieldException, Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference messageAtomic = new AtomicReference<>(); ConsumeMessageConcurrentlyService normalServie = new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(normalServie); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(); Thread.sleep(1000); ConsumeStatus stats = normalServie.getConsumerStatsManager().consumeStatus(pushConsumer.getDefaultMQPushConsumerImpl().groupName(), topic); ConsumerStatsManager mgr = normalServie.getConsumerStatsManager(); Field statItmeSetField = mgr.getClass().getDeclaredField("topicAndGroupConsumeOKTPS"); statItmeSetField.setAccessible(true); StatsItemSet itemSet = (StatsItemSet) statItmeSetField.get(mgr); StatsItem item = itemSet.getAndCreateStatsItem(topic + "@" + pushConsumer.getDefaultMQPushConsumerImpl().groupName()); assertThat(item.getValue().sum()).isGreaterThan(0L); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(new byte[] { 'a' }); } @AfterClass public static void terminate() { pushConsumer.shutdown(); } private static PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(1024); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); pullRequest.setMessageQueue(messageQueue); ProcessQueue processQueue = new ProcessQueue(); processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); pullRequest.setProcessQueue(processQueue); return pullRequest; } private static PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } @Test public void testConsumeThreadName() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference consumeThreadName = new AtomicReference<>(); StringBuilder consumeGroup2 = new StringBuilder(); for (int i = 0; i < 101; i++) { consumeGroup2.append(i).append("#"); } pushConsumer.setConsumerGroup(consumeGroup2.toString()); ConsumeMessageConcurrentlyService normalServie = new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { consumeThreadName.set(Thread.currentThread().getName()); countDownLatch.countDown(); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(normalServie); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(); if (consumeGroup2.length() <= 100) { assertThat(consumeThreadName.get()).startsWith("ConsumeMessageThread_" + consumeGroup2 + "_"); } else { assertThat(consumeThreadName.get()).startsWith("ConsumeMessageThread_" + consumeGroup2.substring(0, 100) + "_"); } } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; public class ConsumeMessageOrderlyServiceTest { private String consumerGroup; private String topic = "FooBar"; private String brokerName = "BrokerA"; private DefaultMQPushConsumer pushConsumer; private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mQClientAPIImpl; private PullAPIWrapper pullAPIWrapper; private RebalancePushImpl rebalancePushImpl; @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); Collection instances = factoryTable.values(); for (MQClientInstance instance : instances) { instance.shutdown(); } factoryTable.clear(); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setPullInterval(60 * 1000); pushConsumer.registerMessageListener(new MessageListenerOrderly() { @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { return ConsumeOrderlyStatus.SUCCESS; } }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); field.set(pushConsumerImpl, rebalancePushImpl); pushConsumer.subscribe(topic, "*"); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); mQClientFactory = spy(mQClientFactory); field = DefaultMQPushConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pushConsumerImpl, mQClientFactory); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); mQClientAPIImpl = mock(MQClientAPIImpl.class); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); field = DefaultMQPushConsumerImpl.class.getDeclaredField("pullAPIWrapper"); field.setAccessible(true); field.set(pushConsumerImpl, pullAPIWrapper); pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl().setmQClientFactory(mQClientFactory); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); return pullResult; } }); doReturn(new FindBrokerResult("127.0.0.1:10912", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); pushConsumer.start(); } @Test public void testConsumeMessageDirectly_WithNoException() { Map map = new HashMap(); map.put(ConsumeOrderlyStatus.SUCCESS, CMResult.CR_SUCCESS); map.put(ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT, CMResult.CR_LATER); map.put(ConsumeOrderlyStatus.COMMIT, CMResult.CR_COMMIT); map.put(ConsumeOrderlyStatus.ROLLBACK, CMResult.CR_ROLLBACK); map.put(null, CMResult.CR_RETURN_NULL); for (ConsumeOrderlyStatus consumeOrderlyStatus : map.keySet()) { final ConsumeOrderlyStatus status = consumeOrderlyStatus; MessageListenerOrderly listenerOrderly = new MessageListenerOrderly() { @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { return status; } }; ConsumeMessageOrderlyService consumeMessageOrderlyService = new ConsumeMessageOrderlyService(pushConsumer.getDefaultMQPushConsumerImpl(), listenerOrderly); MessageExt msg = new MessageExt(); msg.setTopic(topic); assertTrue(consumeMessageOrderlyService.consumeMessageDirectly(msg, brokerName).getConsumeResult().equals(map.get(consumeOrderlyStatus))); } } @Test public void testConsumeMessageDirectly_WithException() { MessageListenerOrderly listenerOrderly = new MessageListenerOrderly() { @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { throw new RuntimeException(); } }; ConsumeMessageOrderlyService consumeMessageOrderlyService = new ConsumeMessageOrderlyService(pushConsumer.getDefaultMQPushConsumerImpl(), listenerOrderly); MessageExt msg = new MessageExt(); msg.setTopic(topic); assertTrue(consumeMessageOrderlyService.consumeMessageDirectly(msg, brokerName).getConsumeResult().equals(CMResult.CR_THROW_EXCEPTION)); } @Test public void testConsumeThreadName() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference consumeThreadName = new AtomicReference<>(); StringBuilder consumeGroup2 = new StringBuilder(); for (int i = 0; i < 101; i++) { consumeGroup2.append(i).append("#"); } pushConsumer.setConsumerGroup(consumeGroup2.toString()); MessageListenerOrderly listenerOrderly = new MessageListenerOrderly() { @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { consumeThreadName.set(Thread.currentThread().getName()); countDownLatch.countDown(); return ConsumeOrderlyStatus.SUCCESS; } }; ConsumeMessageOrderlyService consumeMessageOrderlyService = new ConsumeMessageOrderlyService(pushConsumer.getDefaultMQPushConsumerImpl(), listenerOrderly); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(consumeMessageOrderlyService); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(); if (consumeGroup2.length() <= 100) { assertThat(consumeThreadName.get()).startsWith("ConsumeMessageThread_" + consumeGroup2 + "_"); } else { assertThat(consumeThreadName.get()).startsWith("ConsumeMessageThread_" + consumeGroup2.substring(0, 100) + "_"); } } private PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(1024); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); pullRequest.setMessageQueue(messageQueue); ProcessQueue processQueue = new ProcessQueue(); processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); pullRequest.setProcessQueue(processQueue); return pullRequest; } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ConsumeMessagePopConcurrentlyServiceTest { @Mock private DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; @Mock private MessageListenerConcurrently messageListener; @Mock private DefaultMQPushConsumer defaultMQPushConsumer; private ConsumeMessagePopConcurrentlyService popService; private final String defaultGroup = "defaultGroup"; private final String defaultBroker = "defaultBroker"; private final String defaultTopic = "defaultTopic"; @Before public void init() throws Exception { when(defaultMQPushConsumer.getConsumerGroup()).thenReturn(defaultGroup); when(defaultMQPushConsumer.getConsumeThreadMin()).thenReturn(1); when(defaultMQPushConsumer.getConsumeThreadMax()).thenReturn(3); when(defaultMQPushConsumer.getConsumeMessageBatchMaxSize()).thenReturn(32); when(defaultMQPushConsumerImpl.getDefaultMQPushConsumer()).thenReturn(defaultMQPushConsumer); ConsumerStatsManager consumerStatsManager = mock(ConsumerStatsManager.class); when(defaultMQPushConsumerImpl.getConsumerStatsManager()).thenReturn(consumerStatsManager); popService = new ConsumeMessagePopConcurrentlyService(defaultMQPushConsumerImpl, messageListener); } @Test public void testUpdateCorePoolSize() { popService.updateCorePoolSize(2); popService.incCorePoolSize(); popService.decCorePoolSize(); assertEquals(2, popService.getCorePoolSize()); } @Test public void testConsumeMessageDirectly() { when(messageListener.consumeMessage(any(), any(ConsumeConcurrentlyContext.class))).thenReturn(ConsumeConcurrentlyStatus.CONSUME_SUCCESS); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_SUCCESS, actual.getConsumeResult()); } @Test public void testConsumeMessageDirectlyWithCrLater() { when(messageListener.consumeMessage(any(), any(ConsumeConcurrentlyContext.class))).thenReturn(ConsumeConcurrentlyStatus.RECONSUME_LATER); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_LATER, actual.getConsumeResult()); } @Test public void testConsumeMessageDirectlyWithCrReturnNull() { ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_RETURN_NULL, actual.getConsumeResult()); } @Test public void testConsumeMessageDirectlyWithCrThrowException() { when(messageListener.consumeMessage(any(), any(ConsumeConcurrentlyContext.class))).thenThrow(new RuntimeException("exception")); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_THROW_EXCEPTION, actual.getConsumeResult()); } @Test public void testShutdown() throws IllegalAccessException { popService.shutdown(3000L); Field scheduledExecutorServiceField = FieldUtils.getDeclaredField(popService.getClass(), "scheduledExecutorService", true); Field consumeExecutorField = FieldUtils.getDeclaredField(popService.getClass(), "consumeExecutor", true); ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) scheduledExecutorServiceField.get(popService); ThreadPoolExecutor consumeExecutor = (ThreadPoolExecutor) consumeExecutorField.get(popService); assertTrue(scheduledExecutorService.isShutdown()); assertTrue(scheduledExecutorService.isTerminated()); assertTrue(consumeExecutor.isShutdown()); assertTrue(consumeExecutor.isTerminated()); } @Test public void testSubmitConsumeRequest() { assertThrows(UnsupportedOperationException.class, () -> { List msgs = mock(List.class); ProcessQueue processQueue = mock(ProcessQueue.class); MessageQueue messageQueue = mock(MessageQueue.class); popService.submitConsumeRequest(msgs, processQueue, messageQueue, false); }); } @Test public void testSubmitPopConsumeRequest() throws IllegalAccessException { List msgs = Collections.singletonList(createMessageExt()); PopProcessQueue processQueue = mock(PopProcessQueue.class); MessageQueue messageQueue = mock(MessageQueue.class); ThreadPoolExecutor consumeExecutor = mock(ThreadPoolExecutor.class); FieldUtils.writeDeclaredField(popService, "consumeExecutor", consumeExecutor, true); popService.submitPopConsumeRequest(msgs, processQueue, messageQueue); verify(consumeExecutor, times(1)).submit(any(Runnable.class)); } @Test public void testSubmitPopConsumeRequestWithMultiMsg() throws IllegalAccessException { List msgs = Arrays.asList(createMessageExt(), createMessageExt()); PopProcessQueue processQueue = mock(PopProcessQueue.class); MessageQueue messageQueue = mock(MessageQueue.class); ThreadPoolExecutor consumeExecutor = mock(ThreadPoolExecutor.class); FieldUtils.writeDeclaredField(popService, "consumeExecutor", consumeExecutor, true); when(defaultMQPushConsumer.getConsumeMessageBatchMaxSize()).thenReturn(1); popService.submitPopConsumeRequest(msgs, processQueue, messageQueue); verify(consumeExecutor, times(2)).submit(any(Runnable.class)); } @Test public void testProcessConsumeResult() { ConsumeConcurrentlyContext context = mock(ConsumeConcurrentlyContext.class); ConsumeMessagePopConcurrentlyService.ConsumeRequest consumeRequest = mock(ConsumeMessagePopConcurrentlyService.ConsumeRequest.class); when(consumeRequest.getMsgs()).thenReturn(Arrays.asList(createMessageExt(), createMessageExt())); MessageQueue messageQueue = mock(MessageQueue.class); when(messageQueue.getTopic()).thenReturn(defaultTopic); when(consumeRequest.getMessageQueue()).thenReturn(messageQueue); PopProcessQueue processQueue = mock(PopProcessQueue.class); when(processQueue.ack()).thenReturn(0); when(consumeRequest.getPopProcessQueue()).thenReturn(processQueue); when(defaultMQPushConsumerImpl.getPopDelayLevel()).thenReturn(new int[]{1, 10}); popService.processConsumeResult(ConsumeConcurrentlyStatus.CONSUME_SUCCESS, context, consumeRequest); verify(defaultMQPushConsumerImpl, times(1)).ackAsync(any(MessageExt.class), any()); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, defaultGroup); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); long curTime = System.currentTimeMillis(); result.setBornTimestamp(curTime - 1000); result.getProperties().put(MessageConst.PROPERTY_POP_CK, curTime + " " + curTime + " " + curTime + " " + curTime); result.setKeys("keys"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ConsumeMessagePopOrderlyServiceTest { @Mock private DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; @Mock private MessageListenerOrderly messageListener; @Mock private DefaultMQPushConsumer defaultMQPushConsumer; @Mock private ConsumerStatsManager consumerStatsManager; @Mock private RebalanceImpl rebalanceImpl; private ConsumeMessagePopOrderlyService popService; private final String defaultGroup = "defaultGroup"; private final String defaultBroker = "defaultBroker"; private final String defaultTopic = "defaultTopic"; @Before public void init() throws Exception { when(defaultMQPushConsumer.getConsumerGroup()).thenReturn(defaultGroup); when(defaultMQPushConsumer.getConsumeThreadMin()).thenReturn(1); when(defaultMQPushConsumer.getConsumeThreadMax()).thenReturn(3); when(defaultMQPushConsumerImpl.getDefaultMQPushConsumer()).thenReturn(defaultMQPushConsumer); when(defaultMQPushConsumerImpl.getRebalanceImpl()).thenReturn(rebalanceImpl); when(defaultMQPushConsumerImpl.getConsumerStatsManager()).thenReturn(consumerStatsManager); MQClientInstance mQClientFactory = mock(MQClientInstance.class); DefaultMQProducer defaultMQProducer = mock(DefaultMQProducer.class); when(mQClientFactory.getDefaultMQProducer()).thenReturn(defaultMQProducer); when(defaultMQPushConsumerImpl.getmQClientFactory()).thenReturn(mQClientFactory); popService = new ConsumeMessagePopOrderlyService(defaultMQPushConsumerImpl, messageListener); } @Test public void testShutdown() throws IllegalAccessException { popService.shutdown(3000L); Field scheduledExecutorServiceField = FieldUtils.getDeclaredField(popService.getClass(), "scheduledExecutorService", true); Field consumeExecutorField = FieldUtils.getDeclaredField(popService.getClass(), "consumeExecutor", true); ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) scheduledExecutorServiceField.get(popService); ThreadPoolExecutor consumeExecutor = (ThreadPoolExecutor) consumeExecutorField.get(popService); assertTrue(scheduledExecutorService.isShutdown()); assertTrue(scheduledExecutorService.isTerminated()); assertTrue(consumeExecutor.isShutdown()); assertTrue(consumeExecutor.isTerminated()); } @Test public void testUnlockAllMessageQueues() { popService.unlockAllMessageQueues(); verify(rebalanceImpl, times(1)).unlockAll(eq(false)); } @Test public void testUpdateCorePoolSize() { popService.updateCorePoolSize(2); popService.incCorePoolSize(); popService.decCorePoolSize(); assertEquals(2, popService.getCorePoolSize()); } @Test public void testConsumeMessageDirectly() { when(messageListener.consumeMessage(any(), any(ConsumeOrderlyContext.class))).thenReturn(ConsumeOrderlyStatus.SUCCESS); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_SUCCESS, actual.getConsumeResult()); assertTrue(actual.isOrder()); } @Test public void testConsumeMessageDirectlyWithCommit() { when(messageListener.consumeMessage(any(), any(ConsumeOrderlyContext.class))).thenReturn(ConsumeOrderlyStatus.COMMIT); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_COMMIT, actual.getConsumeResult()); assertTrue(actual.isOrder()); } @Test public void testConsumeMessageDirectlyWithRollback() { when(messageListener.consumeMessage(any(), any(ConsumeOrderlyContext.class))).thenReturn(ConsumeOrderlyStatus.ROLLBACK); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_ROLLBACK, actual.getConsumeResult()); assertTrue(actual.isOrder()); } @Test public void testConsumeMessageDirectlyWithCrLater() { when(messageListener.consumeMessage(any(), any(ConsumeOrderlyContext.class))).thenReturn(ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_LATER, actual.getConsumeResult()); } @Test public void testConsumeMessageDirectlyWithCrReturnNull() { ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_RETURN_NULL, actual.getConsumeResult()); } @Test public void testConsumeMessageDirectlyWithCrThrowException() { when(messageListener.consumeMessage(any(), any(ConsumeOrderlyContext.class))).thenThrow(new RuntimeException("exception")); ConsumeMessageDirectlyResult actual = popService.consumeMessageDirectly(createMessageExt(), defaultBroker); assertEquals(CMResult.CR_THROW_EXCEPTION, actual.getConsumeResult()); } @Test public void testSubmitConsumeRequest() { assertThrows(UnsupportedOperationException.class, () -> { List msgs = mock(List.class); ProcessQueue processQueue = mock(ProcessQueue.class); MessageQueue messageQueue = mock(MessageQueue.class); popService.submitConsumeRequest(msgs, processQueue, messageQueue, false); }); } @Test public void testSubmitPopConsumeRequest() throws IllegalAccessException { List msgs = Collections.singletonList(createMessageExt()); PopProcessQueue processQueue = mock(PopProcessQueue.class); MessageQueue messageQueue = mock(MessageQueue.class); ThreadPoolExecutor consumeExecutor = mock(ThreadPoolExecutor.class); FieldUtils.writeDeclaredField(popService, "consumeExecutor", consumeExecutor, true); popService.submitPopConsumeRequest(msgs, processQueue, messageQueue); verify(consumeExecutor, times(1)).submit(any(Runnable.class)); } @Test public void testLockMQPeriodically() { popService.lockMQPeriodically(); verify(defaultMQPushConsumerImpl, times(1)).getRebalanceImpl(); verify(rebalanceImpl, times(1)).lockAll(); } @Test public void testGetConsumerStatsManager() { ConsumerStatsManager actual = popService.getConsumerStatsManager(); assertNotNull(actual); assertEquals(consumerStatsManager, actual); } @Test public void testSendMessageBack() { assertTrue(popService.sendMessageBack(createMessageExt())); } @Test public void testProcessConsumeResult() { ConsumeOrderlyContext context = mock(ConsumeOrderlyContext.class); ConsumeMessagePopOrderlyService.ConsumeRequest consumeRequest = mock(ConsumeMessagePopOrderlyService.ConsumeRequest.class); assertTrue(popService.processConsumeResult(Collections.singletonList(createMessageExt()), ConsumeOrderlyStatus.SUCCESS, context, consumeRequest)); } @Test public void testResetNamespace() { when(defaultMQPushConsumer.getNamespace()).thenReturn("defaultNamespace"); List msgs = Collections.singletonList(createMessageExt()); popService.resetNamespace(msgs); assertEquals(defaultTopic, msgs.get(0).getTopic()); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, defaultGroup); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); long curTime = System.currentTimeMillis(); result.setBornTimestamp(curTime - 1000); result.getProperties().put(MessageConst.PROPERTY_POP_CK, curTime + " " + curTime + " " + curTime + " " + curTime); result.setKeys("keys"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; public class DefaultLitePullConsumerImplTest { private final DefaultLitePullConsumerImpl consumer = new DefaultLitePullConsumerImpl(new DefaultLitePullConsumer(), null); private static Method isSetEqualMethod; @BeforeClass public static void initReflectionMethod() throws NoSuchMethodException { Class consumerClass = DefaultLitePullConsumerImpl.class; Method testMethod = consumerClass.getDeclaredMethod("isSetEqual", Set.class, Set.class); testMethod.setAccessible(true); isSetEqualMethod = testMethod; } /** * The two empty sets should be equal */ @Test public void testIsSetEqual1() throws InvocationTargetException, IllegalAccessException { Set set1 = new HashSet<>(); Set set2 = new HashSet<>(); boolean equalResult = (boolean) isSetEqualMethod.invoke(consumer, set1, set2); Assert.assertTrue(equalResult); } /** * When a set has elements and one does not, the two sets are not equal */ @Test public void testIsSetEqual2() throws InvocationTargetException, IllegalAccessException { Set set1 = new HashSet<>(); set1.add(new MessageQueue("testTopic","testBroker",111)); Set set2 = new HashSet<>(); boolean equalResult = (boolean) isSetEqualMethod.invoke(consumer, set1, set2); Assert.assertFalse(equalResult); } /** * The two null sets should be equal */ @Test public void testIsSetEqual3() throws InvocationTargetException, IllegalAccessException { Set set1 = null; Set set2 = null; boolean equalResult = (boolean) isSetEqualMethod.invoke(consumer, set1, set2); Assert.assertTrue(equalResult); } @Test public void testIsSetEqual4() throws InvocationTargetException, IllegalAccessException { Set set1 = null; Set set2 = new HashSet<>(); boolean equalResult = (boolean) isSetEqualMethod.invoke(consumer, set1, set2); Assert.assertFalse(equalResult); } @Test public void testIsSetEqual5() throws InvocationTargetException, IllegalAccessException { Set set1 = new HashSet<>(); set1.add(new MessageQueue("testTopic","testBroker",111)); Set set2 = new HashSet<>(); set2.add(new MessageQueue("testTopic","testBroker",111)); boolean equalResult = (boolean) isSetEqualMethod.invoke(consumer, set1, set2); Assert.assertTrue(equalResult); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.hook.FilterMessageContext; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.stat.ConsumerStatsManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatus; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQPushConsumerImplTest { @Mock private DefaultMQPushConsumer defaultMQPushConsumer; @Mock private MQClientInstance mQClientFactory; @Mock private RebalanceImpl rebalanceImpl; @Mock private PullAPIWrapper pullAPIWrapper; @Mock private PullRequest pullRequest; @Mock private PopRequest popRequest; @Mock private ProcessQueue processQueue; @Mock private PopProcessQueue popProcessQueue; @Mock private MQClientAPIImpl mqClientAPIImpl; @Mock private OffsetStore offsetStore; private DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; @Rule public ExpectedException thrown = ExpectedException.none(); private final String defaultKey = "defaultKey"; private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final String defaultGroup = "defaultGroup"; private final long defaultTimeout = 3000L; @Test public void checkConfigTest() throws MQClientException { //test type thrown.expect(MQClientException.class); //test message thrown.expectMessage("consumeThreadMin (10) is larger than consumeThreadMax (9)"); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test_consumer_group"); consumer.setConsumeThreadMin(10); consumer.setConsumeThreadMax(9); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> ConsumeConcurrentlyStatus.CONSUME_SUCCESS); DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(consumer, null); defaultMQPushConsumerImpl.start(); } @Test public void testHook() { DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(defaultMQPushConsumer, null); defaultMQPushConsumerImpl.registerConsumeMessageHook(new ConsumeMessageHook() { @Override public String hookName() { return "consumerHook"; } @Override public void consumeMessageBefore(ConsumeMessageContext context) { assertThat(context).isNotNull(); } @Override public void consumeMessageAfter(ConsumeMessageContext context) { assertThat(context).isNotNull(); } }); defaultMQPushConsumerImpl.registerFilterMessageHook(new FilterMessageHook() { @Override public String hookName() { return "filterHook"; } @Override public void filterMessage(FilterMessageContext context) { assertThat(context).isNotNull(); } }); defaultMQPushConsumerImpl.executeHookBefore(new ConsumeMessageContext()); defaultMQPushConsumerImpl.executeHookAfter(new ConsumeMessageContext()); } @Ignore @Test public void testPush() throws Exception { when(defaultMQPushConsumer.getMessageListener()).thenReturn((MessageListenerConcurrently) (msgs, context) -> { assertThat(msgs).size().isGreaterThan(0); assertThat(context).isNotNull(); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(defaultMQPushConsumer, null); try { defaultMQPushConsumerImpl.start(); } finally { defaultMQPushConsumerImpl.shutdown(); } } @Before public void init() throws NoSuchFieldException, IllegalAccessException { MQAdminImpl mqAdminImpl = mock(MQAdminImpl.class); when(mQClientFactory.getMQAdminImpl()).thenReturn(mqAdminImpl); ConsumerStatsManager consumerStatsManager = mock(ConsumerStatsManager.class); ConsumeStatus consumeStatus = mock(ConsumeStatus.class); when(consumerStatsManager.consumeStatus(any(), any())).thenReturn(consumeStatus); when(mQClientFactory.getConsumerStatsManager()).thenReturn(consumerStatsManager); when(mQClientFactory.getPullMessageService()).thenReturn(mock(PullMessageService.class)); when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mqClientAPIImpl); FindBrokerResult findBrokerResult = mock(FindBrokerResult.class); when(findBrokerResult.getBrokerAddr()).thenReturn(defaultBrokerAddr); when(mQClientFactory.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(findBrokerResult); Set messageQueueSet = Collections.singleton(createMessageQueue()); ConcurrentMap> topicMessageQueueMap = new ConcurrentHashMap<>(); topicMessageQueueMap.put(defaultTopic, messageQueueSet); when(rebalanceImpl.getTopicSubscribeInfoTable()).thenReturn(topicMessageQueueMap); ConcurrentMap processQueueTable = new ConcurrentHashMap<>(); when(rebalanceImpl.getProcessQueueTable()).thenReturn(processQueueTable); RPCHook rpcHook = mock(RPCHook.class); defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(defaultMQPushConsumer, rpcHook); defaultMQPushConsumerImpl.setOffsetStore(offsetStore); FieldUtils.writeDeclaredField(defaultMQPushConsumerImpl, "mQClientFactory", mQClientFactory, true); FieldUtils.writeDeclaredField(defaultMQPushConsumerImpl, "rebalanceImpl", rebalanceImpl, true); FieldUtils.writeDeclaredField(defaultMQPushConsumerImpl, "pullAPIWrapper", pullAPIWrapper, true); FilterMessageHook filterMessageHook = mock(FilterMessageHook.class); ArrayList filterMessageHookList = new ArrayList<>(); filterMessageHookList.add(filterMessageHook); ConsumeMessageService consumeMessagePopService = mock(ConsumeMessageService.class); ConsumeMessageService consumeMessageService = mock(ConsumeMessageService.class); FieldUtils.writeDeclaredField(defaultMQPushConsumerImpl, "filterMessageHookList", filterMessageHookList, true); FieldUtils.writeDeclaredField(defaultMQPushConsumerImpl, "consumeMessageService", consumeMessageService, true); FieldUtils.writeDeclaredField(defaultMQPushConsumerImpl, "consumeMessagePopService", consumeMessagePopService, true); ConcurrentMap subscriptionDataMap = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(defaultTopic); subscriptionDataMap.put(defaultTopic, subscriptionData); when(rebalanceImpl.getSubscriptionInner()).thenReturn(subscriptionDataMap); } @Test public void testFetchSubscribeMessageQueues() throws MQClientException { Set actual = defaultMQPushConsumerImpl.fetchSubscribeMessageQueues(defaultTopic); assertNotNull(actual); Assert.assertEquals(1, actual.size()); MessageQueue next = actual.iterator().next(); assertEquals(defaultTopic, next.getTopic()); assertEquals(defaultBroker, next.getBrokerName()); assertEquals(0, next.getQueueId()); } @Test public void testEarliestMsgStoreTime() throws MQClientException { assertEquals(0, defaultMQPushConsumerImpl.earliestMsgStoreTime(createMessageQueue())); } @Test public void testMaxOffset() throws MQClientException { assertEquals(0, defaultMQPushConsumerImpl.maxOffset(createMessageQueue())); } @Test public void testMinOffset() throws MQClientException { assertEquals(0, defaultMQPushConsumerImpl.minOffset(createMessageQueue())); } @Test public void testGetOffsetStore() { assertEquals(offsetStore, defaultMQPushConsumerImpl.getOffsetStore()); } @Test public void testPullMessageWithStateNotOk() { when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithIsPause() { when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); defaultMQPushConsumerImpl.setPause(true); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithMsgCountFlowControl() { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); TreeMap treeMap = new TreeMap<>(); treeMap.put(1L, new MessageExt()); when(processQueue.getMsgTreeMap()).thenReturn(treeMap); when(pullRequest.getProcessQueue()).thenReturn(processQueue); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(1); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithMsgSizeFlowControl() { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); TreeMap treeMap = new TreeMap<>(); treeMap.put(1L, new MessageExt()); when(processQueue.getMsgTreeMap()).thenReturn(treeMap); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(1); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithMaxSpanFlowControl() { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMaxSpan()).thenReturn(2L); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); TreeMap treeMap = new TreeMap<>(); treeMap.put(1L, new MessageExt()); when(processQueue.getMsgTreeMap()).thenReturn(treeMap); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(10); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithNotLocked() { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); defaultMQPushConsumerImpl.setConsumeOrderly(true); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(10); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithSubscriptionDataIsNull() { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); when(pullRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(10); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithNoMatchedMsg() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); when(pullRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(10); PullResult pullResultMock = mock(PullResult.class); when(pullAPIWrapper.processPullResult(any(MessageQueue.class), any(PullResult.class), any(SubscriptionData.class))).thenReturn(pullResultMock); when(pullResultMock.getPullStatus()).thenReturn(PullStatus.NO_MATCHED_MSG); doAnswer(invocation -> { PullCallback callback = invocation.getArgument(12); PullResult pullResult = mock(PullResult.class); callback.onSuccess(pullResult); return null; }).when(pullAPIWrapper).pullKernelImpl( any(MessageQueue.class), any(), any(), anyLong(), anyLong(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong(), anyLong(), any(CommunicationMode.class), any(PullCallback.class)); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithOffsetIllegal() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); when(pullRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(10); PullResult pullResultMock = mock(PullResult.class); when(pullAPIWrapper.processPullResult(any(MessageQueue.class), any(PullResult.class), any(SubscriptionData.class))).thenReturn(pullResultMock); when(pullResultMock.getPullStatus()).thenReturn(PullStatus.OFFSET_ILLEGAL); doAnswer(invocation -> { PullCallback callback = invocation.getArgument(12); PullResult pullResult = mock(PullResult.class); callback.onSuccess(pullResult); return null; }).when(pullAPIWrapper).pullKernelImpl( any(MessageQueue.class), any(), any(), anyLong(), anyLong(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong(), anyLong(), any(CommunicationMode.class), any(PullCallback.class)); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPullMessageWithException() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { when(processQueue.getMsgCount()).thenReturn(new AtomicLong(2)); when(processQueue.getMsgSize()).thenReturn(new AtomicLong(3 * 1024 * 1024)); when(pullRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(pullRequest.getProcessQueue()).thenReturn(processQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPullThresholdForQueue()).thenReturn(3); when(defaultMQPushConsumer.getPullThresholdSizeForQueue()).thenReturn(10); doAnswer(invocation -> { PullCallback callback = invocation.getArgument(12); callback.onException(new RuntimeException("exception")); return null; }).when(pullAPIWrapper).pullKernelImpl( any(MessageQueue.class), any(), any(), anyLong(), anyLong(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong(), anyLong(), any(CommunicationMode.class), any(PullCallback.class)); defaultMQPushConsumerImpl.pullMessage(pullRequest); } @Test public void testPopMessageWithFound() throws RemotingException, InterruptedException, MQClientException { when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); when(popRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(popRequest.getConsumerGroup()).thenReturn(defaultGroup); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); ConcurrentMap subscriptionDataMap = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTagsSet(Collections.singleton("*")); subscriptionDataMap.put(defaultTopic, subscriptionData); when(rebalanceImpl.getSubscriptionInner()).thenReturn(subscriptionDataMap); doAnswer(invocation -> { PopCallback callback = invocation.getArgument(5); PopResult popResult = mock(PopResult.class); when(popResult.getPopStatus()).thenReturn(PopStatus.FOUND); when(popResult.getMsgFoundList()).thenReturn(Collections.singletonList(createMessageExt())); callback.onSuccess(popResult); return null; }).when(pullAPIWrapper).popAsync( any(MessageQueue.class), anyLong(), anyInt(), any(), anyLong(), any(PopCallback.class), anyBoolean(), anyInt(), anyBoolean(), any(), any()); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithException() throws RemotingException, InterruptedException, MQClientException { when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); when(popRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(popRequest.getConsumerGroup()).thenReturn(defaultGroup); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); ConcurrentMap subscriptionDataMap = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTagsSet(Collections.singleton("*")); subscriptionDataMap.put(defaultTopic, subscriptionData); when(rebalanceImpl.getSubscriptionInner()).thenReturn(subscriptionDataMap); doAnswer(invocation -> { PopCallback callback = invocation.getArgument(5); callback.onException(new RuntimeException("exception")); return null; }).when(pullAPIWrapper).popAsync( any(MessageQueue.class), anyLong(), anyInt(), any(), anyLong(), any(PopCallback.class), anyBoolean(), anyInt(), anyBoolean(), any(), any()); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithNoNewMsg() throws RemotingException, InterruptedException, MQClientException { when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); when(popRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(popRequest.getConsumerGroup()).thenReturn(defaultGroup); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); ConcurrentMap subscriptionDataMap = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTagsSet(Collections.singleton("*")); subscriptionDataMap.put(defaultTopic, subscriptionData); when(rebalanceImpl.getSubscriptionInner()).thenReturn(subscriptionDataMap); doAnswer(invocation -> { PopCallback callback = invocation.getArgument(5); PopResult popResult = mock(PopResult.class); when(popResult.getPopStatus()).thenReturn(PopStatus.NO_NEW_MSG); callback.onSuccess(popResult); return null; }).when(pullAPIWrapper).popAsync( any(MessageQueue.class), anyLong(), anyInt(), any(), anyLong(), any(PopCallback.class), anyBoolean(), anyInt(), anyBoolean(), any(), any()); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithPollingFull() throws RemotingException, InterruptedException, MQClientException { when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); when(popRequest.getMessageQueue()).thenReturn(createMessageQueue()); when(popRequest.getConsumerGroup()).thenReturn(defaultGroup); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); ConcurrentMap subscriptionDataMap = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTagsSet(Collections.singleton("*")); subscriptionDataMap.put(defaultTopic, subscriptionData); when(rebalanceImpl.getSubscriptionInner()).thenReturn(subscriptionDataMap); doAnswer(invocation -> { PopCallback callback = invocation.getArgument(5); PopResult popResult = mock(PopResult.class); when(popResult.getPopStatus()).thenReturn(PopStatus.POLLING_FULL); callback.onSuccess(popResult); return null; }).when(pullAPIWrapper).popAsync(any( MessageQueue.class), anyLong(), anyInt(), any(), anyLong(), any(PopCallback.class), anyBoolean(), anyInt(), anyBoolean(), any(), any()); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithStateNotOk() { when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithIsPause() { when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); defaultMQPushConsumerImpl.setPause(true); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithWaiAckMsgCountFlowControl() { when(popProcessQueue.getWaiAckMsgCount()).thenReturn(2); when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); when(defaultMQPushConsumer.getPopThresholdForQueue()).thenReturn(1); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); defaultMQPushConsumerImpl.popMessage(popRequest); } @Test public void testPopMessageWithSubscriptionDataIsNull() throws RemotingException, InterruptedException, MQClientException { when(popProcessQueue.getWaiAckMsgCount()).thenReturn(2); when(popRequest.getPopProcessQueue()).thenReturn(popProcessQueue); when(popRequest.getMessageQueue()).thenReturn(createMessageQueue()); defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); when(defaultMQPushConsumer.getPopThresholdForQueue()).thenReturn(3); defaultMQPushConsumerImpl.popMessage(popRequest); verify(pullAPIWrapper).popAsync(any(MessageQueue.class), eq(60000L), eq(0), any(), eq(15000L), any(PopCallback.class), eq(true), eq(0), eq(false), any(), any()); } @Test public void testQueryMessage() throws InterruptedException, MQClientException { assertNull(defaultMQPushConsumerImpl.queryMessage(defaultTopic, defaultKey, 1, 0, 1)); } @Test public void testQueryMessageByUniqKey() throws InterruptedException, MQClientException { assertNull(defaultMQPushConsumerImpl.queryMessageByUniqKey(defaultTopic, defaultKey)); } @Test public void testSendMessageBack() throws InterruptedException, MQClientException, MQBrokerException, RemotingException { when(mQClientFactory.findBrokerAddressInPublish(anyString())).thenReturn(defaultBrokerAddr); defaultMQPushConsumerImpl.sendMessageBack(createMessageExt(), 1, createMessageQueue()); verify(mqClientAPIImpl).consumerSendMessageBack( eq(defaultBrokerAddr), eq(defaultBroker), any(MessageExt.class), any(), eq(1), eq(5000L), eq(0)); } @Test public void testAckAsync() throws MQBrokerException, RemotingException, InterruptedException { doAnswer(invocation -> { AckCallback callback = invocation.getArgument(2); AckResult result = mock(AckResult.class); when(result.getStatus()).thenReturn(AckStatus.OK); callback.onSuccess(result); return null; }).when(mqClientAPIImpl).ackMessageAsync(any(), anyLong(), any(AckCallback.class), any(AckMessageRequestHeader.class)); defaultMQPushConsumerImpl.ackAsync(createMessageExt(), defaultGroup); verify(mqClientAPIImpl).ackMessageAsync(eq(defaultBrokerAddr), eq(3000L), any(AckCallback.class), any(AckMessageRequestHeader.class)); } @Test public void testChangePopInvisibleTimeAsync() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { AckCallback callback = mock(AckCallback.class); String extraInfo = createMessageExt().getProperty(MessageConst.PROPERTY_POP_CK); defaultMQPushConsumerImpl.changePopInvisibleTimeAsync(defaultTopic, defaultGroup, extraInfo, defaultTimeout, callback); verify(mqClientAPIImpl).changeInvisibleTimeAsync(eq(defaultBroker), eq(defaultBrokerAddr), any(ChangeInvisibleTimeRequestHeader.class), eq(defaultTimeout), any(AckCallback.class)); } @Test public void testShutdown() { defaultMQPushConsumerImpl.setServiceState(ServiceState.RUNNING); defaultMQPushConsumerImpl.shutdown(); assertEquals(ServiceState.SHUTDOWN_ALREADY, defaultMQPushConsumerImpl.getServiceState()); } @Test public void testSubscribe() throws MQClientException { defaultMQPushConsumerImpl.subscribe(defaultTopic, "fullClassname", "filterClassSource"); RebalanceImpl actual = defaultMQPushConsumerImpl.getRebalanceImpl(); assertEquals(1, actual.getSubscriptionInner().size()); } @Test public void testSubscribeByMessageSelector() throws MQClientException { MessageSelector messageSelector = mock(MessageSelector.class); defaultMQPushConsumerImpl.subscribe(defaultTopic, messageSelector); RebalanceImpl actual = defaultMQPushConsumerImpl.getRebalanceImpl(); assertEquals(1, actual.getSubscriptionInner().size()); } @Test public void testSuspend() { defaultMQPushConsumerImpl.suspend(); assertTrue(defaultMQPushConsumerImpl.isPause()); } @Test public void testViewMessage() throws InterruptedException, MQClientException, MQBrokerException, RemotingException { assertNull(defaultMQPushConsumerImpl.viewMessage(defaultTopic, createMessageExt().getMsgId())); } @Test public void testResetOffsetByTimeStamp() throws MQClientException { ConcurrentMap subscriptionDataMap = new ConcurrentHashMap<>(); subscriptionDataMap.put(defaultTopic, new SubscriptionData()); when(rebalanceImpl.getSubscriptionInner()).thenReturn(subscriptionDataMap); defaultMQPushConsumerImpl.resetOffsetByTimeStamp(System.currentTimeMillis()); verify(mQClientFactory).resetOffset(eq(defaultTopic), any(), any()); } @Test public void testSearchOffset() throws MQClientException { assertEquals(0, defaultMQPushConsumerImpl.searchOffset(createMessageQueue(), System.currentTimeMillis())); } @Test public void testQueryConsumeTimeSpan() throws InterruptedException, MQClientException, MQBrokerException, RemotingException { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.getBrokerDatas().add(createBrokerData()); when(mqClientAPIImpl.getTopicRouteInfoFromNameServer(any(), anyLong())).thenReturn(topicRouteData); List actual = defaultMQPushConsumerImpl.queryConsumeTimeSpan(defaultTopic); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void testTryResetPopRetryTopic() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.getBrokerDatas().add(createBrokerData()); MessageExt messageExt = createMessageExt(); List msgs = new ArrayList<>(); messageExt.setTopic(MixAll.RETRY_GROUP_TOPIC_PREFIX + defaultGroup + "_" + defaultTopic); msgs.add(messageExt); defaultMQPushConsumerImpl.tryResetPopRetryTopic(msgs, defaultGroup); assertEquals(defaultTopic, msgs.get(0).getTopic()); } @Test public void testGetPopDelayLevel() { int[] actual = defaultMQPushConsumerImpl.getPopDelayLevel(); int[] expected = new int[]{10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200}; assertArrayEquals(expected, actual); } @Test public void testGetMessageQueueListener() { assertNull(defaultMQPushConsumerImpl.getMessageQueueListener()); } @Test public void testConsumerRunningInfo() { ConcurrentMap processQueueMap = new ConcurrentHashMap<>(); ConcurrentMap popProcessQueueMap = new ConcurrentHashMap<>(); processQueueMap.put(createMessageQueue(), new ProcessQueue()); popProcessQueueMap.put(createMessageQueue(), new PopProcessQueue()); when(rebalanceImpl.getProcessQueueTable()).thenReturn(processQueueMap); when(rebalanceImpl.getPopProcessQueueTable()).thenReturn(popProcessQueueMap); ConsumerRunningInfo actual = defaultMQPushConsumerImpl.consumerRunningInfo(); assertNotNull(actual); assertEquals(1, actual.getSubscriptionSet().size()); assertEquals(defaultTopic, actual.getSubscriptionSet().iterator().next().getTopic()); assertEquals(1, actual.getMqTable().size()); assertEquals(1, actual.getMqPopTable().size()); assertEquals(1, actual.getStatusTable().size()); } private BrokerData createBrokerData() { BrokerData result = new BrokerData(); HashMap brokerAddrMap = new HashMap<>(); brokerAddrMap.put(MixAll.MASTER_ID, defaultBrokerAddr); result.setBrokerAddrs(brokerAddrMap); result.setBrokerName(defaultBroker); return result; } private MessageQueue createMessageQueue() { MessageQueue result = new MessageQueue(); result.setQueueId(0); result.setBrokerName(defaultBroker); result.setTopic(defaultTopic); return result; } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, defaultGroup); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); long curTime = System.currentTimeMillis(); result.setBornTimestamp(curTime - 1000); String popProps = String.format("%d %d %d %d %d %s %d %d %d", curTime, curTime, curTime, curTime, curTime, defaultBroker, 1, 0L, 1L); result.getProperties().put(MessageConst.PROPERTY_POP_CK, popProps); result.setKeys("keys"); result.setTags("*"); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.remoting.protocol.body.PopProcessQueueInfo; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; @RunWith(MockitoJUnitRunner.class) public class PopProcessQueueTest { private final PopProcessQueueInfo popProcessQueueInfo = new PopProcessQueueInfo(); @Test public void testPopProcessQueue() { long currentTime = System.currentTimeMillis(); PopProcessQueue popRequest1 = createPopProcessQueue(currentTime); PopProcessQueue popRequest2 = createPopProcessQueue(currentTime); assertEquals(popRequest1.getLastPopTimestamp(), popRequest2.getLastPopTimestamp()); assertEquals(popRequest1.toString(), popRequest2.toString()); assertEquals(popRequest1.getWaiAckMsgCount(), popRequest2.getWaiAckMsgCount()); assertEquals(popRequest1.ack(), popRequest2.ack()); assertEquals(popRequest1.isPullExpired(), popRequest2.isPullExpired()); assertEquals(popProcessQueueInfo.getLastPopTimestamp(), popRequest1.getLastPopTimestamp()); assertEquals(popProcessQueueInfo.isDroped(), popRequest1.isDropped()); assertEquals(popProcessQueueInfo.getWaitAckCount(), popRequest1.getWaiAckMsgCount() + popRequest2.getWaiAckMsgCount()); } private PopProcessQueue createPopProcessQueue(final long currentTime) { PopProcessQueue result = new PopProcessQueue(); long curTime = System.currentTimeMillis(); result.setLastPopTimestamp(curTime); result.incFoundMsg(1); result.decFoundMsg(1); result.setLastPopTimestamp(currentTime); result.fillPopProcessQueueInfo(popProcessQueueInfo); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.body.ProcessQueueInfo; import org.assertj.core.util.Lists; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.TreeMap; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class ProcessQueueTest { @Test public void testCachedMessageCount() { ProcessQueue pq = new ProcessQueue(); pq.putMessage(createMessageList()); assertThat(pq.getMsgCount().get()).isEqualTo(100); pq.takeMessages(10); pq.commit(); assertThat(pq.getMsgCount().get()).isEqualTo(90); pq.removeMessage(Collections.singletonList(pq.getMsgTreeMap().lastEntry().getValue())); assertThat(pq.getMsgCount().get()).isEqualTo(89); } @Test public void testCachedMessageSize() { ProcessQueue pq = new ProcessQueue(); pq.putMessage(createMessageList()); assertThat(pq.getMsgSize().get()).isEqualTo(100 * 123); pq.takeMessages(10); pq.commit(); assertThat(pq.getMsgSize().get()).isEqualTo(90 * 123); pq.removeMessage(Collections.singletonList(pq.getMsgTreeMap().lastEntry().getValue())); assertThat(pq.getMsgSize().get()).isEqualTo(89 * 123); } @Test public void testContainsMessage() { ProcessQueue pq = new ProcessQueue(); final List messageList = createMessageList(2); final MessageExt message0 = messageList.get(0); final MessageExt message1 = messageList.get(1); pq.putMessage(Lists.list(message0)); assertThat(pq.containsMessage(message0)).isTrue(); assertThat(pq.containsMessage(message1)).isFalse(); } @Test public void testFillProcessQueueInfo() throws IllegalAccessException { ProcessQueue pq = new ProcessQueue(); pq.putMessage(createMessageList(102400)); ProcessQueueInfo processQueueInfo = new ProcessQueueInfo(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(12); pq.takeMessages(10000); pq.commit(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(10); pq.takeMessages(10000); pq.commit(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(9); pq.takeMessages(80000); pq.commit(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(0); TreeMap consumingMsgOrderlyTreeMap = new TreeMap<>(); consumingMsgOrderlyTreeMap.put(0L, createMessageList(1).get(0)); FieldUtils.writeDeclaredField(pq, "consumingMsgOrderlyTreeMap", consumingMsgOrderlyTreeMap, true); pq.fillProcessQueueInfo(processQueueInfo); assertEquals(0, processQueueInfo.getTransactionMsgMinOffset()); assertEquals(0, processQueueInfo.getTransactionMsgMaxOffset()); assertEquals(1, processQueueInfo.getTransactionMsgCount()); } @Test public void testPopRequest() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { ProcessQueue processQueue = createProcessQueue(); MessageExt messageExt = createMessageList(1).get(0); messageExt.getProperties().put(MessageConst.PROPERTY_CONSUME_START_TIMESTAMP, System.currentTimeMillis() - 20 * 60 * 1000L + ""); processQueue.getMsgTreeMap().put(0L, messageExt); DefaultMQPushConsumer pushConsumer = mock(DefaultMQPushConsumer.class); processQueue.cleanExpiredMsg(pushConsumer); verify(pushConsumer).sendMessageBack(any(MessageExt.class), eq(3)); } @Test public void testRollback() throws IllegalAccessException { ProcessQueue processQueue = createProcessQueue(); processQueue.rollback(); Field consumingMsgOrderlyTreeMapField = FieldUtils.getDeclaredField(processQueue.getClass(), "consumingMsgOrderlyTreeMap", true); TreeMap consumingMsgOrderlyTreeMap = (TreeMap) consumingMsgOrderlyTreeMapField.get(processQueue); assertEquals(0, consumingMsgOrderlyTreeMap.size()); } @Test public void testHasTempMessage() { ProcessQueue processQueue = createProcessQueue(); assertFalse(processQueue.hasTempMessage()); } @Test public void testProcessQueue() { ProcessQueue processQueue1 = createProcessQueue(); ProcessQueue processQueue2 = createProcessQueue(); assertEquals(processQueue1.getMsgAccCnt(), processQueue2.getMsgAccCnt()); assertEquals(processQueue1.getTryUnlockTimes(), processQueue2.getTryUnlockTimes()); assertEquals(processQueue1.getLastPullTimestamp(), processQueue2.getLastPullTimestamp()); } private ProcessQueue createProcessQueue() { ProcessQueue result = new ProcessQueue(); result.setMsgAccCnt(1); result.incTryUnlockTimes(); result.setLastPullTimestamp(10000L); return result; } private List createMessageList() { return createMessageList(100); } private List createMessageList(int count) { List result = new ArrayList<>(); for (int i = 0; i < count; i++) { MessageExt messageExt = new MessageExt(); messageExt.setQueueOffset(i); messageExt.setBody(new byte[123]); messageExt.setKeys("keys" + i); messageExt.getProperties().put(MessageConst.PROPERTY_CONSUME_START_TIMESTAMP, System.currentTimeMillis() + ""); result.add(messageExt); } return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.FilterMessageContext; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PullAPIWrapperTest { @Mock private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mqClientAPIImpl; private PullAPIWrapper pullAPIWrapper; private final String defaultGroup = "defaultGroup"; private final String defaultBroker = "defaultBroker"; private final String defaultTopic = "defaultTopic"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final long defaultTimeout = 3000L; @Before public void init() throws Exception { ClientConfig clientConfig = mock(ClientConfig.class); when(mQClientFactory.getClientConfig()).thenReturn(clientConfig); MQClientAPIImpl mqClientAPIImpl = mock(MQClientAPIImpl.class); when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mqClientAPIImpl); when(mQClientFactory.getTopicRouteTable()).thenReturn(createTopicRouteTable()); FindBrokerResult findBrokerResult = mock(FindBrokerResult.class); when(findBrokerResult.getBrokerAddr()).thenReturn(defaultBrokerAddr); when(mQClientFactory.findBrokerAddressInSubscribe(any(), anyLong(), anyBoolean())).thenReturn(findBrokerResult); pullAPIWrapper = new PullAPIWrapper(mQClientFactory, defaultGroup, false); ArrayList filterMessageHookList = new ArrayList<>(); filterMessageHookList.add(mock(FilterMessageHook.class)); FieldUtils.writeDeclaredField(pullAPIWrapper, "filterMessageHookList", filterMessageHookList, true); } @Test public void testProcessPullResult() throws Exception { PullResultExt pullResult = mock(PullResultExt.class); when(pullResult.getPullStatus()).thenReturn(PullStatus.FOUND); when(pullResult.getMessageBinary()).thenReturn(MessageDecoder.encode(createMessageExt(), false)); SubscriptionData subscriptionData = mock(SubscriptionData.class); PullResult actual = pullAPIWrapper.processPullResult(createMessageQueue(), pullResult, subscriptionData); assertNotNull(actual); assertEquals(0, actual.getNextBeginOffset()); assertEquals(0, actual.getMsgFoundList().size()); } @Test public void testExecuteHook() throws IllegalAccessException { FilterMessageContext filterMessageContext = mock(FilterMessageContext.class); ArrayList filterMessageHookList = new ArrayList<>(); FilterMessageHook filterMessageHook = mock(FilterMessageHook.class); filterMessageHookList.add(filterMessageHook); FieldUtils.writeDeclaredField(pullAPIWrapper, "filterMessageHookList", filterMessageHookList, true); pullAPIWrapper.executeHook(filterMessageContext); verify(filterMessageHook, times(1)).filterMessage(any(FilterMessageContext.class)); } @Test public void testPullKernelImpl() throws Exception { PullCallback pullCallback = mock(PullCallback.class); when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mqClientAPIImpl); PullResult actual = pullAPIWrapper.pullKernelImpl(createMessageQueue(), "", "", 1L, 1L, 1, 1, PullSysFlag.buildSysFlag(false, false, false, true), 1L, System.currentTimeMillis(), defaultTimeout, CommunicationMode.ASYNC, pullCallback); assertNull(actual); verify(mqClientAPIImpl, times(1)).pullMessage(eq(defaultBroker), any(PullMessageRequestHeader.class), eq(defaultTimeout), any(CommunicationMode.class), any(PullCallback.class)); } @Test public void testSetConnectBrokerByUser() { pullAPIWrapper.setConnectBrokerByUser(true); assertTrue(pullAPIWrapper.isConnectBrokerByUser()); } @Test public void testRandomNum() { int randomNum = pullAPIWrapper.randomNum(); assertTrue(randomNum > 0); } @Test public void testSetDefaultBrokerId() { pullAPIWrapper.setDefaultBrokerId(MixAll.MASTER_ID); assertEquals(MixAll.MASTER_ID, pullAPIWrapper.getDefaultBrokerId()); } @Test public void testPopAsync() throws RemotingException, InterruptedException, MQClientException { PopCallback popCallback = mock(PopCallback.class); when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mqClientAPIImpl); pullAPIWrapper.popAsync(createMessageQueue(), System.currentTimeMillis(), 1, defaultGroup, defaultTimeout, popCallback, true, 1, false, "", ""); verify(mqClientAPIImpl, times(1)).popMessageAsync(eq(defaultBroker), eq(defaultBrokerAddr), any(PopMessageRequestHeader.class), eq(13000L), any(PopCallback.class)); } private ConcurrentMap createTopicRouteTable() { TopicRouteData topicRouteData = new TopicRouteData(); List brokerDatas = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName(defaultBroker); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, defaultBroker); brokerData.setBrokerAddrs(brokerAddrs); brokerDatas.add(brokerData); topicRouteData.setBrokerDatas(brokerDatas); HashMap> filterServerTable = new HashMap<>(); List filterServers = new ArrayList<>(); filterServers.add(defaultBroker); filterServerTable.put(defaultBrokerAddr, filterServers); topicRouteData.setFilterServerTable(filterServerTable); ConcurrentMap result = new ConcurrentHashMap<>(); result.put(defaultTopic, topicRouteData); return result; } private MessageQueue createMessageQueue() { MessageQueue result = new MessageQueue(); result.setQueueId(0); result.setBrokerName(defaultBroker); result.setTopic(defaultTopic); return result; } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(defaultTopic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, defaultGroup); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); long curTime = System.currentTimeMillis(); result.setBornTimestamp(curTime - 1000); result.getProperties().put(MessageConst.PROPERTY_POP_CK, curTime + " " + curTime + " " + curTime + " " + curTime); result.setKeys("keys"); result.setSysFlag(MessageSysFlag.INNER_BATCH_FLAG); result.setSysFlag(result.getSysFlag() | MessageSysFlag.NEED_UNWRAP_FLAG); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/PullMessageServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageRequestMode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PullMessageServiceTest { @Mock private MQClientInstance mQClientFactory; @Mock private ScheduledExecutorService executorService; private PullMessageService pullMessageService; private final long defaultTimeout = 3000L; private final String defaultGroup = "defaultGroup"; @Before public void init() throws Exception { pullMessageService = new PullMessageService(mQClientFactory); FieldUtils.writeDeclaredField(pullMessageService, "scheduledExecutorService", executorService, true); pullMessageService.start(); } @Test public void testProcessPullResult() { PopRequest popRequest = mock(PopRequest.class); pullMessageService.executePopPullRequestLater(popRequest, defaultTimeout); pullMessageService.makeStop(); pullMessageService.executePopPullRequestLater(popRequest, defaultTimeout); verify(executorService, times(1)) .schedule(any(Runnable.class), eq(defaultTimeout), eq(TimeUnit.MILLISECONDS)); } @Test public void testExecutePopPullRequestImmediately() throws IllegalAccessException, InterruptedException { PopRequest popRequest = mock(PopRequest.class); LinkedBlockingQueue messageRequestQueue = mock(LinkedBlockingQueue.class); FieldUtils.writeDeclaredField(pullMessageService, "messageRequestQueue", messageRequestQueue, true); pullMessageService.executePopPullRequestImmediately(popRequest); verify(messageRequestQueue, times(1)).put(any(PopRequest.class)); } @Test public void testExecuteTaskLater() { Runnable runnable = mock(Runnable.class); pullMessageService.executeTaskLater(runnable, defaultTimeout); pullMessageService.makeStop(); pullMessageService.executeTaskLater(runnable, defaultTimeout); verify(executorService, times(1)) .schedule(any(Runnable.class), eq(defaultTimeout), eq(TimeUnit.MILLISECONDS)); } @Test public void testExecuteTask() { Runnable runnable = mock(Runnable.class); pullMessageService.executeTask(runnable); pullMessageService.makeStop(); pullMessageService.executeTask(runnable); verify(executorService, times(1)).execute(any(Runnable.class)); } @Test public void testGetScheduledExecutorService() { assertEquals(executorService, pullMessageService.getScheduledExecutorService()); } @Test public void testRun() throws InterruptedException, IllegalAccessException { LinkedBlockingQueue messageRequestQueue = new LinkedBlockingQueue<>(); PopRequest popRequest = mock(PopRequest.class); when(popRequest.getMessageRequestMode()).thenReturn(MessageRequestMode.POP); when(popRequest.getConsumerGroup()).thenReturn(defaultGroup); messageRequestQueue.put(popRequest); DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = mock(DefaultMQPushConsumerImpl.class); when(mQClientFactory.selectConsumer(any())).thenReturn(defaultMQPushConsumerImpl); FieldUtils.writeDeclaredField(pullMessageService, "messageRequestQueue", messageRequestQueue, true); new Thread(() -> pullMessageService.run()).start(); TimeUnit.SECONDS.sleep(1); pullMessageService.makeStop(); verify(mQClientFactory, times(1)).selectConsumer(eq(defaultGroup)); verify(defaultMQPushConsumerImpl).popMessage(any(PopRequest.class)); } @Test public void testRunWithNullConsumer() throws InterruptedException, IllegalAccessException { LinkedBlockingQueue messageRequestQueue = new LinkedBlockingQueue<>(); PopRequest popRequest = mock(PopRequest.class); when(popRequest.getMessageRequestMode()).thenReturn(MessageRequestMode.POP); when(popRequest.getConsumerGroup()).thenReturn(defaultGroup); messageRequestQueue.put(popRequest); FieldUtils.writeDeclaredField(pullMessageService, "messageRequestQueue", messageRequestQueue, true); new Thread(() -> pullMessageService.run()).start(); TimeUnit.SECONDS.sleep(1); pullMessageService.makeStop(); verify(mQClientFactory, times(1)).selectConsumer(eq(defaultGroup)); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class RebalanceLitePullImplTest { private MessageQueue mq = new MessageQueue("topic1", "broker1", 0); private MessageQueue retryMq = new MessageQueue(MixAll.RETRY_GROUP_TOPIC_PREFIX + "group", "broker1", 0); private DefaultLitePullConsumerImpl consumerImpl = mock(DefaultLitePullConsumerImpl.class); private RebalanceLitePullImpl rebalanceImpl = new RebalanceLitePullImpl(consumerImpl); private OffsetStore offsetStore = mock(OffsetStore.class); private DefaultLitePullConsumer consumer = new DefaultLitePullConsumer(); private MQClientInstance client = mock(MQClientInstance.class); private MQAdminImpl admin = mock(MQAdminImpl.class); public RebalanceLitePullImplTest() { when(consumerImpl.getDefaultLitePullConsumer()).thenReturn(consumer); when(consumerImpl.getOffsetStore()).thenReturn(offsetStore); rebalanceImpl.setmQClientFactory(client); when(client.getMQAdminImpl()).thenReturn(admin); } @Test public void testComputePullFromWhereWithException_ne_minus1() throws MQClientException { for (ConsumeFromWhere where : new ConsumeFromWhere[]{ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET, ConsumeFromWhere.CONSUME_FROM_TIMESTAMP}) { consumer.setConsumeFromWhere(where); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(0L); assertEquals(0, rebalanceImpl.computePullFromWhereWithException(mq)); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-2L); assertEquals(-1, rebalanceImpl.computePullFromWhereWithException(mq)); } } @Test public void testComputePullFromWhereWithException_eq_minus1_last() throws MQClientException { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); when(admin.maxOffset(any(MessageQueue.class))).thenReturn(12345L); assertEquals(12345L, rebalanceImpl.computePullFromWhereWithException(mq)); assertEquals(0L, rebalanceImpl.computePullFromWhereWithException(retryMq)); } @Test public void testComputePullFromWhereWithException_eq_minus1_first() throws MQClientException { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); assertEquals(0, rebalanceImpl.computePullFromWhereWithException(mq)); } @Test public void testComputePullFromWhereWithException_eq_minus1_timestamp() throws MQClientException { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); when(admin.searchOffset(any(MessageQueue.class), anyLong())).thenReturn(12345L); when(admin.maxOffset(any(MessageQueue.class))).thenReturn(23456L); assertEquals(12345L, rebalanceImpl.computePullFromWhereWithException(mq)); assertEquals(23456L, rebalanceImpl.computePullFromWhereWithException(retryMq)); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.consumer; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RebalancePushImplTest { @Spy private DefaultMQPushConsumerImpl defaultMQPushConsumer = new DefaultMQPushConsumerImpl(new DefaultMQPushConsumer("RebalancePushImplTest"), null); @Mock private MQClientInstance mqClientInstance; private OffsetStore offsetStore = mock(OffsetStore.class); private String consumerGroup = "CID_RebalancePushImplTest"; private String topic = "TopicA"; private MessageQueue mq = new MessageQueue("topic1", "broker1", 0); private MessageQueue retryMq = new MessageQueue(MixAll.RETRY_GROUP_TOPIC_PREFIX + "group", "broker1", 0); private DefaultMQPushConsumerImpl consumerImpl = mock(DefaultMQPushConsumerImpl.class); private RebalancePushImpl rebalanceImpl = new RebalancePushImpl(consumerImpl); private DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(); private MQClientInstance client = mock(MQClientInstance.class); private MQAdminImpl admin = mock(MQAdminImpl.class); public RebalancePushImplTest() { when(consumerImpl.getDefaultMQPushConsumer()).thenReturn(consumer); when(consumerImpl.getOffsetStore()).thenReturn(offsetStore); rebalanceImpl.setmQClientFactory(client); when(client.getMQAdminImpl()).thenReturn(admin); } @Test public void testMessageQueueChanged_CountThreshold() { RebalancePushImpl rebalancePush = new RebalancePushImpl(consumerGroup, MessageModel.CLUSTERING, new AllocateMessageQueueAveragely(), mqClientInstance, defaultMQPushConsumer); init(rebalancePush); // Just set pullThresholdForQueue defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForQueue(1024); Set allocateResultSet = new HashSet<>(); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.getDefaultMQPushConsumer().getPullThresholdForQueue()).isEqualTo(1024); // Set pullThresholdForTopic defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForTopic(1024); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.getDefaultMQPushConsumer().getPullThresholdForQueue()).isEqualTo(512); // Change message queue allocate result allocateResultSet.add(new MessageQueue(topic, "BrokerA", 2)); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.getDefaultMQPushConsumer().getPullThresholdForQueue()).isEqualTo(341); } private void doRebalanceForcibly(RebalancePushImpl rebalancePush, Set allocateResultSet) { rebalancePush.topicSubscribeInfoTable.put(topic, allocateResultSet); rebalancePush.doRebalance(false); rebalancePush.messageQueueChanged(topic, allocateResultSet, allocateResultSet); } private void init(final RebalancePushImpl rebalancePush) { rebalancePush.getSubscriptionInner().putIfAbsent(topic, new SubscriptionData()); rebalancePush.subscriptionInner.putIfAbsent(topic, new SubscriptionData()); when(mqClientInstance.findConsumerIdList(anyString(), anyString())).thenReturn(Collections.singletonList(consumerGroup)); when(mqClientInstance.getClientId()).thenReturn(consumerGroup); when(defaultMQPushConsumer.getOffsetStore()).thenReturn(offsetStore); } @Test public void testMessageQueueChanged_SizeThreshold() { RebalancePushImpl rebalancePush = new RebalancePushImpl(consumerGroup, MessageModel.CLUSTERING, new AllocateMessageQueueAveragely(), mqClientInstance, defaultMQPushConsumer); init(rebalancePush); // Just set pullThresholdSizeForQueue defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForQueue(1024); Set allocateResultSet = new HashSet<>(); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.getDefaultMQPushConsumer().getPullThresholdSizeForQueue()).isEqualTo(1024); // Set pullThresholdSizeForTopic defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForTopic(1024); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.getDefaultMQPushConsumer().getPullThresholdSizeForQueue()).isEqualTo(512); // Change message queue allocate result allocateResultSet.add(new MessageQueue(topic, "BrokerA", 2)); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.getDefaultMQPushConsumer().getPullThresholdSizeForQueue()).isEqualTo(341); } @Test public void testMessageQueueChanged_ConsumerRuntimeInfo() throws MQClientException { RebalancePushImpl rebalancePush = new RebalancePushImpl(consumerGroup, MessageModel.CLUSTERING, new AllocateMessageQueueAveragely(), mqClientInstance, defaultMQPushConsumer); init(rebalancePush); defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForQueue(1024); defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForQueue(1024); Set allocateResultSet = new HashSet<>(); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); defaultMQPushConsumer.setConsumeMessageService(new ConsumeMessageConcurrentlyService(defaultMQPushConsumer, null)); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForQueue")).isEqualTo("1024"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForQueue")).isEqualTo("1024"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForTopic")).isEqualTo("-1"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForTopic")).isEqualTo("-1"); defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForTopic(1024); defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForTopic(1024); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForQueue")).isEqualTo("512"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForQueue")).isEqualTo("512"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForTopic")).isEqualTo("1024"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForTopic")).isEqualTo("1024"); // Change message queue allocate result allocateResultSet.add(new MessageQueue(topic, "BrokerA", 2)); doRebalanceForcibly(rebalancePush, allocateResultSet); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForQueue")).isEqualTo("341"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForQueue")).isEqualTo("341"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForTopic")).isEqualTo("1024"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForTopic")).isEqualTo("1024"); } @Test public void testComputePullFromWhereWithException_ne_minus1() throws MQClientException { for (ConsumeFromWhere where : new ConsumeFromWhere[]{ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET, ConsumeFromWhere.CONSUME_FROM_TIMESTAMP}) { consumer.setConsumeFromWhere(where); when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(0L); assertEquals(0, rebalanceImpl.computePullFromWhereWithException(mq)); } } @Test public void testComputePullFromWhereWithException_eq_minus1_last() throws MQClientException { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); when(admin.maxOffset(any(MessageQueue.class))).thenReturn(12345L); assertEquals(12345L, rebalanceImpl.computePullFromWhereWithException(mq)); assertEquals(0L, rebalanceImpl.computePullFromWhereWithException(retryMq)); } @Test public void testComputePullFromWhereWithException_eq_minus1_first() throws MQClientException { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); assertEquals(0, rebalanceImpl.computePullFromWhereWithException(mq)); } @Test public void testComputePullFromWhereWithException_eq_minus1_timestamp() throws MQClientException { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(-1L); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); when(admin.searchOffset(any(MessageQueue.class), anyLong())).thenReturn(12345L); when(admin.maxOffset(any(MessageQueue.class))).thenReturn(23456L); assertEquals(12345L, rebalanceImpl.computePullFromWhereWithException(mq)); assertEquals(23456L, rebalanceImpl.computePullFromWhereWithException(retryMq)); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.factory; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.admin.MQAdminExtInner; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageService; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.MQConsumerInner; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.common.HeartbeatV2Result; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class MQClientInstanceTest { @Mock private MQClientAPIImpl mQClientAPIImpl; @Mock private RemotingClient remotingClient; @Mock private ClientConfig clientConfig; private final MQClientInstance mqClientInstance = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); private final String topic = "FooBar"; private final String group = "FooBarGroup"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final String defaultBroker = "BrokerA"; private final ConcurrentMap> brokerAddrTable = new ConcurrentHashMap<>(); private final ConcurrentMap consumerTable = new ConcurrentHashMap<>(); private final ConcurrentMap topicRouteTable = new ConcurrentHashMap<>(); @Before public void init() throws Exception { when(mQClientAPIImpl.getRemotingClient()).thenReturn(remotingClient); FieldUtils.writeDeclaredField(mqClientInstance, "brokerAddrTable", brokerAddrTable, true); FieldUtils.writeDeclaredField(mqClientInstance, "mQClientAPIImpl", mQClientAPIImpl, true); FieldUtils.writeDeclaredField(mqClientInstance, "consumerTable", consumerTable, true); FieldUtils.writeDeclaredField(mqClientInstance, "clientConfig", clientConfig, true); FieldUtils.writeDeclaredField(mqClientInstance, "topicRouteTable", topicRouteTable, true); } @After public void tearDown() throws Exception { brokerAddrTable.clear(); consumerTable.clear(); topicRouteTable.clear(); } @Test public void testFindBrokerAddressInSubscribe() { // dledger normal case String brokerName = "BrokerA"; HashMap addrMap = new HashMap<>(); addrMap.put(0L, "127.0.0.1:10911"); addrMap.put(1L, "127.0.0.1:10912"); addrMap.put(2L, "127.0.0.1:10913"); brokerAddrTable.put(brokerName, addrMap); long brokerId = 1; FindBrokerResult brokerResult = mqClientInstance.findBrokerAddressInSubscribe(brokerName, brokerId, false); assertThat(brokerResult).isNotNull(); assertThat(brokerResult.getBrokerAddr()).isEqualTo("127.0.0.1:10912"); assertThat(brokerResult.isSlave()).isTrue(); // dledger case, when node n0 was voted as the leader brokerName = "BrokerB"; HashMap addrMapNew = new HashMap<>(); addrMapNew.put(0L, "127.0.0.1:10911"); addrMapNew.put(2L, "127.0.0.1:10912"); addrMapNew.put(3L, "127.0.0.1:10913"); brokerAddrTable.put(brokerName, addrMapNew); brokerResult = mqClientInstance.findBrokerAddressInSubscribe(brokerName, brokerId, false); assertThat(brokerResult).isNotNull(); assertThat(brokerResult.getBrokerAddr()).isEqualTo("127.0.0.1:10912"); assertThat(brokerResult.isSlave()).isTrue(); } @Test public void testRegisterProducer() { boolean flag = mqClientInstance.registerProducer(group, mock(DefaultMQProducerImpl.class)); assertThat(flag).isTrue(); flag = mqClientInstance.registerProducer(group, mock(DefaultMQProducerImpl.class)); assertThat(flag).isFalse(); mqClientInstance.unregisterProducer(group); flag = mqClientInstance.registerProducer(group, mock(DefaultMQProducerImpl.class)); assertThat(flag).isTrue(); } @Test public void testRegisterConsumer() { boolean flag = mqClientInstance.registerConsumer(group, mock(MQConsumerInner.class)); assertThat(flag).isTrue(); flag = mqClientInstance.registerConsumer(group, mock(MQConsumerInner.class)); assertThat(flag).isFalse(); mqClientInstance.unregisterConsumer(group); flag = mqClientInstance.registerConsumer(group, mock(MQConsumerInner.class)); assertThat(flag).isTrue(); } @Test public void testConsumerRunningInfoWhenConsumersIsEmptyOrNot() throws RemotingException, InterruptedException, MQBrokerException { MQConsumerInner mockConsumerInner = mock(MQConsumerInner.class); ConsumerRunningInfo mockConsumerRunningInfo = mock(ConsumerRunningInfo.class); when(mockConsumerInner.consumerRunningInfo()).thenReturn(mockConsumerRunningInfo); when(mockConsumerInner.consumeType()).thenReturn(ConsumeType.CONSUME_PASSIVELY); Properties properties = new Properties(); when(mockConsumerRunningInfo.getProperties()).thenReturn(properties); mqClientInstance.unregisterConsumer(group); ConsumerRunningInfo runningInfo = mqClientInstance.consumerRunningInfo(group); assertThat(runningInfo).isNull(); boolean flag = mqClientInstance.registerConsumer(group, mockConsumerInner); assertThat(flag).isTrue(); runningInfo = mqClientInstance.consumerRunningInfo(group); assertThat(runningInfo).isNotNull(); assertThat(mockConsumerInner.consumerRunningInfo().getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).isNotNull(); mqClientInstance.unregisterConsumer(group); flag = mqClientInstance.registerConsumer(group, mock(MQConsumerInner.class)); assertThat(flag).isTrue(); } @Test public void testRegisterAdminExt() { boolean flag = mqClientInstance.registerAdminExt(group, mock(MQAdminExtInner.class)); assertThat(flag).isTrue(); flag = mqClientInstance.registerAdminExt(group, mock(MQAdminExtInner.class)); assertThat(flag).isFalse(); mqClientInstance.unregisterAdminExt(group); flag = mqClientInstance.registerAdminExt(group, mock(MQAdminExtInner.class)); assertThat(flag).isTrue(); } @Test public void testTopicRouteData2TopicPublishInfo() { TopicPublishInfo actual = MQClientInstance.topicRouteData2TopicPublishInfo(topic, createTopicRouteData()); assertThat(actual.isHaveTopicRouterInfo()).isFalse(); assertThat(actual.getMessageQueueList().size()).isEqualTo(4); } @Test public void testTopicRouteData2TopicPublishInfoWithOrderTopicConf() { TopicRouteData topicRouteData = createTopicRouteData(); topicRouteData.setOrderTopicConf("127.0.0.1:4"); TopicPublishInfo actual = MQClientInstance.topicRouteData2TopicPublishInfo(topic, topicRouteData); assertFalse(actual.isHaveTopicRouterInfo()); assertEquals(4, actual.getMessageQueueList().size()); } @Test public void testTopicRouteData2TopicPublishInfoWithTopicQueueMappingByBroker() { TopicRouteData topicRouteData = createTopicRouteData(); topicRouteData.setTopicQueueMappingByBroker(Collections.singletonMap(topic, new TopicQueueMappingInfo())); TopicPublishInfo actual = MQClientInstance.topicRouteData2TopicPublishInfo(topic, topicRouteData); assertFalse(actual.isHaveTopicRouterInfo()); assertEquals(0, actual.getMessageQueueList().size()); } @Test public void testTopicRouteData2TopicSubscribeInfo() { TopicRouteData topicRouteData = createTopicRouteData(); topicRouteData.setTopicQueueMappingByBroker(Collections.singletonMap(topic, new TopicQueueMappingInfo())); Set actual = MQClientInstance.topicRouteData2TopicSubscribeInfo(topic, topicRouteData); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void testParseOffsetTableFromBroker() { Map offsetTable = new HashMap<>(); offsetTable.put(new MessageQueue(), 0L); Map actual = mqClientInstance.parseOffsetTableFromBroker(offsetTable, "defaultNamespace"); assertNotNull(actual); assertEquals(1, actual.size()); } @Test public void testCheckClientInBroker() throws MQClientException, RemotingSendRequestException, RemotingConnectException, RemotingTimeoutException, InterruptedException { doThrow(new MQClientException("checkClientInBroker exception", null)).when(mQClientAPIImpl).checkClientInBroker( any(), any(), any(), any(SubscriptionData.class), anyLong()); topicRouteTable.put(topic, createTopicRouteData()); MQConsumerInner mqConsumerInner = createMQConsumerInner(); mqConsumerInner.subscriptions().clear(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setExpressionType("type"); mqConsumerInner.subscriptions().add(subscriptionData); consumerTable.put(group, mqConsumerInner); Throwable thrown = assertThrows(MQClientException.class, mqClientInstance::checkClientInBroker); assertTrue(thrown.getMessage().contains("checkClientInBroker exception")); } @Test public void testSendHeartbeatToBrokerV1() { consumerTable.put(group, createMQConsumerInner()); assertTrue(mqClientInstance.sendHeartbeatToBroker(0L, defaultBroker, defaultBrokerAddr)); } @Test public void testSendHeartbeatToBrokerV2() throws MQBrokerException, RemotingException, InterruptedException { consumerTable.put(group, createMQConsumerInner()); when(clientConfig.isUseHeartbeatV2()).thenReturn(true); HeartbeatV2Result heartbeatV2Result = mock(HeartbeatV2Result.class); when(heartbeatV2Result.isSupportV2()).thenReturn(true); when(mQClientAPIImpl.sendHeartbeatV2(any(), any(HeartbeatData.class), anyLong())).thenReturn(heartbeatV2Result); assertTrue(mqClientInstance.sendHeartbeatToBroker(0L, defaultBroker, defaultBrokerAddr)); } @Test public void testSendHeartbeatToAllBrokerWithLockV1() { brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); assertTrue(mqClientInstance.sendHeartbeatToAllBrokerWithLock()); } @Test public void testSendHeartbeatToAllBrokerWithLockV2() { brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); when(clientConfig.isUseHeartbeatV2()).thenReturn(true); assertTrue(mqClientInstance.sendHeartbeatToAllBrokerWithLock()); } @Test public void testUpdateTopicRouteInfoFromNameServer() throws RemotingException, InterruptedException, MQClientException { brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); DefaultMQProducer defaultMQProducer = mock(DefaultMQProducer.class); TopicRouteData topicRouteData = createTopicRouteData(); when(mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(anyLong())).thenReturn(topicRouteData); assertTrue(mqClientInstance.updateTopicRouteInfoFromNameServer(topic, true, defaultMQProducer)); assertEquals(topicRouteData, topicRouteTable.get(topic)); } @Test public void testFindBrokerAddressInAdmin() { brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); FindBrokerResult actual = mqClientInstance.findBrokerAddressInAdmin(defaultBroker); assertNotNull(actual); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); } @Test public void testFindBrokerAddressInSubscribeWithOneBroker() throws IllegalAccessException { brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); ConcurrentMap> brokerVersionTable = new ConcurrentHashMap<>(); ConcurrentHashMap addressMap = new ConcurrentHashMap<>(); addressMap.put(defaultBrokerAddr, 0); brokerVersionTable.put(defaultBroker, addressMap); FieldUtils.writeDeclaredField(mqClientInstance, "brokerVersionTable", brokerVersionTable, true); FindBrokerResult actual = mqClientInstance.findBrokerAddressInSubscribe(defaultBroker, 1L, false); assertNotNull(actual); assertEquals(defaultBrokerAddr, actual.getBrokerAddr()); } @Test public void testFindConsumerIdList() { topicRouteTable.put(topic, createTopicRouteData()); brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); List actual = mqClientInstance.findConsumerIdList(topic, group); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void testQueryAssignment() throws MQBrokerException, RemotingException, InterruptedException { topicRouteTable.put(topic, createTopicRouteData()); brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); Set actual = mqClientInstance.queryAssignment(topic, group, "", MessageModel.CLUSTERING, 1000); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void testResetOffset() throws IllegalAccessException { topicRouteTable.put(topic, createTopicRouteData()); brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); Map offsetTable = new HashMap<>(); offsetTable.put(createMessageQueue(), 0L); mqClientInstance.resetOffset(topic, group, offsetTable); Field consumerTableField = FieldUtils.getDeclaredField(mqClientInstance.getClass(), "consumerTable", true); ConcurrentMap consumerTable = (ConcurrentMap) consumerTableField.get(mqClientInstance); DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) consumerTable.get(group); verify(consumer).suspend(); verify(consumer).resume(); verify(consumer, times(1)) .updateConsumeOffset( any(MessageQueue.class), eq(0L)); } @Test public void testGetConsumerStatus() { topicRouteTable.put(topic, createTopicRouteData()); brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); consumerTable.put(group, createMQConsumerInner()); Map actual = mqClientInstance.getConsumerStatus(topic, group); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void testGetAnExistTopicRouteData() { topicRouteTable.put(topic, createTopicRouteData()); TopicRouteData actual = mqClientInstance.getAnExistTopicRouteData(topic); assertNotNull(actual); assertNotNull(actual.getQueueDatas()); assertNotNull(actual.getBrokerDatas()); } @Test public void testConsumeMessageDirectly() { consumerTable.put(group, createMQConsumerInner()); assertNull(mqClientInstance.consumeMessageDirectly(createMessageExt(), group, defaultBroker)); } @Test public void testQueryTopicRouteData() { consumerTable.put(group, createMQConsumerInner()); topicRouteTable.put(topic, createTopicRouteData()); TopicRouteData actual = mqClientInstance.queryTopicRouteData(topic); assertNotNull(actual); assertNotNull(actual.getQueueDatas()); assertNotNull(actual.getBrokerDatas()); } private MessageExt createMessageExt() { MessageExt result = new MessageExt(); result.setBody("body".getBytes(StandardCharsets.UTF_8)); result.setTopic(topic); result.setBrokerName(defaultBroker); result.putUserProperty("key", "value"); result.getProperties().put(MessageConst.PROPERTY_PRODUCER_GROUP, group); result.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "TX1"); long curTime = System.currentTimeMillis(); result.setBornTimestamp(curTime - 1000); result.getProperties().put(MessageConst.PROPERTY_POP_CK, curTime + " " + curTime + " " + curTime + " " + curTime); result.setKeys("keys"); result.setSysFlag(MessageSysFlag.INNER_BATCH_FLAG); result.setSysFlag(result.getSysFlag() | MessageSysFlag.NEED_UNWRAP_FLAG); SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 12911); SocketAddress storeHost = new InetSocketAddress("127.0.0.1", 10911); result.setBornHost(bornHost); result.setStoreHost(storeHost); return result; } private MessageQueue createMessageQueue() { MessageQueue result = new MessageQueue(); result.setQueueId(0); result.setBrokerName(defaultBroker); result.setTopic(topic); return result; } private TopicRouteData createTopicRouteData() { TopicRouteData result = new TopicRouteData(); result.setBrokerDatas(createBrokerDatas()); result.setQueueDatas(createQueueDatas()); return result; } private HashMap createBrokerAddrMap() { HashMap result = new HashMap<>(); result.put(0L, defaultBrokerAddr); return result; } private MQConsumerInner createMQConsumerInner() { DefaultMQPushConsumerImpl result = mock(DefaultMQPushConsumerImpl.class); Set subscriptionDataSet = new HashSet<>(); SubscriptionData subscriptionData = mock(SubscriptionData.class); subscriptionDataSet.add(subscriptionData); when(result.subscriptions()).thenReturn(subscriptionDataSet); RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); ConcurrentMap processQueueMap = new ConcurrentHashMap<>(); ProcessQueue processQueue = new ProcessQueue(); processQueueMap.put(createMessageQueue(), processQueue); when(rebalanceImpl.getProcessQueueTable()).thenReturn(processQueueMap); when(result.getRebalanceImpl()).thenReturn(rebalanceImpl); OffsetStore offsetStore = mock(OffsetStore.class); when(result.getOffsetStore()).thenReturn(offsetStore); ConsumeMessageService consumeMessageService = mock(ConsumeMessageService.class); when(result.getConsumeMessageService()).thenReturn(consumeMessageService); return result; } private List createQueueDatas() { QueueData queueData = new QueueData(); queueData.setBrokerName(defaultBroker); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); return Collections.singletonList(queueData); } private List createBrokerDatas() { BrokerData brokerData = new BrokerData(); brokerData.setBrokerName(defaultBroker); String defaultCluster = "defaultCluster"; brokerData.setCluster(defaultCluster); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, defaultBrokerAddr); brokerData.setBrokerAddrs(brokerAddrs); return Collections.singletonList(brokerData); } @Test public void testSendHeartbeatToAllBrokerConcurrently() { try { String brokerName = "BrokerA"; HashMap addrMap = new HashMap<>(); addrMap.put(0L, "127.0.0.1:10911"); addrMap.put(1L, "127.0.0.1:10912"); addrMap.put(2L, "127.0.0.1:10913"); brokerAddrTable.put(brokerName, addrMap); DefaultMQPushConsumerImpl mockConsumer = mock(DefaultMQPushConsumerImpl.class); when(mockConsumer.subscriptions()).thenReturn(Collections.singleton(new SubscriptionData())); mqClientInstance.registerConsumer("TestConsumerGroup", mockConsumer); ClientConfig clientConfig = new ClientConfig(); FieldUtils.writeDeclaredField(clientConfig, "enableConcurrentHeartbeat", true, true); FieldUtils.writeDeclaredField(mqClientInstance, "clientConfig", clientConfig, true); ExecutorService mockExecutor = mock(ExecutorService.class); doAnswer(invocation -> { try { Runnable task = invocation.getArgument(0); task.run(); } catch (Exception e) { // ignore } return null; }).when(mockExecutor).execute(any(Runnable.class)); FieldUtils.writeDeclaredField(mqClientInstance, "concurrentHeartbeatExecutor", mockExecutor, true); MQClientAPIImpl mockMqClientAPIImpl = mock(MQClientAPIImpl.class); FieldUtils.writeDeclaredField(mqClientInstance, "mQClientAPIImpl", mockMqClientAPIImpl, true); mqClientInstance.sendHeartbeatToAllBrokerWithLock(); assertTrue(true); } catch (Exception e) { fail("failed: " + e.getMessage()); } } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExtTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.mqclient; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; @RunWith(MockitoJUnitRunner.class) public class MQClientAPIExtTest { MQClientAPIExt mqClientAPIExt; @Mock NettyRemotingClient remotingClientMock; @Before public void before() { mqClientAPIExt = Mockito.spy(new MQClientAPIExt(new ClientConfig(), new NettyClientConfig(), null, null)); Mockito.when(mqClientAPIExt.getRemotingClient()).thenReturn(remotingClientMock); Mockito.when(remotingClientMock.invoke(anyString(), any(), anyLong())).thenReturn(FutureUtils.completeExceptionally(new RemotingTimeoutException("addr"))); } @Test public void sendMessageAsync() { String topic = "test"; Message msg = new Message(topic, "test".getBytes()); SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setProducerGroup("test"); requestHeader.setDefaultTopic("test"); requestHeader.setDefaultTopicQueueNums(1); requestHeader.setQueueId(0); requestHeader.setSysFlag(0); requestHeader.setBornTimestamp(0L); requestHeader.setFlag(0); requestHeader.setProperties("test"); requestHeader.setReconsumeTimes(0); requestHeader.setUnitMode(false); requestHeader.setBatch(false); CompletableFuture future = mqClientAPIExt.sendMessageAsync("127.0.0.1:10911", "test", msg, requestHeader, 10); assertThatThrownBy(future::get).getCause().isInstanceOf(RemotingTimeoutException.class); } @Test public void testUpdateConsumerOffsetAsync_Success() throws ExecutionException, InterruptedException { CompletableFuture remotingFuture = new CompletableFuture<>(); remotingFuture.complete(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "")); doReturn(remotingFuture).when(remotingClientMock).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture future = mqClientAPIExt.updateConsumerOffsetAsync("brokerAddr", new UpdateConsumerOffsetRequestHeader(), 3000L); assertNull("Future should be completed without exception", future.get()); } @Test public void testUpdateConsumerOffsetAsync_Fail() throws InterruptedException { CompletableFuture remotingFuture = new CompletableFuture<>(); remotingFuture.complete(RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, "QueueId is null, topic is testTopic")); doReturn(remotingFuture).when(remotingClientMock).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture future = mqClientAPIExt.updateConsumerOffsetAsync("brokerAddr", new UpdateConsumerOffsetRequestHeader(), 3000L); try { future.get(); } catch (ExecutionException e) { MQBrokerException customEx = (MQBrokerException) e.getCause(); assertEquals(customEx.getResponseCode(), ResponseCode.SYSTEM_ERROR); assertEquals(customEx.getErrorMessage(), "QueueId is null, topic is testTopic"); } } @Test public void testRecallMessageAsync_success() { String msgId = "msgId"; RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup("group"); requestHeader.setTopic("topic"); requestHeader.setRecallHandle("handle"); requestHeader.setBrokerName("brokerName"); RemotingCommand response = RemotingCommand.createResponseCommand(RecallMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); RecallMessageResponseHeader responseHeader = (RecallMessageResponseHeader) response.readCustomHeader(); responseHeader.setMsgId(msgId); response.makeCustomHeaderToNet(); CompletableFuture remotingFuture = new CompletableFuture<>(); remotingFuture.complete(response); doReturn(remotingFuture).when(remotingClientMock).invoke(anyString(), any(RemotingCommand.class), anyLong()); String resultId = mqClientAPIExt.recallMessageAsync("brokerAddr", requestHeader, 3000L).join(); Assert.assertEquals(msgId, resultId); } @Test public void testRecallMessageAsync_fail() { RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup("group"); requestHeader.setTopic("topic"); requestHeader.setRecallHandle("handle"); requestHeader.setBrokerName("brokerName"); CompletableFuture remotingFuture = new CompletableFuture<>(); remotingFuture.complete(RemotingCommand.createResponseCommand(ResponseCode.SERVICE_NOT_AVAILABLE, "")); doReturn(remotingFuture).when(remotingClientMock).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletionException exception = Assert.assertThrows(CompletionException.class, () -> { mqClientAPIExt.recallMessageAsync("brokerAddr", requestHeader, 3000L).join(); }); Assert.assertTrue(exception.getCause() instanceof MQBrokerException); MQBrokerException cause = (MQBrokerException) exception.getCause(); Assert.assertEquals(ResponseCode.SERVICE_NOT_AVAILABLE, cause.getResponseCode()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPITest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.impl.mqclient; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import org.apache.rocketmq.client.common.NameserverAccessConfig; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.RPCHook; import org.junit.After; import org.junit.Before; import org.junit.Test; public class MQClientAPITest { private NameserverAccessConfig nameserverAccessConfig; private final ClientRemotingProcessor clientRemotingProcessor = new DoNothingClientRemotingProcessor(null); private final RPCHook rpcHook = null; private ScheduledExecutorService scheduledExecutorService; private MQClientAPIFactory mqClientAPIFactory; @Before public void setUp() { scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor("TestScheduledExecutorService", true); } @After public void tearDown() { scheduledExecutorService.shutdownNow(); } @Test public void testInitWithNamesrvAddr() { nameserverAccessConfig = new NameserverAccessConfig("127.0.0.1:9876", "", ""); mqClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "TestPrefix", 2, clientRemotingProcessor, rpcHook, scheduledExecutorService ); assertEquals("127.0.0.1:9876", System.getProperty("rocketmq.namesrv.addr")); } @Test public void testInitWithNamesrvDomain() { nameserverAccessConfig = new NameserverAccessConfig("", "test-domain", ""); mqClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "TestPrefix", 2, clientRemotingProcessor, rpcHook, scheduledExecutorService ); assertEquals("test-domain", System.getProperty("rocketmq.namesrv.domain")); } @Test public void testInitThrowsExceptionWhenBothEmpty() { nameserverAccessConfig = new NameserverAccessConfig("", "", ""); RuntimeException exception = assertThrows(RuntimeException.class, () -> new MQClientAPIFactory( nameserverAccessConfig, "TestPrefix", 2, clientRemotingProcessor, rpcHook, scheduledExecutorService )); assertEquals("The configuration item NamesrvAddr is not configured", exception.getMessage()); } @Test public void testStartCreatesClients() throws Exception { nameserverAccessConfig = new NameserverAccessConfig("127.0.0.1:9876", "", ""); mqClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "TestPrefix", 2, clientRemotingProcessor, rpcHook, scheduledExecutorService ); System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, "127.0.0.1:123"); mqClientAPIFactory.start(); // Assert MQClientAPIExt client = mqClientAPIFactory.getClient(); List nameServerAddressList = client.getNameServerAddressList(); assertEquals(1, nameServerAddressList.size()); assertEquals("127.0.0.1:9876", nameServerAddressList.get(0)); } @Test public void testOnNameServerAddressChangeUpdatesAllClients() throws Exception { nameserverAccessConfig = new NameserverAccessConfig("127.0.0.1:9876", "", ""); mqClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "TestPrefix", 2, clientRemotingProcessor, rpcHook, scheduledExecutorService ); mqClientAPIFactory.start(); // Act mqClientAPIFactory.onNameServerAddressChange("new-address0;new-address1"); MQClientAPIExt client = mqClientAPIFactory.getClient(); List nameServerAddressList = client.getNameServerAddressList(); assertEquals(2, nameServerAddressList.size()); assertTrue(nameServerAddressList.contains("new-address0")); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.latency; import org.awaitility.core.ThrowingRunnable; import org.junit.Before; import org.junit.Test; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; public class LatencyFaultToleranceImplTest { private LatencyFaultTolerance latencyFaultTolerance; private String brokerName = "BrokerA"; private String anotherBrokerName = "BrokerB"; @Before public void init() { latencyFaultTolerance = new LatencyFaultToleranceImpl(null, null); } @Test public void testUpdateFaultItem() throws Exception { latencyFaultTolerance.updateFaultItem(brokerName, 3000, 3000, true); assertThat(latencyFaultTolerance.isAvailable(brokerName)).isFalse(); assertThat(latencyFaultTolerance.isAvailable(anotherBrokerName)).isTrue(); } @Test public void testIsAvailable() throws Exception { latencyFaultTolerance.updateFaultItem(brokerName, 3000, 50, true); assertThat(latencyFaultTolerance.isAvailable(brokerName)).isFalse(); await().atMost(500, TimeUnit.MILLISECONDS).untilAsserted(new ThrowingRunnable() { @Override public void run() throws Throwable { assertThat(latencyFaultTolerance.isAvailable(brokerName)).isTrue(); } }); } @Test public void testRemove() throws Exception { latencyFaultTolerance.updateFaultItem(brokerName, 3000, 3000, true); assertThat(latencyFaultTolerance.isAvailable(brokerName)).isFalse(); latencyFaultTolerance.remove(brokerName); assertThat(latencyFaultTolerance.isAvailable(brokerName)).isTrue(); } @Test public void testPickOneAtLeast() throws Exception { latencyFaultTolerance.updateFaultItem(brokerName, 1000, 3000, true); assertThat(latencyFaultTolerance.pickOneAtLeast()).isEqualTo(brokerName); // Bad case, since pickOneAtLeast's behavior becomes random // latencyFaultTolerance.updateFaultItem(anotherBrokerName, 1001, 3000, "127.0.0.1:12011", true); // assertThat(latencyFaultTolerance.pickOneAtLeast()).isEqualTo(brokerName); } @Test public void testIsReachable() throws Exception { latencyFaultTolerance.updateFaultItem(brokerName, 1000, 3000, true); assertThat(latencyFaultTolerance.isReachable(brokerName)).isEqualTo(true); latencyFaultTolerance.updateFaultItem(anotherBrokerName, 1001, 3000, false); assertThat(latencyFaultTolerance.isReachable(anotherBrokerName)).isEqualTo(false); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.latency.MQFaultStrategy; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.compression.CompressionType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQProducerTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); @Mock private MQClientAPIImpl mQClientAPIImpl; private DefaultMQProducer producer; private Message message; private Message zeroMsg; private Message bigMessage; private final String topic = "FooBar"; private final String producerGroupPrefix = "FooBar_PID"; private final long defaultTimeout = 3000L; @Before public void init() throws Exception { String producerGroupTemp = producerGroupPrefix + System.currentTimeMillis(); producer = new DefaultMQProducer(producerGroupTemp); producer.setNamesrvAddr("127.0.0.1:9876"); producer.setCompressMsgBodyOverHowmuch(16); message = new Message(topic, new byte[] {'a'}); zeroMsg = new Message(topic, new byte[] {}); bigMessage = new Message(topic, "This is a very huge message!".getBytes()); producer.start(); Field field = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(producer.getDefaultMQProducerImpl(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))).thenCallRealMethod(); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendCallback.class), nullable(TopicPublishInfo.class), nullable(MQClientInstance.class), anyInt(), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))) .thenReturn(createSendResult(SendStatus.SEND_OK)); } @After public void terminate() { producer.shutdown(); } @Test public void testSendMessage_ZeroMessage() throws InterruptedException, RemotingException, MQBrokerException { try { producer.send(zeroMsg); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("message body length is zero"); } } @Test public void testSendMessage_NoNameSrv() throws RemotingException, InterruptedException, MQBrokerException { when(mQClientAPIImpl.getNameServerAddressList()).thenReturn(new ArrayList<>()); try { producer.send(message); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("No name server address"); } } @Test public void testSendMessage_NoRoute() throws RemotingException, InterruptedException, MQBrokerException { when(mQClientAPIImpl.getNameServerAddressList()).thenReturn(Collections.singletonList("127.0.0.1:9876")); try { producer.send(message); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("No route info of this topic"); } } @Test public void testSendMessageSync_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); SendResult sendResult = producer.send(message); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); } @Test public void testSendMessageSync_WithBodyCompressed() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); SendResult sendResult = producer.send(bigMessage); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); } @Test public void testSendMessageAsync_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.send(message, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); countDownLatch.countDown(); } @Override public void onException(Throwable e) { countDownLatch.countDown(); } }); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); } @Test public void testSendMessageAsync() throws RemotingException, MQClientException, InterruptedException { final AtomicInteger cc = new AtomicInteger(0); final CountDownLatch countDownLatch = new CountDownLatch(12); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); SendCallback sendCallback = new SendCallback() { @Override public void onSuccess(SendResult sendResult) { countDownLatch.countDown(); } @Override public void onException(Throwable e) { e.printStackTrace(); cc.incrementAndGet(); countDownLatch.countDown(); } }; MessageQueueSelector messageQueueSelector = new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { return null; } }; // on enableBackpressureForAsyncMode producer.setEnableBackpressureForAsyncMode(true); producer.setBackPressureForAsyncSendNum(5000); producer.setBackPressureForAsyncSendSize(50 * 1024 * 1024); Message message = new Message(); message.setTopic("test"); message.setBody("hello world".getBytes()); producer.send(new Message(), sendCallback); producer.send(message, new MessageQueue(), sendCallback); producer.send(new Message(), new MessageQueue(), sendCallback, 1000); producer.send(new Message(), messageQueueSelector, null, sendCallback); producer.send(message, messageQueueSelector, null, sendCallback, 1000); //this message is send success producer.send(message, sendCallback, 1000); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); assertThat(cc.get()).isEqualTo(5); // off enableBackpressureForAsyncMode producer.setEnableBackpressureForAsyncMode(false); producer.send(new Message(), sendCallback); producer.send(message, new MessageQueue(), sendCallback); producer.send(new Message(), new MessageQueue(), sendCallback, 1000); producer.send(new Message(), messageQueueSelector, null, sendCallback); producer.send(message, messageQueueSelector, null, sendCallback, 1000); //this message is send success producer.send(message, sendCallback, 1000); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); assertThat(cc.get()).isEqualTo(10); } @Test public void testBatchSendMessageAsync() throws RemotingException, MQClientException, InterruptedException, MQBrokerException { final AtomicInteger cc = new AtomicInteger(0); final CountDownLatch countDownLatch = new CountDownLatch(4); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); SendCallback sendCallback = new SendCallback() { @Override public void onSuccess(SendResult sendResult) { countDownLatch.countDown(); } @Override public void onException(Throwable e) { e.printStackTrace(); cc.incrementAndGet(); countDownLatch.countDown(); } }; MessageQueueSelector messageQueueSelector = new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { return null; } }; List msgs = new ArrayList<>(); for (int i = 0; i < 5; i++) { Message message = new Message(); message.setTopic("test"); message.setBody(("hello world" + i).getBytes()); msgs.add(message); } // on enableBackpressureForAsyncMode producer.setEnableBackpressureForAsyncMode(true); producer.send(msgs, sendCallback); producer.send(msgs, sendCallback, 1000); MessageQueue mq = new MessageQueue("test", "BrokerA", 1); producer.send(msgs, mq, sendCallback); // this message is send failed producer.send(msgs, new MessageQueue(), sendCallback, 1000); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); assertThat(cc.get()).isEqualTo(1); // off enableBackpressureForAsyncMode producer.setEnableBackpressureForAsyncMode(false); producer.send(msgs, sendCallback); producer.send(msgs, sendCallback, 1000); producer.send(msgs, mq, sendCallback); // this message is send failed producer.send(msgs, new MessageQueue(), sendCallback, 1000); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); assertThat(cc.get()).isEqualTo(2); } @Test public void testSendMessageAsync_BodyCompressed() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.send(bigMessage, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); countDownLatch.countDown(); } @Override public void onException(Throwable e) { } }); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); } @Test public void testSendMessageSync_SuccessWithHook() throws Throwable { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final Throwable[] assertionErrors = new Throwable[1]; final CountDownLatch countDownLatch = new CountDownLatch(2); producer.getDefaultMQProducerImpl().registerSendMessageHook(new SendMessageHook() { @Override public String hookName() { return "TestHook"; } @Override public void sendMessageBefore(final SendMessageContext context) { assertionErrors[0] = assertInOtherThread(new Runnable() { @Override public void run() { assertThat(context.getMessage()).isEqualTo(message); assertThat(context.getProducer()).isEqualTo(producer); assertThat(context.getCommunicationMode()).isEqualTo(CommunicationMode.SYNC); assertThat(context.getSendResult()).isNull(); } }); countDownLatch.countDown(); } @Override public void sendMessageAfter(final SendMessageContext context) { assertionErrors[0] = assertInOtherThread(new Runnable() { @Override public void run() { assertThat(context.getMessage()).isEqualTo(message); assertThat(context.getProducer()).isEqualTo(producer.getDefaultMQProducerImpl()); assertThat(context.getCommunicationMode()).isEqualTo(CommunicationMode.SYNC); assertThat(context.getSendResult()).isNotNull(); } }); countDownLatch.countDown(); } }); SendResult sendResult = producer.send(message); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); countDownLatch.await(); if (assertionErrors[0] != null) { throw assertionErrors[0]; } } @Test public void testSetCallbackExecutor() throws MQClientException { String producerGroupTemp = "testSetCallbackExecutor_" + System.currentTimeMillis(); producer = new DefaultMQProducer(producerGroupTemp); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); ExecutorService customized = Executors.newCachedThreadPool(); producer.setCallbackExecutor(customized); NettyRemotingClient remotingClient = (NettyRemotingClient) producer.getDefaultMQProducerImpl() .getMqClientFactory().getMQClientAPIImpl().getRemotingClient(); assertThat(remotingClient.getCallbackExecutor()).isEqualTo(customized); } @Test public void testRequestMessage() throws RemotingException, RequestTimeoutException, MQClientException, InterruptedException, MQBrokerException { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final AtomicBoolean finish = new AtomicBoolean(false); new Thread(new Runnable() { @Override public void run() { ConcurrentHashMap responseMap = RequestFutureHolder.getInstance().getRequestFutureTable(); assertThat(responseMap).isNotNull(); while (!finish.get()) { try { Thread.sleep(10); } catch (InterruptedException e) { } MessageExt responseMsg = new MessageExt(); responseMsg.setTopic(message.getTopic()); responseMsg.setBody(message.getBody()); for (Map.Entry entry : responseMap.entrySet()) { RequestResponseFuture future = entry.getValue(); future.putResponseMessage(responseMsg); } } } }).start(); Message result = producer.request(message, 3 * 1000L); finish.getAndSet(true); assertThat(result).isExactlyInstanceOf(MessageExt.class); assertThat(result.getTopic()).isEqualTo("FooBar"); assertThat(result.getBody()).isEqualTo(new byte[] {'a'}); } @Test(expected = RequestTimeoutException.class) public void testRequestMessage_RequestTimeoutException() throws RemotingException, RequestTimeoutException, MQClientException, InterruptedException, MQBrokerException { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); Message result = producer.request(message, 3 * 1000L); } @Test public void testAsyncRequest_OnSuccess() throws Exception { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final CountDownLatch countDownLatch = new CountDownLatch(1); RequestCallback requestCallback = new RequestCallback() { @Override public void onSuccess(Message message) { assertThat(message).isExactlyInstanceOf(MessageExt.class); assertThat(message.getTopic()).isEqualTo("FooBar"); assertThat(message.getBody()).isEqualTo(new byte[] {'a'}); assertThat(message.getFlag()).isEqualTo(1); countDownLatch.countDown(); } @Override public void onException(Throwable e) { } }; producer.request(message, requestCallback, 3 * 1000L); ConcurrentHashMap responseMap = RequestFutureHolder.getInstance().getRequestFutureTable(); assertThat(responseMap).isNotNull(); MessageExt responseMsg = new MessageExt(); responseMsg.setTopic(message.getTopic()); responseMsg.setBody(message.getBody()); responseMsg.setFlag(1); for (Map.Entry entry : responseMap.entrySet()) { RequestResponseFuture future = entry.getValue(); future.setSendRequestOk(true); future.getRequestCallback().onSuccess(responseMsg); } countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); } @Test public void testAsyncRequest_OnException() throws Exception { final AtomicInteger cc = new AtomicInteger(0); final CountDownLatch countDownLatch = new CountDownLatch(1); RequestCallback requestCallback = new RequestCallback() { @Override public void onSuccess(Message message) { } @Override public void onException(Throwable e) { cc.incrementAndGet(); countDownLatch.countDown(); } }; MessageQueueSelector messageQueueSelector = new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { return null; } }; try { producer.request(message, requestCallback, 3 * 1000L); failBecauseExceptionWasNotThrown(Exception.class); } catch (Exception e) { ConcurrentHashMap responseMap = RequestFutureHolder.getInstance().getRequestFutureTable(); assertThat(responseMap).isNotNull(); for (Map.Entry entry : responseMap.entrySet()) { RequestResponseFuture future = entry.getValue(); future.getRequestCallback().onException(e); } } countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); assertThat(cc.get()).isEqualTo(1); } @Test public void testBatchSendMessageAsync_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.setAutoBatch(true); producer.send(message, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); countDownLatch.countDown(); } @Override public void onException(Throwable e) { countDownLatch.countDown(); } }); countDownLatch.await(defaultTimeout, TimeUnit.MILLISECONDS); producer.setAutoBatch(false); } @Test public void testBatchSendMessageSync_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { producer.setAutoBatch(true); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); SendResult sendResult = producer.send(message); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getOffsetMsgId()).isEqualTo("123"); assertThat(sendResult.getQueueOffset()).isEqualTo(456L); producer.setAutoBatch(false); } @Test public void testRunningSetBackCompress() throws RemotingException, InterruptedException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(5); SendCallback sendCallback = new SendCallback() { @Override public void onSuccess(SendResult sendResult) { countDownLatch.countDown(); } @Override public void onException(Throwable e) { e.printStackTrace(); countDownLatch.countDown(); } }; // on enableBackpressureForAsyncMode producer.setEnableBackpressureForAsyncMode(true); producer.setBackPressureForAsyncSendNum(10); producer.setBackPressureForAsyncSendSize(50 * 1024 * 1024); Message message = new Message(); message.setTopic("test"); message.setBody("hello world".getBytes()); MessageQueue mq = new MessageQueue("test", "BrokerA", 1); //this message is send success for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { try { producer.send(message, mq, sendCallback); } catch (MQClientException | RemotingException | InterruptedException e) { throw new RuntimeException(e); } } }).start(); } producer.setBackPressureForAsyncSendNum(15); countDownLatch.await(3000L, TimeUnit.MILLISECONDS); assertThat(producer.defaultMQProducerImpl.getSemaphoreAsyncSendNumAvailablePermits() + countDownLatch.getCount()).isEqualTo(15); producer.setEnableBackpressureForAsyncMode(false); } public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId("123"); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); return sendResult; } private Throwable assertInOtherThread(final Runnable runnable) { final Throwable[] assertionErrors = new Throwable[1]; Thread thread = new Thread(new Runnable() { @Override public void run() { try { runnable.run(); } catch (AssertionError e) { assertionErrors[0] = e; } } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { assertionErrors[0] = e; } return assertionErrors[0]; } @Test public void assertCreateDefaultMQProducer() { String producerGroupTemp = producerGroupPrefix + System.currentTimeMillis(); DefaultMQProducer producer1 = new DefaultMQProducer(producerGroupTemp); assertNotNull(producer1); assertEquals(producerGroupTemp, producer1.getProducerGroup()); assertNotNull(producer1.getDefaultMQProducerImpl()); assertEquals(0, producer1.getTotalBatchMaxBytes()); assertEquals(0, producer1.getBatchMaxBytes()); assertEquals(0, producer1.getBatchMaxDelayMs()); assertNull(producer1.getTopics()); assertFalse(producer1.isEnableTrace()); assertTrue(UtilAll.isBlank(producer1.getTraceTopic())); DefaultMQProducer producer2 = new DefaultMQProducer(producerGroupTemp, mock(RPCHook.class)); assertNotNull(producer2); assertEquals(producerGroupTemp, producer2.getProducerGroup()); assertNotNull(producer2.getDefaultMQProducerImpl()); assertEquals(0, producer2.getTotalBatchMaxBytes()); assertEquals(0, producer2.getBatchMaxBytes()); assertEquals(0, producer2.getBatchMaxDelayMs()); assertNull(producer2.getTopics()); assertFalse(producer2.isEnableTrace()); assertTrue(UtilAll.isBlank(producer2.getTraceTopic())); DefaultMQProducer producer3 = new DefaultMQProducer(producerGroupTemp, mock(RPCHook.class), Collections.singletonList("custom_topic")); assertNotNull(producer3); assertEquals(producerGroupTemp, producer3.getProducerGroup()); assertNotNull(producer3.getDefaultMQProducerImpl()); assertEquals(0, producer3.getTotalBatchMaxBytes()); assertEquals(0, producer3.getBatchMaxBytes()); assertEquals(0, producer3.getBatchMaxDelayMs()); assertNotNull(producer3.getTopics()); assertEquals(1, producer3.getTopics().size()); assertFalse(producer3.isEnableTrace()); assertTrue(UtilAll.isBlank(producer3.getTraceTopic())); DefaultMQProducer producer4 = new DefaultMQProducer(producerGroupTemp, mock(RPCHook.class), true, "custom_trace_topic"); assertNotNull(producer4); assertEquals(producerGroupTemp, producer4.getProducerGroup()); assertNotNull(producer4.getDefaultMQProducerImpl()); assertEquals(0, producer4.getTotalBatchMaxBytes()); assertEquals(0, producer4.getBatchMaxBytes()); assertEquals(0, producer4.getBatchMaxDelayMs()); assertNull(producer4.getTopics()); assertTrue(producer4.isEnableTrace()); assertEquals("custom_trace_topic", producer4.getTraceTopic()); DefaultMQProducer producer5 = new DefaultMQProducer(producerGroupTemp, mock(RPCHook.class), Collections.singletonList("custom_topic"), true, "custom_trace_topic"); assertNotNull(producer5); assertEquals(producerGroupTemp, producer5.getProducerGroup()); assertNotNull(producer5.getDefaultMQProducerImpl()); assertEquals(0, producer5.getTotalBatchMaxBytes()); assertEquals(0, producer5.getBatchMaxBytes()); assertEquals(0, producer5.getBatchMaxDelayMs()); assertNotNull(producer5.getTopics()); assertEquals(1, producer5.getTopics().size()); assertTrue(producer5.isEnableTrace()); assertEquals("custom_trace_topic", producer5.getTraceTopic()); } @Test public void assertSend() throws MQBrokerException, RemotingException, InterruptedException, MQClientException, NoSuchFieldException, IllegalAccessException { setDefaultMQProducerImpl(); setOtherParam(); SendResult send = producer.send(message, defaultTimeout); assertNull(send); Collection msgs = Collections.singletonList(message); send = producer.send(msgs); assertNull(send); send = producer.send(msgs, defaultTimeout); assertNull(send); } @Test public void assertSendOneway() throws RemotingException, InterruptedException, MQClientException, NoSuchFieldException, IllegalAccessException { setDefaultMQProducerImpl(); producer.sendOneway(message); MessageQueue mq = mock(MessageQueue.class); producer.sendOneway(message, mq); MessageQueueSelector selector = mock(MessageQueueSelector.class); producer.sendOneway(message, selector, 1); } @Test public void assertSendByQueue() throws MQBrokerException, RemotingException, InterruptedException, MQClientException, NoSuchFieldException, IllegalAccessException { setDefaultMQProducerImpl(); MessageQueue mq = mock(MessageQueue.class); SendResult send = producer.send(message, mq); assertNull(send); send = producer.send(message, mq, defaultTimeout); assertNull(send); Collection msgs = Collections.singletonList(message); send = producer.send(msgs, mq); assertNull(send); send = producer.send(msgs, mq, defaultTimeout); assertNull(send); } @Test public void assertSendByQueueSelector() throws MQBrokerException, RemotingException, InterruptedException, MQClientException, NoSuchFieldException, IllegalAccessException { setDefaultMQProducerImpl(); MessageQueueSelector selector = mock(MessageQueueSelector.class); SendResult send = producer.send(message, selector, 1); assertNull(send); send = producer.send(message, selector, 1, defaultTimeout); assertNull(send); } @Test public void assertRequest() throws MQBrokerException, RemotingException, InterruptedException, MQClientException, NoSuchFieldException, IllegalAccessException, RequestTimeoutException { setDefaultMQProducerImpl(); MessageQueueSelector selector = mock(MessageQueueSelector.class); Message replyNsg = producer.request(message, selector, 1, defaultTimeout); assertNull(replyNsg); RequestCallback requestCallback = mock(RequestCallback.class); producer.request(message, selector, 1, requestCallback, defaultTimeout); MessageQueue mq = mock(MessageQueue.class); producer.request(message, mq, defaultTimeout); producer.request(message, mq, requestCallback, defaultTimeout); } @Test(expected = RuntimeException.class) public void assertSendMessageInTransaction() throws MQClientException { TransactionSendResult result = producer.sendMessageInTransaction(message, 1); assertNull(result); } @Test public void assertSearchOffset() throws MQClientException, NoSuchFieldException, IllegalAccessException { setDefaultMQProducerImpl(); MessageQueue mq = mock(MessageQueue.class); long result = producer.searchOffset(mq, System.currentTimeMillis()); assertEquals(0L, result); } @Test public void assertBatchMaxDelayMs() throws NoSuchFieldException, IllegalAccessException { setProduceAccumulator(true); assertEquals(0, producer.getBatchMaxDelayMs()); setProduceAccumulator(false); assertEquals(10, producer.getBatchMaxDelayMs()); producer.batchMaxDelayMs(1000); assertEquals(1000, producer.getBatchMaxDelayMs()); } @Test public void assertBatchMaxBytes() throws NoSuchFieldException, IllegalAccessException { setProduceAccumulator(true); assertEquals(0L, producer.getBatchMaxBytes()); setProduceAccumulator(false); assertEquals(32 * 1024L, producer.getBatchMaxBytes()); producer.batchMaxBytes(64 * 1024L); assertEquals(64 * 1024L, producer.getBatchMaxBytes()); } @Test public void assertTotalBatchMaxBytes() throws NoSuchFieldException, IllegalAccessException { setProduceAccumulator(true); assertEquals(0L, producer.getTotalBatchMaxBytes()); } @Test public void assertProduceAccumulatorStart() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer = new DefaultMQProducer(producerGroupTemp); assertEquals(0, producer.getTotalBatchMaxBytes()); assertEquals(0, producer.getBatchMaxBytes()); assertEquals(0, producer.getBatchMaxDelayMs()); assertNull(getField(producer, "produceAccumulator", ProduceAccumulator.class)); producer.start(); assertTrue(producer.getTotalBatchMaxBytes() > 0); assertTrue(producer.getBatchMaxBytes() > 0); assertTrue(producer.getBatchMaxDelayMs() > 0); assertNotNull(getField(producer, "produceAccumulator", ProduceAccumulator.class)); } @Test public void assertProduceAccumulatorBeforeStartSet() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer = new DefaultMQProducer(producerGroupTemp); producer.totalBatchMaxBytes(64 * 1024 * 100); producer.batchMaxBytes(64 * 1024); producer.batchMaxDelayMs(10); producer.start(); assertEquals(64 * 1024, producer.getBatchMaxBytes()); assertEquals(10, producer.getBatchMaxDelayMs()); assertNotNull(getField(producer, "produceAccumulator", ProduceAccumulator.class)); } @Test public void assertProduceAccumulatorAfterStartSet() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer = new DefaultMQProducer(producerGroupTemp); producer.start(); assertNotNull(getField(producer, "produceAccumulator", ProduceAccumulator.class)); producer.totalBatchMaxBytes(64 * 1024 * 100); producer.batchMaxBytes(64 * 1024); producer.batchMaxDelayMs(10); assertEquals(64 * 1024, producer.getBatchMaxBytes()); assertEquals(10, producer.getBatchMaxDelayMs()); } @Test public void assertProduceAccumulatorUnit() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer1 = new DefaultMQProducer(producerGroupTemp); producer1.setUnitName("unit1"); DefaultMQProducer producer2 = new DefaultMQProducer(producerGroupTemp); producer2.setUnitName("unit2"); producer1.start(); producer2.start(); ProduceAccumulator producer1Accumulator = getField(producer1, "produceAccumulator", ProduceAccumulator.class); ProduceAccumulator producer2Accumulator = getField(producer2, "produceAccumulator", ProduceAccumulator.class); assertNotNull(producer1Accumulator); assertNotNull(producer2Accumulator); assertNotEquals(producer1Accumulator, producer2Accumulator); } @Test public void assertProduceAccumulator() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp1 = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer1 = new DefaultMQProducer(producerGroupTemp1); producer1.setInstanceName("instanceName1"); String producerGroupTemp2 = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer2 = new DefaultMQProducer(producerGroupTemp2); producer2.setInstanceName("instanceName2"); producer1.start(); producer2.start(); ProduceAccumulator producer1Accumulator = getField(producer1, "produceAccumulator", ProduceAccumulator.class); ProduceAccumulator producer2Accumulator = getField(producer2, "produceAccumulator", ProduceAccumulator.class); assertNotNull(producer1Accumulator); assertNotNull(producer2Accumulator); assertNotEquals(producer1Accumulator, producer2Accumulator); } @Test public void assertProduceAccumulatorInstanceEqual() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp1 = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer1 = new DefaultMQProducer(producerGroupTemp1); producer1.setInstanceName("equalInstance"); String producerGroupTemp2 = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer2 = new DefaultMQProducer(producerGroupTemp2); producer2.setInstanceName("equalInstance"); producer1.start(); producer2.start(); ProduceAccumulator producer1Accumulator = getField(producer1, "produceAccumulator", ProduceAccumulator.class); ProduceAccumulator producer2Accumulator = getField(producer2, "produceAccumulator", ProduceAccumulator.class); assertNotNull(producer1Accumulator); assertNotNull(producer2Accumulator); assertEquals(producer1Accumulator, producer2Accumulator); } @Test public void assertProduceAccumulatorInstanceAndUnitNameEqual() throws NoSuchFieldException, IllegalAccessException, MQClientException { String producerGroupTemp1 = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer1 = new DefaultMQProducer(producerGroupTemp1); producer1.setInstanceName("equalInstance"); producer1.setUnitName("equalUnitName"); String producerGroupTemp2 = producerGroupPrefix + System.nanoTime(); DefaultMQProducer producer2 = new DefaultMQProducer(producerGroupTemp2); producer2.setInstanceName("equalInstance"); producer2.setUnitName("equalUnitName"); producer1.start(); producer2.start(); ProduceAccumulator producer1Accumulator = getField(producer1, "produceAccumulator", ProduceAccumulator.class); ProduceAccumulator producer2Accumulator = getField(producer2, "produceAccumulator", ProduceAccumulator.class); assertNotNull(producer1Accumulator); assertNotNull(producer2Accumulator); assertEquals(producer1Accumulator, producer2Accumulator); } @Test public void assertGetRetryResponseCodes() { assertNotNull(producer.getRetryResponseCodes()); assertEquals(8, producer.getRetryResponseCodes().size()); } @Test public void assertIsSendLatencyFaultEnable() { assertFalse(producer.isSendLatencyFaultEnable()); } @Test public void assertGetLatencyMax() { assertNotNull(producer.getLatencyMax()); } @Test public void assertGetNotAvailableDuration() { assertNotNull(producer.getNotAvailableDuration()); } @Test public void assertIsRetryAnotherBrokerWhenNotStoreOK() { assertFalse(producer.isRetryAnotherBrokerWhenNotStoreOK()); } private void setOtherParam() { producer.setCreateTopicKey("createTopicKey"); producer.setRetryAnotherBrokerWhenNotStoreOK(false); producer.setDefaultTopicQueueNums(6); producer.setRetryTimesWhenSendFailed(1); producer.setSendMessageWithVIPChannel(false); producer.setNotAvailableDuration(new long[1]); producer.setLatencyMax(new long[1]); producer.setSendLatencyFaultEnable(false); producer.setRetryTimesWhenSendAsyncFailed(1); producer.setTopics(Collections.singletonList(topic)); producer.setStartDetectorEnable(false); producer.setCompressLevel(5); producer.setCompressType(CompressionType.LZ4); producer.addRetryResponseCode(0); ExecutorService executorService = mock(ExecutorService.class); producer.setAsyncSenderExecutor(executorService); } private void setProduceAccumulator(final boolean isDefault) throws NoSuchFieldException, IllegalAccessException { ProduceAccumulator accumulator = null; if (!isDefault) { accumulator = new ProduceAccumulator("instanceName"); } setField(producer, "produceAccumulator", accumulator); } private void setDefaultMQProducerImpl() throws NoSuchFieldException, IllegalAccessException { DefaultMQProducerImpl producerImpl = mock(DefaultMQProducerImpl.class); setField(producer, "defaultMQProducerImpl", producerImpl); when(producerImpl.getMqFaultStrategy()).thenReturn(mock(MQFaultStrategy.class)); } private void setField(final Object target, final String fieldName, final Object newValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = target.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(target, newValue); } private T getField(final Object target, final String fieldName, final Class fieldClassType) throws NoSuchFieldException, IllegalAccessException { Class targetClazz = target.getClass(); Field field = targetClazz.getDeclaredField(fieldName); field.setAccessible(true); return fieldClassType.cast(field.get(target)); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/ProduceAccumulatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ProduceAccumulatorTest { private boolean compareMessageBatch(MessageBatch a, MessageBatch b) { if (!a.getTopic().equals(b.getTopic())) { return false; } if (!Arrays.equals(a.getBody(), b.getBody())) { return false; } return true; } private class MockMQProducer extends DefaultMQProducer { private Message beSendMessage = null; private MessageQueue beSendMessageQueue = null; @Override public SendResult sendDirect(Message msg, MessageQueue mq, SendCallback sendCallback) { this.beSendMessage = msg; this.beSendMessageQueue = mq; SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); if (sendCallback != null) { sendCallback.onSuccess(sendResult); } return sendResult; } } @Test public void testProduceAccumulator_async() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { MockMQProducer mockMQProducer = new MockMQProducer(); ProduceAccumulator produceAccumulator = new ProduceAccumulator("test"); produceAccumulator.start(); final CountDownLatch countDownLatch = new CountDownLatch(1); List messages = new ArrayList(); messages.add(new Message("testTopic", "1".getBytes())); messages.add(new Message("testTopic", "22".getBytes())); messages.add(new Message("testTopic", "333".getBytes())); messages.add(new Message("testTopic", "4444".getBytes())); messages.add(new Message("testTopic", "55555".getBytes())); for (Message message : messages) { produceAccumulator.send(message, new SendCallback() { final CountDownLatch finalCountDownLatch = countDownLatch; @Override public void onSuccess(SendResult sendResult) { finalCountDownLatch.countDown(); } @Override public void onException(Throwable e) { finalCountDownLatch.countDown(); } }, mockMQProducer); } assertThat(countDownLatch.await(3000L, TimeUnit.MILLISECONDS)).isTrue(); assertThat(mockMQProducer.beSendMessage instanceof MessageBatch).isTrue(); MessageBatch messageBatch1 = (MessageBatch) mockMQProducer.beSendMessage; MessageBatch messageBatch2 = MessageBatch.generateFromList(messages); messageBatch2.setBody(messageBatch2.encode()); assertThat(compareMessageBatch(messageBatch1, messageBatch2)).isTrue(); } @Test public void testProduceAccumulator_sync() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { final MockMQProducer mockMQProducer = new MockMQProducer(); final ProduceAccumulator produceAccumulator = new ProduceAccumulator("test"); produceAccumulator.batchMaxDelayMs(3000); produceAccumulator.start(); List messages = new ArrayList(); messages.add(new Message("testTopic", "1".getBytes())); messages.add(new Message("testTopic", "22".getBytes())); messages.add(new Message("testTopic", "333".getBytes())); messages.add(new Message("testTopic", "4444".getBytes())); messages.add(new Message("testTopic", "55555".getBytes())); final CountDownLatch countDownLatch = new CountDownLatch(messages.size()); for (final Message message : messages) { new Thread(new Runnable() { final ProduceAccumulator finalProduceAccumulator = produceAccumulator; final CountDownLatch finalCountDownLatch = countDownLatch; final MockMQProducer finalMockMQProducer = mockMQProducer; final Message finalMessage = message; @Override public void run() { try { finalProduceAccumulator.send(finalMessage, finalMockMQProducer); finalCountDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }).start(); } assertThat(countDownLatch.await(5000L, TimeUnit.MILLISECONDS)).isTrue(); assertThat(mockMQProducer.beSendMessage instanceof MessageBatch).isTrue(); MessageBatch messageBatch1 = (MessageBatch) mockMQProducer.beSendMessage; MessageBatch messageBatch2 = MessageBatch.generateFromList(messages); messageBatch2.setBody(messageBatch2.encode()); assertThat(messageBatch1.getTopic()).isEqualTo(messageBatch2.getTopic()); // The execution order is uncertain, just compare the length assertThat(messageBatch1.getBody().length).isEqualTo(messageBatch2.getBody().length); } @Test public void testProduceAccumulator_sendWithMessageQueue() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { MockMQProducer mockMQProducer = new MockMQProducer(); MessageQueue messageQueue = new MessageQueue("topicTest", "brokerTest", 0); final ProduceAccumulator produceAccumulator = new ProduceAccumulator("test"); produceAccumulator.start(); Message message = new Message("testTopic", "1".getBytes()); produceAccumulator.send(message, messageQueue, mockMQProducer); assertThat(mockMQProducer.beSendMessageQueue).isEqualTo(messageQueue); final CountDownLatch countDownLatch = new CountDownLatch(1); produceAccumulator.send(message, messageQueue, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { countDownLatch.countDown(); } @Override public void onException(Throwable e) { countDownLatch.countDown(); } }, mockMQProducer); assertThat(countDownLatch.await(3000L, TimeUnit.MILLISECONDS)).isTrue(); assertThat(mockMQProducer.beSendMessageQueue).isEqualTo(messageQueue); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.message.Message; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class RequestResponseFutureTest { @Test public void testExecuteRequestCallback() throws Exception { final AtomicInteger cc = new AtomicInteger(0); RequestResponseFuture future = new RequestResponseFuture(UUID.randomUUID().toString(), 3 * 1000L, new RequestCallback() { @Override public void onSuccess(Message message) { cc.incrementAndGet(); } @Override public void onException(Throwable e) { } }); future.setSendRequestOk(true); future.executeRequestCallback(); assertThat(cc.get()).isEqualTo(1); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/SendResultTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import static org.junit.Assert.assertEquals; public class SendResultTest { @Test public void testEncoderSendResultToJson() { SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.SEND_OK); sendResult.setMsgId("12345"); sendResult.setQueueOffset(100L); MessageQueue messageQueue = new MessageQueue("TestTopic", "BrokerA", 1); sendResult.setMessageQueue(messageQueue); String json = SendResult.encoderSendResultToJson(sendResult); SendResult decodedResult = JSON.parseObject(json, SendResult.class); assertEquals(sendResult.getSendStatus(), decodedResult.getSendStatus()); assertEquals(sendResult.getMsgId(), decodedResult.getMsgId()); assertEquals(sendResult.getQueueOffset(), decodedResult.getQueueOffset()); assertEquals(sendResult.getMessageQueue(), decodedResult.getMessageQueue()); } @Test public void testDecoderSendResultFromJson() { String json = "{\"sendStatus\":\"SEND_OK\",\"msgId\":\"12345\",\"queueOffset\":100,\"messageQueue\":{\"topic\":\"TestTopic\",\"brokerName\":\"BrokerA\",\"queueId\":1}}"; SendResult sendResult = SendResult.decoderSendResultFromJson(json); assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); assertEquals("12345", sendResult.getMsgId()); assertEquals(100L, sendResult.getQueueOffset()); assertEquals("TestTopic", sendResult.getMessageQueue().getTopic()); assertEquals("BrokerA", sendResult.getMessageQueue().getBrokerName()); assertEquals(1, sendResult.getMessageQueue().getQueueId()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/selector/DefaultMQProducerImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.client.hook.CheckForbiddenContext; import org.apache.rocketmq.client.hook.CheckForbiddenHook; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.RequestCallback; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThrows; import static org.mockito.AdditionalMatchers.or; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQProducerImplTest { @Mock private Message message; @Mock private MessageQueue messageQueue; @Mock private MessageQueueSelector queueSelector; @Mock private RequestCallback requestCallback; @Mock private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mQClientAPIImpl; private DefaultMQProducerImpl defaultMQProducerImpl; private final long defaultTimeout = 30000L; private final String defaultBrokerName = "broker-0"; private final String defaultBrokerAddr = "127.0.0.1:10911"; private final String defaultTopic = "testTopic"; @Before public void init() throws Exception { when(mQClientFactory.getTopicRouteTable()).thenReturn(mock(ConcurrentMap.class)); when(mQClientFactory.getClientId()).thenReturn("client-id"); when(mQClientFactory.getMQAdminImpl()).thenReturn(mock(MQAdminImpl.class)); ClientConfig clientConfig = mock(ClientConfig.class); when(messageQueue.getTopic()).thenReturn(defaultTopic); when(clientConfig.queueWithNamespace(any())).thenReturn(messageQueue); when(mQClientFactory.getClientConfig()).thenReturn(clientConfig); when(mQClientFactory.getTopicRouteTable()).thenReturn(mock(ConcurrentMap.class)); when(mQClientFactory.getMQClientAPIImpl()).thenReturn(mQClientAPIImpl); when(mQClientFactory.findBrokerAddressInPublish(or(isNull(), anyString()))).thenReturn(defaultBrokerAddr); when(message.getTopic()).thenReturn(defaultTopic); when(message.getProperty(MessageConst.PROPERTY_CORRELATION_ID)).thenReturn("correlation-id"); when(message.getBody()).thenReturn(new byte[1]); TransactionMQProducer producer = new TransactionMQProducer("test-producer-group"); producer.setTransactionListener(mock(TransactionListener.class)); producer.setTopics(Collections.singletonList(defaultTopic)); defaultMQProducerImpl = new DefaultMQProducerImpl(producer); setMQClientFactory(); setCheckExecutor(); setCheckForbiddenHookList(); setTopicPublishInfoTable(); defaultMQProducerImpl.setServiceState(ServiceState.RUNNING); } @Test public void testRequest() throws Exception { defaultMQProducerImpl.request(message, messageQueue, requestCallback, defaultTimeout); defaultMQProducerImpl.request(message, queueSelector, 1, requestCallback, defaultTimeout); } @Test(expected = MQClientException.class) public void testRequestMQClientExceptionByVoid() throws Exception { defaultMQProducerImpl.request(message, requestCallback, defaultTimeout); } @Test public void testCheckTransactionState() { defaultMQProducerImpl.checkTransactionState(defaultBrokerAddr, mock(MessageExt.class), mock(CheckTransactionStateRequestHeader.class)); } @Test public void testCreateTopic() throws MQClientException { defaultMQProducerImpl.createTopic("key", defaultTopic, 0); } @Test public void testExecuteCheckForbiddenHook() throws MQClientException { defaultMQProducerImpl.executeCheckForbiddenHook(mock(CheckForbiddenContext.class)); } @Test(expected = MQClientException.class) public void testSendOneway() throws MQClientException, InterruptedException, RemotingException { defaultMQProducerImpl.sendOneway(message); } @Test public void testSendOnewayByQueueSelector() throws MQClientException, InterruptedException, RemotingException { defaultMQProducerImpl.sendOneway(message, mock(MessageQueueSelector.class), 1); } @Test public void testSendOnewayByQueue() throws MQClientException, InterruptedException, RemotingException { defaultMQProducerImpl.sendOneway(message, messageQueue); } @Test(expected = MQClientException.class) public void testSend() throws RemotingException, InterruptedException, MQClientException, MQBrokerException { assertNull(defaultMQProducerImpl.send(message)); } @Test public void assertSendByQueue() throws InterruptedException, MQBrokerException, RemotingException, MQClientException { SendResult actual = defaultMQProducerImpl.send(message, messageQueue); assertNull(actual); actual = defaultMQProducerImpl.send(message, messageQueue, defaultTimeout); assertNull(actual); } @Test public void assertSendByQueueSelector() throws InterruptedException, MQBrokerException, RemotingException, MQClientException { SendCallback sendCallback = mock(SendCallback.class); defaultMQProducerImpl.send(message, queueSelector, 1, sendCallback); SendResult actual = defaultMQProducerImpl.send(message, queueSelector, 1); assertNull(actual); actual = defaultMQProducerImpl.send(message, queueSelector, 1, defaultTimeout); assertNull(actual); } @Test(expected = MQClientException.class) public void assertMQClientException() throws Exception { assertNull(defaultMQProducerImpl.request(message, defaultTimeout)); } @Test(expected = RequestTimeoutException.class) public void assertRequestRequestTimeoutByQueueSelector() throws Exception { assertNull(defaultMQProducerImpl.request(message, queueSelector, 1, 3000L)); } @Test(expected = Exception.class) public void assertRequestTimeoutExceptionByQueue() throws Exception { assertNull(defaultMQProducerImpl.request(message, messageQueue, 3000L)); } @Test public void testRegisterCheckForbiddenHook() { CheckForbiddenHook checkForbiddenHook = mock(CheckForbiddenHook.class); defaultMQProducerImpl.registerCheckForbiddenHook(checkForbiddenHook); } @Test public void testInitTopicRoute() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class clazz = defaultMQProducerImpl.getClass(); Method method = clazz.getDeclaredMethod("initTopicRoute"); method.setAccessible(true); method.invoke(defaultMQProducerImpl); } @Test public void assertFetchPublishMessageQueues() throws MQClientException { List actual = defaultMQProducerImpl.fetchPublishMessageQueues(defaultTopic); assertNotNull(actual); assertEquals(0, actual.size()); } @Test public void assertSearchOffset() throws MQClientException { assertEquals(0, defaultMQProducerImpl.searchOffset(messageQueue, System.currentTimeMillis())); } @Test public void assertMaxOffset() throws MQClientException { assertEquals(0, defaultMQProducerImpl.maxOffset(messageQueue)); } @Test public void assertMinOffset() throws MQClientException { assertEquals(0, defaultMQProducerImpl.minOffset(messageQueue)); } @Test public void assertEarliestMsgStoreTime() throws MQClientException { assertEquals(0, defaultMQProducerImpl.earliestMsgStoreTime(messageQueue)); } @Test public void assertViewMessage() throws MQClientException, MQBrokerException, RemotingException, InterruptedException { assertNull(defaultMQProducerImpl.viewMessage(defaultTopic, "msgId")); } @Test public void assertQueryMessage() throws MQClientException, InterruptedException { assertNull(defaultMQProducerImpl.queryMessage(defaultTopic, "key", 1, 0L, 10L)); } @Test public void assertQueryMessageByUniqKey() throws MQClientException, InterruptedException { assertNull(defaultMQProducerImpl.queryMessageByUniqKey(defaultTopic, "key")); } @Test public void assertSetAsyncSenderExecutor() { ExecutorService asyncSenderExecutor = mock(ExecutorService.class); defaultMQProducerImpl.setAsyncSenderExecutor(asyncSenderExecutor); assertEquals(asyncSenderExecutor, defaultMQProducerImpl.getAsyncSenderExecutor()); } @Test public void assertServiceState() { ServiceState serviceState = defaultMQProducerImpl.getServiceState(); assertNotNull(serviceState); assertEquals(ServiceState.RUNNING, serviceState); defaultMQProducerImpl.setServiceState(ServiceState.SHUTDOWN_ALREADY); serviceState = defaultMQProducerImpl.getServiceState(); assertNotNull(serviceState); assertEquals(ServiceState.SHUTDOWN_ALREADY, serviceState); } @Test public void assertGetNotAvailableDuration() { long[] notAvailableDuration = defaultMQProducerImpl.getNotAvailableDuration(); assertNotNull(notAvailableDuration); defaultMQProducerImpl.setNotAvailableDuration(new long[1]); notAvailableDuration = defaultMQProducerImpl.getNotAvailableDuration(); assertNotNull(notAvailableDuration); assertEquals(1, notAvailableDuration.length); } @Test public void assertGetLatencyMax() { long[] actual = defaultMQProducerImpl.getLatencyMax(); assertNotNull(actual); defaultMQProducerImpl.setLatencyMax(new long[1]); actual = defaultMQProducerImpl.getLatencyMax(); assertNotNull(actual); assertEquals(1, actual.length); } @Test public void assertIsSendLatencyFaultEnable() { boolean actual = defaultMQProducerImpl.isSendLatencyFaultEnable(); assertFalse(actual); defaultMQProducerImpl.setSendLatencyFaultEnable(true); actual = defaultMQProducerImpl.isSendLatencyFaultEnable(); assertTrue(actual); } @Test public void assertGetMqFaultStrategy() { assertNotNull(defaultMQProducerImpl.getMqFaultStrategy()); } @Test public void assertCheckListener() { assertNull(defaultMQProducerImpl.checkListener()); } @Test public void testRecallMessage_invalid() { assertThrows(MQClientException.class, () -> { defaultMQProducerImpl.recallMessage(MixAll.REPLY_TOPIC_POSTFIX + defaultTopic, "handle"); }); assertThrows(MQClientException.class, () -> { defaultMQProducerImpl.recallMessage(MixAll.DLQ_GROUP_TOPIC_PREFIX + defaultTopic, "handle"); }); assertThrows(MQClientException.class, () -> { defaultMQProducerImpl.recallMessage(defaultTopic, "handle"); }); } @Test public void testRecallMessage_addressNotFound() { String handle = RecallMessageHandle.HandleV1.buildHandle(defaultTopic, defaultBrokerName, "1", "id"); when(mQClientFactory.findBrokerAddressInPublish(defaultBrokerName)).thenReturn(null); MQClientException e = assertThrows(MQClientException.class, () -> { defaultMQProducerImpl.recallMessage(defaultTopic, handle); }); assertEquals("The broker service address not found", e.getErrorMessage()); } @Test public void testRecallMessage_success() throws RemotingException, MQClientException, MQBrokerException, InterruptedException { String handle = RecallMessageHandle.HandleV1.buildHandle(defaultTopic, defaultBrokerName, "1", "id"); when(mQClientFactory.findBrokerAddressInPublish(defaultBrokerName)).thenReturn(defaultBrokerAddr); when(mQClientAPIImpl.recallMessage(any(), any(), anyLong())).thenReturn("id"); String result = defaultMQProducerImpl.recallMessage(defaultTopic, handle); assertEquals("id", result); } private void setMQClientFactory() throws IllegalAccessException, NoSuchFieldException { setField(defaultMQProducerImpl, "mQClientFactory", mQClientFactory); } private void setTopicPublishInfoTable() throws IllegalAccessException, NoSuchFieldException { ConcurrentMap topicPublishInfoTable = new ConcurrentHashMap<>(); TopicPublishInfo topicPublishInfo = mock(TopicPublishInfo.class); when(topicPublishInfo.ok()).thenReturn(true); topicPublishInfoTable.put(defaultTopic, topicPublishInfo); setField(defaultMQProducerImpl, "topicPublishInfoTable", topicPublishInfoTable); } private void setCheckExecutor() throws NoSuchFieldException, IllegalAccessException { setField(defaultMQProducerImpl, "checkExecutor", mock(ExecutorService.class)); } private void setCheckForbiddenHookList() throws NoSuchFieldException, IllegalAccessException { ArrayList checkForbiddenHookList = new ArrayList<>(); checkForbiddenHookList.add(mock(CheckForbiddenHook.class)); setField(defaultMQProducerImpl, "checkForbiddenHookList", checkForbiddenHookList); } private void setField(final Object target, final String fieldName, final Object newValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = target.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(target, newValue); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByHashTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SelectMessageQueueByHashTest { private String topic = "FooBar"; @Test public void testSelect() throws Exception { SelectMessageQueueByHash selector = new SelectMessageQueueByHash(); Message message = new Message(topic, new byte[] {}); List messageQueues = new ArrayList<>(); for (int i = 0; i < 10; i++) { MessageQueue messageQueue = new MessageQueue(topic, "DefaultBroker", i); messageQueues.add(messageQueue); } String orderId = "123"; String anotherOrderId = "234"; MessageQueue selected = selector.select(messageQueues, message, orderId); assertThat(selector.select(messageQueues, message, anotherOrderId)).isNotEqualTo(selected); //No exception is thrown while order Id hashcode is Integer.MIN anotherOrderId = "polygenelubricants"; selector.select(messageQueues, message, anotherOrderId); anotherOrderId = "GydZG_"; selector.select(messageQueues, message, anotherOrderId); anotherOrderId = "DESIGNING WORKHOUSES"; selector.select(messageQueues, message, anotherOrderId); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByRandomTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; public class SelectMessageQueueByRandomTest { private final SelectMessageQueueByRandom selector = new SelectMessageQueueByRandom(); private final String defaultBroker = "defaultBroker"; private final String defaultTopic = "defaultTopic"; @Test public void testSelectRandomMessageQueue() { List messageQueues = createMessageQueues(10); Message message = new Message(defaultTopic, "tag", "key", "body".getBytes()); MessageQueue selectedQueue = selector.select(messageQueues, message, null); assertNotNull(selectedQueue); assertEquals(messageQueues.size(), 10); assertEquals(defaultTopic, selectedQueue.getTopic()); assertEquals(defaultBroker, selectedQueue.getBrokerName()); } @Test public void testSelectEmptyMessageQueue() { List emptyQueues = new ArrayList<>(); Message message = new Message(defaultTopic, "tag", "key", "body".getBytes()); assertThrows(IllegalArgumentException.class, () -> selector.select(emptyQueues, message, null)); } @Test public void testSelectSingleMessageQueue() { List singleQueueList = createMessageQueues(1); Message message = new Message(defaultTopic, "tag", "key", "body".getBytes()); MessageQueue selectedQueue = selector.select(singleQueueList, message, null); assertNotNull(selectedQueue); assertEquals(defaultTopic, selectedQueue.getTopic()); assertEquals(defaultBroker, selectedQueue.getBrokerName()); assertEquals(singleQueueList.get(0).getQueueId(), selectedQueue.getQueueId()); } private List createMessageQueues(final int count) { List result = new ArrayList<>(); for (int i = 0; i < count; i++) { result.add(new MessageQueue(defaultTopic, defaultBroker, i)); } return result; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueRetryTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.producer.selector; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; public class SelectMessageQueueRetryTest { private String topic = "TEST"; @Test public void testSelect() throws Exception { TopicPublishInfo topicPublishInfo = new TopicPublishInfo(); List messageQueueList = new ArrayList(); for (int i = 0; i < 3; i++) { MessageQueue mq = new MessageQueue(); mq.setBrokerName("broker-" + i); mq.setQueueId(0); mq.setTopic(topic); messageQueueList.add(mq); } topicPublishInfo.setMessageQueueList(messageQueueList); Set retryBrokerNameSet = retryBroker(topicPublishInfo); //always in Set (broker-0, broker-1, broker-2) assertThat(retryBroker(topicPublishInfo)).isEqualTo(retryBrokerNameSet); } private Set retryBroker(TopicPublishInfo topicPublishInfo) { MessageQueue mqTmp = null; Set retryBrokerNameSet = new HashSet(); for (int times = 0; times < 3; times++) { String lastBrokerName = null == mqTmp ? null : mqTmp.getBrokerName(); mqTmp = topicPublishInfo.selectOneMessageQueue(lastBrokerName); retryBrokerNameSet.add(mqTmp.getBrokerName()); } return retryBrokerNameSet; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/rpchook/NamespaceRpcHookTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.rpchook; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class NamespaceRpcHookTest { private NamespaceRpcHook namespaceRpcHook; private ClientConfig clientConfig; private String namespace = "namespace"; @Test public void testDoBeforeRequestWithNamespace() { clientConfig = new ClientConfig(); clientConfig.setNamespaceV2(namespace); namespaceRpcHook = new NamespaceRpcHook(clientConfig); PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader); namespaceRpcHook.doBeforeRequest("", request); assertThat(request.getExtFields().get(MixAll.RPC_REQUEST_HEADER_NAMESPACED_FIELD)).isEqualTo("true"); assertThat(request.getExtFields().get(MixAll.RPC_REQUEST_HEADER_NAMESPACE_FIELD)).isEqualTo(namespace); } @Test public void testDoBeforeRequestWithoutNamespace() { clientConfig = new ClientConfig(); namespaceRpcHook = new NamespaceRpcHook(clientConfig); PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader); namespaceRpcHook.doBeforeRequest("", request); assertThat(request.getExtFields()).isNull(); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.tag.Tags; import java.io.ByteArrayOutputStream; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.PullRequest; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalancePushImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.trace.hook.ConsumeMessageOpenTracingHookImpl; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.waitAtMost; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQConsumerWithOpenTracingTest { private String consumerGroup; private String topic = "FooBar"; private String brokerName = "BrokerA"; private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mQClientAPIImpl; private PullAPIWrapper pullAPIWrapper; private RebalancePushImpl rebalancePushImpl; private DefaultMQPushConsumer pushConsumer; private final MockTracer tracer = new MockTracer(); @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); for (Map.Entry entry : factoryTable.entrySet()) { entry.getValue().shutdown(); } factoryTable.clear(); when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); return pullResult; } }); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.getDefaultMQPushConsumerImpl().registerConsumeMessageHook( new ConsumeMessageOpenTracingHookImpl(tracer)); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setPullInterval(60 * 1000); // disable trace to let mock trace work pushConsumer.setEnableTrace(false); OffsetStore offsetStore = Mockito.mock(OffsetStore.class); Mockito.when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(0L); pushConsumer.setOffsetStore(offsetStore); pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return null; } }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); FieldUtils.writeDeclaredField(mQClientFactory, "mQClientAPIImpl", mQClientAPIImpl, true); mQClientFactory = spy(mQClientFactory); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); pushConsumer.subscribe(topic, "*"); pushConsumer.start(); } @After public void terminate() { pushConsumer.shutdown(); } @Test public void testPullMessage_WithTrace_Success() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference messageAtomic = new AtomicReference<>(); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } })); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(30, TimeUnit.SECONDS); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); // wait until consumeMessageAfter hook of tracer is done surely. waitAtMost(1, TimeUnit.SECONDS).until(new Callable() { @Override public Object call() throws Exception { return tracer.finishedSpans().size() == 1; } }); MockSpan span = tracer.finishedSpans().get(0); assertThat(span.tags().get(Tags.MESSAGE_BUS_DESTINATION.getKey())).isEqualTo(topic); assertThat(span.tags().get(Tags.SPAN_KIND.getKey())).isEqualTo(Tags.SPAN_KIND_CONSUMER); assertThat(span.tags().get(TraceConstants.ROCKETMQ_SUCCESS)).isEqualTo(true); } private PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(1024); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); pullRequest.setMessageQueue(messageQueue); ProcessQueue processQueue = new ProcessQueue(); processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); pullRequest.setProcessQueue(processQueue); return pullRequest; } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithTraceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.PullRequest; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalancePushImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class DefaultMQConsumerWithTraceTest { private String consumerGroup; private String consumerGroupNormal; private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); private String topic = "FooBar"; private String brokerName = "BrokerA"; private MQClientInstance mQClientFactory; @Mock private MQClientAPIImpl mQClientAPIImpl; private PullAPIWrapper pullAPIWrapper; private RebalancePushImpl rebalancePushImpl; private DefaultMQPushConsumer pushConsumer; private DefaultMQPushConsumer normalPushConsumer; private DefaultMQPushConsumer customTraceTopicPushConsumer; private AsyncTraceDispatcher asyncTraceDispatcher; private MQClientInstance mQClientTraceFactory; @Mock private MQClientAPIImpl mQClientTraceAPIImpl; private DefaultMQProducer traceProducer; private String customerTraceTopic = "rmq_trace_topic_12345"; @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); for (Map.Entry entry : factoryTable.entrySet()) { entry.getValue().shutdown(); } factoryTable.clear(); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup, true, ""); consumerGroupNormal = "FooBarGroup" + System.currentTimeMillis(); normalPushConsumer = new DefaultMQPushConsumer(consumerGroupNormal, false, ""); customTraceTopicPushConsumer = new DefaultMQPushConsumer(consumerGroup, true, customerTraceTopic); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setUseTLS(true); pushConsumer.setPullInterval(60 * 1000); pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return null; } }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); mQClientFactory = spy(MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true))); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); field.set(pushConsumerImpl, rebalancePushImpl); pushConsumer.subscribe(topic, "*"); pushConsumer.start(); asyncTraceDispatcher = (AsyncTraceDispatcher) pushConsumer.getTraceDispatcher(); traceProducer = asyncTraceDispatcher.getTraceProducer(); mQClientFactory = spy(pushConsumerImpl.getmQClientFactory()); mQClientTraceFactory = spy(pushConsumerImpl.getmQClientFactory()); field = DefaultMQPushConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pushConsumerImpl, mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); Field fieldTrace = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); fieldTrace.setAccessible(true); fieldTrace.set(traceProducer.getDefaultMQProducerImpl(), mQClientTraceFactory); fieldTrace = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); fieldTrace.setAccessible(true); fieldTrace.set(mQClientTraceFactory, mQClientTraceAPIImpl); pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); field = DefaultMQPushConsumerImpl.class.getDeclaredField("pullAPIWrapper"); field.setAccessible(true); field.set(pushConsumerImpl, pullAPIWrapper); pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl().setmQClientFactory(mQClientFactory); mQClientFactory.registerConsumer(consumerGroup, pushConsumerImpl); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public PullResult answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); return pullResult; } }); doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); } @After public void terminate() { pushConsumer.shutdown(); } @Test public void testPullMessage_WithTrace_Success() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference messageAtomic = new AtomicReference<>(); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return null; } })); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(30, TimeUnit.SECONDS); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); } @Test public void testPushConsumerWithTraceTLS() { Assert.assertTrue(asyncTraceDispatcher.getTraceProducer().isUseTLS()); } private PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); pullRequest.setNextOffset(1024); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); pullRequest.setMessageQueue(messageQueue); ProcessQueue processQueue = new ProcessQueue(); processQueue.setLocked(true); processQueue.setLastLockTimestamp(System.currentTimeMillis()); pullRequest.setProcessQueue(processQueue); return pullRequest; } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId("123"); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); return sendResult; } public static TopicRouteData createTraceTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("broker-trace"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10912"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-trace"); queueData.setPerm(6); queueData.setReadQueueNums(1); queueData.setWriteQueueNums(1); queueData.setTopicSysFlag(1); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.MQAdminImpl; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl; import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; import org.apache.rocketmq.client.impl.consumer.RebalanceService; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQLitePullConsumerWithTraceTest { private MQClientInstance mQClientFactory; private MQClientInstance mqClientInstance; private MQClientInstance traceMqClientInstance; @Mock private MQClientAPIImpl mQClientAPIImpl; @Mock private MQAdminImpl mQAdminImpl; private AsyncTraceDispatcher asyncTraceDispatcher; private DefaultMQProducer traceProducer; private RebalanceImpl rebalanceImpl; private OffsetStore offsetStore; private DefaultLitePullConsumerImpl litePullConsumerImpl; private String consumerGroup = "LitePullConsumerGroup"; private String topic = "LitePullConsumerTest"; private String brokerName = "BrokerA"; private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); private String customerTraceTopic = "rmq_trace_topic_12345"; @BeforeClass public static void setUpEnv() { System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); } @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField( MQClientManager.getInstance(), "factoryTable", true); factoryTable.forEach((clientId, instance) -> instance.shutdown()); factoryTable.clear(); mQClientFactory = null; mqClientInstance = null; traceMqClientInstance = null; asyncTraceDispatcher = null; traceProducer = null; rebalanceImpl = null; offsetStore = null; litePullConsumerImpl = null; } @After public void destroy() { if (traceProducer != null) { MQClientInstance traceClientFactory = traceProducer.getDefaultMQProducerImpl().getMqClientFactory(); traceClientFactory.unregisterProducer(producerGroupTraceTemp); traceClientFactory.unregisterProducer(traceProducer.getProducerGroup()); } if (traceMqClientInstance != null && traceProducer != null) { traceMqClientInstance.unregisterProducer(traceProducer.getProducerGroup()); traceMqClientInstance.shutdown(); } if (litePullConsumerImpl != null) { if (mQClientFactory != null) { mQClientFactory.unregisterConsumer(litePullConsumerImpl.groupName()); mQClientFactory.shutdown(); } if (mqClientInstance != null && mqClientInstance != mQClientFactory) { mqClientInstance.unregisterConsumer(litePullConsumerImpl.groupName()); mqClientInstance.shutdown(); } } } @Test public void testSubscribe_PollMessageSuccess_WithDefaultTraceTopic() throws Exception { DefaultLitePullConsumer litePullConsumer = createLitePullConsumerWithDefaultTraceTopic(); try { Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); List result = pollUntilFound(litePullConsumer); assertThat(result).isNotEmpty(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testSubscribe_PollMessageSuccess_WithCustomizedTraceTopic() throws Exception { DefaultLitePullConsumer litePullConsumer = createLitePullConsumerWithCustomizedTraceTopic(); try { Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); List result = pollUntilFound(litePullConsumer); assertThat(result).isNotEmpty(); assertThat(result.get(0).getTopic()).isEqualTo(topic); assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); } finally { litePullConsumer.shutdown(); } } @Test public void testLitePullConsumerWithTraceTLS() throws Exception { DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("consumerGroup"); try { consumer.setUseTLS(true); consumer.setEnableMsgTrace(true); consumer.start(); AsyncTraceDispatcher asyncTraceDispatcher = (AsyncTraceDispatcher) consumer.getTraceDispatcher(); Assert.assertTrue(asyncTraceDispatcher.getTraceProducer().isUseTLS()); } finally { consumer.shutdown(); } } private DefaultLitePullConsumer createLitePullConsumerWithDefaultTraceTopic() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setEnableMsgTrace(true); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.subscribe(topic, "*"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); return litePullConsumer; } private DefaultLitePullConsumer createLitePullConsumerWithCustomizedTraceTopic() throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); litePullConsumer.setEnableMsgTrace(true); litePullConsumer.setCustomizedTraceTopic(customerTraceTopic); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.subscribe(topic, "*"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); litePullConsumer.start(); initDefaultLitePullConsumer(litePullConsumer); return litePullConsumer; } private void initDefaultLitePullConsumer(DefaultLitePullConsumer litePullConsumer) throws Exception { asyncTraceDispatcher = (AsyncTraceDispatcher) litePullConsumer.getTraceDispatcher(); traceProducer = asyncTraceDispatcher.getTraceProducer(); Field field = DefaultLitePullConsumer.class.getDeclaredField("defaultLitePullConsumerImpl"); field.setAccessible(true); litePullConsumerImpl = (DefaultLitePullConsumerImpl) field.get(litePullConsumer); field = DefaultLitePullConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); mqClientInstance = (MQClientInstance) field.get(litePullConsumerImpl); mQClientFactory = spy(mqClientInstance); mQClientFactory.getClientConfig().setDecodeReadBody(true); field.set(litePullConsumerImpl, mQClientFactory); field = MQClientInstance.class.getDeclaredField("rebalanceService"); field.setAccessible(true); RebalanceService rebalanceService = (RebalanceService) field.get(mQClientFactory); field = RebalanceService.class.getDeclaredField("waitInterval"); field.setAccessible(true); field.set(rebalanceService, 100); PullAPIWrapper pullAPIWrapper = litePullConsumerImpl.getPullAPIWrapper(); field = PullAPIWrapper.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(pullAPIWrapper, mQClientFactory); Field fieldTrace = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); fieldTrace.setAccessible(true); traceMqClientInstance = traceProducer.getDefaultMQProducerImpl().getMqClientFactory(); fieldTrace.set(traceProducer.getDefaultMQProducerImpl(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); field = MQClientInstance.class.getDeclaredField("mQAdminImpl"); field.setAccessible(true); field.set(mQClientFactory, mQAdminImpl); field = DefaultLitePullConsumerImpl.class.getDeclaredField("rebalanceImpl"); field.setAccessible(true); rebalanceImpl = (RebalanceImpl) field.get(litePullConsumerImpl); field = RebalanceImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(rebalanceImpl, mQClientFactory); offsetStore = spy(litePullConsumerImpl.getOffsetStore()); field = DefaultLitePullConsumerImpl.class.getDeclaredField("offsetStore"); field.setAccessible(true); field.set(litePullConsumerImpl, offsetStore); traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); lenient().when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); return pullResult; } }); when(mQClientFactory.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(new FindBrokerResult("127.0.0.1:10911", false)); doReturn(Collections.singletonList(mQClientFactory.getClientId())).when(mQClientFactory).findConsumerIdList(anyString(), anyString()); doReturn(123L).when(offsetStore).readOffset(any(MessageQueue.class), any(ReadOffsetType.class)); } private List pollUntilFound(DefaultLitePullConsumer litePullConsumer) { litePullConsumer.setPollTimeoutMillis(1000); long deadline = System.currentTimeMillis() + 20 * 1000; List result = Collections.emptyList(); while (System.currentTimeMillis() < deadline) { result = litePullConsumer.poll(); if (!result.isEmpty()) { return result; } } return result; } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } private MessageQueue createMessageQueue() { MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); messageQueue.setQueueId(0); messageQueue.setTopic(topic); return messageQueue; } private TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId("123"); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); return sendResult; } private static void suppressUpdateTopicRouteInfoFromNameServer(DefaultLitePullConsumer litePullConsumer) throws IllegalAccessException { DefaultLitePullConsumerImpl defaultLitePullConsumerImpl = (DefaultLitePullConsumerImpl) FieldUtils.readDeclaredField(litePullConsumer, "defaultLitePullConsumerImpl", true); if (litePullConsumer.getMessageModel() == MessageModel.CLUSTERING) { litePullConsumer.changeInstanceNameToPID(); } MQClientInstance mQClientFactory = spy(MQClientManager.getInstance().getOrCreateMQClientInstance(litePullConsumer, (RPCHook) FieldUtils.readDeclaredField(defaultLitePullConsumerImpl, "rpcHook", true))); ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); factoryTable.put(litePullConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithOpenTracingTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.tag.Tags; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.trace.hook.SendMessageOpenTracingHookImpl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageType; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQProducerWithOpenTracingTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); @Mock private MQClientAPIImpl mQClientAPIImpl; private DefaultMQProducer producer; private Message message; private String topic = "FooBar"; private String producerGroupPrefix = "FooBar_PID"; private String producerGroupTemp = producerGroupPrefix + System.currentTimeMillis(); private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); private MockTracer tracer = new MockTracer(); @Before public void init() throws Exception { producer = new DefaultMQProducer(producerGroupTemp); producer.getDefaultMQProducerImpl().registerSendMessageHook( new SendMessageOpenTracingHookImpl(tracer)); producer.setNamesrvAddr("127.0.0.1:9876"); message = new Message(topic, new byte[] {'a', 'b', 'c'}); // disable trace to let mock trace work producer.setEnableTrace(false); producer.start(); Field field = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(producer.getDefaultMQProducerImpl(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))).thenCallRealMethod(); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendCallback.class), nullable(TopicPublishInfo.class), nullable(MQClientInstance.class), anyInt(), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))) .thenReturn(createSendResult(SendStatus.SEND_OK)); } @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.send(message); assertThat(tracer.finishedSpans().size()).isEqualTo(1); MockSpan span = tracer.finishedSpans().get(0); assertThat(span.tags().get(Tags.MESSAGE_BUS_DESTINATION.getKey())).isEqualTo(topic); assertThat(span.tags().get(Tags.SPAN_KIND.getKey())).isEqualTo(Tags.SPAN_KIND_PRODUCER); assertThat(span.tags().get(TraceConstants.ROCKETMQ_MSG_ID)).isEqualTo("123"); assertThat(span.tags().get(TraceConstants.ROCKETMQ_BODY_LENGTH)).isEqualTo(3); assertThat(span.tags().get(TraceConstants.ROCKETMQ_REGION_ID)).isEqualTo("HZ"); assertThat(span.tags().get(TraceConstants.ROCKETMQ_MSG_TYPE)).isEqualTo(MessageType.Normal_Msg.name()); assertThat(span.tags().get(TraceConstants.ROCKETMQ_STORE_HOST)).isEqualTo("127.0.0.1:10911"); } @After public void terminate() { producer.shutdown(); } public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId("123"); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); return sendResult; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithTraceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMQProducerWithTraceTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); @Mock private MQClientAPIImpl mQClientAPIImpl; private AsyncTraceDispatcher asyncTraceDispatcher; private DefaultMQProducer producer; private DefaultMQProducer customTraceTopicproducer; private DefaultMQProducer traceProducer; private DefaultMQProducer normalProducer; private Message message; private String topic = "FooBar"; private String producerGroupPrefix = "FooBar_PID"; private String producerGroupTemp = producerGroupPrefix + System.currentTimeMillis(); private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); private String customerTraceTopic = "rmq_trace_topic_12345"; @Before public void init() throws Exception { customTraceTopicproducer = new DefaultMQProducer(producerGroupTemp, false, customerTraceTopic); normalProducer = new DefaultMQProducer(producerGroupTemp, false, ""); producer = new DefaultMQProducer(producerGroupTemp, true, ""); producer.setNamesrvAddr("127.0.0.1:9876"); normalProducer.setNamesrvAddr("127.0.0.1:9877"); customTraceTopicproducer.setNamesrvAddr("127.0.0.1:9878"); message = new Message(topic, new byte[] {'a', 'b', 'c'}); producer.setTraceTopic(customerTraceTopic); producer.setUseTLS(true); producer.start(); asyncTraceDispatcher = (AsyncTraceDispatcher) producer.getTraceDispatcher(); traceProducer = asyncTraceDispatcher.getTraceProducer(); Field field = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(producer.getDefaultMQProducerImpl(), mQClientFactory); Field fieldTrace = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); fieldTrace.setAccessible(true); fieldTrace.set(traceProducer.getDefaultMQProducerImpl(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))).thenCallRealMethod(); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendCallback.class), nullable(TopicPublishInfo.class), nullable(MQClientInstance.class), anyInt(), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))) .thenReturn(createSendResult(SendStatus.SEND_OK)); } @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final CountDownLatch countDownLatch = new CountDownLatch(1); try { producer.send(message); } catch (MQClientException e) { } countDownLatch.await(3000L, TimeUnit.MILLISECONDS); } @Test public void testSendMessageSync_WithTrace_NoBrokerSet_Exception() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final CountDownLatch countDownLatch = new CountDownLatch(1); try { producer.send(message); } catch (MQClientException e) { } countDownLatch.await(3000L, TimeUnit.MILLISECONDS); } @Test public void testProducerWithTraceTLS() { Assert.assertTrue(asyncTraceDispatcher.getTraceProducer().isUseTLS()); } @After public void terminate() { producer.shutdown(); } public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId("123"); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); return sendResult; } public static TopicRouteData createTraceTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("broker-trace"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10912"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-trace"); queueData.setPerm(6); queueData.setReadQueueNums(1); queueData.setWriteQueueNums(1); queueData.setTopicSysFlag(1); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.common.message.MessageType; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; public class TraceDataEncoderTest { private String traceData; private long time; @Before public void init() { time = System.currentTimeMillis(); traceData = new StringBuilder() .append("Pub").append(TraceConstants.CONTENT_SPLITOR) .append(time).append(TraceConstants.CONTENT_SPLITOR) .append("DefaultRegion").append(TraceConstants.CONTENT_SPLITOR) .append("PID-test").append(TraceConstants.CONTENT_SPLITOR) .append("topic-test").append(TraceConstants.CONTENT_SPLITOR) .append("AC1415116D1418B4AAC217FE1B4E0000").append(TraceConstants.CONTENT_SPLITOR) .append("Tags").append(TraceConstants.CONTENT_SPLITOR) .append("Keys").append(TraceConstants.CONTENT_SPLITOR) .append("127.0.0.1:10911").append(TraceConstants.CONTENT_SPLITOR) .append(26).append(TraceConstants.CONTENT_SPLITOR) .append(245).append(TraceConstants.CONTENT_SPLITOR) .append(MessageType.Normal_Msg.ordinal()).append(TraceConstants.CONTENT_SPLITOR) .append("0A9A002600002A9F0000000000002329").append(TraceConstants.CONTENT_SPLITOR) .append(true).append(TraceConstants.FIELD_SPLITOR) .toString(); } @Test public void testDecoderFromTraceDataString() { List contexts = TraceDataEncoder.decoderFromTraceDataString(traceData); Assert.assertEquals(contexts.size(), 1); Assert.assertEquals(contexts.get(0).getTraceType(), TraceType.Pub); } @Test public void testEncoderFromContextBean() { TraceContext context = new TraceContext(); context.setTraceType(TraceType.Pub); context.setGroupName("PID-test"); context.setRegionId("DefaultRegion"); context.setCostTime(245); context.setSuccess(true); context.setTimeStamp(time); TraceBean traceBean = new TraceBean(); traceBean.setTopic("topic-test"); traceBean.setKeys("Keys"); traceBean.setTags("Tags"); traceBean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); traceBean.setOffsetMsgId("0A9A002600002A9F0000000000002329"); traceBean.setStoreHost("127.0.0.1:10911"); traceBean.setStoreTime(time); traceBean.setMsgType(MessageType.Normal_Msg); traceBean.setBodyLength(26); List traceBeans = new ArrayList<>(); traceBeans.add(traceBean); context.setTraceBeans(traceBeans); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(context); Assert.assertEquals(traceTransferBean.getTransData(), traceData); Assert.assertEquals(traceTransferBean.getTransKey().size(), 2); } @Test public void testEncoderFromContextBean_EndTransaction() { TraceContext context = new TraceContext(); context.setTraceType(TraceType.EndTransaction); context.setGroupName("PID-test"); context.setRegionId("DefaultRegion"); context.setTimeStamp(time); TraceBean traceBean = new TraceBean(); traceBean.setTopic("topic-test"); traceBean.setKeys("Keys"); traceBean.setTags("Tags"); traceBean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); traceBean.setStoreHost("127.0.0.1:10911"); traceBean.setMsgType(MessageType.Trans_msg_Commit); traceBean.setTransactionId("transactionId"); traceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); traceBean.setFromTransactionCheck(false); List traceBeans = new ArrayList<>(); traceBeans.add(traceBean); context.setTraceBeans(traceBeans); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(context); Assert.assertEquals(traceTransferBean.getTransKey().size(), 2); String traceData = traceTransferBean.getTransData(); TraceContext contextAfter = TraceDataEncoder.decoderFromTraceDataString(traceData).get(0); Assert.assertEquals(context.getTraceType(), contextAfter.getTraceType()); Assert.assertEquals(context.getTimeStamp(), contextAfter.getTimeStamp()); Assert.assertEquals(context.getGroupName(), contextAfter.getGroupName()); TraceBean before = context.getTraceBeans().get(0); TraceBean after = contextAfter.getTraceBeans().get(0); Assert.assertEquals(before.getTopic(), after.getTopic()); Assert.assertEquals(before.getMsgId(), after.getMsgId()); Assert.assertEquals(before.getTags(), after.getTags()); Assert.assertEquals(before.getKeys(), after.getKeys()); Assert.assertEquals(before.getStoreHost(), after.getStoreHost()); Assert.assertEquals(before.getMsgType(), after.getMsgType()); Assert.assertEquals(before.getClientHost(), after.getClientHost()); Assert.assertEquals(before.getTransactionId(), after.getTransactionId()); Assert.assertEquals(before.getTransactionState(), after.getTransactionState()); Assert.assertEquals(before.isFromTransactionCheck(), after.isFromTransactionCheck()); } @Test public void testPubTraceDataFormatTest() { TraceContext pubContext = new TraceContext(); pubContext.setTraceType(TraceType.Pub); pubContext.setTimeStamp(time); pubContext.setRegionId("Default-region"); pubContext.setGroupName("GroupName-test"); pubContext.setCostTime(34); pubContext.setSuccess(true); TraceBean bean = new TraceBean(); bean.setTopic("topic-test"); bean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); bean.setTags("tags"); bean.setKeys("keys"); bean.setStoreHost("127.0.0.1:10911"); bean.setBodyLength(100); bean.setMsgType(MessageType.Normal_Msg); bean.setOffsetMsgId("AC1415116D1418B4AAC217FE1B4E0000"); pubContext.setTraceBeans(new ArrayList<>(1)); pubContext.getTraceBeans().add(bean); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(pubContext); String transData = traceTransferBean.getTransData(); Assert.assertNotNull(transData); String[] items = transData.split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); Assert.assertEquals(14, items.length); } @Test public void testSubBeforeTraceDataFormatTest() { TraceContext subBeforeContext = new TraceContext(); subBeforeContext.setTraceType(TraceType.SubBefore); subBeforeContext.setTimeStamp(time); subBeforeContext.setRegionId("Default-region"); subBeforeContext.setGroupName("GroupName-test"); subBeforeContext.setRequestId("3455848576927"); TraceBean bean = new TraceBean(); bean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); bean.setRetryTimes(0); bean.setKeys("keys"); subBeforeContext.setTraceBeans(new ArrayList<>(1)); subBeforeContext.getTraceBeans().add(bean); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(subBeforeContext); String transData = traceTransferBean.getTransData(); Assert.assertNotNull(transData); String[] items = transData.split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); Assert.assertEquals(8, items.length); } @Test public void testSubAfterTraceDataFormatTest() { TraceContext subAfterContext = new TraceContext(); subAfterContext.setTraceType(TraceType.SubAfter); subAfterContext.setRequestId("3455848576927"); subAfterContext.setCostTime(20); subAfterContext.setSuccess(true); subAfterContext.setTimeStamp(1625883640000L); subAfterContext.setGroupName("GroupName-test"); subAfterContext.setContextCode(98623046); subAfterContext.setAccessChannel(AccessChannel.LOCAL); TraceBean bean = new TraceBean(); bean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); bean.setKeys("keys"); subAfterContext.setTraceBeans(new ArrayList<>(1)); subAfterContext.getTraceBeans().add(bean); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(subAfterContext); String transData = traceTransferBean.getTransData(); Assert.assertNotNull(transData); String[] items = transData.split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); Assert.assertEquals(9, items.length); } @Test public void testEndTrxTraceDataFormatTest() { TraceContext endTrxContext = new TraceContext(); endTrxContext.setTraceType(TraceType.EndTransaction); endTrxContext.setGroupName("PID-test"); endTrxContext.setRegionId("DefaultRegion"); endTrxContext.setTimeStamp(time); TraceBean endTrxTraceBean = new TraceBean(); endTrxTraceBean.setTopic("topic-test"); endTrxTraceBean.setKeys("Keys"); endTrxTraceBean.setTags("Tags"); endTrxTraceBean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); endTrxTraceBean.setStoreHost("127.0.0.1:10911"); endTrxTraceBean.setMsgType(MessageType.Trans_msg_Commit); endTrxTraceBean.setTransactionId("transactionId"); endTrxTraceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); endTrxTraceBean.setFromTransactionCheck(false); List traceBeans = new ArrayList<>(); traceBeans.add(endTrxTraceBean); endTrxContext.setTraceBeans(traceBeans); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(endTrxContext); String transData = traceTransferBean.getTransData(); Assert.assertNotNull(transData); String[] items = transData.split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); Assert.assertEquals(13, items.length); } @Test public void testTraceKeys() { TraceContext endTrxContext = new TraceContext(); endTrxContext.setTraceType(TraceType.EndTransaction); endTrxContext.setGroupName("PID-test"); endTrxContext.setRegionId("DefaultRegion"); endTrxContext.setTimeStamp(time); TraceBean endTrxTraceBean = new TraceBean(); endTrxTraceBean.setTopic("topic-test"); endTrxTraceBean.setKeys("Keys Keys2"); endTrxTraceBean.setTags("Tags"); endTrxTraceBean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); endTrxTraceBean.setStoreHost("127.0.0.1:10911"); endTrxTraceBean.setMsgType(MessageType.Trans_msg_Commit); endTrxTraceBean.setTransactionId("transactionId"); endTrxTraceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); endTrxTraceBean.setFromTransactionCheck(false); List traceBeans = new ArrayList<>(); traceBeans.add(endTrxTraceBean); endTrxContext.setTraceBeans(traceBeans); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(endTrxContext); Set keys = traceTransferBean.getTransKey(); assertThat(keys).contains("Keys"); assertThat(keys).contains("Keys2"); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/TraceViewTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageType; import org.junit.Assert; import org.junit.Test; import java.nio.charset.StandardCharsets; import java.util.List; public class TraceViewTest { @Test public void testDecodeFromTraceTransData() { String messageBody = new StringBuilder() .append("Pub").append(TraceConstants.CONTENT_SPLITOR) .append(System.currentTimeMillis()).append(TraceConstants.CONTENT_SPLITOR) .append("DefaultRegion").append(TraceConstants.CONTENT_SPLITOR) .append("PID-test").append(TraceConstants.CONTENT_SPLITOR) .append("topic-test").append(TraceConstants.CONTENT_SPLITOR) .append("AC1415116D1418B4AAC217FE1B4E0000").append(TraceConstants.CONTENT_SPLITOR) .append("Tags").append(TraceConstants.CONTENT_SPLITOR) .append("Keys").append(TraceConstants.CONTENT_SPLITOR) .append("127.0.0.1:10911").append(TraceConstants.CONTENT_SPLITOR) .append(26).append(TraceConstants.CONTENT_SPLITOR) .append(245).append(TraceConstants.CONTENT_SPLITOR) .append(MessageType.Normal_Msg.ordinal()).append(TraceConstants.CONTENT_SPLITOR) .append("0A9A002600002A9F0000000000002329").append(TraceConstants.CONTENT_SPLITOR) .append(true).append(TraceConstants.FIELD_SPLITOR) .toString(); MessageExt message = new MessageExt(); message.setBody(messageBody.getBytes(StandardCharsets.UTF_8)); String key = "AC1415116D1418B4AAC217FE1B4E0000"; List traceViews = TraceView.decodeFromTraceTransData(key, message); Assert.assertEquals(traceViews.size(), 1); Assert.assertEquals(traceViews.get(0).getMsgId(), key); key = "AD4233434334AAC217FEFFD0000"; traceViews = TraceView.decodeFromTraceTransData(key, message); Assert.assertEquals(traceViews.size(), 0); } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithOpenTracingTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.tag.Tags; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.client.trace.hook.EndTransactionOpenTracingHookImpl; import org.apache.rocketmq.client.trace.hook.SendMessageOpenTracingHookImpl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageType; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TransactionMQProducerWithOpenTracingTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); @Mock private MQClientAPIImpl mQClientAPIImpl; private TransactionMQProducer producer; private Message message; private String topic = "FooBar"; private String producerGroupPrefix = "FooBar_PID"; private String producerGroupTemp = producerGroupPrefix + System.currentTimeMillis(); private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); private MockTracer tracer = new MockTracer(); @Before public void init() throws Exception { TransactionListener transactionListener = new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { return LocalTransactionState.COMMIT_MESSAGE; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { return LocalTransactionState.COMMIT_MESSAGE; } }; producer = new TransactionMQProducer(producerGroupTemp); producer.getDefaultMQProducerImpl().registerSendMessageHook(new SendMessageOpenTracingHookImpl(tracer)); producer.getDefaultMQProducerImpl().registerEndTransactionHook(new EndTransactionOpenTracingHookImpl(tracer)); producer.setTransactionListener(transactionListener); // disable trace to let mock trace work producer.setEnableTrace(false); producer.setNamesrvAddr("127.0.0.1:9876"); message = new Message(topic, new byte[] {'a', 'b', 'c'}); producer.start(); Field field = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(producer.getDefaultMQProducerImpl(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))).thenCallRealMethod(); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendCallback.class), nullable(TopicPublishInfo.class), nullable(MQClientInstance.class), anyInt(), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))) .thenReturn(createSendResult(SendStatus.SEND_OK)); } @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.sendMessageInTransaction(message, null); assertThat(tracer.finishedSpans().size()).isEqualTo(2); MockSpan span = tracer.finishedSpans().get(1); assertThat(span.tags().get(Tags.MESSAGE_BUS_DESTINATION.getKey())).isEqualTo(topic); assertThat(span.tags().get(Tags.SPAN_KIND.getKey())).isEqualTo(Tags.SPAN_KIND_PRODUCER); assertThat(span.tags().get(TraceConstants.ROCKETMQ_MSG_ID)).isEqualTo("123"); assertThat(span.tags().get(TraceConstants.ROCKETMQ_MSG_TYPE)).isEqualTo(MessageType.Trans_msg_Commit.name()); assertThat(span.tags().get(TraceConstants.ROCKETMQ_TRANSACTION_STATE)).isEqualTo(LocalTransactionState.COMMIT_MESSAGE.name()); assertThat(span.tags().get(TraceConstants.ROCKETMQ_IS_FROM_TRANSACTION_CHECK)).isEqualTo(false); } @After public void terminate() { producer.shutdown(); } public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId(MessageDecoder.createMessageId(new InetSocketAddress("127.0.0.1", 12), 1)); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); sendResult.setMessageQueue(new MessageQueue(topic, "broker-trace", 0)); return sendResult; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithTraceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.trace; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.EndTransactionContext; import org.apache.rocketmq.client.hook.EndTransactionHook; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TransactionMQProducerWithTraceTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); @Mock private MQClientAPIImpl mQClientAPIImpl; @Mock private EndTransactionHook endTransactionHook; private AsyncTraceDispatcher asyncTraceDispatcher; private TransactionMQProducer producer; private DefaultMQProducer traceProducer; private Message message; private String topic = "FooBar"; private String producerGroupPrefix = "FooBar_PID"; private String producerGroupTemp = producerGroupPrefix + System.currentTimeMillis(); private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); private String customerTraceTopic = "rmq_trace_topic_12345"; @Before public void init() throws Exception { TransactionListener transactionListener = new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { return LocalTransactionState.COMMIT_MESSAGE; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { return LocalTransactionState.COMMIT_MESSAGE; } }; producer = new TransactionMQProducer(producerGroupTemp, null, true, null); producer.setTransactionListener(transactionListener); producer.setNamesrvAddr("127.0.0.1:9876"); message = new Message(topic, new byte[] {'a', 'b', 'c'}); producer.start(); asyncTraceDispatcher = (AsyncTraceDispatcher) producer.getTraceDispatcher(); traceProducer = asyncTraceDispatcher.getTraceProducer(); Field field = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); field.set(producer.getDefaultMQProducerImpl(), mQClientFactory); Field fieldTrace = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); fieldTrace.setAccessible(true); fieldTrace.set(traceProducer.getDefaultMQProducerImpl(), mQClientFactory); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); Field fieldHooks = DefaultMQProducerImpl.class.getDeclaredField("endTransactionHookList"); fieldHooks.setAccessible(true); List hooks = new ArrayList<>(); hooks.add(endTransactionHook); fieldHooks.set(producer.getDefaultMQProducerImpl(), hooks); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))).thenCallRealMethod(); when(mQClientAPIImpl.sendMessage(anyString(), anyString(), any(Message.class), any(SendMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(SendCallback.class), nullable(TopicPublishInfo.class), nullable(MQClientInstance.class), anyInt(), nullable(SendMessageContext.class), any(DefaultMQProducerImpl.class))) .thenReturn(createSendResult(SendStatus.SEND_OK)); } @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final AtomicReference context = new AtomicReference<>(); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock mock) throws Throwable { context.set((EndTransactionContext) mock.getArgument(0)); return null; } }).when(endTransactionHook).endTransaction(any(EndTransactionContext.class)); producer.sendMessageInTransaction(message, null); EndTransactionContext ctx = context.get(); assertThat(ctx.getProducerGroup()).isEqualTo(producerGroupTemp); assertThat(ctx.getMsgId()).isEqualTo("123"); assertThat(ctx.isFromTransactionCheck()).isFalse(); assertThat(new String(ctx.getMessage().getBody())).isEqualTo(new String(message.getBody())); assertThat(ctx.getMessage().getTopic()).isEqualTo(topic); } @Test(expected = MQClientException.class) public void testSendMessageInTransaction_NoListener_ThrowsException() throws MQClientException { producer.setTransactionListener(null); producer.sendMessageInTransaction(message, null); } @Test(expected = MQClientException.class) public void testSendMessageInTransaction_DelayMsg_ThrowsException() throws MQClientException { message.setDelayTimeLevel(3); producer.sendMessageInTransaction(message, null); } @After public void terminate() { producer.shutdown(); } public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setFilterServerTable(new HashMap<>()); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); queueData.setReadQueueNums(3); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDataList.add(queueData); topicRouteData.setQueueDatas(queueDataList); return topicRouteData; } private SendResult createSendResult(SendStatus sendStatus) { SendResult sendResult = new SendResult(); sendResult.setMsgId("123"); sendResult.setOffsetMsgId(MessageDecoder.createMessageId(new InetSocketAddress("127.0.0.1", 12), 1)); sendResult.setQueueOffset(456); sendResult.setSendStatus(sendStatus); sendResult.setRegionId("HZ"); sendResult.setMessageQueue(new MessageQueue(topic, "broker-trace", 0)); return sendResult; } } ================================================ FILE: client/src/test/java/org/apache/rocketmq/client/utils/MessageUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.client.utils; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; public class MessageUtilsTest { @Test public void testCreateReplyMessage() throws MQClientException { Message msg = MessageUtil.createReplyMessage(createReplyMessage("clusterName"), new byte[] {'a'}); assertThat(msg.getTopic()).isEqualTo("clusterName" + "_" + MixAll.REPLY_TOPIC_POSTFIX); assertThat(msg.getProperty(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT)).isEqualTo("127.0.0.1"); assertThat(msg.getProperty(MessageConst.PROPERTY_MESSAGE_TTL)).isEqualTo("3000"); } @Test public void testCreateReplyMessage_Exception() throws MQClientException { try { Message msg = MessageUtil.createReplyMessage(createReplyMessage(null), new byte[] {'a'}); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("create reply message fail, requestMessage error, property[" + MessageConst.PROPERTY_CLUSTER + "] is null."); } } @Test public void testCreateReplyMessage_reqMsgIsNull() throws MQClientException { try { Message msg = MessageUtil.createReplyMessage(null, new byte[] {'a'}); failBecauseExceptionWasNotThrown(MQClientException.class); } catch (MQClientException e) { assertThat(e).hasMessageContaining("create reply message fail, requestMessage cannot be null."); } } @Test public void testGetReplyToClient() throws MQClientException { Message msg = createReplyMessage("clusterName"); String replyToClient = MessageUtil.getReplyToClient(msg); assertThat(replyToClient).isNotNull(); assertThat(replyToClient).isEqualTo("127.0.0.1"); } private Message createReplyMessage(String clusterName) { Message requestMessage = new Message(); Map map = new HashMap(); map.put(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT, "127.0.0.1"); map.put(MessageConst.PROPERTY_CLUSTER, clusterName); map.put(MessageConst.PROPERTY_MESSAGE_TTL, "3000"); MessageAccessor.setProperties(requestMessage, map); return requestMessage; } } ================================================ FILE: client/src/test/resources/acl_hook/plain_acl.yml ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## suggested format - accessKey: rocketmq2 secretKey: 12345678 whiteRemoteAddress: 192.168.1.* admin: true ================================================ FILE: client/src/test/resources/conf/plain_acl_incomplete.yml ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## suggested format - accessKey: rocketmq2 secretKey: whiteRemoteAddress: 192.168.1.* # if it is admin, it could access all resources admin: true ================================================ FILE: client/src/test/resources/org/powermock/extensions/configuration.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. powermock.global-ignore=javax.management.* ================================================ FILE: client/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: common/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "common", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_github_luben_zstd_jni", "@maven//:com_google_guava_guava", "@maven//:commons_collections_commons_collections", "@maven//:commons_codec_commons_codec", "@maven//:commons_validator_commons_validator", "@maven//:io_netty_netty_all", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp", "@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_context", "@maven//:org_apache_commons_commons_lang3", "@maven//:org_lz4_lz4_java", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":common", "//:test_deps", "@maven//:com_google_guava_guava", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:commons_codec_commons_codec", "@maven//:io_netty_netty_all", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_context", "@maven//:org_apache_commons_commons_lang3", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: common/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-common rocketmq-common ${project.version} ${basedir}/.. com.alibaba fastjson com.alibaba.fastjson2 fastjson2 io.netty netty-all org.apache.commons commons-lang3 commons-validator commons-validator com.github.luben zstd-jni at.yawk.lz4 lz4-java com.google.guava guava commons-codec commons-codec io.opentelemetry opentelemetry-exporter-otlp io.opentelemetry opentelemetry-exporter-prometheus io.opentelemetry opentelemetry-exporter-logging io.opentelemetry opentelemetry-sdk io.opentelemetry opentelemetry-exporter-logging-otlp io.grpc grpc-stub io.grpc grpc-netty-shaded com.squareup.okio okio-jvm org.apache.tomcat annotations-api io.github.aliyunmq rocketmq-slf4j-api io.github.aliyunmq rocketmq-logback-classic org.apache.rocketmq rocketmq-rocksdb ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/AbortProcessException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.apache.rocketmq.common.help.FAQUrl; /** * * This exception is used for broker hooks only : SendMessageHook, ConsumeMessageHook, RPCHook * This exception is not ignored while executing hooks and it means that * certain processor should return an immediate error response to the client. The * error response code is included in AbortProcessException. it's naming might * be confusing, so feel free to refactor this class. Also when any class implements * the 3 hook interface mentioned above we should be careful if we want to throw * an AbortProcessException, because it will change the control flow of broker * and cause a RemotingCommand return error immediately. So be aware of the side * effect before throw AbortProcessException in your implementation. * */ public class AbortProcessException extends RuntimeException { private static final long serialVersionUID = -5728810933841185841L; private int responseCode; private String errorMessage; public AbortProcessException(String errorMessage, Throwable cause) { super(FAQUrl.attachDefaultURL(errorMessage), cause); this.responseCode = -1; this.errorMessage = errorMessage; } public AbortProcessException(int responseCode, String errorMessage) { super(FAQUrl.attachDefaultURL("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: " + errorMessage)); this.responseCode = responseCode; this.errorMessage = errorMessage; } public int getResponseCode() { return responseCode; } public AbortProcessException setResponseCode(final int responseCode) { this.responseCode = responseCode; return this; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(final String errorMessage) { this.errorMessage = errorMessage; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/BoundaryType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public enum BoundaryType { /** * Indicate that lower boundary is expected. */ LOWER("lower"), /** * Indicate that upper boundary is expected. */ UPPER("upper"); private String name; BoundaryType(String name) { this.name = name; } public String getName() { return name; } public static BoundaryType getType(String name) { if (BoundaryType.UPPER.getName().equalsIgnoreCase(name)) { return UPPER; } return LOWER; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.common.config.ConfigManagerVersion; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.metrics.MetricsExporterType; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.NetworkUtil; public class BrokerConfig extends BrokerIdentity { private String brokerConfigPath = null; private String rocketmqHome = MixAll.ROCKETMQ_HOME_DIR; @ImportantField private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); /** * Listen port for single broker */ @ImportantField private int listenPort = 6888; @ImportantField private String brokerIP1 = NetworkUtil.getLocalAddress(); private String brokerIP2 = NetworkUtil.getLocalAddress(); @ImportantField private boolean recoverConcurrently = false; private int brokerPermission = PermName.PERM_READ | PermName.PERM_WRITE; private int defaultTopicQueueNums = 8; @ImportantField private boolean autoCreateTopicEnable = true; private boolean clusterTopicEnable = true; private boolean brokerTopicEnable = true; @ImportantField private boolean autoCreateSubscriptionGroup = true; private String messageStorePlugIn = ""; private static final int PROCESSOR_NUMBER = Runtime.getRuntime().availableProcessors(); @ImportantField private String msgTraceTopicName = TopicValidator.RMQ_SYS_TRACE_TOPIC; @ImportantField private boolean traceTopicEnable = false; /** * thread numbers for send message thread pool. */ private int sendMessageThreadPoolNums = Math.min(PROCESSOR_NUMBER, 4); private int putMessageFutureThreadPoolNums = Math.min(PROCESSOR_NUMBER, 4); private int pullMessageThreadPoolNums = 16 + PROCESSOR_NUMBER * 2; private int litePullMessageThreadPoolNums = 16 + PROCESSOR_NUMBER * 2; private int ackMessageThreadPoolNums = 16; private int processReplyMessageThreadPoolNums = 16 + PROCESSOR_NUMBER * 2; private int queryMessageThreadPoolNums = 8 + PROCESSOR_NUMBER; private int adminBrokerThreadPoolNums = 16; private int clientManageThreadPoolNums = 32; private int consumerManageThreadPoolNums = 32; private int loadBalanceProcessorThreadPoolNums = 32; private int heartbeatThreadPoolNums = Math.min(32, PROCESSOR_NUMBER); private int recoverThreadPoolNums = 32; /** * Thread numbers for EndTransactionProcessor */ private int endTransactionThreadPoolNums = Math.max(8 + PROCESSOR_NUMBER * 2, sendMessageThreadPoolNums * 4); private int flushConsumerOffsetInterval = 1000 * 5; private int flushConsumerOffsetHistoryInterval = 1000 * 60; @ImportantField private boolean rejectTransactionMessage = false; @ImportantField private boolean fetchNameSrvAddrByDnsLookup = false; @ImportantField private boolean fetchNamesrvAddrByAddressServer = false; private int sendThreadPoolQueueCapacity = 10000; private int putThreadPoolQueueCapacity = 10000; private int pullThreadPoolQueueCapacity = 100000; private int litePullThreadPoolQueueCapacity = 100000; private int ackThreadPoolQueueCapacity = 100000; private int replyThreadPoolQueueCapacity = 10000; private int queryThreadPoolQueueCapacity = 20000; private int clientManagerThreadPoolQueueCapacity = 1000000; private int consumerManagerThreadPoolQueueCapacity = 1000000; private int heartbeatThreadPoolQueueCapacity = 50000; private int endTransactionPoolQueueCapacity = 100000; private int adminBrokerThreadPoolQueueCapacity = 10000; private int loadBalanceThreadPoolQueueCapacity = 100000; private boolean longPollingEnable = true; private long shortPollingTimeMills = 1000; private boolean notifyConsumerIdsChangedEnable = true; private boolean highSpeedMode = false; private int commercialBaseCount = 1; private int commercialSizePerMsg = 4 * 1024; private boolean accountStatsEnable = true; private boolean accountStatsPrintZeroValues = true; private int maxStatsIdleTimeInMinutes = -1; private boolean transferMsgByHeap = true; private String regionId = MixAll.DEFAULT_TRACE_REGION_ID; private int registerBrokerTimeoutMills = 24000; private int sendHeartbeatTimeoutMillis = 1000; private boolean slaveReadEnable = false; private boolean disableConsumeIfConsumerReadSlowly = false; private long consumerFallbehindThreshold = 1024L * 1024 * 1024 * 16; private boolean brokerFastFailureEnable = true; private long waitTimeMillsInSendQueue = 200; private long waitTimeMillsInPullQueue = 5 * 1000; private long waitTimeMillsInLitePullQueue = 5 * 1000; private long waitTimeMillsInHeartbeatQueue = 31 * 1000; private long waitTimeMillsInTransactionQueue = 3 * 1000; private long waitTimeMillsInAckQueue = 3000; private long waitTimeMillsInAdminBrokerQueue = 5 * 1000; private long startAcceptSendRequestTimeStamp = 0L; private boolean traceOn = true; // Switch of filter bit map calculation. // If switch on: // 1. Calculate filter bit map when construct queue. // 2. Filter bit map will be saved to consume queue extend file if allowed. private boolean enableCalcFilterBitMap = false; //Reject the pull consumer instance to pull messages from broker. private boolean rejectPullConsumerEnable = false; // Expect num of consumers will use filter. private int expectConsumerNumUseFilter = 32; // Error rate of bloom filter, 1~100. private int maxErrorRateOfBloomFilter = 20; //how long to clean filter data after dead.Default: 24h private long filterDataCleanTimeSpan = 24 * 3600 * 1000; // whether do filter when retry. private boolean filterSupportRetry = false; private boolean enablePropertyFilter = false; private boolean compressedRegister = false; private boolean forceRegister = true; /** * This configurable item defines interval of topics registration of broker to name server. Allowing values are * between 10,000 and 60,000 milliseconds. */ private int registerNameServerPeriod = 1000 * 30; /** * This configurable item defines interval of update name server address. Default: 120 * 1000 milliseconds */ private int updateNameServerAddrPeriod = 1000 * 120; /** * the interval to send heartbeat to name server for liveness detection. */ private int brokerHeartbeatInterval = 1000; /** * How long the broker will be considered as inactive by nameserver since last heartbeat. Effective only if * enableSlaveActingMaster is true */ private long brokerNotActiveTimeoutMillis = 10 * 1000; private boolean enableNetWorkFlowControl = false; private boolean enableBroadcastOffsetStore = true; private long broadcastOffsetExpireSecond = 2 * 60; private long broadcastOffsetExpireMaxSecond = 5 * 60; private int popPollingSize = 1024; private int popPollingMapSize = 100000; private int popPollingMapExpireTimeSeconds = 60 * 10; // 20w cost 200M heap memory. private long maxPopPollingSize = 100000; private int reviveQueueNum = 8; private long reviveInterval = 1000; private long reviveMaxSlow = 3; private long reviveScanTime = 10000; private boolean enableSkipLongAwaitingAck = false; private long reviveAckWaitMs = TimeUnit.MINUTES.toMillis(3); private boolean enablePopLog = false; private boolean enablePopBufferMerge = false; private int popCkStayBufferTime = 10 * 1000; private int popCkStayBufferTimeOut = 3 * 1000; private int popCkMaxBufferSize = 200000; private int popCkOffsetMaxQueueSize = 20000; private boolean enablePopBatchAck = false; // set the interval to the maxFilterMessageSize in MessageStoreConfig divided by the cq unit size private long popLongPollingForceNotifyInterval = 800; private boolean enableNotifyBeforePopCalculateLag = true; private boolean enableNotifyAfterPopOrderLockRelease = true; private boolean initPopOffsetByCheckMsgInMem = true; // read message from pop retry topic v1, for the compatibility, will be removed in the future version private boolean retrieveMessageFromPopRetryTopicV1 = true; private boolean enableRetryTopicV2 = false; private int popFromRetryProbability = 20; // pop retry probability for priority mode private int popFromRetryProbabilityForPriority = 0; // 0 as the lowest priority if true private boolean priorityOrderAsc = true; private boolean popConsumerFSServiceInit = true; private boolean popConsumerKVServiceLog = false; private boolean popConsumerKVServiceInit = false; private boolean popConsumerKVServiceEnable = false; private int popReviveMaxReturnSizePerRead = 16 * 1024; private int popReviveConcurrency = 32; private int popReviveMaxAttemptTimes = 16; private boolean popReviveSkipIfGroupAbsent = true; // each message queue will have a corresponding retry queue private boolean useSeparateRetryQueue = false; private boolean realTimeNotifyConsumerChange = true; private boolean useMessageFilterForNotification = true; private int maxMessageFilterNumForNotification = 64; private boolean litePullMessageEnable = true; // The period to sync broker member group from namesrv, default value is 1 second private int syncBrokerMemberGroupPeriod = 1000; /** * the interval of pulling topic information from the named server */ private long loadBalancePollNameServerInterval = 1000 * 30; /** * the interval of cleaning */ private int cleanOfflineBrokerInterval = 1000 * 30; private boolean serverLoadBalancerEnable = true; private MessageRequestMode defaultMessageRequestMode = MessageRequestMode.PULL; private int defaultPopShareQueueNum = -1; /** * The minimum time of the transactional message to be checked firstly, one message only exceed this time interval * that can be checked. */ @ImportantField private long transactionTimeOut = 6 * 1000; /** * The maximum number of times the message was checked, if exceed this value, this message will be discarded. */ @ImportantField private int transactionCheckMax = 15; /** * Transaction message check interval. */ @ImportantField private long transactionCheckInterval = 30 * 1000; private long transactionMetricFlushInterval = 10 * 1000; private int transactionCheckRocksdbCoreThreads = 2; private int transactionCheckRocksdbMaxThreads = 5; private int transactionCheckRocksdbQueueCapacity = 2000; /** * transaction batch op message */ private int transactionOpMsgMaxSize = 4096; private int transactionOpBatchInterval = 3000; /** * Acl feature switch */ @ImportantField private boolean aclEnable = false; private boolean storeReplyMessageEnable = true; private boolean enableDetailStat = true; private boolean autoDeleteUnusedStats = true; /** * Whether to distinguish log paths when multiple brokers are deployed on the same machine */ private boolean isolateLogEnable = false; private long forwardTimeout = 3 * 1000; /** * Slave will act master when failover. For example, if master down, timer or transaction message which is expire in slave will * put to master (master of the same process in broker container mode or other masters in cluster when enableFailoverRemotingActing is true) * when enableSlaveActingMaster is true */ private boolean enableSlaveActingMaster = false; private boolean enableRemoteEscape = false; private boolean skipPreOnline = false; private boolean asyncSendEnable = true; private boolean useServerSideResetOffset = true; private long consumerOffsetUpdateVersionStep = 500; private long delayOffsetUpdateVersionStep = 200; /** * Whether to lock quorum replicas. * * True: need to lock quorum replicas succeed. False: only need to lock one replica succeed. */ private boolean lockInStrictMode = false; private boolean compatibleWithOldNameSrv = true; /** * Is startup controller mode, which support auto switch broker's role. */ private boolean enableControllerMode = false; private String controllerAddr = ""; private boolean fetchControllerAddrByDnsLookup = false; private long syncBrokerMetadataPeriod = 5 * 1000; private long checkSyncStateSetPeriod = 5 * 1000; private long syncControllerMetadataPeriod = 10 * 1000; private long controllerHeartBeatTimeoutMills = 10 * 1000; private boolean validateSystemTopicWhenUpdateTopic = true; /** * It is an important basis for the controller to choose the broker master. * The lower the value of brokerElectionPriority, the higher the priority of the broker being selected as the master. * You can set a lower priority for the broker with better machine conditions. */ private int brokerElectionPriority = Integer.MAX_VALUE; private boolean useStaticSubscription = false; private MetricsExporterType metricsExporterType = MetricsExporterType.DISABLE; private int metricsOtelCardinalityLimit = 50 * 1000; private String metricsGrpcExporterTarget = ""; private String metricsGrpcExporterHeader = ""; private long metricGrpcExporterTimeOutInMills = 3 * 1000; private long metricGrpcExporterIntervalInMills = 60 * 1000; private long metricLoggingExporterIntervalInMills = 10 * 1000; private int metricsPromExporterPort = 5557; private String metricsPromExporterHost = ""; // Label pairs in CSV. Each label follows pattern of Key:Value. eg: instance_id:xxx,uid:xxx private String metricsLabel = ""; private boolean metricsInDelta = false; private boolean enableRemotingMetrics = true; private boolean enableMessageStoreMetrics = true; private boolean enablePopMetrics = true; private boolean enableConnectionMetrics = true; private boolean enableTransactionMetrics = true; private boolean enableStatsMetrics = true; private boolean enableRequestMetrics = true; private boolean enableLagAndDlqMetrics = true; private long channelExpiredTimeout = 1000 * 120; private long subscriptionExpiredTimeout = 1000 * 60 * 10; /** * Estimate accumulation or not when subscription filter type is tag and is not SUB_ALL. */ private boolean estimateAccumulation = true; private boolean coldCtrStrategyEnable = false; private boolean usePIDColdCtrStrategy = true; private long cgColdReadThreshold = 3 * 1024 * 1024; private long globalColdReadThreshold = 100 * 1024 * 1024; /** * The interval to fetch namesrv addr, default value is 10 second */ private long fetchNamesrvAddrInterval = 10 * 1000; /** * Pop response returns the actual retry topic rather than tampering with the original topic */ private boolean popResponseReturnActualRetryTopic = false; /** * If both the deleteTopicWithBrokerRegistration flag in the NameServer configuration and this flag are set to true, * it guarantees the ultimate consistency of data between the broker and the nameserver during topic deletion. */ private boolean enableSingleTopicRegister = false; private boolean enableMixedMessageType = false; /** * This flag and deleteTopicWithBrokerRegistration flag in the NameServer cannot be set to true at the same time, * otherwise there will be a loss of routing */ private boolean enableSplitRegistration = false; private boolean enableSplitMetadata = true; private int splitMetadataSize = 2000; private long popInflightMessageThreshold = 10000; private boolean enablePopMessageThreshold = false; private boolean enableFastChannelEventProcess = false; private boolean printChannelGroups = false; private int printChannelGroupsMinNum = 5; private int splitRegistrationSize = 800; /** * Config in this black list will be not allowed to update by command. * Try to update this config black list by restart process. * Try to update configures in black list by restart process. */ private String configBlackList = "configBlackList;brokerConfigPath"; // if false, will still rewrite ck after max times 17 private boolean skipWhenCKRePutReachMaxTimes = false; private boolean appendAckAsync = false; private boolean appendCkAsync = false; private boolean clearRetryTopicWhenDeleteTopic = true; private boolean enableLmqStats = false; /** * V2 is recommended in cases where LMQ feature is extensively used. */ private String configManagerVersion = ConfigManagerVersion.V1.getVersion(); /** * Whether to use a single RocksDB instance with multiple column families for all configs * instead of separate RocksDB instances for Topic, Group, and Offset configs */ private boolean useSingleRocksDBForAllConfigs = false; private boolean allowRecallWhenBrokerNotWriteable = true; private boolean recallMessageEnable = false; private boolean enableRegisterProducer = true; private boolean enableCreateSysGroup = true; private boolean enableLiteEventMode = true; private long liteEventCheckInterval = 10 * 1000; private long liteTtlCheckInterval = 120 * 1000; private long minLiteTTl = 15 * 60 * 1000; private long liteSubscriptionCheckInterval = TimeUnit.MINUTES.toMillis(2); private long liteSubscriptionCheckTimeoutMills = TimeUnit.MINUTES.toMillis(3); // make sense for rocksdb store private boolean persistConsumerOffsetIncrementally = false; private long maxLiteSubscriptionCount = 100000; private boolean enableLitePopLog = false; private int maxClientEventCount = 100; private long liteEventFullDispatchDelayTime = 10 * 1000; // lite metrics // whether to collect storeTime in popLiteProcessor private boolean liteLagLatencyCollectEnable = false; private boolean liteLagLatencyMetricsEnable = false; private boolean liteLagCountMetricsEnable = false; private int liteLagLatencyTopK = 50; public String getConfigBlackList() { return configBlackList; } public void setConfigBlackList(String configBlackList) { this.configBlackList = configBlackList; } public long getMaxPopPollingSize() { return maxPopPollingSize; } public void setMaxPopPollingSize(long maxPopPollingSize) { this.maxPopPollingSize = maxPopPollingSize; } public int getReviveQueueNum() { return reviveQueueNum; } public void setReviveQueueNum(int reviveQueueNum) { this.reviveQueueNum = reviveQueueNum; } public long getReviveInterval() { return reviveInterval; } public void setReviveInterval(long reviveInterval) { this.reviveInterval = reviveInterval; } public int getPopCkStayBufferTime() { return popCkStayBufferTime; } public void setPopCkStayBufferTime(int popCkStayBufferTime) { this.popCkStayBufferTime = popCkStayBufferTime; } public int getPopCkStayBufferTimeOut() { return popCkStayBufferTimeOut; } public void setPopCkStayBufferTimeOut(int popCkStayBufferTimeOut) { this.popCkStayBufferTimeOut = popCkStayBufferTimeOut; } public int getPopPollingMapSize() { return popPollingMapSize; } public void setPopPollingMapSize(int popPollingMapSize) { this.popPollingMapSize = popPollingMapSize; } public int getPopPollingMapExpireTimeSeconds() { return popPollingMapExpireTimeSeconds; } public void setPopPollingMapExpireTimeSeconds(int popPollingMapExpireTimeSeconds) { this.popPollingMapExpireTimeSeconds = popPollingMapExpireTimeSeconds; } public long getReviveScanTime() { return reviveScanTime; } public void setReviveScanTime(long reviveScanTime) { this.reviveScanTime = reviveScanTime; } public long getReviveMaxSlow() { return reviveMaxSlow; } public void setReviveMaxSlow(long reviveMaxSlow) { this.reviveMaxSlow = reviveMaxSlow; } public int getPopPollingSize() { return popPollingSize; } public void setPopPollingSize(int popPollingSize) { this.popPollingSize = popPollingSize; } public boolean isEnablePopBufferMerge() { return enablePopBufferMerge; } public void setEnablePopBufferMerge(boolean enablePopBufferMerge) { this.enablePopBufferMerge = enablePopBufferMerge; } public int getPopCkMaxBufferSize() { return popCkMaxBufferSize; } public void setPopCkMaxBufferSize(int popCkMaxBufferSize) { this.popCkMaxBufferSize = popCkMaxBufferSize; } public int getPopCkOffsetMaxQueueSize() { return popCkOffsetMaxQueueSize; } public void setPopCkOffsetMaxQueueSize(int popCkOffsetMaxQueueSize) { this.popCkOffsetMaxQueueSize = popCkOffsetMaxQueueSize; } public boolean isEnablePopBatchAck() { return enablePopBatchAck; } public void setEnablePopBatchAck(boolean enablePopBatchAck) { this.enablePopBatchAck = enablePopBatchAck; } public boolean isEnableSkipLongAwaitingAck() { return enableSkipLongAwaitingAck; } public void setEnableSkipLongAwaitingAck(boolean enableSkipLongAwaitingAck) { this.enableSkipLongAwaitingAck = enableSkipLongAwaitingAck; } public long getReviveAckWaitMs() { return reviveAckWaitMs; } public void setReviveAckWaitMs(long reviveAckWaitMs) { this.reviveAckWaitMs = reviveAckWaitMs; } public boolean isEnablePopLog() { return enablePopLog; } public void setEnablePopLog(boolean enablePopLog) { this.enablePopLog = enablePopLog; } public int getPopFromRetryProbability() { return popFromRetryProbability; } public void setPopFromRetryProbability(int popFromRetryProbability) { this.popFromRetryProbability = popFromRetryProbability; } public boolean isPopConsumerFSServiceInit() { return popConsumerFSServiceInit; } public void setPopConsumerFSServiceInit(boolean popConsumerFSServiceInit) { this.popConsumerFSServiceInit = popConsumerFSServiceInit; } public boolean isPopConsumerKVServiceLog() { return popConsumerKVServiceLog; } public void setPopConsumerKVServiceLog(boolean popConsumerKVServiceLog) { this.popConsumerKVServiceLog = popConsumerKVServiceLog; } public boolean isPopConsumerKVServiceInit() { return popConsumerKVServiceInit; } public void setPopConsumerKVServiceInit(boolean popConsumerKVServiceInit) { this.popConsumerKVServiceInit = popConsumerKVServiceInit; } public boolean isPopConsumerKVServiceEnable() { return popConsumerKVServiceEnable; } public void setPopConsumerKVServiceEnable(boolean popConsumerKVServiceEnable) { this.popConsumerKVServiceEnable = popConsumerKVServiceEnable; } public int getPopReviveConcurrency() { return popReviveConcurrency; } public void setPopReviveConcurrency(int popReviveConcurrency) { this.popReviveConcurrency = popReviveConcurrency; } public int getPopReviveMaxReturnSizePerRead() { return popReviveMaxReturnSizePerRead; } public void setPopReviveMaxReturnSizePerRead(int popReviveMaxReturnSizePerRead) { this.popReviveMaxReturnSizePerRead = popReviveMaxReturnSizePerRead; } public int getPopReviveMaxAttemptTimes() { return popReviveMaxAttemptTimes; } public void setPopReviveMaxAttemptTimes(int popReviveMaxAttemptTimes) { this.popReviveMaxAttemptTimes = popReviveMaxAttemptTimes; } public boolean isPopReviveSkipIfGroupAbsent() { return popReviveSkipIfGroupAbsent; } public void setPopReviveSkipIfGroupAbsent(boolean popReviveSkipIfGroupAbsent) { this.popReviveSkipIfGroupAbsent = popReviveSkipIfGroupAbsent; } public boolean isTraceOn() { return traceOn; } public void setTraceOn(final boolean traceOn) { this.traceOn = traceOn; } public long getStartAcceptSendRequestTimeStamp() { return startAcceptSendRequestTimeStamp; } public void setStartAcceptSendRequestTimeStamp(final long startAcceptSendRequestTimeStamp) { this.startAcceptSendRequestTimeStamp = startAcceptSendRequestTimeStamp; } public long getWaitTimeMillsInSendQueue() { return waitTimeMillsInSendQueue; } public void setWaitTimeMillsInSendQueue(final long waitTimeMillsInSendQueue) { this.waitTimeMillsInSendQueue = waitTimeMillsInSendQueue; } public long getConsumerFallbehindThreshold() { return consumerFallbehindThreshold; } public void setConsumerFallbehindThreshold(final long consumerFallbehindThreshold) { this.consumerFallbehindThreshold = consumerFallbehindThreshold; } public boolean isBrokerFastFailureEnable() { return brokerFastFailureEnable; } public void setBrokerFastFailureEnable(final boolean brokerFastFailureEnable) { this.brokerFastFailureEnable = brokerFastFailureEnable; } public long getWaitTimeMillsInPullQueue() { return waitTimeMillsInPullQueue; } public void setWaitTimeMillsInPullQueue(final long waitTimeMillsInPullQueue) { this.waitTimeMillsInPullQueue = waitTimeMillsInPullQueue; } public boolean isDisableConsumeIfConsumerReadSlowly() { return disableConsumeIfConsumerReadSlowly; } public void setDisableConsumeIfConsumerReadSlowly(final boolean disableConsumeIfConsumerReadSlowly) { this.disableConsumeIfConsumerReadSlowly = disableConsumeIfConsumerReadSlowly; } public boolean isSlaveReadEnable() { return slaveReadEnable; } public void setSlaveReadEnable(final boolean slaveReadEnable) { this.slaveReadEnable = slaveReadEnable; } public int getRegisterBrokerTimeoutMills() { return registerBrokerTimeoutMills; } public void setRegisterBrokerTimeoutMills(final int registerBrokerTimeoutMills) { this.registerBrokerTimeoutMills = registerBrokerTimeoutMills; } public String getRegionId() { return regionId; } public void setRegionId(final String regionId) { this.regionId = regionId; } public boolean isTransferMsgByHeap() { return transferMsgByHeap; } public void setTransferMsgByHeap(final boolean transferMsgByHeap) { this.transferMsgByHeap = transferMsgByHeap; } public String getMessageStorePlugIn() { return messageStorePlugIn; } public void setMessageStorePlugIn(String messageStorePlugIn) { this.messageStorePlugIn = messageStorePlugIn; } public boolean isHighSpeedMode() { return highSpeedMode; } public void setHighSpeedMode(final boolean highSpeedMode) { this.highSpeedMode = highSpeedMode; } public int getBrokerPermission() { return brokerPermission; } public void setBrokerPermission(int brokerPermission) { this.brokerPermission = brokerPermission; } public int getDefaultTopicQueueNums() { return defaultTopicQueueNums; } public void setDefaultTopicQueueNums(int defaultTopicQueueNums) { this.defaultTopicQueueNums = defaultTopicQueueNums; } public boolean isAutoCreateTopicEnable() { return autoCreateTopicEnable; } public void setAutoCreateTopicEnable(boolean autoCreateTopic) { this.autoCreateTopicEnable = autoCreateTopic; } public String getBrokerIP1() { return brokerIP1; } public void setBrokerIP1(String brokerIP1) { this.brokerIP1 = brokerIP1; } public String getBrokerIP2() { return brokerIP2; } public void setBrokerIP2(String brokerIP2) { this.brokerIP2 = brokerIP2; } public int getSendMessageThreadPoolNums() { return sendMessageThreadPoolNums; } public void setSendMessageThreadPoolNums(int sendMessageThreadPoolNums) { this.sendMessageThreadPoolNums = sendMessageThreadPoolNums; } public int getPutMessageFutureThreadPoolNums() { return putMessageFutureThreadPoolNums; } public void setPutMessageFutureThreadPoolNums(int putMessageFutureThreadPoolNums) { this.putMessageFutureThreadPoolNums = putMessageFutureThreadPoolNums; } public int getPullMessageThreadPoolNums() { return pullMessageThreadPoolNums; } public void setPullMessageThreadPoolNums(int pullMessageThreadPoolNums) { this.pullMessageThreadPoolNums = pullMessageThreadPoolNums; } public int getAckMessageThreadPoolNums() { return ackMessageThreadPoolNums; } public void setAckMessageThreadPoolNums(int ackMessageThreadPoolNums) { this.ackMessageThreadPoolNums = ackMessageThreadPoolNums; } public int getProcessReplyMessageThreadPoolNums() { return processReplyMessageThreadPoolNums; } public void setProcessReplyMessageThreadPoolNums(int processReplyMessageThreadPoolNums) { this.processReplyMessageThreadPoolNums = processReplyMessageThreadPoolNums; } public int getQueryMessageThreadPoolNums() { return queryMessageThreadPoolNums; } public void setQueryMessageThreadPoolNums(final int queryMessageThreadPoolNums) { this.queryMessageThreadPoolNums = queryMessageThreadPoolNums; } public int getAdminBrokerThreadPoolNums() { return adminBrokerThreadPoolNums; } public void setAdminBrokerThreadPoolNums(int adminBrokerThreadPoolNums) { this.adminBrokerThreadPoolNums = adminBrokerThreadPoolNums; } public int getFlushConsumerOffsetInterval() { return flushConsumerOffsetInterval; } public void setFlushConsumerOffsetInterval(int flushConsumerOffsetInterval) { this.flushConsumerOffsetInterval = flushConsumerOffsetInterval; } public int getFlushConsumerOffsetHistoryInterval() { return flushConsumerOffsetHistoryInterval; } public void setFlushConsumerOffsetHistoryInterval(int flushConsumerOffsetHistoryInterval) { this.flushConsumerOffsetHistoryInterval = flushConsumerOffsetHistoryInterval; } public boolean isClusterTopicEnable() { return clusterTopicEnable; } public void setClusterTopicEnable(boolean clusterTopicEnable) { this.clusterTopicEnable = clusterTopicEnable; } public String getNamesrvAddr() { return namesrvAddr; } public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; } public boolean isAutoCreateSubscriptionGroup() { return autoCreateSubscriptionGroup; } public void setAutoCreateSubscriptionGroup(boolean autoCreateSubscriptionGroup) { this.autoCreateSubscriptionGroup = autoCreateSubscriptionGroup; } public String getBrokerConfigPath() { return brokerConfigPath; } public void setBrokerConfigPath(String brokerConfigPath) { this.brokerConfigPath = brokerConfigPath; } public String getRocketmqHome() { return rocketmqHome; } public void setRocketmqHome(String rocketmqHome) { this.rocketmqHome = rocketmqHome; } public int getListenPort() { return listenPort; } public void setListenPort(int listenPort) { this.listenPort = listenPort; } public int getLitePullMessageThreadPoolNums() { return litePullMessageThreadPoolNums; } public void setLitePullMessageThreadPoolNums(int litePullMessageThreadPoolNums) { this.litePullMessageThreadPoolNums = litePullMessageThreadPoolNums; } public int getLitePullThreadPoolQueueCapacity() { return litePullThreadPoolQueueCapacity; } public void setLitePullThreadPoolQueueCapacity(int litePullThreadPoolQueueCapacity) { this.litePullThreadPoolQueueCapacity = litePullThreadPoolQueueCapacity; } public int getAdminBrokerThreadPoolQueueCapacity() { return adminBrokerThreadPoolQueueCapacity; } public void setAdminBrokerThreadPoolQueueCapacity(int adminBrokerThreadPoolQueueCapacity) { this.adminBrokerThreadPoolQueueCapacity = adminBrokerThreadPoolQueueCapacity; } public int getLoadBalanceThreadPoolQueueCapacity() { return loadBalanceThreadPoolQueueCapacity; } public void setLoadBalanceThreadPoolQueueCapacity(int loadBalanceThreadPoolQueueCapacity) { this.loadBalanceThreadPoolQueueCapacity = loadBalanceThreadPoolQueueCapacity; } public int getSendHeartbeatTimeoutMillis() { return sendHeartbeatTimeoutMillis; } public void setSendHeartbeatTimeoutMillis(int sendHeartbeatTimeoutMillis) { this.sendHeartbeatTimeoutMillis = sendHeartbeatTimeoutMillis; } public long getWaitTimeMillsInLitePullQueue() { return waitTimeMillsInLitePullQueue; } public void setWaitTimeMillsInLitePullQueue(long waitTimeMillsInLitePullQueue) { this.waitTimeMillsInLitePullQueue = waitTimeMillsInLitePullQueue; } public boolean isLitePullMessageEnable() { return litePullMessageEnable; } public void setLitePullMessageEnable(boolean litePullMessageEnable) { this.litePullMessageEnable = litePullMessageEnable; } public int getSyncBrokerMemberGroupPeriod() { return syncBrokerMemberGroupPeriod; } public void setSyncBrokerMemberGroupPeriod(int syncBrokerMemberGroupPeriod) { this.syncBrokerMemberGroupPeriod = syncBrokerMemberGroupPeriod; } public boolean isRejectTransactionMessage() { return rejectTransactionMessage; } public void setRejectTransactionMessage(boolean rejectTransactionMessage) { this.rejectTransactionMessage = rejectTransactionMessage; } public boolean isFetchNamesrvAddrByAddressServer() { return fetchNamesrvAddrByAddressServer; } public void setFetchNamesrvAddrByAddressServer(boolean fetchNamesrvAddrByAddressServer) { this.fetchNamesrvAddrByAddressServer = fetchNamesrvAddrByAddressServer; } public int getSendThreadPoolQueueCapacity() { return sendThreadPoolQueueCapacity; } public void setSendThreadPoolQueueCapacity(int sendThreadPoolQueueCapacity) { this.sendThreadPoolQueueCapacity = sendThreadPoolQueueCapacity; } public int getPutThreadPoolQueueCapacity() { return putThreadPoolQueueCapacity; } public void setPutThreadPoolQueueCapacity(int putThreadPoolQueueCapacity) { this.putThreadPoolQueueCapacity = putThreadPoolQueueCapacity; } public int getPullThreadPoolQueueCapacity() { return pullThreadPoolQueueCapacity; } public void setPullThreadPoolQueueCapacity(int pullThreadPoolQueueCapacity) { this.pullThreadPoolQueueCapacity = pullThreadPoolQueueCapacity; } public int getAckThreadPoolQueueCapacity() { return ackThreadPoolQueueCapacity; } public void setAckThreadPoolQueueCapacity(int ackThreadPoolQueueCapacity) { this.ackThreadPoolQueueCapacity = ackThreadPoolQueueCapacity; } public int getReplyThreadPoolQueueCapacity() { return replyThreadPoolQueueCapacity; } public void setReplyThreadPoolQueueCapacity(int replyThreadPoolQueueCapacity) { this.replyThreadPoolQueueCapacity = replyThreadPoolQueueCapacity; } public int getQueryThreadPoolQueueCapacity() { return queryThreadPoolQueueCapacity; } public void setQueryThreadPoolQueueCapacity(final int queryThreadPoolQueueCapacity) { this.queryThreadPoolQueueCapacity = queryThreadPoolQueueCapacity; } public boolean isBrokerTopicEnable() { return brokerTopicEnable; } public void setBrokerTopicEnable(boolean brokerTopicEnable) { this.brokerTopicEnable = brokerTopicEnable; } public boolean isLongPollingEnable() { return longPollingEnable; } public void setLongPollingEnable(boolean longPollingEnable) { this.longPollingEnable = longPollingEnable; } public boolean isNotifyConsumerIdsChangedEnable() { return notifyConsumerIdsChangedEnable; } public void setNotifyConsumerIdsChangedEnable(boolean notifyConsumerIdsChangedEnable) { this.notifyConsumerIdsChangedEnable = notifyConsumerIdsChangedEnable; } public long getShortPollingTimeMills() { return shortPollingTimeMills; } public void setShortPollingTimeMills(long shortPollingTimeMills) { this.shortPollingTimeMills = shortPollingTimeMills; } public int getClientManageThreadPoolNums() { return clientManageThreadPoolNums; } public void setClientManageThreadPoolNums(int clientManageThreadPoolNums) { this.clientManageThreadPoolNums = clientManageThreadPoolNums; } public int getClientManagerThreadPoolQueueCapacity() { return clientManagerThreadPoolQueueCapacity; } public void setClientManagerThreadPoolQueueCapacity(int clientManagerThreadPoolQueueCapacity) { this.clientManagerThreadPoolQueueCapacity = clientManagerThreadPoolQueueCapacity; } public int getConsumerManagerThreadPoolQueueCapacity() { return consumerManagerThreadPoolQueueCapacity; } public void setConsumerManagerThreadPoolQueueCapacity(int consumerManagerThreadPoolQueueCapacity) { this.consumerManagerThreadPoolQueueCapacity = consumerManagerThreadPoolQueueCapacity; } public int getConsumerManageThreadPoolNums() { return consumerManageThreadPoolNums; } public void setConsumerManageThreadPoolNums(int consumerManageThreadPoolNums) { this.consumerManageThreadPoolNums = consumerManageThreadPoolNums; } public int getCommercialBaseCount() { return commercialBaseCount; } public void setCommercialBaseCount(int commercialBaseCount) { this.commercialBaseCount = commercialBaseCount; } public boolean isEnableCalcFilterBitMap() { return enableCalcFilterBitMap; } public void setEnableCalcFilterBitMap(boolean enableCalcFilterBitMap) { this.enableCalcFilterBitMap = enableCalcFilterBitMap; } public int getExpectConsumerNumUseFilter() { return expectConsumerNumUseFilter; } public void setExpectConsumerNumUseFilter(int expectConsumerNumUseFilter) { this.expectConsumerNumUseFilter = expectConsumerNumUseFilter; } public int getMaxErrorRateOfBloomFilter() { return maxErrorRateOfBloomFilter; } public void setMaxErrorRateOfBloomFilter(int maxErrorRateOfBloomFilter) { this.maxErrorRateOfBloomFilter = maxErrorRateOfBloomFilter; } public long getFilterDataCleanTimeSpan() { return filterDataCleanTimeSpan; } public void setFilterDataCleanTimeSpan(long filterDataCleanTimeSpan) { this.filterDataCleanTimeSpan = filterDataCleanTimeSpan; } public boolean isFilterSupportRetry() { return filterSupportRetry; } public void setFilterSupportRetry(boolean filterSupportRetry) { this.filterSupportRetry = filterSupportRetry; } public boolean isEnablePropertyFilter() { return enablePropertyFilter; } public void setEnablePropertyFilter(boolean enablePropertyFilter) { this.enablePropertyFilter = enablePropertyFilter; } public boolean isCompressedRegister() { return compressedRegister; } public void setCompressedRegister(boolean compressedRegister) { this.compressedRegister = compressedRegister; } public boolean isForceRegister() { return forceRegister; } public void setForceRegister(boolean forceRegister) { this.forceRegister = forceRegister; } public int getHeartbeatThreadPoolQueueCapacity() { return heartbeatThreadPoolQueueCapacity; } public void setHeartbeatThreadPoolQueueCapacity(int heartbeatThreadPoolQueueCapacity) { this.heartbeatThreadPoolQueueCapacity = heartbeatThreadPoolQueueCapacity; } public int getHeartbeatThreadPoolNums() { return heartbeatThreadPoolNums; } public void setHeartbeatThreadPoolNums(int heartbeatThreadPoolNums) { this.heartbeatThreadPoolNums = heartbeatThreadPoolNums; } public long getWaitTimeMillsInHeartbeatQueue() { return waitTimeMillsInHeartbeatQueue; } public void setWaitTimeMillsInHeartbeatQueue(long waitTimeMillsInHeartbeatQueue) { this.waitTimeMillsInHeartbeatQueue = waitTimeMillsInHeartbeatQueue; } public int getRegisterNameServerPeriod() { return registerNameServerPeriod; } public void setRegisterNameServerPeriod(int registerNameServerPeriod) { this.registerNameServerPeriod = registerNameServerPeriod; } public long getTransactionTimeOut() { return transactionTimeOut; } public void setTransactionTimeOut(long transactionTimeOut) { this.transactionTimeOut = transactionTimeOut; } public int getTransactionCheckMax() { return transactionCheckMax; } public void setTransactionCheckMax(int transactionCheckMax) { this.transactionCheckMax = transactionCheckMax; } public long getTransactionCheckInterval() { return transactionCheckInterval; } public void setTransactionCheckInterval(long transactionCheckInterval) { this.transactionCheckInterval = transactionCheckInterval; } public int getEndTransactionThreadPoolNums() { return endTransactionThreadPoolNums; } public void setEndTransactionThreadPoolNums(int endTransactionThreadPoolNums) { this.endTransactionThreadPoolNums = endTransactionThreadPoolNums; } public int getEndTransactionPoolQueueCapacity() { return endTransactionPoolQueueCapacity; } public void setEndTransactionPoolQueueCapacity(int endTransactionPoolQueueCapacity) { this.endTransactionPoolQueueCapacity = endTransactionPoolQueueCapacity; } public long getWaitTimeMillsInTransactionQueue() { return waitTimeMillsInTransactionQueue; } public void setWaitTimeMillsInTransactionQueue(long waitTimeMillsInTransactionQueue) { this.waitTimeMillsInTransactionQueue = waitTimeMillsInTransactionQueue; } public String getMsgTraceTopicName() { return msgTraceTopicName; } public long getWaitTimeMillsInAdminBrokerQueue() { return waitTimeMillsInAdminBrokerQueue; } public void setWaitTimeMillsInAdminBrokerQueue(long waitTimeMillsInAdminBrokerQueue) { this.waitTimeMillsInAdminBrokerQueue = waitTimeMillsInAdminBrokerQueue; } public void setMsgTraceTopicName(String msgTraceTopicName) { this.msgTraceTopicName = msgTraceTopicName; } public boolean isTraceTopicEnable() { return traceTopicEnable; } public void setTraceTopicEnable(boolean traceTopicEnable) { this.traceTopicEnable = traceTopicEnable; } public void setAclEnable(boolean aclEnable) { this.aclEnable = aclEnable; } public boolean isStoreReplyMessageEnable() { return storeReplyMessageEnable; } public void setStoreReplyMessageEnable(boolean storeReplyMessageEnable) { this.storeReplyMessageEnable = storeReplyMessageEnable; } public boolean isEnableDetailStat() { return enableDetailStat; } public void setEnableDetailStat(boolean enableDetailStat) { this.enableDetailStat = enableDetailStat; } public boolean isAutoDeleteUnusedStats() { return autoDeleteUnusedStats; } public void setAutoDeleteUnusedStats(boolean autoDeleteUnusedStats) { this.autoDeleteUnusedStats = autoDeleteUnusedStats; } public long getLoadBalancePollNameServerInterval() { return loadBalancePollNameServerInterval; } public void setLoadBalancePollNameServerInterval(long loadBalancePollNameServerInterval) { this.loadBalancePollNameServerInterval = loadBalancePollNameServerInterval; } public int getCleanOfflineBrokerInterval() { return cleanOfflineBrokerInterval; } public void setCleanOfflineBrokerInterval(int cleanOfflineBrokerInterval) { this.cleanOfflineBrokerInterval = cleanOfflineBrokerInterval; } public int getLoadBalanceProcessorThreadPoolNums() { return loadBalanceProcessorThreadPoolNums; } public void setLoadBalanceProcessorThreadPoolNums(int loadBalanceProcessorThreadPoolNums) { this.loadBalanceProcessorThreadPoolNums = loadBalanceProcessorThreadPoolNums; } public boolean isServerLoadBalancerEnable() { return serverLoadBalancerEnable; } public void setServerLoadBalancerEnable(boolean serverLoadBalancerEnable) { this.serverLoadBalancerEnable = serverLoadBalancerEnable; } public MessageRequestMode getDefaultMessageRequestMode() { return defaultMessageRequestMode; } public void setDefaultMessageRequestMode(String defaultMessageRequestMode) { this.defaultMessageRequestMode = MessageRequestMode.valueOf(defaultMessageRequestMode); } public int getDefaultPopShareQueueNum() { return defaultPopShareQueueNum; } public void setDefaultPopShareQueueNum(int defaultPopShareQueueNum) { this.defaultPopShareQueueNum = defaultPopShareQueueNum; } public long getForwardTimeout() { return forwardTimeout; } public void setForwardTimeout(long timeout) { this.forwardTimeout = timeout; } public int getBrokerHeartbeatInterval() { return brokerHeartbeatInterval; } public void setBrokerHeartbeatInterval(int brokerHeartbeatInterval) { this.brokerHeartbeatInterval = brokerHeartbeatInterval; } public long getBrokerNotActiveTimeoutMillis() { return brokerNotActiveTimeoutMillis; } public void setBrokerNotActiveTimeoutMillis(long brokerNotActiveTimeoutMillis) { this.brokerNotActiveTimeoutMillis = brokerNotActiveTimeoutMillis; } public boolean isEnableNetWorkFlowControl() { return enableNetWorkFlowControl; } public void setEnableNetWorkFlowControl(boolean enableNetWorkFlowControl) { this.enableNetWorkFlowControl = enableNetWorkFlowControl; } public long getPopLongPollingForceNotifyInterval() { return popLongPollingForceNotifyInterval; } public void setPopLongPollingForceNotifyInterval(long popLongPollingForceNotifyInterval) { this.popLongPollingForceNotifyInterval = popLongPollingForceNotifyInterval; } public boolean isEnableNotifyBeforePopCalculateLag() { return enableNotifyBeforePopCalculateLag; } public void setEnableNotifyBeforePopCalculateLag(boolean enableNotifyBeforePopCalculateLag) { this.enableNotifyBeforePopCalculateLag = enableNotifyBeforePopCalculateLag; } public boolean isEnableNotifyAfterPopOrderLockRelease() { return enableNotifyAfterPopOrderLockRelease; } public void setEnableNotifyAfterPopOrderLockRelease(boolean enableNotifyAfterPopOrderLockRelease) { this.enableNotifyAfterPopOrderLockRelease = enableNotifyAfterPopOrderLockRelease; } public boolean isInitPopOffsetByCheckMsgInMem() { return initPopOffsetByCheckMsgInMem; } public void setInitPopOffsetByCheckMsgInMem(boolean initPopOffsetByCheckMsgInMem) { this.initPopOffsetByCheckMsgInMem = initPopOffsetByCheckMsgInMem; } public boolean isRetrieveMessageFromPopRetryTopicV1() { return retrieveMessageFromPopRetryTopicV1; } public void setRetrieveMessageFromPopRetryTopicV1(boolean retrieveMessageFromPopRetryTopicV1) { this.retrieveMessageFromPopRetryTopicV1 = retrieveMessageFromPopRetryTopicV1; } public boolean isEnableRetryTopicV2() { return enableRetryTopicV2; } public void setEnableRetryTopicV2(boolean enableRetryTopicV2) { this.enableRetryTopicV2 = enableRetryTopicV2; } public boolean isRealTimeNotifyConsumerChange() { return realTimeNotifyConsumerChange; } public void setRealTimeNotifyConsumerChange(boolean realTimeNotifyConsumerChange) { this.realTimeNotifyConsumerChange = realTimeNotifyConsumerChange; } public boolean isEnableSlaveActingMaster() { return enableSlaveActingMaster; } public void setEnableSlaveActingMaster(boolean enableSlaveActingMaster) { this.enableSlaveActingMaster = enableSlaveActingMaster; } public boolean isEnableRemoteEscape() { return enableRemoteEscape; } public void setEnableRemoteEscape(boolean enableRemoteEscape) { this.enableRemoteEscape = enableRemoteEscape; } public boolean isSkipPreOnline() { return skipPreOnline; } public void setSkipPreOnline(boolean skipPreOnline) { this.skipPreOnline = skipPreOnline; } public boolean isAsyncSendEnable() { return asyncSendEnable; } public void setAsyncSendEnable(boolean asyncSendEnable) { this.asyncSendEnable = asyncSendEnable; } public long getConsumerOffsetUpdateVersionStep() { return consumerOffsetUpdateVersionStep; } public void setConsumerOffsetUpdateVersionStep(long consumerOffsetUpdateVersionStep) { this.consumerOffsetUpdateVersionStep = consumerOffsetUpdateVersionStep; } public long getDelayOffsetUpdateVersionStep() { return delayOffsetUpdateVersionStep; } public void setDelayOffsetUpdateVersionStep(long delayOffsetUpdateVersionStep) { this.delayOffsetUpdateVersionStep = delayOffsetUpdateVersionStep; } public int getCommercialSizePerMsg() { return commercialSizePerMsg; } public void setCommercialSizePerMsg(int commercialSizePerMsg) { this.commercialSizePerMsg = commercialSizePerMsg; } public long getWaitTimeMillsInAckQueue() { return waitTimeMillsInAckQueue; } public void setWaitTimeMillsInAckQueue(long waitTimeMillsInAckQueue) { this.waitTimeMillsInAckQueue = waitTimeMillsInAckQueue; } public boolean isRejectPullConsumerEnable() { return rejectPullConsumerEnable; } public void setRejectPullConsumerEnable(boolean rejectPullConsumerEnable) { this.rejectPullConsumerEnable = rejectPullConsumerEnable; } public boolean isAccountStatsEnable() { return accountStatsEnable; } public void setAccountStatsEnable(boolean accountStatsEnable) { this.accountStatsEnable = accountStatsEnable; } public boolean isAccountStatsPrintZeroValues() { return accountStatsPrintZeroValues; } public void setAccountStatsPrintZeroValues(boolean accountStatsPrintZeroValues) { this.accountStatsPrintZeroValues = accountStatsPrintZeroValues; } public int getMaxStatsIdleTimeInMinutes() { return maxStatsIdleTimeInMinutes; } public void setMaxStatsIdleTimeInMinutes(int maxStatsIdleTimeInMinutes) { this.maxStatsIdleTimeInMinutes = maxStatsIdleTimeInMinutes; } public boolean isLockInStrictMode() { return lockInStrictMode; } public void setLockInStrictMode(boolean lockInStrictMode) { this.lockInStrictMode = lockInStrictMode; } public boolean isIsolateLogEnable() { return isolateLogEnable; } public void setIsolateLogEnable(boolean isolateLogEnable) { this.isolateLogEnable = isolateLogEnable; } public boolean isCompatibleWithOldNameSrv() { return compatibleWithOldNameSrv; } public void setCompatibleWithOldNameSrv(boolean compatibleWithOldNameSrv) { this.compatibleWithOldNameSrv = compatibleWithOldNameSrv; } public boolean isEnableControllerMode() { return enableControllerMode; } public void setEnableControllerMode(boolean enableControllerMode) { this.enableControllerMode = enableControllerMode; } public String getControllerAddr() { return controllerAddr; } public void setControllerAddr(String controllerAddr) { this.controllerAddr = controllerAddr; } public boolean isFetchControllerAddrByDnsLookup() { return fetchControllerAddrByDnsLookup; } public void setFetchControllerAddrByDnsLookup(boolean fetchControllerAddrByDnsLookup) { this.fetchControllerAddrByDnsLookup = fetchControllerAddrByDnsLookup; } public long getSyncBrokerMetadataPeriod() { return syncBrokerMetadataPeriod; } public void setSyncBrokerMetadataPeriod(long syncBrokerMetadataPeriod) { this.syncBrokerMetadataPeriod = syncBrokerMetadataPeriod; } public long getCheckSyncStateSetPeriod() { return checkSyncStateSetPeriod; } public void setCheckSyncStateSetPeriod(long checkSyncStateSetPeriod) { this.checkSyncStateSetPeriod = checkSyncStateSetPeriod; } public long getSyncControllerMetadataPeriod() { return syncControllerMetadataPeriod; } public void setSyncControllerMetadataPeriod(long syncControllerMetadataPeriod) { this.syncControllerMetadataPeriod = syncControllerMetadataPeriod; } public int getBrokerElectionPriority() { return brokerElectionPriority; } public void setBrokerElectionPriority(int brokerElectionPriority) { this.brokerElectionPriority = brokerElectionPriority; } public long getControllerHeartBeatTimeoutMills() { return controllerHeartBeatTimeoutMills; } public void setControllerHeartBeatTimeoutMills(long controllerHeartBeatTimeoutMills) { this.controllerHeartBeatTimeoutMills = controllerHeartBeatTimeoutMills; } public boolean isRecoverConcurrently() { return recoverConcurrently; } public void setRecoverConcurrently(boolean recoverConcurrently) { this.recoverConcurrently = recoverConcurrently; } public int getRecoverThreadPoolNums() { return recoverThreadPoolNums; } public void setRecoverThreadPoolNums(int recoverThreadPoolNums) { this.recoverThreadPoolNums = recoverThreadPoolNums; } public boolean isFetchNameSrvAddrByDnsLookup() { return fetchNameSrvAddrByDnsLookup; } public void setFetchNameSrvAddrByDnsLookup(boolean fetchNameSrvAddrByDnsLookup) { this.fetchNameSrvAddrByDnsLookup = fetchNameSrvAddrByDnsLookup; } public boolean isUseServerSideResetOffset() { return useServerSideResetOffset; } public void setUseServerSideResetOffset(boolean useServerSideResetOffset) { this.useServerSideResetOffset = useServerSideResetOffset; } public boolean isEnableBroadcastOffsetStore() { return enableBroadcastOffsetStore; } public void setEnableBroadcastOffsetStore(boolean enableBroadcastOffsetStore) { this.enableBroadcastOffsetStore = enableBroadcastOffsetStore; } public long getBroadcastOffsetExpireSecond() { return broadcastOffsetExpireSecond; } public void setBroadcastOffsetExpireSecond(long broadcastOffsetExpireSecond) { this.broadcastOffsetExpireSecond = broadcastOffsetExpireSecond; } public long getBroadcastOffsetExpireMaxSecond() { return broadcastOffsetExpireMaxSecond; } public void setBroadcastOffsetExpireMaxSecond(long broadcastOffsetExpireMaxSecond) { this.broadcastOffsetExpireMaxSecond = broadcastOffsetExpireMaxSecond; } public MetricsExporterType getMetricsExporterType() { return metricsExporterType; } public void setMetricsExporterType(MetricsExporterType metricsExporterType) { this.metricsExporterType = metricsExporterType; } public void setMetricsExporterType(int metricsExporterType) { this.metricsExporterType = MetricsExporterType.valueOf(metricsExporterType); } public void setMetricsExporterType(String metricsExporterType) { this.metricsExporterType = MetricsExporterType.valueOf(metricsExporterType); } public int getMetricsOtelCardinalityLimit() { return metricsOtelCardinalityLimit; } public void setMetricsOtelCardinalityLimit(int metricsOtelCardinalityLimit) { this.metricsOtelCardinalityLimit = metricsOtelCardinalityLimit; } public String getMetricsGrpcExporterTarget() { return metricsGrpcExporterTarget; } public void setMetricsGrpcExporterTarget(String metricsGrpcExporterTarget) { this.metricsGrpcExporterTarget = metricsGrpcExporterTarget; } public String getMetricsGrpcExporterHeader() { return metricsGrpcExporterHeader; } public void setMetricsGrpcExporterHeader(String metricsGrpcExporterHeader) { this.metricsGrpcExporterHeader = metricsGrpcExporterHeader; } public long getMetricGrpcExporterTimeOutInMills() { return metricGrpcExporterTimeOutInMills; } public void setMetricGrpcExporterTimeOutInMills(long metricGrpcExporterTimeOutInMills) { this.metricGrpcExporterTimeOutInMills = metricGrpcExporterTimeOutInMills; } public long getMetricGrpcExporterIntervalInMills() { return metricGrpcExporterIntervalInMills; } public void setMetricGrpcExporterIntervalInMills(long metricGrpcExporterIntervalInMills) { this.metricGrpcExporterIntervalInMills = metricGrpcExporterIntervalInMills; } public long getMetricLoggingExporterIntervalInMills() { return metricLoggingExporterIntervalInMills; } public void setMetricLoggingExporterIntervalInMills(long metricLoggingExporterIntervalInMills) { this.metricLoggingExporterIntervalInMills = metricLoggingExporterIntervalInMills; } public String getMetricsLabel() { return metricsLabel; } public void setMetricsLabel(String metricsLabel) { this.metricsLabel = metricsLabel; } public boolean isMetricsInDelta() { return metricsInDelta; } public void setMetricsInDelta(boolean metricsInDelta) { this.metricsInDelta = metricsInDelta; } public int getMetricsPromExporterPort() { return metricsPromExporterPort; } public void setMetricsPromExporterPort(int metricsPromExporterPort) { this.metricsPromExporterPort = metricsPromExporterPort; } public String getMetricsPromExporterHost() { return metricsPromExporterHost; } public void setMetricsPromExporterHost(String metricsPromExporterHost) { this.metricsPromExporterHost = metricsPromExporterHost; } public boolean isEnablePopMetrics() { return enablePopMetrics; } public void setEnablePopMetrics(boolean enablePopMetrics) { this.enablePopMetrics = enablePopMetrics; } public boolean isEnableConnectionMetrics() { return enableConnectionMetrics; } public void setEnableConnectionMetrics(boolean enableConnectionMetrics) { this.enableConnectionMetrics = enableConnectionMetrics; } public boolean isEnableTransactionMetrics() { return enableTransactionMetrics; } public void setEnableTransactionMetrics(boolean enableTransactionMetrics) { this.enableTransactionMetrics = enableTransactionMetrics; } public boolean isEnableStatsMetrics() { return enableStatsMetrics; } public void setEnableStatsMetrics(boolean enableStatsMetrics) { this.enableStatsMetrics = enableStatsMetrics; } public boolean isEnableRequestMetrics() { return enableRequestMetrics; } public void setEnableRequestMetrics(boolean enableRequestMetrics) { this.enableRequestMetrics = enableRequestMetrics; } public boolean isEnableLagAndDlqMetrics() { return enableLagAndDlqMetrics; } public void setEnableLagAndDlqMetrics(boolean enableLagAndDlqMetrics) { this.enableLagAndDlqMetrics = enableLagAndDlqMetrics; } public boolean isEnableRemotingMetrics() { return enableRemotingMetrics; } public void setEnableRemotingMetrics(boolean enableRemotingMetrics) { this.enableRemotingMetrics = enableRemotingMetrics; } public boolean isEnableMessageStoreMetrics() { return enableMessageStoreMetrics; } public void setEnableMessageStoreMetrics(boolean enableMessageStoreMetrics) { this.enableMessageStoreMetrics = enableMessageStoreMetrics; } public int getTransactionOpMsgMaxSize() { return transactionOpMsgMaxSize; } public void setTransactionOpMsgMaxSize(int transactionOpMsgMaxSize) { this.transactionOpMsgMaxSize = transactionOpMsgMaxSize; } public int getTransactionOpBatchInterval() { return transactionOpBatchInterval; } public void setTransactionOpBatchInterval(int transactionOpBatchInterval) { this.transactionOpBatchInterval = transactionOpBatchInterval; } public long getChannelExpiredTimeout() { return channelExpiredTimeout; } public void setChannelExpiredTimeout(long channelExpiredTimeout) { this.channelExpiredTimeout = channelExpiredTimeout; } public long getSubscriptionExpiredTimeout() { return subscriptionExpiredTimeout; } public void setSubscriptionExpiredTimeout(long subscriptionExpiredTimeout) { this.subscriptionExpiredTimeout = subscriptionExpiredTimeout; } public boolean isValidateSystemTopicWhenUpdateTopic() { return validateSystemTopicWhenUpdateTopic; } public void setValidateSystemTopicWhenUpdateTopic(boolean validateSystemTopicWhenUpdateTopic) { this.validateSystemTopicWhenUpdateTopic = validateSystemTopicWhenUpdateTopic; } public boolean isEstimateAccumulation() { return estimateAccumulation; } public void setEstimateAccumulation(boolean estimateAccumulation) { this.estimateAccumulation = estimateAccumulation; } public boolean isColdCtrStrategyEnable() { return coldCtrStrategyEnable; } public void setColdCtrStrategyEnable(boolean coldCtrStrategyEnable) { this.coldCtrStrategyEnable = coldCtrStrategyEnable; } public boolean isUsePIDColdCtrStrategy() { return usePIDColdCtrStrategy; } public void setUsePIDColdCtrStrategy(boolean usePIDColdCtrStrategy) { this.usePIDColdCtrStrategy = usePIDColdCtrStrategy; } public long getCgColdReadThreshold() { return cgColdReadThreshold; } public void setCgColdReadThreshold(long cgColdReadThreshold) { this.cgColdReadThreshold = cgColdReadThreshold; } public long getGlobalColdReadThreshold() { return globalColdReadThreshold; } public void setGlobalColdReadThreshold(long globalColdReadThreshold) { this.globalColdReadThreshold = globalColdReadThreshold; } public boolean isUseStaticSubscription() { return useStaticSubscription; } public void setUseStaticSubscription(boolean useStaticSubscription) { this.useStaticSubscription = useStaticSubscription; } public long getFetchNamesrvAddrInterval() { return fetchNamesrvAddrInterval; } public void setFetchNamesrvAddrInterval(final long fetchNamesrvAddrInterval) { this.fetchNamesrvAddrInterval = fetchNamesrvAddrInterval; } public boolean isPopResponseReturnActualRetryTopic() { return popResponseReturnActualRetryTopic; } public void setPopResponseReturnActualRetryTopic(boolean popResponseReturnActualRetryTopic) { this.popResponseReturnActualRetryTopic = popResponseReturnActualRetryTopic; } public boolean isEnableSingleTopicRegister() { return enableSingleTopicRegister; } public void setEnableSingleTopicRegister(boolean enableSingleTopicRegister) { this.enableSingleTopicRegister = enableSingleTopicRegister; } public boolean isEnableMixedMessageType() { return enableMixedMessageType; } public void setEnableMixedMessageType(boolean enableMixedMessageType) { this.enableMixedMessageType = enableMixedMessageType; } public boolean isEnableSplitRegistration() { return enableSplitRegistration; } public void setEnableSplitRegistration(boolean enableSplitRegistration) { this.enableSplitRegistration = enableSplitRegistration; } public boolean isEnableFastChannelEventProcess() { return enableFastChannelEventProcess; } public void setEnableFastChannelEventProcess(boolean enableFastChannelEventProcess) { this.enableFastChannelEventProcess = enableFastChannelEventProcess; } public boolean isPrintChannelGroups() { return printChannelGroups; } public void setPrintChannelGroups(boolean printChannelGroups) { this.printChannelGroups = printChannelGroups; } public int getPrintChannelGroupsMinNum() { return printChannelGroupsMinNum; } public void setPrintChannelGroupsMinNum(int printChannelGroupsMinNum) { this.printChannelGroupsMinNum = printChannelGroupsMinNum; } public int getSplitRegistrationSize() { return splitRegistrationSize; } public void setSplitRegistrationSize(int splitRegistrationSize) { this.splitRegistrationSize = splitRegistrationSize; } public long getTransactionMetricFlushInterval() { return transactionMetricFlushInterval; } public void setTransactionMetricFlushInterval(long transactionMetricFlushInterval) { this.transactionMetricFlushInterval = transactionMetricFlushInterval; } public void setTransactionCheckRocksdbCoreThreads(int transactionCheckRocksdbCoreThreads) { this.transactionCheckRocksdbCoreThreads = transactionCheckRocksdbCoreThreads; } public int getTransactionCheckRocksdbCoreThreads() { return transactionCheckRocksdbCoreThreads; } public int getTransactionCheckRocksdbMaxThreads() { return transactionCheckRocksdbMaxThreads; } public void setTransactionCheckRocksdbMaxThreads(int transactionCheckRocksdbMaxThreads) { this.transactionCheckRocksdbMaxThreads = transactionCheckRocksdbMaxThreads; } public int getTransactionCheckRocksdbQueueCapacity() { return transactionCheckRocksdbQueueCapacity; } public void setTransactionCheckRocksdbQueueCapacity(int transactionCheckRocksdbQueueCapacity) { this.transactionCheckRocksdbQueueCapacity = transactionCheckRocksdbQueueCapacity; } public long getPopInflightMessageThreshold() { return popInflightMessageThreshold; } public void setPopInflightMessageThreshold(long popInflightMessageThreshold) { this.popInflightMessageThreshold = popInflightMessageThreshold; } public boolean isEnablePopMessageThreshold() { return enablePopMessageThreshold; } public void setEnablePopMessageThreshold(boolean enablePopMessageThreshold) { this.enablePopMessageThreshold = enablePopMessageThreshold; } public boolean isSkipWhenCKRePutReachMaxTimes() { return skipWhenCKRePutReachMaxTimes; } public void setSkipWhenCKRePutReachMaxTimes(boolean skipWhenCKRePutReachMaxTimes) { this.skipWhenCKRePutReachMaxTimes = skipWhenCKRePutReachMaxTimes; } public int getUpdateNameServerAddrPeriod() { return updateNameServerAddrPeriod; } public void setUpdateNameServerAddrPeriod(int updateNameServerAddrPeriod) { this.updateNameServerAddrPeriod = updateNameServerAddrPeriod; } public boolean isAppendAckAsync() { return appendAckAsync; } public void setAppendAckAsync(boolean appendAckAsync) { this.appendAckAsync = appendAckAsync; } public boolean isAppendCkAsync() { return appendCkAsync; } public void setAppendCkAsync(boolean appendCkAsync) { this.appendCkAsync = appendCkAsync; } public boolean isClearRetryTopicWhenDeleteTopic() { return clearRetryTopicWhenDeleteTopic; } public void setClearRetryTopicWhenDeleteTopic(boolean clearRetryTopicWhenDeleteTopic) { this.clearRetryTopicWhenDeleteTopic = clearRetryTopicWhenDeleteTopic; } public boolean isEnableLmqStats() { return enableLmqStats; } public void setEnableLmqStats(boolean enableLmqStats) { this.enableLmqStats = enableLmqStats; } public String getConfigManagerVersion() { return configManagerVersion; } public void setConfigManagerVersion(String configManagerVersion) { this.configManagerVersion = configManagerVersion; } public boolean isUseSingleRocksDBForAllConfigs() { return useSingleRocksDBForAllConfigs; } public void setUseSingleRocksDBForAllConfigs(boolean useSingleRocksDBForAllConfigs) { this.useSingleRocksDBForAllConfigs = useSingleRocksDBForAllConfigs; } public boolean isAllowRecallWhenBrokerNotWriteable() { return allowRecallWhenBrokerNotWriteable; } public void setAllowRecallWhenBrokerNotWriteable(boolean allowRecallWhenBrokerNotWriteable) { this.allowRecallWhenBrokerNotWriteable = allowRecallWhenBrokerNotWriteable; } public boolean isRecallMessageEnable() { return recallMessageEnable; } public void setRecallMessageEnable(boolean recallMessageEnable) { this.recallMessageEnable = recallMessageEnable; } public boolean isEnableRegisterProducer() { return enableRegisterProducer; } public void setEnableRegisterProducer(boolean enableRegisterProducer) { this.enableRegisterProducer = enableRegisterProducer; } public boolean isEnableCreateSysGroup() { return enableCreateSysGroup; } public void setEnableCreateSysGroup(boolean enableCreateSysGroup) { this.enableCreateSysGroup = enableCreateSysGroup; } public boolean isEnableSplitMetadata() { return enableSplitMetadata; } public void setEnableSplitMetadata(boolean enableSplitMetadata) { this.enableSplitMetadata = enableSplitMetadata; } public int getSplitMetadataSize() { return splitMetadataSize; } public void setSplitMetadataSize(int splitMetadataSize) { this.splitMetadataSize = splitMetadataSize; } public int getPopFromRetryProbabilityForPriority() { return popFromRetryProbabilityForPriority; } public void setPopFromRetryProbabilityForPriority(int popFromRetryProbabilityForPriority) { this.popFromRetryProbabilityForPriority = popFromRetryProbabilityForPriority; } public boolean isPriorityOrderAsc() { return priorityOrderAsc; } public void setPriorityOrderAsc(boolean priorityOrderAsc) { this.priorityOrderAsc = priorityOrderAsc; } public boolean isUseSeparateRetryQueue() { return useSeparateRetryQueue; } public void setUseSeparateRetryQueue(boolean useSeparateRetryQueue) { this.useSeparateRetryQueue = useSeparateRetryQueue; } public boolean isEnableLiteEventMode() { return enableLiteEventMode; } public void setEnableLiteEventMode(boolean enableLiteEventMode) { this.enableLiteEventMode = enableLiteEventMode; } public long getLiteEventCheckInterval() { return liteEventCheckInterval; } public void setLiteEventCheckInterval(long liteEventCheckInterval) { this.liteEventCheckInterval = liteEventCheckInterval; } public long getLiteTtlCheckInterval() { return liteTtlCheckInterval; } public void setLiteTtlCheckInterval(long liteTtlCheckInterval) { this.liteTtlCheckInterval = liteTtlCheckInterval; } public long getMinLiteTTl() { return minLiteTTl; } public void setMinLiteTTl(long minLiteTTl) { this.minLiteTTl = minLiteTTl; } public long getLiteSubscriptionCheckInterval() { return liteSubscriptionCheckInterval; } public void setLiteSubscriptionCheckInterval(long liteSubscriptionCheckInterval) { this.liteSubscriptionCheckInterval = liteSubscriptionCheckInterval; } public long getLiteSubscriptionCheckTimeoutMills() { return liteSubscriptionCheckTimeoutMills; } public void setLiteSubscriptionCheckTimeoutMills(long liteSubscriptionCheckTimeoutMills) { this.liteSubscriptionCheckTimeoutMills = liteSubscriptionCheckTimeoutMills; } public boolean isPersistConsumerOffsetIncrementally() { return persistConsumerOffsetIncrementally; } public void setPersistConsumerOffsetIncrementally(boolean persistConsumerOffsetIncrementally) { this.persistConsumerOffsetIncrementally = persistConsumerOffsetIncrementally; } public long getMaxLiteSubscriptionCount() { return maxLiteSubscriptionCount; } public void setMaxLiteSubscriptionCount(long maxLiteSubscriptionCount) { this.maxLiteSubscriptionCount = maxLiteSubscriptionCount; } public boolean isEnableLitePopLog() { return enableLitePopLog; } public void setEnableLitePopLog(boolean enableLitePopLog) { this.enableLitePopLog = enableLitePopLog; } public int getMaxClientEventCount() { return maxClientEventCount; } public void setMaxClientEventCount(int maxClientEventCount) { this.maxClientEventCount = maxClientEventCount; } public long getLiteEventFullDispatchDelayTime() { return liteEventFullDispatchDelayTime; } public void setLiteEventFullDispatchDelayTime(long liteEventFullDispatchDelayTime) { this.liteEventFullDispatchDelayTime = liteEventFullDispatchDelayTime; } public boolean isLiteLagLatencyCollectEnable() { return liteLagLatencyCollectEnable; } public void setLiteLagLatencyCollectEnable(boolean liteLagLatencyCollectEnable) { this.liteLagLatencyCollectEnable = liteLagLatencyCollectEnable; } public boolean isLiteLagLatencyMetricsEnable() { return liteLagLatencyMetricsEnable; } public void setLiteLagLatencyMetricsEnable(boolean liteLagLatencyMetricsEnable) { this.liteLagLatencyMetricsEnable = liteLagLatencyMetricsEnable; } public boolean isLiteLagCountMetricsEnable() { return liteLagCountMetricsEnable; } public void setLiteLagCountMetricsEnable(boolean liteLagCountMetricsEnable) { this.liteLagCountMetricsEnable = liteLagCountMetricsEnable; } public int getLiteLagLatencyTopK() { return liteLagLatencyTopK; } public void setLiteLagLatencyTopK(int liteLagLatencyTopK) { this.liteLagLatencyTopK = liteLagLatencyTopK; } public boolean isUseMessageFilterForNotification() { return useMessageFilterForNotification; } public void setUseMessageFilterForNotification(boolean useMessageFilterForNotification) { this.useMessageFilterForNotification = useMessageFilterForNotification; } public int getMaxMessageFilterNumForNotification() { return maxMessageFilterNumForNotification; } public void setMaxMessageFilterNumForNotification(int maxMessageFilterNumForNotification) { this.maxMessageFilterNumForNotification = maxMessageFilterNumForNotification; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/BrokerConfigSingleton.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.concurrent.atomic.AtomicBoolean; public class BrokerConfigSingleton { private static AtomicBoolean isInit = new AtomicBoolean(); private static BrokerConfig brokerConfig; public static BrokerConfig getBrokerConfig() { if (brokerConfig == null) { throw new IllegalArgumentException("brokerConfig Cannot be null !"); } return brokerConfig; } public static void setBrokerConfig(BrokerConfig brokerConfig) { if (!isInit.compareAndSet(false, true)) { throw new IllegalArgumentException("broker config have inited !"); } BrokerConfigSingleton.brokerConfig = brokerConfig; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/BrokerIdentity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.UnknownHostException; public class BrokerIdentity { private static final String DEFAULT_CLUSTER_NAME = "DefaultCluster"; protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static String localHostName; static { try { localHostName = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { LOGGER.error("Failed to obtain the host name", e); } } // load it after the localHostName is initialized public static final BrokerIdentity BROKER_CONTAINER_IDENTITY = new BrokerIdentity(true); @ImportantField private String brokerName = defaultBrokerName(); @ImportantField private String brokerClusterName = DEFAULT_CLUSTER_NAME; @ImportantField private volatile long brokerId = MixAll.MASTER_ID; private boolean isBrokerContainer = false; // Do not set it manually, it depends on the startup mode // Broker start by BrokerStartup is false, start or add by BrokerContainer is true private boolean isInBrokerContainer = false; public BrokerIdentity() { } public BrokerIdentity(boolean isBrokerContainer) { this.isBrokerContainer = isBrokerContainer; } public BrokerIdentity(String brokerClusterName, String brokerName, long brokerId) { this.brokerName = brokerName; this.brokerClusterName = brokerClusterName; this.brokerId = brokerId; } public BrokerIdentity(String brokerClusterName, String brokerName, long brokerId, boolean isInBrokerContainer) { this.brokerName = brokerName; this.brokerClusterName = brokerClusterName; this.brokerId = brokerId; this.isInBrokerContainer = isInBrokerContainer; } public String getBrokerName() { return brokerName; } public void setBrokerName(final String brokerName) { this.brokerName = brokerName; } public String getBrokerClusterName() { return brokerClusterName; } public void setBrokerClusterName(final String brokerClusterName) { this.brokerClusterName = brokerClusterName; } public long getBrokerId() { return brokerId; } public void setBrokerId(final long brokerId) { this.brokerId = brokerId; } public boolean isInBrokerContainer() { return isInBrokerContainer; } public void setInBrokerContainer(boolean inBrokerContainer) { isInBrokerContainer = inBrokerContainer; } private String defaultBrokerName() { return StringUtils.isEmpty(localHostName) ? "DEFAULT_BROKER" : localHostName; } public String getCanonicalName() { return isBrokerContainer ? "BrokerContainer" : String.format("%s_%s_%d", brokerClusterName, brokerName, brokerId); } public String getIdentifier() { return "#" + getCanonicalName() + "#"; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final BrokerIdentity identity = (BrokerIdentity) o; return new EqualsBuilder() .append(brokerId, identity.brokerId) .append(brokerName, identity.brokerName) .append(brokerClusterName, identity.brokerClusterName) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(brokerName) .append(brokerClusterName) .append(brokerId) .toHashCode(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/CheckRocksdbCqWriteResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public class CheckRocksdbCqWriteResult { String checkResult; int checkStatus; public enum CheckStatus { CHECK_OK(0), CHECK_NOT_OK(1), CHECK_IN_PROGRESS(2), CHECK_ERROR(3); private int value; CheckStatus(int value) { this.value = value; } public int getValue() { return value; } } public String getCheckResult() { return checkResult; } public void setCheckResult(String checkResult) { this.checkResult = checkResult; } public int getCheckStatus() { return checkStatus; } public void setCheckStatus(int checkStatus) { this.checkStatus = checkStatus; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/ConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.io.File; import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Map; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class ConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public boolean load() { String fileName = null; try { fileName = this.configFilePath(); String jsonString = MixAll.file2String(fileName); if (null == jsonString || jsonString.length() == 0) { // delete invalid file Files.deleteIfExists(Paths.get(fileName)); return this.loadBak(); } else { this.decode(jsonString); log.info("load " + fileName + " OK"); return true; } } catch (Exception e) { log.error("load " + fileName + " failed, and try to load backup file", e); try { if (fileName != null) { // delete invalid file Files.deleteIfExists(Paths.get(fileName)); } } catch (Throwable t) { log.error("load " + fileName + " failed, and delete invalid file errr", e); } return this.loadBak(); } } private boolean loadBak() { String fileName = null; try { fileName = this.configFilePath() + ".bak"; String jsonString = MixAll.file2String(fileName); if (jsonString != null && jsonString.length() > 0) { this.decode(jsonString); log.info("load " + fileName + " OK"); return true; } } catch (Exception e) { log.error("load " + fileName + " Failed", e); return false; } return true; } public synchronized void persist(String topicName, T t) { // stub for future this.persist(); } public synchronized void persist(Map m) { // stub for future this.persist(); } public synchronized void persist() { String jsonString = this.encode(true); if (jsonString != null) { try { // bak metrics file String config = configFilePath(); String backup = config + ".bak"; File configFile = new File(config); File bakFile = new File(backup); if (configFile.exists()) { // atomic move Files.move(configFile.toPath(), bakFile.toPath(), StandardCopyOption.ATOMIC_MOVE); // sync the directory, ensure that the bak file is visible MixAll.fsyncDirectory(Paths.get(bakFile.getParent())); } File dir = new File(configFile.getParent()); if (!dir.exists()) { Files.createDirectories(dir.toPath()); } try (RandomAccessFile randomAccessFile = new RandomAccessFile(config, "rw")) { randomAccessFile.write(jsonString.getBytes(StandardCharsets.UTF_8)); randomAccessFile.getChannel().force(true); // sync the directory, ensure that the config file is visible MixAll.fsyncDirectory(Paths.get(configFile.getParent())); } } catch (Throwable t) { log.error("Failed to persist", t); } } } public boolean stop() { return true; } public void shutdown() { stop(); } public abstract String configFilePath(); public abstract String encode(); public abstract String encode(final boolean prettyFormat); public abstract void decode(final String jsonString); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/ControllerConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.io.File; import java.util.Arrays; import org.apache.rocketmq.common.metrics.MetricsExporterType; public class ControllerConfig { private String rocketmqHome = MixAll.ROCKETMQ_HOME_DIR; private String configStorePath = System.getProperty("user.home") + File.separator + "controller" + File.separator + "controller.properties"; public static final String DLEDGER_CONTROLLER = "DLedger"; public static final String JRAFT_CONTROLLER = "jRaft"; private JraftConfig jraftConfig = new JraftConfig(); private String controllerType = DLEDGER_CONTROLLER; /** * Interval of periodic scanning for non-active broker; * Unit: millisecond */ private long scanNotActiveBrokerInterval = 5 * 1000; /** * Indicates the nums of thread to handle broker or operation requests, like REGISTER_BROKER. */ private int controllerThreadPoolNums = 16; /** * Indicates the capacity of queue to hold client requests. */ private int controllerRequestThreadPoolQueueCapacity = 50000; private String controllerDLegerGroup; private String controllerDLegerPeers; private String controllerDLegerSelfId; private int mappedFileSize = 1024 * 1024 * 1024; private String controllerStorePath = ""; /** * Max retry count for electing master when failed because of network or system error. */ private int electMasterMaxRetryCount = 3; /** * Whether the controller can elect a master which is not in the syncStateSet. */ private boolean enableElectUncleanMaster = false; /** * Whether process read event */ private boolean isProcessReadEvent = false; /** * Whether notify broker when its role changed */ private volatile boolean notifyBrokerRoleChanged = true; /** * Interval of periodic scanning for non-active master in each broker-set; * Unit: millisecond */ private long scanInactiveMasterInterval = 5 * 1000; private MetricsExporterType metricsExporterType = MetricsExporterType.DISABLE; private String metricsGrpcExporterTarget = ""; private String metricsGrpcExporterHeader = ""; private long metricGrpcExporterTimeOutInMills = 3 * 1000; private long metricGrpcExporterIntervalInMills = 60 * 1000; private long metricLoggingExporterIntervalInMills = 10 * 1000; private int metricsPromExporterPort = 5557; private String metricsPromExporterHost = ""; // Label pairs in CSV. Each label follows pattern of Key:Value. eg: instance_id:xxx,uid:xxx private String metricsLabel = ""; private boolean metricsInDelta = false; /** * Config in this black list will be not allowed to update by command. * Try to update this config black list by restart process. * Try to update configures in black list by restart process. */ private String configBlackList = "configBlackList;configStorePath"; public String getConfigBlackList() { return configBlackList; } public void setConfigBlackList(String configBlackList) { this.configBlackList = configBlackList; } public String getRocketmqHome() { return rocketmqHome; } public void setRocketmqHome(String rocketmqHome) { this.rocketmqHome = rocketmqHome; } public String getConfigStorePath() { return configStorePath; } public void setConfigStorePath(String configStorePath) { this.configStorePath = configStorePath; } public long getScanNotActiveBrokerInterval() { return scanNotActiveBrokerInterval; } public void setScanNotActiveBrokerInterval(long scanNotActiveBrokerInterval) { this.scanNotActiveBrokerInterval = scanNotActiveBrokerInterval; } public int getControllerThreadPoolNums() { return controllerThreadPoolNums; } public void setControllerThreadPoolNums(int controllerThreadPoolNums) { this.controllerThreadPoolNums = controllerThreadPoolNums; } public int getControllerRequestThreadPoolQueueCapacity() { return controllerRequestThreadPoolQueueCapacity; } public void setControllerRequestThreadPoolQueueCapacity(int controllerRequestThreadPoolQueueCapacity) { this.controllerRequestThreadPoolQueueCapacity = controllerRequestThreadPoolQueueCapacity; } public String getControllerDLegerGroup() { return controllerDLegerGroup; } public void setControllerDLegerGroup(String controllerDLegerGroup) { this.controllerDLegerGroup = controllerDLegerGroup; } public String getControllerDLegerPeers() { return controllerDLegerPeers; } public void setControllerDLegerPeers(String controllerDLegerPeers) { this.controllerDLegerPeers = controllerDLegerPeers; } public String getControllerDLegerSelfId() { return controllerDLegerSelfId; } public void setControllerDLegerSelfId(String controllerDLegerSelfId) { this.controllerDLegerSelfId = controllerDLegerSelfId; } public int getMappedFileSize() { return mappedFileSize; } public void setMappedFileSize(int mappedFileSize) { this.mappedFileSize = mappedFileSize; } public String getControllerStorePath() { if (controllerStorePath.isEmpty()) { controllerStorePath = System.getProperty("user.home") + File.separator + controllerType + "Controller"; } return controllerStorePath; } public void setControllerStorePath(String controllerStorePath) { this.controllerStorePath = controllerStorePath; } public boolean isEnableElectUncleanMaster() { return enableElectUncleanMaster; } public void setEnableElectUncleanMaster(boolean enableElectUncleanMaster) { this.enableElectUncleanMaster = enableElectUncleanMaster; } public boolean isProcessReadEvent() { return isProcessReadEvent; } public void setProcessReadEvent(boolean processReadEvent) { isProcessReadEvent = processReadEvent; } public boolean isNotifyBrokerRoleChanged() { return notifyBrokerRoleChanged; } public void setNotifyBrokerRoleChanged(boolean notifyBrokerRoleChanged) { this.notifyBrokerRoleChanged = notifyBrokerRoleChanged; } public long getScanInactiveMasterInterval() { return scanInactiveMasterInterval; } public void setScanInactiveMasterInterval(long scanInactiveMasterInterval) { this.scanInactiveMasterInterval = scanInactiveMasterInterval; } public String getDLedgerAddress() { return Arrays.stream(this.controllerDLegerPeers.split(";")) .filter(x -> this.controllerDLegerSelfId.equals(x.split("-")[0])) .map(x -> x.split("-")[1]).findFirst().get(); } public MetricsExporterType getMetricsExporterType() { return metricsExporterType; } public void setMetricsExporterType(MetricsExporterType metricsExporterType) { this.metricsExporterType = metricsExporterType; } public void setMetricsExporterType(int metricsExporterType) { this.metricsExporterType = MetricsExporterType.valueOf(metricsExporterType); } public void setMetricsExporterType(String metricsExporterType) { this.metricsExporterType = MetricsExporterType.valueOf(metricsExporterType); } public String getMetricsGrpcExporterTarget() { return metricsGrpcExporterTarget; } public void setMetricsGrpcExporterTarget(String metricsGrpcExporterTarget) { this.metricsGrpcExporterTarget = metricsGrpcExporterTarget; } public String getMetricsGrpcExporterHeader() { return metricsGrpcExporterHeader; } public void setMetricsGrpcExporterHeader(String metricsGrpcExporterHeader) { this.metricsGrpcExporterHeader = metricsGrpcExporterHeader; } public long getMetricGrpcExporterTimeOutInMills() { return metricGrpcExporterTimeOutInMills; } public void setMetricGrpcExporterTimeOutInMills(long metricGrpcExporterTimeOutInMills) { this.metricGrpcExporterTimeOutInMills = metricGrpcExporterTimeOutInMills; } public long getMetricGrpcExporterIntervalInMills() { return metricGrpcExporterIntervalInMills; } public void setMetricGrpcExporterIntervalInMills(long metricGrpcExporterIntervalInMills) { this.metricGrpcExporterIntervalInMills = metricGrpcExporterIntervalInMills; } public long getMetricLoggingExporterIntervalInMills() { return metricLoggingExporterIntervalInMills; } public void setMetricLoggingExporterIntervalInMills(long metricLoggingExporterIntervalInMills) { this.metricLoggingExporterIntervalInMills = metricLoggingExporterIntervalInMills; } public int getMetricsPromExporterPort() { return metricsPromExporterPort; } public void setMetricsPromExporterPort(int metricsPromExporterPort) { this.metricsPromExporterPort = metricsPromExporterPort; } public String getMetricsPromExporterHost() { return metricsPromExporterHost; } public void setMetricsPromExporterHost(String metricsPromExporterHost) { this.metricsPromExporterHost = metricsPromExporterHost; } public String getMetricsLabel() { return metricsLabel; } public void setMetricsLabel(String metricsLabel) { this.metricsLabel = metricsLabel; } public boolean isMetricsInDelta() { return metricsInDelta; } public void setMetricsInDelta(boolean metricsInDelta) { this.metricsInDelta = metricsInDelta; } public String getControllerType() { return controllerType; } public void setControllerType(String controllerType) { this.controllerType = controllerType; } public JraftConfig getJraftConfig() { return jraftConfig; } public void setJraftConfig(JraftConfig jraftConfig) { this.jraftConfig = jraftConfig; } public int getElectMasterMaxRetryCount() { return this.electMasterMaxRetryCount; } public void setElectMasterMaxRetryCount(int electMasterMaxRetryCount) { this.electMasterMaxRetryCount = electMasterMaxRetryCount; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/CountDownLatch2.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * Add reset feature for @see java.util.concurrent.CountDownLatch */ public class CountDownLatch2 { private final Sync sync; /** * Constructs a {@code CountDownLatch2} initialized with the given count. * * @param count the number of times {@link #countDown} must be invoked before threads can pass through {@link * #await} * @throws IllegalArgumentException if {@code count} is negative */ public CountDownLatch2(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } /** * Causes the current thread to wait until the latch has counted down to * zero, unless the thread is {@linkplain Thread#interrupt interrupted}. * *

    If the current count is zero then this method returns immediately. * *

    If the current count is greater than zero then the current * thread becomes disabled for thread scheduling purposes and lies * dormant until one of two things happen: *

      *
    • The count reaches zero due to invocations of the * {@link #countDown} method; or *
    • Some other thread {@linkplain Thread#interrupt interrupts} * the current thread. *
    * *

    If the current thread: *

      *
    • has its interrupted status set on entry to this method; or *
    • is {@linkplain Thread#interrupt interrupted} while waiting, *
    * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * @throws InterruptedException if the current thread is interrupted while waiting */ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } /** * Causes the current thread to wait until the latch has counted down to * zero, unless the thread is {@linkplain Thread#interrupt interrupted}, * or the specified waiting time elapses. * *

    If the current count is zero then this method returns immediately * with the value {@code true}. * *

    If the current count is greater than zero then the current * thread becomes disabled for thread scheduling purposes and lies * dormant until one of three things happen: *

      *
    • The count reaches zero due to invocations of the * {@link #countDown} method; or *
    • Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or *
    • The specified waiting time elapses. *
    * *

    If the count reaches zero then the method returns with the * value {@code true}. * *

    If the current thread: *

      *
    • has its interrupted status set on entry to this method; or *
    • is {@linkplain Thread#interrupt interrupted} while waiting, *
    * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * *

    If the specified waiting time elapses then the value {@code false} * is returned. If the time is less than or equal to zero, the method * will not wait at all. * * @param timeout the maximum time to wait * @param unit the time unit of the {@code timeout} argument * @return {@code true} if the count reached zero and {@code false} if the waiting time elapsed before the count * reached zero * @throws InterruptedException if the current thread is interrupted while waiting */ public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } /** * Decrements the count of the latch, releasing all waiting threads if * the count reaches zero. * *

    If the current count is greater than zero then it is decremented. * If the new count is zero then all waiting threads are re-enabled for * thread scheduling purposes. * *

    If the current count equals zero then nothing happens. */ public void countDown() { sync.releaseShared(1); } /** * Returns the current count. * *

    This method is typically used for debugging and testing purposes. * * @return the current count */ public long getCount() { return sync.getCount(); } public void reset() { sync.reset(); } /** * Returns a string identifying this latch, as well as its state. * The state, in brackets, includes the String {@code "Count ="} * followed by the current count. * * @return a string identifying this latch, as well as its state */ public String toString() { return super.toString() + "[Count = " + sync.getCount() + "]"; } /** * Synchronization control For CountDownLatch2. * Uses AQS state to represent count. */ private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; private final int startCount; Sync(int count) { this.startCount = count; setState(count); } int getCount() { return getState(); } @Override protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } @Override protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (; ; ) { int c = getState(); if (c == 0) return false; int nextc = c - 1; if (compareAndSetState(c, nextc)) return nextc == 0; } } protected void reset() { setState(startCount); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/JraftConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public class JraftConfig { private int jRaftElectionTimeoutMs = 1000; private int jRaftScanWaitTimeoutMs = 1000; private int jRaftSnapshotIntervalSecs = 3600; private String jRaftGroupId = "jRaft-Controller"; private String jRaftServerId = "localhost:9880"; private String jRaftInitConf = "localhost:9880,localhost:9881,localhost:9882"; private String jRaftControllerRPCAddr = "localhost:9770,localhost:9771,localhost:9772"; public int getjRaftElectionTimeoutMs() { return jRaftElectionTimeoutMs; } public void setjRaftElectionTimeoutMs(int jRaftElectionTimeoutMs) { this.jRaftElectionTimeoutMs = jRaftElectionTimeoutMs; } public int getjRaftSnapshotIntervalSecs() { return jRaftSnapshotIntervalSecs; } public void setjRaftSnapshotIntervalSecs(int jRaftSnapshotIntervalSecs) { this.jRaftSnapshotIntervalSecs = jRaftSnapshotIntervalSecs; } public String getjRaftGroupId() { return jRaftGroupId; } public void setjRaftGroupId(String jRaftGroupId) { this.jRaftGroupId = jRaftGroupId; } public String getjRaftServerId() { return jRaftServerId; } public void setjRaftServerId(String jRaftServerId) { this.jRaftServerId = jRaftServerId; } public String getjRaftInitConf() { return jRaftInitConf; } public void setjRaftInitConf(String jRaftInitConf) { this.jRaftInitConf = jRaftInitConf; } public String getjRaftControllerRPCAddr() { return jRaftControllerRPCAddr; } public void setjRaftControllerRPCAddr(String jRaftControllerRPCAddr) { this.jRaftControllerRPCAddr = jRaftControllerRPCAddr; } public String getjRaftAddress() { return this.jRaftServerId; } public int getjRaftScanWaitTimeoutMs() { return jRaftScanWaitTimeoutMs; } public void setjRaftScanWaitTimeoutMs(int jRaftScanWaitTimeoutMs) { this.jRaftScanWaitTimeoutMs = jRaftScanWaitTimeoutMs; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/KeyBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public class KeyBuilder { public static final int POP_ORDER_REVIVE_QUEUE = 999; private static final char POP_RETRY_SEPARATOR_V1 = '_'; private static final char POP_RETRY_SEPARATOR_V2 = '+'; private static final String POP_RETRY_REGEX_SEPARATOR_V2 = "\\+"; public static String buildPopRetryTopic(String topic, String cid, boolean enableRetryV2) { if (enableRetryV2) { return buildPopRetryTopicV2(topic, cid); } return buildPopRetryTopicV1(topic, cid); } public static String buildPopRetryTopic(String topic, String cid) { return MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V1 + topic; } public static String buildPopRetryTopicV2(String topic, String cid) { return MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V2 + topic; } public static String buildPopRetryTopicV1(String topic, String cid) { return MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V1 + topic; } public static String parseNormalTopic(String topic, String cid) { if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V2)) { return topic.substring((MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V2).length()); } return topic.substring((MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V1).length()); } else { return topic; } } public static String parseNormalTopic(String retryTopic) { if (isPopRetryTopicV2(retryTopic)) { String[] result = retryTopic.split(POP_RETRY_REGEX_SEPARATOR_V2); if (result.length == 2) { return result[1]; } } return retryTopic; } public static String parseGroup(String retryTopic) { if (isPopRetryTopicV2(retryTopic)) { String[] result = retryTopic.split(POP_RETRY_REGEX_SEPARATOR_V2); if (result.length == 2) { return result[0].substring(MixAll.RETRY_GROUP_TOPIC_PREFIX.length()); } } return retryTopic.substring(MixAll.RETRY_GROUP_TOPIC_PREFIX.length()); } public static String buildPollingKey(String topic, String cid, int queueId) { return topic + PopAckConstants.SPLIT + cid + PopAckConstants.SPLIT + queueId; } public static boolean isPopRetryTopicV2(String retryTopic) { return retryTopic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && retryTopic.contains(String.valueOf(POP_RETRY_SEPARATOR_V2)); } public static String buildPopLiteLockKey(String group, String lmqName) { return group + PopAckConstants.SPLIT + lmqName; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/LifecycleAwareServiceThread.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public abstract class LifecycleAwareServiceThread extends ServiceThread { private final AtomicBoolean started = new AtomicBoolean(false); @Override public void run() { started.set(true); synchronized (started) { started.notifyAll(); } run0(); } public abstract void run0(); /** * Take spurious wakeup into account. * * @param timeout amount of time in milliseconds * @throws InterruptedException if interrupted */ public void awaitStarted(long timeout) throws InterruptedException { long expire = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout); synchronized (started) { while (!started.get()) { long duration = expire - System.nanoTime(); if (duration < TimeUnit.MILLISECONDS.toNanos(1)) { break; } started.wait(TimeUnit.NANOSECONDS.toMillis(duration)); } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/LockCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; public interface LockCallback { void onSuccess(final Set lockOKMQSet); void onException(final Throwable e); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/MQVersion.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public class MQVersion { public static final int CURRENT_VERSION = Version.V5_4_0.ordinal(); public static String getVersionDesc(int value) { int length = Version.values().length; if (value >= length) { return Version.values()[length - 1].name(); } return Version.values()[value].name(); } public static Version value2Version(int value) { int length = Version.values().length; if (value >= length) { return Version.values()[length - 1]; } return Version.values()[value]; } public enum Version { V3_0_0_SNAPSHOT, V3_0_0_ALPHA1, V3_0_0_BETA1, V3_0_0_BETA2, V3_0_0_BETA3, V3_0_0_BETA4, V3_0_0_BETA5, V3_0_0_BETA6_SNAPSHOT, V3_0_0_BETA6, V3_0_0_BETA7_SNAPSHOT, V3_0_0_BETA7, V3_0_0_BETA8_SNAPSHOT, V3_0_0_BETA8, V3_0_0_BETA9_SNAPSHOT, V3_0_0_BETA9, V3_0_0_FINAL, V3_0_1_SNAPSHOT, V3_0_1, V3_0_2_SNAPSHOT, V3_0_2, V3_0_3_SNAPSHOT, V3_0_3, V3_0_4_SNAPSHOT, V3_0_4, V3_0_5_SNAPSHOT, V3_0_5, V3_0_6_SNAPSHOT, V3_0_6, V3_0_7_SNAPSHOT, V3_0_7, V3_0_8_SNAPSHOT, V3_0_8, V3_0_9_SNAPSHOT, V3_0_9, V3_0_10_SNAPSHOT, V3_0_10, V3_0_11_SNAPSHOT, V3_0_11, V3_0_12_SNAPSHOT, V3_0_12, V3_0_13_SNAPSHOT, V3_0_13, V3_0_14_SNAPSHOT, V3_0_14, V3_0_15_SNAPSHOT, V3_0_15, V3_1_0_SNAPSHOT, V3_1_0, V3_1_1_SNAPSHOT, V3_1_1, V3_1_2_SNAPSHOT, V3_1_2, V3_1_3_SNAPSHOT, V3_1_3, V3_1_4_SNAPSHOT, V3_1_4, V3_1_5_SNAPSHOT, V3_1_5, V3_1_6_SNAPSHOT, V3_1_6, V3_1_7_SNAPSHOT, V3_1_7, V3_1_8_SNAPSHOT, V3_1_8, V3_1_9_SNAPSHOT, V3_1_9, V3_2_0_SNAPSHOT, V3_2_0, V3_2_1_SNAPSHOT, V3_2_1, V3_2_2_SNAPSHOT, V3_2_2, V3_2_3_SNAPSHOT, V3_2_3, V3_2_4_SNAPSHOT, V3_2_4, V3_2_5_SNAPSHOT, V3_2_5, V3_2_6_SNAPSHOT, V3_2_6, V3_2_7_SNAPSHOT, V3_2_7, V3_2_8_SNAPSHOT, V3_2_8, V3_2_9_SNAPSHOT, V3_2_9, V3_3_1_SNAPSHOT, V3_3_1, V3_3_2_SNAPSHOT, V3_3_2, V3_3_3_SNAPSHOT, V3_3_3, V3_3_4_SNAPSHOT, V3_3_4, V3_3_5_SNAPSHOT, V3_3_5, V3_3_6_SNAPSHOT, V3_3_6, V3_3_7_SNAPSHOT, V3_3_7, V3_3_8_SNAPSHOT, V3_3_8, V3_3_9_SNAPSHOT, V3_3_9, V3_4_1_SNAPSHOT, V3_4_1, V3_4_2_SNAPSHOT, V3_4_2, V3_4_3_SNAPSHOT, V3_4_3, V3_4_4_SNAPSHOT, V3_4_4, V3_4_5_SNAPSHOT, V3_4_5, V3_4_6_SNAPSHOT, V3_4_6, V3_4_7_SNAPSHOT, V3_4_7, V3_4_8_SNAPSHOT, V3_4_8, V3_4_9_SNAPSHOT, V3_4_9, V3_5_1_SNAPSHOT, V3_5_1, V3_5_2_SNAPSHOT, V3_5_2, V3_5_3_SNAPSHOT, V3_5_3, V3_5_4_SNAPSHOT, V3_5_4, V3_5_5_SNAPSHOT, V3_5_5, V3_5_6_SNAPSHOT, V3_5_6, V3_5_7_SNAPSHOT, V3_5_7, V3_5_8_SNAPSHOT, V3_5_8, V3_5_9_SNAPSHOT, V3_5_9, V3_6_1_SNAPSHOT, V3_6_1, V3_6_2_SNAPSHOT, V3_6_2, V3_6_3_SNAPSHOT, V3_6_3, V3_6_4_SNAPSHOT, V3_6_4, V3_6_5_SNAPSHOT, V3_6_5, V3_6_6_SNAPSHOT, V3_6_6, V3_6_7_SNAPSHOT, V3_6_7, V3_6_8_SNAPSHOT, V3_6_8, V3_6_9_SNAPSHOT, V3_6_9, V3_7_1_SNAPSHOT, V3_7_1, V3_7_2_SNAPSHOT, V3_7_2, V3_7_3_SNAPSHOT, V3_7_3, V3_7_4_SNAPSHOT, V3_7_4, V3_7_5_SNAPSHOT, V3_7_5, V3_7_6_SNAPSHOT, V3_7_6, V3_7_7_SNAPSHOT, V3_7_7, V3_7_8_SNAPSHOT, V3_7_8, V3_7_9_SNAPSHOT, V3_7_9, V3_8_1_SNAPSHOT, V3_8_1, V3_8_2_SNAPSHOT, V3_8_2, V3_8_3_SNAPSHOT, V3_8_3, V3_8_4_SNAPSHOT, V3_8_4, V3_8_5_SNAPSHOT, V3_8_5, V3_8_6_SNAPSHOT, V3_8_6, V3_8_7_SNAPSHOT, V3_8_7, V3_8_8_SNAPSHOT, V3_8_8, V3_8_9_SNAPSHOT, V3_8_9, V3_9_1_SNAPSHOT, V3_9_1, V3_9_2_SNAPSHOT, V3_9_2, V3_9_3_SNAPSHOT, V3_9_3, V3_9_4_SNAPSHOT, V3_9_4, V3_9_5_SNAPSHOT, V3_9_5, V3_9_6_SNAPSHOT, V3_9_6, V3_9_7_SNAPSHOT, V3_9_7, V3_9_8_SNAPSHOT, V3_9_8, V3_9_9_SNAPSHOT, V3_9_9, V4_0_0_SNAPSHOT, V4_0_0, V4_0_1_SNAPSHOT, V4_0_1, V4_0_2_SNAPSHOT, V4_0_2, V4_0_3_SNAPSHOT, V4_0_3, V4_0_4_SNAPSHOT, V4_0_4, V4_0_5_SNAPSHOT, V4_0_5, V4_0_6_SNAPSHOT, V4_0_6, V4_0_7_SNAPSHOT, V4_0_7, V4_0_8_SNAPSHOT, V4_0_8, V4_0_9_SNAPSHOT, V4_0_9, V4_1_0_SNAPSHOT, V4_1_0, V4_1_1_SNAPSHOT, V4_1_1, V4_1_2_SNAPSHOT, V4_1_2, V4_1_3_SNAPSHOT, V4_1_3, V4_1_4_SNAPSHOT, V4_1_4, V4_1_5_SNAPSHOT, V4_1_5, V4_1_6_SNAPSHOT, V4_1_6, V4_1_7_SNAPSHOT, V4_1_7, V4_1_8_SNAPSHOT, V4_1_8, V4_1_9_SNAPSHOT, V4_1_9, V4_2_0_SNAPSHOT, V4_2_0, V4_2_1_SNAPSHOT, V4_2_1, V4_2_2_SNAPSHOT, V4_2_2, V4_2_3_SNAPSHOT, V4_2_3, V4_2_4_SNAPSHOT, V4_2_4, V4_2_5_SNAPSHOT, V4_2_5, V4_2_6_SNAPSHOT, V4_2_6, V4_2_7_SNAPSHOT, V4_2_7, V4_2_8_SNAPSHOT, V4_2_8, V4_2_9_SNAPSHOT, V4_2_9, V4_3_0_SNAPSHOT, V4_3_0, V4_3_1_SNAPSHOT, V4_3_1, V4_3_2_SNAPSHOT, V4_3_2, V4_3_3_SNAPSHOT, V4_3_3, V4_3_4_SNAPSHOT, V4_3_4, V4_3_5_SNAPSHOT, V4_3_5, V4_3_6_SNAPSHOT, V4_3_6, V4_3_7_SNAPSHOT, V4_3_7, V4_3_8_SNAPSHOT, V4_3_8, V4_3_9_SNAPSHOT, V4_3_9, V4_4_0_SNAPSHOT, V4_4_0, V4_4_1_SNAPSHOT, V4_4_1, V4_4_2_SNAPSHOT, V4_4_2, V4_4_3_SNAPSHOT, V4_4_3, V4_4_4_SNAPSHOT, V4_4_4, V4_4_5_SNAPSHOT, V4_4_5, V4_4_6_SNAPSHOT, V4_4_6, V4_4_7_SNAPSHOT, V4_4_7, V4_4_8_SNAPSHOT, V4_4_8, V4_4_9_SNAPSHOT, V4_4_9, V4_5_0_SNAPSHOT, V4_5_0, V4_5_1_SNAPSHOT, V4_5_1, V4_5_2_SNAPSHOT, V4_5_2, V4_5_3_SNAPSHOT, V4_5_3, V4_5_4_SNAPSHOT, V4_5_4, V4_5_5_SNAPSHOT, V4_5_5, V4_5_6_SNAPSHOT, V4_5_6, V4_5_7_SNAPSHOT, V4_5_7, V4_5_8_SNAPSHOT, V4_5_8, V4_5_9_SNAPSHOT, V4_5_9, V4_6_0_SNAPSHOT, V4_6_0, V4_6_1_SNAPSHOT, V4_6_1, V4_6_2_SNAPSHOT, V4_6_2, V4_6_3_SNAPSHOT, V4_6_3, V4_6_4_SNAPSHOT, V4_6_4, V4_6_5_SNAPSHOT, V4_6_5, V4_6_6_SNAPSHOT, V4_6_6, V4_6_7_SNAPSHOT, V4_6_7, V4_6_8_SNAPSHOT, V4_6_8, V4_6_9_SNAPSHOT, V4_6_9, V4_7_0_SNAPSHOT, V4_7_0, V4_7_1_SNAPSHOT, V4_7_1, V4_7_2_SNAPSHOT, V4_7_2, V4_7_3_SNAPSHOT, V4_7_3, V4_7_4_SNAPSHOT, V4_7_4, V4_7_5_SNAPSHOT, V4_7_5, V4_7_6_SNAPSHOT, V4_7_6, V4_7_7_SNAPSHOT, V4_7_7, V4_7_8_SNAPSHOT, V4_7_8, V4_7_9_SNAPSHOT, V4_7_9, V4_8_0_SNAPSHOT, V4_8_0, V4_8_1_SNAPSHOT, V4_8_1, V4_8_2_SNAPSHOT, V4_8_2, V4_8_3_SNAPSHOT, V4_8_3, V4_8_4_SNAPSHOT, V4_8_4, V4_8_5_SNAPSHOT, V4_8_5, V4_8_6_SNAPSHOT, V4_8_6, V4_8_7_SNAPSHOT, V4_8_7, V4_8_8_SNAPSHOT, V4_8_8, V4_8_9_SNAPSHOT, V4_8_9, V4_9_0_SNAPSHOT, V4_9_0, V4_9_1_SNAPSHOT, V4_9_1, V4_9_2_SNAPSHOT, V4_9_2, V4_9_3_SNAPSHOT, V4_9_3, V4_9_4_SNAPSHOT, V4_9_4, V4_9_5_SNAPSHOT, V4_9_5, V4_9_6_SNAPSHOT, V4_9_6, V4_9_7_SNAPSHOT, V4_9_7, V4_9_8_SNAPSHOT, V4_9_8, V4_9_9_SNAPSHOT, V4_9_9, V5_0_0_SNAPSHOT, V5_0_0, V5_0_1_SNAPSHOT, V5_0_1, V5_0_2_SNAPSHOT, V5_0_2, V5_0_3_SNAPSHOT, V5_0_3, V5_0_4_SNAPSHOT, V5_0_4, V5_0_5_SNAPSHOT, V5_0_5, V5_0_6_SNAPSHOT, V5_0_6, V5_0_7_SNAPSHOT, V5_0_7, V5_0_8_SNAPSHOT, V5_0_8, V5_0_9_SNAPSHOT, V5_0_9, V5_1_0_SNAPSHOT, V5_1_0, V5_1_1_SNAPSHOT, V5_1_1, V5_1_2_SNAPSHOT, V5_1_2, V5_1_3_SNAPSHOT, V5_1_3, V5_1_4_SNAPSHOT, V5_1_4, V5_1_5_SNAPSHOT, V5_1_5, V5_1_6_SNAPSHOT, V5_1_6, V5_1_7_SNAPSHOT, V5_1_7, V5_1_8_SNAPSHOT, V5_1_8, V5_1_9_SNAPSHOT, V5_1_9, V5_2_0_SNAPSHOT, V5_2_0, V5_2_1_SNAPSHOT, V5_2_1, V5_2_2_SNAPSHOT, V5_2_2, V5_2_3_SNAPSHOT, V5_2_3, V5_2_4_SNAPSHOT, V5_2_4, V5_2_5_SNAPSHOT, V5_2_5, V5_2_6_SNAPSHOT, V5_2_6, V5_2_7_SNAPSHOT, V5_2_7, V5_2_8_SNAPSHOT, V5_2_8, V5_2_9_SNAPSHOT, V5_2_9, V5_3_0_SNAPSHOT, V5_3_0, V5_3_1_SNAPSHOT, V5_3_1, V5_3_2_SNAPSHOT, V5_3_2, V5_3_3_SNAPSHOT, V5_3_3, V5_3_4_SNAPSHOT, V5_3_4, V5_3_5_SNAPSHOT, V5_3_5, V5_3_6_SNAPSHOT, V5_3_6, V5_3_7_SNAPSHOT, V5_3_7, V5_3_8_SNAPSHOT, V5_3_8, V5_3_9_SNAPSHOT, V5_3_9, V5_4_0_SNAPSHOT, V5_4_0, V5_4_1_SNAPSHOT, V5_4_1, V5_4_2_SNAPSHOT, V5_4_2, V5_4_3_SNAPSHOT, V5_4_3, V5_4_4_SNAPSHOT, V5_4_4, V5_4_5_SNAPSHOT, V5_4_5, V5_4_6_SNAPSHOT, V5_4_6, V5_4_7_SNAPSHOT, V5_4_7, V5_4_8_SNAPSHOT, V5_4_8, V5_4_9_SNAPSHOT, V5_4_9, V5_5_0_SNAPSHOT, V5_5_0, V5_5_1_SNAPSHOT, V5_5_1, V5_5_2_SNAPSHOT, V5_5_2, V5_5_3_SNAPSHOT, V5_5_3, V5_5_4_SNAPSHOT, V5_5_4, V5_5_5_SNAPSHOT, V5_5_5, V5_5_6_SNAPSHOT, V5_5_6, V5_5_7_SNAPSHOT, V5_5_7, V5_5_8_SNAPSHOT, V5_5_8, V5_5_9_SNAPSHOT, V5_5_9, V5_6_0_SNAPSHOT, V5_6_0, V5_6_1_SNAPSHOT, V5_6_1, V5_6_2_SNAPSHOT, V5_6_2, V5_6_3_SNAPSHOT, V5_6_3, V5_6_4_SNAPSHOT, V5_6_4, V5_6_5_SNAPSHOT, V5_6_5, V5_6_6_SNAPSHOT, V5_6_6, V5_6_7_SNAPSHOT, V5_6_7, V5_6_8_SNAPSHOT, V5_6_8, V5_6_9_SNAPSHOT, V5_6_9, V5_7_0_SNAPSHOT, V5_7_0, V5_7_1_SNAPSHOT, V5_7_1, V5_7_2_SNAPSHOT, V5_7_2, V5_7_3_SNAPSHOT, V5_7_3, V5_7_4_SNAPSHOT, V5_7_4, V5_7_5_SNAPSHOT, V5_7_5, V5_7_6_SNAPSHOT, V5_7_6, V5_7_7_SNAPSHOT, V5_7_7, V5_7_8_SNAPSHOT, V5_7_8, V5_7_9_SNAPSHOT, V5_7_9, V5_8_0_SNAPSHOT, V5_8_0, V5_8_1_SNAPSHOT, V5_8_1, V5_8_2_SNAPSHOT, V5_8_2, V5_8_3_SNAPSHOT, V5_8_3, V5_8_4_SNAPSHOT, V5_8_4, V5_8_5_SNAPSHOT, V5_8_5, V5_8_6_SNAPSHOT, V5_8_6, V5_8_7_SNAPSHOT, V5_8_7, V5_8_8_SNAPSHOT, V5_8_8, V5_8_9_SNAPSHOT, V5_8_9, V5_9_0_SNAPSHOT, V5_9_0, V5_9_1_SNAPSHOT, V5_9_1, V5_9_2_SNAPSHOT, V5_9_2, V5_9_3_SNAPSHOT, V5_9_3, V5_9_4_SNAPSHOT, V5_9_4, V5_9_5_SNAPSHOT, V5_9_5, V5_9_6_SNAPSHOT, V5_9_6, V5_9_7_SNAPSHOT, V5_9_7, V5_9_8_SNAPSHOT, V5_9_8, V5_9_9_SNAPSHOT, V5_9_9, HIGHER_VERSION } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/MixAll.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.URL; import java.net.URLConnection; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; import com.google.common.collect.ImmutableSet; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.IOTinyUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MixAll { public static final String ROCKETMQ_HOME_ENV = "ROCKETMQ_HOME"; public static final String ROCKETMQ_HOME_PROPERTY = "rocketmq.home.dir"; /** * unify the home dir */ public static final String ROCKETMQ_HOME_DIR = System.getProperty(ROCKETMQ_HOME_PROPERTY, System.getenv(ROCKETMQ_HOME_ENV)); public static final String NAMESRV_ADDR_ENV = "NAMESRV_ADDR"; public static final String NAMESRV_ADDR_PROPERTY = "rocketmq.namesrv.addr"; public static final String MESSAGE_COMPRESS_TYPE = "rocketmq.message.compressType"; public static final String MESSAGE_COMPRESS_LEVEL = "rocketmq.message.compressLevel"; public static final String DEFAULT_NAMESRV_ADDR_LOOKUP = "jmenv.tbsite.net"; public static final String WS_DOMAIN_NAME = System.getProperty("rocketmq.namesrv.domain", DEFAULT_NAMESRV_ADDR_LOOKUP); public static final String WS_DOMAIN_SUBGROUP = System.getProperty("rocketmq.namesrv.domain.subgroup", "nsaddr"); public static final String DEFAULT_PRODUCER_GROUP = "DEFAULT_PRODUCER"; public static final String DEFAULT_CONSUMER_GROUP = "DEFAULT_CONSUMER"; public static final String TOOLS_CONSUMER_GROUP = "TOOLS_CONSUMER"; public static final String SCHEDULE_CONSUMER_GROUP = "SCHEDULE_CONSUMER"; public static final String FILTERSRV_CONSUMER_GROUP = "FILTERSRV_CONSUMER"; public static final String MONITOR_CONSUMER_GROUP = "__MONITOR_CONSUMER"; public static final String CLIENT_INNER_PRODUCER_GROUP = "CLIENT_INNER_PRODUCER"; public static final String SELF_TEST_PRODUCER_GROUP = "SELF_TEST_P_GROUP"; public static final String SELF_TEST_CONSUMER_GROUP = "SELF_TEST_C_GROUP"; public static final String ONS_HTTP_PROXY_GROUP = "CID_ONS-HTTP-PROXY"; public static final String CID_ONSAPI_PERMISSION_GROUP = "CID_ONSAPI_PERMISSION"; public static final String CID_ONSAPI_OWNER_GROUP = "CID_ONSAPI_OWNER"; public static final String CID_ONSAPI_PULL_GROUP = "CID_ONSAPI_PULL"; public static final String CID_RMQ_SYS_PREFIX = "CID_RMQ_SYS_"; public static final String IS_SUPPORT_HEART_BEAT_V2 = "IS_SUPPORT_HEART_BEAT_V2"; public static final String IS_SUB_CHANGE = "IS_SUB_CHANGE"; public static final List LOCAL_INET_ADDRESS = getLocalInetAddress(); public static final String LOCALHOST = localhost(); public static final String DEFAULT_CHARSET = "UTF-8"; public static final long MASTER_ID = 0L; public static final long FIRST_SLAVE_ID = 1L; public static final long FIRST_BROKER_CONTROLLER_ID = 1L; public static final long CURRENT_JVM_PID = getPID(); public final static int UNIT_PRE_SIZE_FOR_MSG = 28; public final static int ALL_ACK_IN_SYNC_STATE_SET = -1; public static final String RETRY_GROUP_TOPIC_PREFIX = "%RETRY%"; public static final String DLQ_GROUP_TOPIC_PREFIX = "%DLQ%"; public static final String REPLY_TOPIC_POSTFIX = "REPLY_TOPIC"; public static final String UNIQUE_MSG_QUERY_FLAG = "_UNIQUE_KEY_QUERY"; public static final String DEFAULT_TRACE_REGION_ID = "DefaultRegion"; public static final String CONSUME_CONTEXT_TYPE = "ConsumeContextType"; public static final String CID_SYS_RMQ_TRANS = "CID_RMQ_SYS_TRANS"; public static final String ACL_CONF_TOOLS_FILE = "/conf/tools.yml"; public static final String REPLY_MESSAGE_FLAG = "reply"; public static final String LMQ_PREFIX = "%LMQ%"; public static final int LMQ_QUEUE_ID = 0; public static final String LMQ_DISPATCH_SEPARATOR = ","; public static final String REQ_T = "ReqT"; public static final String ROCKETMQ_ZONE_ENV = "ROCKETMQ_ZONE"; public static final String ROCKETMQ_ZONE_PROPERTY = "rocketmq.zone"; public static final String ROCKETMQ_ZONE_MODE_ENV = "ROCKETMQ_ZONE_MODE"; public static final String ROCKETMQ_ZONE_MODE_PROPERTY = "rocketmq.zone.mode"; public static final String ZONE_NAME = "__ZONE_NAME"; public static final String ZONE_MODE = "__ZONE_MODE"; public final static String RPC_REQUEST_HEADER_NAMESPACED_FIELD = "nsd"; public final static String RPC_REQUEST_HEADER_NAMESPACE_FIELD = "ns"; private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static final String LOGICAL_QUEUE_MOCK_BROKER_PREFIX = "__syslo__"; public static final String METADATA_SCOPE_GLOBAL = "__global__"; public static final String LOGICAL_QUEUE_MOCK_BROKER_NAME_NOT_EXIST = "__syslo__none__"; public static final String MULTI_PATH_SPLITTER = System.getProperty("rocketmq.broker.multiPathSplitter", ","); private static final String OS = System.getProperty("os.name").toLowerCase(); public static final long MILLS_FOR_HOUR = TimeUnit.HOURS.toMillis(1); private static final Set PREDEFINE_GROUP_SET = ImmutableSet.of( DEFAULT_CONSUMER_GROUP, DEFAULT_PRODUCER_GROUP, TOOLS_CONSUMER_GROUP, SCHEDULE_CONSUMER_GROUP, FILTERSRV_CONSUMER_GROUP, MONITOR_CONSUMER_GROUP, CLIENT_INNER_PRODUCER_GROUP, SELF_TEST_PRODUCER_GROUP, SELF_TEST_CONSUMER_GROUP, ONS_HTTP_PROXY_GROUP, CID_ONSAPI_PERMISSION_GROUP, CID_ONSAPI_OWNER_GROUP, CID_ONSAPI_PULL_GROUP, CID_SYS_RMQ_TRANS ); public static boolean isWindows() { return OS.contains("win"); } public static boolean isMac() { return OS.contains("mac"); } public static boolean isUnix() { return OS.contains("nix") || OS.contains("nux") || OS.contains("aix"); } public static boolean isSolaris() { return OS.contains("sunos"); } public static String getWSAddr() { String wsDomainName = System.getProperty("rocketmq.namesrv.domain", DEFAULT_NAMESRV_ADDR_LOOKUP); String wsDomainSubgroup = System.getProperty("rocketmq.namesrv.domain.subgroup", "nsaddr"); String wsAddr = "http://" + wsDomainName + ":8080/rocketmq/" + wsDomainSubgroup; if (wsDomainName.indexOf(":") > 0) { wsAddr = "http://" + wsDomainName + "/rocketmq/" + wsDomainSubgroup; } return wsAddr; } public static String getRetryTopic(final String consumerGroup) { return RETRY_GROUP_TOPIC_PREFIX + consumerGroup; } public static String getReplyTopic(final String clusterName) { return clusterName + "_" + REPLY_TOPIC_POSTFIX; } public static boolean isSysConsumerGroup(final String consumerGroup) { return consumerGroup.startsWith(CID_RMQ_SYS_PREFIX); } public static boolean isSysConsumerGroupAndEnableCreate(final String consumerGroup, final boolean isEnableCreateSysGroup) { return isEnableCreateSysGroup && isSysConsumerGroup(consumerGroup); } public static boolean isPredefinedGroup(final String consumerGroup) { return PREDEFINE_GROUP_SET.contains(consumerGroup); } public static String getDLQTopic(final String consumerGroup) { return DLQ_GROUP_TOPIC_PREFIX + consumerGroup; } public static String brokerVIPChannel(final boolean isChange, final String brokerAddr) { if (isChange) { int split = brokerAddr.lastIndexOf(":"); String ip = brokerAddr.substring(0, split); String port = brokerAddr.substring(split + 1); String brokerAddrNew = ip + ":" + (Integer.parseInt(port) - 2); return brokerAddrNew; } else { return brokerAddr; } } public static long getPID() { String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); if (StringUtils.isNotEmpty(processName)) { try { return Long.parseLong(processName.split("@")[0]); } catch (Exception e) { return 0; } } return 0; } public static synchronized void string2File(final String str, final String fileName) throws IOException { String bakFile = fileName + ".bak"; String prevContent = file2String(fileName); if (prevContent != null) { string2FileNotSafe(prevContent, bakFile); } string2FileNotSafe(str, fileName); } public static void string2FileNotSafe(final String str, final String fileName) throws IOException { File file = new File(fileName); File fileParent = file.getParentFile(); if (fileParent != null) { fileParent.mkdirs(); } IOTinyUtils.writeStringToFile(file, str, DEFAULT_CHARSET); } public static synchronized void fsyncDirectory(Path dir) throws IOException { if (!Files.isDirectory(dir)) { throw new NotDirectoryException(dir.toString()); } if (isWindows()) { return; } try (FileChannel fc = FileChannel.open(dir, StandardOpenOption.READ)) { fc.force(true); } } public static String file2String(final String fileName) throws IOException { File file = new File(fileName); return file2String(file); } public static String file2String(final File file) throws IOException { if (file.exists()) { byte[] data = new byte[(int) file.length()]; boolean result; try (FileInputStream inputStream = new FileInputStream(file)) { int len = inputStream.read(data); result = len == data.length; } if (result) { return new String(data, DEFAULT_CHARSET); } } return null; } public static String file2String(final URL url) { InputStream in = null; try { URLConnection urlConnection = url.openConnection(); urlConnection.setUseCaches(false); in = urlConnection.getInputStream(); int len = in.available(); byte[] data = new byte[len]; in.read(data, 0, len); return new String(data, StandardCharsets.UTF_8); } catch (Exception ignored) { } finally { if (null != in) { try { in.close(); } catch (IOException ignored) { } } } return null; } public static void printObjectProperties(final Logger logger, final Object object) { printObjectProperties(logger, object, false); } public static void printObjectProperties(final Logger logger, final Object object, final boolean onlyImportantField) { Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers())) { String name = field.getName(); if (!name.startsWith("this")) { if (onlyImportantField) { Annotation annotation = field.getAnnotation(ImportantField.class); if (null == annotation) { continue; } } Object value = null; try { field.setAccessible(true); value = field.get(object); if (null == value) { value = ""; } } catch (IllegalAccessException e) { log.error("Failed to obtain object properties", e); } if (logger != null) { logger.info(name + "=" + value); } } } } } public static String properties2String(final Properties properties) { return properties2String(properties, false); } public static String properties2String(final Properties properties, final boolean isSort) { StringBuilder sb = new StringBuilder(); Set> entrySet = isSort ? new TreeMap<>(properties).entrySet() : properties.entrySet(); for (Map.Entry entry : entrySet) { if (entry.getValue() != null) { sb.append(entry.getKey().toString() + "=" + entry.getValue().toString() + "\n"); } } return sb.toString(); } public static Properties string2Properties(final String str) { Properties properties = new Properties(); try { InputStream in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET)); properties.load(in); } catch (Exception e) { log.error("Failed to handle properties", e); return null; } return properties; } public static Properties object2Properties(final Object object) { Properties properties = new Properties(); Class objectClass = object.getClass(); while (true) { Field[] fields = objectClass.getDeclaredFields(); for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers())) { String name = field.getName(); if (!name.startsWith("this")) { Object value = null; try { field.setAccessible(true); value = field.get(object); } catch (IllegalAccessException e) { log.error("Failed to handle properties", e); } if (value != null) { properties.setProperty(name, value.toString()); } } } } if (objectClass == Object.class || objectClass.getSuperclass() == Object.class) { break; } objectClass = objectClass.getSuperclass(); } return properties; } public static void properties2Object(final Properties p, final Object object) { Method[] methods = object.getClass().getMethods(); for (Method method : methods) { String mn = method.getName(); if (mn.startsWith("set")) { try { String tmp = mn.substring(4); String first = mn.substring(3, 4); String key = first.toLowerCase() + tmp; String property = p.getProperty(key); if (property != null) { Class[] pt = method.getParameterTypes(); if (pt.length > 0) { String cn = pt[0].getSimpleName(); Object arg; if (cn.equals("int") || cn.equals("Integer")) { arg = Integer.parseInt(property); } else if (cn.equals("long") || cn.equals("Long")) { arg = Long.parseLong(property); } else if (cn.equals("double") || cn.equals("Double")) { arg = Double.parseDouble(property); } else if (cn.equals("boolean") || cn.equals("Boolean")) { arg = Boolean.parseBoolean(property); } else if (cn.equals("float") || cn.equals("Float")) { arg = Float.parseFloat(property); } else if (cn.equals("String")) { property = property.trim(); arg = property; } else { continue; } method.invoke(object, arg); } } } catch (Throwable ignored) { } } } } public static boolean isPropertiesEqual(final Properties p1, final Properties p2) { return p1.equals(p2); } public static boolean isPropertyValid(Properties props, String key, Predicate validator) { return validator.test(props.getProperty(key)); } public static List getLocalInetAddress() { List inetAddressList = new ArrayList<>(); try { Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); while (enumeration.hasMoreElements()) { NetworkInterface networkInterface = enumeration.nextElement(); Enumeration addrs = networkInterface.getInetAddresses(); while (addrs.hasMoreElements()) { inetAddressList.add(addrs.nextElement().getHostAddress()); } } } catch (SocketException e) { throw new RuntimeException("get local inet address fail", e); } return inetAddressList; } private static String localhost() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (Throwable e) { try { String candidatesHost = getLocalhostByNetworkInterface(); if (candidatesHost != null) return candidatesHost; } catch (Exception ignored) { } throw new RuntimeException("InetAddress java.net.InetAddress.getLocalHost() throws UnknownHostException" + FAQUrl.suggestTodo(FAQUrl.UNKNOWN_HOST_EXCEPTION), e); } } //Reverse logic comparing to RemotingUtil method, consider refactor in RocketMQ 5.0 public static String getLocalhostByNetworkInterface() throws SocketException { List candidatesHost = new ArrayList<>(); Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); while (enumeration.hasMoreElements()) { NetworkInterface networkInterface = enumeration.nextElement(); // Workaround for docker0 bridge if ("docker0".equals(networkInterface.getName()) || !networkInterface.isUp()) { continue; } Enumeration addrs = networkInterface.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress address = addrs.nextElement(); if (address.isLoopbackAddress()) { continue; } //ip4 higher priority if (address instanceof Inet6Address) { candidatesHost.add(address.getHostAddress()); continue; } return address.getHostAddress(); } } if (!candidatesHost.isEmpty()) { return candidatesHost.get(0); } // Fallback to loopback return localhost(); } public static boolean compareAndIncreaseOnly(final AtomicLong target, final long value) { long prev = target.get(); while (value > prev) { boolean updated = target.compareAndSet(prev, value); if (updated) return true; prev = target.get(); } return false; } public static String humanReadableByteCount(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); } public static int compareInteger(int x, int y) { return Integer.compare(x, y); } public static int compareLong(long x, long y) { return Long.compare(x, y); } public static boolean isLmq(String lmqMetaData) { return lmqMetaData != null && lmqMetaData.startsWith(LMQ_PREFIX); } public static String dealFilePath(String aclFilePath) { Path path = Paths.get(aclFilePath); return path.normalize().toString(); } public static boolean isSysConsumerGroupPullMessage(String consumerGroup) { if (DEFAULT_CONSUMER_GROUP.equals(consumerGroup) || TOOLS_CONSUMER_GROUP.equals(consumerGroup) || SCHEDULE_CONSUMER_GROUP.equals(consumerGroup) || FILTERSRV_CONSUMER_GROUP.equals(consumerGroup) || MONITOR_CONSUMER_GROUP.equals(consumerGroup) || SELF_TEST_CONSUMER_GROUP.equals(consumerGroup) || ONS_HTTP_PROXY_GROUP.equals(consumerGroup) || CID_ONSAPI_PERMISSION_GROUP.equals(consumerGroup) || CID_ONSAPI_OWNER_GROUP.equals(consumerGroup) || CID_ONSAPI_PULL_GROUP.equals(consumerGroup) || CID_SYS_RMQ_TRANS.equals(consumerGroup) || consumerGroup.startsWith(CID_RMQ_SYS_PREFIX)) { return true; } return false; } public static boolean topicAllowsLMQ(String topic) { return !topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && !topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX) && !topic.startsWith(TopicValidator.SYSTEM_TOPIC_PREFIX) && !topic.equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC); } public static String adjustConfigForPlatform(String config) { if (StringUtils.isNotBlank(config)) { if (isWindows()) { config = StringUtils.replace(config, "\\", "\\\\"); } } return config; } public static long dealTimeToHourStamps(long timeStamp) { if (timeStamp <= 0L) { return timeStamp; } return (timeStamp / MILLS_FOR_HOUR) * MILLS_FOR_HOUR; } public static boolean isHourTime(Long timeStamp) { if (null == timeStamp) { return false; } if (timeStamp <= 0L) { return false; } return timeStamp % MILLS_FOR_HOUR == 0; } public static List getHours(long startTimeMillis, long endTimeMillis) { if (startTimeMillis > endTimeMillis || startTimeMillis <= 0L || endTimeMillis <= 0L) { return null; } List result = new ArrayList<>(); long startHour = dealTimeToHourStamps(startTimeMillis); long endHour = dealTimeToHourStamps(endTimeMillis); long current = startHour; while (current <= endHour) { result.add(current); //protect system self 30 * 24 if (result.size() >= 720) { return result; } current += MILLS_FOR_HOUR; } return result; } public static boolean isByteArrayEqual(byte[] array1, int offset1, int length1, byte[] array2, int offset2, int length2) { if (null == array1 || null == array2) { return false; } if (length1 != length2) { return false; } if (offset1 < 0 || offset1 + length1 > array1.length || offset2 < 0 || offset2 + length2 > array2.length) { throw new ArrayIndexOutOfBoundsException("Invalid array index"); } for (int i = 0; i < length1; i++) { if (array1[offset1 + i] != array2[offset2 + i]) { return false; } } return true; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/ObjectCreator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public interface ObjectCreator { T create(Object... args); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/OrderedConsumptionLevel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public enum OrderedConsumptionLevel { QUEUE(0), SHARDING_KEY(1); private final int value; OrderedConsumptionLevel(int value) { this.value = value; } public int getValue() { return value; } public static OrderedConsumptionLevel valueOf(int value) { if (value == 1) { return SHARDING_KEY; } return QUEUE; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/Pair.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.io.Serializable; public class Pair implements Serializable { private T1 object1; private T2 object2; public Pair(T1 object1, T2 object2) { this.object1 = object1; this.object2 = object2; } public static Pair of(T1 object1, T2 object2) { return new Pair<>(object1, object2); } public T1 getObject1() { return object1; } public void setObject1(T1 object1) { this.object1 = object1; } public T2 getObject2() { return object2; } public void setObject2(T2 object2) { this.object2 = object2; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.apache.rocketmq.common.topic.TopicValidator; public class PopAckConstants { public static long ackTimeInterval = 1000; public static final long SECOND = 1000; public static long lockTime = 5000; public static int retryQueueNum = 1; public static final String REVIVE_GROUP = MixAll.CID_RMQ_SYS_PREFIX + "REVIVE_GROUP"; public static final String LOCAL_HOST = "127.0.0.1"; public static final String REVIVE_TOPIC = TopicValidator.SYSTEM_TOPIC_PREFIX + "REVIVE_LOG_"; public static final String CK_TAG = "ck"; public static final String ACK_TAG = "ack"; public static final String BATCH_ACK_TAG = "bAck"; public static final String SPLIT = "@"; /** * Build cluster revive topic * * @param clusterName cluster name * @return revive topic */ public static String buildClusterReviveTopic(String clusterName) { return PopAckConstants.REVIVE_TOPIC + clusterName; } public static boolean isStartWithRevivePrefix(String topicName) { return topicName != null && topicName.startsWith(REVIVE_TOPIC); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/ServiceState.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public enum ServiceState { /** * Service just created,not start */ CREATE_JUST, /** * Service Running */ RUNNING, /** * Service shutdown */ SHUTDOWN_ALREADY, /** * Service Start failure */ START_FAILED; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/ServiceThread.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class ServiceThread implements Runnable { protected static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static final long JOIN_TIME = 90 * 1000; protected Thread thread; protected final CountDownLatch2 waitPoint = new CountDownLatch2(1); protected volatile AtomicBoolean hasNotified = new AtomicBoolean(false); protected volatile boolean stopped = false; protected boolean isDaemon = false; //Make it able to restart the thread private final AtomicBoolean started = new AtomicBoolean(false); public ServiceThread() { } public String getServiceName() { return this.getClass().getSimpleName(); } public void start() { log.info("Try to start service thread:{} started:{} lastThread:{}", getServiceName(), started.get(), thread); if (!started.compareAndSet(false, true)) { return; } stopped = false; this.thread = new Thread(this, getServiceName()); this.thread.setDaemon(isDaemon); this.thread.start(); log.info("Start service thread:{} started:{} lastThread:{}", getServiceName(), started.get(), thread); } public void shutdown() { this.shutdown(false); } public void shutdown(final boolean interrupt) { log.info("Try to shutdown service thread:{} started:{} lastThread:{}", getServiceName(), started.get(), thread); if (!started.compareAndSet(true, false)) { return; } this.stopped = true; log.info("shutdown thread[{}] interrupt={} ", getServiceName(), interrupt); //if thead is waiting, wakeup it wakeup(); try { if (interrupt) { this.thread.interrupt(); } long beginTime = System.currentTimeMillis(); if (!this.thread.isDaemon()) { this.thread.join(this.getJoinTime()); } long elapsedTime = System.currentTimeMillis() - beginTime; log.info("join thread[{}], elapsed time: {}ms, join time:{}ms", getServiceName(), elapsedTime, this.getJoinTime()); } catch (InterruptedException e) { log.error("Interrupted", e); } } public long getJoinTime() { return JOIN_TIME; } public void makeStop() { if (!started.get()) { return; } this.stopped = true; log.info("makestop thread[{}] ", this.getServiceName()); } public void wakeup() { if (hasNotified.compareAndSet(false, true)) { waitPoint.countDown(); // notify } } protected void waitForRunning(long interval) { if (hasNotified.compareAndSet(true, false)) { this.onWaitEnd(); return; } //entry to wait waitPoint.reset(); try { waitPoint.await(interval, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("Interrupted", e); } finally { hasNotified.set(false); this.onWaitEnd(); } } protected void onWaitEnd() { } public boolean isStopped() { return stopped; } public boolean isDaemon() { return isDaemon; } public void setDaemon(boolean daemon) { isDaemon = daemon; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/SubscriptionGroupAttributes.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import static com.google.common.collect.Sets.newHashSet; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.attribute.Attribute; import org.apache.rocketmq.common.attribute.BooleanAttribute; import org.apache.rocketmq.common.attribute.EnumAttribute; import org.apache.rocketmq.common.attribute.LongRangeAttribute; import org.apache.rocketmq.common.attribute.StringAttribute; import org.apache.rocketmq.common.attribute.LiteSubModel; public class SubscriptionGroupAttributes { public static final Map ALL; public static final LongRangeAttribute PRIORITY_FACTOR_ATTRIBUTE = new LongRangeAttribute( "priority.factor", true, 0, // disable priority mode 100, // enable priority mode 100 ); public static final StringAttribute LITE_BIND_TOPIC_ATTRIBUTE = new StringAttribute( "lite.bind.topic", true ); public static final EnumAttribute LITE_SUB_MODEL_ATTRIBUTE = new EnumAttribute( "lite.sub.model", true, newHashSet(LiteSubModel.Shared.name(), LiteSubModel.Exclusive.name()), LiteSubModel.Shared.name() ); public static final BooleanAttribute LITE_SUB_RESET_OFFSET_EXCLUSIVE_ATTRIBUTE = new BooleanAttribute( "lite.sub.reset.offset.exclusive", true, false ); public static final BooleanAttribute LITE_SUB_RESET_OFFSET_UNSUBSCRIBE_ATTRIBUTE = new BooleanAttribute( "lite.sub.reset.offset.unsubscribe", true, false ); /** * client-side lite subscription quota limit */ public static final LongRangeAttribute LITE_SUB_CLIENT_QUOTA_ATTRIBUTE = new LongRangeAttribute( "lite.sub.client.quota", true, -1, Long.MAX_VALUE, 2000 ); public static final LongRangeAttribute LITE_SUB_CLIENT_MAX_EVENT_COUNT = new LongRangeAttribute( "lite.sub.client.max.event.cnt", true, 10, Long.MAX_VALUE, 400 ); static { ALL = new HashMap<>(); ALL.put(PRIORITY_FACTOR_ATTRIBUTE.getName(), PRIORITY_FACTOR_ATTRIBUTE); ALL.put(LITE_BIND_TOPIC_ATTRIBUTE.getName(), LITE_BIND_TOPIC_ATTRIBUTE); ALL.put(LITE_SUB_CLIENT_QUOTA_ATTRIBUTE.getName(), LITE_SUB_CLIENT_QUOTA_ATTRIBUTE); ALL.put(LITE_SUB_MODEL_ATTRIBUTE.getName(), LITE_SUB_MODEL_ATTRIBUTE); ALL.put(LITE_SUB_RESET_OFFSET_EXCLUSIVE_ATTRIBUTE.getName(), LITE_SUB_RESET_OFFSET_EXCLUSIVE_ATTRIBUTE); ALL.put(LITE_SUB_RESET_OFFSET_UNSUBSCRIBE_ATTRIBUTE.getName(), LITE_SUB_RESET_OFFSET_UNSUBSCRIBE_ATTRIBUTE); ALL.put(LITE_SUB_CLIENT_MAX_EVENT_COUNT.getName(), LITE_SUB_CLIENT_MAX_EVENT_COUNT); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/SystemClock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public class SystemClock { public long now() { return System.currentTimeMillis(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/ThreadFactoryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ThreadFactoryImpl implements ThreadFactory { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private final AtomicLong threadIndex = new AtomicLong(0); private final String threadNamePrefix; private final boolean daemon; public ThreadFactoryImpl(final String threadNamePrefix) { this(threadNamePrefix, false); } public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) { this.threadNamePrefix = threadNamePrefix; this.daemon = daemon; } public ThreadFactoryImpl(final String threadNamePrefix, BrokerIdentity brokerIdentity) { this(threadNamePrefix, false, brokerIdentity); } public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon, BrokerIdentity brokerIdentity) { this.daemon = daemon; if (brokerIdentity != null && brokerIdentity.isInBrokerContainer()) { this.threadNamePrefix = brokerIdentity.getIdentifier() + threadNamePrefix; } else { this.threadNamePrefix = threadNamePrefix; } } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, threadNamePrefix + this.threadIndex.incrementAndGet()); thread.setDaemon(daemon); // Log all uncaught exception thread.setUncaughtExceptionHandler((t, e) -> LOGGER.error("[BUG] Thread has an uncaught exception, threadId={}, threadName={}", t.getId(), t.getName(), e)); return thread; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/TopicAttributes.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.attribute.Attribute; import org.apache.rocketmq.common.attribute.EnumAttribute; import org.apache.rocketmq.common.attribute.LongRangeAttribute; import org.apache.rocketmq.common.attribute.TopicMessageType; import static com.google.common.collect.Sets.newHashSet; public class TopicAttributes { public static final EnumAttribute QUEUE_TYPE_ATTRIBUTE = new EnumAttribute( "queue.type", false, newHashSet("BatchCQ", "SimpleCQ"), "SimpleCQ" ); public static final EnumAttribute CLEANUP_POLICY_ATTRIBUTE = new EnumAttribute( "cleanup.policy", false, newHashSet("DELETE", "COMPACTION"), "DELETE" ); public static final EnumAttribute TOPIC_MESSAGE_TYPE_ATTRIBUTE = new EnumAttribute( "message.type", true, TopicMessageType.topicMessageTypeSet(), TopicMessageType.NORMAL.getValue() ); public static final LongRangeAttribute TOPIC_RESERVE_TIME_ATTRIBUTE = new LongRangeAttribute( "reserve.time", true, -1, Long.MAX_VALUE, -1 ); public static final LongRangeAttribute LITE_EXPIRATION_ATTRIBUTE = new LongRangeAttribute( "lite.topic.expiration", true, -1, TimeUnit.DAYS.toMinutes(30), -1 ); public static final Map ALL; static { ALL = new HashMap<>(); ALL.put(QUEUE_TYPE_ATTRIBUTE.getName(), QUEUE_TYPE_ATTRIBUTE); ALL.put(CLEANUP_POLICY_ATTRIBUTE.getName(), CLEANUP_POLICY_ATTRIBUTE); ALL.put(TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), TOPIC_MESSAGE_TYPE_ATTRIBUTE); ALL.put(TOPIC_RESERVE_TIME_ATTRIBUTE.getName(), TOPIC_RESERVE_TIME_ATTRIBUTE); ALL.put(LITE_EXPIRATION_ATTRIBUTE.getName(), LITE_EXPIRATION_ATTRIBUTE); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/TopicConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; import com.alibaba.fastjson2.annotation.JSONField; import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.apache.commons.lang3.math.NumberUtils; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; import static org.apache.rocketmq.common.TopicAttributes.LITE_EXPIRATION_ATTRIBUTE; import static org.apache.rocketmq.common.TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE; public class TopicConfig { private static final String SEPARATOR = " "; public static int defaultReadQueueNums = 16; public static int defaultWriteQueueNums = 16; private static final TypeReference> ATTRIBUTES_TYPE_REFERENCE = new TypeReference>() { }; private String topicName; private int readQueueNums = defaultReadQueueNums; private int writeQueueNums = defaultWriteQueueNums; private int perm = PermName.PERM_READ | PermName.PERM_WRITE; private TopicFilterType topicFilterType = TopicFilterType.SINGLE_TAG; private int topicSysFlag = 0; private boolean order = false; // Field attributes should not have ' ' char in key or value, otherwise will lead to decode failure. private Map attributes = new HashMap<>(); public TopicConfig() { } public TopicConfig(String topicName) { this.topicName = topicName; } public TopicConfig(String topicName, int readQueueNums, int writeQueueNums) { this.topicName = topicName; this.readQueueNums = readQueueNums; this.writeQueueNums = writeQueueNums; } public TopicConfig(String topicName, int readQueueNums, int writeQueueNums, int perm) { this.topicName = topicName; this.readQueueNums = readQueueNums; this.writeQueueNums = writeQueueNums; this.perm = perm; } public TopicConfig(String topicName, int readQueueNums, int writeQueueNums, int perm, int topicSysFlag) { this.topicName = topicName; this.readQueueNums = readQueueNums; this.writeQueueNums = writeQueueNums; this.perm = perm; this.topicSysFlag = topicSysFlag; } public TopicConfig(TopicConfig other) { this.topicName = other.topicName; this.readQueueNums = other.readQueueNums; this.writeQueueNums = other.writeQueueNums; this.perm = other.perm; this.topicFilterType = other.topicFilterType; this.topicSysFlag = other.topicSysFlag; this.order = other.order; this.attributes = other.attributes; } public String encode() { StringBuilder sb = new StringBuilder(); //[0] sb.append(this.topicName); sb.append(SEPARATOR); //[1] sb.append(this.readQueueNums); sb.append(SEPARATOR); //[2] sb.append(this.writeQueueNums); sb.append(SEPARATOR); //[3] sb.append(this.perm); sb.append(SEPARATOR); //[4] sb.append(this.topicFilterType); sb.append(SEPARATOR); //[5] if (attributes != null) { sb.append(JSON.toJSONString(attributes)); } return sb.toString(); } public boolean decode(final String in) { String[] strs = in.split(SEPARATOR); if (strs.length >= 5) { this.topicName = strs[0]; this.readQueueNums = Integer.parseInt(strs[1]); this.writeQueueNums = Integer.parseInt(strs[2]); this.perm = Integer.parseInt(strs[3]); this.topicFilterType = TopicFilterType.valueOf(strs[4]); if (strs.length >= 6) { try { this.attributes = JSON.parseObject(strs[5], ATTRIBUTES_TYPE_REFERENCE.getType()); } catch (Exception e) { // ignore exception when parse failed, cause map's key/value can have ' ' char. } } return true; } return false; } public String getTopicName() { return topicName; } public void setTopicName(String topicName) { this.topicName = topicName; } public int getReadQueueNums() { return readQueueNums; } public void setReadQueueNums(int readQueueNums) { this.readQueueNums = readQueueNums; } public int getWriteQueueNums() { return writeQueueNums; } public void setWriteQueueNums(int writeQueueNums) { this.writeQueueNums = writeQueueNums; } public int getPerm() { return perm; } public void setPerm(int perm) { this.perm = perm; } public TopicFilterType getTopicFilterType() { return topicFilterType; } public void setTopicFilterType(TopicFilterType topicFilterType) { this.topicFilterType = topicFilterType; } public int getTopicSysFlag() { return topicSysFlag; } public void setTopicSysFlag(int topicSysFlag) { this.topicSysFlag = topicSysFlag; } public boolean isOrder() { return order; } public void setOrder(boolean isOrder) { this.order = isOrder; } public Map getAttributes() { return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @JSONField(serialize = false, deserialize = false) public TopicMessageType getTopicMessageType() { if (attributes == null) { return TopicMessageType.NORMAL; } String content = attributes.get(TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName()); if (content == null) { return TopicMessageType.NORMAL; } return TopicMessageType.valueOf(content); } @JSONField(serialize = false, deserialize = false) public void setTopicMessageType(TopicMessageType topicMessageType) { attributes.put(TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), topicMessageType.getValue()); } @JSONField(serialize = false, deserialize = false) public void setLiteTopicExpiration(int liteTopicExpiration) { if (!TopicMessageType.LITE.equals(getTopicMessageType())) { return; } attributes.put(LITE_EXPIRATION_ATTRIBUTE.getName(), String.valueOf(liteTopicExpiration)); } @JSONField(serialize = false, deserialize = false) public int getLiteTopicExpiration() { if (!TopicMessageType.LITE.equals(getTopicMessageType())) { return -1; } String content = attributes.get(LITE_EXPIRATION_ATTRIBUTE.getName()); if (content == null) { return -1; } return NumberUtils.toInt(content, -1); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TopicConfig that = (TopicConfig) o; if (readQueueNums != that.readQueueNums) { return false; } if (writeQueueNums != that.writeQueueNums) { return false; } if (perm != that.perm) { return false; } if (topicSysFlag != that.topicSysFlag) { return false; } if (order != that.order) { return false; } if (!Objects.equals(topicName, that.topicName)) { return false; } if (topicFilterType != that.topicFilterType) { return false; } return Objects.equals(attributes, that.attributes); } @Override public int hashCode() { int result = topicName != null ? topicName.hashCode() : 0; result = 31 * result + readQueueNums; result = 31 * result + writeQueueNums; result = 31 * result + perm; result = 31 * result + (topicFilterType != null ? topicFilterType.hashCode() : 0); result = 31 * result + topicSysFlag; result = 31 * result + (order ? 1 : 0); result = 31 * result + (attributes != null ? attributes.hashCode() : 0); return result; } @Override public String toString() { return "TopicConfig [topicName=" + topicName + ", readQueueNums=" + readQueueNums + ", writeQueueNums=" + writeQueueNums + ", perm=" + PermName.perm2String(perm) + ", topicFilterType=" + topicFilterType + ", topicSysFlag=" + topicSysFlag + ", order=" + order + ", attributes=" + attributes + "]"; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/TopicFilterType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public enum TopicFilterType { SINGLE_TAG, MULTI_TAG } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/TopicQueueId.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import com.google.common.base.Objects; public class TopicQueueId { private final String topic; private final int queueId; private final int hash; public TopicQueueId(String topic, int queueId) { this.topic = topic; this.queueId = queueId; this.hash = Objects.hashCode(topic, queueId); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TopicQueueId broker = (TopicQueueId) o; return queueId == broker.queueId && Objects.equal(topic, broker.topic); } @Override public int hashCode() { return hash; } @Override public String toString() { final StringBuilder sb = new StringBuilder("MessageQueueInBroker{"); sb.append("topic='").append(topic).append('\''); sb.append(", queueId=").append(queueId); sb.append('}'); return sb.toString(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/UnlockCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; public interface UnlockCallback { void onSuccess(); void onException(final Throwable e); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/UtilAll.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import io.netty.util.internal.PlatformDependent; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.nio.ByteBuffer; import java.nio.file.Files; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.zip.CRC32; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import java.util.Collections; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.InetAddressValidator; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class UtilAll { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static final Logger STORE_LOG = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; public static final String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd#HH:mm:ss:SSS"; public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; private final static char[] HEX_ARRAY; private final static int PID; static { HEX_ARRAY = "0123456789ABCDEF".toCharArray(); Supplier supplier = () -> { // format: "pid@hostname" String currentJVM = ManagementFactory.getRuntimeMXBean().getName(); try { return Integer.parseInt(currentJVM.substring(0, currentJVM.indexOf('@'))); } catch (Exception e) { return -1; } }; PID = supplier.get(); } public static int getPid() { return PID; } public static void sleep(long sleepMs) { sleep(sleepMs, TimeUnit.MILLISECONDS); } public static void sleep(long timeOut, TimeUnit timeUnit) { if (null == timeUnit) { return; } try { timeUnit.sleep(timeOut); } catch (Throwable ignored) { } } public static String currentStackTrace() { StringBuilder sb = new StringBuilder(); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement ste : stackTrace) { sb.append("\n\t"); sb.append(ste.toString()); } return sb.toString(); } public static String offset2FileName(final long offset) { final NumberFormat nf = NumberFormat.getInstance(); nf.setMinimumIntegerDigits(20); nf.setMaximumFractionDigits(0); nf.setGroupingUsed(false); return nf.format(offset); } public static long computeElapsedTimeMilliseconds(final long beginTime) { return System.currentTimeMillis() - beginTime; } public static boolean isItTimeToDo(final String when) { String[] whiles = when.split(";"); if (whiles.length > 0) { Calendar now = Calendar.getInstance(); for (String w : whiles) { int nowHour = Integer.parseInt(w); if (nowHour == now.get(Calendar.HOUR_OF_DAY)) { return true; } } } return false; } public static String timeMillisToHumanString() { return timeMillisToHumanString(System.currentTimeMillis()); } public static String timeMillisToHumanString(final long t) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(t); return String.format("%04d%02d%02d%02d%02d%02d%03d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND)); } public static long computeNextMorningTimeMillis() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.DAY_OF_MONTH, 1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTimeInMillis(); } public static long computeNextMinutesTimeMillis() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.DAY_OF_MONTH, 0); cal.add(Calendar.HOUR_OF_DAY, 0); cal.add(Calendar.MINUTE, 1); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTimeInMillis(); } public static long computeNextHourTimeMillis() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.DAY_OF_MONTH, 0); cal.add(Calendar.HOUR_OF_DAY, 1); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTimeInMillis(); } public static long computeNextHalfHourTimeMillis() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.DAY_OF_MONTH, 0); cal.add(Calendar.HOUR_OF_DAY, 1); cal.set(Calendar.MINUTE, 30); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTimeInMillis(); } public static String timeMillisToHumanString2(final long t) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(t); return String.format("%04d-%02d-%02d %02d:%02d:%02d,%03d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND)); } public static String timeMillisToHumanString3(final long t) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(t); return String.format("%04d%02d%02d%02d%02d%02d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND)); } public static long getTotalSpace(final String path) { if (null == path || path.isEmpty()) return -1; try { File file = new File(path); if (!file.exists()) return -1; return file.getTotalSpace(); } catch (Exception e) { return -1; } } public static boolean isPathExists(final String path) { File file = new File(path); return file.exists(); } public static double getDiskPartitionSpaceUsedPercent(final String path) { if (null == path || path.isEmpty()) { STORE_LOG.error("Error when measuring disk space usage, path is null or empty, path : {}", path); return -1; } try { File file = new File(path); if (!file.exists()) { STORE_LOG.error("Error when measuring disk space usage, file doesn't exist on this path: {}", path); return -1; } long totalSpace = file.getTotalSpace(); if (totalSpace > 0) { long usedSpace = totalSpace - file.getFreeSpace(); long usableSpace = file.getUsableSpace(); long entireSpace = usedSpace + usableSpace; long roundNum = 0; if (usedSpace * 100 % entireSpace != 0) { roundNum = 1; } long result = usedSpace * 100 / entireSpace + roundNum; return result / 100.0; } } catch (Exception e) { STORE_LOG.error("Error when measuring disk space usage, got exception: :", e); return -1; } return -1; } public static long getDiskPartitionTotalSpace(final String path) { if (null == path || path.isEmpty()) { return -1; } try { File file = new File(path); if (!file.exists()) { return -1; } return file.getTotalSpace() - file.getFreeSpace() + file.getUsableSpace(); } catch (Exception e) { return -1; } } public static int crc32(byte[] array) { if (array != null) { return crc32(array, 0, array.length); } return 0; } public static int crc32(byte[] array, int offset, int length) { CRC32 crc32 = new CRC32(); crc32.update(array, offset, length); return (int) (crc32.getValue() & 0x7FFFFFFF); } public static int crc32(ByteBuffer byteBuffer) { CRC32 crc32 = new CRC32(); crc32.update(byteBuffer); return (int) (crc32.getValue() & 0x7FFFFFFF); } public static int crc32(ByteBuffer[] byteBuffers) { CRC32 crc32 = new CRC32(); for (ByteBuffer buffer : byteBuffers) { crc32.update(buffer); } return (int) (crc32.getValue() & 0x7FFFFFFF); } public static String bytes2string(byte[] src) { char[] hexChars = new char[src.length * 2]; for (int j = 0; j < src.length; j++) { int v = src[j] & 0xFF; hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } return new String(hexChars); } public static void writeInt(char[] buffer, int pos, int value) { for (int moveBits = 28; moveBits >= 0; moveBits -= 4) { buffer[pos++] = HEX_ARRAY[(value >>> moveBits) & 0x0F]; } } public static void writeShort(char[] buffer, int pos, int value) { for (int moveBits = 12; moveBits >= 0; moveBits -= 4) { buffer[pos++] = HEX_ARRAY[(value >>> moveBits) & 0x0F]; } } public static byte[] string2bytes(String hexString) { if (hexString == null || hexString.equals("")) { return null; } hexString = hexString.toUpperCase(); int length = hexString.length() / 2; char[] hexChars = hexString.toCharArray(); byte[] d = new byte[length]; for (int i = 0; i < length; i++) { int pos = i * 2; d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); } return d; } private static byte charToByte(char c) { return (byte) "0123456789ABCDEF".indexOf(c); } /** * use {@link org.apache.rocketmq.common.compression.Compressor#decompress(byte[])} instead. */ @Deprecated public static byte[] uncompress(final byte[] src) throws IOException { byte[] result = src; byte[] uncompressData = new byte[src.length]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src); InflaterInputStream inflaterInputStream = new InflaterInputStream(byteArrayInputStream); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); try { while (true) { int len = inflaterInputStream.read(uncompressData, 0, uncompressData.length); if (len <= 0) { break; } byteArrayOutputStream.write(uncompressData, 0, len); } byteArrayOutputStream.flush(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { throw e; } finally { try { byteArrayInputStream.close(); } catch (IOException e) { log.error("Failed to close the stream", e); } try { inflaterInputStream.close(); } catch (IOException e) { log.error("Failed to close the stream", e); } try { byteArrayOutputStream.close(); } catch (IOException e) { log.error("Failed to close the stream", e); } } return result; } /** * use {@link org.apache.rocketmq.common.compression.Compressor#compress(byte[], int)} instead. */ @Deprecated public static byte[] compress(final byte[] src, final int level) throws IOException { byte[] result = src; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); java.util.zip.Deflater defeater = new java.util.zip.Deflater(level); DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, defeater); try { deflaterOutputStream.write(src); deflaterOutputStream.finish(); deflaterOutputStream.close(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { defeater.end(); throw e; } finally { try { byteArrayOutputStream.close(); } catch (IOException ignored) { } defeater.end(); } return result; } public static int asInt(String str, int defaultValue) { try { return Integer.parseInt(str); } catch (Exception e) { return defaultValue; } } public static long asLong(String str, long defaultValue) { try { return Long.parseLong(str); } catch (Exception e) { return defaultValue; } } public static String formatDate(Date date, String pattern) { SimpleDateFormat df = new SimpleDateFormat(pattern); return df.format(date); } public static Date parseDate(String date, String pattern) { SimpleDateFormat df = new SimpleDateFormat(pattern); try { return df.parse(date); } catch (ParseException e) { return null; } } public static String responseCode2String(final int code) { return Integer.toString(code); } public static String frontStringAtLeast(final String str, final int size) { if (str != null) { if (str.length() > size) { return str.substring(0, size); } } return str; } public static boolean isBlank(String str) { return StringUtils.isBlank(str); } public static String jstack() { return jstack(Thread.getAllStackTraces()); } public static String jstack(Map map) { StringBuilder result = new StringBuilder(); try { Iterator> ite = map.entrySet().iterator(); while (ite.hasNext()) { Map.Entry entry = ite.next(); StackTraceElement[] elements = entry.getValue(); Thread thread = entry.getKey(); if (elements != null && elements.length > 0) { String threadName = entry.getKey().getName(); result.append(String.format("%-40sTID: %d STATE: %s%n", threadName, thread.getId(), thread.getState())); for (StackTraceElement el : elements) { result.append(String.format("%-40s%s%n", threadName, el.toString())); } result.append("\n"); } } } catch (Throwable e) { result.append(exceptionSimpleDesc(e)); } return result.toString(); } public static String exceptionSimpleDesc(final Throwable e) { StringBuilder sb = new StringBuilder(); if (e != null) { sb.append(e); StackTraceElement[] stackTrace = e.getStackTrace(); if (stackTrace != null && stackTrace.length > 0) { StackTraceElement element = stackTrace[0]; sb.append(", "); sb.append(element.toString()); } } return sb.toString(); } public static boolean isInternalIP(byte[] ip) { if (ip.length != 4) { throw new RuntimeException("illegal ipv4 bytes"); } //10.0.0.0~10.255.255.255 //172.16.0.0~172.31.255.255 //192.168.0.0~192.168.255.255 //127.0.0.0~127.255.255.255 if (ip[0] == (byte) 10) { return true; } else if (ip[0] == (byte) 127) { return true; } else if (ip[0] == (byte) 172) { return ip[1] >= (byte) 16 && ip[1] <= (byte) 31; } else if (ip[0] == (byte) 192) { return ip[1] == (byte) 168; } return false; } public static boolean isInternalV6IP(InetAddress inetAddr) { return inetAddr.isAnyLocalAddress() // Wild card ipv6 || inetAddr.isLinkLocalAddress() // Single broadcast ipv6 address: fe80:xx:xx... || inetAddr.isLoopbackAddress() //Loopback ipv6 address || inetAddr.isSiteLocalAddress();// Site local ipv6 address: fec0:xx:xx... } private static boolean ipCheck(byte[] ip) { if (ip.length != 4) { throw new RuntimeException("illegal ipv4 bytes"); } InetAddressValidator validator = InetAddressValidator.getInstance(); return validator.isValidInet4Address(ipToIPv4Str(ip)); } private static boolean ipV6Check(byte[] ip) { if (ip.length != 16) { throw new RuntimeException("illegal ipv6 bytes"); } InetAddressValidator validator = InetAddressValidator.getInstance(); return validator.isValidInet6Address(ipToIPv6Str(ip)); } public static String ipToIPv4Str(byte[] ip) { if (ip.length != 4) { return null; } return new StringBuilder().append(ip[0] & 0xFF).append(".").append( ip[1] & 0xFF).append(".").append(ip[2] & 0xFF) .append(".").append(ip[3] & 0xFF).toString(); } public static String ipToIPv6Str(byte[] ip) { if (ip.length != 16) { return null; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < ip.length; i++) { String hex = Integer.toHexString(ip[i] & 0xFF); if (hex.length() < 2) { sb.append(0); } sb.append(hex); if (i % 2 == 1 && i < ip.length - 1) { sb.append(":"); } } return sb.toString(); } public static byte[] getIP() { try { Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); InetAddress ip; byte[] internalIP = null; while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = allNetInterfaces.nextElement(); Enumeration addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { ip = addresses.nextElement(); if (ip instanceof Inet4Address) { byte[] ipByte = ip.getAddress(); if (ipByte.length == 4) { if (ipCheck(ipByte)) { if (!isInternalIP(ipByte)) { return ipByte; } else if (internalIP == null || internalIP[0] == (byte) 127) { internalIP = ipByte; } } } } else if (ip instanceof Inet6Address) { byte[] ipByte = ip.getAddress(); if (ipByte.length == 16) { if (ipV6Check(ipByte)) { if (!isInternalV6IP(ip)) { return ipByte; } } } } } } if (internalIP != null) { return internalIP; } else { throw new RuntimeException("Can not get local ip"); } } catch (Exception e) { throw new RuntimeException("Can not get local ip", e); } } public static void deleteFile(File file) { if (!file.exists()) { return; } if (file.isFile()) { file.delete(); } else if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) { for (File file1 : files) { deleteFile(file1); } } file.delete(); } } public static String join(List list, String splitter) { if (list == null) { return null; } StringBuilder str = new StringBuilder(); for (int i = 0; i < list.size(); i++) { str.append(list.get(i)); if (i == list.size() - 1) { break; } str.append(splitter); } return str.toString(); } public static List split(String str, String splitter) { if (str == null) { return null; } if (StringUtils.isBlank(str)) { return Collections.EMPTY_LIST; } String[] addrArray = str.split(splitter); return Arrays.asList(addrArray); } public static void deleteEmptyDirectory(File file) { if (file == null || !file.exists()) { return; } if (!file.isDirectory()) { return; } File[] files = file.listFiles(); if (files == null || files.length <= 0) { file.delete(); STORE_LOG.info("delete empty direct, {}", file.getPath()); } } /** * Free direct-buffer's memory actively. * @param buffer Direct buffer to free. */ public static void cleanBuffer(final ByteBuffer buffer) { if (null == buffer) { return; } if (!buffer.isDirect()) { return; } PlatformDependent.freeDirectBuffer(buffer); } public static void ensureDirOK(final String dirName) { if (dirName != null) { if (dirName.contains(MixAll.MULTI_PATH_SPLITTER)) { String[] dirs = dirName.trim().split(MixAll.MULTI_PATH_SPLITTER); for (String dir : dirs) { createDirIfNotExist(dir); } } else { createDirIfNotExist(dirName); } } } private static void createDirIfNotExist(String dirName) { File f = new File(dirName); if (!f.exists()) { boolean result = f.mkdirs(); STORE_LOG.info(dirName + " mkdir " + (result ? "OK" : "Failed")); } } public static long calculateFileSizeInPath(File path) { long size = 0; try { if (!path.exists() || Files.isSymbolicLink(path.toPath())) { return 0; } if (path.isFile()) { return path.length(); } if (path.isDirectory()) { File[] files = path.listFiles(); if (files != null && files.length > 0) { for (File file : files) { long fileSize = calculateFileSizeInPath(file); if (fileSize == -1) return -1; size += fileSize; } } } } catch (Exception e) { log.error("calculate all file size in: {} error", path.getAbsolutePath(), e); return -1; } return size; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/action/Action.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.action; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum Action { UNKNOWN((byte) 0, "Unknown"), ALL((byte) 1, "All"), ANY((byte) 2, "Any"), PUB((byte) 3, "Pub"), SUB((byte) 4, "Sub"), CREATE((byte) 5, "Create"), UPDATE((byte) 6, "Update"), DELETE((byte) 7, "Delete"), GET((byte) 8, "Get"), LIST((byte) 9, "List"); @JSONField(value = true) private final byte code; private final String name; Action(byte code, String name) { this.code = code; this.name = name; } public static Action getByName(String name) { for (Action action : Action.values()) { if (StringUtils.equalsIgnoreCase(action.getName(), name)) { return action; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/action/RocketMQAction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.action; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.apache.rocketmq.common.resource.ResourceType; @Retention(RetentionPolicy.RUNTIME) public @interface RocketMQAction { int value(); ResourceType resource() default ResourceType.UNKNOWN; Action[] action(); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/annotation/ImportantField.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) public @interface ImportantField { } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/Attribute.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; public abstract class Attribute { protected String name; protected boolean changeable; public abstract void verify(String value); public Attribute(String name, boolean changeable) { this.name = name; this.changeable = changeable; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isChangeable() { return changeable; } public void setChangeable(boolean changeable) { this.changeable = changeable; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/AttributeParser.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import com.google.common.base.Strings; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class AttributeParser { public static final String ATTR_ARRAY_SEPARATOR_COMMA = ","; public static final String ATTR_KEY_VALUE_EQUAL_SIGN = "="; public static final String ATTR_ADD_PLUS_SIGN = "+"; private static final String ATTR_DELETE_MINUS_SIGN = "-"; public static Map parseToMap(String attributesModification) { if (Strings.isNullOrEmpty(attributesModification)) { return new HashMap<>(); } // format: +key1=value1,+key2=value2,-key3,+key4=value4 Map attributes = new HashMap<>(); String[] kvs = attributesModification.split(ATTR_ARRAY_SEPARATOR_COMMA); for (String kv : kvs) { String key; String value; if (kv.contains(ATTR_KEY_VALUE_EQUAL_SIGN)) { String[] splits = kv.split(ATTR_KEY_VALUE_EQUAL_SIGN); key = splits[0]; value = splits[1]; if (!key.contains(ATTR_ADD_PLUS_SIGN)) { throw new RuntimeException("add/alter attribute format is wrong: " + key); } } else { key = kv; value = ""; if (!key.contains(ATTR_DELETE_MINUS_SIGN)) { throw new RuntimeException("delete attribute format is wrong: " + key); } } String old = attributes.put(key, value); if (old != null) { throw new RuntimeException("key duplication: " + key); } } return attributes; } public static String parseToString(Map attributes) { if (attributes == null || attributes.size() == 0) { return ""; } List kvs = new ArrayList<>(); for (Map.Entry entry : attributes.entrySet()) { String value = entry.getValue(); if (Strings.isNullOrEmpty(value)) { kvs.add(entry.getKey()); } else { kvs.add(entry.getKey() + ATTR_KEY_VALUE_EQUAL_SIGN + entry.getValue()); } } return String.join(ATTR_ARRAY_SEPARATOR_COMMA, kvs); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/AttributeUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class AttributeUtil { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static Map alterCurrentAttributes(boolean create, Map all, ImmutableMap currentAttributes, ImmutableMap newAttributes) { Map init = new HashMap<>(); Map add = new HashMap<>(); Map update = new HashMap<>(); Map delete = new HashMap<>(); Set keys = new HashSet<>(); for (Map.Entry attribute : newAttributes.entrySet()) { String key = attribute.getKey(); String realKey = realKey(key); String value = attribute.getValue(); validate(realKey); duplicationCheck(keys, realKey); if (create) { if (key.startsWith("+")) { init.put(realKey, value); } else { throw new RuntimeException("only add attribute is supported while creating topic. key: " + realKey); } } else { if (key.startsWith("+")) { if (!currentAttributes.containsKey(realKey)) { add.put(realKey, value); } else { update.put(realKey, value); } } else if (key.startsWith("-")) { if (!currentAttributes.containsKey(realKey)) { throw new RuntimeException("attempt to delete a nonexistent key: " + realKey); } delete.put(realKey, value); } else { throw new RuntimeException("wrong format key: " + realKey); } } } validateAlter(all, init, true, false); validateAlter(all, add, false, false); validateAlter(all, update, false, false); validateAlter(all, delete, false, true); log.info("add: {}, update: {}, delete: {}", add, update, delete); HashMap finalAttributes = new HashMap<>(currentAttributes); finalAttributes.putAll(init); finalAttributes.putAll(add); finalAttributes.putAll(update); for (String s : delete.keySet()) { finalAttributes.remove(s); } return finalAttributes; } private static void duplicationCheck(Set keys, String key) { boolean notExist = keys.add(key); if (!notExist) { throw new RuntimeException("alter duplication key. key: " + key); } } private static void validate(String kvAttribute) { if (Strings.isNullOrEmpty(kvAttribute)) { throw new RuntimeException("kv string format wrong."); } if (kvAttribute.contains("+")) { throw new RuntimeException("kv string format wrong."); } if (kvAttribute.contains("-")) { throw new RuntimeException("kv string format wrong."); } } private static void validateAlter(Map all, Map alter, boolean init, boolean delete) { for (Map.Entry entry : alter.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); Attribute attribute = all.get(key); if (attribute == null) { throw new RuntimeException("unsupported key: " + key); } if (!init && !attribute.isChangeable()) { throw new RuntimeException("attempt to update an unchangeable attribute. key: " + key); } if (!delete) { attribute.verify(value); } } } private static String realKey(String key) { return key.substring(1); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/BooleanAttribute.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import static com.google.common.base.Preconditions.checkNotNull; public class BooleanAttribute extends Attribute { private final boolean defaultValue; public BooleanAttribute(String name, boolean changeable, boolean defaultValue) { super(name, changeable); this.defaultValue = defaultValue; } @Override public void verify(String value) { checkNotNull(value); if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) { throw new RuntimeException("boolean attribute format is wrong."); } } public boolean getDefaultValue() { return defaultValue; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; public enum CQType { SimpleCQ, BatchCQ, RocksDBCQ } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/CleanupPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; public enum CleanupPolicy { DELETE, COMPACTION } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/EnumAttribute.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import java.util.Set; public class EnumAttribute extends Attribute { private final Set universe; private final String defaultValue; public EnumAttribute(String name, boolean changeable, Set universe, String defaultValue) { super(name, changeable); this.universe = universe; this.defaultValue = defaultValue; } @Override public void verify(String value) { if (!this.universe.contains(value)) { throw new RuntimeException("value is not in set: " + this.universe); } } public String getDefaultValue() { return defaultValue; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/LiteSubModel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; public enum LiteSubModel { Shared, Exclusive } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/LongRangeAttribute.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import static java.lang.String.format; public class LongRangeAttribute extends Attribute { private final long min; private final long max; private final long defaultValue; public LongRangeAttribute(String name, boolean changeable, long min, long max, long defaultValue) { super(name, changeable); this.min = min; this.max = max; this.defaultValue = defaultValue; } @Override public void verify(String value) { long l = Long.parseLong(value); if (l < min || l > max) { throw new RuntimeException(format("value is not in range(%d, %d)", min, max)); } } public long getDefaultValue() { return defaultValue; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/StringAttribute.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import static com.google.common.base.Preconditions.checkNotNull; public class StringAttribute extends Attribute { public StringAttribute(String name, boolean changeable) { super(name, changeable); } @Override public void verify(String value) { checkNotNull(value); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/attribute/TopicMessageType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import com.google.common.collect.Sets; import java.util.Map; import java.util.Set; import org.apache.rocketmq.common.message.MessageConst; public enum TopicMessageType { UNSPECIFIED("UNSPECIFIED"), NORMAL("NORMAL"), FIFO("FIFO"), DELAY("DELAY"), TRANSACTION("TRANSACTION"), PRIORITY("PRIORITY"), LITE("LITE"), MIXED("MIXED"); private final String value; TopicMessageType(String value) { this.value = value; } public static Set topicMessageTypeSet() { return Sets.newHashSet(UNSPECIFIED.value, NORMAL.value, FIFO.value, DELAY.value, TRANSACTION.value, PRIORITY.value, LITE.value, MIXED.value); } public String getValue() { return value; } public static TopicMessageType parseFromMessageProperty(Map messageProperty) { // the parse order keeps message types mutually exclusive if (Boolean.parseBoolean(messageProperty.get(MessageConst.PROPERTY_TRANSACTION_PREPARED))) { return TopicMessageType.TRANSACTION; } else if (messageProperty.get(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null || messageProperty.get(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null || messageProperty.get(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null || messageProperty.get(MessageConst.PROPERTY_TIMER_DELAY_MS) != null) { return TopicMessageType.DELAY; } else if (messageProperty.get(MessageConst.PROPERTY_SHARDING_KEY) != null) { return TopicMessageType.FIFO; } else if (messageProperty.get(MessageConst.PROPERTY_PRIORITY) != null) { return TopicMessageType.PRIORITY; } else if (messageProperty.get(MessageConst.PROPERTY_LITE_TOPIC) != null) { return TopicMessageType.LITE; } return TopicMessageType.NORMAL; } public String getMetricsValue() { return value.toLowerCase(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/chain/Handler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.chain; public interface Handler { R handle(T t, HandlerChain chain); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/chain/HandlerChain.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.chain; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class HandlerChain { private List> handlers; private Iterator> iterator; public static HandlerChain create() { return new HandlerChain<>(); } public HandlerChain addNext(Handler handler) { if (this.handlers == null) { this.handlers = new ArrayList<>(); } this.handlers.add(handler); return this; } public R handle(T t) { if (iterator == null) { iterator = handlers.iterator(); } if (iterator.hasNext()) { Handler handler = iterator.next(); return handler.handle(t, this); } return null; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/coldctr/AccAndTimeStamp.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.coldctr; import java.util.concurrent.atomic.AtomicLong; public class AccAndTimeStamp { public AtomicLong coldAcc = new AtomicLong(0L); public Long lastColdReadTimeMills = System.currentTimeMillis(); public Long createTimeMills = System.currentTimeMillis(); public AccAndTimeStamp(AtomicLong coldAcc) { this.coldAcc = coldAcc; } public AtomicLong getColdAcc() { return coldAcc; } public void setColdAcc(AtomicLong coldAcc) { this.coldAcc = coldAcc; } public Long getLastColdReadTimeMills() { return lastColdReadTimeMills; } public void setLastColdReadTimeMills(Long lastColdReadTimeMills) { this.lastColdReadTimeMills = lastColdReadTimeMills; } public Long getCreateTimeMills() { return createTimeMills; } public void setCreateTimeMills(Long createTimeMills) { this.createTimeMills = createTimeMills; } @Override public String toString() { return "AccAndTimeStamp{" + "coldAcc=" + coldAcc + ", lastColdReadTimeMills=" + lastColdReadTimeMills + ", createTimeMills=" + createTimeMills + '}'; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/compression/CompressionType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import org.apache.rocketmq.common.sysflag.MessageSysFlag; public enum CompressionType { /** * Compression types number can be extended to seven {@link MessageSysFlag} * * Benchmarks from https://github.com/facebook/zstd * * | Compressor | Ratio | Compression | Decompress | * |----------------|---------|-------------|------------| * | zstd 1.5.1 | 2.887 | 530 MB/s | 1700 MB/s | * | zlib 1.2.11 | 2.743 | 95 MB/s | 400 MB/s | * | lz4 1.9.3 | 2.101 | 740 MB/s | 4500 MB/s | * */ LZ4(1), ZSTD(2), ZLIB(3); private final int value; CompressionType(int value) { this.value = value; } public int getValue() { return value; } public static CompressionType of(String name) { switch (name.trim().toUpperCase()) { case "LZ4": return CompressionType.LZ4; case "ZSTD": return CompressionType.ZSTD; case "ZLIB": return CompressionType.ZLIB; default: throw new RuntimeException("Unsupported compress type name: " + name); } } public static CompressionType findByValue(int value) { switch (value) { case 1: return LZ4; case 2: return ZSTD; case 0: // To be compatible for older versions without compression type case 3: return ZLIB; default: throw new RuntimeException("Unknown compress type value: " + value); } } public int getCompressionFlag() { switch (value) { case 1: return MessageSysFlag.COMPRESSION_LZ4_TYPE; case 2: return MessageSysFlag.COMPRESSION_ZSTD_TYPE; case 3: return MessageSysFlag.COMPRESSION_ZLIB_TYPE; default: throw new RuntimeException("Unsupported compress type flag: " + value); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/compression/Compressor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import java.io.IOException; public interface Compressor { /** * Compress message by different compressor. * * @param src bytes ready to compress * @param level compression level used to balance compression rate and time consumption * @return compressed byte data * @throws IOException */ byte[] compress(byte[] src, int level) throws IOException; /** * Decompress message by different compressor. * * @param src bytes ready to decompress * @return decompressed byte data * @throws IOException */ byte[] decompress(byte[] src) throws IOException; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/compression/CompressorFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import java.util.EnumMap; public class CompressorFactory { private static final EnumMap COMPRESSORS; static { COMPRESSORS = new EnumMap<>(CompressionType.class); COMPRESSORS.put(CompressionType.LZ4, new Lz4Compressor()); COMPRESSORS.put(CompressionType.ZSTD, new ZstdCompressor()); COMPRESSORS.put(CompressionType.ZLIB, new ZlibCompressor()); } public static Compressor getCompressor(CompressionType type) { return COMPRESSORS.get(type); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/compression/Lz4Compressor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import net.jpountz.lz4.LZ4FrameInputStream; import net.jpountz.lz4.LZ4FrameOutputStream; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class Lz4Compressor implements Compressor { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); @Override public byte[] compress(byte[] src, int level) throws IOException { byte[] result = src; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); LZ4FrameOutputStream outputStream = new LZ4FrameOutputStream(byteArrayOutputStream); try { outputStream.write(src); outputStream.flush(); outputStream.close(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { log.error("Failed to compress data by lz4", e); throw e; } finally { try { byteArrayOutputStream.close(); } catch (IOException ignored) { } } return result; } @Override public byte[] decompress(byte[] src) throws IOException { byte[] result = src; byte[] uncompressData = new byte[src.length]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src); LZ4FrameInputStream lz4InputStream = new LZ4FrameInputStream(byteArrayInputStream); ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream(src.length); try { while (true) { int len = lz4InputStream.read(uncompressData, 0, uncompressData.length); if (len <= 0) { break; } resultOutputStream.write(uncompressData, 0, len); } resultOutputStream.flush(); resultOutputStream.close(); result = resultOutputStream.toByteArray(); } catch (IOException e) { throw e; } finally { try { lz4InputStream.close(); byteArrayInputStream.close(); } catch (IOException e) { log.warn("Failed to close the lz4 compress stream ", e); } } return result; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/compression/ZlibCompressor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ZlibCompressor implements Compressor { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); @Override public byte[] compress(byte[] src, int level) throws IOException { byte[] result = src; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); java.util.zip.Deflater defeater = new java.util.zip.Deflater(level); DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, defeater); try { deflaterOutputStream.write(src); deflaterOutputStream.finish(); deflaterOutputStream.close(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { throw e; } finally { try { byteArrayOutputStream.close(); } catch (IOException ignored) { } defeater.end(); } return result; } @Override public byte[] decompress(byte[] src) throws IOException { byte[] result = src; byte[] uncompressData = new byte[src.length]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src); InflaterInputStream inflaterInputStream = new InflaterInputStream(byteArrayInputStream); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); try { while (true) { int len = inflaterInputStream.read(uncompressData, 0, uncompressData.length); if (len <= 0) { break; } byteArrayOutputStream.write(uncompressData, 0, len); } byteArrayOutputStream.flush(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { throw e; } finally { try { byteArrayInputStream.close(); } catch (IOException e) { log.error("Failed to close the stream", e); } try { inflaterInputStream.close(); } catch (IOException e) { log.error("Failed to close the stream", e); } try { byteArrayOutputStream.close(); } catch (IOException e) { log.error("Failed to close the stream", e); } } return result; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/compression/ZstdCompressor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import com.github.luben.zstd.ZstdInputStream; import com.github.luben.zstd.ZstdOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ZstdCompressor implements Compressor { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); @Override public byte[] compress(byte[] src, int level) throws IOException { byte[] result = src; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); ZstdOutputStream outputStream = new ZstdOutputStream(byteArrayOutputStream, level); try { outputStream.write(src); outputStream.flush(); outputStream.close(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { log.error("Failed to compress data by zstd", e); throw e; } finally { try { byteArrayOutputStream.close(); } catch (IOException ignored) { } } return result; } @Override public byte[] decompress(byte[] src) throws IOException { byte[] result = src; byte[] uncompressData = new byte[src.length]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src); ZstdInputStream zstdInputStream = new ZstdInputStream(byteArrayInputStream); ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream(src.length); try { while (true) { int len = zstdInputStream.read(uncompressData, 0, uncompressData.length); if (len <= 0) { break; } resultOutputStream.write(uncompressData, 0, len); } resultOutputStream.flush(); resultOutputStream.close(); result = resultOutputStream.toByteArray(); } catch (IOException e) { throw e; } finally { try { zstdInputStream.close(); byteArrayInputStream.close(); } catch (IOException e) { log.warn("Failed to close the zstd compress stream", e); } } return result; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.config; import com.google.common.collect.Maps; import io.netty.buffer.PooledByteBufAllocator; import java.io.File; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import org.apache.commons.lang3.ArrayUtils; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.CompactRangeOptions; import org.rocksdb.CompactionOptions; import org.rocksdb.CompressionType; import org.rocksdb.DBOptions; import org.rocksdb.Env; import org.rocksdb.FlushOptions; import org.rocksdb.LiveFileMetaData; import org.rocksdb.Priority; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Slice; import org.rocksdb.Statistics; import org.rocksdb.Status; import org.rocksdb.WriteBatch; import org.rocksdb.WriteOptions; public abstract class AbstractRocksDBStorage { protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); /** * Direct Jemalloc allocator */ public static final PooledByteBufAllocator POOLED_ALLOCATOR = new PooledByteBufAllocator(true); public static final byte CTRL_0 = '\u0000'; public static final byte CTRL_1 = '\u0001'; public static final byte CTRL_2 = '\u0002'; private static final String SPACE = " | "; protected final String dbPath; protected boolean readOnly; protected RocksDB db; protected DBOptions options; protected WriteOptions writeOptions; protected WriteOptions ableWalWriteOptions; protected ReadOptions readOptions; protected ReadOptions totalOrderReadOptions; protected CompactionOptions compactionOptions; protected CompactRangeOptions compactRangeOptions; protected FlushOptions flushOptions; protected ColumnFamilyHandle defaultCFHandle; protected final List cfOptions = new ArrayList<>(); protected final List cfHandles = new ArrayList<>(); protected volatile boolean loaded; protected CompressionType compressionType = CompressionType.LZ4_COMPRESSION; private volatile boolean closed; private final Semaphore reloadPermit = new Semaphore(1); private final ScheduledExecutorService reloadScheduler = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("RocksDBStorageReloadService_")); private final ThreadPoolExecutor manualCompactionThread = (ThreadPoolExecutor) ThreadUtils.newThreadPoolExecutor( 1, 1, 1000 * 60, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadFactoryImpl("RocksDBManualCompactionService_"), new ThreadPoolExecutor.DiscardOldestPolicy()); static { RocksDB.loadLibrary(); } public AbstractRocksDBStorage(String dbPath) { this.dbPath = dbPath; } protected void initOptions() { initWriteOptions(); initAbleWalWriteOptions(); initReadOptions(); initTotalOrderReadOptions(); initCompactRangeOptions(); initCompactionOptions(); initFlushOptions(); } /** * Write options for Atomic Flush */ protected void initWriteOptions() { this.writeOptions = new WriteOptions(); this.writeOptions.setSync(false); this.writeOptions.setDisableWAL(true); // https://github.com/facebook/rocksdb/wiki/Write-Stalls this.writeOptions.setNoSlowdown(false); } protected void initAbleWalWriteOptions() { this.ableWalWriteOptions = new WriteOptions(); this.ableWalWriteOptions.setSync(false); this.ableWalWriteOptions.setDisableWAL(false); // https://github.com/facebook/rocksdb/wiki/Write-Stalls this.ableWalWriteOptions.setNoSlowdown(false); } protected void initReadOptions() { this.readOptions = new ReadOptions(); this.readOptions.setPrefixSameAsStart(true); this.readOptions.setTotalOrderSeek(false); this.readOptions.setTailing(false); } protected void initTotalOrderReadOptions() { this.totalOrderReadOptions = new ReadOptions(); this.totalOrderReadOptions.setPrefixSameAsStart(false); this.totalOrderReadOptions.setTotalOrderSeek(true); this.totalOrderReadOptions.setTailing(false); } protected void initCompactRangeOptions() { this.compactRangeOptions = new CompactRangeOptions(); this.compactRangeOptions.setBottommostLevelCompaction(CompactRangeOptions.BottommostLevelCompaction.kForce); this.compactRangeOptions.setAllowWriteStall(true); this.compactRangeOptions.setExclusiveManualCompaction(false); this.compactRangeOptions.setChangeLevel(true); this.compactRangeOptions.setTargetLevel(-1); this.compactRangeOptions.setMaxSubcompactions(4); } protected void initCompactionOptions() { this.compactionOptions = new CompactionOptions(); this.compactionOptions.setCompression(compressionType); this.compactionOptions.setMaxSubcompactions(4); this.compactionOptions.setOutputFileSizeLimit(4 * 1024 * 1024 * 1024L); } protected void initFlushOptions() { this.flushOptions = new FlushOptions(); } public boolean hold() { if (!this.loaded || this.db == null || this.closed) { LOGGER.error("hold rocksdb Failed. {}", this.dbPath); return false; } else { return true; } } public void release() { } protected void put(ColumnFamilyHandle cfHandle, WriteOptions writeOptions, final byte[] keyBytes, final int keyLen, final byte[] valueBytes, final int valueLen) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { this.db.put(cfHandle, writeOptions, keyBytes, 0, keyLen, valueBytes, 0, valueLen); } catch (RocksDBException e) { scheduleReloadRocksdb(e); LOGGER.error("put Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected void put(ColumnFamilyHandle cfHandle, WriteOptions writeOptions, final ByteBuffer keyBB, final ByteBuffer valueBB) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { this.db.put(cfHandle, writeOptions, keyBB, valueBB); } catch (RocksDBException e) { scheduleReloadRocksdb(e); LOGGER.error("put Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected void batchPut(WriteOptions writeOptions, final WriteBatch batch) throws RocksDBException { try { this.db.write(writeOptions, batch); } catch (RocksDBException e) { scheduleReloadRocksdb(e); LOGGER.error("batchPut Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { batch.clear(); } } protected byte[] get(ColumnFamilyHandle cfHandle, ReadOptions readOptions, byte[] keyBytes) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { return this.db.get(cfHandle, readOptions, keyBytes); } catch (RocksDBException e) { LOGGER.error("get Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected int get(ColumnFamilyHandle cfHandle, ReadOptions readOptions, final ByteBuffer keyBB, final ByteBuffer valueBB) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { return this.db.get(cfHandle, readOptions, keyBB, valueBB); } catch (RocksDBException e) { LOGGER.error("get Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected List multiGet(final ReadOptions readOptions, final List columnFamilyHandleList, final List keys) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { return this.db.multiGetAsList(readOptions, columnFamilyHandleList, keys); } catch (RocksDBException e) { LOGGER.error("multiGet Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected void delete(ColumnFamilyHandle cfHandle, WriteOptions writeOptions, byte[] keyBytes) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { this.db.delete(cfHandle, writeOptions, keyBytes); } catch (RocksDBException e) { LOGGER.error("delete Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected void delete(ColumnFamilyHandle cfHandle, WriteOptions writeOptions, ByteBuffer keyBB) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { this.db.delete(cfHandle, writeOptions, keyBB); } catch (RocksDBException e) { LOGGER.error("delete Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } protected void rangeDelete(ColumnFamilyHandle cfHandle, WriteOptions writeOptions, final byte[] startKey, final byte[] endKey) throws RocksDBException { if (!hold()) { throw new IllegalStateException("rocksDB:" + this + " is not ready"); } try { this.db.deleteRange(cfHandle, writeOptions, startKey, endKey); } catch (RocksDBException e) { scheduleReloadRocksdb(e); LOGGER.error("rangeDelete Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } finally { release(); } } public void iterate(ColumnFamilyHandle columnFamilyHandle, final byte[] prefix, BiConsumer callback) throws RocksDBException { if (ArrayUtils.isEmpty(prefix)) { throw new RocksDBException("Prefix is not allowed to be null"); } iterate(columnFamilyHandle, prefix, null, null, callback); } public void iterate(ColumnFamilyHandle columnFamilyHandle, byte[] prefix, final byte[] start, final byte[] end, BiConsumer callback) throws RocksDBException { if (ArrayUtils.isEmpty(prefix) && ArrayUtils.isEmpty(start)) { throw new RocksDBException( "To determine lower boundary, prefix and start may not be null at the same time."); } if (ArrayUtils.isEmpty(prefix) && ArrayUtils.isEmpty(end)) { throw new RocksDBException( "To determine upper boundary, prefix and end may not be null at the same time."); } if (columnFamilyHandle == null) { return; } ReadOptions readOptions = null; Slice startSlice = null; Slice endSlice = null; Slice prefixSlice = null; RocksIterator iterator = null; try { readOptions = new ReadOptions(); readOptions.setTotalOrderSeek(true); readOptions.setReadaheadSize(4L * 1024 * 1024); boolean hasStart = !ArrayUtils.isEmpty(start); boolean hasPrefix = !ArrayUtils.isEmpty(prefix); if (hasStart) { startSlice = new Slice(start); readOptions.setIterateLowerBound(startSlice); } if (!ArrayUtils.isEmpty(end)) { endSlice = new Slice(end); readOptions.setIterateUpperBound(endSlice); } if (!hasStart && hasPrefix) { prefixSlice = new Slice(prefix); readOptions.setIterateLowerBound(prefixSlice); } iterator = db.newIterator(columnFamilyHandle, readOptions); if (hasStart) { iterator.seek(start); } else if (hasPrefix) { iterator.seek(prefix); } while (iterator.isValid()) { byte[] key = iterator.key(); if (hasPrefix && !checkPrefix(key, prefix)) { break; } callback.accept(iterator.key(), iterator.value()); iterator.next(); } } finally { if (startSlice != null) { startSlice.close(); } if (endSlice != null) { endSlice.close(); } if (prefixSlice != null) { prefixSlice.close(); } if (readOptions != null) { readOptions.close(); } if (iterator != null) { iterator.close(); } } } private boolean checkPrefix(byte[] key, byte[] upperBound) { if (key.length < upperBound.length) { return false; } for (int i = 0; i < upperBound.length; i++) { if (key[i] > upperBound[i]) { return false; } } return true; } protected void manualCompactionDefaultCfRange(CompactRangeOptions compactRangeOptions) { if (!hold()) { return; } long s1 = System.currentTimeMillis(); boolean result = true; try { LOGGER.info("manualCompaction Start. {}", this.dbPath); this.db.compactRange(this.defaultCFHandle, null, null, compactRangeOptions); } catch (RocksDBException e) { result = false; scheduleReloadRocksdb(e); LOGGER.error("manualCompaction Failed. {}, {}", this.dbPath, getStatusError(e)); } finally { release(); LOGGER.info("manualCompaction End. {}, rt: {}(ms), result: {}", this.dbPath, System.currentTimeMillis() - s1, result); } } protected void manualCompaction(long minPhyOffset, final CompactRangeOptions compactRangeOptions) { this.manualCompactionThread.submit(new Runnable() { @Override public void run() { manualCompactionDefaultCfRange(compactRangeOptions); } }); } protected void open(final List cfDescriptors) throws RocksDBException { this.cfHandles.clear(); if (this.readOnly) { this.db = RocksDB.openReadOnly(this.options, this.dbPath, cfDescriptors, cfHandles); } else { this.db = RocksDB.open(this.options, this.dbPath, cfDescriptors, cfHandles); } assert cfDescriptors.size() == cfHandles.size(); if (this.db == null) { throw new RocksDBException("open rocksdb null"); } try (Env env = this.db.getEnv()) { env.setBackgroundThreads(8, Priority.LOW); } } protected abstract boolean postLoad(); public synchronized boolean start() { if (this.loaded) { return true; } if (postLoad()) { this.loaded = true; LOGGER.info("RocksDB [{}] starts OK", this.dbPath); this.closed = false; return true; } else { return false; } } /** * Close column family handles except the default column family */ protected abstract void preShutdown(); public boolean isLoaded() { return loaded; } public synchronized boolean shutdown() { try { if (!this.loaded) { LOGGER.info("RocksDBStorage is not loaded, shutdown OK. dbPath={}, readOnly={}", this.dbPath, this.readOnly); return true; } manualCompactionThread.shutdownNow(); manualCompactionThread.awaitTermination(30, TimeUnit.SECONDS); final FlushOptions flushOptions = new FlushOptions(); flushOptions.setWaitForFlush(true); try { flush(flushOptions); } catch (Throwable e) { LOGGER.error("flush rocksdb wal failed when shutdown", e); } finally { flushOptions.close(); } this.db.cancelAllBackgroundWork(true); this.db.pauseBackgroundWork(); //The close order matters. //1. close column family handles preShutdown(); if (this.defaultCFHandle.isOwningHandle()) { this.defaultCFHandle.close(); } //2. close column family options. for (final ColumnFamilyOptions opt : this.cfOptions) { opt.close(); } //3. close options if (this.writeOptions != null) { this.writeOptions.close(); } if (this.ableWalWriteOptions != null) { this.ableWalWriteOptions.close(); } if (this.readOptions != null) { this.readOptions.close(); } if (this.totalOrderReadOptions != null) { this.totalOrderReadOptions.close(); } if (this.flushOptions != null) { this.flushOptions.close(); } //4. close db. if (db != null && !this.readOnly) { try { this.db.syncWal(); } catch (Throwable e) { LOGGER.error("rocksdb sync wal failed when shutdown", e); } finally { flushOptions.close(); } } if (db != null) { try { this.db.closeE(); } catch (Throwable e) { LOGGER.error("rocksdb db closeE failed when shutdown", e); } } // Close DBOptions after RocksDB instance is closed. if (this.options != null) { this.options.close(); } //5. help gc. this.cfOptions.clear(); this.db = null; this.readOptions = null; this.totalOrderReadOptions = null; this.flushOptions = null; this.writeOptions = null; this.ableWalWriteOptions = null; this.options = null; this.loaded = false; LOGGER.info("RocksDB shutdown OK. {}", this.dbPath); } catch (Exception e) { LOGGER.error("RocksDB shutdown failed. {}", this.dbPath, e); return false; } return true; } public void flush(final FlushOptions flushOptions) throws RocksDBException { flush(flushOptions, this.cfHandles); } public void flush(final FlushOptions flushOptions, List columnFamilyHandles) throws RocksDBException { if (!this.loaded || this.readOnly || closed) { return; } try { if (db != null) { // For atomic-flush, we have to explicitly specify column family handles // See https://github.com/rust-rocksdb/rust-rocksdb/pull/793 // and https://github.com/facebook/rocksdb/blob/8ad4c7efc48d301f5e85467105d7019a49984dc8/include/rocksdb/db.h#L1667 this.db.flush(flushOptions, columnFamilyHandles); } } catch (RocksDBException e) { scheduleReloadRocksdb(e); LOGGER.error("flush Failed. {}, {}", this.dbPath, getStatusError(e)); throw e; } } public void flushWAL() throws RocksDBException { this.db.flushWal(true); } public Statistics getStatistics() { return this.options.statistics(); } public ColumnFamilyHandle getDefaultCFHandle() { return defaultCFHandle; } public List getCompactionStatus() { if (!hold()) { return null; } try { return this.db.getLiveFilesMetaData(); } finally { release(); } } private void scheduleReloadRocksdb(RocksDBException rocksDBException) { if (rocksDBException == null || rocksDBException.getStatus() == null) { return; } Status status = rocksDBException.getStatus(); Status.Code code = status.getCode(); // Status.Code.Incomplete == code if (Status.Code.Aborted == code || Status.Code.Corruption == code || Status.Code.Undefined == code) { LOGGER.error("scheduleReloadRocksdb. {}, {}", this.dbPath, getStatusError(rocksDBException)); scheduleReloadRocksdb0(); } } private void scheduleReloadRocksdb0() { if (!this.reloadPermit.tryAcquire()) { return; } this.closed = true; this.reloadScheduler.schedule(new Runnable() { @Override public void run() { boolean result = true; try { reloadRocksdb(); } catch (Exception e) { result = false; } finally { reloadPermit.release(); } // try to reload rocksdb next time if (!result) { LOGGER.info("reload rocksdb Retry. {}", dbPath); scheduleReloadRocksdb0(); } } }, 10, TimeUnit.SECONDS); } private void reloadRocksdb() throws Exception { LOGGER.info("reload rocksdb Start. {}", this.dbPath); if (!shutdown() || !start()) { LOGGER.error("reload rocksdb Failed. {}", dbPath); throw new Exception("reload rocksdb Error"); } LOGGER.info("reload rocksdb OK. {}", this.dbPath); } private String getStatusError(RocksDBException e) { if (e == null || e.getStatus() == null) { return "null"; } Status status = e.getStatus(); StringBuilder sb = new StringBuilder(64); sb.append("code: "); if (status.getCode() != null) { sb.append(status.getCode().name()); } else { sb.append("null"); } sb.append(", ").append("subCode: "); if (status.getSubCode() != null) { sb.append(status.getSubCode().name()); } else { sb.append("null"); } sb.append(", ").append("state: ").append(status.getState()); return sb.toString(); } public void statRocksdb(Logger logger) { try { // Log Memory Usage String blockCacheMemUsage = this.db.getProperty("rocksdb.block-cache-usage"); String indexesAndFilterBlockMemUsage = this.db.getProperty("rocksdb.estimate-table-readers-mem"); String memTableMemUsage = this.db.getProperty("rocksdb.cur-size-all-mem-tables"); String blocksPinnedByIteratorMemUsage = this.db.getProperty("rocksdb.block-cache-pinned-usage"); logger.info("RocksDB Memory Usage: BlockCache: {}, IndexesAndFilterBlock: {}, MemTable: {}, BlocksPinnedByIterator: {}", blockCacheMemUsage, indexesAndFilterBlockMemUsage, memTableMemUsage, blocksPinnedByIteratorMemUsage); // Log file metadata by level List liveFileMetaDataList = this.getCompactionStatus(); if (liveFileMetaDataList == null || liveFileMetaDataList.isEmpty()) { return; } Map map = Maps.newHashMap(); for (LiveFileMetaData metaData : liveFileMetaDataList) { StringBuilder sb = map.computeIfAbsent(metaData.level(), k -> new StringBuilder(256)); sb.append(new String(metaData.columnFamilyName(), StandardCharsets.UTF_8)).append(SPACE). append(metaData.fileName()).append(SPACE). append("file-size: ").append(metaData.size()).append(SPACE). append("number-of-entries: ").append(metaData.numEntries()).append(SPACE). append("file-read-times: ").append(metaData.numReadsSampled()).append(SPACE). append("deletions: ").append(metaData.numDeletions()).append(SPACE). append("being-compacted: ").append(metaData.beingCompacted()).append("\n"); } map.forEach((key, value) -> logger.info("level: {}\n{}", key, value.toString())); } catch (Exception ignored) { } } public void destroy() { recursiveDelete(new File(dbPath)); } void recursiveDelete(File file) { if (file.isFile()) { if (file.delete()) { LOGGER.info("Delete rocksdb file={}", file.getAbsolutePath()); } } else { File[] files = file.listFiles(); for (File f : files) { recursiveDelete(f); } file.delete(); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/config/ConfigHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.config; import com.google.common.base.Strings; import java.io.File; import org.apache.rocketmq.common.UtilAll; import org.rocksdb.BlockBasedTableConfig; import org.rocksdb.BloomFilter; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.CompactionStyle; import org.rocksdb.CompressionType; import org.rocksdb.DBOptions; import org.rocksdb.DataBlockIndexType; import org.rocksdb.IndexType; import org.rocksdb.InfoLogLevel; import org.rocksdb.LRUCache; import org.rocksdb.RateLimiter; import org.rocksdb.SkipListMemTableConfig; import org.rocksdb.Statistics; import org.rocksdb.StatsLevel; import org.rocksdb.StringAppendOperator; import org.rocksdb.WALRecoveryMode; import org.rocksdb.util.SizeUnit; public class ConfigHelper { public static ColumnFamilyOptions createConfigColumnFamilyOptions() { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(). setFormatVersion(5). setIndexType(IndexType.kBinarySearch). setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch). setBlockSize(32 * SizeUnit.KB). setFilterPolicy(new BloomFilter(16, false)). // Indicating if we'd put index/filter blocks to the block cache. setCacheIndexAndFilterBlocks(true). setCacheIndexAndFilterBlocksWithHighPriority(true). setPinL0FilterAndIndexBlocksInCache(false). setPinTopLevelIndexAndFilter(true). setBlockCache(new LRUCache(4 * SizeUnit.MB, 8, false)). setWholeKeyFiltering(true); ColumnFamilyOptions options = new ColumnFamilyOptions(); return options.setMaxWriteBufferNumber(4). setWriteBufferSize(64 * SizeUnit.MB). setMinWriteBufferNumberToMerge(1). setTableFormatConfig(blockBasedTableConfig). setMemTableConfig(new SkipListMemTableConfig()). setCompressionType(CompressionType.NO_COMPRESSION). setNumLevels(7). setCompactionStyle(CompactionStyle.LEVEL). setLevel0FileNumCompactionTrigger(4). setLevel0SlowdownWritesTrigger(8). setLevel0StopWritesTrigger(12). // The target file size for compaction. setTargetFileSizeBase(64 * SizeUnit.MB). setTargetFileSizeMultiplier(2). // The upper-bound of the total size of L1 files in bytes setMaxBytesForLevelBase(256 * SizeUnit.MB). setMaxBytesForLevelMultiplier(2). setMergeOperator(new StringAppendOperator()). setInplaceUpdateSupport(true); } public static DBOptions createConfigDBOptions() { // Tune based on https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide // and http://gitlab.alibaba-inc.com/aloha/aloha/blob/branch_2_5_0/jstorm-core/src/main/java/com/alibaba/jstorm/cache/rocksdb/RocksDbOptionsFactory.java DBOptions options = new DBOptions(); Statistics statistics = new Statistics(); statistics.setStatsLevel(StatsLevel.EXCEPT_DETAILED_TIMERS); return options. setDbLogDir(getDBLogDir()). setInfoLogLevel(InfoLogLevel.INFO_LEVEL). setWalRecoveryMode(WALRecoveryMode.SkipAnyCorruptedRecords). /* * We use manual flush to achieve desired balance between reliability and performance: * for metadata that matters, including {topic, subscription}-config changes, each write incurs a * flush-and-sync to ensure reliability; for {commit, pull}-offset advancements, group-flush are offered for * every N(configurable, 1024 by default) writes or aging of writes, similar to OS page-cache flush * mechanism. */ setManualWalFlush(true). // This option takes effect only when we have multiple column families // https://github.com/facebook/rocksdb/issues/4180 // setMaxTotalWalSize(1024 * SizeUnit.MB). setDbWriteBufferSize(128 * SizeUnit.MB). setBytesPerSync(SizeUnit.MB). setWalBytesPerSync(SizeUnit.MB). setCreateIfMissing(true). setCreateMissingColumnFamilies(true). setMaxOpenFiles(-1). setMaxLogFileSize(SizeUnit.GB). setKeepLogFileNum(5). setMaxManifestFileSize(SizeUnit.GB). setAllowConcurrentMemtableWrite(false). setStatistics(statistics). setStatsDumpPeriodSec(600). setMaxBackgroundJobs(32). setMaxSubcompactions(4). setParanoidChecks(true). setDelayedWriteRate(16 * SizeUnit.MB). setRateLimiter(new RateLimiter(100 * SizeUnit.MB)). setUseDirectIoForFlushAndCompaction(true). setUseDirectReads(true); } public static String getDBLogDir() { String[] rootPaths = new String[] { System.getProperty("user.home"), System.getProperty("java.io.tmpdir"), File.separator + "data" }; for (String rootPath : rootPaths) { // Refer bazel test encyclopedia: https://bazel.build/reference/test-encyclopedia // Not all directories is available if (Strings.isNullOrEmpty(rootPath)) { continue; } File rootPathFile = new File(rootPath); if (!rootPathFile.exists() || !rootPathFile.canWrite()) { continue; } String logDirectory = rootPath + File.separator + "logs" + File.separator + "rocketmqlogs"; // Create directories recursively. UtilAll.ensureDirOK(logDirectory); return logDirectory; } throw new RuntimeException("Failed to get log directory"); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/config/ConfigManagerVersion.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.config; public enum ConfigManagerVersion { V1("v1"), V2("v2"), ; private final String version; ConfigManagerVersion(String version) { this.version = version; } public String getVersion() { return version; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.config; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.BiConsumer; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.CompressionType; import org.rocksdb.Options; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; public class ConfigRocksDBStorage extends AbstractRocksDBStorage { public static final Charset CHARSET = StandardCharsets.UTF_8; public static final ConcurrentMap STORE_MAP = new ConcurrentHashMap<>(); private final ConcurrentHashMap columnFamilyNameHandleMap; private ColumnFamilyOptions columnFamilyOptions; private ConfigRocksDBStorage(final String dbPath, boolean readOnly, CompressionType compressionType) { super(dbPath); this.readOnly = readOnly; if (compressionType != null) { this.compressionType = compressionType; } this.columnFamilyNameHandleMap = new ConcurrentHashMap<>(); } public ConfigRocksDBStorage(final String dbPath, boolean readOnly) { this(dbPath, readOnly, null); } protected void initOptions() { this.options = ConfigHelper.createConfigDBOptions(); this.columnFamilyOptions = ConfigHelper.createConfigColumnFamilyOptions(); this.cfOptions.add(columnFamilyOptions); super.initOptions(); } @Override protected boolean postLoad() { try { UtilAll.ensureDirOK(this.dbPath); initOptions(); List columnFamilyNames = new ArrayList<>(RocksDB.listColumnFamilies( new Options(options, columnFamilyOptions), dbPath)); addIfNotExists(columnFamilyNames, RocksDB.DEFAULT_COLUMN_FAMILY); List cfDescriptors = new ArrayList<>(); for (byte[] columnFamilyName : columnFamilyNames) { cfDescriptors.add(new ColumnFamilyDescriptor(columnFamilyName, columnFamilyOptions)); } this.open(cfDescriptors); for (int i = 0; i < columnFamilyNames.size(); i++) { columnFamilyNameHandleMap.put(new String(columnFamilyNames.get(i), CHARSET), cfHandles.get(i)); } this.defaultCFHandle = columnFamilyNameHandleMap.get(new String(RocksDB.DEFAULT_COLUMN_FAMILY, CHARSET)); } catch (final Exception e) { AbstractRocksDBStorage.LOGGER.error("postLoad Failed. {}", this.dbPath, e); return false; } return true; } @Override protected void preShutdown() { for (final ColumnFamilyHandle columnFamilyHandle : this.columnFamilyNameHandleMap.values()) { if (columnFamilyHandle.isOwningHandle()) { columnFamilyHandle.close(); } } } // batch operations public void writeBatchPutOperation(String cf, WriteBatch writeBatch, final byte[] key, final byte[] value) throws RocksDBException { writeBatch.put(getOrCreateColumnFamily(cf), key, value); } public void batchPut(final WriteBatch batch) throws RocksDBException { batchPut(this.writeOptions, batch); } public void batchPutWithWal(final WriteBatch batch) throws RocksDBException { batchPut(this.ableWalWriteOptions, batch); } // operations with the specified cf public void put(String cf, final byte[] keyBytes, final int keyLen, final byte[] valueBytes) throws Exception { put(getOrCreateColumnFamily(cf), this.ableWalWriteOptions, keyBytes, keyLen, valueBytes, valueBytes.length); } public void put(String cf, final ByteBuffer keyBB, final ByteBuffer valueBB) throws Exception { put(getOrCreateColumnFamily(cf), this.ableWalWriteOptions, keyBB, valueBB); } public byte[] get(String cf, final byte[] keyBytes) throws Exception { ColumnFamilyHandle columnFamilyHandle = columnFamilyNameHandleMap.get(cf); if (columnFamilyHandle == null) { return null; } return get(columnFamilyHandle, this.totalOrderReadOptions, keyBytes); } public void delete(String cf, final byte[] keyBytes) throws Exception { ColumnFamilyHandle columnFamilyHandle = columnFamilyNameHandleMap.get(cf); if (columnFamilyHandle == null) { return; } delete(columnFamilyHandle, this.ableWalWriteOptions, keyBytes); } public void iterate(final String cf, BiConsumer biConsumer) throws RocksDBException { if (!hold()) { LOGGER.warn("RocksDBKvStore[path={}] has been shut down", dbPath); return; } ColumnFamilyHandle columnFamilyHandle = columnFamilyNameHandleMap.get(cf); if (columnFamilyHandle == null) { return; } try (RocksIterator iterator = this.db.newIterator(columnFamilyHandle)) { for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { biConsumer.accept(iterator.key(), iterator.value()); } iterator.status(); } } public RocksIterator iterator() { return this.db.newIterator(this.defaultCFHandle, this.totalOrderReadOptions); } public ColumnFamilyHandle getOrCreateColumnFamily(String cf) throws RocksDBException { if (!columnFamilyNameHandleMap.containsKey(cf)) { if (readOnly) { String errInfo = String.format("RocksDBKvStore[path=%s] is open as read-only", dbPath); LOGGER.warn(errInfo); throw new RocksDBException(errInfo); } synchronized (this) { if (!columnFamilyNameHandleMap.containsKey(cf)) { ColumnFamilyDescriptor columnFamilyDescriptor = new ColumnFamilyDescriptor(cf.getBytes(CHARSET), columnFamilyOptions); ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily(columnFamilyDescriptor); columnFamilyNameHandleMap.putIfAbsent(cf, columnFamilyHandle); cfHandles.add(columnFamilyHandle); } } } return columnFamilyNameHandleMap.get(cf); } public void addIfNotExists(List columnFamilyNames, byte[] byteArray) { if (columnFamilyNames.stream().noneMatch(array -> Arrays.equals(array, byteArray))) { columnFamilyNames.add(byteArray); } } public static ConfigRocksDBStorage getStore(String path, boolean readOnly, CompressionType compressionType) { return ConcurrentHashMapUtils.computeIfAbsent(STORE_MAP, path, k -> new ConfigRocksDBStorage(path, readOnly, compressionType)); } public static ConfigRocksDBStorage getStore(String path, boolean readOnly) { return getStore(path, readOnly, null); } public static void shutdown(String path) { ConfigRocksDBStorage kvStore = STORE_MAP.remove(path); if (kvStore != null) { kvStore.shutdown(); } } public static void destroy(String path) { ConfigRocksDBStorage kvStore = STORE_MAP.remove(path); if (kvStore != null) { kvStore.shutdown(); kvStore.destroy(); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/consistenthash/ConsistentHashRouter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consistenthash; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.Iterator; import java.util.SortedMap; import java.util.TreeMap; /** * To hash Node objects to a hash ring with a certain amount of virtual node. * Method routeNode will return a Node instance which the object key should be allocated to according to consistent hash * algorithm */ public class ConsistentHashRouter { private final SortedMap> ring = new TreeMap<>(); private final HashFunction hashFunction; public ConsistentHashRouter(Collection pNodes, int vNodeCount) { this(pNodes, vNodeCount, new MD5Hash()); } /** * @param pNodes collections of physical nodes * @param vNodeCount amounts of virtual nodes * @param hashFunction hash Function to hash Node instances */ public ConsistentHashRouter(Collection pNodes, int vNodeCount, HashFunction hashFunction) { if (hashFunction == null) { throw new NullPointerException("Hash Function is null"); } this.hashFunction = hashFunction; if (pNodes != null) { for (T pNode : pNodes) { addNode(pNode, vNodeCount); } } } /** * add physic node to the hash ring with some virtual nodes * * @param pNode physical node needs added to hash ring * @param vNodeCount the number of virtual node of the physical node. Value should be greater than or equals to 0 */ public void addNode(T pNode, int vNodeCount) { if (vNodeCount < 0) throw new IllegalArgumentException("illegal virtual node counts :" + vNodeCount); int existingReplicas = getExistingReplicas(pNode); for (int i = 0; i < vNodeCount; i++) { VirtualNode vNode = new VirtualNode<>(pNode, i + existingReplicas); ring.put(hashFunction.hash(vNode.getKey()), vNode); } } /** * remove the physical node from the hash ring */ public void removeNode(T pNode) { Iterator it = ring.keySet().iterator(); while (it.hasNext()) { Long key = it.next(); VirtualNode virtualNode = ring.get(key); if (virtualNode.isVirtualNodeOf(pNode)) { it.remove(); } } } /** * with a specified key, route the nearest Node instance in the current hash ring * * @param objectKey the object key to find a nearest Node */ public T routeNode(String objectKey) { if (ring.isEmpty()) { return null; } Long hashVal = hashFunction.hash(objectKey); SortedMap> tailMap = ring.tailMap(hashVal); Long nodeHashVal = !tailMap.isEmpty() ? tailMap.firstKey() : ring.firstKey(); return ring.get(nodeHashVal).getPhysicalNode(); } public int getExistingReplicas(T pNode) { int replicas = 0; for (VirtualNode vNode : ring.values()) { if (vNode.isVirtualNodeOf(pNode)) { replicas++; } } return replicas; } //default hash function private static class MD5Hash implements HashFunction { MessageDigest instance; public MD5Hash() { try { instance = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { } } @Override public long hash(String key) { instance.reset(); instance.update(key.getBytes(StandardCharsets.UTF_8)); byte[] digest = instance.digest(); long h = 0; for (int i = 0; i < 4; i++) { h <<= 8; h |= ((int) digest[i]) & 0xFF; } return h; } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/consistenthash/HashFunction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consistenthash; /** * Hash String to long value */ public interface HashFunction { long hash(String key); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/consistenthash/Node.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consistenthash; /** * Represent a node which should be mapped to a hash ring */ public interface Node { /** * @return the key which will be used for hash mapping */ String getKey(); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/consistenthash/VirtualNode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consistenthash; public class VirtualNode implements Node { final T physicalNode; final int replicaIndex; public VirtualNode(T physicalNode, int replicaIndex) { this.replicaIndex = replicaIndex; this.physicalNode = physicalNode; } @Override public String getKey() { return physicalNode.getKey() + "-" + replicaIndex; } public boolean isVirtualNodeOf(T pNode) { return physicalNode.getKey().equals(pNode.getKey()); } public T getPhysicalNode() { return physicalNode; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/CommonConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class CommonConstants { public static final String COLON = ":"; public static final String ASTERISK = "*"; public static final String COMMA = ","; public static final String EQUAL = "="; public static final String SLASH = "/"; public static final String SPACE = " "; public static final String HYPHEN = "-"; public static final String POUND = "#"; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class ConsumeInitMode { public static final int MIN = 0; public static final int MAX = 1; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/DBMsgConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class DBMsgConstants { public static final int MAX_BODY_SIZE = 64 * 1024 * 1024; //64KB } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/FIleReadaheadMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class FIleReadaheadMode { public static final String READ_AHEAD_MODE = "READ_AHEAD_MODE"; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/GrpcConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; import io.grpc.Context; import io.grpc.Metadata; public class GrpcConstants { public static final Context.Key METADATA = Context.key("rpc-metadata"); /** * Remote address key in attributes of call */ public static final Metadata.Key REMOTE_ADDRESS = Metadata.Key.of("rpc-remote-address", Metadata.ASCII_STRING_MARSHALLER); /** * Local address key in attributes of call */ public static final Metadata.Key LOCAL_ADDRESS = Metadata.Key.of("rpc-local-address", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key AUTHORIZATION = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key NAMESPACE_ID = Metadata.Key.of("x-mq-namespace", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key DATE_TIME = Metadata.Key.of("x-mq-date-time", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key REQUEST_ID = Metadata.Key.of("x-mq-request-id", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key LANGUAGE = Metadata.Key.of("x-mq-language", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key CLIENT_VERSION = Metadata.Key.of("x-mq-client-version", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key PROTOCOL_VERSION = Metadata.Key.of("x-mq-protocol", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key RPC_NAME = Metadata.Key.of("x-mq-rpc-name", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key SIMPLE_RPC_NAME = Metadata.Key.of("x-mq-simple-rpc-name", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key SESSION_TOKEN = Metadata.Key.of("x-mq-session-token", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key CLIENT_ID = Metadata.Key.of("x-mq-client-id", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key AUTHORIZATION_AK = Metadata.Key.of("x-mq-authorization-ak", Metadata.ASCII_STRING_MARSHALLER); public static final Metadata.Key CHANNEL_ID = Metadata.Key.of("x-mq-channel-id", Metadata.ASCII_STRING_MARSHALLER); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/HAProxyConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class HAProxyConstants { public static final String CHANNEL_ID = "channel_id"; public static final String PROXY_PROTOCOL_PREFIX = "proxy_protocol_"; public static final String PROXY_PROTOCOL_ADDR = PROXY_PROTOCOL_PREFIX + "addr"; public static final String PROXY_PROTOCOL_PORT = PROXY_PROTOCOL_PREFIX + "port"; public static final String PROXY_PROTOCOL_SERVER_ADDR = PROXY_PROTOCOL_PREFIX + "server_addr"; public static final String PROXY_PROTOCOL_SERVER_PORT = PROXY_PROTOCOL_PREFIX + "server_port"; public static final String PROXY_PROTOCOL_TLV_PREFIX = PROXY_PROTOCOL_PREFIX + "tlv_0x"; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class LoggerName { public static final String FILTERSRV_LOGGER_NAME = "RocketmqFiltersrv"; public static final String NAMESRV_LOGGER_NAME = "RocketmqNamesrv"; public static final String NAMESRV_CONSOLE_LOGGER_NAME = "RocketmqNamesrvConsole"; public static final String CONTROLLER_LOGGER_NAME = "RocketmqController"; public static final String CONTROLLER_CONSOLE_NAME = "RocketmqControllerConsole"; public static final String NAMESRV_WATER_MARK_LOGGER_NAME = "RocketmqNamesrvWaterMark"; public static final String BROKER_LOGGER_NAME = "RocketmqBroker"; public static final String BROKER_CONSOLE_NAME = "RocketmqConsole"; public static final String CLIENT_LOGGER_NAME = "RocketmqClient"; public static final String ROCKETMQ_TRAFFIC_NAME = "RocketmqTraffic"; public static final String ROCKETMQ_REMOTING_NAME = "RocketmqRemoting"; public static final String TOOLS_LOGGER_NAME = "RocketmqTools"; public static final String COMMON_LOGGER_NAME = "RocketmqCommon"; public static final String STORE_LOGGER_NAME = "RocketmqStore"; public static final String STORE_ERROR_LOGGER_NAME = "RocketmqStoreError"; public static final String TRANSACTION_LOGGER_NAME = "RocketmqTransaction"; public static final String REBALANCE_LOCK_LOGGER_NAME = "RocketmqRebalanceLock"; public static final String ROCKETMQ_STATS_LOGGER_NAME = "RocketmqStats"; public static final String DLQ_STATS_LOGGER_NAME = "RocketmqDLQStats"; public static final String DLQ_LOGGER_NAME = "RocketmqDLQ"; public static final String CONSUMER_STATS_LOGGER_NAME = "RocketmqConsumerStats"; public static final String COMMERCIAL_LOGGER_NAME = "RocketmqCommercial"; public static final String ACCOUNT_LOGGER_NAME = "RocketmqAccount"; public static final String FLOW_CONTROL_LOGGER_NAME = "RocketmqFlowControl"; public static final String ROCKETMQ_AUTHORIZE_LOGGER_NAME = "RocketmqAuthorize"; public static final String DUPLICATION_LOGGER_NAME = "RocketmqDuplication"; public static final String PROTECTION_LOGGER_NAME = "RocketmqProtection"; public static final String WATER_MARK_LOGGER_NAME = "RocketmqWaterMark"; public static final String FILTER_LOGGER_NAME = "RocketmqFilter"; public static final String ROCKETMQ_POP_LOGGER_NAME = "RocketmqPop"; public static final String ROCKETMQ_POP_LITE_LOGGER_NAME = "RocketmqPopLite"; public static final String FAILOVER_LOGGER_NAME = "RocketmqFailover"; public static final String STDOUT_LOGGER_NAME = "STDOUT"; public static final String PROXY_LOGGER_NAME = "RocketmqProxy"; public static final String PROXY_WATER_MARK_LOGGER_NAME = "RocketmqProxyWatermark"; public static final String ROCKETMQ_COLDCTR_LOGGER_NAME = "RocketmqColdCtr"; public static final String ROCKSDB_LOGGER_NAME = "RocketmqRocksDB"; public static final String ROCKETMQ_AUTH_AUDIT_LOGGER_NAME = "RocketmqAuthAudit"; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/constant/PermName.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.constant; public class PermName { public static final int INDEX_PERM_PRIORITY = 3; public static final int INDEX_PERM_READ = 2; public static final int INDEX_PERM_WRITE = 1; public static final int INDEX_PERM_INHERIT = 0; public static final int PERM_PRIORITY = 0x1 << INDEX_PERM_PRIORITY; public static final int PERM_READ = 0x1 << INDEX_PERM_READ; public static final int PERM_WRITE = 0x1 << INDEX_PERM_WRITE; public static final int PERM_INHERIT = 0x1 << INDEX_PERM_INHERIT; public static String perm2String(final int perm) { final StringBuilder sb = new StringBuilder("---"); if (isReadable(perm)) { sb.replace(0, 1, "R"); } if (isWriteable(perm)) { sb.replace(1, 2, "W"); } if (isInherited(perm)) { sb.replace(2, 3, "X"); } return sb.toString(); } public static boolean isReadable(final int perm) { return (perm & PERM_READ) == PERM_READ; } public static boolean isWriteable(final int perm) { return (perm & PERM_WRITE) == PERM_WRITE; } public static boolean isInherited(final int perm) { return (perm & PERM_INHERIT) == PERM_INHERIT; } public static boolean isValid(final String perm) { return isValid(Integer.parseInt(perm)); } public static boolean isValid(final int perm) { return perm >= 0 && perm < PERM_PRIORITY; } public static boolean isPriority(final int perm) { return (perm & PERM_PRIORITY) == PERM_PRIORITY; } public static boolean isAccessible(final int perm) { return isReadable(perm) || isWriteable(perm); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/consumer/ConsumeFromWhere.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consumer; public enum ConsumeFromWhere { CONSUME_FROM_LAST_OFFSET, @Deprecated CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST, @Deprecated CONSUME_FROM_MIN_OFFSET, @Deprecated CONSUME_FROM_MAX_OFFSET, CONSUME_FROM_FIRST_OFFSET, CONSUME_FROM_TIMESTAMP, } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consumer; import java.util.Arrays; import java.util.List; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.message.MessageConst; public class ReceiptHandle { private static final String SEPARATOR = MessageConst.KEY_SEPARATOR; public static final String NORMAL_TOPIC = "0"; public static final String RETRY_TOPIC = "1"; public static final String RETRY_TOPIC_V2 = "2"; private final long startOffset; private final long retrieveTime; private final long invisibleTime; private final long nextVisibleTime; private final int reviveQueueId; private final String topicType; private final String brokerName; private final int queueId; private final long offset; private final long commitLogOffset; private final String receiptHandle; public String encode() { return startOffset + SEPARATOR + retrieveTime + SEPARATOR + invisibleTime + SEPARATOR + reviveQueueId + SEPARATOR + topicType + SEPARATOR + brokerName + SEPARATOR + queueId + SEPARATOR + offset + SEPARATOR + commitLogOffset; } public boolean isExpired() { return nextVisibleTime <= System.currentTimeMillis(); } public static ReceiptHandle decode(String receiptHandle) { List dataList = Arrays.asList(receiptHandle.split(SEPARATOR)); if (dataList.size() < 8) { throw new IllegalArgumentException("Parse failed, dataList size " + dataList.size()); } long startOffset = Long.parseLong(dataList.get(0)); long retrieveTime = Long.parseLong(dataList.get(1)); long invisibleTime = Long.parseLong(dataList.get(2)); int reviveQueueId = Integer.parseInt(dataList.get(3)); String topicType = dataList.get(4); String brokerName = dataList.get(5); int queueId = Integer.parseInt(dataList.get(6)); long offset = Long.parseLong(dataList.get(7)); long commitLogOffset = -1L; if (dataList.size() >= 9) { commitLogOffset = Long.parseLong(dataList.get(8)); } return new ReceiptHandleBuilder() .startOffset(startOffset) .retrieveTime(retrieveTime) .invisibleTime(invisibleTime) .reviveQueueId(reviveQueueId) .topicType(topicType) .brokerName(brokerName) .queueId(queueId) .offset(offset) .commitLogOffset(commitLogOffset) .receiptHandle(receiptHandle).build(); } ReceiptHandle(final long startOffset, final long retrieveTime, final long invisibleTime, final long nextVisibleTime, final int reviveQueueId, final String topicType, final String brokerName, final int queueId, final long offset, final long commitLogOffset, final String receiptHandle) { this.startOffset = startOffset; this.retrieveTime = retrieveTime; this.invisibleTime = invisibleTime; this.nextVisibleTime = nextVisibleTime; this.reviveQueueId = reviveQueueId; this.topicType = topicType; this.brokerName = brokerName; this.queueId = queueId; this.offset = offset; this.commitLogOffset = commitLogOffset; this.receiptHandle = receiptHandle; } public static class ReceiptHandleBuilder { private long startOffset; private long retrieveTime; private long invisibleTime; private int reviveQueueId; private String topicType; private String brokerName; private int queueId; private long offset; private long commitLogOffset; private String receiptHandle; ReceiptHandleBuilder() { } public ReceiptHandle.ReceiptHandleBuilder startOffset(final long startOffset) { this.startOffset = startOffset; return this; } public ReceiptHandle.ReceiptHandleBuilder retrieveTime(final long retrieveTime) { this.retrieveTime = retrieveTime; return this; } public ReceiptHandle.ReceiptHandleBuilder invisibleTime(final long invisibleTime) { this.invisibleTime = invisibleTime; return this; } public ReceiptHandle.ReceiptHandleBuilder reviveQueueId(final int reviveQueueId) { this.reviveQueueId = reviveQueueId; return this; } public ReceiptHandle.ReceiptHandleBuilder topicType(final String topicType) { this.topicType = topicType; return this; } public ReceiptHandle.ReceiptHandleBuilder brokerName(final String brokerName) { this.brokerName = brokerName; return this; } public ReceiptHandle.ReceiptHandleBuilder queueId(final int queueId) { this.queueId = queueId; return this; } public ReceiptHandle.ReceiptHandleBuilder offset(final long offset) { this.offset = offset; return this; } public ReceiptHandle.ReceiptHandleBuilder commitLogOffset(final long commitLogOffset) { this.commitLogOffset = commitLogOffset; return this; } public ReceiptHandle.ReceiptHandleBuilder receiptHandle(final String receiptHandle) { this.receiptHandle = receiptHandle; return this; } public ReceiptHandle build() { return new ReceiptHandle(this.startOffset, this.retrieveTime, this.invisibleTime, this.retrieveTime + this.invisibleTime, this.reviveQueueId, this.topicType, this.brokerName, this.queueId, this.offset, this.commitLogOffset, this.receiptHandle); } @Override public String toString() { return "ReceiptHandle.ReceiptHandleBuilder(startOffset=" + this.startOffset + ", retrieveTime=" + this.retrieveTime + ", invisibleTime=" + this.invisibleTime + ", reviveQueueId=" + this.reviveQueueId + ", topic=" + this.topicType + ", brokerName=" + this.brokerName + ", queueId=" + this.queueId + ", offset=" + this.offset + ", commitLogOffset=" + this.commitLogOffset + ", receiptHandle=" + this.receiptHandle + ")"; } } public static ReceiptHandle.ReceiptHandleBuilder builder() { return new ReceiptHandle.ReceiptHandleBuilder(); } public long getStartOffset() { return this.startOffset; } public long getRetrieveTime() { return this.retrieveTime; } public long getInvisibleTime() { return this.invisibleTime; } public long getNextVisibleTime() { return this.nextVisibleTime; } public int getReviveQueueId() { return this.reviveQueueId; } public String getTopicType() { return this.topicType; } public String getBrokerName() { return this.brokerName; } public int getQueueId() { return this.queueId; } public long getOffset() { return this.offset; } public long getCommitLogOffset() { return commitLogOffset; } public String getReceiptHandle() { return this.receiptHandle; } public boolean isRetryTopic() { return RETRY_TOPIC.equals(topicType) || RETRY_TOPIC_V2.equals(topicType); } public String getRealTopic(String topic, String groupName) { if (RETRY_TOPIC.equals(topicType)) { return KeyBuilder.buildPopRetryTopicV1(topic, groupName); } if (RETRY_TOPIC_V2.equals(topicType)) { return KeyBuilder.buildPopRetryTopicV2(topic, groupName); } return topic; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/entity/ClientGroup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.entity; import java.util.Objects; public class ClientGroup { public final String clientId; public final String group; /** * Cache the hash code for the object */ private int hash; // Default to 0 public ClientGroup(String clientId, String group) { this.clientId = clientId; this.group = group; } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } ClientGroup that = (ClientGroup) o; return Objects.equals(clientId, that.clientId) && Objects.equals(group, that.group); } @Override public int hashCode() { if (hash == 0) { hash = Objects.hash(clientId, group); } return hash; } @Override public String toString() { return "ClientGroup{" + "clientId='" + clientId + '\'' + ", group='" + group + '\'' + '}'; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/entity/TopicGroup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.entity; import java.util.Objects; public class TopicGroup { public final String topic; public final String group; /** * Cache the hash code for the object */ private int hash; // Default to 0 public TopicGroup(String topic, String group) { this.topic = topic; this.group = group; } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } TopicGroup that = (TopicGroup) o; return Objects.equals(topic, that.topic) && Objects.equals(group, that.group); } @Override public int hashCode() { if (hash == 0) { hash = Objects.hash(topic, group); } return hash; } @Override public String toString() { return "TopicGroup{" + "topic='" + topic + '\'' + ", group='" + group + '\'' + '}'; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/fastjson/GenericMapSuperclassDeserializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.fastjson; import com.alibaba.fastjson2.JSONException; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.reader.ObjectReader; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; /** * workaround https://github.com/alibaba/fastjson/issues/3730 */ public class GenericMapSuperclassDeserializer implements ObjectReader { public static final GenericMapSuperclassDeserializer INSTANCE = new GenericMapSuperclassDeserializer(); @SuppressWarnings({"unchecked", "rawtypes"}) @Override public Object readObject(JSONReader reader, Type type, Object fieldName, long features) { Class clz = (Class) type; Type genericSuperclass = clz.getGenericSuperclass(); Map map; try { map = (Map) clz.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new JSONException("unsupport type " + type, e); } ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; Type keyType = parameterizedType.getActualTypeArguments()[0]; Type valueType = parameterizedType.getActualTypeArguments()[1]; if (!reader.nextIfObjectStart()) { throw new JSONException(reader.info("expect '{', but " + reader.current())); } while (!reader.nextIfObjectEnd()) { Object key; if (keyType == String.class) { key = reader.readFieldName(); } else { key = reader.getContext().getProvider().getObjectReader(keyType).readObject(reader, keyType, fieldName, features); reader.nextIfMatch(':'); } Object value = reader.getContext().getProvider().getObjectReader(valueType).readObject(reader, valueType, fieldName, features); map.put(key, value); reader.nextIfComma(); } return map; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/ExpressionType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter; public class ExpressionType { /** *
      * Keywords: *
    • {@code AND, OR, NOT, BETWEEN, IN, TRUE, FALSE, IS, NULL}
    • *
    *

    *

      * Data type: *
    • Boolean, like: TRUE, FALSE
    • *
    • String, like: 'abc'
    • *
    • Decimal, like: 123
    • *
    • Float number, like: 3.1415
    • *
    *

    *

      * Grammar: *
    • {@code AND, OR}
    • *
    • {@code >, >=, <, <=, =}
    • *
    • {@code BETWEEN A AND B}, equals to {@code >=A AND <=B}
    • *
    • {@code NOT BETWEEN A AND B}, equals to {@code >B OR *
    • {@code IN ('a', 'b')}, equals to {@code ='a' OR ='b'}, this operation only support String type.
    • *
    • {@code IS NULL}, {@code IS NOT NULL}, check parameter whether is null, or not.
    • *
    • {@code =TRUE}, {@code =FALSE}, check parameter whether is true, or false.
    • *
    *

    *

    * Example: * (a > 10 AND a < 100) OR (b IS NOT NULL AND b=TRUE) *

    */ public static final String SQL92 = "SQL92"; /** * Only support or operation such as * "tag1 || tag2 || tag3",
    * If null or * expression,meaning subscribe all. */ public static final String TAG = "TAG"; public static boolean isTagType(String type) { if (type == null || "".equals(type) || TAG.equals(type)) { return true; } return false; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/FilterContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter; public class FilterContext { private String consumerGroup; public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/MessageFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter; import org.apache.rocketmq.common.message.MessageExt; public interface MessageFilter { boolean match(final MessageExt msg, final FilterContext context); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/impl/Op.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter.impl; public abstract class Op { private String symbol; protected Op(String symbol) { this.symbol = symbol; } public String getSymbol() { return symbol; } public String toString() { return symbol; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/impl/Operand.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter.impl; public class Operand extends Op { public Operand(String symbol) { super(symbol); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/impl/Operator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter.impl; public class Operator extends Op { public static final Operator LEFTPARENTHESIS = new Operator("(", 30, false); public static final Operator RIGHTPARENTHESIS = new Operator(")", 30, false); public static final Operator AND = new Operator("&&", 20, true); public static final Operator OR = new Operator("||", 15, true); private int priority; private boolean compareable; private Operator(String symbol, int priority, boolean compareable) { super(symbol); this.priority = priority; this.compareable = compareable; } public static Operator createOperator(String operator) { if (LEFTPARENTHESIS.getSymbol().equals(operator)) return LEFTPARENTHESIS; else if (RIGHTPARENTHESIS.getSymbol().equals(operator)) return RIGHTPARENTHESIS; else if (AND.getSymbol().equals(operator)) return AND; else if (OR.getSymbol().equals(operator)) return OR; else throw new IllegalArgumentException("unsupport operator " + operator); } public int getPriority() { return priority; } public boolean isCompareable() { return compareable; } public int compare(Operator operator) { if (this.priority > operator.priority) return 1; else if (this.priority == operator.priority) return 0; else return -1; } public boolean isSpecifiedOp(String operator) { return this.getSymbol().equals(operator); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/impl/PolishExpr.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter.impl; import java.util.ArrayList; import java.util.List; import java.util.Stack; import static org.apache.rocketmq.common.filter.impl.Operator.LEFTPARENTHESIS; import static org.apache.rocketmq.common.filter.impl.Operator.RIGHTPARENTHESIS; import static org.apache.rocketmq.common.filter.impl.Operator.createOperator; public class PolishExpr { public static List reversePolish(String expression) { return reversePolish(participle(expression)); } /** * Shunting-yard algorithm
    * http://en.wikipedia.org/wiki/Shunting_yard_algorithm * * @return the compute result of Shunting-yard algorithm */ public static List reversePolish(List tokens) { List segments = new ArrayList<>(); Stack operatorStack = new Stack<>(); for (int i = 0; i < tokens.size(); i++) { Op token = tokens.get(i); if (isOperand(token)) { segments.add(token); } else if (isLeftParenthesis(token)) { operatorStack.push((Operator) token); } else if (isRightParenthesis(token)) { Operator opNew = null; while (!operatorStack.empty() && LEFTPARENTHESIS != (opNew = operatorStack.pop())) { segments.add(opNew); } if (null == opNew || LEFTPARENTHESIS != opNew) throw new IllegalArgumentException("mismatched parentheses"); } else if (isOperator(token)) { Operator opNew = (Operator) token; if (!operatorStack.empty()) { Operator opOld = operatorStack.peek(); if (opOld.isCompareable() && opNew.compare(opOld) != 1) { segments.add(operatorStack.pop()); } } operatorStack.push(opNew); } else throw new IllegalArgumentException("illegal token " + token); } while (!operatorStack.empty()) { Operator operator = operatorStack.pop(); if (LEFTPARENTHESIS == operator || RIGHTPARENTHESIS == operator) throw new IllegalArgumentException("mismatched parentheses " + operator); segments.add(operator); } return segments; } /** * @param expression * @return * @throws Exception */ private static List participle(String expression) { List segments = new ArrayList<>(); int size = expression.length(); int wordStartIndex = -1; int wordLen = 0; Type preType = Type.NULL; for (int i = 0; i < size; i++) { int chValue = (int) expression.charAt(i); if (97 <= chValue && chValue <= 122 || 65 <= chValue && chValue <= 90 || 49 <= chValue && chValue <= 57 || 95 == chValue) { if (Type.OPERATOR == preType || Type.SEPAERATOR == preType || Type.NULL == preType || Type.PARENTHESIS == preType) { if (Type.OPERATOR == preType) { segments.add(createOperator(expression.substring(wordStartIndex, wordStartIndex + wordLen))); } wordStartIndex = i; wordLen = 0; } preType = Type.OPERAND; wordLen++; } else if (40 == chValue || 41 == chValue) { if (Type.OPERATOR == preType) { segments.add(createOperator(expression .substring(wordStartIndex, wordStartIndex + wordLen))); wordStartIndex = -1; wordLen = 0; } else if (Type.OPERAND == preType) { segments.add(new Operand(expression.substring(wordStartIndex, wordStartIndex + wordLen))); wordStartIndex = -1; wordLen = 0; } preType = Type.PARENTHESIS; segments.add(createOperator((char) chValue + "")); } else if (38 == chValue || 124 == chValue) { if (Type.OPERAND == preType || Type.SEPAERATOR == preType || Type.PARENTHESIS == preType) { if (Type.OPERAND == preType) { segments.add(new Operand(expression.substring(wordStartIndex, wordStartIndex + wordLen))); } wordStartIndex = i; wordLen = 0; } preType = Type.OPERATOR; wordLen++; } else if (32 == chValue || 9 == chValue) { if (Type.OPERATOR == preType) { segments.add(createOperator(expression .substring(wordStartIndex, wordStartIndex + wordLen))); wordStartIndex = -1; wordLen = 0; } else if (Type.OPERAND == preType) { segments.add(new Operand(expression.substring(wordStartIndex, wordStartIndex + wordLen))); wordStartIndex = -1; wordLen = 0; } preType = Type.SEPAERATOR; } else { throw new IllegalArgumentException("illegal expression, at index " + i + " " + (char) chValue); } } if (wordLen > 0) { segments.add(new Operand(expression.substring(wordStartIndex, wordStartIndex + wordLen))); } return segments; } public static boolean isOperand(Op token) { return token instanceof Operand; } public static boolean isLeftParenthesis(Op token) { return token instanceof Operator && LEFTPARENTHESIS == (Operator) token; } public static boolean isRightParenthesis(Op token) { return token instanceof Operator && RIGHTPARENTHESIS == (Operator) token; } public static boolean isOperator(Op token) { return token instanceof Operator; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/filter/impl/Type.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.filter.impl; public enum Type { NULL, OPERAND, OPERATOR, PARENTHESIS, SEPAERATOR; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/future/FutureTaskExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.future; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class FutureTaskExt extends FutureTask { private final Runnable runnable; public FutureTaskExt(final Callable callable) { super(callable); this.runnable = null; } public FutureTaskExt(final Runnable runnable, final V result) { super(runnable, result); this.runnable = runnable; } public Runnable getRunnable() { return runnable; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/help/FAQUrl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.help; public class FAQUrl { public static final String DEFAULT_FAQ_URL = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; public static final String APPLY_TOPIC_URL = DEFAULT_FAQ_URL; public static final String NAME_SERVER_ADDR_NOT_EXIST_URL = DEFAULT_FAQ_URL; public static final String GROUP_NAME_DUPLICATE_URL = DEFAULT_FAQ_URL; public static final String CLIENT_PARAMETER_CHECK_URL = DEFAULT_FAQ_URL; public static final String SUBSCRIPTION_GROUP_NOT_EXIST = DEFAULT_FAQ_URL; public static final String CLIENT_SERVICE_NOT_OK = DEFAULT_FAQ_URL; // FAQ: No route info of this topic, TopicABC public static final String NO_TOPIC_ROUTE_INFO = DEFAULT_FAQ_URL; public static final String LOAD_JSON_EXCEPTION = DEFAULT_FAQ_URL; public static final String SAME_GROUP_DIFFERENT_TOPIC = DEFAULT_FAQ_URL; public static final String MQLIST_NOT_EXIST = DEFAULT_FAQ_URL; public static final String UNEXPECTED_EXCEPTION_URL = DEFAULT_FAQ_URL; public static final String SEND_MSG_FAILED = DEFAULT_FAQ_URL; public static final String UNKNOWN_HOST_EXCEPTION = DEFAULT_FAQ_URL; private static final String TIP_STRING_BEGIN = "\nSee "; private static final String TIP_STRING_END = " for further details."; private static final String MORE_INFORMATION = "For more information, please visit the url, "; public static String suggestTodo(final String url) { StringBuilder sb = new StringBuilder(TIP_STRING_BEGIN.length() + url.length() + TIP_STRING_END.length()); sb.append(TIP_STRING_BEGIN); sb.append(url); sb.append(TIP_STRING_END); return sb.toString(); } public static String attachDefaultURL(final String errorMessage) { if (errorMessage != null) { int index = errorMessage.indexOf(TIP_STRING_BEGIN); if (-1 == index) { StringBuilder sb = new StringBuilder(errorMessage.length() + UNEXPECTED_EXCEPTION_URL.length() + MORE_INFORMATION.length() + 1); sb.append(errorMessage); sb.append("\n"); sb.append(MORE_INFORMATION); sb.append(UNEXPECTED_EXCEPTION_URL); return sb.toString(); } } return errorMessage; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/hook/FilterCheckHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.hook; import java.nio.ByteBuffer; public interface FilterCheckHook { String hookName(); boolean isFilterMatched(final boolean isUnitMode, final ByteBuffer byteBuffer); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/lite/LiteLagInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.lite; public class LiteLagInfo { private String liteTopic; private long lagCount; // earliest unconsumed timestamp private long earliestUnconsumedTimestamp = -1; public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } public long getLagCount() { return lagCount; } public void setLagCount(long lagCount) { this.lagCount = lagCount; } public long getEarliestUnconsumedTimestamp() { return earliestUnconsumedTimestamp; } public void setEarliestUnconsumedTimestamp(long earliestUnconsumedTimestamp) { this.earliestUnconsumedTimestamp = earliestUnconsumedTimestamp; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/lite/LiteSubscription.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.lite; import java.util.Collection; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class LiteSubscription { private String group; private String topic; private final Set liteTopicSet = ConcurrentHashMap.newKeySet(); private volatile long updateTime = System.currentTimeMillis(); public boolean addLiteTopic(String liteTopic) { updateTime(); return this.liteTopicSet.add(liteTopic); } public void addLiteTopic(Collection set) { updateTime(); this.liteTopicSet.addAll(set); } public boolean removeLiteTopic(String liteTopic) { updateTime(); return this.liteTopicSet.remove(liteTopic); } public void removeLiteTopic(Collection set) { updateTime(); this.liteTopicSet.removeAll(set); } public String getGroup() { return group; } public LiteSubscription setGroup(String group) { this.group = group; return this; } public String getTopic() { return topic; } public LiteSubscription setTopic(String topic) { this.topic = topic; return this; } public Set getLiteTopicSet() { return liteTopicSet; } public LiteSubscription setLiteTopicSet(Set liteTopicSet) { this.liteTopicSet.addAll(liteTopicSet); return this; } public long getUpdateTime() { return updateTime; } public void setUpdateTime(long updateTime) { this.updateTime = updateTime; } private void updateTime() { this.updateTime = System.currentTimeMillis(); } @Override public String toString() { return "LiteSubscription{" + "group='" + group + '\'' + ", topic='" + topic + '\'' + ", liteTopicSet=" + liteTopicSet + ", updateTime=" + updateTime + '}'; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/lite/LiteSubscriptionAction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.lite; public enum LiteSubscriptionAction { PARTIAL_ADD, PARTIAL_REMOVE, COMPLETE_ADD, COMPLETE_REMOVE } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/lite/LiteSubscriptionDTO.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.lite; import java.util.Set; public class LiteSubscriptionDTO { private LiteSubscriptionAction action; private String clientId; private String group; private String topic; private Set liteTopicSet; private OffsetOption offsetOption; private long version; public LiteSubscriptionAction getAction() { return action; } public LiteSubscriptionDTO setAction(LiteSubscriptionAction action) { this.action = action; return this; } public String getClientId() { return clientId; } public LiteSubscriptionDTO setClientId(String clientId) { this.clientId = clientId; return this; } public String getGroup() { return group; } public LiteSubscriptionDTO setGroup(String group) { this.group = group; return this; } public String getTopic() { return topic; } public LiteSubscriptionDTO setTopic(String topic) { this.topic = topic; return this; } public Set getLiteTopicSet() { return liteTopicSet; } public LiteSubscriptionDTO setLiteTopicSet(Set liteTopicSet) { this.liteTopicSet = liteTopicSet; return this; } public OffsetOption getOffsetOption() { return offsetOption; } public void setOffsetOption(OffsetOption offsetOption) { this.offsetOption = offsetOption; } public long getVersion() { return version; } public LiteSubscriptionDTO setVersion(long version) { this.version = version; return this; } @Override public String toString() { return "LiteSubscriptionDTO{" + "action=" + action + ", clientId='" + clientId + '\'' + ", group='" + group + '\'' + ", topic='" + topic + '\'' + ", liteTopicSet=" + liteTopicSet + ", offsetOption=" + offsetOption + ", version=" + version + '}'; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/lite/LiteUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.lite; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; public class LiteUtil { public static final char SEPARATOR = '$'; public static final String LITE_TOPIC_PREFIX = MixAll.LMQ_PREFIX + SEPARATOR; /** * Lite Topic: A specific type of message topic implemented based on LMQ, which has no retry topic. * A lite topic's underlying storage is a lmq (Light Message Queue), * but the reverse is not true: lmq is not necessarily a lite topic, * we use "$" as a separator to achieve the distinction and assume "$" is not allowed for topic name. * pattern like: %LMQ%$parentTopic$liteTopic * * @param parentTopic act as namespace * @param liteTopic here means child topic string * @return lmqName */ public static String toLmqName(String parentTopic, String liteTopic) { if (StringUtils.isEmpty(parentTopic) || StringUtils.isEmpty(liteTopic)) { return null; } return LITE_TOPIC_PREFIX + parentTopic + SEPARATOR + liteTopic; } /** * whether lmqName is queue of a lite topic, here we only check the prefix. * @param lmqName * @return */ public static boolean isLiteTopicQueue(String lmqName) { return lmqName != null && lmqName.startsWith(LITE_TOPIC_PREFIX); } public static String getParentTopic(String lmqName) { if (!isLiteTopicQueue(lmqName)) { return null; } int index = lmqName.indexOf(SEPARATOR, LITE_TOPIC_PREFIX.length()); if (index == -1 || index == lmqName.length() - 1 || index == LITE_TOPIC_PREFIX.length()) { return null; } if (lmqName.indexOf(SEPARATOR, index + 1) != -1) { return null; } return lmqName.substring(LITE_TOPIC_PREFIX.length(), index); } public static String getLiteTopic(String lmqName) { if (!isLiteTopicQueue(lmqName)) { return null; } int index = lmqName.indexOf(SEPARATOR, LITE_TOPIC_PREFIX.length()); if (index == -1 || index == lmqName.length() - 1 || index == LITE_TOPIC_PREFIX.length()) { return null; } if (lmqName.indexOf(SEPARATOR, index + 1) != -1) { return null; } return lmqName.substring(index + 1); } /** * %LMQ%${parentTopic}${liteTopic} * parse parent topic and child topic from lmqName * @param lmqName * @return */ public static Pair getParentAndLiteTopic(String lmqName) { if (null == lmqName || !lmqName.startsWith(LITE_TOPIC_PREFIX)) { return null; } String[] array = StringUtils.split(lmqName, SEPARATOR); if (array.length != 3) { return null; } return new Pair<>(array[1], array[2]); } /** * whether lmqName is queue of a lite topic and belongs to the specified parent, * here we only check the prefix. * @param lmqName * @param parentTopic * @return */ public static boolean belongsTo(String lmqName, String parentTopic) { return lmqName != null && lmqName.startsWith(LITE_TOPIC_PREFIX + parentTopic + SEPARATOR); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/lite/OffsetOption.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.lite; import java.util.Objects; public class OffsetOption { public static final long POLICY_LAST_VALUE = 0L; public static final long POLICY_MIN_VALUE = 1L; public static final long POLICY_MAX_VALUE = 2L; private Type type; private long value; public OffsetOption() { } public OffsetOption(Type type, long value) { this.type = type; this.value = value; } public Type getType() { return type; } public void setType(Type type) { this.type = type; } public long getValue() { return value; } public void setValue(long value) { this.value = value; } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } OffsetOption option = (OffsetOption) o; return value == option.value && type == option.type; } @Override public int hashCode() { int result = Objects.hashCode(type); result = 31 * result + Long.hashCode(value); return result; } @Override public String toString() { return "OffsetOption{" + "type=" + type + ", value=" + value + '}'; } public enum Type { POLICY, OFFSET, TAIL_N, TIMESTAMP } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/logging/DefaultJoranConfiguratorExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.logging; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.rocketmq.logging.ch.qos.logback.classic.ClassicConstants; import org.apache.rocketmq.logging.ch.qos.logback.classic.LoggerContext; import org.apache.rocketmq.logging.ch.qos.logback.classic.util.DefaultJoranConfigurator; import org.apache.rocketmq.logging.ch.qos.logback.core.LogbackException; import org.apache.rocketmq.logging.ch.qos.logback.core.joran.spi.JoranException; import org.apache.rocketmq.logging.ch.qos.logback.core.status.InfoStatus; import org.apache.rocketmq.logging.ch.qos.logback.core.status.StatusManager; import org.apache.rocketmq.logging.ch.qos.logback.core.util.Loader; import org.apache.rocketmq.logging.ch.qos.logback.core.util.OptionHelper; public class DefaultJoranConfiguratorExt extends DefaultJoranConfigurator { final public static String TEST_AUTOCONFIG_FILE = "rmq.logback-test.xml"; final public static String AUTOCONFIG_FILE = "rmq.logback.xml"; final public static String PROXY_AUTOCONFIG_FILE = "rmq.proxy.logback.xml"; final public static String BROKER_AUTOCONFIG_FILE = "rmq.broker.logback.xml"; final public static String NAMESRV_AUTOCONFIG_FILE = "rmq.namesrv.logback.xml"; final public static String CONTROLLER_AUTOCONFIG_FILE = "rmq.controller.logback.xml"; final public static String TOOLS_AUTOCONFIG_FILE = "rmq.tools.logback.xml"; final public static String CLIENT_AUTOCONFIG_FILE = "rmq.client.logback.xml"; private final List configFiles; public DefaultJoranConfiguratorExt() { this.configFiles = new ArrayList<>(); configFiles.add(TEST_AUTOCONFIG_FILE); configFiles.add(AUTOCONFIG_FILE); configFiles.add(PROXY_AUTOCONFIG_FILE); configFiles.add(BROKER_AUTOCONFIG_FILE); configFiles.add(NAMESRV_AUTOCONFIG_FILE); configFiles.add(CONTROLLER_AUTOCONFIG_FILE); configFiles.add(TOOLS_AUTOCONFIG_FILE); configFiles.add(CLIENT_AUTOCONFIG_FILE); } @Override public ExecutionStatus configure(LoggerContext loggerContext) { URL url = findURLOfDefaultConfigurationFile(true); if (url != null) { try { configureByResource(url); } catch (JoranException e) { e.printStackTrace(); } } // skip other configurator on purpose. return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY; } public void configureByResource(URL url) throws JoranException { if (url == null) { throw new IllegalArgumentException("URL argument cannot be null"); } final String urlString = url.toString(); if (urlString.endsWith("xml")) { JoranConfiguratorExt configurator = new JoranConfiguratorExt(); configurator.setContext(context); configurator.doConfigure0(url); } else { throw new LogbackException( "Unexpected filename extension of file [" + url + "]. Should be .xml"); } } public URL findURLOfDefaultConfigurationFile(boolean updateStatus) { ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this); URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus); if (url != null) { return url; } for (String configFile : configFiles) { url = getResource(configFile, myClassLoader, updateStatus); if (url != null) { return url; } } return null; } private URL findConfigFileURLFromSystemProperties(ClassLoader classLoader, boolean updateStatus) { String logbackConfigFile = OptionHelper.getSystemProperty(ClassicConstants.CONFIG_FILE_PROPERTY); if (logbackConfigFile != null) { URL result = null; try { result = new URL(logbackConfigFile); return result; } catch (MalformedURLException e) { // so, resource is not a URL: // attempt to get the resource from the class path result = Loader.getResource(logbackConfigFile, classLoader); if (result != null) { return result; } File f = new File(logbackConfigFile); if (f.exists() && f.isFile()) { try { result = f.toURI().toURL(); return result; } catch (MalformedURLException ignored) { } } } finally { if (updateStatus) { statusOnResourceSearch(logbackConfigFile, classLoader, result); } } } return null; } private URL getResource(String filename, ClassLoader myClassLoader, boolean updateStatus) { URL url = Loader.getResource(filename, myClassLoader); if (updateStatus) { statusOnResourceSearch(filename, myClassLoader, url); } return url; } private void statusOnResourceSearch(String resourceName, ClassLoader classLoader, URL url) { StatusManager sm = context.getStatusManager(); if (url == null) { sm.add(new InfoStatus("Could NOT find resource [" + resourceName + "]", context)); } else { sm.add(new InfoStatus("Found resource [" + resourceName + "] at [" + url.toString() + "]", context)); multiplicityWarning(resourceName, classLoader); } } private void multiplicityWarning(String resourceName, ClassLoader classLoader) { Set urlSet = null; try { urlSet = Loader.getResources(resourceName, classLoader); } catch (IOException e) { addError("Failed to get url list for resource [" + resourceName + "]", e); } if (urlSet != null && urlSet.size() > 1) { addWarn("Resource [" + resourceName + "] occurs multiple times on the classpath."); for (URL url : urlSet) { addWarn("Resource [" + resourceName + "] occurs at [" + url.toString() + "]"); } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/logging/JoranConfiguratorExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.logging; import com.google.common.io.CharStreams; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.logging.ch.qos.logback.classic.joran.JoranConfigurator; import org.apache.rocketmq.logging.ch.qos.logback.core.joran.spi.JoranException; public class JoranConfiguratorExt extends JoranConfigurator { private InputStream transformXml(InputStream in) throws IOException { try { String str = CharStreams.toString(new InputStreamReader(in, StandardCharsets.UTF_8)); str = str.replace("\"ch.qos.logback", "\"org.apache.rocketmq.logging.ch.qos.logback"); return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); } finally { if (null != in) { in.close(); } } } public final void doConfigure0(URL url) throws JoranException { InputStream in = null; try { informContextOfURLUsedForConfiguration(getContext(), url); URLConnection urlConnection = url.openConnection(); // per http://jira.qos.ch/browse/LBCORE-105 // per http://jira.qos.ch/browse/LBCORE-127 urlConnection.setUseCaches(false); InputStream temp = urlConnection.getInputStream(); in = transformXml(temp); doConfigure(in, url.toExternalForm()); } catch (IOException ioe) { String errMsg = "Could not open URL [" + url + "]."; addError(errMsg, ioe); throw new JoranException(errMsg, ioe); } finally { if (in != null) { try { in.close(); } catch (IOException ioe) { String errMsg = "Could not close input stream"; addError(errMsg, ioe); throw new JoranException(errMsg, ioe); } } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/Message.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import org.apache.commons.lang3.math.NumberUtils; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; public class Message implements Serializable { private static final long serialVersionUID = 8445773977080406428L; private String topic; private int flag; private Map properties; private byte[] body; private String transactionId; public Message() { } public Message(String topic, byte[] body) { this(topic, "", "", 0, body, true); } public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) { this.topic = topic; this.flag = flag; this.body = body; if (tags != null && tags.length() > 0) { this.setTags(tags); } if (keys != null && keys.length() > 0) { this.setKeys(keys); } this.setWaitStoreMsgOK(waitStoreMsgOK); } public Message(String topic, String tags, byte[] body) { this(topic, tags, "", 0, body, true); } public Message(String topic, String tags, String keys, byte[] body) { this(topic, tags, keys, 0, body, true); } public void setKeys(String keys) { this.putProperty(MessageConst.PROPERTY_KEYS, keys); } void putProperty(final String name, final String value) { if (null == this.properties) { this.properties = new HashMap<>(); } this.properties.put(name, value); } void clearProperty(final String name) { if (null != this.properties) { this.properties.remove(name); } } public void putUserProperty(final String name, final String value) { if (MessageConst.STRING_HASH_SET.contains(name)) { throw new RuntimeException(String.format( "The Property<%s> is used by system, input another please", name)); } if (value == null || value.trim().isEmpty() || name == null || name.trim().isEmpty()) { throw new IllegalArgumentException( "The name or value of property can not be null or blank string!" ); } this.putProperty(name, value); } public String getUserProperty(final String name) { return this.getProperty(name); } public String getProperty(final String name) { if (null == this.properties) { this.properties = new HashMap<>(); } return this.properties.get(name); } public boolean hasProperty(final String name) { if (null == this.properties) { return false; } return this.properties.containsKey(name); } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getTags() { return this.getProperty(MessageConst.PROPERTY_TAGS); } public void setTags(String tags) { this.putProperty(MessageConst.PROPERTY_TAGS, tags); } public String getKeys() { return this.getProperty(MessageConst.PROPERTY_KEYS); } public void setKeys(Collection keyCollection) { String keys = String.join(MessageConst.KEY_SEPARATOR, keyCollection); this.setKeys(keys); } public int getDelayTimeLevel() { String t = this.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL); if (t != null) { return Integer.parseInt(t); } return 0; } public void setDelayTimeLevel(int level) { this.putProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL, String.valueOf(level)); } public void setPriority(int priority) { if (priority < 0) { throw new IllegalArgumentException("The priority must be greater than or equal to 0"); } this.putProperty(MessageConst.PROPERTY_PRIORITY, String.valueOf(priority)); } public int getPriority() { return NumberUtils.toInt(this.getProperty(MessageConst.PROPERTY_PRIORITY), -1); } public boolean isWaitStoreMsgOK() { String result = this.getProperty(MessageConst.PROPERTY_WAIT_STORE_MSG_OK); if (null == result) { return true; } return Boolean.parseBoolean(result); } public void setWaitStoreMsgOK(boolean waitStoreMsgOK) { this.putProperty(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, Boolean.toString(waitStoreMsgOK)); } public void setInstanceId(String instanceId) { this.putProperty(MessageConst.PROPERTY_INSTANCE_ID, instanceId); } public int getFlag() { return flag; } public void setFlag(int flag) { this.flag = flag; } public byte[] getBody() { return body; } public void setBody(byte[] body) { this.body = body; } public Map getProperties() { return properties; } void setProperties(Map properties) { this.properties = properties; } public String getBuyerId() { return getProperty(MessageConst.PROPERTY_BUYER_ID); } public void setBuyerId(String buyerId) { putProperty(MessageConst.PROPERTY_BUYER_ID, buyerId); } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } @Override public String toString() { return "Message{" + "topic='" + topic + '\'' + ", flag=" + flag + ", properties=" + properties + ", body=" + Arrays.toString(body) + ", transactionId='" + transactionId + '\'' + '}'; } public void setDelayTimeSec(long sec) { this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC, String.valueOf(sec)); } public long getDelayTimeSec() { String t = this.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC); if (t != null) { return Long.parseLong(t); } return 0; } public void setDelayTimeMs(long timeMs) { this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_MS, String.valueOf(timeMs)); } public long getDelayTimeMs() { String t = this.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS); if (t != null) { return Long.parseLong(t); } return 0; } public void setDeliverTimeMs(long timeMs) { this.putProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS, String.valueOf(timeMs)); } public long getDeliverTimeMs() { String t = this.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS); if (t != null) { return Long.parseLong(t); } return 0; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageAccessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.util.HashMap; import java.util.Map; public class MessageAccessor { public static void clearProperty(final Message msg, final String name) { msg.clearProperty(name); } public static void setProperties(final Message msg, Map properties) { msg.setProperties(properties); } public static void setTransferFlag(final Message msg, String unit) { putProperty(msg, MessageConst.PROPERTY_TRANSFER_FLAG, unit); } public static void putProperty(final Message msg, final String name, final String value) { msg.putProperty(name, value); } public static String getTransferFlag(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_TRANSFER_FLAG); } public static void setCorrectionFlag(final Message msg, String unit) { putProperty(msg, MessageConst.PROPERTY_CORRECTION_FLAG, unit); } public static String getCorrectionFlag(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_CORRECTION_FLAG); } public static void setOriginMessageId(final Message msg, String originMessageId) { putProperty(msg, MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, originMessageId); } public static String getOriginMessageId(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID); } public static void setMQ2Flag(final Message msg, String flag) { putProperty(msg, MessageConst.PROPERTY_MQ2_FLAG, flag); } public static String getMQ2Flag(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_MQ2_FLAG); } public static void setReconsumeTime(final Message msg, String reconsumeTimes) { putProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME, reconsumeTimes); } public static String getReconsumeTime(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_RECONSUME_TIME); } public static void setMaxReconsumeTimes(final Message msg, String maxReconsumeTimes) { putProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES, maxReconsumeTimes); } public static String getMaxReconsumeTimes(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_MAX_RECONSUME_TIMES); } public static void setConsumeStartTimeStamp(final Message msg, String propertyConsumeStartTimeStamp) { putProperty(msg, MessageConst.PROPERTY_CONSUME_START_TIMESTAMP, propertyConsumeStartTimeStamp); } public static String getConsumeStartTimeStamp(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_CONSUME_START_TIMESTAMP); } public static void setLiteTopic(final Message msg, String liteTopic) { MessageAccessor.putProperty(msg, MessageConst.PROPERTY_LITE_TOPIC, liteTopic); } public static Message cloneMessage(final Message msg) { Message newMsg = new Message(msg.getTopic(), msg.getBody()); newMsg.setFlag(msg.getFlag()); newMsg.setProperties(msg.getProperties()); return newMsg; } public static Map deepCopyProperties(Map properties) { if (properties == null) { return null; } return new HashMap<>(properties); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.rocketmq.common.MixAll; public class MessageBatch extends Message implements Iterable { private static final long serialVersionUID = 621335151046335557L; private final List messages; public MessageBatch(List messages) { this.messages = messages; } public byte[] encode() { return MessageDecoder.encodeMessages(messages); } public Iterator iterator() { return messages.iterator(); } public static MessageBatch generateFromList(Collection messages) { assert messages != null; assert messages.size() > 0; List messageList = new ArrayList<>(messages.size()); Message first = null; for (Message message : messages) { if (message.getDelayTimeLevel() > 0 || message.getDelayTimeMs() > 0 || message.getDelayTimeSec() > 0 || message.getDeliverTimeMs() > 0) { throw new UnsupportedOperationException("Delayed messages are not supported for batching"); } if (message.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { throw new UnsupportedOperationException("Retry Group is not supported for batching"); } if (first == null) { first = message; } else { if (!first.getTopic().equals(message.getTopic())) { throw new UnsupportedOperationException("The topic of the messages in one batch should be the same"); } if (first.isWaitStoreMsgOK() != message.isWaitStoreMsgOK()) { throw new UnsupportedOperationException("The waitStoreMsgOK of the messages in one batch should the same"); } } messageList.add(message); } MessageBatch messageBatch = new MessageBatch(messageList); messageBatch.setTopic(first.getTopic()); messageBatch.setWaitStoreMsgOK(first.isWaitStoreMsgOK()); return messageBatch; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageClientExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; public class MessageClientExt extends MessageExt { public String getOffsetMsgId() { return super.getMsgId(); } public void setOffsetMsgId(String offsetMsgId) { super.setMsgId(offsetMsgId); } @Override public String getMsgId() { String uniqID = MessageClientIDSetter.getUniqID(this); if (uniqID == null) { return this.getOffsetMsgId(); } else { return uniqID; } } public void setMsgId(String msgId) { //DO NOTHING //MessageClientIDSetter.setUniqID(this); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageClientIDSetter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.nio.ByteBuffer; import java.util.Calendar; import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.UtilAll; public class MessageClientIDSetter { private static final int LEN; private static final char[] FIX_STRING; private static final AtomicInteger COUNTER; private static long startTime; private static long nextStartTime; static { byte[] ip; try { ip = UtilAll.getIP(); } catch (Exception e) { ip = createFakeIP(); } LEN = ip.length + 2 + 4 + 4 + 2; ByteBuffer tempBuffer = ByteBuffer.allocate(ip.length + 2 + 4); tempBuffer.put(ip); tempBuffer.putShort((short) UtilAll.getPid()); tempBuffer.putInt(MessageClientIDSetter.class.getClassLoader().hashCode()); FIX_STRING = UtilAll.bytes2string(tempBuffer.array()).toCharArray(); setStartTime(System.currentTimeMillis()); COUNTER = new AtomicInteger(0); } private synchronized static void setStartTime(long millis) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(millis); cal.set(Calendar.DAY_OF_MONTH, 1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); startTime = cal.getTimeInMillis(); cal.add(Calendar.MONTH, 1); nextStartTime = cal.getTimeInMillis(); } public static Date getNearlyTimeFromID(String msgID) { ByteBuffer buf = ByteBuffer.allocate(8); byte[] bytes = UtilAll.string2bytes(msgID); int ipLength = bytes.length == 28 ? 16 : 4; buf.put((byte) 0); buf.put((byte) 0); buf.put((byte) 0); buf.put((byte) 0); buf.put(bytes, ipLength + 2 + 4, 4); buf.position(0); long spanMS = buf.getLong(); Calendar cal = Calendar.getInstance(); long now = cal.getTimeInMillis(); cal.set(Calendar.DAY_OF_MONTH, 1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); long monStartTime = cal.getTimeInMillis(); if (monStartTime + spanMS >= now) { cal.add(Calendar.MONTH, -1); monStartTime = cal.getTimeInMillis(); } cal.setTimeInMillis(monStartTime + spanMS); return cal.getTime(); } public static String getIPStrFromID(String msgID) { byte[] ipBytes = getIPFromID(msgID); if (ipBytes.length == 16) { return UtilAll.ipToIPv6Str(ipBytes); } else { return UtilAll.ipToIPv4Str(ipBytes); } } public static byte[] getIPFromID(String msgID) { byte[] bytes = UtilAll.string2bytes(msgID); int ipLength = bytes.length == 28 ? 16 : 4; byte[] result = new byte[ipLength]; System.arraycopy(bytes, 0, result, 0, ipLength); return result; } public static int getPidFromID(String msgID) { byte[] bytes = UtilAll.string2bytes(msgID); ByteBuffer wrap = ByteBuffer.wrap(bytes); int value = wrap.getShort(bytes.length - 2 - 4 - 4 - 2); return value & 0x0000FFFF; } public static String createUniqID() { char[] sb = new char[LEN * 2]; System.arraycopy(FIX_STRING, 0, sb, 0, FIX_STRING.length); long current = System.currentTimeMillis(); if (current >= nextStartTime) { setStartTime(current); } int diff = (int)(current - startTime); if (diff < 0 && diff > -1000_000) { // may cause by NTP diff = 0; } int pos = FIX_STRING.length; UtilAll.writeInt(sb, pos, diff); pos += 8; UtilAll.writeShort(sb, pos, COUNTER.getAndIncrement()); return new String(sb); } public static void setUniqID(final Message msg) { if (msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX) == null) { msg.putProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, createUniqID()); } } public static String getUniqID(final Message msg) { return msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); } public static byte[] createFakeIP() { ByteBuffer bb = ByteBuffer.allocate(8); bb.putLong(System.currentTimeMillis()); bb.position(4); byte[] fakeIP = new byte[4]; bb.get(fakeIP); return fakeIP; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.util.HashSet; public class MessageConst { public static final String PROPERTY_KEYS = "KEYS"; public static final String PROPERTY_TAGS = "TAGS"; public static final String PROPERTY_WAIT_STORE_MSG_OK = "WAIT"; public static final String PROPERTY_DELAY_TIME_LEVEL = "DELAY"; public static final String PROPERTY_RETRY_TOPIC = "RETRY_TOPIC"; public static final String PROPERTY_ORIGIN_GROUP = "ORIGIN_GROUP"; public static final String PROPERTY_REAL_TOPIC = "REAL_TOPIC"; public static final String PROPERTY_REAL_QUEUE_ID = "REAL_QID"; public static final String PROPERTY_TRANSACTION_PREPARED = "TRAN_MSG"; public static final String PROPERTY_PRODUCER_GROUP = "PGROUP"; public static final String PROPERTY_MIN_OFFSET = "MIN_OFFSET"; public static final String PROPERTY_MAX_OFFSET = "MAX_OFFSET"; public static final String PROPERTY_BUYER_ID = "BUYER_ID"; public static final String PROPERTY_ORIGIN_MESSAGE_ID = "ORIGIN_MESSAGE_ID"; public static final String PROPERTY_TRANSFER_FLAG = "TRANSFER_FLAG"; public static final String PROPERTY_CORRECTION_FLAG = "CORRECTION_FLAG"; public static final String PROPERTY_MQ2_FLAG = "MQ2_FLAG"; public static final String PROPERTY_RECONSUME_TIME = "RECONSUME_TIME"; public static final String PROPERTY_MSG_REGION = "MSG_REGION"; public static final String PROPERTY_TRACE_SWITCH = "TRACE_ON"; public static final String PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX = "UNIQ_KEY"; public static final String PROPERTY_TRANS_OFFSET = "TRANS_OFFSET"; public static final String PROPERTY_EXTEND_UNIQ_INFO = "EXTEND_UNIQ_INFO"; public static final String PROPERTY_MAX_RECONSUME_TIMES = "MAX_RECONSUME_TIMES"; public static final String PROPERTY_CONSUME_START_TIMESTAMP = "CONSUME_START_TIME"; public static final String PROPERTY_PRIORITY = "_SYS_MSG_PRIORITY_"; public static final String PROPERTY_INNER_NUM = "INNER_NUM"; public static final String PROPERTY_INNER_BASE = "INNER_BASE"; public static final String DUP_INFO = "DUP_INFO"; public static final String PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS = "CHECK_IMMUNITY_TIME_IN_SECONDS"; public static final String PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET = "TRAN_PREPARED_QUEUE_OFFSET"; public static final String PROPERTY_TRANSACTION_ID = "__transactionId__"; public static final String PROPERTY_TRANSACTION_CHECK_TIMES = "TRANSACTION_CHECK_TIMES"; public static final String PROPERTY_INSTANCE_ID = "INSTANCE_ID"; public static final String PROPERTY_CORRELATION_ID = "CORRELATION_ID"; public static final String PROPERTY_MESSAGE_REPLY_TO_CLIENT = "REPLY_TO_CLIENT"; public static final String PROPERTY_MESSAGE_TTL = "TTL"; public static final String PROPERTY_REPLY_MESSAGE_ARRIVE_TIME = "ARRIVE_TIME"; public static final String PROPERTY_PUSH_REPLY_TIME = "PUSH_REPLY_TIME"; public static final String PROPERTY_CLUSTER = "CLUSTER"; public static final String PROPERTY_MESSAGE_TYPE = "MSG_TYPE"; public static final String PROPERTY_POP_CK = "POP_CK"; public static final String PROPERTY_POP_CK_OFFSET = "POP_CK_OFFSET"; public static final String PROPERTY_FIRST_POP_TIME = "1ST_POP_TIME"; public static final String PROPERTY_SHARDING_KEY = "__SHARDINGKEY"; public static final String PROPERTY_LITE_TOPIC = "__LITE_TOPIC"; public static final String PROPERTY_FORWARD_QUEUE_ID = "PROPERTY_FORWARD_QUEUE_ID"; public static final String PROPERTY_REDIRECT = "REDIRECT"; public static final String PROPERTY_INNER_MULTI_DISPATCH = "INNER_MULTI_DISPATCH"; public static final String PROPERTY_INNER_MULTI_QUEUE_OFFSET = "INNER_MULTI_QUEUE_OFFSET"; public static final String PROPERTY_TRACE_CONTEXT = "TRACE_CONTEXT"; public static final String PROPERTY_TIMER_DELAY_SEC = "TIMER_DELAY_SEC"; public static final String PROPERTY_TIMER_DELIVER_MS = "TIMER_DELIVER_MS"; public static final String PROPERTY_BORN_HOST = "__BORNHOST"; public static final String PROPERTY_BORN_TIMESTAMP = "BORN_TIMESTAMP"; /** * property which name starts with "__RMQ.TRANSIENT." is called transient one that will not stored in broker disks. */ public static final String PROPERTY_TRANSIENT_PREFIX = "__RMQ.TRANSIENT."; /** * the transient property key of topicSysFlag (set by client when pulling messages) */ public static final String PROPERTY_TRANSIENT_TOPIC_CONFIG = PROPERTY_TRANSIENT_PREFIX + "TOPIC_SYS_FLAG"; /** * the transient property key of groupSysFlag (set by client when pulling messages) */ public static final String PROPERTY_TRANSIENT_GROUP_CONFIG = PROPERTY_TRANSIENT_PREFIX + "GROUP_SYS_FLAG"; public static final String KEY_SEPARATOR = " "; public final static String INDEX_KEY_TYPE = "K"; public final static String INDEX_UNIQUE_TYPE = "U"; public final static String INDEX_TAG_TYPE = "T"; public final static String TIMER_ENGINE_TYPE = "E_T"; public final static String TIMER_ENGINE_ROCKSDB_TIMELINE = "R"; public final static String TIMER_ENGINE_FILE_TIME_WHEEL = "F"; public static final HashSet STRING_HASH_SET = new HashSet<>(64); public static final String PROPERTY_TIMER_ENQUEUE_MS = "TIMER_ENQUEUE_MS"; public static final String PROPERTY_TIMER_DEQUEUE_MS = "TIMER_DEQUEUE_MS"; public static final String PROPERTY_TIMER_ROLL_TIMES = "TIMER_ROLL_TIMES"; public static final String PROPERTY_TIMER_OUT_MS = "TIMER_OUT_MS"; public static final String PROPERTY_TIMER_DEL_UNIQKEY = "TIMER_DEL_UNIQKEY"; public static final String PROPERTY_TIMER_ROLL_LABEL = "TIMER_ROLL_LABEL"; public static final String PROPERTY_TIMER_DELAY_LEVEL = "TIMER_DELAY_LEVEL"; public static final String PROPERTY_TIMER_DELAY_MS = "TIMER_DELAY_MS"; public static final String PROPERTY_CRC32 = "__CRC32#"; /** * properties for DLQ */ public static final String PROPERTY_DLQ_ORIGIN_TOPIC = "DLQ_ORIGIN_TOPIC"; public static final String PROPERTY_DLQ_ORIGIN_MESSAGE_ID = "DLQ_ORIGIN_MESSAGE_ID"; static { STRING_HASH_SET.add(PROPERTY_TRACE_SWITCH); STRING_HASH_SET.add(PROPERTY_MSG_REGION); STRING_HASH_SET.add(PROPERTY_KEYS); STRING_HASH_SET.add(PROPERTY_TAGS); STRING_HASH_SET.add(PROPERTY_WAIT_STORE_MSG_OK); STRING_HASH_SET.add(PROPERTY_DELAY_TIME_LEVEL); STRING_HASH_SET.add(PROPERTY_RETRY_TOPIC); STRING_HASH_SET.add(PROPERTY_ORIGIN_GROUP); STRING_HASH_SET.add(PROPERTY_REAL_TOPIC); STRING_HASH_SET.add(PROPERTY_REAL_QUEUE_ID); STRING_HASH_SET.add(PROPERTY_TRANSACTION_PREPARED); STRING_HASH_SET.add(PROPERTY_PRODUCER_GROUP); STRING_HASH_SET.add(PROPERTY_MIN_OFFSET); STRING_HASH_SET.add(PROPERTY_MAX_OFFSET); STRING_HASH_SET.add(PROPERTY_BUYER_ID); STRING_HASH_SET.add(PROPERTY_ORIGIN_MESSAGE_ID); STRING_HASH_SET.add(PROPERTY_TRANSFER_FLAG); STRING_HASH_SET.add(PROPERTY_CORRECTION_FLAG); STRING_HASH_SET.add(PROPERTY_MQ2_FLAG); STRING_HASH_SET.add(PROPERTY_RECONSUME_TIME); STRING_HASH_SET.add(PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); STRING_HASH_SET.add(PROPERTY_MAX_RECONSUME_TIMES); STRING_HASH_SET.add(PROPERTY_CONSUME_START_TIMESTAMP); STRING_HASH_SET.add(PROPERTY_POP_CK); STRING_HASH_SET.add(PROPERTY_POP_CK_OFFSET); STRING_HASH_SET.add(PROPERTY_FIRST_POP_TIME); STRING_HASH_SET.add(PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); STRING_HASH_SET.add(DUP_INFO); STRING_HASH_SET.add(PROPERTY_EXTEND_UNIQ_INFO); STRING_HASH_SET.add(PROPERTY_INSTANCE_ID); STRING_HASH_SET.add(PROPERTY_CORRELATION_ID); STRING_HASH_SET.add(PROPERTY_MESSAGE_REPLY_TO_CLIENT); STRING_HASH_SET.add(PROPERTY_MESSAGE_TTL); STRING_HASH_SET.add(PROPERTY_REPLY_MESSAGE_ARRIVE_TIME); STRING_HASH_SET.add(PROPERTY_PUSH_REPLY_TIME); STRING_HASH_SET.add(PROPERTY_CLUSTER); STRING_HASH_SET.add(PROPERTY_MESSAGE_TYPE); STRING_HASH_SET.add(PROPERTY_INNER_MULTI_QUEUE_OFFSET); STRING_HASH_SET.add(PROPERTY_TIMER_DELAY_MS); STRING_HASH_SET.add(PROPERTY_TIMER_DELAY_SEC); STRING_HASH_SET.add(PROPERTY_TIMER_DELIVER_MS); STRING_HASH_SET.add(PROPERTY_TIMER_ENQUEUE_MS); STRING_HASH_SET.add(PROPERTY_TIMER_DEQUEUE_MS); STRING_HASH_SET.add(PROPERTY_TIMER_ROLL_TIMES); STRING_HASH_SET.add(PROPERTY_TIMER_OUT_MS); STRING_HASH_SET.add(PROPERTY_TIMER_DEL_UNIQKEY); STRING_HASH_SET.add(PROPERTY_TIMER_DELAY_LEVEL); STRING_HASH_SET.add(PROPERTY_BORN_HOST); STRING_HASH_SET.add(PROPERTY_BORN_TIMESTAMP); STRING_HASH_SET.add(PROPERTY_DLQ_ORIGIN_TOPIC); STRING_HASH_SET.add(PROPERTY_DLQ_ORIGIN_MESSAGE_ID); STRING_HASH_SET.add(PROPERTY_CRC32); STRING_HASH_SET.add(PROPERTY_PRIORITY); STRING_HASH_SET.add(PROPERTY_LITE_TOPIC); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import io.netty.buffer.ByteBuf; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.compression.Compressor; import org.apache.rocketmq.common.compression.CompressorFactory; import org.apache.rocketmq.common.sysflag.MessageSysFlag; public class MessageDecoder { // public final static int MSG_ID_LENGTH = 8 + 8; public final static Charset CHARSET_UTF8 = StandardCharsets.UTF_8; public final static int MESSAGE_MAGIC_CODE_POSITION = 4; public final static int MESSAGE_FLAG_POSITION = 16; public final static int MESSAGE_PHYSIC_OFFSET_POSITION = 28; public final static int MESSAGE_STORE_TIMESTAMP_POSITION = 56; // Set message magic code v2 if topic length > 127 public final static int MESSAGE_MAGIC_CODE = -626843481; public final static int MESSAGE_MAGIC_CODE_V2 = -626843477; // End of file empty MAGIC CODE cbd43194 public final static int BLANK_MAGIC_CODE = -875286124; public static final char NAME_VALUE_SEPARATOR = 1; public static final char PROPERTY_SEPARATOR = 2; public static final int PHY_POS_POSITION = 4 + 4 + 4 + 4 + 4 + 8; public static final int QUEUE_OFFSET_POSITION = 4 + 4 + 4 + 4 + 4; public static final int SYSFLAG_POSITION = 4 + 4 + 4 + 4 + 4 + 8 + 8; // public static final int BODY_SIZE_POSITION = 4 // 1 TOTALSIZE // + 4 // 2 MAGICCODE // + 4 // 3 BODYCRC // + 4 // 4 QUEUEID // + 4 // 5 FLAG // + 8 // 6 QUEUEOFFSET // + 8 // 7 PHYSICALOFFSET // + 4 // 8 SYSFLAG // + 8 // 9 BORNTIMESTAMP // + 8 // 10 BORNHOST // + 8 // 11 STORETIMESTAMP // + 8 // 12 STOREHOSTADDRESS // + 4 // 13 RECONSUMETIMES // + 8; // 14 Prepared Transaction Offset public static String createMessageId(final ByteBuffer input, final ByteBuffer addr, final long offset) { input.flip(); int msgIDLength = addr.limit() == 8 ? 16 : 28; input.limit(msgIDLength); input.put(addr); input.putLong(offset); return UtilAll.bytes2string(input.array()); } public static String createMessageId(SocketAddress socketAddress, long transactionIdhashCode) { InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; int msgIDLength = inetSocketAddress.getAddress() instanceof Inet4Address ? 16 : 28; ByteBuffer byteBuffer = ByteBuffer.allocate(msgIDLength); byteBuffer.put(inetSocketAddress.getAddress().getAddress()); byteBuffer.putInt(inetSocketAddress.getPort()); byteBuffer.putLong(transactionIdhashCode); byteBuffer.flip(); return UtilAll.bytes2string(byteBuffer.array()); } public static MessageId decodeMessageId(final String msgId) throws UnknownHostException { byte[] bytes = UtilAll.string2bytes(msgId); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); // address(ip+port) byte[] ip = new byte[msgId.length() == 32 ? 4 : 16]; byteBuffer.get(ip); int port = byteBuffer.getInt(); SocketAddress address = new InetSocketAddress(InetAddress.getByAddress(ip), port); // offset long offset = byteBuffer.getLong(); return new MessageId(address, offset); } /** * Just decode properties from msg buffer. * * @param byteBuffer msg commit log buffer. */ public static Map decodeProperties(ByteBuffer byteBuffer) { int sysFlag = byteBuffer.getInt(SYSFLAG_POSITION); int magicCode = byteBuffer.getInt(MESSAGE_MAGIC_CODE_POSITION); MessageVersion version = MessageVersion.valueOfMagicCode(magicCode); int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; int storehostAddressLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 8 : 20; int bodySizePosition = 4 // 1 TOTALSIZE + 4 // 2 MAGICCODE + 4 // 3 BODYCRC + 4 // 4 QUEUEID + 4 // 5 FLAG + 8 // 6 QUEUEOFFSET + 8 // 7 PHYSICALOFFSET + 4 // 8 SYSFLAG + 8 // 9 BORNTIMESTAMP + bornhostLength // 10 BORNHOST + 8 // 11 STORETIMESTAMP + storehostAddressLength // 12 STOREHOSTADDRESS + 4 // 13 RECONSUMETIMES + 8; // 14 Prepared Transaction Offset int topicLengthPosition = bodySizePosition + 4 + byteBuffer.getInt(bodySizePosition); byteBuffer.position(topicLengthPosition); int topicLengthSize = version.getTopicLengthSize(); int topicLength = version.getTopicLength(byteBuffer); int propertiesPosition = topicLengthPosition + topicLengthSize + topicLength; short propertiesLength = byteBuffer.getShort(propertiesPosition); byteBuffer.position(propertiesPosition + 2); if (propertiesLength > 0) { byte[] properties = new byte[propertiesLength]; byteBuffer.get(properties); String propertiesString = new String(properties, CHARSET_UTF8); return string2messageProperties(propertiesString); } return null; } public static void createCrc32(final ByteBuffer input, int crc32) { input.put(MessageConst.PROPERTY_CRC32.getBytes(StandardCharsets.UTF_8)); input.put((byte) NAME_VALUE_SEPARATOR); for (int i = 0; i < 10; i++) { byte b = '0'; if (crc32 > 0) { b += (byte) (crc32 % 10); crc32 /= 10; } input.put(b); } input.put((byte) PROPERTY_SEPARATOR); } public static void createCrc32(final ByteBuf input, int crc32) { input.writeBytes(MessageConst.PROPERTY_CRC32.getBytes(StandardCharsets.UTF_8)); input.writeByte((byte) NAME_VALUE_SEPARATOR); for (int i = 0; i < 10; i++) { byte b = '0'; if (crc32 > 0) { b += (byte) (crc32 % 10); crc32 /= 10; } input.writeByte(b); } input.writeByte((byte) PROPERTY_SEPARATOR); } public static MessageExt decode(ByteBuffer byteBuffer) { return decode(byteBuffer, true, true, false); } public static MessageExt clientDecode(ByteBuffer byteBuffer, final boolean readBody) { return decode(byteBuffer, readBody, true, true); } public static MessageExt decode(ByteBuffer byteBuffer, final boolean readBody) { return decode(byteBuffer, readBody, true, false); } public static byte[] encode(MessageExt messageExt, boolean needCompress) throws Exception { byte[] body = messageExt.getBody(); byte[] topics = messageExt.getTopic().getBytes(CHARSET_UTF8); byte topicLen = (byte) topics.length; String properties = messageProperties2String(messageExt.getProperties()); byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8); short propertiesLength = (short) propertiesBytes.length; int sysFlag = messageExt.getSysFlag(); int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; int storehostAddressLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 8 : 20; byte[] newBody = messageExt.getBody(); if (needCompress && (sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(sysFlag)); newBody = compressor.compress(body, 5); } int bodyLength = newBody.length; int storeSize = messageExt.getStoreSize(); ByteBuffer byteBuffer; if (storeSize > 0) { byteBuffer = ByteBuffer.allocate(storeSize); } else { storeSize = 4 // 1 TOTALSIZE + 4 // 2 MAGICCODE + 4 // 3 BODYCRC + 4 // 4 QUEUEID + 4 // 5 FLAG + 8 // 6 QUEUEOFFSET + 8 // 7 PHYSICALOFFSET + 4 // 8 SYSFLAG + 8 // 9 BORNTIMESTAMP + bornhostLength // 10 BORNHOST + 8 // 11 STORETIMESTAMP + storehostAddressLength // 12 STOREHOSTADDRESS + 4 // 13 RECONSUMETIMES + 8 // 14 Prepared Transaction Offset + 4 + bodyLength // 14 BODY + 1 + topicLen // 15 TOPIC + 2 + propertiesLength // 16 propertiesLength + 0; byteBuffer = ByteBuffer.allocate(storeSize); } // 1 TOTALSIZE byteBuffer.putInt(storeSize); // 2 MAGICCODE byteBuffer.putInt(MESSAGE_MAGIC_CODE); // 3 BODYCRC int bodyCRC = messageExt.getBodyCRC(); byteBuffer.putInt(bodyCRC); // 4 QUEUEID int queueId = messageExt.getQueueId(); byteBuffer.putInt(queueId); // 5 FLAG int flag = messageExt.getFlag(); byteBuffer.putInt(flag); // 6 QUEUEOFFSET long queueOffset = messageExt.getQueueOffset(); byteBuffer.putLong(queueOffset); // 7 PHYSICALOFFSET long physicOffset = messageExt.getCommitLogOffset(); byteBuffer.putLong(physicOffset); // 8 SYSFLAG byteBuffer.putInt(sysFlag); // 9 BORNTIMESTAMP long bornTimeStamp = messageExt.getBornTimestamp(); byteBuffer.putLong(bornTimeStamp); // 10 BORNHOST InetSocketAddress bornHost = (InetSocketAddress) messageExt.getBornHost(); byteBuffer.put(bornHost.getAddress().getAddress()); byteBuffer.putInt(bornHost.getPort()); // 11 STORETIMESTAMP long storeTimestamp = messageExt.getStoreTimestamp(); byteBuffer.putLong(storeTimestamp); // 12 STOREHOST InetSocketAddress serverHost = (InetSocketAddress) messageExt.getStoreHost(); byteBuffer.put(serverHost.getAddress().getAddress()); byteBuffer.putInt(serverHost.getPort()); // 13 RECONSUMETIMES int reconsumeTimes = messageExt.getReconsumeTimes(); byteBuffer.putInt(reconsumeTimes); // 14 Prepared Transaction Offset long preparedTransactionOffset = messageExt.getPreparedTransactionOffset(); byteBuffer.putLong(preparedTransactionOffset); // 15 BODY byteBuffer.putInt(bodyLength); byteBuffer.put(newBody); // 16 TOPIC byteBuffer.put(topicLen); byteBuffer.put(topics); // 17 properties byteBuffer.putShort(propertiesLength); byteBuffer.put(propertiesBytes); return byteBuffer.array(); } /** * Encode without store timestamp and store host, skip blank msg. * * @param messageExt msg * @param needCompress need compress or not * @return byte array * @throws IOException when compress failed */ public static byte[] encodeUniquely(MessageExt messageExt, boolean needCompress) throws IOException { byte[] body = messageExt.getBody(); byte[] topics = messageExt.getTopic().getBytes(CHARSET_UTF8); byte topicLen = (byte) topics.length; String properties = messageProperties2String(messageExt.getProperties()); byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8); short propertiesLength = (short) propertiesBytes.length; int sysFlag = messageExt.getSysFlag(); int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; byte[] newBody = messageExt.getBody(); if (needCompress && (sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { newBody = UtilAll.compress(body, 5); } int bodyLength = newBody.length; int storeSize = messageExt.getStoreSize(); ByteBuffer byteBuffer; if (storeSize > 0) { byteBuffer = ByteBuffer.allocate(storeSize - 8); // except size for store timestamp } else { storeSize = 4 + // 1 TOTALSIZE 4 + // 2 MAGICCODE 4 + // 3 BODYCRC 4 + // 4 QUEUEID 4 + // 5 FLAG 8 + // 6 QUEUEOFFSET 8 + // 7 PHYSICALOFFSET 4 + // 8 SYSFLAG 8 + // 9 BORNTIMESTAMP bornhostLength + // 10 BORNHOST 4 + // 11 RECONSUMETIMES 8 + // 12 Prepared Transaction Offset 4 + bodyLength + // 13 BODY +1 + topicLen + // 14 TOPIC 2 + propertiesLength // 15 propertiesLength ; byteBuffer = ByteBuffer.allocate(storeSize); } // 1 TOTALSIZE byteBuffer.putInt(storeSize); // 2 MAGICCODE byteBuffer.putInt(MESSAGE_MAGIC_CODE); // 3 BODYCRC int bodyCRC = messageExt.getBodyCRC(); byteBuffer.putInt(bodyCRC); // 4 QUEUEID int queueId = messageExt.getQueueId(); byteBuffer.putInt(queueId); // 5 FLAG int flag = messageExt.getFlag(); byteBuffer.putInt(flag); // 6 QUEUEOFFSET long queueOffset = messageExt.getQueueOffset(); byteBuffer.putLong(queueOffset); // 7 PHYSICALOFFSET long physicOffset = messageExt.getCommitLogOffset(); byteBuffer.putLong(physicOffset); // 8 SYSFLAG byteBuffer.putInt(sysFlag); // 9 BORNTIMESTAMP long bornTimeStamp = messageExt.getBornTimestamp(); byteBuffer.putLong(bornTimeStamp); // 10 BORNHOST InetSocketAddress bornHost = (InetSocketAddress) messageExt.getBornHost(); byteBuffer.put(bornHost.getAddress().getAddress()); byteBuffer.putInt(bornHost.getPort()); // 11 RECONSUMETIMES int reconsumeTimes = messageExt.getReconsumeTimes(); byteBuffer.putInt(reconsumeTimes); // 12 Prepared Transaction Offset long preparedTransactionOffset = messageExt.getPreparedTransactionOffset(); byteBuffer.putLong(preparedTransactionOffset); // 13 BODY byteBuffer.putInt(bodyLength); byteBuffer.put(newBody); // 14 TOPIC byteBuffer.put(topicLen); byteBuffer.put(topics); // 15 properties byteBuffer.putShort(propertiesLength); byteBuffer.put(propertiesBytes); return byteBuffer.array(); } public static MessageExt decode( ByteBuffer byteBuffer, final boolean readBody, final boolean deCompressBody) { return decode(byteBuffer, readBody, deCompressBody, false); } public static MessageExt decode( java.nio.ByteBuffer byteBuffer, final boolean readBody, final boolean deCompressBody, final boolean isClient) { return decode(byteBuffer, readBody, deCompressBody, isClient, false, false); } public static MessageExt decode( java.nio.ByteBuffer byteBuffer, final boolean readBody, final boolean deCompressBody, final boolean isClient, final boolean isSetPropertiesString) { return decode(byteBuffer, readBody, deCompressBody, isClient, isSetPropertiesString, false); } public static MessageExt decode( java.nio.ByteBuffer byteBuffer, final boolean readBody, final boolean deCompressBody, final boolean isClient, final boolean isSetPropertiesString, final boolean checkCRC) { try { MessageExt msgExt; if (isClient) { msgExt = new MessageClientExt(); } else { msgExt = new MessageExt(); } // 1 TOTALSIZE int storeSize = byteBuffer.getInt(); msgExt.setStoreSize(storeSize); // 2 MAGICCODE int magicCode = byteBuffer.getInt(); MessageVersion version = MessageVersion.valueOfMagicCode(magicCode); // 3 BODYCRC int bodyCRC = byteBuffer.getInt(); msgExt.setBodyCRC(bodyCRC); // 4 QUEUEID int queueId = byteBuffer.getInt(); msgExt.setQueueId(queueId); // 5 FLAG int flag = byteBuffer.getInt(); msgExt.setFlag(flag); // 6 QUEUEOFFSET long queueOffset = byteBuffer.getLong(); msgExt.setQueueOffset(queueOffset); // 7 PHYSICALOFFSET long physicOffset = byteBuffer.getLong(); msgExt.setCommitLogOffset(physicOffset); // 8 SYSFLAG int sysFlag = byteBuffer.getInt(); msgExt.setSysFlag(sysFlag); // 9 BORNTIMESTAMP long bornTimeStamp = byteBuffer.getLong(); msgExt.setBornTimestamp(bornTimeStamp); // 10 BORNHOST int bornhostIPLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 : 16; byte[] bornHost = new byte[bornhostIPLength]; byteBuffer.get(bornHost, 0, bornhostIPLength); int port = byteBuffer.getInt(); msgExt.setBornHost(new InetSocketAddress(InetAddress.getByAddress(bornHost), port)); // 11 STORETIMESTAMP long storeTimestamp = byteBuffer.getLong(); msgExt.setStoreTimestamp(storeTimestamp); // 12 STOREHOST int storehostIPLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 : 16; byte[] storeHost = new byte[storehostIPLength]; byteBuffer.get(storeHost, 0, storehostIPLength); port = byteBuffer.getInt(); msgExt.setStoreHost(new InetSocketAddress(InetAddress.getByAddress(storeHost), port)); // 13 RECONSUMETIMES int reconsumeTimes = byteBuffer.getInt(); msgExt.setReconsumeTimes(reconsumeTimes); // 14 Prepared Transaction Offset long preparedTransactionOffset = byteBuffer.getLong(); msgExt.setPreparedTransactionOffset(preparedTransactionOffset); // 15 BODY int bodyLen = byteBuffer.getInt(); if (bodyLen > 0) { if (readBody) { byte[] body = new byte[bodyLen]; byteBuffer.get(body); if (checkCRC) { //crc body int crc = UtilAll.crc32(body, 0, bodyLen); if (crc != bodyCRC) { throw new Exception("Msg crc is error!"); } } // inflate body if (deCompressBody && (sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(sysFlag)); body = compressor.decompress(body); sysFlag &= ~MessageSysFlag.COMPRESSED_FLAG; } msgExt.setBody(body); msgExt.setSysFlag(sysFlag); } else { byteBuffer.position(byteBuffer.position() + bodyLen); } } // 16 TOPIC int topicLen = version.getTopicLength(byteBuffer); byte[] topic = new byte[topicLen]; byteBuffer.get(topic); msgExt.setTopic(new String(topic, CHARSET_UTF8)); // 17 properties short propertiesLength = byteBuffer.getShort(); if (propertiesLength > 0) { byte[] properties = new byte[propertiesLength]; byteBuffer.get(properties); String propertiesString = new String(properties, CHARSET_UTF8); if (!isSetPropertiesString) { Map map = string2messageProperties(propertiesString); msgExt.setProperties(map); } else { Map map = string2messageProperties(propertiesString); map.put("propertiesString", propertiesString); msgExt.setProperties(map); } } int msgIDLength = storehostIPLength + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); String msgId = createMessageId(byteBufferMsgId, msgExt.getStoreHostBytes(), msgExt.getCommitLogOffset()); msgExt.setMsgId(msgId); if (isClient) { ((MessageClientExt) msgExt).setOffsetMsgId(msgId); } return msgExt; } catch (Exception e) { byteBuffer.position(byteBuffer.limit()); } return null; } public static List decodes(ByteBuffer byteBuffer) { return decodes(byteBuffer, true); } public static List decodesBatch(ByteBuffer byteBuffer, final boolean readBody, final boolean decompressBody, final boolean isClient) { List msgExts = new ArrayList<>(); while (byteBuffer.hasRemaining()) { MessageExt msgExt = decode(byteBuffer, readBody, decompressBody, isClient); if (null != msgExt) { msgExts.add(msgExt); } else { break; } } return msgExts; } public static List decodes(ByteBuffer byteBuffer, final boolean readBody) { List msgExts = new ArrayList<>(); while (byteBuffer.hasRemaining()) { MessageExt msgExt = clientDecode(byteBuffer, readBody); if (null != msgExt) { msgExts.add(msgExt); } else { break; } } return msgExts; } public static String messageProperties2String(Map properties) { if (properties == null) { return ""; } int len = 0; for (final Map.Entry entry : properties.entrySet()) { final String name = entry.getKey(); final String value = entry.getValue(); if (value == null) { continue; } if (name != null) { len += name.length(); } len += value.length(); len += 2; // separator } StringBuilder sb = new StringBuilder(len); for (final Map.Entry entry : properties.entrySet()) { final String name = entry.getKey(); final String value = entry.getValue(); if (value == null) { continue; } sb.append(name); sb.append(NAME_VALUE_SEPARATOR); sb.append(value); sb.append(PROPERTY_SEPARATOR); } return sb.toString(); } public static Map string2messageProperties(final String properties) { Map map = new HashMap<>(128); if (properties != null) { int len = properties.length(); int index = 0; while (index < len) { int newIndex = properties.indexOf(PROPERTY_SEPARATOR, index); if (newIndex < 0) { newIndex = len; } if (newIndex - index >= 3) { int kvSepIndex = properties.indexOf(NAME_VALUE_SEPARATOR, index); if (kvSepIndex > index && kvSepIndex < newIndex - 1) { String k = properties.substring(index, kvSepIndex); String v = properties.substring(kvSepIndex + 1, newIndex); map.put(k, v); } } index = newIndex + 1; } } return map; } public static byte[] encodeMessage(Message message) { //only need flag, body, properties byte[] body = message.getBody(); int bodyLen = body.length; String properties = messageProperties2String(message.getProperties()); byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8); //note properties length must not more than Short.MAX short propertiesLength = (short) propertiesBytes.length; int storeSize = 4 // 1 TOTALSIZE + 4 // 2 MAGICCOD + 4 // 3 BODYCRC + 4 // 4 FLAG + 4 + bodyLen // 4 BODY + 2 + propertiesLength; ByteBuffer byteBuffer = ByteBuffer.allocate(storeSize); // 1 TOTALSIZE byteBuffer.putInt(storeSize); // 2 MAGICCODE byteBuffer.putInt(0); // 3 BODYCRC byteBuffer.putInt(0); // 4 FLAG int flag = message.getFlag(); byteBuffer.putInt(flag); // 5 BODY byteBuffer.putInt(bodyLen); byteBuffer.put(body); // 6 properties byteBuffer.putShort(propertiesLength); byteBuffer.put(propertiesBytes); return byteBuffer.array(); } public static Message decodeMessage(ByteBuffer byteBuffer) throws Exception { Message message = new Message(); // 1 TOTALSIZE byteBuffer.getInt(); // 2 MAGICCODE byteBuffer.getInt(); // 3 BODYCRC byteBuffer.getInt(); // 4 FLAG int flag = byteBuffer.getInt(); message.setFlag(flag); // 5 BODY int bodyLen = byteBuffer.getInt(); byte[] body = new byte[bodyLen]; byteBuffer.get(body); message.setBody(body); // 6 properties short propertiesLen = byteBuffer.getShort(); byte[] propertiesBytes = new byte[propertiesLen]; byteBuffer.get(propertiesBytes); message.setProperties(string2messageProperties(new String(propertiesBytes, CHARSET_UTF8))); return message; } public static byte[] encodeMessages(List messages) { //TO DO refactor, accumulate in one buffer, avoid copies List encodedMessages = new ArrayList<>(messages.size()); int allSize = 0; for (Message message : messages) { byte[] tmp = encodeMessage(message); encodedMessages.add(tmp); allSize += tmp.length; } byte[] allBytes = new byte[allSize]; int pos = 0; for (byte[] bytes : encodedMessages) { System.arraycopy(bytes, 0, allBytes, pos, bytes.length); pos += bytes.length; } return allBytes; } public static List decodeMessages(ByteBuffer byteBuffer) throws Exception { //TO DO add a callback for processing, avoid creating lists List msgs = new ArrayList<>(); while (byteBuffer.hasRemaining()) { Message msg = decodeMessage(byteBuffer); msgs.add(msg); } return msgs; } public static void decodeMessage(MessageExt messageExt, List list) throws Exception { List messages = MessageDecoder.decodeMessages(ByteBuffer.wrap(messageExt.getBody())); for (int i = 0; i < messages.size(); i++) { Message message = messages.get(i); MessageClientExt messageClientExt = new MessageClientExt(); messageClientExt.setTopic(messageExt.getTopic()); messageClientExt.setQueueOffset(messageExt.getQueueOffset() + i); messageClientExt.setQueueId(messageExt.getQueueId()); messageClientExt.setFlag(message.getFlag()); MessageAccessor.setProperties(messageClientExt, message.getProperties()); messageClientExt.setBody(message.getBody()); messageClientExt.setStoreHost(messageExt.getStoreHost()); messageClientExt.setBornHost(messageExt.getBornHost()); messageClientExt.setBornTimestamp(messageExt.getBornTimestamp()); messageClientExt.setStoreTimestamp(messageExt.getStoreTimestamp()); messageClientExt.setSysFlag(messageExt.getSysFlag()); messageClientExt.setCommitLogOffset(messageExt.getCommitLogOffset()); messageClientExt.setWaitStoreMsgOK(messageExt.isWaitStoreMsgOK()); list.add(messageClientExt); } } public static int countInnerMsgNum(ByteBuffer buffer) { int count = 0; while (buffer.hasRemaining()) { count++; int currPos = buffer.position(); int size = buffer.getInt(); buffer.position(currPos + size); } return count; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.sysflag.MessageSysFlag; public class MessageExt extends Message { private static final long serialVersionUID = 5720810158625748049L; private String brokerName; private int queueId; private int storeSize; private long queueOffset; private int sysFlag; private long bornTimestamp; private SocketAddress bornHost; private long storeTimestamp; private SocketAddress storeHost; private String msgId; private long commitLogOffset; private int bodyCRC; private int reconsumeTimes; private long preparedTransactionOffset; public MessageExt() { } public MessageExt(int queueId, long bornTimestamp, SocketAddress bornHost, long storeTimestamp, SocketAddress storeHost, String msgId) { this.queueId = queueId; this.bornTimestamp = bornTimestamp; this.bornHost = bornHost; this.storeTimestamp = storeTimestamp; this.storeHost = storeHost; this.msgId = msgId; } public static TopicFilterType parseTopicFilterType(final int sysFlag) { if ((sysFlag & MessageSysFlag.MULTI_TAGS_FLAG) == MessageSysFlag.MULTI_TAGS_FLAG) { return TopicFilterType.MULTI_TAG; } return TopicFilterType.SINGLE_TAG; } public static ByteBuffer socketAddress2ByteBuffer(final SocketAddress socketAddress, final ByteBuffer byteBuffer) { InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; InetAddress address = inetSocketAddress.getAddress(); if (address instanceof Inet4Address) { byteBuffer.put(inetSocketAddress.getAddress().getAddress(), 0, 4); } else { byteBuffer.put(inetSocketAddress.getAddress().getAddress(), 0, 16); } byteBuffer.putInt(inetSocketAddress.getPort()); byteBuffer.flip(); return byteBuffer; } public static ByteBuffer socketAddress2ByteBuffer(SocketAddress socketAddress) { InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; InetAddress address = inetSocketAddress.getAddress(); ByteBuffer byteBuffer; if (address instanceof Inet4Address) { byteBuffer = ByteBuffer.allocate(4 + 4); } else { byteBuffer = ByteBuffer.allocate(16 + 4); } return socketAddress2ByteBuffer(socketAddress, byteBuffer); } public ByteBuffer getBornHostBytes() { return socketAddress2ByteBuffer(this.bornHost); } public ByteBuffer getBornHostBytes(ByteBuffer byteBuffer) { return socketAddress2ByteBuffer(this.bornHost, byteBuffer); } public ByteBuffer getStoreHostBytes() { return socketAddress2ByteBuffer(this.storeHost); } public ByteBuffer getStoreHostBytes(ByteBuffer byteBuffer) { return socketAddress2ByteBuffer(this.storeHost, byteBuffer); } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } public long getBornTimestamp() { return bornTimestamp; } public void setBornTimestamp(long bornTimestamp) { this.bornTimestamp = bornTimestamp; } public SocketAddress getBornHost() { return bornHost; } public void setBornHost(SocketAddress bornHost) { this.bornHost = bornHost; } public String getBornHostString() { if (null != this.bornHost) { InetAddress inetAddress = ((InetSocketAddress) this.bornHost).getAddress(); return null != inetAddress ? inetAddress.getHostAddress() : null; } return null; } public String getBornHostNameString() { if (null != this.bornHost) { if (bornHost instanceof InetSocketAddress) { // without reverse dns lookup return ((InetSocketAddress) bornHost).getHostString(); } InetAddress inetAddress = ((InetSocketAddress) this.bornHost).getAddress(); return null != inetAddress ? inetAddress.getHostName() : null; } return null; } public long getStoreTimestamp() { return storeTimestamp; } public void setStoreTimestamp(long storeTimestamp) { this.storeTimestamp = storeTimestamp; } public SocketAddress getStoreHost() { return storeHost; } public void setStoreHost(SocketAddress storeHost) { this.storeHost = storeHost; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public int getSysFlag() { return sysFlag; } public void setSysFlag(int sysFlag) { this.sysFlag = sysFlag; } public void setStoreHostAddressV6Flag() { this.sysFlag = this.sysFlag | MessageSysFlag.STOREHOSTADDRESS_V6_FLAG; } public void setBornHostV6Flag() { this.sysFlag = this.sysFlag | MessageSysFlag.BORNHOST_V6_FLAG; } public int getBodyCRC() { return bodyCRC; } public void setBodyCRC(int bodyCRC) { this.bodyCRC = bodyCRC; } public long getQueueOffset() { return queueOffset; } public void setQueueOffset(long queueOffset) { this.queueOffset = queueOffset; } public long getCommitLogOffset() { return commitLogOffset; } public void setCommitLogOffset(long physicOffset) { this.commitLogOffset = physicOffset; } public int getStoreSize() { return storeSize; } public void setStoreSize(int storeSize) { this.storeSize = storeSize; } public int getReconsumeTimes() { return reconsumeTimes; } public void setReconsumeTimes(int reconsumeTimes) { this.reconsumeTimes = reconsumeTimes; } public long getPreparedTransactionOffset() { return preparedTransactionOffset; } public void setPreparedTransactionOffset(long preparedTransactionOffset) { this.preparedTransactionOffset = preparedTransactionOffset; } /** * * achieves topicSysFlag value from transient properties * * @return */ public Integer getTopicSysFlag() { String topicSysFlagString = getProperty(MessageConst.PROPERTY_TRANSIENT_TOPIC_CONFIG); if (topicSysFlagString != null && topicSysFlagString.length() > 0) { return Integer.valueOf(topicSysFlagString); } return null; } /** * set topicSysFlag to transient properties, or clear it * * @param topicSysFlag */ public void setTopicSysFlag(Integer topicSysFlag) { if (topicSysFlag == null) { clearProperty(MessageConst.PROPERTY_TRANSIENT_TOPIC_CONFIG); } else { putProperty(MessageConst.PROPERTY_TRANSIENT_TOPIC_CONFIG, String.valueOf(topicSysFlag)); } } /** * * achieves groupSysFlag value from transient properties * * @return */ public Integer getGroupSysFlag() { String groupSysFlagString = getProperty(MessageConst.PROPERTY_TRANSIENT_GROUP_CONFIG); if (groupSysFlagString != null && groupSysFlagString.length() > 0) { return Integer.valueOf(groupSysFlagString); } return null; } /** * * set groupSysFlag to transient properties, or clear it * * @param groupSysFlag */ public void setGroupSysFlag(Integer groupSysFlag) { if (groupSysFlag == null) { clearProperty(MessageConst.PROPERTY_TRANSIENT_GROUP_CONFIG); } else { putProperty(MessageConst.PROPERTY_TRANSIENT_GROUP_CONFIG, String.valueOf(groupSysFlag)); } } @Override public String toString() { return "MessageExt [brokerName=" + brokerName + ", queueId=" + queueId + ", storeSize=" + storeSize + ", queueOffset=" + queueOffset + ", sysFlag=" + sysFlag + ", bornTimestamp=" + bornTimestamp + ", bornHost=" + bornHost + ", storeTimestamp=" + storeTimestamp + ", storeHost=" + storeHost + ", msgId=" + msgId + ", commitLogOffset=" + commitLogOffset + ", bodyCRC=" + bodyCRC + ", reconsumeTimes=" + reconsumeTimes + ", preparedTransactionOffset=" + preparedTransactionOffset + ", toString()=" + super.toString() + "]"; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageExtBatch.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.nio.ByteBuffer; public class MessageExtBatch extends MessageExtBrokerInner { private static final long serialVersionUID = -2353110995348498537L; /** * Inner batch means the batch does not need to be unwrapped */ private boolean isInnerBatch = false; public ByteBuffer wrap() { assert getBody() != null; return ByteBuffer.wrap(getBody(), 0, getBody().length); } public boolean isInnerBatch() { return isInnerBatch; } public void setInnerBatch(boolean innerBatch) { isInnerBatch = innerBatch; } private ByteBuffer encodedBuff; public ByteBuffer getEncodedBuff() { return encodedBuff; } public void setEncodedBuff(ByteBuffer encodedBuff) { this.encodedBuff = encodedBuff; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import com.google.common.base.Strings; import java.nio.ByteBuffer; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.utils.MessageUtils; public class MessageExtBrokerInner extends MessageExt { private static final long serialVersionUID = 7256001576878700634L; private String propertiesString; private long tagsCode; private ByteBuffer encodedBuff; private volatile boolean encodeCompleted; private MessageVersion version = MessageVersion.MESSAGE_VERSION_V1; public ByteBuffer getEncodedBuff() { return encodedBuff; } public void setEncodedBuff(ByteBuffer encodedBuff) { this.encodedBuff = encodedBuff; } public static long tagsString2tagsCode(final TopicFilterType filter, final String tags) { if (Strings.isNullOrEmpty(tags)) { return 0; } return tags.hashCode(); } public static long tagsString2tagsCode(final String tags) { return tagsString2tagsCode(null, tags); } public String getPropertiesString() { return propertiesString; } public void setPropertiesString(String propertiesString) { this.propertiesString = propertiesString; } public void deleteProperty(String name) { super.clearProperty(name); if (propertiesString != null) { this.setPropertiesString(MessageUtils.deleteProperty(propertiesString, name)); } } public long getTagsCode() { return tagsCode; } public void setTagsCode(long tagsCode) { this.tagsCode = tagsCode; } public MessageVersion getVersion() { return version; } public void setVersion(MessageVersion version) { this.version = version; } public void removeWaitStorePropertyString() { if (this.getProperties().containsKey(MessageConst.PROPERTY_WAIT_STORE_MSG_OK)) { // There is no need to store "WAIT=true", remove it from propertiesString to save 9 bytes for each message. // It works for most case. In some cases msgInner.setPropertiesString invoked later and replace it. String waitStoreMsgOKValue = this.getProperties().remove(MessageConst.PROPERTY_WAIT_STORE_MSG_OK); this.setPropertiesString(MessageDecoder.messageProperties2String(this.getProperties())); // Reput to properties, since msgInner.isWaitStoreMsgOK() will be invoked later this.getProperties().put(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, waitStoreMsgOKValue); } else { this.setPropertiesString(MessageDecoder.messageProperties2String(this.getProperties())); } } public boolean isEncodeCompleted() { return encodeCompleted; } public void setEncodeCompleted(boolean encodeCompleted) { this.encodeCompleted = encodeCompleted; } public boolean needDispatchLMQ() { return StringUtils.isNoneBlank(getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)) && MixAll.topicAllowsLMQ(getTopic()); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageId.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.net.SocketAddress; public class MessageId { private SocketAddress address; private long offset; public MessageId(SocketAddress address, long offset) { this.address = address; this.offset = offset; } public SocketAddress getAddress() { return address; } public void setAddress(SocketAddress address) { this.address = address; } public long getOffset() { return offset; } public void setOffset(long offset) { this.offset = offset; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.io.Serializable; public class MessageQueue implements Comparable, Serializable { private static final long serialVersionUID = 6191200464116433425L; private String topic; private String brokerName; private int queueId; public MessageQueue() { } public MessageQueue(MessageQueue other) { this.topic = other.topic; this.brokerName = other.brokerName; this.queueId = other.queueId; } public MessageQueue(String topic, String brokerName, int queueId) { this.topic = topic; this.brokerName = brokerName; this.queueId = queueId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((brokerName == null) ? 0 : brokerName.hashCode()); result = prime * result + queueId; result = prime * result + ((topic == null) ? 0 : topic.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MessageQueue other = (MessageQueue) obj; if (brokerName == null) { if (other.brokerName != null) return false; } else if (!brokerName.equals(other.brokerName)) return false; if (queueId != other.queueId) return false; if (topic == null) { if (other.topic != null) return false; } else if (!topic.equals(other.topic)) return false; return true; } @Override public String toString() { return "MessageQueue [topic=" + topic + ", brokerName=" + brokerName + ", queueId=" + queueId + "]"; } @Override public int compareTo(MessageQueue o) { { int result = this.topic.compareTo(o.topic); if (result != 0) { return result; } } { int result = this.brokerName.compareTo(o.brokerName); if (result != 0) { return result; } } return this.queueId - o.queueId; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.io.Serializable; import java.util.Map; public class MessageQueueAssignment implements Serializable { private static final long serialVersionUID = 8092600270527861645L; private MessageQueue messageQueue; private MessageRequestMode mode = MessageRequestMode.PULL; private Map attachments; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((messageQueue == null) ? 0 : messageQueue.hashCode()); result = prime * result + ((mode == null) ? 0 : mode.hashCode()); result = prime * result + ((attachments == null) ? 0 : attachments.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MessageQueueAssignment other = (MessageQueueAssignment) obj; return messageQueue.equals(other.messageQueue); } @Override public String toString() { return "MessageQueueAssignment [MessageQueue=" + messageQueue + ", Mode=" + mode + "]"; } public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public MessageRequestMode getMode() { return mode; } public void setMode(MessageRequestMode mode) { this.mode = mode; } public Map getAttachments() { return attachments; } public void setAttachments(Map attachments) { this.attachments = attachments; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageQueueForC.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.io.Serializable; public class MessageQueueForC implements Comparable, Serializable { private static final long serialVersionUID = 5320967846569962104L; private String topic; private String brokerName; private int queueId; private long offset; public MessageQueueForC(String topic, String brokerName, int queueId, long offset) { this.topic = topic; this.brokerName = brokerName; this.queueId = queueId; this.offset = offset; } @Override public int compareTo(MessageQueueForC o) { int result = this.topic.compareTo(o.topic); if (result != 0) { return result; } result = this.brokerName.compareTo(o.brokerName); if (result != 0) { return result; } result = this.queueId - o.queueId; if (result != 0) { return result; } if ((this.offset - o.offset) > 0) { return 1; } else if ((this.offset - o.offset) == 0) { return 0; } else { return -1; } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((brokerName == null) ? 0 : brokerName.hashCode()); result = prime * result + queueId; result = prime * result + ((topic == null) ? 0 : topic.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MessageQueueForC other = (MessageQueueForC) obj; if (brokerName == null) { if (other.brokerName != null) return false; } else if (!brokerName.equals(other.brokerName)) return false; if (queueId != other.queueId) return false; if (topic == null) { if (other.topic != null) return false; } else if (!topic.equals(other.topic)) return false; if (offset != other.offset) { return false; } return true; } @Override public String toString() { return "MessageQueueForC [topic=" + topic + ", brokerName=" + brokerName + ", queueId=" + queueId + ", offset=" + offset + "]"; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } public long getOffset() { return offset; } public void setOffset(long offset) { this.offset = offset; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; /** * Message Request Mode */ public enum MessageRequestMode { /** * pull */ PULL("PULL"), /** * pop, consumer working in pop mode could share MessageQueue */ POP("POP"); private String name; MessageRequestMode(String name) { this.name = name; } public String getName() { return name; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; public enum MessageType { Normal_Msg("Normal"), Trans_Msg_Half("Trans"), Trans_msg_Commit("TransCommit"), Delay_Msg("Delay"), Order_Msg("Order"); private final String shortName; MessageType(String shortName) { this.shortName = shortName; } public String getShortName() { return shortName; } public static MessageType getByShortName(String shortName) { for (MessageType msgType : MessageType.values()) { if (msgType.getShortName().equals(shortName)) { return msgType; } } return Normal_Msg; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/message/MessageVersion.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import java.nio.ByteBuffer; public enum MessageVersion { MESSAGE_VERSION_V1(MessageDecoder.MESSAGE_MAGIC_CODE) { @Override public int getTopicLengthSize() { return 1; } @Override public int getTopicLength(ByteBuffer buffer) { return buffer.get(); } @Override public int getTopicLength(ByteBuffer buffer, int index) { return buffer.get(index); } @Override public void putTopicLength(ByteBuffer buffer, int topicLength) { buffer.put((byte) topicLength); } }, MESSAGE_VERSION_V2(MessageDecoder.MESSAGE_MAGIC_CODE_V2) { @Override public int getTopicLengthSize() { return 2; } @Override public int getTopicLength(ByteBuffer buffer) { return buffer.getShort(); } @Override public int getTopicLength(ByteBuffer buffer, int index) { return buffer.getShort(index); } @Override public void putTopicLength(ByteBuffer buffer, int topicLength) { buffer.putShort((short) topicLength); } }; private final int magicCode; MessageVersion(int magicCode) { this.magicCode = magicCode; } public static MessageVersion valueOfMagicCode(int magicCode) { for (MessageVersion version : MessageVersion.values()) { if (version.getMagicCode() == magicCode) { return version; } } throw new IllegalArgumentException("Invalid magicCode " + magicCode); } public int getMagicCode() { return magicCode; } public abstract int getTopicLengthSize(); public abstract int getTopicLength(java.nio.ByteBuffer buffer); public abstract int getTopicLength(java.nio.ByteBuffer buffer, int index); public abstract void putTopicLength(java.nio.ByteBuffer buffer, int topicLength); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/metrics/MetricsExporterType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.metrics; public enum MetricsExporterType { DISABLE(0), OTLP_GRPC(1), PROM(2), LOG(3); private final int value; MetricsExporterType(int value) { this.value = value; } public int getValue() { return value; } public static MetricsExporterType valueOf(int value) { switch (value) { case 1: return OTLP_GRPC; case 2: return PROM; case 3: return LOG; default: return DISABLE; } } public boolean isEnable() { return this.value > 0; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/metrics/NopLongCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.metrics; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.context.Context; public class NopLongCounter implements LongCounter { @Override public void add(long l) { } @Override public void add(long l, Attributes attributes) { } @Override public void add(long l, Attributes attributes, Context context) { } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/metrics/NopLongHistogram.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.metrics; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.context.Context; public class NopLongHistogram implements LongHistogram { @Override public void record(long l) { } @Override public void record(long l, Attributes attributes) { } @Override public void record(long l, Attributes attributes, Context context) { } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/metrics/NopLongUpDownCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.metrics; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.context.Context; public class NopLongUpDownCounter implements LongUpDownCounter { @Override public void add(long l) { } @Override public void add(long l, Attributes attributes) { } @Override public void add(long l, Attributes attributes, Context context) { } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/metrics/NopObservableDoubleGauge.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.metrics; import io.opentelemetry.api.metrics.ObservableDoubleGauge; public class NopObservableDoubleGauge implements ObservableDoubleGauge { } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/metrics/NopObservableLongGauge.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.metrics; import io.opentelemetry.api.metrics.ObservableLongGauge; public class NopObservableLongGauge implements ObservableLongGauge { } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/namesrv/DefaultTopAddressing.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.namesrv; import com.google.common.base.Strings; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; import java.util.Map; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.utils.HttpTinyClient; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class DefaultTopAddressing implements TopAddressing { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private String nsAddr; private String wsAddr; private String unitName; private Map para; private List topAddressingList; public DefaultTopAddressing(final String wsAddr) { this(wsAddr, null); } public DefaultTopAddressing(final String wsAddr, final String unitName) { this.wsAddr = wsAddr; this.unitName = unitName; this.topAddressingList = loadCustomTopAddressing(); } public DefaultTopAddressing(final String unitName, final Map para, final String wsAddr) { this.wsAddr = wsAddr; this.unitName = unitName; this.para = para; this.topAddressingList = loadCustomTopAddressing(); } private static String clearNewLine(final String str) { String newString = str.trim(); int index = newString.indexOf("\r"); if (index != -1) { return newString.substring(0, index); } index = newString.indexOf("\n"); if (index != -1) { return newString.substring(0, index); } return newString; } private List loadCustomTopAddressing() { ServiceLoader serviceLoader = ServiceLoader.load(TopAddressing.class); Iterator iterator = serviceLoader.iterator(); List topAddressingList = new ArrayList<>(); if (iterator.hasNext()) { topAddressingList.add(iterator.next()); } return topAddressingList; } @Override public final String fetchNSAddr() { if (!topAddressingList.isEmpty()) { for (TopAddressing topAddressing : topAddressingList) { String nsAddress = topAddressing.fetchNSAddr(); if (!Strings.isNullOrEmpty(nsAddress)) { return nsAddress; } } } // Return result of default implementation return fetchNSAddr(true, 3000); } @Override public void registerChangeCallBack(NameServerUpdateCallback changeCallBack) { if (!topAddressingList.isEmpty()) { for (TopAddressing topAddressing : topAddressingList) { topAddressing.registerChangeCallBack(changeCallBack); } } } public final String fetchNSAddr(boolean verbose, long timeoutMills) { StringBuilder url = new StringBuilder(this.wsAddr); try { if (null != para && para.size() > 0) { if (!UtilAll.isBlank(this.unitName)) { url.append("-").append(this.unitName).append("?nofix=1&"); } else { url.append("?"); } for (Map.Entry entry : this.para.entrySet()) { url.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); } url = new StringBuilder(url.substring(0, url.length() - 1)); } else { if (!UtilAll.isBlank(this.unitName)) { url.append("-").append(this.unitName).append("?nofix=1"); } } HttpTinyClient.HttpResult result = HttpTinyClient.httpGet(url.toString(), null, null, "UTF-8", timeoutMills); if (200 == result.code) { String responseStr = result.content; if (responseStr != null) { return clearNewLine(responseStr); } else { LOGGER.error("fetch nameserver address is null"); } } else { LOGGER.error("fetch nameserver address failed. statusCode=" + result.code); } } catch (IOException e) { if (verbose) { LOGGER.error("fetch name server address exception", e); } } if (verbose) { String errorMsg = "connect to " + url + " failed, maybe the domain name " + MixAll.getWSAddr() + " not bind in /etc/hosts"; errorMsg += FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL); LOGGER.warn(errorMsg); } return null; } public String getNsAddr() { return nsAddr; } public void setNsAddr(String nsAddr) { this.nsAddr = nsAddr; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/namesrv/NameServerUpdateCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.namesrv; public interface NameServerUpdateCallback { String onNameServerAddressChange(String namesrvAddress); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/namesrv/NamesrvConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: NamesrvConfig.java 1839 2013-05-16 02:12:02Z vintagewang@apache.org $ */ package org.apache.rocketmq.common.namesrv; import java.io.File; import org.apache.rocketmq.common.MixAll; public class NamesrvConfig { private String rocketmqHome = MixAll.ROCKETMQ_HOME_DIR; private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json"; private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties"; private String productEnvName = "center"; private boolean clusterTest = false; private boolean orderMessageEnable = false; private boolean returnOrderTopicConfigToBroker = true; /** * Indicates the nums of thread to handle client requests, like GET_ROUTEINTO_BY_TOPIC. */ private int clientRequestThreadPoolNums = 8; /** * Indicates the nums of thread to handle broker or operation requests, like REGISTER_BROKER. */ private int defaultThreadPoolNums = 16; /** * Indicates the capacity of queue to hold client requests. */ private int clientRequestThreadPoolQueueCapacity = 50000; /** * Indicates the capacity of queue to hold broker or operation requests. */ private int defaultThreadPoolQueueCapacity = 10000; /** * Interval of periodic scanning for non-active broker; */ private long scanNotActiveBrokerInterval = 5 * 1000; private int unRegisterBrokerQueueCapacity = 3000; /** * Support acting master or not. * * The slave can be an acting master when master node is down to support following operations: * 1. support lock/unlock message queue operation. * 2. support searchOffset, query maxOffset/minOffset operation. * 3. support query earliest msg store time. */ private boolean supportActingMaster = false; private volatile boolean enableAllTopicList = true; private volatile boolean enableTopicList = true; private volatile boolean notifyMinBrokerIdChanged = false; /** * Is startup the controller in this name-srv */ private boolean enableControllerInNamesrv = false; private volatile boolean needWaitForService = false; private int waitSecondsForService = 45; /** * If enable this flag, the topics that don't exist in broker registration payload will be deleted from name server. * * WARNING: * 1. Enable this flag and "enableSingleTopicRegister" of broker config meanwhile to avoid losing topic route info unexpectedly. * 2. This flag does not support static topic currently. */ private boolean deleteTopicWithBrokerRegistration = false; /** * Config in this black list will be not allowed to update by command. * Try to update this config black list by restart process. * Try to update configures in black list by restart process. */ private String configBlackList = "configBlackList;configStorePath;kvConfigPath"; public String getConfigBlackList() { return configBlackList; } public void setConfigBlackList(String configBlackList) { this.configBlackList = configBlackList; } public boolean isOrderMessageEnable() { return orderMessageEnable; } public void setOrderMessageEnable(boolean orderMessageEnable) { this.orderMessageEnable = orderMessageEnable; } public String getRocketmqHome() { return rocketmqHome; } public void setRocketmqHome(String rocketmqHome) { this.rocketmqHome = rocketmqHome; } public String getKvConfigPath() { return kvConfigPath; } public void setKvConfigPath(String kvConfigPath) { this.kvConfigPath = kvConfigPath; } public String getProductEnvName() { return productEnvName; } public void setProductEnvName(String productEnvName) { this.productEnvName = productEnvName; } public boolean isClusterTest() { return clusterTest; } public void setClusterTest(boolean clusterTest) { this.clusterTest = clusterTest; } public String getConfigStorePath() { return configStorePath; } public void setConfigStorePath(final String configStorePath) { this.configStorePath = configStorePath; } public boolean isReturnOrderTopicConfigToBroker() { return returnOrderTopicConfigToBroker; } public void setReturnOrderTopicConfigToBroker(boolean returnOrderTopicConfigToBroker) { this.returnOrderTopicConfigToBroker = returnOrderTopicConfigToBroker; } public int getClientRequestThreadPoolNums() { return clientRequestThreadPoolNums; } public void setClientRequestThreadPoolNums(final int clientRequestThreadPoolNums) { this.clientRequestThreadPoolNums = clientRequestThreadPoolNums; } public int getDefaultThreadPoolNums() { return defaultThreadPoolNums; } public void setDefaultThreadPoolNums(final int defaultThreadPoolNums) { this.defaultThreadPoolNums = defaultThreadPoolNums; } public int getClientRequestThreadPoolQueueCapacity() { return clientRequestThreadPoolQueueCapacity; } public void setClientRequestThreadPoolQueueCapacity(final int clientRequestThreadPoolQueueCapacity) { this.clientRequestThreadPoolQueueCapacity = clientRequestThreadPoolQueueCapacity; } public int getDefaultThreadPoolQueueCapacity() { return defaultThreadPoolQueueCapacity; } public void setDefaultThreadPoolQueueCapacity(final int defaultThreadPoolQueueCapacity) { this.defaultThreadPoolQueueCapacity = defaultThreadPoolQueueCapacity; } public long getScanNotActiveBrokerInterval() { return scanNotActiveBrokerInterval; } public void setScanNotActiveBrokerInterval(long scanNotActiveBrokerInterval) { this.scanNotActiveBrokerInterval = scanNotActiveBrokerInterval; } public int getUnRegisterBrokerQueueCapacity() { return unRegisterBrokerQueueCapacity; } public void setUnRegisterBrokerQueueCapacity(final int unRegisterBrokerQueueCapacity) { this.unRegisterBrokerQueueCapacity = unRegisterBrokerQueueCapacity; } public boolean isSupportActingMaster() { return supportActingMaster; } public void setSupportActingMaster(final boolean supportActingMaster) { this.supportActingMaster = supportActingMaster; } public boolean isEnableAllTopicList() { return enableAllTopicList; } public void setEnableAllTopicList(boolean enableAllTopicList) { this.enableAllTopicList = enableAllTopicList; } public boolean isEnableTopicList() { return enableTopicList; } public void setEnableTopicList(boolean enableTopicList) { this.enableTopicList = enableTopicList; } public boolean isNotifyMinBrokerIdChanged() { return notifyMinBrokerIdChanged; } public void setNotifyMinBrokerIdChanged(boolean notifyMinBrokerIdChanged) { this.notifyMinBrokerIdChanged = notifyMinBrokerIdChanged; } public boolean isEnableControllerInNamesrv() { return enableControllerInNamesrv; } public void setEnableControllerInNamesrv(boolean enableControllerInNamesrv) { this.enableControllerInNamesrv = enableControllerInNamesrv; } public boolean isNeedWaitForService() { return needWaitForService; } public void setNeedWaitForService(boolean needWaitForService) { this.needWaitForService = needWaitForService; } public int getWaitSecondsForService() { return waitSecondsForService; } public void setWaitSecondsForService(int waitSecondsForService) { this.waitSecondsForService = waitSecondsForService; } public boolean isDeleteTopicWithBrokerRegistration() { return deleteTopicWithBrokerRegistration; } public void setDeleteTopicWithBrokerRegistration(boolean deleteTopicWithBrokerRegistration) { this.deleteTopicWithBrokerRegistration = deleteTopicWithBrokerRegistration; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/namesrv/NamesrvUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.namesrv; public class NamesrvUtil { public static final String NAMESPACE_ORDER_TOPIC_CONFIG = "ORDER_TOPIC_CONFIG"; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/namesrv/TopAddressing.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.namesrv; public interface TopAddressing { String fetchNSAddr(); void registerChangeCallBack(NameServerUpdateCallback changeCallBack); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/producer/RecallMessageHandle.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.producer; import org.apache.commons.codec.DecoderException; import org.apache.commons.lang3.StringUtils; import java.util.Base64; import static java.nio.charset.StandardCharsets.UTF_8; /** * handle to recall a message, only support delay message for now * v1 pattern like this: * version topic brokerName timestamp messageId * use Base64 to encode it */ public class RecallMessageHandle { private static final String SEPARATOR = " "; private static final String VERSION_1 = "v1"; public static class HandleV1 extends RecallMessageHandle { private String version; private String topic; private String brokerName; private String timestampStr; private String messageId; // id of unique key public HandleV1(String topic, String brokerName, String timestamp, String messageId) { this.version = VERSION_1; this.topic = topic; this.brokerName = brokerName; this.timestampStr = timestamp; this.messageId = messageId; } // no param check public static String buildHandle(String topic, String brokerName, String timestampStr, String messageId) { String rawString = String.join(SEPARATOR, VERSION_1, topic, brokerName, timestampStr, messageId); return Base64.getUrlEncoder().encodeToString(rawString.getBytes(UTF_8)); } public String getTopic() { return topic; } public String getBrokerName() { return brokerName; } public String getTimestampStr() { return timestampStr; } public String getMessageId() { return messageId; } public String getVersion() { return version; } } public static RecallMessageHandle decodeHandle(String handle) throws DecoderException { if (StringUtils.isEmpty(handle)) { throw new DecoderException("recall handle is invalid"); } String rawString; try { rawString = new String(Base64.getUrlDecoder().decode(handle.getBytes(UTF_8)), UTF_8); } catch (IllegalArgumentException e) { throw new DecoderException("recall handle is invalid"); } String[] items = rawString.split(SEPARATOR); if (!VERSION_1.equals(items[0]) || items.length < 5) { throw new DecoderException("recall handle is invalid"); } return new HandleV1(items[1], items[2], items[3], items[4]); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/queue/ConcurrentTreeMap.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.queue; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.locks.ReentrantLock; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * thread safe */ public class ConcurrentTreeMap { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final ReentrantLock lock; private TreeMap tree; private RoundQueue roundQueue; public ConcurrentTreeMap(int capacity, Comparator comparator) { tree = new TreeMap<>(comparator); roundQueue = new RoundQueue<>(capacity); lock = new ReentrantLock(true); } public Map.Entry pollFirstEntry() { lock.lock(); try { return tree.pollFirstEntry(); } finally { lock.unlock(); } } public V putIfAbsentAndRetExsit(K key, V value) { lock.lock(); try { if (roundQueue.put(key)) { V exist = tree.get(key); if (null == exist) { tree.put(key, value); exist = value; } log.warn("putIfAbsentAndRetExsit success. " + key); return exist; } else { V exist = tree.get(key); return exist; } } finally { lock.unlock(); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/queue/RoundQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.queue; import java.util.LinkedList; import java.util.Queue; /** * not thread safe */ public class RoundQueue { private Queue queue; private int capacity; public RoundQueue(int capacity) { this.capacity = capacity; queue = new LinkedList<>(); } public boolean put(E e) { boolean ok = false; if (!queue.contains(e)) { if (queue.size() >= capacity) { queue.poll(); } queue.add(e); ok = true; } return ok; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/resource/ResourcePattern.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.resource; import com.alibaba.fastjson2.annotation.JSONField; public enum ResourcePattern { ANY((byte) 1, "ANY"), LITERAL((byte) 2, "LITERAL"), PREFIXED((byte) 3, "PREFIXED"); @JSONField(value = true) private final byte code; private final String name; ResourcePattern(byte code, String name) { this.code = code; this.name = name; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/resource/ResourceType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.resource; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.commons.lang3.StringUtils; public enum ResourceType { UNKNOWN((byte) 0, "Unknown"), ANY((byte) 1, "Any"), CLUSTER((byte) 2, "Cluster"), NAMESPACE((byte) 3, "Namespace"), TOPIC((byte) 4, "Topic"), GROUP((byte) 5, "Group"); @JSONField(value = true) private final byte code; private final String name; ResourceType(byte code, String name) { this.code = code; this.name = name; } public static ResourceType getByName(String name) { for (ResourceType resourceType : ResourceType.values()) { if (StringUtils.equalsIgnoreCase(resourceType.getName(), name)) { return resourceType; } } return null; } public byte getCode() { return code; } public String getName() { return name; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/resource/RocketMQResource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.resource; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface RocketMQResource { ResourceType value(); String splitter() default ""; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/running/RunningStats.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.running; public enum RunningStats { commitLogMaxOffset, commitLogMinOffset, commitLogDiskRatio, consumeQueueDiskRatio, scheduleMessageOffset, } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/state/StateEventListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.state; public interface StateEventListener { void fireEvent(T event); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/FutureHolder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; public class FutureHolder { private ConcurrentMap> futureMap = new ConcurrentHashMap<>(8); public void addFuture(T t, Future future) { BlockingQueue list = futureMap.get(t); if (list == null) { list = new LinkedBlockingQueue<>(); BlockingQueue old = futureMap.putIfAbsent(t, list); if (old != null) { list = old; } } list.add(future); } public void removeAllFuture(T t) { cancelAll(t, false); futureMap.remove(t); } private void cancelAll(T t, boolean mayInterruptIfRunning) { BlockingQueue list = futureMap.get(t); if (list != null) { for (Future future : list) { future.cancel(mayInterruptIfRunning); } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/Interceptor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; /** * interceptor */ public interface Interceptor { /** * increase multiple values * * @param deltas */ void inc(long... deltas); void reset(); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBrief.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import org.apache.commons.lang3.ArrayUtils; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class StatisticsBrief { public static final int META_RANGE_INDEX = 0; public static final int META_SLOT_NUM_INDEX = 1; // TopPercentile private long[][] topPercentileMeta; private AtomicInteger[] counts; private AtomicLong totalCount; // max min avg total private long max; private long min; private long total; public StatisticsBrief(long[][] topPercentileMeta) { if (!isLegalMeta(topPercentileMeta)) { throw new IllegalArgumentException("illegal topPercentileMeta"); } this.topPercentileMeta = topPercentileMeta; this.counts = new AtomicInteger[slotNum(topPercentileMeta)]; this.totalCount = new AtomicLong(0); reset(); } public void reset() { for (int i = 0; i < counts.length; i++) { if (counts[i] == null) { counts[i] = new AtomicInteger(0); } else { counts[i].set(0); } } totalCount.set(0); synchronized (this) { max = 0; min = Long.MAX_VALUE; total = 0; } } private static boolean isLegalMeta(long[][] meta) { if (ArrayUtils.isEmpty(meta)) { return false; } for (long[] line : meta) { if (ArrayUtils.isEmpty(line) || line.length != 2) { return false; } } return true; } private static int slotNum(long[][] meta) { int ret = 1; for (long[] line : meta) { ret += line[META_SLOT_NUM_INDEX]; } return ret; } public void sample(long value) { int index = getSlotIndex(value); counts[index].incrementAndGet(); totalCount.incrementAndGet(); synchronized (this) { max = Math.max(max, value); min = Math.min(min, value); total += value; } } public long tp999() { return getTPValue(0.999f); } public long getTPValue(float ratio) { if (ratio <= 0 || ratio >= 1) { ratio = 0.99f; } long count = totalCount.get(); long excludes = (long)(count - count * ratio); if (excludes == 0) { return getMax(); } int tmp = 0; for (int i = counts.length - 1; i > 0; i--) { tmp += counts[i].get(); if (tmp > excludes) { return Math.min(getSlotTPValue(i), getMax()); } } return 0; } private long getSlotTPValue(int index) { int slotNumLeft = index; for (int i = 0; i < topPercentileMeta.length; i++) { int slotNum = (int)topPercentileMeta[i][META_SLOT_NUM_INDEX]; if (slotNumLeft < slotNum) { long metaRangeMax = topPercentileMeta[i][META_RANGE_INDEX]; long metaRangeMin = 0; if (i > 0) { metaRangeMin = topPercentileMeta[i - 1][META_RANGE_INDEX]; } return metaRangeMin + (metaRangeMax - metaRangeMin) / slotNum * (slotNumLeft + 1); } else { slotNumLeft -= slotNum; } } // MAX_VALUE: the last slot return Integer.MAX_VALUE; } private int getSlotIndex(long num) { int index = 0; for (int i = 0; i < topPercentileMeta.length; i++) { long rangeMax = topPercentileMeta[i][META_RANGE_INDEX]; int slotNum = (int)topPercentileMeta[i][META_SLOT_NUM_INDEX]; long rangeMin = (i > 0) ? topPercentileMeta[i - 1][META_RANGE_INDEX] : 0; if (rangeMin <= num && num < rangeMax) { index += (num - rangeMin) / ((rangeMax - rangeMin) / slotNum); break; } index += slotNum; } return index; } /** * Getters * * @return */ public long getMax() { return max; } public long getMin() { return totalCount.get() > 0 ? min : 0; } public long getTotal() { return total; } public long getCnt() { return totalCount.get(); } public double getAvg() { return totalCount.get() != 0 ? ((double)total) / totalCount.get() : 0; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBriefInterceptor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; /** * interceptor to generate statistics brief */ public class StatisticsBriefInterceptor implements Interceptor { private int[] indexOfItems; private StatisticsBrief[] statisticsBriefs; public StatisticsBriefInterceptor(StatisticsItem item, Pair[] briefMetas) { indexOfItems = new int[briefMetas.length]; statisticsBriefs = new StatisticsBrief[briefMetas.length]; for (int i = 0; i < briefMetas.length; i++) { String name = briefMetas[i].getKey(); int index = ArrayUtils.indexOf(item.getItemNames(), name); if (index < 0) { throw new IllegalArgumentException("illegal briefItemName: " + name); } indexOfItems[i] = index; statisticsBriefs[i] = new StatisticsBrief(briefMetas[i].getValue()); } } @Override public void inc(long... itemValues) { for (int i = 0; i < indexOfItems.length; i++) { int indexOfItem = indexOfItems[i]; if (indexOfItem < itemValues.length) { statisticsBriefs[i].sample(itemValues[indexOfItem]); } } } @Override public void reset() { for (StatisticsBrief brief : statisticsBriefs) { brief.reset(); } } public int[] getIndexOfItems() { return indexOfItems; } public void setIndexOfItems(int[] indexOfItems) { this.indexOfItems = indexOfItems; } public StatisticsBrief[] getStatisticsBriefs() { return statisticsBriefs; } public void setStatisticsBriefs(StatisticsBrief[] statisticsBriefs) { this.statisticsBriefs = statisticsBriefs; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import java.security.InvalidParameterException; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.ArrayUtils; /** * Statistics Item */ public class StatisticsItem { private String statKind; private String statObject; private String[] itemNames; private AtomicLong[] itemAccumulates; private AtomicLong invokeTimes; private Interceptor interceptor; /** * last timestamp when the item was updated */ private AtomicLong lastTimeStamp; public StatisticsItem(String statKind, String statObject, String... itemNames) { if (itemNames == null || itemNames.length <= 0) { throw new InvalidParameterException("StatisticsItem \"itemNames\" is empty"); } this.statKind = statKind; this.statObject = statObject; this.itemNames = itemNames; AtomicLong[] accs = new AtomicLong[itemNames.length]; for (int i = 0; i < itemNames.length; i++) { accs[i] = new AtomicLong(0); } this.itemAccumulates = accs; this.invokeTimes = new AtomicLong(); this.lastTimeStamp = new AtomicLong(System.currentTimeMillis()); } public void incItems(long... itemIncs) { int len = Math.min(itemIncs.length, itemAccumulates.length); for (int i = 0; i < len; i++) { itemAccumulates[i].addAndGet(itemIncs[i]); } invokeTimes.addAndGet(1); lastTimeStamp.set(System.currentTimeMillis()); if (interceptor != null) { interceptor.inc(itemIncs); } } public boolean allZeros() { if (invokeTimes.get() == 0) { return true; } for (AtomicLong acc : itemAccumulates) { if (acc.get() != 0) { return false; } } return true; } public String getStatKind() { return statKind; } public String getStatObject() { return statObject; } public String[] getItemNames() { return itemNames; } public AtomicLong[] getItemAccumulates() { return itemAccumulates; } public AtomicLong getInvokeTimes() { return invokeTimes; } public AtomicLong getLastTimeStamp() { return lastTimeStamp; } public AtomicLong getItemAccumulate(String itemName) { int index = ArrayUtils.indexOf(itemNames, itemName); if (index < 0) { return new AtomicLong(0); } return itemAccumulates[index]; } /** * get snapshot *

    * Warning: no guarantee of itemAccumulates consistency * * @return */ public StatisticsItem snapshot() { StatisticsItem ret = new StatisticsItem(statKind, statObject, itemNames); ret.itemAccumulates = new AtomicLong[itemAccumulates.length]; for (int i = 0; i < itemAccumulates.length; i++) { ret.itemAccumulates[i] = new AtomicLong(itemAccumulates[i].get()); } ret.invokeTimes = new AtomicLong(invokeTimes.longValue()); ret.lastTimeStamp = new AtomicLong(lastTimeStamp.longValue()); return ret; } /** * subtract another StatisticsItem * * @param item * @return */ public StatisticsItem subtract(StatisticsItem item) { if (item == null) { return snapshot(); } if (!statKind.equals(item.statKind) || !statObject.equals(item.statObject) || !Arrays.equals(itemNames, item.itemNames)) { throw new IllegalArgumentException("StatisticsItem's kind, key and itemNames must be exactly the same"); } StatisticsItem ret = new StatisticsItem(statKind, statObject, itemNames); ret.invokeTimes = new AtomicLong(invokeTimes.get() - item.invokeTimes.get()); ret.itemAccumulates = new AtomicLong[itemAccumulates.length]; for (int i = 0; i < itemAccumulates.length; i++) { ret.itemAccumulates[i] = new AtomicLong(itemAccumulates[i].get() - item.itemAccumulates[i].get()); } return ret; } public Interceptor getInterceptor() { return interceptor; } public void setInterceptor(Interceptor interceptor) { this.interceptor = interceptor; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemFormatter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import java.util.concurrent.atomic.AtomicLong; public class StatisticsItemFormatter { public String format(StatisticsItem statItem) { final String separator = "|"; StringBuilder sb = new StringBuilder(); sb.append(statItem.getStatKind()).append(separator); sb.append(statItem.getStatObject()).append(separator); for (AtomicLong acc : statItem.getItemAccumulates()) { sb.append(acc.get()).append(separator); } sb.append(statItem.getInvokeTimes()); return sb.toString(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemPrinter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import org.apache.rocketmq.logging.org.slf4j.Logger; public class StatisticsItemPrinter { private Logger log; private StatisticsItemFormatter formatter; public StatisticsItemPrinter(StatisticsItemFormatter formatter, Logger log) { this.formatter = formatter; this.log = log; } public void log(Logger log) { this.log = log; } public void formatter(StatisticsItemFormatter formatter) { this.formatter = formatter; } public void print(String prefix, StatisticsItem statItem, String... suffixs) { StringBuilder suffix = new StringBuilder(); for (String str : suffixs) { suffix.append(str); } if (log != null) { log.info("{}{}{}", prefix, formatter.format(statItem), suffix.toString()); } // System.out.printf("%s %s%s%s\n", new Date().toString(), prefix, formatter.format(statItem), suffix.toString()); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledIncrementPrinter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class StatisticsItemScheduledIncrementPrinter extends StatisticsItemScheduledPrinter { private String[] tpsItemNames; public static final int TPS_INITIAL_DELAY = 0; public static final int TPS_INTREVAL = 1000; public static final String SEPARATOR = "|"; /** * last snapshots of all scheduled items */ private final ConcurrentHashMap> lastItemSnapshots = new ConcurrentHashMap<>(); private final ConcurrentHashMap> sampleBriefs = new ConcurrentHashMap<>(); public StatisticsItemScheduledIncrementPrinter(String name, StatisticsItemPrinter printer, ScheduledExecutorService executor, InitialDelay initialDelay, long interval, String[] tpsItemNames, Valve valve) { super(name, printer, executor, initialDelay, interval, valve); this.tpsItemNames = tpsItemNames; } /** * schedule a StatisticsItem to print the Increments periodically */ @Override public void schedule(final StatisticsItem item) { setItemSampleBrief(item.getStatKind(), item.getStatObject(), new StatisticsItemSampleBrief(item, tpsItemNames)); // print log every ${interval} milliseconds ScheduledFuture future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (!enabled()) { return; } StatisticsItem snapshot = item.snapshot(); StatisticsItem lastSnapshot = getItemSnapshot(lastItemSnapshots, item.getStatKind(), item.getStatObject()); StatisticsItem increment = snapshot.subtract(lastSnapshot); Interceptor interceptor = item.getInterceptor(); String interceptorStr = formatInterceptor(interceptor); if (interceptor != null) { interceptor.reset(); } StatisticsItemSampleBrief brief = getSampleBrief(item.getStatKind(), item.getStatObject()); if (brief != null && (!increment.allZeros() || printZeroLine())) { printer.print(name, increment, interceptorStr, brief.toString()); } setItemSnapshot(lastItemSnapshots, snapshot); if (brief != null) { brief.reset(); } } }, getInitialDelay(), interval, TimeUnit.MILLISECONDS); addFuture(item, future); // sample every TPS_INTERVAL ScheduledFuture futureSample = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (!enabled()) { return; } StatisticsItem snapshot = item.snapshot(); StatisticsItemSampleBrief brief = getSampleBrief(item.getStatKind(), item.getStatObject()); if (brief != null) { brief.sample(snapshot); } } }, TPS_INTREVAL, TPS_INTREVAL, TimeUnit.MILLISECONDS); addFuture(item, futureSample); } @Override public void remove(StatisticsItem item) { // remove task removeAllFuture(item); String kind = item.getStatKind(); String key = item.getStatObject(); ConcurrentHashMap lastItemMap = lastItemSnapshots.get(kind); if (lastItemMap != null) { lastItemMap.remove(key); } ConcurrentHashMap briefMap = sampleBriefs.get(kind); if (briefMap != null) { briefMap.remove(key); } } private StatisticsItem getItemSnapshot( ConcurrentHashMap> snapshots, String kind, String key) { ConcurrentHashMap itemMap = snapshots.get(kind); return (itemMap != null) ? itemMap.get(key) : null; } private StatisticsItemSampleBrief getSampleBrief(String kind, String key) { ConcurrentHashMap itemMap = sampleBriefs.get(kind); return (itemMap != null) ? itemMap.get(key) : null; } private void setItemSnapshot(ConcurrentHashMap> snapshots, StatisticsItem item) { String kind = item.getStatKind(); String key = item.getStatObject(); ConcurrentHashMap itemMap = snapshots.get(kind); if (itemMap == null) { itemMap = new ConcurrentHashMap<>(); ConcurrentHashMap oldItemMap = snapshots.putIfAbsent(kind, itemMap); if (oldItemMap != null) { itemMap = oldItemMap; } } itemMap.put(key, item); } private void setItemSampleBrief(String kind, String key, StatisticsItemSampleBrief brief) { ConcurrentHashMap itemMap = sampleBriefs.get(kind); if (itemMap == null) { itemMap = new ConcurrentHashMap<>(); ConcurrentHashMap oldItemMap = sampleBriefs.putIfAbsent(kind, itemMap); if (oldItemMap != null) { itemMap = oldItemMap; } } itemMap.put(key, brief); } private String formatInterceptor(Interceptor interceptor) { if (interceptor == null) { return ""; } if (interceptor instanceof StatisticsBriefInterceptor) { StringBuilder sb = new StringBuilder(); StatisticsBriefInterceptor briefInterceptor = (StatisticsBriefInterceptor)interceptor; for (StatisticsBrief brief : briefInterceptor.getStatisticsBriefs()) { long max = brief.getMax(); long tp999 = Math.min(brief.tp999(), max); //sb.append(SEPARATOR).append(brief.getTotal()); sb.append(SEPARATOR).append(max); //sb.append(SEPARATOR).append(brief.getMin()); sb.append(SEPARATOR).append(String.format("%.2f", brief.getAvg())); sb.append(SEPARATOR).append(tp999); } return sb.toString(); } return ""; } public static class StatisticsItemSampleBrief { private StatisticsItem lastSnapshot; public String[] itemNames; public ItemSampleBrief[] briefs; public StatisticsItemSampleBrief(StatisticsItem statItem, String[] itemNames) { this.lastSnapshot = statItem.snapshot(); this.itemNames = itemNames; this.briefs = new ItemSampleBrief[itemNames.length]; for (int i = 0; i < itemNames.length; i++) { this.briefs[i] = new ItemSampleBrief(); } } public synchronized void reset() { for (ItemSampleBrief brief : briefs) { brief.reset(); } } public synchronized void sample(StatisticsItem snapshot) { if (snapshot == null) { return; } for (int i = 0; i < itemNames.length; i++) { String name = itemNames[i]; long lastValue = lastSnapshot != null ? lastSnapshot.getItemAccumulate(name).get() : 0; long increment = snapshot.getItemAccumulate(name).get() - lastValue; briefs[i].sample(increment); } lastSnapshot = snapshot; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < briefs.length; i++) { ItemSampleBrief brief = briefs[i]; sb.append(SEPARATOR).append(brief.getMax()); //sb.append(SEPARATOR).append(brief.getMin()); sb.append(SEPARATOR).append(String.format("%.2f", brief.getAvg())); } return sb.toString(); } } /** * sample brief of a item for a period of time */ public static class ItemSampleBrief { private long max; private long min; private long total; private long cnt; public ItemSampleBrief() { reset(); } public void sample(long value) { max = Math.max(max, value); min = Math.min(min, value); total += value; cnt++; } public void reset() { max = 0; min = Long.MAX_VALUE; total = 0; cnt = 0; } /** * Getters * * @return */ public long getMax() { return max; } public long getMin() { return cnt > 0 ? min : 0; } public long getTotal() { return total; } public long getCnt() { return cnt; } public double getAvg() { return cnt != 0 ? ((double)total) / cnt : 0; } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledPrinter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class StatisticsItemScheduledPrinter extends FutureHolder { protected String name; protected StatisticsItemPrinter printer; protected ScheduledExecutorService executor; protected long interval; protected InitialDelay initialDelay; protected Valve valve; public StatisticsItemScheduledPrinter(String name, StatisticsItemPrinter printer, ScheduledExecutorService executor, InitialDelay initialDelay, long interval, Valve valve) { this.name = name; this.printer = printer; this.executor = executor; this.initialDelay = initialDelay; this.interval = interval; this.valve = valve; } /** * schedule a StatisticsItem to print all the values periodically */ public void schedule(final StatisticsItem statisticsItem) { ScheduledFuture future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (enabled()) { printer.print(name, statisticsItem); } } }, getInitialDelay(), interval, TimeUnit.MILLISECONDS); addFuture(statisticsItem, future); } public void remove(final StatisticsItem statisticsItem) { removeAllFuture(statisticsItem); } public interface InitialDelay { /** * Get initial delay value * @return */ long get(); } public interface Valve { /** * whether enabled * @return */ boolean enabled(); /** * whether print zero lines * @return */ boolean printZeroLine(); } protected long getInitialDelay() { return initialDelay != null ? initialDelay.get() : 0; } protected boolean enabled() { return valve != null ? valve.enabled() : false; } protected boolean printZeroLine() { return valve != null ? valve.printZeroLine() : false; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemStateGetter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; public interface StatisticsItemStateGetter { boolean online(StatisticsItem item); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsKindMeta.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; /** * Statistics Kind Metadata */ public class StatisticsKindMeta { private String name; private String[] itemNames; private StatisticsItemScheduledPrinter scheduledPrinter; public String getName() { return name; } public void setName(String name) { this.name = name; } public String[] getItemNames() { return itemNames; } public void setItemNames(String[] itemNames) { this.itemNames = itemNames; } public StatisticsItemScheduledPrinter getScheduledPrinter() { return scheduledPrinter; } public void setScheduledPrinter(StatisticsItemScheduledPrinter scheduledPrinter) { this.scheduledPrinter = scheduledPrinter; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.statistics; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.tuple.Pair; import org.apache.rocketmq.common.utils.ThreadUtils; public class StatisticsManager { /** * Set of Statistics Kind Metadata */ private Map kindMetaMap; /** * item names to calculate statistics brief */ private Pair[] briefMetas; /** * Statistics */ private final ConcurrentHashMap> statsTable = new ConcurrentHashMap<>(); private static final int MAX_IDLE_TIME = 10 * 60 * 1000; private final ScheduledExecutorService executor = ThreadUtils.newSingleThreadScheduledExecutor( "StatisticsManagerCleaner", true); private StatisticsItemStateGetter statisticsItemStateGetter; public StatisticsManager() { kindMetaMap = new HashMap<>(); start(); } public StatisticsManager(Map kindMeta) { this.kindMetaMap = kindMeta; start(); } public void addStatisticsKindMeta(StatisticsKindMeta kindMeta) { kindMetaMap.put(kindMeta.getName(), kindMeta); statsTable.putIfAbsent(kindMeta.getName(), new ConcurrentHashMap<>(16)); } public void setBriefMeta(Pair[] briefMetas) { this.briefMetas = briefMetas; } private void start() { int maxIdleTime = MAX_IDLE_TIME; executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { Iterator>> iter = statsTable.entrySet().iterator(); while (iter.hasNext()) { Map.Entry> entry = iter.next(); String kind = entry.getKey(); ConcurrentHashMap itemMap = entry.getValue(); if (itemMap == null || itemMap.isEmpty()) { continue; } HashMap tmpItemMap = new HashMap<>(itemMap); for (StatisticsItem item : tmpItemMap.values()) { // remove when expired if (System.currentTimeMillis() - item.getLastTimeStamp().get() > MAX_IDLE_TIME && (statisticsItemStateGetter == null || !statisticsItemStateGetter.online(item))) { remove(item); } } } } }, maxIdleTime, maxIdleTime / 3, TimeUnit.MILLISECONDS); } /** * Increment a StatisticsItem * * @param kind * @param key * @param itemAccumulates */ public boolean inc(String kind, String key, long... itemAccumulates) { ConcurrentHashMap itemMap = statsTable.get(kind); if (itemMap != null) { StatisticsItem item = itemMap.get(key); // if not exist, create and schedule if (item == null) { item = new StatisticsItem(kind, key, kindMetaMap.get(kind).getItemNames()); item.setInterceptor(new StatisticsBriefInterceptor(item, briefMetas)); StatisticsItem oldItem = itemMap.putIfAbsent(key, item); if (oldItem != null) { item = oldItem; } else { scheduleStatisticsItem(item); } } // do increment item.incItems(itemAccumulates); return true; } return false; } private void scheduleStatisticsItem(StatisticsItem item) { kindMetaMap.get(item.getStatKind()).getScheduledPrinter().schedule(item); } public void remove(StatisticsItem item) { ConcurrentHashMap itemMap = statsTable.get(item.getStatKind()); if (itemMap != null) { itemMap.remove(item.getStatObject(), item); } StatisticsKindMeta kindMeta = kindMetaMap.get(item.getStatKind()); if (kindMeta != null && kindMeta.getScheduledPrinter() != null) { kindMeta.getScheduledPrinter().remove(item); } } public StatisticsItemStateGetter getStatisticsItemStateGetter() { return statisticsItemStateGetter; } public void setStatisticsItemStateGetter(StatisticsItemStateGetter statisticsItemStateGetter) { this.statisticsItemStateGetter = statisticsItemStateGetter; } public void shutdown() { executor.shutdown(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.logging.org.slf4j.Logger; public class MomentStatsItem { private final AtomicLong value = new AtomicLong(0); private final String statsName; private final String statsKey; private final ScheduledExecutorService scheduledExecutorService; private final Logger log; private long lastUpdateTimestamp = System.currentTimeMillis(); public MomentStatsItem(String statsName, String statsKey, ScheduledExecutorService scheduledExecutorService, Logger log) { this.statsName = statsName; this.statsKey = statsKey; this.scheduledExecutorService = scheduledExecutorService; this.log = log; } public void init() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtMinutes(); MomentStatsItem.this.value.set(0); } catch (Throwable e) { } } }, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 5, TimeUnit.MILLISECONDS); } public void printAtMinutes() { log.info("[{}] [{}] Stats Every 5 Minutes, Value: {}", this.statsName, this.statsKey, this.value.get()); } public AtomicLong getValue() { return value; } public String getStatsKey() { return statsKey; } public String getStatsName() { return statsName; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MomentStatsItemSet { private static final Logger COMMERCIAL_LOG = LoggerFactory.getLogger(LoggerName.COMMERCIAL_LOGGER_NAME); private final ConcurrentMap statsItemTable = new ConcurrentHashMap<>(128); private final String statsName; private final ScheduledExecutorService scheduledExecutorService; private final Logger log; public MomentStatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, Logger log) { this.statsName = statsName; this.scheduledExecutorService = scheduledExecutorService; this.log = log; this.init(); } public ConcurrentMap getStatsItemTable() { return statsItemTable; } public String getStatsName() { return statsName; } public void init() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtMinutes(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 5, TimeUnit.MILLISECONDS); } private void printAtMinutes() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().printAtMinutes(); } } public void setValue(final String statsKey, final int value) { MomentStatsItem statsItem = this.getAndCreateStatsItem(statsKey); statsItem.getValue().set(value); statsItem.setLastUpdateTimestamp(System.currentTimeMillis()); } public void setValue(final String statsKey, final long value) { MomentStatsItem statsItem = this.getAndCreateStatsItem(statsKey); statsItem.getValue().set(value); statsItem.setLastUpdateTimestamp(System.currentTimeMillis()); } public void delValueByInfixKey(final String statsKey, String separator) { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getKey().contains(separator + statsKey + separator)) { it.remove(); } } } public void delValueBySuffixKey(final String statsKey, String separator) { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getKey().endsWith(separator + statsKey)) { it.remove(); } } } public MomentStatsItem getAndCreateStatsItem(final String statsKey) { MomentStatsItem statsItem = this.statsItemTable.get(statsKey); if (null == statsItem) { statsItem = new MomentStatsItem(this.statsName, statsKey, this.scheduledExecutorService, this.log); MomentStatsItem prev = this.statsItemTable.putIfAbsent(statsKey, statsItem); if (null != prev) { statsItem = prev; // statsItem.init(); } } return statsItem; } public void cleanResource(int maxStatsIdleTimeInMinutes) { COMMERCIAL_LOG.info("CleanStatisticItem: kind:{}, size:{}", statsName, this.statsItemTable.size()); Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); MomentStatsItem statsItem = next.getValue(); if (System.currentTimeMillis() - statsItem.getLastUpdateTimestamp() > maxStatsIdleTimeInMinutes * 60 * 1000L) { it.remove(); COMMERCIAL_LOG.info("CleanStatisticItem: removeKind:{}, removeKey:{}", statsName, statsItem.getStatsKey()); } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/RTStatsItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; import java.util.concurrent.ScheduledExecutorService; import org.apache.rocketmq.logging.org.slf4j.Logger; /** * A StatItem for response time, the only difference between from StatsItem is it has a different log output. */ public class RTStatsItem extends StatsItem { public RTStatsItem(String statsName, String statsKey, ScheduledExecutorService scheduledExecutorService, Logger logger) { super(statsName, statsKey, scheduledExecutorService, logger); } /** * For Response Time stat Item, the print detail should be a little different, TPS and SUM makes no sense. * And we give a name "AVGRT" rather than AVGPT for value getAvgpt() */ @Override protected String statPrintDetail(StatsSnapshot ss) { return String.format("TIMES: %d AVGRT: %.2f", ss.getTimes(), ss.getAvgpt()); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/Stats.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; public class Stats { public static final String QUEUE_PUT_NUMS = "QUEUE_PUT_NUMS"; public static final String QUEUE_PUT_SIZE = "QUEUE_PUT_SIZE"; public static final String QUEUE_GET_NUMS = "QUEUE_GET_NUMS"; public static final String QUEUE_GET_SIZE = "QUEUE_GET_SIZE"; public static final String TOPIC_PUT_NUMS = "TOPIC_PUT_NUMS"; public static final String TOPIC_PUT_SIZE = "TOPIC_PUT_SIZE"; public static final String GROUP_GET_NUMS = "GROUP_GET_NUMS"; public static final String GROUP_GET_SIZE = "GROUP_GET_SIZE"; public static final String SNDBCK_PUT_NUMS = "SNDBCK_PUT_NUMS"; public static final String BROKER_PUT_NUMS = "BROKER_PUT_NUMS"; public static final String BROKER_GET_NUMS = "BROKER_GET_NUMS"; public static final String GROUP_GET_FROM_DISK_NUMS = "GROUP_GET_FROM_DISK_NUMS"; public static final String GROUP_GET_FROM_DISK_SIZE = "GROUP_GET_FROM_DISK_SIZE"; public static final String BROKER_GET_FROM_DISK_NUMS = "BROKER_GET_FROM_DISK_NUMS"; public static final String BROKER_GET_FROM_DISK_SIZE = "BROKER_GET_FROM_DISK_SIZE"; public static final String COMMERCIAL_SEND_TIMES = "COMMERCIAL_SEND_TIMES"; public static final String COMMERCIAL_SNDBCK_TIMES = "COMMERCIAL_SNDBCK_TIMES"; public static final String COMMERCIAL_RCV_TIMES = "COMMERCIAL_RCV_TIMES"; public static final String COMMERCIAL_RCV_EPOLLS = "COMMERCIAL_RCV_EPOLLS"; public static final String COMMERCIAL_SEND_SIZE = "COMMERCIAL_SEND_SIZE"; public static final String COMMERCIAL_RCV_SIZE = "COMMERCIAL_RCV_SIZE"; public static final String COMMERCIAL_PERM_FAILURES = "COMMERCIAL_PERM_FAILURES"; public static final String GROUP_GET_FALL_SIZE = "GROUP_GET_FALL_SIZE"; public static final String GROUP_GET_FALL_TIME = "GROUP_GET_FALL_TIME"; public static final String GROUP_GET_LATENCY = "GROUP_GET_LATENCY"; public static final String TOPIC_PUT_LATENCY = "TOPIC_PUT_LATENCY"; public static final String GROUP_ACK_NUMS = "GROUP_ACK_NUMS"; public static final String GROUP_CK_NUMS = "GROUP_CK_NUMS"; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/StatsItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; import java.util.LinkedList; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.logging.org.slf4j.Logger; public class StatsItem { private final LongAdder value = new LongAdder(); private final LongAdder times = new LongAdder(); private final LinkedList csListMinute = new LinkedList<>(); private final LinkedList csListHour = new LinkedList<>(); private final LinkedList csListDay = new LinkedList<>(); private final String statsName; private final String statsKey; private long lastUpdateTimestamp = System.currentTimeMillis(); private final ScheduledExecutorService scheduledExecutorService; private final Logger logger; public StatsItem(String statsName, String statsKey, ScheduledExecutorService scheduledExecutorService, Logger logger) { this.statsName = statsName; this.statsKey = statsKey; this.scheduledExecutorService = scheduledExecutorService; this.logger = logger; } private static StatsSnapshot computeStatsData(final LinkedList csList) { StatsSnapshot statsSnapshot = new StatsSnapshot(); synchronized (csList) { double tps = 0; double avgpt = 0; long sum = 0; long timesDiff = 0; if (!csList.isEmpty()) { CallSnapshot first = csList.getFirst(); CallSnapshot last = csList.getLast(); sum = last.getValue() - first.getValue(); tps = (sum * 1000.0d) / (last.getTimestamp() - first.getTimestamp()); timesDiff = last.getTimes() - first.getTimes(); if (timesDiff > 0) { avgpt = (sum * 1.0d) / timesDiff; } } statsSnapshot.setSum(sum); statsSnapshot.setTps(tps); statsSnapshot.setAvgpt(avgpt); statsSnapshot.setTimes(timesDiff); } return statsSnapshot; } public StatsSnapshot getStatsDataInMinute() { return computeStatsData(this.csListMinute); } public StatsSnapshot getStatsDataInHour() { return computeStatsData(this.csListHour); } public StatsSnapshot getStatsDataInDay() { return computeStatsData(this.csListDay); } public void init() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { samplingInSeconds(); } catch (Throwable ignored) { } } }, 0, 10, TimeUnit.SECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { samplingInMinutes(); } catch (Throwable ignored) { } } }, 0, 10, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { samplingInHour(); } catch (Throwable ignored) { } } }, 0, 1, TimeUnit.HOURS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtMinutes(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60, TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtHour(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextHourTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 60, TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtDay(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextMorningTimeMillis() - System.currentTimeMillis()) - 2000, 1000 * 60 * 60 * 24, TimeUnit.MILLISECONDS); } public void samplingInSeconds() { synchronized (this.csListMinute) { if (this.csListMinute.size() == 0) { this.csListMinute.add(new CallSnapshot(System.currentTimeMillis() - 10 * 1000, 0, 0)); } this.csListMinute.add(new CallSnapshot(System.currentTimeMillis(), this.times.sum(), this.value .sum())); if (this.csListMinute.size() > 7) { this.csListMinute.removeFirst(); } } } public void samplingInMinutes() { synchronized (this.csListHour) { if (this.csListHour.size() == 0) { this.csListHour.add(new CallSnapshot(System.currentTimeMillis() - 10 * 60 * 1000, 0, 0)); } this.csListHour.add(new CallSnapshot(System.currentTimeMillis(), this.times.sum(), this.value .sum())); if (this.csListHour.size() > 7) { this.csListHour.removeFirst(); } } } public void samplingInHour() { synchronized (this.csListDay) { if (this.csListDay.size() == 0) { this.csListDay.add(new CallSnapshot(System.currentTimeMillis() - 1 * 60 * 60 * 1000, 0, 0)); } this.csListDay.add(new CallSnapshot(System.currentTimeMillis(), this.times.sum(), this.value .sum())); if (this.csListDay.size() > 25) { this.csListDay.removeFirst(); } } } public void printAtMinutes() { StatsSnapshot ss = computeStatsData(this.csListMinute); logger.info(String.format("[%s] [%s] Stats In One Minute, ", this.statsName, this.statsKey) + statPrintDetail(ss)); } public void printAtHour() { StatsSnapshot ss = computeStatsData(this.csListHour); logger.info(String.format("[%s] [%s] Stats In One Hour, ", this.statsName, this.statsKey) + statPrintDetail(ss)); } public void printAtDay() { StatsSnapshot ss = computeStatsData(this.csListDay); logger.info(String.format("[%s] [%s] Stats In One Day, ", this.statsName, this.statsKey) + statPrintDetail(ss)); } protected String statPrintDetail(StatsSnapshot ss) { return String.format("SUM: %d TPS: %.2f AVGPT: %.2f", ss.getSum(), ss.getTps(), ss.getAvgpt()); } public LongAdder getValue() { return value; } public String getStatsKey() { return statsKey; } public String getStatsName() { return statsName; } public LongAdder getTimes() { return times; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } } class CallSnapshot { private final long timestamp; private final long times; private final long value; public CallSnapshot(long timestamp, long times, long value) { super(); this.timestamp = timestamp; this.times = times; this.value = value; } public long getTimestamp() { return timestamp; } public long getTimes() { return times; } public long getValue() { return value; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class StatsItemSet { private static final Logger COMMERCIAL_LOG = LoggerFactory.getLogger(LoggerName.COMMERCIAL_LOGGER_NAME); private final ConcurrentMap statsItemTable = new ConcurrentHashMap<>(128); private final String statsName; private final ScheduledExecutorService scheduledExecutorService; private final Logger logger; public StatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, Logger logger) { this.logger = logger; this.statsName = statsName; this.scheduledExecutorService = scheduledExecutorService; this.init(); } public void init() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { samplingInSeconds(); } catch (Throwable ignored) { } } }, 0, 10, TimeUnit.SECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { samplingInMinutes(); } catch (Throwable ignored) { } } }, 0, 10, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { samplingInHour(); } catch (Throwable ignored) { } } }, 0, 1, TimeUnit.HOURS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtMinutes(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60, TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtHour(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextHourTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 60, TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printAtDay(); } catch (Throwable ignored) { } } }, Math.abs(UtilAll.computeNextMorningTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 60 * 24, TimeUnit.MILLISECONDS); } private void samplingInSeconds() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().samplingInSeconds(); } } private void samplingInMinutes() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().samplingInMinutes(); } } private void samplingInHour() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().samplingInHour(); } } private void printAtMinutes() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().printAtMinutes(); } } private void printAtHour() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().printAtHour(); } } private void printAtDay() { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); next.getValue().printAtDay(); } } public void addValue(final String statsKey, final int incValue, final int incTimes) { StatsItem statsItem = this.getAndCreateStatsItem(statsKey); statsItem.getValue().add(incValue); statsItem.getTimes().add(incTimes); statsItem.setLastUpdateTimestamp(System.currentTimeMillis()); } public void addRTValue(final String statsKey, final int incValue, final int incTimes) { StatsItem statsItem = this.getAndCreateRTStatsItem(statsKey); statsItem.getValue().add(incValue); statsItem.getTimes().add(incTimes); statsItem.setLastUpdateTimestamp(System.currentTimeMillis()); } public void delValue(final String statsKey) { StatsItem statsItem = this.statsItemTable.get(statsKey); if (null != statsItem) { this.statsItemTable.remove(statsKey); } } public void delValueByPrefixKey(final String statsKey, String separator) { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getKey().startsWith(statsKey + separator)) { it.remove(); } } } public void delValueByInfixKey(final String statsKey, String separator) { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getKey().contains(separator + statsKey + separator)) { it.remove(); } } } public void delValueBySuffixKey(final String statsKey, String separator) { Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); if (next.getKey().endsWith(separator + statsKey)) { it.remove(); } } } public StatsItem getAndCreateStatsItem(final String statsKey) { return getAndCreateItem(statsKey, false); } public StatsItem getAndCreateRTStatsItem(final String statsKey) { return getAndCreateItem(statsKey, true); } public StatsItem getAndCreateItem(final String statsKey, boolean rtItem) { StatsItem statsItem = this.statsItemTable.get(statsKey); if (null == statsItem) { if (rtItem) { statsItem = new RTStatsItem(this.statsName, statsKey, this.scheduledExecutorService, logger); } else { statsItem = new StatsItem(this.statsName, statsKey, this.scheduledExecutorService, logger); } StatsItem prev = this.statsItemTable.putIfAbsent(statsKey, statsItem); if (null != prev) { statsItem = prev; // statsItem.init(); } } return statsItem; } public StatsSnapshot getStatsDataInMinute(final String statsKey) { StatsItem statsItem = this.statsItemTable.get(statsKey); if (null != statsItem) { return statsItem.getStatsDataInMinute(); } return new StatsSnapshot(); } public StatsSnapshot getStatsDataInHour(final String statsKey) { StatsItem statsItem = this.statsItemTable.get(statsKey); if (null != statsItem) { return statsItem.getStatsDataInHour(); } return new StatsSnapshot(); } public StatsSnapshot getStatsDataInDay(final String statsKey) { StatsItem statsItem = this.statsItemTable.get(statsKey); if (null != statsItem) { return statsItem.getStatsDataInDay(); } return new StatsSnapshot(); } public StatsItem getStatsItem(final String statsKey) { return this.statsItemTable.get(statsKey); } public void cleanResource(int maxStatsIdleTimeInMinutes) { COMMERCIAL_LOG.info("CleanStatisticItemOld: kind:{}, size:{}", statsName, this.statsItemTable.size()); Iterator> it = this.statsItemTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); StatsItem statsItem = next.getValue(); if (System.currentTimeMillis() - statsItem.getLastUpdateTimestamp() > maxStatsIdleTimeInMinutes * 60 * 1000L) { it.remove(); COMMERCIAL_LOG.info("CleanStatisticItemOld: removeKind:{}, removeKey:{}", statsName, statsItem.getStatsKey()); } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/stats/StatsSnapshot.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; public class StatsSnapshot { private long sum; private double tps; private long times; private double avgpt; public long getSum() { return sum; } public void setSum(long sum) { this.sum = sum; } public double getTps() { return tps; } public void setTps(double tps) { this.tps = tps; } public double getAvgpt() { return avgpt; } public void setAvgpt(double avgpt) { this.avgpt = avgpt; } public long getTimes() { return times; } public void setTimes(long times) { this.times = times; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/sysflag/MessageSysFlag.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.sysflag; import org.apache.rocketmq.common.compression.CompressionType; public class MessageSysFlag { /** * Meaning of each bit in the system flag * * | bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * |--------|---|---|-----------|----------|-------------|------------------|------------------|------------------| * | byte 1 | | | STOREHOST | BORNHOST | TRANSACTION | TRANSACTION | MULTI_TAGS | COMPRESSED | * | byte 2 | | | | | | COMPRESSION_TYPE | COMPRESSION_TYPE | COMPRESSION_TYPE | * | byte 3 | | | | | | | | | * | byte 4 | | | | | | | | | */ public final static int COMPRESSED_FLAG = 0x1; public final static int MULTI_TAGS_FLAG = 0x1 << 1; public final static int TRANSACTION_NOT_TYPE = 0; public final static int TRANSACTION_PREPARED_TYPE = 0x1 << 2; public final static int TRANSACTION_COMMIT_TYPE = 0x2 << 2; public final static int TRANSACTION_ROLLBACK_TYPE = 0x3 << 2; public final static int BORNHOST_V6_FLAG = 0x1 << 4; public final static int STOREHOSTADDRESS_V6_FLAG = 0x1 << 5; //Mark the flag for batch to avoid conflict public final static int NEED_UNWRAP_FLAG = 0x1 << 6; public final static int INNER_BATCH_FLAG = 0x1 << 7; // COMPRESSION_TYPE public final static int COMPRESSION_LZ4_TYPE = 0x1 << 8; public final static int COMPRESSION_ZSTD_TYPE = 0x2 << 8; public final static int COMPRESSION_ZLIB_TYPE = 0x3 << 8; public final static int COMPRESSION_TYPE_COMPARATOR = 0x7 << 8; public static int getTransactionValue(final int flag) { return flag & TRANSACTION_ROLLBACK_TYPE; } public static int resetTransactionValue(final int flag, final int type) { return (flag & (~TRANSACTION_ROLLBACK_TYPE)) | type; } public static int clearCompressedFlag(final int flag) { return flag & (~COMPRESSED_FLAG); } // To match the compression type public static CompressionType getCompressionType(final int flag) { return CompressionType.findByValue((flag & COMPRESSION_TYPE_COMPARATOR) >> 8); } public static boolean check(int flag, int expectedFlag) { return (flag & expectedFlag) != 0; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/sysflag/PullSysFlag.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.sysflag; public class PullSysFlag { private final static int FLAG_COMMIT_OFFSET = 0x1; private final static int FLAG_SUSPEND = 0x1 << 1; private final static int FLAG_SUBSCRIPTION = 0x1 << 2; private final static int FLAG_CLASS_FILTER = 0x1 << 3; private final static int FLAG_LITE_PULL_MESSAGE = 0x1 << 4; public static int buildSysFlag(final boolean commitOffset, final boolean suspend, final boolean subscription, final boolean classFilter) { int flag = 0; if (commitOffset) { flag |= FLAG_COMMIT_OFFSET; } if (suspend) { flag |= FLAG_SUSPEND; } if (subscription) { flag |= FLAG_SUBSCRIPTION; } if (classFilter) { flag |= FLAG_CLASS_FILTER; } return flag; } public static int buildSysFlag(final boolean commitOffset, final boolean suspend, final boolean subscription, final boolean classFilter, final boolean litePull) { int flag = buildSysFlag(commitOffset, suspend, subscription, classFilter); if (litePull) { flag |= FLAG_LITE_PULL_MESSAGE; } return flag; } public static int clearCommitOffsetFlag(final int sysFlag) { return sysFlag & (~FLAG_COMMIT_OFFSET); } public static boolean hasCommitOffsetFlag(final int sysFlag) { return (sysFlag & FLAG_COMMIT_OFFSET) == FLAG_COMMIT_OFFSET; } public static boolean hasSuspendFlag(final int sysFlag) { return (sysFlag & FLAG_SUSPEND) == FLAG_SUSPEND; } public static int clearSuspendFlag(final int sysFlag) { return sysFlag & (~FLAG_SUSPEND); } public static boolean hasSubscriptionFlag(final int sysFlag) { return (sysFlag & FLAG_SUBSCRIPTION) == FLAG_SUBSCRIPTION; } public static int buildSysFlagWithSubscription(final int sysFlag) { return sysFlag | FLAG_SUBSCRIPTION; } public static boolean hasClassFilterFlag(final int sysFlag) { return (sysFlag & FLAG_CLASS_FILTER) == FLAG_CLASS_FILTER; } public static boolean hasLitePullFlag(final int sysFlag) { return (sysFlag & FLAG_LITE_PULL_MESSAGE) == FLAG_LITE_PULL_MESSAGE; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/sysflag/SubscriptionSysFlag.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.sysflag; public class SubscriptionSysFlag { private final static int FLAG_UNIT = 0x1 << 0; public static int buildSysFlag(final boolean unit) { int sysFlag = 0; if (unit) { sysFlag |= FLAG_UNIT; } return sysFlag; } public static int setUnitFlag(final int sysFlag) { return sysFlag | FLAG_UNIT; } public static int clearUnitFlag(final int sysFlag) { return sysFlag & (~FLAG_UNIT); } public static boolean hasUnitFlag(final int sysFlag) { return (sysFlag & FLAG_UNIT) == FLAG_UNIT; } public static void main(String[] args) { } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/sysflag/TopicSysFlag.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.sysflag; public class TopicSysFlag { private final static int FLAG_UNIT = 0x1 << 0; private final static int FLAG_UNIT_SUB = 0x1 << 1; public static int buildSysFlag(final boolean unit, final boolean hasUnitSub) { int sysFlag = 0; if (unit) { sysFlag |= FLAG_UNIT; } if (hasUnitSub) { sysFlag |= FLAG_UNIT_SUB; } return sysFlag; } public static int setUnitFlag(final int sysFlag) { return sysFlag | FLAG_UNIT; } public static int clearUnitFlag(final int sysFlag) { return sysFlag & (~FLAG_UNIT); } public static boolean hasUnitFlag(final int sysFlag) { return (sysFlag & FLAG_UNIT) == FLAG_UNIT; } public static int setUnitSubFlag(final int sysFlag) { return sysFlag | FLAG_UNIT_SUB; } public static int clearUnitSubFlag(final int sysFlag) { return sysFlag & (~FLAG_UNIT_SUB); } public static boolean hasUnitSubFlag(final int sysFlag) { return (sysFlag & FLAG_UNIT_SUB) == FLAG_UNIT_SUB; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.thread; import java.util.concurrent.BlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.RunnableFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.future.FutureTaskExt; public class FutureTaskExtThreadPoolExecutor extends ThreadPoolExecutor { public FutureTaskExtThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } @Override protected RunnableFuture newTaskFor(final Runnable runnable, final T value) { return new FutureTaskExt<>(runnable, value); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.thread; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ThreadPoolMonitor { private static Logger jstackLogger = LoggerFactory.getLogger(ThreadPoolMonitor.class); private static Logger waterMarkLogger = LoggerFactory.getLogger(ThreadPoolMonitor.class); private static final List MONITOR_EXECUTOR = new CopyOnWriteArrayList<>(); private static final ScheduledExecutorService MONITOR_SCHEDULED = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("ThreadPoolMonitor-%d").build() ); private static volatile long threadPoolStatusPeriodTime = TimeUnit.SECONDS.toMillis(3); private static volatile boolean enablePrintJstack = true; private static volatile long jstackPeriodTime = 60000; private static volatile long jstackTime = System.currentTimeMillis(); public static void config(Logger jstackLoggerConfig, Logger waterMarkLoggerConfig, boolean enablePrintJstack, long jstackPeriodTimeConfig, long threadPoolStatusPeriodTimeConfig) { jstackLogger = jstackLoggerConfig; waterMarkLogger = waterMarkLoggerConfig; threadPoolStatusPeriodTime = threadPoolStatusPeriodTimeConfig; ThreadPoolMonitor.enablePrintJstack = enablePrintJstack; jstackPeriodTime = jstackPeriodTimeConfig; } public static ThreadPoolExecutor createAndMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, String name, int queueCapacity) { return createAndMonitor(corePoolSize, maximumPoolSize, keepAliveTime, unit, name, queueCapacity, Collections.emptyList()); } public static ThreadPoolExecutor createAndMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, String name, int queueCapacity, ThreadPoolStatusMonitor... threadPoolStatusMonitors) { return createAndMonitor(corePoolSize, maximumPoolSize, keepAliveTime, unit, name, queueCapacity, Lists.newArrayList(threadPoolStatusMonitors)); } public static ThreadPoolExecutor createAndMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, String name, int queueCapacity, List threadPoolStatusMonitors) { ThreadPoolExecutor executor = (ThreadPoolExecutor) ThreadUtils.newThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<>(queueCapacity), new ThreadFactoryBuilder().setNameFormat(name + "-%d").build(), new ThreadPoolExecutor.DiscardOldestPolicy()); List printers = Lists.newArrayList(new ThreadPoolQueueSizeMonitor(queueCapacity)); printers.addAll(threadPoolStatusMonitors); MONITOR_EXECUTOR.add(ThreadPoolWrapper.builder() .name(name) .threadPoolExecutor(executor) .statusPrinters(printers) .build()); return executor; } public static void logThreadPoolStatus() { for (ThreadPoolWrapper threadPoolWrapper : MONITOR_EXECUTOR) { List monitors = threadPoolWrapper.getStatusPrinters(); for (ThreadPoolStatusMonitor monitor : monitors) { double value = monitor.value(threadPoolWrapper.getThreadPoolExecutor()); String nameFormatted = String.format("%-40s", threadPoolWrapper.getName()); String descFormatted = String.format("%-12s", monitor.describe()); waterMarkLogger.info("{}{}{}", nameFormatted, descFormatted, value); if (enablePrintJstack) { if (monitor.needPrintJstack(threadPoolWrapper.getThreadPoolExecutor(), value) && System.currentTimeMillis() - jstackTime > jstackPeriodTime) { jstackTime = System.currentTimeMillis(); jstackLogger.warn("jstack start\n{}", UtilAll.jstack()); } } } } } public static void init() { MONITOR_SCHEDULED.scheduleAtFixedRate(ThreadPoolMonitor::logThreadPoolStatus, 20, threadPoolStatusPeriodTime, TimeUnit.MILLISECONDS); } public static void shutdown() { MONITOR_SCHEDULED.shutdown(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.thread; import java.util.concurrent.ThreadPoolExecutor; public class ThreadPoolQueueSizeMonitor implements ThreadPoolStatusMonitor { private final int maxQueueCapacity; public ThreadPoolQueueSizeMonitor(int maxQueueCapacity) { this.maxQueueCapacity = maxQueueCapacity; } @Override public String describe() { return "queueSize"; } @Override public double value(ThreadPoolExecutor executor) { return executor.getQueue().size(); } @Override public boolean needPrintJstack(ThreadPoolExecutor executor, double value) { return value > maxQueueCapacity * 0.85; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.thread; import java.util.concurrent.ThreadPoolExecutor; public interface ThreadPoolStatusMonitor { String describe(); double value(ThreadPoolExecutor executor); boolean needPrintJstack(ThreadPoolExecutor executor, double value); } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.thread; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; public class ThreadPoolWrapper { private String name; private ThreadPoolExecutor threadPoolExecutor; private List statusPrinters; ThreadPoolWrapper(final String name, final ThreadPoolExecutor threadPoolExecutor, final List statusPrinters) { this.name = name; this.threadPoolExecutor = threadPoolExecutor; this.statusPrinters = statusPrinters; } public static class ThreadPoolWrapperBuilder { private String name; private ThreadPoolExecutor threadPoolExecutor; private List statusPrinters; ThreadPoolWrapperBuilder() { } public ThreadPoolWrapper.ThreadPoolWrapperBuilder name(final String name) { this.name = name; return this; } public ThreadPoolWrapper.ThreadPoolWrapperBuilder threadPoolExecutor( final ThreadPoolExecutor threadPoolExecutor) { this.threadPoolExecutor = threadPoolExecutor; return this; } public ThreadPoolWrapper.ThreadPoolWrapperBuilder statusPrinters( final List statusPrinters) { this.statusPrinters = statusPrinters; return this; } public ThreadPoolWrapper build() { return new ThreadPoolWrapper(this.name, this.threadPoolExecutor, this.statusPrinters); } @java.lang.Override public java.lang.String toString() { return "ThreadPoolWrapper.ThreadPoolWrapperBuilder(name=" + this.name + ", threadPoolExecutor=" + this.threadPoolExecutor + ", statusPrinters=" + this.statusPrinters + ")"; } } public static ThreadPoolWrapper.ThreadPoolWrapperBuilder builder() { return new ThreadPoolWrapper.ThreadPoolWrapperBuilder(); } public String getName() { return this.name; } public ThreadPoolExecutor getThreadPoolExecutor() { return this.threadPoolExecutor; } public List getStatusPrinters() { return this.statusPrinters; } public void setName(final String name) { this.name = name; } public void setThreadPoolExecutor(final ThreadPoolExecutor threadPoolExecutor) { this.threadPoolExecutor = threadPoolExecutor; } public void setStatusPrinters(final List statusPrinters) { this.statusPrinters = statusPrinters; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ThreadPoolWrapper wrapper = (ThreadPoolWrapper) o; return Objects.equal(name, wrapper.name) && Objects.equal(threadPoolExecutor, wrapper.threadPoolExecutor) && Objects.equal(statusPrinters, wrapper.statusPrinters); } @Override public int hashCode() { return Objects.hashCode(name, threadPoolExecutor, statusPrinters); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("name", name) .add("threadPoolExecutor", threadPoolExecutor) .add("statusPrinters", statusPrinters) .toString(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.topic; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; public class TopicValidator { public static final String AUTO_CREATE_TOPIC_KEY_TOPIC = "TBW102"; // Will be created at broker when isAutoCreateTopicEnable public static final String RMQ_SYS_SCHEDULE_TOPIC = "SCHEDULE_TOPIC_XXXX"; public static final String RMQ_SYS_BENCHMARK_TOPIC = "BenchmarkTest"; public static final String RMQ_SYS_TRANS_HALF_TOPIC = "RMQ_SYS_TRANS_HALF_TOPIC"; public static final String RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC = "RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC"; public static final String RMQ_SYS_TRACE_TOPIC = "RMQ_SYS_TRACE_TOPIC"; public static final String RMQ_SYS_TRANS_OP_HALF_TOPIC = "RMQ_SYS_TRANS_OP_HALF_TOPIC"; public static final String RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC = "RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC"; public static final String RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC = "TRANS_CHECK_MAX_TIME_TOPIC"; public static final String RMQ_SYS_SELF_TEST_TOPIC = "SELF_TEST_TOPIC"; public static final String RMQ_SYS_OFFSET_MOVED_EVENT = "OFFSET_MOVED_EVENT"; public static final String RMQ_SYS_ROCKSDB_OFFSET_TOPIC = "CHECKPOINT_TOPIC"; public static final String SYSTEM_TOPIC_PREFIX = "rmq_sys_"; public static final String SYNC_BROKER_MEMBER_GROUP_PREFIX = SYSTEM_TOPIC_PREFIX + "SYNC_BROKER_MEMBER_"; public static final boolean[] VALID_CHAR_BIT_MAP = new boolean[128]; private static final int TOPIC_MAX_LENGTH = 127; /* * Group name max length is 120, for it will be used to make up retry and DLQ topic, * like pull retry: %RETRY%group_topic and pop retry: %RETRY%group_topic. */ private static final int GROUP_MAX_LENGTH = 120; private static final int RETRY_OR_DLQ_TOPIC_MAX_LENGTH = 255; private static final Set SYSTEM_TOPIC_SET = new HashSet<>(); /** * Topic set which client can not send msg! */ private static final Set NOT_ALLOWED_SEND_TOPIC_SET = new HashSet<>(); static { SYSTEM_TOPIC_SET.add(AUTO_CREATE_TOPIC_KEY_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_SCHEDULE_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_BENCHMARK_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_TRANS_HALF_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_TRACE_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_TRANS_OP_HALF_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_SELF_TEST_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT); SYSTEM_TOPIC_SET.add(RMQ_SYS_ROCKSDB_OFFSET_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_SCHEDULE_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_HALF_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_OP_HALF_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_SELF_TEST_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC); // regex: ^[%|a-zA-Z0-9_-]+$ // % VALID_CHAR_BIT_MAP['%'] = true; // - VALID_CHAR_BIT_MAP['-'] = true; // _ VALID_CHAR_BIT_MAP['_'] = true; // | VALID_CHAR_BIT_MAP['|'] = true; for (int i = 0; i < VALID_CHAR_BIT_MAP.length; i++) { if (i >= '0' && i <= '9') { // 0-9 VALID_CHAR_BIT_MAP[i] = true; } else if (i >= 'A' && i <= 'Z') { // A-Z VALID_CHAR_BIT_MAP[i] = true; } else if (i >= 'a' && i <= 'z') { // a-z VALID_CHAR_BIT_MAP[i] = true; } } } public static boolean isTopicOrGroupIllegal(String str) { int strLen = str.length(); int len = VALID_CHAR_BIT_MAP.length; for (int i = 0; i < strLen; i++) { char ch = str.charAt(i); if (ch >= len || !VALID_CHAR_BIT_MAP[ch]) { return true; } } return false; } public static ValidateResult validateTopic(String topic) { if (UtilAll.isBlank(topic)) { return new ValidateResult(false, "The specified topic is blank."); } if (isTopicOrGroupIllegal(topic)) { String falseRemark = "The specified topic: " + topic + ", contains illegal characters, allowing only ^[%|a-zA-Z0-9_-]+$"; return new ValidateResult(false, falseRemark); } if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) || topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)) { if (topic.length() > RETRY_OR_DLQ_TOPIC_MAX_LENGTH) { String falseRemark = "The specified topic is DLQ or Retry topic: " + topic + ", and it's longer than topic max length: " + RETRY_OR_DLQ_TOPIC_MAX_LENGTH; return new ValidateResult(false, falseRemark); } } else { if (topic.length() > TOPIC_MAX_LENGTH) { String falseRemark = "The specified topic: " + topic + ", is longer than topic max length: " + TOPIC_MAX_LENGTH; return new ValidateResult(false, falseRemark); } } return new ValidateResult(true, ""); } public static ValidateResult validateGroup(String group) { if (UtilAll.isBlank(group)) { return new ValidateResult(false, "The specified group is blank."); } if (isTopicOrGroupIllegal(group)) { String falseRemark = "The specified group: " + group + ", contains illegal characters, allowing only ^[%|a-zA-Z0-9_-]+$"; return new ValidateResult(false, falseRemark); } if (group.length() > GROUP_MAX_LENGTH) { String falseRemark = "The specified group: " + group + ", is longer than group max length: " + GROUP_MAX_LENGTH; return new ValidateResult(false, falseRemark); } return new ValidateResult(true, ""); } public static class ValidateResult { private final boolean valid; private final String remark; public ValidateResult(boolean valid, String remark) { this.valid = valid; this.remark = remark; } public boolean isValid() { return valid; } public String getRemark() { return remark; } } public static boolean isSystemTopic(String topic) { return SYSTEM_TOPIC_SET.contains(topic) || topic.startsWith(SYSTEM_TOPIC_PREFIX); } public static boolean isNotAllowedSendTopic(String topic) { return NOT_ALLOWED_SEND_TOPIC_SET.contains(topic); } public static void addSystemTopic(String systemTopic) { SYSTEM_TOPIC_SET.add(systemTopic); } public static Set getSystemTopicSet() { return SYSTEM_TOPIC_SET; } public static Set getNotAllowedSendTopicSet() { return NOT_ALLOWED_SEND_TOPIC_SET; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/AbstractStartAndShutdown.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public abstract class AbstractStartAndShutdown implements StartAndShutdown { protected List startAndShutdownList = new CopyOnWriteArrayList<>(); protected void appendStartAndShutdown(StartAndShutdown startAndShutdown) { this.startAndShutdownList.add(startAndShutdown); } @Override public void start() throws Exception { for (StartAndShutdown startAndShutdown : startAndShutdownList) { startAndShutdown.start(); } } @Override public void shutdown() throws Exception { int index = startAndShutdownList.size() - 1; for (; index >= 0; index--) { startAndShutdownList.get(index).shutdown(); } } @Override public void preShutdown() throws Exception { int index = startAndShutdownList.size() - 1; for (; index >= 0; index--) { startAndShutdownList.get(index).preShutdown(); } } public void appendStart(Start start) { this.appendStartAndShutdown(new StartAndShutdown() { @Override public void shutdown() throws Exception { } @Override public void start() throws Exception { start.start(); } }); } public void appendShutdown(Shutdown shutdown) { this.appendStartAndShutdown(new StartAndShutdown() { @Override public void shutdown() throws Exception { shutdown.shutdown(); } @Override public void start() throws Exception { } }); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/AsyncShutdownHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class AsyncShutdownHelper { private final AtomicBoolean shutdown; private final List targetList; private CountDownLatch countDownLatch; public AsyncShutdownHelper() { this.targetList = new ArrayList<>(); this.shutdown = new AtomicBoolean(false); } public void addTarget(Shutdown target) { if (shutdown.get()) { return; } targetList.add(target); } public AsyncShutdownHelper shutdown() { if (shutdown.get()) { return this; } if (targetList.isEmpty()) { return this; } this.countDownLatch = new CountDownLatch(targetList.size()); for (Shutdown target : targetList) { Runnable runnable = () -> { try { target.shutdown(); } catch (Exception ignored) { } finally { countDownLatch.countDown(); } }; new Thread(runnable).start(); } return this; } public boolean await(long time, TimeUnit unit) throws InterruptedException { if (shutdown.get()) { return false; } try { return this.countDownLatch.await(time, unit); } finally { shutdown.compareAndSet(false, true); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Hex; public class BinaryUtil { public static byte[] calculateMd5(byte[] binaryData) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5 algorithm not found."); } messageDigest.update(binaryData); return messageDigest.digest(); } public static String generateMd5(String bodyStr) { byte[] bytes = calculateMd5(bodyStr.getBytes(Charset.forName("UTF-8"))); return Hex.encodeHexString(bytes, false); } public static String generateMd5(byte[] content) { byte[] bytes = calculateMd5(content); return Hex.encodeHexString(bytes, false); } /** * Returns true if subject contains only bytes that are spec-compliant ASCII characters. * @param subject * @return */ public static boolean isAscii(byte[] subject) { if (subject == null) { return false; } for (byte b : subject) { if (b < 32 || b > 126) { return false; } } return true; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/ChannelUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import io.netty.channel.Channel; import java.net.InetAddress; import java.net.InetSocketAddress; public class ChannelUtil { public static String getRemoteIp(Channel channel) { InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress(); if (inetSocketAddress == null) { return ""; } final InetAddress inetAddr = inetSocketAddress.getAddress(); return inetAddr != null ? inetAddr.getHostAddress() : inetSocketAddress.getHostName(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/CheckpointFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; /** * Entry Checkpoint file util * Format: *

  • First line: Entries size *
  • Second line: Entries crc32 *
  • Next: Entry data per line *

    * Example: *

  • 2 (size) *
  • 773307083 (crc32) *
  • 7-7000 (entry data) *
  • 8-8000 (entry data) */ public class CheckpointFile { /** * Not check crc32 when value is 0 */ private static final int NOT_CHECK_CRC_MAGIC_CODE = 0; private final String filePath; private final CheckpointSerializer serializer; public interface CheckpointSerializer { /** * Serialize entry to line */ String toLine(final T entry); /** * DeSerialize line to entry */ T fromLine(final String line); } public CheckpointFile(final String filePath, final CheckpointSerializer serializer) { this.filePath = filePath; this.serializer = serializer; } public String getBackFilePath() { return this.filePath + ".bak"; } /** * Write entries to file */ public void write(final List entries) throws IOException { if (entries.isEmpty()) { return; } synchronized (this) { StringBuilder entryContent = new StringBuilder(); for (T entry : entries) { final String line = this.serializer.toLine(entry); if (line != null && !line.isEmpty()) { entryContent.append(line); entryContent.append(System.lineSeparator()); } } int crc32 = UtilAll.crc32(entryContent.toString().getBytes(StandardCharsets.UTF_8)); String content = entries.size() + System.lineSeparator() + crc32 + System.lineSeparator() + entryContent; MixAll.string2File(content, this.filePath); } } private List read(String filePath) throws IOException { final ArrayList result = new ArrayList<>(); synchronized (this) { final File file = new File(filePath); if (!file.exists()) { return result; } try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { // Read size int expectedLines = Integer.parseInt(reader.readLine()); // Read block crc int expectedCrc32 = Integer.parseInt(reader.readLine()); // Read entries StringBuilder sb = new StringBuilder(); String line = reader.readLine(); while (line != null) { sb.append(line).append(System.lineSeparator()); final T entry = this.serializer.fromLine(line); if (entry != null) { result.add(entry); } line = reader.readLine(); } int truthCrc32 = UtilAll.crc32(sb.toString().getBytes(StandardCharsets.UTF_8)); if (result.size() != expectedLines) { final String err = String.format( "Expect %d entries, only found %d entries", expectedLines, result.size()); throw new IOException(err); } if (NOT_CHECK_CRC_MAGIC_CODE != expectedCrc32 && truthCrc32 != expectedCrc32) { final String err = String.format( "Entries crc32 not match, file=%s, truth=%s", expectedCrc32, truthCrc32); throw new IOException(err); } return result; } } } /** * Read entries from file */ public List read() throws IOException { try { List result = this.read(this.filePath); if (CollectionUtils.isEmpty(result)) { result = this.read(this.getBackFilePath()); } return result; } catch (IOException e) { return this.read(this.getBackFilePath()); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/CleanupPolicyUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CleanupPolicy; import java.util.Map; import java.util.Objects; import java.util.Optional; public class CleanupPolicyUtils { public static boolean isCompaction(Optional topicConfig) { return Objects.equals(CleanupPolicy.COMPACTION, getDeletePolicy(topicConfig)); } public static CleanupPolicy getDeletePolicy(Optional topicConfig) { if (!topicConfig.isPresent()) { return CleanupPolicy.valueOf(TopicAttributes.CLEANUP_POLICY_ATTRIBUTE.getDefaultValue()); } String attributeName = TopicAttributes.CLEANUP_POLICY_ATTRIBUTE.getName(); Map attributes = topicConfig.get().getAttributes(); if (attributes == null || attributes.size() == 0) { return CleanupPolicy.valueOf(TopicAttributes.CLEANUP_POLICY_ATTRIBUTE.getDefaultValue()); } if (attributes.containsKey(attributeName)) { return CleanupPolicy.valueOf(attributes.get(attributeName)); } else { return CleanupPolicy.valueOf(TopicAttributes.CLEANUP_POLICY_ATTRIBUTE.getDefaultValue()); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.Objects; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; public abstract class ConcurrentHashMapUtils { private static boolean isJdk8; static { // Java 8 // Java 9+: 9,11,17 try { isJdk8 = System.getProperty("java.version").startsWith("1.8."); } catch (Exception ignore) { isJdk8 = true; } } /** * A temporary workaround for Java 8 specific performance issue JDK-8161372 .
    Use implementation of * ConcurrentMap.computeIfAbsent instead. * * Requirement: The mapping function should not modify this map during computation. * * @see https://bugs.openjdk.java.net/browse/JDK-8161372 */ public static V computeIfAbsent(ConcurrentMap map, K key, Function func) { Objects.requireNonNull(func); if (isJdk8) { V v = map.get(key); if (null == v) { // this bug fix methods maybe cause `func.apply` multiple calls. v = func.apply(key); if (null == v) { return null; } final V res = map.putIfAbsent(key, v); if (null != res) { // if pre value present, means other thread put value already, and putIfAbsent not effect // return exist value return res; } } return v; } else { return map.computeIfAbsent(key, func); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/CorrelationIdUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.UUID; public class CorrelationIdUtil { public static String createCorrelationId() { return UUID.randomUUID().toString(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.nio.ByteBuffer; import java.nio.charset.Charset; public class DataConverter { public static final Charset CHARSET_UTF8 = Charset.forName("UTF-8"); public static byte[] Long2Byte(Long v) { ByteBuffer tmp = ByteBuffer.allocate(8); tmp.putLong(v); return tmp.array(); } public static int setBit(int value, int index, boolean flag) { if (flag) { return (int) (value | (1L << index)); } else { return (int) (value & ~(1L << index)); } } public static boolean getBit(int value, int index) { return (value & (1L << index)) != 0; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/ExceptionUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; public class ExceptionUtils { public static Throwable getRealException(Throwable throwable) { if (throwable instanceof CompletionException || throwable instanceof ExecutionException) { if (throwable.getCause() != null) { throwable = throwable.getCause(); } } return throwable; } public static String getErrorDetailMessage(Throwable t) { if (t == null) { return null; } StringBuilder sb = new StringBuilder(); sb.append(t.getMessage()).append(". ").append(t.getClass().getSimpleName()); if (t.getStackTrace().length > 0) { sb.append(". ").append(t.getStackTrace()[0]); } return sb.toString(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/FastJsonSerializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import com.alibaba.fastjson2.JSON; import org.apache.commons.lang3.SerializationException; /** * The object serializer based on fastJson */ public class FastJsonSerializer implements Serializer { @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } else { try { return JSON.toJSONBytes(t); } catch (Exception var3) { throw new SerializationException("Could not serialize: " + var3.getMessage(), var3); } } } @Override public T deserialize(byte[] bytes, Class type) throws SerializationException { if (bytes != null && bytes.length != 0) { try { return JSON.parseObject(bytes, type); } catch (Exception var3) { throw new SerializationException("Could not deserialize: " + var3.getMessage(), var3); } } else { return null; } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/FutureUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; public class FutureUtils { public static CompletableFuture appendNextFuture(CompletableFuture future, CompletableFuture nextFuture, ExecutorService executor) { future.whenCompleteAsync((t, throwable) -> { if (throwable != null) { nextFuture.completeExceptionally(throwable); } else { nextFuture.complete(t); } }, executor); return nextFuture; } public static CompletableFuture addExecutor(CompletableFuture future, ExecutorService executor) { return appendNextFuture(future, new CompletableFuture<>(), executor); } public static CompletableFuture completeExceptionally(Throwable t) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(t); return future; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/HttpTinyClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Iterator; import java.util.List; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; public class HttpTinyClient { static public HttpResult httpGet(String url, List headers, List paramValues, String encoding, long readTimeoutMs) throws IOException { String encodedContent = encodingParams(paramValues, encoding); url += (null == encodedContent) ? "" : ("?" + encodedContent); HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout((int) readTimeoutMs); conn.setReadTimeout((int) readTimeoutMs); setHeaders(conn, headers, encoding); conn.connect(); int respCode = conn.getResponseCode(); String resp = null; if (HttpURLConnection.HTTP_OK == respCode) { resp = IOTinyUtils.toString(conn.getInputStream(), encoding); } else { resp = IOTinyUtils.toString(conn.getErrorStream(), encoding); } return new HttpResult(respCode, resp); } finally { if (conn != null) { conn.disconnect(); } } } static private String encodingParams(List paramValues, String encoding) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); if (null == paramValues) { return null; } for (Iterator iter = paramValues.iterator(); iter.hasNext(); ) { sb.append(iter.next()).append("="); sb.append(URLEncoder.encode(iter.next(), encoding)); if (iter.hasNext()) { sb.append("&"); } } return sb.toString(); } static private void setHeaders(HttpURLConnection conn, List headers, String encoding) { if (null != headers) { for (Iterator iter = headers.iterator(); iter.hasNext(); ) { conn.addRequestProperty(iter.next(), iter.next()); } } conn.addRequestProperty("Client-Version", MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION)); conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding); String ts = String.valueOf(System.currentTimeMillis()); conn.addRequestProperty("Metaq-Client-RequestTS", ts); } /** * @return the http response of given http post request */ static public HttpResult httpPost(String url, List headers, List paramValues, String encoding, long readTimeoutMs) throws IOException { String encodedContent = encodingParams(paramValues, encoding); HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(3000); conn.setReadTimeout((int) readTimeoutMs); conn.setDoOutput(true); conn.setDoInput(true); setHeaders(conn, headers, encoding); conn.getOutputStream().write(encodedContent.getBytes(MixAll.DEFAULT_CHARSET)); int respCode = conn.getResponseCode(); String resp = null; if (HttpURLConnection.HTTP_OK == respCode) { resp = IOTinyUtils.toString(conn.getInputStream(), encoding); } else { resp = IOTinyUtils.toString(conn.getErrorStream(), encoding); } return new HttpResult(respCode, resp); } finally { if (null != conn) { conn.disconnect(); } } } static public class HttpResult { final public int code; final public String content; public HttpResult(int code, String content) { this.code = code; this.content = content; } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/IOTinyUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.io.BufferedReader; import java.io.CharArrayWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; public class IOTinyUtils { static public String toString(InputStream input, String encoding) throws IOException { return (null == encoding) ? toString(new InputStreamReader(input, StandardCharsets.UTF_8)) : toString(new InputStreamReader( input, encoding)); } static public String toString(Reader reader) throws IOException { CharArrayWriter sw = new CharArrayWriter(); copy(reader, sw); return sw.toString(); } static public long copy(Reader input, Writer output) throws IOException { char[] buffer = new char[1 << 12]; long count = 0; for (int n = 0; (n = input.read(buffer)) >= 0; ) { output.write(buffer, 0, n); count += n; } return count; } static public List readLines(Reader input) throws IOException { BufferedReader reader = toBufferedReader(input); List list = new ArrayList<>(); String line; for (; ; ) { line = reader.readLine(); if (null != line) { list.add(line); } else { break; } } return list; } static private BufferedReader toBufferedReader(Reader reader) { return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); } static public void copyFile(String source, String target) throws IOException { File sf = new File(source); if (!sf.exists()) { throw new IllegalArgumentException("source file does not exist."); } File tf = new File(target); tf.getParentFile().mkdirs(); if (!tf.exists() && !tf.createNewFile()) { throw new RuntimeException("failed to create target file."); } FileChannel sc = null; FileChannel tc = null; try { tc = new FileOutputStream(tf).getChannel(); sc = new FileInputStream(sf).getChannel(); sc.transferTo(0, sc.size(), tc); } finally { if (null != sc) { sc.close(); } if (null != tc) { tc.close(); } } } public static void delete(File fileOrDir) throws IOException { if (fileOrDir == null) { return; } if (fileOrDir.isDirectory()) { cleanDirectory(fileOrDir); } fileOrDir.delete(); } public static void cleanDirectory(File directory) throws IOException { if (!directory.exists()) { String message = directory + " does not exist"; throw new IllegalArgumentException(message); } if (!directory.isDirectory()) { String message = directory + " is not a directory"; throw new IllegalArgumentException(message); } File[] files = directory.listFiles(); if (files == null) { // null if security restricted throw new IOException("Failed to list contents of " + directory); } IOException exception = null; for (File file : files) { try { delete(file); } catch (IOException ioe) { exception = ioe; } } if (null != exception) { throw exception; } } public static void writeStringToFile(File file, String data, String encoding) throws IOException { OutputStream os = null; try { os = new FileOutputStream(file); os.write(data.getBytes(encoding)); } finally { if (null != os) { os.close(); } } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/IPAddressUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.math.BigInteger; import java.net.InetAddress; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.InetAddressValidator; public class IPAddressUtils { private static final String SLASH = "/"; private static final InetAddressValidator VALIDATOR = InetAddressValidator.getInstance(); public static boolean isValidIPOrCidr(String ipOrCidr) { return isValidIp(ipOrCidr) || isValidCidr(ipOrCidr); } public static boolean isValidIp(String ip) { return VALIDATOR.isValid(ip); } public static boolean isValidIPv4(String ip) { return VALIDATOR.isValidInet4Address(ip); } public static boolean isValidIPv6(String ip) { return VALIDATOR.isValidInet6Address(ip); } public static boolean isValidCidr(String cidr) { return isValidIPv4Cidr(cidr) || isValidIPv6Cidr(cidr); } public static boolean isValidIPv4Cidr(String cidr) { try { String[] parts = cidr.split(SLASH); if (parts.length != 2) { return false; } InetAddress ip = InetAddress.getByName(parts[0]); if (ip.getAddress().length != 4) { return false; } int prefix = Integer.parseInt(parts[1]); return prefix >= 0 && prefix <= 32; } catch (Exception e) { return false; } } public static boolean isValidIPv6Cidr(String cidr) { try { String[] parts = cidr.split(SLASH); if (parts.length != 2) { return false; } InetAddress ip = InetAddress.getByName(parts[0]); if (ip.getAddress().length != 16) { return false; } int prefix = Integer.parseInt(parts[1]); return prefix >= 0 && prefix <= 128; } catch (Exception e) { return false; } } public static boolean isIPInRange(String ip, String cidr) { try { String[] parts = cidr.split(SLASH); if (parts.length == 1) { return StringUtils.equals(ip, cidr); } if (parts.length != 2) { return false; } InetAddress cidrIp = InetAddress.getByName(parts[0]); int prefixLength = Integer.parseInt(parts[1]); BigInteger cidrIpBigInt = new BigInteger(1, cidrIp.getAddress()); BigInteger ipBigInt = new BigInteger(1, InetAddress.getByName(ip).getAddress()); BigInteger mask = BigInteger.valueOf(-1).shiftLeft(cidrIp.getAddress().length * 8 - prefixLength); BigInteger cidrIpLower = cidrIpBigInt.and(mask); BigInteger cidrIpUpper = cidrIpLower.add(mask.not()); return ipBigInt.compareTo(cidrIpLower) >= 0 && ipBigInt.compareTo(cidrIpUpper) <= 0; } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/MessageUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashSet; import java.util.Set; import com.google.common.hash.Hashing; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import static org.apache.rocketmq.common.message.MessageDecoder.NAME_VALUE_SEPARATOR; import static org.apache.rocketmq.common.message.MessageDecoder.PROPERTY_SEPARATOR; public class MessageUtils { public static int getShardingKeyIndex(String shardingKey, int indexSize) { return Math.abs(Hashing.murmur3_32().hashBytes(shardingKey.getBytes(StandardCharsets.UTF_8)).asInt() % indexSize); } public static int getShardingKeyIndexByMsg(MessageExt msg, int indexSize) { String shardingKey = msg.getProperty(MessageConst.PROPERTY_SHARDING_KEY); if (shardingKey == null) { shardingKey = ""; } return getShardingKeyIndex(shardingKey, indexSize); } public static Set getShardingKeyIndexes(Collection msgs, int indexSize) { Set indexSet = new HashSet<>(indexSize); for (MessageExt msg : msgs) { indexSet.add(getShardingKeyIndexByMsg(msg, indexSize)); } return indexSet; } public static String deleteProperty(String propertiesString, String name) { if (propertiesString != null) { int idx0 = 0; int idx1; int idx2; idx1 = propertiesString.indexOf(name, idx0); if (idx1 != -1) { // cropping may be required StringBuilder stringBuilder = new StringBuilder(propertiesString.length()); while (true) { int startIdx = idx0; while (true) { idx1 = propertiesString.indexOf(name, startIdx); if (idx1 == -1) { break; } startIdx = idx1 + name.length(); if (idx1 == 0 || propertiesString.charAt(idx1 - 1) == PROPERTY_SEPARATOR) { if (propertiesString.length() > idx1 + name.length() && propertiesString.charAt(idx1 + name.length()) == NAME_VALUE_SEPARATOR) { break; } } } if (idx1 == -1) { // there are no characters that need to be skipped. Append all remaining characters. stringBuilder.append(propertiesString, idx0, propertiesString.length()); break; } // there are characters that need to be cropped stringBuilder.append(propertiesString, idx0, idx1); // move idx2 to the end of the cropped character idx2 = propertiesString.indexOf(PROPERTY_SEPARATOR, idx1 + name.length() + 1); // all subsequent characters will be cropped if (idx2 == -1) { break; } idx0 = idx2 + 1; } return stringBuilder.toString(); } } return propertiesString; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/NameServerAddressUtils.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; public class NameServerAddressUtils { public static final String INSTANCE_PREFIX = "MQ_INST_"; public static final String INSTANCE_REGEX = INSTANCE_PREFIX + "\\w+_\\w+"; public static final String ENDPOINT_PREFIX = "(\\w+://|)"; public static final Pattern NAMESRV_ENDPOINT_PATTERN = Pattern.compile("^http://.*"); public static final Pattern INST_ENDPOINT_PATTERN = Pattern.compile("^" + ENDPOINT_PREFIX + INSTANCE_REGEX + "\\..*"); public static String getNameServerAddresses() { return System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); } public static boolean validateInstanceEndpoint(String endpoint) { return INST_ENDPOINT_PATTERN.matcher(endpoint).matches(); } public static String parseInstanceIdFromEndpoint(String endpoint) { if (StringUtils.isEmpty(endpoint)) { return null; } return endpoint.substring(endpoint.lastIndexOf("/") + 1, endpoint.indexOf('.')); } public static String getNameSrvAddrFromNamesrvEndpoint(String nameSrvEndpoint) { if (StringUtils.isEmpty(nameSrvEndpoint)) { return null; } return nameSrvEndpoint.substring(nameSrvEndpoint.lastIndexOf('/') + 1); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/NetworkUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.SocketAddress; import java.net.SocketException; import java.nio.channels.Selector; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import org.apache.commons.validator.routines.InetAddressValidator; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class NetworkUtil { public static final String OS_NAME = System.getProperty("os.name"); private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static boolean isLinuxPlatform = false; private static boolean isWindowsPlatform = false; static { if (OS_NAME != null && OS_NAME.toLowerCase().contains("linux")) { isLinuxPlatform = true; } if (OS_NAME != null && OS_NAME.toLowerCase().contains("windows")) { isWindowsPlatform = true; } } public static boolean isWindowsPlatform() { return isWindowsPlatform; } public static Selector openSelector() throws IOException { Selector result = null; if (isLinuxPlatform()) { try { final Class providerClazz = Class.forName("sun.nio.ch.EPollSelectorProvider"); try { final Method method = providerClazz.getMethod("provider"); final SelectorProvider selectorProvider = (SelectorProvider) method.invoke(null); if (selectorProvider != null) { result = selectorProvider.openSelector(); } } catch (final Exception e) { log.warn("Open ePoll Selector for linux platform exception", e); } } catch (final Exception e) { // ignore } } if (result == null) { result = Selector.open(); } return result; } public static boolean isLinuxPlatform() { return isLinuxPlatform; } public static List getLocalInetAddressList() throws SocketException { Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); List inetAddressList = new ArrayList<>(); // Traversal Network interface to get the non-bridge and non-virtual and non-ppp and up address while (enumeration.hasMoreElements()) { final NetworkInterface nif = enumeration.nextElement(); if (isBridge(nif) || nif.isVirtual() || nif.isPointToPoint() || !nif.isUp()) { continue; } InetAddressValidator validator = InetAddressValidator.getInstance(); final Enumeration en = nif.getInetAddresses(); while (en.hasMoreElements()) { final InetAddress address = en.nextElement(); if (address instanceof Inet4Address) { byte[] ipByte = address.getAddress(); if (ipByte.length == 4) { if (validator.isValidInet4Address(UtilAll.ipToIPv4Str(ipByte))) { inetAddressList.add(address); } } } else if (address instanceof Inet6Address) { byte[] ipByte = address.getAddress(); if (ipByte.length == 16) { if (validator.isValidInet6Address(UtilAll.ipToIPv6Str(ipByte))) { inetAddressList.add(address); } } } } } return inetAddressList; } public static InetAddress getLocalInetAddress() { try { ArrayList ipv4Result = new ArrayList<>(); ArrayList ipv6Result = new ArrayList<>(); List localInetAddressList = getLocalInetAddressList(); for (InetAddress inetAddress : localInetAddressList) { // Skip loopback addresses if (inetAddress.isLoopbackAddress()) { continue; } if (inetAddress instanceof Inet6Address) { ipv6Result.add(inetAddress); } else { ipv4Result.add(inetAddress); } } // prefer ipv4 and prefer external ip if (!ipv4Result.isEmpty()) { for (InetAddress ip : ipv4Result) { if (UtilAll.isInternalIP(ip.getAddress())) { continue; } return ip; } return ipv4Result.get(ipv4Result.size() - 1); } else if (!ipv6Result.isEmpty()) { for (InetAddress ip : ipv6Result) { if (UtilAll.isInternalV6IP(ip)) { continue; } return ip; } return ipv6Result.get(0); } //If failed to find,fall back to localhost return InetAddress.getLocalHost(); } catch (Exception e) { log.error("Failed to obtain local address", e); } return null; } public static String getLocalAddress() { InetAddress localHost = getLocalInetAddress(); return normalizeHostAddress(localHost); } public static String normalizeHostAddress(final InetAddress localHost) { if (localHost instanceof Inet6Address) { return "[" + localHost.getHostAddress() + "]"; } else { return localHost.getHostAddress(); } } public static String denormalizeHostAddress(final String bracketedAddress) { if (bracketedAddress == null) { return null; } if (bracketedAddress.startsWith("[") && bracketedAddress.endsWith("]")) { return bracketedAddress.substring(1, bracketedAddress.length() - 1); } else { return bracketedAddress; } } public static SocketAddress string2SocketAddress(final String addr) { int split = addr.lastIndexOf(":"); String host = addr.substring(0, split); String port = addr.substring(split + 1); return new InetSocketAddress(host, Integer.parseInt(port)); } public static String socketAddress2String(final SocketAddress addr) { StringBuilder sb = new StringBuilder(); InetSocketAddress inetSocketAddress = (InetSocketAddress) addr; sb.append(inetSocketAddress.getAddress().getHostAddress()); sb.append(":"); sb.append(inetSocketAddress.getPort()); return sb.toString(); } public static String convert2IpString(final String addr) { return socketAddress2String(string2SocketAddress(addr)); } private static boolean isBridge(NetworkInterface networkInterface) { try { if (isLinuxPlatform()) { String interfaceName = networkInterface.getName(); File file = new File("/sys/class/net/" + interfaceName + "/bridge"); return file.exists(); } } catch (SecurityException e) { //Ignore } return false; } // valid various ipv6 format like: // with scope 2001:0db8:85a3:0000:0000:8a2e:0370:7334%eth0 // with bracketed [2001:0db8:85a3:0000:0000:8a2e:0370:7334] public static boolean validCommonInet6Address(String ipOrCidr) { String ipWithoutBracketed = denormalizeHostAddress(ipOrCidr); if (ipWithoutBracketed != null && ipWithoutBracketed.length() != 0) { InetAddressValidator validator = InetAddressValidator.getInstance(); if (validator.isValidInet6Address(ipWithoutBracketed.split("%")[0])) { return true; } } return false; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/PositiveAtomicCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.concurrent.atomic.AtomicInteger; public class PositiveAtomicCounter { private static final int MASK = 0x7FFFFFFF; private final AtomicInteger atom; public PositiveAtomicCounter() { atom = new AtomicInteger(0); } public final int incrementAndGet() { final int rt = atom.incrementAndGet(); return rt & MASK; } public int intValue() { return atom.intValue(); } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/QueueTypeUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CQType; import java.util.Map; import java.util.Objects; import java.util.Optional; public class QueueTypeUtils { public static boolean isBatchCq(Optional topicConfig) { return Objects.equals(CQType.BatchCQ, getCQType(topicConfig)); } public static CQType getCQType(Optional topicConfig) { if (!topicConfig.isPresent()) { return CQType.valueOf(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getDefaultValue()); } String attributeName = TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName(); Map attributes = topicConfig.get().getAttributes(); if (attributes == null || attributes.size() == 0) { return CQType.valueOf(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getDefaultValue()); } if (attributes.containsKey(attributeName)) { return CQType.valueOf(attributes.get(attributeName)); } else { return CQType.valueOf(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getDefaultValue()); } } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/Serializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import org.apache.commons.lang3.SerializationException; /** * Serializer */ public interface Serializer { /** * Serialize object t to byte[] */ byte[] serialize(T t) throws SerializationException; /** * De-serialize bytes to T */ T deserialize(byte[] bytes, Class type) throws SerializationException; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/ServiceProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class ServiceProvider { private static final Logger LOG = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); /** * A reference to the classloader that loaded this class. It's more efficient to compute it once and cache it here. */ private static ClassLoader thisClassLoader; /** * JDK1.3+ 'Service Provider' * specification. */ public static final String PREFIX = "META-INF/service/"; static { thisClassLoader = getClassLoader(ServiceProvider.class); } /** * Returns a string that uniquely identifies the specified object, including its class. *

    * The returned string is of form "classname@hashcode", ie is the same as the return value of the Object.toString() * method, but works even when the specified object's class has overridden the toString method. * * @param o may be null. * @return a string of form classname@hashcode, or "null" if param o is null. */ protected static String objectId(Object o) { if (o == null) { return "null"; } else { return o.getClass().getName() + "@" + System.identityHashCode(o); } } protected static ClassLoader getClassLoader(Class clazz) { try { return clazz.getClassLoader(); } catch (SecurityException e) { LOG.error("Unable to get classloader for class {} due to security restrictions , error info {}", clazz, e.getMessage()); throw e; } } protected static ClassLoader getContextClassLoader() { ClassLoader classLoader = null; try { classLoader = Thread.currentThread().getContextClassLoader(); } catch (SecurityException ex) { /** * The getContextClassLoader() method throws SecurityException when the context * class loader isn't an ancestor of the calling class's class * loader, or if security permissions are restricted. */ } return classLoader; } protected static InputStream getResourceAsStream(ClassLoader loader, String name) { if (loader != null) { return loader.getResourceAsStream(name); } else { return ClassLoader.getSystemResourceAsStream(name); } } public static List load(Class clazz) { String fullName = PREFIX + clazz.getName(); return load(fullName, clazz); } public static List load(String name, Class clazz) { LOG.info("Looking for a resource file of name [{}] ...", name); List services = new ArrayList<>(); InputStream is = getResourceAsStream(getContextClassLoader(), name); if (is == null) { LOG.warn("No resource file with name [{}] found.", name); return services; } try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String serviceName = reader.readLine(); List names = new ArrayList<>(); while (serviceName != null && !"".equals(serviceName)) { LOG.info( "Creating an instance as specified by file {} which was present in the path of the context classloader.", name); if (!names.contains(serviceName)) { names.add(serviceName); services.add(initService(getContextClassLoader(), serviceName, clazz)); } serviceName = reader.readLine(); } } catch (Exception e) { LOG.error("Error occurred when looking for resource file " + name, e); } return services; } public static T loadClass(Class clazz) { String fullName = PREFIX + clazz.getName(); return loadClass(fullName, clazz); } public static T loadClass(String name, Class clazz) { LOG.info("Looking for a resource file of name [{}] ...", name); T s = null; InputStream is = getResourceAsStream(getContextClassLoader(), name); if (is == null) { LOG.warn("No resource file with name [{}] found.", name); return null; } try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String serviceName = reader.readLine(); if (serviceName != null && !"".equals(serviceName)) { s = initService(getContextClassLoader(), serviceName, clazz); } else { LOG.warn("ServiceName is empty!"); } } catch (Exception e) { LOG.warn("Error occurred when looking for resource file " + name, e); } return s; } protected static T initService(ClassLoader classLoader, String serviceName, Class clazz) { Class serviceClazz = null; try { if (classLoader != null) { try { // Warning: must typecast here & allow exception to be generated/caught & recast properly serviceClazz = classLoader.loadClass(serviceName); if (clazz.isAssignableFrom(serviceClazz)) { LOG.info("Loaded class {} from classloader {}", serviceClazz.getName(), objectId(classLoader)); } else { // This indicates a problem with the ClassLoader tree. An incompatible ClassLoader was used to load the implementation. LOG.error( "Class {} loaded from classloader {} does not extend {} as loaded by this classloader.", serviceClazz.getName(), objectId(serviceClazz.getClassLoader()), clazz.getName()); } return (T) serviceClazz.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException ex) { if (classLoader == thisClassLoader) { // Nothing more to try, onwards. LOG.warn("Unable to locate any class {} via classloader {}", serviceName, objectId(classLoader)); throw ex; } // Ignore exception, continue } catch (NoClassDefFoundError e) { if (classLoader == thisClassLoader) { // Nothing more to try, onwards. LOG.warn( "Class {} cannot be loaded via classloader {}.it depends on some other class that cannot be found.", serviceClazz, objectId(classLoader)); throw e; } // Ignore exception, continue } } } catch (Exception e) { LOG.error("Unable to init service.", e); } return (T) serviceClazz; } } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/Shutdown.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; public interface Shutdown { void shutdown() throws Exception; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/Start.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; public interface Start { void start() throws Exception; } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/StartAndShutdown.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; public interface StartAndShutdown extends Start, Shutdown { default void preShutdown() throws Exception {} } ================================================ FILE: common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.thread.FutureTaskExtThreadPoolExecutor; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public final class ThreadUtils { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TOOLS_LOGGER_NAME); public static ExecutorService newSingleThreadExecutor(String processName, boolean isDaemon) { return ThreadUtils.newSingleThreadExecutor(newThreadFactory(processName, isDaemon)); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return ThreadUtils.newThreadPoolExecutor(1, threadFactory); } public static ExecutorService newThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { return ThreadUtils.newThreadPoolExecutor(corePoolSize, corePoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), threadFactory); } public static ExecutorService newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, String processName, boolean isDaemon) { return ThreadUtils.newThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, newThreadFactory(processName, isDaemon)); } public static ExecutorService newThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue workQueue, final ThreadFactory threadFactory) { return ThreadUtils.newThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new ThreadPoolExecutor.AbortPolicy()); } public static ExecutorService newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { return new FutureTaskExtThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } public static ScheduledExecutorService newSingleThreadScheduledExecutor(String processName, boolean isDaemon) { return ThreadUtils.newScheduledThreadPool(1, processName, isDaemon); } public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return ThreadUtils.newScheduledThreadPool(1, threadFactory); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return ThreadUtils.newScheduledThreadPool(corePoolSize, Executors.defaultThreadFactory()); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, String processName, boolean isDaemon) { return ThreadUtils.newScheduledThreadPool(corePoolSize, newThreadFactory(processName, isDaemon)); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) { return ThreadUtils.newScheduledThreadPool(corePoolSize, threadFactory, new ThreadPoolExecutor.AbortPolicy()); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory, handler); } public static ThreadFactory newThreadFactory(String processName, boolean isDaemon) { return newGenericThreadFactory("ThreadUtils-" + processName, isDaemon); } public static ThreadFactory newGenericThreadFactory(String processName) { return newGenericThreadFactory(processName, false); } public static ThreadFactory newGenericThreadFactory(String processName, int threads) { return newGenericThreadFactory(processName, threads, false); } public static ThreadFactory newGenericThreadFactory(final String processName, final boolean isDaemon) { return new ThreadFactoryImpl(processName + "_", isDaemon); } public static ThreadFactory newGenericThreadFactory(final String processName, final int threads, final boolean isDaemon) { return new ThreadFactoryImpl(String.format("%s_%d_", processName, threads), isDaemon); } /** * Create a new thread * * @param name The name of the thread * @param runnable The work for the thread to do * @param daemon Should the thread block JVM stop? * @return The unstarted thread */ public static Thread newThread(String name, Runnable runnable, boolean daemon) { Thread thread = new Thread(runnable, name); thread.setDaemon(daemon); thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { LOGGER.error("Uncaught exception in thread '" + t.getName() + "':", e); } }); return thread; } /** * Shutdown passed thread using isAlive and join. * * @param t Thread to stop */ public static void shutdownGracefully(final Thread t) { shutdownGracefully(t, 0); } /** * Shutdown passed thread using isAlive and join. * * @param millis Pass 0 if we're to wait forever. * @param t Thread to stop */ public static void shutdownGracefully(final Thread t, final long millis) { if (t == null) return; while (t.isAlive()) { try { t.interrupt(); t.join(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } /** * An implementation of the graceful stop sequence recommended by * {@link ExecutorService}. * * @param executor executor * @param timeout timeout * @param timeUnit timeUnit */ public static void shutdownGracefully(ExecutorService executor, long timeout, TimeUnit timeUnit) { // Disable new tasks from being submitted. executor.shutdown(); try { // Wait a while for existing tasks to terminate. if (!executor.awaitTermination(timeout, timeUnit)) { executor.shutdownNow(); // Wait a while for tasks to respond to being cancelled. if (!executor.awaitTermination(timeout, timeUnit)) { LOGGER.warn(String.format("%s didn't terminate!", executor)); } } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted. executor.shutdownNow(); // Preserve interrupt status. Thread.currentThread().interrupt(); } } /** * Shutdown the specific ExecutorService * * @param executorService the executor */ public static void shutdown(ExecutorService executorService) { if (executorService != null) { executorService.shutdown(); } } /** * A constructor to stop this class being constructed. */ private ThreadUtils() { // Unused } } ================================================ FILE: common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator ================================================ org.apache.rocketmq.common.logging.DefaultJoranConfiguratorExt ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/BrokerConfigSingletonTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.junit.Assert; import org.junit.Test; public class BrokerConfigSingletonTest { /** * Tests the behavior of getting the broker configuration when it has not been initialized. * Expects an IllegalArgumentException to be thrown, ensuring that the configuration cannot be obtained without initialization. */ @Test(expected = IllegalArgumentException.class) public void getBrokerConfig_NullConfiguration_ThrowsException() { BrokerConfigSingleton.getBrokerConfig(); } /** * Tests the behavior of setting the broker configuration after it has already been initialized. * Expects an IllegalArgumentException to be thrown, ensuring that the configuration cannot be reset once set. * Also asserts that the returned brokerConfig instance is the same as the one set, confirming the singleton property. */ @Test(expected = IllegalArgumentException.class) public void setBrokerConfig_AlreadyInitialized_ThrowsException() { BrokerConfig config = new BrokerConfig(); BrokerConfigSingleton.setBrokerConfig(config); Assert.assertSame("Expected brokerConfig instance is not returned", config, BrokerConfigSingleton.getBrokerConfig()); BrokerConfigSingleton.setBrokerConfig(config); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/BrokerConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class BrokerConfigTest { @Test public void testConsumerFallBehindThresholdOverflow() { long expect = 1024L * 1024 * 1024 * 16; assertThat(new BrokerConfig().getConsumerFallbehindThreshold()).isEqualTo(expect); } @Test public void testBrokerConfigAttribute() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setNamesrvAddr("127.0.0.1:9876"); brokerConfig.setAutoCreateTopicEnable(false); brokerConfig.setBrokerName("broker-a"); brokerConfig.setBrokerId(0); brokerConfig.setBrokerClusterName("DefaultCluster"); brokerConfig.setMsgTraceTopicName("RMQ_SYS_TRACE_TOPIC4"); brokerConfig.setAutoDeleteUnusedStats(true); assertThat(brokerConfig.getBrokerClusterName()).isEqualTo("DefaultCluster"); assertThat(brokerConfig.getNamesrvAddr()).isEqualTo("127.0.0.1:9876"); assertThat(brokerConfig.getMsgTraceTopicName()).isEqualTo("RMQ_SYS_TRACE_TOPIC4"); assertThat(brokerConfig.getBrokerId()).isEqualTo(0); assertThat(brokerConfig.getBrokerName()).isEqualTo("broker-a"); assertThat(brokerConfig.isAutoCreateTopicEnable()).isEqualTo(false); assertThat(brokerConfig.isAutoDeleteUnusedStats()).isEqualTo(true); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/ConfigManagerTest.java ================================================ package org.apache.rocketmq.common;/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.PrintWriter; import java.lang.reflect.Method; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ConfigManagerTest { private static final String PATH_FILE = System.getProperty("java.io.tmpdir") + File.separator + "org.apache.rocketmq.common.ConfigManagerTest"; private static final String CONTENT_ENCODE = "Encode content for ConfigManager"; @Test public void testLoad() throws Exception { ConfigManager testConfigManager = buildTestConfigManager(); File file = createAndWriteFile(testConfigManager.configFilePath()); assertTrue(testConfigManager.load()); file.delete(); File fileBak = createAndWriteFile(testConfigManager.configFilePath() + ".bak"); assertTrue(testConfigManager.load()); fileBak.delete(); } @Test public void testLoadBak() throws Exception { ConfigManager testConfigManager = buildTestConfigManager(); File file = createAndWriteFile(testConfigManager.configFilePath() + ".bak"); // invoke private method "loadBak()" Method declaredMethod = ConfigManager.class.getDeclaredMethod("loadBak"); declaredMethod.setAccessible(true); Boolean loadBakResult = (Boolean) declaredMethod.invoke(testConfigManager); assertTrue(loadBakResult); file.delete(); Boolean loadBakResult2 = (Boolean) declaredMethod.invoke(testConfigManager); assertTrue(loadBakResult2); declaredMethod.setAccessible(false); } @Test public void testPersist() throws Exception { ConfigManager testConfigManager = buildTestConfigManager(); testConfigManager.persist(); File file = new File(testConfigManager.configFilePath()); assertEquals(CONTENT_ENCODE, MixAll.file2String(file)); } private ConfigManager buildTestConfigManager() { return new ConfigManager() { @Override public String encode() { return encode(false); } @Override public String configFilePath() { return PATH_FILE; } @Override public void decode(String jsonString) { } @Override public String encode(boolean prettyFormat) { return CONTENT_ENCODE; } }; } private File createAndWriteFile(String fileName) throws Exception { File file = new File(fileName); if (file.exists()) { file.delete(); } file.createNewFile(); PrintWriter out = new PrintWriter(fileName); out.write("TestForConfigManager"); out.close(); return file; } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/CountDownLatch2Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.junit.Test; import java.util.concurrent.TimeUnit; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** * CountDownLatch2 Unit Test * * @see CountDownLatch2 */ public class CountDownLatch2Test { /** * test constructor with invalid init param * * @see CountDownLatch2#CountDownLatch2(int) */ @Test public void testConstructorError() { int count = -1; try { CountDownLatch2 latch = new CountDownLatch2(count); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), is("count < 0")); } } /** * test constructor with valid init param * * @see CountDownLatch2#CountDownLatch2(int) */ @Test public void testConstructor() { int count = 10; CountDownLatch2 latch = new CountDownLatch2(count); assertEquals("Expected equal", count, latch.getCount()); assertThat("Expected contain", latch.toString(), containsString("[Count = " + count + "]")); } /** * test await timeout * * @see CountDownLatch2#await(long, TimeUnit) */ @Test public void testAwaitTimeout() throws InterruptedException { int count = 1; CountDownLatch2 latch = new CountDownLatch2(count); boolean await = latch.await(10, TimeUnit.MILLISECONDS); assertFalse("Expected false", await); latch.countDown(); boolean await2 = latch.await(10, TimeUnit.MILLISECONDS); assertTrue("Expected true", await2); } /** * test reset * * @see CountDownLatch2#countDown() */ @Test(timeout = 1000) public void testCountDownAndGetCount() throws InterruptedException { int count = 2; CountDownLatch2 latch = new CountDownLatch2(count); assertEquals("Expected equal", count, latch.getCount()); latch.countDown(); assertEquals("Expected equal", count - 1, latch.getCount()); latch.countDown(); latch.await(); assertEquals("Expected equal", 0, latch.getCount()); } /** * test reset * * @see CountDownLatch2#reset() */ @Test public void testReset() throws InterruptedException { int count = 2; CountDownLatch2 latch = new CountDownLatch2(count); latch.countDown(); assertEquals("Expected equal", count - 1, latch.getCount()); latch.reset(); assertEquals("Expected equal", count, latch.getCount()); latch.countDown(); latch.countDown(); latch.await(); assertEquals("Expected equal", 0, latch.getCount()); // coverage Sync#tryReleaseShared, c==0 latch.countDown(); assertEquals("Expected equal", 0, latch.getCount()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/KeyBuilderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class KeyBuilderTest { String topic = "test-topic"; String group = "test-group"; @Test public void testBuildPopRetryTopic() { assertThat(KeyBuilder.buildPopRetryTopicV2(topic, group)).isEqualTo(MixAll.RETRY_GROUP_TOPIC_PREFIX + group + "+" + topic); } @Test public void testBuildPopRetryTopicV1() { assertThat(KeyBuilder.buildPopRetryTopicV1(topic, group)).isEqualTo(MixAll.RETRY_GROUP_TOPIC_PREFIX + group + "_" + topic); } @Test public void testParseNormalTopic() { String popRetryTopic = KeyBuilder.buildPopRetryTopicV2(topic, group); assertThat(KeyBuilder.parseNormalTopic(popRetryTopic, group)).isEqualTo(topic); String popRetryTopicV1 = KeyBuilder.buildPopRetryTopicV1(topic, group); assertThat(KeyBuilder.parseNormalTopic(popRetryTopicV1, group)).isEqualTo(topic); popRetryTopic = KeyBuilder.buildPopRetryTopicV2(topic, group); assertThat(KeyBuilder.parseNormalTopic(popRetryTopic)).isEqualTo(topic); } @Test public void testParseGroup() { String popRetryTopic = KeyBuilder.buildPopRetryTopicV2(topic, group); assertThat(KeyBuilder.parseGroup(popRetryTopic)).isEqualTo(group); } @Test public void testIsPopRetryTopicV2() { String popRetryTopic = KeyBuilder.buildPopRetryTopicV2(topic, group); assertThat(KeyBuilder.isPopRetryTopicV2(popRetryTopic)).isEqualTo(true); String popRetryTopicV1 = KeyBuilder.buildPopRetryTopicV1(topic, group); assertThat(KeyBuilder.isPopRetryTopicV2(popRetryTopicV1)).isEqualTo(false); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/MQVersionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MQVersionTest { @Test public void testGetVersionDesc() throws Exception { String desc = "V3_0_0_SNAPSHOT"; assertThat(MQVersion.getVersionDesc(0)).isEqualTo(desc); } @Test public void testGetVersionDesc_higherVersion() throws Exception { String desc = "HIGHER_VERSION"; assertThat(MQVersion.getVersionDesc(Integer.MAX_VALUE)).isEqualTo(desc); } @Test public void testValue2Version() throws Exception { assertThat(MQVersion.value2Version(0)).isEqualTo(MQVersion.Version.V3_0_0_SNAPSHOT); } @Test public void testValue2Version_HigherVersion() throws Exception { assertThat(MQVersion.value2Version(Integer.MAX_VALUE)).isEqualTo(MQVersion.Version.HIGHER_VERSION); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/MessageBatchTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.junit.Test; public class MessageBatchTest { public List generateMessages() { List messages = new ArrayList<>(); Message message1 = new Message("topic1", "body".getBytes()); Message message2 = new Message("topic1", "body".getBytes()); messages.add(message1); messages.add(message2); return messages; } @Test public void testGenerate_OK() throws Exception { List messages = generateMessages(); MessageBatch.generateFromList(messages); } @Test(expected = UnsupportedOperationException.class) public void testGenerate_DiffTopic() throws Exception { List messages = generateMessages(); messages.get(1).setTopic("topic2"); MessageBatch.generateFromList(messages); } @Test(expected = UnsupportedOperationException.class) public void testGenerate_DiffWaitOK() throws Exception { List messages = generateMessages(); messages.get(1).setWaitStoreMsgOK(false); MessageBatch.generateFromList(messages); } @Test(expected = UnsupportedOperationException.class) public void testGenerate_Delay() throws Exception { List messages = generateMessages(); messages.get(1).setDelayTimeLevel(1); MessageBatch.generateFromList(messages); } @Test(expected = UnsupportedOperationException.class) public void testGenerate_Retry() throws Exception { List messages = generateMessages(); messages.get(1).setTopic(MixAll.RETRY_GROUP_TOPIC_PREFIX + "topic"); MessageBatch.generateFromList(messages); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/MessageEncodeDecodeTest.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.junit.Test; import static org.junit.Assert.assertTrue; public class MessageEncodeDecodeTest { @Test public void testEncodeDecodeSingle() throws Exception { Message message = new Message("topic", "body".getBytes()); message.setFlag(12); message.putUserProperty("key", "value"); byte[] bytes = MessageDecoder.encodeMessage(message); ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.put(bytes); buffer.flip(); Message newMessage = MessageDecoder.decodeMessage(buffer); assertTrue(message.getFlag() == newMessage.getFlag()); assertTrue(newMessage.getProperty("key").equals(newMessage.getProperty("key"))); assertTrue(Arrays.equals(newMessage.getBody(), message.getBody())); } @Test public void testEncodeDecodeList() throws Exception { List messages = new ArrayList<>(128); for (int i = 0; i < 100; i++) { Message message = new Message("topic", ("body" + i).getBytes()); message.setFlag(i); message.putUserProperty("key", "value" + i); messages.add(message); } byte[] bytes = MessageDecoder.encodeMessages(messages); ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.put(bytes); buffer.flip(); List newMsgs = MessageDecoder.decodeMessages(buffer); assertTrue(newMsgs.size() == messages.size()); for (int i = 0; i < newMsgs.size(); i++) { Message message = messages.get(i); Message newMessage = newMsgs.get(i); assertTrue(message.getFlag() == newMessage.getFlag()); assertTrue(newMessage.getProperty("key").equals(newMessage.getProperty("key"))); assertTrue(Arrays.equals(newMessage.getBody(), message.getBody())); } } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/MessageExtBrokerInnerTest.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MessageExtBrokerInnerTest { @Test public void testDeleteProperty() { MessageExtBrokerInner messageExtBrokerInner = new MessageExtBrokerInner(); String propertiesString = ""; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA\u0002KeyA\u0001ValueA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA\u0002KeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyB\u0001ValueB\u0002KeyA\u0001ValueA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002"); propertiesString = "KeyB\u0001ValueB\u0002KeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002"); propertiesString = "KeyB\u0001ValueB\u0002KeyA\u0001ValueA\u0002KeyB\u0001ValueB\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002KeyB\u0001ValueB\u0002"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueB\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueB"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueBKeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueBKeyA\u0001ValueA\u0002"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueBKeyA\u0001"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueBKeyA\u0001"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueBKeyA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueBKeyA"); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/MixAllTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.net.InetAddress; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MixAllTest { @Test public void testGetLocalInetAddress() throws Exception { List localInetAddress = MixAll.getLocalInetAddress(); String local = InetAddress.getLocalHost().getHostAddress(); assertThat(localInetAddress).contains("127.0.0.1"); assertThat(local).isNotNull(); } @Test public void testBrokerVIPChannel() { assertThat(MixAll.brokerVIPChannel(true, "127.0.0.1:10911")).isEqualTo("127.0.0.1:10909"); } @Test public void testCompareAndIncreaseOnly() { AtomicLong target = new AtomicLong(5); assertThat(MixAll.compareAndIncreaseOnly(target, 6)).isTrue(); assertThat(target.get()).isEqualTo(6); assertThat(MixAll.compareAndIncreaseOnly(target, 4)).isFalse(); assertThat(target.get()).isEqualTo(6); } @Test public void testFile2String() throws IOException { String fileName = System.getProperty("java.io.tmpdir") + File.separator + "MixAllTest" + System.currentTimeMillis(); File file = new File(fileName); if (file.exists()) { file.delete(); } file.createNewFile(); PrintWriter out = new PrintWriter(fileName); out.write("TestForMixAll"); out.close(); String string = MixAll.file2String(fileName); assertThat(string).isEqualTo("TestForMixAll"); file.delete(); } @Test public void testString2File() throws IOException { String fileName = System.getProperty("java.io.tmpdir") + File.separator + "MixAllTest" + System.currentTimeMillis(); MixAll.string2File("MixAll_testString2File", fileName); assertThat(MixAll.file2String(fileName)).isEqualTo("MixAll_testString2File"); } @Test public void testIsLmq() { String testLmq = null; assertThat(MixAll.isLmq(testLmq)).isFalse(); testLmq = "lmq"; assertThat(MixAll.isLmq(testLmq)).isFalse(); testLmq = "%LMQ%queue123"; assertThat(MixAll.isLmq(testLmq)).isTrue(); testLmq = "%LMQ%GID_TEST"; assertThat(MixAll.isLmq(testLmq)).isTrue(); } @Test public void testAdjustConfigForPlatform_OnWindows() { if (MixAll.isWindows()) { String configWithSingleBackslash = "data\\path\\config\\file.properties"; String adjusted = MixAll.adjustConfigForPlatform(configWithSingleBackslash); assertThat(adjusted).isEqualTo("data\\\\path\\\\config\\\\file.properties"); String configWithMultipleBackslashes = "C:\\\\RocketMQ\\\\logs\\\\broker.log"; adjusted = MixAll.adjustConfigForPlatform(configWithMultipleBackslashes); assertThat(adjusted).isEqualTo("C:\\\\\\\\RocketMQ\\\\\\\\logs\\\\\\\\broker.log"); String configWithoutBackslash = "listenPort=10911"; adjusted = MixAll.adjustConfigForPlatform(configWithoutBackslash); assertThat(adjusted).isEqualTo("listenPort=10911"); String emptyConfig = ""; adjusted = MixAll.adjustConfigForPlatform(emptyConfig); assertThat(adjusted).isEqualTo(""); adjusted = MixAll.adjustConfigForPlatform(null); assertThat(adjusted).isNull(); } else { String configWithSingleBackslash = "/home/rocketmq/conf/broker.conf"; String adjusted = MixAll.adjustConfigForPlatform(configWithSingleBackslash); assertThat(adjusted).isEqualTo("/home/rocketmq/conf/broker.conf"); String linuxPathWithBackslash = "some\\directory\\file.txt"; adjusted = MixAll.adjustConfigForPlatform(linuxPathWithBackslash); assertThat(adjusted).isEqualTo("some\\directory\\file.txt"); String emptyConfig = ""; adjusted = MixAll.adjustConfigForPlatform(emptyConfig); assertThat(adjusted).isEqualTo(""); adjusted = MixAll.adjustConfigForPlatform(null); assertThat(adjusted).isNull(); } } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/NetworkUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.apache.rocketmq.common.utils.NetworkUtil; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class NetworkUtilTest { @Test public void testGetLocalAddress() { String localAddress = NetworkUtil.getLocalAddress(); assertThat(localAddress).isNotNull(); assertThat(localAddress.length()).isGreaterThan(0); } @Test public void testConvert2IpStringWithIp() { String result = NetworkUtil.convert2IpString("127.0.0.1:9876"); assertThat(result).isEqualTo("127.0.0.1:9876"); } @Test public void testConvert2IpStringWithHost() { String result = NetworkUtil.convert2IpString("localhost:9876"); assertThat(result).isEqualTo("127.0.0.1:9876"); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/ServiceThreadTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import static org.junit.Assert.assertEquals; import org.junit.Test; public class ServiceThreadTest { @Test public void testShutdown() { shutdown(false, false); shutdown(false, true); shutdown(true, false); shutdown(true, true); } @Test public void testMakeStop() { ServiceThread testServiceThread = startTestServiceThread(); testServiceThread.makeStop(); assertEquals(true, testServiceThread.isStopped()); } @Test public void testWakeup() { ServiceThread testServiceThread = startTestServiceThread(); testServiceThread.wakeup(); assertEquals(true, testServiceThread.hasNotified.get()); assertEquals(0, testServiceThread.waitPoint.getCount()); } @Test public void testWaitForRunning() { ServiceThread testServiceThread = startTestServiceThread(); // test waitForRunning testServiceThread.waitForRunning(1000); assertEquals(false, testServiceThread.hasNotified.get()); assertEquals(1, testServiceThread.waitPoint.getCount()); // test wake up testServiceThread.wakeup(); assertEquals(true, testServiceThread.hasNotified.get()); assertEquals(0, testServiceThread.waitPoint.getCount()); // repeat waitForRunning testServiceThread.waitForRunning(1000); assertEquals(false, testServiceThread.hasNotified.get()); assertEquals(0, testServiceThread.waitPoint.getCount()); // repeat waitForRunning again testServiceThread.waitForRunning(1000); assertEquals(false, testServiceThread.hasNotified.get()); assertEquals(1, testServiceThread.waitPoint.getCount()); } private ServiceThread startTestServiceThread() { return startTestServiceThread(false); } private ServiceThread startTestServiceThread(boolean daemon) { ServiceThread testServiceThread = new ServiceThread() { @Override public void run() { doNothing(); } private void doNothing() {} @Override public String getServiceName() { return "TestServiceThread"; } }; testServiceThread.setDaemon(daemon); // test start testServiceThread.start(); assertEquals(false, testServiceThread.isStopped()); return testServiceThread; } public void shutdown(boolean daemon, boolean interrupt) { ServiceThread testServiceThread = startTestServiceThread(daemon); shutdown0(interrupt, testServiceThread); // repeat shutdown0(interrupt, testServiceThread); } private void shutdown0(boolean interrupt, ServiceThread testServiceThread) { if (interrupt) { testServiceThread.shutdown(true); } else { testServiceThread.shutdown(); } assertEquals(true, testServiceThread.isStopped()); assertEquals(true, testServiceThread.hasNotified.get()); assertEquals(0, testServiceThread.waitPoint.getCount()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/TopicConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class TopicConfigTest { String topicName = "topic"; int queueNums = 8; int perm = PermName.PERM_READ | PermName.PERM_WRITE; TopicFilterType topicFilterType = TopicFilterType.SINGLE_TAG; @Test public void testEncode() { TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setReadQueueNums(queueNums); topicConfig.setWriteQueueNums(queueNums); topicConfig.setPerm(perm); topicConfig.setTopicFilterType(topicFilterType); topicConfig.setTopicMessageType(TopicMessageType.FIFO); String encode = topicConfig.encode(); assertThat(encode).isEqualTo("topic 8 8 6 SINGLE_TAG {\"message.type\":\"FIFO\"}"); } @Test public void testDecode() { String encode = "topic 8 8 6 SINGLE_TAG {\"message.type\":\"FIFO\"}"; TopicConfig decodeTopicConfig = new TopicConfig(); decodeTopicConfig.decode(encode); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setReadQueueNums(queueNums); topicConfig.setWriteQueueNums(queueNums); topicConfig.setPerm(perm); topicConfig.setTopicFilterType(topicFilterType); topicConfig.setTopicMessageType(TopicMessageType.FIFO); assertThat(decodeTopicConfig).isEqualTo(topicConfig); } @Test public void testDecodeWhenCompatible() { String encode = "topic 8 8 6 SINGLE_TAG"; TopicConfig decodeTopicConfig = new TopicConfig(); decodeTopicConfig.decode(encode); TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topicName); topicConfig.setReadQueueNums(queueNums); topicConfig.setWriteQueueNums(queueNums); topicConfig.setPerm(perm); topicConfig.setTopicFilterType(topicFilterType); assertThat(decodeTopicConfig).isEqualTo(topicConfig); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common; import java.io.File; import java.io.FileOutputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class UtilAllTest { @Rule public TemporaryFolder tempDir = new TemporaryFolder(); @Test public void testCurrentStackTrace() { String currentStackTrace = UtilAll.currentStackTrace(); assertThat(currentStackTrace).contains("UtilAll.currentStackTrace"); assertThat(currentStackTrace).contains("UtilAllTest.testCurrentStackTrace("); } @Test public void testProperties2Object() { DemoConfig demoConfig = new DemoConfig(); Properties properties = new Properties(); properties.setProperty("demoWidth", "123"); properties.setProperty("demoLength", "456"); properties.setProperty("demoOK", "true"); properties.setProperty("demoName", "TestDemo"); MixAll.properties2Object(properties, demoConfig); assertThat(demoConfig.getDemoLength()).isEqualTo(456); assertThat(demoConfig.getDemoWidth()).isEqualTo(123); assertThat(demoConfig.isDemoOK()).isTrue(); assertThat(demoConfig.getDemoName()).isEqualTo("TestDemo"); } @Test public void testProperties2String() { DemoSubConfig demoConfig = new DemoSubConfig(); demoConfig.setDemoLength(123); demoConfig.setDemoWidth(456); demoConfig.setDemoName("TestDemo"); demoConfig.setDemoOK(true); demoConfig.setSubField0("1"); demoConfig.setSubField1(false); Properties properties = MixAll.object2Properties(demoConfig); assertThat(properties.getProperty("demoLength")).isEqualTo("123"); assertThat(properties.getProperty("demoWidth")).isEqualTo("456"); assertThat(properties.getProperty("demoOK")).isEqualTo("true"); assertThat(properties.getProperty("demoName")).isEqualTo("TestDemo"); assertThat(properties.getProperty("subField0")).isEqualTo("1"); assertThat(properties.getProperty("subField1")).isEqualTo("false"); properties = MixAll.object2Properties(new Object()); assertEquals(0, properties.size()); } @Test public void testIsPropertiesEqual() { final Properties p1 = new Properties(); final Properties p2 = new Properties(); p1.setProperty("a", "1"); p1.setProperty("b", "2"); p2.setProperty("a", "1"); p2.setProperty("b", "2"); assertThat(MixAll.isPropertiesEqual(p1, p2)).isTrue(); } @Test public void testGetPid() { assertThat(UtilAll.getPid()).isGreaterThan(0); } @Test public void testGetDiskPartitionSpaceUsedPercent() { String tmpDir = System.getProperty("java.io.tmpdir"); assertThat(UtilAll.getDiskPartitionSpaceUsedPercent(null)).isCloseTo(-1, within(0.000001)); assertThat(UtilAll.getDiskPartitionSpaceUsedPercent("")).isCloseTo(-1, within(0.000001)); assertThat(UtilAll.getDiskPartitionSpaceUsedPercent("nonExistingPath")).isCloseTo(-1, within(0.000001)); assertThat(UtilAll.getDiskPartitionSpaceUsedPercent(tmpDir)).isNotCloseTo(-1, within(0.000001)); } @Test public void testIsBlank() { assertThat(UtilAll.isBlank("Hello ")).isFalse(); assertThat(UtilAll.isBlank(" Hello")).isFalse(); assertThat(UtilAll.isBlank("He llo")).isFalse(); assertThat(UtilAll.isBlank(" ")).isTrue(); assertThat(UtilAll.isBlank("Hello")).isFalse(); } @Test public void testIPv6Check() throws UnknownHostException { InetAddress nonInternal = InetAddress.getByName("2408:4004:0180:8100:3FAA:1DDE:2B3F:898A"); InetAddress internal = InetAddress.getByName("FE80:0000:0000:0000:0000:0000:0000:FFFF"); assertThat(UtilAll.isInternalV6IP(nonInternal)).isFalse(); assertThat(UtilAll.isInternalV6IP(internal)).isTrue(); assertThat(UtilAll.ipToIPv6Str(nonInternal.getAddress()).toUpperCase()).isEqualTo("2408:4004:0180:8100:3FAA:1DDE:2B3F:898A"); } @Test public void testJoin() { List list = Arrays.asList("groupA=DENY", "groupB=PUB|SUB", "groupC=SUB"); String comma = ","; assertEquals("groupA=DENY,groupB=PUB|SUB,groupC=SUB", UtilAll.join(list, comma)); assertEquals(null, UtilAll.join(null, comma)); List objects = Collections.emptyList(); assertEquals("", UtilAll.join(objects, comma)); } @Test public void testSplit() { List list = Arrays.asList("groupA=DENY", "groupB=PUB|SUB", "groupC=SUB"); String comma = ","; assertEquals(list, UtilAll.split("groupA=DENY,groupB=PUB|SUB,groupC=SUB", comma)); assertEquals(null, UtilAll.split(null, comma)); assertEquals(Collections.EMPTY_LIST, UtilAll.split("", comma)); } static class DemoConfig { private int demoWidth = 0; private int demoLength = 0; private boolean demoOK = false; private String demoName = "haha"; int getDemoWidth() { return demoWidth; } public void setDemoWidth(int demoWidth) { this.demoWidth = demoWidth; } public int getDemoLength() { return demoLength; } public void setDemoLength(int demoLength) { this.demoLength = demoLength; } public boolean isDemoOK() { return demoOK; } public void setDemoOK(boolean demoOK) { this.demoOK = demoOK; } public String getDemoName() { return demoName; } public void setDemoName(String demoName) { this.demoName = demoName; } @Override public String toString() { return "DemoConfig{" + "demoWidth=" + demoWidth + ", demoLength=" + demoLength + ", demoOK=" + demoOK + ", demoName='" + demoName + '\'' + '}'; } } static class DemoSubConfig extends DemoConfig { private String subField0 = "0"; public boolean subField1 = true; public String getSubField0() { return subField0; } public void setSubField0(String subField0) { this.subField0 = subField0; } public boolean isSubField1() { return subField1; } public void setSubField1(boolean subField1) { this.subField1 = subField1; } } @Test public void testCleanBuffer() { UtilAll.cleanBuffer(null); UtilAll.cleanBuffer(ByteBuffer.allocateDirect(10)); UtilAll.cleanBuffer(ByteBuffer.allocateDirect(0)); UtilAll.cleanBuffer(ByteBuffer.allocate(10)); } @Test public void testCalculateFileSizeInPath() throws Exception { /** * testCalculateFileSizeInPath * - file_0 * - dir_1 * - file_1_0 * - file_1_1 * - dir_1_2 * - file_1_2_0 * - dir_2 */ File baseFile = tempDir.getRoot(); // test empty path assertEquals(0, UtilAll.calculateFileSizeInPath(baseFile)); File file0 = new File(baseFile, "file_0"); assertTrue(file0.createNewFile()); writeFixedBytesToFile(file0, 1313); assertEquals(1313, UtilAll.calculateFileSizeInPath(baseFile)); // build a file tree like above File dir1 = new File(baseFile, "dir_1"); dir1.mkdirs(); File file10 = new File(dir1, "file_1_0"); File file11 = new File(dir1, "file_1_1"); File dir12 = new File(dir1, "dir_1_2"); dir12.mkdirs(); File file120 = new File(dir12, "file_1_2_0"); File dir2 = new File(baseFile, "dir_2"); dir2.mkdirs(); // write all file with 1313 bytes data assertTrue(file10.createNewFile()); writeFixedBytesToFile(file10, 1313); assertTrue(file11.createNewFile()); writeFixedBytesToFile(file11, 1313); assertTrue(file120.createNewFile()); writeFixedBytesToFile(file120, 1313); assertEquals(1313 * 4, UtilAll.calculateFileSizeInPath(baseFile)); } private void writeFixedBytesToFile(File file, int size) throws Exception { FileOutputStream outputStream = new FileOutputStream(file); byte[] bytes = new byte[size]; outputStream.write(bytes, 0, size); outputStream.close(); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/action/ActionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.action; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class ActionTest { @Test public void getByName_NullName_ReturnsNull() { Action result = Action.getByName("null"); assertNull(result); } @Test public void getByName_UnknownName_ReturnsUnknown() { Action result = Action.getByName("unknown"); assertEquals(Action.UNKNOWN, result); } @Test public void getByName_AllName_ReturnsAll() { Action result = Action.getByName("All"); assertEquals(Action.ALL, result); } @Test public void getByName_AnyName_ReturnsAny() { Action result = Action.getByName("Any"); assertEquals(Action.ANY, result); } @Test public void getByName_PubName_ReturnsPub() { Action result = Action.getByName("Pub"); assertEquals(Action.PUB, result); } @Test public void getByName_SubName_ReturnsSub() { Action result = Action.getByName("Sub"); assertEquals(Action.SUB, result); } @Test public void getByName_CreateName_ReturnsCreate() { Action result = Action.getByName("Create"); assertEquals(Action.CREATE, result); } @Test public void getByName_UpdateName_ReturnsUpdate() { Action result = Action.getByName("Update"); assertEquals(Action.UPDATE, result); } @Test public void getByName_DeleteName_ReturnsDelete() { Action result = Action.getByName("Delete"); assertEquals(Action.DELETE, result); } @Test public void getByName_GetName_ReturnsGet() { Action result = Action.getByName("Get"); assertEquals(Action.GET, result); } @Test public void getByName_ListName_ReturnsList() { Action result = Action.getByName("List"); assertEquals(Action.LIST, result); } @Test public void getCode_ReturnsCorrectCode() { assertEquals((byte) 1, Action.ALL.getCode()); assertEquals((byte) 2, Action.ANY.getCode()); assertEquals((byte) 3, Action.PUB.getCode()); assertEquals((byte) 4, Action.SUB.getCode()); assertEquals((byte) 5, Action.CREATE.getCode()); assertEquals((byte) 6, Action.UPDATE.getCode()); assertEquals((byte) 7, Action.DELETE.getCode()); assertEquals((byte) 8, Action.GET.getCode()); assertEquals((byte) 9, Action.LIST.getCode()); } @Test public void getName_ReturnsCorrectName() { assertEquals("All", Action.ALL.getName()); assertEquals("Any", Action.ANY.getName()); assertEquals("Pub", Action.PUB.getName()); assertEquals("Sub", Action.SUB.getName()); assertEquals("Create", Action.CREATE.getName()); assertEquals("Update", Action.UPDATE.getName()); assertEquals("Delete", Action.DELETE.getName()); assertEquals("Get", Action.GET.getName()); assertEquals("List", Action.LIST.getName()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/action/RocketMQActionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.action; import org.apache.rocketmq.common.resource.ResourceType; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; public class RocketMQActionTest { @Test public void testRocketMQAction_DefaultResourceType_CustomisedValueAndActionArray() { RocketMQAction annotation = DemoClass.class.getAnnotation(RocketMQAction.class); assertEquals(0, annotation.value()); assertEquals(ResourceType.UNKNOWN, annotation.resource()); assertArrayEquals(new Action[] {}, annotation.action()); } @Test public void testRocketMQAction_CustomisedValueAndResourceTypeAndActionArray() { RocketMQAction annotation = CustomisedDemoClass.class.getAnnotation(RocketMQAction.class); assertEquals(1, annotation.value()); assertEquals(ResourceType.TOPIC, annotation.resource()); assertArrayEquals(new Action[] {Action.CREATE, Action.DELETE}, annotation.action()); } @RocketMQAction(value = 0, resource = ResourceType.UNKNOWN, action = {}) private static class DemoClass { } @RocketMQAction(value = 1, resource = ResourceType.TOPIC, action = {Action.CREATE, Action.DELETE}) private static class CustomisedDemoClass { } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/AttributeParserTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import com.google.common.collect.Maps; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Assert; import org.junit.Test; import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class AttributeParserTest { @Test public void parseToMap_EmptyString_ReturnsEmptyMap() { String attributesModification = ""; Map result = AttributeParser.parseToMap(attributesModification); assertTrue(result.isEmpty()); } @Test public void parseToMap_NullString_ReturnsEmptyMap() { String attributesModification = null; Map result = AttributeParser.parseToMap(attributesModification); assertTrue(result.isEmpty()); } @Test public void parseToMap_ValidAttributesModification_ReturnsExpectedMap() { String attributesModification = "+key1=value1,+key2=value2,-key3,+key4=value4"; Map result = AttributeParser.parseToMap(attributesModification); Map expectedMap = new HashMap<>(); expectedMap.put("+key1", "value1"); expectedMap.put("+key2", "value2"); expectedMap.put("-key3", ""); expectedMap.put("+key4", "value4"); assertEquals(expectedMap, result); } @Test(expected = RuntimeException.class) public void parseToMap_InvalidAddAttributeFormat_ThrowsRuntimeException() { String attributesModification = "+key1=value1,key2=value2,-key3,+key4=value4"; AttributeParser.parseToMap(attributesModification); } @Test(expected = RuntimeException.class) public void parseToMap_InvalidDeleteAttributeFormat_ThrowsRuntimeException() { String attributesModification = "+key1=value1,+key2=value2,key3,+key4=value4"; AttributeParser.parseToMap(attributesModification); } @Test(expected = RuntimeException.class) public void parseToMap_DuplicateKey_ThrowsRuntimeException() { String attributesModification = "+key1=value1,+key1=value2"; AttributeParser.parseToMap(attributesModification); } @Test public void parseToString_EmptyMap_ReturnsEmptyString() { Map attributes = new HashMap<>(); String result = AttributeParser.parseToString(attributes); assertEquals("", result); } @Test public void parseToString_ValidAttributes_ReturnsExpectedString() { Map attributes = new HashMap<>(); attributes.put("key1", "value1"); attributes.put("key2", "value2"); attributes.put("key3", ""); String result = AttributeParser.parseToString(attributes); String expectedString = "key1=value1,key2=value2,key3"; assertEquals(expectedString, result); } @Test public void testParseToMap() { Assert.assertEquals(0, AttributeParser.parseToMap(null).size()); AttributeParser.parseToMap("++=++"); AttributeParser.parseToMap("--"); Assert.assertThrows(RuntimeException.class, () -> AttributeParser.parseToMap("x")); Assert.assertThrows(RuntimeException.class, () -> AttributeParser.parseToMap("+")); Assert.assertThrows(RuntimeException.class, () -> AttributeParser.parseToMap("++")); } @Test public void testParseToString() { Assert.assertEquals("", AttributeParser.parseToString(null)); Assert.assertEquals("", AttributeParser.parseToString(newHashMap())); HashMap map = new HashMap<>(); int addSize = 10; for (int i = 0; i < addSize; i++) { map.put("+add.key" + i, "value" + i); } int deleteSize = 10; for (int i = 0; i < deleteSize; i++) { map.put("-delete.key" + i, ""); } Assert.assertEquals(addSize + deleteSize, AttributeParser.parseToString(map).split(",").length); } @Test public void testParseBetweenStringAndMapWithoutDistortion() { List testCases = Arrays.asList("-a", "+a=b,+c=d,+z=z,+e=e", "+a=b,-d", "+a=b", "-a,-b"); for (String testCase : testCases) { assertTrue(Maps.difference(AttributeParser.parseToMap(testCase), AttributeParser.parseToMap(parse(testCase))).areEqual()); } } private String parse(String original) { Map stringStringMap = AttributeParser.parseToMap(original); return AttributeParser.parseToString(stringStringMap); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/AttributeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class AttributeTest { private Attribute attribute; @Before public void setUp() { attribute = new Attribute("testAttribute", true) { @Override public void verify(String value) { throw new UnsupportedOperationException(); } }; } @Test public void testGetName_ShouldReturnCorrectName() { assertEquals("testAttribute", attribute.getName()); } @Test public void testSetName_ShouldSetCorrectName() { attribute.setName("newTestAttribute"); assertEquals("newTestAttribute", attribute.getName()); } @Test public void testIsChangeable_ShouldReturnCorrectChangeableStatus() { assertTrue(attribute.isChangeable()); } @Test public void testSetChangeable_ShouldSetCorrectChangeableStatus() { attribute.setChangeable(false); assertFalse(attribute.isChangeable()); } @Test(expected = UnsupportedOperationException.class) public void testVerify_ShouldThrowUnsupportedOperationException() { attribute.verify("testValue"); } @Test public void testEnumAttribute() { EnumAttribute enumAttribute = new EnumAttribute("enum.key", true, newHashSet("enum-1", "enum-2", "enum-3"), "enum-1"); Assert.assertThrows(RuntimeException.class, () -> enumAttribute.verify("")); Assert.assertThrows(RuntimeException.class, () -> enumAttribute.verify("x")); Assert.assertThrows(RuntimeException.class, () -> enumAttribute.verify("enum-4")); enumAttribute.verify("enum-1"); enumAttribute.verify("enum-2"); enumAttribute.verify("enum-3"); } @Test public void testLongRangeAttribute() { LongRangeAttribute longRangeAttribute = new LongRangeAttribute("long.range.key", true, 10, 20, 15); Assert.assertThrows(RuntimeException.class, () -> longRangeAttribute.verify("")); Assert.assertThrows(RuntimeException.class, () -> longRangeAttribute.verify(",")); Assert.assertThrows(RuntimeException.class, () -> longRangeAttribute.verify("a")); Assert.assertThrows(RuntimeException.class, () -> longRangeAttribute.verify("-1")); Assert.assertThrows(RuntimeException.class, () -> longRangeAttribute.verify("21")); longRangeAttribute.verify("11"); longRangeAttribute.verify("10"); longRangeAttribute.verify("20"); } @Test public void testBooleanAttribute() { BooleanAttribute booleanAttribute = new BooleanAttribute("bool.key", false, false); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify("")); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify("a")); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify(",")); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify("checked")); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify("1")); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify("0")); Assert.assertThrows(RuntimeException.class, () -> booleanAttribute.verify("-1")); booleanAttribute.verify("true"); booleanAttribute.verify("tRue"); booleanAttribute.verify("false"); booleanAttribute.verify("falSe"); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/AttributeUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class AttributeUtilTest { private Map allAttributes; private ImmutableMap currentAttributes; @Before public void setUp() { allAttributes = new HashMap<>(); allAttributes.put("attr1", new TestAttribute("value1", true, value -> true)); allAttributes.put("attr2", new TestAttribute("value2", true, value -> true)); allAttributes.put("attr3", new TestAttribute("value3", true, value -> value.equals("valid"))); currentAttributes = ImmutableMap.of("attr1", "value1", "attr2", "value2"); } @Test public void alterCurrentAttributes_CreateMode_ShouldReturnOnlyAddedAttributes() { ImmutableMap newAttributes = ImmutableMap.of("+attr1", "new_value1", "+attr2", "value2", "+attr3", "value3"); Map result = AttributeUtil.alterCurrentAttributes(true, allAttributes, currentAttributes, newAttributes); assertEquals(3, result.size()); assertTrue(result.containsKey("attr1")); assertEquals("new_value1", result.get("attr1")); assertTrue(result.containsKey("attr3")); assertEquals("value3", result.get("attr3")); assertTrue(result.containsKey("attr2")); } @Test(expected = RuntimeException.class) public void alterCurrentAttributes_CreateMode_AddNonAddableAttribute_ShouldThrowException() { ImmutableMap newAttributes = ImmutableMap.of("attr1", "value1"); AttributeUtil.alterCurrentAttributes(true, allAttributes, currentAttributes, newAttributes); } @Test public void alterCurrentAttributes_UpdateMode_ShouldReturnUpdatedAndAddedAttributes() { ImmutableMap newAttributes = ImmutableMap.of("+attr1", "new_value1", "-attr2", "value2", "+attr3", "value3"); Map result = AttributeUtil.alterCurrentAttributes(false, allAttributes, currentAttributes, newAttributes); assertEquals(2, result.size()); assertTrue(result.containsKey("attr1")); assertEquals("new_value1", result.get("attr1")); assertTrue(result.containsKey("attr3")); assertEquals("value3", result.get("attr3")); assertFalse(result.containsKey("attr2")); } @Test(expected = RuntimeException.class) public void alterCurrentAttributes_UpdateMode_DeleteNonExistentAttribute_ShouldThrowException() { ImmutableMap newAttributes = ImmutableMap.of("-attr4", "value4"); AttributeUtil.alterCurrentAttributes(false, allAttributes, currentAttributes, newAttributes); } @Test(expected = RuntimeException.class) public void alterCurrentAttributes_UpdateMode_WrongFormatKey_ShouldThrowException() { ImmutableMap newAttributes = ImmutableMap.of("attr1", "+value1"); AttributeUtil.alterCurrentAttributes(false, allAttributes, currentAttributes, newAttributes); } @Test(expected = RuntimeException.class) public void alterCurrentAttributes_UnsupportedKey_ShouldThrowException() { ImmutableMap newAttributes = ImmutableMap.of("unsupported_attr", "value"); AttributeUtil.alterCurrentAttributes(false, allAttributes, currentAttributes, newAttributes); } @Test(expected = RuntimeException.class) public void alterCurrentAttributes_AttemptToUpdateUnchangeableAttribute_ShouldThrowException() { ImmutableMap newAttributes = ImmutableMap.of("attr2", "new_value2"); AttributeUtil.alterCurrentAttributes(false, allAttributes, currentAttributes, newAttributes); } private static class TestAttribute extends Attribute { private final AttributeValidator validator; public TestAttribute(String name, boolean changeable, AttributeValidator validator) { super(name, changeable); this.validator = validator; } @Override public void verify(String value) { validator.validate(value); } } private interface AttributeValidator { boolean validate(String value); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/BooleanAttributeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; public class BooleanAttributeTest { private BooleanAttribute booleanAttribute; @Before public void setUp() { booleanAttribute = new BooleanAttribute("testAttribute", true, false); } @Test public void testVerify_ValidValue_NoExceptionThrown() { booleanAttribute.verify("true"); booleanAttribute.verify("false"); } @Test public void testVerify_InvalidValue_ExceptionThrown() { assertThrows(RuntimeException.class, () -> booleanAttribute.verify("invalid")); assertThrows(RuntimeException.class, () -> booleanAttribute.verify("1")); assertThrows(RuntimeException.class, () -> booleanAttribute.verify("0")); assertThrows(RuntimeException.class, () -> booleanAttribute.verify("")); } @Test public void testGetDefaultValue() { assertFalse(booleanAttribute.getDefaultValue()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/CQTypeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import org.junit.Test; import static org.junit.Assert.assertEquals; public class CQTypeTest { @Test public void testValues() { CQType[] values = CQType.values(); assertEquals(3, values.length); assertEquals(CQType.SimpleCQ, values[0]); assertEquals(CQType.BatchCQ, values[1]); assertEquals(CQType.RocksDBCQ, values[2]); } @Test public void testValueOf() { assertEquals(CQType.SimpleCQ, CQType.valueOf("SimpleCQ")); assertEquals(CQType.BatchCQ, CQType.valueOf("BatchCQ")); assertEquals(CQType.RocksDBCQ, CQType.valueOf("RocksDBCQ")); } @Test(expected = IllegalArgumentException.class) public void testValueOf_InvalidName() { CQType.valueOf("InvalidCQ"); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/CleanupPolicyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import org.junit.Test; import static org.junit.Assert.assertEquals; public class CleanupPolicyTest { @Test public void testCleanupPolicy_Delete() { CleanupPolicy cleanupPolicy = CleanupPolicy.DELETE; assertEquals("DELETE", cleanupPolicy.toString()); } @Test public void testCleanupPolicy_Compaction() { CleanupPolicy cleanupPolicy = CleanupPolicy.COMPACTION; assertEquals("COMPACTION", cleanupPolicy.toString()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/EnumAttributeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import org.junit.Before; import org.junit.Test; import java.util.HashSet; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; public class EnumAttributeTest { private EnumAttribute enumAttribute; @Before public void setUp() { Set universe = new HashSet<>(); universe.add("value1"); universe.add("value2"); universe.add("value3"); enumAttribute = new EnumAttribute("testAttribute", true, universe, "value1"); } @Test public void verify_ValidValue_NoExceptionThrown() { enumAttribute.verify("value1"); enumAttribute.verify("value2"); enumAttribute.verify("value3"); } @Test public void verify_InvalidValue_ExceptionThrown() { RuntimeException exception = assertThrows(RuntimeException.class, () -> { enumAttribute.verify("invalidValue"); }); assertTrue(exception.getMessage().startsWith("value is not in set:")); } @Test public void getDefaultValue_ReturnsDefaultValue() { assertEquals("value1", enumAttribute.getDefaultValue()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/LongRangeAttributeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; public class LongRangeAttributeTest { private LongRangeAttribute longRangeAttribute; @Before public void setUp() { longRangeAttribute = new LongRangeAttribute("testAttribute", true, 0, 100, 50); } @Test public void verify_ValidValue_NoExceptionThrown() { longRangeAttribute.verify("50"); } @Test public void verify_MinValue_NoExceptionThrown() { longRangeAttribute.verify("0"); } @Test public void verify_MaxValue_NoExceptionThrown() { longRangeAttribute.verify("100"); } @Test public void verify_ValueLessThanMin_ThrowsRuntimeException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> longRangeAttribute.verify("-1")); assertEquals("value is not in range(0, 100)", exception.getMessage()); } @Test public void verify_ValueGreaterThanMax_ThrowsRuntimeException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> longRangeAttribute.verify("101")); assertEquals("value is not in range(0, 100)", exception.getMessage()); } @Test public void getDefaultValue_ReturnsDefaultValue() { assertEquals(50, longRangeAttribute.getDefaultValue()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/attribute/TopicMessageTypeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.attribute; import com.google.common.collect.Sets; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.rocketmq.common.message.MessageConst; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class TopicMessageTypeTest { private Map normalMessageProperty; private Map transactionMessageProperty; private Map delayMessageProperty; private Map fifoMessageProperty; private Map priorityMessageProperty; @Before public void setUp() { normalMessageProperty = new HashMap<>(); transactionMessageProperty = new HashMap<>(); delayMessageProperty = new HashMap<>(); fifoMessageProperty = new HashMap<>(); priorityMessageProperty = new HashMap<>(); transactionMessageProperty.put(MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); delayMessageProperty.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "1"); fifoMessageProperty.put(MessageConst.PROPERTY_SHARDING_KEY, "shardingKey"); priorityMessageProperty.put(MessageConst.PROPERTY_PRIORITY, "3"); } @Test public void testTopicMessageTypeSet() { Set expectedSet = Sets.newHashSet("UNSPECIFIED", "NORMAL", "FIFO", "DELAY", "TRANSACTION", "PRIORITY", "LITE", "MIXED"); Set actualSet = TopicMessageType.topicMessageTypeSet(); assertEquals(expectedSet, actualSet); } @Test public void testParseFromMessageProperty_Normal() { TopicMessageType actual = TopicMessageType.parseFromMessageProperty(normalMessageProperty); assertEquals(TopicMessageType.NORMAL, actual); } @Test public void testParseFromMessageProperty_Transaction() { TopicMessageType actual = TopicMessageType.parseFromMessageProperty(transactionMessageProperty); assertEquals(TopicMessageType.TRANSACTION, actual); } @Test public void testParseFromMessageProperty_Delay() { TopicMessageType actual = TopicMessageType.parseFromMessageProperty(delayMessageProperty); assertEquals(TopicMessageType.DELAY, actual); } @Test public void testParseFromMessageProperty_Fifo() { TopicMessageType actual = TopicMessageType.parseFromMessageProperty(fifoMessageProperty); assertEquals(TopicMessageType.FIFO, actual); } @Test public void testParseFromMessageProperty_Priority() { TopicMessageType actual = TopicMessageType.parseFromMessageProperty(priorityMessageProperty); assertEquals(TopicMessageType.PRIORITY, actual); } @Test public void testGetMetricsValue() { for (TopicMessageType type : TopicMessageType.values()) { String expected = type.getValue().toLowerCase(); String actual = type.getMetricsValue(); assertEquals(expected, actual); } } @Test public void testParseFromMessageProperty() { Map properties = new HashMap<>(); // TRANSACTION properties.put(MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); Assert.assertEquals(TopicMessageType.TRANSACTION, TopicMessageType.parseFromMessageProperty(properties)); // DELAY properties.clear(); properties.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3"); Assert.assertEquals(TopicMessageType.DELAY, TopicMessageType.parseFromMessageProperty(properties)); properties.clear(); properties.put(MessageConst.PROPERTY_TIMER_DELIVER_MS, System.currentTimeMillis() + 10000 + ""); Assert.assertEquals(TopicMessageType.DELAY, TopicMessageType.parseFromMessageProperty(properties)); properties.clear(); properties.put(MessageConst.PROPERTY_TIMER_DELAY_SEC, 10 + ""); Assert.assertEquals(TopicMessageType.DELAY, TopicMessageType.parseFromMessageProperty(properties)); properties.clear(); properties.put(MessageConst.PROPERTY_TIMER_DELAY_MS, 10000 + ""); Assert.assertEquals(TopicMessageType.DELAY, TopicMessageType.parseFromMessageProperty(properties)); // FIFO properties.clear(); properties.put(MessageConst.PROPERTY_SHARDING_KEY, "sharding_key"); Assert.assertEquals(TopicMessageType.FIFO, TopicMessageType.parseFromMessageProperty(properties)); // PRIORITY properties.clear(); properties.put(MessageConst.PROPERTY_PRIORITY, "3"); Assert.assertEquals(TopicMessageType.PRIORITY, TopicMessageType.parseFromMessageProperty(properties)); properties.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3"); Assert.assertEquals(TopicMessageType.DELAY, TopicMessageType.parseFromMessageProperty(properties)); // NORMAL properties.clear(); Assert.assertEquals(TopicMessageType.NORMAL, TopicMessageType.parseFromMessageProperty(properties)); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/chain/HandlerChainTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.chain; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class HandlerChainTest { private HandlerChain handlerChain; private Handler handler1; private Handler handler2; @Before public void setUp() { handlerChain = HandlerChain.create(); handler1 = (t, chain) -> "Handler1"; handler2 = (t, chain) -> null; } @Test public void testHandle_withEmptyChain() { handlerChain.addNext(handler1); handlerChain.handle(1); assertNull("Expected null since the handler chain is empty", handlerChain.handle(2)); } @Test public void testHandle_withNonEmptyChain() { handlerChain.addNext(handler1); String result = handlerChain.handle(1); assertEquals("Handler1", result); } @Test public void testHandle_withMultipleHandlers() { handlerChain.addNext(handler1); handlerChain.addNext(handler2); String result1 = handlerChain.handle(1); String result2 = handlerChain.handle(2); assertEquals("Handler1", result1); assertNull("Expected null since there are no more handlers", result2); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/coldctr/AccAndTimeStampTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.coldctr; import java.util.concurrent.atomic.AtomicLong; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class AccAndTimeStampTest { private AccAndTimeStamp accAndTimeStamp; @Before public void setUp() { accAndTimeStamp = new AccAndTimeStamp(new AtomicLong()); } @Test public void testInitialValues() { assertEquals("Cold accumulator should be initialized to 0", 0, accAndTimeStamp.getColdAcc().get()); assertTrue("Last cold read time should be initialized to current time", accAndTimeStamp.getLastColdReadTimeMills() >= System.currentTimeMillis() - 1000); assertTrue("Create time should be initialized to current time", accAndTimeStamp.getCreateTimeMills() >= System.currentTimeMillis() - 1000); } @Test public void testSetColdAcc() { AtomicLong newColdAcc = new AtomicLong(100L); accAndTimeStamp.setColdAcc(newColdAcc); assertEquals("Cold accumulator should be set to new value", newColdAcc, accAndTimeStamp.getColdAcc()); } @Test public void testSetLastColdReadTimeMills() { long newLastColdReadTimeMills = System.currentTimeMillis() + 1000; accAndTimeStamp.setLastColdReadTimeMills(newLastColdReadTimeMills); assertEquals("Last cold read time should be set to new value", newLastColdReadTimeMills, accAndTimeStamp.getLastColdReadTimeMills().longValue()); } @Test public void testSetCreateTimeMills() { long newCreateTimeMills = System.currentTimeMillis() + 2000; accAndTimeStamp.setCreateTimeMills(newCreateTimeMills); assertEquals("Create time should be set to new value", newCreateTimeMills, accAndTimeStamp.getCreateTimeMills().longValue()); } @Test public void testToStringContainsCorrectInformation() { String toStringOutput = accAndTimeStamp.toString(); assertTrue("ToString should contain cold accumulator value", toStringOutput.contains("coldAcc=" + accAndTimeStamp.getColdAcc())); assertTrue("ToString should contain last cold read time", toStringOutput.contains("lastColdReadTimeMills=" + accAndTimeStamp.getLastColdReadTimeMills())); assertTrue("ToString should contain create time", toStringOutput.contains("createTimeMills=" + accAndTimeStamp.getCreateTimeMills())); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/compression/CompressionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Random; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class CompressionTest { private int level; private Compressor zstd; private Compressor zlib; private Compressor lz4; @Before public void setUp() { level = 5; zstd = CompressorFactory.getCompressor(CompressionType.ZSTD); zlib = CompressorFactory.getCompressor(CompressionType.ZLIB); lz4 = CompressorFactory.getCompressor(CompressionType.LZ4); } @Test public void testCompressionZlib() throws IOException { assertThat(CompressionType.of("zlib")).isEqualTo(CompressionType.ZLIB); assertThat(CompressionType.of(" ZLiB ")).isEqualTo(CompressionType.ZLIB); assertThat(CompressionType.of("ZLIB")).isEqualTo(CompressionType.ZLIB); int randomKB = 4096; Random random = new Random(); for (int i = 0; i < 5; ++i) { String message = RandomStringUtils.randomAlphanumeric(random.nextInt(randomKB) * 1024); byte[] srcBytes = message.getBytes(StandardCharsets.UTF_8); byte[] compressed = zlib.compress(srcBytes, level); byte[] decompressed = zlib.decompress(compressed); // compression ratio may be negative for some random string data // assertThat(compressed.length).isLessThan(srcBytes.length); assertThat(decompressed).isEqualTo(srcBytes); assertThat(new String(decompressed)).isEqualTo(message); } } @Test public void testCompressionZstd() throws IOException { assertThat(CompressionType.of("zstd")).isEqualTo(CompressionType.ZSTD); assertThat(CompressionType.of("ZStd ")).isEqualTo(CompressionType.ZSTD); assertThat(CompressionType.of("ZSTD")).isEqualTo(CompressionType.ZSTD); int randomKB = 4096; Random random = new Random(); for (int i = 0; i < 5; ++i) { String message = RandomStringUtils.randomAlphanumeric(random.nextInt(randomKB) * 1024); byte[] srcBytes = message.getBytes(StandardCharsets.UTF_8); byte[] compressed = zstd.compress(srcBytes, level); byte[] decompressed = zstd.decompress(compressed); // compression ratio may be negative for some random string data // assertThat(compressed.length).isLessThan(srcBytes.length); assertThat(decompressed).isEqualTo(srcBytes); assertThat(new String(decompressed)).isEqualTo(message); } } @Test public void testCompressionLz4() throws IOException { assertThat(CompressionType.of("lz4")).isEqualTo(CompressionType.LZ4); int randomKB = 4096; Random random = new Random(); for (int i = 0; i < 5; ++i) { String message = RandomStringUtils.randomAlphanumeric(random.nextInt(randomKB) * 1024); byte[] srcBytes = message.getBytes(StandardCharsets.UTF_8); byte[] compressed = lz4.compress(srcBytes, level); byte[] decompressed = lz4.decompress(compressed); // compression ratio may be negative for some random string data // assertThat(compressed.length).isLessThan(srcBytes.length); assertThat(decompressed).isEqualTo(srcBytes); assertThat(new String(decompressed)).isEqualTo(message); } } @Test(expected = RuntimeException.class) public void testCompressionUnsupportedType() { CompressionType.of("snappy"); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/compression/CompressionTypeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; public class CompressionTypeTest { @Test public void testCompressionTypeValues() { assertEquals(1, CompressionType.LZ4.getValue()); assertEquals(2, CompressionType.ZSTD.getValue()); assertEquals(3, CompressionType.ZLIB.getValue()); } @Test public void testCompressionTypeOf() { assertEquals(CompressionType.LZ4, CompressionType.of("LZ4")); assertEquals(CompressionType.ZSTD, CompressionType.of("ZSTD")); assertEquals(CompressionType.ZLIB, CompressionType.of("ZLIB")); assertThrows(RuntimeException.class, () -> CompressionType.of("UNKNOWN")); } @Test public void testCompressionTypeFindByValue() { assertEquals(CompressionType.LZ4, CompressionType.findByValue(1)); assertEquals(CompressionType.ZSTD, CompressionType.findByValue(2)); assertEquals(CompressionType.ZLIB, CompressionType.findByValue(3)); assertEquals(CompressionType.ZLIB, CompressionType.findByValue(0)); assertThrows(RuntimeException.class, () -> CompressionType.findByValue(99)); } @Test public void testCompressionFlag() { assertEquals(MessageSysFlag.COMPRESSION_LZ4_TYPE, CompressionType.LZ4.getCompressionFlag()); assertEquals(MessageSysFlag.COMPRESSION_ZSTD_TYPE, CompressionType.ZSTD.getCompressionFlag()); assertEquals(MessageSysFlag.COMPRESSION_ZLIB_TYPE, CompressionType.ZLIB.getCompressionFlag()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/compression/CompressorFactoryTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import org.junit.Assert; import org.junit.Test; public class CompressorFactoryTest { @Test public void testGetCompressor_ReturnsNonNull() { for (CompressionType type : CompressionType.values()) { Compressor compressor = CompressorFactory.getCompressor(type); Assert.assertNotNull("Compressor should not be null for type " + type, compressor); } } @Test public void testGetCompressor_ReturnsCorrectType() { for (CompressionType type : CompressionType.values()) { Compressor compressor = CompressorFactory.getCompressor(type); Assert.assertTrue("Compressor type mismatch for " + type, compressor instanceof Lz4Compressor && type == CompressionType.LZ4 || compressor instanceof ZstdCompressor && type == CompressionType.ZSTD || compressor instanceof ZlibCompressor && type == CompressionType.ZLIB); } } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/compression/Lz4CompressorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import org.junit.Test; public class Lz4CompressorTest { private static final String TEST_STRING = "The quick brown fox jumps over the lazy dog"; @Test public void testCompressAndDecompress() throws Exception { byte[] originalData = TEST_STRING.getBytes(); Compressor compressor = new Lz4Compressor(); byte[] compressedData = compressor.compress(originalData, 1); assertTrue("Compressed data should be bigger than original", compressedData.length > originalData.length); byte[] decompressedData = compressor.decompress(compressedData); assertArrayEquals("Decompressed data should match original data", originalData, decompressedData); } @Test public void testCompressWithIOException() throws Exception { byte[] originalData = new byte[] {1, 2, 3}; Compressor compressor = new Lz4Compressor(); compressor.compress(originalData, 1); } @Test(expected = IOException.class) public void testDecompressWithIOException() throws Exception { byte[] compressedData = new byte[] {1, 2, 3}; Compressor compressor = new Lz4Compressor(); compressor.decompress(compressedData); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/compression/ZlibCompressorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import org.junit.Test; public class ZlibCompressorTest { private static final String TEST_STRING = "The quick brown fox jumps over the lazy dog"; @Test public void testCompressionAndDecompression() throws Exception { byte[] originalData = TEST_STRING.getBytes(); ZlibCompressor compressor = new ZlibCompressor(); byte[] compressedData = compressor.compress(originalData, 0); assertTrue("Compressed data should be bigger than original", compressedData.length > originalData.length); byte[] decompressedData = compressor.decompress(compressedData); assertArrayEquals("Decompressed data should match original", originalData, decompressedData); } @Test public void testCompressionFailureWithInvalidData() throws Exception { byte[] originalData = new byte[] {0, 1, 2, 3, 4}; ZlibCompressor compressor = new ZlibCompressor(); compressor.compress(originalData, 0); } @Test(expected = IOException.class) public void testDecompressionFailureWithInvalidData() throws Exception { byte[] compressedData = new byte[] {0, 1, 2, 3, 4}; ZlibCompressor compressor = new ZlibCompressor(); compressor.decompress(compressedData); // Invalid compressed data } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/compression/ZstdCompressorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.compression; import java.io.IOException; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; public class ZstdCompressorTest { @Test public void testCompressAndDecompress() throws IOException { byte[] originalData = "RocketMQ is awesome!".getBytes(); ZstdCompressor compressor = new ZstdCompressor(); byte[] compressedData = compressor.compress(originalData, 1); assertTrue("Compressed data should be bigger than original", compressedData.length > originalData.length); byte[] decompressedData = compressor.decompress(compressedData); assertArrayEquals("Decompressed data should match original data", originalData, decompressedData); } @Test public void testCompressWithInvalidData() throws IOException { byte[] invalidData = new byte[] {-1, -1, -1, -1}; ZstdCompressor compressor = new ZstdCompressor(); compressor.compress(invalidData, 1); } @Test(expected = IOException.class) public void testDecompressWithInvalidData() throws IOException { byte[] invalidData = new byte[] {-1, -1, -1, -1}; ZstdCompressor compressor = new ZstdCompressor(); compressor.decompress(invalidData); } @Test public void testCompressAndDecompressEmptyString() throws IOException { byte[] originalData = "".getBytes(); ZstdCompressor compressor = new ZstdCompressor(); byte[] compressedData = compressor.compress(originalData, 1); assertTrue("Compressed data for empty string should not be empty", compressedData.length > 0); byte[] decompressedData = compressor.decompress(compressedData); assertArrayEquals("Decompressed data for empty string should match original", originalData, decompressedData); } @Test public void testCompressAndDecompressLargeData() throws IOException { StringBuilder largeStringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { largeStringBuilder.append("RocketMQ is awesome! "); } byte[] originalData = largeStringBuilder.toString().getBytes(); ZstdCompressor compressor = new ZstdCompressor(); byte[] compressedData = compressor.compress(originalData, 1); assertTrue("Compressed data for large data should be smaller than original", compressedData.length < originalData.length); byte[] decompressedData = compressor.decompress(compressedData); assertArrayEquals("Decompressed data for large data should match original", originalData, decompressedData); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/config/ConfigHelperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.config; import org.junit.Test; public class ConfigHelperTest { @Test public void testGetDBLogDir() { // Should not raise exception. ConfigHelper.getDBLogDir(); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/consumer/ReceiptHandleTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.consumer; import org.apache.rocketmq.common.KeyBuilder; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ReceiptHandleTest { @Test public void testEncodeAndDecode() { long startOffset = 1000L; long retrieveTime = System.currentTimeMillis(); long invisibleTime = 1000L; int reviveQueueId = 1; String topicType = "NORMAL"; String brokerName = "BrokerA"; int queueId = 2; long offset = 2000L; long commitLogOffset = 3000L; ReceiptHandle receiptHandle = ReceiptHandle.builder() .startOffset(startOffset) .retrieveTime(retrieveTime) .invisibleTime(invisibleTime) .reviveQueueId(reviveQueueId) .topicType(topicType) .brokerName(brokerName) .queueId(queueId) .offset(offset) .commitLogOffset(commitLogOffset) .build(); String encoded = receiptHandle.encode(); ReceiptHandle decoded = ReceiptHandle.decode(encoded); assertEquals(receiptHandle.getStartOffset(), decoded.getStartOffset()); assertEquals(receiptHandle.getRetrieveTime(), decoded.getRetrieveTime()); assertEquals(receiptHandle.getInvisibleTime(), decoded.getInvisibleTime()); assertEquals(receiptHandle.getReviveQueueId(), decoded.getReviveQueueId()); assertEquals(receiptHandle.getTopicType(), decoded.getTopicType()); assertEquals(receiptHandle.getBrokerName(), decoded.getBrokerName()); assertEquals(receiptHandle.getQueueId(), decoded.getQueueId()); assertEquals(receiptHandle.getOffset(), decoded.getOffset()); assertEquals(receiptHandle.getCommitLogOffset(), decoded.getCommitLogOffset()); } @Test(expected = IllegalArgumentException.class) public void testDecodeWithInvalidString() { String invalidReceiptHandle = "invalid_data"; ReceiptHandle.decode(invalidReceiptHandle); } @Test public void testIsExpired() { long startOffset = 1000L; long retrieveTime = System.currentTimeMillis(); long invisibleTime = 1000L; int reviveQueueId = 1; String topicType = "NORMAL"; String brokerName = "BrokerA"; int queueId = 2; long offset = 2000L; long commitLogOffset = 3000L; long pastTime = System.currentTimeMillis() - 1000L; ReceiptHandle receiptHandle = new ReceiptHandle(startOffset, retrieveTime, invisibleTime, pastTime, reviveQueueId, topicType, brokerName, queueId, offset, commitLogOffset, ""); boolean isExpired = receiptHandle.isExpired(); assertTrue(isExpired); } @Test public void testGetRealTopic() { // Arrange String topic = "TestTopic"; String groupName = "TestGroup"; ReceiptHandle receiptHandle = ReceiptHandle.builder() .topicType(ReceiptHandle.RETRY_TOPIC) .build(); String realTopic = receiptHandle.getRealTopic(topic, groupName); assertEquals(KeyBuilder.buildPopRetryTopicV1(topic, groupName), realTopic); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/fastjson/GenericMapSuperclassDeserializerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.fastjson; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONException; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class GenericMapSuperclassDeserializerTest { public static class CustomMap extends HashMap { private static final long serialVersionUID = 1L; } public static class IntKeyMap extends HashMap { private static final long serialVersionUID = 1L; } @Test public void testBasicDeserialization() { JSON.registerIfAbsent(CustomMap.class, GenericMapSuperclassDeserializer.INSTANCE); String json = "{\"key1\":\"value1\",\"key2\":42,\"key3\":true}"; CustomMap map = JSON.parseObject(json, CustomMap.class); assertNotNull(map); assertEquals(3, map.size()); assertEquals("value1", map.get("key1")); assertEquals(42, map.get("key2")); assertEquals(true, map.get("key3")); } @Test public void testNestedObjects() { JSON.registerIfAbsent(CustomMap.class, GenericMapSuperclassDeserializer.INSTANCE); String json = "{\"simple\":\"value\",\"nested\":{\"inner\":123},\"array\":[1,2,3]}"; CustomMap map = JSON.parseObject(json, CustomMap.class); assertNotNull(map); assertEquals(3, map.size()); assertEquals("value", map.get("simple")); assertTrue(map.get("nested") instanceof Map); Map nestedMap = (Map) map.get("nested"); assertEquals(123, nestedMap.get("inner")); assertTrue(map.get("array") instanceof java.util.List); java.util.List array = (java.util.List) map.get("array"); assertEquals(3, array.size()); assertEquals(1, array.get(0)); assertEquals(2, array.get(1)); assertEquals(3, array.get(2)); } @Test public void testEmptyObject() { JSON.registerIfAbsent(CustomMap.class, GenericMapSuperclassDeserializer.INSTANCE); String json = "{}"; CustomMap map = JSON.parseObject(json, CustomMap.class); assertNotNull(map); assertEquals(0, map.size()); } @Test public void testNonStringKey() { JSON.registerIfAbsent(IntKeyMap.class, GenericMapSuperclassDeserializer.INSTANCE); String json = "{1:\"one\",2:\"two\",3:\"three\"}"; IntKeyMap map = JSON.parseObject(json, IntKeyMap.class); assertNotNull(map); assertEquals(3, map.size()); assertEquals("one", map.get(1)); assertEquals("two", map.get(2)); assertEquals("three", map.get(3)); } @Test(expected = JSONException.class) public void testMalformedJson() { JSON.registerIfAbsent(CustomMap.class, GenericMapSuperclassDeserializer.INSTANCE); String json = "{\"key\":\"missing closing brace\""; JSON.parseObject(json, CustomMap.class); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/help/FAQUrlTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.help; import org.junit.Test; import static org.junit.Assert.assertEquals; public class FAQUrlTest { @Test public void testSuggestTodo() { String expected = "\nSee " + FAQUrl.DEFAULT_FAQ_URL + " for further details."; String actual = FAQUrl.suggestTodo(FAQUrl.DEFAULT_FAQ_URL); assertEquals(expected, actual); } @Test public void testAttachDefaultURL() { String errorMsg = "errorMsg"; String expected = errorMsg + "\nFor more information, please visit the url, " + FAQUrl.UNEXPECTED_EXCEPTION_URL; String actual = FAQUrl.attachDefaultURL(errorMsg); assertEquals(expected, actual); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import org.apache.rocketmq.common.UtilAll; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MessageClientIDSetterTest { @Test public void testGetTimeFromID() { long t = System.currentTimeMillis(); String uniqID = MessageClientIDSetter.createUniqID(); long t2 = MessageClientIDSetter.getNearlyTimeFromID(uniqID).getTime(); assertThat(t2 - t < 20).isTrue(); } @Test public void testGetCountFromID() { String uniqID = MessageClientIDSetter.createUniqID(); String uniqID2 = MessageClientIDSetter.createUniqID(); String idHex = uniqID.substring(uniqID.length() - 4); String idHex2 = uniqID2.substring(uniqID2.length() - 4); int s1 = Integer.parseInt(idHex, 16); int s2 = Integer.parseInt(idHex2, 16); assertThat(s1 == s2 - 1).isTrue(); } @Test public void testGetIPStrFromID() { byte[] ip = UtilAll.getIP(); String ipStr = (4 == ip.length) ? UtilAll.ipToIPv4Str(ip) : UtilAll.ipToIPv6Str(ip); String uniqID = MessageClientIDSetter.createUniqID(); String ipStrFromID = MessageClientIDSetter.getIPStrFromID(uniqID); assertThat(ipStr).isEqualTo(ipStrFromID); } @Test public void testGetPidFromID() { // Temporary fix on MacOS short pid = (short) UtilAll.getPid(); String uniqID = MessageClientIDSetter.createUniqID(); short pidFromID = (short) MessageClientIDSetter.getPidFromID(uniqID); assertThat(pid).isEqualTo(pidFromID); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.junit.Test; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Map; import static org.apache.rocketmq.common.message.MessageDecoder.NAME_VALUE_SEPARATOR; import static org.apache.rocketmq.common.message.MessageDecoder.PROPERTY_SEPARATOR; import static org.apache.rocketmq.common.message.MessageDecoder.createMessageId; import static org.apache.rocketmq.common.message.MessageDecoder.decodeMessageId; import static org.assertj.core.api.Assertions.assertThat; public class MessageDecoderTest { @Test public void testDecodeProperties() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("645100FA00002A9F000000489A3AA09E"); messageExt.setTopic("abc"); messageExt.setBody("hello!q!".getBytes()); try { messageExt.setBornHost(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setCommitLogOffset(123456); messageExt.setPreparedTransactionOffset(0); messageExt.setQueueId(0); messageExt.setQueueOffset(123); messageExt.setReconsumeTimes(0); try { messageExt.setStoreHost(new InetSocketAddress(InetAddress.getLocalHost(), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.putUserProperty("a", "123"); messageExt.putUserProperty("b", "hello"); messageExt.putUserProperty("c", "3.14"); { byte[] msgBytes = new byte[0]; try { msgBytes = MessageDecoder.encode(messageExt, false); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); Map properties = MessageDecoder.decodeProperties(byteBuffer); assertThat(properties).isNotNull(); assertThat("123").isEqualTo(properties.get("a")); assertThat("hello").isEqualTo(properties.get("b")); assertThat("3.14").isEqualTo(properties.get("c")); } { byte[] msgBytes = new byte[0]; try { msgBytes = MessageDecoder.encode(messageExt, false); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); Map properties = MessageDecoder.decodeProperties(byteBuffer); assertThat(properties).isNotNull(); assertThat("123").isEqualTo(properties.get("a")); assertThat("hello").isEqualTo(properties.get("b")); assertThat("3.14").isEqualTo(properties.get("c")); } } @Test public void testDecodePropertiesOnIPv6Host() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExt.setBornHostV6Flag(); messageExt.setStoreHostAddressV6Flag(); messageExt.setTopic("abc"); messageExt.setBody("hello!q!".getBytes()); try { messageExt.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setCommitLogOffset(123456); messageExt.setPreparedTransactionOffset(0); messageExt.setQueueId(0); messageExt.setQueueOffset(123); messageExt.setReconsumeTimes(0); try { messageExt.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.putUserProperty("a", "123"); messageExt.putUserProperty("b", "hello"); messageExt.putUserProperty("c", "3.14"); byte[] msgBytes = new byte[0]; try { msgBytes = MessageDecoder.encode(messageExt, false); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); Map properties = MessageDecoder.decodeProperties(byteBuffer); assertThat(properties).isNotNull(); assertThat("123").isEqualTo(properties.get("a")); assertThat("hello").isEqualTo(properties.get("b")); assertThat("3.14").isEqualTo(properties.get("c")); } @Test public void testEncodeAndDecode() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("645100FA00002A9F000000489A3AA09E"); messageExt.setTopic("abc"); messageExt.setBody("hello!q!".getBytes()); try { messageExt.setBornHost(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setCommitLogOffset(123456); messageExt.setPreparedTransactionOffset(0); messageExt.setQueueId(1); messageExt.setQueueOffset(123); messageExt.setReconsumeTimes(0); try { messageExt.setStoreHost(new InetSocketAddress(InetAddress.getLocalHost(), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.putUserProperty("a", "123"); messageExt.putUserProperty("b", "hello"); messageExt.putUserProperty("c", "3.14"); messageExt.setBodyCRC(UtilAll.crc32(messageExt.getBody())); byte[] msgBytes = new byte[0]; try { msgBytes = MessageDecoder.encode(messageExt, false); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); byteBuffer.flip(); MessageExt decodedMsg = MessageDecoder.decode(byteBuffer); assertThat(decodedMsg).isNotNull(); assertThat(1).isEqualTo(decodedMsg.getQueueId()); assertThat(123456L).isEqualTo(decodedMsg.getCommitLogOffset()); assertThat("hello!q!".getBytes()).isEqualTo(decodedMsg.getBody()); int msgIDLength = 4 + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); String msgId = createMessageId(byteBufferMsgId, messageExt.getStoreHostBytes(), messageExt.getCommitLogOffset()); assertThat(msgId).isEqualTo(decodedMsg.getMsgId()); assertThat("abc").isEqualTo(decodedMsg.getTopic()); } @Test public void testEncodeAndDecodeOnIPv6Host() { MessageExt messageExt = new MessageExt(); messageExt.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExt.setBornHostV6Flag(); messageExt.setStoreHostAddressV6Flag(); messageExt.setTopic("abc"); messageExt.setBody("hello!q!".getBytes()); try { messageExt.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setCommitLogOffset(123456); messageExt.setPreparedTransactionOffset(0); messageExt.setQueueId(1); messageExt.setQueueOffset(123); messageExt.setReconsumeTimes(0); try { messageExt.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } messageExt.putUserProperty("a", "123"); messageExt.putUserProperty("b", "hello"); messageExt.putUserProperty("c", "3.14"); messageExt.setBodyCRC(UtilAll.crc32(messageExt.getBody())); byte[] msgBytes = new byte[0]; try { msgBytes = MessageDecoder.encode(messageExt, false); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); byteBuffer.flip(); MessageExt decodedMsg = MessageDecoder.decode(byteBuffer); assertThat(decodedMsg).isNotNull(); assertThat(1).isEqualTo(decodedMsg.getQueueId()); assertThat(123456L).isEqualTo(decodedMsg.getCommitLogOffset()); assertThat("hello!q!".getBytes()).isEqualTo(decodedMsg.getBody()); // assertThat(48).isEqualTo(decodedMsg.getSysFlag()); assertThat(MessageSysFlag.check(messageExt.getSysFlag(), MessageSysFlag.STOREHOSTADDRESS_V6_FLAG)).isTrue(); int msgIDLength = 16 + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); String msgId = createMessageId(byteBufferMsgId, messageExt.getStoreHostBytes(), messageExt.getCommitLogOffset()); assertThat(msgId).isEqualTo(decodedMsg.getMsgId()); assertThat("abc").isEqualTo(decodedMsg.getTopic()); } @Test public void testNullValueProperty() throws Exception { MessageExt msg = new MessageExt(); msg.setBody("x".getBytes()); msg.setTopic("x"); msg.setBornHost(new InetSocketAddress("127.0.0.1", 9000)); msg.setStoreHost(new InetSocketAddress("127.0.0.1", 9000)); String key = "NullValueKey"; msg.putProperty(key, null); try { byte[] encode = MessageDecoder.encode(msg, false); MessageExt decode = MessageDecoder.decode(ByteBuffer.wrap(encode)); assertThat(decode.getProperty(key)).isNull(); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } @Test public void testString2messageProperties() { StringBuilder sb = new StringBuilder(); sb.append("k1").append(NAME_VALUE_SEPARATOR).append("v1"); Map m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("k1")).isEqualTo("v1"); m = MessageDecoder.string2messageProperties(""); assertThat(m).size().isEqualTo(0); m = MessageDecoder.string2messageProperties(" "); assertThat(m).size().isEqualTo(0); m = MessageDecoder.string2messageProperties("aaa"); assertThat(m).size().isEqualTo(0); sb.setLength(0); sb.append("k1").append(NAME_VALUE_SEPARATOR); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(0); sb.setLength(0); sb.append(NAME_VALUE_SEPARATOR).append("v1"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(0); sb.setLength(0); sb.append("k1").append(NAME_VALUE_SEPARATOR).append("v1").append(PROPERTY_SEPARATOR); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("k1")).isEqualTo("v1"); sb.setLength(0); sb.append("k1").append(NAME_VALUE_SEPARATOR).append("v1").append(PROPERTY_SEPARATOR) .append("k2").append(NAME_VALUE_SEPARATOR).append("v2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(2); assertThat(m.get("k1")).isEqualTo("v1"); assertThat(m.get("k2")).isEqualTo("v2"); sb.setLength(0); sb.append("k1").append(NAME_VALUE_SEPARATOR).append("v1").append(PROPERTY_SEPARATOR) .append(NAME_VALUE_SEPARATOR).append("v2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("k1")).isEqualTo("v1"); sb.setLength(0); sb.append("k1").append(NAME_VALUE_SEPARATOR).append("v1").append(PROPERTY_SEPARATOR) .append("k2").append(NAME_VALUE_SEPARATOR); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("k1")).isEqualTo("v1"); sb.setLength(0); sb.append(NAME_VALUE_SEPARATOR).append("v1").append(PROPERTY_SEPARATOR) .append("k2").append(NAME_VALUE_SEPARATOR).append("v2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("k2")).isEqualTo("v2"); sb.setLength(0); sb.append("k1").append(NAME_VALUE_SEPARATOR).append(PROPERTY_SEPARATOR) .append("k2").append(NAME_VALUE_SEPARATOR).append("v2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("k2")).isEqualTo("v2"); sb.setLength(0); sb.append("1").append(NAME_VALUE_SEPARATOR).append("1").append(PROPERTY_SEPARATOR) .append("2").append(NAME_VALUE_SEPARATOR).append("2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(2); assertThat(m.get("1")).isEqualTo("1"); assertThat(m.get("2")).isEqualTo("2"); sb.setLength(0); sb.append("1").append(NAME_VALUE_SEPARATOR).append(PROPERTY_SEPARATOR) .append("2").append(NAME_VALUE_SEPARATOR).append("2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("2")).isEqualTo("2"); sb.setLength(0); sb.append(NAME_VALUE_SEPARATOR).append("1").append(PROPERTY_SEPARATOR) .append("2").append(NAME_VALUE_SEPARATOR).append("2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("2")).isEqualTo("2"); sb.setLength(0); sb.append("1").append(NAME_VALUE_SEPARATOR).append("1").append(PROPERTY_SEPARATOR) .append("2").append(NAME_VALUE_SEPARATOR); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("1")).isEqualTo("1"); sb.setLength(0); sb.append("1").append(NAME_VALUE_SEPARATOR).append("1").append(PROPERTY_SEPARATOR) .append(NAME_VALUE_SEPARATOR).append("2"); m = MessageDecoder.string2messageProperties(sb.toString()); assertThat(m).size().isEqualTo(1); assertThat(m.get("1")).isEqualTo("1"); } @Test public void testMessageId() throws Exception { // ipv4 messageId test MessageExt msgExt = new MessageExt(); msgExt.setStoreHost(new InetSocketAddress("127.0.0.1", 9103)); msgExt.setCommitLogOffset(123456); verifyMessageId(msgExt); // ipv6 messageId test msgExt.setStoreHostAddressV6Flag(); msgExt.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 0)); verifyMessageId(msgExt); } private void verifyMessageId(MessageExt msgExt) throws UnknownHostException { int storehostIPLength = (msgExt.getSysFlag() & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 : 16; int msgIDLength = storehostIPLength + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); String msgId = createMessageId(byteBufferMsgId, msgExt.getStoreHostBytes(), msgExt.getCommitLogOffset()); MessageId messageId = decodeMessageId(msgId); assertThat(messageId.getAddress()).isEqualTo(msgExt.getStoreHost()); assertThat(messageId.getOffset()).isEqualTo(msgExt.getCommitLogOffset()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/message/MessageTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.message; import org.junit.Assert; import org.junit.Test; import static org.apache.rocketmq.common.message.MessageConst.PROPERTY_TRACE_SWITCH; public class MessageTest { @Test(expected = RuntimeException.class) public void putUserPropertyWithRuntimeException() throws Exception { Message m = new Message(); m.putUserProperty(PROPERTY_TRACE_SWITCH, ""); } @Test(expected = IllegalArgumentException.class) public void putUserNullValuePropertyWithException() throws Exception { Message m = new Message(); m.putUserProperty("prop1", null); } @Test(expected = IllegalArgumentException.class) public void putUserEmptyValuePropertyWithException() throws Exception { Message m = new Message(); m.putUserProperty("prop1", " "); } @Test(expected = IllegalArgumentException.class) public void putUserNullNamePropertyWithException() throws Exception { Message m = new Message(); m.putUserProperty(null, "val1"); } @Test(expected = IllegalArgumentException.class) public void putUserEmptyNamePropertyWithException() throws Exception { Message m = new Message(); m.putUserProperty(" ", "val1"); } @Test public void putUserProperty() throws Exception { Message m = new Message(); m.putUserProperty("prop1", "val1"); Assert.assertEquals("val1", m.getUserProperty("prop1")); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/producer/RecallMessageHandleTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.producer; import org.apache.commons.codec.DecoderException; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.junit.Assert; import org.junit.Test; import java.nio.charset.StandardCharsets; import java.util.Base64; public class RecallMessageHandleTest { @Test public void testHandleInvalid() { Assert.assertThrows(DecoderException.class, () -> { RecallMessageHandle.decodeHandle(""); }); Assert.assertThrows(DecoderException.class, () -> { RecallMessageHandle.decodeHandle(null); }); Assert.assertThrows(DecoderException.class, () -> { String invalidHandle = Base64.getUrlEncoder().encodeToString("v1 a b c".getBytes(StandardCharsets.UTF_8)); RecallMessageHandle.decodeHandle(invalidHandle); }); Assert.assertThrows(DecoderException.class, () -> { String invalidHandle = Base64.getUrlEncoder().encodeToString("v2 a b c d".getBytes(StandardCharsets.UTF_8)); RecallMessageHandle.decodeHandle(invalidHandle); }); Assert.assertThrows(DecoderException.class, () -> { String invalidHandle = "v1 a b c d"; RecallMessageHandle.decodeHandle(invalidHandle); }); } @Test public void testEncodeAndDecodeV1() throws DecoderException { String topic = "topic"; String brokerName = "broker-0"; String timestampStr = String.valueOf(System.currentTimeMillis()); String messageId = MessageClientIDSetter.createUniqID(); String handle = RecallMessageHandle.HandleV1.buildHandle(topic, brokerName, timestampStr, messageId); RecallMessageHandle handleEntity = RecallMessageHandle.decodeHandle(handle); Assert.assertTrue(handleEntity instanceof RecallMessageHandle.HandleV1); RecallMessageHandle.HandleV1 handleV1 = (RecallMessageHandle.HandleV1) handleEntity; Assert.assertEquals(handleV1.getVersion(), "v1"); Assert.assertEquals(handleV1.getTopic(), topic); Assert.assertEquals(handleV1.getBrokerName(), brokerName); Assert.assertEquals(handleV1.getTimestampStr(), timestampStr); Assert.assertEquals(handleV1.getMessageId(), messageId); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/stats/StatsItemSetTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.stats; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.junit.After; import org.junit.Test; import static org.junit.Assert.assertEquals; public class StatsItemSetTest { private ThreadPoolExecutor executor; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @Test public void test_getAndCreateStatsItem_multiThread() throws InterruptedException { assertEquals(20L, test_unit().longValue()); } @Test public void test_getAndCreateMomentStatsItem_multiThread() throws InterruptedException { assertEquals(10, test_unit_moment().longValue()); } @Test public void test_statsOfFirstStatisticsCycle() throws InterruptedException { final String tpsStatKey = "tpsTest"; final String rtStatKey = "rtTest"; final StatsItemSet statsItemSet = new StatsItemSet(tpsStatKey, scheduler, null); executor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadFactoryImpl("testMultiThread")); for (int i = 0; i < 10; i++) { executor.submit(new Runnable() { @Override public void run() { statsItemSet.addValue(tpsStatKey, 2, 1); statsItemSet.addRTValue(rtStatKey, 2, 1); } }); } while (true) { if (executor.getCompletedTaskCount() == 10) { break; } Thread.sleep(1000); } // simulate schedule task execution , tps stat { statsItemSet.getStatsItem(tpsStatKey).samplingInSeconds(); statsItemSet.getStatsItem(tpsStatKey).samplingInMinutes(); statsItemSet.getStatsItem(tpsStatKey).samplingInHour(); assertEquals(20L, statsItemSet.getStatsDataInMinute(tpsStatKey).getSum()); assertEquals(20L, statsItemSet.getStatsDataInHour(tpsStatKey).getSum()); assertEquals(20L, statsItemSet.getStatsDataInDay(tpsStatKey).getSum()); assertEquals(10L, statsItemSet.getStatsDataInDay(tpsStatKey).getTimes()); assertEquals(10L, statsItemSet.getStatsDataInHour(tpsStatKey).getTimes()); assertEquals(10L, statsItemSet.getStatsDataInDay(tpsStatKey).getTimes()); } // simulate schedule task execution , rt stat { statsItemSet.getStatsItem(rtStatKey).samplingInSeconds(); statsItemSet.getStatsItem(rtStatKey).samplingInMinutes(); statsItemSet.getStatsItem(rtStatKey).samplingInHour(); assertEquals(20L, statsItemSet.getStatsDataInMinute(rtStatKey).getSum()); assertEquals(20L, statsItemSet.getStatsDataInHour(rtStatKey).getSum()); assertEquals(20L, statsItemSet.getStatsDataInDay(rtStatKey).getSum()); assertEquals(10L, statsItemSet.getStatsDataInDay(rtStatKey).getTimes()); assertEquals(10L, statsItemSet.getStatsDataInHour(rtStatKey).getTimes()); assertEquals(10L, statsItemSet.getStatsDataInDay(rtStatKey).getTimes()); } } private LongAdder test_unit() throws InterruptedException { final StatsItemSet statsItemSet = new StatsItemSet("topicTest", scheduler, null); executor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadFactoryImpl("testMultiThread")); for (int i = 0; i < 10; i++) { executor.submit(new Runnable() { @Override public void run() { statsItemSet.addValue("topicTest", 2, 1); } }); } while (true) { if (executor.getCompletedTaskCount() == 10) { break; } Thread.sleep(1000); } return statsItemSet.getStatsItem("topicTest").getValue(); } private AtomicLong test_unit_moment() throws InterruptedException { final MomentStatsItemSet statsItemSet = new MomentStatsItemSet("topicTest", scheduler, null); executor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadFactoryImpl("testMultiThread")); for (int i = 0; i < 10; i++) { executor.submit(new Runnable() { @Override public void run() { statsItemSet.setValue("test", 10); } }); } while (true) { if (executor.getCompletedTaskCount() == 10) { break; } Thread.sleep(1000); } return statsItemSet.getAndCreateStatsItem("test").getValue(); } @After public void shutdown() { executor.shutdown(); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/sysflag/CompressionFlagTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.sysflag; import org.apache.rocketmq.common.compression.CompressionType; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class CompressionFlagTest { @Test public void testCompressionFlag() { int flag = 0; flag |= MessageSysFlag.COMPRESSED_FLAG; assertThat(MessageSysFlag.getCompressionType(flag)).isEqualTo(CompressionType.ZLIB); flag |= MessageSysFlag.COMPRESSION_LZ4_TYPE; assertThat(MessageSysFlag.getCompressionType(flag)).isEqualTo(CompressionType.LZ4); flag &= ~MessageSysFlag.COMPRESSION_TYPE_COMPARATOR; flag |= MessageSysFlag.COMPRESSION_ZSTD_TYPE; assertThat(MessageSysFlag.getCompressionType(flag)).isEqualTo(CompressionType.ZSTD); flag &= ~MessageSysFlag.COMPRESSION_TYPE_COMPARATOR; flag |= MessageSysFlag.COMPRESSION_ZLIB_TYPE; assertThat(MessageSysFlag.getCompressionType(flag)).isEqualTo(CompressionType.ZLIB); } @Test(expected = RuntimeException.class) public void testCompressionFlagNotMatch() { int flag = 0; flag |= MessageSysFlag.COMPRESSED_FLAG; flag |= 0x4 << 8; MessageSysFlag.getCompressionType(flag); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/sysflag/PullSysFlagTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.sysflag; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class PullSysFlagTest { @Test public void testLitePullFlag() { int flag = PullSysFlag.buildSysFlag(false, false, false, false, true); assertThat(PullSysFlag.hasLitePullFlag(flag)).isTrue(); } @Test public void testLitePullFlagFalse() { int flag = PullSysFlag.buildSysFlag(false, false, false, false, false); assertThat(PullSysFlag.hasLitePullFlag(flag)).isFalse(); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/topic/TopicValidatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.topic; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class TopicValidatorTest { @Test public void testTopicValidator_NotPass() { TopicValidator.ValidateResult res = TopicValidator.validateTopic(""); assertThat(res.isValid()).isFalse(); assertThat(res.getRemark()).contains("The specified topic is blank"); res = TopicValidator.validateTopic("../TopicTest"); assertThat(res.isValid()).isFalse(); res = TopicValidator.validateTopic(generateString(128)); assertThat(res.isValid()).isFalse(); res = TopicValidator.validateTopic(generateRetryTopic(256)); assertThat(res.isValid()).isFalse(); res = TopicValidator.validateTopic(generateDlqTopic(256)); assertThat(res.isValid()).isFalse(); } @Test public void testTopicValidator_Pass() { TopicValidator.ValidateResult res = TopicValidator.validateTopic("TestTopic"); assertThat(res.isValid()).isTrue(); assertThat(res.getRemark()).isEmpty(); res = TopicValidator.validateTopic(generateString2(127)); assertThat(res.isValid()).isTrue(); assertThat(res.getRemark()).isEmpty(); res = TopicValidator.validateTopic(generateRetryTopic(255)); assertThat(res.isValid()).isTrue(); assertThat(res.getRemark()).isEmpty(); res = TopicValidator.validateTopic(generateDlqTopic(255)); assertThat(res.isValid()).isTrue(); assertThat(res.getRemark()).isEmpty(); } @Test public void testGroupValidator_Pass() { TopicValidator.ValidateResult res = TopicValidator.validateGroup("TestGroup"); assertThat(res.isValid()).isTrue(); assertThat(res.getRemark()).isEmpty(); res = TopicValidator.validateGroup(generateString2(120)); assertThat(res.isValid()).isTrue(); assertThat(res.getRemark()).isEmpty(); } @Test public void testGroupValidator__NotPass() { TopicValidator.ValidateResult res = TopicValidator.validateGroup(""); assertThat(res.isValid()).isFalse(); assertThat(res.getRemark()).contains("The specified group is blank"); res = TopicValidator.validateGroup("../GroupTest"); assertThat(res.isValid()).isFalse(); res = TopicValidator.validateGroup(generateString(120)); assertThat(res.isValid()).isFalse(); res = TopicValidator.validateGroup(generateString2(121)); assertThat(res.isValid()).isFalse(); } @Test public void testAddSystemTopic() { String topic = "SYSTEM_TOPIC_TEST"; TopicValidator.addSystemTopic(topic); assertThat(TopicValidator.getSystemTopicSet()).contains(topic); } @Test public void testIsSystemTopic() { boolean res; for (String topic : TopicValidator.getSystemTopicSet()) { res = TopicValidator.isSystemTopic(topic); assertThat(res).isTrue(); } String topic = TopicValidator.SYSTEM_TOPIC_PREFIX + "_test"; res = TopicValidator.isSystemTopic(topic); assertThat(res).isTrue(); topic = "test_not_system_topic"; res = TopicValidator.isSystemTopic(topic); assertThat(res).isFalse(); } @Test public void testIsSystemTopicWithResponse() { boolean res; for (String topic : TopicValidator.getSystemTopicSet()) { res = TopicValidator.isSystemTopic(topic); assertThat(res).isTrue(); } String topic = "test_not_system_topic"; res = TopicValidator.isSystemTopic(topic); assertThat(res).isFalse(); } @Test public void testIsNotAllowedSendTopic() { boolean res; for (String topic : TopicValidator.getNotAllowedSendTopicSet()) { res = TopicValidator.isNotAllowedSendTopic(topic); assertThat(res).isTrue(); } String topic = "test_allowed_send_topic"; res = TopicValidator.isNotAllowedSendTopic(topic); assertThat(res).isFalse(); } @Test public void testIsNotAllowedSendTopicWithResponse() { boolean res; for (String topic : TopicValidator.getNotAllowedSendTopicSet()) { res = TopicValidator.isNotAllowedSendTopic(topic); assertThat(res).isTrue(); } String topic = "test_allowed_send_topic"; res = TopicValidator.isNotAllowedSendTopic(topic); assertThat(res).isFalse(); } private static String generateString(int length) { StringBuilder stringBuffer = new StringBuilder(); String tmpStr = "0123456789"; for (int i = 0; i < length; i++) { stringBuffer.append(tmpStr); } return stringBuffer.toString(); } private static String generateString2(int length) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < length; i++) { stringBuilder.append("a"); } return stringBuilder.toString(); } private static String generateRetryTopic(int length) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("%RETRY%"); for (int i = 0; i < length - 7; i++) { stringBuilder.append("a"); } return stringBuilder.toString(); } private static String generateDlqTopic(int length) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("%DLQ%"); for (int i = 0; i < length - 5; i++) { stringBuilder.append("a"); } return stringBuilder.toString(); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.util.concurrent.ConcurrentHashMap; import org.junit.Test; import static org.junit.Assert.assertEquals; public class ConcurrentHashMapUtilsTest { @Test public void computeIfAbsent() { ConcurrentHashMap map = new ConcurrentHashMap<>(); map.put("123", "1111"); String value = ConcurrentHashMapUtils.computeIfAbsent(map, "123", k -> "234"); assertEquals("1111", value); String value1 = ConcurrentHashMapUtils.computeIfAbsent(map, "1232", k -> "2342"); assertEquals("2342", value1); String value2 = ConcurrentHashMapUtils.computeIfAbsent(map, "123", k -> "2342"); assertEquals("1111", value2); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.CharArrayReader; import java.io.CharArrayWriter; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class IOTinyUtilsTest { /** * https://bazel.build/reference/test-encyclopedia#filesystem */ private String testRootDir = System.getProperty("java.io.tmpdir") + File.separator + "iotinyutilstest"; @Before public void init() { File dir = new File(testRootDir); if (dir.exists()) { UtilAll.deleteFile(dir); } dir.mkdirs(); } @After public void destroy() { File file = new File(testRootDir); UtilAll.deleteFile(file); } @Test public void testToString() throws Exception { byte[] b = "testToString".getBytes(StandardCharsets.UTF_8); InputStream is = new ByteArrayInputStream(b); String str = IOTinyUtils.toString(is, null); assertEquals("testToString", str); is = new ByteArrayInputStream(b); str = IOTinyUtils.toString(is, StandardCharsets.UTF_8.name()); assertEquals("testToString", str); is = new ByteArrayInputStream(b); Reader isr = new InputStreamReader(is, StandardCharsets.UTF_8); str = IOTinyUtils.toString(isr); assertEquals("testToString", str); } @Test public void testCopy() throws Exception { char[] arr = "testToString".toCharArray(); Reader reader = new CharArrayReader(arr); Writer writer = new CharArrayWriter(); long count = IOTinyUtils.copy(reader, writer); assertEquals(arr.length, count); } @Test public void testReadLines() throws Exception { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10; i++) { sb.append("testReadLines").append("\n"); } StringReader reader = new StringReader(sb.toString()); List lines = IOTinyUtils.readLines(reader); assertEquals(10, lines.size()); } @Test public void testToBufferedReader() throws Exception { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10; i++) { sb.append("testToBufferedReader").append("\n"); } StringReader reader = new StringReader(sb.toString()); Method method = IOTinyUtils.class.getDeclaredMethod("toBufferedReader", new Class[]{Reader.class}); method.setAccessible(true); Object bReader = method.invoke(IOTinyUtils.class, reader); assertTrue(bReader instanceof BufferedReader); } @Test public void testWriteStringToFile() throws Exception { File file = new File(testRootDir, "testWriteStringToFile"); assertTrue(!file.exists()); IOTinyUtils.writeStringToFile(file, "testWriteStringToFile", StandardCharsets.UTF_8.name()); assertTrue(file.exists()); } @Test public void testCleanDirectory() throws Exception { for (int i = 0; i < 10; i++) { IOTinyUtils.writeStringToFile(new File(testRootDir, "testCleanDirectory" + i), "testCleanDirectory", StandardCharsets.UTF_8.name()); } File dir = new File(testRootDir); assertTrue(dir.exists() && dir.isDirectory()); assertTrue(dir.listFiles().length > 0); IOTinyUtils.cleanDirectory(new File(testRootDir)); assertTrue(dir.listFiles().length == 0); } @Test public void testDelete() throws Exception { for (int i = 0; i < 10; i++) { IOTinyUtils.writeStringToFile(new File(testRootDir, "testDelete" + i), "testCleanDirectory", StandardCharsets.UTF_8.name()); } File dir = new File(testRootDir); assertTrue(dir.exists() && dir.isDirectory()); assertTrue(dir.listFiles().length > 0); IOTinyUtils.delete(new File(testRootDir)); assertTrue(!dir.exists()); } @Test public void testCopyFile() throws Exception { File source = new File(testRootDir, "source"); String target = testRootDir + File.separator + "dest"; IOTinyUtils.writeStringToFile(source, "testCopyFile", StandardCharsets.UTF_8.name()); IOTinyUtils.copyFile(source.getCanonicalPath(), target); File dest = new File(target); assertTrue(dest.exists()); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/utils/IPAddressUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import org.junit.Test; import static org.apache.rocketmq.common.utils.NetworkUtil.validCommonInet6Address; public class IPAddressUtilsTest { @Test public void isIPInRange() { // IPv4 test String ipv4Address = "192.168.1.10"; String ipv4Cidr = "192.168.1.0/24"; assert IPAddressUtils.isIPInRange(ipv4Address, ipv4Cidr); ipv4Address = "192.168.2.10"; assert !IPAddressUtils.isIPInRange(ipv4Address, ipv4Cidr); // IPv6 test String ipv6Address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; String ipv6Cidr = "2001:0db8:85a3::/48"; assert IPAddressUtils.isIPInRange(ipv6Address, ipv6Cidr); } @Test public void isValidCidr() { String ipv4Cidr = "192.168.1.0/24"; String ipv6Cidr = "2001:0db8:1234:5678::/64"; String invalidCidr = "192.168.1.0"; assert IPAddressUtils.isValidCidr(ipv4Cidr); assert IPAddressUtils.isValidCidr(ipv6Cidr); assert !IPAddressUtils.isValidCidr(invalidCidr); } @Test public void isValidIp() { String ipv4 = "192.168.1.0"; String ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; String invalidIp = "192.168.1.256"; String ipv4Cidr = "192.168.1.0/24"; assert IPAddressUtils.isValidIp(ipv4); assert IPAddressUtils.isValidIp(ipv6); assert !IPAddressUtils.isValidIp(invalidIp); assert !IPAddressUtils.isValidIp(ipv4Cidr); } @Test public void isValidIPOrCidr() { String ipv4 = "192.168.1.0"; String ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; String ipv4Cidr = "192.168.1.0/24"; String ipv6Cidr = "2001:0db8:1234:5678::/64"; assert IPAddressUtils.isValidIPOrCidr(ipv4); assert IPAddressUtils.isValidIPOrCidr(ipv6); assert IPAddressUtils.isValidIPOrCidr(ipv4Cidr); assert IPAddressUtils.isValidIPOrCidr(ipv6Cidr); } @Test public void isValidIPv6Common() { String ipv6WithoutScope = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; assert validCommonInet6Address(ipv6WithoutScope); String ipv6WithScope = "2001:0db8:85a3:0000:0000:8a2e:0370:7334%eth0"; assert validCommonInet6Address(ipv6WithScope); String ipv6WithBracketedAndScope = "[2001:0db8:85a3:0000:0000:8a2e:0370:7334%eth0]"; assert validCommonInet6Address(ipv6WithBracketedAndScope); String ipv4 = "192.168.1.0"; assert !validCommonInet6Address(ipv4); String ipv4Cidr = "192.168.1.0/24"; assert !validCommonInet6Address(ipv4Cidr); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/utils/LiteUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.lite.LiteUtil; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class LiteUtilTest { @Test public void testToLmqName() { String result = LiteUtil.toLmqName("parentTopic", "liteTopic"); String expected = LiteUtil.LITE_TOPIC_PREFIX + "parentTopic" + LiteUtil.SEPARATOR + "liteTopic"; assertEquals(expected, result); assertNull(LiteUtil.toLmqName(null, "liteTopic")); assertNull(LiteUtil.toLmqName("parentTopic", null)); assertNull(LiteUtil.toLmqName("", "liteTopic")); assertNull(LiteUtil.toLmqName("parentTopic", "")); } @Test public void testIsLiteTopicQueue() { assertTrue(LiteUtil.isLiteTopicQueue("%LMQ%$parentTopic$liteTopic")); assertFalse(LiteUtil.isLiteTopicQueue("%LMQ%parentTopic")); assertFalse(LiteUtil.isLiteTopicQueue("parentTopic")); assertFalse(LiteUtil.isLiteTopicQueue(null)); assertFalse(LiteUtil.isLiteTopicQueue("%LMQ$")); } @Test public void testGetParentTopic() { assertEquals("parentTopic", LiteUtil.getParentTopic("%LMQ%$parentTopic$liteTopic")); assertNull(LiteUtil.getParentTopic(null)); assertNull(LiteUtil.getParentTopic("parentTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%parentTopic$liteTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$$")); assertNull(LiteUtil.getParentTopic("%LMQ%$parentTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$parentTopic$")); assertNull(LiteUtil.getParentTopic("%LMQ%$$liteTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$parent$lite$extra")); } @Test public void testGetLiteTopic() { assertEquals("liteTopic", LiteUtil.getLiteTopic("%LMQ%$parentTopic$liteTopic")); assertNull(LiteUtil.getLiteTopic(null)); assertNull(LiteUtil.getLiteTopic("parentTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%parentTopic$liteTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$$")); assertNull(LiteUtil.getLiteTopic("%LMQ%$parentTopic")); assertNull(LiteUtil.getLiteTopic("%LMQ%$parentTopic$")); assertNull(LiteUtil.getLiteTopic("%LMQ%$$liteTopic")); assertNull(LiteUtil.getLiteTopic("%LMQ%$parent$lite$extra")); } @Test public void testGetParentAndLiteTopic() { Pair result = LiteUtil.getParentAndLiteTopic("%LMQ%$parentTopic$liteTopic"); assertNotNull(result); assertEquals("parentTopic", result.getObject1()); assertEquals("liteTopic", result.getObject2()); assertNull(LiteUtil.getParentTopic(null)); assertNull(LiteUtil.getParentTopic("parentTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%parentTopic$liteTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$$")); assertNull(LiteUtil.getParentTopic("%LMQ%$parentTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$parentTopic$")); assertNull(LiteUtil.getParentTopic("%LMQ%$$liteTopic")); assertNull(LiteUtil.getParentTopic("%LMQ%$parent$lite$extra")); } @Test public void testBelongsTo() { assertTrue(LiteUtil.belongsTo("%LMQ%$parentTopic$liteTopic", "parentTopic")); assertTrue(LiteUtil.belongsTo("%LMQ%$parentTopic$", "parentTopic")); // only check prefix assertTrue(LiteUtil.belongsTo("%LMQ%$parentTopic$liteTopic$xxx", "parentTopic")); // only check prefix assertFalse(LiteUtil.belongsTo("%LMQ%$parentTopic$liteTopic", "otherParent")); assertFalse(LiteUtil.belongsTo("parentTopic", "parentTopic")); assertFalse(LiteUtil.belongsTo(null, "parentTopic")); assertFalse(LiteUtil.belongsTo("%LMQ%$parentTopic$liteTopic", null)); } } ================================================ FILE: common/src/test/java/org/apache/rocketmq/common/utils/NameServerAddressUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.common.utils; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class NameServerAddressUtilsTest { private static String endpoint1 = "http://127.0.0.1:9876"; private static String endpoint2 = "127.0.0.1:9876"; private static String endpoint3 = "http://MQ_INST_123456789_BXXUzaee.xxx:80"; private static String endpoint4 = "MQ_INST_123456789_BXXUzaee.xxx:80"; @Test public void testValidateInstanceEndpoint() { assertThat(NameServerAddressUtils.validateInstanceEndpoint(endpoint1)).isEqualTo(false); assertThat(NameServerAddressUtils.validateInstanceEndpoint(endpoint2)).isEqualTo(false); assertThat(NameServerAddressUtils.validateInstanceEndpoint(endpoint3)).isEqualTo(true); assertThat(NameServerAddressUtils.validateInstanceEndpoint(endpoint4)).isEqualTo(true); } @Test public void testParseInstanceIdFromEndpoint() { assertThat(NameServerAddressUtils.parseInstanceIdFromEndpoint(endpoint3)).isEqualTo( "MQ_INST_123456789_BXXUzaee"); assertThat(NameServerAddressUtils.parseInstanceIdFromEndpoint(endpoint4)).isEqualTo( "MQ_INST_123456789_BXXUzaee"); } @Test public void testGetNameSrvAddrFromNamesrvEndpoint() { assertThat(NameServerAddressUtils.getNameSrvAddrFromNamesrvEndpoint(endpoint1)) .isEqualTo("127.0.0.1:9876"); assertThat(NameServerAddressUtils.getNameSrvAddrFromNamesrvEndpoint(endpoint2)) .isEqualTo("127.0.0.1:9876"); assertThat(NameServerAddressUtils.getNameSrvAddrFromNamesrvEndpoint(endpoint3)) .isEqualTo("MQ_INST_123456789_BXXUzaee.xxx:80"); assertThat(NameServerAddressUtils.getNameSrvAddrFromNamesrvEndpoint(endpoint4)) .isEqualTo("MQ_INST_123456789_BXXUzaee.xxx:80"); } } ================================================ FILE: common/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: container/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "container", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//auth", "//broker", "//common", "//remoting", "//client", "//srvutil", "//store", "@maven//:io_openmessaging_storage_dledger", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_validator_commons_validator", "@maven//:commons_collections_commons_collections", "@maven//:commons_codec_commons_codec", "@maven//:com_github_luben_zstd_jni", "@maven//:org_lz4_lz4_java", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", "@maven//:org_slf4j_slf4j_api", "@maven//:ch_qos_logback_logback_core", "@maven//:ch_qos_logback_logback_classic", "@maven//:commons_cli_commons_cli", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":container", "//auth", "//broker", "//common", "//remoting", "//client", "//srvutil", "//store", "@maven//:io_openmessaging_storage_dledger", "//:test_deps", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], # The following tests are flaky, fix them later. exclude_tests = [ "src/test/java/org/apache/rocketmq/container/BrokerContainerStartupTest", "src/test/java/org/apache/rocketmq/container/BrokerContainerTest", ], ) ================================================ FILE: container/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-container rocketmq-container ${project.version} ${basedir}/.. org.apache.rocketmq rocketmq-broker ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/BrokerBootHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.util.Properties; public interface BrokerBootHook { /** * Name of the hook. * * @return name of the hook */ String hookName(); /** * Code to execute before broker start. * * @param innerBrokerController inner broker to start * @param properties broker properties * @throws Exception when execute hook */ void executeBeforeStart(InnerBrokerController innerBrokerController, Properties properties) throws Exception; /** * Code to execute after broker start. * * @param innerBrokerController inner broker to start * @param properties broker properties * @throws Exception when execute hook */ void executeAfterStart(InnerBrokerController innerBrokerController, Properties properties) throws Exception; } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.Configuration; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class BrokerContainer implements IBrokerContainer { private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, new BasicThreadFactory.Builder() .namingPattern("BrokerContainerScheduledThread") .daemon(true) .build()); protected final NettyServerConfig nettyServerConfig; protected final NettyClientConfig nettyClientConfig; protected final BrokerOuterAPI brokerOuterAPI; protected final ContainerClientHouseKeepingService containerClientHouseKeepingService; protected final ConcurrentMap slaveBrokerControllers = new ConcurrentHashMap<>(); protected final ConcurrentMap masterBrokerControllers = new ConcurrentHashMap<>(); protected final ConcurrentMap dLedgerBrokerControllers = new ConcurrentHashMap<>(); protected final List brokerBootHookList = new ArrayList<>(); protected final BrokerContainerProcessor brokerContainerProcessor; protected final Configuration configuration; protected final BrokerContainerConfig brokerContainerConfig; protected RemotingServer remotingServer; protected RemotingServer fastRemotingServer; protected ExecutorService brokerContainerExecutor; public BrokerContainer( final BrokerContainerConfig brokerContainerConfig, final NettyServerConfig nettyServerConfig, final NettyClientConfig nettyClientConfig ) { this.brokerContainerConfig = brokerContainerConfig; this.nettyServerConfig = nettyServerConfig; this.nettyClientConfig = nettyClientConfig; this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig, null); this.brokerContainerProcessor = new BrokerContainerProcessor(this); this.brokerContainerProcessor.registerBrokerBootHook(this.brokerBootHookList); this.containerClientHouseKeepingService = new ContainerClientHouseKeepingService(this); this.configuration = new Configuration( LOG, BrokerPathConfigHelper.getBrokerConfigPath(), this.brokerContainerConfig, this.nettyServerConfig, this.nettyClientConfig); } @Override public String getBrokerContainerAddr() { return this.brokerContainerConfig.getBrokerContainerIP() + ":" + this.nettyServerConfig.getListenPort(); } @Override public BrokerContainerConfig getBrokerContainerConfig() { return brokerContainerConfig; } @Override public NettyServerConfig getNettyServerConfig() { return nettyServerConfig; } @Override public NettyClientConfig getNettyClientConfig() { return nettyClientConfig; } @Override public BrokerOuterAPI getBrokerOuterAPI() { return brokerOuterAPI; } @Override public RemotingServer getRemotingServer() { return remotingServer; } public Configuration getConfiguration() { return this.configuration; } private void updateNamesrvAddr() { if (this.brokerContainerConfig.isFetchNameSrvAddrByDnsLookup()) { this.brokerOuterAPI.updateNameServerAddressListByDnsLookup(this.brokerContainerConfig.getNamesrvAddr()); } else { this.brokerOuterAPI.updateNameServerAddressList(this.brokerContainerConfig.getNamesrvAddr()); } } public boolean initialize() { this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.containerClientHouseKeepingService); this.fastRemotingServer = this.remotingServer.newRemotingServer(this.nettyServerConfig.getListenPort() - 2); this.brokerContainerExecutor = ThreadUtils.newThreadPoolExecutor( 1, 1, 1000 * 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10000), new ThreadFactoryImpl("SharedBrokerThread_")); this.registerProcessor(); if (this.brokerContainerConfig.getNamesrvAddr() != null) { this.updateNamesrvAddr(); LOG.info("Set user specified name server address: {}", this.brokerContainerConfig.getNamesrvAddr()); // also auto update namesrv if specify this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerContainer.this.updateNamesrvAddr(); } catch (Throwable e) { LOG.error("ScheduledTask fetchNameServerAddr exception", e); } } }, 1000 * 10, this.brokerContainerConfig.getUpdateNamesrvAddrInterval(), TimeUnit.MILLISECONDS); } else if (this.brokerContainerConfig.isFetchNamesrvAddrByAddressServer()) { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerContainer.this.brokerOuterAPI.fetchNameServerAddr(); } catch (Throwable e) { LOG.error("ScheduledTask fetchNameServerAddr exception", e); } } }, 1000 * 10, this.brokerContainerConfig.getFetchNamesrvAddrInterval(), TimeUnit.MILLISECONDS); } this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { BrokerContainer.this.brokerOuterAPI.refreshMetadata(); } catch (Exception e) { LOG.error("ScheduledTask refresh metadata exception", e); } } }, 10, 5, TimeUnit.SECONDS); return true; } public void registerProcessor() { remotingServer.registerDefaultProcessor(brokerContainerProcessor, this.brokerContainerExecutor); fastRemotingServer.registerDefaultProcessor(brokerContainerProcessor, this.brokerContainerExecutor); } @Override public void start() throws Exception { if (this.remotingServer != null) { this.remotingServer.start(); } if (this.fastRemotingServer != null) { this.fastRemotingServer.start(); } if (this.brokerOuterAPI != null) { this.brokerOuterAPI.start(); } } @Override public void shutdown() { // Shutdown slave brokers for (InnerSalveBrokerController slaveBrokerController : slaveBrokerControllers.values()) { slaveBrokerController.shutdown(); } slaveBrokerControllers.clear(); // Shutdown master brokers for (BrokerController masterBrokerController : masterBrokerControllers.values()) { masterBrokerController.shutdown(); } masterBrokerControllers.clear(); // Shutdown dLedger brokers dLedgerBrokerControllers.values().forEach(InnerBrokerController::shutdown); dLedgerBrokerControllers.clear(); // Shutdown the remoting server with a high priority to avoid further traffic if (this.remotingServer != null) { this.remotingServer.shutdown(); } if (this.fastRemotingServer != null) { this.fastRemotingServer.shutdown(); } // Shutdown the request executors ThreadUtils.shutdown(this.brokerContainerExecutor); if (this.brokerOuterAPI != null) { this.brokerOuterAPI.shutdown(); } } public void registerClientRPCHook(RPCHook rpcHook) { this.getBrokerOuterAPI().registerRPCHook(rpcHook); } public void clearClientRPCHook() { this.getBrokerOuterAPI().clearRPCHook(); } public List getBrokerBootHookList() { return brokerBootHookList; } public void registerBrokerBootHook(BrokerBootHook brokerBootHook) { this.brokerBootHookList.add(brokerBootHook); LOG.info("register BrokerBootHook, {}", brokerBootHook.hookName()); } @Override public InnerBrokerController addBroker(ConfigContext configContext) throws Exception { BrokerConfig brokerConfig = configContext.getBrokerConfig(); MessageStoreConfig storeConfig = configContext.getMessageStoreConfig(); AuthConfig authConfig = configContext.getAuthConfig(); if (storeConfig.isEnableDLegerCommitLog()) { return this.addDLedgerBroker(brokerConfig, storeConfig, authConfig); } else { if (brokerConfig.getBrokerId() == MixAll.MASTER_ID && storeConfig.getBrokerRole() != BrokerRole.SLAVE) { return this.addMasterBroker(brokerConfig, storeConfig, authConfig); } if (brokerConfig.getBrokerId() != MixAll.MASTER_ID && storeConfig.getBrokerRole() == BrokerRole.SLAVE) { return this.addSlaveBroker(brokerConfig, storeConfig, authConfig); } } return null; } public InnerBrokerController addDLedgerBroker(final BrokerConfig brokerConfig, final MessageStoreConfig storeConfig, final AuthConfig authConfig) throws Exception { brokerConfig.setInBrokerContainer(true); if (storeConfig.isDuplicationEnable()) { LOG.error("Can not add broker to container when duplicationEnable is true currently"); throw new Exception("Can not add broker to container when duplicationEnable is true currently"); } InnerBrokerController brokerController = new InnerBrokerController(this, brokerConfig, storeConfig, authConfig); BrokerIdentity brokerIdentity = brokerController.getBrokerIdentity(); final BrokerController previousBroker = dLedgerBrokerControllers.putIfAbsent(brokerIdentity, brokerController); if (previousBroker == null) { // New dLedger broker added, start it try { final boolean initResult = brokerController.initialize(); if (!initResult) { dLedgerBrokerControllers.remove(brokerIdentity); brokerController.shutdown(); throw new Exception("Failed to init dLedger broker " + brokerIdentity.getCanonicalName()); } } catch (Exception e) { // Remove the failed dLedger broker and throw the exception dLedgerBrokerControllers.remove(brokerIdentity); brokerController.shutdown(); throw new Exception("Failed to initialize dLedger broker " + brokerIdentity.getCanonicalName(), e); } return brokerController; } throw new Exception(brokerIdentity.getCanonicalName() + " has already been added to current broker container"); } public InnerBrokerController addMasterBroker(final BrokerConfig masterBrokerConfig, final MessageStoreConfig storeConfig, final AuthConfig authConfig) throws Exception { masterBrokerConfig.setInBrokerContainer(true); if (storeConfig.isDuplicationEnable()) { LOG.error("Can not add broker to container when duplicationEnable is true currently"); throw new Exception("Can not add broker to container when duplicationEnable is true currently"); } InnerBrokerController masterBroker = new InnerBrokerController(this, masterBrokerConfig, storeConfig, authConfig); BrokerIdentity brokerIdentity = masterBroker.getBrokerIdentity(); final BrokerController previousBroker = masterBrokerControllers.putIfAbsent(brokerIdentity, masterBroker); if (previousBroker == null) { // New master broker added, start it try { final boolean initResult = masterBroker.initialize(); if (!initResult) { masterBrokerControllers.remove(brokerIdentity); masterBroker.shutdown(); throw new Exception("Failed to init master broker " + masterBrokerConfig.getCanonicalName()); } for (InnerSalveBrokerController slaveBroker : this.getSlaveBrokers()) { if (slaveBroker.getMessageStore().getMasterStoreInProcess() == null) { slaveBroker.getMessageStore().setMasterStoreInProcess(masterBroker.getMessageStore()); } } } catch (Exception e) { // Remove the failed master broker and throw the exception masterBrokerControllers.remove(brokerIdentity); masterBroker.shutdown(); throw new Exception("Failed to initialize master broker " + masterBrokerConfig.getCanonicalName(), e); } return masterBroker; } throw new Exception(masterBrokerConfig.getCanonicalName() + " has already been added to current broker container"); } /** * This function will create a slave broker along with the main broker, and start it with a different port. * * @param slaveBrokerConfig the specific slave broker config * @throws Exception is thrown if an error occurs */ public InnerSalveBrokerController addSlaveBroker(final BrokerConfig slaveBrokerConfig, final MessageStoreConfig storeConfig, final AuthConfig authConfig) throws Exception { slaveBrokerConfig.setInBrokerContainer(true); if (storeConfig.isDuplicationEnable()) { LOG.error("Can not add broker to container when duplicationEnable is true currently"); throw new Exception("Can not add broker to container when duplicationEnable is true currently"); } int ratio = storeConfig.getAccessMessageInMemoryMaxRatio() - 10; storeConfig.setAccessMessageInMemoryMaxRatio(Math.max(ratio, 0)); InnerSalveBrokerController slaveBroker = new InnerSalveBrokerController(this, slaveBrokerConfig, storeConfig, authConfig); BrokerIdentity brokerIdentity = slaveBroker.getBrokerIdentity(); final InnerSalveBrokerController previousBroker = slaveBrokerControllers.putIfAbsent(brokerIdentity, slaveBroker); if (previousBroker == null) { // New slave broker added, start it try { final boolean initResult = slaveBroker.initialize(); if (!initResult) { slaveBrokerControllers.remove(brokerIdentity); slaveBroker.shutdown(); throw new Exception("Failed to init slave broker " + slaveBrokerConfig.getCanonicalName()); } BrokerController masterBroker = this.peekMasterBroker(); if (slaveBroker.getMessageStore().getMasterStoreInProcess() == null && masterBroker != null) { slaveBroker.getMessageStore().setMasterStoreInProcess(masterBroker.getMessageStore()); } } catch (Exception e) { // Remove the failed slave broker and throw the exception slaveBrokerControllers.remove(brokerIdentity); slaveBroker.shutdown(); throw new Exception("Failed to initialize slave broker " + slaveBrokerConfig.getCanonicalName(), e); } return slaveBroker; } throw new Exception(slaveBrokerConfig.getCanonicalName() + " has already been added to current broker container"); } @Override public BrokerController removeBroker(final BrokerIdentity brokerIdentity) throws Exception { InnerBrokerController dLedgerController = dLedgerBrokerControllers.remove(brokerIdentity); if (dLedgerController != null) { dLedgerController.shutdown(); return dLedgerController; } InnerSalveBrokerController slaveBroker = slaveBrokerControllers.remove(brokerIdentity); if (slaveBroker != null) { slaveBroker.shutdown(); return slaveBroker; } BrokerController masterBroker = masterBrokerControllers.remove(brokerIdentity); BrokerController nextMasterBroker = this.peekMasterBroker(); for (InnerSalveBrokerController slave : this.getSlaveBrokers()) { if (nextMasterBroker == null) { slave.getMessageStore().setMasterStoreInProcess(null); } else { slave.getMessageStore().setMasterStoreInProcess(nextMasterBroker.getMessageStore()); } } if (masterBroker != null) { masterBroker.shutdown(); return masterBroker; } return null; } @Override public BrokerController getBroker(final BrokerIdentity brokerIdentity) { InnerSalveBrokerController slaveBroker = slaveBrokerControllers.get(brokerIdentity); if (slaveBroker != null) { return slaveBroker; } return masterBrokerControllers.get(brokerIdentity); } @Override public Collection getMasterBrokers() { return masterBrokerControllers.values(); } @Override public Collection getSlaveBrokers() { return slaveBrokerControllers.values(); } @Override public List getBrokerControllers() { List brokerControllers = new ArrayList<>(); brokerControllers.addAll(this.getMasterBrokers()); brokerControllers.addAll(this.getSlaveBrokers()); return brokerControllers; } @Override public BrokerController peekMasterBroker() { if (!masterBrokerControllers.isEmpty()) { return masterBrokerControllers.values().iterator().next(); } return null; } public BrokerController findBrokerControllerByBrokerName(String brokerName) { for (BrokerController brokerController : masterBrokerControllers.values()) { if (brokerController.getBrokerConfig().getBrokerName().equals(brokerName)) { return brokerController; } } for (BrokerController brokerController : slaveBrokerControllers.values()) { if (brokerController.getBrokerConfig().getBrokerName().equals(brokerName)) { return brokerController; } } return null; } public ExecutorService getBrokerContainerExecutor() { return brokerContainerExecutor; } } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/BrokerContainerConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.common.utils.NetworkUtil; public class BrokerContainerConfig { private String rocketmqHome = MixAll.ROCKETMQ_HOME_DIR; @ImportantField private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); @ImportantField private boolean fetchNameSrvAddrByDnsLookup = false; @ImportantField private boolean fetchNamesrvAddrByAddressServer = false; @ImportantField private String brokerContainerIP = NetworkUtil.getLocalAddress(); private String brokerConfigPaths = null; /** * The interval to fetch namesrv addr, default value is 10 second */ private long fetchNamesrvAddrInterval = 10 * 1000; /** * The interval to update namesrv addr, default value is 120 second */ private long updateNamesrvAddrInterval = 60 * 2 * 1000; /** * Config in this black list will be not allowed to update by command. * Try to update this config black list by restart process. * Try to update configures in black list by restart process. */ private String configBlackList = "configBlackList;brokerConfigPaths"; public String getRocketmqHome() { return rocketmqHome; } public void setRocketmqHome(String rocketmqHome) { this.rocketmqHome = rocketmqHome; } public String getNamesrvAddr() { return namesrvAddr; } public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; } public boolean isFetchNameSrvAddrByDnsLookup() { return fetchNameSrvAddrByDnsLookup; } public void setFetchNameSrvAddrByDnsLookup(boolean fetchNameSrvAddrByDnsLookup) { this.fetchNameSrvAddrByDnsLookup = fetchNameSrvAddrByDnsLookup; } public boolean isFetchNamesrvAddrByAddressServer() { return fetchNamesrvAddrByAddressServer; } public void setFetchNamesrvAddrByAddressServer(boolean fetchNamesrvAddrByAddressServer) { this.fetchNamesrvAddrByAddressServer = fetchNamesrvAddrByAddressServer; } public String getBrokerContainerIP() { return brokerContainerIP; } public String getBrokerConfigPaths() { return brokerConfigPaths; } public void setBrokerConfigPaths(String brokerConfigPaths) { this.brokerConfigPaths = brokerConfigPaths; } public long getFetchNamesrvAddrInterval() { return fetchNamesrvAddrInterval; } public void setFetchNamesrvAddrInterval(final long fetchNamesrvAddrInterval) { this.fetchNamesrvAddrInterval = fetchNamesrvAddrInterval; } public long getUpdateNamesrvAddrInterval() { return updateNamesrvAddrInterval; } public void setUpdateNamesrvAddrInterval(long updateNamesrvAddrInterval) { this.updateNamesrvAddrInterval = updateNamesrvAddrInterval; } public String getConfigBlackList() { return configBlackList; } public void setConfigBlackList(String configBlackList) { this.configBlackList = configBlackList; } } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import io.netty.channel.ChannelHandlerContext; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.List; import java.util.Properties; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerStartup; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.AddBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetBrokerConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.RemoveBrokerRequestHeader; import org.apache.rocketmq.store.config.MessageStoreConfig; public class BrokerContainerProcessor implements NettyRequestProcessor { protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); protected final BrokerContainer brokerContainer; protected List brokerBootHookList; protected final Set configBlackList = new HashSet<>(); public BrokerContainerProcessor(BrokerContainer brokerContainer) { this.brokerContainer = brokerContainer; initConfigBlackList(); } private void initConfigBlackList() { configBlackList.add("brokerConfigPaths"); configBlackList.add("rocketmqHome"); configBlackList.add("configBlackList"); String[] configArray = brokerContainer.getBrokerContainerConfig().getConfigBlackList().split(";"); configBlackList.addAll(Arrays.asList(configArray)); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { switch (request.getCode()) { case RequestCode.ADD_BROKER: return this.addBroker(ctx, request); case RequestCode.REMOVE_BROKER: return this.removeBroker(ctx, request); case RequestCode.GET_BROKER_CONFIG: return this.getBrokerConfig(ctx, request); case RequestCode.UPDATE_BROKER_CONFIG: return this.updateBrokerConfig(ctx, request); default: break; } return null; } @Override public boolean rejectRequest() { return false; } protected synchronized RemotingCommand addBroker(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final AddBrokerRequestHeader requestHeader = (AddBrokerRequestHeader) request.decodeCommandCustomHeader(AddBrokerRequestHeader.class); LOGGER.info("addBroker called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); Properties brokerProperties = null; String configPath = requestHeader.getConfigPath(); if (configPath != null && !configPath.isEmpty()) { BrokerStartup.SystemConfigFileHelper configFileHelper = new BrokerStartup.SystemConfigFileHelper(); configFileHelper.setFile(configPath); try { brokerProperties = configFileHelper.loadConfig(); } catch (Exception e) { LOGGER.error("addBroker load config from {} failed, {}", configPath, e); } } else { LOGGER.error("addBroker config path is empty"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("addBroker config path is empty"); return response; } if (brokerProperties == null) { LOGGER.error("addBroker properties empty"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("addBroker properties empty"); return response; } BrokerConfig brokerConfig = new BrokerConfig(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); AuthConfig authConfig = new AuthConfig(); MixAll.properties2Object(brokerProperties, brokerConfig); MixAll.properties2Object(brokerProperties, messageStoreConfig); MixAll.properties2Object(brokerProperties, authConfig); messageStoreConfig.setHaListenPort(brokerConfig.getListenPort() + 1); brokerConfig.setBrokerConfigPath(configPath); if (!messageStoreConfig.isEnableDLegerCommitLog()) { if (!brokerConfig.isEnableControllerMode()) { switch (messageStoreConfig.getBrokerRole()) { case ASYNC_MASTER: case SYNC_MASTER: brokerConfig.setBrokerId(MixAll.MASTER_ID); break; case SLAVE: if (brokerConfig.getBrokerId() <= 0) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("slave broker id must be > 0"); return response; } break; default: break; } } if (messageStoreConfig.getTotalReplicas() < messageStoreConfig.getInSyncReplicas() || messageStoreConfig.getTotalReplicas() < messageStoreConfig.getMinInSyncReplicas() || messageStoreConfig.getInSyncReplicas() < messageStoreConfig.getMinInSyncReplicas()) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("invalid replicas number"); return response; } } ConfigContext configContext = new ConfigContext.Builder(). brokerConfig(brokerConfig). messageStoreConfig(messageStoreConfig). authConfig(authConfig). properties(brokerProperties). build(); InnerBrokerController innerBrokerController; try { innerBrokerController = this.brokerContainer.addBroker(configContext); } catch (Exception e) { LOGGER.error("addBroker exception", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); return response; } if (innerBrokerController != null) { innerBrokerController.getConfiguration().registerConfig(brokerProperties); try { for (BrokerBootHook brokerBootHook : brokerBootHookList) { brokerBootHook.executeBeforeStart(innerBrokerController, brokerProperties); } innerBrokerController.start(); for (BrokerBootHook brokerBootHook : brokerBootHookList) { brokerBootHook.executeAfterStart(innerBrokerController, brokerProperties); } } catch (Exception e) { LOGGER.error("start broker exception", e); BrokerIdentity brokerIdentity; if (messageStoreConfig.isEnableDLegerCommitLog()) { brokerIdentity = new BrokerIdentity(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), Integer.parseInt(messageStoreConfig.getdLegerSelfId().substring(1))); } else { brokerIdentity = new BrokerIdentity(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerConfig.getBrokerId()); } this.brokerContainer.removeBroker(brokerIdentity); innerBrokerController.shutdown(); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("start broker failed" + e); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("add broker return null"); } return response; } protected synchronized RemotingCommand removeBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final RemoveBrokerRequestHeader requestHeader = (RemoveBrokerRequestHeader) request.decodeCommandCustomHeader(RemoveBrokerRequestHeader.class); LOGGER.info("removeBroker called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); BrokerIdentity brokerIdentity = new BrokerIdentity(requestHeader.getBrokerClusterName(), requestHeader.getBrokerName(), requestHeader.getBrokerId()); BrokerController brokerController; try { brokerController = this.brokerContainer.removeBroker(brokerIdentity); } catch (Exception e) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(e.getMessage()); return response; } if (brokerController != null) { response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.BROKER_NOT_EXIST); response.setRemark("Broker not exist"); } return response; } public void registerBrokerBootHook(List brokerBootHookList) { this.brokerBootHookList = brokerBootHookList; } private RemotingCommand updateBrokerConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LOGGER.info("updateSharedBrokerConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); byte[] body = request.getBody(); if (body != null) { try { String bodyStr = new String(body, MixAll.DEFAULT_CHARSET); Properties properties = MixAll.string2Properties(bodyStr); if (properties == null) { LOGGER.error("string2Properties error"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string2Properties error"); return response; } if (validateBlackListConfigExist(properties)) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("Can not update config in black list."); return response; } LOGGER.info("updateBrokerContainerConfig, new config: [{}] client: {} ", properties, ctx.channel().remoteAddress()); this.brokerContainer.getConfiguration().update(properties); } catch (UnsupportedEncodingException e) { LOGGER.error("", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private boolean validateBlackListConfigExist(Properties properties) { for (String blackConfig : configBlackList) { if (properties.containsKey(blackConfig)) { return true; } } return false; } private RemotingCommand getBrokerConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(GetBrokerConfigResponseHeader.class); final GetBrokerConfigResponseHeader responseHeader = (GetBrokerConfigResponseHeader) response.readCustomHeader(); String content = this.brokerContainer.getConfiguration().getAllConfigsFormatString(); if (content != null && content.length() > 0) { try { content = MixAll.adjustConfigForPlatform(content); response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { LOGGER.error("", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } responseHeader.setVersion(this.brokerContainer.getConfiguration().getDataVersionJson()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/BrokerContainerStartup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.NettySystemConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.store.config.MessageStoreConfig; public class BrokerContainerStartup { private static final String BROKER_CONTAINER_CONFIG_OPTION = "c"; private static final String BROKER_CONFIG_OPTION = "b"; private static final String PRINT_PROPERTIES_OPTION = "p"; private static final String PRINT_IMPORTANT_PROPERTIES_OPTION = "m"; public static Properties properties = null; public static CommandLine commandLine = null; public static String configFile = null; public static Logger log; public static final SystemConfigFileHelper CONFIG_FILE_HELPER = new SystemConfigFileHelper(); public static String rocketmqHome = null; public static void main(String[] args) { final BrokerContainerConfig containerConfig = new BrokerContainerConfig(); final NettyServerConfig nettyServerConfig = new NettyServerConfig(); final NettyClientConfig nettyClientConfig = new NettyClientConfig(); parseCmdLineToConfig(args, containerConfig, nettyServerConfig, nettyClientConfig); final BrokerContainer brokerContainer = startBrokerContainer(createBrokerContainer(containerConfig, nettyServerConfig, nettyClientConfig)); createAndStartBrokers(brokerContainer); } public static List createAndStartBrokers(BrokerContainer brokerContainer) { String[] configPaths = parseBrokerConfigPath(); List brokerControllerList = new ArrayList<>(); if (configPaths != null && configPaths.length > 0) { SystemConfigFileHelper configFileHelper = new SystemConfigFileHelper(); for (String configPath : configPaths) { System.out.printf("Start broker from config file path %s%n", configPath); configFileHelper.setFile(configPath); Properties brokerProperties = null; try { brokerProperties = configFileHelper.loadConfig(); } catch (Exception e) { e.printStackTrace(); System.exit(-1); } final InnerBrokerController innerBrokerController = createAndInitializeBroker(brokerContainer, configPath, brokerProperties); if (innerBrokerController != null) { brokerControllerList.add(innerBrokerController); startBrokerController(brokerContainer, innerBrokerController, brokerProperties); } } } return brokerControllerList; } public static String[] parseBrokerConfigPath() { String brokerConfigList = null; if (commandLine.hasOption(BROKER_CONFIG_OPTION)) { brokerConfigList = commandLine.getOptionValue(BROKER_CONFIG_OPTION); } else if (commandLine.hasOption(BROKER_CONTAINER_CONFIG_OPTION)) { String brokerContainerConfigPath = commandLine.getOptionValue(BROKER_CONTAINER_CONFIG_OPTION); if (brokerContainerConfigPath != null) { BrokerContainerConfig brokerContainerConfig = new BrokerContainerConfig(); SystemConfigFileHelper configFileHelper = new SystemConfigFileHelper(); configFileHelper.setFile(brokerContainerConfigPath); Properties brokerContainerProperties = null; try { brokerContainerProperties = configFileHelper.loadConfig(); } catch (Exception e) { e.printStackTrace(); System.exit(-1); } if (brokerContainerProperties != null) { MixAll.properties2Object(brokerContainerProperties, brokerContainerConfig); } brokerConfigList = brokerContainerConfig.getBrokerConfigPaths(); } } if (brokerConfigList != null) { return brokerConfigList.split(":"); } return null; } public static InnerBrokerController createAndInitializeBroker(BrokerContainer brokerContainer, String filePath, Properties brokerProperties) { final BrokerConfig brokerConfig = new BrokerConfig(); final MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); final AuthConfig authConfig = new AuthConfig(); if (brokerProperties != null) { properties2SystemEnv(brokerProperties); MixAll.properties2Object(brokerProperties, brokerConfig); MixAll.properties2Object(brokerProperties, messageStoreConfig); MixAll.properties2Object(brokerProperties, authConfig); } messageStoreConfig.setHaListenPort(brokerConfig.getListenPort() + 1); MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), brokerConfig); if (!brokerConfig.isEnableControllerMode()) { switch (messageStoreConfig.getBrokerRole()) { case ASYNC_MASTER: case SYNC_MASTER: brokerConfig.setBrokerId(MixAll.MASTER_ID); break; case SLAVE: if (brokerConfig.getBrokerId() <= 0) { System.out.printf("Slave's brokerId must be > 0%n"); System.exit(-3); } break; default: break; } } if (messageStoreConfig.getTotalReplicas() < messageStoreConfig.getInSyncReplicas() || messageStoreConfig.getTotalReplicas() < messageStoreConfig.getMinInSyncReplicas() || messageStoreConfig.getInSyncReplicas() < messageStoreConfig.getMinInSyncReplicas()) { System.out.printf("invalid replicas number%n"); System.exit(-3); } brokerConfig.setBrokerConfigPath(filePath); log = LoggerFactory.getLogger(brokerConfig.getIdentifier() + LoggerName.BROKER_LOGGER_NAME); MixAll.printObjectProperties(log, brokerConfig); MixAll.printObjectProperties(log, messageStoreConfig); ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(brokerConfig) .messageStoreConfig(messageStoreConfig) .authConfig(authConfig) .properties(brokerProperties) .build(); try { InnerBrokerController innerBrokerController = brokerContainer.addBroker(configContext); if (innerBrokerController != null) { innerBrokerController.getConfiguration().registerConfig(brokerProperties); return innerBrokerController; } else { System.out.printf("Add broker [%s-%s] failed.%n", brokerConfig.getBrokerName(), brokerConfig.getBrokerId()); } } catch (Exception e) { e.printStackTrace(); System.exit(-1); } return null; } public static BrokerContainer startBrokerContainer(BrokerContainer brokerContainer) { try { brokerContainer.start(); String tip = "The broker container boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer(); if (null != brokerContainer.getBrokerContainerConfig().getNamesrvAddr()) { tip += " and name server is " + brokerContainer.getBrokerContainerConfig().getNamesrvAddr(); } log.info(tip); System.out.printf("%s%n", tip); return brokerContainer; } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } return null; } public static void startBrokerController(BrokerContainer brokerContainer, InnerBrokerController innerBrokerController, Properties brokerProperties) { try { for (BrokerBootHook hook : brokerContainer.getBrokerBootHookList()) { hook.executeBeforeStart(innerBrokerController, brokerProperties); } innerBrokerController.start(); for (BrokerBootHook hook : brokerContainer.getBrokerBootHookList()) { hook.executeAfterStart(innerBrokerController, brokerProperties); } String tip = String.format("Broker [%s-%s] boot success. serializeType=%s", innerBrokerController.getBrokerConfig().getBrokerName(), innerBrokerController.getBrokerConfig().getBrokerId(), RemotingCommand.getSerializeTypeConfigInThisServer()); log.info(tip); System.out.printf("%s%n", tip); } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } } public static void shutdown(final BrokerContainer controller) { if (null != controller) { controller.shutdown(); } } public static Properties parseCmdLineToConfig(String[] args, BrokerContainerConfig containerConfig, NettyServerConfig nettyServerConfig, NettyClientConfig nettyClientConfig) { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); if (null == System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE)) { NettySystemConfig.socketSndbufSize = 131072; } if (null == System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE)) { NettySystemConfig.socketRcvbufSize = 131072; } try { //PackageConflictDetect.detectFastjson(); Options options = ServerUtil.buildCommandlineOptions(new Options()); commandLine = ServerUtil.parseCmdLine("mqbroker", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } nettyServerConfig.setListenPort(10811); if (commandLine.hasOption(BROKER_CONTAINER_CONFIG_OPTION)) { String file = commandLine.getOptionValue(BROKER_CONTAINER_CONFIG_OPTION); if (file != null) { CONFIG_FILE_HELPER.setFile(file); configFile = file; BrokerPathConfigHelper.setBrokerConfigPath(file); } } properties = CONFIG_FILE_HELPER.loadConfig(); if (properties != null) { properties2SystemEnv(properties); MixAll.properties2Object(properties, containerConfig); MixAll.properties2Object(properties, nettyServerConfig); MixAll.properties2Object(properties, nettyClientConfig); } MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), containerConfig); if (null == containerConfig.getRocketmqHome()) { System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation", MixAll.ROCKETMQ_HOME_ENV); System.exit(-2); } rocketmqHome = containerConfig.getRocketmqHome(); String namesrvAddr = containerConfig.getNamesrvAddr(); if (null != namesrvAddr) { try { String[] addrArray = namesrvAddr.split(";"); for (String addr : addrArray) { NetworkUtil.string2SocketAddress(addr); } } catch (Exception e) { System.out.printf( "The Name Server Address[%s] illegal, please set it as follows, \"127.0.0.1:9876;192.168.0.1:9876\"%n", namesrvAddr); System.exit(-3); } } if (commandLine.hasOption(PRINT_PROPERTIES_OPTION)) { Logger console = LoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); MixAll.printObjectProperties(console, containerConfig); MixAll.printObjectProperties(console, nettyServerConfig); MixAll.printObjectProperties(console, nettyClientConfig); System.exit(0); } else if (commandLine.hasOption(PRINT_IMPORTANT_PROPERTIES_OPTION)) { Logger console = LoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); MixAll.printObjectProperties(console, containerConfig, true); MixAll.printObjectProperties(console, nettyServerConfig, true); MixAll.printObjectProperties(console, nettyClientConfig, true); System.exit(0); } } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } return properties; } public static BrokerContainer createBrokerContainer(BrokerContainerConfig containerConfig, NettyServerConfig nettyServerConfig, NettyClientConfig nettyClientConfig) { log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); MixAll.printObjectProperties(log, containerConfig); MixAll.printObjectProperties(log, nettyServerConfig); MixAll.printObjectProperties(log, nettyClientConfig); final BrokerContainer brokerContainer = new BrokerContainer( containerConfig, nettyServerConfig, nettyClientConfig); // remember all configs to prevent discard brokerContainer.getConfiguration().registerConfig(properties); boolean initResult = brokerContainer.initialize(); if (!initResult) { brokerContainer.shutdown(); System.exit(-3); } setupShutdownHook(brokerContainer); return brokerContainer; } public static void setupShutdownHook(final BrokerContainer brokerContainer) { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { private volatile boolean hasShutdown = false; private AtomicInteger shutdownTimes = new AtomicInteger(0); @Override public void run() { synchronized (this) { log.info("Shutdown hook was invoked, {}", this.shutdownTimes.incrementAndGet()); if (!this.hasShutdown) { this.hasShutdown = true; long beginTime = System.currentTimeMillis(); brokerContainer.shutdown(); long consumingTimeTotal = System.currentTimeMillis() - beginTime; log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal); } } } }, "ShutdownHook")); } private static void properties2SystemEnv(Properties properties) { if (properties == null) { return; } String rmqAddressServerDomain = properties.getProperty("rmqAddressServerDomain", MixAll.WS_DOMAIN_NAME); String rmqAddressServerSubGroup = properties.getProperty("rmqAddressServerSubGroup", MixAll.WS_DOMAIN_SUBGROUP); System.setProperty("rocketmq.namesrv.domain", rmqAddressServerDomain); System.setProperty("rocketmq.namesrv.domain.subgroup", rmqAddressServerSubGroup); } private static Options buildCommandlineOptions(final Options options) { Option opt = new Option(BROKER_CONTAINER_CONFIG_OPTION, "configFile", true, "Config file for shared broker"); opt.setRequired(false); options.addOption(opt); opt = new Option(PRINT_PROPERTIES_OPTION, "printConfigItem", false, "Print all config item"); opt.setRequired(false); options.addOption(opt); opt = new Option(PRINT_IMPORTANT_PROPERTIES_OPTION, "printImportantConfig", false, "Print important config item"); opt.setRequired(false); options.addOption(opt); opt = new Option(BROKER_CONFIG_OPTION, "brokerConfigFiles", true, "The path of broker config files, split by ':'"); opt.setRequired(false); options.addOption(opt); return options; } public static class SystemConfigFileHelper { private static final Logger LOGGER = LoggerFactory.getLogger(SystemConfigFileHelper.class); private String file; public SystemConfigFileHelper() { } public Properties loadConfig() throws Exception { Properties properties = new Properties(); try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { properties.load(in); } return properties; } public void update(Properties properties) throws Exception { LOGGER.error("[SystemConfigFileHelper] update no thing."); } public void setFile(String file) { this.file = file; } public String getFile() { return file; } } } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/ContainerClientHouseKeepingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import io.netty.channel.Channel; import java.util.Collection; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.remoting.ChannelEventListener; public class ContainerClientHouseKeepingService implements ChannelEventListener { private final IBrokerContainer brokerContainer; public ContainerClientHouseKeepingService(final IBrokerContainer brokerContainer) { this.brokerContainer = brokerContainer; } @Override public void onChannelConnect(String remoteAddr, Channel channel) { onChannelOperation(CallbackCode.CONNECT, remoteAddr, channel); } @Override public void onChannelClose(String remoteAddr, Channel channel) { onChannelOperation(CallbackCode.CLOSE, remoteAddr, channel); } @Override public void onChannelException(String remoteAddr, Channel channel) { onChannelOperation(CallbackCode.EXCEPTION, remoteAddr, channel); } @Override public void onChannelIdle(String remoteAddr, Channel channel) { onChannelOperation(CallbackCode.IDLE, remoteAddr, channel); } @Override public void onChannelActive(String remoteAddr, Channel channel) { onChannelOperation(CallbackCode.ACTIVE, remoteAddr, channel); } private void onChannelOperation(CallbackCode callbackCode, String remoteAddr, Channel channel) { Collection masterBrokers = this.brokerContainer.getMasterBrokers(); Collection slaveBrokers = this.brokerContainer.getSlaveBrokers(); for (BrokerController masterBroker : masterBrokers) { brokerOperation(masterBroker, callbackCode, remoteAddr, channel); } for (InnerSalveBrokerController slaveBroker : slaveBrokers) { brokerOperation(slaveBroker, callbackCode, remoteAddr, channel); } } private void brokerOperation(BrokerController brokerController, CallbackCode callbackCode, String remoteAddr, Channel channel) { if (callbackCode == CallbackCode.CONNECT) { brokerController.getBrokerStatsManager().incChannelConnectNum(); return; } boolean removed = brokerController.getProducerManager().doChannelCloseEvent(remoteAddr, channel); removed &= brokerController.getConsumerManager().doChannelCloseEvent(remoteAddr, channel); if (removed) { switch (callbackCode) { case CLOSE: brokerController.getBrokerStatsManager().incChannelCloseNum(); break; case EXCEPTION: brokerController.getBrokerStatsManager().incChannelExceptionNum(); break; case IDLE: brokerController.getBrokerStatsManager().incChannelIdleNum(); break; default: break; } } } public enum CallbackCode { /** * onChannelConnect */ CONNECT, /** * onChannelClose */ CLOSE, /** * onChannelException */ EXCEPTION, /** * onChannelIdle */ IDLE, /** * onChannelActive */ ACTIVE } } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/IBrokerContainer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.util.Collection; import java.util.List; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; /** * An interface for broker container to hold multiple master and slave brokers. */ public interface IBrokerContainer { /** * Start broker container */ void start() throws Exception; /** * Shutdown broker container and all the brokers inside. */ void shutdown(); /** * Add a broker to this container with specific broker config. * * @param configContext the specified config context * @return the added BrokerController or null if the broker already exists * @throws Exception when initialize broker */ BrokerController addBroker(ConfigContext configContext) throws Exception; /** * Remove the broker from this container associated with the specific broker identity * * @param brokerIdentity the specific broker identity * @return the removed BrokerController or null if the broker doesn't exists */ BrokerController removeBroker(BrokerIdentity brokerIdentity) throws Exception; /** * Return the broker controller associated with the specific broker identity * * @param brokerIdentity the specific broker identity * @return the associated messaging broker or null */ BrokerController getBroker(BrokerIdentity brokerIdentity); /** * Return all the master brokers belong to this container * * @return the master broker list */ Collection getMasterBrokers(); /** * Return all the slave brokers belong to this container * * @return the slave broker list */ Collection getSlaveBrokers(); /** * Return all broker controller in this container * * @return all broker controller */ List getBrokerControllers(); /** * Return the address of broker container. * * @return broker container address. */ String getBrokerContainerAddr(); /** * Peek the first master broker in container. * * @return the first master broker in container */ BrokerController peekMasterBroker(); /** * Return the config of the broker container * * @return the broker container config */ BrokerContainerConfig getBrokerContainerConfig(); /** * Get netty server config. * * @return netty server config */ NettyServerConfig getNettyServerConfig(); /** * Get netty client config. * * @return netty client config */ NettyClientConfig getNettyClientConfig(); /** * Return the shared BrokerOuterAPI * * @return the shared BrokerOuterAPI */ BrokerOuterAPI getBrokerOuterAPI(); /** * Return the shared RemotingServer * * @return the shared RemotingServer */ RemotingServer getRemotingServer(); } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; public class InnerBrokerController extends BrokerController { protected BrokerContainer brokerContainer; public InnerBrokerController( final BrokerContainer brokerContainer, final BrokerConfig brokerConfig, final MessageStoreConfig messageStoreConfig, final AuthConfig authConfig ) { super(brokerConfig, messageStoreConfig, authConfig); this.brokerContainer = brokerContainer; this.brokerOuterAPI = this.brokerContainer.getBrokerOuterAPI(); } @Override protected void initializeRemotingServer() { RemotingServer remotingServer = this.brokerContainer.getRemotingServer().newRemotingServer(brokerConfig.getListenPort()); RemotingServer fastRemotingServer = this.brokerContainer.getRemotingServer().newRemotingServer(brokerConfig.getListenPort() - 2); if (this.brokerMetricsManager != null && remotingServer instanceof NettyRemotingServer) { ((NettyRemotingServer) remotingServer).setRemotingMetricsManager(this.brokerMetricsManager.getRemotingMetricsManager()); } if (this.brokerMetricsManager != null && fastRemotingServer instanceof NettyRemotingServer) { ((NettyRemotingServer) fastRemotingServer).setRemotingMetricsManager(this.brokerMetricsManager.getRemotingMetricsManager()); } setRemotingServer(remotingServer); setFastRemotingServer(fastRemotingServer); } @Override protected void initializeScheduledTasks() { initializeBrokerScheduledTasks(); } @Override public void start() throws Exception { this.shouldStartTime = System.currentTimeMillis() + messageStoreConfig.getDisappearTimeAfterStart(); if (messageStoreConfig.getTotalReplicas() > 1 && this.brokerConfig.isEnableSlaveActingMaster()) { isIsolated = true; } startBasicService(); if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, true); } scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { 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; } InnerBrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister()); } catch (Throwable e) { BrokerController.LOG.error("registerBrokerAll Exception", e); } } }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS)); if (this.brokerConfig.isEnableSlaveActingMaster()) { scheduleSendHeartbeat(); scheduledFutures.add(this.syncBrokerMemberGroupExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { InnerBrokerController.this.syncBrokerMemberGroup(); } catch (Throwable e) { BrokerController.LOG.error("sync BrokerMemberGroup error. ", e); } } }, 1000, this.brokerConfig.getSyncBrokerMemberGroupPeriod(), TimeUnit.MILLISECONDS)); } if (this.brokerConfig.isEnableControllerMode()) { scheduleSendHeartbeat(); } if (brokerConfig.isSkipPreOnline()) { startServiceWithoutCondition(); } } @Override public void shutdown() { shutdownBasicService(); for (ScheduledFuture scheduledFuture : scheduledFutures) { scheduledFuture.cancel(true); } if (getRemotingServer() != null) { this.brokerContainer.getRemotingServer().removeRemotingServer(brokerConfig.getListenPort()); } if (getFastRemotingServer() != null) { this.brokerContainer.getRemotingServer().removeRemotingServer(brokerConfig.getListenPort() - 2); } } @Override public String getBrokerAddr() { return this.brokerConfig.getBrokerIP1() + ":" + this.brokerConfig.getListenPort(); } @Override public String getHAServerAddr() { return this.brokerConfig.getBrokerIP2() + ":" + this.messageStoreConfig.getHaListenPort(); } @Override public long getMinBrokerIdInGroup() { return this.minBrokerIdInGroup; } @Override public int getListenPort() { return this.brokerConfig.getListenPort(); } public BrokerOuterAPI getBrokerOuterAPI() { return brokerContainer.getBrokerOuterAPI(); } public BrokerContainer getBrokerContainer() { return this.brokerContainer; } public NettyServerConfig getNettyServerConfig() { return brokerContainer.getNettyServerConfig(); } public NettyClientConfig getNettyClientConfig() { return brokerContainer.getNettyClientConfig(); } public MessageStore getMessageStoreByBrokerName(String brokerName) { if (this.brokerConfig.getBrokerName().equals(brokerName)) { return this.getMessageStore(); } BrokerController brokerController = this.brokerContainer.findBrokerControllerByBrokerName(brokerName); if (brokerController != null) { return brokerController.getMessageStore(); } return null; } @Override public BrokerController peekMasterBroker() { if (brokerConfig.getBrokerId() == MixAll.MASTER_ID) { return this; } return this.brokerContainer.peekMasterBroker(); } } ================================================ FILE: container/src/main/java/org/apache/rocketmq/container/InnerSalveBrokerController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import com.google.common.base.Preconditions; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.config.MessageStoreConfig; public class InnerSalveBrokerController extends InnerBrokerController { public InnerSalveBrokerController(final BrokerContainer brokerContainer, final BrokerConfig brokerConfig, final MessageStoreConfig storeConfig, final AuthConfig authConfig) { super(brokerContainer, brokerConfig, storeConfig, authConfig); // Check configs checkSlaveBrokerConfig(); } private void checkSlaveBrokerConfig() { Preconditions.checkNotNull(brokerConfig.getBrokerClusterName()); Preconditions.checkNotNull(brokerConfig.getBrokerName()); Preconditions.checkArgument(brokerConfig.getBrokerId() != MixAll.MASTER_ID); } } ================================================ FILE: container/src/test/java/org/apache/rocketmq/container/BrokerContainerExtensibilityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.io.File; import java.util.Properties; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class BrokerContainerExtensibilityTest { private BrokerContainer brokerContainer; private BrokerContainerConfig containerConfig; private NettyServerConfig nettyServerConfig; private NettyClientConfig nettyClientConfig; private File tempDir; @Before public void setUp() { tempDir = new File(System.getProperty("java.io.tmpdir") + File.separator + "container-test-" + UUID.randomUUID()); tempDir.mkdirs(); containerConfig = new BrokerContainerConfig(); // Note: brokerContainerIP is automatically set to local address nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(0); // Random port nettyClientConfig = new NettyClientConfig(); brokerContainer = new BrokerContainer(containerConfig, nettyServerConfig, nettyClientConfig); } @After public void tearDown() { if (brokerContainer != null) { try { brokerContainer.shutdown(); } catch (Exception e) { // Ignore cleanup errors } } UtilAll.deleteFile(tempDir); } @Test public void testBrokerBootHookExtensibility() throws Exception { // Test that BrokerBootHook system provides proper extensibility // This test verifies that hooks can be registered and executed correctly // during the broker lifecycle (before and after start) brokerContainer.initialize(); // Create a test hook AtomicInteger beforeStartCount = new AtomicInteger(0); AtomicInteger afterStartCount = new AtomicInteger(0); AtomicBoolean hookExecuted = new AtomicBoolean(false); BrokerBootHook testHook = new BrokerBootHook() { @Override public String hookName() { return "TestBrokerBootHook"; } @Override public void executeBeforeStart(InnerBrokerController innerBrokerController, Properties properties) throws Exception { beforeStartCount.incrementAndGet(); hookExecuted.set(true); assertThat(innerBrokerController).isNotNull(); } @Override public void executeAfterStart(InnerBrokerController innerBrokerController, Properties properties) throws Exception { afterStartCount.incrementAndGet(); assertThat(innerBrokerController).isNotNull(); } }; // Register the hook brokerContainer.getBrokerBootHookList().add(testHook); // Verify hook is registered assertThat(brokerContainer.getBrokerBootHookList()).contains(testHook); assertThat(brokerContainer.getBrokerBootHookList().size()).isGreaterThan(0); // Start container brokerContainer.start(); // Create a broker to trigger hooks BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerClusterName("test-cluster"); brokerConfig.setBrokerName("test-broker"); brokerConfig.setBrokerId(0); MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setStorePathRootDir(tempDir.getAbsolutePath()); storeConfig.setStorePathCommitLog(tempDir.getAbsolutePath() + File.separator + "commitlog"); Properties testProperties = new Properties(); testProperties.setProperty("brokerName", "test-broker"); ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(brokerConfig) .messageStoreConfig(storeConfig) .nettyServerConfig(new NettyServerConfig()) .nettyClientConfig(new NettyClientConfig()) .properties(testProperties) .build(); // Add broker should trigger hooks try { InnerBrokerController innerBroker = brokerContainer.addBroker(configContext); // Manually execute hooks as they would be executed during broker startup // This simulates the real scenario where hooks are executed before broker.start() for (BrokerBootHook brokerBootHook : brokerContainer.getBrokerBootHookList()) { brokerBootHook.executeBeforeStart(innerBroker, testProperties); } // Verify hook was executed assertThat(hookExecuted.get()).isTrue(); assertThat(beforeStartCount.get()).isEqualTo(1); // Test executeAfterStart hook as well for (BrokerBootHook brokerBootHook : brokerContainer.getBrokerBootHookList()) { brokerBootHook.executeAfterStart(innerBroker, testProperties); } assertThat(afterStartCount.get()).isEqualTo(1); // Cleanup brokerContainer.removeBroker(innerBroker.getBrokerIdentity()); } catch (Exception e) { // Expected for some configurations in test environment } } @Test public void testContainerConfigurationExtensibility() throws Exception { // Test that container configuration is properly accessible and modifiable assertThat(brokerContainer.getBrokerContainerConfig()).isNotNull(); assertThat(brokerContainer.getBrokerContainerConfig()).isSameAs(containerConfig); // Test address configuration String expectedAddr = containerConfig.getBrokerContainerIP() + ":" + nettyServerConfig.getListenPort(); assertThat(brokerContainer.getBrokerContainerAddr()).isEqualTo(expectedAddr); // Test network configuration access assertThat(brokerContainer.getNettyServerConfig()).isSameAs(nettyServerConfig); assertThat(brokerContainer.getNettyClientConfig()).isSameAs(nettyClientConfig); assertThat(brokerContainer.getBrokerOuterAPI()).isNotNull(); } @Test public void testContainerInitialization() throws Exception { // Test container initialization process assertThat(brokerContainer.initialize()).isTrue(); // Verify essential components are initialized assertThat(brokerContainer.getRemotingServer()).isNotNull(); assertThat(brokerContainer.getBrokerOuterAPI()).isNotNull(); assertThat(brokerContainer.getConfiguration()).isNotNull(); // Test that container can be started and stopped brokerContainer.start(); assertThat(brokerContainer.getRemotingServer()).isNotNull(); brokerContainer.shutdown(); } @Test public void testBrokerContainerProcessor() throws Exception { // Test that BrokerContainerProcessor is properly integrated brokerContainer.initialize(); brokerContainer.start(); // Verify processor is registered assertThat(brokerContainer.getRemotingServer()).isNotNull(); // The processor should handle container-specific requests // (Implementation details are tested in the processor's own tests) assertThat(true).isTrue(); // Placeholder for successful integration } @Test public void testContainerStartupAndShutdownSequence() throws Exception { // Test proper startup and shutdown sequence assertThat(brokerContainer.initialize()).isTrue(); // Start should succeed brokerContainer.start(); // Shutdown should clean up properly brokerContainer.shutdown(); // Multiple shutdown calls should be safe brokerContainer.shutdown(); } @Test public void testContainerExtensibilityPoints() throws Exception { // Test that the container provides proper extension points brokerContainer.initialize(); // Verify that hook list is accessible and modifiable int initialHookCount = brokerContainer.getBrokerBootHookList().size(); BrokerBootHook extensionHook = new BrokerBootHook() { @Override public String hookName() { return "ExtensionTestHook"; } @Override public void executeBeforeStart(InnerBrokerController innerBrokerController, Properties properties) { // Extension point for before start } @Override public void executeAfterStart(InnerBrokerController innerBrokerController, Properties properties) { // Extension point for after start } }; brokerContainer.getBrokerBootHookList().add(extensionHook); assertThat(brokerContainer.getBrokerBootHookList().size()).isEqualTo(initialHookCount + 1); // Verify hook name is accessible assertThat(extensionHook.hookName()).isEqualTo("ExtensionTestHook"); } } ================================================ FILE: container/src/test/java/org/apache/rocketmq/container/BrokerContainerStartupTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.assertj.core.util.Arrays; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.apache.rocketmq.container.BrokerContainerStartup.parseCmdLineToConfig; import static org.assertj.core.api.Java6Assertions.assertThat; @RunWith(MockitoJUnitRunner.class) public class BrokerContainerStartupTest { private static final List TMP_FILE_LIST = new ArrayList<>(); private static final String BROKER_NAME_PREFIX = "TestBroker"; private static final String SHARED_BROKER_NAME_PREFIX = "TestBrokerContainer"; private static String brokerConfigPath; private static String brokerContainerConfigPath; @Mock private BrokerConfig brokerConfig; private String storePathRootDir = "store/test"; @Mock private NettyClientConfig nettyClientConfig; @Mock private NettyServerConfig nettyServerConfig; @Before public void init() throws IOException { String brokerName = BROKER_NAME_PREFIX + "_" + System.currentTimeMillis(); BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerName(brokerName); if (brokerConfig.getRocketmqHome() == null) { brokerConfig.setRocketmqHome("../distribution"); } MessageStoreConfig storeConfig = new MessageStoreConfig(); String baseDir = createBaseDir(brokerConfig.getBrokerName() + "_" + brokerConfig.getBrokerId()).getAbsolutePath(); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); brokerConfigPath = "/tmp/" + brokerName; brokerConfig.setBrokerConfigPath(brokerConfigPath); File file = new File(brokerConfigPath); TMP_FILE_LIST.add(file); Properties brokerConfigProp = MixAll.object2Properties(brokerConfig); Properties storeConfigProp = MixAll.object2Properties(storeConfig); for (Object key : storeConfigProp.keySet()) { brokerConfigProp.put(key, storeConfigProp.get(key)); } MixAll.string2File(MixAll.properties2String(brokerConfigProp), brokerConfigPath); brokerContainerConfigPath = "/tmp/" + SHARED_BROKER_NAME_PREFIX + System.currentTimeMillis(); BrokerContainerConfig brokerContainerConfig = new BrokerContainerConfig(); brokerContainerConfig.setBrokerConfigPaths(brokerConfigPath); if (brokerContainerConfig.getRocketmqHome() == null) { brokerContainerConfig.setRocketmqHome("../distribution"); } File file1 = new File(brokerContainerConfigPath); TMP_FILE_LIST.add(file1); Properties brokerContainerConfigProp = MixAll.object2Properties(brokerContainerConfig); MixAll.string2File(MixAll.properties2String(brokerContainerConfigProp), brokerContainerConfigPath); } @After public void destroy() { for (File file : TMP_FILE_LIST) { UtilAll.deleteFile(file); } } @Test public void testStartBrokerContainer() { final BrokerContainerConfig containerConfig = new BrokerContainerConfig(); final NettyServerConfig nettyServerConfig = new NettyServerConfig(); final NettyClientConfig nettyClientConfig = new NettyClientConfig(); parseCmdLineToConfig(Arrays.array("-c", brokerContainerConfigPath), containerConfig, nettyServerConfig, nettyClientConfig); BrokerContainer brokerContainer = BrokerContainerStartup.startBrokerContainer( BrokerContainerStartup.createBrokerContainer(containerConfig, nettyServerConfig, nettyClientConfig)); assertThat(brokerContainer).isNotNull(); List brokers = BrokerContainerStartup.createAndStartBrokers(brokerContainer); assertThat(brokers.size()).isEqualTo(1); brokerContainer.shutdown(); assertThat(brokerContainer.getBrokerControllers().size()).isEqualTo(0); } private static File createBaseDir(String prefix) { final File file; try { file = Files.createTempDirectory(prefix).toFile(); TMP_FILE_LIST.add(file); System.out.printf("create file at %s%n", file.getAbsolutePath()); return file; } catch (IOException e) { throw new RuntimeException("Couldn't create tmp folder", e); } } @Before public void clear() { UtilAll.deleteFile(new File(storePathRootDir)); } @After public void tearDown() { File configFile = new File(storePathRootDir); UtilAll.deleteFile(configFile); UtilAll.deleteEmptyDirectory(configFile); UtilAll.deleteEmptyDirectory(configFile.getParentFile()); } } ================================================ FILE: container/src/test/java/org/apache/rocketmq/container/BrokerContainerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class BrokerContainerTest { private static final List TMP_FILE_LIST = new ArrayList<>(); private static final Random RANDOM = new Random(); private static final Set PORTS_IN_USE = new HashSet<>(); /** * Tests if the controller can be properly stopped and started. * * @throws Exception If fails. */ @Test public void testBrokerContainerRestart() throws Exception { BrokerContainer brokerController = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(brokerController.initialize()).isTrue(); brokerController.start(); brokerController.shutdown(); } @Test public void testRegisterIncrementBrokerData() throws Exception { BrokerController brokerController = new BrokerController( new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); brokerController.getBrokerConfig().setEnableSlaveActingMaster(true); BrokerOuterAPI brokerOuterAPI = mock(BrokerOuterAPI.class); Field field = BrokerController.class.getDeclaredField("brokerOuterAPI"); field.setAccessible(true); field.set(brokerController, brokerOuterAPI); List topicConfigList = new ArrayList<>(2); for (int i = 0; i < 2; i++) { topicConfigList.add(new TopicConfig("topic-" + i)); } DataVersion dataVersion = new DataVersion(); // Check normal condition. testRegisterIncrementBrokerDataWithPerm(brokerController, brokerOuterAPI, topicConfigList, dataVersion, PermName.PERM_READ | PermName.PERM_WRITE, 1); // Check unwritable broker. testRegisterIncrementBrokerDataWithPerm(brokerController, brokerOuterAPI, topicConfigList, dataVersion, PermName.PERM_READ, 2); // Check unreadable broker. testRegisterIncrementBrokerDataWithPerm(brokerController, brokerOuterAPI, topicConfigList, dataVersion, PermName.PERM_WRITE, 3); } @Test public void testRegisterIncrementBrokerDataPerm() throws Exception { BrokerController brokerController = new BrokerController( new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); brokerController.getBrokerConfig().setEnableSlaveActingMaster(true); BrokerOuterAPI brokerOuterAPI = mock(BrokerOuterAPI.class); Field field = BrokerController.class.getDeclaredField("brokerOuterAPI"); field.setAccessible(true); field.set(brokerController, brokerOuterAPI); List topicConfigList = new ArrayList<>(2); for (int i = 0; i < 2; i++) { topicConfigList.add(new TopicConfig("topic-" + i)); } DataVersion dataVersion = new DataVersion(); brokerController.getBrokerConfig().setBrokerPermission(4); brokerController.registerIncrementBrokerData(topicConfigList, dataVersion); // Get topicConfigSerializeWrapper created by registerIncrementBrokerData() from brokerOuterAPI.registerBrokerAll() ArgumentCaptor captor = ArgumentCaptor.forClass(TopicConfigSerializeWrapper.class); ArgumentCaptor brokerIdentityCaptor = ArgumentCaptor.forClass(BrokerIdentity.class); verify(brokerOuterAPI).registerBrokerAll(anyString(), anyString(), anyString(), anyLong(), anyString(), captor.capture(), ArgumentMatchers.anyList(), anyBoolean(), anyInt(), anyBoolean(), anyBoolean(), anyLong(), brokerIdentityCaptor.capture()); TopicConfigSerializeWrapper wrapper = captor.getValue(); for (Map.Entry entry : wrapper.getTopicConfigTable().entrySet()) { assertThat(entry.getValue().getPerm()).isEqualTo(brokerController.getBrokerConfig().getBrokerPermission()); } } @Test public void testMasterScaleOut() throws Exception { BrokerContainer brokerContainer = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(brokerContainer.initialize()).isTrue(); brokerContainer.getBrokerContainerConfig().setNamesrvAddr("127.0.0.1:9876"); brokerContainer.start(); BrokerConfig masterBrokerConfig = new BrokerConfig(); String baseDir = createBaseDir("unnittest-master").getAbsolutePath(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(masterBrokerConfig) .messageStoreConfig(messageStoreConfig) .build(); InnerBrokerController brokerController = brokerContainer.addBroker(configContext); assertThat(brokerController.isIsolated()).isFalse(); brokerContainer.shutdown(); brokerController.getMessageStore().destroy(); } @Test public void testAddMasterFailed() throws Exception { BrokerContainer brokerContainer = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(brokerContainer.initialize()).isTrue(); brokerContainer.start(); BrokerConfig masterBrokerConfig = new BrokerConfig(); masterBrokerConfig.setListenPort(brokerContainer.getNettyServerConfig().getListenPort()); boolean exceptionCaught = false; try { String baseDir = createBaseDir("unnittest-master").getAbsolutePath(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(masterBrokerConfig) .messageStoreConfig(messageStoreConfig) .build(); brokerContainer.addBroker(configContext); } catch (Exception e) { exceptionCaught = true; } finally { brokerContainer.shutdown(); } assertThat(exceptionCaught).isTrue(); } @Test public void testAddSlaveFailed() throws Exception { BrokerContainer sharedBrokerController = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(sharedBrokerController.initialize()).isTrue(); sharedBrokerController.start(); BrokerConfig slaveBrokerConfig = new BrokerConfig(); slaveBrokerConfig.setBrokerId(1); slaveBrokerConfig.setListenPort(sharedBrokerController.getNettyServerConfig().getListenPort()); MessageStoreConfig slaveMessageStoreConfig = new MessageStoreConfig(); slaveMessageStoreConfig.setBrokerRole(BrokerRole.SLAVE); String baseDir = createBaseDir("unnittest-slave").getAbsolutePath(); slaveMessageStoreConfig.setStorePathRootDir(baseDir); slaveMessageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); boolean exceptionCaught = false; ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(slaveBrokerConfig) .messageStoreConfig(slaveMessageStoreConfig) .build(); try { sharedBrokerController.addBroker(configContext); } catch (Exception e) { exceptionCaught = true; } finally { sharedBrokerController.shutdown(); } assertThat(exceptionCaught).isTrue(); } @Test public void testAddAndRemoveMaster() throws Exception { BrokerContainer brokerContainer = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(brokerContainer.initialize()).isTrue(); brokerContainer.start(); BrokerConfig masterBrokerConfig = new BrokerConfig(); String baseDir = createBaseDir("unnittest-master").getAbsolutePath(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(masterBrokerConfig) .messageStoreConfig(messageStoreConfig) .build(); InnerBrokerController master = brokerContainer.addBroker(configContext); assertThat(master).isNotNull(); master.start(); assertThat(master.isIsolated()).isFalse(); brokerContainer.removeBroker(new BrokerIdentity(masterBrokerConfig.getBrokerClusterName(), masterBrokerConfig.getBrokerName(), masterBrokerConfig.getBrokerId())); assertThat(brokerContainer.getMasterBrokers().size()).isEqualTo(0); brokerContainer.shutdown(); master.getMessageStore().destroy(); } @Test public void testAddAndRemoveDLedgerBroker() throws Exception { BrokerContainer brokerContainer = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(brokerContainer.initialize()).isTrue(); brokerContainer.start(); BrokerConfig dLedgerBrokerConfig = new BrokerConfig(); String baseDir = createBaseDir("unnittest-dLedger").getAbsolutePath(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); messageStoreConfig.setEnableDLegerCommitLog(true); messageStoreConfig.setdLegerSelfId("n0"); messageStoreConfig.setdLegerGroup("group"); messageStoreConfig.setdLegerPeers(String.format("n0-localhost:%d", generatePort(30900, 10000))); ConfigContext configContext = new ConfigContext.Builder() .brokerConfig(dLedgerBrokerConfig) .messageStoreConfig(messageStoreConfig) .build(); InnerBrokerController dLedger = brokerContainer.addBroker(configContext); assertThat(dLedger).isNotNull(); dLedger.start(); assertThat(dLedger.isIsolated()).isFalse(); brokerContainer.removeBroker(new BrokerIdentity(dLedgerBrokerConfig.getBrokerClusterName(), dLedgerBrokerConfig.getBrokerName(), Integer.parseInt(messageStoreConfig.getdLegerSelfId().substring(1)))); assertThat(brokerContainer.getMasterBrokers().size()).isEqualTo(0); brokerContainer.shutdown(); dLedger.getMessageStore().destroy(); } @Test public void testAddAndRemoveSlaveSuccess() throws Exception { BrokerContainer brokerContainer = new BrokerContainer( new BrokerContainerConfig(), new NettyServerConfig(), new NettyClientConfig()); assertThat(brokerContainer.initialize()).isTrue(); brokerContainer.start(); BrokerConfig masterBrokerConfig = new BrokerConfig(); String baseDir = createBaseDir("unnittest-master").getAbsolutePath(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); ConfigContext masterBrokerConfigContext = new ConfigContext.Builder() .brokerConfig(masterBrokerConfig) .messageStoreConfig(messageStoreConfig) .build(); InnerBrokerController master = brokerContainer.addBroker(masterBrokerConfigContext); assertThat(master).isNotNull(); master.start(); assertThat(master.isIsolated()).isFalse(); BrokerConfig slaveBrokerConfig = new BrokerConfig(); slaveBrokerConfig.setListenPort(generatePort(masterBrokerConfig.getListenPort(), 10000)); slaveBrokerConfig.setBrokerId(1); MessageStoreConfig slaveMessageStoreConfig = new MessageStoreConfig(); slaveMessageStoreConfig.setBrokerRole(BrokerRole.SLAVE); slaveMessageStoreConfig.setHaListenPort(generatePort(messageStoreConfig.getHaListenPort(), 10000)); baseDir = createBaseDir("unnittest-slave").getAbsolutePath(); slaveMessageStoreConfig.setStorePathRootDir(baseDir); slaveMessageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); ConfigContext slaveBrokerConfigContext = new ConfigContext.Builder() .brokerConfig(slaveBrokerConfig) .messageStoreConfig(slaveMessageStoreConfig) .build(); InnerBrokerController slave = brokerContainer.addBroker(slaveBrokerConfigContext); assertThat(slave).isNotNull(); slave.start(); assertThat(slave.isIsolated()).isFalse(); brokerContainer.removeBroker(new BrokerIdentity(slaveBrokerConfig.getBrokerClusterName(), slaveBrokerConfig.getBrokerName(), slaveBrokerConfig.getBrokerId())); assertThat(brokerContainer.getSlaveBrokers().size()).isEqualTo(0); brokerContainer.removeBroker(new BrokerIdentity(masterBrokerConfig.getBrokerClusterName(), masterBrokerConfig.getBrokerName(), masterBrokerConfig.getBrokerId())); assertThat(brokerContainer.getMasterBrokers().size()).isEqualTo(0); brokerContainer.shutdown(); slave.getMessageStore().destroy(); master.getMessageStore().destroy(); } private static File createBaseDir(String prefix) { final File file; try { file = Files.createTempDirectory(prefix).toFile(); TMP_FILE_LIST.add(file); return file; } catch (IOException e) { throw new RuntimeException("Couldn't create tmp folder", e); } } public static int generatePort(int base, int range) { int result = base + RANDOM.nextInt(range); while (PORTS_IN_USE.contains(result) || PORTS_IN_USE.contains(result - 2)) { result = base + RANDOM.nextInt(range); } PORTS_IN_USE.add(result); PORTS_IN_USE.add(result - 2); return result; } @After public void destroy() { for (File file : TMP_FILE_LIST) { UtilAll.deleteFile(file); } } private void testRegisterIncrementBrokerDataWithPerm(BrokerController brokerController, BrokerOuterAPI brokerOuterAPI, List topicConfigList, DataVersion dataVersion, int perm, int times) { brokerController.getBrokerConfig().setBrokerPermission(perm); brokerController.registerIncrementBrokerData(topicConfigList, dataVersion); // Get topicConfigSerializeWrapper created by registerIncrementBrokerData() from brokerOuterAPI.registerBrokerAll() ArgumentCaptor captor = ArgumentCaptor.forClass(TopicConfigSerializeWrapper.class); ArgumentCaptor brokerIdentityCaptor = ArgumentCaptor.forClass(BrokerIdentity.class); verify(brokerOuterAPI, times(times)).registerBrokerAll(anyString(), anyString(), anyString(), anyLong(), anyString(), captor.capture(), ArgumentMatchers.anyList(), anyBoolean(), anyInt(), anyBoolean(), anyBoolean(), anyLong(), brokerIdentityCaptor.capture()); TopicConfigSerializeWrapper wrapper = captor.getValue(); for (TopicConfig topicConfig : topicConfigList) { topicConfig.setPerm(perm); } assertThat(wrapper.getDataVersion()).isEqualTo(dataVersion); assertThat(wrapper.getTopicConfigTable()).containsExactly( entry("topic-0", topicConfigList.get(0)), entry("topic-1", topicConfigList.get(1))); for (TopicConfig topicConfig : topicConfigList) { topicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); } } } ================================================ FILE: container/src/test/java/org/apache/rocketmq/container/BrokerPreOnlineServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.container; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPreOnlineService; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.broker.transaction.TransactionalMessageCheckService; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.BrokerSyncInfo; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class BrokerPreOnlineServiceTest { @Mock private BrokerContainer brokerContainer; private InnerBrokerController innerBrokerController; @Mock private BrokerOuterAPI brokerOuterAPI; public void init(final long brokerId) throws Exception { when(brokerContainer.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); BrokerMemberGroup brokerMemberGroup1 = new BrokerMemberGroup(); Map brokerAddrMap = new HashMap<>(); brokerAddrMap.put(0L, "127.0.0.1:10911"); brokerMemberGroup1.setBrokerAddrs(brokerAddrMap); BrokerMemberGroup brokerMemberGroup2 = new BrokerMemberGroup(); brokerMemberGroup2.setBrokerAddrs(new HashMap<>()); when(brokerOuterAPI.syncBrokerMemberGroup(anyString(), anyString(), anyBoolean())) .thenReturn(brokerMemberGroup1) .thenReturn(brokerMemberGroup2); BrokerSyncInfo brokerSyncInfo = mock(BrokerSyncInfo.class); when(brokerOuterAPI.retrieveBrokerHaInfo(anyString())).thenReturn(brokerSyncInfo); DefaultMessageStore defaultMessageStore = mock(DefaultMessageStore.class); when(defaultMessageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerId(brokerId); when(defaultMessageStore.getBrokerConfig()).thenReturn(brokerConfig); // HAService haService = new DefaultHAService(); // haService.init(defaultMessageStore); // haService.start(); // // when(defaultMessageStore.getHaService()).thenReturn(haService); innerBrokerController = new InnerBrokerController(brokerContainer, defaultMessageStore.getBrokerConfig(), defaultMessageStore.getMessageStoreConfig(), null); innerBrokerController.setTransactionalMessageCheckService(new TransactionalMessageCheckService(innerBrokerController)); Field field = BrokerController.class.getDeclaredField("isIsolated"); field.setAccessible(true); field.set(innerBrokerController, true); field = BrokerController.class.getDeclaredField("messageStore"); field.setAccessible(true); field.set(innerBrokerController, defaultMessageStore); } @Test public void testMasterOnlineConnTimeout() throws Exception { init(0); BrokerPreOnlineService brokerPreOnlineService = new BrokerPreOnlineService(innerBrokerController); brokerPreOnlineService.start(); await().atMost(Duration.ofSeconds(30)).until(() -> !innerBrokerController.isIsolated()); } @Test public void testMasterOnlineNormal() throws Exception { init(0); BrokerPreOnlineService brokerPreOnlineService = new BrokerPreOnlineService(innerBrokerController); brokerPreOnlineService.start(); TimeUnit.SECONDS.sleep(1); brokerPreOnlineService.shutdown(); await().atMost(Duration.ofSeconds(30)).until(brokerPreOnlineService::isStopped); } @Test public void testSlaveOnlineNormal() throws Exception { init(1); BrokerPreOnlineService brokerPreOnlineService = new BrokerPreOnlineService(innerBrokerController); brokerPreOnlineService.start(); TimeUnit.SECONDS.sleep(1); brokerPreOnlineService.shutdown(); await().atMost(Duration.ofSeconds(30)).until(brokerPreOnlineService::isStopped); } @Test public void testGetServiceName() throws Exception { init(1); BrokerPreOnlineService brokerPreOnlineService = new BrokerPreOnlineService(innerBrokerController); assertEquals(BrokerPreOnlineService.class.getSimpleName(), brokerPreOnlineService.getServiceName()); } } ================================================ FILE: container/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: controller/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "controller", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "//remoting", "//srvutil", "@maven//:io_openmessaging_storage_dledger", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_validator_commons_validator", "@maven//:commons_collections_commons_collections", "@maven//:commons_codec_commons_codec", "@maven//:com_github_luben_zstd_jni", "@maven//:org_lz4_lz4_java", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", "@maven//:org_slf4j_slf4j_api", "@maven//:ch_qos_logback_logback_core", "@maven//:ch_qos_logback_logback_classic", "@maven//:commons_cli_commons_cli", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:io_opentelemetry_opentelemetry_exporter_logging", "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp", "@maven//:org_slf4j_jul_to_slf4j", "@maven//:com_alipay_sofa_jraft_core", "@maven//:com_alipay_sofa_hessian", "@maven//:commons_io_commons_io", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":controller", "//common", "//remoting", "//srvutil", "@maven//:io_openmessaging_storage_dledger", "//:test_deps", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", "@maven//:com_alibaba_fastjson2_fastjson2", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], exclude_tests = [ # This test is buggy, exclude it. "src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest", ], medium_tests = [ "src/test/java/org/apache/rocketmq/controller/impl/controller/impl/DLedgerControllerTest", ], ) ================================================ FILE: controller/pom.xml ================================================ rocketmq-all org.apache.rocketmq ${revision} 4.0.0 jar rocketmq-controller rocketmq-controller ${project.version} ${basedir}/.. io.openmessaging.storage dledger org.apache.rocketmq rocketmq-remoting org.slf4j slf4j-api io.github.aliyunmq rocketmq-shaded-slf4j-api-bridge ${project.groupId} rocketmq-client test ${project.groupId} rocketmq-srvutil org.slf4j jul-to-slf4j com.alipay.sofa jraft-core com.google.protobuf protobuf-java-util ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/BrokerHeartbeatManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import io.netty.channel.Channel; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; import org.apache.rocketmq.controller.impl.heartbeat.DefaultBrokerHeartbeatManager; import org.apache.rocketmq.controller.impl.heartbeat.RaftBrokerHeartBeatManager; import java.util.Map; public interface BrokerHeartbeatManager { long DEFAULT_BROKER_CHANNEL_EXPIRED_TIME = 1000 * 10; static BrokerHeartbeatManager newBrokerHeartbeatManager(ControllerConfig controllerConfig) { if (controllerConfig.getControllerType().equals(ControllerConfig.JRAFT_CONTROLLER)) { return new RaftBrokerHeartBeatManager(controllerConfig); } else { return new DefaultBrokerHeartbeatManager(controllerConfig); } } /** * Initialize the resources */ void initialize(); /** * Broker new heartbeat. */ void onBrokerHeartbeat(final String clusterName, final String brokerName, final String brokerAddr, final Long brokerId, final Long timeoutMillis, final Channel channel, final Integer epoch, final Long maxOffset, final Long confirmOffset, final Integer electionPriority); /** * Start heartbeat manager. */ void start(); /** * Shutdown heartbeat manager. */ void shutdown(); /** * Add BrokerLifecycleListener. */ void registerBrokerLifecycleListener(final BrokerLifecycleListener listener); /** * Broker channel close */ void onBrokerChannelClose(final Channel channel); /** * Get broker live information by clusterName and brokerAddr * * @return broker live information or null if not found */ BrokerLiveInfo getBrokerLiveInfo(String clusterName, String brokerName, Long brokerId); /** * Check whether broker active */ boolean isBrokerActive(final String clusterName, final String brokerName, final Long brokerId); /** * Count the number of active brokers in each broker-set of each cluster * * @return active brokers count */ Map> getActiveBrokersNum(); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/BrokerHousekeepingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import io.netty.channel.Channel; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; public class BrokerHousekeepingService implements ChannelEventListener { private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private final ControllerManager controllerManager; public BrokerHousekeepingService(ControllerManager controllerManager) { this.controllerManager = controllerManager; } @Override public void onChannelConnect(String remoteAddr, Channel channel) { } @Override public void onChannelClose(String remoteAddr, Channel channel) { this.controllerManager.getHeartbeatManager().onBrokerChannelClose(channel); } @Override public void onChannelException(String remoteAddr, Channel channel) { this.controllerManager.getHeartbeatManager().onBrokerChannelClose(channel); } @Override public void onChannelIdle(String remoteAddr, Channel channel) { this.controllerManager.getHeartbeatManager().onBrokerChannelClose(channel); } @Override public void onChannelActive(String remoteAddr, Channel channel) { } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/Controller.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; /** * The api for controller */ public interface Controller { /** * Startup controller */ void startup(); /** * Shutdown controller */ void shutdown(); /** * Start scheduling controller events, this function only will be triggered when the controller becomes leader. */ void startScheduling(); /** * Stop scheduling controller events, this function only will be triggered when the controller lose leadership. */ void stopScheduling(); /** * Whether this controller is in leader state. */ boolean isLeaderState(); /** * Alter SyncStateSet of broker replicas. * * @param request AlterSyncStateSetRequestHeader * @return RemotingCommand(AlterSyncStateSetResponseHeader) */ CompletableFuture alterSyncStateSet( final AlterSyncStateSetRequestHeader request, final SyncStateSet syncStateSet); /** * Elect new master for a broker. * * @param request ElectMasterRequest * @return RemotingCommand(ElectMasterResponseHeader) */ CompletableFuture electMaster(final ElectMasterRequestHeader request); CompletableFuture getNextBrokerId(final GetNextBrokerIdRequestHeader request); CompletableFuture applyBrokerId(final ApplyBrokerIdRequestHeader request); /** * Register broker with unique brokerId and now broker address * * @param request RegisterBrokerToControllerRequest * @return RemotingCommand(RegisterBrokerToControllerResponseHeader) */ CompletableFuture registerBroker(final RegisterBrokerToControllerRequestHeader request); /** * Get the Replica Info for a target broker. * * @param request GetRouteInfoRequest * @return RemotingCommand(GetReplicaInfoResponseHeader) */ CompletableFuture getReplicaInfo(final GetReplicaInfoRequestHeader request); /** * Get Metadata of controller * * @return RemotingCommand(GetControllerMetadataResponseHeader) */ RemotingCommand getControllerMetadata(); /** * Get SyncStateData for target brokers, this api is used for admin tools. */ CompletableFuture getSyncStateData(final List brokerNames); /** * Add broker's lifecycle listener * @param listener listener */ void registerBrokerLifecycleListener(final BrokerLifecycleListener listener); /** * Get the remotingServer used by the controller, the upper layer will reuse this remotingServer. */ RemotingServer getRemotingServer(); /** * Clean controller broker data * */ CompletableFuture cleanBrokerData(final CleanControllerBrokerDataRequestHeader requestHeader); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import java.io.IOException; import java.util.Map; import java.util.Objects; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy; import org.apache.rocketmq.controller.impl.DLedgerController; import org.apache.rocketmq.controller.impl.JRaftController; import org.apache.rocketmq.controller.impl.heartbeat.RaftBrokerHeartBeatManager; import org.apache.rocketmq.controller.metrics.ControllerMetricsManager; import org.apache.rocketmq.controller.processor.ControllerRequestProcessor; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.Configuration; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.RoleChangeNotifyEntry; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.NotifyBrokerRoleChangedRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; public class ControllerManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final ControllerConfig controllerConfig; private final NettyServerConfig nettyServerConfig; private final NettyClientConfig nettyClientConfig; private final BrokerHousekeepingService brokerHousekeepingService; private final Configuration configuration; private final RemotingClient remotingClient; private Controller controller; private final BrokerHeartbeatManager heartbeatManager; private ExecutorService controllerRequestExecutor; private BlockingQueue controllerRequestThreadPoolQueue; private final NotifyService notifyService; private ControllerMetricsManager controllerMetricsManager; public ControllerManager(ControllerConfig controllerConfig, NettyServerConfig nettyServerConfig, NettyClientConfig nettyClientConfig) { this.controllerConfig = controllerConfig; this.nettyServerConfig = nettyServerConfig; this.nettyClientConfig = nettyClientConfig; this.brokerHousekeepingService = new BrokerHousekeepingService(this); this.configuration = new Configuration(log, this.controllerConfig, this.nettyServerConfig); this.configuration.setStorePathFromConfig(this.controllerConfig, "configStorePath"); this.remotingClient = new NettyRemotingClient(nettyClientConfig); this.heartbeatManager = BrokerHeartbeatManager.newBrokerHeartbeatManager(controllerConfig); this.notifyService = new NotifyService(); } public boolean initialize() { this.controllerRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.controllerConfig.getControllerRequestThreadPoolQueueCapacity()); this.controllerRequestExecutor = ThreadUtils.newThreadPoolExecutor( this.controllerConfig.getControllerThreadPoolNums(), this.controllerConfig.getControllerThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.controllerRequestThreadPoolQueue, new ThreadFactoryImpl("ControllerRequestExecutorThread_")); this.notifyService.initialize(); if (controllerConfig.getControllerType().equals(ControllerConfig.JRAFT_CONTROLLER)) { if (StringUtils.isEmpty(this.controllerConfig.getJraftConfig().getjRaftInitConf())) { throw new IllegalArgumentException("Attribute value jRaftInitConf of ControllerConfig is null or empty"); } if (StringUtils.isEmpty(this.controllerConfig.getJraftConfig().getjRaftServerId())) { throw new IllegalArgumentException("Attribute value jRaftServerId of ControllerConfig is null or empty"); } try { this.controller = new JRaftController(controllerConfig, this.brokerHousekeepingService); ((RaftBrokerHeartBeatManager) this.heartbeatManager).setController((JRaftController) this.controller); } catch (IOException e) { throw new RuntimeException(e); } } else { if (StringUtils.isEmpty(this.controllerConfig.getControllerDLegerPeers())) { throw new IllegalArgumentException("Attribute value controllerDLegerPeers of ControllerConfig is null or empty"); } if (StringUtils.isEmpty(this.controllerConfig.getControllerDLegerSelfId())) { throw new IllegalArgumentException("Attribute value controllerDLegerSelfId of ControllerConfig is null or empty"); } this.controller = new DLedgerController(this.controllerConfig, this.heartbeatManager::isBrokerActive, this.nettyServerConfig, this.nettyClientConfig, this.brokerHousekeepingService, new DefaultElectPolicy(this.heartbeatManager::isBrokerActive, this.heartbeatManager::getBrokerLiveInfo)); } // Initialize the basic resources this.heartbeatManager.initialize(); // Register broker inactive listener this.heartbeatManager.registerBrokerLifecycleListener(this::onBrokerInactive); this.controller.registerBrokerLifecycleListener(this::onBrokerInactive); registerProcessor(); this.controllerMetricsManager = ControllerMetricsManager.getInstance(this); return true; } /** * When the heartbeatManager detects the "Broker is not active", we call this method to elect a master and do * something else. * * @param clusterName The cluster name of this inactive broker * @param brokerName The inactive broker name * @param brokerId The inactive broker id, null means that the election forced to be triggered */ private void onBrokerInactive(String clusterName, String brokerName, Long brokerId) { log.info("Controller Manager received broker inactive event, clusterName: {}, brokerName: {}, brokerId: {}", clusterName, brokerName, brokerId); if (controller.isLeaderState()) { if (brokerId == null) { // Means that force triggering election for this broker-set triggerElectMaster(brokerName); return; } final CompletableFuture replicaInfoFuture = controller.getReplicaInfo(new GetReplicaInfoRequestHeader(brokerName)); replicaInfoFuture.whenCompleteAsync((replicaInfoResponse, err) -> { if (err != null || replicaInfoResponse == null) { log.error("Failed to get replica-info for broker-set: {} when OnBrokerInactive", brokerName, err); return; } final GetReplicaInfoResponseHeader replicaInfoResponseHeader = (GetReplicaInfoResponseHeader) replicaInfoResponse.readCustomHeader(); // Not master broker offline if (!brokerId.equals(replicaInfoResponseHeader.getMasterBrokerId())) { log.warn("The broker with brokerId: {} in broker-set: {} has been inactive", brokerId, brokerName); return; } // Trigger election triggerElectMaster(brokerName); }); } else { log.warn("The broker with brokerId: {} in broker-set: {} has been inactive", brokerId, brokerName); } } private CompletableFuture triggerElectMaster0(String brokerName) { final CompletableFuture electMasterFuture = controller.electMaster(ElectMasterRequestHeader.ofControllerTrigger(brokerName)); return electMasterFuture.handleAsync((electMasterResponse, err) -> { if (err != null || electMasterResponse == null || electMasterResponse.getCode() != ResponseCode.SUCCESS) { log.error("Failed to trigger elect-master in broker-set: {}", brokerName, err); return false; } if (electMasterResponse.getCode() == ResponseCode.SUCCESS) { log.info("Elect a new master in broker-set: {} done, result: {}", brokerName, electMasterResponse); if (controllerConfig.isNotifyBrokerRoleChanged()) { notifyBrokerRoleChanged(RoleChangeNotifyEntry.convert(electMasterResponse)); } return true; } //default is false return false; }); } private void triggerElectMaster(String brokerName) { int maxRetryCount = controllerConfig.getElectMasterMaxRetryCount(); for (int i = 0; i < maxRetryCount; i++) { try { Boolean electResult = triggerElectMaster0(brokerName).get(3, TimeUnit.SECONDS); if (electResult) { return; } } catch (Exception e) { log.warn("Failed to trigger elect-master in broker-set: {}, retryCount: {}", brokerName, i, e); } } } /** * Notify master and all slaves for a broker that the master role changed. */ public void notifyBrokerRoleChanged(final RoleChangeNotifyEntry entry) { final BrokerMemberGroup memberGroup = entry.getBrokerMemberGroup(); if (memberGroup != null) { final Long masterBrokerId = entry.getMasterBrokerId(); String clusterName = memberGroup.getCluster(); String brokerName = memberGroup.getBrokerName(); if (masterBrokerId == null) { log.warn("Notify broker role change failed, because member group is not null but the new master brokerId is empty, entry:{}", entry); return; } // Inform all active brokers final Map brokerAddrs = memberGroup.getBrokerAddrs(); brokerAddrs.entrySet().stream().filter(x -> this.heartbeatManager.isBrokerActive(clusterName, brokerName, x.getKey())) .forEach(x -> this.notifyService.notifyBroker(x.getValue(), entry)); } } /** * Notify broker that there are roles-changing in controller * * @param brokerAddr target broker's address to notify * @param entry role change entry */ public void doNotifyBrokerRoleChanged(final String brokerAddr, final RoleChangeNotifyEntry entry) { if (StringUtils.isNoneEmpty(brokerAddr)) { log.info("Try notify broker {} that role changed, RoleChangeNotifyEntry:{}", brokerAddr, entry); final NotifyBrokerRoleChangedRequestHeader requestHeader = new NotifyBrokerRoleChangedRequestHeader(entry.getMasterAddress(), entry.getMasterBrokerId(), entry.getMasterEpoch(), entry.getSyncStateSetEpoch()); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFY_BROKER_ROLE_CHANGED, requestHeader); request.setBody(new SyncStateSet(entry.getSyncStateSet(), entry.getSyncStateSetEpoch()).encode()); try { this.remotingClient.invokeOneway(brokerAddr, request, 3000); } catch (final Exception e) { log.error("Failed to notify broker {} that role changed", brokerAddr, e); } } } public void registerProcessor() { final ControllerRequestProcessor controllerRequestProcessor = new ControllerRequestProcessor(this); RemotingServer controllerRemotingServer = this.controller.getRemotingServer(); assert controllerRemotingServer != null; controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_ELECT_MASTER, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_REGISTER_BROKER, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_GET_REPLICA_INFO, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_GET_METADATA_INFO, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_GET_SYNC_STATE_DATA, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.BROKER_HEARTBEAT, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.UPDATE_CONTROLLER_CONFIG, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.GET_CONTROLLER_CONFIG, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CLEAN_BROKER_DATA, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_GET_NEXT_BROKER_ID, controllerRequestProcessor, this.controllerRequestExecutor); controllerRemotingServer.registerProcessor(RequestCode.CONTROLLER_APPLY_BROKER_ID, controllerRequestProcessor, this.controllerRequestExecutor); } public void start() { this.controller.startup(); this.heartbeatManager.start(); this.remotingClient.start(); } public void shutdown() { this.heartbeatManager.shutdown(); this.controllerRequestExecutor.shutdown(); this.notifyService.shutdown(); this.controller.shutdown(); this.remotingClient.shutdown(); } public BrokerHeartbeatManager getHeartbeatManager() { return heartbeatManager; } public ControllerConfig getControllerConfig() { return controllerConfig; } public Controller getController() { return controller; } public NettyServerConfig getNettyServerConfig() { return nettyServerConfig; } public NettyClientConfig getNettyClientConfig() { return nettyClientConfig; } public BrokerHousekeepingService getBrokerHousekeepingService() { return brokerHousekeepingService; } public Configuration getConfiguration() { return configuration; } class NotifyService { private ExecutorService executorService; private Map currentNotifyFutures; public NotifyService() { } public void initialize() { this.executorService = Executors.newFixedThreadPool(3, new ThreadFactoryImpl("ControllerManager_NotifyService_")); this.currentNotifyFutures = new ConcurrentHashMap<>(); } public void notifyBroker(String brokerAddress, RoleChangeNotifyEntry entry) { int masterEpoch = entry.getMasterEpoch(); NotifyTask oldTask = this.currentNotifyFutures.get(brokerAddress); if (oldTask != null && masterEpoch > oldTask.getMasterEpoch()) { // cancel current future Future oldFuture = oldTask.getFuture(); if (oldFuture != null && !oldFuture.isDone()) { oldFuture.cancel(true); } } final NotifyTask task = new NotifyTask(masterEpoch, null); Runnable runnable = () -> { doNotifyBrokerRoleChanged(brokerAddress, entry); this.currentNotifyFutures.remove(brokerAddress, task); }; this.currentNotifyFutures.put(brokerAddress, task); Future future = this.executorService.submit(runnable); task.setFuture(future); } public void shutdown() { if (!this.executorService.isShutdown()) { this.executorService.shutdownNow(); } } class NotifyTask extends Pair { public NotifyTask(Integer masterEpoch, Future future) { super(masterEpoch, future); } public Integer getMasterEpoch() { return super.getObject1(); } public Future getFuture() { return super.getObject2(); } public void setFuture(Future future) { super.setObject2(future); } @Override public int hashCode() { return Objects.hashCode(super.getObject1()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof NotifyTask)) { return false; } NotifyTask task = (NotifyTask) obj; return super.getObject1().equals(task.getObject1()); } } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.concurrent.Callable; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.JraftConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.srvutil.ShutdownHookThread; public class ControllerStartup { private static Logger log; private static Properties properties = null; private static CommandLine commandLine = null; public static void main(String[] args) { main0(args); } public static ControllerManager main0(String[] args) { try { ControllerManager controller = createControllerManager(args); start(controller); String tip = "The Controller 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; } public static ControllerManager createControllerManager(String[] args) throws IOException { Options options = ServerUtil.buildCommandlineOptions(new Options()); commandLine = ServerUtil.parseCmdLine("mqcontroller", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); return null; } final ControllerConfig controllerConfig = new ControllerConfig(); final JraftConfig jraftConfig = new JraftConfig(); controllerConfig.setJraftConfig(jraftConfig); final NettyServerConfig nettyServerConfig = new NettyServerConfig(); final NettyClientConfig nettyClientConfig = new NettyClientConfig(); nettyServerConfig.setListenPort(19876); 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, controllerConfig); MixAll.properties2Object(properties, jraftConfig); MixAll.properties2Object(properties, nettyServerConfig); MixAll.properties2Object(properties, nettyClientConfig); System.out.printf("load config properties file OK, %s%n", file); in.close(); } } if (commandLine.hasOption('p')) { Logger console = LoggerFactory.getLogger(LoggerName.CONTROLLER_CONSOLE_NAME); MixAll.printObjectProperties(console, controllerConfig); MixAll.printObjectProperties(console, jraftConfig); MixAll.printObjectProperties(console, nettyServerConfig); MixAll.printObjectProperties(console, nettyClientConfig); System.exit(0); } MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), controllerConfig); if (StringUtils.isEmpty(controllerConfig.getRocketmqHome())) { System.out.printf("Please set the %s or %s variable in your environment!%n", MixAll.ROCKETMQ_HOME_ENV, MixAll.ROCKETMQ_HOME_PROPERTY); System.exit(-1); } log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); MixAll.printObjectProperties(log, controllerConfig); MixAll.printObjectProperties(log, nettyServerConfig); final ControllerManager controllerManager = new ControllerManager(controllerConfig, nettyServerConfig, nettyClientConfig); // remember all configs to prevent discard controllerManager.getConfiguration().registerConfig(properties); return controllerManager; } public static ControllerManager start(final ControllerManager controller) throws Exception { if (null == controller) { throw new IllegalArgumentException("ControllerManager is null"); } boolean initResult = controller.initialize(); if (!initResult) { controller.shutdown(); System.exit(-3); } Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable) () -> { controller.shutdown(); return null; })); controller.start(); return controller; } public static void shutdown(final ControllerManager controller) { controller.shutdown(); } public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("c", "configFile", true, "Controller config properties file"); opt.setRequired(false); options.addOption(opt); opt = new Option("p", "printConfigItem", false, "Print all config items"); opt.setRequired(false); options.addOption(opt); return options; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/elect/ElectPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.elect; import java.util.Set; public interface ElectPolicy { /** * elect a master * * @param clusterName the broker group belongs to * @param brokerName the broker group name * @param syncStateBrokers all broker replicas in syncStateSet * @param allReplicaBrokers all broker replicas * @param oldMaster old master * @param brokerId broker id(can be used as prefer or assigned in some elect policy) * @return new master's broker id */ Long elect(String clusterName, String brokerName, Set syncStateBrokers, Set allReplicaBrokers, Long oldMaster, Long brokerId); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/elect/impl/DefaultElectPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.elect.impl; import org.apache.rocketmq.controller.elect.ElectPolicy; import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; import org.apache.rocketmq.controller.helper.BrokerLiveInfoGetter; import org.apache.rocketmq.controller.helper.BrokerValidPredicate; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; public class DefaultElectPolicy implements ElectPolicy { // , Used to judge whether a broker // has preliminary qualification to be selected as master private BrokerValidPredicate validPredicate; // , Used to obtain the BrokerLiveInfo information of a broker private BrokerLiveInfoGetter brokerLiveInfoGetter; // Sort in descending order according to, and sort in ascending order according to priority private final Comparator comparator = (o1, o2) -> { if (o1.getEpoch() == o2.getEpoch()) { return o1.getMaxOffset() == o2.getMaxOffset() ? o1.getElectionPriority() - o2.getElectionPriority() : (int) (o2.getMaxOffset() - o1.getMaxOffset()); } else { return o2.getEpoch() - o1.getEpoch(); } }; public DefaultElectPolicy(BrokerValidPredicate validPredicate, BrokerLiveInfoGetter brokerLiveInfoGetter) { this.validPredicate = validPredicate; this.brokerLiveInfoGetter = brokerLiveInfoGetter; } public DefaultElectPolicy() { } /** * We will try to select a new master from syncStateBrokers and allReplicaBrokers in turn. * The strategies are as follows: * - Filter alive brokers by 'validPredicate'. * - Check whether the old master is still valid. * - If preferBrokerAddr is not empty and valid, select it as master. * - Otherwise, we will sort the array of 'brokerLiveInfo' according to (epoch, offset, electionPriority), and select the best candidate as the new master. * * @param clusterName the brokerGroup belongs * @param syncStateBrokers all broker replicas in syncStateSet * @param allReplicaBrokers all broker replicas * @param oldMaster old master's broker id * @param preferBrokerId the broker id prefer to be elected * @return master elected by our own policy */ @Override public Long elect(String clusterName, String brokerName, Set syncStateBrokers, Set allReplicaBrokers, Long oldMaster, Long preferBrokerId) { Long newMaster = null; // try to elect in syncStateBrokers if (syncStateBrokers != null) { newMaster = tryElect(clusterName, brokerName, syncStateBrokers, oldMaster, preferBrokerId); } if (newMaster != null) { return newMaster; } // try to elect in all allReplicaBrokers if (allReplicaBrokers != null) { newMaster = tryElect(clusterName, brokerName, allReplicaBrokers, oldMaster, preferBrokerId); } return newMaster; } private Long tryElect(String clusterName, String brokerName, Set brokers, Long oldMaster, Long preferBrokerId) { if (this.validPredicate != null) { brokers = brokers.stream().filter(brokerAddr -> this.validPredicate.check(clusterName, brokerName, brokerAddr)).collect(Collectors.toSet()); } if (!brokers.isEmpty()) { // if old master is still valid, and preferBrokerAddr is blank or is equals to oldMaster if (brokers.contains(oldMaster) && (preferBrokerId == null || preferBrokerId.equals(oldMaster))) { return oldMaster; } // if preferBrokerAddr is valid, we choose it, otherwise we choose nothing if (preferBrokerId != null) { return brokers.contains(preferBrokerId) ? preferBrokerId : null; } if (this.brokerLiveInfoGetter != null) { // sort brokerLiveInfos by (epoch,maxOffset) TreeSet brokerLiveInfos = new TreeSet<>(this.comparator); brokers.forEach(brokerAddr -> brokerLiveInfos.add(this.brokerLiveInfoGetter.get(clusterName, brokerName, brokerAddr))); if (brokerLiveInfos.size() >= 1) { return brokerLiveInfos.first().getBrokerId(); } } // elect random return brokers.iterator().next(); } return null; } public void setBrokerLiveInfoGetter(BrokerLiveInfoGetter brokerLiveInfoGetter) { this.brokerLiveInfoGetter = brokerLiveInfoGetter; } public void setValidPredicate(BrokerValidPredicate validPredicate) { this.validPredicate = validPredicate; } public BrokerLiveInfoGetter getBrokerLiveInfoGetter() { return brokerLiveInfoGetter; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLifecycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.helper; public interface BrokerLifecycleListener { /** * Trigger when broker inactive. */ void onBrokerInactive(final String clusterName, final String brokerName, final Long brokerId); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLiveInfoGetter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.helper; import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; public interface BrokerLiveInfoGetter { BrokerLiveInfo get(String clusterName, String brokerName, Long brokerId); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerValidPredicate.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.helper; public interface BrokerValidPredicate { boolean check(String clusterName, String brokerName, Long brokerId); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import com.google.common.base.Stopwatch; import io.openmessaging.storage.dledger.AppendFuture; import io.openmessaging.storage.dledger.DLedgerConfig; import io.openmessaging.storage.dledger.DLedgerLeaderElector; import io.openmessaging.storage.dledger.DLedgerServer; import io.openmessaging.storage.dledger.MemberState; import io.openmessaging.storage.dledger.protocol.AppendEntryRequest; import io.openmessaging.storage.dledger.protocol.AppendEntryResponse; import io.openmessaging.storage.dledger.protocol.BatchAppendEntryRequest; import io.opentelemetry.api.common.AttributesBuilder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.controller.Controller; import org.apache.rocketmq.controller.elect.ElectPolicy; import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy; import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; import org.apache.rocketmq.controller.helper.BrokerValidPredicate; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.controller.impl.event.EventMessage; import org.apache.rocketmq.controller.impl.event.EventSerializer; import org.apache.rocketmq.controller.impl.manager.ReplicasInfoManager; import org.apache.rocketmq.controller.metrics.ControllerMetricsConstant; import org.apache.rocketmq.controller.metrics.ControllerMetricsManager; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_BROKER_SET; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_CLUSTER_NAME; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_DLEDGER_OPERATION; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_DLEDGER_OPERATION_STATUS; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_ELECTION_RESULT; /** * The implementation of controller, based on DLedger (raft). */ public class DLedgerController implements Controller { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final DLedgerServer dLedgerServer; private final ControllerConfig controllerConfig; private final DLedgerConfig dLedgerConfig; private final ReplicasInfoManager replicasInfoManager; private final EventScheduler scheduler; private final EventSerializer eventSerializer; private final RoleChangeHandler roleHandler; private final DLedgerControllerStateMachine statemachine; private final ScheduledExecutorService scanInactiveMasterService; private ScheduledFuture scanInactiveMasterFuture; private final List brokerLifecycleListeners; // use for checking whether the broker is alive private BrokerValidPredicate brokerAlivePredicate; // use for elect a master private ElectPolicy electPolicy; private final AtomicBoolean isScheduling = new AtomicBoolean(false); public DLedgerController(final ControllerConfig config, final BrokerValidPredicate brokerAlivePredicate) { this(config, brokerAlivePredicate, null, null, null, null); } public DLedgerController(final ControllerConfig controllerConfig, final BrokerValidPredicate brokerAlivePredicate, final NettyServerConfig nettyServerConfig, final NettyClientConfig nettyClientConfig, final ChannelEventListener channelEventListener, final ElectPolicy electPolicy) { this.controllerConfig = controllerConfig; this.eventSerializer = new EventSerializer(); this.scheduler = new EventScheduler(); this.brokerAlivePredicate = brokerAlivePredicate; this.electPolicy = electPolicy == null ? new DefaultElectPolicy() : electPolicy; this.dLedgerConfig = new DLedgerConfig(); this.dLedgerConfig.setGroup(controllerConfig.getControllerDLegerGroup()); this.dLedgerConfig.setPeers(controllerConfig.getControllerDLegerPeers()); this.dLedgerConfig.setSelfId(controllerConfig.getControllerDLegerSelfId()); this.dLedgerConfig.setStoreBaseDir(controllerConfig.getControllerStorePath()); this.dLedgerConfig.setMappedFileSizeForEntryData(controllerConfig.getMappedFileSize()); this.roleHandler = new RoleChangeHandler(dLedgerConfig.getSelfId()); this.replicasInfoManager = new ReplicasInfoManager(controllerConfig); this.statemachine = new DLedgerControllerStateMachine(replicasInfoManager, this.eventSerializer, dLedgerConfig.getGroup(), dLedgerConfig.getSelfId()); // Register statemachine and role handler. this.dLedgerServer = new DLedgerServer(dLedgerConfig, nettyServerConfig, nettyClientConfig, channelEventListener); this.dLedgerServer.registerStateMachine(this.statemachine); this.dLedgerServer.getDLedgerLeaderElector().addRoleChangeHandler(this.roleHandler); this.scanInactiveMasterService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DLedgerController_scanInactiveService_")); this.brokerLifecycleListeners = new ArrayList<>(); } @Override public void startup() { this.dLedgerServer.startup(); } @Override public void shutdown() { this.cancelScanInactiveFuture(); this.dLedgerServer.shutdown(); } @Override public void startScheduling() { if (this.isScheduling.compareAndSet(false, true)) { log.info("Start scheduling controller events"); this.scheduler.start(); } } @Override public void stopScheduling() { if (this.isScheduling.compareAndSet(true, false)) { log.info("Stop scheduling controller events"); this.scheduler.shutdown(true); } } @Override public boolean isLeaderState() { return this.roleHandler.isLeaderState(); } public ControllerConfig getControllerConfig() { return controllerConfig; } @Override public CompletableFuture alterSyncStateSet(AlterSyncStateSetRequestHeader request, final SyncStateSet syncStateSet) { return this.scheduler.appendEvent("alterSyncStateSet", () -> this.replicasInfoManager.alterSyncStateSet(request, syncStateSet, this.brokerAlivePredicate), true); } @Override public CompletableFuture electMaster(final ElectMasterRequestHeader request) { return this.scheduler.appendEvent("electMaster", () -> { ControllerResult electResult = this.replicasInfoManager.electMaster(request, this.electPolicy); AttributesBuilder attributesBuilder = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_CLUSTER_NAME, request.getClusterName()) .put(LABEL_BROKER_SET, request.getBrokerName()); switch (electResult.getResponseCode()) { case ResponseCode.SUCCESS: ControllerMetricsManager.electionTotal.add(1, attributesBuilder.put(LABEL_ELECTION_RESULT, ControllerMetricsConstant.ElectionResult.NEW_MASTER_ELECTED.getLowerCaseName()).build()); break; case ResponseCode.CONTROLLER_MASTER_STILL_EXIST: ControllerMetricsManager.electionTotal.add(1, attributesBuilder.put(LABEL_ELECTION_RESULT, ControllerMetricsConstant.ElectionResult.KEEP_CURRENT_MASTER.getLowerCaseName()).build()); break; case ResponseCode.CONTROLLER_MASTER_NOT_AVAILABLE: case ResponseCode.CONTROLLER_ELECT_MASTER_FAILED: ControllerMetricsManager.electionTotal.add(1, attributesBuilder.put(LABEL_ELECTION_RESULT, ControllerMetricsConstant.ElectionResult.NO_MASTER_ELECTED.getLowerCaseName()).build()); break; default: break; } return electResult; }, true); } @Override public CompletableFuture getNextBrokerId(GetNextBrokerIdRequestHeader request) { return this.scheduler.appendEvent("getNextBrokerId", () -> this.replicasInfoManager.getNextBrokerId(request), false); } @Override public CompletableFuture applyBrokerId(ApplyBrokerIdRequestHeader request) { return this.scheduler.appendEvent("applyBrokerId", () -> this.replicasInfoManager.applyBrokerId(request), true); } @Override public CompletableFuture registerBroker(RegisterBrokerToControllerRequestHeader request) { return this.scheduler.appendEvent("registerSuccess", () -> this.replicasInfoManager.registerBroker(request, brokerAlivePredicate), true); } @Override public CompletableFuture getReplicaInfo(final GetReplicaInfoRequestHeader request) { return this.scheduler.appendEvent("getReplicaInfo", () -> this.replicasInfoManager.getReplicaInfo(request), false); } @Override public CompletableFuture getSyncStateData(List brokerNames) { return this.scheduler.appendEvent("getSyncStateData", () -> this.replicasInfoManager.getSyncStateData(brokerNames, brokerAlivePredicate), false); } @Override public void registerBrokerLifecycleListener(BrokerLifecycleListener listener) { this.brokerLifecycleListeners.add(listener); } @Override public RemotingCommand getControllerMetadata() { final MemberState state = getMemberState(); final Map peers = state.getPeerMap(); final StringBuilder sb = new StringBuilder(); for (Map.Entry entry : peers.entrySet()) { final String peer = entry.getKey() + ":" + entry.getValue(); sb.append(peer).append(";"); } return RemotingCommand.createResponseCommandWithHeader(ResponseCode.SUCCESS, new GetMetaDataResponseHeader( state.getGroup(), state.getLeaderId(), state.getLeaderAddr(), state.isLeader(), sb.toString())); } @Override public RemotingServer getRemotingServer() { return this.dLedgerServer.getRemotingServer(); } @Override public CompletableFuture cleanBrokerData( final CleanControllerBrokerDataRequestHeader requestHeader) { return this.scheduler.appendEvent("cleanBrokerData", () -> this.replicasInfoManager.cleanBrokerData(requestHeader, this.brokerAlivePredicate), true); } /** * Scan all broker-set in statemachine, find that the broker-set which * its master has been timeout but still has at least one broker keep alive with controller, * and we trigger an election to update its state. */ private void scanInactiveMasterAndTriggerReelect() { if (!this.roleHandler.isLeaderState()) { cancelScanInactiveFuture(); return; } List brokerSets = this.replicasInfoManager.scanNeedReelectBrokerSets(this.brokerAlivePredicate); for (String brokerName : brokerSets) { // Notify ControllerManager this.brokerLifecycleListeners.forEach(listener -> listener.onBrokerInactive(null, brokerName, null)); } } /** * Append the request to DLedger, and wait for DLedger to commit the request. */ private boolean appendToDLedgerAndWait(final AppendEntryRequest request) { if (request != null) { request.setGroup(this.dLedgerConfig.getGroup()); request.setRemoteId(this.dLedgerConfig.getSelfId()); Stopwatch stopwatch = Stopwatch.createStarted(); AttributesBuilder attributesBuilder = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_DLEDGER_OPERATION, ControllerMetricsConstant.DLedgerOperation.APPEND.getLowerCaseName()); try { final AppendFuture dLedgerFuture = (AppendFuture) dLedgerServer.handleAppend(request); if (dLedgerFuture.getPos() == -1) { ControllerMetricsManager.dLedgerOpTotal.add(1, attributesBuilder.put(LABEL_DLEDGER_OPERATION_STATUS, ControllerMetricsConstant.DLedgerOperationStatus.FAILED.getLowerCaseName()).build()); return false; } dLedgerFuture.get(5, TimeUnit.SECONDS); ControllerMetricsManager.dLedgerOpTotal.add(1, attributesBuilder.put(LABEL_DLEDGER_OPERATION_STATUS, ControllerMetricsConstant.DLedgerOperationStatus.SUCCESS.getLowerCaseName()).build()); ControllerMetricsManager.dLedgerOpLatency.record(stopwatch.elapsed(TimeUnit.MICROSECONDS), attributesBuilder.build()); } catch (Exception e) { log.error("Failed to append entry to DLedger", e); if (e instanceof TimeoutException) { ControllerMetricsManager.dLedgerOpTotal.add(1, attributesBuilder.put(LABEL_DLEDGER_OPERATION_STATUS, ControllerMetricsConstant.DLedgerOperationStatus.TIMEOUT.getLowerCaseName()).build()); } else { ControllerMetricsManager.dLedgerOpTotal.add(1, attributesBuilder.put(LABEL_DLEDGER_OPERATION_STATUS, ControllerMetricsConstant.DLedgerOperationStatus.FAILED.getLowerCaseName()).build()); } return false; } return true; } return false; } // Only for test public MemberState getMemberState() { return this.dLedgerServer.getMemberState(); } public void setBrokerAlivePredicate(BrokerValidPredicate brokerAlivePredicate) { this.brokerAlivePredicate = brokerAlivePredicate; } public void setElectPolicy(ElectPolicy electPolicy) { this.electPolicy = electPolicy; } private void cancelScanInactiveFuture() { if (this.scanInactiveMasterFuture != null) { this.scanInactiveMasterFuture.cancel(true); this.scanInactiveMasterFuture = null; } } /** * Event handler that handle event */ interface EventHandler { /** * Run the controller event */ void run() throws Throwable; /** * Return the completableFuture */ CompletableFuture future(); /** * Handle Exception. */ void handleException(final Throwable t); } /** * Event scheduler, schedule event handler from event queue */ class EventScheduler extends ServiceThread { private final BlockingQueue eventQueue; public EventScheduler() { this.eventQueue = new LinkedBlockingQueue<>(1024); } @Override public String getServiceName() { return EventScheduler.class.getName(); } @Override public void run() { log.info("Start event scheduler."); while (!isStopped()) { EventHandler handler; try { handler = this.eventQueue.poll(5, TimeUnit.SECONDS); } catch (final InterruptedException e) { continue; } try { if (handler != null) { handler.run(); } } catch (final Throwable e) { handler.handleException(e); } } } public CompletableFuture appendEvent(final String name, final Supplier> supplier, boolean isWriteEvent) { if (isStopped() || !DLedgerController.this.roleHandler.isLeaderState()) { final RemotingCommand command = RemotingCommand.createResponseCommand(ResponseCode.CONTROLLER_NOT_LEADER, "The controller is not in leader state"); final CompletableFuture future = new CompletableFuture<>(); future.complete(command); return future; } final EventHandler event = new ControllerEventHandler<>(name, supplier, isWriteEvent); int tryTimes = 0; while (true) { try { if (!this.eventQueue.offer(event, 5, TimeUnit.SECONDS)) { continue; } return event.future(); } catch (final InterruptedException e) { log.error("Error happen in EventScheduler when append event", e); tryTimes++; if (tryTimes > 3) { return null; } } } } } /** * Event handler, get events from supplier, and append events to DLedger */ class ControllerEventHandler implements EventHandler { private final String name; private final Supplier> supplier; private final CompletableFuture future; private final boolean isWriteEvent; ControllerEventHandler(final String name, final Supplier> supplier, final boolean isWriteEvent) { this.name = name; this.supplier = supplier; this.future = new CompletableFuture<>(); this.isWriteEvent = isWriteEvent; } @Override public void run() throws Throwable { final ControllerResult result = this.supplier.get(); log.info("Event queue run event {}, get the result {}", this.name, result); boolean appendSuccess = true; if (!this.isWriteEvent || result.getEvents() == null || result.getEvents().isEmpty()) { // read event, or write event with empty events in response which also equals to read event if (DLedgerController.this.controllerConfig.isProcessReadEvent()) { // Now the DLedger don't have the function of Read-Index or Lease-Read, // So we still need to propose an empty request to DLedger. final AppendEntryRequest request = new AppendEntryRequest(); request.setBody(new byte[0]); appendSuccess = appendToDLedgerAndWait(request); } } else { // write event final List events = result.getEvents(); final List eventBytes = new ArrayList<>(events.size()); for (final EventMessage event : events) { if (event != null) { final byte[] data = DLedgerController.this.eventSerializer.serialize(event); if (data != null && data.length > 0) { eventBytes.add(data); } } } // Append events to DLedger if (!eventBytes.isEmpty()) { // batch append events final BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setBatchMsgs(eventBytes); appendSuccess = appendToDLedgerAndWait(request); } } if (appendSuccess) { final RemotingCommand response = RemotingCommand.createResponseCommandWithHeader(result.getResponseCode(), (CommandCustomHeader) result.getResponse()); if (result.getBody() != null) { response.setBody(result.getBody()); } if (result.getRemark() != null) { response.setRemark(result.getRemark()); } this.future.complete(response); } else { log.error("Failed to append event to DLedger, the response is {}, try cancel the future", result.getResponse()); this.future.cancel(true); } } @Override public CompletableFuture future() { return this.future; } @Override public void handleException(final Throwable t) { log.error("Error happen when handle event {}", this.name, t); this.future.completeExceptionally(t); } } /** * Role change handler, trigger the startScheduling() and stopScheduling() when role change. */ class RoleChangeHandler implements DLedgerLeaderElector.RoleChangeHandler { private final String selfId; private final ExecutorService executorService = ThreadUtils.newSingleThreadExecutor(new ThreadFactoryImpl("DLedgerControllerRoleChangeHandler_")); private volatile MemberState.Role currentRole = MemberState.Role.FOLLOWER; public RoleChangeHandler(final String selfId) { this.selfId = selfId; } @Override public void handle(long term, MemberState.Role role) { Runnable runnable = () -> { switch (role) { case CANDIDATE: ControllerMetricsManager.recordRole(role, this.currentRole); this.currentRole = MemberState.Role.CANDIDATE; log.info("Controller {} change role to candidate", this.selfId); DLedgerController.this.stopScheduling(); DLedgerController.this.cancelScanInactiveFuture(); break; case FOLLOWER: ControllerMetricsManager.recordRole(role, this.currentRole); this.currentRole = MemberState.Role.FOLLOWER; log.info("Controller {} change role to Follower, leaderId:{}", this.selfId, getMemberState().getLeaderId()); DLedgerController.this.stopScheduling(); DLedgerController.this.cancelScanInactiveFuture(); break; case LEADER: { log.info("Controller {} change role to leader, try process a initial proposal", this.selfId); // Because the role becomes to leader, but the memory statemachine of the controller is still in the old point, // some committed logs have not been applied. Therefore, we must first process an empty request to DLedger, // and after the request is committed, the controller can provide services(startScheduling). int tryTimes = 0; while (true) { final AppendEntryRequest request = new AppendEntryRequest(); request.setBody(new byte[0]); try { if (appendToDLedgerAndWait(request)) { ControllerMetricsManager.recordRole(role, this.currentRole); this.currentRole = MemberState.Role.LEADER; DLedgerController.this.startScheduling(); if (DLedgerController.this.scanInactiveMasterFuture == null) { long scanInactiveMasterInterval = DLedgerController.this.controllerConfig.getScanInactiveMasterInterval(); DLedgerController.this.scanInactiveMasterFuture = DLedgerController.this.scanInactiveMasterService.scheduleAtFixedRate(DLedgerController.this::scanInactiveMasterAndTriggerReelect, scanInactiveMasterInterval, scanInactiveMasterInterval, TimeUnit.MILLISECONDS); } break; } } catch (final Throwable e) { log.error("Error happen when controller leader append initial request to DLedger", e); } if (!DLedgerController.this.getMemberState().isLeader()) { // now is not a leader log.error("Append a initial log failed because current state is not leader"); break; } tryTimes++; log.error("Controller leader append initial log failed, try {} times", tryTimes); if (tryTimes % 3 == 0) { log.warn("Controller leader append initial log failed too many times, please wait a while"); } } break; } } }; this.executorService.submit(runnable); } @Override public void startup() { } @Override public void shutdown() { if (this.currentRole == MemberState.Role.LEADER) { DLedgerController.this.stopScheduling(); } this.executorService.shutdown(); } public boolean isLeaderState() { return this.currentRole == MemberState.Role.LEADER; } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerControllerStateMachine.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import io.openmessaging.storage.dledger.entry.DLedgerEntry; import io.openmessaging.storage.dledger.exception.DLedgerException; import io.openmessaging.storage.dledger.snapshot.SnapshotReader; import io.openmessaging.storage.dledger.snapshot.SnapshotWriter; import io.openmessaging.storage.dledger.statemachine.CommittedEntryIterator; import io.openmessaging.storage.dledger.statemachine.StateMachine; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.impl.event.EventMessage; import org.apache.rocketmq.controller.impl.event.EventSerializer; import org.apache.rocketmq.controller.impl.manager.ReplicasInfoManager; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * The state machine implementation of the dledger controller */ public class DLedgerControllerStateMachine implements StateMachine { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final ReplicasInfoManager replicasInfoManager; private final EventSerializer eventSerializer; private final String dLedgerId; public DLedgerControllerStateMachine(final ReplicasInfoManager replicasInfoManager, final EventSerializer eventSerializer, final String dLedgerGroupId, final String dLedgerSelfId) { this.replicasInfoManager = replicasInfoManager; this.eventSerializer = eventSerializer; this.dLedgerId = generateDLedgerId(dLedgerGroupId, dLedgerSelfId); } @Override public String generateDLedgerId(String dLedgerGroupId, String dLedgerSelfId) { return new StringBuilder(20).append(dLedgerGroupId).append("#").append(dLedgerSelfId).toString(); } @Override public void onApply(CommittedEntryIterator iterator) { int applyingSize = 0; long firstApplyIndex = -1; long lastApplyIndex = -1; while (iterator.hasNext()) { final DLedgerEntry entry = iterator.next(); final byte[] body = entry.getBody(); if (body != null && body.length > 0) { final EventMessage event = this.eventSerializer.deserialize(body); this.replicasInfoManager.applyEvent(event); } firstApplyIndex = firstApplyIndex == -1 ? entry.getIndex() : firstApplyIndex; lastApplyIndex = entry.getIndex(); applyingSize++; } log.info("Apply {} events index from {} to {} on controller {}", applyingSize, firstApplyIndex, lastApplyIndex, this.dLedgerId); } @Override public boolean onSnapshotSave(SnapshotWriter writer) { return true; } @Override public boolean onSnapshotLoad(SnapshotReader reader) { return false; } @Override public void onShutdown() { log.info("StateMachine {} onShutdown", this.dLedgerId); } @Override public void onError(DLedgerException exception) { log.error("Encountered an error on StateMachine {}, dLedger may stop working since some error occurs, you should figure out the cause and repair or remove this node.", this.dLedgerId, exception); } @Override public String getBindDLedgerId() { return this.dLedgerId; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/JRaftController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import com.alipay.sofa.jraft.Node; import com.alipay.sofa.jraft.RaftGroupService; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.NodeId; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.entity.Task; import com.alipay.sofa.jraft.option.NodeOptions; import org.apache.commons.io.FileUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.Controller; import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; import org.apache.rocketmq.controller.impl.closure.ControllerClosure; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoRequest; import org.apache.rocketmq.controller.impl.task.GetSyncStateDataRequest; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventRequest; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; public class JRaftController implements Controller { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final RaftGroupService raftGroupService; private Node node; private final JRaftControllerStateMachine stateMachine; private final ControllerConfig controllerConfig; private final List brokerLifecycleListeners; private final Map peerIdToAddr; private final NettyRemotingServer remotingServer; public JRaftController(ControllerConfig controllerConfig, final ChannelEventListener channelEventListener) throws IOException { this.controllerConfig = controllerConfig; this.brokerLifecycleListeners = new ArrayList<>(); final NodeOptions nodeOptions = new NodeOptions(); nodeOptions.setElectionTimeoutMs(controllerConfig.getJraftConfig().getjRaftElectionTimeoutMs()); nodeOptions.setSnapshotIntervalSecs(controllerConfig.getJraftConfig().getjRaftSnapshotIntervalSecs()); final PeerId serverId = new PeerId(); if (!serverId.parse(controllerConfig.getJraftConfig().getjRaftServerId())) { throw new IllegalArgumentException("Fail to parse serverId:" + controllerConfig.getJraftConfig().getjRaftServerId()); } final Configuration initConf = new Configuration(); if (!initConf.parse(controllerConfig.getJraftConfig().getjRaftInitConf())) { throw new IllegalArgumentException("Fail to parse initConf:" + controllerConfig.getJraftConfig().getjRaftInitConf()); } nodeOptions.setInitialConf(initConf); FileUtils.forceMkdir(new File(controllerConfig.getControllerStorePath())); nodeOptions.setLogUri(controllerConfig.getControllerStorePath() + File.separator + "log"); nodeOptions.setRaftMetaUri(controllerConfig.getControllerStorePath() + File.separator + "raft_meta"); nodeOptions.setSnapshotUri(controllerConfig.getControllerStorePath() + File.separator + "snapshot"); this.stateMachine = new JRaftControllerStateMachine(controllerConfig, new NodeId(controllerConfig.getJraftConfig().getjRaftGroupId(), serverId)); this.stateMachine.registerOnLeaderStart(this::onLeaderStart); this.stateMachine.registerOnLeaderStop(this::onLeaderStop); nodeOptions.setFsm(this.stateMachine); this.raftGroupService = new RaftGroupService(controllerConfig.getJraftConfig().getjRaftGroupId(), serverId, nodeOptions); this.peerIdToAddr = new HashMap<>(); initPeerIdMap(); NettyServerConfig nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(Integer.parseInt(this.peerIdToAddr.get(serverId).split(":")[1])); remotingServer = new NettyRemotingServer(nettyServerConfig, channelEventListener); } private void initPeerIdMap() { String[] peers = this.controllerConfig.getJraftConfig().getjRaftInitConf().split(","); String[] rpcAddrs = this.controllerConfig.getJraftConfig().getjRaftControllerRPCAddr().split(","); for (int i = 0; i < peers.length; i++) { PeerId peerId = new PeerId(); if (!peerId.parse(peers[i])) { throw new IllegalArgumentException("Fail to parse peerId:" + peers[i]); } this.peerIdToAddr.put(peerId, rpcAddrs[i]); } } @Override public void startup() { this.remotingServer.start(); this.node = this.raftGroupService.start(); log.info("Controller {} started.", node.getNodeId()); } @Override public void shutdown() { this.stopScheduling(); this.raftGroupService.shutdown(); this.remotingServer.shutdown(); log.info("Controller {} stopped.", node.getNodeId()); } @Override public void startScheduling() { } @Override public void stopScheduling() { } @Override public boolean isLeaderState() { return node.isLeader(); } private CompletableFuture applyToJRaft(RemotingCommand request) { if (!isLeaderState()) { final RemotingCommand command = RemotingCommand.createResponseCommand(ResponseCode.CONTROLLER_NOT_LEADER, "The controller is not in leader state"); final CompletableFuture future = new CompletableFuture<>(); future.complete(command); log.warn("Apply to none leader controller, controller state is {}", node.getNodeState()); return future; } ControllerClosure closure = new ControllerClosure(request); Task task = closure.taskWithThisClosure(); if (task != null) { node.apply(task); return closure.getFuture(); } else { log.error("Apply task failed, task is null."); return CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.CONTROLLER_JRAFT_INTERNAL_ERROR, "Apply task failed, Please see the server log.")); } } @Override public CompletableFuture alterSyncStateSet(AlterSyncStateSetRequestHeader request, SyncStateSet syncStateSet) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET, request); requestCommand.setBody(syncStateSet.encode()); return applyToJRaft(requestCommand); } @Override public CompletableFuture electMaster(ElectMasterRequestHeader request) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ELECT_MASTER, request); return applyToJRaft(requestCommand); } @Override public CompletableFuture getNextBrokerId(GetNextBrokerIdRequestHeader request) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_NEXT_BROKER_ID, request); return applyToJRaft(requestCommand); } @Override public CompletableFuture applyBrokerId(ApplyBrokerIdRequestHeader request) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_APPLY_BROKER_ID, request); return applyToJRaft(requestCommand); } @Override public CompletableFuture registerBroker(RegisterBrokerToControllerRequestHeader request) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_REGISTER_BROKER, request); return applyToJRaft(requestCommand); } @Override public CompletableFuture getReplicaInfo(GetReplicaInfoRequestHeader request) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_REPLICA_INFO, request); return applyToJRaft(requestCommand); } @Override public CompletableFuture getSyncStateData(List brokerNames) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_SYNC_STATE_DATA, new GetSyncStateDataRequest()); requestCommand.setBody(RemotingSerializable.encode(brokerNames)); return applyToJRaft(requestCommand); } @Override public CompletableFuture cleanBrokerData(CleanControllerBrokerDataRequestHeader requestHeader) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CLEAN_BROKER_DATA, requestHeader); return applyToJRaft(requestCommand); } @Override public void registerBrokerLifecycleListener(BrokerLifecycleListener listener) { this.brokerLifecycleListeners.add(listener); } @Override public RemotingCommand getControllerMetadata() { List peers = node.getOptions().getInitialConf().getPeers(); final StringBuilder sb = new StringBuilder(); for (PeerId peer : peers) { sb.append(peerIdToAddr.get(peer)).append(";"); } return RemotingCommand.createResponseCommandWithHeader(ResponseCode.SUCCESS, new GetMetaDataResponseHeader( node.getGroupId(), node.getLeaderId() == null ? "" : node.getLeaderId().toString(), this.peerIdToAddr.get(node.getLeaderId()), node.isLeader(), sb.toString() )); } @Override public RemotingServer getRemotingServer() { return remotingServer; } public void onLeaderStart(long term) { log.info("Controller start leadership, term: {}.", term); } public void onLeaderStop(Status status) { log.info("Controller {} stop leadership, status: {}.", node.getNodeId(), status); this.stopScheduling(); } public CompletableFuture getBrokerLiveInfo(GetBrokerLiveInfoRequest requestHeader) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_LIVE_INFO_REQUEST, requestHeader); return applyToJRaft(requestCommand); } public CompletableFuture onBrokerHeartBeat(RaftBrokerHeartBeatEventRequest requestHeader) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.RAFT_BROKER_HEART_BEAT_EVENT_REQUEST, requestHeader); return applyToJRaft(requestCommand); } public CompletableFuture onBrokerCloseChannel(BrokerCloseChannelRequest requestHeader) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.BROKER_CLOSE_CHANNEL_REQUEST, requestHeader); return applyToJRaft(requestCommand); } public CompletableFuture checkNotActiveBroker(CheckNotActiveBrokerRequest requestHeader) { final RemotingCommand requestCommand = RemotingCommand.createRequestCommand(RequestCode.CHECK_NOT_ACTIVE_BROKER_REQUEST, requestHeader); return applyToJRaft(requestCommand); } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/JRaftControllerStateMachine.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import com.alipay.sofa.jraft.Closure; import com.alipay.sofa.jraft.Iterator; import com.alipay.sofa.jraft.StateMachine; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.LeaderChangeContext; import com.alipay.sofa.jraft.entity.NodeId; import com.alipay.sofa.jraft.error.RaftError; import com.alipay.sofa.jraft.error.RaftException; import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; import com.alipay.sofa.jraft.util.Utils; import io.opentelemetry.api.common.AttributesBuilder; import org.apache.commons.io.FileUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy; import org.apache.rocketmq.controller.impl.closure.ControllerClosure; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.controller.impl.manager.RaftReplicasInfoManager; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoRequest; import org.apache.rocketmq.controller.impl.task.GetSyncStateDataRequest; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventRequest; import org.apache.rocketmq.controller.metrics.ControllerMetricsConstant; import org.apache.rocketmq.controller.metrics.ControllerMetricsManager; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_BROKER_SET; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_CLUSTER_NAME; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_ELECTION_RESULT; public class JRaftControllerStateMachine implements StateMachine { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final List> onLeaderStartCallbacks; private final List> onLeaderStopCallbacks; private final RaftReplicasInfoManager replicasInfoManager; private final NodeId nodeId; public JRaftControllerStateMachine(ControllerConfig controllerConfig, NodeId nodeId) { this.replicasInfoManager = new RaftReplicasInfoManager(controllerConfig); this.nodeId = nodeId; this.onLeaderStartCallbacks = new ArrayList<>(); this.onLeaderStopCallbacks = new ArrayList<>(); } @Override public void onApply(Iterator iter) { while (iter.hasNext()) { byte[] data = iter.getData().array(); ControllerClosure controllerClosure = (ControllerClosure) iter.done(); processEvent(controllerClosure, data, iter.getTerm(), iter.getIndex()); iter.next(); } } private void processEvent(ControllerClosure controllerClosure, byte[] data, long term, long index) { RemotingCommand request; ControllerResult result; try { if (controllerClosure != null) { request = controllerClosure.getRequestEvent(); } else { request = RemotingCommand.decode(Arrays.copyOfRange(data, 4, data.length)); } log.info("process event: term {}, index {}, request code {}", term, index, request.getCode()); switch (request.getCode()) { case RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET: AlterSyncStateSetRequestHeader requestHeader = (AlterSyncStateSetRequestHeader) request.decodeCommandCustomHeader(AlterSyncStateSetRequestHeader.class); SyncStateSet syncStateSet = RemotingSerializable.decode(request.getBody(), SyncStateSet.class); result = alterSyncStateSet(requestHeader, syncStateSet); break; case RequestCode.CONTROLLER_ELECT_MASTER: ElectMasterRequestHeader electMasterRequestHeader = (ElectMasterRequestHeader) request.decodeCommandCustomHeader(ElectMasterRequestHeader.class); result = electMaster(electMasterRequestHeader); break; case RequestCode.CONTROLLER_GET_NEXT_BROKER_ID: GetNextBrokerIdRequestHeader getNextBrokerIdRequestHeader = (GetNextBrokerIdRequestHeader) request.decodeCommandCustomHeader(GetNextBrokerIdRequestHeader.class); result = getNextBrokerId(getNextBrokerIdRequestHeader); break; case RequestCode.CONTROLLER_APPLY_BROKER_ID: ApplyBrokerIdRequestHeader applyBrokerIdRequestHeader = (ApplyBrokerIdRequestHeader) request.decodeCommandCustomHeader(ApplyBrokerIdRequestHeader.class); result = applyBrokerId(applyBrokerIdRequestHeader); break; case RequestCode.CONTROLLER_REGISTER_BROKER: RegisterBrokerToControllerRequestHeader registerBrokerToControllerRequestHeader = (RegisterBrokerToControllerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerToControllerRequestHeader.class); result = registerBroker(registerBrokerToControllerRequestHeader); break; case RequestCode.CONTROLLER_GET_REPLICA_INFO: GetReplicaInfoRequestHeader getReplicaInfoRequestHeader = (GetReplicaInfoRequestHeader) request.decodeCommandCustomHeader(GetReplicaInfoRequestHeader.class); result = getReplicaInfo(getReplicaInfoRequestHeader); break; case RequestCode.CONTROLLER_GET_SYNC_STATE_DATA: List brokerNames = RemotingSerializable.decode(request.getBody(), List.class); GetSyncStateDataRequest getSyncStateDataRequest = (GetSyncStateDataRequest) request.decodeCommandCustomHeader(GetSyncStateDataRequest.class); result = getSyncStateData(brokerNames, getSyncStateDataRequest.getInvokeTime()); break; case RequestCode.CLEAN_BROKER_DATA: CleanControllerBrokerDataRequestHeader cleanBrokerDataRequestHeader = (CleanControllerBrokerDataRequestHeader) request.decodeCommandCustomHeader(CleanControllerBrokerDataRequestHeader.class); result = cleanBrokerData(cleanBrokerDataRequestHeader); break; case RequestCode.GET_BROKER_LIVE_INFO_REQUEST: GetBrokerLiveInfoRequest getBrokerLiveInfoRequest = (GetBrokerLiveInfoRequest) request.decodeCommandCustomHeader(GetBrokerLiveInfoRequest.class); result = replicasInfoManager.getBrokerLiveInfo(getBrokerLiveInfoRequest); break; case RequestCode.RAFT_BROKER_HEART_BEAT_EVENT_REQUEST: RaftBrokerHeartBeatEventRequest brokerHeartbeatRequestHeader = (RaftBrokerHeartBeatEventRequest) request.decodeCommandCustomHeader(RaftBrokerHeartBeatEventRequest.class); result = replicasInfoManager.onBrokerHeartBeat(brokerHeartbeatRequestHeader); break; case RequestCode.BROKER_CLOSE_CHANNEL_REQUEST: BrokerCloseChannelRequest brokerCloseChannelRequest = (BrokerCloseChannelRequest) request.decodeCommandCustomHeader(BrokerCloseChannelRequest.class); result = replicasInfoManager.onBrokerCloseChannel(brokerCloseChannelRequest); break; case RequestCode.CHECK_NOT_ACTIVE_BROKER_REQUEST: CheckNotActiveBrokerRequest checkNotActiveBrokerRequest = (CheckNotActiveBrokerRequest) request.decodeCommandCustomHeader(CheckNotActiveBrokerRequest.class); result = replicasInfoManager.checkNotActiveBroker(checkNotActiveBrokerRequest); break; default: throw new RemotingCommandException("Unknown request code: " + request.getCode()); } result.getEvents().forEach(replicasInfoManager::applyEvent); } catch (RemotingCommandException e) { log.error("Fail to process event", e); if (controllerClosure != null) { controllerClosure.run(new Status(RaftError.EINTERNAL, e.getMessage())); } return; } log.info("process event: term {}, index {}, request code {} success with result {}", term, index, request.getCode(), result.toString()); if (controllerClosure != null) { controllerClosure.setControllerResult(result); controllerClosure.run(Status.OK()); } } private ControllerResult alterSyncStateSet( AlterSyncStateSetRequestHeader requestHeader, SyncStateSet syncStateSet) { return replicasInfoManager.alterSyncStateSet(requestHeader, syncStateSet, new RaftReplicasInfoManager.BrokerValidPredicateWithInvokeTime(requestHeader.getInvokeTime(), this.replicasInfoManager)); } private ControllerResult electMaster(ElectMasterRequestHeader request) { ControllerResult electResult = this.replicasInfoManager.electMaster(request, new DefaultElectPolicy( (clusterName, brokerName, brokerId) -> replicasInfoManager.isBrokerActive(clusterName, brokerName, brokerId, request.getInvokeTime()), replicasInfoManager::getBrokerLiveInfo )); log.info("elect master, request :{}, result: {}", request.toString(), electResult.toString()); AttributesBuilder attributesBuilder = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_CLUSTER_NAME, request.getClusterName()) .put(LABEL_BROKER_SET, request.getBrokerName()); switch (electResult.getResponseCode()) { case ResponseCode.SUCCESS: ControllerMetricsManager.electionTotal.add(1, attributesBuilder.put(LABEL_ELECTION_RESULT, ControllerMetricsConstant.ElectionResult.NEW_MASTER_ELECTED.getLowerCaseName()).build()); break; case ResponseCode.CONTROLLER_MASTER_STILL_EXIST: ControllerMetricsManager.electionTotal.add(1, attributesBuilder.put(LABEL_ELECTION_RESULT, ControllerMetricsConstant.ElectionResult.KEEP_CURRENT_MASTER.getLowerCaseName()).build()); break; case ResponseCode.CONTROLLER_MASTER_NOT_AVAILABLE: case ResponseCode.CONTROLLER_ELECT_MASTER_FAILED: ControllerMetricsManager.electionTotal.add(1, attributesBuilder.put(LABEL_ELECTION_RESULT, ControllerMetricsConstant.ElectionResult.NO_MASTER_ELECTED.getLowerCaseName()).build()); break; default: break; } return electResult; } private ControllerResult getNextBrokerId( GetNextBrokerIdRequestHeader requestHeader) { return replicasInfoManager.getNextBrokerId(requestHeader); } private ControllerResult applyBrokerId(ApplyBrokerIdRequestHeader requestHeader) { return replicasInfoManager.applyBrokerId(requestHeader); } private ControllerResult registerBroker(RegisterBrokerToControllerRequestHeader request) { return replicasInfoManager.registerBroker(request, new RaftReplicasInfoManager.BrokerValidPredicateWithInvokeTime(request.getInvokeTime(), this.replicasInfoManager)); } private ControllerResult getReplicaInfo(GetReplicaInfoRequestHeader request) { return replicasInfoManager.getReplicaInfo(request); } private ControllerResult getSyncStateData(List brokerNames, long invokeTile) { return replicasInfoManager.getSyncStateData(brokerNames, new RaftReplicasInfoManager.BrokerValidPredicateWithInvokeTime(invokeTile, this.replicasInfoManager)); } private ControllerResult cleanBrokerData(CleanControllerBrokerDataRequestHeader requestHeader) { return replicasInfoManager.cleanBrokerData(requestHeader, new RaftReplicasInfoManager.BrokerValidPredicateWithInvokeTime(requestHeader.getInvokeTime(), this.replicasInfoManager)); } @Override public void onShutdown() { log.info("StateMachine {} node {} onShutdown", getClass().getName(), nodeId.toString()); } @Override public void onSnapshotSave(SnapshotWriter writer, Closure done) { byte[] data; try { data = this.replicasInfoManager.serialize(); } catch (Throwable e) { done.run(new Status(RaftError.EIO, "Fail to serialize replicasInfoManager state machine data")); return; } Utils.runInThread(() -> { try { FileUtils.writeByteArrayToFile(new File(writer.getPath() + File.separator + "data"), data); if (writer.addFile("data")) { log.info("Save snapshot, path={}", writer.getPath()); done.run(Status.OK()); } else { throw new IOException("Fail to add file to writer"); } } catch (IOException e) { log.error("Fail to save snapshot", e); done.run(new Status(RaftError.EIO, "Fail to save snapshot")); } }); } @Override public boolean onSnapshotLoad(SnapshotReader reader) { if (reader.getFileMeta("data") == null) { log.error("Fail to find data file in {}", reader.getPath()); return false; } try { byte[] data = FileUtils.readFileToByteArray(new File(reader.getPath() + File.separator + "data")); this.replicasInfoManager.deserializeFrom(data); log.info("Load snapshot from {}", reader.getPath()); return true; } catch (Throwable e) { log.error("Fail to load snapshot from {}", reader.getPath(), e); return false; } } @Override public void onLeaderStart(long term) { for (Consumer callback : onLeaderStartCallbacks) { callback.accept(term); } log.info("node {} Start Leader, term={}", nodeId.toString(), term); } @Override public void onLeaderStop(Status status) { for (Consumer callback : onLeaderStopCallbacks) { callback.accept(status); } log.info("node {} Stop Leader, status={}", nodeId.toString(), status); } public void registerOnLeaderStart(Consumer callback) { onLeaderStartCallbacks.add(callback); } public void registerOnLeaderStop(Consumer callback) { onLeaderStopCallbacks.add(callback); } @Override public void onError(RaftException e) { log.error("Encountered an error={} on StateMachine {}, node {}, raft may stop working since some error occurs, you should figure out the cause and repair or remove this node.", e.getStatus(), this.getClass().getName(), nodeId.toString(), e); } @Override public void onConfigurationCommitted(Configuration conf) { log.info("Configuration committed, conf={}", conf); } @Override public void onStopFollowing(LeaderChangeContext ctx) { log.info("Stop following, ctx={}", ctx); } @Override public void onStartFollowing(LeaderChangeContext ctx) { log.info("Start following, ctx={}", ctx); } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/closure/ControllerClosure.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.closure; import com.alipay.sofa.jraft.Closure; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.entity.Task; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import java.util.concurrent.CompletableFuture; public class ControllerClosure implements Closure { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final RemotingCommand requestEvent; private final CompletableFuture future; private ControllerResult controllerResult; private Task task; public ControllerClosure(RemotingCommand requestEvent) { this.requestEvent = requestEvent; this.future = new CompletableFuture<>(); this.task = null; } public CompletableFuture getFuture() { return future; } public void setControllerResult(ControllerResult controllerResult) { this.controllerResult = controllerResult; } @Override public void run(Status status) { if (status.isOk()) { final RemotingCommand response = RemotingCommand.createResponseCommandWithHeader(controllerResult.getResponseCode(), (CommandCustomHeader) controllerResult.getResponse()); if (controllerResult.getBody() != null) { response.setBody(controllerResult.getBody()); } if (controllerResult.getRemark() != null) { response.setRemark(controllerResult.getRemark()); } future.complete(response); } else { log.error("Failed to append to jRaft node, error is: {}.", status); future.complete(RemotingCommand.createResponseCommand(ResponseCode.CONTROLLER_JRAFT_INTERNAL_ERROR, status.getErrorMsg())); } } public Task taskWithThisClosure() { if (task != null) { return task; } task = new Task(); task.setDone(this); task.setData(requestEvent.encode()); return task; } public RemotingCommand getRequestEvent() { return requestEvent; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/AlterSyncStateSetEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import java.util.HashSet; import java.util.Set; /** * The event alters the syncStateSet of target broker. * Triggered by the AlterSyncStateSetApi. */ public class AlterSyncStateSetEvent implements EventMessage { private final String brokerName; private final Set newSyncStateSet; public AlterSyncStateSetEvent(String brokerName, Set newSyncStateSet) { this.brokerName = brokerName; this.newSyncStateSet = new HashSet<>(newSyncStateSet); } @Override public EventType getEventType() { return EventType.ALTER_SYNC_STATE_SET_EVENT; } public String getBrokerName() { return brokerName; } public Set getNewSyncStateSet() { return new HashSet<>(newSyncStateSet); } @Override public String toString() { return "AlterSyncStateSetEvent{" + "brokerName='" + brokerName + '\'' + ", newSyncStateSet=" + newSyncStateSet + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/ApplyBrokerIdEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; /** * The event trys to apply a new id for a new broker. * Triggered by the RegisterBrokerApi. */ public class ApplyBrokerIdEvent implements EventMessage { private final String clusterName; private final String brokerName; private final String brokerAddress; private final String registerCheckCode; private final long newBrokerId; public ApplyBrokerIdEvent(String clusterName, String brokerName, String brokerAddress, long newBrokerId, String registerCheckCode) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerAddress = brokerAddress; this.newBrokerId = newBrokerId; this.registerCheckCode = registerCheckCode; } @Override public EventType getEventType() { return EventType.APPLY_BROKER_ID_EVENT; } public String getBrokerName() { return brokerName; } public String getBrokerAddress() { return brokerAddress; } public long getNewBrokerId() { return newBrokerId; } public String getClusterName() { return clusterName; } public String getRegisterCheckCode() { return registerCheckCode; } @Override public String toString() { return "ApplyBrokerIdEvent{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerAddress='" + brokerAddress + '\'' + ", registerCheckCode='" + registerCheckCode + '\'' + ", newBrokerId=" + newBrokerId + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/CleanBrokerDataEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import java.util.Set; public class CleanBrokerDataEvent implements EventMessage { private String brokerName; private Set brokerIdSetToClean; public CleanBrokerDataEvent(String brokerName, Set brokerIdSetToClean) { this.brokerName = brokerName; this.brokerIdSetToClean = brokerIdSetToClean; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public void setBrokerIdSetToClean(Set brokerIdSetToClean) { this.brokerIdSetToClean = brokerIdSetToClean; } public Set getBrokerIdSetToClean() { return brokerIdSetToClean; } /** * Returns the event type of this message */ @Override public EventType getEventType() { return EventType.CLEAN_BROKER_DATA_EVENT; } @Override public String toString() { return "CleanBrokerDataEvent{" + "brokerName='" + brokerName + '\'' + ", brokerIdSetToClean=" + brokerIdSetToClean + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/ControllerResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.remoting.protocol.ResponseCode; public class ControllerResult { private final List events; private final T response; private byte[] body; private int responseCode = ResponseCode.SUCCESS; private String remark; public ControllerResult() { this(null); } public ControllerResult(T response) { this.events = new ArrayList<>(); this.response = response; } public ControllerResult(List events, T response) { this.events = new ArrayList<>(events); this.response = response; } public static ControllerResult of(List events, T response) { return new ControllerResult<>(events, response); } public List getEvents() { return new ArrayList<>(events); } public T getResponse() { return response; } public byte[] getBody() { return body; } public void setBody(byte[] body) { this.body = body; } public void setCodeAndRemark(int responseCode, String remark) { this.responseCode = responseCode; this.remark = remark; } public int getResponseCode() { return responseCode; } public String getRemark() { return remark; } public void addEvent(EventMessage event) { this.events.add(event); } @Override public String toString() { return "ControllerResult{" + "events=" + events + ", response=" + response + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/ElectMasterEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; /** * The event trys to elect a new master for target broker. * Triggered by the ElectMasterApi. */ public class ElectMasterEvent implements EventMessage { // Mark whether a new master was elected. private final boolean newMasterElected; private final String brokerName; private final Long newMasterBrokerId; public ElectMasterEvent(boolean newMasterElected, String brokerName) { this(newMasterElected, brokerName, null); } public ElectMasterEvent(String brokerName, Long newMasterBrokerId) { this(true, brokerName, newMasterBrokerId); } public ElectMasterEvent(boolean newMasterElected, String brokerName, Long newMasterBrokerId) { this.newMasterElected = newMasterElected; this.brokerName = brokerName; this.newMasterBrokerId = newMasterBrokerId; } @Override public EventType getEventType() { return EventType.ELECT_MASTER_EVENT; } public boolean getNewMasterElected() { return newMasterElected; } public String getBrokerName() { return brokerName; } public Long getNewMasterBrokerId() { return newMasterBrokerId; } @Override public String toString() { return "ElectMasterEvent{" + "newMasterElected=" + newMasterElected + ", brokerName='" + brokerName + '\'' + ", newMasterBrokerId=" + newMasterBrokerId + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventMessage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; /** * The parent class of Event, the subclass needs to indicate eventType. */ public interface EventMessage { /** * Returns the event type of this message */ EventType getEventType(); } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventSerializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import org.apache.commons.lang3.SerializationException; import org.apache.rocketmq.common.utils.FastJsonSerializer; /** * EventMessage serializer */ public class EventSerializer { private final FastJsonSerializer serializer; public EventSerializer() { this.serializer = new FastJsonSerializer(); } private void putShort(byte[] memory, int index, int value) { memory[index] = (byte) (value >>> 8); memory[index + 1] = (byte) value; } private short getShort(byte[] memory, int index) { return (short) (memory[index] << 8 | memory[index + 1] & 0xFF); } public byte[] serialize(EventMessage message) throws SerializationException { final short eventType = message.getEventType().getId(); final byte[] data = this.serializer.serialize(message); if (data != null && data.length > 0) { final byte[] result = new byte[2 + data.length]; putShort(result, 0, eventType); System.arraycopy(data, 0, result, 2, data.length); return result; } return null; } public EventMessage deserialize(byte[] bytes) throws SerializationException { if (bytes.length < 2) { return null; } final short eventId = getShort(bytes, 0); if (eventId > 0) { final byte[] data = new byte[bytes.length - 2]; System.arraycopy(bytes, 2, data, 0, data.length); final EventType eventType = EventType.from(eventId); if (eventType != null) { switch (eventType) { case ALTER_SYNC_STATE_SET_EVENT: return this.serializer.deserialize(data, AlterSyncStateSetEvent.class); case APPLY_BROKER_ID_EVENT: return this.serializer.deserialize(data, ApplyBrokerIdEvent.class); case ELECT_MASTER_EVENT: return this.serializer.deserialize(data, ElectMasterEvent.class); case CLEAN_BROKER_DATA_EVENT: return this.serializer.deserialize(data, CleanBrokerDataEvent.class); case UPDATE_BROKER_ADDRESS: return this.serializer.deserialize(data, UpdateBrokerAddressEvent.class); default: break; } } } return null; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; /** * Event type (name, id); */ public enum EventType { ALTER_SYNC_STATE_SET_EVENT("AlterSyncStateSetEvent", (short) 1), APPLY_BROKER_ID_EVENT("ApplyBrokerIdEvent", (short) 2), ELECT_MASTER_EVENT("ElectMasterEvent", (short) 3), READ_EVENT("ReadEvent", (short) 4), CLEAN_BROKER_DATA_EVENT("CleanBrokerDataEvent", (short) 5), UPDATE_BROKER_ADDRESS("UpdateBrokerAddressEvent", (short) 6); private final String name; private final short id; EventType(String name, short id) { this.name = name; this.id = id; } public static EventType from(short id) { switch (id) { case 1: return ALTER_SYNC_STATE_SET_EVENT; case 2: return APPLY_BROKER_ID_EVENT; case 3: return ELECT_MASTER_EVENT; case 4: return READ_EVENT; case 5: return CLEAN_BROKER_DATA_EVENT; case 6: return UPDATE_BROKER_ADDRESS; } return null; } public String getName() { return name; } public short getId() { return id; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/ListEventSerializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import org.apache.commons.lang3.SerializationException; import org.apache.rocketmq.common.utils.FastJsonSerializer; import org.apache.rocketmq.common.utils.Serializer; import org.apache.rocketmq.logging.org.slf4j.Logger; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; public class ListEventSerializer { private ListEventSerializer() { } private static final Serializer SERIALIZER = new FastJsonSerializer(); private static void putShort(byte[] memory, int index, int value) { memory[index] = (byte) (value >>> 8); memory[index + 1] = (byte) value; } private static void putShort(ByteArrayOutputStream outputStream, int value) { outputStream.write((byte) (value >>> 8)); outputStream.write((byte) value); } private static short getShort(byte[] memory, int index) { return (short) (memory[index] << 8 | memory[index + 1] & 0xFF); } private static void putInt(byte[] memory, int index, int value) { memory[index] = (byte) (value >>> 24); memory[index + 1] = (byte) (value >>> 16); memory[index + 2] = (byte) (value >>> 8); memory[index + 3] = (byte) value; } private static void putInt(ByteArrayOutputStream outputStream, int value) { outputStream.write((byte) (value >>> 24)); outputStream.write((byte) (value >>> 16)); outputStream.write((byte) (value >>> 8)); outputStream.write((byte) value); } private static int getInt(byte[] memory, int index) { return memory[index] << 24 | (memory[index + 1] & 0xFF) << 16 | (memory[index + 2] & 0xFF) << 8 | memory[index + 3] & 0xFF; } public static byte[] serialize(List message, Logger log) throws SerializationException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (EventMessage eventMessage : message) { final short eventType = eventMessage.getEventType().getId(); final byte[] data = SERIALIZER.serialize(eventMessage); if (data != null && data.length > 0) { putShort(outputStream, eventType); putInt(outputStream, data.length); outputStream.write(data, 0, data.length); } else { log.error("serialize event message error, event: {}, this event will be discard", eventMessage); } } return outputStream.toByteArray(); } public static List deserialize(byte[] bytes, Logger log) throws SerializationException { List eventMessages = new ArrayList<>(); if (bytes == null || bytes.length <= 6) { return eventMessages; } int index = 0; while (index < bytes.length) { final short eventId = getShort(bytes, index); index += 2; final int dataLength = getInt(bytes, index); index += 4; if (dataLength > 0) { final byte[] data = new byte[dataLength]; System.arraycopy(bytes, index, data, 0, dataLength); final EventType eventType = EventType.from(eventId); if (eventType != null) { switch (eventType) { case ALTER_SYNC_STATE_SET_EVENT: eventMessages.add(SERIALIZER.deserialize(data, AlterSyncStateSetEvent.class)); break; case APPLY_BROKER_ID_EVENT: eventMessages.add(SERIALIZER.deserialize(data, ApplyBrokerIdEvent.class)); break; case ELECT_MASTER_EVENT: eventMessages.add(SERIALIZER.deserialize(data, ElectMasterEvent.class)); break; case CLEAN_BROKER_DATA_EVENT: eventMessages.add(SERIALIZER.deserialize(data, CleanBrokerDataEvent.class)); break; case UPDATE_BROKER_ADDRESS: eventMessages.add(SERIALIZER.deserialize(data, UpdateBrokerAddressEvent.class)); break; default: log.error("deserialize event message error, event id: {}, data: {}", eventId, data); break; } } else { log.error("deserialize event message error, event id: {}, data: {}", eventId, data); } index += dataLength; } else { log.error("deserialize event message error, event id: {}, data length: {}", eventId, dataLength); } } return eventMessages; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/event/UpdateBrokerAddressEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; public class UpdateBrokerAddressEvent implements EventMessage { private String clusterName; private String brokerName; private String brokerAddress; private Long brokerId; public UpdateBrokerAddressEvent(String clusterName, String brokerName, String brokerAddress, Long brokerId) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerAddress = brokerAddress; this.brokerId = brokerId; } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public String getBrokerAddress() { return brokerAddress; } public Long getBrokerId() { return brokerId; } @Override public String toString() { return "UpdateBrokerAddressEvent{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerAddress='" + brokerAddress + '\'' + ", brokerId=" + brokerId + '}'; } @Override public EventType getEventType() { return EventType.UPDATE_BROKER_ADDRESS; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerIdentityInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.heartbeat; import java.io.Serializable; import org.apache.rocketmq.common.UtilAll; import java.util.Objects; public class BrokerIdentityInfo implements Serializable { private static final long serialVersionUID = 883597359635995567L; private final String clusterName; private final String brokerName; private final Long brokerId; public BrokerIdentityInfo(String clusterName, String brokerName, Long brokerId) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerId = brokerId; } public String getClusterName() { return clusterName; } public Long getBrokerId() { return brokerId; } public String getBrokerName() { return brokerName; } public boolean isEmpty() { return UtilAll.isBlank(clusterName) && UtilAll.isBlank(brokerName) && brokerId == null; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof BrokerIdentityInfo) { BrokerIdentityInfo addr = (BrokerIdentityInfo) obj; return clusterName.equals(addr.clusterName) && brokerName.equals(addr.brokerName) && brokerId.equals(addr.brokerId); } return false; } @Override public int hashCode() { return Objects.hash(this.clusterName, this.brokerName, this.brokerId); } @Override public String toString() { return "BrokerIdentityInfo{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerLiveInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.heartbeat; import io.netty.channel.Channel; import java.io.Serializable; public class BrokerLiveInfo implements Serializable { private static final long serialVersionUID = 3612173344946510993L; private final String brokerName; private String brokerAddr; private long heartbeatTimeoutMillis; private Channel channel; private long brokerId; private long lastUpdateTimestamp; private int epoch; private long maxOffset; private long confirmOffset; private Integer electionPriority; public BrokerLiveInfo(String brokerName, String brokerAddr, long brokerId, long lastUpdateTimestamp, long heartbeatTimeoutMillis, Channel channel, int epoch, long maxOffset, Integer electionPriority) { this.brokerName = brokerName; this.brokerAddr = brokerAddr; this.brokerId = brokerId; this.lastUpdateTimestamp = lastUpdateTimestamp; this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; this.channel = channel; this.epoch = epoch; this.electionPriority = electionPriority; this.maxOffset = maxOffset; } public BrokerLiveInfo(String brokerName, String brokerAddr, long brokerId, long lastUpdateTimestamp, long heartbeatTimeoutMillis, Channel channel, int epoch, long maxOffset, Integer electionPriority, long confirmOffset) { this.brokerName = brokerName; this.brokerAddr = brokerAddr; this.brokerId = brokerId; this.lastUpdateTimestamp = lastUpdateTimestamp; this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; this.channel = channel; this.epoch = epoch; this.maxOffset = maxOffset; this.electionPriority = electionPriority; this.confirmOffset = confirmOffset; } @Override public String toString() { return "BrokerLiveInfo{" + "brokerName='" + brokerName + '\'' + ", brokerAddr='" + brokerAddr + '\'' + ", heartbeatTimeoutMillis=" + heartbeatTimeoutMillis + ", channel=" + channel + ", brokerId=" + brokerId + ", lastUpdateTimestamp=" + lastUpdateTimestamp + ", epoch=" + epoch + ", maxOffset=" + maxOffset + ", confirmOffset=" + confirmOffset + '}'; } public String getBrokerName() { return brokerName; } public long getHeartbeatTimeoutMillis() { return heartbeatTimeoutMillis; } public void setHeartbeatTimeoutMillis(long heartbeatTimeoutMillis) { this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; } public Channel getChannel() { return channel; } public long getBrokerId() { return brokerId; } public void setBrokerId(long brokerId) { this.brokerId = brokerId; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } public int getEpoch() { return epoch; } public void setEpoch(int epoch) { this.epoch = epoch; } public long getMaxOffset() { return maxOffset; } public void setMaxOffset(long maxOffset) { this.maxOffset = maxOffset; } public String getBrokerAddr() { return brokerAddr; } public void setConfirmOffset(long confirmOffset) { this.confirmOffset = confirmOffset; } public void setElectionPriority(Integer electionPriority) { this.electionPriority = electionPriority; } public Integer getElectionPriority() { return electionPriority; } public long getConfirmOffset() { return confirmOffset; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public void setChannel(Channel channel) { this.channel = channel; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.heartbeat; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.controller.BrokerHeartbeatManager; import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; public class DefaultBrokerHeartbeatManager implements BrokerHeartbeatManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private ScheduledExecutorService scheduledService; private ExecutorService executor; private final ControllerConfig controllerConfig; private final Map brokerLiveTable; private final List brokerLifecycleListeners; public DefaultBrokerHeartbeatManager(final ControllerConfig controllerConfig) { this.controllerConfig = controllerConfig; this.brokerLiveTable = new ConcurrentHashMap<>(256); this.brokerLifecycleListeners = new ArrayList<>(); } @Override public void start() { this.scheduledService.scheduleAtFixedRate(this::scanNotActiveBroker, 2000, this.controllerConfig.getScanNotActiveBrokerInterval(), TimeUnit.MILLISECONDS); } @Override public void shutdown() { this.scheduledService.shutdown(); this.executor.shutdown(); } @Override public void initialize() { this.scheduledService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DefaultBrokerHeartbeatManager_scheduledService_")); this.executor = Executors.newFixedThreadPool(2, new ThreadFactoryImpl("DefaultBrokerHeartbeatManager_executorService_")); } public void scanNotActiveBroker() { try { log.info("start scanNotActiveBroker"); final Iterator> iterator = this.brokerLiveTable.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry next = iterator.next(); long last = next.getValue().getLastUpdateTimestamp(); long timeoutMillis = next.getValue().getHeartbeatTimeoutMillis(); if (System.currentTimeMillis() - last > timeoutMillis) { final Channel channel = next.getValue().getChannel(); iterator.remove(); if (channel != null) { RemotingHelper.closeChannel(channel); } this.executor.submit(() -> notifyBrokerInActive(next.getKey().getClusterName(), next.getValue().getBrokerName(), next.getValue().getBrokerId())); log.warn("The broker channel {} expired, brokerInfo {}, expired {}ms", next.getValue().getChannel(), next.getKey(), timeoutMillis); } } } catch (Exception e) { log.error("scanNotActiveBroker exception", e); } } private void notifyBrokerInActive(String clusterName, String brokerName, Long brokerId) { for (BrokerLifecycleListener listener : this.brokerLifecycleListeners) { listener.onBrokerInactive(clusterName, brokerName, brokerId); } } @Override public void registerBrokerLifecycleListener(BrokerLifecycleListener listener) { this.brokerLifecycleListeners.add(listener); } @Override public void onBrokerHeartbeat(String clusterName, String brokerName, String brokerAddr, Long brokerId, Long timeoutMillis, Channel channel, Integer epoch, Long maxOffset, Long confirmOffset, Integer electionPriority) { BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo(clusterName, brokerName, brokerId); BrokerLiveInfo prev = this.brokerLiveTable.get(brokerIdentityInfo); int realEpoch = Optional.ofNullable(epoch).orElse(-1); long realBrokerId = Optional.ofNullable(brokerId).orElse(-1L); long realMaxOffset = Optional.ofNullable(maxOffset).orElse(-1L); long realConfirmOffset = Optional.ofNullable(confirmOffset).orElse(-1L); long realTimeoutMillis = Optional.ofNullable(timeoutMillis).orElse(DEFAULT_BROKER_CHANNEL_EXPIRED_TIME); int realElectionPriority = Optional.ofNullable(electionPriority).orElse(Integer.MAX_VALUE); if (null == prev) { this.brokerLiveTable.put(brokerIdentityInfo, new BrokerLiveInfo(brokerName, brokerAddr, realBrokerId, System.currentTimeMillis(), realTimeoutMillis, channel, realEpoch, realMaxOffset, realElectionPriority)); log.info("new broker registered, {}, brokerId:{}", brokerIdentityInfo, realBrokerId); } else { prev.setLastUpdateTimestamp(System.currentTimeMillis()); prev.setHeartbeatTimeoutMillis(realTimeoutMillis); prev.setElectionPriority(realElectionPriority); if (realEpoch > prev.getEpoch() || realEpoch == prev.getEpoch() && realMaxOffset > prev.getMaxOffset()) { prev.setEpoch(realEpoch); prev.setMaxOffset(realMaxOffset); prev.setConfirmOffset(realConfirmOffset); } } } @Override public void onBrokerChannelClose(Channel channel) { BrokerIdentityInfo addrInfo = null; for (Map.Entry entry : this.brokerLiveTable.entrySet()) { if (entry.getValue().getChannel() == channel) { log.info("Channel {} inactive, broker {}, addr:{}, id:{}", entry.getValue().getChannel(), entry.getValue().getBrokerName(), entry.getValue().getBrokerAddr(), entry.getValue().getBrokerId()); addrInfo = entry.getKey(); this.executor.submit(() -> notifyBrokerInActive(entry.getKey().getClusterName(), entry.getValue().getBrokerName(), entry.getValue().getBrokerId())); break; } } if (addrInfo != null) { this.brokerLiveTable.remove(addrInfo); } } @Override public BrokerLiveInfo getBrokerLiveInfo(String clusterName, String brokerName, Long brokerId) { return this.brokerLiveTable.get(new BrokerIdentityInfo(clusterName, brokerName, brokerId)); } @Override public boolean isBrokerActive(String clusterName, String brokerName, Long brokerId) { final BrokerLiveInfo info = this.brokerLiveTable.get(new BrokerIdentityInfo(clusterName, brokerName, brokerId)); if (info != null) { long last = info.getLastUpdateTimestamp(); long timeoutMillis = info.getHeartbeatTimeoutMillis(); return (last + timeoutMillis) >= System.currentTimeMillis(); } return false; } @Override public Map> getActiveBrokersNum() { Map> map = new HashMap<>(); this.brokerLiveTable.keySet().stream() .filter(brokerIdentity -> this.isBrokerActive(brokerIdentity.getClusterName(), brokerIdentity.getBrokerName(), brokerIdentity.getBrokerId())) .forEach(id -> { map.computeIfAbsent(id.getClusterName(), k -> new HashMap<>()); map.get(id.getClusterName()).compute(id.getBrokerName(), (broker, num) -> num == null ? 1 : num + 1 ); }); return map; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/RaftBrokerHeartBeatManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.heartbeat; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; import io.netty.channel.Channel; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.BrokerHeartbeatManager; import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; import org.apache.rocketmq.controller.impl.JRaftController; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoResponse; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventRequest; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class RaftBrokerHeartBeatManager implements BrokerHeartbeatManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private JRaftController controller; private final List brokerLifecycleListeners = new ArrayList<>(); private final ScheduledExecutorService scheduledService; private final ExecutorService executor; private final ControllerConfig controllerConfig; private final Map brokerChannelIdentityInfoMap = new HashMap<>(); // resolve the scene // when controller all down and startup again, we wait for some time to avoid electing a new leader,which is not necessary private volatile long firstReceivedHeartbeatTime = -1; public RaftBrokerHeartBeatManager(ControllerConfig controllerConfig) { this.scheduledService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RaftBrokerHeartbeatManager_scheduledService_")); this.executor = Executors.newFixedThreadPool(2, new ThreadFactoryImpl("RaftBrokerHeartbeatManager_executorService_")); this.controllerConfig = controllerConfig; } public void setController(JRaftController controller) { this.controller = controller; } @Override public void initialize() { } @Override public void start() { this.scheduledService.scheduleAtFixedRate(this::scanNotActiveBroker, 2000, this.controllerConfig.getScanNotActiveBrokerInterval(), TimeUnit.MILLISECONDS); } @Override public void shutdown() { this.scheduledService.shutdown(); this.executor.shutdown(); } @Override public void registerBrokerLifecycleListener(BrokerLifecycleListener listener) { brokerLifecycleListeners.add(listener); } @Override public void onBrokerHeartbeat(String clusterName, String brokerName, String brokerAddr, Long brokerId, Long timeoutMillis, Channel channel, Integer epoch, Long maxOffset, Long confirmOffset, Integer electionPriority) { if (firstReceivedHeartbeatTime == -1) { firstReceivedHeartbeatTime = System.currentTimeMillis(); } BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo(clusterName, brokerName, brokerId); int realEpoch = Optional.ofNullable(epoch).orElse(-1); long realBrokerId = Optional.ofNullable(brokerId).orElse(-1L); long realMaxOffset = Optional.ofNullable(maxOffset).orElse(-1L); long realConfirmOffset = Optional.ofNullable(confirmOffset).orElse(-1L); long realTimeoutMillis = Optional.ofNullable(timeoutMillis).orElse(DEFAULT_BROKER_CHANNEL_EXPIRED_TIME); int realElectionPriority = Optional.ofNullable(electionPriority).orElse(Integer.MAX_VALUE); BrokerLiveInfo liveInfo = new BrokerLiveInfo(brokerName, brokerAddr, realBrokerId, System.currentTimeMillis(), realTimeoutMillis, null, realEpoch, realMaxOffset, realElectionPriority, realConfirmOffset); log.info("broker {} heart beat", brokerIdentityInfo); RaftBrokerHeartBeatEventRequest requestHeader = new RaftBrokerHeartBeatEventRequest(brokerIdentityInfo, liveInfo); CompletableFuture future = controller.onBrokerHeartBeat(requestHeader); try { RemotingCommand remotingCommand = future.get(5, java.util.concurrent.TimeUnit.SECONDS); if (remotingCommand.getCode() != ResponseCode.SUCCESS && remotingCommand.getCode() != ResponseCode.CONTROLLER_NOT_LEADER) { throw new RuntimeException("on broker heartbeat return invalid code, code: " + remotingCommand.getCode()); } } catch (ExecutionException | InterruptedException | TimeoutException | RuntimeException e) { log.error("on broker heartbeat through raft failed", e); } brokerChannelIdentityInfoMap.put(channel, brokerIdentityInfo); } @Override public void onBrokerChannelClose(Channel channel) { BrokerIdentityInfo brokerIdentityInfo = brokerChannelIdentityInfoMap.get(channel); log.info("Channel {} inactive, broker identity info: {}", channel, brokerIdentityInfo); if (brokerIdentityInfo != null) { BrokerCloseChannelRequest requestHeader = new BrokerCloseChannelRequest(brokerIdentityInfo); CompletableFuture future = controller.onBrokerCloseChannel(requestHeader); try { RemotingCommand remotingCommand = future.get(5, java.util.concurrent.TimeUnit.SECONDS); if (remotingCommand.getCode() != ResponseCode.SUCCESS) { throw new RuntimeException("on broker close channel return invalid code, code: " + remotingCommand.getCode()); } this.executor.submit(() -> notifyBrokerInActive(brokerIdentityInfo.getClusterName(), brokerIdentityInfo.getBrokerName(), brokerIdentityInfo.getBrokerId())); brokerChannelIdentityInfoMap.remove(channel); } catch (ExecutionException | InterruptedException | TimeoutException | RuntimeException e) { log.error("on broker close channel through raft failed", e); } } } /** * @param brokerIdentityInfo null means get broker live info of all brokers */ private Map getBrokerLiveInfo(BrokerIdentityInfo brokerIdentityInfo) { GetBrokerLiveInfoRequest requestHeader; if (brokerIdentityInfo == null) { requestHeader = new GetBrokerLiveInfoRequest(); } else { requestHeader = new GetBrokerLiveInfoRequest(brokerIdentityInfo); } CompletableFuture future = controller.getBrokerLiveInfo(requestHeader); try { RemotingCommand remotingCommand = future.get(5, java.util.concurrent.TimeUnit.SECONDS); if (remotingCommand.getCode() != ResponseCode.SUCCESS) { throw new RuntimeException("get broker live info return invalid code, code: " + remotingCommand.getCode()); } GetBrokerLiveInfoResponse getBrokerLiveInfoResponse = (GetBrokerLiveInfoResponse) remotingCommand.decodeCommandCustomHeader(GetBrokerLiveInfoResponse.class); return JSON.parseObject(remotingCommand.getBody(), new TypeReference>() { }.getType()); } catch (Throwable e) { log.error("get broker live info through raft failed", e); } return new HashMap<>(); } private void scanNotActiveBroker() { if (!controller.isLeaderState()) { log.info("current node is not leader, skip scan not active broker"); return; } // if has not received any heartbeat from broker, we do not need to scan if (this.firstReceivedHeartbeatTime == -1 || this.firstReceivedHeartbeatTime + controllerConfig.getJraftConfig().getjRaftScanWaitTimeoutMs() > System.currentTimeMillis()) { log.info("has not received any heartbeat from broker, skip scan not active broker"); return; } log.info("start scan not active broker"); CheckNotActiveBrokerRequest requestHeader = new CheckNotActiveBrokerRequest(); CompletableFuture future = this.controller.checkNotActiveBroker(requestHeader); try { RemotingCommand remotingCommand = future.get(5, java.util.concurrent.TimeUnit.SECONDS); if (remotingCommand.getCode() != ResponseCode.SUCCESS) { throw new RuntimeException("check not active broker return invalid code, code: " + remotingCommand.getCode()); } List notActiveAndNeedReElectBrokerIdentityInfoList = JSON.parseObject(remotingCommand.getBody(), new TypeReference>() { }.getType()); if (notActiveAndNeedReElectBrokerIdentityInfoList != null && !notActiveAndNeedReElectBrokerIdentityInfoList.isEmpty()) { notActiveAndNeedReElectBrokerIdentityInfoList.forEach(brokerIdentityInfo -> { Iterator> iterator = brokerChannelIdentityInfoMap.entrySet().iterator(); Channel channel = null; while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (entry.getValue().getBrokerId() == null) { continue; } if (entry.getValue().equals(brokerIdentityInfo)) { channel = entry.getKey(); RemotingHelper.closeChannel(entry.getKey()); iterator.remove(); break; } } this.executor.submit(() -> notifyBrokerInActive(brokerIdentityInfo.getClusterName(), brokerIdentityInfo.getBrokerName(), brokerIdentityInfo.getBrokerId())); log.warn("The broker channel {} expired, brokerInfo {}", channel, brokerIdentityInfo); }); } } catch (Throwable e) { log.error("check not active broker through raft failed", e); } } @Override public BrokerLiveInfo getBrokerLiveInfo(String clusterName, String brokerName, Long brokerId) { log.info("get broker live info, clusterName: {}, brokerName: {}, brokerId: {}", clusterName, brokerName, brokerId); BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo(clusterName, brokerName, brokerId); Map brokerLiveInfoMap = getBrokerLiveInfo(brokerIdentityInfo); return brokerLiveInfoMap.get(brokerIdentityInfo); } @Override public boolean isBrokerActive(String clusterName, String brokerName, Long brokerId) { BrokerLiveInfo info = null; try { info = getBrokerLiveInfo(clusterName, brokerName, brokerId); } catch (RuntimeException e) { log.error("get broker live info failed", e); return false; } if (info != null) { long last = info.getLastUpdateTimestamp(); long timeoutMillis = info.getHeartbeatTimeoutMillis(); return (last + timeoutMillis) >= System.currentTimeMillis(); } return false; } @Override public Map> getActiveBrokersNum() { Map> map = new HashMap<>(); Map brokerLiveInfoMap = getBrokerLiveInfo(null); brokerLiveInfoMap.keySet().stream() .filter(brokerIdentity -> this.isBrokerActive(brokerIdentity.getClusterName(), brokerIdentity.getBrokerName(), brokerIdentity.getBrokerId())) .forEach(id -> { map.computeIfAbsent(id.getClusterName(), k -> new HashMap<>()); map.get(id.getClusterName()).compute(id.getBrokerName(), (broker, num) -> num == null ? 1 : num + 1 ); }); return map; } private void notifyBrokerInActive(String clusterName, String brokerName, Long brokerId) { log.info("Broker {}-{}-{} inactive", clusterName, brokerName, brokerId); for (BrokerLifecycleListener listener : this.brokerLifecycleListeners) { listener.onBrokerInactive(clusterName, brokerName, brokerId); } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/manager/BrokerReplicaInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.manager; import java.io.Serializable; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** * Broker replicas info, mapping from brokerAddress to {brokerId, brokerHaAddress}. */ public class BrokerReplicaInfo implements Serializable { private final String clusterName; private final String brokerName; // Start from 1 private final AtomicLong nextAssignBrokerId; private final Map> brokerIdInfo; public BrokerReplicaInfo(String clusterName, String brokerName) { this.clusterName = clusterName; this.brokerName = brokerName; this.nextAssignBrokerId = new AtomicLong(MixAll.FIRST_BROKER_CONTROLLER_ID); this.brokerIdInfo = new ConcurrentHashMap<>(); } public void removeBrokerId(final Long brokerId) { this.brokerIdInfo.remove(brokerId); } public Long getNextAssignBrokerId() { return nextAssignBrokerId.get(); } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public void addBroker(final Long brokerId, final String ipAddress, final String registerCheckCode) { this.brokerIdInfo.put(brokerId, new Pair<>(ipAddress, registerCheckCode)); this.nextAssignBrokerId.incrementAndGet(); } public boolean isBrokerExist(final Long brokerId) { return this.brokerIdInfo.containsKey(brokerId); } public Set getAllBroker() { return new HashSet<>(this.brokerIdInfo.keySet()); } public Map getBrokerIdTable() { Map map = new HashMap<>(this.brokerIdInfo.size()); this.brokerIdInfo.forEach((id, pair) -> { map.put(id, pair.getObject1()); }); return map; } public String getBrokerAddress(final Long brokerId) { if (brokerId == null) { return null; } Pair pair = this.brokerIdInfo.get(brokerId); if (pair != null) { return pair.getObject1(); } return null; } public String getBrokerRegisterCheckCode(final Long brokerId) { if (brokerId == null) { return null; } Pair pair = this.brokerIdInfo.get(brokerId); if (pair != null) { return pair.getObject2(); } return null; } public void updateBrokerAddress(final Long brokerId, final String brokerAddress) { if (brokerId == null) return; Pair oldPair = this.brokerIdInfo.get(brokerId); if (oldPair != null) { this.brokerIdInfo.put(brokerId, new Pair<>(brokerAddress, oldPair.getObject2())); } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/manager/RaftReplicasInfoManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.manager; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.helper.BrokerValidPredicate; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.controller.impl.heartbeat.BrokerIdentityInfo; import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelRequest; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelResponse; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerResponse; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoResponse; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventRequest; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventResponse; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.ResponseCode; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public class RaftReplicasInfoManager extends ReplicasInfoManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private final Map brokerLiveTable = new ConcurrentHashMap<>(256); public RaftReplicasInfoManager(ControllerConfig controllerConfig) { super(controllerConfig); } public ControllerResult getBrokerLiveInfo(final GetBrokerLiveInfoRequest request) { BrokerIdentityInfo brokerIdentityInfo = request.getBrokerIdentity(); ControllerResult result = new ControllerResult<>(new GetBrokerLiveInfoResponse()); Map resBrokerLiveTable = new HashMap<>(); if (brokerIdentityInfo == null || brokerIdentityInfo.isEmpty()) { resBrokerLiveTable.putAll(this.brokerLiveTable); } else { if (brokerLiveTable.containsKey(brokerIdentityInfo)) { resBrokerLiveTable.put(brokerIdentityInfo, brokerLiveTable.get(brokerIdentityInfo)); } else { log.warn("GetBrokerLiveInfo failed, brokerIdentityInfo: {} not exist", brokerIdentityInfo); result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_LIVE_INFO_NOT_EXISTS, "brokerIdentityInfo not exist"); } } try { result.setBody(JSON.toJSONBytes(resBrokerLiveTable)); } catch (Throwable e) { log.error("json serialize resBrokerLiveTable {} error", resBrokerLiveTable, e); result.setCodeAndRemark(ResponseCode.SYSTEM_ERROR, "serialize error"); } return result; } public ControllerResult onBrokerHeartBeat( RaftBrokerHeartBeatEventRequest request) { BrokerIdentityInfo brokerIdentityInfo = request.getBrokerIdentityInfo(); BrokerLiveInfo brokerLiveInfo = request.getBrokerLiveInfo(); ControllerResult result = new ControllerResult<>(new RaftBrokerHeartBeatEventResponse()); BrokerLiveInfo prev = brokerLiveTable.computeIfAbsent(brokerIdentityInfo, identityInfo -> { log.info("new broker registered, brokerIdentityInfo: {}", identityInfo); return brokerLiveInfo; }); prev.setLastUpdateTimestamp(brokerLiveInfo.getLastUpdateTimestamp()); prev.setHeartbeatTimeoutMillis(brokerLiveInfo.getHeartbeatTimeoutMillis()); prev.setElectionPriority(brokerLiveInfo.getElectionPriority()); if (brokerLiveInfo.getEpoch() > prev.getEpoch() || brokerLiveInfo.getEpoch() == prev.getEpoch() && brokerLiveInfo.getMaxOffset() > prev.getMaxOffset()) { prev.setEpoch(brokerLiveInfo.getEpoch()); prev.setMaxOffset(brokerLiveInfo.getMaxOffset()); prev.setConfirmOffset(brokerLiveInfo.getConfirmOffset()); } return result; } public ControllerResult onBrokerCloseChannel(BrokerCloseChannelRequest request) { BrokerIdentityInfo brokerIdentityInfo = request.getBrokerIdentityInfo(); ControllerResult result = new ControllerResult<>(new BrokerCloseChannelResponse()); if (brokerIdentityInfo == null || brokerIdentityInfo.isEmpty()) { log.warn("onBrokerCloseChannel failed, brokerIdentityInfo is null"); } else { brokerLiveTable.remove(brokerIdentityInfo); log.info("onBrokerCloseChannel success, brokerIdentityInfo: {}", brokerIdentityInfo); } return result; } public ControllerResult checkNotActiveBroker(CheckNotActiveBrokerRequest request) { List notActiveBrokerIdentityInfoList = new ArrayList<>(); long checkTime = request.getCheckTimeMillis(); final Iterator> iterator = this.brokerLiveTable.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry next = iterator.next(); long last = next.getValue().getLastUpdateTimestamp(); long timeoutMillis = next.getValue().getHeartbeatTimeoutMillis(); if (checkTime - last > timeoutMillis) { notActiveBrokerIdentityInfoList.add(next.getKey()); iterator.remove(); log.warn("Broker expired, brokerInfo {}, expired {}ms", next.getKey(), timeoutMillis); } } List needReElectBrokerNames = scanNeedReelectBrokerSets(new BrokerValidPredicate() { @Override public boolean check(String clusterName, String brokerName, Long brokerId) { return !isBrokerActive(clusterName, brokerName, brokerId, checkTime); } }); Set alreadyReportedBrokerName = notActiveBrokerIdentityInfoList.stream() .map(BrokerIdentityInfo::getBrokerName) .collect(Collectors.toSet()); // avoid to duplicate report, filter by name, // because BrokerIdentityInfo in needReElectBrokerNames does not have brokerId or clusterName notActiveBrokerIdentityInfoList.addAll(needReElectBrokerNames.stream() .filter(brokerName -> !alreadyReportedBrokerName.contains(brokerName)) .map(brokerName -> new BrokerIdentityInfo(null, brokerName, null)) .collect(Collectors.toList())); ControllerResult result = new ControllerResult<>(new CheckNotActiveBrokerResponse()); try { result.setBody(JSON.toJSONBytes(notActiveBrokerIdentityInfoList)); } catch (Throwable e) { log.error("json serialize notActiveBrokerIdentityInfoList {} error", notActiveBrokerIdentityInfoList, e); result.setCodeAndRemark(ResponseCode.SYSTEM_ERROR, "serialize error"); } return result; } public boolean isBrokerActive(String clusterName, String brokerName, Long brokerId, long invokeTime) { final BrokerLiveInfo info = this.brokerLiveTable.get(new BrokerIdentityInfo(clusterName, brokerName, brokerId)); if (info != null) { long last = info.getLastUpdateTimestamp(); long timeoutMillis = info.getHeartbeatTimeoutMillis(); return (last + timeoutMillis) >= invokeTime; } return false; } public BrokerLiveInfo getBrokerLiveInfo(String clusterName, String brokerName, Long brokerId) { return this.brokerLiveTable.get(new BrokerIdentityInfo(clusterName, brokerName, brokerId)); } @Override public byte[] serialize() throws Throwable { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { final byte[] superSerialize = super.serialize(); putInt(outputStream, superSerialize.length); outputStream.write(superSerialize); putInt(outputStream, this.brokerLiveTable.size()); for (Map.Entry entry : brokerLiveTable.entrySet()) { final byte[] brokerIdentityInfo = hessianSerialize(entry.getKey()); final byte[] brokerLiveInfo = hessianSerialize(entry.getValue()); putInt(outputStream, brokerIdentityInfo.length); outputStream.write(brokerIdentityInfo); putInt(outputStream, brokerLiveInfo.length); outputStream.write(brokerLiveInfo); } return outputStream.toByteArray(); } catch (Throwable e) { log.error("serialize replicaInfoTable or syncStateSetInfoTable error", e); throw e; } } @Override public void deserializeFrom(byte[] data) throws Throwable { int index = 0; this.brokerLiveTable.clear(); try { int superTableSize = getInt(data, index); index += 4; byte[] superTableData = new byte[superTableSize]; System.arraycopy(data, index, superTableData, 0, superTableSize); super.deserializeFrom(superTableData); index += superTableSize; int brokerLiveTableSize = getInt(data, index); index += 4; for (int i = 0; i < brokerLiveTableSize; i++) { int brokerIdentityInfoLength = getInt(data, index); index += 4; byte[] brokerIdentityInfoArray = new byte[brokerIdentityInfoLength]; System.arraycopy(data, index, brokerIdentityInfoArray, 0, brokerIdentityInfoLength); BrokerIdentityInfo brokerIdentityInfo = (BrokerIdentityInfo) hessianDeserialize(brokerIdentityInfoArray); index += brokerIdentityInfoLength; int brokerLiveInfoLength = getInt(data, index); index += 4; byte[] brokerLiveInfoArray = new byte[brokerLiveInfoLength]; System.arraycopy(data, index, brokerLiveInfoArray, 0, brokerLiveInfoLength); BrokerLiveInfo brokerLiveInfo = (BrokerLiveInfo) hessianDeserialize(brokerLiveInfoArray); index += brokerLiveInfoLength; this.brokerLiveTable.put(brokerIdentityInfo, brokerLiveInfo); } } catch (Throwable e) { log.error("deserialize replicaInfoTable or syncStateSetInfoTable error", e); throw e; } } public static class BrokerValidPredicateWithInvokeTime implements BrokerValidPredicate { private final long invokeTime; private final RaftReplicasInfoManager raftBrokerHeartBeatManager; public BrokerValidPredicateWithInvokeTime(long invokeTime, RaftReplicasInfoManager raftBrokerHeartBeatManager) { this.invokeTime = invokeTime; this.raftBrokerHeartBeatManager = raftBrokerHeartBeatManager; } @Override public boolean check(String clusterName, String brokerName, Long brokerId) { return raftBrokerHeartBeatManager.isBrokerActive(clusterName, brokerName, brokerId, invokeTime); } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.manager; import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.caucho.hessian.io.SerializerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.elect.ElectPolicy; import org.apache.rocketmq.controller.helper.BrokerValidPredicate; import org.apache.rocketmq.controller.impl.event.AlterSyncStateSetEvent; import org.apache.rocketmq.controller.impl.event.ApplyBrokerIdEvent; import org.apache.rocketmq.controller.impl.event.CleanBrokerDataEvent; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.controller.impl.event.ElectMasterEvent; import org.apache.rocketmq.controller.impl.event.EventMessage; import org.apache.rocketmq.controller.impl.event.EventType; import org.apache.rocketmq.controller.impl.event.UpdateBrokerAddressEvent; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo; import org.apache.rocketmq.remoting.protocol.body.ElectMasterResponseBody; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; /** * The manager that manages the replicas info for all brokers. We can think of this class as the controller's memory * state machine. If the upper layer want to update the statemachine, it must sequentially call its methods. */ public class ReplicasInfoManager { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); protected static final SerializerFactory SERIALIZER_FACTORY = new SerializerFactory(); protected final ControllerConfig controllerConfig; private final Map replicaInfoTable; private final Map syncStateSetInfoTable; protected static byte[] hessianSerialize(Object object) throws IOException { try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { Hessian2Output hessianOut = new Hessian2Output(bout); hessianOut.setSerializerFactory(SERIALIZER_FACTORY); hessianOut.writeObject(object); hessianOut.close(); return bout.toByteArray(); } } protected static Object hessianDeserialize(byte[] data) throws IOException { try (ByteArrayInputStream bin = new ByteArrayInputStream(data, 0, data.length)) { Hessian2Input hin = new Hessian2Input(bin); hin.setSerializerFactory(new SerializerFactory()); Object o = hin.readObject(); hin.close(); return o; } } public ReplicasInfoManager(final ControllerConfig config) { this.controllerConfig = config; this.replicaInfoTable = new ConcurrentHashMap(); this.syncStateSetInfoTable = new ConcurrentHashMap(); } public ControllerResult alterSyncStateSet( final AlterSyncStateSetRequestHeader request, final SyncStateSet syncStateSet, final BrokerValidPredicate brokerAlivePredicate) { final String brokerName = request.getBrokerName(); final ControllerResult result = new ControllerResult<>(new AlterSyncStateSetResponseHeader()); final AlterSyncStateSetResponseHeader response = result.getResponse(); if (!isContainsBroker(brokerName)) { result.setCodeAndRemark(ResponseCode.CONTROLLER_ALTER_SYNC_STATE_SET_FAILED, "Broker metadata is not existed"); return result; } final Set newSyncStateSet = syncStateSet.getSyncStateSet(); final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); // Check whether the oldSyncStateSet is equal with newSyncStateSet final Set oldSyncStateSet = syncStateInfo.getSyncStateSet(); if (oldSyncStateSet.size() == newSyncStateSet.size() && oldSyncStateSet.containsAll(newSyncStateSet)) { String err = "The newSyncStateSet is equal with oldSyncStateSet, no needed to update syncStateSet"; LOGGER.warn("{}", err); result.setCodeAndRemark(ResponseCode.CONTROLLER_ALTER_SYNC_STATE_SET_FAILED, err); return result; } // Check master if (syncStateInfo.getMasterBrokerId() == null || !syncStateInfo.getMasterBrokerId().equals(request.getMasterBrokerId())) { String err = String.format("Rejecting alter syncStateSet request because the current leader is:{%s}, not {%s}", syncStateInfo.getMasterBrokerId(), request.getMasterBrokerId()); LOGGER.error("{}", err); result.setCodeAndRemark(ResponseCode.CONTROLLER_INVALID_MASTER, err); return result; } // Check master epoch if (request.getMasterEpoch() != syncStateInfo.getMasterEpoch()) { String err = String.format("Rejecting alter syncStateSet request because the current master epoch is:{%d}, not {%d}", syncStateInfo.getMasterEpoch(), request.getMasterEpoch()); LOGGER.error("{}", err); result.setCodeAndRemark(ResponseCode.CONTROLLER_FENCED_MASTER_EPOCH, err); return result; } // Check syncStateSet epoch if (syncStateSet.getSyncStateSetEpoch() != syncStateInfo.getSyncStateSetEpoch()) { String err = String.format("Rejecting alter syncStateSet request because the current syncStateSet epoch is:{%d}, not {%d}", syncStateInfo.getSyncStateSetEpoch(), syncStateSet.getSyncStateSetEpoch()); LOGGER.error("{}", err); result.setCodeAndRemark(ResponseCode.CONTROLLER_FENCED_SYNC_STATE_SET_EPOCH, err); return result; } // Check newSyncStateSet correctness for (Long replica : newSyncStateSet) { if (!brokerReplicaInfo.isBrokerExist(replica)) { String err = String.format("Rejecting alter syncStateSet request because the replicas {%s} don't exist", replica); LOGGER.error("{}", err); result.setCodeAndRemark(ResponseCode.CONTROLLER_INVALID_REPLICAS, err); return result; } if (!brokerAlivePredicate.check(brokerReplicaInfo.getClusterName(), brokerReplicaInfo.getBrokerName(), replica)) { String err = String.format("Rejecting alter syncStateSet request because the replicas {%s} don't alive", replica); LOGGER.error(err); result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_NOT_ALIVE, err); return result; } } if (!newSyncStateSet.contains(syncStateInfo.getMasterBrokerId())) { String err = String.format("Rejecting alter syncStateSet request because the newSyncStateSet don't contains origin leader {%s}", syncStateInfo.getMasterBrokerId()); LOGGER.error(err); result.setCodeAndRemark(ResponseCode.CONTROLLER_ALTER_SYNC_STATE_SET_FAILED, err); return result; } // Generate event int epoch = syncStateInfo.getSyncStateSetEpoch() + 1; response.setNewSyncStateSetEpoch(epoch); result.setBody(new SyncStateSet(newSyncStateSet, epoch).encode()); final AlterSyncStateSetEvent event = new AlterSyncStateSetEvent(brokerName, newSyncStateSet); result.addEvent(event); return result; } public ControllerResult electMaster(final ElectMasterRequestHeader request, final ElectPolicy electPolicy) { final String brokerName = request.getBrokerName(); final Long brokerId = request.getBrokerId(); final ControllerResult result = new ControllerResult<>(new ElectMasterResponseHeader()); final ElectMasterResponseHeader response = result.getResponse(); if (!isContainsBroker(brokerName)) { // this broker set hasn't been registered result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_NEED_TO_BE_REGISTERED, "Broker hasn't been registered"); return result; } final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final Set syncStateSet = syncStateInfo.getSyncStateSet(); final Long oldMaster = syncStateInfo.getMasterBrokerId(); Set allReplicaBrokers = controllerConfig.isEnableElectUncleanMaster() ? brokerReplicaInfo.getAllBroker() : null; Long newMaster = null; if (syncStateInfo.isFirstTimeForElect()) { // If never have a master in this broker set, in other words, it is the first time to elect a master // elect it as the first master newMaster = brokerId; } // elect by policy if (newMaster == null || newMaster == -1) { // we should assign this assignedBrokerId when the brokerAddress need to be elected by force Long assignedBrokerId = request.getDesignateElect() ? brokerId : null; newMaster = electPolicy.elect(brokerReplicaInfo.getClusterName(), brokerReplicaInfo.getBrokerName(), syncStateSet, allReplicaBrokers, oldMaster, assignedBrokerId); } if (newMaster != null && newMaster.equals(oldMaster)) { // old master still valid, change nothing String err = String.format("The old master %s is still alive, not need to elect new master for broker %s", oldMaster, brokerReplicaInfo.getBrokerName()); LOGGER.warn("{}", err); // the master still exist response.setMasterEpoch(syncStateInfo.getMasterEpoch()); response.setSyncStateSetEpoch(syncStateInfo.getSyncStateSetEpoch()); response.setMasterBrokerId(oldMaster); response.setMasterAddress(brokerReplicaInfo.getBrokerAddress(oldMaster)); result.setBody(new ElectMasterResponseBody(syncStateSet).encode()); result.setCodeAndRemark(ResponseCode.CONTROLLER_MASTER_STILL_EXIST, err); return result; } // a new master is elected if (newMaster != null) { final int masterEpoch = syncStateInfo.getMasterEpoch(); final int syncStateSetEpoch = syncStateInfo.getSyncStateSetEpoch(); final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(newMaster); response.setMasterBrokerId(newMaster); response.setMasterAddress(brokerReplicaInfo.getBrokerAddress(newMaster)); response.setMasterEpoch(masterEpoch + 1); response.setSyncStateSetEpoch(syncStateSetEpoch + 1); ElectMasterResponseBody responseBody = new ElectMasterResponseBody(newSyncStateSet); BrokerMemberGroup brokerMemberGroup = buildBrokerMemberGroup(brokerReplicaInfo); if (null != brokerMemberGroup) { responseBody.setBrokerMemberGroup(brokerMemberGroup); } result.setBody(responseBody.encode()); final ElectMasterEvent event = new ElectMasterEvent(brokerName, newMaster); result.addEvent(event); LOGGER.info("Elect new master {} for broker {}", newMaster, brokerName); return result; } // If elect failed and the electMaster is triggered by controller (we can figure it out by brokerAddress), // we still need to apply an ElectMasterEvent to tell the statemachine // that the master was shutdown and no new master was elected. if (request.getBrokerId() == null || request.getBrokerId() == -1) { final ElectMasterEvent event = new ElectMasterEvent(false, brokerName); result.addEvent(event); result.setCodeAndRemark(ResponseCode.CONTROLLER_MASTER_NOT_AVAILABLE, "Old master has down and failed to elect a new broker master"); } else { result.setCodeAndRemark(ResponseCode.CONTROLLER_ELECT_MASTER_FAILED, "Failed to elect a new master"); } LOGGER.warn("Failed to elect a new master for broker {}", brokerName); return result; } private BrokerMemberGroup buildBrokerMemberGroup(final BrokerReplicaInfo brokerReplicaInfo) { if (brokerReplicaInfo != null) { final BrokerMemberGroup group = new BrokerMemberGroup(brokerReplicaInfo.getClusterName(), brokerReplicaInfo.getBrokerName()); final Map brokerIdTable = brokerReplicaInfo.getBrokerIdTable(); final Map memberGroup = new HashMap<>(); brokerIdTable.forEach((id, addr) -> memberGroup.put(id, addr)); group.setBrokerAddrs(memberGroup); return group; } return null; } public ControllerResult getNextBrokerId(final GetNextBrokerIdRequestHeader request) { final String clusterName = request.getClusterName(); final String brokerName = request.getBrokerName(); BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final ControllerResult result = new ControllerResult<>(new GetNextBrokerIdResponseHeader(clusterName, brokerName)); final GetNextBrokerIdResponseHeader response = result.getResponse(); if (brokerReplicaInfo == null) { // means that none of brokers in this broker-set are registered response.setNextBrokerId(MixAll.FIRST_BROKER_CONTROLLER_ID); } else { response.setNextBrokerId(brokerReplicaInfo.getNextAssignBrokerId()); } return result; } public ControllerResult applyBrokerId(final ApplyBrokerIdRequestHeader request) { final String clusterName = request.getClusterName(); final String brokerName = request.getBrokerName(); final Long brokerId = request.getAppliedBrokerId(); final String registerCheckCode = request.getRegisterCheckCode(); final String brokerAddress = registerCheckCode.split(";")[0]; BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final ControllerResult result = new ControllerResult<>(new ApplyBrokerIdResponseHeader(clusterName, brokerName)); final ApplyBrokerIdEvent event = new ApplyBrokerIdEvent(clusterName, brokerName, brokerAddress, brokerId, registerCheckCode); // broker-set unregistered if (brokerReplicaInfo == null) { // first brokerId if (brokerId == MixAll.FIRST_BROKER_CONTROLLER_ID) { result.addEvent(event); } else { result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_ID_INVALID, String.format("Broker-set: %s hasn't been registered in controller, but broker try to apply brokerId: %d", brokerName, brokerId)); } return result; } // broker-set registered if (!brokerReplicaInfo.isBrokerExist(brokerId) || registerCheckCode.equals(brokerReplicaInfo.getBrokerRegisterCheckCode(brokerId))) { // if brokerId hasn't been assigned or brokerId was assigned to this broker result.addEvent(event); return result; } result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_ID_INVALID, String.format("Fail to apply brokerId: %d in broker-set: %s", brokerId, brokerName)); return result; } public ControllerResult registerBroker( final RegisterBrokerToControllerRequestHeader request, final BrokerValidPredicate alivePredicate) { final String brokerAddress = request.getBrokerAddress(); final String brokerName = request.getBrokerName(); final String clusterName = request.getClusterName(); final Long brokerId = request.getBrokerId(); final ControllerResult result = new ControllerResult<>(new RegisterBrokerToControllerResponseHeader(clusterName, brokerName)); final RegisterBrokerToControllerResponseHeader response = result.getResponse(); if (!isContainsBroker(brokerName)) { result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_NEED_TO_BE_REGISTERED, String.format("Broker-set: %s hasn't been registered in controller", brokerName)); return result; } final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); if (!brokerReplicaInfo.isBrokerExist(brokerId)) { result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_NEED_TO_BE_REGISTERED, String.format("BrokerId: %d hasn't been registered in broker-set: %s", brokerId, brokerName)); return result; } if (syncStateInfo.isMasterExist() && alivePredicate.check(clusterName, brokerName, syncStateInfo.getMasterBrokerId())) { // if master still exist response.setMasterBrokerId(syncStateInfo.getMasterBrokerId()); response.setMasterAddress(brokerReplicaInfo.getBrokerAddress(response.getMasterBrokerId())); response.setMasterEpoch(syncStateInfo.getMasterEpoch()); response.setSyncStateSetEpoch(syncStateInfo.getSyncStateSetEpoch()); } result.setBody(new SyncStateSet(syncStateInfo.getSyncStateSet(), syncStateInfo.getSyncStateSetEpoch()).encode()); // if this broker's address has been changed, we need to update it if (!brokerAddress.equals(brokerReplicaInfo.getBrokerAddress(brokerId))) { final UpdateBrokerAddressEvent event = new UpdateBrokerAddressEvent(clusterName, brokerName, brokerAddress, brokerId); result.addEvent(event); } return result; } public ControllerResult getReplicaInfo(final GetReplicaInfoRequestHeader request) { final String brokerName = request.getBrokerName(); final ControllerResult result = new ControllerResult<>(new GetReplicaInfoResponseHeader()); final GetReplicaInfoResponseHeader response = result.getResponse(); if (isContainsBroker(brokerName)) { // If exist broker metadata, just return metadata final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final Long masterBrokerId = syncStateInfo.getMasterBrokerId(); response.setMasterBrokerId(masterBrokerId); response.setMasterAddress(brokerReplicaInfo.getBrokerAddress(masterBrokerId)); response.setMasterEpoch(syncStateInfo.getMasterEpoch()); result.setBody(new SyncStateSet(syncStateInfo.getSyncStateSet(), syncStateInfo.getSyncStateSetEpoch()).encode()); return result; } result.setCodeAndRemark(ResponseCode.CONTROLLER_BROKER_METADATA_NOT_EXIST, "Broker metadata is not existed"); return result; } public ControllerResult getSyncStateData(final List brokerNames, final BrokerValidPredicate brokerAlivePredicate) { final ControllerResult result = new ControllerResult<>(); final BrokerReplicasInfo brokerReplicasInfo = new BrokerReplicasInfo(); for (String brokerName : brokerNames) { if (isContainsBroker(brokerName)) { // If exist broker metadata, just return metadata final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final Set syncStateSet = syncStateInfo.getSyncStateSet(); final Long masterBrokerId = syncStateInfo.getMasterBrokerId(); final ArrayList inSyncReplicas = new ArrayList<>(); final ArrayList notInSyncReplicas = new ArrayList<>(); if (brokerReplicaInfo == null) { continue; } brokerReplicaInfo.getBrokerIdTable().forEach((brokerId, brokerAddress) -> { Boolean isAlive = brokerAlivePredicate.check(brokerReplicaInfo.getClusterName(), brokerName, brokerId); BrokerReplicasInfo.ReplicaIdentity replica = new BrokerReplicasInfo.ReplicaIdentity(brokerName, brokerId, brokerAddress); replica.setAlive(isAlive); if (syncStateSet.contains(brokerId)) { inSyncReplicas.add(replica); } else { notInSyncReplicas.add(replica); } }); final BrokerReplicasInfo.ReplicasInfo inSyncState = new BrokerReplicasInfo.ReplicasInfo(masterBrokerId, brokerReplicaInfo.getBrokerAddress(masterBrokerId), syncStateInfo.getMasterEpoch(), syncStateInfo.getSyncStateSetEpoch(), inSyncReplicas, notInSyncReplicas); brokerReplicasInfo.addReplicaInfo(brokerName, inSyncState); } } result.setBody(brokerReplicasInfo.encode()); return result; } public ControllerResult cleanBrokerData(final CleanControllerBrokerDataRequestHeader requestHeader, final BrokerValidPredicate validPredicate) { final ControllerResult result = new ControllerResult<>(); final String clusterName = requestHeader.getClusterName(); final String brokerName = requestHeader.getBrokerName(); final String brokerControllerIdsToClean = requestHeader.getBrokerControllerIdsToClean(); Set brokerIdSet = null; if (!requestHeader.isCleanLivingBroker()) { //if SyncStateInfo.masterAddress is not empty, at least one broker with the same BrokerName is alive SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); if (StringUtils.isBlank(brokerControllerIdsToClean) && null != syncStateInfo && syncStateInfo.getMasterBrokerId() != null) { String remark = String.format("Broker %s is still alive, clean up failure", requestHeader.getBrokerName()); result.setCodeAndRemark(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, remark); return result; } if (StringUtils.isNotBlank(brokerControllerIdsToClean)) { try { brokerIdSet = Stream.of(brokerControllerIdsToClean.split(";")).map(idStr -> Long.valueOf(idStr)).collect(Collectors.toSet()); } catch (NumberFormatException numberFormatException) { String remark = String.format("Please set the option according to the format, exception: %s", numberFormatException); result.setCodeAndRemark(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, remark); return result; } for (Long brokerId : brokerIdSet) { if (validPredicate.check(clusterName, brokerName, brokerId)) { String remark = String.format("Broker [%s, %s] is still alive, clean up failure", requestHeader.getBrokerName(), brokerId); result.setCodeAndRemark(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, remark); return result; } } } } if (isContainsBroker(brokerName)) { final CleanBrokerDataEvent event = new CleanBrokerDataEvent(brokerName, brokerIdSet); result.addEvent(event); return result; } result.setCodeAndRemark(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, String.format("Broker %s is not existed,clean broker data failure.", brokerName)); return result; } public List scanNeedReelectBrokerSets(final BrokerValidPredicate validPredicate) { List needReelectBrokerSets = new LinkedList<>(); this.syncStateSetInfoTable.forEach((brokerName, syncStateInfo) -> { Long masterBrokerId = syncStateInfo.getMasterBrokerId(); String clusterName = syncStateInfo.getClusterName(); // Now master is inactive if (masterBrokerId != null && !validPredicate.check(clusterName, brokerName, masterBrokerId)) { // Still at least one broker alive Set brokerIds = this.replicaInfoTable.get(brokerName).getBrokerIdTable().keySet(); boolean alive = brokerIds.stream().anyMatch(id -> validPredicate.check(clusterName, brokerName, id)); if (alive) { needReelectBrokerSets.add(brokerName); } } }); return needReelectBrokerSets; } /** * Apply events to memory statemachine. * * @param event event message */ public void applyEvent(final EventMessage event) { final EventType type = event.getEventType(); switch (type) { case ALTER_SYNC_STATE_SET_EVENT: handleAlterSyncStateSet((AlterSyncStateSetEvent) event); break; case APPLY_BROKER_ID_EVENT: handleApplyBrokerId((ApplyBrokerIdEvent) event); break; case ELECT_MASTER_EVENT: handleElectMaster((ElectMasterEvent) event); break; case CLEAN_BROKER_DATA_EVENT: handleCleanBrokerDataEvent((CleanBrokerDataEvent) event); break; case UPDATE_BROKER_ADDRESS: handleUpdateBrokerAddress((UpdateBrokerAddressEvent) event); break; default: break; } } private void handleAlterSyncStateSet(final AlterSyncStateSetEvent event) { final String brokerName = event.getBrokerName(); if (isContainsBroker(brokerName)) { final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); syncStateInfo.updateSyncStateSetInfo(event.getNewSyncStateSet()); } } private void handleApplyBrokerId(final ApplyBrokerIdEvent event) { final String brokerName = event.getBrokerName(); if (isContainsBroker(brokerName)) { final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); if (!brokerReplicaInfo.isBrokerExist(event.getNewBrokerId())) { brokerReplicaInfo.addBroker(event.getNewBrokerId(), event.getBrokerAddress(), event.getRegisterCheckCode()); } } else { // First time to register in this broker set // Initialize the replicaInfo about this broker set final String clusterName = event.getClusterName(); final BrokerReplicaInfo brokerReplicaInfo = new BrokerReplicaInfo(clusterName, brokerName); brokerReplicaInfo.addBroker(event.getNewBrokerId(), event.getBrokerAddress(), event.getRegisterCheckCode()); this.replicaInfoTable.put(brokerName, brokerReplicaInfo); final SyncStateInfo syncStateInfo = new SyncStateInfo(clusterName, brokerName); // Initialize an empty syncStateInfo for this broker set this.syncStateSetInfoTable.put(brokerName, syncStateInfo); } } private void handleUpdateBrokerAddress(final UpdateBrokerAddressEvent event) { final String brokerName = event.getBrokerName(); final String brokerAddress = event.getBrokerAddress(); final Long brokerId = event.getBrokerId(); BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); brokerReplicaInfo.updateBrokerAddress(brokerId, brokerAddress); } private void handleElectMaster(final ElectMasterEvent event) { final String brokerName = event.getBrokerName(); final Long newMaster = event.getNewMasterBrokerId(); if (isContainsBroker(brokerName)) { final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); if (event.getNewMasterElected()) { // Record new master syncStateInfo.updateMasterInfo(newMaster); // Record new newSyncStateSet list final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(newMaster); syncStateInfo.updateSyncStateSetInfo(newSyncStateSet); } else { // If new master was not elected, which means old master was shutdown and the newSyncStateSet list had no more replicas // So we should delete old master, but retain newSyncStateSet list. syncStateInfo.updateMasterInfo(null); } return; } LOGGER.error("Receive an ElectMasterEvent which contains the un-registered broker, event = {}", event); } private void handleCleanBrokerDataEvent(final CleanBrokerDataEvent event) { final String brokerName = event.getBrokerName(); final Set brokerIdSetToClean = event.getBrokerIdSetToClean(); if (null == brokerIdSetToClean || brokerIdSetToClean.isEmpty()) { this.replicaInfoTable.remove(brokerName); this.syncStateSetInfoTable.remove(brokerName); return; } if (!isContainsBroker(brokerName)) { return; } final BrokerReplicaInfo brokerReplicaInfo = this.replicaInfoTable.get(brokerName); final SyncStateInfo syncStateInfo = this.syncStateSetInfoTable.get(brokerName); for (Long brokerId : brokerIdSetToClean) { brokerReplicaInfo.removeBrokerId(brokerId); syncStateInfo.removeFromSyncState(brokerId); } if (brokerReplicaInfo.getBrokerIdTable().isEmpty()) { this.replicaInfoTable.remove(brokerName); } if (syncStateInfo.getSyncStateSet().isEmpty()) { this.syncStateSetInfoTable.remove(brokerName); } } /** * Is the broker existed in the memory metadata * * @return true if both existed in replicaInfoTable and inSyncReplicasInfoTable */ private boolean isContainsBroker(final String brokerName) { return this.replicaInfoTable.containsKey(brokerName) && this.syncStateSetInfoTable.containsKey(brokerName); } protected void putInt(ByteArrayOutputStream outputStream, int value) { outputStream.write((byte) (value >>> 24)); outputStream.write((byte) (value >>> 16)); outputStream.write((byte) (value >>> 8)); outputStream.write((byte) value); } protected int getInt(byte[] memory, int index) { return memory[index] << 24 | (memory[index + 1] & 0xFF) << 16 | (memory[index + 2] & 0xFF) << 8 | memory[index + 3] & 0xFF; } public byte[] serialize() throws Throwable { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { putInt(outputStream, this.replicaInfoTable.size()); for (Map.Entry entry : replicaInfoTable.entrySet()) { final byte[] brokerName = entry.getKey().getBytes(StandardCharsets.UTF_8); byte[] brokerReplicaInfo = hessianSerialize(entry.getValue()); putInt(outputStream, brokerName.length); outputStream.write(brokerName); putInt(outputStream, brokerReplicaInfo.length); outputStream.write(brokerReplicaInfo); } putInt(outputStream, this.syncStateSetInfoTable.size()); for (Map.Entry entry : syncStateSetInfoTable.entrySet()) { final byte[] brokerName = entry.getKey().getBytes(StandardCharsets.UTF_8); byte[] syncStateInfo = hessianSerialize(entry.getValue()); putInt(outputStream, brokerName.length); outputStream.write(brokerName); putInt(outputStream, syncStateInfo.length); outputStream.write(syncStateInfo); } return outputStream.toByteArray(); } catch (Throwable e) { LOGGER.error("serialize replicaInfoTable or syncStateSetInfoTable error", e); throw e; } } public void deserializeFrom(byte[] data) throws Throwable { int index = 0; this.replicaInfoTable.clear(); this.syncStateSetInfoTable.clear(); try { int replicaInfoTableSize = getInt(data, index); index += 4; for (int i = 0; i < replicaInfoTableSize; i++) { int brokerNameLength = getInt(data, index); index += 4; String brokerName = new String(data, index, brokerNameLength, StandardCharsets.UTF_8); index += brokerNameLength; int brokerReplicaInfoLength = getInt(data, index); index += 4; byte[] brokerReplicaInfoArray = new byte[brokerReplicaInfoLength]; System.arraycopy(data, index, brokerReplicaInfoArray, 0, brokerReplicaInfoLength); BrokerReplicaInfo brokerReplicaInfo = (BrokerReplicaInfo) hessianDeserialize(brokerReplicaInfoArray); index += brokerReplicaInfoLength; this.replicaInfoTable.put(brokerName, brokerReplicaInfo); } int syncStateSetInfoTableSize = getInt(data, index); index += 4; for (int i = 0; i < syncStateSetInfoTableSize; i++) { int brokerNameLength = getInt(data, index); index += 4; String brokerName = new String(data, index, brokerNameLength, StandardCharsets.UTF_8); index += brokerNameLength; int syncStateInfoLength = getInt(data, index); index += 4; byte[] syncStateInfoArray = new byte[syncStateInfoLength]; System.arraycopy(data, index, syncStateInfoArray, 0, syncStateInfoLength); SyncStateInfo syncStateInfo = (SyncStateInfo) hessianDeserialize(syncStateInfoArray); index += syncStateInfoLength; this.syncStateSetInfoTable.put(brokerName, syncStateInfo); } } catch (Throwable e) { LOGGER.error("deserialize replicaInfoTable or syncStateSetInfoTable error", e); throw e; } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/manager/SyncStateInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.manager; import java.io.Serializable; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Manages the syncStateSet of broker replicas. */ public class SyncStateInfo implements Serializable { private final String clusterName; private final String brokerName; private final AtomicInteger masterEpoch; private final AtomicInteger syncStateSetEpoch; private Set syncStateSet; private Long masterBrokerId; public SyncStateInfo(String clusterName, String brokerName) { this.clusterName = clusterName; this.brokerName = brokerName; this.masterEpoch = new AtomicInteger(0); this.syncStateSetEpoch = new AtomicInteger(0); this.syncStateSet = Collections.emptySet(); } public void updateMasterInfo(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; this.masterEpoch.incrementAndGet(); } public void updateSyncStateSetInfo(Set newSyncStateSet) { this.syncStateSet = new HashSet<>(newSyncStateSet); this.syncStateSetEpoch.incrementAndGet(); } public boolean isFirstTimeForElect() { return this.masterEpoch.get() == 0; } public boolean isMasterExist() { return masterBrokerId != null; } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public Set getSyncStateSet() { return new HashSet<>(syncStateSet); } public int getSyncStateSetEpoch() { return syncStateSetEpoch.get(); } public Long getMasterBrokerId() { return masterBrokerId; } public int getMasterEpoch() { return masterEpoch.get(); } public void removeFromSyncState(final Long brokerId) { syncStateSet.remove(brokerId); } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/BrokerCloseChannelRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.controller.impl.heartbeat.BrokerIdentityInfo; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class BrokerCloseChannelRequest implements CommandCustomHeader { @CFNullable private String clusterName; @CFNullable private String brokerName; @CFNullable private Long brokerId; public BrokerCloseChannelRequest() { this.clusterName = null; this.brokerName = null; this.brokerId = null; } public BrokerCloseChannelRequest(BrokerIdentityInfo brokerIdentityInfo) { this.clusterName = brokerIdentityInfo.getClusterName(); this.brokerName = brokerIdentityInfo.getBrokerName(); this.brokerId = brokerIdentityInfo.getBrokerId(); } public BrokerIdentityInfo getBrokerIdentityInfo() { return new BrokerIdentityInfo(this.clusterName, this.brokerName, this.brokerId); } public void setBrokerIdentityInfo(BrokerIdentityInfo brokerIdentityInfo) { this.clusterName = brokerIdentityInfo.getClusterName(); this.brokerName = brokerIdentityInfo.getBrokerName(); this.brokerId = brokerIdentityInfo.getBrokerId(); } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "BrokerCloseChannelRequest{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/BrokerCloseChannelResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class BrokerCloseChannelResponse implements CommandCustomHeader { public BrokerCloseChannelResponse() { } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "BrokerCloseChannelResponse{}"; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/CheckNotActiveBrokerRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class CheckNotActiveBrokerRequest implements CommandCustomHeader { private final Long checkTimeMillis = System.currentTimeMillis(); public CheckNotActiveBrokerRequest() { } public Long getCheckTimeMillis() { return checkTimeMillis; } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "CheckNotActiveBrokerRequest{" + "checkTimeMillis=" + checkTimeMillis + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/CheckNotActiveBrokerResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class CheckNotActiveBrokerResponse implements CommandCustomHeader { public CheckNotActiveBrokerResponse() { } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "CheckNotActiveBrokerResponse{}"; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/GetBrokerLiveInfoRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.controller.impl.heartbeat.BrokerIdentityInfo; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetBrokerLiveInfoRequest implements CommandCustomHeader { private String clusterName; private String brokerName; private Long brokerId; public GetBrokerLiveInfoRequest() { this.clusterName = null; this.brokerName = null; this.brokerId = null; } /** * @param brokerIdentity The BrokerIdentityInfo that needs to be queried, if it is null, it means obtaining BrokerLiveInfo for all brokers */ public GetBrokerLiveInfoRequest(BrokerIdentityInfo brokerIdentity) { this.clusterName = brokerIdentity.getClusterName(); this.brokerName = brokerIdentity.getBrokerName(); this.brokerId = brokerIdentity.getBrokerId(); } public BrokerIdentityInfo getBrokerIdentity() { return new BrokerIdentityInfo(this.clusterName, this.brokerName, this.brokerId); } public void setBrokerIdentity(BrokerIdentityInfo brokerIdentity) { this.clusterName = brokerIdentity.getClusterName(); this.brokerName = brokerIdentity.getBrokerName(); this.brokerId = brokerIdentity.getBrokerId(); } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "GetBrokerLiveInfoRequest{" + "brokerIdentity=" + getBrokerIdentity() + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/GetBrokerLiveInfoResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetBrokerLiveInfoResponse implements CommandCustomHeader { public GetBrokerLiveInfoResponse() { } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "GetBrokerLiveInfoResponse{}"; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/GetSyncStateDataRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetSyncStateDataRequest implements CommandCustomHeader { private final Long invokeTime = System.currentTimeMillis(); @Override public void checkFields() throws RemotingCommandException { } public GetSyncStateDataRequest() { } public Long getInvokeTime() { return invokeTime; } @Override public String toString() { return "GetSyncStateDataRequest{" + "invokeTime=" + invokeTime + '}'; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/RaftBrokerHeartBeatEventRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.controller.impl.heartbeat.BrokerIdentityInfo; import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RaftBrokerHeartBeatEventRequest implements CommandCustomHeader { // brokerIdentityInfo private String clusterNameIdentityInfo; private String brokerNameIdentityInfo; private Long brokerIdIdentityInfo; // brokerLiveInfo private String brokerName; private String brokerAddr; private Long heartbeatTimeoutMillis; private Long brokerId; private Long lastUpdateTimestamp; private Integer epoch; private Long maxOffset; private Long confirmOffset; private Integer electionPriority; public RaftBrokerHeartBeatEventRequest() { } public RaftBrokerHeartBeatEventRequest(BrokerIdentityInfo brokerIdentityInfo, BrokerLiveInfo brokerLiveInfo) { this.clusterNameIdentityInfo = brokerIdentityInfo.getClusterName(); this.brokerNameIdentityInfo = brokerIdentityInfo.getBrokerName(); this.brokerIdIdentityInfo = brokerIdentityInfo.getBrokerId(); this.brokerName = brokerLiveInfo.getBrokerName(); this.brokerAddr = brokerLiveInfo.getBrokerAddr(); this.heartbeatTimeoutMillis = brokerLiveInfo.getHeartbeatTimeoutMillis(); this.brokerId = brokerLiveInfo.getBrokerId(); this.lastUpdateTimestamp = brokerLiveInfo.getLastUpdateTimestamp(); this.epoch = brokerLiveInfo.getEpoch(); this.maxOffset = brokerLiveInfo.getMaxOffset(); this.confirmOffset = brokerLiveInfo.getConfirmOffset(); this.electionPriority = brokerLiveInfo.getElectionPriority(); } public BrokerIdentityInfo getBrokerIdentityInfo() { return new BrokerIdentityInfo(clusterNameIdentityInfo, brokerNameIdentityInfo, brokerIdIdentityInfo); } public void setBrokerIdentityInfo(BrokerIdentityInfo brokerIdentityInfo) { this.clusterNameIdentityInfo = brokerIdentityInfo.getClusterName(); this.brokerNameIdentityInfo = brokerIdentityInfo.getBrokerName(); this.brokerIdIdentityInfo = brokerIdentityInfo.getBrokerId(); } public BrokerLiveInfo getBrokerLiveInfo() { return new BrokerLiveInfo(brokerName, brokerAddr, brokerId, lastUpdateTimestamp, heartbeatTimeoutMillis, null, epoch, maxOffset, electionPriority, confirmOffset); } public void setBrokerLiveInfo(BrokerLiveInfo brokerLiveInfo) { this.brokerName = brokerLiveInfo.getBrokerName(); this.brokerAddr = brokerLiveInfo.getBrokerAddr(); this.heartbeatTimeoutMillis = brokerLiveInfo.getHeartbeatTimeoutMillis(); this.brokerId = brokerLiveInfo.getBrokerId(); this.lastUpdateTimestamp = brokerLiveInfo.getLastUpdateTimestamp(); this.epoch = brokerLiveInfo.getEpoch(); this.maxOffset = brokerLiveInfo.getMaxOffset(); this.confirmOffset = brokerLiveInfo.getConfirmOffset(); this.electionPriority = brokerLiveInfo.getElectionPriority(); } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "RaftBrokerHeartBeatEventRequest{" + "brokerIdentityInfo=" + getBrokerIdentityInfo() + ", brokerLiveInfo=" + getBrokerLiveInfo() + "}"; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/impl/task/RaftBrokerHeartBeatEventResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.task; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RaftBrokerHeartBeatEventResponse implements CommandCustomHeader { public RaftBrokerHeartBeatEventResponse() { } @Override public void checkFields() throws RemotingCommandException { } @Override public String toString() { return "RaftBrokerHeartBeatEventResponse{}"; } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsConstant.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.metrics; import org.apache.rocketmq.remoting.protocol.RequestCode; public class ControllerMetricsConstant { public static final String LABEL_ADDRESS = "address"; public static final String LABEL_GROUP = "group"; public static final String LABEL_PEER_ID = "peer_id"; public static final String LABEL_AGGREGATION = "aggregation"; public static final String AGGREGATION_DELTA = "delta"; public static final String OPEN_TELEMETRY_METER_NAME = "controller"; public static final String GAUGE_ROLE = "role"; // unit: B public static final String GAUGE_DLEDGER_DISK_USAGE = "dledger_disk_usage"; public static final String GAUGE_ACTIVE_BROKER_NUM = "active_broker_num"; public static final String COUNTER_REQUEST_TOTAL = "request_total"; public static final String COUNTER_DLEDGER_OP_TOTAL = "dledger_op_total"; public static final String COUNTER_ELECTION_TOTAL = "election_total"; // unit: us public static final String HISTOGRAM_REQUEST_LATENCY = "request_latency"; // unit: us public static final String HISTOGRAM_DLEDGER_OP_LATENCY = "dledger_op_latency"; public static final String LABEL_CLUSTER_NAME = "cluster"; public static final String LABEL_BROKER_SET = "broker_set"; public static final String LABEL_REQUEST_TYPE = "request_type"; public static final String LABEL_REQUEST_HANDLE_STATUS = "request_handle_status"; public static final String LABEL_DLEDGER_OPERATION = "dledger_operation"; public static final String LABEL_DLEDGER_OPERATION_STATUS = "dLedger_operation_status"; public static final String LABEL_ELECTION_RESULT = "election_result"; public enum RequestType { CONTROLLER_ALTER_SYNC_STATE_SET(RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET), CONTROLLER_ELECT_MASTER(RequestCode.CONTROLLER_ELECT_MASTER), CONTROLLER_REGISTER_BROKER(RequestCode.CONTROLLER_REGISTER_BROKER), CONTROLLER_GET_REPLICA_INFO(RequestCode.CONTROLLER_GET_REPLICA_INFO), CONTROLLER_GET_METADATA_INFO(RequestCode.CONTROLLER_GET_METADATA_INFO), CONTROLLER_GET_SYNC_STATE_DATA(RequestCode.CONTROLLER_GET_SYNC_STATE_DATA), CONTROLLER_GET_BROKER_EPOCH_CACHE(RequestCode.GET_BROKER_EPOCH_CACHE), CONTROLLER_NOTIFY_BROKER_ROLE_CHANGED(RequestCode.NOTIFY_BROKER_ROLE_CHANGED), CONTROLLER_BROKER_HEARTBEAT(RequestCode.BROKER_HEARTBEAT), CONTROLLER_UPDATE_CONTROLLER_CONFIG(RequestCode.UPDATE_CONTROLLER_CONFIG), CONTROLLER_GET_CONTROLLER_CONFIG(RequestCode.GET_CONTROLLER_CONFIG), CONTROLLER_CLEAN_BROKER_DATA(RequestCode.CLEAN_BROKER_DATA), CONTROLLER_GET_NEXT_BROKER_ID(RequestCode.CONTROLLER_GET_NEXT_BROKER_ID), CONTROLLER_APPLY_BROKER_ID(RequestCode.CONTROLLER_APPLY_BROKER_ID); private final int code; RequestType(int code) { this.code = code; } public static String getLowerCaseNameByCode(int code) { for (RequestType requestType : RequestType.values()) { if (requestType.code == code) { return requestType.name(); } } return null; } } public enum RequestHandleStatus { SUCCESS, FAILED, TIMEOUT; public String getLowerCaseName() { return this.name().toLowerCase(); } } public enum DLedgerOperation { APPEND; public String getLowerCaseName() { return this.name().toLowerCase(); } } public enum DLedgerOperationStatus { SUCCESS, FAILED, TIMEOUT; public String getLowerCaseName() { return this.name().toLowerCase(); } } public enum ElectionResult { NEW_MASTER_ELECTED, KEEP_CURRENT_MASTER, NO_MASTER_ELECTED; public String getLowerCaseName() { return this.name().toLowerCase(); } } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.metrics; import com.google.common.base.Splitter; import io.openmessaging.storage.dledger.MemberState; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import io.opentelemetry.sdk.resources.Resource; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.metrics.MetricsExporterType; import org.apache.rocketmq.common.metrics.NopLongCounter; import org.apache.rocketmq.common.metrics.NopLongHistogram; import org.apache.rocketmq.common.metrics.NopLongUpDownCounter; import org.apache.rocketmq.common.metrics.NopObservableLongGauge; import org.apache.rocketmq.controller.ControllerManager; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import java.io.File; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.AGGREGATION_DELTA; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.COUNTER_DLEDGER_OP_TOTAL; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.COUNTER_ELECTION_TOTAL; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.COUNTER_REQUEST_TOTAL; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.GAUGE_ACTIVE_BROKER_NUM; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.GAUGE_DLEDGER_DISK_USAGE; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.GAUGE_ROLE; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.HISTOGRAM_DLEDGER_OP_LATENCY; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.HISTOGRAM_REQUEST_LATENCY; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_ADDRESS; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_AGGREGATION; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_BROKER_SET; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_CLUSTER_NAME; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_GROUP; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_PEER_ID; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.OPEN_TELEMETRY_METER_NAME; public class ControllerMetricsManager { private static final Logger logger = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private static volatile ControllerMetricsManager instance; private static final Map LABEL_MAP = new HashMap<>(); // metrics about node status public static LongUpDownCounter role = new NopLongUpDownCounter(); public static ObservableLongGauge dLedgerDiskUsage = new NopObservableLongGauge(); public static ObservableLongGauge activeBrokerNum = new NopObservableLongGauge(); public static LongCounter requestTotal = new NopLongCounter(); public static LongCounter dLedgerOpTotal = new NopLongCounter(); public static LongCounter electionTotal = new NopLongCounter(); // metrics about latency public static LongHistogram requestLatency = new NopLongHistogram(); public static LongHistogram dLedgerOpLatency = new NopLongHistogram(); private static double us = 1d; private static double ms = 1000 * us; private static double s = 1000 * ms; private final ControllerManager controllerManager; private final ControllerConfig config; private Meter controllerMeter; private OtlpGrpcMetricExporter metricExporter; private PeriodicMetricReader periodicMetricReader; private PrometheusHttpServer prometheusHttpServer; private MetricExporter loggingMetricExporter; public static ControllerMetricsManager getInstance(ControllerManager controllerManager) { if (instance == null) { synchronized (ControllerMetricsManager.class) { if (instance == null) { instance = new ControllerMetricsManager(controllerManager); } } } return instance; } public static AttributesBuilder newAttributesBuilder() { AttributesBuilder builder = Attributes.builder(); LABEL_MAP.forEach(builder::put); return builder; } public static void recordRole(MemberState.Role newRole, MemberState.Role oldRole) { role.add(getRoleValue(newRole) - getRoleValue(oldRole), newAttributesBuilder().build()); } private static int getRoleValue(MemberState.Role role) { switch (role) { case UNKNOWN: return 0; case CANDIDATE: return 1; case FOLLOWER: return 2; case LEADER: return 3; default: logger.error("Unknown role {}", role); return 0; } } private ControllerMetricsManager(ControllerManager controllerManager) { this.controllerManager = controllerManager; this.config = this.controllerManager.getControllerConfig(); if (config.getControllerType().equals(ControllerConfig.JRAFT_CONTROLLER)) { this.LABEL_MAP.put(LABEL_ADDRESS, this.config.getJraftConfig().getjRaftAddress()); this.LABEL_MAP.put(LABEL_GROUP, this.config.getJraftConfig().getjRaftGroupId()); this.LABEL_MAP.put(LABEL_PEER_ID, this.config.getJraftConfig().getjRaftServerId()); } else { this.LABEL_MAP.put(LABEL_ADDRESS, this.config.getDLedgerAddress()); this.LABEL_MAP.put(LABEL_GROUP, this.config.getControllerDLegerGroup()); this.LABEL_MAP.put(LABEL_PEER_ID, this.config.getControllerDLegerSelfId()); } this.init(); } private boolean checkConfig() { if (config == null) { return false; } MetricsExporterType exporterType = config.getMetricsExporterType(); if (!exporterType.isEnable()) { return false; } switch (exporterType) { case OTLP_GRPC: return StringUtils.isNotBlank(config.getMetricsGrpcExporterTarget()); case PROM: return true; case LOG: return true; } return false; } private void registerMetricsView(SdkMeterProviderBuilder providerBuilder) { // define latency bucket List latencyBuckets = Arrays.asList( 1 * us, 3 * us, 5 * us, 10 * us, 30 * us, 50 * us, 100 * us, 300 * us, 500 * us, 1 * ms, 3 * ms, 5 * ms, 10 * ms, 30 * ms, 50 * ms, 100 * ms, 300 * ms, 500 * ms, 1 * s, 3 * s, 5 * s, 10 * s ); View latencyView = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(latencyBuckets)) .build(); InstrumentSelector requestLatencySelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_REQUEST_LATENCY) .build(); InstrumentSelector dLedgerOpLatencySelector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_DLEDGER_OP_LATENCY) .build(); providerBuilder.registerView(requestLatencySelector, latencyView); providerBuilder.registerView(dLedgerOpLatencySelector, latencyView); } private void initMetric(Meter meter) { role = meter.upDownCounterBuilder(GAUGE_ROLE) .setDescription("role of current node") .build(); dLedgerDiskUsage = meter.gaugeBuilder(GAUGE_DLEDGER_DISK_USAGE) .setDescription("disk usage of dledger") .setUnit("bytes") .ofLongs() .buildWithCallback(measurement -> { String path = config.getControllerStorePath(); if (!UtilAll.isPathExists(path)) { return; } File file = new File(path); Long diskUsage = UtilAll.calculateFileSizeInPath(file); if (diskUsage == -1) { logger.error("calculateFileSizeInPath error, path: {}", path); return; } measurement.record(diskUsage, newAttributesBuilder().build()); }); activeBrokerNum = meter.gaugeBuilder(GAUGE_ACTIVE_BROKER_NUM) .setDescription("now active brokers num") .ofLongs() .buildWithCallback(measurement -> { Map> activeBrokersNum = controllerManager.getHeartbeatManager().getActiveBrokersNum(); activeBrokersNum.forEach((cluster, brokerSetAndNum) -> { brokerSetAndNum.forEach((brokerSet, num) -> measurement.record(num, newAttributesBuilder().put(LABEL_CLUSTER_NAME, cluster).put(LABEL_BROKER_SET, brokerSet).build())); }); }); requestTotal = meter.counterBuilder(COUNTER_REQUEST_TOTAL) .setDescription("total request num") .build(); dLedgerOpTotal = meter.counterBuilder(COUNTER_DLEDGER_OP_TOTAL) .setDescription("total dledger operation num") .build(); electionTotal = meter.counterBuilder(COUNTER_ELECTION_TOTAL) .setDescription("total elect num") .build(); requestLatency = meter.histogramBuilder(HISTOGRAM_REQUEST_LATENCY) .setDescription("request latency") .setUnit("us") .ofLongs() .build(); dLedgerOpLatency = meter.histogramBuilder(HISTOGRAM_DLEDGER_OP_LATENCY) .setDescription("dledger operation latency") .setUnit("us") .ofLongs() .build(); } public void init() { MetricsExporterType type = this.config.getMetricsExporterType(); if (type == MetricsExporterType.DISABLE) { return; } if (!checkConfig()) { logger.error("check metric config failed, will not export metrics"); return; } String labels = config.getMetricsLabel(); if (StringUtils.isNotBlank(labels)) { List labelList = Splitter.on(',').omitEmptyStrings().splitToList(labels); for (String label : labelList) { String[] pair = label.split(":"); if (pair.length != 2) { logger.warn("metrics label is not valid: {}", label); continue; } LABEL_MAP.put(pair[0], pair[1]); } } if (config.isMetricsInDelta()) { LABEL_MAP.put(LABEL_AGGREGATION, AGGREGATION_DELTA); } SdkMeterProviderBuilder providerBuilder = SdkMeterProvider.builder().setResource(Resource.empty()); if (type == MetricsExporterType.OTLP_GRPC) { String endpoint = config.getMetricsGrpcExporterTarget(); if (!endpoint.startsWith("http")) { endpoint = "https://" + endpoint; } OtlpGrpcMetricExporterBuilder metricExporterBuilder = OtlpGrpcMetricExporter.builder() .setEndpoint(endpoint) .setTimeout(config.getMetricGrpcExporterTimeOutInMills(), TimeUnit.MILLISECONDS) .setAggregationTemporalitySelector(x -> { if (config.isMetricsInDelta() && (x == InstrumentType.COUNTER || x == InstrumentType.OBSERVABLE_COUNTER || x == InstrumentType.HISTOGRAM)) { return AggregationTemporality.DELTA; } return AggregationTemporality.CUMULATIVE; }); String headers = config.getMetricsGrpcExporterHeader(); if (StringUtils.isNotBlank(headers)) { Map headerMap = new HashMap<>(); List headerList = Splitter.on(',').omitEmptyStrings().splitToList(headers); for (String header : headerList) { String[] pair = header.split(":"); if (pair.length != 2) { logger.warn("metricsGrpcExporterHeader is not valid: {}", headers); continue; } headerMap.put(pair[0], pair[1]); } headerMap.forEach(metricExporterBuilder::addHeader); } metricExporter = metricExporterBuilder.build(); periodicMetricReader = PeriodicMetricReader.builder(metricExporter) .setInterval(config.getMetricGrpcExporterIntervalInMills(), TimeUnit.MILLISECONDS) .build(); providerBuilder.registerMetricReader(periodicMetricReader); } if (type == MetricsExporterType.PROM) { String promExporterHost = config.getMetricsPromExporterHost(); if (StringUtils.isBlank(promExporterHost)) { promExporterHost = "0.0.0.0"; } prometheusHttpServer = PrometheusHttpServer.builder() .setHost(promExporterHost) .setPort(config.getMetricsPromExporterPort()) .build(); providerBuilder.registerMetricReader(prometheusHttpServer); } if (type == MetricsExporterType.LOG) { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); loggingMetricExporter = OtlpJsonLoggingMetricExporter.create(config.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE); java.util.logging.Logger.getLogger(OtlpJsonLoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST); periodicMetricReader = PeriodicMetricReader.builder(loggingMetricExporter) .setInterval(config.getMetricLoggingExporterIntervalInMills(), TimeUnit.MILLISECONDS) .build(); providerBuilder.registerMetricReader(periodicMetricReader); } registerMetricsView(providerBuilder); controllerMeter = OpenTelemetrySdk.builder().setMeterProvider(providerBuilder.build()) .build().getMeter(OPEN_TELEMETRY_METER_NAME); initMetric(controllerMeter); } } ================================================ FILE: controller/src/main/java/org/apache/rocketmq/controller/processor/ControllerRequestProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.processor; import com.google.common.base.Stopwatch; import io.netty.channel.ChannelHandlerContext; import io.opentelemetry.api.common.Attributes; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.controller.BrokerHeartbeatManager; import org.apache.rocketmq.controller.ControllerManager; import org.apache.rocketmq.controller.metrics.ControllerMetricsConstant; import org.apache.rocketmq.controller.metrics.ControllerMetricsManager; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.RoleChangeNotifyEntry; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.BrokerHeartbeatRequestHeader; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_REQUEST_HANDLE_STATUS; import static org.apache.rocketmq.controller.metrics.ControllerMetricsConstant.LABEL_REQUEST_TYPE; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_APPLY_BROKER_ID; import static org.apache.rocketmq.remoting.protocol.RequestCode.BROKER_HEARTBEAT; import static org.apache.rocketmq.remoting.protocol.RequestCode.CLEAN_BROKER_DATA; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_ELECT_MASTER; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_GET_METADATA_INFO; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_GET_REPLICA_INFO; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_GET_SYNC_STATE_DATA; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_REGISTER_BROKER; import static org.apache.rocketmq.remoting.protocol.RequestCode.GET_CONTROLLER_CONFIG; import static org.apache.rocketmq.remoting.protocol.RequestCode.CONTROLLER_GET_NEXT_BROKER_ID; import static org.apache.rocketmq.remoting.protocol.RequestCode.UPDATE_CONTROLLER_CONFIG; /** * Processor for controller request */ public class ControllerRequestProcessor implements NettyRequestProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.CONTROLLER_LOGGER_NAME); private static final int WAIT_TIMEOUT_OUT = 5; private final ControllerManager controllerManager; private final BrokerHeartbeatManager heartbeatManager; protected Set configBlackList = new HashSet<>(); public ControllerRequestProcessor(final ControllerManager controllerManager) { this.controllerManager = controllerManager; this.heartbeatManager = controllerManager.getHeartbeatManager(); initConfigBlackList(); } private void initConfigBlackList() { configBlackList.add("configBlackList"); configBlackList.add("configStorePath"); configBlackList.add("rocketmqHome"); String[] configArray = controllerManager.getControllerConfig().getConfigBlackList().split(";"); configBlackList.addAll(Arrays.asList(configArray)); } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { if (ctx != null) { log.debug("Receive request, {} {} {}", request.getCode(), RemotingHelper.parseChannelRemoteAddr(ctx.channel()), request); } Stopwatch stopwatch = Stopwatch.createStarted(); try { RemotingCommand resp = handleRequest(ctx, request); Attributes attributes = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_TYPE, ControllerMetricsConstant.RequestType.getLowerCaseNameByCode(request.getCode())) .put(LABEL_REQUEST_HANDLE_STATUS, ControllerMetricsConstant.RequestHandleStatus.SUCCESS.getLowerCaseName()) .build(); ControllerMetricsManager.requestTotal.add(1, attributes); attributes = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_TYPE, ControllerMetricsConstant.RequestType.getLowerCaseNameByCode(request.getCode())) .build(); ControllerMetricsManager.requestLatency.record(stopwatch.elapsed(TimeUnit.MICROSECONDS), attributes); return resp; } catch (Exception e) { log.error("process request: {} error, ", request, e); Attributes attributes; if (e instanceof TimeoutException) { attributes = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_TYPE, ControllerMetricsConstant.RequestType.getLowerCaseNameByCode(request.getCode())) .put(LABEL_REQUEST_HANDLE_STATUS, ControllerMetricsConstant.RequestHandleStatus.TIMEOUT.getLowerCaseName()) .build(); } else { attributes = ControllerMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_TYPE, ControllerMetricsConstant.RequestType.getLowerCaseNameByCode(request.getCode())) .put(LABEL_REQUEST_HANDLE_STATUS, ControllerMetricsConstant.RequestHandleStatus.FAILED.getLowerCaseName()) .build(); } ControllerMetricsManager.requestTotal.add(1, attributes); throw e; } } private RemotingCommand handleRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { switch (request.getCode()) { case CONTROLLER_ALTER_SYNC_STATE_SET: return this.handleAlterSyncStateSet(ctx, request); case CONTROLLER_ELECT_MASTER: return this.handleControllerElectMaster(ctx, request); case CONTROLLER_GET_REPLICA_INFO: return this.handleControllerGetReplicaInfo(ctx, request); case CONTROLLER_GET_METADATA_INFO: return this.handleControllerGetMetadataInfo(ctx, request); case BROKER_HEARTBEAT: return this.handleBrokerHeartbeat(ctx, request); case CONTROLLER_GET_SYNC_STATE_DATA: return this.handleControllerGetSyncStateData(ctx, request); case UPDATE_CONTROLLER_CONFIG: return this.handleUpdateControllerConfig(ctx, request); case GET_CONTROLLER_CONFIG: return this.handleGetControllerConfig(ctx, request); case CLEAN_BROKER_DATA: return this.handleCleanBrokerData(ctx, request); case CONTROLLER_GET_NEXT_BROKER_ID: return this.handleGetNextBrokerId(ctx, request); case CONTROLLER_APPLY_BROKER_ID: return this.handleApplyBrokerId(ctx, request); case CONTROLLER_REGISTER_BROKER: return this.handleRegisterBroker(ctx, request); default: { final String error = " request type " + request.getCode() + " not supported"; return RemotingCommand.createResponseCommand(ResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); } } } private RemotingCommand handleAlterSyncStateSet(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final AlterSyncStateSetRequestHeader controllerRequest = (AlterSyncStateSetRequestHeader) request.decodeCommandCustomHeader(AlterSyncStateSetRequestHeader.class); final SyncStateSet syncStateSet = RemotingSerializable.decode(request.getBody(), SyncStateSet.class); final CompletableFuture future = this.controllerManager.getController().alterSyncStateSet(controllerRequest, syncStateSet); if (future != null) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleControllerElectMaster(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final ElectMasterRequestHeader electMasterRequest = (ElectMasterRequestHeader) request.decodeCommandCustomHeader(ElectMasterRequestHeader.class); final CompletableFuture future = this.controllerManager.getController().electMaster(electMasterRequest); if (future != null) { final RemotingCommand response = future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); if (response.getCode() == ResponseCode.SUCCESS) { if (this.controllerManager.getControllerConfig().isNotifyBrokerRoleChanged()) { this.controllerManager.notifyBrokerRoleChanged(RoleChangeNotifyEntry.convert(response)); } } return response; } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleControllerGetReplicaInfo(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final GetReplicaInfoRequestHeader controllerRequest = (GetReplicaInfoRequestHeader) request.decodeCommandCustomHeader(GetReplicaInfoRequestHeader.class); final CompletableFuture future = this.controllerManager.getController().getReplicaInfo(controllerRequest); if (future != null) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleControllerGetMetadataInfo(ChannelHandlerContext ctx, RemotingCommand request) { return this.controllerManager.getController().getControllerMetadata(); } private RemotingCommand handleBrokerHeartbeat(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final BrokerHeartbeatRequestHeader requestHeader = (BrokerHeartbeatRequestHeader) request.decodeCommandCustomHeader(BrokerHeartbeatRequestHeader.class); if (requestHeader.getBrokerId() == null) { return RemotingCommand.createResponseCommand(ResponseCode.CONTROLLER_INVALID_REQUEST, "Heart beat with empty brokerId"); } this.heartbeatManager.onBrokerHeartbeat(requestHeader.getClusterName(), requestHeader.getBrokerName(), requestHeader.getBrokerAddr(), requestHeader.getBrokerId(), requestHeader.getHeartbeatTimeoutMills(), ctx.channel(), requestHeader.getEpoch(), requestHeader.getMaxOffset(), requestHeader.getConfirmOffset(), requestHeader.getElectionPriority()); return RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Heart beat success"); } private RemotingCommand handleControllerGetSyncStateData(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { if (request.getBody() != null) { final List brokerNames = RemotingSerializable.decode(request.getBody(), List.class); if (brokerNames != null && brokerNames.size() > 0) { final CompletableFuture future = this.controllerManager.getController().getSyncStateData(brokerNames); if (future != null) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } } } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleCleanBrokerData(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final CleanControllerBrokerDataRequestHeader requestHeader = (CleanControllerBrokerDataRequestHeader) request.decodeCommandCustomHeader(CleanControllerBrokerDataRequestHeader.class); final CompletableFuture future = this.controllerManager.getController().cleanBrokerData(requestHeader); if (null != future) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleGetNextBrokerId(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final GetNextBrokerIdRequestHeader requestHeader = (GetNextBrokerIdRequestHeader) request.decodeCommandCustomHeader(GetNextBrokerIdRequestHeader.class); CompletableFuture future = this.controllerManager.getController().getNextBrokerId(requestHeader); if (future != null) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleApplyBrokerId(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { final ApplyBrokerIdRequestHeader requestHeader = (ApplyBrokerIdRequestHeader) request.decodeCommandCustomHeader(ApplyBrokerIdRequestHeader.class); CompletableFuture future = this.controllerManager.getController().applyBrokerId(requestHeader); if (future != null) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleRegisterBroker(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { RegisterBrokerToControllerRequestHeader requestHeader = (RegisterBrokerToControllerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerToControllerRequestHeader.class); CompletableFuture future = this.controllerManager.getController().registerBroker(requestHeader); if (future != null) { return future.get(WAIT_TIMEOUT_OUT, TimeUnit.SECONDS); } return RemotingCommand.createResponseCommand(null); } private RemotingCommand handleUpdateControllerConfig(ChannelHandlerContext ctx, RemotingCommand request) { if (ctx != null) { log.info("updateConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); } final RemotingCommand response = RemotingCommand.createResponseCommand(null); byte[] body = request.getBody(); if (body != null) { String bodyStr; try { bodyStr = new String(body, MixAll.DEFAULT_CHARSET); } catch (UnsupportedEncodingException e) { log.error("updateConfig byte array to string error: ", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } Properties properties = MixAll.string2Properties(bodyStr); if (properties == null) { log.error("updateConfig MixAll.string2Properties error {}", bodyStr); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string2Properties error"); return response; } if (validateBlackListConfigExist(properties)) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("Can not update config in black list."); return response; } this.controllerManager.getConfiguration().update(properties); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand handleGetControllerConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); String content = this.controllerManager.getConfiguration().getAllConfigsFormatString(); if (content != null && content.length() > 0) { try { content = MixAll.adjustConfigForPlatform(content); response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { log.error("getConfig error, ", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } @Override public boolean rejectRequest() { return false; } private boolean validateBlackListConfigExist(Properties properties) { for (String blackConfig : configBlackList) { if (properties.containsKey(blackConfig)) { return true; } } return false; } } ================================================ FILE: controller/src/main/resources/rmq.controller.logback.xml ================================================ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}controller_default.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}controller_default.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}dledger.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}dledger.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 0 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}jraft.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}jraft.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 0 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}controller_traffic.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}controller_traffic.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}controller.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}controller.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 0 %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/ControllerManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.controller.impl.DLedgerController; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.BrokerHeartbeatRequestHeader; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class ControllerManagerTest { public static final String STORE_BASE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "ControllerManagerTest"; public static final String STORE_PATH = STORE_BASE_PATH + File.separator + UUID.randomUUID(); private List controllers; private NettyRemotingClient remotingClient; private NettyRemotingClient remotingClient1; public ControllerManager launchManager(final String group, final String peers, final String selfId) { final String path = STORE_PATH + File.separator + group + File.separator + selfId; final ControllerConfig config = new ControllerConfig(); config.setControllerDLegerGroup(group); config.setControllerDLegerPeers(peers); config.setControllerDLegerSelfId(selfId); config.setControllerStorePath(path); config.setMappedFileSize(10 * 1024 * 1024); config.setEnableElectUncleanMaster(true); config.setScanNotActiveBrokerInterval(1000L); config.setNotifyBrokerRoleChanged(false); final NettyServerConfig serverConfig = new NettyServerConfig(); final ControllerManager manager = new ControllerManager(config, serverConfig, new NettyClientConfig()); manager.initialize(); manager.start(); this.controllers.add(manager); return manager; } @Before public void startup() { UtilAll.deleteFile(new File(STORE_BASE_PATH)); this.controllers = new ArrayList<>(); this.remotingClient = new NettyRemotingClient(new NettyClientConfig()); this.remotingClient.start(); this.remotingClient1 = new NettyRemotingClient(new NettyClientConfig()); this.remotingClient1.start(); } public ControllerManager waitLeader(final List controllers) throws Exception { if (controllers.isEmpty()) { return null; } DLedgerController c1 = (DLedgerController) controllers.get(0).getController(); ControllerManager manager = await().atMost(Duration.ofSeconds(10)).until(() -> { String leaderId = c1.getMemberState().getLeaderId(); if (null == leaderId) { return null; } for (ControllerManager controllerManager : controllers) { final DLedgerController controller = (DLedgerController) controllerManager.getController(); if (controller.getMemberState().getSelfId().equals(leaderId) && controller.isLeaderState()) { return controllerManager; } } return null; }, item -> item != null); return manager; } public void mockData() { String group = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d;n1-localhost:%d;n2-localhost:%d", 30000, 30001, 30002); launchManager(group, peers, "n0"); launchManager(group, peers, "n1"); launchManager(group, peers, "n2"); } /** * Register broker to controller */ public void registerBroker( final String controllerAddress, final String clusterName, final String brokerName, final Long brokerId, final String brokerAddress, final Long expectMasterBrokerId, final RemotingClient client) throws Exception { // Get next brokerId; final GetNextBrokerIdRequestHeader getNextBrokerIdRequestHeader = new GetNextBrokerIdRequestHeader(clusterName, brokerName); final RemotingCommand getNextBrokerIdRequest = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_NEXT_BROKER_ID, getNextBrokerIdRequestHeader); final RemotingCommand getNextBrokerIdResponse = client.invokeSync(controllerAddress, getNextBrokerIdRequest, 3000); final GetNextBrokerIdResponseHeader getNextBrokerIdResponseHeader = (GetNextBrokerIdResponseHeader) getNextBrokerIdResponse.decodeCommandCustomHeader(GetNextBrokerIdResponseHeader.class); String registerCheckCode = brokerAddress + ";" + System.currentTimeMillis(); assertEquals(ResponseCode.SUCCESS, getNextBrokerIdResponse.getCode()); assertEquals(brokerId, getNextBrokerIdResponseHeader.getNextBrokerId()); // Apply brokerId final ApplyBrokerIdRequestHeader applyBrokerIdRequestHeader = new ApplyBrokerIdRequestHeader(clusterName, brokerName, brokerId, registerCheckCode); final RemotingCommand applyBrokerIdRequest = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_APPLY_BROKER_ID, applyBrokerIdRequestHeader); final RemotingCommand applyBrokerIdResponse = client.invokeSync(controllerAddress, applyBrokerIdRequest, 3000); final ApplyBrokerIdResponseHeader applyBrokerIdResponseHeader = (ApplyBrokerIdResponseHeader) applyBrokerIdResponse.decodeCommandCustomHeader(ApplyBrokerIdResponseHeader.class); assertEquals(ResponseCode.SUCCESS, applyBrokerIdResponse.getCode()); // Register success final RegisterBrokerToControllerRequestHeader registerBrokerToControllerRequestHeader = new RegisterBrokerToControllerRequestHeader(clusterName, brokerName, brokerId, brokerAddress); final RemotingCommand registerSuccessRequest = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_REGISTER_BROKER, registerBrokerToControllerRequestHeader); final RemotingCommand registerSuccessResponse = client.invokeSync(controllerAddress, registerSuccessRequest, 3000); final RegisterBrokerToControllerResponseHeader registerBrokerToControllerResponseHeader = (RegisterBrokerToControllerResponseHeader) registerSuccessResponse.decodeCommandCustomHeader(RegisterBrokerToControllerResponseHeader.class); assertEquals(ResponseCode.SUCCESS, registerSuccessResponse.getCode()); assertEquals(expectMasterBrokerId, registerBrokerToControllerResponseHeader.getMasterBrokerId()); } public RemotingCommand brokerTryElect(final String controllerAddress, final String clusterName, final String brokerName, final Long brokerId, final RemotingClient client) throws Exception { final ElectMasterRequestHeader requestHeader = ElectMasterRequestHeader.ofBrokerTrigger(clusterName, brokerName, brokerId); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ELECT_MASTER, requestHeader); RemotingCommand response = client.invokeSync(controllerAddress, request, 10000); assertNotNull(response); return response; } public void sendHeartbeat(final String controllerAddress, final String clusterName, final String brokerName, final Long brokerId, final String brokerAddress, final Long timeout, final RemotingClient client) throws Exception { final BrokerHeartbeatRequestHeader heartbeatRequestHeader0 = new BrokerHeartbeatRequestHeader(); heartbeatRequestHeader0.setBrokerId(brokerId); heartbeatRequestHeader0.setClusterName(clusterName); heartbeatRequestHeader0.setBrokerName(brokerName); heartbeatRequestHeader0.setBrokerAddr(brokerAddress); heartbeatRequestHeader0.setHeartbeatTimeoutMills(timeout); final RemotingCommand heartbeatRequest = RemotingCommand.createRequestCommand(RequestCode.BROKER_HEARTBEAT, heartbeatRequestHeader0); RemotingCommand remotingCommand = client.invokeSync(controllerAddress, heartbeatRequest, 3000); assertEquals(ResponseCode.SUCCESS, remotingCommand.getCode()); } @Test public void testSomeApi() throws Exception { mockData(); final ControllerManager leader = waitLeader(this.controllers); String leaderAddr = "localhost" + ":" + leader.getController().getRemotingServer().localListenPort(); // Register two broker registerBroker(leaderAddr, "cluster1", "broker1", 1L, "127.0.0.1:8000", null, this.remotingClient); registerBroker(leaderAddr, "cluster1", "broker1", 2L, "127.0.0.1:8001", null, this.remotingClient1); // Send heartbeat sendHeartbeat(leaderAddr, "cluster1", "broker1", 1L, "127.0.0.1:8000", 3000L, remotingClient); sendHeartbeat(leaderAddr, "cluster1", "broker1", 2L, "127.0.0.1:8001", 3000L, remotingClient1); // Two all try elect itself as master, but only the first can be the master RemotingCommand tryElectCommand1 = brokerTryElect(leaderAddr, "cluster1", "broker1", 1L, this.remotingClient); ElectMasterResponseHeader brokerTryElectResponseHeader1 = (ElectMasterResponseHeader) tryElectCommand1.decodeCommandCustomHeader(ElectMasterResponseHeader.class); RemotingCommand tryElectCommand2 = brokerTryElect(leaderAddr, "cluster1", "broker1", 2L, this.remotingClient1); ElectMasterResponseHeader brokerTryElectResponseHeader2 = (ElectMasterResponseHeader) tryElectCommand2.decodeCommandCustomHeader(ElectMasterResponseHeader.class); assertEquals(ResponseCode.SUCCESS, tryElectCommand1.getCode()); assertEquals(1L, brokerTryElectResponseHeader1.getMasterBrokerId().longValue()); assertEquals("127.0.0.1:8000", brokerTryElectResponseHeader1.getMasterAddress()); assertEquals(1, brokerTryElectResponseHeader1.getMasterEpoch().intValue()); assertEquals(1, brokerTryElectResponseHeader1.getSyncStateSetEpoch().intValue()); assertEquals(ResponseCode.CONTROLLER_MASTER_STILL_EXIST, tryElectCommand2.getCode()); assertEquals(1L, brokerTryElectResponseHeader2.getMasterBrokerId().longValue()); assertEquals("127.0.0.1:8000", brokerTryElectResponseHeader2.getMasterAddress()); assertEquals(1, brokerTryElectResponseHeader2.getMasterEpoch().intValue()); assertEquals(1, brokerTryElectResponseHeader2.getSyncStateSetEpoch().intValue()); // Send heartbeat for broker2 every one second ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(() -> { final BrokerHeartbeatRequestHeader heartbeatRequestHeader = new BrokerHeartbeatRequestHeader(); heartbeatRequestHeader.setClusterName("cluster1"); heartbeatRequestHeader.setBrokerName("broker1"); heartbeatRequestHeader.setBrokerAddr("127.0.0.1:8001"); heartbeatRequestHeader.setBrokerId(2L); heartbeatRequestHeader.setHeartbeatTimeoutMills(3000L); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.BROKER_HEARTBEAT, heartbeatRequestHeader); try { final RemotingCommand remotingCommand = this.remotingClient1.invokeSync(leaderAddr, request, 3000); } catch (Exception e) { e.printStackTrace(); } }, 0, 1000L, TimeUnit.MILLISECONDS); Boolean flag = await().atMost(Duration.ofSeconds(10)).until(() -> { final GetReplicaInfoRequestHeader requestHeader = new GetReplicaInfoRequestHeader("broker1"); final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_REPLICA_INFO, requestHeader); final RemotingCommand response = this.remotingClient1.invokeSync(leaderAddr, request, 3000); final GetReplicaInfoResponseHeader responseHeader = (GetReplicaInfoResponseHeader) response.decodeCommandCustomHeader(GetReplicaInfoResponseHeader.class); return responseHeader.getMasterBrokerId().equals(2L); }, item -> item); // The new master should be broker2. assertTrue(flag); executor.shutdown(); } @After public void tearDown() { for (ControllerManager controller : this.controllers) { controller.shutdown(); } UtilAll.deleteFile(new File(STORE_BASE_PATH)); this.remotingClient.shutdown(); this.remotingClient1.shutdown(); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/ControllerRequestProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.controller.processor.ControllerRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Before; import org.junit.Test; import java.nio.charset.StandardCharsets; import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; public class ControllerRequestProcessorTest { private ControllerRequestProcessor controllerRequestProcessor; @Before public void init() throws Exception { controllerRequestProcessor = new ControllerRequestProcessor(new ControllerManager(new ControllerConfig(), new NettyServerConfig(), new NettyClientConfig())); } @Test public void testProcessRequest_UpdateConfigPath() throws Exception { final RemotingCommand updateConfigRequest = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONTROLLER_CONFIG, null); Properties properties = new Properties(); // Update allowed value properties.setProperty("notifyBrokerRoleChanged", "true"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); RemotingCommand response = controllerRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); // Update disallowed value properties.clear(); properties.setProperty("configStorePath", "test/path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = controllerRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list."); // Update disallowed value properties.clear(); properties.setProperty("rocketmqHome", "test/path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = controllerRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list."); // Update disallowed value properties.clear(); properties.setProperty("configBlackList", "test;path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = controllerRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list."); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/ControllerTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller; public class ControllerTestBase { public final static String DEFAULT_CLUSTER_NAME = "cluster-a"; public final static String DEFAULT_BROKER_NAME = "broker-set-a"; public final static String[] DEFAULT_IP = {"127.0.0.1:9000", "127.0.0.1:9001", "127.0.0.1:9002"}; } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.controller.Controller; import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import static org.apache.rocketmq.controller.ControllerTestBase.DEFAULT_BROKER_NAME; import static org.apache.rocketmq.controller.ControllerTestBase.DEFAULT_CLUSTER_NAME; import static org.apache.rocketmq.controller.ControllerTestBase.DEFAULT_IP; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class DLedgerControllerTest { private List baseDirs; private List controllers; public DLedgerController launchController(final String group, final String peers, final String selfId, final boolean isEnableElectUncleanMaster) { String tmpdir = System.getProperty("java.io.tmpdir"); final String path = (StringUtils.endsWith(tmpdir, File.separator) ? tmpdir : tmpdir + File.separator) + group + File.separator + selfId; baseDirs.add(path); final ControllerConfig config = new ControllerConfig(); config.setControllerDLegerGroup(group); config.setControllerDLegerPeers(peers); config.setControllerDLegerSelfId(selfId); config.setControllerStorePath(path); config.setMappedFileSize(10 * 1024 * 1024); config.setEnableElectUncleanMaster(isEnableElectUncleanMaster); config.setScanInactiveMasterInterval(1000); final DLedgerController controller = new DLedgerController(config, (str1, str2, str3) -> true); controller.startup(); return controller; } @Before public void startup() { this.baseDirs = new ArrayList<>(); this.controllers = new ArrayList<>(); } @After public void tearDown() { for (Controller controller : this.controllers) { controller.shutdown(); } for (String dir : this.baseDirs) { new File(dir).delete(); } } public void registerNewBroker(Controller leader, String clusterName, String brokerName, String brokerAddress, Long expectBrokerId) throws Exception { // Get next brokerId final GetNextBrokerIdRequestHeader getNextBrokerIdRequest = new GetNextBrokerIdRequestHeader(clusterName, brokerName); RemotingCommand remotingCommand = leader.getNextBrokerId(getNextBrokerIdRequest).get(2, TimeUnit.SECONDS); GetNextBrokerIdResponseHeader getNextBrokerIdResp = (GetNextBrokerIdResponseHeader) remotingCommand.readCustomHeader(); Long nextBrokerId = getNextBrokerIdResp.getNextBrokerId(); String registerCheckCode = brokerAddress + ";" + System.currentTimeMillis(); // Check response assertEquals(expectBrokerId, nextBrokerId); // Apply brokerId final ApplyBrokerIdRequestHeader applyBrokerIdRequestHeader = new ApplyBrokerIdRequestHeader(clusterName, brokerName, nextBrokerId, registerCheckCode); RemotingCommand remotingCommand1 = leader.applyBrokerId(applyBrokerIdRequestHeader).get(2, TimeUnit.SECONDS); // Check response assertEquals(ResponseCode.SUCCESS, remotingCommand1.getCode()); // Register success final RegisterBrokerToControllerRequestHeader registerBrokerToControllerRequestHeader = new RegisterBrokerToControllerRequestHeader(clusterName, brokerName, nextBrokerId, brokerAddress); RemotingCommand remotingCommand2 = leader.registerBroker(registerBrokerToControllerRequestHeader).get(2, TimeUnit.SECONDS); assertEquals(ResponseCode.SUCCESS, remotingCommand2.getCode()); } public void brokerTryElectMaster(Controller leader, String clusterName, String brokerName, String brokerAddress, Long brokerId, boolean exceptSuccess) throws Exception { final ElectMasterRequestHeader electMasterRequestHeader = ElectMasterRequestHeader.ofBrokerTrigger(clusterName, brokerName, brokerId); RemotingCommand command = leader.electMaster(electMasterRequestHeader).get(2, TimeUnit.SECONDS); ElectMasterResponseHeader header = (ElectMasterResponseHeader) command.readCustomHeader(); assertEquals(exceptSuccess, ResponseCode.SUCCESS == command.getCode()); } private boolean alterNewInSyncSet(Controller leader, String brokerName, Long masterBrokerId, Integer masterEpoch, Set newSyncStateSet, Integer syncStateSetEpoch) throws Exception { final AlterSyncStateSetRequestHeader alterRequest = new AlterSyncStateSetRequestHeader(brokerName, masterBrokerId, masterEpoch); final RemotingCommand response = leader.alterSyncStateSet(alterRequest, new SyncStateSet(newSyncStateSet, syncStateSetEpoch)).get(10, TimeUnit.SECONDS); if (null == response || response.getCode() != ResponseCode.SUCCESS) { return false; } final RemotingCommand getInfoResponse = leader.getReplicaInfo(new GetReplicaInfoRequestHeader(brokerName)).get(10, TimeUnit.SECONDS); final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) getInfoResponse.readCustomHeader(); final SyncStateSet syncStateSet = RemotingSerializable.decode(getInfoResponse.getBody(), SyncStateSet.class); assertArrayEquals(syncStateSet.getSyncStateSet().toArray(), newSyncStateSet.toArray()); assertEquals(syncStateSet.getSyncStateSetEpoch(), syncStateSetEpoch + 1); return true; } public DLedgerController waitLeader(final List controllers) throws Exception { if (controllers.isEmpty()) { return null; } DLedgerController c1 = controllers.get(0); DLedgerController dLedgerController = await().atMost(Duration.ofSeconds(10)).until(() -> { String leaderId = c1.getMemberState().getLeaderId(); if (null == leaderId) { return null; } for (DLedgerController controller : controllers) { if (controller.getMemberState().getSelfId().equals(leaderId) && controller.isLeaderState()) { return controller; } } return null; }, item -> item != null); return dLedgerController; } public DLedgerController mockMetaData(boolean enableElectUncleanMaster) throws Exception { String group = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d;n1-localhost:%d;n2-localhost:%d", 30000, 30001, 30002); DLedgerController c0 = launchController(group, peers, "n0", enableElectUncleanMaster); DLedgerController c1 = launchController(group, peers, "n1", enableElectUncleanMaster); DLedgerController c2 = launchController(group, peers, "n2", enableElectUncleanMaster); controllers.add(c0); controllers.add(c1); controllers.add(c2); DLedgerController leader = waitLeader(controllers); // register registerNewBroker(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L); registerNewBroker(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L); registerNewBroker(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L); // try elect brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, true); brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, false); brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, false); final RemotingCommand getInfoResponse = leader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)).get(10, TimeUnit.SECONDS); final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) getInfoResponse.readCustomHeader(); assertEquals(1, replicaInfo.getMasterEpoch().intValue()); assertEquals(DEFAULT_IP[0], replicaInfo.getMasterAddress()); // Try alter SyncStateSet final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(1L); newSyncStateSet.add(2L); newSyncStateSet.add(3L); assertTrue(alterNewInSyncSet(leader, DEFAULT_BROKER_NAME, 1L, 1, newSyncStateSet, 1)); return leader; } public void setBrokerAlivePredicate(DLedgerController controller, Long... deathBroker) { controller.setBrokerAlivePredicate((clusterName, brokerName, brokerId) -> { for (Long broker : deathBroker) { if (broker.equals(brokerId)) { return false; } } return true; }); } public void setBrokerElectPolicy(DLedgerController controller, Long... deathBroker) { controller.setElectPolicy(new DefaultElectPolicy((clusterName, brokerName, brokerId) -> { for (Long broker : deathBroker) { if (broker.equals(brokerId)) { return false; } } return true; }, null)); } @Test public void testElectMaster() throws Exception { final DLedgerController leader = mockMetaData(false); final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); setBrokerElectPolicy(leader, 1L); final RemotingCommand resp = leader.electMaster(request).get(10, TimeUnit.SECONDS); final ElectMasterResponseHeader response = (ElectMasterResponseHeader) resp.readCustomHeader(); assertEquals(2, response.getMasterEpoch().intValue()); assertNotEquals(1L, response.getMasterBrokerId().longValue()); assertNotEquals(DEFAULT_IP[0], response.getMasterAddress()); } @Test public void testBrokerLifecycleListener() throws Exception { final DLedgerController leader = mockMetaData(false); assertTrue(leader.isLeaderState()); // Mock that master broker has been inactive, and try to elect a new master from sync-state-set // But we shut down two controller, so the ElectMasterEvent will be appended to DLedger failed. // So the statemachine still keep the stale master's information List removed = controllers.stream().filter(controller -> controller != leader).collect(Collectors.toList()); for (DLedgerController dLedgerController : removed) { dLedgerController.shutdown(); controllers.remove(dLedgerController); } final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); setBrokerElectPolicy(leader, 1L); Exception exception = null; RemotingCommand remotingCommand = null; try { remotingCommand = leader.electMaster(request).get(5, TimeUnit.SECONDS); } catch (Exception e) { exception = e; } assertTrue(exception != null || remotingCommand != null && remotingCommand.getCode() == ResponseCode.CONTROLLER_NOT_LEADER); // Shut down leader controller leader.shutdown(); controllers.remove(leader); // Restart two controller for (DLedgerController controller : removed) { if (controller != leader) { ControllerConfig config = controller.getControllerConfig(); DLedgerController newController = launchController(config.getControllerDLegerGroup(), config.getControllerDLegerPeers(), config.getControllerDLegerSelfId(), false); controllers.add(newController); newController.startup(); } } DLedgerController newLeader = waitLeader(controllers); setBrokerAlivePredicate(newLeader, 1L); // Check if the statemachine is stale final RemotingCommand resp = newLeader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)). get(10, TimeUnit.SECONDS); final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) resp.readCustomHeader(); assertEquals(1, replicaInfo.getMasterBrokerId().longValue()); assertEquals(1, replicaInfo.getMasterEpoch().intValue()); // Register broker's lifecycle listener AtomicBoolean atomicBoolean = new AtomicBoolean(false); newLeader.registerBrokerLifecycleListener((clusterName, brokerName, brokerId) -> { assertEquals(DEFAULT_BROKER_NAME, brokerName); atomicBoolean.set(true); }); Thread.sleep(2000); assertTrue(atomicBoolean.get()); } @Test public void testAllReplicasShutdownAndRestartWithUnEnableElectUnCleanMaster() throws Exception { final DLedgerController leader = mockMetaData(false); final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(1L); assertTrue(alterNewInSyncSet(leader, DEFAULT_BROKER_NAME, 1L, 1, newSyncStateSet, 2)); // Now we trigger electMaster api, which means the old master is shutdown and want to elect a new master. // However, the syncStateSet in statemachine is {1}, not more replicas can be elected as master, it will be failed. final ElectMasterRequestHeader electRequest = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); setBrokerElectPolicy(leader, 1L); leader.electMaster(electRequest).get(10, TimeUnit.SECONDS); final RemotingCommand resp = leader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)). get(10, TimeUnit.SECONDS); final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) resp.readCustomHeader(); final SyncStateSet syncStateSet = RemotingSerializable.decode(resp.getBody(), SyncStateSet.class); assertEquals(syncStateSet.getSyncStateSet(), newSyncStateSet); assertEquals(null, replicaInfo.getMasterAddress()); assertEquals(2, replicaInfo.getMasterEpoch().intValue()); // Now, we start broker - id[2]address[127.0.0.1:9001] to try elect, but it was not in syncStateSet, so it will not be elected as master. final ElectMasterRequestHeader request1 = ElectMasterRequestHeader.ofBrokerTrigger(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, 2L); final ElectMasterResponseHeader r1 = (ElectMasterResponseHeader) leader.electMaster(request1).get(10, TimeUnit.SECONDS).readCustomHeader(); assertEquals(null, r1.getMasterBrokerId()); assertEquals(null, r1.getMasterAddress()); // Now, we start broker - id[1]address[127.0.0.1:9000] to try elect, it will be elected as master setBrokerElectPolicy(leader); final ElectMasterRequestHeader request2 = ElectMasterRequestHeader.ofBrokerTrigger(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, 1L); final ElectMasterResponseHeader r2 = (ElectMasterResponseHeader) leader.electMaster(request2).get(10, TimeUnit.SECONDS).readCustomHeader(); assertEquals(1L, r2.getMasterBrokerId().longValue()); assertEquals(DEFAULT_IP[0], r2.getMasterAddress()); assertEquals(3, r2.getMasterEpoch().intValue()); } @Test public void testEnableElectUnCleanMaster() throws Exception { final DLedgerController leader = mockMetaData(true); final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(1L); assertTrue(alterNewInSyncSet(leader, DEFAULT_BROKER_NAME, 1L, 1, newSyncStateSet, 2)); // Now we trigger electMaster api, which means the old master is shutdown and want to elect a new master. // However, event if the syncStateSet in statemachine is {DEFAULT_IP[0]} // the option {enableElectUncleanMaster = true}, so the controller sill can elect a new master final ElectMasterRequestHeader electRequest = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); setBrokerElectPolicy(leader, 1L); final CompletableFuture future = leader.electMaster(electRequest); future.get(10, TimeUnit.SECONDS); final RemotingCommand resp = leader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)).get(10, TimeUnit.SECONDS); final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) resp.readCustomHeader(); final SyncStateSet syncStateSet = RemotingSerializable.decode(resp.getBody(), SyncStateSet.class); final HashSet newSyncStateSet2 = new HashSet<>(); newSyncStateSet2.add(replicaInfo.getMasterBrokerId()); assertEquals(syncStateSet.getSyncStateSet(), newSyncStateSet2); assertNotEquals(1L, replicaInfo.getMasterBrokerId().longValue()); assertNotEquals(DEFAULT_IP[0], replicaInfo.getMasterAddress()); assertEquals(2, replicaInfo.getMasterEpoch().intValue()); } @Test public void testChangeControllerLeader() throws Exception { final DLedgerController leader = mockMetaData(false); leader.shutdown(); this.controllers.remove(leader); // Wait leader again final DLedgerController newLeader = waitLeader(this.controllers); assertNotNull(newLeader); RemotingCommand response = await().atMost(Duration.ofSeconds(10)).until(() -> { final RemotingCommand resp = newLeader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)).get(10, TimeUnit.SECONDS); if (resp.getCode() == ResponseCode.SUCCESS) { return resp; } return null; }, item -> item != null); final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) response.readCustomHeader(); final SyncStateSet syncStateSetResult = RemotingSerializable.decode(response.getBody(), SyncStateSet.class); assertEquals(replicaInfo.getMasterAddress(), DEFAULT_IP[0]); assertEquals(1, replicaInfo.getMasterEpoch().intValue()); final HashSet syncStateSet = new HashSet<>(); syncStateSet.add(1L); syncStateSet.add(2L); syncStateSet.add(3L); assertEquals(syncStateSetResult.getSyncStateSet(), syncStateSet); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/DefaultBrokerHeartbeatManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.controller.BrokerHeartbeatManager; import org.apache.rocketmq.controller.impl.heartbeat.DefaultBrokerHeartbeatManager; import org.junit.Before; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertTrue; public class DefaultBrokerHeartbeatManagerTest { private BrokerHeartbeatManager heartbeatManager; @Before public void init() { final ControllerConfig config = new ControllerConfig(); config.setScanNotActiveBrokerInterval(2000); this.heartbeatManager = new DefaultBrokerHeartbeatManager(config); this.heartbeatManager.initialize(); this.heartbeatManager.start(); } @Test public void testDetectBrokerAlive() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); this.heartbeatManager.registerBrokerLifecycleListener((clusterName, brokerName, brokerId) -> { latch.countDown(); }); this.heartbeatManager.onBrokerHeartbeat("cluster1", "broker1", "127.0.0.1:7000", 1L, 3000L, null, 1, 1L, -1L, 0); assertTrue(latch.await(5000, TimeUnit.MILLISECONDS)); this.heartbeatManager.shutdown(); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/RaftBrokerHeartBeatManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import io.netty.channel.DefaultChannelPromise; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.controller.impl.heartbeat.BrokerIdentityInfo; import org.apache.rocketmq.controller.impl.heartbeat.RaftBrokerHeartBeatManager; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RaftBrokerHeartBeatManagerTest { @Mock private JRaftController jRaftController; @Mock private Channel brokerChannel; private RaftBrokerHeartBeatManager heartbeatManager; private final ControllerConfig config = new ControllerConfig(); @Before public void init() { when(jRaftController.isLeaderState()).thenReturn(true); config.setScanNotActiveBrokerInterval(1000); this.heartbeatManager = new RaftBrokerHeartBeatManager(config); this.heartbeatManager.setController(jRaftController); this.heartbeatManager.initialize(); this.heartbeatManager.start(); } @Test public void testDetectBrokerAlive() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); this.heartbeatManager.registerBrokerLifecycleListener((clusterName, brokerName, brokerId) -> { latch.countDown(); // onBrokerInactive }); String clusterName = "cluster-1"; String brokerName = "broker-1"; String brokerAddr = "127.0.0.1:10911"; long brokerId = 1L; RemotingCommand onBrokerHeartbeat = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); RemotingCommand checkNotActiveResp1 = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); checkNotActiveResp1.setBody(JSON.toJSONBytes(Collections.emptyList())); RemotingCommand checkNotActiveResp2 = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); checkNotActiveResp2.setBody(JSON.toJSONBytes(Collections.singletonList(new BrokerIdentityInfo(clusterName, brokerName, brokerId)))); when(jRaftController.onBrokerHeartBeat(any())) .thenReturn(CompletableFuture.completedFuture(onBrokerHeartbeat)); when(jRaftController.checkNotActiveBroker(any())) .thenReturn(CompletableFuture.completedFuture(checkNotActiveResp1)) .thenReturn(CompletableFuture.completedFuture(checkNotActiveResp2)); DefaultChannelPromise channelPromise = new DefaultChannelPromise(brokerChannel); channelPromise.setSuccess(); when(brokerChannel.close()).thenReturn(channelPromise); this.heartbeatManager.onBrokerHeartbeat(clusterName, brokerName, brokerAddr, brokerId, 3000L, brokerChannel, 1, 1L, -1L, 0); assertTrue(latch.await(5000, TimeUnit.MILLISECONDS)); this.heartbeatManager.shutdown(); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/event/EventSerializerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.common.utils.FastJsonSerializer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class EventSerializerTest { @Mock private FastJsonSerializer serializer; private final EventSerializer eventSerializer = new EventSerializer(); @Before public void init() throws IllegalAccessException { FieldUtils.writeDeclaredField(eventSerializer, "serializer", serializer, true); } @Test public void testSerializeValidEventMessageShouldReturnSerializedData() { EventMessage eventMessage = mock(EventMessage.class); EventType eventType = EventType.APPLY_BROKER_ID_EVENT; when(eventMessage.getEventType()).thenReturn(eventType); when(serializer.serialize(eventMessage)).thenReturn("{\"event\":\"APPLY_BROKER_ID_EVENT\"}".getBytes()); byte[] result = eventSerializer.serialize(eventMessage); assertNotNull(result); } @Test public void testSerializeEventMessageWithNoEventType() { EventMessage eventMessage = mock(EventMessage.class); when(eventMessage.getEventType()).thenReturn(null); assertThrows(NullPointerException.class, () -> eventSerializer.serialize(eventMessage)); } @Test public void testSerializeSerializerReturnsNullShouldReturnNull() { EventMessage eventMessage = mock(EventMessage.class); EventType eventType = EventType.READ_EVENT; when(eventMessage.getEventType()).thenReturn(eventType); when(serializer.serialize(eventMessage)).thenReturn(null); byte[] result = eventSerializer.serialize(eventMessage); assertNull(result); } @Test public void testSerializeSerializerThrowsException() { EventMessage eventMessage = mock(EventMessage.class); EventType eventType = EventType.ELECT_MASTER_EVENT; when(eventMessage.getEventType()).thenReturn(eventType); when(serializer.serialize(eventMessage)).thenThrow(new RuntimeException("Serialization error")); assertThrows(RuntimeException.class, () -> eventSerializer.serialize(eventMessage)); } @Test public void testDeserializeBytesLessThanTwoReturnsNull() { byte[] bytes = new byte[1]; assertNull(eventSerializer.deserialize(bytes)); } @Test public void testDeserializeInvalidEventIdReturnsNull() { assertNull(eventSerializer.deserialize(new byte[]{0, 0xF})); } @Test public void testDeserializeValidEventTypeReturnsEventMessage() throws SerializationException { byte[] data = new byte[]{0, 0xF}; byte[] bytes = new byte[]{0, (byte) EventType.ALTER_SYNC_STATE_SET_EVENT.getId(), data[0], data[1]}; AlterSyncStateSetEvent alterSyncStateSetEvent = mock(AlterSyncStateSetEvent.class); when(serializer.deserialize(any(byte[].class), eq(AlterSyncStateSetEvent.class))).thenReturn(alterSyncStateSetEvent); EventMessage result = eventSerializer.deserialize(bytes); assertNotNull(result); assertTrue(result instanceof AlterSyncStateSetEvent); } @Test public void testDeserializeSerializerThrowsException() throws SerializationException { byte[] data = new byte[]{0, 0xF}; byte[] bytes = new byte[]{0, (byte) EventType.ALTER_SYNC_STATE_SET_EVENT.getId(), data[0], data[1]}; when(serializer.deserialize(any(byte[].class), eq(AlterSyncStateSetEvent.class))).thenThrow(new SerializationException("Deserialization failed")); assertThrows(SerializationException.class, () -> eventSerializer.deserialize(bytes)); } @Test public void testDeserializeValidEventTypeUnknownEventReturnsNull() throws SerializationException { byte[] data = new byte[]{0, 0xF}; byte[] bytes = new byte[]{0, (short) 99, data[0], data[1]}; assertNull(eventSerializer.deserialize(bytes)); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/event/ListEventSerializerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.event; import org.apache.commons.lang3.SerializationException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ListEventSerializerTest { @Mock private Logger logger; @Test public void testSerializeEmptyList() { List events = Collections.emptyList(); byte[] result = ListEventSerializer.serialize(events, null); assertNotNull(result); assertEquals(0, result.length); } @Test public void testSerializeValidEventMessage() { EventMessage eventMessage = new ElectMasterEvent("brokerA", 0L); List events = Collections.singletonList(eventMessage); byte[] result = ListEventSerializer.serialize(events, null); assertNotNull(result); assertTrue(result.length > 0); } @Test public void testSerializeEventMessageWithNullEventType() { EventMessage eventMessage = mock(EventMessage.class); when(eventMessage.getEventType()).thenReturn(null); List events = Collections.singletonList(eventMessage); assertThrows(NullPointerException.class, () -> ListEventSerializer.serialize(events, logger)); } @Test public void testDeserializeBytesIsNull() throws SerializationException { List result = ListEventSerializer.deserialize(null, logger); assertNotNull(result); assertTrue(result.isEmpty()); } @Test public void testDeserializeBytesLengthLessThanSix() throws SerializationException { byte[] bytes = new byte[5]; List result = ListEventSerializer.deserialize(bytes, logger); assertNotNull(result); assertTrue(result.isEmpty()); } @Test public void testDeserializeValidBytesWithKnownEventType() throws SerializationException { byte[] bytes = new byte[]{0x01, 0x00, 0x06, 0x00, 0x00, 0x00}; assertNotNull(ListEventSerializer.deserialize(bytes, logger)); } @Test public void testDeserializeException() throws SerializationException { byte[] bytes = new byte[]{0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00}; assertThrows(ArrayIndexOutOfBoundsException.class, () -> ListEventSerializer.deserialize(bytes, logger)); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/heartbeat/RaftBrokerHeartBeatManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.heartbeat; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.JraftConfig; import org.apache.rocketmq.controller.impl.JRaftController; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoResponse; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RaftBrokerHeartBeatManagerTest { @Mock private JRaftController controller; private RaftBrokerHeartBeatManager raftBrokerHeartBeatManager; @Before public void init() throws IllegalAccessException { ControllerConfig controllerConfig = new ControllerConfig(); raftBrokerHeartBeatManager = new RaftBrokerHeartBeatManager(controllerConfig); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "controller", controller, true); } @Test public void testOnBrokerHeartbeatSuccess() { Channel channel = mock(Channel.class); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); when(controller.onBrokerHeartBeat(any())).thenReturn(future); raftBrokerHeartBeatManager.onBrokerHeartbeat("cluster1", "broker1", "127.0.0.1:10911", 1L, 3000L, channel, 1, 1000L, 500L, 1); verify(channel, never()).close(); } @Test public void testOnBrokerHeartbeatLeaderNotAvailable() { Channel channel = mock(Channel.class); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.CONTROLLER_NOT_LEADER, "Not Leader")); when(controller.onBrokerHeartBeat(any())).thenReturn(future); raftBrokerHeartBeatManager.onBrokerHeartbeat("cluster1", "broker1", "127.0.0.1:10911", 1L, 3000L, channel, 1, 1000L, 500L, 1); verify(channel, never()).close(); } @Test public void testOnBrokerHeartbeatException() throws Exception { Channel channel = mock(Channel.class); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); when(controller.onBrokerHeartBeat(any())).thenReturn(future); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "brokerChannelIdentityInfoMap", null, true); assertThrows(NullPointerException.class, () -> raftBrokerHeartBeatManager.onBrokerHeartbeat("cluster1", "broker1", "127.0.0.1:10911", 1L, 3000L, channel, 1, 1000L, 500L, 1)); } @Test public void testOnBrokerChannelCloseBrokerIdentityInfoNotNullSuccess() throws Exception { Channel channel = mock(Channel.class); BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo("cluster1", "broker1", 1L); Map brokerChannelIdentityInfoMap = new HashMap<>(); brokerChannelIdentityInfoMap.put(channel, brokerIdentityInfo); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "brokerChannelIdentityInfoMap", brokerChannelIdentityInfoMap, true); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); when(controller.onBrokerCloseChannel(any(BrokerCloseChannelRequest.class))).thenReturn(future); raftBrokerHeartBeatManager.onBrokerChannelClose(channel); verify(controller).onBrokerCloseChannel(any(BrokerCloseChannelRequest.class)); } @Test public void testOnBrokerChannelCloseBrokerIdentityInfoNotNullException() throws Exception { Channel channel = mock(Channel.class); BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo("cluster1", "broker1", 1L); Map brokerChannelIdentityInfoMap = new HashMap<>(); brokerChannelIdentityInfoMap.put(channel, brokerIdentityInfo); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "brokerChannelIdentityInfoMap", brokerChannelIdentityInfoMap, true); CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new ExecutionException(new RuntimeException("Test Exception"))); when(controller.onBrokerCloseChannel(any(BrokerCloseChannelRequest.class))).thenReturn(future); raftBrokerHeartBeatManager.onBrokerChannelClose(channel); verify(controller).onBrokerCloseChannel(any(BrokerCloseChannelRequest.class)); } @Test public void testOnBrokerChannelCloseBrokerIdentityInfoNull() { Channel channel = mock(Channel.class); raftBrokerHeartBeatManager.onBrokerChannelClose(channel); verify(controller, never()).onBrokerCloseChannel(any(BrokerCloseChannelRequest.class)); } @Test public void testOnBrokerChannelCloseBrokerIdentityInfoNotNullTimeoutException() throws Exception { Channel channel = mock(Channel.class); BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo("cluster1", "broker1", 1L); Map brokerChannelIdentityInfoMap = new HashMap<>(); brokerChannelIdentityInfoMap.put(channel, brokerIdentityInfo); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "brokerChannelIdentityInfoMap", brokerChannelIdentityInfoMap, true); CompletableFuture future = new CompletableFuture<>(); when(controller.onBrokerCloseChannel(any(BrokerCloseChannelRequest.class))).thenReturn(future); raftBrokerHeartBeatManager.onBrokerChannelClose(channel); verify(controller).onBrokerCloseChannel(any(BrokerCloseChannelRequest.class)); } @Test public void testScanNotActiveBrokerSuccess() throws Exception { ControllerConfig controllerConfig = new ControllerConfig(); JraftConfig jraftConfig = new JraftConfig(); jraftConfig.setjRaftScanWaitTimeoutMs(10000); controllerConfig.setJraftConfig(jraftConfig); raftBrokerHeartBeatManager = new RaftBrokerHeartBeatManager(controllerConfig); controller = mock(JRaftController.class); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "controller", controller, true); when(controller.isLeaderState()).thenReturn(true); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "firstReceivedHeartbeatTime", 1000, true); List inactiveBrokers = new ArrayList<>(); BrokerIdentityInfo brokerInfo = new BrokerIdentityInfo("testCluster", "testBroker", 1L); inactiveBrokers.add(brokerInfo); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success"); response.setBody(JSON.toJSONString(inactiveBrokers).getBytes()); CompletableFuture future = CompletableFuture.completedFuture(response); when(controller.checkNotActiveBroker(any())).thenReturn(future); Channel channel = mock(Channel.class); Map brokerChannelMap = new HashMap<>(); brokerChannelMap.put(channel, brokerInfo); FieldUtils.writeDeclaredField(raftBrokerHeartBeatManager, "brokerChannelIdentityInfoMap", brokerChannelMap, true); Method method = RaftBrokerHeartBeatManager.class.getDeclaredMethod("scanNotActiveBroker"); method.setAccessible(true); method.invoke(raftBrokerHeartBeatManager); verify(controller).checkNotActiveBroker(any(CheckNotActiveBrokerRequest.class)); } @Test public void testGetBrokerLiveInfoSuccess() throws Exception { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo(clusterName, brokerName, brokerId); BrokerLiveInfo expectedBrokerLiveInfo = new BrokerLiveInfo(brokerName, "127.0.0.1:10911", brokerId, System.currentTimeMillis(), 3000L, null, 1, 1000L, 500); Map expectedResponse = new HashMap<>(); expectedResponse.put(brokerIdentityInfo, expectedBrokerLiveInfo); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(expectedResponse).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); BrokerLiveInfo brokerLiveInfo = raftBrokerHeartBeatManager.getBrokerLiveInfo(clusterName, brokerName, brokerId); assertEquals(expectedBrokerLiveInfo.getBrokerName(), brokerLiveInfo.getBrokerName()); assertEquals(expectedBrokerLiveInfo.getBrokerAddr(), brokerLiveInfo.getBrokerAddr()); assertEquals(expectedBrokerLiveInfo.getBrokerId(), brokerLiveInfo.getBrokerId()); } @Test public void testGetBrokerLiveInfoAllBrokers() throws Exception { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; BrokerIdentityInfo brokerIdentityInfo = new BrokerIdentityInfo(clusterName, brokerName, brokerId); BrokerLiveInfo expectedBrokerLiveInfo = new BrokerLiveInfo(brokerName, "127.0.0.1:10911", brokerId, System.currentTimeMillis(), 3000L, null, 1, 1000L, 500); Map expectedResponse = new HashMap<>(); expectedResponse.put(brokerIdentityInfo, expectedBrokerLiveInfo); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(expectedResponse).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); BrokerLiveInfo brokerLiveInfo = raftBrokerHeartBeatManager.getBrokerLiveInfo(null, null, null); assertNull(brokerLiveInfo); } @Test public void testIsBrokerActiveBrokerActive() throws Exception { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; BrokerLiveInfo brokerLiveInfo = new BrokerLiveInfo(brokerName, "127.0.0.1:10911", brokerId, System.currentTimeMillis(), 3000L, null, 1, 1000L, 500); Map responseMap = new HashMap<>(); responseMap.put(new BrokerIdentityInfo(clusterName, brokerName, brokerId), brokerLiveInfo); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(responseMap).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); assertTrue(raftBrokerHeartBeatManager.isBrokerActive(clusterName, brokerName, brokerId)); } @Test public void testIsBrokerActiveBrokerNotActive() throws Exception { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; BrokerLiveInfo brokerLiveInfo = new BrokerLiveInfo(brokerName, "127.0.0.1:10911", brokerId, System.currentTimeMillis() - 4000L, 3000L, null, 1, 1000L, 500); Map responseMap = new HashMap<>(); responseMap.put(new BrokerIdentityInfo(clusterName, brokerName, brokerId), brokerLiveInfo); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(responseMap).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); assertFalse(raftBrokerHeartBeatManager.isBrokerActive(clusterName, brokerName, brokerId)); } @Test public void testIsBrokerActiveException() { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new ExecutionException(new RuntimeException("Test Exception"))); when(controller.getBrokerLiveInfo(any())).thenReturn(future); assertFalse(raftBrokerHeartBeatManager.isBrokerActive(clusterName, brokerName, brokerId)); } @Test public void testIsBrokerActiveNoInfo() throws Exception { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(new HashMap()).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); assertFalse(raftBrokerHeartBeatManager.isBrokerActive(clusterName, brokerName, brokerId)); } @Test public void testIsBrokerActiveInvalidResponseCode() { String clusterName = "cluster1"; String brokerName = "broker1"; Long brokerId = 1L; CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.RPC_TIME_OUT, "Timeout")); when(controller.getBrokerLiveInfo(any())).thenReturn(future); assertFalse(raftBrokerHeartBeatManager.isBrokerActive(clusterName, brokerName, brokerId)); } @Test public void testGetActiveBrokersNumAllBrokers() throws Exception { String clusterName1 = "cluster1"; String brokerName1 = "broker1"; Long brokerId1 = 1L; String clusterName2 = "cluster2"; String brokerName2 = "broker2"; Long brokerId2 = 2L; BrokerIdentityInfo brokerIdentityInfo1 = new BrokerIdentityInfo(clusterName1, brokerName1, brokerId1); BrokerIdentityInfo brokerIdentityInfo2 = new BrokerIdentityInfo(clusterName2, brokerName2, brokerId2); BrokerLiveInfo brokerLiveInfo1 = new BrokerLiveInfo(brokerName1, "127.0.0.1:10911", brokerId1, System.currentTimeMillis(), 3000L, null, 1, 1000L, 500); BrokerLiveInfo brokerLiveInfo2 = new BrokerLiveInfo(brokerName2, "127.0.0.1:10912", brokerId2, System.currentTimeMillis(), 3000L, null, 1, 1000L, 500); Map responseMap = new HashMap<>(); responseMap.put(brokerIdentityInfo1, brokerLiveInfo1); responseMap.put(brokerIdentityInfo2, brokerLiveInfo2); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(responseMap).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); Map> activeBrokersNum = raftBrokerHeartBeatManager.getActiveBrokersNum(); assertEquals(2, activeBrokersNum.size()); assertEquals(1, activeBrokersNum.get(clusterName1).size()); assertEquals(1, activeBrokersNum.get(clusterName2).size()); assertEquals(1, (int) activeBrokersNum.get(clusterName1).get(brokerName1)); assertEquals(1, (int) activeBrokersNum.get(clusterName2).get(brokerName2)); } @Test public void testGetActiveBrokersNum() throws Exception { String clusterName1 = "cluster1"; String brokerName1 = "broker1"; Long brokerId1 = 1L; String clusterName2 = "cluster2"; String brokerName2 = "broker2"; Long brokerId2 = 2L; BrokerIdentityInfo brokerIdentityInfo1 = new BrokerIdentityInfo(clusterName1, brokerName1, brokerId1); BrokerIdentityInfo brokerIdentityInfo2 = new BrokerIdentityInfo(clusterName2, brokerName2, brokerId2); BrokerLiveInfo brokerLiveInfo1 = new BrokerLiveInfo(brokerName1, "127.0.0.1:10911", brokerId1, System.currentTimeMillis(), 3000L, null, 1, 1000L, 500); BrokerLiveInfo brokerLiveInfo2 = new BrokerLiveInfo(brokerName2, "127.0.0.1:10912", brokerId2, System.currentTimeMillis() - 4000L, 3000L, null, 1, 1000L, 500); Map responseMap = new HashMap<>(); responseMap.put(brokerIdentityInfo1, brokerLiveInfo1); responseMap.put(brokerIdentityInfo2, brokerLiveInfo2); CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(responseMap).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); Map> activeBrokersNum = raftBrokerHeartBeatManager.getActiveBrokersNum(); assertEquals(1, activeBrokersNum.size()); } @Test public void testGetActiveBrokersNumException() { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new ExecutionException(new RuntimeException("Test Exception"))); when(controller.getBrokerLiveInfo(any())).thenReturn(future); Map> activeBrokersNum = raftBrokerHeartBeatManager.getActiveBrokersNum(); assertTrue(activeBrokersNum.isEmpty()); } @Test public void testGetActiveBrokersNumNoBrokers() throws Exception { CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "Success")); future.get().writeCustomHeader(new GetBrokerLiveInfoResponse()); future.get().setBody(JSON.toJSONString(new HashMap()).getBytes()); when(controller.getBrokerLiveInfo(any())).thenReturn(future); Map> activeBrokersNum = raftBrokerHeartBeatManager.getActiveBrokersNum(); assertTrue(activeBrokersNum.isEmpty()); } @Test public void testGetActiveBrokersNumInvalidResponseCode() { CompletableFuture future = CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.RPC_TIME_OUT, "Timeout")); when(controller.getBrokerLiveInfo(any())).thenReturn(future); Map> activeBrokersNum = raftBrokerHeartBeatManager.getActiveBrokersNum(); assertTrue(activeBrokersNum.isEmpty()); } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/manager/RaftReplicasInfoManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.manager; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.controller.impl.heartbeat.BrokerIdentityInfo; import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; import org.apache.rocketmq.controller.impl.task.BrokerCloseChannelRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerRequest; import org.apache.rocketmq.controller.impl.task.CheckNotActiveBrokerResponse; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoRequest; import org.apache.rocketmq.controller.impl.task.GetBrokerLiveInfoResponse; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventRequest; import org.apache.rocketmq.controller.impl.task.RaftBrokerHeartBeatEventResponse; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(MockitoJUnitRunner.class) public class RaftReplicasInfoManagerTest { @Mock private ControllerConfig controllerConfig; private RaftReplicasInfoManager raftReplicasInfoManager; @Before public void init() { raftReplicasInfoManager = new RaftReplicasInfoManager(controllerConfig); } @Test public void testGetBrokerLiveInfoBrokerIdentityInfoIsNullReturnsAllBrokersInfo() throws IllegalAccessException { List brokerIdentityInfos = createBrokerIdentityInfos(2); List brokerLiveInfos = createBrokerLiveInfos(2); Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(brokerIdentityInfos.get(0), brokerLiveInfos.get(0)); brokerLiveTable.put(brokerIdentityInfos.get(1), brokerLiveInfos.get(1)); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); GetBrokerLiveInfoRequest request = new GetBrokerLiveInfoRequest(); ControllerResult result = raftReplicasInfoManager.getBrokerLiveInfo(request); assertNotNull(result); assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); } @Test public void testGetBrokerLiveInfoBrokerIdentityInfoExistsReturnsBrokerInfo() throws IllegalAccessException { BrokerIdentityInfo brokerIdentityInfo = createBrokerIdentityInfo(); Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(brokerIdentityInfo, createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); GetBrokerLiveInfoRequest request = new GetBrokerLiveInfoRequest(brokerIdentityInfo); ControllerResult result = raftReplicasInfoManager.getBrokerLiveInfo(request); assertNotNull(result); assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); } @Test public void testGetBrokerLiveInfoBrokerIdentityInfoNotExistsReturnsError() { GetBrokerLiveInfoRequest request = new GetBrokerLiveInfoRequest(createBrokerIdentityInfo()); ControllerResult result = raftReplicasInfoManager.getBrokerLiveInfo(request); assertNotNull(result); assertEquals(ResponseCode.CONTROLLER_BROKER_LIVE_INFO_NOT_EXISTS, result.getResponseCode()); } @Test public void testOnBrokerHeartBeatNewBrokerRegistered() { RaftBrokerHeartBeatEventRequest request = new RaftBrokerHeartBeatEventRequest(createBrokerIdentityInfo(), createBrokerLiveInfo()); ControllerResult result = raftReplicasInfoManager.onBrokerHeartBeat(request); assertNotNull(result); assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); } @Test public void testOnBrokerHeartBeatExistingBrokerUpdate() throws IllegalAccessException { BrokerIdentityInfo brokerIdentityInfo = createBrokerIdentityInfo(); Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(brokerIdentityInfo, createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); BrokerLiveInfo updatedInfo = new BrokerLiveInfo("brokerName1", "brokerAddr1", 1L, System.currentTimeMillis(), 2000L, null, 2, 200L, 2); RaftBrokerHeartBeatEventRequest request = new RaftBrokerHeartBeatEventRequest(brokerIdentityInfo, updatedInfo); ControllerResult result = raftReplicasInfoManager.onBrokerHeartBeat(request); assertNotNull(result); assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); } @Test public void testOnBrokerCloseChannelBrokerIdentityInfoIsNullLogsWarningAndReturnsResult() { assertNotNull(raftReplicasInfoManager.onBrokerCloseChannel(new BrokerCloseChannelRequest())); } @Test public void testCheckNotActiveBrokerNoBrokersInTableReturnsEmptyList() { CheckNotActiveBrokerRequest request = new CheckNotActiveBrokerRequest(); ControllerResult result = raftReplicasInfoManager.checkNotActiveBroker(request); assertNotNull(result); assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); } @Test public void testCheckNotActiveBrokerBrokerLiveTableNotEmptyIdentifiesNotActiveBrokers() throws IllegalAccessException { List brokerIdentityInfos = createBrokerIdentityInfos(2); List brokerLiveInfos = createBrokerLiveInfos(2); Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(brokerIdentityInfos.get(0), brokerLiveInfos.get(0)); brokerLiveTable.put(brokerIdentityInfos.get(1), brokerLiveInfos.get(1)); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); CheckNotActiveBrokerRequest request = new CheckNotActiveBrokerRequest(); ControllerResult result = raftReplicasInfoManager.checkNotActiveBroker(request); assertNotNull(result); assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); assertNotNull(result.getBody()); } @Test public void testCheckNotActiveBrokerSerializeErrorSetsErrorRemark() throws IllegalAccessException { Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(createBrokerIdentityInfo(), createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); CheckNotActiveBrokerRequest request = new CheckNotActiveBrokerRequest(); ControllerResult result = raftReplicasInfoManager.checkNotActiveBroker(request); assertNotNull(result); } @Test public void testIsBrokerActiveBrokerLiveInfoNotNullAndActiveReturnsTrue() throws IllegalAccessException { Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(createBrokerIdentityInfo(), createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); long invokeTime = System.currentTimeMillis() + 500; boolean brokerActive = raftReplicasInfoManager.isBrokerActive("cluster0", "broker0", 0L, invokeTime); assertTrue(brokerActive); } @Test public void testIsBrokerActiveBrokerLiveInfoNotNullAndNotActiveReturnsFalse() throws IllegalAccessException { Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(createBrokerIdentityInfo(), createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); long invokeTime = System.currentTimeMillis(); assertFalse(raftReplicasInfoManager.isBrokerActive("cluster1", "broker1", 1L, invokeTime)); } @Test public void testIsBrokerActiveBrokerLiveInfoNullReturnsFalse() { assertFalse(raftReplicasInfoManager.isBrokerActive("cluster1", "broker1", 1L, System.currentTimeMillis())); } @Test public void testSerializeWithPopulatedTablesReturnsByteArray() throws Throwable { Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(createBrokerIdentityInfo(), createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); byte[] result = raftReplicasInfoManager.serialize(); assertNotNull(result); assertTrue(result.length > 0); } @Test public void testDeserializeFromValidDataSuccess() throws Throwable { BrokerIdentityInfo brokerIdentityInfo = createBrokerIdentityInfo(); Map brokerLiveTable = new HashMap<>(); brokerLiveTable.put(brokerIdentityInfo, createBrokerLiveInfo()); FieldUtils.writeDeclaredField(raftReplicasInfoManager, "brokerLiveTable", brokerLiveTable, true); raftReplicasInfoManager.deserializeFrom(raftReplicasInfoManager.serialize()); assertNotNull(brokerLiveTable); assertEquals(1, brokerLiveTable.size()); assertTrue(brokerLiveTable.containsKey(brokerIdentityInfo)); } @Test public void testDeserializeFromInvalidDataExceptionThrown() { byte[] invalidData = new byte[]{0x00, 0x01, 0x02, 0x03}; try { raftReplicasInfoManager.deserializeFrom(invalidData); fail("Expected an exception to be thrown"); } catch (Throwable e) { assertTrue(e instanceof ArrayIndexOutOfBoundsException); } } private BrokerIdentityInfo createBrokerIdentityInfo() { return createBrokerIdentityInfos(1).get(0); } private List createBrokerIdentityInfos(final int count) { List result = new ArrayList<>(); for (int i = 0; i < count; i++) { result.add(new BrokerIdentityInfo("cluster" + i, "broker" + i, (long) i)); } return result; } private BrokerLiveInfo createBrokerLiveInfo() { return createBrokerLiveInfos(1).get(0); } private List createBrokerLiveInfos(final int count) { List result = new ArrayList<>(); for (int i = 0; i < count; i++) { result.add(new BrokerLiveInfo("brokerName" + i, "brokerAddr" + i, i, System.currentTimeMillis(), 1000L, null, 1, 100L, 1)); } return result; } } ================================================ FILE: controller/src/test/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.controller.impl.manager; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.controller.elect.ElectPolicy; import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy; import org.apache.rocketmq.controller.helper.BrokerValidPredicate; import org.apache.rocketmq.controller.impl.event.ControllerResult; import org.apache.rocketmq.controller.impl.event.ElectMasterEvent; import org.apache.rocketmq.controller.impl.event.EventMessage; import org.apache.rocketmq.controller.impl.heartbeat.DefaultBrokerHeartbeatManager; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerResponseHeader; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import static org.apache.rocketmq.controller.ControllerTestBase.DEFAULT_BROKER_NAME; import static org.apache.rocketmq.controller.ControllerTestBase.DEFAULT_CLUSTER_NAME; import static org.apache.rocketmq.controller.ControllerTestBase.DEFAULT_IP; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class ReplicasInfoManagerTest { private ReplicasInfoManager replicasInfoManager; private DefaultBrokerHeartbeatManager heartbeatManager; private ControllerConfig config; @Before public void init() { this.config = new ControllerConfig(); this.config.setEnableElectUncleanMaster(false); this.config.setScanNotActiveBrokerInterval(300000000); this.replicasInfoManager = new ReplicasInfoManager(config); this.heartbeatManager = new DefaultBrokerHeartbeatManager(config); this.heartbeatManager.initialize(); this.heartbeatManager.start(); } @After public void destroy() { this.replicasInfoManager = null; this.heartbeatManager.shutdown(); this.heartbeatManager = null; } private BrokerReplicasInfo.ReplicasInfo getReplicasInfo(String brokerName) { ControllerResult syncStateData = this.replicasInfoManager.getSyncStateData(Arrays.asList(brokerName), (a, b, c) -> true); BrokerReplicasInfo replicasInfo = RemotingSerializable.decode(syncStateData.getBody(), BrokerReplicasInfo.class); return replicasInfo.getReplicasInfoTable().get(brokerName); } public void registerNewBroker(String clusterName, String brokerName, String brokerAddress, Long exceptBrokerId, Long exceptMasterBrokerId) { // Get next brokerId final GetNextBrokerIdRequestHeader getNextBrokerIdRequestHeader = new GetNextBrokerIdRequestHeader(clusterName, brokerName); final ControllerResult nextBrokerIdResult = this.replicasInfoManager.getNextBrokerId(getNextBrokerIdRequestHeader); Long nextBrokerId = nextBrokerIdResult.getResponse().getNextBrokerId(); String registerCheckCode = brokerAddress + ";" + System.currentTimeMillis(); // check response assertEquals(ResponseCode.SUCCESS, nextBrokerIdResult.getResponseCode()); assertEquals(exceptBrokerId, nextBrokerId); // Apply brokerId final ApplyBrokerIdRequestHeader applyBrokerIdRequestHeader = new ApplyBrokerIdRequestHeader(clusterName, brokerName, nextBrokerId, registerCheckCode); final ControllerResult applyBrokerIdResult = this.replicasInfoManager.applyBrokerId(applyBrokerIdRequestHeader); apply(applyBrokerIdResult.getEvents()); // check response assertEquals(ResponseCode.SUCCESS, applyBrokerIdResult.getResponseCode()); // check it in state machine BrokerReplicasInfo.ReplicasInfo replicasInfo = getReplicasInfo(brokerName); BrokerReplicasInfo.ReplicaIdentity replicaIdentity = replicasInfo.getNotInSyncReplicas().stream().filter(x -> x.getBrokerId().equals(nextBrokerId)).findFirst().get(); assertNotNull(replicaIdentity); assertEquals(brokerName, replicaIdentity.getBrokerName()); assertEquals(exceptBrokerId, replicaIdentity.getBrokerId()); assertEquals(brokerAddress, replicaIdentity.getBrokerAddress()); // register success final RegisterBrokerToControllerRequestHeader registerBrokerToControllerRequestHeader = new RegisterBrokerToControllerRequestHeader(clusterName, brokerName, exceptBrokerId, brokerAddress); ControllerResult registerSuccessResult = this.replicasInfoManager.registerBroker(registerBrokerToControllerRequestHeader, (a, b, c) -> true); apply(registerSuccessResult.getEvents()); // check response assertEquals(ResponseCode.SUCCESS, registerSuccessResult.getResponseCode()); assertEquals(exceptMasterBrokerId, registerSuccessResult.getResponse().getMasterBrokerId()); } public void brokerElectMaster(String clusterName, Long brokerId, String brokerName, String brokerAddress, boolean isFirstTryElect, boolean expectToBeElected) { this.brokerElectMaster(clusterName, brokerId, brokerName, brokerAddress, isFirstTryElect, expectToBeElected, (a, b, c) -> true); } public void brokerElectMaster(String clusterName, Long brokerId, String brokerName, String brokerAddress, boolean isFirstTryElect, boolean expectToBeElected, BrokerValidPredicate validPredicate) { final GetReplicaInfoResponseHeader replicaInfoBefore = this.replicasInfoManager.getReplicaInfo(new GetReplicaInfoRequestHeader(brokerName)).getResponse(); BrokerReplicasInfo.ReplicasInfo syncStateSetInfo = getReplicasInfo(brokerName); // Try elect itself as a master ElectMasterRequestHeader requestHeader = ElectMasterRequestHeader.ofBrokerTrigger(clusterName, brokerName, brokerId); final ControllerResult result = this.replicasInfoManager.electMaster(requestHeader, new DefaultElectPolicy(validPredicate, null)); apply(result.getEvents()); final GetReplicaInfoResponseHeader replicaInfoAfter = this.replicasInfoManager.getReplicaInfo(new GetReplicaInfoRequestHeader(brokerName)).getResponse(); final ElectMasterResponseHeader response = result.getResponse(); if (isFirstTryElect) { // it should be elected // check response assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); assertEquals(1, response.getMasterEpoch().intValue()); assertEquals(1, response.getSyncStateSetEpoch().intValue()); assertEquals(brokerAddress, response.getMasterAddress()); assertEquals(brokerId, response.getMasterBrokerId()); // check it in state machine assertEquals(brokerAddress, replicaInfoAfter.getMasterAddress()); assertEquals(1, replicaInfoAfter.getMasterEpoch().intValue()); assertEquals(brokerId, replicaInfoAfter.getMasterBrokerId()); } else { // failed because now master still exist if (replicaInfoBefore.getMasterBrokerId() != null && validPredicate.check(clusterName, brokerName, replicaInfoBefore.getMasterBrokerId())) { assertEquals(ResponseCode.CONTROLLER_MASTER_STILL_EXIST, result.getResponseCode()); assertEquals(replicaInfoBefore.getMasterAddress(), response.getMasterAddress()); assertEquals(replicaInfoBefore.getMasterEpoch(), response.getMasterEpoch()); assertEquals(replicaInfoBefore.getMasterBrokerId(), response.getMasterBrokerId()); assertEquals(replicaInfoBefore.getMasterBrokerId(), replicaInfoAfter.getMasterBrokerId()); return; } if (syncStateSetInfo.isExistInSync(brokerName, brokerId, brokerAddress) || this.config.isEnableElectUncleanMaster()) { // a new master can be elected successfully assertEquals(ResponseCode.SUCCESS, result.getResponseCode()); assertEquals(replicaInfoBefore.getMasterEpoch() + 1, replicaInfoAfter.getMasterEpoch().intValue()); if (expectToBeElected) { assertEquals(brokerAddress, response.getMasterAddress()); assertEquals(brokerId, response.getMasterBrokerId()); assertEquals(brokerAddress, replicaInfoAfter.getMasterAddress()); assertEquals(brokerId, replicaInfoAfter.getMasterBrokerId()); } } else { // failed because elect nothing assertEquals(ResponseCode.CONTROLLER_ELECT_MASTER_FAILED, result.getResponseCode()); } } } private boolean alterNewInSyncSet(String brokerName, Long brokerId, Integer masterEpoch, Set newSyncStateSet, Integer syncStateSetEpoch) { final AlterSyncStateSetRequestHeader alterRequest = new AlterSyncStateSetRequestHeader(brokerName, brokerId, masterEpoch); final ControllerResult result = this.replicasInfoManager.alterSyncStateSet(alterRequest, new SyncStateSet(newSyncStateSet, syncStateSetEpoch), (cluster, brokerName1, brokerId1) -> true); apply(result.getEvents()); final ControllerResult resp = this.replicasInfoManager.getReplicaInfo(new GetReplicaInfoRequestHeader(brokerName)); final GetReplicaInfoResponseHeader replicaInfo = resp.getResponse(); final SyncStateSet syncStateSet = RemotingSerializable.decode(resp.getBody(), SyncStateSet.class); assertArrayEquals(syncStateSet.getSyncStateSet().toArray(), newSyncStateSet.toArray()); assertEquals(syncStateSet.getSyncStateSetEpoch(), syncStateSetEpoch + 1); return true; } private void apply(final List events) { for (EventMessage event : events) { this.replicasInfoManager.applyEvent(event); } } public void mockMetaData() { registerNewBroker(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, null); registerNewBroker(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, null); registerNewBroker(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, null); brokerElectMaster(DEFAULT_CLUSTER_NAME, 1L, DEFAULT_BROKER_NAME, DEFAULT_IP[0], true, true); brokerElectMaster(DEFAULT_CLUSTER_NAME, 2L, DEFAULT_BROKER_NAME, DEFAULT_IP[1], false, false); brokerElectMaster(DEFAULT_CLUSTER_NAME, 3L, DEFAULT_BROKER_NAME, DEFAULT_IP[2], false, false); final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(1L); newSyncStateSet.add(2L); newSyncStateSet.add(3L); assertTrue(alterNewInSyncSet(DEFAULT_BROKER_NAME, 1L, 1, newSyncStateSet, 1)); } public void mockHeartbeatDataMasterStillAlive() { this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, 10000000000L, null, 1, 1L, -1L, 0); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, 10000000000L, null, 1, 2L, -1L, 0); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, 10000000000L, null, 1, 3L, -1L, 0); } public void mockHeartbeatDataHigherEpoch() { this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, -10000L, null, 1, 3L, -1L, 0); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, 10000000000L, null, 1, 2L, -1L, 0); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, 10000000000L, null, 0, 3L, -1L, 0); } public void mockHeartbeatDataHigherOffset() { this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, -10000L, null, 1, 3L, -1L, 0); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, 10000000000L, null, 1, 2L, -1L, 0); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, 10000000000L, null, 1, 3L, -1L, 0); } public void mockHeartbeatDataHigherPriority() { this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, -10000L, null, 1, 3L, -1L, 3); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, 10000000000L, null, 1, 3L, -1L, 2); this.heartbeatManager.onBrokerHeartbeat(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, 10000000000L, null, 1, 3L, -1L, 1); } @Test public void testRegisterBrokerSuccess() { mockMetaData(); BrokerReplicasInfo.ReplicasInfo replicasInfo = getReplicasInfo(DEFAULT_BROKER_NAME); assertEquals(1L, replicasInfo.getMasterBrokerId().longValue()); assertEquals(DEFAULT_IP[0], replicasInfo.getMasterAddress()); assertEquals(1, replicasInfo.getMasterEpoch()); assertEquals(2, replicasInfo.getSyncStateSetEpoch()); assertEquals(3, replicasInfo.getInSyncReplicas().size()); assertEquals(0, replicasInfo.getNotInSyncReplicas().size()); } @Test public void testRegisterWithMasterExistResp() { registerNewBroker(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, null); registerNewBroker(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, null); brokerElectMaster(DEFAULT_CLUSTER_NAME, 1L, DEFAULT_BROKER_NAME, DEFAULT_IP[0], true, true); brokerElectMaster(DEFAULT_CLUSTER_NAME, 2L, DEFAULT_BROKER_NAME, DEFAULT_IP[1], false, false); registerNewBroker(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, 1L); brokerElectMaster(DEFAULT_CLUSTER_NAME, 3L, DEFAULT_BROKER_NAME, DEFAULT_IP[2], false, false); BrokerReplicasInfo.ReplicasInfo replicasInfo = getReplicasInfo(DEFAULT_BROKER_NAME); assertEquals(1L, replicasInfo.getMasterBrokerId().longValue()); assertEquals(DEFAULT_IP[0], replicasInfo.getMasterAddress()); assertEquals(1, replicasInfo.getMasterEpoch()); assertEquals(1, replicasInfo.getSyncStateSetEpoch()); assertEquals(1, replicasInfo.getInSyncReplicas().size()); assertEquals(2, replicasInfo.getNotInSyncReplicas().size()); } @Test public void testRegisterWithOldMasterInactive() { mockMetaData(); // If now only broker-3 alive, it will be elected to be a new master brokerElectMaster(DEFAULT_CLUSTER_NAME, 3L, DEFAULT_BROKER_NAME, DEFAULT_IP[2], false, true, (a, b, c) -> c.equals(3L)); // Check in statemachine BrokerReplicasInfo.ReplicasInfo replicasInfo = getReplicasInfo(DEFAULT_BROKER_NAME); assertEquals(3L, replicasInfo.getMasterBrokerId().longValue()); assertEquals(DEFAULT_IP[2], replicasInfo.getMasterAddress()); assertEquals(2, replicasInfo.getMasterEpoch()); assertEquals(3, replicasInfo.getSyncStateSetEpoch()); assertEquals(1, replicasInfo.getInSyncReplicas().size()); assertEquals(2, replicasInfo.getNotInSyncReplicas().size()); } @Test public void testElectMasterOldMasterStillAlive() { mockMetaData(); final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); ElectPolicy electPolicy = new DefaultElectPolicy(this.heartbeatManager::isBrokerActive, this.heartbeatManager::getBrokerLiveInfo); mockHeartbeatDataMasterStillAlive(); final ControllerResult cResult = this.replicasInfoManager.electMaster(request, electPolicy); assertEquals(ResponseCode.CONTROLLER_MASTER_STILL_EXIST, cResult.getResponseCode()); } @Test public void testElectMasterPreferHigherEpoch() { mockMetaData(); final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); ElectPolicy electPolicy = new DefaultElectPolicy(this.heartbeatManager::isBrokerActive, this.heartbeatManager::getBrokerLiveInfo); mockHeartbeatDataHigherEpoch(); final ControllerResult cResult = this.replicasInfoManager.electMaster(request, electPolicy); final ElectMasterResponseHeader response = cResult.getResponse(); assertEquals(DEFAULT_IP[1], response.getMasterAddress()); assertEquals(2L, response.getMasterBrokerId().longValue()); assertEquals(2, response.getMasterEpoch().intValue()); } @Test public void testElectMasterPreferHigherOffsetWhenEpochEquals() { mockMetaData(); final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); ElectPolicy electPolicy = new DefaultElectPolicy(this.heartbeatManager::isBrokerActive, this.heartbeatManager::getBrokerLiveInfo); mockHeartbeatDataHigherOffset(); final ControllerResult cResult = this.replicasInfoManager.electMaster(request, electPolicy); final ElectMasterResponseHeader response = cResult.getResponse(); assertEquals(DEFAULT_IP[2], response.getMasterAddress()); assertEquals(3L, response.getMasterBrokerId().longValue()); assertEquals(2, response.getMasterEpoch().intValue()); } @Test public void testElectMasterPreferHigherPriorityWhenEpochAndOffsetEquals() { mockMetaData(); final ElectMasterRequestHeader request = new ElectMasterRequestHeader(DEFAULT_BROKER_NAME); ElectPolicy electPolicy = new DefaultElectPolicy(this.heartbeatManager::isBrokerActive, this.heartbeatManager::getBrokerLiveInfo); mockHeartbeatDataHigherPriority(); final ControllerResult cResult = this.replicasInfoManager.electMaster(request, electPolicy); final ElectMasterResponseHeader response = cResult.getResponse(); assertEquals(DEFAULT_IP[2], response.getMasterAddress()); assertEquals(3L, response.getMasterBrokerId().longValue()); assertEquals(2, response.getMasterEpoch().intValue()); } @Test public void testElectMaster() { mockMetaData(); final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); final ControllerResult cResult = this.replicasInfoManager.electMaster(request, new DefaultElectPolicy((cluster, brokerName, brokerId) -> !brokerId.equals(1L), null)); final ElectMasterResponseHeader response = cResult.getResponse(); assertEquals(2, response.getMasterEpoch().intValue()); assertNotEquals(1L, response.getMasterBrokerId().longValue()); assertNotEquals(DEFAULT_IP[0], response.getMasterAddress()); apply(cResult.getEvents()); final Set brokerSet = new HashSet<>(); brokerSet.add(1L); brokerSet.add(2L); brokerSet.add(3L); assertTrue(alterNewInSyncSet(DEFAULT_BROKER_NAME, response.getMasterBrokerId(), response.getMasterEpoch(), brokerSet, response.getSyncStateSetEpoch())); // test admin try to elect a assignedMaster, but it isn't alive final ElectMasterRequestHeader assignRequest = ElectMasterRequestHeader.ofAdminTrigger(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, 1L); final ControllerResult cResult1 = this.replicasInfoManager.electMaster(assignRequest, new DefaultElectPolicy((cluster, brokerName, brokerId) -> !brokerId.equals(1L), null)); assertEquals(cResult1.getResponseCode(), ResponseCode.CONTROLLER_ELECT_MASTER_FAILED); // test admin try to elect a assignedMaster but old master still alive, and the old master is equals to assignedMaster final ElectMasterRequestHeader assignRequest1 = ElectMasterRequestHeader.ofAdminTrigger(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, response.getMasterBrokerId()); final ControllerResult cResult2 = this.replicasInfoManager.electMaster(assignRequest1, new DefaultElectPolicy((cluster, brokerName, brokerId) -> true, null)); assertEquals(cResult2.getResponseCode(), ResponseCode.CONTROLLER_MASTER_STILL_EXIST); // admin successful elect a assignedMaster. final ElectMasterRequestHeader assignRequest2 = ElectMasterRequestHeader.ofAdminTrigger(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, 1L); final ControllerResult cResult3 = this.replicasInfoManager.electMaster(assignRequest2, new DefaultElectPolicy((cluster, brokerName, brokerId) -> !brokerId.equals(response.getMasterBrokerId()), null)); assertEquals(cResult3.getResponseCode(), ResponseCode.SUCCESS); final ElectMasterResponseHeader response3 = cResult3.getResponse(); assertEquals(1L, response3.getMasterBrokerId().longValue()); assertEquals(DEFAULT_IP[0], response3.getMasterAddress()); assertEquals(3, response3.getMasterEpoch().intValue()); } @Test public void testAllReplicasShutdownAndRestart() { mockMetaData(); final HashSet newSyncStateSet = new HashSet<>(); newSyncStateSet.add(1L); assertTrue(alterNewInSyncSet(DEFAULT_BROKER_NAME, 1L, 1, newSyncStateSet, 2)); // Now we trigger electMaster api, which means the old master is shutdown and want to elect a new master. // However, the syncStateSet in statemachine is {DEFAULT_IP[0]}, not more replicas can be elected as master, it will be failed. final ElectMasterRequestHeader electRequest = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME); final ControllerResult cResult = this.replicasInfoManager.electMaster(electRequest, new DefaultElectPolicy((cluster, brokerName, brokerId) -> !brokerId.equals(1L), null)); final List events = cResult.getEvents(); assertEquals(events.size(), 1); final ElectMasterEvent event = (ElectMasterEvent) events.get(0); assertFalse(event.getNewMasterElected()); apply(cResult.getEvents()); final GetReplicaInfoResponseHeader replicaInfo = this.replicasInfoManager.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)).getResponse(); assertEquals(replicaInfo.getMasterAddress(), null); assertEquals(2, replicaInfo.getMasterEpoch().intValue()); } @Test public void testCleanBrokerData() { mockMetaData(); CleanControllerBrokerDataRequestHeader header1 = new CleanControllerBrokerDataRequestHeader(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, "1"); ControllerResult result1 = this.replicasInfoManager.cleanBrokerData(header1, (cluster, brokerName, brokerId) -> true); assertEquals(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, result1.getResponseCode()); CleanControllerBrokerDataRequestHeader header2 = new CleanControllerBrokerDataRequestHeader(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, null); ControllerResult result2 = this.replicasInfoManager.cleanBrokerData(header2, (cluster, brokerName, brokerId) -> true); assertEquals(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, result2.getResponseCode()); assertEquals("Broker broker-set-a is still alive, clean up failure", result2.getRemark()); CleanControllerBrokerDataRequestHeader header3 = new CleanControllerBrokerDataRequestHeader(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, "1"); ControllerResult result3 = this.replicasInfoManager.cleanBrokerData(header3, (cluster, brokerName, brokerId) -> false); assertEquals(ResponseCode.SUCCESS, result3.getResponseCode()); CleanControllerBrokerDataRequestHeader header4 = new CleanControllerBrokerDataRequestHeader(DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, "1;2;3"); ControllerResult result4 = this.replicasInfoManager.cleanBrokerData(header4, (cluster, brokerName, brokerId) -> false); assertEquals(ResponseCode.SUCCESS, result4.getResponseCode()); CleanControllerBrokerDataRequestHeader header5 = new CleanControllerBrokerDataRequestHeader(DEFAULT_CLUSTER_NAME, "broker12", "1;2;3", true); ControllerResult result5 = this.replicasInfoManager.cleanBrokerData(header5, (cluster, brokerName, brokerId) -> false); assertEquals(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, result5.getResponseCode()); assertEquals("Broker broker12 is not existed,clean broker data failure.", result5.getRemark()); CleanControllerBrokerDataRequestHeader header6 = new CleanControllerBrokerDataRequestHeader(null, "broker12", "1;2;3", true); ControllerResult result6 = this.replicasInfoManager.cleanBrokerData(header6, (cluster, brokerName, brokerId) -> cluster != null); assertEquals(ResponseCode.CONTROLLER_INVALID_CLEAN_BROKER_METADATA, result6.getResponseCode()); CleanControllerBrokerDataRequestHeader header7 = new CleanControllerBrokerDataRequestHeader(null, DEFAULT_BROKER_NAME, "1;2;3", true); ControllerResult result7 = this.replicasInfoManager.cleanBrokerData(header7, (cluster, brokerName, brokerId) -> false); assertEquals(ResponseCode.SUCCESS, result7.getResponseCode()); } @Test public void testSerialize() { mockMetaData(); byte[] data; try { data = this.replicasInfoManager.serialize(); } catch (Throwable e) { throw new RuntimeException(e); } final ReplicasInfoManager newReplicasInfoManager = new ReplicasInfoManager(config); try { newReplicasInfoManager.deserializeFrom(data); } catch (Throwable e) { throw new RuntimeException(e); } Map oldReplicaInfoTable = new TreeMap<>(); Map newReplicaInfoTable = new TreeMap<>(); Map oldSyncStateTable = new TreeMap<>(); Map newSyncStateTable = new TreeMap<>(); try { Field field = ReplicasInfoManager.class.getDeclaredField("replicaInfoTable"); field.setAccessible(true); oldReplicaInfoTable.putAll((Map) field.get(this.replicasInfoManager)); newReplicaInfoTable.putAll((Map) field.get(newReplicasInfoManager)); field = ReplicasInfoManager.class.getDeclaredField("syncStateSetInfoTable"); field.setAccessible(true); oldSyncStateTable.putAll((Map) field.get(this.replicasInfoManager)); newSyncStateTable.putAll((Map) field.get(newReplicasInfoManager)); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } assertArrayEquals(oldReplicaInfoTable.keySet().toArray(), newReplicaInfoTable.keySet().toArray()); assertArrayEquals(oldSyncStateTable.keySet().toArray(), newSyncStateTable.keySet().toArray()); for (String brokerName : oldReplicaInfoTable.keySet()) { BrokerReplicaInfo oldReplicaInfo = oldReplicaInfoTable.get(brokerName); BrokerReplicaInfo newReplicaInfo = newReplicaInfoTable.get(brokerName); Field[] fields = oldReplicaInfo.getClass().getFields(); } } } ================================================ FILE: controller/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: dev/merge_rocketmq_pr.py ================================================ #!/usr/bin/env python # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # This script is a modified version of the one created by the RocketMQ # project (https://github.com/apache/rocketmq/blob/master/dev/merge_rocketmq_pr.py). # Utility for creating well-formed pull request merges and pushing them to Apache. # usage: ./merge_rocketmq_pr.py (see config env vars below) # # This utility assumes you already have local a RocketMQ git folder and that you # have added remotes corresponding to both (i) the github apache RocketMQ # mirror and (ii) the apache git repo. import json import os import re import subprocess import sys import urllib2 try: import jira.client JIRA_IMPORTED = True except ImportError: JIRA_IMPORTED = False # Location of your RocketMQ git development area ROCKETMQ_HOME = os.environ.get("ROCKETMQ_HOME", os.getcwd()) # Remote name which points to the Gihub site PR_REMOTE_NAME = os.environ.get("PR_REMOTE_NAME", "apache-github") # Remote name which points to Apache git PUSH_REMOTE_NAME = os.environ.get("PUSH_REMOTE_NAME", "origin") # ASF JIRA username JIRA_USERNAME = os.environ.get("JIRA_USERNAME", "") # ASF JIRA password JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD", "") # OAuth key used for issuing requests against the GitHub API. If this is not defined, then requests # will be unauthenticated. You should only need to configure this if you find yourself regularly # exceeding your IP's unauthenticated request rate limit. You can create an OAuth key at # https://github.com/settings/tokens. This script only requires the "public_repo" scope. GITHUB_OAUTH_KEY = os.environ.get("GITHUB_OAUTH_KEY") GITHUB_BASE = "https://github.com/apache/rocketmq/pull" GITHUB_API_BASE = "https://api.github.com/repos/apache/rocketmq" JIRA_BASE = "https://issues.apache.org/jira/browse" JIRA_API_BASE = "https://issues.apache.org/jira" # Prefix added to temporary branches BRANCH_PREFIX = "PR_TOOL" DEVELOP_BRANCH = "develop" def get_json(url): try: request = urllib2.Request(url) if GITHUB_OAUTH_KEY: request.add_header('Authorization', 'token %s' % GITHUB_OAUTH_KEY) return json.load(urllib2.urlopen(request)) except urllib2.HTTPError as e: if "X-RateLimit-Remaining" in e.headers and e.headers["X-RateLimit-Remaining"] == '0': print("Exceeded the GitHub API rate limit; see the instructions in " + "dev/merge_rocketmq_pr.py to configure an OAuth token for making authenticated " + "GitHub requests.") else: print("Unable to fetch URL, exiting: %s" % url) sys.exit(-1) def fail(msg): print(msg) clean_up() sys.exit(-1) def run_cmd(cmd): print(cmd) if isinstance(cmd, list): return subprocess.check_output(cmd) else: return subprocess.check_output(cmd.split(" ")) def continue_maybe(prompt): result = raw_input("\n%s (y/n): " % prompt) if result.lower() != "y": fail("Okay, exiting") def clean_up(): print("Restoring head pointer to %s" % original_head) run_cmd("git checkout %s" % original_head) branches = run_cmd("git branch").replace(" ", "").split("\n") for branch in filter(lambda x: x.startswith(BRANCH_PREFIX), branches): print("Deleting local branch %s" % branch) run_cmd("git branch -D %s" % branch) # merge the requested PR and return the merge hash def merge_pr(pr_num, target_ref, title, body, pr_repo_desc): pr_branch_name = "%s_MERGE_PR_%s" % (BRANCH_PREFIX, pr_num) target_branch_name = "%s_MERGE_PR_%s_%s" % (BRANCH_PREFIX, pr_num, target_ref.upper()) run_cmd("git fetch %s pull/%s/head:%s" % (PR_REMOTE_NAME, pr_num, pr_branch_name)) run_cmd("git fetch %s %s:%s" % (PUSH_REMOTE_NAME, target_ref, target_branch_name)) run_cmd("git checkout %s" % target_branch_name) had_conflicts = False try: run_cmd(['git', 'merge', pr_branch_name, '--squash']) except Exception as e: msg = "Error merging: %s\nWould you like to manually fix-up this merge?" % e continue_maybe(msg) msg = "Okay, please fix any conflicts and 'git add' conflicting files... Finished?" continue_maybe(msg) had_conflicts = True commit_authors = run_cmd(['git', 'log', 'HEAD..%s' % pr_branch_name, '--pretty=format:%an <%ae>']).split("\n") distinct_authors = sorted(set(commit_authors), key=lambda x: commit_authors.count(x), reverse=True) primary_author = raw_input( "Enter primary author in the format of \"name \" [%s]: " % distinct_authors[0]) if primary_author == "": primary_author = distinct_authors[0] commits = run_cmd(['git', 'log', 'HEAD..%s' % pr_branch_name, '--pretty=format:%h [%an] %s']).split("\n\n") merge_message_flags = [] modified_title = raw_input("Modify commit log [%s]: " % title) if modified_title == "": modified_title = title merge_message_flags += ["-m", modified_title] authors = "\n".join(["Author: %s" % a for a in distinct_authors]) merge_message_flags += ["-m", authors] if had_conflicts: committer_name = run_cmd("git config --get user.name").strip() committer_email = run_cmd("git config --get user.email").strip() message = "This patch had conflicts when merged, resolved by\nCommitter: %s <%s>" % ( committer_name, committer_email) merge_message_flags += ["-m", message] # The string "Closes #%s" string is required for GitHub to correctly close the PR merge_message_flags += ["-m", "Closes #%s from %s." % (pr_num, pr_repo_desc)] run_cmd(['git', 'commit', '--author="%s"' % primary_author] + merge_message_flags) continue_maybe("Merge complete (local ref %s). Push to %s?" % ( target_branch_name, PUSH_REMOTE_NAME)) try: run_cmd('git push %s %s:%s' % (PUSH_REMOTE_NAME, target_branch_name, target_ref)) except Exception as e: clean_up() fail("Exception while pushing: %s" % e) merge_hash = run_cmd("git rev-parse %s" % target_branch_name)[:8] clean_up() print("Pull request #%s merged!" % pr_num) print("Merge hash: %s" % merge_hash) return merge_hash def cherry_pick(pr_num, merge_hash, default_branch): pick_ref = raw_input("Enter a branch name [%s]: " % default_branch) if pick_ref == "": pick_ref = default_branch pick_branch_name = "%s_PICK_PR_%s_%s" % (BRANCH_PREFIX, pr_num, pick_ref.upper()) run_cmd("git fetch %s %s:%s" % (PUSH_REMOTE_NAME, pick_ref, pick_branch_name)) run_cmd("git checkout %s" % pick_branch_name) try: run_cmd("git cherry-pick -sx %s" % merge_hash) except Exception as e: msg = "Error cherry-picking: %s\nWould you like to manually fix-up this merge?" % e continue_maybe(msg) msg = "Okay, please fix any conflicts and finish the cherry-pick. Finished?" continue_maybe(msg) continue_maybe("Pick complete (local ref %s). Push to %s?" % ( pick_branch_name, PUSH_REMOTE_NAME)) try: run_cmd('git push %s %s:%s' % (PUSH_REMOTE_NAME, pick_branch_name, pick_ref)) except Exception as e: clean_up() fail("Exception while pushing: %s" % e) pick_hash = run_cmd("git rev-parse %s" % pick_branch_name)[:8] clean_up() print("Pull request #%s picked into %s!" % (pr_num, pick_ref)) print("Pick hash: %s" % pick_hash) return pick_ref def fix_version_from_branch(branch, versions): # Note: Assumes this is a sorted (newest->oldest) list of un-released versions if branch == "master": return versions[0] else: branch_ver = branch.replace("branch-", "") return filter(lambda x: x.name.startswith(branch_ver), versions)[-1] def resolve_jira_issue(merge_branches, comment, default_jira_id=""): asf_jira = jira.client.JIRA({'server': JIRA_API_BASE}, basic_auth=(JIRA_USERNAME, JIRA_PASSWORD)) jira_id = raw_input("Enter a JIRA id [%s]: " % default_jira_id) if jira_id == "": jira_id = default_jira_id try: issue = asf_jira.issue(jira_id) except Exception as e: fail("ASF JIRA could not find %s\n%s" % (jira_id, e)) cur_status = issue.fields.status.name cur_summary = issue.fields.summary cur_assignee = issue.fields.assignee if cur_assignee is None: cur_assignee = "NOT ASSIGNED!!!" else: cur_assignee = cur_assignee.displayName if cur_status == "Resolved" or cur_status == "Closed": fail("JIRA issue %s already has status '%s'" % (jira_id, cur_status)) print("=== JIRA %s ===" % jira_id) print("summary\t\t%s\nassignee\t%s\nstatus\t\t%s\nurl\t\t%s/%s\n" % (cur_summary, cur_assignee, cur_status, JIRA_BASE, jira_id)) versions = asf_jira.project_versions("ROCKETMQ") versions = sorted(versions, key=lambda x: x.name, reverse=True) versions = filter(lambda x: x.raw['released'] is False, versions) # Consider only x.y.z versions versions = filter(lambda x: re.match('\d+\.\d+\.\d+', x.name), versions) default_fix_versions = map(lambda x: fix_version_from_branch(x, versions).name, merge_branches) for v in default_fix_versions: # Handles the case where we have forked a release branch but not yet made the release. # In this case, if the PR is committed to the master branch and the release branch, we # only consider the release branch to be the fix version. E.g. it is not valid to have # both 1.1.0 and 1.0.0 as fix versions. (major, minor, patch) = v.split(".") if patch == "0": previous = "%s.%s.%s" % (major, int(minor) - 1, 0) if previous in default_fix_versions: default_fix_versions = filter(lambda x: x != v, default_fix_versions) default_fix_versions = ",".join(default_fix_versions) fix_versions = raw_input("Enter comma-separated fix version(s) [%s]: " % default_fix_versions) if fix_versions == "": fix_versions = default_fix_versions fix_versions = fix_versions.replace(" ", "").split(",") def get_version_json(version_str): return filter(lambda v: v.name == version_str, versions)[0].raw jira_fix_versions = map(lambda v: get_version_json(v), fix_versions) resolve = filter(lambda a: a['name'] == "Resolve Issue", asf_jira.transitions(jira_id))[0] resolution = filter(lambda r: r.raw['name'] == "Fixed", asf_jira.resolutions())[0] asf_jira.transition_issue( jira_id, resolve["id"], fixVersions=jira_fix_versions, comment=comment, resolution={'id': resolution.raw['id']}) print("Successfully resolved %s with fixVersions=%s!" % (jira_id, fix_versions)) def resolve_jira_issues(title, merge_branches, comment): jira_ids = re.findall("ROCKETMQ-[0-9]{4,5}", title) if len(jira_ids) == 0: resolve_jira_issue(merge_branches, comment) for jira_id in jira_ids: resolve_jira_issue(merge_branches, comment, jira_id) def standardize_jira_ref(text): """ Standardize the [ROCKETMQ-XXXXX] [MODULE] prefix Converts "[ROCKETMQ-XXX][mllib] Issue", "[MLLib] ROCKETMQ-XXX. Issue" or "ROCKETMQ XXX [MLLIB]: Issue" to "[ROCKETMQ-XXX][MLLIB] Issue" """ jira_refs = [] components = [] # If the string is compliant, no need to process any further if (re.search(r'^\[ROCKETMQ-[0-9]{3,6}\](\[[A-Z0-9_\s,]+\] )+\S+', text)): return text # Extract JIRA ref(s): pattern = re.compile(r'(ROCKETMQ[-\s]*[0-9]{3,6})+', re.IGNORECASE) for ref in pattern.findall(text): # Add brackets, replace spaces with a dash, & convert to uppercase jira_refs.append('[' + re.sub(r'\s+', '-', ref.upper()) + ']') text = text.replace(ref, '') # Extract rocketmq component(s): # Look for alphanumeric chars, spaces, dashes, periods, and/or commas pattern = re.compile(r'(\[[\w\s,-\.]+\])', re.IGNORECASE) for component in pattern.findall(text): components.append(component.upper()) text = text.replace(component, '') # Cleanup any remaining symbols: pattern = re.compile(r'^\W+(.*)', re.IGNORECASE) if (pattern.search(text) is not None): text = pattern.search(text).groups()[0] # Assemble full text (JIRA ref(s), module(s), remaining text) clean_text = ''.join(jira_refs).strip() + ''.join(components).strip() + " " + text.strip() # Replace multiple spaces with a single space, e.g. if no jira refs and/or components were # included clean_text = re.sub(r'\s+', ' ', clean_text.strip()) return clean_text def get_current_ref(): ref = run_cmd("git rev-parse --abbrev-ref HEAD").strip() if ref == 'HEAD': # The current ref is a detached HEAD, so grab its SHA. return run_cmd("git rev-parse HEAD").strip() else: return ref def main(): global original_head os.chdir(ROCKETMQ_HOME) original_head = get_current_ref() latest_branch = DEVELOP_BRANCH pr_num = raw_input("Which pull request would you like to merge? (e.g. 34): ") pr = get_json("%s/pulls/%s" % (GITHUB_API_BASE, pr_num)) pr_events = get_json("%s/issues/%s/events" % (GITHUB_API_BASE, pr_num)) url = pr["url"] # Decide whether to use the modified title or not modified_title = standardize_jira_ref(pr["title"]) if modified_title != pr["title"]: print("I've re-written the title as follows to match the standard format:") print("Original: %s" % pr["title"]) print("Modified: %s" % modified_title) result = raw_input("Would you like to use the modified title? (y/n): ") if result.lower() == "y": title = modified_title print("Using modified title:") else: title = pr["title"] print("Using original title:") print(title) else: title = pr["title"] body = pr["body"] target_ref = pr["base"]["ref"] user_login = pr["user"]["login"] base_ref = pr["head"]["ref"] pr_repo_desc = "%s/%s" % (user_login, base_ref) # Merged pull requests don't appear as merged in the GitHub API; # Instead, they're closed by asfgit. merge_commits = \ [e for e in pr_events if e["actor"]["login"] == "asfgit" and e["event"] == "closed"] if merge_commits: merge_hash = merge_commits[0]["commit_id"] message = get_json("%s/commits/%s" % (GITHUB_API_BASE, merge_hash))["commit"]["message"] print("Pull request %s has already been merged, assuming you want to backport" % pr_num) commit_is_downloaded = run_cmd(['git', 'rev-parse', '--quiet', '--verify', "%s^{commit}" % merge_hash]).strip() != "" if not commit_is_downloaded: fail("Couldn't find any merge commit for #%s, you may need to update HEAD." % pr_num) print("Found commit %s:\n%s" % (merge_hash, message)) cherry_pick(pr_num, merge_hash, latest_branch) sys.exit(0) if not bool(pr["mergeable"]): msg = "Pull request %s is not mergeable in its current form.\n" % pr_num + \ "Continue? (experts only!)" continue_maybe(msg) print("\n=== Pull Request #%s ===" % pr_num) print("title\t%s\nsource\t%s\ntarget\t%s\nurl\t%s" % (title, pr_repo_desc, target_ref, url)) continue_maybe("Proceed with merging pull request #%s?" % pr_num) merged_refs = [target_ref] merge_hash = merge_pr(pr_num, target_ref, title, body, pr_repo_desc) pick_prompt = "Would you like to pick %s into another branch?" % merge_hash while raw_input("\n%s (y/n): " % pick_prompt).lower() == "y": merged_refs = merged_refs + [cherry_pick(pr_num, merge_hash, latest_branch)] if JIRA_IMPORTED: if JIRA_USERNAME and JIRA_PASSWORD: continue_maybe("Would you like to update an associated JIRA?") jira_comment = "Issue resolved by pull request %s\n[%s/%s]" % \ (pr_num, GITHUB_BASE, pr_num) resolve_jira_issues(title, merged_refs, jira_comment) else: print("JIRA_USERNAME and JIRA_PASSWORD not set") print("Exiting without trying to close the associated JIRA.") else: print("Could not find jira-python library. Run 'sudo pip install jira' to install.") print("Exiting without trying to close the associated JIRA.") if __name__ == "__main__": import doctest (failure_count, test_count) = doctest.testmod() if failure_count: exit(-1) try: main() except: clean_up() raise ================================================ FILE: distribution/LICENSE-BIN ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ------ This product has a bundle logback, which is available under the EPL v1.0 License. The source code of logback can be found at https://github.com/qos-ch/logback. Logback LICENSE --------------- Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights reserved. This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License v1.0 as published by the Eclipse Foundation or (per the licensee's choosing) under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. ------ This product has a bundle slf4j, which is available under the MIT License. The source code of slf4j can be found at https://github.com/qos-ch/slf4j. Copyright (c) 2004-2017 QOS.ch All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------ This product has a bundle fastjson, which is available under the ASL2 License. The source code of fastjson can be found at https://github.com/alibaba/fastjson. Copyright 1999-2016 Alibaba Group Holding Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ------ This product has a bundle javassist, which is available under the ASL2 License. The source code of javassist can be found at https://github.com/jboss-javassist/javassist. Copyright (C) 1999- by Shigeru Chiba, All rights reserved. Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level. If the users use the source- level API, they can edit a class file without knowledge of the specifications of the Java bytecode. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly. On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors. This software is distributed under the Mozilla Public License Version 1.1, the GNU Lesser General Public License Version 2.1 or later, or the Apache License Version 2.0. ------ This product has a bundle jna, which is available under the ASL2 License. The source code of jna can be found at https://github.com/java-native-access/jna. This copy of JNA is licensed under the Apache (Software) License, version 2.0 ("the License"). See the License for details about distribution rights, and the specific rights regarding derivate works. You may obtain a copy of the License at: http://www.apache.org/licenses/ A copy is also included in the downloadable source code package containing JNA, in file "AL2.0", under the same directory as this file. ------ This product has a bundle guava, which is available under the ASL2 License. The source code of guava can be found at https://github.com/google/guava. Copyright (C) 2007 The Guava authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ------ This product has a bundle OpenMessaging, which is available under the ASL2 License. The source code of OpenMessaging can be found at https://github.com/openmessaging/openmessaging. Copyright (C) 2017 The OpenMessaging authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: distribution/NOTICE-BIN ================================================ Apache RocketMQ Copyright 2016-2022 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). ------ This product has a bundle netty: The Netty Project ================= Please visit the Netty web site for more information: * http://netty.io/ Copyright 2014 The Netty Project The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Also, please refer to each LICENSE..txt file, which is located in the 'license' directory of the distribution file, for the license terms of the components that this product depends on. ------ This product has a bundle commons-lang, which includes software from the Spring Framework, under the Apache License 2.0 (see: StringUtils.containsWhitespace()) ================================================ FILE: distribution/benchmark/batchproducer.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. sh ./runclass.sh org.apache.rocketmq.example.benchmark.BatchProducer $@ & ================================================ FILE: distribution/benchmark/consumer.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. sh ./runclass.sh org.apache.rocketmq.example.benchmark.Consumer $@ & ================================================ FILE: distribution/benchmark/producer.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. sh ./runclass.sh -Dorg.apache.rocketmq.client.sendSmartMsg=true org.apache.rocketmq.example.benchmark.Producer $@ & ================================================ FILE: distribution/benchmark/runclass.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ $# -lt 1 ]; then echo "USAGE: $0 classname opts" exit 1 fi BASE_DIR=$(dirname $0)/.. CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} # The RAMDisk initializing size in MB on Darwin OS for gc-log DIR_SIZE_IN_MB=600 choose_gc_log_directory() { case "`uname`" in Darwin) if [ ! -d "/Volumes/RAMDisk" ]; then # create ram disk on Darwin systems as gc-log directory DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS." fi GC_LOG_DIR="/Volumes/RAMDisk" ;; *) # check if /dev/shm exists on other systems if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac } choose_gc_log_directory JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:PermSize=128m -XX:MaxPermSize=320m" JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:+DisableExplicitGC" JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_run_class_gc_%p_%t.log -XX:+PrintGCDetails" JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages" JAVA_OPT="${JAVA_OPT} -XX:+PerfDisableSharedMem" #JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib:${JAVA_HOME}/lib/ext" JAVA_OPT="${JAVA_OPT} -Drmq.logback.configurationFile=${BASE_DIR}/conf/rmq.client.logback.xml" if [ -z "$JAVA_HOME" ]; then JAVA_HOME=/usr/java fi JAVA="$JAVA_HOME/bin/java" $JAVA ${JAVA_OPT} $@ ================================================ FILE: distribution/benchmark/shutdown.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. case $1 in producer) pid=`ps ax | grep -i 'org.apache.rocketmq.example.benchmark.Producer' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No benchmark producer running." exit -1; fi echo "The benchmark producer(${pid}) is running..." kill ${pid} echo "Send shutdown request to benchmark producer(${pid}) OK" ;; consumer) pid=`ps ax | grep -i 'org.apache.rocketmq.example.benchmark.Consumer' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No benchmark consumer running." exit -1; fi echo "The benchmark consumer(${pid}) is running..." kill ${pid} echo "Send shutdown request to benchmark consumer(${pid}) OK" ;; tproducer) pid=`ps ax | grep -i 'org.apache.rocketmq.example.benchmark.TransactionProducer' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No benchmark transaction producer running." exit -1; fi echo "The benchmark transaction producer(${pid}) is running..." kill ${pid} echo "Send shutdown request to benchmark transaction producer(${pid}) OK" ;; bproducer) pid=`ps ax | grep -i 'org.apache.rocketmq.example.benchmark.BatchProducer' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No benchmark batch producer running." exit -1; fi echo "The benchmark batch producer(${pid}) is running..." kill ${pid} echo "Send shutdown request to benchmark batch producer(${pid}) OK" ;; *) echo "Usage: shutdown producer | consumer | tproducer | bproducer" esac ================================================ FILE: distribution/benchmark/tproducer.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. sh ./runclass.sh org.apache.rocketmq.example.benchmark.TransactionProducer $@ & ================================================ FILE: distribution/bin/README.md ================================================ ### Operating system tuning Before deploying broker servers, it is highly recommended to run **os.sh**, which optimizes your operating system for better performance. ## Notice ### os.sh should be executed only once with root permission. ### os.sh parameter settings are for reference purposes only. You can tune them according to your target host configurations. ### Start broker * Unix platform `nohup sh mqbroker &` ### Shutdown broker sh mqshutdown broker ### Start Nameserver * Unix platform `nohup sh mqnamesrv &` ### Shutdown Nameserver sh mqshutdown namesrv ### Update or create Topic sh mqadmin updateTopic -b 127.0.0.1:10911 -t TopicA ### Update or create subscription group sh mqadmin updateSubGroup -b 127.0.0.1:10911 -g SubGroupA ================================================ FILE: distribution/bin/cachedog.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. export PATH=$PATH:/sbin while true; do nr_free_pages=`fgrep -A 10 Normal /proc/zoneinfo |grep nr_free_pages |awk -F ' ' '{print $2}'` high=`fgrep -A 10 Normal /proc/zoneinfo |grep high |awk -F ' ' '{print $2}'` NOW_DATE=`date +%D` NOW_TIME=`date +%T` if [ ${nr_free_pages} -le ${high} ]; then sysctl -w vm.drop_caches=3 nr_free_pages_new=`fgrep -A 10 Normal /proc/zoneinfo |grep nr_free_pages |awk -F ' ' '{print $2}'` printf "%s %s [CLEAN] nr_free_pages < high, clean cache. nr_free_pages=%s ====> nr_free_pages=%s\n" "${NOW_DATE}" "${NOW_TIME}" ${nr_free_pages} ${nr_free_pages_new} sysctl -w vm.drop_caches=1 echo echo echo else printf "%s %s [NOTHING] nr_free_pages=%s high=%s\n" "${NOW_DATE}" "${NOW_TIME}" ${nr_free_pages} ${high} fi sleep 1 done ================================================ FILE: distribution/bin/cleancache.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. export PATH=$PATH:/sbin sysctl -w vm.drop_caches=3 ================================================ FILE: distribution/bin/cleancache.v1.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. export PATH=$PATH:/sbin # # GB # function changeFreeCache() { EXTRA=$1 MIN=$2 sysctl -w vm.extra_free_kbytes=${EXTRA}000000 sysctl -w vm.min_free_kbytes=${MIN}000000 } if [ $# -ne 1 ] then echo "Usage: $0 freecache(GB)" echo "Example: $0 15" exit fi changeFreeCache 3 $1 changeFreeCache 3 1 ================================================ FILE: distribution/bin/controller/fast-try-independent-deployment.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. set ROCKETMQ_HOME=%cd% if not exist "%ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n0.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n0.conf exists & EXIT /B 1 if not exist "%ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n1.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n1.conf exists & EXIT /B 1 if not exist "%ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n2.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n2.conf exists & EXIT /B 1 set "JAVA_OPT_EXT= -server -Xms512m -Xmx512m" start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.controller.ControllerStartup -c %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n0.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Controller start OK" ) timeout /T 3 /NOBREAK start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.controller.ControllerStartup -c %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n1.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Controller start OK" ) timeout /T 3 /NOBREAK start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.controller.ControllerStartup -c %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n2.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Controller start OK" ) ================================================ FILE: distribution/bin/controller/fast-try-independent-deployment.sh ================================================ #!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## Revise the base dir CURRENT_DIR="$(cd "$(dirname "$0")"; pwd)" RMQ_DIR=$CURRENT_DIR/../.. cd $RMQ_DIR startController() { export JAVA_OPT_EXT=" -Xms512m -Xmx512m " conf_name=$1 nohup bin/mqcontroller -c $conf_name & } stopController() { PIDS=$(ps -ef|grep java|grep ControllerStartup|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -s TERM $PIDS fi } stopAll() { stopController } startAll() { startController ./conf/controller/cluster-3n-independent/controller-n0.conf startController ./conf/controller/cluster-3n-independent/controller-n1.conf startController ./conf/controller/cluster-3n-independent/controller-n2.conf } checkConf() { if [ ! -f ./conf/controller/cluster-3n-independent/controller-n0.conf -o ! -f ./conf/controller/cluster-3n-independent/controller-n1.conf -o ! -f ./conf/controller/cluster-3n-independent/controller-n2.conf ]; then echo "Make sure the ./conf/controller/cluster-3n-independent/controller-n0.conf, ./conf/controller/cluster-3n-independent/controller-n1.conf, ./conf/controller/cluster-3n-independent/controller-n2.conf exists" exit 1 fi } ## Main if [ $# -lt 1 ]; then echo "Usage: sh $0 start|stop" exit 1 fi action=$1 checkConf case $action in "start") startAll exit ;; "stop") stopAll exit ;; *) echo "Usage: sh $0 start|stop" exit ;; esac ================================================ FILE: distribution/bin/controller/fast-try-namesrv-plugin.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. set ROCKETMQ_HOME=%cd% if not exist "%ROCKETMQ_HOME%\conf\controller\cluster-3n-namesrv-plugin\namesrv-n0.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n0.conf exists & EXIT /B 1 if not exist "%ROCKETMQ_HOME%\conf\controller\cluster-3n-namesrv-plugin\namesrv-n1.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n1.conf exists & EXIT /B 1 if not exist "%ROCKETMQ_HOME%\conf\controller\cluster-3n-namesrv-plugin\namesrv-n2.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\cluster-3n-independent\controller-n2.conf exists & EXIT /B 1 set "JAVA_OPT_EXT= -server -Xms512m -Xmx512m" start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.namesrv.NamesrvStartup -c %ROCKETMQ_HOME%\conf\controller\cluster-3n-namesrv-plugin\namesrv-n0.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Controller start OK" ) timeout /T 3 /NOBREAK start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.namesrv.NamesrvStartup -c %ROCKETMQ_HOME%\conf\controller\cluster-3n-namesrv-plugin\namesrv-n1.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Controller start OK" ) timeout /T 3 /NOBREAK start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.namesrv.NamesrvStartup -c %ROCKETMQ_HOME%\conf\controller\cluster-3n-namesrv-plugin\namesrv-n2.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Controller start OK" ) ================================================ FILE: distribution/bin/controller/fast-try-namesrv-plugin.sh ================================================ #!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## Revise the base dir CURRENT_DIR="$(cd "$(dirname "$0")"; pwd)" RMQ_DIR=$CURRENT_DIR/../.. cd $RMQ_DIR startNameserver() { export JAVA_OPT_EXT=" -Xms512m -Xmx512m " conf_name=$1 nohup bin/mqnamesrv -c $conf_name & } stopNameserver() { PIDS=$(ps -ef|grep java|grep NamesrvStartup|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -s TERM $PIDS fi } stopAll() { stopNameserver } startAll() { startNameserver ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf startNameserver ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf startNameserver ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf } checkConf() { if [ ! -f ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf -o ! -f ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf -o ! -f ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf ]; then echo "Make sure the ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf, ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf, ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf exists" exit 1 fi } ## Main if [ $# -lt 1 ]; then echo "Usage: sh $0 start|stop" exit 1 fi action=$1 checkConf case $action in "start") startAll exit ;; "stop") stopAll exit ;; *) echo "Usage: sh $0 start|stop" exit ;; esac ================================================ FILE: distribution/bin/controller/fast-try.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. set ROCKETMQ_HOME=%cd% if not exist "%ROCKETMQ_HOME%\conf\controller\quick-start\namesrv.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\quick-start\namesrv.conf exists & EXIT /B 1 if not exist "%ROCKETMQ_HOME%\conf\controller\quick-start\broker-n0.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\quick-start\broker-n0.conf exists & EXIT /B 1 if not exist "%ROCKETMQ_HOME%\conf\controller\quick-start\broker-n1.conf" echo Make sure the %ROCKETMQ_HOME%\conf\controller\quick-start\broker-n1.conf exists & EXIT /B 1 set "JAVA_OPT_EXT= -server -Xms512m -Xmx512m" start call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.namesrv.NamesrvStartup -c %ROCKETMQ_HOME%\conf\controller\quick-start\namesrv.conf IF %ERRORLEVEL% EQU 0 ( ECHO "Namesrv start OK" ) timeout /T 3 /NOBREAK set "JAVA_OPT_EXT= -server -Xms1g -Xmx1g" start call "%ROCKETMQ_HOME%\bin\runbroker.cmd" org.apache.rocketmq.broker.BrokerStartup -c %ROCKETMQ_HOME%\conf\controller\quick-start\broker-n0.conf timeout /T 1 /NOBREAK start call "%ROCKETMQ_HOME%\bin\runbroker.cmd" org.apache.rocketmq.broker.BrokerStartup -c %ROCKETMQ_HOME%\conf\controller\quick-start\broker-n1.conf timeout /T 1 /NOBREAK IF %ERRORLEVEL% EQU 0 ( ECHO "Broker starts OK" ) ================================================ FILE: distribution/bin/controller/fast-try.sh ================================================ #!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## Revise the base dir CURRENT_DIR="$(cd "$(dirname "$0")"; pwd)" RMQ_DIR=$CURRENT_DIR/../.. cd $RMQ_DIR startNameserver() { export JAVA_OPT_EXT=" -Xms512m -Xmx512m " conf_name=$1 nohup bin/mqnamesrv -c $conf_name & } startBroker() { export JAVA_OPT_EXT=" -Xms1g -Xmx1g " conf_name=$1 nohup bin/mqbroker -c $conf_name & } stopNameserver() { PIDS=$(ps -ef|grep java|grep NamesrvStartup|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -s TERM $PIDS fi } stopBroker() { conf_name=$1 PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') i=1 while [ ! -z "$PIDS" -a $i -lt 5 ] do echo "Waiting to kill ..." kill -s TERM $PIDS i=`expr $i + 1` sleep 2 PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') done PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -9 $PIDS fi } stopAll() { ps -ef|grep java|grep BrokerStartup|grep -v grep|awk '{print $2}'|xargs kill stopNameserver stopBroker ./conf/controller/quick-start/broker-n0.conf stopBroker ./conf/controller/quick-start/broker-n1.conf } startAll() { startNameserver ./conf/controller/quick-start/namesrv.conf startBroker ./conf/controller/quick-start/broker-n0.conf startBroker ./conf/controller/quick-start/broker-n1.conf } checkConf() { if [ ! -f ./conf/controller/quick-start/broker-n0.conf -o ! -f ./conf/controller/quick-start/broker-n1.conf -o ! -f ./conf/controller/quick-start/namesrv.conf ]; then echo "Make sure the ./conf/controller/quick-start/broker-n0.conf, ./conf/controller/quick-start/broker-n1.conf, ./conf/controller/quick-start/namesrv.conf exists" exit 1 fi } ## Main if [ $# -lt 1 ]; then echo "Usage: sh $0 start|stop" exit 1 fi action=$1 checkConf case $action in "start") startAll exit ;; "stop") stopAll ;; *) echo "Usage: sh $0 start|stop" ;; esac ================================================ FILE: distribution/bin/dledger/fast-try.sh ================================================ #!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## Revise the base dir CURRENT_DIR="$(cd "$(dirname "$0")"; pwd)" RMQ_DIR=$CURRENT_DIR/../.. cd $RMQ_DIR startNameserver() { export JAVA_OPT_EXT=" -Xms512m -Xmx512m " nohup bin/mqnamesrv & } startBroker() { export JAVA_OPT_EXT=" -Xms1g -Xmx1g " conf_name=$1 nohup bin/mqbroker -c $conf_name & } stopNameserver() { PIDS=$(ps -ef|grep java|grep NamesrvStartup|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -s TERM $PIDS fi } stopBroker() { conf_name=$1 PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') i=1 while [ ! -z "$PIDS" -a $i -lt 5 ] do echo "Waiting to kill ..." kill -s TERM $PIDS i=`expr $i + 1` sleep 2 PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') done PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -9 $PIDS fi } stopAll() { ps -ef|grep java|grep BrokerStartup|grep -v grep|awk '{print $2}'|xargs kill stopNameserver stopBroker ./conf/dledger/broker-n0.conf stopBroker ./conf/dledger/broker-n1.conf stopBroker ./conf/dledger/broker-n2.conf } startAll() { startNameserver startBroker ./conf/dledger/broker-n0.conf startBroker ./conf/dledger/broker-n1.conf startBroker ./conf/dledger/broker-n2.conf } checkConf() { if [ ! -f ./conf/dledger/broker-n0.conf -o ! -f ./conf/dledger/broker-n1.conf -o ! -f ./conf/dledger/broker-n2.conf ]; then echo "Make sure the ./conf/dledger/broker-n0.conf, ./conf/dledger/broker-n1.conf, ./conf/dledger/broker-n2.conf exists" exit 1 fi } ## Main if [ $# -lt 1 ]; then echo "Usage: sh $0 start|stop" exit 1 fi action=$1 checkConf case $action in "start") startAll exit ;; "stop") stopAll ;; *) echo "Usage: sh $0 start|stop" ;; esac ================================================ FILE: distribution/bin/export.sh ================================================ #!/bin/bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ]; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ]; do ls=$(ls -ld "$PRG") link=$(expr "$ls" : '.*-> \(.*\)$') if expr "$link" : '/.*' >/dev/null; then PRG="$link" else PRG="$(dirname "$PRG")/$link" fi done saveddir=$(pwd) ROCKETMQ_HOME=$(dirname "$PRG")/.. # make it fully qualified ROCKETMQ_HOME=$(cd "$ROCKETMQ_HOME" && pwd) cd "$saveddir" fi export ROCKETMQ_HOME namesrvAddr= while [ -z "${namesrvAddr}" ]; do read -p "Enter name server address list:" namesrvAddr done clusterName= while [ -z "${clusterName}" ]; do read -p "Choose a cluster to export:" clusterName done read -p "Enter file path to export [default /tmp/rocketmq/export]:" filePath if [ -z "${filePath}" ]; then filePath="/tmp/rocketmq/config" fi if [[ -e ${filePath} ]]; then rm -rf ${filePath} fi sh ${ROCKETMQ_HOME}/bin/mqadmin exportMetrics -c ${clusterName} -n ${namesrvAddr} -f ${filePath} sh ${ROCKETMQ_HOME}/bin/mqadmin exportConfigs -c ${clusterName} -n ${namesrvAddr} -f ${filePath} sh ${ROCKETMQ_HOME}/bin/mqadmin exportMetadata -c ${clusterName} -n ${namesrvAddr} -f ${filePath} cd ${filePath} || exit configs=$(cat ./configs.json) if [ -z "$configs" ]; then configs="{}" fi metadata=$(cat ./metadata.json) if [ -z "$metadata" ]; then metadata="{}" fi metrics=$(cat ./metrics.json) if [ -z "$metrics" ]; then metrics="{}" fi echo "{ \"configs\": ${configs}, \"metadata\": ${metadata}, \"metrics\": ${metrics} }" >rocketmq-metadata-export.json echo -e "[INFO] The RocketMQ metadata has been exported to the file:${filePath}/rocketmq-metadata-export.json" ================================================ FILE: distribution/bin/mqadmin ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME sh ${ROCKETMQ_HOME}/bin/tools.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.tools.logback.xml org.apache.rocketmq.tools.command.MQAdminStartup "$@" ================================================ FILE: distribution/bin/mqadmin.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%ROCKETMQ_HOME%\bin\tools.cmd" echo Please set the ROCKETMQ_HOME variable in your environment! & EXIT /B 1 call "%ROCKETMQ_HOME%\bin\tools.cmd" -Drmq.logback.configurationFile=%ROCKETMQ_HOME%\conf\rmq.tools.logback.xml org.apache.rocketmq.tools.command.MQAdminStartup %* ================================================ FILE: distribution/bin/mqbroker ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME other_args=" " enable_proxy=false while [ $# -gt 0 ]; do case $1 in --enable-proxy) enable_proxy=true shift ;; -c|--configFile) broker_config="$2" shift shift ;; *) other_args=${other_args}" "${1} shift ;; esac done if [ "$enable_proxy" = true ]; then args_for_proxy=$other_args" -pm local" if [ "$broker_config" != "" ]; then args_for_proxy=${args_for_proxy}" -bc "${broker_config} fi sh ${ROCKETMQ_HOME}/bin/runbroker.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.proxy.logback.xml org.apache.rocketmq.proxy.ProxyStartup ${args_for_proxy} else args_for_broker=$other_args if [ "$broker_config" != "" ]; then args_for_broker=${args_for_broker}" -c "${broker_config} fi sh ${ROCKETMQ_HOME}/bin/runbroker.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.broker.logback.xml org.apache.rocketmq.broker.BrokerStartup ${args_for_broker} fi ================================================ FILE: distribution/bin/mqbroker.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%ROCKETMQ_HOME%\bin\runbroker.cmd" echo Please set the ROCKETMQ_HOME variable in your environment! & EXIT /B 1 call "%ROCKETMQ_HOME%\bin\runbroker.cmd" -Drmq.logback.configurationFile=%ROCKETMQ_HOME%\conf\rmq.broker.logback.xml org.apache.rocketmq.broker.BrokerStartup %* IF %ERRORLEVEL% EQU 0 ( ECHO "Broker starts OK" ) ================================================ FILE: distribution/bin/mqbroker.numanode0 ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME export RMQ_NUMA_NODE=0 sh ${ROCKETMQ_HOME}/bin/mqbroker $@ ================================================ FILE: distribution/bin/mqbroker.numanode1 ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME export RMQ_NUMA_NODE=1 sh ${ROCKETMQ_HOME}/bin/mqbroker $@ ================================================ FILE: distribution/bin/mqbroker.numanode2 ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME export RMQ_NUMA_NODE=2 sh ${ROCKETMQ_HOME}/bin/mqbroker $@ ================================================ FILE: distribution/bin/mqbroker.numanode3 ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME export RMQ_NUMA_NODE=3 sh ${ROCKETMQ_HOME}/bin/mqbroker $@ ================================================ FILE: distribution/bin/mqbrokercontainer ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.container.BrokerContainerStartup $@ ================================================ FILE: distribution/bin/mqcontroller ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.controller.logback.xml org.apache.rocketmq.controller.ControllerStartup $@ ================================================ FILE: distribution/bin/mqcontroller.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%ROCKETMQ_HOME%\bin\runserver.cmd" echo Please set the ROCKETMQ_HOME variable in your environment! & EXIT /B 1 call "%ROCKETMQ_HOME%\bin\runserver.cmd" -Drmq.logback.configurationFile=%ROCKETMQ_HOME%\conf\rmq.controller.logback.xml org.apache.rocketmq.controller.ControllerStartup %* IF %ERRORLEVEL% EQU 0 ( ECHO "Controller starts OK" ) ================================================ FILE: distribution/bin/mqnamesrv ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.namesrv.logback.xml org.apache.rocketmq.namesrv.NamesrvStartup $@ ================================================ FILE: distribution/bin/mqnamesrv.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%ROCKETMQ_HOME%\bin\runserver.cmd" echo Please set the ROCKETMQ_HOME variable in your environment! & EXIT /B 1 call "%ROCKETMQ_HOME%\bin\runserver.cmd" -Drmq.logback.configurationFile=%ROCKETMQ_HOME%\conf\rmq.namesrv.logback.xml org.apache.rocketmq.namesrv.NamesrvStartup %* IF %ERRORLEVEL% EQU 0 ( ECHO "Namesrv starts OK" ) ================================================ FILE: distribution/bin/mqproxy ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.proxy.logback.xml org.apache.rocketmq.proxy.ProxyStartup $@ ================================================ FILE: distribution/bin/mqproxy.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%ROCKETMQ_HOME%\bin\runserver.cmd" echo Please set the ROCKETMQ_HOME variable in your environment! & EXIT /B 1 call "%ROCKETMQ_HOME%\bin\runserver.cmd" -Drmq.logback.configurationFile=%ROCKETMQ_HOME%\conf\rmq.proxy.logback.xml org.apache.rocketmq.proxy.ProxyStartup %* IF %ERRORLEVEL% EQU 0 ( ECHO "Proxy starts OK" ) ================================================ FILE: distribution/bin/mqshutdown ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. case $1 in broker) pid=`ps ax | grep -i 'org.apache.rocketmq.proxy.ProxyStartup' | grep '\-pm local' |grep java | grep -v grep | awk '{print $1}'` if [ "$pid" != "" ] ; then echo "The mqbroker with proxy enable is running(${pid})..." kill ${pid} echo "Send shutdown request to mqbroker with proxy enable OK(${pid})" fi pid=`ps ax | grep -i 'org.apache.rocketmq.broker.BrokerStartup' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No mqbroker running." exit 1; fi echo "The mqbroker(${pid}) is running..." kill ${pid} echo "Send shutdown request to mqbroker(${pid}) OK" ;; brokerContainer) pid=`ps ax | grep -i 'org.apache.rocketmq.container.BrokerContainerStartup' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No broker container running." exit 1; fi echo "The broker container(${pid}) is running..." kill ${pid} echo "Send shutdown request to broker container(${pid}) OK" ;; namesrv) pid=`ps ax | grep -i 'org.apache.rocketmq.namesrv.NamesrvStartup' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No mqnamesrv running." exit 1; fi echo "The mqnamesrv(${pid}) is running..." kill ${pid} echo "Send shutdown request to mqnamesrv(${pid}) OK" ;; controller) pid=`ps ax | grep -i 'org.apache.rocketmq.controller.ControllerStartup' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No mqcontroller running." exit 1; fi echo "The mqcontroller(${pid}) is running..." kill ${pid} echo "Send shutdown request to mqcontroller(${pid}) OK" ;; proxy) pid=`ps ax | grep -i 'org.apache.rocketmq.proxy.ProxyStartup' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No mqproxy running." exit 1; fi echo "The mqproxy(${pid}) is running..." kill ${pid} echo "Send shutdown request to mqproxy(${pid}) OK" ;; *) echo "Usage: mqshutdown broker | namesrv | controller | proxy" esac ================================================ FILE: distribution/bin/mqshutdown.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%JAVA_HOME%\bin\jps.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! & EXIT /B 1 setlocal set "PATH=%JAVA_HOME%\bin;%PATH%" if /I "%1" == "broker" ( echo killing broker for /f "tokens=1" %%i in ('jps -m ^| find "BrokerStartup"') do ( taskkill /F /PID %%i ) echo Done! ) else if /I "%1" == "namesrv" ( echo killing name server for /f "tokens=1" %%i in ('jps -m ^| find "NamesrvStartup"') do ( taskkill /F /PID %%i ) echo Done! ) else if /I "%1" == "controller" ( echo killing controller server for /f "tokens=1" %%i in ('jps -m ^| find "ControllerStartup"') do ( taskkill /F /PID %%i ) echo Done! ) else ( echo Unknown role to kill, please specify broker or namesrv or controller ) ================================================ FILE: distribution/bin/os.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. export PATH=$PATH:/sbin # sudo sysctl -w vm.extra_free_kbytes=2000000 # sudo sysctl -w vm.min_free_kbytes=1000000 sudo sysctl -w vm.overcommit_memory=1 sudo sysctl -w vm.drop_caches=1 sudo sysctl -w vm.zone_reclaim_mode=0 sudo sysctl -w vm.max_map_count=655360 sudo sysctl -w vm.dirty_background_ratio=50 sudo sysctl -w vm.dirty_ratio=50 sudo sysctl -w vm.dirty_writeback_centisecs=360000 sudo sysctl -w vm.page-cluster=3 sudo sysctl -w vm.swappiness=1 echo 'ulimit -n 655350' >> /etc/profile echo '* hard nofile 655350' >> /etc/security/limits.conf echo '* hard memlock unlimited' >> /etc/security/limits.conf echo '* soft memlock unlimited' >> /etc/security/limits.conf DISK=`df -k | sort -n -r -k 2 | awk -F/ 'NR==1 {gsub(/[0-9].*/,"",$3); print $3}'` [ "$DISK" = 'cciss' ] && DISK='cciss!c0d0' echo 'deadline' > /sys/block/${DISK}/queue/scheduler echo "---------------------------------------------------------------" sysctl vm.extra_free_kbytes sysctl vm.min_free_kbytes sysctl vm.overcommit_memory sysctl vm.drop_caches sysctl vm.zone_reclaim_mode sysctl vm.max_map_count sysctl vm.dirty_background_ratio sysctl vm.dirty_ratio sysctl vm.dirty_writeback_centisecs sysctl vm.page-cluster sysctl vm.swappiness su - admin -c 'ulimit -n' cat /sys/block/$DISK/queue/scheduler if [ -d ${HOME}/tmpfs ] ; then echo "tmpfs exist, do nothing." else ln -s /dev/shm ${HOME}/tmpfs echo "create tmpfs ok" fi ================================================ FILE: distribution/bin/play.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. START /B mqnamesrv > ns.log 2>&1 IF %ERRORLEVEL% NEQ 0 ( echo "Failed to start name server. Please check ns.log" EXIT /B 1 ) START /B mqbroker -n localhost:9876 > bk.log 2>&1 IF %ERRORLEVEL% NEQ 0 ( ECHO "Failed to start broker. Please check bk.log" EXIT /B 1 ) echo "Start Name Server and Broker Successfully." ================================================ FILE: distribution/bin/play.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Name Server # nohup sh mqnamesrv > ns.log 2>&1 & # # Service Addr # ADDR=`hostname -i`:9876 # # Broker # nohup sh mqbroker -n ${ADDR} > bk.log 2>&1 & echo "Start Name Server and Broker Successfully, ${ADDR}" ================================================ FILE: distribution/bin/runbroker.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! & EXIT /B 1 set "JAVA=%JAVA_HOME%\bin\java.exe" setlocal set BASE_DIR=%~dp0 set BASE_DIR=%BASE_DIR:~0,-1% for %%d in (%BASE_DIR%) do set BASE_DIR=%%~dpd set CLASSPATH=.;%BASE_DIR%conf;%BASE_DIR%lib\*;"%CLASSPATH%" rem =========================================================================================== rem JVM Configuration rem =========================================================================================== for /f "tokens=2 delims=" %%v in ('java -version 2^>^&1 ^| findstr /i "version"') do ( for /f "tokens=1 delims=." %%m in ("%%v") do set "JAVA_MAJOR_VERSION=%%m" ) if "%JAVA_MAJOR_VERSION%"=="" ( set "JAVA_MAJOR_VERSION=0" ) if %JAVA_MAJOR_VERSION% lss 17 ( set "JAVA_OPT=%JAVA_OPT% -server -Xms2g -Xmx2g" set "JAVA_OPT=%JAVA_OPT% -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8" set "JAVA_OPT=%JAVA_OPT% -verbose:gc -Xloggc:%USERPROFILE%\mq_gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" set "JAVA_OPT=%JAVA_OPT% -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" set "JAVA_OPT=%JAVA_OPT% -XX:+AlwaysPreTouch" set "JAVA_OPT=%JAVA_OPT% -XX:MaxDirectMemorySize=15g" set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages -XX:-UseBiasedLocking" set "JAVA_OPT=%JAVA_OPT% %JAVA_OPT_EXT% -cp %CLASSPATH%" ) else ( set "JAVA_OPT=%JAVA_OPT% -server -Xms2g -Xmx2g" set "JAVA_OPT=%JAVA_OPT% -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8" rem set "JAVA_OPT=%JAVA_OPT% -verbose:gc -Xloggc:%USERPROFILE%\mq_gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" rem set "JAVA_OPT=%JAVA_OPT% -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" set "JAVA_OPT=%JAVA_OPT% -XX:+AlwaysPreTouch" set "JAVA_OPT=%JAVA_OPT% -XX:MaxDirectMemorySize=15g" set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages -XX:-UseBiasedLocking -XX:+IgnoreUnrecognizedVMOptions" set "JAVA_OPT=%JAVA_OPT% %JAVA_OPT_EXT% -cp "%CLASSPATH%"" ) "%JAVA%" %JAVA_OPT% %* ================================================ FILE: distribution/bin/runbroker.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #=========================================================================================== # Java Environment Setting #=========================================================================================== error_exit () { echo "ERROR: $1 !!" exit 1 } find_java_home() { case "`uname`" in Darwin) if [ -n "$JAVA_HOME" ]; then JAVA_HOME=$JAVA_HOME return fi JAVA_HOME=$(/usr/libexec/java_home) ;; *) JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac } find_java_home [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java [ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" export BASE_DIR=$(dirname $0)/.. export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} #=========================================================================================== # JVM Configuration #=========================================================================================== # The RAMDisk initializing size in MB on Darwin OS for gc-log DIR_SIZE_IN_MB=600 choose_gc_log_directory() { case "`uname`" in Darwin) if [ ! -d "/Volumes/RAMDisk" ]; then # create ram disk on Darwin systems as gc-log directory DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS." fi GC_LOG_DIR="/Volumes/RAMDisk" ;; *) # check if /dev/shm exists on other systems if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac } choose_gc_options() { JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/^1\.//' | cut -d'.' -f1) if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "8" ] ; then JAVA_OPT="${JAVA_OPT} -Xmn4g -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" fi if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M" fi } choose_gc_log_directory JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g" choose_gc_options JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch" JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g" JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking -XX:+IgnoreUnrecognizedVMOptions" #JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n" JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" numactl --interleave=all pwd > /dev/null 2>&1 if [ $? -eq 0 ] then if [ -z "$RMQ_NUMA_NODE" ] ; then numactl --interleave=all $JAVA ${JAVA_OPT} $@ else numactl --cpunodebind=$RMQ_NUMA_NODE --membind=$RMQ_NUMA_NODE $JAVA ${JAVA_OPT} $@ fi else "$JAVA" ${JAVA_OPT} $@ fi ================================================ FILE: distribution/bin/runserver.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! & EXIT /B 1 set "JAVA=%JAVA_HOME%\bin\java.exe" setlocal set BASE_DIR=%~dp0 set BASE_DIR=%BASE_DIR:~0,-1% for %%d in (%BASE_DIR%) do set BASE_DIR=%%~dpd set CLASSPATH=.;%BASE_DIR%conf;%BASE_DIR%lib\*;%CLASSPATH% REM Example of JAVA_MAJOR_VERSION value: '1', '9', '10', '11', ... REM '1' means releases before Java 9 for /f "tokens=2 delims=" %%v in ('java -version 2^>^&1 ^| findstr /i "version"') do ( for /f "tokens=1 delims=." %%m in ("%%v") do set "JAVA_MAJOR_VERSION=%%m" ) if "%JAVA_MAJOR_VERSION%"=="" ( set "JAVA_MAJOR_VERSION=0" ) if %JAVA_MAJOR_VERSION% lss 17 ( set "JAVA_OPT=%JAVA_OPT% -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" set "JAVA_OPT=%JAVA_OPT% -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" set "JAVA_OPT=%JAVA_OPT% -verbose:gc -Xloggc:"%USERPROFILE%\rmq_srv_gc.log" -XX:+PrintGCDetails -XX:+PrintGCDateStamps" set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages" set "JAVA_OPT=%JAVA_OPT% %JAVA_OPT_EXT% -cp "%CLASSPATH%"" ) else ( set "JAVA_OPT=%JAVA_OPT% -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" rem set "JAVA_OPT=%JAVA_OPT% -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" rem set "JAVA_OPT=%JAVA_OPT% -verbose:gc -Xloggc:"%USERPROFILE%\rmq_srv_gc.log" -XX:+PrintGCDetails -XX:+PrintGCDateStamps" set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages" set "JAVA_OPT=%JAVA_OPT% %JAVA_OPT_EXT% -cp "%CLASSPATH%"" ) "%JAVA%" %JAVA_OPT% %* ================================================ FILE: distribution/bin/runserver.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #=========================================================================================== # Java Environment Setting #=========================================================================================== error_exit () { echo "ERROR: $1 !!" exit 1 } find_java_home() { case "`uname`" in Darwin) if [ -n "$JAVA_HOME" ]; then JAVA_HOME=$JAVA_HOME return fi JAVA_HOME=$(/usr/libexec/java_home) ;; *) JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac } find_java_home [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java [ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" export BASE_DIR=$(dirname $0)/.. export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} #=========================================================================================== # JVM Configuration #=========================================================================================== # The RAMDisk initializing size in MB on Darwin OS for gc-log DIR_SIZE_IN_MB=600 choose_gc_log_directory() { case "`uname`" in Darwin) if [ ! -d "/Volumes/RAMDisk" ]; then # create ram disk on Darwin systems as gc-log directory DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS." fi GC_LOG_DIR="/Volumes/RAMDisk" ;; *) # check if /dev/shm exists on other systems if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac } choose_gc_options() { # Example of JAVA_MAJOR_VERSION value : '1', '9', '10', '11', ... # '1' means releases before Java 9 JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{print $1}') if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps" JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" else JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M" fi } choose_gc_log_directory choose_gc_options JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages" #JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n" JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" "$JAVA" ${JAVA_OPT} $@ ================================================ FILE: distribution/bin/setcache.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. export PATH=$PATH:/sbin # # GB # function changeFreeCache() { EXTRA=$1 MIN=$2 sysctl -w vm.extra_free_kbytes=${EXTRA}000000 sysctl -w vm.min_free_kbytes=${MIN}000000 sysctl -w vm.swappiness=0 } if [ $# -ne 2 ] then echo "Usage: $0 extra_free_kbytes(GB) min_free_kbytes(GB)" echo "Example: $0 3 1" exit fi changeFreeCache $1 $2 ================================================ FILE: distribution/bin/startfsrv.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME nohup sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.filtersrv.FiltersrvStartup $@ & ================================================ FILE: distribution/bin/tools.cmd ================================================ @echo off rem Licensed to the Apache Software Foundation (ASF) under one or more rem contributor license agreements. See the NOTICE file distributed with rem this work for additional information regarding copyright ownership. rem The ASF licenses this file to You under the Apache License, Version 2.0 rem (the "License"); you may not use this file except in compliance with rem the License. You may obtain a copy of the License at rem rem http://www.apache.org/licenses/LICENSE-2.0 rem rem Unless required by applicable law or agreed to in writing, software rem distributed under the License is distributed on an "AS IS" BASIS, rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! & EXIT /B 1 set "JAVA=%JAVA_HOME%\bin\java.exe" setlocal set BASE_DIR=%~dp0 set BASE_DIR=%BASE_DIR:~0,-1% for %%d in (%BASE_DIR%) do set BASE_DIR=%%~dpd set CLASSPATH=.;%BASE_DIR%conf;%BASE_DIR%lib\*;%CLASSPATH% rem =========================================================================================== rem JVM Configuration rem =========================================================================================== set "JAVA_OPT=%JAVA_OPT% -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m" set "JAVA_OPT=%JAVA_OPT% -cp "%CLASSPATH%"" "%JAVA%" %JAVA_OPT% %* ================================================ FILE: distribution/bin/tools.sh ================================================ #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #=========================================================================================== # Java Environment Setting #=========================================================================================== error_exit () { echo "ERROR: $1 !!" exit 1 } find_java_home() { if [ -n "$JAVA_HOME" ]; then JAVA_HOME=$JAVA_HOME return fi case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac } find_java_home [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java [ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" export BASE_DIR=$(dirname $0)/.. export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} #=========================================================================================== # JVM Configuration #=========================================================================================== JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" "$JAVA" ${JAVA_OPT} "$@" ================================================ FILE: distribution/conf/2m-2s-async/broker-a-s.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-a brokerId=1 deleteWhen=04 fileReservedTime=48 brokerRole=SLAVE flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-async/broker-a.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-async/broker-b-s.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-b brokerId=1 deleteWhen=04 fileReservedTime=48 brokerRole=SLAVE flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-async/broker-b.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-b brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-sync/broker-a-s.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-a brokerId=1 deleteWhen=04 fileReservedTime=48 brokerRole=SLAVE flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-sync/broker-a.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=SYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-sync/broker-b-s.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-b brokerId=1 deleteWhen=04 fileReservedTime=48 brokerRole=SLAVE flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-2s-sync/broker-b.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-b brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=SYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-noslave/broker-a.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-noslave/broker-b.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName=DefaultCluster brokerName=broker-b brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/2m-noslave/broker-trace.properties ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # brokerClusterName=DefaultCluster brokerName=broker-trace brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH ================================================ FILE: distribution/conf/broker.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH ================================================ FILE: distribution/conf/container/2container-2m-2s/broker-a-in-container1.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #Master配置 brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 brokerRole=SYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/root/broker-a/store storePathCommitLog=/root/broker-a/store/commitlog listenPort=10911 haListenPort=10912 totalReplicas=2 inSyncReplicas=2 minInSyncReplicas=1 enableAutoInSyncReplicas=true slaveReadEnable=true brokerHeartbeatInterval=1000 brokerNotActiveTimeoutMillis=5000 sendHeartbeatTimeoutMillis=1000 enableSlaveActingMaster=true ================================================ FILE: distribution/conf/container/2container-2m-2s/broker-a-in-container2.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #Master配置 brokerClusterName=DefaultCluster brokerName=broker-a brokerId=1 brokerRole=SLAVE flushDiskType=ASYNC_FLUSH storePathRootDir=/root/broker-a/store storePathCommitLog=/root/broker-a/store/commitlog listenPort=10911 haListenPort=10912 totalReplicas=2 inSyncReplicas=2 minInSyncReplicas=1 enableAutoInSyncReplicas=true slaveReadEnable=true brokerHeartbeatInterval=1000 brokerNotActiveTimeoutMillis=5000 sendHeartbeatTimeoutMillis=1000 enableSlaveActingMaster=true ================================================ FILE: distribution/conf/container/2container-2m-2s/broker-b-in-container1.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #Slave配置 brokerClusterName=DefaultCluster brokerName=broker-b brokerId=1 brokerRole=SLAVE flushDiskType=ASYNC_FLUSH storePathRootDir=/root/broker-b/store storePathCommitLog=/root/broker-b/store/commitlog listenPort=20911 haListenPort=20912 totalReplicas=2 inSyncReplicas=2 minInSyncReplicas=1 enableAutoInSyncReplicas=true slaveReadEnable=true brokerHeartbeatInterval=1000 brokerNotActiveTimeoutMillis=5000 sendHeartbeatTimeoutMillis=1000 enableSlaveActingMaster=true ================================================ FILE: distribution/conf/container/2container-2m-2s/broker-b-in-container2.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #Slave配置 brokerClusterName=DefaultCluster brokerName=broker-b brokerId=0 brokerRole=SYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/root/broker-b/store storePathCommitLog=/root/broker-b/store/commitlog listenPort=20911 haListenPort=20912 totalReplicas=2 inSyncReplicas=2 minInSyncReplicas=1 enableAutoInSyncReplicas=true slaveReadEnable=true brokerHeartbeatInterval=1000 brokerNotActiveTimeoutMillis=5000 sendHeartbeatTimeoutMillis=1000 enableSlaveActingMaster=true ================================================ FILE: distribution/conf/container/2container-2m-2s/broker-container1.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #配置端口,用于接收mqadmin命令 listenPort=10811 #指定namesrv namesrvAddr=172.22.144.49:9876 #或指定自动获取namesrv fetchNamesrvAddrByAddressServer=false #指定要向BrokerContainer内添加的brokerConfig路径,多个config间用“:”分隔; #不指定则只启动BrokerConainer,具体broker可通过mqadmin工具添加 brokerConfigPaths=/root/2container-2m-2s/broker-a-in-container1.conf:/root/2container-2m-2s/broker-b-in-container1.conf ================================================ FILE: distribution/conf/container/2container-2m-2s/broker-container2.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #配置端口,用于接收mqadmin命令 listenPort=10811 #指定namesrv namesrvAddr=172.22.144.49:9876 #或指定自动获取namesrv fetchNamesrvAddrByAddressServer=false #指定要向BrokerContainer内添加的brokerConfig路径,多个config间用“:”分隔; #不指定则只启动BrokerConainer,具体broker可通过mqadmin工具添加 brokerConfigPaths=/root/2container-2m-2s/broker-a-in-container2.conf:/root/2container-2m-2s/broker-b-in-container2.conf ================================================ FILE: distribution/conf/container/2container-2m-2s/nameserver.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. supportActingMaster=true ================================================ FILE: distribution/conf/controller/cluster-3n-independent/controller-n0.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878;n1-127.0.0.1:9868;n2-127.0.0.1:9858 controllerDLegerSelfId = n0 ================================================ FILE: distribution/conf/controller/cluster-3n-independent/controller-n1.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878;n1-127.0.0.1:9868;n2-127.0.0.1:9858 controllerDLegerSelfId = n1 ================================================ FILE: distribution/conf/controller/cluster-3n-independent/controller-n2.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878;n1-127.0.0.1:9868;n2-127.0.0.1:9858 controllerDLegerSelfId = n2 ================================================ FILE: distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf ================================================ #Namesrv config listenPort = 9876 enableControllerInNamesrv = true #controller config controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878;n1-127.0.0.1:9868;n2-127.0.0.1:9858 controllerDLegerSelfId = n0 ================================================ FILE: distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf ================================================ #Namesrv config listenPort = 9886 enableControllerInNamesrv = true #controller config controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878;n1-127.0.0.1:9868;n2-127.0.0.1:9858 controllerDLegerSelfId = n1 ================================================ FILE: distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf ================================================ #Namesrv config listenPort = 9896 enableControllerInNamesrv = true #controller config controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878;n1-127.0.0.1:9868;n2-127.0.0.1:9858 controllerDLegerSelfId = n2 ================================================ FILE: distribution/conf/controller/controller-standalone.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878 controllerDLegerSelfId = n0 ================================================ FILE: distribution/conf/controller/quick-start/broker-n0.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = DefaultCluster brokerName = broker-a brokerId = -1 brokerRole = SLAVE deleteWhen = 04 fileReservedTime = 48 enableControllerMode = true controllerAddr = 127.0.0.1:9878 namesrvAddr = 127.0.0.1:9876 allAckInSyncStateSet=true listenPort=30911 storePathRootDir=/tmp/rmqstore/node00 storePathCommitLog=/tmp/rmqstore/node00/commitlog ================================================ FILE: distribution/conf/controller/quick-start/broker-n1.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = DefaultCluster brokerName = broker-a brokerId = -1 brokerRole = SLAVE deleteWhen = 04 fileReservedTime = 48 enableControllerMode = true controllerAddr = 127.0.0.1:9878 namesrvAddr = 127.0.0.1:9876 allAckInSyncStateSet=true listenPort=30921 storePathRootDir=/tmp/rmqstore/node01 storePathCommitLog=/tmp/rmqstore/node01/commitlog ================================================ FILE: distribution/conf/controller/quick-start/namesrv.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. enableControllerInNamesrv = true controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9878 controllerDLegerSelfId = n0 ================================================ FILE: distribution/conf/dledger/broker-n0.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = RaftCluster brokerName=RaftNode00 listenPort=30911 namesrvAddr=127.0.0.1:9876 storePathRootDir=/tmp/rmqstore/node00 storePathCommitLog=/tmp/rmqstore/node00/commitlog enableDLegerCommitLog=true dLegerGroup=RaftNode00 dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 ## must be unique dLegerSelfId=n0 sendMessageThreadPoolNums=16 ================================================ FILE: distribution/conf/dledger/broker-n1.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = RaftCluster brokerName=RaftNode00 listenPort=30921 namesrvAddr=127.0.0.1:9876 storePathRootDir=/tmp/rmqstore/node01 storePathCommitLog=/tmp/rmqstore/node01/commitlog enableDLegerCommitLog=true dLegerGroup=RaftNode00 dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 ## must be unique dLegerSelfId=n1 sendMessageThreadPoolNums=16 ================================================ FILE: distribution/conf/dledger/broker-n2.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = RaftCluster brokerName=RaftNode00 listenPort=30931 namesrvAddr=127.0.0.1:9876 storePathRootDir=/tmp/rmqstore/node02 storePathCommitLog=/tmp/rmqstore/node02/commitlog enableDLegerCommitLog=true dLegerGroup=RaftNode00 dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 ## must be unique dLegerSelfId=n2 sendMessageThreadPoolNums=16 ================================================ FILE: distribution/conf/rmq-proxy.json ================================================ { "rocketMQClusterName": "DefaultCluster" } ================================================ FILE: distribution/conf/tools.yml ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. accessKey: rocketmq2 secretKey: 12345678 ================================================ FILE: distribution/pom.xml ================================================ 4.0.0 org.apache.rocketmq rocketmq-all ${revision} rocketmq-distribution rocketmq-distribution ${project.version} pom ${basedir}/.. release-all org.apache.rocketmq rocketmq-container org.apache.rocketmq rocketmq-controller org.apache.rocketmq rocketmq-broker org.apache.rocketmq rocketmq-proxy org.apache.rocketmq rocketmq-client org.apache.rocketmq rocketmq-tools org.apache.rocketmq rocketmq-example maven-assembly-plugin release-all single package release.xml false rocketmq-${project.version} release-client org.apache.rocketmq rocketmq-client maven-assembly-plugin release-client single package release-client.xml false rocketmq-client-${project.version} ================================================ FILE: distribution/release-client.xml ================================================ client true dir tar.gz zip ../ README.md LICENSE-BIN LICENSE NOTICE-BIN NOTICE true org.apache.rocketmq:rocketmq-client org.apache.rocketmq:rocketmq-openmessaging ./ false ./ ================================================ FILE: distribution/release.xml ================================================ all true dir tar.gz zip ../ README.md conf/** benchmark/* bin/** 0755 LICENSE-BIN LICENSE NOTICE-BIN NOTICE ../broker/src/main/resources/rmq.broker.logback.xml conf/rmq.broker.logback.xml ../client/src/main/resources/rmq.client.logback.xml conf/rmq.client.logback.xml ../controller/src/main/resources/rmq.controller.logback.xml conf/rmq.controller.logback.xml ../namesrv/src/main/resources/rmq.namesrv.logback.xml conf/rmq.namesrv.logback.xml ../tools/src/main/resources/rmq.tools.logback.xml conf/rmq.tools.logback.xml ../proxy/src/main/resources/rmq.proxy.logback.xml conf/rmq.proxy.logback.xml true org.apache.rocketmq:rocketmq-container org.apache.rocketmq:rocketmq-broker org.apache.rocketmq:rocketmq-tools org.apache.rocketmq:rocketmq-client org.apache.rocketmq:rocketmq-namesrv org.apache.rocketmq:rocketmq-example org.apache.rocketmq:rocketmq-openmessaging org.apache.rocketmq:rocketmq-controller lib/ false lib/ io.jaegertracing:jaeger-core io.jaegertracing:jaeger-client ================================================ FILE: docs/cn/BrokerContainer.md ================================================ # BrokerContainer ## 背景 在RocketMQ 4.x 版本中,一个进程只有一个broker,通常会以主备或者DLedger(Raft)的形式部署,但是一个进程中只有一个broker,而slave一般只承担冷备或热备的作用,节点之间角色的不对等导致slave节点资源没有充分被利用。 因此在RocketMQ 5.x 版本中,提供一种新的模式BrokerContainer,在一个BrokerContainer进程中可以加入多个Broker(Master Broker、Slave Broker、DLedger Broker),来提高单个节点的资源利用率,并且可以通过各种形式的交叉部署来实现节点之间的对等部署。 该特性的优点包括: 1. 一个BrokerContainer进程中可以加入多个broker,通过进程内混部来提高单个节点的资源利用率 2. 通过各种形式的交叉部署来实现节点之间的对等部署,增强单节点的高可用能力 3. 利用BrokerContainer可以实现单进程内多CommitLog写入,也可以实现单机的多磁盘写入 4. BrokerContainer中的CommitLog天然隔离的,不同的CommitLog(broker)可以采取不同作用,比如可以用来比如创建单独的broker做不同TTL的CommitLog。 ## 架构 ### 单进程视图 ![](https://s4.ax1x.com/2022/01/26/7LMZHP.png) 相比于原来一个Broker一个进程,RocketMQ 5.0将增加BrokerContainer概念,一个BrokerContainer可以存放多个Broker,每个Broker拥有不同的端口,但它们共享同一个传输层(remoting层),而每一个broker在功能上是完全独立的。BrokerContainer也拥有自己端口,在运行时可以通过admin命令来增加或减少Broker。 ### 对等部署形态 在BrokerContainer模式下,可以通过各种形式的交叉部署完成节点的对等部署 - 二副本对等部署 ![](https://s4.ax1x.com/2022/01/26/7LQi5T.png) 二副本对等部署情况下,每个节点都会有一主一备,资源利用率均等。另外假设图中Node1宕机,由于Node2的broker_2可读可写,broker_1可以备读,因此普通消息的收发不会收到影响,单节点的高可用能力得到了增强。 - 三副本对等部署 ![](https://s4.ax1x.com/2022/01/26/7LQMa6.png) 三副本对等部署情况下,每个节点都会有一主两备,资源利用率均等。此外,和二副本一样,任意一个节点的宕机也不会影响到普通消息的收发。 ### 传输层共享 ![](https://s4.ax1x.com/2022/02/07/HMNIVs.png) BrokerContainer中的所有broker共享同一个传输层,就像RocketMQ客户端中同进程的Consumer和Producer共享同一个传输层一样。 这里为NettyRemotingServer提供SubRemotingServer支持,通过为一个RemotingServer绑定另一个端口即可生成SubRemotingServer,其共享NettyRemotingServer的Netty实例、计算资源、以及协议栈等,但拥有不同的端口以及ProcessorTable。另外同一个BrokerContainer中的所有的broker也会共享同一个BrokerOutAPI(RemotingClient)。 ## 启动方式和配置 ![](https://s4.ax1x.com/2022/01/26/7LQ1PO.png) 像Broker启动利用BrokerStartup一样,使用BrokerContainerStartup来启动BrokerContainer。我们可以通过两种方式向BrokerContainer中增加broker,一种是通过启动时通过在配置文件中指定 BrokerContainer配置文件内容主要是Netty网络层参数(由于传输层共享),BrokerContainer的监听端口、namesrv配置,以及最重要的brokerConfigPaths参数,brokerConfigPaths是指需要向BrokerContainer内添加的brokerConfig路径,多个config间用“:”分隔,不指定则只启动BrokerConainer,具体broker可通过mqadmin工具添加 broker-container.conf(distribution/conf/container/broker-container.conf): ``` #配置端口,用于接收mqadmin命令 listenPort=10811 #指定namesrv namesrvAddr=127.0.0.1:9876 #或指定自动获取namesrv fetchNamesrvAddrByAddressServer=true #指定要向BrokerContainer内添加的brokerConfig路径,多个config间用“:”分隔; #不指定则只启动BrokerConainer,具体broker可通过mqadmin工具添加 brokerConfigPaths=/home/admin/broker-a.conf:/home/admin/broker-b.conf ``` broker的配置和以前一样,但在BrokerContainer模式下broker配置文件中下Netty网络层参数和nameserver参数不生效,均使用BrokerContainer的配置参数。 完成配置文件后,可以以如下命令启动 ``` sh mqbrokercontainer -c broker-container.conf ``` mqbrokercontainer脚本路径为distribution/bin/mqbrokercontainer。 ## 运行时增加或减少Broker 当BrokerContainer进程启动后,也可以通过Admin命令来增加或减少Broker。 AddBrokerCommand ``` usage: mqadmin addBroker -b -c [-h] [-n ] -b,--brokerConfigPath Broker config path -c,--brokerContainerAddr Broker container address -h,--help Print help -n,--namesrvAddr Name server address list, eg: 192.168.0.1:9876;192.168.0.2:9876 ``` RemoveBroker Command ``` usage: mqadmin removeBroker -b -c [-h] [-n ] -b,--brokerIdentity Information to identify a broker: clusterName:brokerName:brokerId -c,--brokerContainerAddr Broker container address -h,--help Print help -n,--namesrvAddr Name server address list, eg: 192.168.0.1:9876;192.168.0.2:9876 ``` ## 存储变化 storePathRootDir,storePathCommitLog路径依然为MessageStoreConfig中配置值,需要注意的是同一个brokerContainer中的broker不能使用相同的storePathRootDir,storePathCommitLog,否则不同的broker占用同一个存储目录,发生数据混乱。 在文件删除策略上,仍然单个Broker的视角来进行删除,但MessageStoreConfig新增replicasPerDiskPartition参数和logicalDiskSpaceCleanForciblyThreshold。 replicasPerDiskPartition表示同一磁盘分区上有多少个副本,即该broker的存储目录所在的磁盘分区被几个broker共享,默认值为1。该配置用于计算当同一节点上的多个broker共享同一磁盘分区时,各broker的磁盘配额 e.g. replicasPerDiskPartition==2且broker所在磁盘空间为1T时,则该broker磁盘配额为512G,该broker的逻辑磁盘空间利用率基于512G的空间进行计算。 logicalDiskSpaceCleanForciblyThreshold,该值只在replicasPerDiskPartition大于1时生效,表示逻辑磁盘空间强制清理阈值,默认为0.80(80%), 逻辑磁盘空间利用率为该broker在自身磁盘配额内的空间利用率,物理磁盘空间利用率为该磁盘分区总空间利用率。由于在BrokerContainer实现中,考虑计算效率的情况下,仅统计了commitLog+consumeQueue(+ BCQ)+indexFile作为broker的存储空间占用,其余文件如元数据、消费进度、磁盘脏数据等未统计在内,故在多个broker存储空间达到动态平衡时,各broker所占空间可能有相差,以一个BrokerContainer中有两个broker为例,两broker存储空间差异可表示为: ![](https://s4.ax1x.com/2022/01/26/7L14v4.png) 其中,R_logical为logicalDiskSpaceCleanForciblyThreshold,R_phy为diskSpaceCleanForciblyRatio,T为磁盘分区总空间,x为除上述计算的broker存储空间外的其他文件所占磁盘总空间比例,可见,当 ![](https://s4.ax1x.com/2022/01/26/7L1TbR.png) 时,可保证BrokerContainer各Broker存储空间在达到动态平衡时相差无几。 eg.假设broker获取到的配额是500g(根据replicasPerDiskPartition计算获得),logicalDiskSpaceCleanForciblyThreshold为默认值0.8,则默认commitLog+consumeQueue(+ BCQ)+indexFile总量超过400g就会强制清理文件。 其他清理阈值(diskSpaceCleanForciblyRatio、diskSpaceWarningLevelRatio),文件保存时间(fileReservedTime)等逻辑与之前保持一致。 注意:当以普通broker方式启动而非brokerContainer启动时,且replicasPerDiskPartition=1(默认值)时,清理逻辑与之前完全一致。replicasPerDiskPartition>1时,逻辑磁盘空间强制清理阈值logicalDiskSpaceCleanForciblyThreshold将会生效。 ## 日志变化 在BrokerContainer模式下日志的默认输出路径将发生变化,具体为: ``` {user.home}/logs/rocketmqlogs/${brokerCanonicalName}/ ``` 其中 `brokerCanonicalName` 为 `{BrokerClusterName_BrokerName_BrokerId}`。 ================================================ FILE: docs/cn/Configuration_System.md ================================================ # 系统配置 本节重点介绍系统(JVM/OS)的配置 --- ## **1 JVM 选项** ## 建议使用最新发布的 JDK 1.8 版本。设置相同的 Xms 和 Xmx 值以防止 JVM 调整堆大小,并获得更好的性能。一种通用的JVM配置如下: -server -Xms8g -Xmx8g -Xmn4g 设置 Direct ByteBuffer 内存大小。当 Direct ByteBuffer 达到指定大小时,将触发 Full GC: -XXMaxDirectMemorySize=15g 如果你不在乎 RocketMQ broker 的启动时间,建议启用预分配 Java 堆以确保在 JVM 初始化期间为每个页面分配内存。你可以通过以下方式启用它: -XX+AlwaysPreTouch 禁用偏向锁定可以减少 JVM 停顿: -XX-UseBiasedLocking 关于垃圾收集器,推荐使用 JDK 1.8 的 G1 收集器: -XX+UseG1GC -XXG1HeapRegionSize=16m -XXG1ReservePercent=25 -XXInitiatingHeapOccupancyPercent=30 这些 GC 选项看起来有点激进,但事实证明它在生产环境中具有良好的性能 不要把-XXMaxGCPauseMillis 的值设置太小,否则JVM会使用一个小的新生代来实现这个目标,从而导致频繁发生minor GC。因此,建议使用滚动 GC 日志文件: -XX+UseGCLogFileRotation -XXNumberOfGCLogFiles=5 -XXGCLogFileSize=30m 写 GC 文件会增加 broker 的延迟,因此可以考虑将 GC 日志文件重定向到内存文件系统: -Xloggcdevshmmq_gc_%p.log123 ## 2 Linux 内核参数 ## 在 bin 文件夹里,有一个 os.sh 脚本,里面列出了许多的内核参数,只需稍作更改即可用于生产用途。需特别关注以下参数,如想了解更多细节,请参考文档/proc/sys/vm/*。 - **vm.extra_free_kbytes**, 控制VM在后台回收(kswapd)开始的阈值和直接回收(通过分配进程)开始的阈值之间保留额外的空闲内存。通过使用这个参数,RocketMQ 可以避免在内存分配过程中出现高延迟。(与内核版本有关) - **vm.min_free_kbytes**, 该值不应设置低于1024KB,否则系统将遭到破坏,并且在高负载环境下容易出现死锁。 - **vm.max_map_count**, 规定进程可以拥有的最大内存映射区域数。 RocketMQ 使用 mmap 来加载 CommitLog 和 ConsumeQueue,因此建议将此参数设置为较大的值。 - **vm.swappiness**, 定义内核交换内存页的频率。该值若较大,则会导致频繁交换,较小则会减少交换量。为了避免交换延迟,建议将此值设为 10。 - **File descriptor limits**, RocketMQ 需要给文件(CommitLog 和 ConsumeQueue)和网络连接分配文件描述符。因此建议将该值设置为 655350。 - **Disk scheduler**, 推荐使用deadline IO 调度器,它可以为请求提供有保证的延迟。 ================================================ FILE: docs/cn/Configuration_TLS.md ================================================ # TLS配置 本节介绍TLS相关配置 ## 1 生成证书 开发、测试的证书可以自行安装OpenSSL进行生成.建议在Linux环境下安装Open SSL并进行证书生成。 ### 1.1 生成ca.pem ```shell openssl req -newkey rsa:2048 -keyout ca_rsa_private.pem -x509 -days 365 -out ca.pem ``` ### 1.2 生成server.csr ```shell openssl req -newkey rsa:2048 -keyout server_rsa.key -out server.csr ``` ### 1.3 生成server.pem ```shell openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca_rsa_private.pem -CAcreateserial -out server.pem ``` ### 1.4 生成client.csr ```shell openssl req -newkey rsa:2048 -keyout client_rsa.key -out client.csr ``` ### 1.5 生成client.pem ```shell openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca_rsa_private.pem -CAcreateserial -out client.pem ``` ### 1.6 生成server.key ```shell openssl pkcs8 -topk8 -v1 PBE-SHA1-RC4-128 -in server_rsa.key -out server.key ``` ### 1.7 生成client.key ```shell openssl pkcs8 -topk8 -v1 PBE-SHA1-RC4-128 -in client_rsa.key -out client.key ``` ## 2 创建tls.properties 创建tls.properties文件,并将生成证书的路径和密码进行正确的配置. ```properties # The flag to determine whether use test mode when initialize TLS context. default is true tls.test.mode.enable=false # Indicates how SSL engine respect to client authentication, default is none tls.server.need.client.auth=require # The store path of server-side private key tls.server.keyPath=/opt/certFiles/server.key # The password of the server-side private key tls.server.keyPassword=123456 # The store path of server-side X.509 certificate chain in PEM format tls.server.certPath=/opt/certFiles/server.pem # To determine whether verify the client endpoint's certificate strictly. default is false tls.server.authClient=false # The store path of trusted certificates for verifying the client endpoint's certificate tls.server.trustCertPath=/opt/certFiles/ca.pem # The ciphers in TLS # tls.ciphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # The protocols in TLS # tls.protocols=TLSv1.2,TLSv1.3 ``` 如果需要客户端连接时也进行认证,则还需要在该文件中增加以下内容 ```properties # The store path of client-side private key tls.client.keyPath=/opt/certFiles/client.key # The password of the client-side private key tls.client.keyPassword=123456 # The store path of client-side X.509 certificate chain in PEM format tls.client.certPath=/opt/certFiles/client.pem # To determine whether verify the server endpoint's certificate strictly tls.client.authServer=false # The store path of trusted certificates for verifying the server endpoint's certificate tls.client.trustCertPath=/opt/certFiles/ca.pem # The ciphers in TLS # tls.ciphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # The protocols in TLS # tls.protocols=TLSv1.2,TLSv1.3 ``` ## 3 配置Rocketmq启动参数 编辑rocketmq/bin路径下的配置文件,使tls.properties配置生效.-Dtls.config.file的值需要替换为步骤2中创建的tls.peoperties文件的路径 ### 3.1 编辑runserver.sh,在JAVA_OPT中增加以下内容: ```shell JAVA_OPT="${JAVA_OPT} -Dtls.server.mode=enforcing -Dtls.config.file=/opt/rocketmq-4.9.3/conf/tls.properties" ``` ### 3.2 编辑runbroker.sh,在JAVA_OPT中增加以下内容: ```shell JAVA_OPT="${JAVA_OPT} -Dorg.apache.rocketmq.remoting.ssl.mode=enforcing -Dtls.config.file=/opt/rocketmq-4.9.3/conf/tls.properties -Dtls.enable=true" ``` # 4 客户端连接 创建客户端使用的tlsclient.properties,并加入以下内容: ```properties # The store path of client-side private key tls.client.keyPath=/opt/certFiles/client.key # The password of the client-side private key tls.client.keyPassword=123456 # The store path of client-side X.509 certificate chain in PEM format tls.client.certPath=/opt/certFiles/client.pem # The store path of trusted certificates for verifying the server endpoint's certificate tls.client.trustCertPath=/opt/certFiles/ca.pem # The ciphers in TLS # tls.ciphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 # The protocols in TLS # tls.protocols=TLSv1.2,TLSv1.3 ``` JVM中需要加以下参数.tls.config.file的值需要使用之前创建的文件: ```shell -Dtls.client.authServer=true -Dtls.enable=true -Dtls.test.mode.enable=false -Dtls.config.file=/opt/certs/tlsclient.properties ``` 在客户端连接的代码中,需要将setUseTLS设置为true: ```java public class ExampleProducer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); //setUseTLS should be true producer.setUseTLS(true); producer.start(); // Send messages as usual. producer.shutdown(); } } ``` ## 5 Proxy TLS 配置 RocketMQ Proxy 使用 `rmq-proxy.json`(而非 `tls.properties`)进行 TLS 配置。Proxy 的 gRPC 和 Remoting 协议端口均支持 TLS。 ### 5.1 配置 rmq-proxy.json 在 `distribution/conf/rmq-proxy.json` 中添加 TLS 相关字段: ```json { "rocketMQClusterName": "DefaultCluster", "tlsTestModeEnable": false, "tlsKeyPath": "/opt/certFiles/server.key", "tlsKeyPassword": "123456", "tlsCertPath": "/opt/certFiles/server.pem" } ``` | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `tlsTestModeEnable` | boolean | `true` | 是否使用自签名测试证书,生产环境需设为 `false` | | `tlsKeyPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.key` | 服务端私钥文件路径(PKCS#8 PEM 格式) | | `tlsKeyPassword` | string | `""` | 加密私钥的密码,私钥未加密时留空 | | `tlsCertPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.crt` | 服务端证书链文件路径(X.509 PEM 格式) | | `tlsCertWatchIntervalMs` | int | `3600000` | 证书文件变更检测间隔(毫秒) | ### 5.2 配置 Proxy 启动参数 编辑 `runproxy.sh`(或启动 Proxy 的脚本),启用 TLS enforcing 模式: ```shell JAVA_OPT="${JAVA_OPT} -Dtls.server.mode=enforcing" ``` 三种 TLS 模式说明: - `disabled` - 不支持 TLS,拒绝所有 TLS 握手请求 - `permissive` - TLS 可选,同时接受 TLS 和非 TLS 连接 - `enforcing` - 强制 TLS,拒绝非 TLS 连接 ================================================ FILE: docs/cn/Debug_In_Idea.md ================================================ ## 本地调试RocketMQ ### Step0: 解决依赖问题 1. 运行前下载RocketMQ需要的maven依赖,可以使用`mvn clean install -Dmaven.test.skip=true` 2. 确保本地能够编译通过 ### Step1: 启动NameServer 1. NamerServer的启动类在`org.apache.rocketmq.namesrv.NamesrvStartup` 2. `Idea-Edit Configurations`中添加环境变量 `ROCKETMQ_HOME=` ![Idea_config_nameserver.png](image/Idea_config_nameserver.png) 3. 运行NameServer,观察到如下日志输出则启动成功 ```shell The Name Server boot success. serializeType=JSON, address 0.0.0.0:9876 ``` ### Step2: 启动Broker 1. Broker的启动类在`org.apache.rocketmq.broker.BrokerStartup` 2. 创建`/rocketmq/conf/broker.conf`文件或直接在官方release发布包中拷贝即可 ```shell # broker.conf brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH namesrvAddr = 127.0.0.1:9876 ``` 3. `Idea-Edit Configurations`中添加运行参数 `-c /Users/xxx/rocketmq/conf/broker.conf` 以及环境变量 `ROCKETMQ_HOME=` ![Idea_config_broker.png](image/Idea_config_broker.png) 4. 运行Broker,观察到如下日志则启动成功 ```shell The broker[broker-a,192.169.1.2:10911] boot success... ``` ### Step3: 发送或消费消息 至此已经完成了RocketMQ的启动,可以使用`/example`里的示例进行收发消息 ### 补充:本地启动Proxy 1. RocketMQ5.x支持了Proxy模式,使用`LOCAL`模式可以免去`Step2`,启动类在`org.apache.rocketmq.proxy.ProxyStartup` 2. `Idea-Edit Configurations`中添加环境变量 `ROCKETMQ_HOME=` 3. 在`/conf/`下新建配置文件`rmq-proxy.json` ```json { "rocketMQClusterName": "DefaultCluster", "nameSrvAddr": "127.0.0.1:9876", "proxyMode": "local" } ``` 4. 运行Proxy,观察到如下日志则启动成功 ```shell Sat Aug 26 15:29:33 CST 2023 rocketmq-proxy startup successfully ``` ================================================ FILE: docs/cn/Deployment.md ================================================ # 部署架构和设置步骤 ## 集群的设置 ### 1 单master模式 这是最简单但也是最危险的模式,一旦broker服务器重启或宕机,整个服务将不可用。 建议在生产环境中不要使用这种部署方式,在本地测试和开发可以选择这种模式。 以下是构建的步骤。 **1)启动NameServer** ```shell ### 第一步启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` 我们可以在namesrv.log 中看到'The Name Server boot success..',表示NameServer 已成功启动。 **2)启动Broker** ```shell ### 第一步先启动broker $ nohup sh bin/mqbroker -n localhost:9876 & ### 验证broker是否启动成功,比如,broker的ip是192.168.1.2 然后名字是broker-a $ tail -f ~/logs/rocketmqlogs/Broker.log The broker[broker-a,192.169.1.2:10911] boot success... ``` 我们可以在 Broker.log 中看到“The broker[brokerName,ip:port] boot success..”,这表明 broker 已成功启动。 ### 2 多Master模式 该模式是指所有节点都是master主节点(比如2个或3个主节点),没有slave从节点的模式。 这种模式的优缺点如下: - 优点: 1. 配置简单。 2. 一个master节点的宕机或者重启(维护)对应用程序没有影响。 3. 当磁盘配置为RAID10时,消息不会丢失,因为RAID10磁盘非常可靠,即使机器不可恢复(消息异步刷盘模式的情况下,会丢失少量消息;如果消息是同步刷盘模式,不会丢失任何消息)。 4. 在这种模式下,性能是最高的。 - 缺点: 1. 单台机器宕机时,本机未消费的消息,直到机器恢复后才会订阅,影响消息实时性。 多Master模式的启动步骤如下: **1)启动 NameServer** ```shell ### 第一步先启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` **2)启动 Broker 集群** ```shell ### 比如在A机器上启动第一个Master,假设配置的NameServer IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-a.properties & ### 然后在机器B上启动第二个Master,假设配置的NameServer IP是:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-b.properties & ... ``` 上面显示的启动命令用于单个NameServer的情况。对于多个NameServer的集群,broker 启动命令中-n参数后面的地址列表用分号隔开,例如 192.168.1.1:9876;192.168.1.2:9876 ### 3 多Master多Slave模式-异步复制 每个主节点配置多个从节点,多对主从。HA采用异步复制,主节点和从节点之间有短消息延迟(毫秒)。这种模式的优缺点如下: - 优点: 1. 即使磁盘损坏,也只会丢失极少的消息,不影响消息的实时性能。 2. 同时,当主节点宕机时,消费者仍然可以消费从节点的消息,这个过程对应用本身是透明的,不需要人为干预。 3. 性能几乎与多Master模式一样高。 - 缺点: 1. 主节点宕机、磁盘损坏时,会丢失少量消息。 多主多从模式的启动步骤如下: **1)启动 NameServer** ```shell ### 第一步先启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` **2)启动 Broker 集群** ```shell ### 例如在A机器上启动第一个Master,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a.properties & ### 然后在机器B上启动第二个Master,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b.properties & ### 然后在C机器上启动第一个Slave,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a-s.properties & ### 最后在D机启动第二个Slave,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b-s.properties & ``` 上图显示了 2M-2S-Async 模式的启动命令,类似于其他 nM-nS-Async 模式。 ### 4 多Master多Slave模式-同步双写 这种模式下,每个master节点配置多个slave节点,有多对Master-Slave。HA采用同步双写,即只有消息成功写入到主节点并复制到多个从节点,才会返回成功响应给应用程序。 这种模式的优缺点如下: - 优点: 1. 数据和服务都没有单点故障。 2. 在master节点关闭的情况下,消息也没有延迟。 3. 服务可用性和数据可用性非常高; - 缺点: 1. 这种模式下的性能略低于异步复制模式(大约低 10%)。 2. 发送单条消息的RT略高,目前版本,master节点宕机后,slave节点无法自动切换到master。 启动步骤如下: **1)启动NameServer** ```shell ### 第一步启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` **2)启动 Broker 集群** ```shell ### 例如在A机器上启动第一个Master,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a.properties & ### 然后在B机器上启动第二个Master,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b.properties & ### 然后在C机器上启动第一个Slave,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a-s.properties & ### 最后在D机启动第二个Slave,假设配置的NameServer IP为:192.168.1.1,端口为9876。 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b-s.properties & ``` 上述Master和Slave是通过指定相同的config命名为“brokerName”来配对的,master节点的brokerId必须为0,slave节点的brokerId必须大于0。 ### 5 RocketMQ 5.0 自动主从切换 RocketMQ 5.0 开始支持自动主从切换的模式,可参考以下文档 [快速开始](controller/quick_start.md) [部署文档](controller/deploy.md) [设计思想](controller/design.md) ================================================ FILE: docs/cn/Example_Batch.md ================================================ # 批量消息发送 批量消息发送能够提高发送效率,提升系统吞吐量。同一批批量消息的topic、waitStoreMsgOK属性必须保持一致,批量消息不支持延迟消息。批量消息发送一次最多可以发送 4MiB 的消息,但是如果需要发送更大的消息,建议将较大的消息分成多个不超过 1MiB 的小消息。 ### 1 发送批量消息 如果你一次只发送不超过 4MiB 的消息,使用批处理很容易: ```java String topic = "BatchTest"; List messages = new ArrayList<>(); messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes())); messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes())); messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes())); try { producer.send(messages); } catch (Exception e) { e.printStackTrace(); //handle the error } ``` ### 2 拆分 当您发送较大的消息时,复杂性会增加,如果您不确定它是否超过 4MiB的限制。 这时候,您最好将较大的消息分成多个不超过 1MiB 的小消息: ```java public class ListSplitter implements Iterator> { private final int SIZE_LIMIT = 1024 * 1024 * 4; private final List messages; private int currIndex; public ListSplitter(List messages) { this.messages = messages; } @Override public boolean hasNext() { return currIndex < messages.size(); } @Override public List next() { int startIndex = getStartIndex(); int nextIndex = startIndex; int totalSize = 0; for (; nextIndex < messages.size(); nextIndex++) { Message message = messages.get(nextIndex); int tmpSize = calcMessageSize(message); if (tmpSize + totalSize > SIZE_LIMIT) { break; } else { totalSize += tmpSize; } } List subList = messages.subList(startIndex, nextIndex); currIndex = nextIndex; return subList; } private int getStartIndex() { Message currMessage = messages.get(currIndex); int tmpSize = calcMessageSize(currMessage); while(tmpSize > SIZE_LIMIT) { currIndex += 1; Message message = messages.get(curIndex); tmpSize = calcMessageSize(message); } return currIndex; } private int calcMessageSize(Message message) { int tmpSize = message.getTopic().length() + message.getBody().length(); Map properties = message.getProperties(); for (Map.Entry entry : properties.entrySet()) { tmpSize += entry.getKey().length() + entry.getValue().length(); } tmpSize = tmpSize + 20; // Increase the log overhead by 20 bytes return tmpSize; } } // then you could split the large list into small ones: ListSplitter splitter = new ListSplitter(messages); while (splitter.hasNext()) { try { List listItem = splitter.next(); producer.send(listItem); } catch (Exception e) { e.printStackTrace(); // handle the error } } ``` ================================================ FILE: docs/cn/Example_Compaction_Topic_cn.md ================================================ # Compaction Topic ## 使用方式 ### 打开namesrv上支持顺序消息的开关 CompactionTopic依赖顺序消息来保障一致性 ```shell $ bin/mqadmin updateNamesrvConfig -k orderMessageEnable -v true ``` ### 创建compaction topic ```shell $ bin/mqadmin updateTopic -w 8 -r 8 -a +cleanup.policy=COMPACTION -n localhost:9876 -t ctopic -o true -c DefaultCluster create topic to 127.0.0.1:10911 success. TopicConfig [topicName=ctopic, readQueueNums=8, writeQueueNums=8, perm=RW-, topicFilterType=SINGLE_TAG, topicSysFlag=0, order=false, attributes={+cleanup.policy=COMPACTION}] ``` ### 生产数据 与普通消息一样 ```java DefaultMQProducer producer = new DefaultMQProducer("CompactionTestGroup"); producer.setNamesrvAddr("localhost:9876"); producer.start(); String topic = "ctopic"; String tag = "tag1"; String key = "key1"; Message msg = new Message(topic, tag, key, "bodys".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(msg, (mqs, message, shardingKey) -> { int select = Math.abs(shardingKey.hashCode()); if (select < 0) { select = 0; } return mqs.get(select % mqs.size()); }, key); System.out.printf("%s%n", sendResult); ``` ### 消费数据 消费offset与compaction之前保持不变,如果指定offset消费,当指定的offset不存在时,返回后面最近的一条数据 在compaction场景下,大部分消费都是从0开始消费完整的数据 ```java DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("compactionTestGroup"); consumer.setNamesrvAddr("localhost:9876"); consumer.setPullThreadNums(4); consumer.start(); Collection messageQueueList = consumer.fetchMessageQueues("ctopic"); consumer.assign(messageQueueList); messageQueueList.forEach(mq -> { try { consumer.seekToBegin(mq); } catch (MQClientException e) { e.printStackTrace(); } }); Map kvStore = Maps.newHashMap(); while (true) { List msgList = consumer.poll(1000); if (CollectionUtils.isNotEmpty(msgList)) { msgList.forEach(msg -> kvStore.put(msg.getKeys(), msg.getBody())); } } //use the kvStore ``` ================================================ FILE: docs/cn/Example_CreateTopic.md ================================================ # 创建主题 ## 背景 RocketMQ 5.0 引入了 `TopicMessageType` 的概念,并且使用了现有的主题属性功能来实现它。 主题的创建是通过 `mqadmin` 工具来申明 `message.type` 属性。 ## 使用案例 ```shell # default sh ./mqadmin updateTopic -n -t -c DefaultCluster # normal topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=NORMAL # fifo topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=FIFO # delay topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=DELAY # transaction topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=TRANSACTION ``` ================================================ FILE: docs/cn/Example_Delay.md ================================================ # Schedule example ### 1 启动消费者等待传入的订阅消息 ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class ScheduledMessageConsumer { public static void main(String[] args) throws Exception { // Instantiate message consumer DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer"); // Subscribe topics consumer.subscribe("TestTopic", "*"); // Register message listener consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) { for (MessageExt message : messages) { // Print approximate delay time period System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // Launch consumer consumer.start(); } } ``` ### 2 发送延迟消息 ```java import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; public class ScheduledMessageProducer { public static void main(String[] args) throws Exception { // Instantiate a producer to send scheduled messages DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); // Launch producer producer.start(); int totalMessagesToSend = 100; for (int i = 0; i < totalMessagesToSend; i++) { Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes()); // This message will be delivered to consumer 10 seconds later. message.setDelayTimeLevel(3); // Send the message producer.send(message); } // Shutdown producer after use. producer.shutdown(); } } ``` ### 3 确认 您应该会看到消息在其存储时间后大约 10 秒被消耗。 ### 4 延迟消息的使用场景 例如在电子商务中,如果提交订单,可以发送延迟消息,1小时后可以查看订单状态。 如果订单仍未付款,则可以取消订单并释放库存。 ### 5 使用延迟消息的限制 ```java // org/apache/rocketmq/store/config/MessageStoreConfig.java private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; ``` 当前 RocketMQ 不支持任意时间的延迟。 生产者发送延迟消息前需要设置几个固定的延迟级别,分别对应1s到2h的1到18个延迟级,消息消费失败会进入延迟消息队列,消息发送时间与设置的延迟级别和重试次数有关。 See `SendMessageProcessor.java` ================================================ FILE: docs/cn/Example_LMQ.md ================================================ # Light message queue (LMQ) LMQ采用的读放大的策略,写一份数据,多个LMQ队列分发, 因为存储的成本和效率对用户的体感最明显。写多份不仅加大了存储成本,同时也对性能和数据准确一致性提出了挑战。 ![](image/LMQ_1.png) 上图描述的是LMQ的队列存储模型,消息可以来自各个接入场景 (如服务端的MQ/AMQP,客户端的MQTT),但只会写一份存到commitlog里面,然后分发出多个需求场景的队列索引(ConsumerQueue),如服务端场景(MQ/AMQP)可以按照一级Topic队列进行传统的服务端消费,客户端MQTT场景可以按照MQTT多级Topic(也即 LMQ)进行消费消息。 ## 一、broker启动配置 broker.conf文件需要增加以下的配置项,开启LMQ开关,这样就可以识别LMQ相关属性的消息,进行原子分发消息到LMQ队列 ```properties enableLmq = true enableMultiDispatch = true ``` ## 二、发送消息 发送消息的时候通过设置 INNER_MULTI_DISPATCH 属性,LMQ queue使用逗号分割,queue前缀必须是 %LMQ%,这样broker就可以识别LMQ queue. 以下代码只是demo伪代码 具体逻辑参照执行即可 ```java DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876"); producer.start(); /* * Create a message instance, specifying topic, tag and message body. */ Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); /* * INNER_MULTI_DISPATCH property and PREFIX must start as "%LMQ%", * If it is multiple LMQ, need to use “,” split */ message.putUserProperty("INNER_MULTI_DISPATCH", "%LMQ%123,%LMQ%456"); /* * Call send message to deliver message to one of brokers. */ SendResult sendResult = producer.send(msg); ``` ## 三、拉取消息 LMQ queue在每个broker上只有一个queue,也即queueId为0, 指明轻量级的MessageQueue,就可以拉取消息进行消费。 以下代码只是demo伪代码 具体逻辑参照执行即可 ```java DefaultMQPullConsumer defaultMQPullConsumer = new DefaultMQPullConsumer(); defaultMQPullConsumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876"); defaultMQPullConsumer.setVipChannelEnabled(false); defaultMQPullConsumer.setConsumerGroup("CID_RMQ_SYS_LMQ_TEST"); defaultMQPullConsumer.setInstanceName("CID_RMQ_SYS_LMQ_TEST"); defaultMQPullConsumer.setRegisterTopics(new HashSet<>(Arrays.asList("TopicTest"))); defaultMQPullConsumer.setBrokerSuspendMaxTimeMillis(2000); defaultMQPullConsumer.setConsumerTimeoutMillisWhenSuspend(3000); defaultMQPullConsumer.start(); String brokerName = "set broker Name"; MessageQueue mq = new MessageQueue("%LMQ%123", brokerName, 0); defaultMQPullConsumer.getDefaultMQPullConsumerImpl().getRebalanceImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer("TopicTest"); Thread.sleep(30000); Long offset = defaultMQPullConsumer.maxOffset(mq); defaultMQPullConsumer.pullBlockIfNotFound( mq, "*", offset, 32, new PullCallback() { @Override public void onSuccess(PullResult pullResult) { List list = pullResult.getMsgFoundList(); if (list == null || list.isEmpty()) { return; } for (MessageExt messageExt : list) { System.out.println(messageExt); } } @Override public void onException(Throwable e) { } }); ``` ​ ================================================ FILE: docs/cn/Example_Simple_cn.md ================================================ # Basic Sample ------ 基本示例中提供了以下两个功能 * RocketMQ可用于以三种方式发送消息:可靠的同步、可靠的异步和单向传输。前两种消息类型是可靠的,因为无论它们是否成功发送都有响应。 * RocketMQ可以用来消费消息。 ### 1 添加依赖 maven: ``` java org.apache.rocketmq rocketmq-client 4.3.0 ``` gradle: ``` java compile 'org.apache.rocketmq:rocketmq-client:4.3.0' ``` ### 2 发送消息 ##### 2.1 使用Producer发送同步消息 可靠的同步传输被广泛应用于各种场景,如重要的通知消息、短消息通知等。 ``` java public class SyncProducer { public static void main(String[] args) throws Exception { // Instantiate with a producer group name DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch the producer instance producer.start(); for (int i = 0; i < 100; i++) { // Create a message instance with specifying topic, tag and message body Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // Send message to one of brokers SendResult sendResult = producer.send(msg); // Check whether the message has been delivered by the callback of sendResult System.out.printf("%s%n", sendResult); } // Shut down once the producer instance is not longer in use producer.shutdown(); } } ``` ##### 2.2 发送异步消息 异步传输通常用于响应时间敏感的业务场景。这意味着发送方无法等待代理的响应太长时间。 ``` java public class AsyncProducer { public static void main(String[] args) throws Exception { // Instantiate with a producer group name DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch the producer instance producer.start(); producer.setRetryTimesWhenSendAsyncFailed(0); for (int i = 0; i < 100; i++) { final int index = i; // Create a message instance with specifying topic, tag and message body Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); // SendCallback: receive the callback of the asynchronous return result. producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId()); } @Override public void onException(Throwable e) { System.out.printf("%-10d Exception %s %n", index, e); e.printStackTrace(); } }); } // Shut down once the producer instance is not longer in use producer.shutdown(); } } ``` ##### 2.3 以单向模式发送消息 单向传输用于需要中等可靠性的情况,如日志收集。 ``` java public class OnewayProducer { public static void main(String[] args) throws Exception{ // Instantiate with a producer group name DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch the producer instance producer.start(); for (int i = 0; i < 100; i++) { // Create a message instance with specifying topic, tag and message body Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // Send in one-way mode, no return result producer.sendOneway(msg); } // Shut down once the producer instance is not longer in use producer.shutdown(); } } ``` ### 3 消费消息 ``` java public class Consumer { public static void main(String[] args) throws InterruptedException, MQClientException { // Instantiate with specified consumer group name DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); // Specify name server addresses consumer.setNamesrvAddr("localhost:9876"); // Subscribe one or more topics and tags for finding those messages need to be consumed consumer.subscribe("TopicTest", "*"); // Register callback to execute on arrival of messages fetched from brokers consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); // Mark the message that have been consumed successfully return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // Launch the consumer instance consumer.start(); System.out.printf("Consumer Started.%n"); } } ``` ================================================ FILE: docs/cn/FAQ.md ================================================ # 经常被问到的问题 以下是关于RocketMQ项目的常见问题 ## 1 基本 1. **为什么我们要使用RocketMQ而不是选择其他的产品?** 请参考[为什么要选择RocketMQ](http://rocketmq.apache.org/docs/motivation/) 2. **我是否需要安装其他的软件才能使用RocketMQ,例如zookeeper?** 不需要,RocketMQ可以独立的运行。 ## 2 使用 1. **新创建的Consumer ID从哪里开始消费消息?** 1)如果发送的消息在三天之内,那么消费者会从服务器中保存的第一条消息开始消费。 2)如果发送的消息已经超过三天,则消费者会从服务器中的最新消息开始消费,也就是从队列的尾部开始消费。 3)如果消费者重新启动,那么它会从最后一个消费位置开始消费消息。 2. **当消费失败的时候如何重新消费消息?** 1)在集群模式下,消费的业务逻辑代码会返回Action.ReconsumerLater,NULL,或者抛出异常,如果一条消息消费失败,最多会重试16次,之后该消息会被丢弃。 2)在广播消费模式下,广播消费仍然保证消息至少被消费一次,但不提供重发的选项。 3. **当消费失败的时候如何找到失败的消息?** 1)使用按时间的主题查询,可以查询到一段时间内的消息。 2)使用主题和消息ID来准确查询消息。 3)使用主题和消息的Key来准确查询所有消息Key相同的消息。 4. **消息只会被传递一次吗?** RocketMQ 确保所有消息至少传递一次。 在大多数情况下,消息不会重复。 5. **如何增加一个新的Broker?** 1)启动一个新的Broker并将其注册到name server中的Broker列表里。 2)默认只自动创建内部系统topic和consumer group。 如果您希望在新节点上拥有您的业务主题和消费者组,请从现有的Broker中复制它们。 我们提供了管理工具和命令行来处理此问题。 ## 3 配置相关 以下回答均为默认值,可通过配置修改。 1. **消息在服务器上可以保存多长时间?** 存储的消息将最多保存 3 天,超过 3 天未使用的消息将被删除。 2. **消息体的大小限制是多少?** 通常是256KB 3. **怎么设置消费者线程数?** 当你启动消费者的时候,可以设置 ConsumeThreadNums属性的值,举例如下: ```java consumer.setConsumeThreadMin(20); consumer.setConsumeThreadMax(20); ``` ## 4 错误 1. **当你启动一个生产者或消费者的过程失败了并且错误信息是生产者组或消费者重复** 原因:使用同一个Producer/Consumer Group在同一个JVM中启动多个Producer/Consumer实例可能会导致客户端无法启动。 解决方案:确保一个 Producer/Consumer Group 对应的 JVM 只启动一个 Producer/Consumer 实例。 2. **消费者无法在广播模式下开始加载 json 文件** 原因:fastjson 版本太低,无法让广播消费者加载本地 offsets.json,导致消费者启动失败。 损坏的 fastjson 文件也会导致同样的问题。 解决方案:Fastjson 版本必须升级到 RocketMQ 客户端依赖版本,以确保可以加载本地 offsets.json。 默认情况下,offsets.json 文件在 /home/{user}/.rocketmq_offsets 中。 或者检查fastjson的完整性。 3. **Broker崩溃以后有什么影响?** 1)Master节点崩溃 消息不能再发送到该Broker集群,但是如果您有另一个可用的Broker集群,那么在主题存在的条件下仍然可以发送消息。消息仍然可以从Slave节点消费。 2)一些Slave节点崩溃 只要有另一个工作的slave,就不会影响发送消息。 对消费消息也不会产生影响,除非消费者组设置为优先从该Slave消费。 默认情况下,消费者组从 master 消费。 3)所有Slave节点崩溃 向master发送消息不会有任何影响,但是,如果master是SYNC_MASTER,producer会得到一个SLAVE_NOT_AVAILABLE,表示消息没有发送给任何slave。 对消费消息也没有影响,除非消费者组设置为优先从slave消费。 默认情况下,消费者组从master消费。 4. **Producer提示“No Topic Route Info”,如何诊断?** 当您尝试将消息发送到一个路由信息对生产者不可用的主题时,就会发生这种情况。 1)确保生产者可以连接到名称服务器并且能够从中获取路由元信息。 2)确保名称服务器确实包含主题的路由元信息。 您可以使用管理工具或 Web 控制台通过 topicRoute 从名称服务器查询路由元信息。 3)确保您的Broker将心跳发送到您的生产者正在连接的同一name server列表。 4)确保主题的权限为6(rw-),或至少为2(-w-)。 如果找不到此主题,请通过管理工具命令updateTopic或Web控制台在Broker上创建它。 ================================================ FILE: docs/cn/QuorumACK.md ================================================ # Quorum Write和自动降级 ## 背景 ![](https://s4.ax1x.com/2022/02/05/HnWo2d.png) 在RocketMQ中,主备之间的复制模式主要有同步复制和异步复制,如上图所示,Slave1的复制是同步的,在向Producer报告成功写入之前,Master需要等待Slave1成功复制该消息并确认,Slave2的复制是异步的,Master不需要等待Slave2的响应。在RocketMQ中,发送一条消息,如果一切都顺利,那最后会返回给Producer客户端一个PUT_OK的状态,如果是Slave同步超时则返回FLUSH_SLAVE_TIMEOUT状态,如果是Slave不可用或者Slave与Master之间CommitLog差距超过一定的值(默认是256MB),则返回SLAVE_NOT_AVAILABLE,后面两个状态并不会导致系统异常而无法写入下一条消息。 同步复制可以保证Master失效后,数据仍然能在Slave中找到,适合可靠性要求较高的场景。异步复制虽然消息可能会丢失,但是由于无需等待Slave的确认,效率上要高于同步复制,适合对效率有一定要求的场景。但是只有两种模式仍然不够灵活,比如在三副本甚至五副本且对可靠性要求高场景中,采用异步复制无法满足需求,但采用同步复制则需要每一个副本确认后才会返回,在副本数多的情况下严重影响效率。另一方面,在同步复制的模式下,如果副本组中的某一个Slave出现假死,整个发送将一直失败直到进行手动处理。 因此,RocketMQ 5 提出了副本组的quorum write,在同步复制的模式下,用户可以在broker端指定发送后至少需要写入多少副本数后才能返回,并且提供自适应降级的方式,可以根据存活的副本数以及CommitLog差距自动完成降级。 ## 架构和参数 ### Quorum Write 通过增加两个参数来支持quorum write。 - **totalReplicas**:副本组broker总数。默认为1。 - **inSyncReplicas**:正常情况需保持同步的副本组数量。默认为1。 通过这两个参数,可以在同步复制的模式下,灵活指定需要ACK的副本数。 ![](https://s4.ax1x.com/2022/02/05/HnWHKI.png) 如上图所示,在两副本情况下,如果inSyncReplicas为2,则该条消息需要在Master和Slave中均复制完成后才会返回给客户端;在三副本情况下,如果inSyncReplicas为2,则该条消息除了需要复制在Master上,还需要复制到任意一个slave上,才会返回给客户端。在四副本情况下,如果inSyncReplicas为3,则条消息除了需要复制在Master上,还需要复制到任意两个slave上,才会返回给客户端。通过灵活设置totalReplicas和inSyncReplicas,可以满足用户各类场景的需求。 ### 自动降级 自动降级的标准是 - 当前副本组的存活副本数 - Master Commitlog和Slave CommitLog的高度差 > **注意:自动降级只在slaveActingMaster模式开启后才生效** 通过Nameserver的反向通知以及GetBrokerMemberGroup请求可以获取当前副本组的存活信息,而Master与Slave的Commitlog高度差也可以通过HA服务中的位点记录计算出来。将增加以下参数完成自动降级: - **minInSyncReplicas**:最小需保持同步的副本组数量,仅在enableAutoInSyncReplicas为true时生效,默认为1 - **enableAutoInSyncReplicas**:自动同步降级开关,开启后,若当前副本组处于同步状态的broker数量(包括master自身)不满足inSyncReplicas指定的数量,则按照minInSyncReplicas进行同步。同步状态判断条件为:slave commitLog落后master长度不超过haSlaveFallBehindMax。默认为false。 - **haMaxGapNotInSync**:slave是否与master处于in-sync状态的判断值,slave commitLog落后master长度超过该值则认为slave已处于非同步状态。当enableAutoInSyncReplicas打开时,该值越小,越容易触发master的自动降级,当enableAutoInSyncReplicas关闭,且totalReplicas==inSyncReplicas时,该值越小,越容易导致在大流量时发送请求失败,故在该情况下可适当调大haMaxGapNotInSync。默认为256K。 注意:在RocketMQ 4.x中存在haSlaveFallbehindMax参数,默认256MB,表明Slave与Master的CommitLog高度差多少后判定其为不可用,在[RIP-34](https://github.com/apache/rocketmq/wiki/RIP-34-Support-quorum-write-and-adaptive-degradation-in-master-slave-architecture)中该参数被取消。 ```java //计算needAckNums int inSyncReplicas = Math.min(this.defaultMessageStore.getAliveReplicaNumInGroup(), this.defaultMessageStore.getHaService().inSyncSlaveNums(currOffset) + 1); needAckNums = calcNeedAckNums(inSyncReplicas); if (needAckNums > inSyncReplicas) { // Tell the producer, don't have enough slaves to handle the send request return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null)); } private int calcNeedAckNums(int inSyncReplicas) { int needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas(); if (this.defaultMessageStore.getMessageStoreConfig().isEnableAutoInSyncReplicas()) { needAckNums = Math.min(needAckNums, inSyncReplicas); needAckNums = Math.max(needAckNums, this.defaultMessageStore.getMessageStoreConfig().getMinInSyncReplicas()); } return needAckNums; } ``` 当enableAutoInSyncReplicas=true是开启自适应降级模式,当副本组中存活的副本数减少或Master和Slave Commitlog高度差过大时,都会进行自动降级,最小降级到minInSyncReplicas副本数。比如在两副本中,如果设置totalReplicas=2,InSyncReplicas=2,minInSyncReplicas=1,enableAutoInSyncReplicas=true,正常情况下,两个副本均会处于同步复制,当Slave下线或假死时,会进行自适应降级,producer只需要发送到master即成功。 ## 兼容性 用户需要设置正确的参数才能完成正确的向后兼容。举个例子,假设用户原集群为两副本同步复制,在没有修改任何参数的情况下,升级到RocketMQ 5的版本,由于totalReplicas、inSyncReplicas默认都为1,将降级为异步复制,如果需要和以前行为保持一致,则需要将totalReplicas和inSyncReplicas均设置为2。 参考文档: - [RIP-34](https://github.com/apache/rocketmq/wiki/RIP-34-Support-quorum-write-and-adaptive-degradation-in-master-slave-architecture) ================================================ FILE: docs/cn/README.md ================================================ Apache RocketMQ开发者指南 -------- ##### 这个开发者指南旨在帮助您快速了解并使用 Apache RocketMQ ### 1. 概念和特性 - [概念(Concept)](concept.md):介绍RocketMQ的基本概念模型。 - [特性(Features)](features.md):介绍RocketMQ实现的功能特性。 ### 2. 架构设计 - [架构(Architecture)](architecture.md):介绍RocketMQ部署架构和技术架构。 - [设计(Design)](design.md):介绍RocketMQ关键机制的设计原理,主要包括消息存储、通信机制、消息过滤、负载均衡、事务消息等。 ### 3. 样例 - [样例(Example)](RocketMQ_Example.md) :介绍RocketMQ的常见用法,包括基本样例、顺序消息样例、延时消息样例、批量消息样例、过滤消息样例、事务消息样例等。 ### 4. 最佳实践 - [最佳实践(Best Practice)](best_practice.md):介绍RocketMQ的最佳实践,包括生产者、消费者、Broker以及NameServer的最佳实践,客户端的配置方式以及JVM和linux的最佳参数配置。 - [消息轨迹指南(Message Trace)](msg_trace/user_guide.md):介绍RocketMQ消息轨迹的使用方法。 - [权限管理(Auth Management)](acl/user_guide.md):介绍如何快速部署和使用支持权限控制特性的RocketMQ集群。 - [自动主从切换快速开始](controller/quick_start.md):RocketMQ 5.0 自动主从切换快速开始。 - [自动主从切换部署升级指南](controller/deploy.md):RocketMQ 5.0 自动主从切换部署升级指南。 - [Proxy 部署指南](proxy/deploy_guide.md):介绍如何部署Proxy (包括 `Local` 模式和 `Cluster` 模式). ### 5. 运维管理 - [集群部署(Operation)](operation.md):介绍单Master模式、多Master模式、多Master多slave模式等RocketMQ集群各种形式的部署方法以及运维工具mqadmin的使用方式。 ### 6. RocketMQ 5.0 新特性 - [POP消费](https://github.com/apache/rocketmq/wiki/%5BRIP-19%5D-Server-side-rebalance,--lightweight-consumer-client-support) - [StaticTopic](statictopic/RocketMQ_Static_Topic_Logic_Queue_设计.md) - [BatchConsumeQueue](https://github.com/apache/rocketmq/wiki/RIP-26-Improve-Batch-Message-Processing-Throughput) - [自动主从切换](controller/design.md) - [BrokerContainer](BrokerContainer.md) - [SlaveActingMaster模式](SlaveActingMasterMode.md) - [Grpc Proxy](../../proxy/README.md) ### 7. API Reference(待补充) - [DefaultMQProducer API Reference](client/java/API_Reference_DefaultMQProducer.md) ================================================ FILE: docs/cn/RocketMQ_Example.md ================================================ # 样例 ----- * [目录](#样例) * [1 基本样例](#1-基本样例) * [1.1 加入依赖:](#11-加入依赖) * [1.2 消息发送](#12-消息发送) * [1、Producer端发送同步消息](#1producer端发送同步消息) * [2、发送异步消息](#2发送异步消息) * [3、单向发送消息](#3单向发送消息) * [1.3 消费消息](#13-消费消息) * [2 顺序消息样例](#2-顺序消息样例) * [2.1 顺序消息生产](#21-顺序消息生产) * [2.2 顺序消费消息](#22-顺序消费消息) * [3 延时消息样例](#3-延时消息样例) * [3.1 启动消费者等待传入订阅消息](#31-启动消费者等待传入订阅消息) * [3.2 发送延时消息](#32-发送延时消息) * [3.3 验证](#33-验证) * [3.4 延时消息的使用场景](#34-延时消息的使用场景) * [3.5 延时消息的使用限制](#35-延时消息的使用限制) * [4 批量消息样例](#4-批量消息样例) * [4.1 发送批量消息](#41-发送批量消息) * [4.2 消息列表分割](#42-消息列表分割) * [5 过滤消息样例](#5-过滤消息样例) * [5.1 基本语法](#51-基本语法) * [5.2 使用样例](#52-使用样例) * [1、生产者样例](#1生产者样例) * [2、消费者样例](#2消费者样例) * [6 消息事务样例](#6-消息事务样例) * [6.1 发送事务消息样例](#61-发送事务消息样例) * [1、创建事务性生产者](#1创建事务性生产者) * [2、实现事务的监听接口](#2实现事务的监听接口) * [6.2 事务消息使用上的限制](#62-事务消息使用上的限制) * [7 Logappender样例](#7-logappender样例) * [7.1 log4j样例](#71-log4j样例) * [7.2 log4j2样例](#72-log4j2样例) * [7.3 logback样例](#73-logback样例) * [8 OpenMessaging样例](#8-openmessaging样例) * [8.1 OMSProducer样例](#81-omsproducer样例) * [8.2 OMSPullConsumer](#82-omspullconsumer) * [8.3 OMSPushConsumer](#83-omspushconsumer) ----- ## 1 基本样例 在基本样例中我们提供如下的功能场景: * 使用RocketMQ发送三种类型的消息:同步消息、异步消息和单向消息。其中前两种消息是可靠的,因为会有发送是否成功的应答。 * 使用RocketMQ来消费接收到的消息。 ### 1.1 加入依赖: `maven:` ``` org.apache.rocketmq rocketmq-client 4.9.1 ``` `gradle` ``` compile 'org.apache.rocketmq:rocketmq-client:4.3.0' ``` ### 1.2 消息发送 #### 1、Producer端发送同步消息 这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。 ```java public class SyncProducer { public static void main(String[] args) throws Exception { // 实例化消息生产者Producer DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // 设置NameServer的地址 producer.setNamesrvAddr("localhost:9876"); // 启动Producer实例 producer.start(); for (int i = 0; i < 100; i++) { // 创建消息,并指定Topic,Tag和消息体 Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // 发送消息到一个Broker SendResult sendResult = producer.send(msg); // 通过sendResult返回消息是否成功送达 System.out.printf("%s%n", sendResult); } // 如果不再发送消息,关闭Producer实例。 producer.shutdown(); } } ``` #### 2、发送异步消息 异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。 ```java public class AsyncProducer { public static void main(String[] args) throws Exception { // 实例化消息生产者Producer DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // 设置NameServer的地址 producer.setNamesrvAddr("localhost:9876"); // 启动Producer实例 producer.start(); producer.setRetryTimesWhenSendAsyncFailed(0); int messageCount = 100; // 根据消息数量实例化倒计时计算器 final CountDownLatch2 countDownLatch = new CountDownLatch2(messageCount); for (int i = 0; i < messageCount; i++) { final int index = i; // 创建消息,并指定Topic,Tag和消息体 Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); // SendCallback接收异步返回结果的回调 producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { countDownLatch.countDown(); System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId()); } @Override public void onException(Throwable e) { countDownLatch.countDown(); System.out.printf("%-10d Exception %s %n", index, e); e.printStackTrace(); } }); } // 等待5s countDownLatch.await(5, TimeUnit.SECONDS); // 如果不再发送消息,关闭Producer实例。 producer.shutdown(); } } ``` #### 3、单向发送消息 这种方式主要用在不特别关心发送结果的场景,例如日志发送。 ```java public class OnewayProducer { public static void main(String[] args) throws Exception{ // 实例化消息生产者Producer DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // 设置NameServer的地址 producer.setNamesrvAddr("localhost:9876"); // 启动Producer实例 producer.start(); for (int i = 0; i < 100; i++) { // 创建消息,并指定Topic,Tag和消息体 Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // 发送单向消息,没有任何返回结果 producer.sendOneway(msg); } // 如果不再发送消息,关闭Producer实例。 producer.shutdown(); } } ``` ### 1.3 消费消息 ```java public class Consumer { public static void main(String[] args) throws InterruptedException, MQClientException { // 实例化消费者 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); // 设置NameServer的地址 consumer.setNamesrvAddr("localhost:9876"); // 订阅一个或者多个Topic,以及Tag来过滤需要消费的消息 consumer.subscribe("TopicTest", "*"); // 注册回调实现类来处理从broker拉取回来的消息 consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); // 标记该消息已经被成功消费 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // 启动消费者实例 consumer.start(); System.out.printf("Consumer Started.%n"); } } ``` 2 顺序消息样例 ---------- 消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。 顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。 下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。 ### 2.1 顺序消息生产 ```java package org.apache.rocketmq.example.order2; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Producer,发送顺序消息 */ public class Producer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); String[] tags = new String[]{"TagA", "TagC", "TagD"}; // 订单列表 List orderList = new Producer().buildOrders(); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); for (int i = 0; i < 10; i++) { // 加个时间前缀 String body = dateStr + " Hello RocketMQ " + orderList.get(i); Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i, body.getBytes()); SendResult sendResult = producer.send(msg, new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { Long id = (Long) arg; //根据订单id选择发送queue long index = id % mqs.size(); return mqs.get((int) index); } }, orderList.get(i).getOrderId());//订单id System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s", sendResult.getSendStatus(), sendResult.getMessageQueue().getQueueId(), body)); } producer.shutdown(); } /** * 订单的步骤 */ private static class OrderStep { private long orderId; private String desc; public long getOrderId() { return orderId; } public void setOrderId(long orderId) { this.orderId = orderId; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "OrderStep{" + "orderId=" + orderId + ", desc='" + desc + '\'' + '}'; } } /** * 生成模拟订单数据 */ private List buildOrders() { List orderList = new ArrayList(); OrderStep orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("创建"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103111065L); orderDemo.setDesc("创建"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("付款"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103117235L); orderDemo.setDesc("创建"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103111065L); orderDemo.setDesc("付款"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103117235L); orderDemo.setDesc("付款"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103111065L); orderDemo.setDesc("完成"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("推送"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103117235L); orderDemo.setDesc("完成"); orderList.add(orderDemo); orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("完成"); orderList.add(orderDemo); return orderList; } } ``` ### 2.2 顺序消费消息 ```java package org.apache.rocketmq.example.order2; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; /** * 顺序消息消费,带事务方式(应用可控制Offset什么时候提交) */ public class ConsumerInOrder { public static void main(String[] args) throws Exception { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3"); consumer.setNamesrvAddr("127.0.0.1:9876"); /** * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
    * 如果非第一次启动,那么按照上次消费的位置继续消费 */ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicTest", "TagA || TagC || TagD"); consumer.registerMessageListener(new MessageListenerOrderly() { Random random = new Random(); @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { context.setAutoCommit(true); for (MessageExt msg : msgs) { // 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序 System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody())); } try { //模拟业务逻辑处理中... TimeUnit.SECONDS.sleep(random.nextInt(10)); } catch (Exception e) { e.printStackTrace(); } return ConsumeOrderlyStatus.SUCCESS; } }); consumer.start(); System.out.println("Consumer Started."); } } ``` 3 延时消息样例 ---------- ### 3.1 启动消费者等待传入订阅消息 ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class ScheduledMessageConsumer { public static void main(String[] args) throws Exception { // 实例化消费者 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer"); // 设置NameServer的地址 consumer.setNamesrvAddr("localhost:9876"); // 订阅Topics consumer.subscribe("TestTopic", "*"); // 注册消息监听者 consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) { for (MessageExt message : messages) { // Print approximate delay time period System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getBornTimestamp()) + "ms later"); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // 启动消费者 consumer.start(); } } ``` ### 3.2 发送延时消息 ```java import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; public class ScheduledMessageProducer { public static void main(String[] args) throws Exception { // 实例化一个生产者来产生延时消息 DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); // 设置NameServer的地址 producer.setNamesrvAddr("localhost:9876"); // 启动生产者 producer.start(); int totalMessagesToSend = 100; for (int i = 0; i < totalMessagesToSend; i++) { Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes()); // 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel) message.setDelayTimeLevel(3); // 发送消息 producer.send(message); } // 关闭生产者 producer.shutdown(); } } ``` ### 3.3 验证 您将会看到消息的消费比存储时间晚10秒。 ### 3.4 延时消息的使用场景 比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。 ### 3.5 延时消息的使用限制 ```java // org/apache/rocketmq/store/config/MessageStoreConfig.java private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; ``` 现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18 消息消费失败会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关,详见代码`SendMessageProcessor.java` 4 批量消息样例 ---------- 批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB。 ### 4.1 发送批量消息 如果您每次只发送不超过4MB的消息,则很容易使用批处理,样例如下: ```java String topic = "BatchTest"; List messages = new ArrayList<>(); messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes())); messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes())); messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes())); try { producer.send(messages); } catch (Exception e) { e.printStackTrace(); //处理error } ``` ### 4.2 消息列表分割 复杂度只有当你发送大批量时才会增长,你可能不确定它是否超过了大小限制(4MB)。这时候你最好把你的消息列表分割一下: ```java public class ListSplitter implements Iterator> { private final int SIZE_LIMIT = 1024 * 1024 * 4; private final List messages; private int currIndex; public ListSplitter(List messages) { this.messages = messages; } @Override public boolean hasNext() { return currIndex < messages.size(); } @Override public List next() { int startIndex = getStartIndex(); int nextIndex = startIndex; int totalSize = 0; for (; nextIndex < messages.size(); nextIndex++) { Message message = messages.get(nextIndex); int tmpSize = calcMessageSize(message); if (tmpSize + totalSize > SIZE_LIMIT) { break; } else { totalSize += tmpSize; } } List subList = messages.subList(startIndex, nextIndex); currIndex = nextIndex; return subList; } private int getStartIndex() { Message currMessage = messages.get(currIndex); int tmpSize = calcMessageSize(currMessage); while(tmpSize > SIZE_LIMIT) { currIndex += 1; Message message = messages.get(currIndex); tmpSize = calcMessageSize(message); } return currIndex; } private int calcMessageSize(Message message) { int tmpSize = message.getTopic().length() + message.getBody().length; Map properties = message.getProperties(); for (Map.Entry entry : properties.entrySet()) { tmpSize += entry.getKey().length() + entry.getValue().length(); } tmpSize = tmpSize + 20; // 增加⽇日志的开销20字节 return tmpSize; } } //把大的消息分裂成若干个小的消息 ListSplitter splitter = new ListSplitter(messages); while (splitter.hasNext()) { try { List listItem = splitter.next(); producer.send(listItem); } catch (Exception e) { e.printStackTrace(); //处理error } } ``` 5 过滤消息样例 ---------- 在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如: ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE"); consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC"); ``` 消费者将接收包含TAGA或TAGB或TAGC的消息。但是限制是一个消息只能有一个标签,这对于复杂的场景可能不起作用。在这种情况下,可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子: ``` ------------ | message | |----------| a > 5 AND b = 'abc' | a = 10 | --------------------> Gotten | b = 'abc'| | c = true | ------------ ------------ | message | |----------| a > 5 AND b = 'abc' | a = 1 | --------------------> Missed | b = 'abc'| | c = true | ------------ ``` ### 5.1 基本语法 RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。 - 数值比较,比如:**>,>=,<,<=,BETWEEN,=;** - 字符比较,比如:**=,<>,IN;** - **IS NULL** 或者 **IS NOT NULL;** - 逻辑符号 **AND,OR,NOT;** 常量支持类型为: - 数值,比如:**123,3.1415;** - 字符,比如:**'abc',必须用单引号包裹起来;** - **NULL**,特殊的常量 - 布尔值,**TRUE** 或 **FALSE** 只有使用push模式的消费者才能用使用SQL92标准的sql语句,接口如下: ``` public void subscribe(final String topic, final MessageSelector messageSelector) ``` ### 5.2 使用样例 #### 1、生产者样例 发送消息时,你能通过`putUserProperty`来设置消息的属性 ```java DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); Message msg = new Message("TopicTest", tag, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) ); // 设置一些属性 msg.putUserProperty("a", String.valueOf(i)); SendResult sendResult = producer.send(msg); producer.shutdown(); ``` #### 2、消费者样例 用MessageSelector.bySql来使用sql筛选消息 ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4"); // 只有订阅的消息有这个属性a, a >=0 and a <= 3 consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); ``` 6 消息事务样例 ---------- 事务消息共有三种状态,提交状态、回滚状态、中间状态: - TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。 - TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费。 - TransactionStatus.Unknown: 中间状态,它代表需要检查消息队列来确定状态。 ### 6.1 发送事务消息样例 #### 1、创建事务性生产者 使用 `TransactionMQProducer`类创建生产者,并指定唯一的 `ProducerGroup`,就可以设置自定义线程池来处理这些检查请求。执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态在请参考前一节。 ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class TransactionProducer { public static void main(String[] args) throws MQClientException, InterruptedException { TransactionListener transactionListener = new TransactionListenerImpl(); TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name"); ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue(2000), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("client-transaction-msg-check-thread"); return thread; } }); producer.setExecutorService(executorService); producer.setTransactionListener(transactionListener); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 10; i++) { try { Message msg = new Message("TopicTest1234", tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); Thread.sleep(10); } catch (MQClientException | UnsupportedEncodingException e) { e.printStackTrace(); } } for (int i = 0; i < 100000; i++) { Thread.sleep(1000); } producer.shutdown(); } } ``` #### 2、实现事务的监听接口 当发送半消息成功时,我们使用 `executeLocalTransaction` 方法来执行本地事务。它返回前一节中提到的三个事务状态之一。`checkLocalTransaction` 方法用于检查本地事务状态,并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。 ```java public class TransactionListenerImpl implements TransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); private ConcurrentHashMap localTrans = new ConcurrentHashMap<>(); @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { int value = transactionIndex.getAndIncrement(); int status = value % 3; localTrans.put(msg.getTransactionId(), status); return LocalTransactionState.UNKNOW; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { Integer status = localTrans.get(msg.getTransactionId()); if (null != status) { switch (status) { case 0: return LocalTransactionState.UNKNOW; case 1: return LocalTransactionState.COMMIT_MESSAGE; case 2: return LocalTransactionState.ROLLBACK_MESSAGE; } } return LocalTransactionState.COMMIT_MESSAGE; } } ``` ### 6.2 事务消息使用上的限制 1. 事务消息不支持延时消息和批量消息。 2. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的 `transactionCheckMax`参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = `transactionCheckMax` ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 `AbstractTransactionalMessageCheckListener` 类来修改这个行为。 3. 事务消息将在 Broker 配置文件中的参数 transactionTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 `transactionTimeout` 参数。 4. 事务性消息可能不止一次被检查或消费。 5. 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。 6. 事务消息的生产者 GroupName 不能与其他类型消息的生产者 GroupName 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 GroupName 查询到生产者。 7 Logappender样例 ----------------- RocketMQ日志提供log4j、log4j2和logback日志框架作为业务应用,下面是配置样例 ### 7.1 log4j样例 按下面样例使用log4j属性配置 ``` log4j.appender.mq=org.apache.rocketmq.logappender.log4j.RocketmqLog4jAppender log4j.appender.mq.Tag=yourTag log4j.appender.mq.Topic=yourLogTopic log4j.appender.mq.ProducerGroup=yourLogGroup log4j.appender.mq.NameServerAddress=yourRocketmqNameserverAddress log4j.appender.mq.layout=org.apache.log4j.PatternLayout log4j.appender.mq.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-4r [%t] (%F:%L) %-5p - %m%n ``` 按下面样例使用log4j xml配置来使用异步添加日志 ```                         ``` ### 7.2 log4j2样例 用log4j2时,配置如下,如果想要非阻塞,只需要使用异步添加引用即可 ```   ``` ### 7.3 logback样例 ```   yourTag   yourLogTopic   yourLogGroup   yourRocketmqNameserverAddress         %date %p %t - %m%n     1024   80   2000   true   ``` 8 OpenMessaging样例 --------------- [OpenMessaging](https://www.google.com/url?q=http://openmessaging.cloud/&sa=D&ust=1546524111089000)旨在建立消息和流处理规范,以为金融、电子商务、物联网和大数据领域提供通用框架及工业级指导方案。在分布式异构环境中,设计原则是面向云、简单、灵活和独立于语言。符合这些规范将帮助企业方便的开发跨平台和操作系统的异构消息传递应用程序。提供了openmessaging-api 0.3.0-alpha的部分实现,下面的示例演示如何基于OpenMessaging访问RocketMQ。 ### 8.1 OMSProducer样例 下面的示例演示如何在同步、异步或单向传输中向RocketMQ代理发送消息。 ```java import io.openmessaging.Future; import io.openmessaging.FutureListener; import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.producer.Producer; import io.openmessaging.producer.SendResult; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; public class SimpleProducer { public static void main(String[] args) { final MessagingAccessPoint messagingAccessPoint = OMS.getMessagingAccessPoint("oms:rocketmq://localhost:9876/default:default"); final Producer producer = messagingAccessPoint.createProducer(); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); producer.startup(); System.out.printf("Producer startup OK%n"); { Message message = producer.createBytesMessage("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(message); //final Void aVoid = result.get(3000L); System.out.printf("Send async message OK, msgId: %s%n", sendResult.messageId()); } final CountDownLatch countDownLatch = new CountDownLatch(1); { final Future result = producer.sendAsync(producer.createBytesMessage("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(StandardCharsets.UTF_8))); result.addListener(new FutureListener() { @Override public void operationComplete(Future future) { if (future.getThrowable() != null) { System.out.printf("Send async message Failed, error: %s%n", future.getThrowable().getMessage()); } else { System.out.printf("Send async message OK, msgId: %s%n", future.get().messageId()); } countDownLatch.countDown(); } }); } { producer.sendOneway(producer.createBytesMessage("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(StandardCharsets.UTF_8))); System.out.printf("Send oneway message OK%n"); } try { countDownLatch.await(); Thread.sleep(500); // 等一些时间来发送消息 } catch (InterruptedException ignore) { } producer.shutdown(); } } ``` ### 8.2 OMSPullConsumer 用OMS PullConsumer 来从指定的队列中拉取消息 ```java import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.PullConsumer; import io.openmessaging.producer.Producer; import io.openmessaging.producer.SendResult; public class SimplePullConsumer { public static void main(String[] args) { final MessagingAccessPoint messagingAccessPoint = OMS.getMessagingAccessPoint("oms:rocketmq://localhost:9876/default:default"); messagingAccessPoint.startup(); final Producer producer = messagingAccessPoint.createProducer(); final PullConsumer consumer = messagingAccessPoint.createPullConsumer( OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "OMS_CONSUMER")); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); final String queueName = "TopicTest"; producer.startup(); Message msg = producer.createBytesMessage(queueName, "Hello Open Messaging".getBytes()); SendResult sendResult = producer.send(msg); System.out.printf("Send Message OK. MsgId: %s%n", sendResult.messageId()); producer.shutdown(); consumer.attachQueue(queueName); consumer.startup(); System.out.printf("Consumer startup OK%n"); // 运行直到发现一个消息被发送了 boolean stop = false; while (!stop) { Message message = consumer.receive(); if (message != null) { String msgId = message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID); System.out.printf("Received one message: %s%n", msgId); consumer.ack(msgId); if (!stop) { stop = msgId.equalsIgnoreCase(sendResult.messageId()); } } else { System.out.printf("Return without any message%n"); } } consumer.shutdown(); messagingAccessPoint.shutdown(); } } ``` ### 8.3 OMSPushConsumer 以下示范如何将 OMS PushConsumer 添加到指定的队列,并通过 MessageListener 消费这些消息。 ```java import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.MessageListener; import io.openmessaging.consumer.PushConsumer; public class SimplePushConsumer { public static void main(String[] args) { final MessagingAccessPoint messagingAccessPoint = OMS .getMessagingAccessPoint("oms:rocketmq://localhost:9876/default:default"); final PushConsumer consumer = messagingAccessPoint. createPushConsumer(OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "OMS_CONSUMER")); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { consumer.shutdown(); messagingAccessPoint.shutdown(); } })); consumer.attachQueue("OMS_HELLO_TOPIC", new MessageListener() { @Override public void onReceived(Message message, Context context) { System.out.printf("Received one message: %s%n", message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID)); context.ack(); } }); consumer.startup(); System.out.printf("Consumer startup OK%n"); } } ``` ================================================ FILE: docs/cn/SlaveActingMasterMode.md ================================================ # Slave Acting Master模式 ## 背景 ![](https://s4.ax1x.com/2022/02/05/HnW3CQ.png) 上图为当前RocketMQ Master-Slave冷备部署,在该部署方式下,即使一个Master掉线,发送端仍然可以向其他Master发送消息,对于消费端而言,若开启备读,Consumer会自动重连到对应的Slave机器,不会出现消费停滞的情况。但也存在以下问题: 1. 一些仅限于在Master上进行的操作将无法进行,包括且不限于: - searchOffset - maxOffset - minOffset - earliestMsgStoreTime - endTransaction 所有锁MQ相关操作,包括lock,unlock,lockBatch,unlockAll 具体影响为: - 客户端无法获取位于该副本组的mq的锁,故当本地锁过期后,将无法消费该组的顺序消息 - 客户端无法主动结束处于半状态的事务消息,只能等待broker回查事务状态 - Admin tools或控制中依赖查询offset及earliestMsgStoreTime等操作在该组上无法生效 2. 故障Broker组上的二级消息消费将会中断,该类消息特点依赖Master Broker上的线程扫描CommitLog上的特殊Topic,并将满足要求的消息投放回CommitLog,如果Master Broker下线,会出现二级消息的消费延迟或丢失。具体会影响到当前版本的延迟消息消费、事务消息消费、Pop消费。 3. 没有元数据的反向同步。Master重新被人工拉起后,容易造成元数据的回退,如Master上线后将落后的消费位点同步给备,该组broker的消费位点回退,造成大量消费重复。 ![](https://s4.ax1x.com/2022/02/05/HnWwUU.png) 上图为DLedger(Raft)架构,其可以通过选主一定程度上规避上述存在的问题,但可以看到DLedger模式下当前需要强制三副本及以上。 提出一个新的方案,Slave代理Master模式,作为Master-Slave部署模式的升级。在原先Master-Slave部署模式下,通过备代理主、轻量级心跳、副本组信息获取、broker预上线机制、二级消息逃逸等方式,当同组Master发生故障时,Slave将承担更加重要的作用,包括: - 当Master下线后,该组中brokerId最小的Slave会承担备读 以及 一些 客户端和管控会访问 但却只能在Master节点上完成的任务。包括且不限于searchOffset、maxOffset、minOffset、earliestMsgStoreTime、endTransaction以及所有锁MQ相关操作lock,unlock,lockBatch,unlockAll。 - 当Master下线后,故障Broker组上的二级消息消费将不会中断,由该组中该组中brokerId最小的Slave承担起该任务,定时消息、Pop消息、事务消息等仍然可以正常运行。 - 当Master下线后,在Slave代理Master一段时间主后,然后当Master再次上线后,通过预上线机制,Master会自动完成元数据的反向同步后再上线,不会出现元数据回退,造成消息大量重复消费或二级消息大量重放。 ## 架构 ### 备代理主 Master下线后Slave能正常消费,且在不修改客户端代码情况下完成只能在Master完成的操作源自于Namesrv对“代理”Master的支持。此处“代理”Master指的是,当副本组处于无主状态时,Namesrv将把brokerId最小的存活Slave视为“代理”Master,具体表现为在构建TopicRouteData时,将该Slave的brokerId替换为0,并将brokerPermission修改为4(Read-Only),从而使得该Slave在客户端视图中充当只读模式的Master的角色。 此外,当Master下线后,brokerId最小的Slave会承担起二级消息的扫描和重新投递功能,这也是“代理”的一部分。 ```java //改变二级消息扫描状态 public void changeSpecialServiceStatus(boolean shouldStart) { …… //改变延迟消息服务的状态 changeScheduleServiceStatus(shouldStart); //改变事务消息服务的状态 changeTransactionCheckServiceStatus(shouldStart); //改变Pop消息服务状态 if (this.ackMessageProcessor != null) { LOG.info("Set PopReviveService Status to {}", shouldStart); this.ackMessageProcessor.setPopReviveServiceStatus(shouldStart); } } ``` ### 轻量级心跳 如上文所述,brokerId最小的存活Slave在Master故障后开启自动代理Master模式,因此需要一种机制,这个机制需要保证: 1. Nameserver能及时发现broker上下线并完成路由替换以及下线broker的路由剔除。 2. Broker能及时感知到同组Broker的上下线情况。 针对1,Nameserver原本就存在判活机制,定时会扫描不活跃的broker使其下线,而原本broker与nameserver的“心跳”则依赖于registerBroker操作,而这个操作涉及到topic信息上报,过于“重”,而且注册间隔过于长,因此需要一个轻量级的心跳机制,RocketMQ 5.0在nameserver和broker间新增BrokerHeartbeat请求,broker会定时向nameserver发送心跳,若nameserver定时任务扫描发现超过心跳超时时间仍未收到该broker的心跳,将unregister该broker。registerBroker时会完成心跳超时时间的设置,并且注册时如果发现broker组内最小brokerId发生变化,将反向通知该组所有broker,并在路由获取时将最小brokerId的Slave路由替换使其充当只读模式的Master的角色 针对2,通过两个机制来及时感知同组broker上下线情况,1是上文中介绍的当nameserver发现该broker组内最小brokerId发生变化,反向通知该组所有broker。2是broker自身会有定时任务,向nameserver同步本broker组存活broker的信息,RocketMQ 5.0会新增GetBrokerMemberGroup请求来完成该工作。 Slave Broker发现自己是该组中最小的brokerId,将会开启代理模式,而一旦Master Broker重新上线,Slave Broker同样会通过Nameserver反向通知或自身定时任务同步同组broker的信息感知到,并自动结束代理模式。 ### 二级消息逃逸 代理模式开启后,brokerId最小的Slave会承担起二级消息的扫描和重新投递功能。 二级消息一般分为两个阶段,发送或者消费时会发送到一个特殊topic中,后台会有线程会扫描,最终的满足要求的消息会被重新投递到Commitlog中。我们可以让brokerId最小的Slave进行扫描,但如果扫描之后的消息重新投递到本Commitlog,那将会破坏Slave不可写的语义,造成Commitlog分叉。因此RocketMQ 5.0提出一种逃逸机制,将重放的二级消息远程或本地投放到其他Master的Commitlog中。 - 远程逃逸 ![](https://s4.ax1x.com/2022/02/05/HnWWVK.png) 如上图所示,假设Region A发生故障,Region B中的节点2将会承担二级消息的扫描任务,同时将最终的满足要求的消息通过EscapeBridge远程发送到当前Broker集群中仍然存活的Master上。 - 本地逃逸 ![](https://s4.ax1x.com/2022/02/05/HnWfUO.png) 本地逃逸需要在BrokerContainer下进行,如果BrokerContainer中存在存活的Master,会优先向同进程的Master Commitlog中逃逸,避免远程RPC。 #### 各类二级消息变化 **延迟消息** Slave代理Master时,ScheduleMessageService将启动,时间到期的延迟消息将通过EscapeBridge优先往本地Master逃逸,若没有则向远程的Master逃逸。该broker上存量的时间未到期的消息将会被逃逸到存活的其他Master上,数据量上如果该broker上有大量的延迟消息未到期,远程逃逸会造成集群内部会有较大数据流转,但基本可控。 **POP消息** 1. CK/ACK拼key的时候增加brokerName属性。这样每个broker能在扫描自身commitlog的revive topic时抵消其他broker的CK/ACK消息。 2. Slave上的CK/ACK消息将被逃逸到其他指定的Master A上(需要同一个Master,否则CK/ACK无法抵消,造成消息重复),Master A扫描自身Commitlog revive消息并进行抵消,若超时,则将根据CK消息中的信息向Slave拉取消息(若本地有则拉取本地,否则远程拉取),然后投放到本地的retry topic中。 数据量上,如果是远程投递或拉取,且有消费者大量通过Pop消费存量的Slave消息,并且长时间不ACK,则在集群内部会有较大数据流转。 ### 预上线机制 ![](https://s4.ax1x.com/2022/02/05/HnW5Pe.png) 当Master Broker下线后,Slave Broker将承担备读的作用,并对二级消息进行代理,因此Slave Broker中的部分元数据包括消费位点、定时消息进度等会比下线的Master Broker更加超前。如果Master Broker重新上线,Slave Broker元数据将被Master Broker覆盖,该组Broker元数据将发生回退,可能造成大量消息重复。因此,需要一套预上线机制来完成元数据的反向同步。 需要为consumerOffset和delayOffset等元数据增加版本号(DataVersion)的概念,并且为了防止版本号更新太频繁,增加更新步长的概念,比如对于消费位点来说,默认每更新位点超过500次,版本号增加到下一个版本。 如上图所示,Master Broker启动前会进行预上线,再预上线之前,对外不可见(Broker会有isIsolated标记自己的状态,当其为true时,不会像nameserver注册和发送心跳),因此也不会对外提供服务,二级消息的扫描流程也不会进行启动,具体预上线机制如下: 1. Master Broker向NameServer获取Slave Broker地址(GetBrokerMemberGroup请求),但不注册 2. Master Broker向Slave Broker发送自己的状态信息和地址 3. Slave Broker得到Master Broker地址后和状态信息后,建立HA连接,并完成握手,进入Transfer状态 4. Master Broker再完成握手后,反向获取备的元数据,包括消费位点、定时消息进度等,根据版本号决定是否更新。 5. Master Broker对broker组内所有Slave Broker都完成1-4步操作后,正式上线,向NameServer注册,正式对外提供服务。 ### 锁Quorum 当Slave代理Master时,外部看到的是“只读”的Master,因此顺序消息仍然可以对队列上锁,消费不会中断。但当真的Master重新上线后,在一定的时间差内可能会造成多个consumer锁定同一个队列,比如一个consumer仍然锁着代理的备某一个队列,一个consumer锁刚上线的主的同一队列,造成顺序消息的乱序和重复。 因此在lock操作时要求,需锁broker副本组的大多数成员(quorum原则)均成功才算锁成功。但两副本下达不到quorum的原则,所以提供了lockInStrictMode参数,表示消费端消费顺序消息锁队列时是否使用严格模式。严格模式即对单个队列而言,需锁副本组的大多数成员(quorum原则)均成功才算锁成功,非严格模式即锁任意一副本成功就算锁成功,该参数默认为false。当对消息顺序性高于可用性时,需将该参数设置为false。 ## 配置更新 Nameserver - scanNotActiveBrokerInterval:扫描不活跃broker间隔,每次扫描将判断broker心跳是否超时,默认5s。 - supportActingMaster:nameserver端是否支持Slave代理Master模式,开启后,副本组在无master状态下,brokerId==1的slave将在TopicRoute中被替换成master(即brokerId=0),并以只读模式对客户端提供服务,默认为false。 Broker - enableSlaveActingMaster:broker端开启slave代理master模式总开关,默认为false。 - enableRemoteEscape:是否允许远程逃逸,默认为false。 - brokerHeartbeatInterval:broker向nameserver发送心跳间隔(不同于注册间隔),默认1s。 - brokerNotActiveTimeoutMillis:broker不活跃超时时间,超过此时间nameserver仍未收到broker心跳,则判定broker下线,默认10s。 - sendHeartbeatTimeoutMillis:broker发送心跳请求超时时间,默认1s。 - lockInStrictMode:消费端消费顺序消息锁队列时是否使用严格模式,默认为false,上文已介绍。 - skipPreOnline:broker跳过预上线流程,默认为false。 - compatibleWithOldNameSrv:是否以兼容模式访问旧nameserver,默认为true。 ## 兼容性方案 新版nameserver和旧版broker:新版nameserver可以完全兼容旧版broker,无兼容问题。 旧版nameserver和新版Broker:新版Broker开启Slave代理Master,会向Nameserver发送 BROKER_HEARTBEAT以及GET_BROKER_MEMBER_GROUP请求,但由于旧版本nameserver无法处理这些请求。因此需要在brokerConfig中配置compatibleWithOldNameSrv=true,开启对旧版nameserver的兼容模式,在该模式下,broker的一些新增RPC将通过复用原有RequestCode实现,具体为: 新增轻量级心跳将通过复用QUERY_DATA_VERSION实现 新增获取BrokerMemberGroup数据将通过复用GET_ROUTEINFO_BY_TOPIC实现,具体实现方式是每个broker都会新增rmq_sys_{brokerName}的系统topic,通过获取该系统topic的路由来获取该副本组的存活信息。 但旧版nameserver无法提供代理功能,Slave代理Master的功能将无法生效,但不影响其他功能。 客户端对新旧版本的nameserver和broker均无兼容性问题。 参考文档:[原RIP](https://github.com/apache/rocketmq/wiki/RIP-32-Slave-Acting-Master-Mode) ================================================ FILE: docs/cn/acl/RocketMQ_Multiple_ACL_Files_设计.md ================================================ # Version记录 | 时间 | 主要内容 | 作者 | | --- | --- | --- | | 2022-01-27 | 初版,包括需求背景、兼容性影响、重要业务逻辑和后续扩展性考虑 | sunxi92 | 中文文档在描述特定专业术语时,仍然使用英文。 # 需求背景 RocketMQ ACL特性目前只支持单个ACL配置文件,当存在很多用户时该配置文件会非常大,因此提出支持多ACL配置文件的想法。 如果支持该特性那么也方便对RocketMQ用户进行分类。 # 兼容性影响 当前在支持多ACL配置文件特性的设计上是向前兼容的。 # 重要业务逻辑 ## 1. ACL配置文件存储路径 ACL配置文件夹是在RocketMQ安装目录下的conf/acl目录中,也可以在该路径新建子目录并在子目录中新建ACL配置文件,同时也保留了之前默认的配置文件conf/plain_acl.yml。 注意:目前用户还不能自定义配置文件目录。 ## 2. ACL配置文件更新 热感知:当检测到ACL配置文件改动会自动刷新数据,判断ACL配置文件是否发生变化的依据是文件的修改时间是否发生变化 ## 3. RocketMQ Broker缓存ACL配置信息数据结构设计 - aclPlainAccessResourceMap aclPlainAccessResourceMap是个Map类型,用来缓存所有ACL配置文件的权限数据,其中key表示ACL配置文件的绝对路径, value表示相应配置文件中的权限数据,需要注意的是value也是一个Map类型,其中key是String类型表示AccessKey,value是PlainAccessResource类型。 - accessKeyTable accessKeyTable是个Map类型,用来缓存AccessKey和ACL配置文件的映射关系,其中key表示AccessKey,value表示ACL配置文件的绝对路径。 - globalWhiteRemoteAddressStrategy globalWhiteRemoteAddressStrategy用来缓存所有ACL配置文件的全局白名单。 - globalWhiteRemoteAddressStrategyMap globalWhiteRemoteAddressStrategyMap是个Map类型,用来缓存ACL配置文件和全局白名单的映射关系 - dataVersionMap dataVersionMap是个Map类型,用来缓存所有ACL配置文件的DataVersion,其中key表示ACL配置文件的绝对路径,value表示该配置文件对应的DataVersion。 ## 4.加载和监控ACL配置文件 ### 4.1 加载ACL配置文件 - load() load()方法会获取"RocketMQ安装目录/conf/acl"目录(包括该目录的子目录)和"rocketmq.acl.plain.file"下所有ACL配置文件,然后遍历这些文件读取权限数据和全局白名单。 - load(String aclFilePath) load(String aclFilePath)方法完成加载指定ACL配置文件内容的功能,将配置文件中的全局白名单globalWhiteRemoteAddresses和用户权限accounts加载到缓存中, 这里需要注意以下几点: (1)判断缓存中该配置文件的全局白名单globalWhiteRemoteAddresses和用户权限accounts数据是否为空,如果不为空则需要注意删除文件原有数据 (2)相同的accessKey只允许存在在一个ACL配置文件中 ### 4.2 监控ACL配置文件 watch()方法用来监控"RocketMQ安装目录/conf/acl"目录下所有ACL配置文件和"rocketmq.acl.plain.file"是否发生变化,变化考虑两种情况:一种是ACL配置文件的数量发生变化, 此时会调用load()方法重新加载所有配置文件的数据;一种是配置文件的内容发生变化;具体完成监控ACL配置文件变化的是AclFileWatchService服务, 该服务是一个线程,当启动该服务后它会以WATCH_INTERVAL(该参数目前设置为5秒,目前还不能在Broker配置文件中设置)的时间间隔来执行其核心逻辑。在该服务中会记录其监控的ACL配置文件目录aclPath、 ACL配置文件的数量aclFilesNum、所有ACL配置文件绝对路径fileList以及每个ACL配置文件最近一次修改的时间fileLastModifiedTime (Map类型,key为ACL配置文件的绝对路径,value为其最近一次修改时间)。 该服务的核心逻辑如下: 获取ACL配置文件数量并和aclFilesNum进行比较是否相等,如果不相等则更新aclFilesNum和fileList并调用load()方法重新加载所有配置文件; 如果相等则遍历每个ACL配置文件,获取其最近一次修改的时间,并将该时间与fileLastModifiedTime中记录的时间进行比较,如果不相等则表示该文件发生过修改, 此时调用load(String aclFilePath)方法重新加载该配置文件。 ## 5. 权限数据相关操作修改 (1) updateAclConfigFileVersion(Map updateAclConfigMap) 添加对缓存dataVersionMap的修改 (2)updateAccessConfig(PlainAccessConfig plainAccessConfig) 将该方法原有的逻辑修改为:首先判断accessKeyTable中是否包含待修改的accessKey,如果包含则根据accessKey来获取其对应的ACL配置文件绝对路径, 再根据该路径更新aclPlainAccessResourceMap中缓存的数据,最后将该ACL配置文件中的数据写回原文件;如果不包含则会将数据写到"rocketmq.acl.plain.file"配置文件中, 然后更新accessKeyTable和aclPlainAccessResourceMap,最后最后将该ACL配置文件中的数据写回原文件。 (3)deleteAccessConfig(String accessKey) 将该方法原有的逻辑修改为:判断accessKeyTable中是否存在accessKey,如果不存在则返回false,否则将其删除并将修改后的数据写回原文件。 (4)getAllAclConfig() fileList中存储了所有ACL配置文件的绝对路径,遍历fileList分别从各ACL配置文件中读取数据并组装返回 (5)updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList, String fileName) 该方法是新增的,完成功能是修改指定ACL配置文件的全局白名单,为后续添加相关运维命令做准备 ## 6. ACL相关运维命令修改 (1)ClusterAclConfigVersionListSubCommand 将printClusterBaseInfo(final DefaultMQAdminExt defaultMQAdminExt, final String addr)方法原有的逻辑修改为: 获取全部的ACL配置文件的DataVersion并输出。注意:获取的全部ACL配置文件的DataVersion集合可能为空,这里需要添加判断 (2)GetBrokerAclConfigResponseHeader 在GetBrokerAclConfigResponseHeader中新增allAclFileVersion字段,它是个Map类型,其key表示ACL配置文件的绝对路径,value表示对应ACL配置文件的DataVersion (3)ClusterAclVersionInfo 在ClusterAclVersionInfo中废弃了aclConfigDataVersion属性,增加了allAclConfigDataVersion属性,该属性是个Map类型,用来存储所有ACL配置文件的版本数据, 其中key表示ACL配置文件的绝对路径,value表示对应ACL配置文件的DataVersion ## 7. 关于ACL配置文件DataVersion存储修改 在原来版本中ACL权限数据存储在一个配置文件中,所以只记录了该配置文件的DataVersion,而现在需要支持多个配置文件特性,每个配置文件都有自己的DataVersion, 为了能够准确记录所有配置文件的DataVersion,需要调整相关类型的属性、接口及方法。 (1)PlainPermissionManager 对PlainPermissionManager属性的修改具体如下: - 废弃dataVersion属性,该属性在历史版本中是用来存来存储默认ACL配置文件的DataVersion - 新增dataVersionMap属性用来缓存所有ACL配置文件的DataVersion,它是一个Map类型,其key表示ACL配置文件的绝对路径,value表示对应配置文件的DataVersion (2)AccessValidator 对AccessValidator的修改如下: - 废弃String getAclConfigVersion();,该接口原来是获取ACL配置文件文件的版本数据 - 新增Map getAllAclConfigVersion();该接口是用来获取所有ACL配置文件的版本数据,接口会返回一个Map类型数据, key表示各ACL配置文件的绝对路径,value表示对应配置文件的版本数据 (3)PlainAccessValidator 由于PlainAccessValidator实现了AccessValidator接口,所以相应地增加了getAllAclConfigVersion()方法 # 后续扩展性考虑 1.目前的修改只支持ACL配置文件存储在"RocketMQ安装目录/conf/acl"目录下,后续可以考虑支持多目录; 2.目前ACL配置文件路径是不支持让用户指定,后续可以考虑让用户指定指定ACL配置文件的存储路径 3.当前updateGlobalWhiteAddrsConfig命令只支持修改"rocketmq.acl.plain.file"文件中全局白名单, 后续可以扩展为修改指定ACL配置文件的全局白名单(如果参数中没有传ACL配置文件则会修改"rocketmq.acl.plain.file"文件) 4.目前ACL数据中的secretKey是以明文形式存储在文件中,在一些对此类信息敏感的行业是不允许以明文落地,后续可以考虑安全性问题 5.目前ACL数据存储只支持文件形式存储,后续可以考虑增加数据库存储 ================================================ FILE: docs/cn/acl/user_guide.md ================================================ # 权限控制 ---- ## 1.权限控制特性介绍 权限控制(ACL)主要为RocketMQ提供Topic资源级别的用户访问控制。用户在使用RocketMQ权限控制时,可以在Client客户端通过 RPCHook注入AccessKey和SecretKey签名;同时,将对应的权限控制属性(包括Topic访问权限、IP白名单和AccessKey和SecretKey签名等)设置在distribution/conf/plain_acl.yml的配置文件中。Broker端对AccessKey所拥有的权限进行校验,校验不过,抛出异常; ACL客户端可以参考:**org.apache.rocketmq.example.simple**包下面的**AclClient**代码。 ## 2. 权限控制的定义与属性值 ### 2.1权限定义 对RocketMQ的Topic资源访问权限控制定义主要如下表所示,分为以下四种 | 权限 | 含义 | | --- | --- | | DENY | 拒绝 | | ANY | PUB 或者 SUB 权限 | | PUB | 发送权限 | | SUB | 订阅权限 | ### 2.2 权限定义的关键属性 | 字段 | 取值 | 含义 | | --- | --- | --- | | globalWhiteRemoteAddresses | \*;192.168.\*.\*;192.168.0.1 | 全局IP白名单 | | accessKey | 字符串 | Access Key | | secretKey | 字符串 | Secret Key | | whiteRemoteAddress | \*;192.168.\*.\*;192.168.0.1 | 用户IP白名单 | | admin | true;false | 是否管理员账户 | | defaultTopicPerm | DENY;PUB;SUB;PUB\|SUB | 默认的Topic权限 | | defaultGroupPerm | DENY;PUB;SUB;PUB\|SUB | 默认的ConsumerGroup权限 | | topicPerms | topic=权限 | 各个Topic的权限 | | groupPerms | group=权限 | 各个ConsumerGroup的权限 | 具体可以参考**distribution/conf/plain_acl.yml**配置文件 ## 3. 支持权限控制的集群部署 在**distribution/conf/plain_acl.yml**配置文件中按照上述说明定义好权限属性后,打开**aclEnable**开关变量即可开启RocketMQ集群的ACL特性。这里贴出Broker端开启ACL特性的properties配置文件内容: ``` brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/data/rocketmq/rootdir-a-m storePathCommitLog=/data/rocketmq/commitlog-a-m autoCreateSubscriptionGroup=true ## if acl is open,the flag will be true aclEnable=true listenPort=10911 brokerIP1=XX.XX.XX.XX1 namesrvAddr=XX.XX.XX.XX:9876 ``` ## 4. 权限控制主要流程 ACL主要流程分为两部分,主要包括权限解析和权限校验。 ### 4.1 权限解析 Broker端对客户端的RequestCommand请求进行解析,拿到需要鉴权的属性字段。 主要包括: (1)AccessKey:类似于用户名,代指用户主体,权限数据与之对应; (2)Signature:客户根据 SecretKey 签名得到的串,服务端再用SecretKey进行签名验证; ### 4.2 权限校验 Broker端对权限的校验逻辑主要分为以下几步: (1)检查是否命中全局 IP 白名单;如果是,则认为校验通过;否则走 2; (2)检查是否命中用户 IP 白名单;如果是,则认为校验通过;否则走 3; (3)校验签名,校验不通过,抛出异常;校验通过,则走 4; (4)对用户请求所需的权限 和 用户所拥有的权限进行校验;不通过,抛出异常; 用户所需权限的校验需要注意已下内容: (1)特殊的请求例如 UPDATE_AND_CREATE_TOPIC 等,只能由 admin 账户进行操作; (2)对于某个资源,如果有显性配置权限,则采用配置的权限;如果没有显性配置权限,则采用默认的权限; ## 5. 热加载修改后权限控制定义 RocketMQ的权限控制存储的默认实现是基于yml配置文件。用户可以动态修改权限控制定义的属性,而不需重新启动Broker服务节点。 ## 6. 权限控制的使用限制 (1)如果ACL与高可用部署(Master/Slave架构)同时启用,那么需要在Broker Master节点的distribution/conf/plain_acl.yml配置文件中 设置全局白名单信息,即为将Slave节点的ip地址设置至Master节点plain_acl.yml配置文件的全局白名单中。 (2)如果ACL与高可用部署(多副本Dledger架构)同时启用,由于出现节点宕机时,Dledger Group组内会自动选主,那么就需要将Dledger Group组 内所有Broker节点的plain_acl.yml配置文件的白名单设置所有Broker节点的ip地址。 ## 7. ACL mqadmin配置管理命令 ### 7.1 更新ACL配置文件中“account”的属性值 该命令的示例如下: sh mqadmin updateAclConfig -n 192.168.1.2:9876 -b 192.168.12.134:10911 -a RocketMQ -s 1234567809123 -t topicA=DENY,topicD=SUB -g groupD=DENY,groupB=SUB 说明:如果不存在则会在ACL Config YAML配置文件中创建;若存在,则会更新对应的“accounts”的属性值; 如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 | 参数 | 取值 | 含义 | | --- | --- | --- | | n | eg:192.168.1.2:9876 | namesrv地址(必填) | | c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | | b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | | a | eg:RocketMQ | Access Key值(必填) | | s | eg:1234567809123 | Secret Key值(可选) | | m | eg:true | 是否管理员账户(可选) | | w | eg:192.168.0.* | whiteRemoteAddress,用户IP白名单(可选) | | i | eg:DENY;PUB;SUB;PUB\|SUB | defaultTopicPerm,默认Topic权限(可选) | | u | eg:DENY;PUB;SUB;PUB\|SUB | defaultGroupPerm,默认ConsumerGroup权限(可选) | | t | eg:topicA=DENY,topicD=SUB | topicPerms,各个Topic的权限(可选) | | g | eg:groupD=DENY,groupB=SUB | groupPerms,各个ConsumerGroup的权限(可选) | ### 7.2 删除ACL配置文件里面的对应“account” 该命令的示例如下: sh mqadmin deleteAccessConfig -n 192.168.1.2:9876 -c DefaultCluster -a RocketMQ 说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 其中,参数"a"为Access Key的值,用以标识唯一账户id,因此该命令的参数中指定账户id即可。 | 参数 | 取值 | 含义 | | --- | --- | --- | | n | eg:192.168.1.2:9876 | namesrv地址(必填) | | c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | | b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | | a | eg:RocketMQ | Access Key的值(必填) | ### 7.3 更新ACL配置文件里面中的全局白名单 该命令的示例如下: sh mqadmin updateGlobalWhiteAddr -n 192.168.1.2:9876 -b 192.168.12.134:10911 -g 10.10.154.1,10.10.154.2 说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 其中,参数"g"为全局IP白名的值,用以更新ACL配置文件中的“globalWhiteRemoteAddresses”字段的属性值。 | 参数 | 取值 | 含义 | | --- | --- | --- | | n | eg:192.168.1.2:9876 | namesrv地址(必填) | | c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | | b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | | g | eg:10.10.154.1,10.10.154.2 | 全局IP白名单(必填) | ### 7.4 查询集群/Broker的ACL配置文件版本信息 该命令的示例如下: sh mqadmin clusterAclConfigVersion -n 192.168.1.2:9876 -c DefaultCluster 说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 | 参数 | 取值 | 含义 | | --- | --- | --- | | n | eg:192.168.1.2:9876 | namesrv地址(必填) | | c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | | b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | ### 7.5 查询集群/Broker的ACL配置文件全部内容 该命令的示例如下: sh mqadmin getAclConfig -n 192.168.1.2:9876 -c DefaultCluster 说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 | 参数 | 取值 | 含义 | | --- | --- | --- | | n | eg:192.168.1.2:9876 | namesrv地址(必填) | | c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | | b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | **特别注意**开启Acl鉴权认证后导致Master/Slave和Dledger模式下Broker同步数据异常的问题, 在社区[4.5.1]版本中已经修复,具体的PR链接为:#1149。 ================================================ FILE: docs/cn/architecture.md ================================================ # 架构设计 --- ## 1 技术架构 ![](image/rocketmq_architecture_1.png) RocketMQ架构上主要分为四部分,如上图所示: - Producer:消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。 - Consumer:消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。 - NameServer:NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能:Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer和Consumer仍然可以动态感知Broker的路由的信息。 - BrokerServer:Broker主要负责消息的存储、投递和查询以及服务高可用保证,为了实现这些功能,Broker包含了以下几个重要子模块。 1. Remoting Module:整个Broker的实体,负责处理来自Client端的请求。 2. Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息。 3. Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。 4. HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能。 5. Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。 ![](image/rocketmq_architecture_2.png) ## 2 部署架构 ![](image/rocketmq_architecture_3.png) ### RocketMQ 网络部署特点 - NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。 - Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。 注意:当前RocketMQ版本在部署架构上支持一Master多Slave,但只有BrokerId=1的从服务器才会参与消息的读负载。 - Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic 服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。 - Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,消费者在向Master拉取消息时,Master服务器会根据拉取偏移量与最大偏移量的距离(判断是否读老消息,产生读I/O),以及从服务器是否可读等因素建议下一次是从Master还是Slave拉取。 结合部署架构图,描述集群工作流程: - 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。 - Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。 - 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。 - Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。 - Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。 ================================================ FILE: docs/cn/best_practice.md ================================================ # 最佳实践 --- ## 1 生产者 ### 1.1 发送消息注意事项 #### 1 Tags的使用 一个应用尽可能用一个Topic,而消息子类型则可以用tags来标识。tags可以由应用自由设置,只有生产者在发送消息设置了tags,消费方在订阅消息时才可以利用tags通过broker做消息过滤:message.setTags("TagA")。 #### 2 Keys的使用 每个消息在业务层面的唯一标识码要设置到keys字段,方便将来定位消息丢失问题。服务器会为每个消息创建索引(哈希索引),应用可以通过topic、key来查询这条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证key尽可能唯一,这样可以避免潜在的哈希冲突。 ```java // 订单Id String orderId = "20034568923546"; message.setKeys(orderId); ``` #### 3 日志的打印 ​消息发送成功或者失败要打印消息日志,务必要打印SendResult和key字段。send消息方法只要不抛异常,就代表发送成功。发送成功会有多个状态,在sendResult里定义。以下对每个状态进行说明: - **SEND_OK** 消息发送成功。要注意的是消息发送成功也不意味着它是可靠的。要确保不会丢失任何消息,还应启用同步Master服务器或同步刷盘,即SYNC_MASTER或SYNC_FLUSH。 - **FLUSH_DISK_TIMEOUT** 消息发送成功但是服务器刷盘超时。此时消息已经进入服务器队列(内存),只有服务器宕机,消息才会丢失。消息存储配置参数中可以设置刷盘方式和同步刷盘时间长度,如果Broker服务器设置了刷盘方式为同步刷盘,即FlushDiskType=SYNC_FLUSH(默认为异步刷盘方式),当Broker服务器未在同步刷盘时间内(默认为5s)完成刷盘,则将返回该状态——刷盘超时。 - **FLUSH_SLAVE_TIMEOUT** 消息发送成功,但是服务器同步到Slave时超时。此时消息已经进入服务器队列,只有服务器宕机,消息才会丢失。如果Broker服务器的角色是同步Master,即SYNC_MASTER(默认是异步Master即ASYNC_MASTER),并且从Broker服务器未在同步刷盘时间(默认为5秒)内完成与主服务器的同步,则将返回该状态——数据同步到Slave服务器超时。 - **SLAVE_NOT_AVAILABLE** 消息发送成功,但是此时Slave不可用。如果Broker服务器的角色是同步Master,即SYNC_MASTER(默认是异步Master服务器即ASYNC_MASTER),但没有配置slave Broker服务器,则将返回该状态——无Slave服务器可用。 ### 1.2 消息发送失败处理方式 Producer的send方法本身支持内部重试,重试逻辑如下: - 至多重试2次。 - 如果同步模式发送失败,则轮转到下一个Broker,如果异步模式发送失败,则只会在当前Broker进行重试。这个方法的总耗时时间不超过sendMsgTimeout设置的值,默认10s。 - 如果本身向broker发送消息产生超时异常,就不会再重试。 以上策略也是在一定程度上保证了消息可以发送成功。如果业务对消息可靠性要求比较高,建议应用增加相应的重试逻辑:比如调用send同步方法发送失败时,则尝试将消息存储到db,然后由后台线程定时重试,确保消息一定到达Broker。 上述db重试方式为什么没有集成到MQ客户端内部做,而是要求应用自己去完成,主要基于以下几点考虑:首先,MQ的客户端设计为无状态模式,方便任意的水平扩展,且对机器资源的消耗仅仅是cpu、内存、网络。其次,如果MQ客户端内部集成一个KV存储模块,那么数据只有同步落盘才能较可靠,而同步落盘本身性能开销较大,所以通常会采用异步落盘,又由于应用关闭过程不受MQ运维人员控制,可能经常会发生 kill -9 这样暴力方式关闭,造成数据没有及时落盘而丢失。第三,Producer所在机器的可靠性较低,一般为虚拟机,不适合存储重要数据。综上,建议重试过程交由应用来控制。 ### 1.3选择oneway形式发送 通常消息的发送是这样一个过程: - 客户端发送请求到服务器 - 服务器处理请求 - 服务器向客户端返回应答 所以,一次消息发送的耗时时间是上述三个步骤的总和,而某些场景要求耗时非常短,但是对可靠性要求并不高,例如日志收集类应用,此类应用可以采用oneway形式调用,oneway形式只发送请求不等待应答,而发送请求在客户端实现层面仅仅是一个操作系统系统调用的开销,即将数据写入客户端的socket缓冲区,此过程耗时通常在微秒级。 ## 2 消费者 ### 2.1 消费过程幂等 RocketMQ无法避免消息重复(Exactly-Once),所以如果业务对消费重复非常敏感,务必要在业务层面进行去重处理。可以借助关系数据库进行去重。首先需要确定消息的唯一键,可以是msgId,也可以是消息内容中的唯一标识字段,例如订单Id等。在消费之前判断唯一键是否在关系数据库中存在。如果不存在则插入,并消费,否则跳过。(实际过程要考虑原子性问题,判断是否存在可以尝试插入,如果报主键冲突,则插入失败,直接跳过) msgId一定是全局唯一标识符,但是实际使用中,可能会存在相同的消息有两个不同msgId的情况(消费者主动重发、因客户端重投机制导致的重复等),这种情况就需要使业务字段进行重复消费。 ### 2.2 消费速度慢的处理方式 #### 1 提高消费并行度 绝大部分消息消费行为都属于 IO 密集型,即可能是操作数据库,或者调用 RPC,这类消费行为的消费速度在于后端数据库或者外系统的吞吐量,通过增加消费并行度,可以提高总的消费吞吐量,但是并行度增加到一定程度,反而会下降。所以,应用必须要设置合理的并行度。 如下有几种修改消费并行度的方法: - 同一个 ConsumerGroup 下,通过增加 Consumer 实例数量来提高并行度(需要注意的是超过订阅队列数的 Consumer 实例无效)。可以通过加机器,或者在已有机器启动多个进程的方式。 - 提高单个 Consumer 的消费并行线程,通过修改参数 consumeThreadMin、consumeThreadMax实现。 #### 2 批量方式消费 某些业务流程如果支持批量方式消费,则可以很大程度上提高消费吞吐量,例如订单扣款类应用,一次处理一个订单耗时 1 s,一次处理 10 个订单可能也只耗时 2 s,这样即可大幅度提高消费的吞吐量,通过设置 consumer的 consumeMessageBatchMaxSize 返个参数,默认是 1,即一次只消费一条消息,例如设置为 N,那么每次消费的消息数小于等于 N。 #### 3 跳过非重要消息 发生消息堆积时,如果消费速度一直追不上发送速度,如果业务对数据要求不高的话,可以选择丢弃不重要的消息。例如,当某个队列的消息数堆积到100000条以上,则尝试丢弃部分或全部消息,这样就可以快速追上发送消息的速度。示例代码如下: ```java public ConsumeConcurrentlyStatus consumeMessage( List msgs, ConsumeConcurrentlyContext context) { long offset = msgs.get(0).getQueueOffset(); String maxOffset = msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET); long diff = Long.parseLong(maxOffset) - offset; if (diff > 100000) { // TODO 消息堆积情况的特殊处理 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } // TODO 正常消费过程 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } ``` #### 4 优化每条消息消费过程 举例如下,某条消息的消费过程如下: - 根据消息从 DB 查询【数据 1】 - 根据消息从 DB 查询【数据 2】 - 复杂的业务计算 - 向 DB 插入【数据 3】 - 向 DB 插入【数据 4】 这条消息的消费过程中有4次与 DB的 交互,如果按照每次 5ms 计算,那么总共耗时 20ms,假设业务计算耗时 5ms,那么总过耗时 25ms,所以如果能把 4 次 DB 交互优化为 2 次,那么总耗时就可以优化到 15ms,即总体性能提高了 40%。所以应用如果对时延敏感的话,可以把DB部署在SSD硬盘,相比于SCSI磁盘,前者的RT会小很多。 ### 2.3 消费打印日志 如果消息量较少,建议在消费入口方法打印消息,消费耗时等,方便后续排查问题。 ```java public ConsumeConcurrentlyStatus consumeMessage( List msgs, ConsumeConcurrentlyContext context) { log.info("RECEIVE_MSG_BEGIN: " + msgs.toString()); // TODO 正常消费过程 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } ``` 如果能打印每条消息消费耗时,那么在排查消费慢等线上问题时,会更方便。 ### 2.4 其他消费建议 #### 1 关于消费者和订阅 ​第一件需要注意的事情是,不同的消费者组可以独立的消费一些 topic,并且每个消费者组都有自己的消费偏移量,请确保同一组内的每个消费者订阅信息保持一致。 #### 2 关于有序消息 消费者将锁定每个消息队列,以确保他们被逐个消费,虽然这将会导致性能下降,但是当你关心消息顺序的时候会很有用。我们不建议抛出异常,你可以返回 ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT 作为替代。 #### 3 关于并发消费 顾名思义,消费者将并发消费这些消息,建议你使用它来获得良好性能,我们不建议抛出异常,你可以返回 ConsumeConcurrentlyStatus.RECONSUME_LATER 作为替代。 #### 4 关于消费状态Consume Status 对于并发的消费监听器,你可以返回 RECONSUME_LATER 来通知消费者现在不能消费这条消息,并且希望可以稍后重新消费它。然后,你可以继续消费其他消息。对于有序的消息监听器,因为你关心它的顺序,所以不能跳过消息,但是你可以返回SUSPEND_CURRENT_QUEUE_A_MOMENT 告诉消费者等待片刻。 #### 5 关于Blocking 不建议阻塞监听器,因为它会阻塞线程池,并最终可能会终止消费进程 #### 6 关于线程数设置 消费者使用 ThreadPoolExecutor 在内部对消息进行消费,所以你可以通过设置 setConsumeThreadMin 或 setConsumeThreadMax 来改变它。 #### 7 关于消费位点 当建立一个新的消费者组时,需要决定是否需要消费已经存在于 Broker 中的历史消息CONSUME_FROM_LAST_OFFSET 将会忽略历史消息,并消费之后生成的任何消息。CONSUME_FROM_FIRST_OFFSET 将会消费每个存在于 Broker 中的信息。你也可以使用 CONSUME_FROM_TIMESTAMP 来消费在指定时间戳后产生的消息。 ## 3 Broker ### 3.1 Broker 角色 ​ Broker 角色分为 ASYNC_MASTER(异步主机)、SYNC_MASTER(同步主机)以及SLAVE(从机)。如果对消息的可靠性要求比较严格,可以采用 SYNC_MASTER加SLAVE的部署方式。如果对消息可靠性要求不高,可以采用ASYNC_MASTER加SLAVE的部署方式。如果只是测试方便,则可以选择仅ASYNC_MASTER或仅SYNC_MASTER的部署方式。 ### 3.2 FlushDiskType ​ SYNC_FLUSH(同步刷新)相比于ASYNC_FLUSH(异步处理)会损失很多性能,但是也更可靠,所以需要根据实际的业务场景做好权衡。 ### 3.3 Broker 配置 | 参数名 | 默认值 | 说明 | | -------------------------------- | ----------------------------- | ------------------------------------------------------------ | | listenPort | 10911 | 接受客户端连接的监听端口 | | namesrvAddr | null | nameServer 地址 | | brokerIP1 | 网卡的 InetAddress | 当前 broker 监听的 IP | | brokerIP2 | 跟 brokerIP1 一样 | 存在主从 broker 时,如果在 broker 主节点上配置了 brokerIP2 属性,broker 从节点会连接主节点配置的 brokerIP2 进行同步 | | brokerName | null | broker 的名称 | | brokerClusterName | DefaultCluster | 本 broker 所属的 Cluster 名称 | | brokerId | 0 | broker id,0 表示 master,其他的正整数表示 slave | | storePathRootDir | $HOME/store/ | 存储根路径 | | storePathCommitLog | $HOME/store/commitlog/ | 存储 commit log 的路径 | | mappedFileSizeCommitLog | 1024 * 1024 * 1024(1G) | commit log 的映射文件大小 |​ | deleteWhen | 04 | 在每天的什么时间删除已经超过文件保留时间的 commit log |​ | fileReservedTime | 72 | 以小时计算的文件保留时间 |​ | brokerRole | ASYNC_MASTER | SYNC_MASTER/ASYNC_MASTER/SLAVE |​ | flushDiskType | ASYNC_FLUSH | SYNC_FLUSH/ASYNC_FLUSH SYNC_FLUSH 模式下的 broker 保证在收到确认生产者之前将消息刷盘。ASYNC_FLUSH 模式下的 broker 则利用刷盘一组消息的模式,可以取得更好的性能。 |​ ## 4 NameServer ​RocketMQ 中,Name Servers 被设计用来做简单的路由管理。其职责包括: - Brokers 定期向每个名称服务器注册路由数据。 - 名称服务器为客户端,包括生产者,消费者和命令行客户端提供最新的路由信息。 ​ ​ ## 5 客户端配置 ​ 相对于RocketMQ的Broker集群,生产者和消费者都是客户端。本小节主要描述生产者和消费者公共的行为配置。 ### 5.1 客户端寻址方式 RocketMQ可以令客户端找到Name Server,然后通过Name Server再找到Broker。如下所示有多种配置方式,优先级由高到低,高优先级会覆盖低优先级。 - 代码中指定Name Server地址,多个namesrv地址之间用分号分割 ```java producer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876"); consumer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876"); ``` - Java启动参数中指定Name Server地址 ```text -Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876 ``` - 环境变量指定Name Server地址 ```text export NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876 ``` - HTTP静态服务器寻址(默认) 客户端启动后,会定时访问一个静态HTTP服务器,地址如下:,这个URL的返回内容如下: ```text 192.168.0.1:9876;192.168.0.2:9876 ``` 客户端默认每隔2分钟访问一次这个HTTP服务器,并更新本地的Name Server地址。URL已经在代码中硬编码,可通过修改/etc/hosts文件来改变要访问的服务器,例如在/etc/hosts增加如下配置: ```text 10.232.22.67 jmenv.tbsite.net ``` 推荐使用HTTP静态服务器寻址方式,好处是客户端部署简单,且Name Server集群可以热升级。 ### 5.2 客户端配置 DefaultMQProducer、TransactionMQProducer、DefaultMQPushConsumer、DefaultMQPullConsumer都继承于ClientConfig类,ClientConfig为客户端的公共配置类。客户端的配置都是get、set形式,每个参数都可以用spring来配置,也可以在代码中配置,例如namesrvAddr这个参数可以这样配置,producer.setNamesrvAddr("192.168.0.1:9876"),其他参数同理。 #### 1 客户端的公共配置 | 参数名 | 默认值 | 说明 | | ----------------------------- | ------- | ------------------------------------------------------------ | | namesrvAddr | | Name Server地址列表,多个NameServer地址用分号隔开 | | clientIP | 本机IP | 客户端本机IP地址,某些机器会发生无法识别客户端IP地址情况,需要应用在代码中强制指定 | | instanceName | DEFAULT | 客户端实例名称,客户端创建的多个Producer、Consumer实际是共用一个内部实例(这个实例包含网络连接、线程资源等) | | clientCallbackExecutorThreads | 4 | 通信层异步回调线程数 | | pollNameServerInterval | 30000 | 轮询Name Server间隔时间,单位毫秒 | | heartbeatBrokerInterval | 30000 | 向Broker发送心跳间隔时间,单位毫秒 | | persistConsumerOffsetInterval | 5000 | 持久化Consumer消费进度间隔时间,单位毫秒 | #### 2 Producer配置 | 参数名 | 默认值 | 说明 | | -------------------------------- | ---------------- | ------------------------------------------------------------ | | producerGroup | DEFAULT_PRODUCER | Producer组名,多个Producer如果属于一个应用,发送同样的消息,则应该将它们归为同一组 | | createTopicKey | TBW102 | 在发送消息时,自动创建服务器不存在的topic,需要指定Key,该Key可用于配置发送消息所在topic的默认路由。 | | defaultTopicQueueNums | 4 | 在发送消息,自动创建服务器不存在的topic时,默认创建的队列数 | | sendMsgTimeout | 3000 | 发送消息超时时间,单位毫秒 | | compressMsgBodyOverHowmuch | 4096 | 消息Body超过多大开始压缩(Consumer收到消息会自动解压缩),单位字节 | | retryAnotherBrokerWhenNotStoreOK | FALSE | 如果发送消息返回sendResult,但是sendStatus!=SEND_OK,是否重试发送 | | retryTimesWhenSendFailed | 2 | 如果消息发送失败,最大重试次数,该参数只对同步发送模式起作用 | | maxMessageSize | 4MB | 客户端限制的消息体大小,超过报错,同时服务端也会限制,所以需要跟服务端配合使用。 | | transactionCheckListener | | 事务消息回查监听器,如果发送事务消息,必须设置 | | checkThreadPoolMinSize | 1 | Broker回查Producer事务状态时,线程池最小线程数 | | checkThreadPoolMaxSize | 1 | Broker回查Producer事务状态时,线程池最大线程数 | | checkRequestHoldMax | 2000 | Broker回查Producer事务状态时,Producer本地缓冲请求队列大小 | | RPCHook | null | 该参数是在Producer创建时传入的,包含消息发送前的预处理和消息响应后的处理两个接口,用户可以在第一个接口中做一些安全控制或者其他操作。 | #### 3 PushConsumer配置 | 参数名 | 默认值 | 说明 | | ---------------------------- | ----------------------------- | ------------------------------------------------------------ | | consumerGroup | DEFAULT_CONSUMER | Consumer组名,多个Consumer如果属于一个应用,订阅同样的消息,且消费逻辑一致,则应该将它们归为同一组 | | messageModel | CLUSTERING | 消费模型支持集群消费和广播消费两种 | | consumeFromWhere | CONSUME_FROM_LAST_OFFSET | Consumer启动后,默认从上次消费的位置开始消费,这包含两种情况:一种是上次消费的位置未过期,则消费从上次中止的位置进行;一种是上次消费位置已经过期,则从当前队列第一条消息开始消费 | | consumeTimestamp | 半个小时前 | 只有当consumeFromWhere值为CONSUME_FROM_TIMESTAMP时才起作用。 | | allocateMessageQueueStrategy | AllocateMessageQueueAveragely | Rebalance算法实现策略 | | subscription | | 订阅关系 | | messageListener | | 消息监听器 | | offsetStore | | 消费进度存储 | | consumeThreadMin | 20 | 消费线程池最小线程数 | | consumeThreadMax | 20 | 消费线程池最大线程数 | | consumeConcurrentlyMaxSpan | 2000 | 单队列并行消费允许的最大跨度 | | pullThresholdForQueue | 1000 | 拉消息本地队列缓存消息最大数 | | pullInterval | 0 | 拉消息间隔,由于是长轮询,所以为0,但是如果应用为了流控,也可以设置大于0的值,单位毫秒 | | consumeMessageBatchMaxSize | 1 | 批量消费,一次消费多少条消息 | | pullBatchSize | 32 | 批量拉消息,一次最多拉多少条 | #### 4 PullConsumer配置 | 参数名 | 默认值 | 说明 | | -------------------------------- | ----------------------------- | ------------------------------------------------------------ | | consumerGroup | DEFAULT_CONSUMER | Consumer组名,多个Consumer如果属于一个应用,订阅同样的消息,且消费逻辑一致,则应该将它们归为同一组 | | brokerSuspendMaxTimeMillis | 20000 | 长轮询,Consumer拉消息请求在Broker挂起最长时间,单位毫秒 | | consumerTimeoutMillisWhenSuspend | 30000 | 长轮询,Consumer拉消息请求在Broker挂起超过指定时间,客户端认为超时,单位毫秒 | | consumerPullTimeoutMillis | 10000 | 非长轮询,拉消息超时时间,单位毫秒 | | messageModel | BROADCASTING | 消息支持两种模式:集群消费和广播消费 | | messageQueueListener | | 监听队列变化 | | offsetStore | | 消费进度存储 | | registerTopics | | 注册的topic集合 | | allocateMessageQueueStrategy | AllocateMessageQueueAveragely | Rebalance算法实现策略 | #### 5 Message数据结构 | 字段名 | 默认值 | 说明 | | -------------- | ------ | ------------------------------------------------------------ | | Topic | null | 必填,消息所属topic的名称 | | Body | null | 必填,消息体 | | Tags | null | 选填,消息标签,方便服务器过滤使用。目前只支持每个消息设置一个tag | | Keys | null | 选填,代表这条消息的业务关键词,服务器会根据keys创建哈希索引,设置后,可以在Console系统根据Topic、Keys来查询消息,由于是哈希索引,请尽可能保证key唯一,例如订单号,商品Id等。 | | Flag | 0 | 选填,完全由应用来设置,RocketMQ不做干预 | | DelayTimeLevel | 0 | 选填,消息延时级别,0表示不延时,大于0会延时特定的时间才会被消费 | | WaitStoreMsgOK | TRUE | 选填,表示消息是否在服务器落盘后才返回应答。 | ## 6 系统配置 本小节主要介绍系统(JVM/OS)相关的配置。 ### 6.1 JVM选项 ​ 推荐使用最新发布的JDK 1.8版本。通过设置相同的Xms和Xmx值来防止JVM调整堆大小以获得更好的性能。简单的JVM配置如下所示: ​ ​``` ​ ​-server -Xms8g -Xmx8g -Xmn4g ​ ``` ​ ​ 如果您不关心RocketMQ Broker的启动时间,还有一种更好的选择,就是通过“预触摸”Java堆以确保在JVM初始化期间每个页面都将被分配。那些不关心启动时间的人可以启用它: ​ -XX:+AlwaysPreTouch 禁用偏置锁定可能会减少JVM暂停, ​ -XX:-UseBiasedLocking 至于垃圾回收,建议使用带JDK 1.8的G1收集器。 ```text -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 ``` ​ 这些GC选项看起来有点激进,但事实证明它在我们的生产环境中具有良好的性能。另外不要把-XX:MaxGCPauseMillis的值设置太小,否则JVM将使用一个小的年轻代来实现这个目标,这将导致非常频繁的minor GC,所以建议使用rolling GC日志文件: ```text -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m ``` 如果写入GC文件会增加代理的延迟,可以考虑将GC日志文件重定向到内存文件系统: ```text -Xloggc:/dev/shm/mq_gc_%p.log123 ``` ### 6.2 Linux内核参数 ​ os.sh脚本在bin文件夹中列出了许多内核参数,可以进行微小的更改然后用于生产用途。下面的参数需要注意,更多细节请参考/proc/sys/vm/*的[文档](https://www.kernel.org/doc/Documentation/sysctl/vm.txt) - **vm.extra_free_kbytes**,告诉VM在后台回收(kswapd)启动的阈值与直接回收(通过分配进程)的阈值之间保留额外的可用内存。RocketMQ使用此参数来避免内存分配中的长延迟。(与具体内核版本相关) - **vm.min_free_kbytes**,如果将其设置为低于1024KB,将会巧妙的将系统破坏,并且系统在高负载下容易出现死锁。 - **vm.max_map_count**,限制一个进程可能具有的最大内存映射区域数。RocketMQ将使用mmap加载CommitLog和ConsumeQueue,因此建议将为此参数设置较大的值。(agressiveness --> aggressiveness) - **vm.swappiness**,定义内核交换内存页面的积极程度。较高的值会增加攻击性,较低的值会减少交换量。建议将值设置为10来避免交换延迟。 - **File descriptor limits**,RocketMQ需要为文件(CommitLog和ConsumeQueue)和网络连接打开文件描述符。我们建议设置文件描述符的值为655350。 - [Disk scheduler](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/ch06s04s02.html),RocketMQ建议使用I/O截止时间调度器,它试图为请求提供有保证的延迟。 []([]()) ================================================ FILE: docs/cn/client/java/API_Reference_ DefaultPullConsumer.md ================================================ ## DefaultPullConsumer --- ### 类简介 1. `DefaultMQPullConsumer extends ClientConfig implements MQPullConsumer` 2. `DefaultMQPullConsumer`主动的从Broker拉取消息,主动权由应用控制,可以实现批量的消费消息。Pull方式取消息的过程需要用户自己写,首先通过打算消费的Topic拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每个MessageQueue批量取消息,也可以自定义与控制offset位置。 3. 优势:consumer可以按需消费,不用担心自己处理能力,而broker堆积消息也会相对简单,无需记录每一个要发送消息的状态,只需要维护所有消息的队列和偏移量就可以了。所以对于慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适消息延迟与忙等。 4. 缺点:由于主动权在消费方,消费方无法及时获取最新的消息。比较适合不及时批处理场景。 ``` java import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class MQPullConsumer { private static final Map OFFSE_TABLE = new HashMap(); public static void main(String[] args) throws MQClientException { DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("groupName"); consumer.setNamesrvAddr("127.0.0.1:9876"); consumer.start(); // 从指定topic中拉取所有消息队列 Set mqs = consumer.fetchSubscribeMessageQueues("order-topic"); for(MessageQueue mq:mqs){ try { // 获取消息的offset,指定从store中获取 long offset = consumer.fetchConsumeOffset(mq,true); System.out.println("consumer from the queue:"+mq+":"+offset); while(true){ PullResult pullResult = consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32); putMessageQueueOffset(mq,pullResult.getNextBeginOffset()); switch(pullResult.getPullStatus()){ case FOUND: List messageExtList = pullResult.getMsgFoundList(); for (MessageExt m : messageExtList) { System.out.println(new String(m.getBody())); } break; case NO_MATCHED_MSG: break; case NO_NEW_MSG: break; case OFFSET_ILLEGAL: break; } } } catch (Exception e) { e.printStackTrace(); } } consumer.shutdown(); } // 保存上次消费的消息下标 private static void putMessageQueueOffset(MessageQueue mq, long nextBeginOffset) { OFFSE_TABLE.put(mq, nextBeginOffset); } // 获取上次消费的消息的下标 private static Long getMessageQueueOffset(MessageQueue mq) { Long offset = OFFSE_TABLE.get(mq); if(offset != null){ return offset; } return 0l; } } ``` ### 字段摘要 |类型|字段名称|描述| |------|-------|-------| |DefaultMQPullConsumerImpl|defaultMQPullConsumerImpl|DefaultMQPullConsumer的内部核心处理默认实现| |String|consumerGroup|消费的唯一分组| |long|brokerSuspendMaxTimeMillis|consumer取连接broker的最大延迟时间,不建议修改| |long|consumerTimeoutMillisWhenSuspend|pull取连接的最大超时时间,必须大于brokerSuspendMaxTimeMillis,不建议修改| |long|consumerPullTimeoutMillis|socket连接的最大超时时间,不建议修改| |String|messageModel|默认cluster模式| |int|messageQueueListener|消息queue监听器,用来获取topic的queue变化| |int|offsetStore|RemoteBrokerOffsetStore 远程与本地offset存储器| |int|registerTopics|注册到该consumer的topic集合| |int|allocateMessageQueueStrategy|consumer的默认获取queue的负载分配策略算法| ### 构造方法摘要 |方法名称|方法描述| |-------|------------| |DefaultMQPullConsumer()|由默认参数值创建一个Pull消费者 | |DefaultMQPullConsumer(final String consumerGroup, RPCHook rpcHook)|使用指定的分组名,hook创建一个消费者| |DefaultMQPullConsumer(final String consumerGroup)|使用指定的分组名消费者| |DefaultMQPullConsumer(RPCHook rpcHook)|使用指定的hook创建一个生产者| ### 使用方法摘要 |返回值|方法名称| 方法描述 | |-------|-------|----------------------------------------------------------------------------------------| |MQAdmin接口method|-------| ------------ | |void|createTopic(String key, String newTopic, int queueNum)| 在broker上创建指定的topic | |void|createTopic(String key, String newTopic, int queueNum, int topicSysFlag)| 在broker上创建指定的topic | |long|earliestMsgStoreTime(MessageQueue mq)| 查询最早的消息存储时间 | |long|maxOffset(MessageQueue mq)| 查询给定消息队列的最大offset | |long|minOffset(MessageQueue mq)| 查询给定消息队列的最小offset | |QueryResult|queryMessage(String topic, String key, int maxNum, long begin, long end)| 按关键字查询消息 | |long|searchOffset(MessageQueue mq, long timestamp)| 查找指定时间的消息队列的物理offset | |MessageExt|viewMessage(String offsetMsgId)| 根据给定的msgId查询消息 | |MessageExt|public MessageExt viewMessage(String topic, String msgId)| 根据给定的msgId查询消息,并指定topic | |MQConsumer接口method|-------| ------------ | |Set|fetchSubscribeMessageQueues(String topic)| 根据topic获取订阅的Queue | |void|sendMessageBack(final MessageExt msg, final int delayLevel)| 如果消息出来失败,可以发送回去延迟消费,delayLevel=DelayConf.DELAY_LEVEL | |void|sendMessageBack(final MessageExt msg, final int delayLevel, final String brokerName)| 如果消息出来失败,可以发送回去延迟消费,delayLevel=DelayConf.DELAY_LEVEL | |MQPullConsumer接口method|-------| ------------ | |long|fetchConsumeOffset(MessageQueue mq, boolean fromStore)| 查询给定消息队列的最大offset | |PullResult |pull(final MessageQueue mq, final String subExpression, final long offset,final int maxNums)| 异步拉取制定匹配的消息 | |PullResult| pull(final MessageQueue mq, final String subExpression, final long offset,final int maxNums, final long timeout)| 异步拉取制定匹配的消息 | |PullResult|pull(final MessageQueue mq, final MessageSelector selector, final long offset,final int maxNums)| 异步拉取制定匹配的消息,通过MessageSelector器来过滤消息,参考org.apache.rocketmq.common.filter.ExpressionType | |PullResult|pullBlockIfNotFound(final MessageQueue mq, final String subExpression,final long offset, final int maxNums)| 异步拉取制定匹配的消息,如果没有消息讲block住,并指定超时时间consumerPullTimeoutMillis | |void|pullBlockIfNotFound(final MessageQueue mq, final String subExpression, final long offset,final int maxNums, final PullCallback pullCallback)| 异步拉取制定匹配的消息,如果没有消息讲block住,并指定超时时间consumerPullTimeoutMillis,通过回调pullCallback来消费 | |void|updateConsumeOffset(final MessageQueue mq, final long offset)| 更新指定mq的offset | |long|fetchMessageQueuesInBalance(String topic)| 根据topic获取订阅的Queue(是balance分配后的) | |void|void sendMessageBack(MessageExt msg, int delayLevel, String brokerName, String consumerGroup)| 如果消息出来失败,可以发送回去延迟消费,delayLevel=DelayConf.DELAY_LEVEL,消息可能在同一个consumerGroup消费 | |void|shutdown()| 关闭当前消费者实例并释放相关资源 | |void|start()| 启动消费者 | ================================================ FILE: docs/cn/client/java/API_Reference_DefaultMQProducer.md ================================================ ## DefaultMQProducer --- ### 类简介 `public class DefaultMQProducer extends ClientConfig implements MQProducer` >`DefaultMQProducer`类是应用用来投递消息的入口,开箱即用,可通过无参构造方法快速创建一个生产者。主要负责消息的发送,支持同步/异步/oneway的发送方式,这些发送方式均支持批量发送。可以通过该类提供的getter/setter方法,调整发送者的参数。`DefaultMQProducer`提供了多个send方法,每个send方法略有不同,在使用前务必详细了解其意图。下面给出一个生产者示例代码,[点击查看更多示例代码](https://github.com/apache/rocketmq/blob/master/example/src/main/java/org/apache/rocketmq/example/)。 ``` java public class Producer { public static void main(String[] args) throws MQClientException { // 创建指定分组名的生产者 DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName"); // 启动生产者 producer.start(); for (int i = 0; i < 128; i++) try { // 构建消息 Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); // 同步发送 SendResult sendResult = producer.send(msg); // 打印发送结果 System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); } producer.shutdown(); } } ``` **注意**:该类是线程安全的。在配置并启动完成后可在多个线程间安全共享。 ### 字段摘要 |类型|字段名称|描述| |------|-------|-------| |DefaultMQProducerImpl|defaultMQProducerImpl|生产者的内部默认实现| |String|producerGroup|生产者分组| |String|createTopicKey|在发送消息时,自动创建服务器不存在的topic| |int|defaultTopicQueueNums|创建topic时默认的队列数量| |int|sendMsgTimeout|发送消息的超时时间| |int|compressMsgBodyOverHowmuch|压缩消息体的阈值| |int|retryTimesWhenSendFailed|同步模式下内部尝试发送消息的最大次数| |int|retryTimesWhenSendAsyncFailed|异步模式下内部尝试发送消息的最大次数| |boolean|retryAnotherBrokerWhenNotStoreOK|是否在内部发送失败时重试另一个broker| |int|maxMessageSize|消息体的最大长度| |TraceDispatcher|traceDispatcher|基于RPCHooK实现的消息轨迹插件| ### 构造方法摘要 |方法名称|方法描述| |-------|------------| |DefaultMQProducer()|由默认参数值创建一个生产者 | |DefaultMQProducer(final String producerGroup)|使用指定的分组名创建一个生产者| |DefaultMQProducer(final String producerGroup, boolean enableMsgTrace)|使用指定的分组名创建一个生产者,并设置是否开启消息轨迹| |DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic)|使用指定的分组名创建一个生产者,并设置是否开启消息轨迹及追踪topic的名称| |DefaultMQProducer(RPCHook rpcHook)|使用指定的hook创建一个生产者| |DefaultMQProducer(final String producerGroup, RPCHook rpcHook)|使用指定的分组名及自定义hook创建一个生产者| |DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic)|使用指定的分组名及自定义hook创建一个生产者,并设置是否开启消息轨迹及追踪topic的名称| ### 使用方法摘要 |返回值|方法名称|方法描述| |-------|-------|------------| |void|createTopic(String key, String newTopic, int queueNum)|在broker上创建指定的topic| |void|createTopic(String key, String newTopic, int queueNum, int topicSysFlag)|在broker上创建指定的topic| |long|earliestMsgStoreTime(MessageQueue mq)|查询最早的消息存储时间| |List|fetchPublishMessageQueues(String topic)|获取topic的消息队列| |long|maxOffset(MessageQueue mq)|查询给定消息队列的最大offset| |long|minOffset(MessageQueue mq)|查询给定消息队列的最小offset| |QueryResult|queryMessage(String topic, String key, int maxNum, long begin, long end)|按关键字查询消息| |long|searchOffset(MessageQueue mq, long timestamp)|查找指定时间的消息队列的物理offset| |SendResult|send(Collection msgs)|同步批量发送消息| |SendResult|send(Collection msgs, long timeout)|同步批量发送消息| |SendResult|send(Collection msgs, MessageQueue messageQueue)|向指定的消息队列同步批量发送消息| |SendResult|send(Collection msgs, MessageQueue messageQueue, long timeout)|向指定的消息队列同步批量发送消息,并指定超时时间| |SendResult|send(Message msg)|同步单条发送消息| |SendResult|send(Message msg, long timeout)|同步发送单条消息,并指定超时时间| |SendResult|send(Message msg, MessageQueue mq)|向指定的消息队列同步发送单条消息| |SendResult|send(Message msg, MessageQueue mq, long timeout)|向指定的消息队列同步单条发送消息,并指定超时时间| |void|send(Message msg, MessageQueue mq, SendCallback sendCallback)|向指定的消息队列异步单条发送消息,并指定回调方法| |void|send(Message msg, MessageQueue mq, SendCallback sendCallback, long timeout)|向指定的消息队列异步单条发送消息,并指定回调方法和超时时间| |SendResult|send(Message msg, MessageQueueSelector selector, Object arg)|向消息队列同步单条发送消息,并指定发送队列选择器| |SendResult|send(Message msg, MessageQueueSelector selector, Object arg, long timeout)|向消息队列同步单条发送消息,并指定发送队列选择器与超时时间| |void|send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback)|向指定的消息队列异步单条发送消息| |void|send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback, long timeout)|向指定的消息队列异步单条发送消息,并指定超时时间| |void|send(Message msg, SendCallback sendCallback)|异步发送消息| |void|send(Message msg, SendCallback sendCallback, long timeout)|异步发送消息,并指定回调方法和超时时间| |TransactionSendResult|sendMessageInTransaction(Message msg, LocalTransactionExecuter tranExecuter, final Object arg)|发送事务消息,并指定本地执行事务实例| |TransactionSendResult|sendMessageInTransaction(Message msg, Object arg)|发送事务消息| |void|sendOneway(Message msg)|单向发送消息,不等待broker响应| |void|sendOneway(Message msg, MessageQueue mq) |单向发送消息到指定队列,不等待broker响应| |void|sendOneway(Message msg, MessageQueueSelector selector, Object arg)|单向发送消息到队列选择器的选中的队列,不等待broker响应| |void|shutdown()|关闭当前生产者实例并释放相关资源| |void|start()|启动生产者| |MessageExt|viewMessage(String offsetMsgId)|根据给定的msgId查询消息| |MessageExt|public MessageExt viewMessage(String topic, String msgId)|根据给定的msgId查询消息,并指定topic| ### 字段详细信息 - [producerGroup](https://rocketmq.apache.org/docs/introduction/02concepts) `private String producerGroup` 生产者的分组名称。相同的分组名称表明生产者实例在概念上归属于同一分组。这对事务消息十分重要,如果原始生产者在事务之后崩溃,那么broker可以联系同一生产者分组的不同生产者实例来提交或回滚事务。 默认值:DEFAULT_PRODUCER 注意: 由数字、字母、下划线、横杠(-)、竖线(|)或百分号组成;不能为空;长度不能超过255。 - defaultMQProducerImpl `protected final transient DefaultMQProducerImpl defaultMQProducerImpl` 生产者的内部默认实现,在构造生产者时内部自动初始化,提供了大部分方法的内部实现。 - createTopicKey `private String createTopicKey = MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC` 在发送消息时,自动创建服务器不存在的topic,需要指定Key,该Key可用于配置发送消息所在topic的默认路由。 默认值:TBW102 建议:测试或者demo使用,生产环境下不建议打开自动创建配置。 - defaultTopicQueueNums `private volatile int defaultTopicQueueNums = 4` 创建topic时默认的队列数量。 默认值:4 - sendMsgTimeout `private int sendMsgTimeout = 3000` 发送消息时的超时时间。 默认值:3000,单位:毫秒 建议:不建议修改该值,该值应该与broker配置中的sendTimeout一致,发送超时,可临时修改该值,建议解决超时问题,提高broker集群的Tps。 - compressMsgBodyOverHowmuch `private int compressMsgBodyOverHowmuch = 1024 * 4` 压缩消息体阈值。大于4K的消息体将默认进行压缩。 默认值:1024 * 4,单位:字节 建议:可通过DefaultMQProducerImpl.setZipCompressLevel方法设置压缩率(默认为5,可选范围[0,9]);可通过DefaultMQProducerImpl.tryToCompressMessage方法测试出compressLevel与compressMsgBodyOverHowmuch最佳值。 - retryTimesWhenSendFailed `private int retryTimesWhenSendFailed = 2` 同步模式下,在返回发送失败之前,内部尝试重新发送消息的最大次数。 默认值:2,即:默认情况下一条消息最多会被投递3次。 注意:在极端情况下,这可能会导致消息的重复。 - retryTimesWhenSendAsyncFailed `private int retryTimesWhenSendAsyncFailed = 2` 异步模式下,在发送失败之前,内部尝试重新发送消息的最大次数。 默认值:2,即:默认情况下一条消息最多会被投递3次。 注意:在极端情况下,这可能会导致消息的重复。 - retryAnotherBrokerWhenNotStoreOK `private boolean retryAnotherBrokerWhenNotStoreOK = false` 同步模式下,消息保存失败时是否重试其他broker。 默认值:false 注意:此配置关闭时,非投递时产生异常情况下,会忽略retryTimesWhenSendFailed配置。 - maxMessageSize `private int maxMessageSize = 1024 * 1024 * 4` 消息体的最大大小。当消息体的字节数超过maxMessageSize就发送失败。 默认值:1024 * 1024 * 4,单位:字节 - [traceDispatcher](https://github.com/apache/rocketmq/wiki/RIP-6-Message-Trace) `private TraceDispatcher traceDispatcher = null` 在开启消息轨迹后,该类通过hook的方式把消息生产者,消息存储的broker和消费者消费消息的信息像链路一样记录下来。在构造生产者时根据构造入参enableMsgTrace来决定是否创建该对象。 ### 构造方法详细信息 1. DefaultMQProducer `public DefaultMQProducer()` 创建一个新的生产者。 2. DefaultMQProducer `DefaultMQProducer(final String producerGroup)` 使用指定的分组名创建一个生产者。 - 入参描述: 参数名 | 类型 | 是否必须 | 缺省值 |描述 ---|---|---|---|--- producerGroup | String | 是 | DEFAULT_PRODUCER | 生产者的分组名称 3. DefaultMQProducer `DefaultMQProducer(final String producerGroup, boolean enableMsgTrace)` 使用指定的分组名创建一个生产者,并设置是否开启消息轨迹。 - 入参描述: 参数名 | 类型 | 是否必须 | 缺省值 |描述 ---|---|---|---|--- producerGroup | String | 是 | DEFAULT_PRODUCER | 生产者的分组名称 enableMsgTrace | boolean | 是 | false |是否开启消息轨迹 4. DefaultMQProducer `DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic)` 使用指定的分组名创建一个生产者,并设置是否开启消息轨迹及追踪topic的名称。 - 入参描述: 参数名 | 类型 | 是否必须 | 缺省值 |描述 ---|---|---|---|--- producerGroup | String | 是 | DEFAULT_PRODUCER | 生产者的分组名称 rpcHook | RPCHook | 否 | null |每个远程命令执行后会回调rpcHook enableMsgTrace | boolean | 是 | false |是否开启消息轨迹 customizedTraceTopic | String | 否 | RMQ_SYS_TRACE_TOPIC | 消息轨迹topic的名称 5. DefaultMQProducer `DefaultMQProducer(RPCHook rpcHook)` 使用指定的hook创建一个生产者。 - 入参描述: 参数名 | 类型 | 是否必须 | 缺省值 |描述 ---|---|---|---|--- rpcHook | RPCHook | 否 | null |每个远程命令执行后会回调rpcHook 6. DefaultMQProducer `DefaultMQProducer(final String producerGroup, RPCHook rpcHook)` 使用指定的分组名及自定义hook创建一个生产者。 - 入参描述: 参数名 | 类型 | 是否必须 | 缺省值 |描述 ---|---|---|---|--- producerGroup | String | 是 | DEFAULT_PRODUCER | 生产者的分组名称 rpcHook | RPCHook | 否 | null |每个远程命令执行后会回调rpcHook 7. DefaultMQProducer `DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic)` 使用指定的分组名及自定义hook创建一个生产者,并设置是否开启消息轨迹及追踪topic的名称。 - 入参描述: 参数名 | 类型 | 是否必须 | 缺省值 |描述 ---|---|---|---|--- producerGroup | String | 是 | DEFAULT_PRODUCER | 生产者的分组名称 rpcHook | RPCHook | 否 | null |每个远程命令执行后会回调rpcHook enableMsgTrace | boolean | 是 | false |是否开启消息轨迹 customizedTraceTopic | String | 否 | RMQ_SYS_TRACE_TOPIC | 消息轨迹topic的名称 ### 使用方法详细信息 1. createTopic `public void createTopic(String key, String newTopic, int queueNum)` 在broker上创建一个topic。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 |值范围 | 说明 ---|---|---|---|---|--- key | String | 是 | | | 访问密钥。 newTopic | String | 是 | | | 新建topic的名称。由数字、字母、下划线(_)、横杠(-)、竖线(|)或百分号(%)组成;
    长度小于255;不能为TBW102或空。 queueNum | int | 是 | 0 | (0, maxIntValue] | topic的队列数量。 - 返回值描述: void - 异常描述: MQClientException - 生产者状态非Running;未找到broker等客户端异常。 2. createTopic `public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag)` 在broker上创建一个topic。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 |值范围 | 说明 ---|---|---|---|---|--- key | String | 是 | | | 访问密钥。 newTopic | String | 是 | | | 新建topic的名称。 queueNum | int | 是 | 0 | (0, maxIntValue] | topic的队列数量。 topicSysFlag | int | 是 | 0 | | 保留字段,暂未使用。 - 返回值描述: void - 异常描述: MQClientException - 生产者状态非Running;未找到broker等客户端异常。 3. earliestMsgStoreTime `public long earliestMsgStoreTime(MessageQueue mq)` 查询最早的消息存储时间。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- mq | MessageQueue | 是 | | | 要查询的消息队列 - 返回值描述: 指定队列最早的消息存储时间。单位:毫秒。 - 异常描述: MQClientException - 生产者状态非Running;没有找到broker;broker返回失败;网络异常;线程中断等客户端异常。 4. fetchPublishMessageQueues `public List fetchPublishMessageQueues(String topic)` 获取topic的消息队列。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- topic | String | 是 | | | topic名称 - 返回值描述: 传入topic下的消息队列。 - 异常描述: MQClientException - 生产者状态非Running;没有找到broker;broker返回失败;网络异常;线程中断等客户端异常。 5. maxOffset `public long maxOffset(MessageQueue mq)` 查询消息队列的最大物理偏移量。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- mq | MessageQueue | 是 | | | 要查询的消息队列 - 返回值描述: 给定消息队列的最大物理偏移量。 - 异常描述: MQClientException - 生产者状态非Running;没有找到broker;broker返回失败;网络异常;线程中断等客户端异常。 6. minOffset `public long minOffset(MessageQueue mq)` 查询给定消息队列的最小物理偏移量。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- mq | MessageQueue | 是 | | | 要查询的消息队列 - 返回值描述: 给定消息队列的最小物理偏移量。 - 异常描述: MQClientException - 生产者状态非Running;没有找到broker;broker返回失败;网络异常;线程中断等客户端异常。 7. queryMessage `public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end)` 按关键字查询消息。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- topic | String | 是 | | | topic名称 key | String | 否 | null | | 查找的关键字 maxNum | int | 是 | | | 返回消息的最大数量 begin | long | 是 | | | 开始时间戳,单位:毫秒 end | long | 是 | | | 结束时间戳,单位:毫秒 - 返回值描述: 查询到的消息集合。 - 异常描述: MQClientException - 生产者状态非Running;没有找到broker;broker返回失败;网络异常等客户端异常客户端异常。
    InterruptedException - 线程中断。 8. searchOffset `public long searchOffset(MessageQueue mq, long timestamp)` 查找指定时间的消息队列的物理偏移量。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- mq | MessageQueue | 是 | | | 要查询的消息队列。 timestamp | long | 是 | | | 指定要查询时间的时间戳。单位:毫秒。 - 返回值描述: 指定时间的消息队列的物理偏移量。 - 异常描述: MQClientException - 生产者状态非Running;没有找到broker;broker返回失败;网络异常;线程中断等客户端异常。 9. send `public SendResult send(Collection msgs)` 同步批量发送消息。在返回发送失败之前,内部尝试重新发送消息的最大次数(参见*retryTimesWhenSendFailed*属性)。未明确指定发送队列,默认采取轮询策略发送。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msgs | Collection | 是 | | | 待发送的消息集合。集合内的消息必须属同一个topic。 - 返回值描述: 批量消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 10. send `public SendResult send(Collection msgs, long timeout)` 同步批量发送消息,如果在指定的超时时间内未完成消息投递,会抛出*RemotingTooMuchRequestException*。 在返回发送失败之前,内部尝试重新发送消息的最大次数(参见*retryTimesWhenSendFailed*属性)。未明确指定发送队列,默认采取轮询策略发送。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msgs | Collection | 是 | | | 待发送的消息集合。集合内的消息必须属同一个topic。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 - 返回值描述: 批量消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 11. send `public SendResult send(Collection msgs, MessageQueue messageQueue)` 向给定队列同步批量发送消息。 注意:指定队列意味着所有消息均为同一个topic。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msgs | Collection | 是 | | | 待发送的消息集合。集合内的消息必须属同一个topic。 messageQueue | MessageQueue | 是 | | | 待投递的消息队列。指定队列意味着待投递消息均为同一个topic。 - 返回值描述: 批量消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 12. send `public SendResult send(Collection msgs, MessageQueue messageQueue, long timeout)` 向给定队列同步批量发送消息,如果在指定的超时时间内未完成消息投递,会抛出*RemotingTooMuchRequestException*。 注意:指定队列意味着所有消息均为同一个topic。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msgs | Collection | 是 | | | 待发送的消息集合。集合内的消息必须属同一个topic。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 messageQueue | MessageQueue | 是 | | | 待投递的消息队列。指定队列意味着待投递消息均为同一个topic。 - 返回值描述: 批量消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 13. send `public SendResult send(Message msg)` 以同步模式发送消息,仅当发送过程完全完成时,此方法才会返回。 在返回发送失败之前,内部尝试重新发送消息的最大次数(参见*retryTimesWhenSendFailed*属性)。未明确指定发送队列,默认采取轮询策略发送。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 - 返回值描述: 消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 14. send `public SendResult send(Message msg, long timeout)` 以同步模式发送消息,如果在指定的超时时间内未完成消息投递,会抛出*RemotingTooMuchRequestException*。仅当发送过程完全完成时,此方法才会返回。 在返回发送失败之前,内部尝试重新发送消息的最大次数(参见*retryTimesWhenSendFailed*属性)。未明确指定发送队列,默认采取轮询策略发送。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 - 返回值描述: 消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 15. send `public SendResult send(Message msg, MessageQueue mq)` 向指定的消息队列同步发送单条消息。仅当发送过程完全完成时,此方法才会返回。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 mq | MessageQueue | 是 | | | 待投递的消息队列。 - 返回值描述: 消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 16. send `public SendResult send(Message msg, MessageQueue mq, long timeout)` 向指定的消息队列同步发送单条消息,如果在指定的超时时间内未完成消息投递,会抛出*RemotingTooMuchRequestException*。仅当发送过程完全完成时,此方法才会返回。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 mq | MessageQueue | 是 | | | 待投递的消息队列。指定队列意味着待投递消息均为同一个topic。 - 返回值描述: 消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 17. send `public void send(Message msg, MessageQueue mq, SendCallback sendCallback)` 向指定的消息队列异步发送单条消息,异步发送调用后直接返回,并在在发送成功或者异常时回调`sendCallback`,所以异步发送时`sendCallback`参数不能为null,否则在回调时会抛出`NullPointerException`。 异步发送时,在成功发送前,其内部会尝试重新发送消息的最大次数(参见*retryTimesWhenSendAsyncFailed*属性)。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 mq | MessageQueue | 是 | | | 待投递的消息队列。指定队列意味着待投递消息均为同一个topic。 sendCallback | SendCallback | 是 | | | 回调接口的实现。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 18. send `public void send(Message msg, MessageQueue mq, SendCallback sendCallback, long timeout)` 向指定的消息队列异步发送单条消息,异步发送调用后直接返回,并在在发送成功或者异常时回调`sendCallback`,所以异步发送时`sendCallback`参数不能为null,否则在回调时会抛出`NullPointerException`。 若在指定时间内消息未发送成功,回调方法会收到*RemotingTooMuchRequestException*异常。 异步发送时,在成功发送前,其内部会尝试重新发送消息的最大次数(参见*retryTimesWhenSendAsyncFailed*属性)。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 mq | MessageQueue | 是 | | | 待投递的消息队列。 sendCallback | SendCallback | 是 | | | 回调接口的实现。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 19. send `public SendResult send(Message msg, MessageQueueSelector selector, Object arg)` 向通过`MessageQueueSelector`计算出的队列同步发送消息。 可以通过自实现`MessageQueueSelector`接口,将某一类消息发送至固定的队列。比如:将同一个订单的状态变更消息投递至固定的队列。 注意:此消息发送失败内部不会重试。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 selector | MessageQueueSelector | 是 | | | 队列选择器。 arg | Object | 否 | | | 供队列选择器使用的参数对象。 - 返回值描述: 消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 20. send `public SendResult send(Message msg, MessageQueueSelector selector, Object arg, long timeout)` 向通过`MessageQueueSelector`计算出的队列同步发送消息,并指定发送超时时间。 可以通过自实现`MessageQueueSelector`接口,将某一类消息发送至固定的队列。比如:将同一个订单的状态变更消息投递至固定的队列。 注意:此消息发送失败内部不会重试。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 selector | MessageQueueSelector | 是 | | | 队列选择器。 arg | Object | 否 | | | 供队列选择器使用的参数对象。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 - 返回值描述: 消息的发送结果,包含msgId,发送状态等信息。 - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    MQBrokerException - broker发生错误。
    InterruptedException - 发送线程中断。
    RemotingTooMuchRequestException - 发送超时。 21. send `public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback)` 向通过`MessageQueueSelector`计算出的队列异步发送单条消息,异步发送调用后直接返回,并在在发送成功或者异常时回调`sendCallback`,所以异步发送时`sendCallback`参数不能为null,否则在回调时会抛出`NullPointerException`。 异步发送时,在成功发送前,其内部会尝试重新发送消息的最大次数(参见*retryTimesWhenSendAsyncFailed*属性)。 可以通过自实现`MessageQueueSelector`接口,将某一类消息发送至固定的队列。比如:将同一个订单的状态变更消息投递至固定的队列。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 selector | MessageQueueSelector | 是 | | | 队列选择器。 arg | Object | 否 | | | 供队列选择器使用的参数对象。 sendCallback | SendCallback | 是 | | | 回调接口的实现。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 22. send `public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback, long timeout)` 向通过`MessageQueueSelector`计算出的队列异步发送单条消息,异步发送调用后直接返回,并在在发送成功或者异常时回调`sendCallback`,所以异步发送时`sendCallback`参数不能为null,否则在回调时会抛出`NullPointerException`。 异步发送时,在成功发送前,其内部会尝试重新发送消息的最大次数(参见*retryTimesWhenSendAsyncFailed*属性)。 可以通过自实现`MessageQueueSelector`接口,将某一类消息发送至固定的队列。比如:将同一个订单的状态变更消息投递至固定的队列。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 selector | MessageQueueSelector | 是 | | | 队列选择器。 arg | Object | 否 | | | 供队列选择器使用的参数对象。 sendCallback | SendCallback | 是 | | | 回调接口的实现。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 23. send `public void send(Message msg, SendCallback sendCallback)` 异步发送单条消息,异步发送调用后直接返回,并在在发送成功或者异常时回调`sendCallback`,所以异步发送时`sendCallback`参数不能为null,否则在回调时会抛出`NullPointerException`。 异步发送时,在成功发送前,其内部会尝试重新发送消息的最大次数(参见*retryTimesWhenSendAsyncFailed*属性)。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 sendCallback | SendCallback | 是 | | | 回调接口的实现。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 24. send `public void send(Message msg, SendCallback sendCallback, long timeout)` 异步发送单条消息,异步发送调用后直接返回,并在在发送成功或者异常时回调`sendCallback`,所以异步发送时`sendCallback`参数不能为null,否则在回调时会抛出`NullPointerException`。 异步发送时,在成功发送前,其内部会尝试重新发送消息的最大次数(参见*retryTimesWhenSendAsyncFailed*属性)。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 sendCallback | SendCallback | 是 | | | 回调接口的实现。 timeout | long | 是 | 参见*sendMsgTimeout*属性 | | 发送超时时间,单位:毫秒。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 25. sendMessageInTransaction `public TransactionSendResult sendMessageInTransaction(Message msg, LocalTransactionExecuter tranExecuter, final Object arg)` 发送事务消息。该类不做默认实现,抛出`RuntimeException`异常。参见:`TransactionMQProducer`类。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待投递的事务消息 tranExecuter | `LocalTransactionExecuter` | 是 | | | 本地事务执行器。该类*已过期*,将在5.0.0版本中移除。请勿使用该方法。 arg | Object | 是 | | | 供本地事务执行程序使用的参数对象 - 返回值描述: 事务结果,参见:`LocalTransactionState`类。 - 异常描述: RuntimeException - 永远抛出该异常。 26. sendMessageInTransaction `public TransactionSendResult sendMessageInTransaction(Message msg, final Object arg)` 发送事务消息。该类不做默认实现,抛出`RuntimeException`异常。参见:`TransactionMQProducer`类。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待投递的事务消息 arg | Object | 是 | | | 供本地事务执行程序使用的参数对象 - 返回值描述: 事务结果,参见:`LocalTransactionState`类。 - 异常描述: RuntimeException - 永远抛出该异常。 27. sendOneway `public void sendOneway(Message msg)` 以oneway形式发送消息,broker不会响应任何执行结果,和[UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol)类似。它具有最大的吞吐量但消息可能会丢失。 可在消息量大,追求高吞吐量并允许消息丢失的情况下使用该方式。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待投递的消息 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 28. sendOneway `public void sendOneway(Message msg, MessageQueue mq)` 向指定队列以oneway形式发送消息,broker不会响应任何执行结果,和[UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol)类似。它具有最大的吞吐量但消息可能会丢失。 可在消息量大,追求高吞吐量并允许消息丢失的情况下使用该方式。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待投递的消息 mq | MessageQueue | 是 | | | 待投递的消息队列 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 29. sendOneway `public void sendOneway(Message msg, MessageQueueSelector selector, Object arg)` 向通过`MessageQueueSelector`计算出的队列以oneway形式发送消息,broker不会响应任何执行结果,和[UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol)类似。它具有最大的吞吐量但消息可能会丢失。 可在消息量大,追求高吞吐量并允许消息丢失的情况下使用该方式。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msg | Message | 是 | | | 待发送的消息。 selector | MessageQueueSelector | 是 | | | 队列选择器。 arg | Object | 否 | | | 供队列选择器使用的参数对象。 - 返回值描述: void - 异常描述: MQClientException - broker不存在或未找到;namesrv地址为空;未找到topic的路由信息等客户端异常。
    RemotingException - 网络异常。
    InterruptedException - 发送线程中断。 30. shutdown `public void shutdown()` 关闭当前生产者实例并释放相关资源。 - 入参描述: 无。 - 返回值描述: void - 异常描述: 31. start `public void start()` 启动生产者实例。在发送或查询消息之前必须调用此方法。它执行了许多内部初始化,比如:检查配置、与namesrv建立连接、启动一系列心跳等定时任务等。 - 入参描述: 无。 - 返回值描述: void - 异常描述: MQClientException - 初始化过程中出现失败。 32. viewMessage `public MessageExt viewMessage(String offsetMsgId)` 根据给定的msgId查询消息。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- offsetMsgId | String | 是 | | | offsetMsgId - 返回值描述: 返回`MessageExt`,包含:topic名称,消息题,消息ID,消费次数,生产者host等信息。 - 异常描述: RemotingException - 网络层发生错误。
    MQBrokerException - broker发生错误。
    InterruptedException - 线程被中断。
    MQClientException - 生产者状态非Running;msgId非法等。 33. viewMessage `public MessageExt viewMessage(String topic, String msgId)` 根据给定的msgId查询消息,并指定topic。 - 入参描述: 参数名 | 类型 | 是否必须 | 默认值 | 值范围 | 说明 ---|---|---|---|---|--- msgId | String | 是 | | | msgId topic | String | 是 | | | topic名称 - 返回值描述: 返回`MessageExt`,包含:topic名称,消息题,消息ID,消费次数,生产者host等信息。 - 异常描述: RemotingException - 网络层发生错误。
    MQBrokerException - broker发生错误。
    InterruptedException - 线程被中断。
    MQClientException - 生产者状态非Running;msgId非法等。 ================================================ FILE: docs/cn/concept.md ================================================ # 基本概念 ---- ## 1 消息模型(Message Model) RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。 ## 2 消息生产者(Producer) 负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。 ## 3 消息消费者(Consumer) 负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费。 ## 4 主题(Topic) 表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。 ## 5 代理服务器(Broker Server) 消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。 ## 6 名字服务(Name Server) 名字服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。 ## 7 拉取式消费(Pull Consumer) Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。 ## 8 推动式消费(Push Consumer) Consumer消费的一种类型,应用不需要主动调用Consumer的拉消息方法,在底层已经封装了拉取的调用逻辑,在用户层面看来是broker把消息推送过来的,其实底层还是consumer去broker主动拉取消息。 ## 9 生产者组(Producer Group) 同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。 ## 10 消费者组(Consumer Group) 同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。 ## 11 集群消费(Clustering) 集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。 ## 12 广播消费(Broadcasting) 广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。 ## 13 普通顺序消息(Normal Ordered Message) 普通顺序消费模式下,消费者通过同一个消息队列( Topic 分区,称作 Message Queue) 收到的消息是有顺序的,不同消息队列收到的消息则可能是无顺序的。 ## 14 严格顺序消息(Strictly Ordered Message) 严格顺序消息模式下,消费者收到的所有消息均是有顺序的。 ## 15 消息(Message) 消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。 ## 16 标签(Tag) 为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。 ================================================ FILE: docs/cn/controller/deploy.md ================================================ # 部署和升级指南 ## Controller部署 若需要保证Controller具备容错能力,Controller部署需要三副本及以上(遵循Raft的多数派协议)。 > Controller若只部署单副本也能完成Broker Failover,但若该单点Controller故障,会影响切换能力,但不会影响存量集群的正常收发。 Controller部署有两种方式。一种是嵌入于NameServer进行部署,可以通过配置enableControllerInNamesrv打开(可以选择性打开,并不强制要求每一台NameServer都打开),在该模式下,NameServer本身能力仍然是无状态的,也就是内嵌模式下若NameServer挂掉多数派,只影响切换能力,不影响原来路由获取等功能。另一种是独立部署,需要单独部署Controller组件。 ### 嵌入NameServer部署 嵌入NameServer部署时只需要在NameServer的配置文件中设置enableControllerInNamesrv=true,并填上Controller的配置即可。 ``` enableControllerInNamesrv = true controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9877;n1-127.0.0.1:9878;n2-127.0.0.1:9879 controllerDLegerSelfId = n0 controllerStorePath = /home/admin/DledgerController enableElectUncleanMaster = false notifyBrokerRoleChanged = true ``` 参数解释: - enableControllerInNamesrv:Nameserver中是否开启controller,默认false。 - controllerDLegerGroup:DLedger Raft Group的名字,同一个DLedger Raft Group保持一致即可。 - controllerDLegerPeers:DLedger Group 内各节点的地址信息,同一个 Group 内的各个节点配置必须要保证一致。 - controllerDLegerSelfId:节点 id,必须属于 controllerDLegerPeers 中的一个;同 Group 内各个节点要唯一。 - controllerStorePath:controller日志存储位置。controller是有状态的,controller重启或宕机需要依靠日志来恢复数据,该目录非常重要,不可以轻易删除。 - enableElectUncleanMaster:是否可以从SyncStateSet以外选举Master,若为true,可能会选取数据落后的副本作为Master而丢失消息,默认为false。 - notifyBrokerRoleChanged:当broker副本组上角色发生变化时是否主动通知,默认为true。 - scanNotActiveBrokerInterval:扫描 Broker是否存活的时间间隔。 - 其他一些参数可以参考ControllerConfig代码。 > 5.2.0 Controller 开始支持 jRaft 内核启动,不支持 DLedger 内核到 jRaft 内核原地升级 ``` controllerType = jRaft jRaftGroupId = jRaft-Controller jRaftServerId = localhost:9880 jRaftInitConf = localhost:9880,localhost:9881,localhost:9882 jRaftControllerRPCAddr = localhost:9770,localhost:9771,localhost:9772 jRaftSnapshotIntervalSecs = 3600 ``` jRaft 版本相关参数 - controllerType:controllerType=jRaft的时候内核启动使用jRaft,默认为DLedger。 - jRaftGroupId:jRaft Group的名字,同一个jRaft Group保持一致即可。 - jRaftServerId:标志自己节点的ServerId,必须出现在 jRaftInitConf 中。 - jRaftInitConf:jRaft Group 内部通信各节点的地址信息,用逗号分隔,是 jRaft 内部通信来做选举和复制所用的地址。 - jRaftControllerRPCAddr:Controller 外部通信的各节点的地址信息,用逗号分隔,比如 Controller 与 Broker 通信会使用该地址。 - jRaftSnapshotIntervalSecs:Raft Snapshot 持久化时间间隔。 参数设置完成后,指定配置文件启动Nameserver即可。 ### 独立部署 独立部署执行以下脚本即可 ```shell sh bin/mqcontroller -c controller.conf ``` mqcontroller脚本在distribution/bin/mqcontroller,配置参数与内嵌模式相同。 ## Broker Controller模式部署 Broker启动方法与之前相同,增加以下参数 - enableControllerMode:Broker controller模式的总开关,只有该值为true,controller模式才会打开。默认为false。 - controllerAddr:controller的地址,两种方式填写。 - 直接填写多个Controller IP地址,多个controller中间用分号隔开,例如`controllerAddr = 127.0.0.1:9877;127.0.0.1:9878;127.0.0.1:9879`。注意由于Broker需要向所有controller发送心跳,因此请填上所有的controller地址。 - 填写域名,然后设置fetchControllerAddrByDnsLookup为true,则Broker去自动解析域名后面的多个真实controller地址。 - fetchControllerAddrByDnsLookup:controllerAddr填写域名时,如果设置该参数为true,会自动获取所有controller的地址。默认为false。 - controllerHeartBeatTimeoutMills:Broker和controller之间心跳超时时间,心跳超过该时间判断Broker不在线。 - syncBrokerMetadataPeriod:向controller同步Broker副本信息的时间间隔。默认5000(5s)。 - checkSyncStateSetPeriod:检查SyncStateSet的时间间隔,检查SyncStateSet可能会shrink SyncState。默认5000(5s)。 - syncControllerMetadataPeriod:同步controller元数据的时间间隔,主要是获取active controller的地址。默认10000(10s)。 - haMaxTimeSlaveNotCatchup:表示slave没有跟上Master的最大时间间隔,若在SyncStateSet中的slave超过该时间间隔会将其从SyncStateSet移除。默认为15000(15s)。 - storePathEpochFile:存储epoch文件的位置。epoch文件非常重要,不可以随意删除。默认在store目录下。 - allAckInSyncStateSet:若该值为true,则一条消息需要复制到SyncStateSet中的每一个副本才会向客户端返回成功,可以保证消息不丢失。默认为false。 - syncFromLastFile:若slave是空盘启动,是否从最后一个文件进行复制。默认为false。 - asyncLearner:若该值为true,则该副本不会进入SyncStateSet,也就是不会被选举成Master,而是一直作为一个learner副本进行异步复制。默认为false。 - inSyncReplicas:需保持同步的副本组数量,默认为1,allAckInSyncStateSet=true时该参数无效。 - minInSyncReplicas:最小需保持同步的副本组数量,若SyncStateSet中副本个数小于minInSyncReplicas则putMessage直接返回PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH,默认为1。 在Controller模式下,Broker配置必须设置enableControllerMode=true,并填写controllerAddr。 ### 重要参数解析 1.写入副本参数 其中inSyncReplicas、minInSyncReplicas等参数在普通Master-Salve部署、SlaveActingMaster模式、自动主从切换架构有重叠和不同含义,具体区别如下 | | inSyncReplicas | minInSyncReplicas | enableAutoInSyncReplicas | allAckInSyncStateSet | haMaxGapNotInSync | haMaxTimeSlaveNotCatchup | |----------------------|---------------------------------------------------------------------|--------------------------------------------------------------------------|---------------------------------------------|---------------------------------------------------------------|---------------------------------------|---------------------------------------------------| | 普通Master-Salve部署 | 同步复制下需要ACK的副本数,异步复制无效 | 无效 | 无效 | 无效 | 无效 | 无效 | | 开启SlaveActingMaster (slaveActingMaster=true) | 不自动降级情况下同步复制下需要ACK的副本数 | 自动降级后,需要ACK最小副本数 | 是否开启自动降级,自动降级后,ACK最小副本数降级到minInSyncReplicas | 无效 | 判断降级依据:Slave与Master Commitlog差距值,单位字节 | 无效 | | 自动主从切换架构(enableControllerMode=true) | 不开启allAckInSyncStateSet下,同步复制下需要ACK的副本数,开启allAckInSyncStateSet后该值无效 | SyncStateSet可以降低到最小的副本数,如果SyncStateSet中副本个数小于minInSyncReplicas则直接返回副本数不足 | 无效 | 若该值为true,则一条消息需要复制到SyncStateSet中的每一个副本才会向客户端返回成功,该参数可以保证消息不丢失 | 无效 | SyncStateSet收缩时,Slave最小未跟上Master的时间差,详见[RIP-44](https://shimo.im/docs/N2A1Mz9QZltQZoAD) | 总结来说: - 普通Master-Slave下无自动降级能力,除了inSyncReplicas其他参数均无效,inSyncReplicas表示同步复制下需要ACK的副本数。 - slaveActingMaster模式下开启enableAutoInSyncReplicas有降级能力,最小可降级到minInSyncReplicas副本数,降级判断依据是主备Commitlog高度差(haMaxGapNotInSync)以及副本存活情况,参考[slaveActingMaster模式自动降级](../QuorumACK.md)。 > SlaveActingMaster为其他高可用部署方式,该模式下如果不使用可不参考 - 自动主从切换(Controller模式)依赖SyncStateSet的收缩进行自动降级,SyncStateSet副本数最小收缩到minInSyncReplicas仍能正常工作,小于minInSyncReplicas直接返回副本数不足,收缩依据之一是Slave跟上的时间间隔(haMaxTimeSlaveNotCatchup)而非Commitlog高度。 - 自动主从切换(Controller模式)正常情况是要求保证不丢消息的,只需设置allAckInSyncStateSet = true 即可,不需要考虑inSyncReplicas参数(该参数无效),如果副本较多、距离较远对延迟有要求,可以参考设置部分副本设置为asyncLearner。 2.SyncStateSet收缩检查配置 checkSyncStateSetPeriod 参数决定定时检查SyncStateSet是否需要收缩的时间间隔 haMaxTimeSlaveNotCatchup 参数决定备跟不上主的时间 当allAckInSyncState = true时(保证不丢消息), - haMaxTimeSlaveNotCatchup 值越小,对SyncStateSet收缩越敏感,比如主备之间网络抖动就可能导致SyncStateSet收缩,造成不必要的集群抖动。 - haMaxTimeSlaveNotCatchup 值越大,对SyncStateSet收缩虽然不敏感,但是可能加大SyncStateSet收缩时的RTO时间。该RTO时间可以按照 checkSyncStateSetPeriod/2 + haMaxTimeSlaveNotCatchup 估算。 3.消息可靠性配置 保证 allAckInSyncStateSet = true 以及 enableElectUncleanMaster = false 4.延迟 当 allAckInSyncStateSet = true 后,一条消息要复制到SyncStateSet所有副本才能确认返回,假设SyncStateSet有3副本,其中1副本距离较远,则会影响到消息延迟。可以设置延迟最高距离最远的副本为asyncLearner,该副本不会进入SyncStateSet,只会进行异步复制,该副本作为冗余副本。 ## 兼容性 该模式未对任何客户端层面 API 进行新增或修改,不存在客户端的兼容性问题。 Nameserver本身能力未做任何修改,Nameserver不存在兼容性问题。如开启enableControllerInNamesrv且controller参数配置正确,则开启controller功能。 Broker若设置enableControllerMode=false,则仍然以之前方式运行。若设置enableControllerMode=true,则需要部署controller且参数配置正确才能正常运行。 具体行为如下表所示: | | 旧版Nameserver | 旧版Nameserver+独立部署Controller | 新版Nameserver开启controller功能 | 新版Nameserver关闭controller功能 | |-------------------------|--------------|-----------------------------|----------------------------|----------------------------| | 旧版Broker | 正常运行,无法切换 | 正常运行,无法切换 | 正常运行,无法切换 | 正常运行,无法切换 | | 新版Broker开启Controller模式 | 无法正常上线 | 正常运行,可以切换 | 正常运行,可以切换 | 无法正常上线 | | 新版Broker不开启Controller模式 | 正常运行,无法切换 | 正常运行,无法切换 |正常运行,无法切换 | 正常运行,无法切换 | ## 升级注意事项 从上述兼容性表述可以看出,NameServer正常升级即可,无兼容性问题。在不想升级Nameserver情况,可以独立部署Controller组件来获得切换能力。 针对Broker升级,分为两种情况: (1)Master-Slave部署升级成Controller切换架构 可以带数据进行原地升级,对于每组Broker,停机主、备Broker,**保证主、备的Commitlog对齐**(可以在升级前禁写该组Broker一段时间,或则通过拷贝方式保证一致),升级包后重新启动即可。 > 若主备commitlog不对齐,需要保证主上线以后再上线备,否则可能会因为数据截断而丢失消息。 (2)原DLedger模式升级到Controller切换架构 由于原DLedger模式消息数据格式与Master-Slave下数据格式存在区别,不提供带数据原地升级的路径。在部署多组Broker的情况下,可以禁写某一组Broker一段时间(只要确认存量消息被全部消费即可,比如根据消息的保存时间来决定),然后清空store目录下除config/topics.json、subscriptionGroup.json下(保留topic和订阅关系的元数据)的其他文件后,进行空盘升级。 ### 持久化BrokerID版本的升级注意事项 目前版本支持采用了新的持久化BrokerID版本,详情可以参考[该文档](persistent_unique_broker_id.md),从该版本前的5.x升级到当前版本需要注意如下事项。 4.x版本升级遵守上述正常流程即可。 5.x非持久化BrokerID版本升级到持久化BrokerID版本按照如下流程: **升级Controller** 1. 将旧版本Controller组停机。 2. 清除Controller数据,即默认在`~/DLedgerController`下的数据文件。 3. 上线新版Controller组。 > 在上述升级Controller流程中,Broker仍可正常运行,但无法切换。 **升级Broker** 1. 将Broker从节点停机。 2. 将Broker主节点停机。 3. 将所有的Broker的Epoch文件删除,即默认为`~/store/epochFileCheckpoint`和`~/store/epochFileCheckpoint.bak`。 4. 将原先的主Broker先上线,等待该Broker当选为master。(可使用`admin`命令的`getSyncStateSet`来观察) 5. 将原来的从Broker全部上线。 > 建议停机时先停从再停主,上线时先上原先的主再上原先的从,这样可以保证原来的主备关系。 > 若需要改变升级前后主备关系,则需要停机时保证主、备的CommitLog对齐,否则可能导致数据被截断而丢失。 ================================================ FILE: docs/cn/controller/design.md ================================================ # 背景 当前 RocketMQ Raft 模式主要是利用 DLedger Commitlog 替换原来的 Commitlog,使 Commitlog 拥有选举复制能力,但这也造成了一些问题: - Raft 模式下,Broker组内副本数必须是三副本及以上,副本的ACK也必须遵循多数派协议。 - RocketMQ 存在两套 HA 复制流程,且 Raft 模式下的复制无法利用 RocketMQ 原生的存储能力。 因此我们希望利用 DLedger 实现一个基于 Raft 的一致性模块(DLedger Controller),并当作一个可选的选主组件,支持独立部署,也可以嵌入在 Nameserver 中,Broker 通过与 Controller 的交互完成 Master 的选举,从而解决上述问题,我们将该新模式称为 Controller 模式。 # 架构 ## 核心思想 ![架构图](../image/controller/controller_design_1.png) 如图是 Controller 模式的核心架构,介绍如下: - DledgerController:利⽤ DLedger ,构建⼀个保证元数据强⼀致性的 DLedger Controller 控制器,利⽤ Raft 选举会选出⼀个 Active DLedger Controller 作为主控制器,DLedger Controller 可以内嵌在 Nameserver中,也可以独立的部署。其主要作用是,用来存储和管理 Broker 的 SyncStateSet 列表,并在某个 Broker 的 Master Broker 下线或⽹络隔离时,主动发出调度指令来切换 Broker 的 Master。 - SyncStateSet:主要表示⼀个 broker 副本组中跟上 Master 的 Slave 副本加上 Master 的集合。主要判断标准是 Master 和 Slave 之间的差距。当 Master 下线时,我们会从 SyncStateSet 列表中选出新的 Master。 SyncStateSet 列表的变更主要由 Master Broker 发起。Master通过定时任务判断和同步过程中完成 SyncStateSet 的Shrink 和 Expand,并向选举组件 Controller 发起 Alter SyncStateSet 请求。 - AutoSwitchHAService:一个新的 HAService,在 DefaultHAService 的基础上,支持 BrokerRole 的切换,支持 Master 和 Slave 之间互相转换 (在 Controller 的控制下) 。此外,该 HAService 统一了日志复制流程,会在 HA HandShake 阶段进行日志的截断。 - ReplicasManager:作为一个中间组件,起到承上启下的作用。对上,可以定期同步来自 Controller 的控制指令,对下,可以定期监控 HAService 的状态,并在合适的时间修改 SyncStateSet。ReplicasManager 会定期同步 Controller 中关于该 Broker 的元数据,当 Controller 选举出一个新的 Master 的时候,ReplicasManager 能够感知到元数据的变化,并进行 BrokerRole 的切换。 ## DLedgerController 核心设计 ![image-20220605213143645](../image/controller/quick-start/controller.png) 如图是 DledgerController 的核心设计: - DLedgerController 可以内嵌在 Namesrv 中,也可以独立的部署。 - Active DLedgerController 是 DLedger 选举出来的 Leader,其会接受来自客户端的事件请求,并通过 DLedger 发起共识,最后应用到内存元数据状态机中。 - Not Active DLedgerController,也即 Follower 角色,其会通过 DLedger 复制来自 Active DLedgerController 的事件日志,然后直接运用到状态机中。 ## 日志复制 ### 基本概念与流程 为了统一日志复制流程,区分每一任 Master 的日志复制边界,方便日志截断,引入了 MasterEpoch 的概念,代表当前 Master 的任期号 (类似 Raft Term 的含义)。 对于每一任 Master,其都有 MasterEpoch 与 StartOffset,分别代表该 Master 的任期号与起始日志位移。 需要注意的是,MasterEpoch 是由 Controller 决定的,且其是单调递增的。 此外,我们还引入了 EpochFile,用于存放 序列。 **当⼀个 Broker 成为 Master,其会:** - 将 Commitlog 截断到最后⼀条消息的边界。 - 同时最新将 持久化到 EpochFile,startOffset 也即当前 CommitLog 的 MaxPhyOffset 。 - 然后 HAService 监听连接,创建 HAConnection,配合 Slave 完成流程交互。 **当一个 Broker 成为 Slave,其会:** Ready 阶段: - 将Commitlog截断到最后⼀条消息的边界。 - 与Master建⽴连接。 Handshake 阶段: - 进⾏⽇志截断,这⾥关键在于 Slave 利⽤本地的 epoch 与 startOffset 和 Master 对⽐,找到⽇志截断点,进⾏⽇志截断。 Transfer 阶段: - 从 Master 同步日志。 ### 截断算法 具体的日志截断算法流程如下: - 在 HandShake 阶段, Slave 会从 Master 处获取 Master 的 EpochCache 。 - Slave ⽐较获取到的 Master EpochCahce ,从后往前依次和本地进行比对,如果二者的 Epoch 与 StartOffset 相等, 则该 Epoch 有效,截断位点为两者中较⼩的 Endoffset,截断后修正⾃⼰的 信息,进⼊Transfer 阶 段;如果不相等,对比 Slave 前⼀个epoch,直到找到截断位点。 ```java slave:TreeMap> epochMap; Iterator iterator = epochMap.entrySet().iterator(); truncateOffset = -1; //Epoch为从⼤到⼩排序 while (iterator.hasNext()) { Map.Entry> curEntry = iterator.next(); Pair masterOffset= findMasterOffsetByEpoch(curEntry.getKey()); if(masterOffset != null && curEntry.getKey().getObejct1() == masterOffset.getObejct1()) { truncateOffset = Math.min(curEntry.getKey().getObejct2(), masterOffset.getObejct2()); break; } } ``` ### 复制流程 由于 Ha 是基于流进行日志复制的,我们无法分清日志的边界 (也即传输的一批日志可能横跨多个 MasterEpoch),Slave 无法感知到 MasterEpoch 的变化,也就无法及时修改 EpochFile。 因此,我们做了如下改进: Master 传输⽇志时,保证⼀次发送的⼀个 batch 是同⼀个 epoch 中的,⽽不能横跨多个 epoch。可以在WriteSocketService 中新增两个变量: - currentTransferEpoch:代表当前 WriteSocketService.nextTransferFromWhere 对应在哪个 epoch 中 - currentTransferEpochEndOffset: 对应 currentTransferEpoch 的 end offset.。如果 currentTransferEpoch == MaxEpoch,则 currentTransferEpochEndOffset= -1,表示没有界限。 WriteSocketService 传输下⼀批⽇志时 (假设这⼀批⽇志总⼤⼩为 size),如果发现 nextTransferFromWhere + size > currentTransferEpochEndOffset,则将 selectMappedBufferResult limit ⾄ currentTransferEpochEndOffset。 最后,修改 currentTransferEpoch 和 currentTransferEpochEndOffset ⾄下⼀个 epoch。 相应的, Slave 接受⽇志时,如果从 header 中发现 epoch 变化,则记录到本地 epoch⽂件中。 ### 复制协议 根据上文我们可以知道,AutoSwitchHaService 对日志复制划分为多个阶段,下面介绍是该 HaService 的协议。 #### Handshake 阶段 1.AutoSwitchHaClient (Slave) 会向 Master 发送 HandShake 包,如下: ![示意图](../image/controller/controller_design_3.png) `current state(4byte) + Two flags(4byte) + slaveBrokerId(8byte)` - Current state 代表当前的 HAConnectionState,也即 HANDSHAKE。 - Two flags 是两个状态标志位,其中,isSyncFromLastFile 代表是否要从 Master 的最后一个文件开始复制,isAsyncLearner 代表该 Slave 是否是异步复制,并以 Learner 的形式接入 Master。 - slaveBrokerId 代表了该 Slave 的 brokerId,用于后续加入 SyncStateSet 。 2.AutoSwitchHaConnection (Master) 会向 Slave 回送 HandShake 包,如下: ![示意图](../image/controller/controller_design_4.png) `current state(4byte) + body size(4byte) + offset(8byte) + epoch(4byte) + body` - Current state 代表当前的 HAConnectionState,也即 HANDSHAKE。 - Body size 代表了 body 的长度。 - Offset 代表 Master 端日志的最大偏移量。 - Epoch 代表了 Master 的 Epoch 。 - Body 中传输的是 Master 端的 EpochEntryList 。 Slave 收到 Master 回送的包后,就会在本地进行上文阐述的日志截断流程。 #### Transfer 阶段 1.AutoSwitchHaConnection (Master) 会不断的往 Slave 发送日志包,如下: ![示意图](../image/controller/controller_design_5.png) `current state(4byte) + body size(4byte) + offset(8byte) + epoch(4byte) + epochStartOffset(8byte) + additionalInfo(confirmOffset) (8byte)+ body` - Current state:代表当前的 HAConnectionState,也即 Transfer 。 - Body size:代表了 body 的长度。 - Offset:当前这一批次的日志的起始偏移量。 - Epoch:代表当前这一批次日志所属的 MasterEpoch。 - epochStartOffset:代表当前这一批次日志的 MasterEpoch 对应的 StartOffset。 - confirmOffset:代表在 SyncStateSet 中的副本的最小偏移量。 - Body:日志。 2.AutoSwitchHaClient (Slave) 会向 Master 发送 ACK 包: ![示意图](../image/controller/controller_design_6.png) ` current state(4byte) + maxOffset(8byte)` - Current state:代表当前的 HAConnectionState,也即 Transfer 。 - MaxOffset:代表当前 Slave 的最大日志偏移量。 ## Master 选举 ### 基本流程 ELectMaster 主要是在某 Broker 副本组的 Master 下线或不可访问时,重新从 SyncStateSet 列表⾥⾯选出⼀个新的 Master,该事件由 Controller ⾃身或者通过运维命令`electMaster` 发起Master选举。 无论 Controller 是独立部署,还是嵌入在 Namesrv 中,其都会监听每个 Broker 的连接通道,如果某个 Broker channel inActive 了,就会判断该 Broker 是否为 Master,如果是,则会触发选主的流程。 选举 Master 的⽅式⽐较简单,我们只需要在该组 Broker 所对应的 SyncStateSet 列表中,挑选⼀个出来成为新的 Master 即可,并通过 DLedger 共识后应⽤到内存元数据,最后将结果通知对应的Broker副本组。 ### SyncStateSet 变更 SyncStateSet 是选主的重要依据,SyncStateSet 列表的变更主要由 Master Broker 发起。Master通过定时任务判断和同步过程中完成 SyncStateSet 的Shrink 和 Expand,并向选举组件 Controller 发起 Alter SyncStateSet 请求。 #### Shrink Shrink SyncStateSet ,指把 SyncStateSet 副本集合中那些与Master差距过⼤的副本移除,判断依据如下: - 增加 haMaxTimeSlaveNotCatchUp 参数 。 - HaConnection 中记录 Slave 上⼀次跟上 Master 的时间戳 lastCaughtUpTimeMs,该时间戳含义是:每次Master 向 Slave 发送数据(transferData)时记录⾃⼰当前的 MaxOffset 为 lastMasterMaxOffset 以及当前时间戳 lastTransferTimeMs。 - ReadSocketService 接收到 slaveAckOffset 时若 slaveAckOffset >= lastMasterMaxOffset 则将lastCaughtUpTimeMs 更新为 lastTransferTimeMs。 - Master 端通过定时任务扫描每一个 HaConnection,如果 (cur_time - connection.lastCaughtUpTimeMs) > haMaxTimeSlaveNotCatchUp,则该 Slave 是 Out-of-sync 的。 - 如果检测到 Slave out of sync ,master 会立刻向 Controller 上报SyncStateSet,从而 Shrink SyncStateSet。 #### Expand 如果⼀个 Slave 副本追赶上了 Master,Master 需要及时向Controller Alter SyncStateSet 。加⼊SyncStateSet 的条件是 slaveAckOffset >= ConfirmOffset(当前 SyncStateSet 中所有副本的 MaxOffset 的最⼩值)。 ## 参考资料 [RIP-44原文](https://github.com/apache/rocketmq/wiki/RIP-44-Support-DLedger-Controller) ================================================ FILE: docs/cn/controller/persistent_unique_broker_id.md ================================================ # 持久化的唯一BrokerId ## 现阶段问题 在 RocketMQ 5.0.0 和 5.1.0 版本中,采用`BrokerAddress`作为Broker在Controller模式下的唯一标识。导致如下情景出现问题: - 在容器或者K8s环境下,每次Broker的重启或升级都可能会导致IP发生变化,导致之前的`BrokerAddress`留下的记录没办法和重启后的Broker联系起来,比如说`ReplicaInfo`, `SyncStateSet`等数据。 ## 改进方案 在Controller侧采用`BrokerName:BrokerId`作为唯一标识,不再以`BrokerAddress`作为唯一标识。并且需要对`BrokerId`进行持久化存储,由于`ClusterName`和`BrokerName`都是启动的时候在配置文件中配置好的,所以只需要处理`BrokerId`的分配和持久化问题。 Broker第一次上线的时候,只有配置文件中配置的`ClusterName`和`BrokerName`,以及自身的`BrokerAddress`。那么我们需要和`Controller`协商出一个在整个集群生命周期中都唯一确定的标识:`BrokerId`,该`BrokerId`从1开始分配。当某一个Broker被选为Master的时候,在向Name Server中重新注册时,将更改为`BrokerId`为0 (兼容之前逻辑 brokerId为0代表着Broker是Master身份)。 ### 上线流程 ![register process](../image/controller/persistent_unique_broker_id/register_process.png) #### 1. GetNextBrokerId Request 这时候发起一个`GetNextBrokerId`的请求到Controller,为了拿到当前的下一个待分配的`BrokerId`(从1开始分配)。 #### 1.1 ReadFromDLedger 此时Controller接收到请求,然后走DLedger去获取到状态机的`NextBrokerId`数据。 #### 2. GetNextBrokerId Response Controller将`NextBrokerId`返回给Broker。 #### 2.1 CreateTempMetaFile Broker拿到`NextBrokerId`之后,创建一个临时文件`.broker.meta.temp`,里面记录了`NextBrokerId`(也就是期望应用的`BrokerId`),以及自己生成一个`RegisterCode`(用于之后的身份校验)也持久化到临时文件中。 #### 3. ApplyBrokerId Request Broker携带着当前自己的基本数据(`ClusterName`、`BrokerName`和`BrokerAddress`)以及此时期望应用的`BrokerId`和`RegisterCode`,发送一个`ApplyBrokerId`的请求到Controller。 #### 3.1 CASApplyBrokerId Controller通过DLedger写入该事件,当该事件(日志)被应用到状态机的时候,判断此时是否可以应用该`BrokerId`(若`BrokerId`已被分配并且也不是分配给该Broker时则失败)。并且此时会记录下来该`BrokerId`和`RegisterCode`之间的关系。 #### 4. ApplyBrokerId Response 若上一步成功应用了该`BrokerId`,此时则返回成功给Broker,若失败则返回当前的`NextBrokerId`。 #### 4.1 CreateMetaFileFromTemp 若上一步成功的应用了该`BrokerId`,那么此时可以视为Broker侧成功的分配了该BrokerId,那么此时我们也需要彻底将这个BrokerId的信息持久化,那么我们就可以直接原子删除`.broker.meta.temp`并创建`.broker.meta`。删除和创建这两步需为原子操作。 > 经过上述流程,第一次上线的broker和controller成功协商出一个双方都认同的brokeId并持久化保存起来。 #### 5. RegisterBrokerToController Request 之前的步骤已经正确协商出了`BrokerId`,但是这时候有可能Controller侧保存的`BrokerAddress`是上次Broker上线的时候的`BrokerAddress`,所以现在需要更新一下`BrokerAddress`,发送一个`RegisterBrokerToController` 请求并带上当前的`BrokerAddress`。 #### 5.1 UpdateBrokerAddress Controller比对当前该Broker在Controller状态机中保存的`BrokerAddress`,若和请求中携带的不一致则更新为请求中的`BrokerAddress`。 #### 6. RegisterBrokerToController Response Controller侧在更新完`BrokerAddress`之后可携带着当前该Broker所在的`Broker-set`的主从信息返回,用于通知Broker进行相应的身份转变。 ### 注册状态轮转 ![register state transfer](../image/controller/persistent_unique_broker_id/register_state_transfer.png) ### 故障容错 > 如果在正常上线流程中出现了各种情况的宕机,则以下流程保证正确的`BrokerId`分配 #### 正常重启后的节点上线 若是正常重启,那么则已经在双方协商出唯一的`BrokerId`,并且本地也在`broker.meta`中有该`BrokerId`的数据,那么就该注册流程不需要进行,直接继续后面的流程即可。即从`RegisterBrokerToController`处继续上线即可。 ![restart normally](../image/controller/persistent_unique_broker_id/normal_restart.png) #### CreateTempMetaFile失败 ![fail at creating temp metadata file](../image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png) 如果是上图中的流程失败的话,那么Broker重启后,Controller侧的状态机本身也没有分配任何`BrokerId`。Broker自身也没有任何数据被保存。因此直接重新按照上述流程从头开始走即可。 #### CreateTempMetaFile成功,ApplyBrokerId未成功 若是Controller侧已经认为本次`ApplyBrokerId`请求不对(请求去分配一个已被分配的`BrokerId`并且该 `RegisterCode`不相等),并且此时返回当前的`NextBrokerId`给Broker,那么此时Broker直接删除`.broker.meta.temp`文件,接下来回到第2步,重新开始该流程以及后续流程。 ![fail at applying broker id](../image/controller/persistent_unique_broker_id/fail_apply_broker_id.png) #### ApplyBrokerId成功,CreateMetaFileFromTemp未成功 上述情况可以出现在`ApplyResult`丢失、CAS删除并创建`broker.meta`失败,这俩流程中。 那么重启后,Controller侧是已经认为我们`ApplyBrokerId`流程是成功的了,而且也已经在状态机中修改了BrokerId的分配数据,那么我们这时候重新直接开始步骤3,也就是发送`ApplyBrokerId`请求的这一步。 ![fail at create metadata file](../image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png) 因为我们有`.broker.meta.temp`文件,可以从中拿到我们之前成功在Controller侧应用的`BrokerId`和`RegisterCode`,那么直接发送给Controller,如果Controller中存在该`BrokerId`并且`RegisterCode`和请求中的`RegisterCode`相等,那么视为成功。 ### 正确上线后使用BrokerId作为唯一标识 当正确上线之后,之后Broker的请求和状态记录都以`BrokerId`作为唯一标识。心跳等数据的记录都以`BrokerId`为标识。 同时Controller侧也会记录当前该`BrokerId`的`BrokerAddress`,在主从切换等时候用于通知Broker状态变化。 > 默认持久化ID的文件在~/store/brokerIdentity,也可以设置storePathBrokerIdentity参数来决定存储路径。在自动主备切换模式下,不要随意删除该文件,否则该 Broker 会被当作新 Broker 上线。 ## 升级方案 4.x 版本升级遵守 5.0 升级文档流程即可。 5.0.0 和 5.1.0 非持久化BrokerId版本升级到 5.1.1 及以上持久化BrokerId版本按照如下流程: ### 升级Controller 1. 将旧版本Controller组停机。 2. 清除Controller数据,即默认在`~/DLedgerController`下的数据文件。 3. 上线新版Controller组。 > 在上述升级Controller流程中,Broker仍可正常运行,但无法切换。 ### 升级Broker 1. 将Broker从节点停机。 2. 将Broker主节点停机。 3. 将所有的Broker的Epoch文件删除,即默认为`~/store/epochFileCheckpoint`和`~/store/epochFileCheckpoint.bak`。 4. 将原先的主Broker先上线,等待该Broker当选为master。(可使用`admin`命令的`getSyncStateSet`来观察) 5. 将原来的从Broker全部上线。 > 建议停机时先停从再停主,上线时先上原先的主再上原先的从,这样可以保证原来的主备关系。 若需要改变升级前后主备关系,则需要停机时保证主、备的CommitLog对齐,否则可能导致数据被截断而丢失。 ### 兼容性 | | 5.1.0 及以下版本 Controller | 5.1.1 及以上版本 Controller | |--------------------|------------------------|------------------------------------| | 5.1.0 及以下版本 Broker | 正常运行,可切换 | 若已主备确定则可正常运行,不可切换。若broker重新启动则无法上线 | | 5.1.1 及以上版本 Broker | 无法正常上线 | 正常运行,可切换 | ================================================ FILE: docs/cn/controller/quick_start.md ================================================ # 自动主从切换快速开始 ## 前言 ![架构图](../image/controller/controller_design_2.png) 该文档主要介绍如何快速构建自动主从切换的 RocketMQ 集群,其架构如上图所示,主要增加支持自动主从切换的Controller组件,其可以独立部署也可以内嵌在NameServer中。 详细设计思路请参考 [设计思想](design.md). 详细的新集群部署和旧集群升级指南请参考 [部署指南](deploy.md)。 ## 编译 RocketMQ 源码 ```shell $ git clone https://github.com/apache/rocketmq.git $ cd rocketmq $ mvn -Prelease-all -DskipTests clean install -U ``` ## 快速部署 在构建成功后 ```shell #{rocketmq-version} replace with rocketmq actual version. example: 5.0.0-SNAPSHOT $ cd distribution/target/rocketmq-{rocketmq-version}/rocketmq-{rocketmq-version}/ $ sh bin/controller/fast-try.sh start ``` 如果上面的步骤执行成功,可以通过运维命令查看Controller状态。 ```shell $ sh bin/mqadmin getControllerMetaData -a localhost:9878 ``` -a代表集群中任意一个Controller的地址 至此,启动成功,现在可以向集群收发消息,并进行切换测试了。 如果需要关闭快速集群,可以执行: ```shell $ sh bin/controller/fast-try.sh stop ``` 对于快速部署,默认配置在 conf/controller/quick-start里面,默认的存储路径在 /tmp/rmqstore,且会开启一个 Controller (嵌入在 Namesrv) 和两个 Broker。 ### 查看 SyncStateSet 可以通过运维工具查看 SyncStateSet: ```shell $ sh bin/mqadmin getSyncStateSet -a localhost:9878 -b broker-a ``` -a 代表的是任意一个 Controller 的地址 如果顺利的话,可以看到以下内容: ![image-20220605205259913](../image/controller/quick-start/syncstateset.png) ### 查看 BrokerEpoch 可以通过运维工具查看 BrokerEpochEntry: ```shell $ sh bin/mqadmin getBrokerEpoch -n localhost:9876 -b broker-a ``` -n 代表的是任意一个 Namesrv 的地址 如果顺利的话,可以看到以下内容: ![image-20220605205247476](../image/controller/quick-start/epoch.png) ## 切换 部署成功后,现在尝试进行 Master 切换。 首先,kill 掉原 Master 的进程,在上文的例子中,就是使用端口 30911 的进程: ```shell #查找端口: $ ps -ef|grep java|grep BrokerStartup|grep ./conf/controller/quick-start/broker-n0.conf|grep -v grep|awk '{print $2}' #杀掉 master: $ kill -9 PID ``` 接着,用 SyncStateSet admin 脚本查看: ```shell $ sh bin/mqadmin getSyncStateSet -a localhost:9878 -b broker-a ``` 可以发现 Master 已经发生了切换。 ![image-20220605211244128](../image/controller/quick-start/changemaster.png) ## Controller内嵌Namesvr集群部署 Controller以插件方式内嵌Namesvr集群(3个Node组成)部署,快速启动: ```shell $ sh bin/controller/fast-try-namesrv-plugin.sh start ``` 或者通过命令单独启动: ```shell $ nohup bin/mqnamesrv -c ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf & $ nohup bin/mqnamesrv -c ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf & $ nohup bin/mqnamesrv -c ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf & ``` 如果上面的步骤执行成功,可以通过运维命令查看Controller集群状态。 ```shell $ sh bin/mqadmin getControllerMetaData -a localhost:9878 ``` -a代表的是任意一个 Controller 的地址 如果controller启动成功可以看到以下内容: ``` #ControllerGroup group1 #ControllerLeaderId n0 #ControllerLeaderAddress 127.0.0.1:9878 #Peer: n0:127.0.0.1:9878 #Peer: n1:127.0.0.1:9868 #Peer: n2:127.0.0.1:9858 ``` 启动成功后Broker Controller模式部署就能使用Controller集群。 如果需要快速停止集群: ```shell $ sh bin/controller/fast-try-namesrv-plugin.sh stop ``` 使用 fast-try-namesrv-plugin.sh 脚本快速部署,默认配置在 conf/controller/cluster-3n-namesrv-plugin里面并且会启动3个Namesvr和3个Controller(内嵌Namesrv)。 ## Controller独立集群部署 Controller独立集群(3个Node组成)部署,快速启动: ```shell $ sh bin/controller/fast-try-independent-deployment.sh start ``` 或者通过命令单独启动: ```shell $ nohup bin/mqcontroller -c ./conf/controller/cluster-3n-independent/controller-n0.conf & $ nohup bin/mqcontroller -c ./conf/controller/cluster-3n-independent/controller-n1.conf & $ nohup bin/mqcontroller -c ./conf/controller/cluster-3n-independent/controller-n2.conf & ``` 如果上面的步骤执行成功,可以通过运维命令查看Controller集群状态。 ```shell $ sh bin/mqadmin getControllerMetaData -a localhost:9878 ``` -a代表的是任意一个 Controller 的地址 如果Controller启动成功可以看到以下内容: ``` #ControllerGroup group1 #ControllerLeaderId n1 #ControllerLeaderAddress 127.0.0.1:9868 #Peer: n0:127.0.0.1:9878 #Peer: n1:127.0.0.1:9868 #Peer: n2:127.0.0.1:9858 ``` 启动成功后Broker Controller模式部署就能使用Controller集群。 如果需要快速停止集群: ```shell $ sh bin/controller/fast-try-independent-deployment.sh stop ``` 使用fast-try-independent-deployment.sh 脚本快速部署,默认配置在 conf/controller/cluster-3n-independent里面并且会启动3个Controller(独立部署)组成一个集群。 ## 注意说明 - 若需要保证Controller具备容错能力,Controller部署需要三副本及以上(遵循Raft的多数派协议) - Controller部署配置文件中配置参数`controllerDLegerPeers` 中的IP地址配置成其他节点能够访问的IP,在多机器部署的时候尤为重要。例子仅供参考需要根据实际情况进行修改调整。 ================================================ FILE: docs/cn/design.md ================================================ # 设计(design) --- ### 1 消息存储 ![](image/rocketmq_design_1.png) 消息存储是RocketMQ中最为复杂和最为重要的一部分,本节将分别从RocketMQ的消息存储整体架构、PageCache与Mmap内存映射以及RocketMQ中两种不同的刷盘方式三方面来分别展开叙述。 #### 1.1 消息存储整体架构 消息存储架构图中主要有下面三个跟消息存储相关的文件构成。 (1) CommitLog:消息主体以及元数据的存储主体,存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G, 文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件; (2) ConsumeQueue:消息消费索引,引入的目的主要是提高消息消费的性能。由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件,根据topic检索消息是非常低效的。Consumer可根据ConsumeQueue来查找待消费的消息。其中,ConsumeQueue作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样consumequeue文件采取定长设计,每一个条目共20个字节,分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.72M; (3) IndexFile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是:$HOME/store/index/{fileName},文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构,故RocketMQ的索引文件其底层实现为hash索引。 在上面的RocketMQ的消息存储整体架构图中可以看出,RocketMQ采用的是混合型的存储结构,即为Broker单个实例下所有的队列共用一个日志数据文件(即为CommitLog)来存储。RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储结构,Producer发送消息至Broker端,然后Broker端使用同步或者异步的方式对消息刷盘持久化,保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中,那么Producer发送的消息就不会丢失。正因为如此,Consumer也就肯定有机会去消费这条消息。当无法拉取到消息后,可以等下一次消息拉取,同时服务端也支持长轮询模式,如果一个消息拉取请求未拉取到消息,Broker允许等待30s的时间,只要这段时间内有新消息到达,将直接返回给消费端。这里,RocketMQ的具体做法是,使用Broker端的后台服务线程—ReputMessageService不停地分发请求并异步构建ConsumeQueue(逻辑消费队列)和IndexFile(索引文件)数据。 #### 1.2 页缓存与内存映射 页缓存(PageCache)是OS对文件的缓存,用于加速对文件的读写。一般来说,程序对文件进行顺序读写的速度几乎接近于内存的读写速度,主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化,将一部分的内存用作PageCache。对于数据的写入,OS会先写入至Cache内,随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上。对于数据的读取,如果一次读取文件时出现未命中PageCache的情况,OS从物理磁盘上访问读取文件的同时,会顺序对其他相邻块的数据文件进行预读取。 在RocketMQ中,ConsumeQueue逻辑消费队列存储的数据较少,并且是顺序读取,在page cache机制的预读取作用下,Consume Queue文件的读性能几乎接近读内存,即使在有消息堆积情况下也不会影响性能。而对于CommitLog消息存储的日志数据文件来说,读取消息内容时候会产生较多的随机访问读取,严重影响性能。如果选择合适的系统IO调度算法,比如设置调度算法为“Deadline”(此时块存储采用SSD的话),随机读的性能也会有所提升。 另外,RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(正因为需要使用内存映射机制,故RocketMQ的文件存储都使用定长结构来存储,方便一次将整个文件映射至内存)。 #### 1.3 消息刷盘 ![](image/rocketmq_design_2.png) (1) 同步刷盘:如上图所示,只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。 (2) 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。 ### 2 通信机制 RocketMQ消息队列集群主要包括NameServer、Broker(Master/Slave)、Producer、Consumer4个角色,基本通讯流程如下: (1) Broker启动后需要完成一次将自己注册至NameServer的操作;随后每隔30s时间定时向NameServer上报Topic路由信息。 (2) 消息生产者Producer作为客户端发送消息时候,需要根据消息的Topic从本地缓存的TopicPublishInfoTable获取路由信息。如果没有则更新路由信息会从NameServer上重新拉取,同时Producer会默认每隔30s向NameServer拉取一次路由信息。 (3) 消息生产者Producer根据2)中获取的路由信息选择一个队列(MessageQueue)进行消息发送;Broker作为消息的接收者接收消息并落盘存储。 (4) 消息消费者Consumer根据2)中获取的路由信息,并再完成客户端的负载均衡后,选择其中的某一个或者某几个消息队列来拉取消息并进行消费。 从上面1)~3)中可以看出在消息生产者,Broker和NameServer之间都会发生通信(这里只说了MQ的部分通信),因此如何设计一个良好的网络通信模块在MQ中至关重要,它将决定RocketMQ集群整体的消息传输能力与最终的性能。 rocketmq-remoting 模块是 RocketMQ消息队列中负责网络通信的模块,它几乎被其他所有需要网络通信的模块(诸如rocketmq-client、rocketmq-broker、rocketmq-namesrv)所依赖和引用。为了实现客户端与服务器之间高效的数据请求与接收,RocketMQ消息队列自定义了通信协议并在Netty的基础之上扩展了通信模块。 #### 2.1 Remoting通信类结构 ![](image/rocketmq_design_3.png) #### 2.2 协议设计与编解码 在Client和Server之间完成一次消息发送时,需要对发送的消息进行一个协议约定,因此就有必要自定义RocketMQ的消息协议。同时,为了高效地在网络中传输消息和对收到的消息读取,就需要对消息进行编解码。在RocketMQ中,RemotingCommand这个类在消息传输过程中对所有数据内容的封装,不但包含了所有的数据结构,还包含了编码解码操作。 Header字段 | 类型 | Request说明 | Response说明 --- | --- | --- | --- | code |int | 请求操作码,应答方根据不同的请求码进行不同的业务处理 | 应答响应码。0表示成功,非0则表示各种错误 language | LanguageCode | 请求方实现的语言 | 应答方实现的语言 version | int | 请求方程序的版本 | 应答方程序的版本 opaque | int |相当于requestId,在同一个连接上的不同请求标识码,与响应消息中的相对应 | 应答不做修改直接返回 flag | int | 区分是普通RPC还是onewayRPC的标志 | 区分是普通RPC还是onewayRPC的标志 remark | String | 传输自定义文本信息 | 传输自定义文本信息 extFields | HashMap | 请求自定义扩展信息 | 响应自定义扩展信息 ![](image/rocketmq_design_4.png) 可见传输内容主要可以分为以下4部分: (1) 消息长度:总长度,四个字节存储,占用一个int类型; (2) 序列化类型&消息头长度:同样占用一个int类型,第一个字节表示序列化类型,后面三个字节表示消息头长度; (3) 消息头数据:经过序列化后的消息头数据; (4) 消息主体数据:消息主体的二进制字节数据内容; #### 2.3 消息的通信方式和流程 在RocketMQ消息队列中支持通信的方式主要有同步(sync)、异步(async)、单向(oneway) 三种。其中“单向”通信模式相对简单,一般用在发送心跳包场景下,无需关注其Response。这里,主要介绍RocketMQ的异步通信流程。 ![](image/rocketmq_design_5.png) #### 2.4 Reactor多线程设计 RocketMQ的RPC通信采用Netty组件作为底层通信库,同样也遵循了Reactor多线程模型,同时又在这之上做了一些扩展和优化。 ![](image/rocketmq_design_6.png) 从上面的框图中可以大致了解RocketMQ中NettyRemotingServer的Reactor 多线程模型。一个 Reactor 主线程(eventLoopGroupBoss,即为上面的1)负责监听 TCP网络连接请求,建立好连接,创建SocketChannel,并注册到selector上。RocketMQ的源码中会自动根据OS的类型选择NIO和Epoll,也可以通过参数配置),然后监听真正的网络数据。拿到网络数据后,再丢给Worker线程池(eventLoopGroupSelector,即为上面的“N”,源码中默认设置为3),在真正执行业务逻辑之前需要进行SSL验证、编解码、空闲检查、网络连接管理,这些工作交给defaultEventExecutorGroup(即为上面的“M1”,源码中默认设置为8)去做。而处理业务操作放在业务线程池中执行,根据 RomotingCommand 的业务请求码code去processorTable这个本地缓存变量中找到对应的 processor,然后封装成task任务后,提交给对应的业务processor处理线程池来执行(sendMessageExecutor,以发送消息为例,即为上面的 “M2”)。从入口到业务逻辑的几个步骤中线程池一直再增加,这跟每一步逻辑复杂性相关,越复杂,需要的并发通道越宽。 线程数 | 线程名 | 线程具体说明 --- | --- | --- 1 | NettyBoss_%d | Reactor 主线程 N | NettyServerEPOLLSelector_%d_%d | Reactor 线程池 M1 | NettyServerCodecThread_%d | Worker线程池 M2 | RemotingExecutorThread_%d | 业务processor处理线程池 ### 3 消息过滤 RocketMQ分布式消息队列的消息过滤方式有别于其它MQ中间件,是在Consumer端订阅消息时再做消息过滤的。RocketMQ这么做是在于其Producer端写入消息和Consumer端订阅消息采用分离存储的机制来实现的,Consumer端订阅消息是需要通过ConsumeQueue这个消息消费的逻辑队列拿到一个索引,然后再从CommitLog里面读取真正的消息实体内容,所以说到底也是还绕不开其存储结构。其ConsumeQueue的存储结构如下,可以看到其中有8个字节存储的Message Tag的哈希值,基于Tag的消息过滤正是基于这个字段值的。 ![](image/rocketmq_design_7.png) 主要支持如下2种的过滤方式 (1) Tag过滤方式:Consumer端在订阅消息时除了指定Topic还可以指定TAG,如果一个消息有多个TAG,可以用||分隔。其中,Consumer端会将这个订阅请求构建成一个 SubscriptionData,发送一个Pull消息的请求给Broker端。Broker端从RocketMQ的文件存储层—Store读取数据之前,会用这些数据先构建一个MessageFilter,然后传给Store。Store从 ConsumeQueue读取到一条记录后,会用它记录的消息tag hash值去做过滤,由于在服务端只是根据hashcode进行判断,无法精确对tag原始字符串进行过滤,故在消息消费端拉取到消息后,还需要对消息的原始tag字符串进行比对,如果不同,则丢弃该消息,不进行消息消费。 (2) SQL92的过滤方式:这种方式的大致做法和上面的Tag过滤方式一样,只是在Store层的具体过滤过程不太一样,真正的 SQL expression 的构建和执行由rocketmq-filter模块负责的。每次过滤都去执行SQL表达式会影响效率,所以RocketMQ使用了BloomFilter避免了每次都去执行。SQL92的表达式上下文为消息的属性。 ### 4 负载均衡 RocketMQ中的负载均衡都在Client端完成,具体来说的话,主要可以分为Producer端发送消息时候的负载均衡和Consumer端订阅消息的负载均衡。 #### 4.1 Producer的负载均衡 Producer端在发送消息的时候,会先根据Topic找到指定的TopicPublishInfo,在获取了TopicPublishInfo路由信息后,RocketMQ的客户端在默认方式下selectOneMessageQueue()方法会从TopicPublishInfo中的messageQueueList中选择一个队列(MessageQueue)进行发送消息。具体的容错策略均在MQFaultStrategy这个类中定义。这里有一个sendLatencyFaultEnable开关变量,如果开启,在随机递增取模的基础上,再过滤掉not available的Broker代理。所谓的"latencyFaultTolerance",是指对之前失败的,按一定的时间做退避。例如,如果上次请求的latency超过550L ms,就退避30000L ms;超过1000L,就退避60000L;如果关闭,采用随机递增取模的方式选择一个队列(MessageQueue)来发送消息,latencyFaultTolerance机制是实现消息发送高可用的核心关键所在。 #### 4.2 Consumer的负载均衡 在RocketMQ中,Consumer端的两种消费模式(Push/Pull)都是基于拉模式来获取消息的,而在Push模式只是对pull模式的一种封装,其本质实现为消息拉取线程在从服务器拉取到一批消息后,然后提交到消息消费线程池后,又“马不停蹄”的继续向服务器再次尝试拉取消息。如果未拉取到消息,则延迟一下又继续拉取。在两种基于拉模式的消费方式(Push/Pull)中,均需要Consumer端知道从Broker端的哪一个消息队列中去获取消息。因此,有必要在Consumer端来做负载均衡,即Broker端中多个MessageQueue分配给同一个ConsumerGroup中的哪些Consumer消费。 1、Consumer端的心跳包发送 在Consumer启动后,它就会通过定时任务不断地向RocketMQ集群中的所有Broker实例发送心跳包(其中包含了,消息消费分组名称、订阅关系集合、消息通信模式和客户端id的值等信息)。Broker端在收到Consumer的心跳消息后,会将它维护在ConsumerManager的本地缓存变量—consumerTable,同时并将封装后的客户端网络通道信息保存在本地缓存变量—channelInfoTable中,为之后做Consumer端的负载均衡提供可以依据的元数据信息。 2、Consumer端实现负载均衡的核心类—RebalanceImpl 在Consumer实例的启动流程中的启动MQClientInstance实例部分,会完成负载均衡服务线程—RebalanceService的启动(每隔20s执行一次)。通过查看源码可以发现,RebalanceService线程的run()方法最终调用的是RebalanceImpl类的rebalanceByTopic()方法,该方法是实现Consumer端负载均衡的核心。这里,rebalanceByTopic()方法会根据消费者通信类型为“广播模式”还是“集群模式”做不同的逻辑处理。这里主要来看下集群模式下的主要处理流程: (1) 从rebalanceImpl实例的本地缓存变量—topicSubscribeInfoTable中,获取该Topic主题下的消息消费队列集合(mqSet); (2) 根据topic和consumerGroup为参数调用mQClientFactory.findConsumerIdList()方法向Broker端发送获取该消费组下消费者Id列表的RPC通信请求(Broker端基于前面Consumer端上报的心跳包数据而构建的consumerTable做出响应返回,业务请求码:GET_CONSUMER_LIST_BY_GROUP); (3) 先对Topic下的消息消费队列、消费者Id排序,然后用消息队列分配策略算法(默认为:消息队列的平均分配算法),计算出待拉取的消息队列。这里的平均分配算法,类似于分页的算法,将所有MessageQueue排好序类似于记录,将所有消费端Consumer排好序类似页数,并求出每一页需要包含的平均size和每个页面记录的范围range,最后遍历整个range而计算出当前Consumer端应该分配到的记录(这里即为:MessageQueue)。 ![](image/rocketmq_design_8.png) (4) 然后,调用updateProcessQueueTableInRebalance()方法,具体的做法是,先将分配到的消息队列集合(mqSet)与processQueueTable做一个过滤比对。 ![](image/rocketmq_design_9.png) - 上图中processQueueTable标注的红色部分,表示与分配到的消息队列集合mqSet互不包含。将这些队列设置Dropped属性为true,然后查看这些队列是否可以移除出processQueueTable缓存变量,这里具体执行removeUnnecessaryMessageQueue()方法,即每隔1s 查看是否可以获取当前消费处理队列的锁,拿到的话返回true。如果等待1s后,仍然拿不到当前消费处理队列的锁则返回false。如果返回true,则从processQueueTable缓存变量中移除对应的Entry; - 上图中processQueueTable的绿色部分,表示与分配到的消息队列集合mqSet的交集。判断该ProcessQueue是否已经过期了,在Pull模式的不用管,如果是Push模式的,设置Dropped属性为true,并且调用removeUnnecessaryMessageQueue()方法,像上面一样尝试移除Entry; 最后,为过滤后的消息队列集合(mqSet)中的每个MessageQueue创建一个ProcessQueue对象并存入RebalanceImpl的processQueueTable队列中(其中调用RebalanceImpl实例的computePullFromWhere(MessageQueue mq)方法获取该MessageQueue对象的下一个进度消费值offset,随后填充至接下来要创建的pullRequest对象属性中),并创建拉取请求对象—pullRequest添加到拉取列表—pullRequestList中,最后执行dispatchPullRequest()方法,将Pull消息的请求对象PullRequest依次放入PullMessageService服务线程的阻塞队列pullRequestQueue中,待该服务线程取出后向Broker端发起Pull消息的请求。 消息消费队列在同一消费组不同消费者之间的负载均衡,其核心设计理念是在一个消息消费队列在同一时间只允许被同一消费组内的一个消费者消费,一个消息消费者能同时消费多个消息队列。 ### 5 事务消息 Apache RocketMQ在4.3.0版中已经支持分布式事务消息,这里RocketMQ采用了2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如下图所示。 ![](image/rocketmq_design_10.png) #### 5.1 RocketMQ事务消息流程概要 上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。 1.事务消息发送及提交: (1) 发送消息(half消息)。 (2) 服务端响应消息写入结果。 (3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。 (4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见) 2.补偿流程: (1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查” (2) Producer收到回查消息,检查回查消息对应的本地事务的状态 (3) 根据本地事务状态,重新Commit或者Rollback 其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。 #### 5.2 RocketMQ事务消息设计 1.事务消息在一阶段对用户不可见 在RocketMQ事务消息的主要流程中,一阶段的消息如何对用户不可见。其中,事务消息相对普通消息最大的特点就是一阶段发送的消息对用户是不可见的。那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后改变主题为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。 在RocketMQ中,消息在服务端的存储结构如下,每条消息都会有对应的索引信息,Consumer通过ConsumeQueue这个二级索引来读取消息实体内容,其流程如下: ![](image/rocketmq_design_11.png) RocketMQ的具体实现策略是:写入的如果事务消息,对消息的Topic和Queue等属性进行替换,同时将原来的Topic和Queue信息存储到消息的属性中,正因为消息主题被替换,故消息并不会转发到该原主题的消息消费队列,消费者无法感知消息的存在,不会消费。其实改变消息主题是RocketMQ的常用“套路”,回想一下延时消息的实现机制。 2.Commit和Rollback操作以及Op消息的引入 在完成一阶段写入一条对用户不可见的消息后,二阶段如果是Commit操作,则需要让消息对用户可见;如果是Rollback则需要撤销一阶段的消息。先说Rollback的情况。对于Rollback,本身一阶段的消息对用户是不可见的,其实不需要真正撤销消息(实际上RocketMQ也无法去真正的删除一条消息,因为是顺序写文件的)。但是区别于这条消息没有确定状态(Pending状态,事务悬而未决),需要一个操作来标识这条消息的最终状态。RocketMQ事务消息方案中引入了Op消息的概念,用Op消息标识事务消息已经确定的状态(Commit或者Rollback)。如果一条事务消息没有对应的Op消息,说明这个事务的状态还无法确定(可能是二阶段失败了)。引入Op消息后,事务消息无论是Commit或者Rollback都会记录一个Op操作。Commit相对于Rollback只是在写入Op消息前创建Half消息的索引。 3.Op消息的存储和对应关系 RocketMQ将Op消息写入到全局一个特定的Topic中通过源码中的方法—TransactionalMessageUtil.buildOpTopic();这个Topic是一个内部的Topic(像Half消息的Topic一样),不会被用户消费。Op消息的内容为对应的Half消息的存储的Offset,这样通过Op消息能索引到Half消息进行后续的回查操作。 ![](image/rocketmq_design_12.png) 4.Half消息的索引构建 在执行二阶段Commit操作时,需要构建出Half消息的索引。一阶段的Half消息由于是写到一个特殊的Topic,所以二阶段构建索引时需要读取出Half消息,并将Topic和Queue替换成真正的目标的Topic和Queue,之后通过一次普通消息的写入操作来生成一条对用户可见的消息。所以RocketMQ事务消息二阶段其实是利用了一阶段存储的消息的内容,在二阶段时恢复出一条完整的普通消息,然后走一遍消息写入流程。 5.如何处理二阶段失败的消息? 如果在RocketMQ事务消息的二阶段过程中失败了,例如在做Commit操作时,出现网络问题导致Commit失败,那么需要通过一定的策略使这条消息最终被Commit。RocketMQ采用了一种补偿机制,称为“回查”。Broker端对未确定状态的消息发起回查,将消息发送到对应的Producer端(同一个Group的Producer),由Producer根据消息来检查本地事务的状态,进而执行Commit或者Rollback。Broker端通过对比Half消息和Op消息进行事务消息的回查并且推进CheckPoint(记录那些事务消息的状态是确定的)。 值得注意的是,rocketmq并不会无休止的的信息事务状态回查,默认回查15次,如果15次回查还是无法得知事务状态,rocketmq默认回滚该消息。 ### 6 消息查询 RocketMQ支持按照下面两种维度(“按照Message Id查询消息”、“按照Message Key查询消息”)进行消息查询。 #### 6.1 按照MessageId查询消息 RocketMQ中的MessageId的长度总共有16字节,其中包含了消息存储主机地址(IP地址和端口),消息Commit Log offset。“按照MessageId查询消息”在RocketMQ中具体做法是:Client端从MessageId中解析出Broker的地址(IP地址和端口)和Commit Log的偏移地址后封装成一个RPC请求后通过Remoting通信层发送(业务请求码:VIEW_MESSAGE_BY_ID)。Broker端走的是QueryMessageProcessor,读取消息的过程用其中的 commitLog offset 和 size 去 commitLog 中找到真正的记录并解析成一个完整的消息返回。 #### 6.2 按照Message Key查询消息 “按照Message Key查询消息”,主要是基于RocketMQ的IndexFile索引文件来实现的。RocketMQ的索引文件逻辑结构,类似JDK中HashMap的实现。索引文件的具体结构如下: ![](image/rocketmq_design_13.png) IndexFile索引文件为用户提供通过“按照Message Key查询消息”的消息索引查询服务,IndexFile文件的存储位置是:$HOME\store\index\${fileName},文件名fileName是以创建时的时间戳命名的,文件大小是固定的,等于40+500W\*4+2000W\*20= 420000040个字节大小。如果消息的properties中设置了UNIQ_KEY这个属性,就用 topic + “#” + UNIQ_KEY的value作为 key 来做写入操作。如果消息设置了KEYS属性(多个KEY以空格分隔),也会用 topic + “#” + KEY 来做索引。 其中的索引数据包含了Key Hash/CommitLog Offset/Timestamp/NextIndex offset 这四个字段,一共20 Byte。NextIndex offset 即前面读出来的 slotValue,如果有 hash冲突,就可以用这个字段将所有冲突的索引用链表的方式串起来了。Timestamp记录的是消息storeTimestamp之间的差,并不是一个绝对的时间。整个Index File的结构如图,40 Byte 的Header用于保存一些总的统计信息,4\*500W的 Slot Table并不保存真正的索引数据,而是保存每个槽位对应的单向链表的头。20\*2000W 是真正的索引数据,即一个 Index File 可以保存 2000W个索引。 “按照Message Key查询消息”的方式,RocketMQ的具体做法是,主要通过Broker端的QueryMessageProcessor业务处理器来查询,读取消息的过程就是用topic和key找到IndexFile索引文件中的一条记录,根据其中的commitLog offset从CommitLog文件中读取消息的实体内容。 ================================================ FILE: docs/cn/dledger/deploy_guide.md ================================================ # Dledger集群搭建 > 该模式为4.x的切换方式,建议采用 5.x [自动主从切换](../controller/design.md) --- ## 前言 该文档主要介绍如何部署自动容灾切换的 RocketMQ-on-DLedger Group。 RocketMQ-on-DLedger Group 是指一组**相同名称**的 Broker,至少需要 3 个节点,通过 Raft 自动选举出一个 Leader,其余节点 作为 Follower,并在 Leader 和 Follower 之间复制数据以保证高可用。 RocketMQ-on-DLedger Group 能自动容灾切换,并保证数据一致。 RocketMQ-on-DLedger Group 是可以水平扩展的,也即可以部署任意多个 RocketMQ-on-DLedger Group 同时对外提供服务。 ## 1. 新集群部署 #### 1.1 编写配置 每个 RocketMQ-on-DLedger Group 至少准备三台机器(本文假设为 3)。 编写 3 个配置文件,建议参考 conf/dledger 目录下的配置文件样例。 关键配置介绍: | name | 含义 | 举例 | | --- | --- | --- | | enableDLegerCommitLog | 是否启动 DLedger  | true | | dLegerGroup | DLedger Raft Group的名字,建议和 brokerName 保持一致 | RaftNode00 | | dLegerPeers | DLedger Group 内各节点的端口信息,同一个 Group 内的各个节点配置必须要保证一致 | n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 | | dLegerSelfId | 节点 id,必须属于 dLegerPeers 中的一个;同 Group 内各个节点要唯一 | n0 | | sendMessageThreadPoolNums | 发送线程个数,建议配置成 Cpu 核数 | 16 | 这里贴出 conf/dledger/broker-n0.conf 的配置举例。 ``` brokerClusterName = RaftCluster brokerName=RaftNode00 listenPort=30911 namesrvAddr=127.0.0.1:9876 storePathRootDir=/tmp/rmqstore/node00 storePathCommitLog=/tmp/rmqstore/node00/commitlog enableDLegerCommitLog=true dLegerGroup=RaftNode00 dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 ## must be unique dLegerSelfId=n0 sendMessageThreadPoolNums=16 ``` ### 1.2 启动 Broker 与老版本的启动方式一致。 `nohup sh bin/mqbroker -c conf/dledger/xxx-n0.conf & ` `nohup sh bin/mqbroker -c conf/dledger/xxx-n1.conf & ` `nohup sh bin/mqbroker -c conf/dledger/xxx-n2.conf & ` ## 2. 旧集群升级 如果旧集群采用 Master 方式部署,则每个 Master 都需要转换成一个 RocketMQ-on-DLedger Group。 如果旧集群采用 Master-Slave 方式部署,则每个 Master-Slave 组都需要转换成一个 RocketMQ-on-DLedger Group。 ### 2.1 杀掉旧的 Broker 可以通过 kill 命令来完成,也可以调用 `bin/mqshutdown broker`。 ### 2.2 检查旧的 Commitlog RocketMQ-on-DLedger 组中的每个节点,可以兼容旧的 Commitlog ,但其 Raft 复制过程,只能针对新增加的消息。因此,为了避免出现异常,需要保证 旧的 Commitlog 是一致的。 如果旧的集群是采用 Master-Slave 方式部署,有可能在shutdown时,其数据并不是一致的,建议通过md5sum 的方式,检查最近的最少 2 个 Commmitlog 文件,如果发现不一致,则通过拷贝的方式进行对齐。 虽然 RocketMQ-on-DLedger Group 也可以以 2 节点方式部署,但其会丧失容灾切换能力(2n + 1 原则,至少需要3个节点才能容忍其中 1 个宕机)。 所以在对齐了 Master 和 Slave 的 Commitlog 之后,还需要准备第 3 台机器,并把旧的 Commitlog 从 Master 拷贝到 第 3 台机器(记得同时拷贝一下 config 文件夹)。 在 3 台机器准备好了之后,旧 Commitlog 文件也保证一致之后,就可以开始走下一步修改配置了。 ### 2.3 修改配置 参考新集群部署。 ### 2.4 重新启动 Broker 参考新集群部署。 ================================================ FILE: docs/cn/dledger/quick_start.md ================================================ # Dledger快速搭建 > 该模式为4.x的切换方式,建议采用 5.x [自动主从切换](../controller/quick_start.md) --- ### 前言 该文档主要介绍如何快速构建和部署基于 DLedger 的可以自动容灾切换的 RocketMQ 集群。 详细的新集群部署和旧集群升级指南请参考 [部署指南](deploy_guide.md)。 ### 1. 源码构建 构建分为两个部分,需要先构建 DLedger,然后 构建 RocketMQ #### 1.1 构建 DLedger ```shell $ git clone https://github.com/openmessaging/dledger.git $ cd dledger $ mvn clean install -DskipTests ``` #### 1.2 构建 RocketMQ ```shell $ git clone https://github.com/apache/rocketmq.git $ cd rocketmq $ git checkout -b store_with_dledger origin/store_with_dledger $ mvn -Prelease-all -DskipTests clean install -U ``` ### 2. 快速部署 在构建成功后 ```shell #{rocketmq-version} replace with rocketmq actual version. example: 5.0.0-SNAPSHOT $ cd distribution/target/rocketmq-{rocketmq-version}/rocketmq-{rocketmq-version} $ sh bin/dledger/fast-try.sh start ``` 如果上面的步骤执行成功,可以通过 mqadmin 运维命令查看集群状态。 ```shell $ sh bin/mqadmin clusterList -n 127.0.0.1:9876 ``` 顺利的话,会看到如下内容: ![ClusterList](https://img.alicdn.com/5476e8b07b923/TB11Z.ZyCzqK1RjSZFLXXcn2XXa) (BID 为 0 的表示 Master,其余都是 Follower) 启动成功,现在可以向集群收发消息,并进行容灾切换测试了。 关闭快速集群,可以执行: ```shell $ sh bin/dledger/fast-try.sh stop ``` 快速部署,默认配置在 conf/dledger 里面,默认的存储路径在 /tmp/rmqstore。 ### 3. 容灾切换 部署成功,杀掉 Leader 之后(在上面的例子中,杀掉端口 30931 所在的进程),等待约 10s 左右,用 clusterList 命令查看集群,就会发现 Leader 切换到另一个节点了。 ================================================ FILE: docs/cn/features.md ================================================ # 特性(features) ---- ## 1 订阅与发布 消息的发布是指某个生产者向某个topic发送消息;消息的订阅是指某个消费者关注了某个topic中带有某些tag的消息,进而从该topic消费数据。 ## 2 消息顺序 消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了三条消息分别是订单创建、订单付款、订单完成。消费时要按照这个顺序消费才能有意义,但是同时订单之间是可以并行消费的。RocketMQ可以严格的保证消息有序。 顺序消息分为全局顺序消息与分区顺序消息,全局顺序是指某个Topic下的所有消息都要保证顺序;部分顺序消息只要保证每一组消息被顺序消费即可。 - 全局顺序 对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。 适用场景:性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景 - 分区顺序 对于指定的一个 Topic,所有消息根据 sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。 Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。 适用场景:性能要求高,以 sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景。 ## 3 消息过滤 RocketMQ的消费者可以根据Tag进行消息过滤,也支持自定义属性过滤。消息过滤目前是在Broker端实现的,优点是减少了对于Consumer无用消息的网络传输,缺点是增加了Broker的负担、而且实现相对复杂。 ## 4 消息可靠性 RocketMQ支持消息的高可靠,影响消息可靠性的几种情况: 1) Broker非正常关闭 2) Broker异常Crash 3) OS Crash 4) 机器掉电,但是能立即恢复供电情况 5) 机器无法开机(可能是cpu、主板、内存等关键设备损坏) 6) 磁盘设备损坏 1)、2)、3)、4) 四种情况都属于硬件资源可立即恢复情况,RocketMQ在这四种情况下能保证消息不丢,或者丢失少量数据(依赖刷盘方式是同步还是异步)。 5)、6)属于单点故障,且无法恢复,一旦发生,在此单点上的消息全部丢失。RocketMQ在这两种情况下,通过异步复制,可保证99%的消息不丢,但是仍然会有极少量的消息可能丢失。通过同步双写技术可以完全避免单点,同步双写势必会影响性能,适合对消息可靠性要求极高的场合,例如与Money相关的应用。注:RocketMQ从3.0版本开始支持同步双写。 ## 5 至少一次 至少一次(At least Once)指每个消息必须投递一次。Consumer先Pull消息到本地,消费完成后,才向服务器返回ack,如果没有消费一定不会ack消息,所以RocketMQ可以很好的支持此特性。 ## 6 回溯消费 回溯消费是指Consumer已经消费成功的消息,由于业务上需求需要重新消费,要支持此功能,Broker在向Consumer投递成功消息后,消息仍然需要保留。并且重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据,那么Broker要提供一种机制,可以按照时间维度来回退消费进度。RocketMQ支持按照时间回溯消费,时间维度精确到毫秒。 ## 7 事务消息 RocketMQ事务消息(Transactional Message)是指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。RocketMQ的事务消息提供类似 X/Open XA 的分布事务功能,通过事务消息能达到分布式事务的最终一致。 ## 8 定时消息 定时消息(延迟队列)是指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic。 broker有配置项messageDelayLevel,默认值为“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,18个level。可以配置自定义messageDelayLevel。注意,messageDelayLevel是broker的属性,不属于某个topic。发消息时,设置delayLevel等级即可:msg.setDelayLevel(level)。level有以下三种情况: - level == 0,消息为非延迟消息 - 1<=level<=maxLevel,消息延迟特定时间,例如level==1,延迟1s - level > maxLevel,则level== maxLevel,例如level==20,延迟2h 定时消息会暂存在名为SCHEDULE_TOPIC_XXXX的topic中,并根据delayTimeLevel存入特定的queue,queueId = delayTimeLevel – 1,即一个queue只存相同延迟的消息,保证具有相同发送延迟的消息能够顺序消费。broker会调度地消费SCHEDULE_TOPIC_XXXX,将消息写入真实的topic。 需要注意的是,定时消息会在第一次写入和调度写入真实topic时都会计数,因此发送数量、tps都会变高。 ## 9 消息重试 Consumer消费消息失败后,要提供一种重试机制,令消息再消费一次。Consumer消费消息失败通常可以认为有以下几种情况: - 由于消息本身的原因,例如反序列化失败,消息数据本身无法处理(例如话费充值,当前消息的手机号被注销,无法充值)等。这种错误通常需要跳过这条消息,再消费其它消息,而这条失败的消息即使立刻重试消费,99%也不成功,所以最好提供一种定时重试机制,即过10秒后再重试。 - 由于依赖的下游应用服务不可用,例如db连接不可用,外系统网络不可达等。遇到这种错误,即使跳过当前失败的消息,消费其他消息同样也会报错。这种情况建议应用sleep 30s,再消费下一条消息,这样可以减轻Broker重试消息的压力。 RocketMQ会为每个消费组都设置一个Topic名称为“%RETRY%+consumerGroup”的重试队列(这里需要注意的是,这个Topic的重试队列是针对消费组,而不是针对每个Topic设置的),用于暂时保存因为各种异常而导致Consumer端无法消费的消息。考虑到异常恢复起来需要一些时间,会为重试队列设置多个重试级别,每个重试级别都有与之对应的重新投递延时,重试次数越多投递延时就越大。RocketMQ对于重试消息的处理是先保存至Topic名称为“SCHEDULE_TOPIC_XXXX”的延迟队列中,后台定时任务按照对应的时间进行Delay后重新保存至“%RETRY%+consumerGroup”的重试队列中。 ## 10 消息重投 生产者在发送消息时,同步消息失败会重投,异步消息有重试,oneway没有任何保证。消息重投保证消息尽可能发送成功、不丢失,但可能会造成消息重复,消息重复在RocketMQ中是无法避免的问题。消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复就会是大概率事件。另外,生产者主动重发、consumer负载变化也会导致重复消息。如下方法可以设置消息重试策略: - retryTimesWhenSendFailed:同步发送失败重投次数,默认为2,因此生产者会最多尝试发送retryTimesWhenSendFailed + 1次。不会选择上次失败的broker,尝试向其他broker发送,最大程度保证消息不丢。超过重投次数,抛出异常,由客户端保证消息不丢。当出现RemotingException、MQClientException和部分MQBrokerException时会重投。 - retryTimesWhenSendAsyncFailed:异步发送失败重试次数,异步重试不会选择其他broker,仅在同一个broker上做重试,不保证消息不丢。 - retryAnotherBrokerWhenNotStoreOK:消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK),是否尝试发送到其他broker,默认false。十分重要消息可以开启。 ## 11 流量控制 生产者流控,因为broker处理能力达到瓶颈;消费者流控,因为消费能力达到瓶颈。 生产者流控: - commitLog文件被锁时间超过osPageCacheBusyTimeOutMills时,参数默认为1000ms,返回流控。 - 如果开启transientStorePoolEnable == true,且broker为异步刷盘的主机,且transientStorePool中资源不足,拒绝当前send请求,返回流控。 - broker每隔10ms检查send请求队列头部请求的等待时间,如果超过waitTimeMillsInSendQueue,默认200ms,拒绝当前send请求,返回流控。 - broker通过拒绝send 请求方式实现流量控制。 注意,生产者流控,不会尝试消息重投。 消费者流控: - 消费者本地缓存消息数超过pullThresholdForQueue时,默认1000。 - 消费者本地缓存消息大小超过pullThresholdSizeForQueue时,默认100MB。 - 消费者本地缓存消息跨度超过consumeConcurrentlyMaxSpan时,默认2000。 消费者流控的结果是降低拉取频率。 ## 12 死信队列 死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。 RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。在RocketMQ中,可以通过使用console控制台对死信队列中的消息进行重发来使得消费者实例再次进行消费。 ================================================ FILE: docs/cn/msg_trace/user_guide.md ================================================ # 消息轨迹 ---- ## 1. 消息轨迹数据关键属性 | Producer端| Consumer端 | Broker端 | | --- | --- | --- | | 生产实例信息 | 消费实例信息 | 消息的Topic | | 发送消息时间 | 投递时间,投递轮次  | 消息存储位置 | | 消息是否发送成功 | 消息是否消费成功 | 消息的Key值 | | 发送耗时 | 消费耗时 | 消息的Tag值 | ## 2. 支持消息轨迹集群部署 ### 2.1 Broker端配置文件 这里贴出Broker端开启消息轨迹特性的properties配置文件内容: ``` brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/data/rocketmq/rootdir-a-m storePathCommitLog=/data/rocketmq/commitlog-a-m autoCreateSubscriptionGroup=true ## if msg tracing is open,the flag will be true traceTopicEnable=true listenPort=10911 brokerIP1=XX.XX.XX.XX1 namesrvAddr=XX.XX.XX.XX:9876 ``` ### 2.2 普通模式 RocketMQ集群中每一个Broker节点均用于存储Client端收集并发送过来的消息轨迹数据。因此,对于RocketMQ集群中的Broker节点数量并无要求和限制。 ### 2.3 物理IO隔离模式 对于消息轨迹数据量较大的场景,可以在RocketMQ集群中选择其中一个Broker节点专用于存储消息轨迹,使得用户普通的消息数据与消息轨迹数据的物理IO完全隔离,互不影响。在该模式下,RocketMQ集群中至少有两个Broker节点,其中一个Broker节点定义为存储消息轨迹数据的服务端。 ### 2.4 启动开启消息轨迹的Broker `nohup sh mqbroker -c ../conf/2m-noslave/broker-a.properties &` ## 3. 保存消息轨迹的Topic定义 RocketMQ的消息轨迹特性支持两种存储轨迹数据的方式: ### 3.1 系统级的TraceTopic 在默认情况下,消息轨迹数据是存储于系统级的TraceTopic中(其名称为:**RMQ_SYS_TRACE_TOPIC**)。该Topic在Broker节点启动时,会自动创建出来(如上所叙,需要在Broker端的配置文件中将**traceTopicEnable**的开关变量设置为**true**)。 ### 3.2 用户自定义的TraceTopic 如果用户不准备将消息轨迹的数据存储于系统级的默认TraceTopic,也可以自己定义并创建用户级的Topic来保存轨迹(即为创建普通的Topic用于保存消息轨迹数据)。下面一节会介绍Client客户端的接口如何支持用户自定义的TraceTopic。 ## 4. 支持消息轨迹的Client客户端实践 为了尽可能地减少用户业务系统使用RocketMQ消息轨迹特性的改造工作量,作者在设计时候采用对原来接口增加一个开关参数(**enableMsgTrace**)来实现消息轨迹是否开启;并新增一个自定义参数(**customizedTraceTopic**)来实现用户存储消息轨迹数据至自己创建的用户级Topic。 ### 4.1 发送消息时开启消息轨迹 ```java DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true); producer.setNamesrvAddr("XX.XX.XX.XX1"); producer.start(); try { { Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } } catch (Exception e) { e.printStackTrace(); } ``` ### 4.2 订阅消息时开启消息轨迹 ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true); consumer.subscribe("TopicTest", "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.setConsumeTimestamp("20181109221800"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); ``` ### 4.3 支持自定义存储消息轨迹Topic 在上面的发送和订阅消息时候分别将DefaultMQProducer和DefaultMQPushConsumer实例的初始化修改为如下即可支持自定义存储消息轨迹Topic。 ``` ##其中Topic_test11111需要用户自己预先创建,来保存消息轨迹; DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true,"Topic_test11111"); ...... DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true,"Topic_test11111"); ...... ``` ### 4.4 使用mqadmin命令发送和查看轨迹 - 发送消息 ```shell ./mqadmin sendMessage -m true --topic some-topic-name -n 127.0.0.1:9876 -p "your message content" ``` - 查询轨迹 ```shell ./mqadmin QueryMsgTraceById -n 127.0.0.1:9876 -i "some-message-id" ``` - 查询轨迹结果 ``` RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0). RocketMQLog:WARN Please initialize the logger system properly. #Type #ProducerGroup #ClientHost #SendTime #CostTimes #Status Pub 1623305799667 xxx.xxx.xxx.xxx 2021-06-10 14:16:40 131ms success ``` ================================================ FILE: docs/cn/operation.md ================================================ # 运维管理 --- ### 1 集群搭建 #### 1.1 单Master模式 这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。 ##### 1)启动 NameServer ```bash ### 首先启动Name Server $ nohup sh mqnamesrv & ### 验证Name Server 是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)启动 Broker ```bash ### 启动Broker $ nohup sh bin/mqbroker -n localhost:9876 & ### 验证Broker是否启动成功,例如Broker的IP为:192.168.1.2,且名称为broker-a $ tail -f ~/logs/rocketmqlogs/broker.log The broker[broker-a, 192.169.1.2:10911] boot success... ``` #### 1.2 多Master模式 一个集群无Slave,全是Master,例如2个Master或者3个Master,这种模式的优缺点如下: - 优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高; - 缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。 ##### 1)启动NameServer NameServer需要先于Broker启动,且如果在生产环境使用,为了保证高可用,建议一般规模的集群启动3个NameServer,各节点的启动命令相同,如下: ```bash ### 首先启动Name Server $ nohup sh mqnamesrv & ### 验证Name Server 是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)启动Broker集群 ```bash ### 在机器A,启动第一个Master,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-a.properties & ### 在机器B,启动第二个Master,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-b.properties & ... ``` 如上启动命令是在单个NameServer情况下使用的。对于多个NameServer的集群,Broker启动命令中`-n`后面的地址列表用分号隔开即可,例如 `192.168.1.1:9876;192.161.2:9876`。 #### 1.3 多Master多Slave模式-异步复制 每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下: - 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样; - 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。 ##### 1)启动NameServer ```bash ### 首先启动Name Server $ nohup sh mqnamesrv & ### 验证Name Server 是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)启动Broker集群 ```bash ### 在机器A,启动第一个Master,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a.properties & ### 在机器B,启动第二个Master,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b.properties & ### 在机器C,启动第一个Slave,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a-s.properties & ### 在机器D,启动第二个Slave,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b-s.properties & ``` #### 1.4 多Master多Slave模式-同步双写 每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下: - 优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高; - 缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。 ##### 1)启动NameServer ```bash ### 首先启动Name Server $ nohup sh mqnamesrv & ### 验证Name Server 是否启动成功 $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)启动Broker集群 ```bash ### 在机器A,启动第一个Master,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a.properties & ### 在机器B,启动第二个Master,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b.properties & ### 在机器C,启动第一个Slave,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a-s.properties & ### 在机器D,启动第二个Slave,例如NameServer的IP为:192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b-s.properties & ``` 以上Broker与Slave配对是通过指定相同的BrokerName参数来配对,Master的BrokerId必须是0,Slave的BrokerId必须是大于0的数。另外一个Master下面可以挂载多个Slave,同一Master下的多个Slave通过指定不同的BrokerId来区分。$ROCKETMQ_HOME指的RocketMQ安装目录,需要用户自己设置此环境变量。 #### 1.5 RocketMQ 5.0 自动主从切换 RocketMQ 5.0 开始支持自动主从切换的模式,可参考以下文档 [快速开始](controller/quick_start.md) [部署文档](controller/deploy.md) [设计思想](controller/design.md) ### 2 mqadmin管理工具 > 注意: > > 1. 执行命令方法:`./mqadmin {command} {args}` > 2. 几乎所有命令都需要配置-n表示NameServer地址,格式为ip:port > 3. 几乎所有命令都可以通过-h获取帮助 > 4. 如果既有Broker地址(-b)配置项又有clusterName(-c)配置项,则优先以Broker地址执行命令,如果不配置Broker地址,则对集群中所有主机执行命令,只支持一个Broker地址。-b格式为ip:port,port默认是10911 > 5. 在tools下可以看到很多命令,但并不是所有命令都能使用,只有在MQAdminStartup中初始化的命令才能使用,你也可以修改这个类,增加或自定义命令 > 6. 由于版本更新问题,少部分命令可能未及时更新,遇到错误请直接阅读相关命令源码 #### 2.1 Topic相关
    名称 含义 命令选项 说明
    updateTopic 创建更新Topic配置 -b Broker 地址,表示 topic 所在 Broker,只支持单台Broker,地址为ip:port
    -c cluster 名称,表示 topic 所在集群(集群可通过 clusterList 查询)
    -h- 打印帮助
    -n NameServer服务地址,格式 ip:port
    -p 指定新topic的读写权限( W=2|R=4|WR=6 )
    -r 可读队列数(默认为 8)
    -w 可写队列数(默认为 8)
    -t topic 名称(名称只能使用字符 ^[a-zA-Z0-9_-]+$ )
    deleteTopic 删除Topic -c cluster 名称,表示删除某集群下的某个 topic (集群 可通过 clusterList 查询)
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic 名称(名称只能使用字符 ^[a-zA-Z0-9_-]+$ )
    topicList 查看 Topic 列表信息 -h 打印帮助
    -c 不配置-c只返回topic列表,增加-c返回clusterName, topic, consumerGroup信息,即topic的所属集群和订阅关系,没有参数
    -n NameServer 服务地址,格式 ip:port
    topicRoute 查看 Topic 路由信息 -t topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    topicStatus 查看 Topic 消息队列offset -t topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    topicClusterList 查看 Topic 所在集群列表 -t topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    updateTopicPerm 更新 Topic 读写权限 -t topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -b Broker 地址,表示 topic 所在 Broker,只支持单台Broker,地址为ip:port
    -p 指定新 topic 的读写权限( W=2|R=4|WR=6 )
    -c cluster 名称,表示 topic 所在集群(集群可通过 clusterList 查询),-b优先,如果没有-b,则对集群中所有Broker执行命令
    updateOrderConf 从NameServer上创建、删除、获取特定命名空间的kv配置,目前还未启用 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic,键
    -v orderConf,值
    -m method,可选get、put、delete
    allocateMQ 以平均负载算法计算消费者列表负载消息队列的负载结果 -t topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -i ipList,用逗号分隔,计算这些ip去负载Topic的消息队列
    statsAll 打印Topic订阅关系、TPS、积累量、24h读写总量等信息 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -a 是否只打印活跃topic
    -t 指定topic
    #### 2.2 集群相关
    名称 含义 命令选项 说明
    clusterList 查看集群信息,集群、BrokerName、BrokerId、TPS等信息 -m 打印更多信息 (增加打印出如下信息 #InTotalYest, #OutTotalYest, #InTotalToday ,#OutTotalToday)
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -i 打印间隔,单位秒
    clusterRT 发送消息检测集群各Broker RT。消息发往${BrokerName} Topic。 -a amount,每次探测的总数,RT = 总时间 / amount
    -s 消息大小,单位B
    -c 探测哪个集群
    -p 是否打印格式化日志,以|分割,默认不打印
    -h 打印帮助
    -m 所属机房,打印使用
    -i 发送间隔,单位秒
    -n NameServer 服务地址,格式 ip:port
    #### 2.3 Broker相关
    名称 含义 命令选项 说明
    updateBrokerConfig 更新 Broker 配置文件,会修改Broker.conf -b Broker 地址,格式为ip:port
    -c cluster 名称
    -k key 值
    -v value 值
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    brokerStatus 查看 Broker 统计信息、运行状态(你想要的信息几乎都在里面) -b Broker 地址,地址为ip:port
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    brokerConsumeStats Broker中各个消费者的消费情况,按Message Queue维度返回Consume Offset,Broker Offset,Diff,TImestamp等信息 -b Broker 地址,地址为ip:port
    -t 请求超时时间
    -l diff阈值,超过阈值才打印
    -o 是否为顺序topic,一般为false
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    getBrokerConfig 获取Broker配置 -b Broker 地址,地址为ip:port
    -n NameServer 服务地址,格式 ip:port
    wipeWritePerm 从NameServer上清除 Broker写权限 -b BrokerName
    -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    addWritePerm 从NameServer上添加 Broker写权限 -b BrokerName
    -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    cleanExpiredCQ 清理Broker上过期的Consume Queue,如果手动减少对列数可能产生过期队列 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -b Broker 地址,地址为ip:port
    -c 集群名称
    deleteExpiredCommitLog 清理Broker上过期的CommitLog文件,Broker最多会执行20次删除操作,每次最多删除10个文件 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -b Broker 地址,地址为ip:port
    -c 集群名称
    cleanUnusedTopic 清理Broker上不使用的Topic,从内存中释放Topic的Consume Queue,如果手动删除Topic会产生不使用的Topic -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -b Broker 地址,地址为ip:port
    -c 集群名称
    sendMsgStatus 向Broker发消息,返回发送状态和RT -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -b BrokerName,注意不同于Broker地址
    -s 消息大小,单位B
    -c 发送次数
    #### 2.4 消息相关
    名称 含义 命令选项 说明
    queryMsgById 根据offsetMsgId查询msg,如果使用开源控制台,应使用offsetMsgId,此命令还有其他参数,具体作用请阅读QueryMsgByIdSubCommand。 -i msgId
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    queryMsgByKey 根据消息 Key 查询消息 -k msgKey
    -t Topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    queryMsgByOffset 根据 Offset 查询消息 -b Broker 名称,(这里需要注意 填写的是 Broker 的名称,不是 Broker 的地址,Broker 名称可以在 clusterList 查到)
    -i query 队列 id
    -o offset 值
    -t topic 名称
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    queryMsgByUniqueKey 根据msgId查询,msgId不同于offsetMsgId,区别详见常见运维问题。-g,-d配合使用,查到消息后尝试让特定的消费者消费消息并返回消费结果 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -i uniqe msg id
    -g consumerGroup
    -d clientId
    -t topic名称
    checkMsgSendRT 检测向topic发消息的RT,功能类似clusterRT -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic名称
    -a 探测次数
    -s 消息大小
    sendMessage 发送一条消息,可以根据配置发往特定Message Queue,或普通发送。 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic名称
    -p body,消息体
    -k keys
    -c tags
    -b BrokerName
    -i queueId
    consumeMessage 消费消息。可以根据offset、开始&结束时间戳、消息队列消费消息,配置不同执行不同消费逻辑,详见ConsumeMessageCommand。 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic名称
    -b BrokerName
    -o 从offset开始消费
    -i queueId
    -g 消费者分组
    -s 开始时间戳,格式详见-h
    -d 结束时间戳
    -c 消费多少条消息
    printMsg 从Broker消费消息并打印,可选时间段 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic名称
    -c 字符集,例如UTF-8
    -s subExpress,过滤表达式
    -b 开始时间戳,格式参见-h
    -e 结束时间戳
    -d 是否打印消息体
    printMsgByQueue 类似printMsg,但指定Message Queue -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -t topic名称
    -i queueId
    -a BrokerName
    -c 字符集,例如UTF-8
    -s subExpress,过滤表达式
    -b 开始时间戳,格式参见-h
    -e 结束时间戳
    -p 是否打印消息
    -d 是否打印消息体
    -f 是否统计tag数量并打印
    resetOffsetByTime 按时间戳重置offset,Broker和consumer都会重置 -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -g 消费者分组
    -t topic名称
    -s 重置为此时间戳对应的offset
    -f 是否强制重置,如果false,只支持回溯offset,如果true,不管时间戳对应offset与consumeOffset关系
    -c 是否重置c++客户端offset
    #### 2.5 消费者、消费组相关
    名称 含义 命令选项 说明
    consumerProgress 查看订阅组消费状态,可以查看具体的client IP的消息积累量 -g 消费者所属组名
    -s 是否打印client IP
    -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    consumerStatus 查看消费者状态,包括同一个分组中是否都是相同的订阅,分析Process Queue是否堆积,返回消费者jstack结果,内容较多,使用者参见ConsumerStatusSubCommand -h 打印帮助
    -n NameServer 服务地址,格式 ip:port
    -g consumer group
    -i clientId
    -s 是否执行jstack
    updateSubGroup 更新或创建订阅关系 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -b Broker地址
    -c 集群名称
    -g 消费者分组名称
    -s 分组是否允许消费
    -m 是否从最小offset开始消费
    -d 是否是广播模式
    -q 重试队列数量
    -r 最大重试次数
    -i 当slaveReadEnable开启时有效,且还未达到从slave消费时建议从哪个BrokerId消费,可以配置备机id,主动从备机消费
    -w 如果Broker建议从slave消费,配置决定从哪个slave消费,配置BrokerId,例如1
    -a 当消费者数量变化时是否通知其他消费者负载均衡
    deleteSubGroup 从Broker删除订阅关系 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -b Broker地址
    -c 集群名称
    -g 消费者分组名称
    cloneGroupOffset 在目标群组中使用源群组的offset -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -s 源消费者组
    -d 目标消费者组
    -t topic名称
    -o 暂未使用
    #### 2.6 连接相关
    名称 含义 命令选项 说明
    consumerConnection 查询 Consumer 的网络连接 -g 消费者所属组名
    -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    producerConnection 查询 Producer 的网络连接 -g 生产者所属组名
    -t 主题名称
    -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    #### 2.7 NameServer相关
    名称 含义 命令选项 说明
    updateKvConfig 更新NameServer的kv配置,目前还未使用 -s 命名空间
    -k key
    -v value
    -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    deleteKvConfig 删除NameServer的kv配置 -s 命名空间
    -k key
    -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    getNamesrvConfig 获取NameServer配置 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    updateNamesrvConfig 修改NameServer配置 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    -k key
    -v value
    #### 2.8 其他
    名称 含义 命令选项 说明
    startMonitoring 开启监控进程,监控消息误删、重试队列消息数等 -n NameServer 服务地址,格式 ip:port
    -h 打印帮助
    ### 3 运维常见问题 #### 3.1 RocketMQ的mqadmin命令报错问题 > 问题描述:有时候在部署完RocketMQ集群后,尝试执行“mqadmin”一些运维命令,会出现下面的异常信息: > > ```java > org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to failed > ``` 解决方法:可以在部署RocketMQ集群的虚拟机上执行`export NAMESRV_ADDR=ip:9876`(ip指的是集群中部署NameServer组件的机器ip地址)命令之后再使用“mqadmin”的相关命令进行查询,即可得到结果。 #### 3.2 RocketMQ生产端和消费端版本不一致导致不能正常消费的问题 > 问题描述:同一个生产端发出消息,A消费端可消费,B消费端却无法消费,rocketMQ Console中出现: > > ```java > Not found the consumer group consume stats, because return offset table is empty, maybe the consumer not consume any message的异常消息。 > ``` 解决方案:RocketMQ 的jar包:rocketmq-client等包应该保持生产端,消费端使用相同的version。 #### 3.3 新增一个topic的消费组时,无法消费历史消息的问题 > 问题描述:当同一个topic的新增消费组启动时,消费的消息是当前的offset的消息,并未获取历史消息。 解决方案:rocketmq默认策略是从消息队列尾部,即跳过历史消息。如果想消费历史消息,则需要设置:`org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#setConsumeFromWhere`。常用的有以下三种配置: - 默认配置,一个新的订阅组第一次启动从队列的最后位置开始消费,后续再启动接着上次消费的进度开始消费,即跳过历史消息; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); ``` - 一个新的订阅组第一次启动从队列的最前位置开始消费,后续再启动接着上次消费的进度开始消费,即消费Broker未过期的历史消息; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); ``` - 一个新的订阅组第一次启动从指定时间点开始消费,后续再启动接着上次消费的进度开始消费,和consumer.setConsumeTimestamp()配合使用,默认是半个小时以前; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); ``` #### 3.4 如何开启从Slave读数据功能 在某些情况下,Consumer需要将消费位点重置到1-2天前,这时在内存有限的Master Broker上,CommitLog会承载比较重的IO压力,影响到该Broker的其它消息的读与写。可以开启`slaveReadEnable=true`,当Master Broker发现Consumer的消费位点与CommitLog的最新值的差值的容量超过该机器内存的百分比(`accessMessageInMemoryMaxRatio=40%`),会推荐Consumer从Slave Broker中去读取数据,降低Master Broker的IO。 #### 3.5 性能调优问题 异步刷盘建议使用自旋锁,同步刷盘建议使用重入锁,调整Broker配置项`useReentrantLockWhenPutMessage`,默认为false;异步刷盘建议开启`TransientStorePoolEnable`;建议关闭transferMsgByHeap,提高拉消息效率;同步刷盘建议适当增大`sendMessageThreadPoolNums`,具体配置需要经过压测。 #### 3.6 在RocketMQ中msgId和offsetMsgId的含义与区别 使用RocketMQ完成生产者客户端消息发送后,通常会看到如下日志打印信息: ```java SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD0000, offsetMsgId=0A42333A00002A9F000000000134F1F5, messageQueue=MessageQueue [topic=topicTest1, BrokerName=mac.local, queueId=3], queueOffset=4] ``` - msgId,对于客户端来说msgId是由客户端producer实例端生成的,具体来说,调用方法`MessageClientIDSetter.createUniqIDBuffer()`生成唯一的Id; - offsetMsgId,offsetMsgId是由Broker服务端在写入消息时生成的(采用”IP地址+Port端口”与“CommitLog的物理偏移量地址”做了一个字符串拼接),其中offsetMsgId就是在RocketMQ控制台直接输入查询的那个messageId。 ================================================ FILE: docs/cn/proxy/deploy_guide.md ================================================ # RocketMQ Proxy部署指南 ## 概述 RocketMQ Proxy 支持两种代理模式: `Local` and `Cluster`。 ## 配置 该配置适用于 `Cluster` 和 `Local` 两种模式, 默认路径为 `distribution/conf/rmq-proxy.json`。 ## `Cluster` 模式 * 设置 `nameSrvAddr` * 设置 `proxyMode` 为 `cluster` (不区分大小写) 运行以下命令: ```shell nohup sh mqproxy & ``` 该命令仅会启动 `Proxy` 组件本身。它假设已经在指定的 `nameSrvAddr` 地址上运行着 `Namesrv` 节点,同时也有 broker 节点通过 `nameSrvAddr` 注册自己并运行。 ## `Local` 模式 * 设置 `nameSrvAddr` * 设置 `proxyMode` 为 `local` (不区分大小写) 运行以下命令: ```shell nohup sh mqproxy & ``` 上面的命令将启动`Proxy`,并在同一进程中运行`Broker`。它假设`Namesrv`节点正在按照`nameSrvAddr`指定的地址运行。 ================================================ FILE: docs/cn/rpc_request.md ================================================ # “Request-Reply”特性 --- ## 1 使用场景 随着服务规模的扩大,单机服务无法满足性能和容量的要求,此时需要将服务拆分为更小粒度的服务或者部署多个服务实例构成集群来提供服务。在分布式场景下,RPC是最常用的联机调用的方式。 在构建分布式应用时,有些领域,例如金融服务领域,常常使用消息队列来构建服务总线,实现联机调用的目的。消息队列的主要场景是解耦、削峰填谷,在联机调用的场景下,需要将服务的调用抽象成基于消息的交互,并增强同步调用的这种交互逻辑。为了更好地支持消息队列在联机调用场景下的应用,rocketmq-4.6.0推出了“Request-Reply”特性来支持RPC调用。 ## 2 设计思路 在rocketmq中,整个同步调用主要包括两个过程: (1)请求方生成消息,发送给响应方,并等待响应方回包; (2)响应方收到请求消息后,消费这条消息,并发出一条响应消息给请求方。 整个过程实质上是两个消息收发过程的组合。所以这里最关键的问题是如何将异步的消息收发过程构建成一个同步的过程。其中主要有两个问题需要解决: ### 2.1 请求方如何同步等待回包 这个问题的解决方案中,一个关键的数据结构是RequestResponseFuture。 ``` public class RequestResponseFuture { private final String correlationId; private final RequestCallback requestCallback; private final long beginTimestamp = System.currentTimeMillis(); private final Message requestMsg = null; private long timeoutMillis; private CountDownLatch countDownLatch = new CountDownLatch(1); private volatile Message responseMsg = null; private volatile boolean sendRequestOk = true; private volatile Throwable cause = null; } ``` RequestResponseFuture中,利用correlationId来标识一个请求。如下图所示,Producer发送request时创建一个RequestResponseFuture,以correlationId为key,RequestResponseFuture为value存入map,同时请求中带上RequestResponseFuture中的correlationId,收到回包后根据correlationId拿到对应的RequestResponseFuture,并设置回包内容。 ![](image/producer_send_request.png) ### 2.2 consumer消费消息后,如何准确回包 (1)producer在发送消息的时候,会给每条消息生成唯一的标识符,同时还带上了producer的clientId。当consumer收到并消费消息后,从消息中取出消息的标识符correlationId和producer的标识符clientId,放入响应消息,用来确定此响应消息是哪条请求消息的回包,以及此响应消息应该发给哪个producer。同时响应消息中设置了消息的类型以及响应消息的topic,然后consumer将消息发给broker,如下图所示。 ![](image/consumer_reply.png) (2)broker收到响应消息后,需要将消息发回给指定的producer。Broker如何知道发回给哪个producer?因为消息中包含了producer的标识符clientId,在ProducerManager中,维护了标识符和channel信息的对应关系,通过这个对应关系,就能把回包发给对应的producer。 响应消息发送和一般的消息发送流程区别在于,响应消息不需要producer拉取,而是由broker直接推给producer。同时选择broker的策略也有变化:请求消息从哪个broker发过来,响应消息也发到对应的broker上。 Producer收到响应消息后,根据消息中的唯一标识符,从RequestResponseFuture的map中找到对应的RequestResponseFuture结构,设置响应消息,同时计数器减一,解除等待状态,使请求方收到响应消息。 ## 3 使用方法 同步调用的示例在example文件夹的rpc目录下。 ### 3.1 Producer ``` Message msg = new Message(topic, "", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); long begin = System.currentTimeMillis(); Message retMsg = producer.request(msg, ttl); long cost = System.currentTimeMillis() - begin; System.out.printf("request to <%s> cost: %d replyMessage: %s %n", topic, cost, retMsg); ``` 调用接口替换为request即可。 ### 3.2 Consumer 需要启动一个producer,同时在覆写consumeMessage方法的时候,自定义响应消息并发送。 ``` @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); for (MessageExt msg : msgs) { try { System.out.printf("handle message: %s", msg.toString()); String replyTo = MessageUtil.getReplyToClient(msg); byte[] replyContent = "reply message contents.".getBytes(); // create reply message with given util, do not create reply message by yourself Message replyMessage = MessageUtil.createReplyMessage(msg, replyContent); // send reply message with producer SendResult replyResult = replyProducer.send(replyMessage, 3000); System.out.printf("reply to %s , %s %n", replyTo, replyResult.toString()); } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { e.printStackTrace(); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } ``` ## 4 接口参数 4.1 public Message request(Message msg,long timeout) msg:待发送的消息 timeout:同步调用超时时间 4.2 public void request(Message msg, final RequestCallback requestCallback, long timeout) msg:待发送的消息 requestCallback:回调函数 timeout:同步调用超时时间 4.3 public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) msg:待发送的消息 selector:消息队列选择器 arg:消息队列选择器需要的参数 timeout:同步调用超时时间 4.4 public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) msg:待发送的消息 selector:消息队列选择器 arg:消息队列选择器需要的参数 requestCallback:回调函数 timeout:同步调用超时时间 4.5 public Message request(final Message msg, final MessageQueue mq, final long timeout) msg:待发送的消息 mq:目标消息队列 timeout:同步调用超时时间 4.6 public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) msg:待发送的消息 mq:目标消息队列 requestCallback:回调函数 timeout:同步调用超时时间 ================================================ FILE: docs/cn/statictopic/RocketMQ_Static_Topic_Logic_Queue_设计.md ================================================ ### Version 记录 | 时间 | 主要内容 | 作者 | | --- | --- | --- | | 2021-11-01 | 初稿,包括背景、目标、SOT定义与持久化、SOT生命周期、SOT的使用、API逻辑修改、问题与风险 | dongeforever | | 2021-11-15 | 修改 LogicQueue 的定义,不要引入新字段,完全复用旧的MessageQueue;RemappingStaticTopic时,不要迁移『位点』『幂等数据』等,而是采用Double-Check-Read 的机制| dongforever | | 2021-12-01 | 更新问题与风险,增加关于一致性、OutOfRange、拉取中断的详细说明| dongforever | | 2021-12-03 | 增加代码走读的说明| dongforever | | 2021-12-10 | 引入Scope概念,保留『多集群动态零耦合』的集群设计模型 | dongforever | | 2021-12-23 | 梳理待完成事项;讨论Admin接口的适配方式 | dongforever | | 2021-01-05 | Offset存储改成『转换制』,以更好适配原有逻辑 | dongforever | 中文文档在描述特定专业术语时,仍然使用英文。 ### 需求背景 StaticTopic/LogicQueue 本质上是解决『固定队列数量』的需求。 这个需求是不是必需的呢,如果是做应用集成,则可能不是必需的,但如果是做数据集成,则是必需的。 固定队列数量,首先可以解决『顺序性』的问题。 在应用集成场景下,应用是无需感知到队列的,只要MQ能保证按顺序投递给应用即可,MQ底层队列数量如何变化,对应用来说是不关心。比如,MQ之前的那套『禁读禁写』就是可以玩转的。 但在数据集成场景中,队列或者叫『分片』,是要暴露给客户端的,客户端所有的数据计算场景,都是基于『分片』来进行的,如果『分片』里的数据,发生了错乱,则计算结果都是错误的。比如,计算WordCount,源数据经过预处理之后,按key写入清洗后的Topic,然后计算侧根据清洗的结果,按照分片来并行计算。如果分片发生变化,则整个清洗逻辑,需要重新处理。 有人可能会反驳,说计算组件清洗后,可以以批的方式写入其它存储组件。这当然是可以的,但如果是这样,MQ的价值就纯粹是一个『源头』价值,而不是『通道』价值。 MQ要想成为一个『数据通道』,则必需要具备可以让计算组件『回写』数据的能力,具备存储『Clean Data』的能力,这样才让MQ有可能在数据集成领域站稳脚跟。 如果是 RocketMQ Streams 这种轻量化的组件,则『回写』会更频繁,更重要。 除此之外,『固定队列数据』对于,RocketMQ 自身后续的发展,也是至关重要的: - compact topic,如果不能做到严格按key hash,则这个KV系统是有问题的 - 事务或者其它Coordinator的实现,采用『固定队列数量』,可以选取到准确的Broker来充当协调器 - Metadata 的存储,按key hash,那么就可以在Broker上,存储千万级的 Topic 『固定队列数量』对于RocketMQ挺进『数据集成』这个领域,有着不可或缺的作用。 LogicQueue的思路就是为了解决这一问题。 ### 设计目标 #### 总体目标 提供『Static Topic』的特性。 引入以下核心概念: - physical message queue, physical queue for short, a shard bound to a specified broker. - logic message queue, logic queue for short, a shard vertically composed by physical queues. - dynamic sharded topic, dynamic topic for short, which has queues increasing with the broker numbers. - static sharded topic, static topic for short, which has fixed queues, implemented with logic queues. 『Static Topic』拥有固定的分片数量,每个分片称之为『Logic Queue』。 每个『Logic Queue』由多个『Physical Queue』进行纵向分段映射组成。 引入以下非核心概念,对用户无感知,但对于讨论问题非常重要: - Leader Queue, 某个『Logic Queue』最新映射的『Physical Queue』,也即可写的那个Queue - Second Leader Queue,某个『Logic Queue』次新映射的『Physical Queue』,也即最新一次切换之前的『Leader Queue』 #### Scope 目标 单集群固定 和 全网固定,参考 [The_Scope_Of_Static_Topic](The_Scope_Of_Static_Topic.md)。 #### LogicQueue 目标 在客户端,LogicQueue 与 Physical Queue 使用体感上没有任何区别,使用一样的概念和对象,遵循一样的语义。 在服务端,针对 LogicQueue 去适配相关的API。 #### 队列语义 RocketMQ Physical Queue 含有以下语义: - 队列内的Offset,单调递增且连续 - 属于同一个 Broker 上的队列,编号单调递增且连续 LogicQueue 需要保障的语义: - 队列内的offset,单调递增 LogicQueue 可以不保障的语义: - 队列内的 offset 连续 - 属于同一个 Broker 上的队列,编号单调递增且连续 offset连续,是一个应该尽量保证的语义,可以允许有少量空洞,但不应该出现大面积不连续的位点。 offset不连续最直接的问题就是: - 计算Lag会比较麻烦 - 不方便客户端进行各种优化计算(比如切批等) 但只要空洞不是大量频繁出现的,那么也是问题不大的。 单机队列编号连续,除了在注册元数据时,可以简约部分字节外,没有其它实际用处,可以不保证。 当前客户端使用到『单机队列编号连续』这个特点的场景主要有: - 客户端在获取到TopicRouteData后,转化成MessageQueue时,利用编号进行遍历 #### LogicQueue 定义 当前 MessageQueue 的定义如下 ``` private String topic; private String brokerName; private int queueId; ``` LogicQueue需要对客户直接暴露,为了保证使用习惯一致,采用同样的定义,其中 queueId相当于全局Id,而brokerName 固定如下: ``` MixAll.LOGICAL_QUEUE_MOCK_BROKER_NAME = "__logical_queue_broker__"; ``` 此时,brokerName没有实际含义,但可以用来识别是否是LogicQueue。 采用此种定义,对于客户端内部的实现习惯改变如下: - **无法直接根据 brokerName 找到addr,而是需要根据 MessageQueue 找到 addr** 具体改法是MQClientInstance中维护一个映射关系 ``` private final ConcurrentMap> topicEndPointsTable = new ConcurrentHashMap<>(); ``` 基本目标与定义清楚了,接下来的设计,从 Source of Truth 开始。 ### SOT 定义和持久化 LogicQueue 的 Source of Truth 就是 LogicQueue 到 Physical Queue 的映射关系。 只要这个映射关系不丢失,则整个系统的状态都是可以恢复的。 反之,整个系统可能陷入混乱。 这个SOT,命名为 TopicQueueMapping。 #### Mapping Schema ``` { "version":"1", "bname": "broker02" //标记这份数据的原始存储位置,如果发送误拷贝,可以利用这个字段来进行标识 "epoch": 0, //标记修改版本,用来做一致性校验 "totalQueues":"50", //当前Topic 总共有多少 LogicQueues "hostedQueues": { //当前Broker 所拥有的 LogicQueues "3" : [ { "queue":"0", "bname":"broker01" "gen":"0", //标记切换代次 "logicOffset":"0", //logicOffset的起始位置 "startOffset":"0", // 物理offset的起始位置 "endOffset":"1000" // 可选,物理offset的最大位置,可以根据上下文算出来 "timeOfStart":"1561018349243" //可选,用来优化时间搜索 "timeOfEnd":"1561018349243" //可选,用来优化时间搜索 "updateTime":"1561018349243" //可选,记录更新的时间 }, { "queue":"0", "bname":"broker02", "gen":"1", //可选,标记切换代次 "logicOffset":"1000", //logicOffset的起始位置 "startOffset":"0", // 物理offset的起始位置 "endOffset":"-1" // 可选,物理offset的最大位置,可以根据上下文算出来,最新的一个应该是活跃的 "timeOfStart":"1561018349243" //可选,用来优化时间搜索 "timeOfEnd":"1561018349243" //可选,用来优化时间搜索 "updateTime":"1561018349243" //可选,记录更新的时间 } ] } } ``` 上述示例的含义是: * broker02 拥有 LogicQueue 3 * LogicQueue 3 由 2 个 Physical Queue 组成 * 位点范围『0-1000』映射到 Physical Queue 『broker01-0』上面 * 位点范围『1000-』映射到 Physical Queue 『broker02-0』上面 『拥有』的定义是指,Leader Queue 在当前Broker。注意,在实现时,也会把Second Leader Queue存储下来作为备份。 注意以下要点: - 这个数据量会很大,后续需要考虑进行压缩优化(大部分字段可以压缩) - 如果将来利用 LogicQueue 去做 Serverless 弹缩,则这个数据会加速膨胀,对这个数据的利用要谨慎 - 要写上 bname,以具备自我识别能力 #### Leader Completeness RocketMQ 没有中心化的元数据存储,那就遵循『Leader Completeness』原则。 对于每个逻辑队列,把所有映射关系存储在『最新队列所在的Broker上面』,最新队列,其实也是可写队列。 Leader Completeness,避免了数据的切割,对于后续其它操作有极大的便利。 #### Global Epoch Check 对于每个Static Topic,在每个Broker都应该拥有一份『TopicQueueMapping』,每份都带有Epoch。 在创建和更新时,要对已有数据进行完备性校验,如果发现不完备,则说明上次操作失败,或者部分Broker数据丢失,应该先修复再操作。 注意: 即使当前Broker不拥有任何 LogicQueue 或者 PhysicalQueue,也应该存储一份,以做校验。 假设某个Static Topic只拥有1个Logic Queue,而对应的Broker整好宕机,则此时可以根据其它Broker的信息判断出该Topic不完备。 #### File Isolation 由于 RocketMQ 很多的运维习惯,都是直接拷贝 Topics.json 到别的机器进行部署的。 而 TopicQueueMapping 是 Broker 相关的,如果把 TopicQueueMapping 从一个Broker拷贝到另一个Broker,则会造成SOT冲突。 在设计上,TopicQueueMapping 采取独立文件,避免冲突。 在格式上,queue 里面要写上 bname,以具备自我识别能力,这样即使误拷贝到另一台机器,可以识别并报错,进行忽略即可。 ### SOT 生命周期 #### 创建和更新 映射关系的创建,第一期应该只由 MQAdmin 来进行操作。 后续,可以考虑引入自动化组件。 这里的要点是: - TopicConfig 和 TopicQueueMapping 分开存储,但写入时,需要先写 TopicQueueMapping 再写 TopicConfig(SOT先写) - 【加强校验】需要在 TopicConfig 里面加上一个字段来标识『LogicQueue』的 Topic - 【加强校验】允许单独更新 TopicConfig,但要带上 TotalQueues 这些基础数据 - 允许更新单 LogicQueue 更多细节在API逻辑修改里面 #### 存储 按照 『Leader Completeness』原则进行存储。 #### 切换 如果为了保证严格顺序,则应该采取『禁旧再切新』的原则: - 从旧 Leader 所在 Broker 获取信息,进行计算 - 写入旧 Leader,也即禁写旧 Leader - 写入新 Leader 如果为了保证最高可用性,则应该采取『切新禁旧再切新』: - 从旧 Leader 所在 Broker 获取信息,进行计算 - 写入新 Leader,保证新 Leader 可写,此时 logicOffset 未定 - 写入旧 Leader,禁写旧 Leader - 更新新 Leader,确定 logicOffset 切换失败处理: - 不管哪种方式,数据存储至少成功了1份,后续可以手工恢复 #### 清除 有两部分信息需要清除 - 旧 Broker 上超过2代的映射关系进行清除 - 对于单个LogicQueue,清除已经过期的 Broker Queue 映射项目 ### SOT 的使用 #### Broker 注册数据 SOT存储在Broker上,所以使用从 Broker开始。 在 RegisterBrokerBody 中,需要带上两个信息: - 对于每个Topic,带上本机队列编号和逻辑队列编号的映射关系,也即queueMap - 对于每个Topic,需要带上 totalQueueNum 这个信息 异常情况需要考虑,假如本 Broker 不拥有任何 LogicQueue 呢?依然需要带上 totalQueueNum 这个信息。 注意,不需要带上所有的映射关系,否则Nameserver很快会被打爆。 #### Nameserver 组装数据 原先的 QueueData 增加2个字段: - totalQueues,标识总逻辑队列数 - queueMap,也即本机队列编号和逻辑队列编号的映射关系 如果 QueueData 里面 totalQueues 的值 > 0 则认为是逻辑队列,在客户端解析时要进行判断。 遗留问题: 是否需要尊重 readQueueNums 和 writeQueueNums ? 在LogicQueue这里,这个场景是没有意义的,但依然保持尊重。 #### Client 解析数据 改动两个方法即可: - topicRouteData2TopicPublishInfo - topicRouteData2TopicSubscribeInfo 注意,逻辑队列要求队列数是固定,如果发现,解析完之后,存在部分队列空洞,要用虚拟Broker值进行补全。 Producer 侧如果要对无 key 场景进行优化,可以通过虚拟Broker值来判断,当前队列是不可用的。 对于key场景,应该让客户端报错。 ### API 逻辑修改 #### Tools LogicQueue是为了解决『Static Sharding』的问题。对于客户来说,『LogicQueue』是手段,『Static』才是目的。本着『用户知晓目的,开发者才需要关心手段』的原则,对用户应该只暴露『Static』的概念。所有QueueMapping的生命周期维护,应该都对用户透明。 #### UpdateStaticTopic 新增UpdateStaticTopic命令,对应RequestCode.UPDATE_AND_CREATE_STATIC_TOPIC=513,主要参数是: - -t,topic 名字 - -qn,总队列数量 - -c cluster列表 或者 -b broker列表 UpdateStaticTopic 命令会自动计算预期的分布情况,包括但不限于执行以下逻辑: - 检测Topic的命名冲突 - 检测旧数据的一致性 - 创建必要的物理队列 - 创建必要的映射关系 #### RemappingStaticTopic 迁移动作不引入新命令,计算好之后,执行UPDATE_AND_CREATE_STATIC_TOPIC即可. 主要参数: - -t, topic 名字 - -c cluster列表 或者 -b broker列表 基本操作流程: - 1 获取旧Leader,计算映射关系 - 2 迁移『位点』『幂等数据』等 - 3 映射关系写入新/旧 Leader 其中第二步,数据量可能会很大,导致迁移周期非常长,且不是并发安全的。 但这些数据,都是覆盖型的,因此可以改造成不迁移的方式,而是在API层做兼容,也即『Double-Read-Check』机制: - 读取数据时,先从 Leader 读,如果Leader没有,则从Sub-Leader读取。 - 提交数据,直接在 Leader 层面操作即可,覆盖旧数据。 将来实现的幂等逻辑,也是类似。 #### UpdateTopic 服务端判断『StaticTopic』,禁止该命令进行修改。 #### DeleteTopic 复用现有逻辑,对于 StaticTopic,执行必要的清除动作。 #### TopicStatus 复用现有逻辑,同时展现『Logical Queue』和『Physical Queue』。 #### Broker #### pullMessage 分段映射,执行远程读,在返回消息时,不进行offset转换,而是返回 OffsetDelta 变量,由客户端进行转换。 这里的方式,类似于Batch。 #### getMinOffset 寻找映射关系,读最早的队列的MinOffset #### getMaxOffset 本机读,转换成logicOffset即可。 #### getOffsetByTime 需要分段查找。 如果要优化查找速度,应该在映射关系里面,插入时间戳。 #### consumerOffsets 系列 Offset的存储,进行转换,存储在对应PhysicalQueue 所在的 Broker上面。 读取时,采取『Double-Read-Check』机制,并进行转换。 这样可以最大程度与 PhysicalQueue 的相关逻辑进行适配,比如 ConsumerProgress 可以看到『最近拉取时间』。 #### Client - MQClientInstance.topicRouteData2TopicXXXInfo,修改解析 TopicRouteData的逻辑 - Consumer解压消息时,需要加上OffsetDelta逻辑 #### SDK 兼容性分析 如果要使用StaticTopic,则需要升级Client、Broker、Nameserver。 ### 问题与风险 #### 数据一致性问题 RocketMQ 没有引入中心化的存储组件,那么如何保证 SOT 的全局一致性呢? 主要利用两个信息 * TopicQueueMapping 带上的 epoch * TopicQueueMapping 带上 totalQueues 在更新或者切换时,获取所有Broker上的 TopicQueueMapping,校验 epoch 和 totalQueues,并且根据 TopicQueueMapping 可以完整地构建出对应的Logic Queue,则说明数据是完整一致的。 如果发现数据不一致,可能是以下因素引入的: * 集群中有Broker宕机 * 上次更新没有完全成功 应该要先修复数据,再执行 更新或切换 操作 #### No Target Brokers Target Brokers 是指拥有 LogicQueue 的 Broker。 考察1个场景,如果某个Topic 只有1个 LogicQueue,而拥有这个 LogicQueue 的 Broker 正好宕机了。此时去更新 Topic,会不会误认为该 Topic 不存在? 解决这个问题的办法是引入 No Target Brokers,也即集群中除去『Target Brokers』之外的 Broker。 对于 No Target Broker,依然需要写入一份 TopicQueueMapping,带上 epoch 和 totalQueues,但不拥有任何 LogicQueue。 有了这个信息之后,在进行一致性校验时,就可以识别出上述场景。 尤其要注意,如果 Nameserver 中没有任何信息,则需要主动去所有 Broker 拉取一遍。 #### 切换时最新 LogicQueueMappingItem 的 logicOffset 决策问题 logicOffset的决策,依赖于上一个 PhysicalQueue 的最大位点。 此时,要么跳跃位点,要么等待上一个 PhysicalQueue 确保已经禁写。 当前实现,为了保障高可用,采用『切新禁旧再切新』的方式,同时跳跃位点。 #### logicOffset 为 -1 时的处理 此时,可以写,但返回给 客户端的 offset 也是-1。 此时,不可以读最新 PhysicalQueue。 需要非常小心触发位点被重置: - 忽略logicOffset为 -1 的item - 计算staticOffset时,如果发现logicOffset为-1,则报错 目前只允许,SendMessage和GetMin时,返回-1。其余场景,要严格校验并报错。 #### 队列重复映射 如果允许1个 PhysicalQueue 被重复利用,也即多段映射给多个 LogicQueue,或者从非0开始映射。 会带来以下麻烦: * 所有位点相关的API,需要考虑 MappingItem endOffset,因为超过了 endOffset 可能已经不属于 当前 LogicQueue 了 * 新建 MappingItem,需要先获取 旧 MappingItem 的 endOffset 当前实现,为了保证简洁,禁止 PhysicalQueue 被重复利用,每次更新映射都会让物理层面的 writeQueues++ 和 readQueues++。 后续实现,可以考虑复用已经被清除掉的Physical,也即已经没有数据,位点从0开始。 #### 备机更新映射 当前,admin操作都是要求在Master操作的。因此,没有这个问题。 Command操作时,提前预判Master是否存在,如果不存在,则提前报错,减少中间失败率。 #### 拉取消息时 OutOfRange 的判断 以下情况会影响 OutOfRange 的判断 * 从备机拉取消息(默认不会返回OFFSET_MOVED) * 中间 MappingItem 因为Commitlog的提前删除导致 PhysicalQueue Offset Truncation 所以,OutOfRange 的判断,遵循以下原则: * 从 Leader Item 拉取,只有requestOffset > maxOffset,尊重 OFFSET_MOVED * 从 Earliest Item 拉取,只有 requestOffset < minOffset,尊重 OFFSET_MOVED * 其它情况,都忽略 OFFSET_MOVED 如果没有恰当地处理 OFFSET_MOVED,可能造成位点被重置。 需要注意,这个地方,产生了对 PullMessageResponseHeader 中 minOffset 和 maxOffset 的强依赖。 在次此之前,这两个信息,只对客户端的限流有作用,对业务没有实际的影响。 #### 拉取消息时的 中断问题 当1个 PhysicalQueue 被拉取干净时,需要修正 nextBeginOffset 到下一个 PhysicalQueue。 如果没有处理好,则直接会导致拉取中断,无法前进。 #### pullResult 位点由谁设置的问题 类似于Batch,由客户端设置,避免服务端解开消息: 在PullResultExt中新增字段 offsetDelta。 #### Admin接口与User接口的适配方式区别 User 接口,使用范围广泛如多语言等,应该尽可能简单,把适配逻辑做在服务端,对『客户端』透明。 那么 Admin 接口呢,比如 examineTopicStats,适配逻辑是做在『服务端』还是『客户端』? 一个 Admin 接口,通常需要访问所有 Broker 的所有队列。 如果做在服务端,则可能产生交叉访问,在极端情况下,性能会非常差,举个例子: 100 个 Broker,相互交叉映射过一遍,则Admin客户端首先要向 100 个 Broker 发请求,然后这 100 个 Broker 为了获取远程信息,各自向其余 Broker 发请求。 其实际网络请求数就是 100 * 100 = 10000 个网络请求。放大效应十分明显。 同时,考虑到 Admin 接口,使用范围是有限的,无需考虑多语言适配等问题,可以把适配逻辑做在 Admin 客户端。 #### 远程读的性能问题 从实战经验来看,性能损耗几乎不计。 #### 使用习惯的改变 利用新的创建命令进行隔离。 #### 消费SendBack问题 目前的实现里,消费Send Back,是直接传回Commitlog Pos,这个在LogicQueue里行不通。 需要修改API,改成传回『Logic Queue Offset』。 #### 二阶消息的兼容性 二阶消息,也即『原始消息』存储在『系统Topic』中,需要经过一轮『Read-ReWrite』逻辑才会被用户看见的消息。 例如,定时消息,事务消息。 二阶消息需要支持远程读写操作。 一期的LogicQueue不支持『二阶消息』。 ### 待完成事项 #### 阻止旧客户端的请求 旧的客户端无法解析逻辑路由,但可以识别物理路由。如果错误使用,则会影响映射关系的准确性。 #### 阻止Pop模式、事务消息、定时消息使用 LogicQueue 不兼容 事务消息和定时消息。 LogicQueue 当前不支持Pop模式消费。 #### Nameserver 相关生命周期完善 目前没有处理Nameserver中Mapping数据的生命周期 #### ConsumeQueue 的 correctMinOffset 逻辑存在缺陷 可能导致 LogicQueue 无法清除已经过期的 MappingItem。 #### getOffsetInQueueByTime 语义有缺陷 可能导致 LogicQueue 时间搜索不准确。需要专项修复。 #### Metadata 更新机制 当前的更新机制太慢。且访问『不存在Broker』时,会频繁访问Nameserver,有打爆Nameserver的风险。 #### examineConsumeStats 接口获取不到『最近消费时间』 位点相关的消息可能不在本机,需要远程访问。 #### resetOffset 需要适配 当前没有适配。重置位点,可能会得到不符合预期的结果。 #### MessageQueue 没有被物理清除 当前只是产生遗留数据,占用一点点存储空间,没有太大影响。 如果将来要实现 物理 Queue 复用,则需要先完善相关逻辑。 ### 代码走读要点 #### Admin 入口 主要看两个类: UpdateStaticTopicSubCommand RemappingStaticTopicSubCommand #### Metadata 入口 主要看: TopicQueueMappingManager #### Client 入口 重点关注: MQClientInstance.updateTopicRouteInfoFromNameServer #### Server 入口 以 SendMessageProcessor 为例,插桩代码普遍是以下风格: ``` TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader, true); RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); if (rewriteResult != null) { return CompletableFuture.completedFuture(rewriteResult); } ``` 其它Processor类似 #### 测试入口 rocketmq-test模块,statictopic目录。 ================================================ FILE: docs/cn/statictopic/The_Scope_Of_Static_Topic.md ================================================ ### Version 记录 | 时间 | 主要内容 | 作者 | | --- | --- | --- | | 2021-11-01 | 初稿,探讨Static Topic的Scope视线范围 | dongeforever | 中文文档在描述特定专业术语时,仍然使用英文。 ### 需求背景 RocketMQ的集群设计,是一个多集群、动态、零耦合的设计,具体体现在以下地方: - 一个 Nameserver 可以管理多个 Cluster - Broker 与 Cluster 之间是弱关联,Cluster仅仅只是一个标识符,主要在运维时使用来界定Topic的创建范围 - 开发用户对 Cluster 无感知 - 不同 Broker 之间没有任何关联 这样的设计,在运维时带来了极大的便利,但也带来了一个问题: - Topic 的队列数无法固定 基于 Logic Queue 技术而实现的 Static Topic,就是用来解决『固定队列数量』的问题。 但这个『固定』要到何种范围呢?是一个值得探讨的问题。 从理论上可以分析出来,有以下三种情况: - 单集群固定 - 多集群固定 - 全网固定 #### 单集群固定 一个 Static Topic,固定在一个 Cluster 内漂移。 不同的 Cluster 内,可以拥有相同的 Static Topic。 对应MessageQueue的Broker 命名规范为: ``` __logic__{clusterName} ``` #### 多集群固定 一个 Static Topic,固定在特定的几个 Cluster 内漂移。 没有交集的Cluster集合之间,可以拥有相同的 Static Topic。 对应MessageQueue的Broker 命名规范为: ``` __logic__{cluster1}_{cluster2}_{xxx} ``` #### 全网固定 全网是指『同一个Nameserver内』。 一个 Static Topic,不与特定Cluster绑定,同一个Nameserver内,全网漂移。 同一个Nameserver内,只有一个同名的 Static Topic。 对应MessageQueue的Broker 命名规范为: ``` __logic__global ``` #### 为什么要引入Scope 直接全网固定不就好了吗,为啥还要引入Scope呢? 主要原因是,不想完全放弃 RocketMQ 『多集群、动态、零耦合』的设计优势。 而全网固定,则意味着彻底失去了这个优势。 举1个『多活保序』的场景: - ClusterA 部署在 SiteA 内,创建 Static Topic 『TopicTest』,有50个队列。 - ClusterB 部署在 SiteB 内,创建 Static Topic 『TopicTest』,有50个队列。 对Nameserver稍作修改,支持传入标识符(比如为scope或者unitName),来获取特定范围内的 Topic Route。 正常情况下: - SiteA 的Producer和Consumer 只能看见 ClusterA 的 MessageQueue,brokerName为 "__logic__clusterA"。 - SiteB 的Producer和Consumer 只能看见 ClusterB 的 MessageQueue,brokerName为 "__logic__clusterB"。 - 机房内就近访问,且机房内严格保序。 假设 SiteA 宕机,此时对Nameserver发指令允许全网读,也即忽略客户端传入的 Scope或者unitName 标识符: - SiteB 的 Producer 仍然看见并写入 ClusterB 的 MessageQueue,brokerName为 "__logic__clusterB" - SiteB 的 Consumer 可以同时看见并读取 ClusterA 的 MessageQueue 和 ClusterB MessageQueue, brokerName为 "__logic__clusterB" 和 "__logic__clusterA - 在这种场景下,Consumer 在消费 ClusterB 数据的同时,同时去消费 ClusterA 未消费完的数据 不同地域的客户端,看见不同Scope的元数据,从而访问不同Scope的节点。 #### 全球容灾集群 RocketMQ 多个集群的元数据可以无缝在Nameserver处汇聚,同时又可以无缝地根据标识符拆分给不同地域的Producer和Consumer。 这样一个『元数据可分可合』的设计优势,是其它消息中间件所不具备的,应该值得挖掘一下。 引入以下概念: - 融合集群,共享同一个Nameserver的集群之和 - 单元集群,clusterName名字一样的集群,不同单元集群之间,物理隔离 - namespace,租户,逻辑隔离,只是命名的区别 如果单元集群部署在异地,所形成的『融合集群』,就是全球容灾集群: - 客户端引入 scope 或者 unitName 字段,默认情况,不同 scope或者unitName 获取的都是单元集群的元数据 - 顺序性,关键在于 固定Producer端可见的队列,单元内的队列是固定的,因此可以保证单元内是顺序的 - Consumer 端按照队列消费,天然是顺序的 - 正常情况下,单元内封闭,也即单元产生的数据在同单元内消费掉 - 灾难发生时,改变元数据的可见性,允许读其它 单元集群 未消费完的数据,也即跨单元读 - 跨单元读,是指读『其它clusterName』的队列,不一定是远程读,如果本单元有相应的Slave节点,则直接本地读 ### 设计目标 Static Topic 实现 单集群固定 和 全网固定 两种Scope,以适配『全球容灾集群』。 多集群,暂时没有必要。 一期只实现 全网固定 这个Scope,但在格式上注意兼容 #### SOT 增加 Scope 字段 ``` { "version":"1", "scope": "clusterA", "bname": "broker02" //标记这份数据的原始存储位置,如果发送误拷贝,可以利用这个字段来进行标识 "epoch": 0, //标记修改版本,用来做一致性校验 "totalQueues":"50", //当前Topic 总共有多少 LogicQueues } ``` scope字段: - 单集群固定,则就是 Cluster 名字 - 全网固定,则为常量『global』 ================================================ FILE: docs/en/CLITools.md ================================================ # Instructions on the use of mqadmin Management tools Before introducing the mqadmin management tool, the following points need to be declared: - The way of executing a command is:./mqadmin {command} {args} - Almost all commands need to attach the -n option to represent the nameServer address, formatted as ip:port; - Almost all commands can get help information with the -h option; - If the broker address -b option and clusterName -c option are both configured with specific values, the command execution will select the broker address specified by -b option. The value of the -b option can only be configured with a single address. The format is ip:port. The default port value is 10911. If the value of the -b option is not configured, the command will be applied to all brokers in the entire cluster. - You can see many commands under tools, but not all commands can be used, only the commands initialized in MQAdminStartup can be used, you can also modify this class, add or customize commands; - Due to the issue of version update, a small number of commands may not be updated in time, please read the related command source code to eliminate and resolve the error. ## 1 Topic related command instructions
    Name Meaning Command option Explain
    updateTopic Create or update the configuration of topic -b The -b option declares the specific address of the broker, indicating that the broker, in which the topic is located supports only a single broker and the address format is ip:port.
    -c The -c option declares the name of the cluster, which represents the cluster in which the current topic is located. (clusters are available through clusterList query)
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    -p The -p option is used to specify the read and write permission for the new topic (W=2 | R=4 | WR=6)
    -r The -r option declares the number of readable queues (default 8)
    -w The -w option declares the number of writable queues (default 8)
    -t The -t option declares the name of the topic (the name can only use characters^ [a-zA-Z0-9s -] + $)
    deleteTopic Delete the topic command -c The -c option specifies the name of the cluster, which means that one of the topic in the specified cluster is deleted (cluster names can be queried via clusterList)
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    -t The -t option declares the name of the topic (the name can only use characters^ [a-zA-Z0-9s -] + $)
    topicList View topic list information -h Print help information
    -c If the -c option is not configured, only the topic list is returned, and the addition of -c option returns additional information about the clusterName, topic, consumerGroup, that is, the cluster and subscription to which the topic belongs, and no other option need to be configured.
    -n Declare the service address of the nameServer, and the option format is ip:port
    topicRoute To view topic specific routing information -t Used to specify the name of the topic
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    topicStatus The location of the offset used to view the topic message queue -t Used to specify the name of the topic
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    topicClusterList To view the list of clusters to which topic belongs -t Used to specify the name of the topic
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    updateTopicPerm This command is used to update read and write permissions for topic -t Used to specify the name of the topic
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    -b The -b option declares the specific address of the broker, indicating that the broker, in which the topic is located supports only a single broker and the address format is ip:port.
    -p The -p option is used to specify the read and write permission for the new topic (W=2 | R=4 | WR=6)
    -c Used to specify the name of the cluster that represents the cluster in which the topic is located, which can be accessed through the clusterList query, but the -b parameter has a higher priority, and if no -b option related configuration is specified, the command is executed on all broker in the cluster
    updateOrderConf The key, value configuration that creates, deletes, and retrieves specific namespaces from nameServer is not yet enabled. -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    -t topic, key
    -v orderConf, value
    -m method, available values include get, put, delete
    allocateMQ Computing load result of load message queue in consumer list with average load algorithm -t Used to specify the name of the topic
    -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    -i IpList, is separated by commas to calculate which message queues these ip unload topic
    statsAll For printing topic subscription, TPS, cumulative amount, 24 hours read and write total, etc. -h Print help information
    -n Declare the service address of the nameServer, and the option format is ip:port
    -a Whether to print only active topic
    -t Used to specify the name of the topic
    ## 2 Cluster related command instructions ####
    Name Meaning Command option Explain
    clusterList View cluster information, cluster, brokerName, brokerId, TPS, and so on -m Print more information (add print to # InTotalYest, #OutTotalYest, #InTotalToday ,#OutTotalToday)
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -i Print interval, unit basis is seconds
    clusterRT Send message to detect each broker RT of the cluster.the message send to ${BrokerName} Topic -a amount, total number per probe, RT = Total time/amount
    -s Message size, unit basis is B
    -c Which cluster to detect.
    -p Whether to print the formatted log,split with "|", not printed by default
    -h Print help information
    -m Owned computer room for printing
    -i The interval, in seconds, at which a message is sent.
    -n Service address used to specify nameServer and formatted as ip:port
    ## 3 Broker related command instructions
    Name Meaning Command option Explain
    updateBrokerConfig The configuration information used to update the broker and the contents of the Broker.conf file are modified -b Declare the address of the broker and format as ip:port
    -c Specify the name of the cluster
    -k the value of k
    -v the value of value
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    brokerStatus For viewing broker related statistics and running status (almost all the information you want is inside) -b Declare the address of the broker and format as ip:port
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    brokerConsumeStats Get the consumption of each consumer in broker and return information such as consume Offset,broker Offset,diff,timestamp by message queue dimension -b Declare the address of the broker and format as ip:port
    -t Configure the timeout of the request
    -l Configure the diff threshold beyond which to print
    -o Specifies whether the order topic, is typically false
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    getBrokerConfig Get configuration information for the broker -b Declare the address of the broker and format as ip:port
    -n Service address used to specify nameServer and formatted as ip:port
    wipeWritePerm Clear write permissions for broker from nameServer -b Declare the BrokerName
    addWritePerm Add write permissions for broker from nameServer -b Declare the BrokerName
    -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    cleanExpiredCQ Clean up expired consume Queue on broker, An expired queue may be generated if the number of columns is reduced manually -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -b Declare the address of the broker and format as ip:port
    -c Used to specify the name of the cluster
    deleteExpiredCommitLog Clean up expired CommitLog files on broker. A maximum of 20 deletion operations can be performed, and a maximum of 10 files can be deleted each time. -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -b Declare the address of the broker and format as ip:port
    -c Used to specify the name of the cluster
    cleanUnusedTopic Clean up unused topic on broker and release topic's consume Queue from memory, If the topic is removed manually, an unused topic will be generated -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -b Declare the address of the broker and format as ip:port
    -c Used to specify the name of the cluster
    sendMsgStatus Send a message to the broker and then return the send status and RT -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -b brokerName, note that this is not broker's address
    -s Message size, the unit of account is B
    -c Number of messages sent
    ## 4 Message related command instructions ####
    Name Meaning Command option Explain
    queryMsgById Query msg according to offsetMsgId. If you use open source console, you should use offsetMsgId. There are other parameters for this command. For details, please read QueryMsgByIdSubCommand. -i msgId
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    queryMsgByKey Query messages based on message Key -k msgKey
    -t The name of the topic
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    queryMsgByOffset Query messages based on Offset -b The name of broker,(Note here: the name of broker is filled in, not the address of broker, and the broker name can be found in clusterList)
    -i Queue id of the query
    -o The value of offset
    -t The name of the topic
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    queryMsgByUniqueKey According to the msgId query, msgId is different from offsetMsgId. The specific differences can be found in common operational and maintenance problems. "-g" option and "-d" option are to be used together, and when you find the message, try to get a particular consumer to consume the message and return the result of the consumption. -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -i unique msg id
    -g consumerGroup
    -d clientId
    -t The name of the topic
    checkMsgSendRT Detect RT to send a message to topic, function similar to clusterRT -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -t The name of the topic
    -a the number of probes
    -s The size of message
    sendMessage Send a message that can be sent, as configured, to a particular message Queue, or to a normal send. -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -t The name of the topic
    -p body, message body
    -k keys
    -c tags
    -b brokerName
    -i queueId
    consumeMessage Consumer messages. You can consume messages based on offset, start timestamps, end timestamps, message queues, and configure different consumption logic for different execution, as detailed in ConsumeMessageCommand. -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -t The name of the topic
    -b brokerName
    -o Start consumption from offset
    -i queueId
    -g Group of consumers
    -s Specify a start timestamp in a format see -h
    -d Specify a end timestamp
    -c Specify how many messages to consume
    printMsg Consume messages from broker and print them, optional time periods -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -t The name of the topic
    -c Character set, for example UTF-8
    -s subExpress, filter expression
    -b Specify a start timestamp in a format see -h
    -e Specify the end timestamp
    -d Whether to print the message body
    printMsgByQueue Similar to printMsg, but specifying message queue -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -t The name of the topic
    -i queueId
    -a brokerName
    -c Character set, for example UTF-8
    -s subExpress, filter expression
    -b Specify a start timestamp in a format see -h
    -e Specify the end timestamp
    -p Whether to print a message
    -d Whether to print the message body
    -f Whether to count the number of tags and print
    resetOffsetByTime Reset consumer offset by timestamp(without client restart). -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -g Group of consumers
    -t The name of the topic
    -s Resets the offset corresponding to this timestamp in a format see -h, if you want to reset to maxOffset, the value is 'now'.
    -f Whether to force a reset, if set to false, only supports backtracking offset, if it is true, regardless of the relationship between offset and consume Offset with the timestamp
    -c Whether to reset the C++ client offset
    ## 5 Consumer and Consumer Group related command instructions ####
    Name Meaning Command option Explain
    consumerProgress To view the subscriber consumption status, you can see the amount of message accumulation for a specific client IP -g The group name of consumer
    -s Whether to print client IP
    -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    consumerStatus See the consumer status, including whether the same subscription is in the same group, analyze whether the process queue is stacked, return the consumer jstack results, more content, and see ConsumerStatusSubCommand for the user -h Print help information
    -n Service address used to specify nameServer and formatted as ip:port
    -g consumer group
    -i clientId
    -s Whether to execute jstack
    getConsumerStatus Get Consumer consumption progress -g the group name of consumer
    -t Query topic
    -i Ip address of consumer client
    -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    updateSubGroup Update or create a subscription -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -b the address of broker
    -c The name of cluster
    -g The group name of consumer
    -s Whether the group is allowed to consume
    -m Whether to start consumption from the minimum offset
    -d Is it a broadcast mode
    -q The Number of retry queues
    -r Maximum number of retries
    -i When the slaveReadEnable is on and which brokerId consumption is recommended for consumption from slave, the brokerid of slave, can be configured to consume from the slave actively
    -w If broker recommends consumption from slave, configuration determines which slave consumption to consume from, and configure a specific brokerId, such as 1
    -a Whether to notify other consumers of load balancing when the number of consumers changes
    deleteSubGroup Remove subscriptions from broker -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -b the address of broker
    -c The name of cluster
    -g The group name of consumer
    cloneGroupOffset Use the offset of the source group in the target group -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -s Source consumer group
    -d Target consumer group
    -t The name of topic
    -o Not used yet
    ## 6 Connection related command instructions ####
    Name Meaning Command option Explain
    consumerConnection Query the network connection of consumer -g The group name of consumer
    -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    producerConnection Query the network connection of producer -g the group name of producer
    -t The name of topic
    -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    ## 7 NameServer related command instructions ####
    Name Meaning Command option Explain
    updateKvConfig Update the kv configuration of nameServer, which is not currently used -s Specify a specific namespace
    -k key
    -v value
    -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    deleteKvConfig Delete the kv configuration of nameServer -s Specify a specific namespace
    -k key
    -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    getNamesrvConfig Get the configuration of the nameServer -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    updateNamesrvConfig Modifying the configuration of nameServer -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    -k The value of key
    -v The value of value
    ## 8 Other relevant command notes ####
    Name Meaning Command option Explain
    startMonitoring Used to start the monitoring process, monitor message deletion, retry queue messages, etc. -n Service address used to specify nameServer and formatted as ip:port
    -h Print help information
    ================================================ FILE: docs/en/Concept.md ================================================ # Basic Concept ## 1 Message Model RocketMQ message model is mainly composed of Producer, Broker and Consumer. The producer is responsible for producing messages and the consumer is for consuming messages, while the broker stores messages. The broker is an independent server during actual deployment, and each broker can store messages from multiple topics. Even messages from the same topic can be stored in the different brokers by sharding strategy. The message queue is used to store physical offsets of messages, and the message addresses are stored in separate queues. The consumer group consists of multiple consumer instances. ## 2 Producer The Producer is responsible for producing messages, typically by business systems. It sends messages generated by the systems to brokers. RocketMQ provides multiple paradigms of sending: synchronous, asynchronous, sequential and one-way. Both synchronous and asynchronous methods require the confirmation information return from the Broker, but one-way method does not require it. ## 3 Consumer The Consumer is responsible for consuming messages, typically the background system is responsible for asynchronous consumption. The consumer pulls messages from brokers and feeds them into application. From the perspective of user, two types of consumers are provided: pull consumer and push consumer. ## 4 Topic The Topic refers to a collection of one kind of message. Each topic contains several messages and one message can only belong to one topic. The topic is the basic unit of RocketMQ for message subscription. ## 5 Broker Server As the role of the transfer station, the Broker Server stores and forwards messages. In RocketMQ, the broker server is responsible for receiving messages sent from producers, storing them and preparing to handle pull requests. It also stores the related message meta data, including consumer groups, consuming progress, topics, queues info and so on. ## 6 Name Server The Name Server serves as the provider of routing service. The producer or the consumer can find the list of broker IP addresses for each topic through name server. Multiple name servers can be deployed in one cluster, but they are independent of each other and do not exchange information. ## 7 Pull Consumer A type of Consumer, the application pulls messages from brokers by actively invoking the consumer pull message method, and the application has the advantages of controlling the timing and frequency of pulling messages. Once the batch of messages is pulled, user application will initiate consuming process. ## 8 Push Consumer A type of Consumer, the application does not invoke the consumer pull message method to pull messages, instead the client invokes the pull message method itself. At the user level it seems like brokers push to the consumer when new messages arrive. ## 9 Producer Group A collection of the same type of Producer, which sends the same type of messages with consistent logic. If a transaction message is sent and the original producer crashes after sending, the broker server will contact other producers in the same producer group to commit or rollback the transactional message. ## 10 Consumer Group A collection of the same type of Consumer, which consume the same type of messages with consistent logic. The consumer group makes load-balance and fault-tolerance super easy in terms of message consuming. Warning: consumer instances of one consumer group must have exactly the same topic subscription(s). RocketMQ supports two types of consumption mode: Clustering and Broadcasting. ## 11 Consumption Mode - Clustering Under the Clustering mode, all the messages from one topic will be delivered to all the consumer instances as evenly as possible. That is, one message can be consumed by only one consumer instance. ## 12 Consumption Mode - Broadcasting Under the Broadcasting mode, each consumer instance of the same consumer group receives every message published to the corresponding topic. ## 13 Normal Ordered Message Under the Normal Ordered Message mode, the messages received by consumers from the same ConsumeQueue are sequential, but the messages received from the different message queues may be non-sequential. ## 14 Strictly Ordered Message Under the Strictly Ordered Message mode, all messages received by the consumers from the same topic are sequential as the order they are stored. ## 15 Message The physical carrier of information transmitted by a messaging system, the smallest unit of production and consumption data, each message must belong to one topic. Each Message in RocketMQ has a unique message id and can carry a key used to store business-related value. The system has the function to query messages by its id or key. ## 16 Tag Flags set for messages to distinguish different types of messages under the same topic, functioning as a "sub-topic". Messages from the same business unit can set different tags under the same topic for different business purposes. The tag can effectively maintain the clarity and consistency of the code and optimize the query system provided by RocketMQ. The consumer can realize different "sub-topics" by using tags in order to achieve better extensibility. ================================================ FILE: docs/en/Configuration_Client.md ================================================ ## Client Configuration Relative to RocketMQ's Broker cluster, producers and consumers are clients. This section describes the common behavior configuration of producers and consumers, including updates for **RocketMQ 5.x**. ### 1 Client Addressing Mode `RocketMQ` allows clients to locate the `Name Server`, which then helps them find the `Broker`. Below are various configurations, prioritized from highest to lowest. Higher priority configurations override lower ones. - Specified `Name Server` address in the code (multiple addresses separated by semicolons): ```java producer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876"); consumer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876"); ``` - Specified `Name Server` address in Java setup parameters: ```text -Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876 ``` - Specified `Name Server` address in environment variables: ```text export NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876 ``` - HTTP static server addressing (default): Clients retrieve `Name Server` addresses from a static HTTP server: ```text http://jmenv.tbsite.net:8080/rocketmq/nsaddr ``` - **New in RocketMQ 5.x:** - Improved service discovery mechanism, allowing dynamic Name Server registration. - Introduces `VIP_CHANNEL_ENABLED` for better failover: ```java producer.setVipChannelEnabled(false); consumer.setVipChannelEnabled(false); ``` ### 2 Client Configuration `DefaultMQProducer`, `TransactionMQProducer`, `DefaultMQPushConsumer`, and `DefaultMQPullConsumer` all extend the `ClientConfig` class, which provides common client configurations. #### 2.1 Client Common Configuration | Parameter Name | Default Value | Description | |-------------------------------|---------------|--------------| | namesrvAddr | | Name Server address list (semicolon-separated) | | clientIP | Local IP | Client's local IP address (useful if automatic detection fails) | | instanceName | DEFAULT | Unique name for the client instance | | clientCallbackExecutorThreads | 4 | Number of communication layer asynchronous callback threads | | pollNameServerInterval | 30000 | Interval (ms) to poll Name Server | | heartbeatBrokerInterval | 30000 | Interval (ms) for sending heartbeats to Broker | | persistConsumerOffsetInterval | 5000 | Interval (ms) for persisting consumer offsets | | **autoUpdateNameServer** (5.x) | true | Automatically update Name Server addresses from registry | | **instanceId** (5.x) | | Unique identifier for each client instance | #### 2.2 Producer Configuration | Parameter Name | Default Value | Description | |--------------------------------|----------------------|--------------| | producerGroup | DEFAULT_PRODUCER | Producer group name | | sendMsgTimeout | 3000 | Timeout (ms) for sending messages | | retryTimesWhenSendFailed | 2 | Max retries for failed messages | | **enableBatchSend** (5.x) | true | Enables batch message sending | | **enableBackPressure** (5.x) | true | Prevents overload in high-traffic scenarios | #### 2.3 PushConsumer Configuration | Parameter Name | Default Value | Description | |--------------------------------------|------------------------------------|-------------| | consumerGroup | DEFAULT_CONSUMER | Consumer group name | | messageModel | CLUSTERING | Message consumption mode (CLUSTERING or BROADCAST) | | consumeFromWhere | CONSUME_FROM_LAST_OFFSET | Default consumption position | | consumeThreadMin | 20 | Min consumption thread count | | consumeThreadMax | 20 | Max consumption thread count | | **Rebalance Strategies (5.x)** | AllocateMessageQueueAveragelyByCircle | New rebalance strategy for better distribution | #### 2.4 PullConsumer Configuration | Parameter Name | Default Value | Description | |------------------------------------|------------------------------|-------------| | consumerGroup | DEFAULT_CONSUMER | Consumer group name | | brokerSuspendMaxTimeMillis | 20000 | Max suspension time (ms) for long polling | | consumerPullTimeoutMillis | 10000 | Timeout (ms) for pull requests | | **messageGroup** (5.x) | | Enables orderly consumption based on groups | #### 2.5 Message Data Structure | Field Name | Default Value | Description | |-------------------|---------------|-------------| | Topic | null | Required: Name of the message topic | | Body | null | Required: Message content | | Tags | null | Optional: Tag for filtering | | Keys | null | Optional: Business keys (e.g., order IDs) | | Flag | 0 | Optional: Custom flag | | DelayTimeLevel | 0 | Optional: Message delay level | | WaitStoreMsgOK | TRUE | Optional: Acknowledgment before storing | | **maxReconsumeTimes** (5.x) | | Max retries before moving to dead-letter queue | | **messageGroup** (5.x) | | Group-based message ordering | --- ================================================ FILE: docs/en/Configuration_System.md ================================================ # The System Configuration (RocketMQ 5.x) This section focuses on the configuration of the system (JVM/OS) for **RocketMQ 5.x**. ## **1 JVM Options** ## The latest released version of JDK 1.8 is recommended. Set the same Xms and Xmx value to prevent the JVM from resizing the heap for better performance. A simple JVM configuration is as follows: -server -Xms8g -Xmx8g -Xmn4g Direct ByteBuffer memory size setting. Full GC will be triggered when the Direct ByteBuffer up to the specified size: -XX:MaxDirectMemorySize=15g If you don’t care about the boot time of RocketMQ broker, pre-touch the Java heap to make sure that every page will be allocated during JVM initialization is a better choice. Those who don’t care about the boot time can enable it: -XX:+AlwaysPreTouch Disable biased locking to potentially reduce JVM pauses: -XX:-UseBiasedLocking As for garbage collection, the G1 collector with JDK 1.8 is recommended: -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 These GC options may seem a bit aggressive, but they have been proven to provide good performance in our production environment. Do not set a too small value for -XX:MaxGCPauseMillis, as it may cause the JVM to use a small young generation, leading to frequent minor GCs. Using a rolling GC log file is recommended: -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m If writing GC logs to disk increases broker latency, consider redirecting the GC log file to a memory file system: -Xloggc:/dev/shm/mq_gc_%p.log123 ## **2 Linux Kernel Parameters** ## There is an `os.sh` script in the `bin` folder that lists several kernel parameters that can be used for production with minor modifications. Below parameters require attention. For more details, refer to the documentation for `/proc/sys/vm/*`. - **vm.extra_free_kbytes**: Tells the VM to keep extra free memory between the threshold where background reclaim (kswapd) kicks in and the threshold where direct reclaim (by allocating processes) kicks in. RocketMQ uses this parameter to avoid high latency in memory allocation. (Kernel version-specific) - **vm.min_free_kbytes**: If set lower than 1024KB, the system may become subtly broken and prone to deadlocks under high loads. - **vm.max_map_count**: Limits the maximum number of memory map areas a process may have. RocketMQ uses `mmap` to load `CommitLog` and `ConsumeQueue`, so setting a higher value is recommended. - **vm.swappiness**: Defines how aggressively the kernel swaps memory pages. Higher values increase aggressiveness, while lower values decrease the amount of swap. A value of **10** is recommended to avoid swap latency. - **File descriptor limits**: RocketMQ requires open file descriptors for files (`CommitLog` and `ConsumeQueue`) and network connections. It is recommended to set this to **655350**. - **Disk scheduler**: The **deadline I/O scheduler** is recommended for RocketMQ as it attempts to provide a guaranteed latency for requests. --- ================================================ FILE: docs/en/Configuration_TLS.md ================================================ # TLS Configuration This section introduces TLS configuration in RocketMQ. ## 1 Generate Certificate Files Users can generate certificate files using OpenSSL. It is suggested to generate files in Linux. ### 1.1 Generate ca.pem ```shell openssl req -newkey rsa:2048 -keyout ca_rsa_private.pem -x509 -days 365 -out ca.pem ``` ### 1.2 Generate server.csr ```shell openssl req -newkey rsa:2048 -keyout server_rsa.key -out server.csr ``` ### 1.3 Generate server.pem ```shell openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca_rsa_private.pem -CAcreateserial -out server.pem ``` ### 1.4 Generate client.csr ```shell openssl req -newkey rsa:2048 -keyout client_rsa.key -out client.csr ``` ### 1.5 Generate client.pem ```shell openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca_rsa_private.pem -CAcreateserial -out client.pem ``` ### 1.6 Generate server.key ```shell openssl pkcs8 -topk8 -v1 PBE-SHA1-RC4-128 -in server_rsa.key -out server.key ``` ### 1.7 Generate client.key ```shell openssl pkcs8 -topk8 -v1 PBE-SHA1-RC4-128 -in client_rsa.key -out client.key ``` ## 2 Create tls.properties Create tls.properties, correctly configure the path and password of the generated certificates. ```properties # The flag to determine whether use test mode when initialize TLS context. default is true tls.test.mode.enable=false # Indicates how SSL engine respect to client authentication, default is none tls.server.need.client.auth=require # The store path of server-side private key tls.server.keyPath=/opt/certFiles/server.key # The password of the server-side private key tls.server.keyPassword=123456 # The store path of server-side X.509 certificate chain in PEM format tls.server.certPath=/opt/certFiles/server.pem # To determine whether verify the client endpoint's certificate strictly. default is false tls.server.authClient=false # The store path of trusted certificates for verifying the client endpoint's certificate tls.server.trustCertPath=/opt/certFiles/ca.pem ``` If you need to authenticate the client connection, you also need to add the following content to the file. ```properties # The store path of client-side private key tls.client.keyPath=/opt/certFiles/client.key # The password of the client-side private key tls.client.keyPassword=123456 # The store path of client-side X.509 certificate chain in PEM format tls.client.certPath=/opt/certFiles/client.pem # To determine whether verify the server endpoint's certificate strictly tls.client.authServer=false # The store path of trusted certificates for verifying the server endpoint's certificate tls.client.trustCertPath=/opt/certFiles/ca.pem ``` ## 3 Update Rocketmq JVM parameters Edit the configuration file under the rocketmq/bin path to make tls.properties configurations take effect. The value of "tls.config.file" needs to be replaced by the file path created in step 2. ### 3.1 Edit runserver.sh Add following content in JAVA_OPT: ```shell JAVA_OPT="${JAVA_OPT} -Dtls.server.mode=enforcing -Dtls.config.file=/opt/rocketmq-4.9.3/conf/tls.properties" ``` ### 3.2 Edit runbroker.sh Add following content in JAVA_OPT: ```shell JAVA_OPT="${JAVA_OPT} -Dorg.apache.rocketmq.remoting.ssl.mode=enforcing -Dtls.config.file=/opt/rocketmq-4.9.3/conf/tls.properties -Dtls.enable=true" ``` # 4 Client connection Create tlsclient.properties using by client. Add following content: ```properties # The store path of client-side private key tls.client.keyPath=/opt/certFiles/client.key # The password of the client-side private key tls.client.keyPassword=123456 # The store path of client-side X.509 certificate chain in PEM format tls.client.certPath=/opt/certFiles/client.pem # The store path of trusted certificates for verifying the server endpoint's certificate tls.client.trustCertPath=/opt/certFiles/ca.pem ``` Add following parameters in JVM. The value of "tls.config.file" needs to be replaced by the file path we created: ```properties -Dtls.client.authServer=true -Dtls.enable=true -Dtls.test.mode.enable=false -Dtls.config.file=/opt/certs/tlsclient.properties ``` Enable TLS for client like the following: ```Java public class ExampleProducer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); //setUseTLS should be true producer.setUseTLS(true); producer.start(); // Send messages as usual. producer.shutdown(); } } ``` ## 5 Proxy TLS Configuration RocketMQ Proxy uses `rmq-proxy.json` (not `tls.properties`) for TLS configuration. The proxy supports TLS for both its gRPC and Remoting protocol endpoints. ### 5.1 Configure rmq-proxy.json Add TLS-related fields to `distribution/conf/rmq-proxy.json`: ```json { "rocketMQClusterName": "DefaultCluster", "tlsTestModeEnable": false, "tlsKeyPath": "/opt/certFiles/server.key", "tlsKeyPassword": "123456", "tlsCertPath": "/opt/certFiles/server.pem" } ``` | Field | Type | Default | Description | |-------|------|---------|-------------| | `tlsTestModeEnable` | boolean | `true` | Use self-signed certificates for testing. Set to `false` for production. | | `tlsKeyPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.key` | Path to the server private key file (PKCS#8 PEM format). | | `tlsKeyPassword` | string | `""` | Password for the encrypted private key. Leave empty if the key is not encrypted. | | `tlsCertPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.crt` | Path to the server certificate chain file (X.509 PEM format). | | `tlsCertWatchIntervalMs` | int | `3600000` | Interval in milliseconds to check for certificate file changes. | ### 5.2 Update Proxy JVM parameters Edit `runproxy.sh` (or the script that launches the proxy) to enable TLS enforcing mode: ```shell JAVA_OPT="${JAVA_OPT} -Dtls.server.mode=enforcing" ``` The three available TLS modes are: - `disabled` - TLS is not supported; incoming TLS handshakes are rejected. - `permissive` - TLS is optional; the proxy accepts both TLS and non-TLS connections. - `enforcing` - TLS is required; non-TLS connections are rejected. ================================================ FILE: docs/en/Debug_In_Idea.md ================================================ ## How to Debug RocketMQ in Idea ### Step0: Resolve dependencies 1. To download the Maven dependencies required for running RocketMQ, you can use the following command:`mvn clean install -Dmaven.test.skip=true` 2. Ensure successful local compilation. ### Step1: Start NameServer 1. The startup class for NameServer is located in `org.apache.rocketmq.namesrv.NamesrvStartup`. 2. Add environment variable `ROCKETMQ_HOME=` in `Idea-Edit Configurations`. ![Idea_config_nameserver.png](../cn/image/Idea_config_nameserver.png) 3. Run NameServer and if the following log output is observed, it indicates successful startup. ```shell The Name Server boot success. serializeType=JSON, address 0.0.0.0:9876 ``` ### Step2: Start Broker 1. The startup class for Broker is located in`org.apache.rocketmq.broker.BrokerStartup` 2. Create the `/rocketmq/conf/broker.conf` file or simply copy it from the official release package. ```shell # broker.conf brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH namesrvAddr = 127.0.0.1:9876 ``` 3. Add the runtime parameter `-c /Users/xxx/rocketmq/conf/broker.conf` and the environment variable `ROCKETMQ_HOME=` in `Idea-Edit Configurations`. ![Idea_config_broker.png](../cn/image/Idea_config_broker.png) 4. Run the Broker and if the following log is observed, it indicates successful startup. ```shell The broker[broker-a,192.169.1.2:10911] boot success... ``` ### Step3: Send or Consume Messages RocketMQ startup is now complete. You can use the examples provided in `/example` to send and consume messages. ### Additional: Start the Proxy locally. 1. RocketMQ 5.x introduced the Proxy mode. Using the `LOCAL` mode eliminates the need for `Step2`. The startup class is located at `org.apache.rocketmq.proxy.ProxyStartup`. 2. Add the environment variable `ROCKETMQ_HOME=` in `Idea-Edit Configurations`. 3. Create a new configuration file named `rmq-proxy.json` in the `/conf/` directory. ```json { "rocketMQClusterName": "DefaultCluster", "nameSrvAddr": "127.0.0.1:9876", "proxyMode": "local" } ``` 4. Run the Proxy, and if the following log is observed, it indicates successful startup. ```shell Sat Aug 26 15:29:33 CST 2023 rocketmq-proxy startup successfully ``` ================================================ FILE: docs/en/Deployment.md ================================================ # Deployment Architectures and Setup Steps ## Cluster Setup ### 1 Single Master mode This is the simplest, but also the riskiest mode, that makes the entire service unavailable once the broker restarts or goes down. Production environments are not recommended, but it can be used for local testing and development. Here are the steps to build. **1)Start NameServer** ```shell ### Start Name Server first $ nohup sh mqnamesrv & ### Then verify that the Name Server starts successfully $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` We can see 'The Name Server boot success.. ' in namesrv.log that indicates the NameServer has been started successfully. **2)Start Broker** ```shell ### Also start broker first $ nohup sh bin/mqbroker -n localhost:9876 & ### Then verify that the broker is started successfully, for example, the IP of broker is 192.168.1.2 and the name is broker-a $ tail -f ~/logs/rocketmqlogs/Broker.log The broker[broker-a,192.169.1.2:10911] boot success... ``` We can see 'The broker[brokerName,ip:port] boot success..' in Broker.log that indicates the broker has been started successfully. ### 2 Multiple Master mode Multiple master mode means a mode with all master nodes(such as 2 or 3 master nodes) and no slave node. The advantages and disadvantages of this mode are as follows: - Advantages: 1. Simple configuration. 2. Outage or restart(for maintenance) of one master node has no impact on the application. 3. When the disk is configured as RAID10, messages are not lost because the RAID10 disk is very reliable, even if the machine is not recoverable (In the case of asynchronous flush disk mode of the message, a small number of messages are lost; If the brush mode of a message is synchronous, no message will be lost). 4. In this mode, the performance is the highest. - Disadvantages: 1. During a single machine outage, messages that are not consumed on this machine are not subscribed to until the machine recovers, and message real-time is affected. The starting steps for multiple master mode are as follows: **1)Start NameServer** ```shell ### Start Name Server first $ nohup sh mqnamesrv & ### Then verify that the Name Server starts successfully $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` **2)Start the Broker cluster** ```shell ### For example, starting the first Master on machine A, assuming that the configured NameServer IP is: 192.168.1.1. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-a.properties & ### Then starting the second Master on machine B, assuming that the configured NameServer IP is: 192.168.1.1. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-b.properties & ... ``` The boot command shown above is used in the case of a single NameServer.For clusters of multiple NameServer, the address list after the -n argument in the broker boot command is separated by semicolons, for example, 192.168.1.1: 9876;192.168.1.2: 9876. ### 3 Multiple Master And Multiple Slave Mode-Asynchronous replication Each master node configures more than one slave nodes, with multiple pairs of master-slave.HA uses asynchronous replication, with a short message delay (millisecond) between master node and slave node.The advantages and disadvantages of this mode are as follows: - Advantages: 1. Even if the disk is corrupted, very few messages will be lost and the real-time performance of the message will not be affected. 2. At the same time, when master node is down, consumers can still consume messages from slave node, and the process is transparent to the application itself and does not require human intervention. 3. Performance is almost as high as multiple master mode. - Disadvantages: 1. A small number of messages will be lost when master node is down and the disk is corrupted. The starting steps for multiple master and multiple slave mode are as follows: **1)Start NameServer** ```shell ### Start Name Server first $ nohup sh mqnamesrv & ### Then verify that the Name Server starts successfully $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` **2)Start the Broker cluster** ```shell ### For example, starting the first Master on machine A, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a.properties & ### Then starting the second Master on machine B, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b.properties & ### Then starting the first Slave on machine C, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a-s.properties & ### Last starting the second Slave on machine D, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b-s.properties & ``` The above shows a startup command for 2M-2S-Async mode, similar to other nM-nS-Async modes. ### 4 Multiple Master And Multiple Slave Mode-Synchronous dual write In this mode, multiple slave node are configured for each master node and there are multiple pairs of Master-Slave.HA uses synchronous double-write, that is, the success response will be returned to the application only when the message is successfully written into the master node and replicated to more than one slave node. The advantages and disadvantages of this model are as follows: - Advantages: 1. Neither the data nor the service has a single point of failure. 2. In the case of master node shutdown, the message is also undelayed. 3. Service availability and data availability are very high; - Disadvantages: 1. The performance in this mode is slightly lower than in asynchronous replication mode (about 10% lower). 2. The RT sending a single message is slightly higher, and the current version, the slave node cannot automatically switch to the master after the master node is down. The starting steps are as follows: **1)Start NameServer** ```shell ### Start Name Server first $ nohup sh mqnamesrv & ### Then verify that the Name Server starts successfully $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` **2)Start the Broker cluster** ```shell ### For example, starting the first Master on machine A, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a.properties & ### Then starting the second Master on machine B, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b.properties & ### Then starting the first Slave on machine C, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a-s.properties & ### Last starting the second Slave on machine D, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876. $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b-s.properties & ``` The above Master and Slave are paired by specifying the same config named "brokerName", the "brokerId" of the master node must be 0, and the "brokerId" of the slave node must be greater than 0. ================================================ FILE: docs/en/Design_Filter.md ================================================ # Message Filter RocketMQ - a distributed message queue, is different with all other MQ middleware, on the way of filtering messages. It's do the filter when the messages are subscribed via consumer side.RocketMQ do it lies in the separate storage mechanism that Producer side writing messages and Consumer subscribe messages, Consumer side will get an index from a logical message queue ConsumeQueue when subscribing, then read message entity from CommitLog using the index. So in the end, it is still impossible to get around its storage structure.The storage structure of ConsumeQueue is as follows, and there is a 8-byte Message Tag hashcode, The message filter based on Tag value is just used this Message Tag hash-code. ![](images/rocketmq_design_7.png) The RocketMQ has two mainly filter types: * Tag filtering: Consumer can specify not only the message topic but also the message tag values, when subscribing. Multiple tag values need to be separated by '||'. When consumer subscribing a message, it builds the subscription request into a `SubscriptionData` object and sends a pull message request to the Broker side. Before the Broker side reads data from the RocketMQ file storage layer - Store, it will construct a `MessageFilter` using the `SubscriptionData` object and then pass it to the Store. Store get a record from `ConsumeQueue`, and it will filter the message by the saved tag hashcode, it is unable to filter the messages exactly in the server side because of only the hashcode will be used when filtering, Therefore, after the Consumer pulls the message, it also needs to compare the original tag string of the message. If the original tag string is not same with the expected, the message will be ignored. * SQL92 filtering: This filter behavior is almost same with the above `Tag filtering` method. The only difference is on the way how Store works. The rocketmq-filter module is responsible for the construction and execution of the real SQL expression. Executing an SQL expression every time a filter is executed affects efficiency, so RocketMQ uses BloomFilter to avoid doing it every time. The expression context of SQL92 is a property of the message. ================================================ FILE: docs/en/Design_LoadBlancing.md ================================================ ## Load Balancing Load balancing in RocketMQ is accomplished on Client side. Specifically, it can be divided into load balancing at Producer side when sending messages and load balancing at Consumer side when subscribing messages. ### Producer Load Balancing When the Producer sends a message, it will first find the specified TopicPublishInfo according to Topic. After getting the routing information of TopicPublishInfo, the RocketMQ client will select a queue (MessageQueue) from the messageQueue List in TopicPublishInfo to send the message by default.Specific fault-tolerant strategies are defined in the MQFaultStrategy class. Here is a sendLatencyFaultEnable switch variable, which, if turned on, filters out the Broker agents that are not available on the basis of randomly gradually increasing modular arithmetic selection. The so-called "latencyFault Tolerance" refers to a certain period of time to avoid previous failures. For example, if the latency of the last request exceeds 550 Lms, it will evade 30000 Lms; if it exceeds 1000L, it will evade 60000L; if it is closed, it will choose a queue (MessageQueue) to send messages by randomly gradually increasing modular arithmetic, and the latencyFault Tolerance mechanism is the key to achieve high availability of message sending. ### Consumer Load Balancing In RocketMQ, the two consumption modes (Push/Pull) on the Consumer side are both based on the pull mode to get the message, while in the Push mode it is only a kind of encapsulation of the pull mode, which is essentially implemented as the message pulling thread after pulling a batch of messages from the server. After submitting to the message consuming thread pool, it continues to try again to pull the message to the server. If the message is not pulled, the pull is delayed and continues. In both pull mode based consumption patterns (Push/Pull), the Consumer needs to know which message queue - queue from the Broker side to get the message. Therefore, it is necessary to do load balancing on the Consumer side, that is, which Consumer consumption is allocated to the same ConsumerGroup by more than one MessageQueue on the Broker side. #### 1 Heartbeat Packet Sending on Consumer side After Consumer is started, it continuously sends heartbeat packets to all Broker instances in the RocketMQ cluster via timing task (which contains the message consumption group name, subscription relationship collection,Message communication mode and the value of the client id,etc). After receiving the heartbeat message from Consumer, Broker side maintains it in Consumer Manager's local caching variable—consumerTable, At the same time, the encapsulated client network channel information is stored in the local caching variable—channelInfoTable, which can provide metadata information for the later load balancing of Consumer. #### 2 Core Class for Load Balancing on Consumer side—RebalanceImpl Starting the MQClientInstance instance in the startup process of the Consumer instance will complete the start of the load balancing service thread-RebalanceService (executed every 20 s). By looking at the source code, we can find that the run () method of the RebalanceService thread calls the rebalanceByTopic () method of the RebalanceImpl class, which is the core of the Consumer end load balancing. Here, rebalanceByTopic () method will do different logical processing depending on whether the consumer communication type is "broadcast mode" or "cluster mode". Here we mainly look at the main processing flow in cluster mode: ##### 1) Get the message consumption queue set (mqSet) under the Topic from the local cache variable—topicSubscribeInfoTable of the rebalanceImpl instance. ##### 2) Call mQClientFactory. findConsumerIdList () method to send a RPC communication request to Broker side to obtain the consumer Id list under the consumer group based on the parameters of topic and consumer group (consumer table constructed by Broker side based on the heartbeat data reported by the front consumer side responds and returns, business request code: GET_CONSUMER_LIST_BY_GROUP); ##### 3) First, the message consumption queue and the consumer Id under Topic are sorted, then the message queue to be pulled is calculated by using the message queue allocation strategy algorithm (default: the average allocation algorithm of the message queue). The average allocation algorithm here is similar to the paging algorithm. It ranks all MessageQueues like records. It ranks all consumers like pages. It calculates the average size of each page and the range of each page record. Finally, it traverses the whole range and calculates the records that the current consumer should allocate to (MessageQueue here). ![Image text](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_8.png) ##### 4) Then, the updateProcessQueueTableInRebalance () method is invoked, which first compares the allocated message queue set (mqSet) with processQueueTable for filtering. ![Image text](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_9.png) - The red part of the processQueueTable annotation in the figure above indicates that it is not included with the assigned message queue set mqSet. Set the Dropped attribute to true for these queues, and then check whether these queues can remove the processQueueTable cache variable or not. The removeUnnecessaryMessageQueue () method is executed here, that is, check every 1s to see if the locks of the current consumption processing queue can be retrieved and return true if they are retrieved. If the lock of the current consumer processing queue is still not available after waiting for 1s, it returns false. If true is returned, the corresponding Entry is removed from the processQueueTable cache variable. - The green section in processQueueTable above represents the intersection with the assigned message queue set mqSet. Determine whether the ProcessQueue has expired, regardless of Pull mode, if it is Push mode, set the Dropped attribute to true, and call the removeUnnecessaryMessageQueue () method to try to remove Entry as above; Finally, a ProcessQueue object is created for each MessageQueue in the filtered message queue set (mqSet) and stored in the processQueueTable queue of RebalanceImpl (where the computePullFromWhere (MessageQueue mq) method of the RebalanceImpl instance is invoked to obtain the next progress consumption value offset of the MessageQueue object, which is then populated into the attribute of pullRequest object to be created next time.), and create pull request object—pullRequest to add to pull list—pullRequestList, and finally execute dispatchPullRequest () method. PullRequest object of Pull message is put into the blocking queue pullRequestQueue of PullMessageService service thread in turn, and the request of Pull message is sent to Broker end after the service thread takes out. The core design idea of message consumption queue is that a message consumption queue can only be consumed by one consumer in the same consumer group at the same time, and a message consumer can consume multiple message queues at the same time. ================================================ FILE: docs/en/Design_Query.md ================================================ # Message Queries RocketMQ supports message queries by two dimensions, which are "Query Message by Message Id" and "Query Message by Message Key". ## 1. Query Message by Message Id The MessageId in RocketMQ has a total length of 16 bytes, including the broker address (IP address and port) and CommitLog offset. In RocketMQ, the specific approach is that the Client resolves the Broker's address (IP address and port) and the CommitLog's offset address from the MessageId. Then both of them are encapsulated into an RPC request, and finally it will be sent through the communication layer (business request code: VIEW_MESSAGE_BY_ID). The Broker reads a message by using the CommitLog offset and size to find the real message in the CommitLog and then return, which is how QueryMessageProcessor works. ## 2. Query Message by Message Key "Query Messages by Message Key" is mainly based on RocketMQ's IndexFile. The logical structure of the IndexFile is similar to the implementation of HashMap in JDK. The specific structure of the IndexFile is as follows: ![](images/rocketmq_design_message_query.png) The IndexFile provides the user with the querying service by “Querying Messages by Message Key”. The IndexFile is stored in $HOME\store\index${fileName}, and the file name is named after the timestamp at the time of creation. The file size is fixed, which is 420,000,040 bytes (40+5million\*4+20million\*20). If the UNIQ_KEY is set in the properties of the message, then the "topic + ‘#’ + UNIQ_KEY" will be used as the index. Likewise, if the KEYS is set in the properties of the message (multiple KEYs should be separated by spaces), then the "topic + ‘#’ + KEY" will be used as the index. The header of IndexFile contains eight fields, `beginTimestamp`(8 bytes), `endTimestamp`(8 bytes), `beginPhyOffset`(8 bytes), `endPhyOffset`(8 bytes), `hashSlotCount`(4 bytes), and `indexCount`(4 bytes).`beginTimestamp` and `endTimestamp` represents the storeTimestamp of the message corresponding to the first and last index, `beginPhyOffset` and `endPhyOffset` represent the physical offset of the message corresponding to the first and last index. `hashSlotCount` represents the count of hash slot. `indexCount` represents the count of indexes. The index data contains four fields, `Key Hash`, `CommitLog offset`, `Timestamp` and `NextIndex offset`, for a total of 20 Bytes. The `NextIndex offset` of the index data will point to the previous index data if the `Key Hash` of the index data is the same as that of the previous index data. If a hash conflict occurs, then the `NextIndex offset` can be used as the field to string all conflicting indexes in a linked list. What the `Timestamp` records is the time difference between the `storeTimestamp` of the message associated with the current key and the `BeginTimestamp` in the `IndexHeader`, instead of a specific time. The structure of the entire IndexFile is shown in the graph. The Header is used to store some general statistics, which needs 40 bytes. The Slot Table of 4\*5million bytes does not save the real index data, but saves the header of the singly linked list corresponding to each slot. The Index Linked List of 20\*20million is the real index data, that is, an Index File can hold 20million indexes. The specific method of "Query Message by Message Key" is that the topic and message key are used to find the record in the IndexFile, and then read the message from the file of CommitLog according to the CommitLog offset in this record. ================================================ FILE: docs/en/Design_Remoting.md ================================================ RocketMQ message queue cluster mainly includes four roles: NameServer, Broker (Master/Slave), Producer and Consumer. The basic communication process is as follows: (1) After Broker start-up, it needs to complete one operation: register itself to NameServer, and then report Topic routing information to NameServer at regular intervals of 30 seconds. (2) When message Producer sends a message as a client, it needs to obtain routing information from the local cache TopicPublishInfoTable according to the Topic of the message. If not, it will be retrieved from NameServer and update to local cache, at the same time, Producer will retrieve routing information from NameServer every 30 seconds by default. (3) Message Producer chooses a queue to send the message according to the routing information obtained in 2); Broker receives the message and records it in disk as the receiver of the message. (4) After message Consumer gets the routing information according to 2) and complete the load balancing of the client, then select one or several message queues to pull messages and consume them. From 1) ~ 3) above, we can see that both Producer, Broker and NameServer communicate with each other(only part of MQ communication is mentioned here), so how to design a good network communication module is very important in MQ. It will determine the overall messaging capability and final performance of the RocketMQ cluster. rocketmq-remoting module is the module responsible for network communication in RocketMQ message queue. It is relied on and referenced by almost all other modules (such as rocketmq-client,rocketmq-broker,rocketmq-namesrv) that need network communication. In order to realize the efficient data request and reception between the client and the server, the RocketMQ message queue defines the communication protocol and extends the communication module on the basis of Netty. ### 1 Remoting Communication Class Structure ![](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_3.png) ### 2 Protocol Design and Code When a message is sent between Client and Server, a protocol convention is needed for the message sent, so it is necessary to customize the message protocol of RocketMQ. At the same time, in order to efficiently transmit messages and read the received messages, it is necessary to encode and decode the messages. In RocketMQ, the RemotingCommand class encapsulates all data content in the process of message transmission, which includes not only all data structures, but also encoding and decoding operations. Header field | Type | Request desc | Response desc --- | --- | --- | --- | code |int | Request code. answering business processing is different according to different requests code | Response code. 0 means success, and non-zero means errors. language | LanguageCode | Language implemented by the requester | Language implemented by the responder version | int | Version of Request Equation | Version of Response Equation opaque | int |Equivalent to requestId, the different request identification codes on the same connection correspond to those in the response message| The response returns directly without modification flag | int | Sign, used to distinguish between ordinary RPC or oneway RPC | Sign, used to distinguish between ordinary RPC or oneway RPC remark | String | Transfer custom text information | Transfer custom text information extFields | HashMap | Request custom extension information| Response custom extension information ![](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_4.png) From the above figure, the transport content can be divided into four parts: (1) Message length: total length, four bytes of storage, occupying an int type; (2) Serialization type header length: occupying an int type. The first byte represents the serialization type, and the last three bytes represent the header length; (3) Header data: serialized header data; (4) Message body data: binary byte data content of message body; ### 3 Message Communication Mode and Procedure There are three main ways to support communication in RocketMQ message queue: synchronous (sync), asynchronous (async), one-way (oneway). The "one-way" communication mode is relatively simple and is generally used in sending heartbeat packets without paying attention to its Response. Here, mainly introduce the asynchronous communication flow of RocketMQ. ![](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_5.png) ### 4 Reactor Multithread Design The RPC communication of RocketMQ uses Netty component as the underlying communication library, and also follows the Reactor multithread model. At the same time, some extensions and optimizations are made on it. ![](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_6.png) Above block diagram can roughly understand the Reactor multi-thread model of NettyRemotingServer in RocketMQ. A Reactor main thread (eventLoopGroupBoss, is 1 above) is responsible for listening to TCP network connection requests, establishing connections, creating SocketChannel, and registering on selector. The source code of RocketMQ automatically selects NIO and Epoll according to the type of OS. Then listen to real network data. After you get the network data, you throw it to the Worker thread pool (eventLoopGroupSelector, is the "N" above, the default is 3 in the source code). You need to do SSL verification, codec, idle check, network connection management before you really execute the business logic. These tasks to defaultEventExecutorGroup (that is, "M1" above, the default set to 8 in the source code) to do. The processing business operations are executed in the business thread pool. According to the RomotingCommand business request code, the corresponding processor is found in the processorTable local cache variable and encapsulated into the task, and then submitted to the corresponding business processor processing thread pool for execution (sendMessageExecutor,). Take sending a message, for example, the "M2" above. The thread pool continues to increase in several steps from entry to business logic, which is related to the complexity of each step. The more complex the thread pool is, the wider the concurrent channel is required. Number of thread | Name of thread | Desc of thread --- | --- | --- 1 | NettyBoss_%d | Reactor Main thread N | NettyServerEPOLLSelector_%d_%d | Reactor thread pool M1 | NettyServerCodecThread_%d | Worker thread pool M2 | RemotingExecutorThread_%d | business processor thread pool ================================================ FILE: docs/en/Design_Store.md ================================================ # Message Storage ![](images/rocketmq_storage_arch.png) Message storage is the most complicated and important part of RocketMQ. This section will describe the three aspects of RocketMQ: * Message storage architecture * PageCache and memory mapping * RocketMQ's two different disk flushing methods. ## 1 Message Storage Architecture The message storage architecture diagram consists of 3 files related to message storage: `CommitLog` file, `ConsumeQueue` file, and `IndexFile`. * `CommitLog`: The `CommitLog` file stores message body and metadata sent by producer, and the message content is not fixed length. The default size of one `CommitLog` file is 1G, the length of the file name is 20 digits, the left side is zero padded, and the remaining is the starting offset. For example, `00000000000000000000` represents the first file, the starting offset is 0, and the file size is 1G=1073741824, when the first `CommitLog` file is full, the second `CommitLog` file is `00000000001073741824`, the starting offset is 1073741824, and so on. The message is mainly appended to the log file sequentially. When one `CommitLog` file is full, the next will be written. * `ConsumeQueue`: The `ConsumeQueue` is used to improve the performance of message consumption. Since RocketMQ uses topic-based subscription mode, message consumption is specific to the topic. Traversing the commitlog file to retrieve messages of one topic is very inefficient. The consumer can find the messages to be consumed according to the `ConsumeQueue`. The `ConsumeQueue`(logic consume queue) as an index of the consuming message stores the starting physical offset `offset` in `CommitLog` of the specified topic, the message size `size` and the hash code of the message tag. The `ConsumeQueue` file can be regarded as a topic-based `CommitLog` index file, so the consumequeue folder is organized as follows: `topic/queue/file` three-layer organization structure, the specific storage path is `$HOME/store/consumequeue/{topic}/{queueId }/{fileName}`. The consumequeue file uses a fixed-length design, each entry occupies 20 bytes, which is an 8-byte commitlog physical offset, a 4-byte message length, and an 8-byte tag hashcode. One consumequeue file consists of 0.3 million entries, each entry can be randomly accessed like an array, each `ConsumeQueue` file's size is about 5.72MB. * `IndexFile`: The `IndexFile` provides a way to query messages by key or time interval. The path of the `IndexFile` is `$HOME/store/index/${fileName}`, the file name `fileName` is named after the timestamp when it was created. One IndexFile's size is about 400M, and it can store 2000W indexes. The underlying storage of `IndexFile` is designed to implement the `HashMap` structure in the file system, so RocketMQ's index file is a hash index. From the above architecture of the RocketMQ message storage, we can see RocketMQ uses a hybrid storage structure, that is, all the queues in an instance of the broker share a single log file `CommitLog` to store messages. RocketMQ's hybrid storage structure(messages of multiple topics are stored in one CommitLog) uses a separate storage structure for the data and index parts for Producer and Consumer respectively. The Producer sends the message to the Broker, then the Broker persists the message to the CommitLog file synchronously or asynchronously. As long as the message is persisted to the CommitLog on the disk, the message sent by the Producer will not be lost. Because of this, Consumer will definitely have the opportunity to consume this message. When no message can be pulled, the consumer can wait for the next pull. And the server also supports the long polling mode: if a pull request pulls no messages, the Broker can wait for 30 seconds, as long as new message arrives in this interval, it will be returned directly to the consumer. Here, RocketMQ's specific approach is using Broker's background service thread `ReputMessageService` to continuously dispatch requests and asynchronously build ConsumeQueue (Logical Queue) and IndexFile data. ## 2 PageCache and Memory Map PageCache is a cache of files by the operating system to speed up the reading and writing of files. In general, the speed of sequential read and write files is almost the same as the speed of read and write memory. The main reason is that the OS uses a portion of the memory as PageCache to optimize the performance of the read and write operations. For data writing, the OS will first write to the Cache, and then the `pdflush` kernel thread asynchronously flush the data in the Cache to the physical disk. For data reading, if it can not hit the page cache when reading a file at a time, the OS will read the file from the physical disk and prefetch the data files of other neighboring blocks sequentially. In RocketMQ, the logic consumption queue `ConsumeQueue` stores less data and is read sequentially. With the help of prefetch of the page cache mechanism, the read performance of the `ConsumeQueue` file is almost close to the memory read, even in the case of message accumulation, it does not affect performance. But for the log data file `CommitLog`, it will generate many random access reads when reading the message content, which seriously affects the performance. If you choose the appropriate IO scheduling algorithm, such as setting the IO scheduling algorithm to "Deadline" (when the block storage uses SSD), the performance of random reads will also be improved. In addition, RocketMQ mainly reads and writes files through `MappedByteBuffer`. `MappedByteBuffer` uses the `FileChannel` model in NIO to directly map the physical files on the disk to the memory address in user space (`Mmap` method reduces the performance overhead of traditional IO copying disk file data back and forth between the buffer in kernel space and the buffer in user space), it converts the file operation into direct memory address manipulation, which greatly improves the efficiency of reading and writing files (Because of the need to use the memory mapping mechanism, RocketMQ's file storage is fixed-length, making it easy to map the entire file to memory at a time). ## 3 Message Disk Flush ![](images/rocketmq_storage_flush.png) * synchronous flush: As shown above, the RocketMQ's Broker will return a successful `ACK` response to the Producer after the message is truly persisted to disk. Synchronous flushing is a good guarantee for the reliability of MQ messages, but it will have a big impact on performance. Generally, it is suitable for financial business applications. * asynchronous flush: Asynchronous flushing can take full advantage of the PageCache of the OS, as long as the message is written to the PageCache, the successful `ACK` can be returned to the Producer. The message flushing is performed by the background asynchronous thread, which reduces the read and write delay and improves the performance and throughput of the MQ. ================================================ FILE: docs/en/Design_Trancation.md ================================================ # Transaction Message ## 1 Transaction Message Apache RocketMQ supports distributed transaction message from version 4.3.0. RocketMQ implements transaction message by using the protocol of 2PC(two-phase commit), in addition adding a compensation logic to handle timeout-case or failure-case of commit-phase, as shown below. ![](../cn/image/rocketmq_design_10.png) ### 1.1 The Process of RocketMQ Transaction Message The picture above shows the overall architecture of transaction message, including the sending of message(commit-request phase), the sending of commit/rollback(commit phase) and the compensation process. 1 The sending of message and Commit/Rollback. (1) Sending the message(named Half message in RocketMQ) (2) The server responds the writing result(success or failure) of Half message. (3) Handle local transaction according to the result(local transaction won't be executed when the result is failure). (4) Sending Commit/Rollback to broker according to the result of local transaction(Commit will generate message index and make the message visible to consumers). 2 Compensation process (1) For a transaction message without a Commit/Rollback (means the message in the pending status), a "back-check" request is initiated from the broker. (2) The Producer receives the "back-check" request and checks the status of the local transaction corresponding to the "back-check" message. (3) Redo Commit or Rollback based on local transaction status. The compensation phase is used to resolve the timeout or failure case of the message Commit or Rollback. ### 1.2 The design of RocketMQ Transaction Message 1 Transaction message is invisible to users in first phase(commit-request phase) In the main process of transaction message, the message of first phase is invisible to the user. This is also the biggest difference from normal message. So how do we write the message while making it invisible to the user? And below is the solution of RocketMQ: if the message is a Half message, the topic and queueId of the original message will be backed up, and then changes the topic to RMQ_SYS_TRANS_HALF_TOPIC. Since the consumer group does not subscribe to the topic, the consumer cannot consume the Half message. Then RocketMQ starts a timing task, pulls the message for RMQ_SYS_TRANS_HALF_TOPIC, obtains a channel according to producer group and sends a back-check to query local transaction status, and decide whether to submit or roll back the message according to the status. In RocketMQ, the storage structure of the message in the broker is as follows. Each message has corresponding index information. The Consumer reads the content of the message through the secondary index of the ConsumeQueue. The flow is as follows: ![](../cn/image/rocketmq_design_11.png) The specific implementation strategy of RocketMQ is: if the transaction message is written, topic and queueId of the message are replaced, and the original topic and queueId are stored in the properties of the message. Because the replace of the topic, the message will not be forwarded to the Consumer Queue of the original topic, and the consumer cannot perceive the existence of the message and will not consume it. In fact, changing the topic is the conventional method of RocketMQ(just recall the implementation mechanism of the delay message). 2 Commit/Rollback operation and introduction of Op message After finishing writing a message that is invisible to the user in the first phase, here comes two cases in the second phase. One is Commit operation, after which the message needs to be visible to the user; the other one is Rollback operation, after which the first phase message(Half message) needs to be revoked. For the case of Rollback, since first-phase message itself is invisible to the user, there is no need to actually revoke the message (in fact, RocketMQ can't actually delete a message because it is a sequential-write file). But still some operation needs to be done to identity the final status of the message, to differ it from pending status message. To do this, the concept of "Op message" is introduced, which means the message has a certain status(Commit or Rollback). If a transaction message does not have a corresponding Op message, the status of the transaction is still undetermined (probably the second-phase failed). By introducing the Op message, the RocketMQ records an Op message for every Half message regardless it is Commit or Rollback. The only difference between Commit and Rollback is that when it comes to Commit, the index of the Half message is created before the Op message is written. 3 How Op message stored and the correspondence between Op message and Half message RocketMQ writes the Op message to a specific system topic(RMQ_SYS_TRANS_OP_HALF_TOPIC) which will be created via the method - TransactionalMessageUtil.buildOpTopic(); this topic is an internal Topic (like the topic of RMQ_SYS_TRANS_HALF_TOPIC) and will not be consumed by the user. The content of the Op message is the physical offset of the corresponding Half message. Through the Op message we can index to the Half message for subsequent check-back operation. ![](../cn/image/rocketmq_design_12.png) 4 Index construction of Half messages When performing Commit operation of the second phase, the index of the Half message needs to be built. Since the Half message is written to a special topic(RMQ_SYS_TRANS_HALF_TOPIC) in the first phase of 2PC, so it needs to be read out from the special topic when building index, and replace the topic and queueId with the real target topic and queueId, and then write through a normal message that is visible to the user. Therefore, in conclusion, the second phase recovers a complete normal message using the content of the Half message stored in the first phase, and then goes through the message-writing process. 5 How to handle the message failed in the second phase? If commit/rollback phase fails, for example, a network problem causes the Commit to fail when you do Commit. Then certain strategy is required to make sure the message finally commit. RocketMQ uses a compensation mechanism called "back-check". The broker initiates a back-check request for the message in pending status, and sends the request to the corresponding producer side (the same producer group as the producer group who sent the Half message). The producer checks the status of local transaction and redo Commit or Rollback. The broker performs the back-check by comparing the RMQ_SYS_TRANS_HALF_TOPIC messages and the RMQ_SYS_TRANS_OP_HALF_TOPIC messages and advances the checkpoint(recording those transaction messages that the status are certain). RocketMQ does not back-check the status of transaction messages endlessly. The default time is 15. If the transaction status is still unknown after 15 times, RocketMQ will roll back the message by default. ================================================ FILE: docs/en/Example_Batch.md ================================================ # Batch Message Sample ------ Sending messages in batch improves performance of delivering small messages. Messages of the same batch should have: same topic, same waitStoreMsgOK and no schedule support. You can send messages up to 4MiB at a time, but if you need to send a larger message, it is recommended to divide the larger messages into multiple small messages of no more than 1MiB. ### 1 Send Batch Messages If you just send messages of no more than 4MiB at a time, it is easy to use batch: ```java String topic = "BatchTest"; List messages = new ArrayList<>(); messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes())); messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes())); messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes())); try { producer.send(messages); } catch (Exception e) { e.printStackTrace(); //handle the error } ``` ### 2 Split into Lists The complexity only grow when you send large batch and you may not sure if it exceeds the size limit (4MiB). At this time, you’d better split the lists: ```java public class ListSplitter implements Iterator> { private final int SIZE_LIMIT = 1024 * 1024 * 4; private final List messages; private int currIndex; public ListSplitter(List messages) { this.messages = messages; } @Override public boolean hasNext() { return currIndex < messages.size(); } @Override public List next() { int startIndex = getStartIndex(); int nextIndex = startIndex; int totalSize = 0; for (; nextIndex < messages.size(); nextIndex++) { Message message = messages.get(nextIndex); int tmpSize = calcMessageSize(message); if (tmpSize + totalSize > SIZE_LIMIT) { break; } else { totalSize += tmpSize; } } List subList = messages.subList(startIndex, nextIndex); currIndex = nextIndex; return subList; } private int getStartIndex() { Message currMessage = messages.get(currIndex); int tmpSize = calcMessageSize(currMessage); while(tmpSize > SIZE_LIMIT) { currIndex += 1; Message message = messages.get(curIndex); tmpSize = calcMessageSize(message); } return currIndex; } private int calcMessageSize(Message message) { int tmpSize = message.getTopic().length() + message.getBody().length; Map properties = message.getProperties(); for (Map.Entry entry : properties.entrySet()) { tmpSize += entry.getKey().length() + entry.getValue().length(); } tmpSize = tmpSize + 20; // Increase the log overhead by 20 bytes return tmpSize; } } // then you could split the large list into small ones: ListSplitter splitter = new ListSplitter(messages); while (splitter.hasNext()) { try { List listItem = splitter.next(); producer.send(listItem); } catch (Exception e) { e.printStackTrace(); // handle the error } } ``` ================================================ FILE: docs/en/Example_Compaction_Topic.md ================================================ # Compaction Topic ## use example ### Turn on the opening of support for orderMessages on namesrv CompactionTopic relies on orderMessages to ensure consistency ```shell $ bin/mqadmin updateNamesrvConfig -k orderMessageEnable -v true ``` ### create compaction topic ```shell $ bin/mqadmin updateTopic -w 8 -r 8 -a +cleanup.policy=COMPACTION -n localhost:9876 -t ctopic -o true -c DefaultCluster create topic to 127.0.0.1:10911 success. TopicConfig [topicName=ctopic, readQueueNums=8, writeQueueNums=8, perm=RW-, topicFilterType=SINGLE_TAG, topicSysFlag=0, order=false, attributes={+cleanup.policy=COMPACTION}] ``` ### produce message the same with ordinary message ```java DefaultMQProducer producer = new DefaultMQProducer("CompactionTestGroup"); producer.setNamesrvAddr("localhost:9876"); producer.start(); String topic = "ctopic"; String tag = "tag1"; String key = "key1"; Message msg = new Message(topic, tag, key, "bodys".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(msg, (mqs, message, shardingKey) -> { int select = Math.abs(shardingKey.hashCode()); if (select < 0) { select = 0; } return mqs.get(select % mqs.size()); }, key); System.out.printf("%s%n", sendResult); ``` ### consume message the message offset remains unchanged after compaction. If the consumer specified offset does not exist, return the most recent message after the offset. In the compaction scenario, most consumption was started from the beginning of the queue. ```java DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("compactionTestGroup"); consumer.setNamesrvAddr("localhost:9876"); consumer.setPullThreadNums(4); consumer.start(); Collection messageQueueList = consumer.fetchMessageQueues("ctopic"); consumer.assign(messageQueueList); messageQueueList.forEach(mq -> { try { consumer.seekToBegin(mq); } catch (MQClientException e) { e.printStackTrace(); } }); Map kvStore = Maps.newHashMap(); while (true) { List msgList = consumer.poll(1000); if (CollectionUtils.isNotEmpty(msgList)) { msgList.forEach(msg -> kvStore.put(msg.getKeys(), msg.getBody())); } } //use the kvStore ``` ================================================ FILE: docs/en/Example_CreateTopic.md ================================================ # Create Topic ## Background The `TopicMessageType` concept is introduced in RocketMQ 5.0, using the existing topic attribute feature to implement it. The topic is created by `mqadmin` tool declaring the `message.type` attribute. ## User Example ```shell # default sh ./mqadmin updateTopic -n -t -c DefaultCluster # normal topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=NORMAL # fifo topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=FIFO # delay topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=DELAY # transaction topic sh ./mqadmin updateTopic -n -t -c DefaultCluster -a +message.type=TRANSACTION ``` ================================================ FILE: docs/en/Example_Delay.md ================================================ # Schedule example ### 1 Start consumer to wait for incoming subscribed messages ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class ScheduledMessageConsumer { public static void main(String[] args) throws Exception { // Instantiate message consumer DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer"); // Specify name server addresses consumer.setNamesrvAddr("localhost:9876"); // Subscribe topics consumer.subscribe("TestTopic", "*"); // Register message listener consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) { for (MessageExt message : messages) { // Print approximate delay time period System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // Launch consumer consumer.start(); } } ``` ### 2 Send scheduled messages ```java import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; public class ScheduledMessageProducer { public static void main(String[] args) throws Exception { // Instantiate a producer to send scheduled messages DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch producer producer.start(); int totalMessagesToSend = 100; for (int i = 0; i < totalMessagesToSend; i++) { Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes()); // This message will be delivered to consumer 10 seconds later. message.setDelayTimeLevel(3); // Send the message producer.send(message); } // Shutdown producer after use. producer.shutdown(); } } ``` ### 3 Verification You should see messages are consumed about 10 seconds later than their storing time. ### 4 Use scenarios for scheduled messages For example, in e-commerce, if an order is submitted, a delay message can be sent, and the status of the order can be checked after 1 hour. If the order is still unpaid, the order can be cancelled and the inventory released. ### 5 Restrictions on the use of scheduled messages ```java // org/apache/rocketmq/store/config/MessageStoreConfig.java private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; ``` Nowadays RocketMq does not support any time delay. It needs to set several fixed delay levels, which correspond to level 1 to 18 from 1s to 2h. Message consumption failure will enter the delay message queue. Message sending time is related to the set delay level and the number of retries. See `SendMessageProcessor.java` ================================================ FILE: docs/en/Example_Filter.md ================================================ # Filter Example ---------- In most cases, tag is a simple and useful design to select messages you want. For example: ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE"); consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC"); ``` The consumer will receive messages that contains TAGA or TAGB or TAGC. But the limitation is that one message only can have one tag, and this may not work for sophisticated scenarios. In this case, you can use SQL expression to filter out messages. SQL feature could do some calculation through the properties you put in when sending messages. Under the grammars defined by RocketMQ, you can implement some interesting logic. Here is an example: ``` ------------ | message | |----------| a > 5 AND b = 'abc' | a = 10 | --------------------> Gotten | b = 'abc'| | c = true | ------------ ------------ | message | |----------| a > 5 AND b = 'abc' | a = 1 | --------------------> Missed | b = 'abc'| | c = true | ------------ ``` ## 1 Grammars RocketMQ only defines some basic grammars to support this feature. You could also extend it easily. - Numeric comparison, like **>**, **>=**, **<**, **<=**, **BETWEEN**, **=**; - Character comparison, like **=**, **<>**, **IN**; - **IS NULL** or **IS NOT NULL**; - Logical **AND**, **OR**, **NOT**; Constant types are: - Numeric, like **123, 3.1415**; - Character, like **‘abc’**, must be made with single quotes; - **NULL**, special constant; - Boolean, **TRUE** or **FALSE**; ## 2 Usage constraints Only push consumer could select messages by SQL92. The interface is: ``` public void subscribe(finalString topic, final MessageSelector messageSelector) ``` ## 3 Producer example You can put properties in message through method putUserProperty when sending. ```java DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); Message msg = new Message("TopicTest", tag, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) ); // Set some properties. msg.putUserProperty("a", String.valueOf(i)); SendResult sendResult = producer.send(msg); producer.shutdown(); ``` ## 4 Consumer example Use `MessageSelector.bySql` to select messages through SQL when consuming. ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4"); // only subscribe messages have property a, also a >=0 and a <= 3 consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3")); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); ``` ================================================ FILE: docs/en/Example_OpenMessaging.md ================================================ # OpenMessaging Example [OpenMessaging](https://openmessaging.github.io/), which includes the establishment of industry guidelines and messaging, streaming specifications to provide a common framework for finance, e-commerce, IoT and big-data area. The design principles are the cloud-oriented, simplicity, flexibility, and language independent in distributed heterogeneous environments. Conformance to these specifications will make it possible to develop a heterogeneous messaging applications across all major platforms and operating systems. RocketMQ provides a partial implementation of OpenMessaging 0.1.0-alpha, the following examples demonstrate how to access RocketMQ based on OpenMessaging. ## OMSProducer The following example shows how to send message to RocketMQ broker in synchronous, asynchronous, or one-way transmissions. ``` public class OMSProducer { public static void main(String[] args) { final MessagingAccessPoint messagingAccessPoint = MessagingAccessPointFactory .getMessagingAccessPoint("openmessaging:rocketmq://IP1:9876,IP2:9876/namespace"); final Producer producer = messagingAccessPoint.createProducer(); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); producer.startup(); System.out.printf("Producer startup OK%n"); { Message message = producer.createBytesMessageToTopic("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8"))); SendResult sendResult = producer.send(message); System.out.printf("Send sync message OK, msgId: %s%n", sendResult.messageId()); } { final Promise result = producer.sendAsync(producer.createBytesMessageToTopic("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8")))); result.addListener(new PromiseListener() { @Override public void operationCompleted(Promise promise) { System.out.printf("Send async message OK, msgId: %s%n", promise.get().messageId()); } @Override public void operationFailed(Promise promise) { System.out.printf("Send async message Failed, error: %s%n", promise.getThrowable().getMessage()); } }); } { producer.sendOneway(producer.createBytesMessageToTopic("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8")))); System.out.printf("Send oneway message OK%n"); } producer.shutdown(); messagingAccessPoint.shutdown(); } } ``` ## OMSPullConsumer Use OMS PullConsumer to poll messages from a specified queue. ``` public class OMSPullConsumer { public static void main(String[] args) { final MessagingAccessPoint messagingAccessPoint = MessagingAccessPointFactory .getMessagingAccessPoint("openmessaging:rocketmq://IP1:9876,IP2:9876/namespace"); final PullConsumer consumer = messagingAccessPoint.createPullConsumer("OMS_HELLO_TOPIC", OMS.newKeyValue().put(NonStandardKeys.CONSUMER_GROUP, "OMS_CONSUMER")); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); consumer.startup(); System.out.printf("Consumer startup OK%n"); Message message = consumer.poll(); if (message != null) { String msgId = message.headers().getString(MessageHeader.MESSAGE_ID); System.out.printf("Received one message: %s%n", msgId); consumer.ack(msgId); } consumer.shutdown(); messagingAccessPoint.shutdown(); } } ``` ## OMSPushConsumer Attaches OMS PushConsumer to a specified queue and consumes messages by MessageListener ``` public class OMSPushConsumer { public static void main(String[] args) { final MessagingAccessPoint messagingAccessPoint = MessagingAccessPointFactory .getMessagingAccessPoint("openmessaging:rocketmq://IP1:9876,IP2:9876/namespace"); final PushConsumer consumer = messagingAccessPoint. createPushConsumer(OMS.newKeyValue().put(NonStandardKeys.CONSUMER_GROUP, "OMS_CONSUMER")); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { consumer.shutdown(); messagingAccessPoint.shutdown(); } })); consumer.attachQueue("OMS_HELLO_TOPIC", new MessageListener() { @Override public void onMessage(final Message message, final ReceivedMessageContext context) { System.out.printf("Received one message: %s%n", message.headers().getString(MessageHeader.MESSAGE_ID)); context.ack(); } }); } } ``` ================================================ FILE: docs/en/Example_Orderly.md ================================================ # Example for Ordered Messages RocketMQ provides ordered messages using FIFO order. All related messages need to be sent into the same message queue in an orderly manner. The following demonstrates ordered messages by ensuring order of create, pay, send and finish steps of sales order process. ## 1 produce ordered messages ```java package org.apache.rocketmq.example.order2 import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /* * ordered messages producer */ public class Producer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); String[] tags = new String[]{"TagA", "TagC", "TagD"}; // sales orders list List orderList = new Producer().buildOrders(); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); for (int i = 0; i < 10; i++) { // generate message timestamp String body = dateStr + " Hello RocketMQ " + orderList.get(i); Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i, body.getBytes()); SendResult sendResult = producer.send(msg, new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { Long id = (Long) arg; //message queue is selected by #salesOrderID long index = id % mqs.size(); return mqs.get((int) index); } }, orderList.get(i).getOrderId()); System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s", sendResult.getSendStatus(), sendResult.getMessageQueue().getQueueId(), body)); } producer.shutdown(); } /** * each sales order step */ private static class OrderStep { private long orderId; private String desc; public long getOrderId() { return orderId; } public void setOrderId(long orderId) { this.orderId = orderId; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "OrderStep{" + "orderId=" + orderId + ", desc='" + desc + '\'' + '}'; } } /** * to generate ten OrderStep objects for three sales orders: * #SalesOrder "15103111039L": create, pay, send, finish; * #SalesOrder "15103111065L": create, pay, finish; * #SalesOrder "15103117235L": create, pay, finish; */ private List buildOrders() { List orderList = new ArrayList(); //create sales order with orderid="15103111039L" OrderStep orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("create"); orderList.add(orderDemo); //create sales order with orderid="15103111065L" orderDemo = new OrderStep(); orderDemo.setOrderId(15103111065L); orderDemo.setDesc("create"); orderList.add(orderDemo); //pay sales order #"15103111039L" orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("pay"); orderList.add(orderDemo); //create sales order with orderid="15103117235L" orderDemo = new OrderStep(); orderDemo.setOrderId(15103117235L); orderDemo.setDesc("create"); orderList.add(orderDemo); //pay sales order #"15103111065L" orderDemo = new OrderStep(); orderDemo.setOrderId(15103111065L); orderDemo.setDesc("pay"); orderList.add(orderDemo); //pay sales order #"15103117235L" orderDemo = new OrderStep(); orderDemo.setOrderId(15103117235L); orderDemo.setDesc("pay"); orderList.add(orderDemo); //mark sales order #"15103111065L" as "finish" orderDemo = new OrderStep(); orderDemo.setOrderId(15103111065L); orderDemo.setDesc("finish"); orderList.add(orderDemo); //mark mark sales order #"15103111039L" as "send" orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("send"); orderList.add(orderDemo); ////mark sales order #"15103117235L" as "finish" orderDemo = new OrderStep(); orderDemo.setOrderId(15103117235L); orderDemo.setDesc("finish"); orderList.add(orderDemo); //mark sales order #"15103111039L" as "finish" orderDemo = new OrderStep(); orderDemo.setOrderId(15103111039L); orderDemo.setDesc("finish"); orderList.add(orderDemo); return orderList; } } ``` ## 2 Consume ordered messages ```java package org.apache.rocketmq.example.order2; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; /** * consume messages in order */ public class ConsumerInOrder { public static void main(String[] args) throws Exception { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3"); consumer.setNamesrvAddr("127.0.0.1:9876"); /** * when the consumer is first run, the start point of message queue where it can get messages will be set. * or if it is restarted, it will continue from the last place to get messages. */ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicTest", "TagA || TagC || TagD"); consumer.registerMessageListener(new MessageListenerOrderly() { Random random = new Random(); @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { context.setAutoCommit(true); for (MessageExt msg : msgs) { // one consumer for each message queue, and messages order are kept in a single message queue. System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody())); } try { TimeUnit.SECONDS.sleep(random.nextInt(10)); } catch (Exception e) { e.printStackTrace(); } return ConsumeOrderlyStatus.SUCCESS; } }); consumer.start(); System.out.println("Consumer Started."); } } ``` ================================================ FILE: docs/en/Example_Simple.md ================================================ # Basic Sample ------ Two functions below are provided in the basic sample: * The RocketMQ can be utilized to send messages in three ways: reliable synchronous, reliable asynchronous, and one-way transmission. The first two message types are reliable because there is a response whether they were sent successfully. * The RocketMQ can be utilized to consume messages. ### 1 Add Dependency maven: ``` java org.apache.rocketmq rocketmq-client 4.3.0 ``` gradle: ``` java compile 'org.apache.rocketmq:rocketmq-client:4.3.0' ``` ### 2 Send Messages ##### 2.1 Use Producer to Send Synchronous Messages Reliable synchronous transmission is used in extensive scenes, such as important notification messages, SMS notification. ``` java public class SyncProducer { public static void main(String[] args) throws Exception { // Instantiate with a producer group name DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch the producer instance producer.start(); for (int i = 0; i < 100; i++) { // Create a message instance with specifying topic, tag and message body Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // Send message to one of brokers SendResult sendResult = producer.send(msg); // Check whether the message has been delivered by the callback of sendResult System.out.printf("%s%n", sendResult); } // Shut down once the producer instance is not longer in use producer.shutdown(); } } ``` ##### 2.2 Send Asynchronous Messages Asynchronous transmission is generally used in response time sensitive business scenarios. It means that it is unable for the sender to wait the response of the Broker too long. ``` java public class AsyncProducer { public static void main(String[] args) throws Exception { // Instantiate with a producer group name DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch the producer instance producer.start(); producer.setRetryTimesWhenSendAsyncFailed(0); for (int i = 0; i < 100; i++) { final int index = i; // Create a message instance with specifying topic, tag and message body Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); // SendCallback: receive the callback of the asynchronous return result. producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId()); } @Override public void onException(Throwable e) { System.out.printf("%-10d Exception %s %n", index, e); e.printStackTrace(); } }); } // Shut down once the producer instance is not longer in use producer.shutdown(); } } ``` ##### 2.3 Send Messages in One-way Mode One-way transmission is used for cases requiring moderate reliability, such as log collection. ``` java public class OnewayProducer { public static void main(String[] args) throws Exception{ // Instantiate with a producer group name DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses producer.setNamesrvAddr("localhost:9876"); // Launch the producer instance producer.start(); for (int i = 0; i < 100; i++) { // Create a message instance with specifying topic, tag and message body Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // Send in one-way mode, no return result producer.sendOneway(msg); } // Shut down once the producer instance is not longer in use producer.shutdown(); } } ``` ### 3 Consume Messages ``` java public class Consumer { public static void main(String[] args) throws InterruptedException, MQClientException { // Instantiate with specified consumer group name DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); // Specify name server addresses consumer.setNamesrvAddr("localhost:9876"); // Subscribe one or more topics and tags for finding those messages need to be consumed consumer.subscribe("TopicTest", "*"); // Register callback to execute on arrival of messages fetched from brokers consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); // Mark the message that have been consumed successfully return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // Launch the consumer instance consumer.start(); System.out.printf("Consumer Started.%n"); } } ``` ================================================ FILE: docs/en/Example_Transaction.md ================================================ # Transaction Message Example ## 1 Transaction message status There are three states for transaction message: - LocalTransactionState.COMMIT_MESSAGE: commit transaction, it means that allow consumers to consume this message. - LocalTransactionState.ROLLBACK_MESSAGE: rollback transaction, it means that the message will be deleted and not allowed to consume. - LocalTransactionState.UNKNOW: intermediate state, it means that MQ is needed to check back to determine the status. ## 2 Send transactional message example ### 2.1 Create the transactional producer Use ```TransactionMQProducer```class to create producer client, and specify a unique ```ProducerGroup```, and you can set up a custom thread pool to process check requests. After executing the local transaction, you need to reply to MQ according to the execution result, and the reply status is described in the above section. ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class TransactionProducer { public static void main(String[] args) throws MQClientException, InterruptedException { TransactionListener transactionListener = new TransactionListenerImpl(); TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name"); ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue(2000), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("client-transaction-msg-check-thread"); return thread; } }); producer.setExecutorService(executorService); producer.setTransactionListener(transactionListener); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 10; i++) { try { Message msg = new Message("TopicTest1234", tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); Thread.sleep(10); } catch (MQClientException | UnsupportedEncodingException e) { e.printStackTrace(); } } for (int i = 0; i < 100000; i++) { Thread.sleep(1000); } producer.shutdown(); } } ``` ### 2.2 Implement the TransactionListener interface The ```executeLocalTransaction``` method is used to execute local transaction when send half message succeed. It returns one of three transaction status mentioned in the previous section. The ```checkLocalTransaction``` method is used to check the local transaction status and respond to MQ check requests. It also returns one of three transaction status mentioned in the previous section. ```java public class TransactionListenerImpl implements TransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); private ConcurrentHashMap localTrans = new ConcurrentHashMap<>(); @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { int value = transactionIndex.getAndIncrement(); int status = value % 3; localTrans.put(msg.getTransactionId(), status); return LocalTransactionState.UNKNOW; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { Integer status = localTrans.get(msg.getTransactionId()); if (null != status) { switch (status) { case 0: return LocalTransactionState.UNKNOW; case 1: return LocalTransactionState.COMMIT_MESSAGE; case 2: return LocalTransactionState.ROLLBACK_MESSAGE; } } return LocalTransactionState.COMMIT_MESSAGE; } } ``` ## 3 Usage Constraint 1. Messages of the transactional have no schedule and batch support. 2. In order to avoid a single message being checked too many times and lead to half queue message accumulation, we limited the number of checks for a single message to 15 times by default, but users can change this limit by change the ```transactionCheckMax``` parameter in the configuration of the broker, if one message has been checked over ```transactionCheckMax``` times, broker will discard this message and print an error log at the same time by default. Users can change this behavior by override the ```AbstractTransactionalMessageCheckListener``` class. 3. A transactional message will be checked after a certain period of time that determined by parameter ```transactionTimeout``` in the configuration of the broker. And users also can change this limit by set user property ```CHECK_IMMUNITY_TIME_IN_SECONDS``` when sending transactional message, this parameter takes precedence over the ```transactionTimeout``` parameter. 4. A transactional message maybe checked or consumed more than once. 5. Committed message reput to the user’s target topic may fail. Currently, it depends on the log record. High availability is ensured by the high availability mechanism of RocketMQ itself. If you want to ensure that the transactional message isn’t lost and the transaction integrity is guaranteed, it is recommended to use synchronous double write mechanism. 6. `producerGroup` for producers of transactional messages cannot be shared with `producerGroup` for producers of other types of messages. Unlike other types of message, transactional messages allow backward queries. MQ Server query clients by their `producerGroup` of producers. ================================================ FILE: docs/en/FAQ.md ================================================ # Frequently Asked Questions The following questions are frequently asked with regard to the RocketMQ project in general. ## 1 General 1. Why did we create rocketmq project instead of selecting other products? Please refer to [Why RocketMQ](http://rocketmq.apache.org/docs/motivation) 2. Do I have to install other software, such as zookeeper, to use RocketMQ? No. RocketMQ can run independently. ## 2 Usage ### 1. Where does the newly created Consumer ID start consuming messages?  1) If the topic sends a message within three days, then the consumer start consuming messages from the first message saved in the server.  2) If the topic sends a message three days ago, the consumer starts to consume messages from the latest message in the server, in other words, starting from the tail of message queue.  3) If such consumer is rebooted, then it starts to consume messages from the last consumption location. ### 2. How to reconsume message when consumption fails?  1) Cluster consumption pattern, The consumer business logic code returns Action.ReconsumerLater, NULL, or throws an exception, if a message failed to be consumed, it will retry for up to 16 times, after that, the message would be discarded.  2) Broadcast consumption pattern. The broadcast consumption still ensures that a message is consumed at least once, but no resend option is provided. ### 3. How to query the failed message if there is a consumption failure?  1) Using topic query by time, you can query messages within a period of time.  2) Using Topic and Message Id to accurately query the message.  3) Using Topic and Message Key accurately query a class of messages with the same Message Key. ### 4. Are messages delivered exactly once? RocketMQ ensures that all messages are delivered at least once. In most cases, the messages are not repeated. ### 5. How to add a new broker?  1) Start up a new broker and register it to the same list of name servers.  2) By default, only internal system topics and consumer groups are created automatically. If you would like to have your business topic and consumer groups on the new node, please replicate them from the existing broker. Admin tool and command lines are provided to handle this. ## 3 Configuration related The following answers are all default values and can be modified by configuration. ### 1. How long are the messages saved on the server? Stored messages will be saved for up to 3 days, and messages that are not consumed for more than 3 days will be deleted. ### 2. What is the size limit for message Body? Generally 256KB. ### 3. How to set the number of consumer threads? When you start Consumer, set a ConsumeThreadNums property, example is as follows: ``` consumer.setConsumeThreadMin(20); consumer.setConsumeThreadMax(20); ``` ## 4 Errors ### 1. If you start a producer or consumer failed and the error message is producer group or consumer repeat. Reason: Using the same Producer /Consumer Group to launch multiple instances of Producer/Consumer in the same JVM may cause the client fail to start. Solution: Make sure that a JVM corresponding to one Producer /Consumer Group starts only with one Producer/Consumer instance. ### 2. Consumer failed to start loading json file in broadcast mode. Reason: Fastjson version is too low to allow the broadcast consumer to load local offsets.json, causing the consumer boot failure. Damaged fastjson file can also cause the same problem. Solution: Fastjson version has to be upgraded to rocketmq client dependent version to ensure that the local offsets.json can be loaded. By default offsets.json file is in /home/{user}/.rocketmq_offsets. Or check the integrity of fastjson. ### 3. What is the impact of a broker crash.  1) Master crashes Messages can no longer be sent to this broker set, but if you have another broker set available, messages can still be sent given the topic is present. Messages can still be consumed from slaves.  2) Some slave crash As long as there is another working slave, there will be no impact on sending messages. There will also be no impact on consuming messages except when the consumer group is set to consume from this slave preferably. By default, consumer group consumes from master.  3) All slaves crash There will be no impact on sending messages to master, but, if the master is SYNC_MASTER, producer will get a SLAVE_NOT_AVAILABLE indicating that the message is not sent to any slaves. There will also be no impact on consuming messages except that if the consumer group is set to consume from slave preferably. By default, consumer group consumes from master. ### 4. Producer complains “No Topic Route Info”, how to diagnose? This happens when you are trying to send messages to a topic whose routing info is not available to the producer.  1) Make sure that the producer can connect to a name server and is capable of fetching routing meta info from it.  2) Make sure that name servers do contain routing meta info of the topic. You may query the routing meta info from name server through topicRoute using admin tools or web console.  3) Make sure that your brokers are sending heartbeats to the same list of name servers your producer is connecting to.  4) Make sure that the topic’s permission is 6(rw-), or at least 2(-w-). If you can’t find this topic, create it on a broker via admin tools command updateTopic or web console. ================================================ FILE: docs/en/Feature.md ================================================ # Features ## 1 Subscribe and Publish Message publication refers to that a producer sends messages to a topic; Message subscription means a consumer follows a topic with certain tags and then consumes data from that topic. ## 2 Message Ordering Message ordering refers to that a group of messages can be consumed orderly as they are published. For example, an order generates three messages: order creation, order payment, and order completion. It only makes sense to consume them in their generated order, but orders can be consumed in parallel at the same time. RocketMQ can strictly guarantee these messages are in order. Orderly message is divided into global orderly message and partitioned orderly message. Global order means that all messages under a certain topic must be in order, partitioned order only requires each group of messages are consumed orderly. - Global message ordering: For a given Topic, all messages are published and consumed in strict first-in-first-out (FIFO) order. Applicable scenario: the performance requirement is not high, and all messages are published and consumed according to FIFO principle strictly. - Partitioned message ordering: For a given Topic, all messages are partitioned according to sharding key. Messages within the same partition are published and consumed in strict FIFO order. Sharding key is the key field to distinguish message's partition, which is a completely different concept from the key of ordinary messages. Applicable scenario: high performance requirement, with sharding key as the partition field, messages within the same partition are published and consumed according to FIFO principle strictly. ## 3 Message Filter Consumers of RocketMQ can filter messages based on tags as well as supporting for user-defined attribute filtering. Message filter is currently implemented on the Broker side, with the advantage of reducing the network transmission of useless messages for Consumer and the disadvantage of increasing the burden on the Broker and relatively complex implementation. ## 4 Message Reliability RocketMQ supports high reliability of messages in several situations: 1 Broker shutdown normally 2 Broker abnormal crash 3 OS Crash 4 The machine is out of power, but it can be recovered immediately 5 The machine cannot be started up (the CPU, motherboard, memory and other key equipment may be damaged) 6 Disk equipment damaged In the four cases of 1), 2), 3), and 4) where the hardware resource can be recovered immediately, RocketMQ guarantees that the message will not be lost or a small amount of data will be lost (depending on whether the flush disk type is synchronous or asynchronous). 5 ) and 6) are single point of failure and cannot be recovered. Once it happens, all messages on the single point will be lost. In both cases, RocketMQ ensures that 99% of the messages are not lost through asynchronous replication, but a very few number of messages may still be lost. Synchronous double write mode can completely avoid single point of failure, which will surely affect the performance and suitable for the occasion of high demand for message reliability, such as money related applications. Note: RocketMQ supports synchronous double writes since version 3.0. ## 5 At Least Once At least Once refers to that every message will be delivered at least once. RocketMQ supports this feature because the Consumer pulls the message locally and does not send an ack back to the server until it has consumed it. ## 6 Backtracking Consumption Backtracking consumption refers to that the Consumer has consumed the message successfully, but the business needs to consume again. To support this function, the message still needs to be retained after the Broker sends the message to the Consumer successfully. The re-consumption is normally based on time dimension. For example, after the recovery of the Consumer system failures, the data one hour ago needs to be re-consumed, then the Broker needs to provide a mechanism to reverse the consumption progress according to the time dimension. RocketMQ supports backtracking consumption by time trace, with the time dimension down to milliseconds. ## 7 Transactional Message RocketMQ transactional message refers to the fact that the application of a local transaction and the sending of a Message operation can be defined in a global transaction which means both succeed or failed simultaneously. RocketMQ transactional message provides distributed transaction functionality similar to X/Open XA, enabling the ultimate consistency of distributed transactions through transactional message. ## 8 Scheduled Message Scheduled message(delay queue) refers to that messages are not consumed immediately after they are sent to the broker, but waiting to be delivered to the real topic after a specific time. The broker has a configuration item, `messageDelayLevel`, with default values “1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”, 18 levels. Users can configure a custom `messageDelayLevel`. Note that `messageDelayLevel` is a broker's property rather than a topic's. When sending a message, just set the delayLevel level: msg.setDelayLevel(level). There are three types of levels: - level == 0, The message is not a delayed message - 1<=level<=maxLevel, Message delay specific time, such as level==1, delay for 1s - level > maxLevel, than level== maxLevel, such as level==20, delay for 2h Scheduled messages are temporarily saved in a topic named SCHEDULE_TOPIC_XXXX, and saved in a specific queue according to delayTimeLevel, queueId = delayTimeLevel - 1, that is, only messages with the same delay are saved in a queue, ensuring that messages with the same sending delay can be consumed orderly. The broker consumes SCHEDULE_TOPIC_XXXX on schedule and writes messages to the real topic. Note that Scheduled messages are counted both the first time they are written and the time they are scheduled to be written to the real topic, so both the send number and the TPS are increased. ## 9 Message Retry When the Consumer fails to consume the message, a retry mechanism is needed to make the message to be consumed again. Consumer's consume failure can usually be classified as follows: - Due to the reasons of the message itself, such as deserialization failure, the message data itself cannot be processed (for example, the phone number of the current message is cancelled and cannot be charged), etc. This kind of error usually requires skipping this message and consuming others since immediately retry would be failed 99%, so it is better to provide a timed retry mechanism that retries after 10 seconds. - Due to the reasons of dependent downstream application services are not available, such as db connection is not usable, perimeter network is not unreachable, etc. When this kind of error is encountered, consuming other messages will also result in an error even if the current failed message is skipped. In this case, it is recommended to sleep for 30s before consuming the next message, which will reduce the pressure on the broker to retry the message. RocketMQ will set up a retry queue named “%RETRY%+consumerGroup” for each consumer group(Note that the retry queue for this topic is for consumer groups, not for each topic) to temporarily save messages cannot be consumed by customer due to all kinds of reasons. Considering that it takes some time for the exception to recover, multiple retry levels are set for the retry queue, and each retry level has a corresponding re-deliver delay. The more retries, the greater the deliver delay. RocketMQ first save retry messages to the delay queue which topic is named “SCHEDULE_TOPIC_XXXX”, then background schedule task will save the messages to “%RETRY%+consumerGroup” retry queue according to their corresponding delay. ## 10 Message Resend When a producer sends a message, the synchronous message will be resent if fails, the asynchronous message will retry and oneway message is without any guarantee. Message resend ensures that messages are sent successfully and without lost as much as possible, but it can lead to message duplication, which is an unavoidable problem in RocketMQ. Under normal circumstances, message duplication will not occur, but when there is a large number of messages and network jitter, message duplication will be a high-probability event. In addition, producer initiative messages resend and the consumer load changes will also result in duplicate messages. The message retry policy can be set as follows: - `retryTimesWhenSendFailed`: Synchronous message retry times when send failed, default value is 2, so the producer will try to send `retryTimesWhenSendFailed` + 1 times at most. To ensure that the message is not lost, producer will try sending the message to another broker instead of selecting the broker that failed last time. An exception will be thrown if it reaches the retry limit, and the client should guarantee that the message will not be lost. Messages will resend when RemotingException, MQClientException, and partial MQBrokerException occur. - `retryTimesWhenSendAsyncFailed`: Asynchronous message retry times when send failed, asynchronous retry sends message to the same broker instead of selecting another one and does not guarantee that the message wont lost. - `retryAnotherBrokerWhenNotStoreOK`: Message flush disk (master or slave) timeout or slave not available (return status is not SEND_OK), whether to try to send to another broker, default value is false. Very important messages can set to true. ## 11 Flow Control Producer flow control, because broker processing capacity reaches a bottleneck; Consumer flow control, because the consumption capacity reaches a bottleneck. Producer flow control: - When commitLog file locked time exceeds osPageCacheBusyTimeOutMills, default value of `osPageCacheBusyTimeOutMills` is 1000 ms, then return flow control. - If `transientStorePoolEnable` == true, and the broker is asynchronous flush disk type, and resources are insufficient in the transientStorePool, reject the current send request and return flow control. - The broker checks the head request wait time of the send request queue every 10ms. If the wait time exceeds waitTimeMillsInSendQueue, which default value is 200ms, the current send request is rejected and the flow control is returned. - The broker implements flow control by rejecting send requests. Consumer flow control: - When consumer local cache messages number exceeds pullThresholdForQueue, default value is 1000. - When consumer local cache messages size exceeds pullThresholdSizeForQueue, default value is 100MB. - When consumer local cache messages span exceeds consumeConcurrentlyMaxSpan, default value is 2000. The result of consumer flow control is to reduce the pull frequency. ## 12 Dead Letter Queue Dead letter queue is used to deal messages that cannot be consumed normally. When a message is consumed failed at first time, the message queue will automatically resend the message. If the consumption still fails after the maximum number retry, it indicates that the consumer cannot properly consume the message under normal circumstances. At this time, the message queue will not immediately abandon the message, but send it to the special queue corresponding to the consumer. RocketMQ defines the messages that could not be consumed under normal circumstances as Dead-Letter Messages, and the special queue in which the Dead-Letter Messages are saved as Dead-Letter Queues. In RocketMQ, the consumer instance can consume again by resending messages in the Dead-Letter Queue using console. ## 13 Pop Consuming Pop consuming refers to that broker fetches messages from queues owned by same broker and returns to clients, which ensures one queue will be consumed by multiple clients. The whole behavior is like a queue pop process. By invoking `setConsumeMode` sub command of mqadmin, one consumer group can be switch to POP consuming instead of classical PULL consuming without changing a single code line. The new pop consuming will help to mitigate the impact for one queue consuming of an abnormal behaving client. ================================================ FILE: docs/en/Operations_Broker.md ================================================ # 3 Broker ## 3.1 Broker Role Broker Role is ASYNC_MASTER, SYNC_MASTER or SLAVE. If you cannot tolerate message missing, we suggest you deploy SYNC_MASTER and attach a SLAVE to it. If you feel ok about missing, but you want the Broker to be always available, you may deploy ASYNC_MASTER with SLAVE. If you just want to make it easy, you may only need a ASYNC_MASTER without SLAVE. ## 3.2 FlushDiskType ASYNC_FLUSH is recommended, for SYNC_FLUSH is expensive and will cause too much performance loss. If you want reliability, we recommend you use SYNC_MASTER with SLAVE. ## 3.3 Broker Configuration | Parameter name | Default | Description | | -------------------------------- | ----------------------------- | ------------------------------------------------------------ | | listenPort | 10911 | listen port for client | | namesrvAddr | null | name server address | | brokerIP1 | InetAddress for network interface | Should be configured if having multiple addresses | | brokerIP2 | InetAddress for network interface | If configured for the Master broker in the Master/Slave cluster, slave broker will connect to this port for data synchronization | | brokerName | null | broker name | | brokerClusterName | DefaultCluster | this broker belongs to which cluster | | brokerId | 0 | broker id, 0 means master, positive integers mean slave | | storePathCommitLog | $HOME/store/commitlog/ | file path for commit log | | storePathConsumerQueue | $HOME/store/consumequeue/ | file path for consume queue | | mappedFileSizeCommitLog | 1024 * 1024 * 1024(1G) | mapped file size for commit log |​ | deleteWhen | 04 | When to delete the commitlog which is out of the reserve time |​ | fileReserverdTime | 72 | The number of hours to keep a commitlog before deleting it |​ | brokerRole | ASYNC_MASTER | SYNC_MASTER/ASYNC_MASTER/SLAVE |​ | flushDiskType | ASYNC_FLUSH | {SYNC_FLUSH/ASYNC_FLUSH}. Broker of SYNC_FLUSH mode flushes each message onto disk before acknowledging producer. Broker of ASYNC_FLUSH mode, on the other hand, takes advantage of group-committing, achieving better performance. |​ ================================================ FILE: docs/en/Operations_Consumer.md ================================================ ## Consumer ---- ### 1 Consumption process idempotent RocketMQ cannot achieve Exactly-Once, so if the business is very sensitive to consumption repetition, it is important to perform deduplication at the business level. Deduplication can be done with a relational database. First, you need to determine the unique key of the message, which can be either msgId or a unique identifier field in the message content, such as the order Id. Determine if a unique key exists in the relational database before consumption. If it does not exist, insert it and consume it, otherwise skip it. (The actual process should consider the atomic problem, determine whether there is an attempt to insert, if the primary key conflicts, the insertion fails, skip directly) ### 2 Slow message processing #### 2.1 Increase consumption parallelism Most messages consumption behaviors are IO-intensive, That is, it may be to operate the database, or call RPC. The consumption speed of such consumption behavior lies in the throughput of the back-end database or the external system. By increasing the consumption parallelism, the total consumption throughput can be increased, but the degree of parallelism is increased to a certain extent. Instead it will fall.Therefore, the application must set a reasonable degree of parallelism. There are several ways to modify the degree of parallelism of consumption as follows: * Under the same ConsumerGroup, increase the degree of parallelism by increasing the number of Consumer instances (note that the Consumer instance that exceeds the number of subscription queues is invalid). Can be done by adding machines, or by starting multiple processes on an existing machine. * Improve the consumption parallel thread of a single Consumer by modifying the parameters consumeThreadMin and consumeThreadMax. #### 2.2 Batch mode consumption Some business processes can increase consumption throughput to a large extent if they support batch mode consumption. For example, order deduction application, it takes 1s to process one order at a time, and it takes only 2s to process 10 orders at a time. In this way, the throughput of consumption can be greatly improved. By setting the consumer's consumeMessageBatchMaxSize to return a parameter, the default is 1, that is, only one message is consumed at a time, for example, set to N, then the number of messages consumed each time is less than or equal to N. #### 2.3 Skip non-critical messages When a message is accumulated, if the consumption speed cannot keep up with the transmission speed, if the service does not require high data, you can choose to discard the unimportant message. For example, when the number of messages in a queue is more than 100,000 , try to discard some or all of the messages, so that you can quickly catch up with the speed of sending messages. The sample code is as follows: ```java public ConsumeConcurrentlyStatus consumeMessage( List msgs, ConsumeConcurrentlyContext context){ long offset = msgs.get(0).getQueueOffset(); String maxOffset = msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET); long diff = Long.parseLong(maxOffset) - offset; if(diff > 100000){ //TODO Special handling of message accumulation return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } //TODO Normal consumption process return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } ``` #### 2.4 Optimize each message consumption process For example, the consumption process of a message is as follows: * Query from DB according to the message [data 1] * Query from DB according to the message [data 2] * Complex business calculations * Insert [Data 3] into the DB * Insert [Data 4] into the DB There are 4 interactions with the DB in the consumption process of this message. If it is calculated by 5ms each time, it takes a total of 20ms. If the business calculation takes 5ms, then the total time is 25ms, So if you can optimize 4 DB interactions to 2 times, the total time can be optimized to 15ms, which means the overall performance is increased by 40%. Therefore, if the application is sensitive to delay, the DB can be deployed on the SSD hard disk. Compared with the SCSI disk, the former RT will be much smaller. ### 3 Print Log If the amount of messages is small, it is recommended to print the message in the consumption entry method, consume time, etc., to facilitate subsequent troubleshooting. ```java public ConsumeConcurrentlyStatus consumeMessage( List msgs, ConsumeConcurrentlyContext context){ log.info("RECEIVE_MSG_BEGIN: " + msgs.toString()); //TODO Normal consumption process return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } ``` If you can print the time spent on each message, it will be more convenient when troubleshooting online problems such as slow consumption. ### 4 Other consumption suggestions #### 4.1、Consumer Group and Subscriptions The first thing you should be aware of is that different Consumer Group can consume the same topic independently, and each of them will have their own consuming offsets. Please make sure each Consumer within the same Group to subscribe the same topics. #### 4.2、Orderly The Consumer will lock each MessageQueue to make sure it is consumed one by one in order. This will cause a performance loss, but it is useful when you care about the order of the messages. It is not recommended to throw exceptions, you can return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT instead. #### 4.3、Concurrently As the name tells, the Consumer will consume the messages concurrently. It is recommended to use this for good performance. It is not recommended to throw exceptions, you can return ConsumeConcurrentlyStatus.RECONSUME_LATER instead. #### 4.4、Consume Status For MessageListenerConcurrently, you can return RECONSUME_LATER to tell the consumer that you can not consume it right now and want to reconsume it later. Then you can continue to consume other messages. For MessageListenerOrderly, because you care about the order, you can not jump over the message, but you can return SUSPEND_CURRENT_QUEUE_A_MOMENT to tell the consumer to wait for a moment. #### 4.5、Blocking It is not recommend to block the Listener, because it will block the thread pool, and eventually may stop the consuming process. #### 4.6、Thread Number The consumer use a ThreadPoolExecutor to process consuming internally, so you can change it by setting setConsumeThreadMin or setConsumeThreadMax. #### 4.7、ConsumeFromWhere When a new Consumer Group is established, it will need to decide whether it needs to consume the historical messages which had already existed in the Broker. CONSUME_FROM_LAST_OFFSET will ignore the historical messages, and consume anything produced after that. CONSUME_FROM_FIRST_OFFSET will consume every message existed in the Broker. You can also use CONSUME_FROM_TIMESTAMP to consume messages produced after the specified timestamp. ================================================ FILE: docs/en/Operations_Producer.md ================================================ ### Producer ---- ##### 1 Message Sending Tips ###### 1.1 The Use of Tags One application instance should use one topic as much as possible and the subtype of messages can be marked by tags. Tag provides extra flexibility to users. In the consume subscribing process, the messages filtering can only be handled by using tags when the tags are specified in the message sending process: `message.setTags("TagA")`. ###### 1.2 The Use of Keys A business key can be set in one message and it will be easier to look up the message on a broker server to diagnose issues during development. Each message will be created index(hash index) by server, instance can query the content of this message by topic and key and who consumes the message.Because of the hash index, make sure that the key should be unique in order to avoid potential hash index conflict. ``` java // Order Id String orderId = "20034568923546"; message.setKeys(orderId); ``` ###### 1.3 The Log Print When sending a message,no matter success or fail, a message log must be printed which contains SendResult and Key. It is assumed that we will always get SEND_OK if no exception is thrown. Below is a list of descriptions about each status: * SEND_OK SEND_OK means sending message successfully. SEND_OK does not mean it is reliable. To make sure no message would be lost, you should also enable SYNC_MASTER or SYNC_FLUSH. * FLUSH_DISK_TIMEOUT FLUSH_DISK_TIMEOUT means sending message successfully but the Broker flushing the disk with timeout. In this kind of condition, the Broker has saved this message in memory, this message will be lost only if the Broker was down. The FlushDiskType and SyncFlushTimeout could be specified in MessageStoreConfig. If the Broker set MessageStoreConfig’s FlushDiskType=SYNC_FLUSH(default is ASYNC_FLUSH), and the Broker doesn’t finish flushing the disk within MessageStoreConfig’s syncFlushTimeout(default is 5 secs), you will get this status. * FLUSH_SLAVE_TIMEOUT FLUSH_SLAVE_TIMEOUT means sending messages successfully but the slave Broker does not finish synchronizing with the master. If the Broker’s role is SYNC_MASTER(default is ASYNC_MASTER), and the slave Broker doesn’t finish synchronizing with the master within the MessageStoreConfig’s syncFlushTimeout(default is 5 secs), you will get this status. * SLAVE_NOT_AVAILABLE SLAVE_NOT_AVAILABLE means sending messages successfully but no slave Broker configured. If the Broker’s role is SYNC_MASTER(default is ASYNC_MASTER), but no slave Broker is configured, you will get this status. ##### 2 Operations on Message Sending failed The send method of Producer can be retried, the retry process is illustrated below: * The method will retry at most 2 times(2 times in synchronous mode, 0 times in asynchronous mode). * If sending failed, it will turn to the next Broker. This strategy will be executed when the total costing time is less then sendMsgTimeout(default is 10 seconds). * The retry method will be terminated if timeout exception was thrown when sending messages to Broker. The strategy above could make sure message sending successfully to a certain degree. Some more retry strategies, such as we could try to save the message to database if calling the send synchronous method failed and then retry by background thread's timed tasks, which will make sure the message is sent to Broker,could be improved if asking for high reliability business requirement. The reasons why the retry strategy using database have not integrated by the RocketMQ client will be explained below: Firstly, the design mode of the RocketMQ client is stateless mode. It means that the client is designed to be horizontally scalable at each level and the consumption of the client to physical resources is only CPU, memory and network. Then, if a key-value memory module is integrated by the client itself, the Async-Saving strategy will be utilized in consideration of the high resource consumption of the Syn-Saving strategy. However, given that operations staff does not manage the client shutoff, some special commands, such as kill -9, may be used which will lead to the lost of message because of no saving in time. Furthermore, the physical resource running Producer is not appropriate to save some significant data because of low reliability. Above all, the retry process should be controlled by program itself. ##### 3 Send Messages in One-way Mode The message sending is usually a process like below: * Client sends request to sever. * Sever handles request * Sever returns response to client The total costing time of sending one message is the sum of costing time of three steps above. Some situations demand that total costing time must be in a quite low level, however, do not take reliable performance into consideration, such as log collection. This kind of application could be called in one-way mode, which means client sends request but not wait for response. In this kind of mode, the cost of sending request is only a call of system operation which means one operation writing data to client socket buffer. Generally, the time cost of this process will be controlled n microseconds level. ================================================ FILE: docs/en/Operations_Trace.md ================================================ # Message Trace ## 1 Key Attributes of Message Trace Data | Producer | Consumer | Broker | | ---------------- | ----------------- | ------------ | | production instance information | consumption instance information | message Topic | | send message time | post time, post round | message storage location | | whether the message was sent successfully | Whether the message was successfully consumed | The Key of the message | | Time spent sending | Time spent consuming | Tag of the message | ## 2 Support for Message Trace Cluster Deployment ### 2.1 Broker Configuration Fille The properties profile content of the Broker side enabled message trace feature is pasted here: ``` brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/data/rocketmq/rootdir-a-m storePathCommitLog=/data/rocketmq/commitlog-a-m autoCreateSubscriptionGroup=true ## if msg tracing is open,the flag will be true traceTopicEnable=true listenPort=10911 brokerIP1=XX.XX.XX.XX1 namesrvAddr=XX.XX.XX.XX:9876 ``` ### 2.2 Normal Mode Each Broker node in the RocketMQ cluster is used to store message trace data collected and sent from the Client.Therefore, there are no requirements or restrictions on the number of Broker nodes in the RocketMQ cluster. ### 2.3 Physical IO Isolation Mode For scenarios with large amount of trace message data , one of the Broker nodes in the RocketMQ cluster can be selected to store the trace message , so that the common message data of the user and the physical IO of the trace message data are completely isolated from each other.In this mode, there are at least two Broker nodes in the RocketMQ cluster, one of which is defined as the server on which message trace data is stored. ### 2.4 Start the Broker that Starts the MessageTrace `nohup sh mqbroker -c ../conf/2m-noslave/broker-a.properties &` ## 3 Save the Topic Definition of Message Trace RocketMQ's message trace feature supports two ways to store trace data: ### 3.1 System-level TraceTopic By default, message track data is stored in the system-level TraceTopic(names: **RMQ_SYS_TRACE_TOPIC**).This Topic is automatically created when the Broker node is started(As described above, the switch variable **traceTopicEnable** needs to be set to **true** in the Broker configuration file). ### 3.2 Custom TraceTopic If the user is not prepared to store the message track data in the system-level default TraceTopic, you can also define and create a user-level Topic to save the track (that is, to create a regular Topic to save the message track data).The following section introduces how the Client interface supports the user-defined TraceTopic. ## 4 Client Practices that Support Message Trace In order to reduce as much as possible the transformation work of RocketMQ message trace feature used in the user service system, the author added a switch parameter (**enableMsgTrace**) to the original interface in the design to realize whether the message trace is opened or not. ### 4.1 Opening the Message Trace when Sending the Message ``` DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true); producer.setNamesrvAddr("XX.XX.XX.XX1"); producer.start(); try { { Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } } catch (Exception e) { e.printStackTrace(); } ``` ### 4.2 Opening Message Trace whenSubscribing to a Message ``` DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true); consumer.subscribe("TopicTest", "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.setConsumeTimestamp("20181109221800"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); ``` ### 4.3 Support for Custom Storage Message Trace Topic The initialization of `DefaultMQProducer` and `DefaultMQPushConsumer` instances can be changed to support the custom storage message trace Topic as follows when sending and subscribing messages above. ``` ##Where Topic_test11111 needs to be pre-created by the user to save the message trace; DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true,"Topic_test11111"); ...... DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true,"Topic_test11111"); ...... ``` ================================================ FILE: docs/en/QuorumACK.md ================================================ # Quorum write and automatic downgrade ## Background ![](https://s4.ax1x.com/2022/02/05/HnWo2d.png) In RocketMQ, there are two main replication modes between primary and secondary servers: synchronous replication and asynchronous replication. As shown in the above figure, the replication of Slave1 is synchronous, and the Master needs to wait for Slave1 to successfully replicate the message and confirm before reporting success to the Producer. The replication of Slave2 is asynchronous, and the Master does not need to wait for the response from Slave2. In RocketMQ, if everything goes well when sending a message, the Producer client will eventually receive a PUT_OK status. If the Slave synchronization times out, it will return a FLUSH_SLAVE_TIMEOUT status. If the Slave is unavailable or the difference between the CommitLog of the Slave and Master exceeds a certain value (default is 256MB), it will return a SLAVE_NOT_AVAILABLE status. The latter two states will not cause system exceptions and prevent the next message from being written. Synchronous replication ensures that the data can still be found in the Slave after the Master fails, which is suitable for scenarios with high reliability requirements. Although asynchronous replication may result in message loss, it is more efficient than synchronous replication because it does not need to wait for the Slave's confirmation, and is suitable for scenarios with certain efficiency requirements. However, only two modes are not flexible enough. For example, in scenarios with three or even five copies and high reliability requirements, asynchronous replication cannot meet the requirements, but synchronous replication needs to wait for each copy to confirm before returning, which seriously affects efficiency in the case of many copies. On the other hand, in the synchronous replication mode, if one of the Slaves in the copy group becomes inactive, the entire send will fail until manual processing is performed. Therefore, RocketMQ 5 introduces quorum write for copy groups. In the synchronous replication mode, the user can specify on the broker side how many copies need to be written before returning after sending, and provides an adaptive downgrade method that can automatically downgrade based on the number of surviving copies and the CommitLog gap. ## Architecture and Parameters ### Quorum Write quorum write is supported by adding two parameters: - **totalReplicas**:Total number of brokers in the copy replica. default is 1. - **inSyncReplicas**:The number of replica groups that should normally be kept in synchronization. default is 1. With these two parameters, you can flexibly specify the number of copies that need ACK in the synchronous replication mode. ![](https://s4.ax1x.com/2022/02/05/HnWHKI.png) As shown in the above figure, in the case of two copies, if inSyncReplicas is 2, the message needs to be copied in both the Master and the Slave before it is returned to the client; in the case of three copies, if inSyncReplicas is 2, the message needs to be copied in the Master and any slave before it is returned to the client. In the case of four copies, if inSyncReplicas is 3, the message needs to be copied in the Master and any two slaves before it is returned to the client. By flexibly setting totalReplicas and inSyncReplicas, users can meet the needs of various scenarios. ### Automatic downgrade The standards for automatic downgrade are: - The number of surviving replicas in the current replica group - The height difference between the Master Commitlog and the Slave CommitLog > **NOTE: Automatic downgrade is only effective after the slaveActingMaster mode is enabled** The current survival information of the copy group can be obtained through the reverse notification of the Nameserver and the GetBrokerMemberGroup request, and the height difference between the Master and the Slave Commitlog can also be calculated through the position record in the HA service. The following parameters will be added to complete the automatic downgrade: - **minInSyncReplicas**:The minimum number of copies in the group that must be kept in sync, only effective when enableAutoInSyncReplicas is true, default is 1 - **enableAutoInSyncReplicas**:The switch for automatic synchronization downgrade, when turned on, if the number of brokers in the current copy group in the synchronization state (including the master itself) does not meet the number specified by inSyncReplicas, it will be synchronized according to minInSyncReplicas. The synchronization state judgment condition is that the slave commitLog lags behind the master length by no more than haSlaveFallBehindMax. The default is false. - **haMaxGapNotInSync**:The value for determining whether the slave is in sync with the master. If the slave commitLog lags behind the master length by more than this value, the slave is considered to be out of sync. When enableAutoInSyncReplicas is turned on, the smaller the value, the easier it is to trigger automatic downgrade of the master. When enableAutoInSyncReplicas is turned off and `totalReplicas == inSyncReplicas`, the smaller the value, the more likely it is to cause requests to fail during high traffic. Therefore, in this case, it is appropriate to increase haMaxGapNotInSync. The default is 256K. Note: In RocketMQ 4.x, there is a haSlaveFallbehindMax parameter, with a default value of 256MB, indicating the CommitLog height difference at which the Slave is considered unavailable. This parameter was cancelled in [RIP-34](https://github.com/apache/rocketmq/wiki/RIP-34-Support-quorum-write-and-adaptive-degradation-in-master-slave-architecture). ```java //calculate needAckNums int inSyncReplicas = Math.min(this.defaultMessageStore.getAliveReplicaNumInGroup(), this.defaultMessageStore.getHaService().inSyncSlaveNums(currOffset) + 1); needAckNums = calcNeedAckNums(inSyncReplicas); if (needAckNums > inSyncReplicas) { // Tell the producer, don't have enough slaves to handle the send request return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null)); } private int calcNeedAckNums(int inSyncReplicas) { int needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas(); if (this.defaultMessageStore.getMessageStoreConfig().isEnableAutoInSyncReplicas()) { needAckNums = Math.min(needAckNums, inSyncReplicas); needAckNums = Math.max(needAckNums, this.defaultMessageStore.getMessageStoreConfig().getMinInSyncReplicas()); } return needAckNums; } ``` When enableAutoInSyncReplicas=true, the adaptive downgrade mode is enabled. When the number of surviving replicas in the replica group decreases or the height difference between the Master and the Slave Commitlog is too large, automatic downgrade will be performed, with a minimum of minInSyncReplicas replicas. For example, in two replicas, if totalReplicas=2, InSyncReplicas=2, minInSyncReplicas=1, and enableAutoInSyncReplicas=true are set, under normal circumstances, the two replicas will be in synchronous replication. When the Slave goes offline or hangs, adaptive downgrade will be performed, and the producer only needs to send to the master to succeed. ## Compatibility To ensure backward compatibility, users need to set the correct parameters. For example, if the user's original cluster is a two-replica synchronous replication and no parameters are modified, when upgrading to the RocketMQ 5 version, due to the default totalReplicas and inSyncReplicas both being 1, it will downgrade to asynchronous replication. If you want to maintain the same behavior as before, you need to set both totalReplicas and inSyncReplicas to 2. **references:** - [RIP-34](https://github.com/apache/rocketmq/wiki/RIP-34-Support-quorum-write-and-adaptive-degradation-in-master-slave-architecture) ================================================ FILE: docs/en/README.md ================================================ Apache RocketMQ Developer Guide -------- ##### This guide helps developers to understand and use Apache RocketMQ quickly. ### 1. Concepts & Features - [Concept](Concept.md): introduce basic concepts in RocketMQ. - [Feature](Feature.md): introduce functional features of RocketMQ's implementations. ### 2. Architecture Design - [Architecture](architecture.md): introduce RocketMQ's deployment and technical architecture. - [Design](design.md): introduce design concept of RocketMQ's key mechanisms, including message storage, communication mechanisms, message filter, loadbalance, transaction message, etc. ### 3. Example - [Example](RocketMQ_Example.md): introduce RocketMQ's common usage, including basic example, sequence message example, delay message example, batch message example, filter message example, transaction message example, etc. ### 4. Best Practice - [Best Practice](best_practice.md): introduce RocketMQ's best practice, including producer, consumer, broker, NameServer, configuration of client, and the best parameter configuration of JVM, linux. - [Message Trace](msg_trace/user_guide.md): introduce how to use RocketMQ's message tracing feature. - [Auth Management](acl/Operations_ACL.md): introduce how to deployment quickly and how to use RocketMQ cluster enabling auth management feature. - [Quick Start](dledger/quick_start.md): introduce how to deploy Dledger quickly. - [Cluster Deployment](dledger/deploy_guide.md): introduce how to deploy Dledger in cluster. - [Proxy Deployment](proxy/deploy_guide.md) Introduce how to deploy proxy (both `Local` mode and `Cluster` mode). ### 5. Operation and maintenance management - [Operation](operation.md): introduce RocketMQ's deployment modes that including single-master mode, multi-master mode, multi-master multi-slave mode and so on, as well as the usage of operation tool mqadmin. ### 6. API Reference(TODO) - [DefaultMQProducer API Reference](client/java/API_Reference_DefaultMQProducer.md) ================================================ FILE: docs/en/RocketMQ_Example.md ================================================ ### Examples List - [basic example](Example_Simple.md) - [sequence message example](Example_Orderly.md) - [delay message example](Example_Delay.md) - [batch message example](Example_Batch.md) - [filter message example](Example_Filter.md) - [transaction message example](Example_Transaction.md) - [openmessaging example](Example_OpenMessaging.md) ================================================ FILE: docs/en/Troubleshoopting.md ================================================ # Operation FAQ ## 1 RocketMQ's mqadmin command error. > Problem: Sometimes after deploying the RocketMQ cluster, when you try to execute some commands of "mqadmin", the following exception will appear: > > ```java > org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to failed > ``` Solution: Execute `export NAMESRV_ADDR=ip:9876` (ip refers to the address of NameServer deployed in the cluster) on the VM that deploys the RocketMQ cluster.Then you will execute commands of "mqadmin" successfully. ## 2 The inconsistent version of RocketMQ between the producer and consumer leads to the problem that messages can't be consumed normally. > Problem: The same producer sends a message, consumer A can consume, but consumer B can't consume, and the RocketMQ Console appears: > > ```java > Not found the consumer group consume stats, because return offset table is empty, maybe the consumer not consume any message > ``` Solution: The jar package of RocketMQ, such as rocketmq-client, should be the same version on the consumer and producer. ## 3 When adding a new topic consumer group, historical messages can't be consumed. > Problem: When a new consumer group of the same topic is started, the consumed message is the current offset message, and the historical message is not obtained. Solution: The default policy of rocketmq is to start from the end of the message queue and skip the historical message. If you want to consume historical message, you need to set: ```java org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#setConsumeFromWhere ``` There are three common configurations: - By default, a new subscription group starts to consume from the end of the queue for the first time, and then restarts and continue to consume from the last consume position, that is, to skip the historical message. ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); ``` - A new subscription group starts to consume from the head of the queue for the first time, and then restarts and continue to consume from the last consume position, that is, to consume the historical message that is not expired on Broker. ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); ``` - A new subscription group starts to consume from the specified time point for the first time, and then restarts and continue to consume from the last consume position. It is used together with `consumer.setConsumeTimestamp()`. The default is half an hour ago. ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); ``` ## 4 How to enable reading data from Slave In some cases, the Consumer needs to reset the consume position to 1-2 days ago. At this time, on the Master Broker with limited memory, the CommitLog will carry a relatively heavy IO pressure, affecting the reading and writing of other messages on that Broker. You can enable `slaveReadEnable=true`. When Master Broker finds that the difference between the Consumer's consume position and the latest value of CommitLog exceeds the percentage of machine's memory (`accessMessageInMemoryMaxRatio=40%`), it will recommend Consumer to read from Slave Broker and relieve Master Broker's IO. ## 5 Performance Asynchronous flush disk is recommended to use spin lock. Synchronous flush disk is recommended to use reentrant lock. Adjust the Broker configuration item `useReentrantLockWhenPutMessage`, and the default value is false. Asynchronous flush disk is recommended to open `TransientStorePoolEnable` and close `transferMsgByHeap` to improve the efficiency of pulling message; Synchronous flush disk is recommended to increase the `sendMessageThreadPoolNums` appropriately. The specific configuration needs to be tested. ## 6 The meaning and difference between msgId and offsetMsgId in RocketMQ After sending message with RocketMQ, you will usually see the following log: ```java SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD0000, offsetMsgId=0A42333A00002A9F000000000134F1F5, messageQueue=MessageQueue [topic=topicTest1, BrokerName=mac.local, queueId=3], queueOffset=4] ``` - msgId, for the client, the msgId is generated by the producer instance. Specifically, the method `MessageClientIDSetter.createUniqIDBuffer()` is called to generate a unique Id. - offsetMsgId, offsetMsgId is generated by the Broker server when writing a message ( string consists of "IP address + port" and "CommitLog's physical offset address"), and offsetMsgId is the messageId used to query in the RocketMQ console. ================================================ FILE: docs/en/acl/Operations_ACL.md ================================================ # Access control list ## Overview This document focuses on how to quickly deploy and use a RocketMQ cluster that supports the privilege control feature. ## 1. Access control features Access Control (ACL) mainly provides Topic resource level user access control for RocketMQ.If you want to enable RocketMQ permission control, you can inject the AccessKey and SecretKey signatures through the RPCHook on the Client side.And then, the corresponding permission control attributes (including Topic access rights, IP whitelist and AccessKey and SecretKey signature) are set in the configuration file of distribution/conf/plain_acl.yml.The Broker side will check the permissions owned by the AccessKey, and if the verification fails, an exception is thrown; The source code about ACL on the Client side can be find in **org.apache.rocketmq.example.simple.AclClient.java** ## 2. Access control definition and attribute values ### 2.1 Access control definition The definition of Topic resource access control for RocketMQ is mainly as shown in the following table. | Permission | explanation | | --- | --- | | DENY | permission deny | | ANY | PUB or SUB permission | | PUB | Publishing permission | | SUB | Subscription permission | ### 2.2 Main properties | key | value | explanation | | --- | --- | --- | | globalWhiteRemoteAddresses | string |Global IP whitelist,example:
    \*;
    192.168.\*.\*;
    192.168.0.1 | | accessKey | string | Access Key | | secretKey | string | Secret Key | | whiteRemoteAddress | string | User IP whitelist,example:
    \*;
    192.168.\*.\*;
    192.168.0.1 | | admin | true;false | Whether an administrator account | | defaultTopicPerm | DENY;PUB;SUB;PUB\|SUB | Default Topic permission | | defaultGroupPerm | DENY;PUB;SUB;PUB\|SUB | Default ConsumerGroup permission | | topicPerms | topic=permission | Topic only permission | | groupPerms | group=permission | ConsumerGroup only permission | For details, please refer to the **distribution/conf/plain_acl.yml** configuration file. ## 3. Cluster deployment with permission control After defining the permission attribute in the **distribution/conf/plain_acl.yml** configuration file as described above, open the **aclEnable** switch variable to enable the ACL feature of the RocketMQ cluster.The configuration file of the ACL feature enabled on the broker is as follows: ```properties brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/data/rocketmq/rootdir-a-m storePathCommitLog=/data/rocketmq/commitlog-a-m autoCreateSubscriptionGroup=true ## if acl is open,the flag will be true aclEnable=true listenPort=10911 brokerIP1=XX.XX.XX.XX1 namesrvAddr=XX.XX.XX.XX:9876 ``` ## 4. Main process of access control The main ACL process is divided into two parts, including privilege resolution and privilege check. ### 4.1 Privilege resolution The Broker side parses the client's RequestCommand request and obtains the attribute field that needs to be authenticated. main attributes: (1) AccessKey:Similar to the user name, on behalf of the user entity, the permission data corresponds to it; (2) Signature:The client obtains the string according to the signature of the SecretKey, and the server uses the SecretKey to perform signature verification. ### 4.2 Privilege check The check logic of the right side of the broker is mainly divided into the following steps: (1) Check if the global IP whitelist is hit; if yes, the check passes; otherwise, go to step (2); (2) Check if the user IP whitelist is hit; if yes, the check passes; otherwise, go to step (3); (3) Check the signature, if the verification fails, throw an exception; if the verification passes, go to step (4); (4) Check the permissions required by the user request and the permissions owned by the user; if not, throw an exception; The verification of the required permissions of the user requires attention to the following points: (1) Special requests such as UPDATE_AND_CREATE_TOPIC can only be operated by the admin account; (2) For a resource, if there is explicit configuration permission, the configured permission is used; if there is no explicit configuration permission, the default permission is adopted; ## 5. Hot loading modified Access control The default implementation of RocketMQ's permission control store is based on the yml configuration file. Users can dynamically modify the properties defined by the permission control without restarting the Broker service node. ================================================ FILE: docs/en/architecture.md ================================================ # Architecture design ## Technology Architecture ![](image/rocketmq_architecture_1.png) The RocketMQ architecture is divided into four parts, as shown in the figure above: - Producer: The role of message publishing supports distributed cluster mode deployment. Producer selects the corresponding Broker cluster queue for message delivery through MQ's load balancing module. The delivery process supports fast failure and low latency. - Consumer: The role of message consumption supports distributed cluster deployment. Support push, pull two modes to consume messages. It also supports cluster mode and broadcast mode consumption, and it provides a real-time message subscription mechanism to meet the needs of most users. - NameServer: NameServer is a very simple Topic routing registry with a role similar to ZooKeeper in Dubbo, which supports dynamic registration and discovery of Broker. It mainly includes two functions: Broker management, NameServer accepts the registration information of the Broker cluster and saves it as the basic data of the routing information. Then provide a heartbeat detection mechanism to check whether the broker is still alive; routing information management, each NameServer will save the entire routing information about the Broker cluster and the queue information for the client query. Then the Producer and Consumer can know the routing information of the entire Broker cluster through the NameServer, so as to deliver and consume the message. The NameServer is usually deployed in a cluster mode, and each instance does not communicate with each other. Broker registers its own routing information with each NameServer, so each NameServer instance stores a complete routing information. When a NameServer is offline for some reason, the Broker can still synchronize its routing information with other NameServers. The Producer and Consumer can still dynamically sense the information of the Broker's routing. - BrokerServer: Broker is responsible for the storage, delivery and query of messages and high availability guarantees. In order to achieve these functions, Broker includes the following important sub-modules. 1. Remoting Module: The entire broker entity handles requests from the clients side. 2. Client Manager: Topic subscription information for managing the client (Producer/Consumer) and maintaining the Consumer 3. Store Service: Provides a convenient and simple API interface for handling message storage to physical hard disks and query functions. 4. HA Service: Highly available service that provides data synchronization between Master Broker and Slave Broker. 5. Index Service: The message delivered to the Broker is indexed according to a specific Message key to provide a quick query of the message. ![](image/rocketmq_architecture_2.png) ## Deployment architecture ![](image/rocketmq_architecture_3.png) ### RocketMQ Network deployment features - NameServer is an almost stateless node that can be deployed in a cluster without any information synchronization between nodes. - The broker deployment is relatively complex. The Broker is divided into the Master and the Slave. One Master can correspond to multiple Slaves. However, one Slave can only correspond to one Master. The correspondence between the Master and the Slave is defined by specifying the same BrokerName and different BrokerId. The BrokerId 0 indicates Master, non-zero means Slave. The Master can also deploy multiple. Each broker establishes a long connection with all nodes in the NameServer cluster, and periodically registers Topic information to all NameServers. Note: The current RocketMQ version supports a Master Multi Slave on the deployment architecture, but only the slave server with BrokerId=1 will participate in the read load of the message. - The Producer establishes a long connection with one of the nodes in the NameServer cluster (randomly selected), periodically obtains Topic routing information from the NameServer, and establishes a long connection to the Master that provides the Topic service, and periodically sends a heartbeat to the Master. Producer is completely stateless and can be deployed in a cluster. - The Consumer establishes a long connection with one of the nodes in the NameServer cluster (randomly selected), periodically obtains Topic routing information from the NameServer, and establishes a long connection to the Master and Slave that provides the Topic service, and periodically sends heartbeats to the Master and Slave. The Consumer can subscribe to the message from the Master or subscribe to the message from the Slave. When the consumer pulls the message to the Master, the Master server will generate a read according to the distance between the offset and the maximum offset. I/O), and whether the server is readable or not, the next time it is recommended to pull from the Master or Slave. Describe the cluster workflow in conjunction with the deployment architecture diagram: - Start the NameServer, listen to the port after the NameServer, and wait for the Broker, Producer, and Consumer to connect, which is equivalent to a routing control center. - The Broker starts, keeps a long connection with all NameServers, and sends heartbeat packets periodically. The heartbeat packet contains the current broker information (IP+ port, etc.) and stores all Topic information. After the registration is successful, there is a mapping relationship between Topic and Broker in the NameServer cluster. - Before sending and receiving a message, create a Topic. When creating a Topic, you need to specify which Brokers the Topic should be stored on, or you can automatically create a Topic when sending a message. - Producer sends a message. When starting, it first establishes a long connection with one of the NameServer clusters, and obtains from the NameServer which Brokers are currently sent by the Topic. Polling selects a queue from the queue list and then establishes with the broker where the queue is located. Long connection to send a message to the broker. - The Consumer is similar to the Producer. It establishes a long connection with one of the NameServers, obtains which Brokers the current Topic exists on, and then directly establishes a connection channel with the Broker to start consuming messages. ================================================ FILE: docs/en/best_practice.md ================================================ # Best practices ## 1 Producer ### 1.1 Attention of send message #### 1 Uses of tags An application should use one topic as far as possible, but identify the message's subtype with tags. Tags can be set freely by the application. Only when producers set tags while sending messages, can consumers filter messages through broker with tags when subscribing messages: message.setTags("TagA"). #### 2 Uses of keys The unique identifier for each message at the business level set to the Keys field to help locate message loss problems in the future. The server creates an index(hash index) for each message, and the application can query the message content via Topic,key,and who consumed the message. Since it is a hash index, make sure that the key is as unique as possible to avoid potential hash conflicts. ```java // order id String orderId = "20034568923546"; message.setKeys(orderId); ``` If you have multiple keys for a message, please concatenate them with 'KEY_SEPARATOR' char, as shown below: ```java // order id String orderId = "20034568923546"; String otherKey = "19101121210831"; String keys = new StringBuilder(orderId) .append(org.apache.rocketmq.common.message.MessageConst.KEY_SEPARATOR) .append(otherKey).toString(); message.setKeys(keys); ``` And if you want to query the message, please use `orderId` and `otherKey` to query respectively instead of `keys`, because the server will unwrap `keys` with `KEY_SEPARATOR` and create corresponding index. In the above example, the server will create two indexes, one for `orderId` and one for `otherKey`. #### 3 Log print Print the message log when send success or failed, make sure to print the SendResult and key fields. Send messages is successful as long as it does not throw exception. Send successful will have multiple states defined in sendResult. Each state is describing below: - **SEND_OK** Message send successfully. Note that even though message send successfully, it doesn't mean that it is reliable. To make sure nothing lost, you should also enable the SYNC_MASTER or SYNC_FLUSH. - **FLUSH_DISK_TIMEOUT** Message send successfully, but the server flush messages to disk timeout. At this point, the message has entered the server's memory, and the message will be lost only when the server is down. Flush mode and sync flush time interval can be set in the configuration parameters. It will return FLUSH_DISK_TIMEOUT when Broker server doesn't finish flush message to disk in timeout(default is 5s ) when sets FlushDiskType=SYNC_FLUSH(default is async flush). - **FLUSH_SLAVE_TIMEOUT** Message send successfully, but sync to slave timeout. At this point, the message has entered the server's memory, and the message will be lost only when the server is down. It will return FLUSH_SLAVE_TIMEOUT when Broker server role is SYNC_MASTER(default is ASYNC_MASTER),and it doesn't sync message to slave successfully in the timeout(default 5s). - **SLAVE_NOT_AVAILABLE** Message send successfully, but slave is not available. It will return SLAVE_NOT_AVAILABLE when Broker role is SYNC_MASTER(default is ASYNC_MASTER), and it doesn't have a slave server available. ### 1.2 Handling of message send failure Send method of producer itself supports internal retry. The logic of retry is as follows: - At most twice. - Try next broker when sync send mode, try current broker when async mode. The total elapsed time of this method does not exceed the value of sendMsgTimeout(default is 10s). - It will not be retried when the message is sent to the Broker with a timeout exception. The strategy above ensures the success of message sending to some extent.If the business has a high requirement for message reliability, it is recommended to add the corresponding retry logic: for example, if the sync send method fails, try to store the message in DB, and then retry periodically by the bg thread to ensure the message must send to broker successfully. Why the above DB retry strategy is not integrated into the MQ client, but requires the application to complete it by itself is mainly based on the following considerations: First, the MQ client is designed to be stateless mode, convenient for arbitrary horizontal expansion, and only consumes CPU, memory and network resources. Second, if the MQ client internal integration a KV storage module, the data can only be relatively reliable when sync flush to disk, but the sync flush will cause performance lose, so it's usually use async flush.Because the application shutdown is not controlled by the MQ operators, A violent shutdown like kill -9 may often occur, resulting in data not flushed to disk and being lost. Thirdly, the producer is a virtual machine with low reliability, which is not suitable for storing important data. In conclusion, it is recommended that the retry process must be controlled by the application. ### 1.3 Send message by oneway Typically, this is the process by which messages are sent: - Client send request to server - Server process request - Server response to client So, the time taken to send a message is the sum of the three steps above. Some scenarios require very little time, but not much reliability, such as log collect application. This type application can use oneway to send messages. Oneway only send request without waiting for a reply, and send a request at the client implementation level is simply the overhead of an operating system call that writes data to the client's socket buffer, this process that typically takes microseconds. ## 2 Consumer ## 3 Broker ### 3.1 Broker Role ### 3.2 FlushDiskType ### 3.3 Broker Configuration | Parameter name | Default | Description | | -------------------------------- | ----------------------------- | ------------------------------------------------------------ | | listenPort | 10911 | listen port for client | | namesrvAddr | null | name server address | | brokerIP1 | InetAddress for network interface | Should be configured if having multiple addresses | | brokerIP2 | InetAddress for network interface | If configured for the Master broker in the Master/Slave cluster, slave broker will connect to this port for data synchronization | | brokerName | null | broker name | | brokerClusterName | DefaultCluster | this broker belongs to which cluster | | brokerId | 0 | broker id, 0 means master, positive integers mean slave | | storePathRootDir | $HOME/store/ | file path for root store | | storePathCommitLog | $HOME/store/commitlog/ | file path for commit log | | mappedFileSizeCommitLog | 1024 * 1024 * 1024(1G) | mapped file size for commit log |​ | deleteWhen | 04 | When to delete the commitlog which is out of the reserve time |​ | fileReserverdTime | 72 | The number of hours to keep a commitlog before deleting it |​ | brokerRole | ASYNC_MASTER | SYNC_MASTER/ASYNC_MASTER/SLAVE |​ | flushDiskType | ASYNC_FLUSH | {SYNC_FLUSH/ASYNC_FLUSH}. Broker of SYNC_FLUSH mode flushes each message onto disk before acknowledging producer. Broker of ASYNC_FLUSH mode, on the other hand, takes advantage of group-committing, achieving better performance. |​ ================================================ FILE: docs/en/client/java/API_Reference_DefaultMQProducer.md ================================================ ## DefaultMQProducer --- ### Class introduction `public class DefaultMQProducer extends ClientConfig implements MQProducer` >`DefaultMQProducer` is the entry point for an application to post messages, out of the box, ca quickly create a producer with a no-argument construction. it is mainly responsible for message sending, support synchronous、asynchronous、one-way send. All of these send methods support batch send. The parameters of the sender can be adjusted through the getter/setter methods , provided by this class. `DefaultMQProducer` has multi send method and each method is slightly different. Make sure you know the usage before you use it . Blow is a producer example . [see more examples](https://github.com/apache/rocketmq/blob/master/example/src/main/java/org/apache/rocketmq/example/). ``` java public class Producer { public static void main(String[] args) throws MQClientException { // create a produce with producer_group_name DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName"); // start the producer producer.start(); for (int i = 0; i < 128; i++) try { // construct the msg Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); // send sync SendResult sendResult = producer.send(msg); // print the result System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); } producer.shutdown(); } } ``` **Note** : This class is thread safe. It can be safely shared between multiple threads after configuration and startup is complete. ### Variable |Type|Name| description | |------|-------|-------| |DefaultMQProducerImpl|defaultMQProducerImpl|The producer's internal default implementation| |String|producerGroup|The producer's group| |String|createTopicKey| Topics that do not exist on the server are automatically created when the message is sent | |int|defaultTopicQueueNums|The default number of queues to create a topic| |int|sendMsgTimeout|The timeout for the message to be sent| |int|compressMsgBodyOverHowmuch|the threshold of the compress of message body| |int|retryTimesWhenSendFailed|Maximum number of internal attempts to send a message in synchronous mode| |int|retryTimesWhenSendAsyncFailed|Maximum number of internal attempts to send a message in asynchronous mode| |boolean|retryAnotherBrokerWhenNotStoreOK|Whether to retry another broker if an internal send fails| |int|maxMessageSize| Maximum length of message body | |TraceDispatcher|traceDispatcher| Message trackers. Use rcpHook to track messages | ### construction method |Method name|Method description| |-------|------------| |DefaultMQProducer()| creates a producer with default parameter values | |DefaultMQProducer(final String producerGroup)| creates a producer with producer group name. | |DefaultMQProducer(final String producerGroup, boolean enableMsgTrace)|creates a producer with producer group name and set whether to enable message tracking| |DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic)|creates a producer with producer group name and set whether to enable message tracking、the trace topic.| |DefaultMQProducer(RPCHook rpcHook)|creates a producer with a rpc hook.| |DefaultMQProducer(final String producerGroup, RPCHook rpcHook)|creates a producer with a rpc hook and producer group.| |DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic)|all of above.| ================================================ FILE: docs/en/controller/deploy.md ================================================ # Deployment and upgrade guidelines ## Controller deployment If the controller needs to be fault-tolerant, it needs to be deployed in three or more replicas (following the Raft majority protocol). > Controller can also complete Broker Failover with only one deployment, but if the single point Controller fails, it will affect the switching ability, but will not affect the normal reception and transmission of the existing cluster. There are two ways to deploy Controller. One is to embed it in NameServer for deployment, which can be opened through the configuration enableControllerInNamesrv (it can be opened selectively and is not required to be opened on every NameServer). In this mode, the NameServer itself is still stateless, that is, if the NameServer crashes in the embedded mode, it will only affect the switching ability and not affect the original routing acquisition and other functions. The other is independent deployment, which requires separate deployment of the controller. ### Embed NameServer deployment When embedded in NameServer deployment, you only need to set `enableControllerInNamesrv=true` in the NameServer configuration file and fill in the controller configuration. ``` enableControllerInNamesrv = true controllerDLegerGroup = group1 controllerDLegerPeers = n0-127.0.0.1:9877;n1-127.0.0.1:9878;n2-127.0.0.1:9879 controllerDLegerSelfId = n0 controllerStorePath = /home/admin/DledgerController enableElectUncleanMaster = false notifyBrokerRoleChanged = true ``` Parameter explain: - enableControllerInNamesrv: Whether to enable controller in Nameserver, default is false. - controllerDLegerGroup: The name of the DLedger Raft Group, all nodes in the same DLedger Raft Group should be consistent. - controllerDLegerPeers: The port information of the nodes in the DLedger Group, the configuration of each node in the same Group must be consistent. - controllerDLegerSelfId: The node id, must belong to one of the controllerDLegerPeers; unique within the Group. - controllerStorePath: The location to store controller logs. Controller is stateful and needs to rely on logs to recover data when restarting or crashing, this directory is very important and should not be easily deleted. - enableElectUncleanMaster: Whether it is possible to elect Master from outside SyncStateSet, if true, it may select a replica with lagging data as Master and lose messages, default is false. - notifyBrokerRoleChanged: Whether to actively notify when the role of the broker replica group changes, default is true. Some other parameters can be referred to in the ControllerConfig code. After setting the parameters, start the Nameserver by specifying the configuration file. ### Independent deployment To deploy independently, execute the following script: ```shell sh bin/mqcontroller -c controller.conf ``` The mqcontroller script is located at distribution/bin/mqcontroller, and the configuration parameters are the same as in embedded mode. ## Broker Controller mode deployment The Broker start method is the same as before, with the following parameters added: - enableControllerMode: The overall switch for the Broker controller mode, only when this value is true will the controller mode be opened. Default is false. - controllerAddr: The address of the controller, separated by semicolons if there are multiple controllers. For example, `controllerAddr = 127.0.0.1:9877;127.0.0.1:9878;127.0.0.1:9879` - syncBrokerMetadataPeriod: The interval for synchronizing Broker replica information with the controller. Default is 5000 (5s). - checkSyncStateSetPeriod: The interval for checking SyncStateSet, checking SyncStateSet may shrink SyncState. Default is 5000 (5s). - syncControllerMetadataPeriod: The interval for synchronizing controller metadata, mainly to obtain the address of the active controller. Default is 10000 (10s). - haMaxTimeSlaveNotCatchup: The maximum interval that a slave has not caught up to the Master, if a slave in SyncStateSet exceeds this interval, it will be removed from SyncStateSet. Default is 15000 (15s). - storePathEpochFile: The location to store the epoch file. The epoch file is very important and should not be deleted arbitrarily. Default is in the store directory. - allAckInSyncStateSet: If this value is true, a message needs to be replicated to each replica in SyncStateSet before it is returned to the client as successful, ensuring that the message is not lost. Default is false. - syncFromLastFile: If the slave is a blank disk start, whether to replicate from the last file. Default is false. - asyncLearner: If this value is true, the replica will not enter SyncStateSet, that is, it will not be elected as Master, but will always be a learner replica that performs asynchronous replication. Default is false. - inSyncReplicas: The number of replica groups that need to be kept in sync, default is 1, inSyncReplicas is invalid when allAckInSyncStateSet=true. - minInSyncReplicas: The minimum number of replica groups that need to be kept in sync, if the number of replicas in SyncStateSet is less than minInSyncReplicas, putMessage will return PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH directly, default is 1. In Controller mode, the Broker configuration must set `enableControllerMode=true` and fill in controllerAddr. ### Analysis of important parameters Among the parameters such as inSyncReplicas and minInSyncReplicas, there are overlapping and different meanings in normal Master-Slave deployment, SlaveActingMaster mode, and automatic master-slave switching architecture. The specific differences are as follows: | | inSyncReplicas | minInSyncReplicas | enableAutoInSyncReplicas | allAckInSyncStateSet | haMaxGapNotInSync | haMaxTimeSlaveNotCatchup | |----------------------|---------------------------------------------------------------------|--------------------------------------------------------------------------|---------------------------------------------|---------------------------------------------------------------|---------------------------------------|---------------------------------------------------| | Normal Master-Slave deployment | The number of replicas that need to be ACKed in synchronous replication, invalid in asynchronous replication | invalid | invalid | invalid | invalid | invalid | | Enable SlaveActingMaster (slaveActingMaster=true) | The number of replicas that need to be ACKed in synchronous replication in the absence of auto-degradation | The minimum number of replicas that need to be ACKed after auto-degradation | Whether to enable auto-degradation, and the minimum number of replicas that need to be ACKed after auto-degradation is reduced to minInSyncReplicas | invalid | Basis for degradation determination: the difference in Commitlog heights between Slave and Master, in bytes | invalid | | Automatic master-slave switching architecture(enableControllerMode=true) | The number of replicas that need to be ACKed in synchronous replication when allAckInSyncStateSet is not enabled, and this value is invalid when allAckInSyncStateSet is enabled | SyncStateSet can be reduced to the minimum number of replicas, and if the number of replicas in SyncStateSet is less than minInSyncReplicas, it will return directly with insufficient number of replicas | invalid | If this value is true, a message needs to be replicated to every replica in SyncStateSet before it is returned to the client as successful, and this parameter can ensure that the message is not lost | invalid | The minimum time difference between Slave and Master when SyncStateSet is contracted, see [RIP-44](https://shimo.im/docs/N2A1Mz9QZltQZoAD) for details. | To summarize: - In a normal Master-Slave configuration, there is no ability for auto-degradation, and all parameters except for inSyncReplicas are invalid. inSyncReplicas indicates the number of replicas that need to be ACKed in synchronous replication. - In slaveActingMaster mode, enabling enableAutoInSyncReplicas enables the ability for degradation, and the minimum number of replicas that can be degraded to is minInSyncReplicas. The basis for degradation is the difference in Commitlog heights (haMaxGapNotInSync) and the survival of the replicas, refer to [SlaveActingMaster mode adaptive degradation](../QuorumACK.md). - Automatic master-slave switching (Controller mode) relies on SyncStateSet contraction for auto-degradation. SyncStateSet replicas can work normally as long as they are contracted to a minimum of minInSyncReplicas. If it is less than minInSyncReplicas, it will return directly with insufficient number of replicas. One of the basis for contraction is the time interval (haMaxTimeSlaveNotCatchup) at which the Slave catches up, rather than the Commitlog height. If allAckInSyncStateSet=true, the inSyncReplicas parameter is invalid. ## Compatibility This mode does not make any changes or modifications to any client-level APIs, and there are no compatibility issues with clients. The Nameserver itself has not been modified and there are no compatibility issues with the Nameserver. If enableControllerInNamesrv is enabled and the controller parameters are configured correctly, the controller function is enabled. If Broker is set to **`enableControllerMode=false`**, it will still operate as before. If **`enableControllerMode=true`**, the Controller must be deployed and the parameters must be configured correctly in order to operate properly. The specific behavior is shown in the following table: | | Old nameserver | Old nameserver + Deploy controllers independently | New nameserver enables controller | New nameserver disable controller | | ---------------------------------- | ------------------------------- | ------------------------------------------------- | --------------------------------- | --------------------------------- | | Old broker | Normal running, cannot failover | Normal running, cannot failover | Normal running, cannot failover | Normal running, cannot failover | | New broker enable controller mode | Unable to go online normally | Normal running, can failover | Normal running, can failover | Unable to go online normally | | New broker disable controller mode | Normal running, cannot failover | Normal running, cannot failover | Normal running, cannot failover | Normal running, cannot failover | ## Upgrade Considerations From the compatibility statements above, it can be seen that NameServer can be upgraded normally without compatibility issues. In the case where the Nameserver is not to be upgraded, the controller component can be deployed independently to obtain switching capabilities. For broker upgrades, there are two cases: 1. Master-Slave deployment is upgraded to controller switching architecture In-place upgrade with data is possible. For each group of Brokers, stop the primary and secondary Brokers and ensure that the CommitLogs of the primary and secondary are aligned (you can either disable writing to this group of Brokers for a certain period of time before the upgrade or ensure consistency by copying). After upgrading the package, restart it. > If the primary and secondary CommitLogs are not aligned, it is necessary to ensure that the primary is online before the secondary is online, otherwise messages may be lost due to data truncation. 2. Upgrade from DLedger mode to Controller switching architecture Due to the differences in the format of message data in DLedger mode and Master-Slave mode, there is no in-place upgrade with data. In the case of deploying multiple groups of Brokers, it is possible to disable writing to a group of Brokers for a certain period of time (as long as it is confirmed that all existing messages have been consumed), and then upgrade and deploy the Controller and new Brokers. In this way, the new Brokers will consume messages from the existing Brokers and the existing Brokers will consume messages from the new Brokers until the consumption is balanced, and then the existing Brokers can be decommissioned. ### Upgrade considerations for persistent BrokerID version The current version supports a new high-availability architecture with persistent BrokerID version. Upgrading from version 5.x to the current version requires the following considerations: For version 4.x, follow the above procedure to upgrade. For upgrading from non-persistent BrokerID version in 5.x to persistent BrokerID version, follow the procedure below: **Upgrade Controller** 1. Stop the old version Controller group. 2. Clear Controller data, i.e., data files located in `~/DLedgerController` by default. 3. Bring up the new version Controller group. > During the Controller upgrade process, Broker can still run normally but cannot failover. **Upgrade Broker** 1. Stop the secondary Broker. 2. Stop the primary Broker. 3. Delete all Epoch files of all Brokers, i.e., `~/store/epochFileCheckpoint` and `~/store/epochFileCheckpoint.bak` by default. 4. Bring up the original primary Broker and wait for it to be elected as master (you can use the `getSyncStateSet` command of admin to observe). 5. Bring up all the original secondary Brokers. > It is recommended to stop the secondary Broker before stopping the primary Broker and bring up the original primary Broker before the original secondary during the online process. This can ensure the original primary-secondary relationship. > If you need to change the primary-secondary relationship before and after the upgrade, make sure that the CommitLog of the primary and secondary are aligned when shutting down. Otherwise, data may be truncated and lost. ================================================ FILE: docs/en/controller/design.md ================================================ # Background In the current RocketMQ Raft mode, the DLedger Commitlog is mainly used to replace the original Commitlog, enabling the Commitlog to have the ability to elect and replicate. However, this also causes some problems: - In the Raft mode, the number of replicas within the Broker group must be three or more, and the ACK of the replicas must also follow the majority protocol. - RocketMQ has two sets of HA replication processes, and the replication in Raft mode cannot utilize RocketMQ's native storage capability. Therefore, we hope to use DLedger to implement a consistency module (DLedger Controller) based on Raft, and use it as an optional leader election component. It can be deployed independently or embedded in the Nameserver. The Broker completes the election of the Master through interaction with the Controller, thus solving the above problems. We refer to this new mode as the Controller mode. # Architecture ### Core idea ![架构图](../image/controller/controller_design_1.png) - The following is a description of the core architecture of the Controller mode, as shown in the figure: - DledgerController: Using DLedger, a DLedger controller that ensures the consistency of metadata is constructed. The Raft election will select an Active DLedger Controller as the main controller. The DLedger Controller can be embedded in the Nameserver or deployed independently. Its main function is to store and manage the SyncStateSet list of Brokers, and actively issue scheduling instructions to switch the Master of the Broker when the Master of the Broker is offline or network isolated. - SyncStateSet: Mainly represents a set of Slave replicas following the Master in a broker replica group, with the main criterion for judgment being the gap between the Master and the Slave. When the Master is offline, we will select a new Master from the SyncStateSet list. The SyncStateSet list is mainly initiated by the Master Broker. The Master completes the Shrink and Expand of the SyncStateSet through a periodic task to determine and synchronize the SyncStateSet, and initiates an Alter SyncStateSet request to the election component Controller. - AutoSwitchHAService: A new HAService that, based on DefaultHAService, supports the switching of BrokerRole and the mutual conversion between Master and Slave (under the control of the Controller). In addition, this HAService unifies the log replication process and truncates the logs during the HA HandShake stage. - ReplicasManager: As an intermediate component, it serves as a link between the upper and lower levels. Upward, it can regularly synchronize control instructions from the Controller, and downward, it can regularly monitor the state of the HAService and modify the SyncStateSet at the appropriate time. The ReplicasManager regularly synchronizes metadata about the Broker from the Controller, and when the Controller elects a new Master, the ReplicasManager can detect the change in metadata and switch the BrokerRole. ## DLedgerController core design ![image-20220605213143645](../image/controller/quick-start/controller.png) - The following is a description of the core design of the DLedgerController: - DLedgerController can be embedded in Namesrv or deployed independently. - Active DLedgerController is the Leader elected by DLedger. It will accept event requests from clients and initiate consensus through DLedger, and finally apply them to the in-memory metadata state machine. - Not Active DLedgerController, also known as the Follower role, will replicate the event logs from the Active DLedgerController through DLedger and then apply them directly to the state machine. ## Log replication ### Basic concepts and processes In order to unify the log replication process, distinguish the log replication boundary of each Master, and facilitate log truncation, the concept of MasterEpoch is introduced, which represents the current Master's term number (similar to the meaning of Raft Term). For each Master, it has a MasterEpoch and a StartOffset, which respectively represent the term number and the starting log offset of the Master. It should be noted that the MasterEpoch is determined by the Controller and is monotonically increasing. In addition, we have introduced the EpochFile, which is used to store the \ sequence. **When a Broker becomes the Master, it will:** - Truncate the Commitlog to the boundary of the last message. - Persist the latest \ to the EpochFile, where startOffset is the current CommitLog's MaxPhyOffset. - Then the HAService listens for connections and creates the HAConnection to interact with the Slave to complete the process. **When a Broker becomes the Slave, it will:** Ready stage: - Truncate the Commitlog to the boundary of the last message. - Establish a connection with the Master. Handshake stage: - Perform log truncation, where the key is for the Slave to compare its local epoch and startOffset with the Master to find the log truncation point and perform log truncation. Transfer stage: - Synchronize logs from the Master. ### Truncation algorithm The specific log truncation algorithm flow is as follows: - During the Handshake stage, the Slave obtains the Master's EpochCache from the Master. - The Slave compares the obtained Master EpochCache \, and compares them with the local cache from back to front. If the Epoch and StartOffset of the two are equal, the Epoch is valid, and the truncation point is the smaller Endoffset between them. After truncation, the \ information is corrected and enters the Transfer stage. If they are not equal, the previous epoch of the Slave is compared until the truncation point is found. ```java slave:TreeMap> epochMap; Iterator iterator = epochMap.entrySet().iterator(); truncateOffset = -1; //The epochs are sorted from largest to smallest while (iterator.hasNext()) { Map.Entry> curEntry = iterator.next(); Pair masterOffset= findMasterOffsetByEpoch(curEntry.getKey()); if(masterOffset != null && curEntry.getKey().getObejct1() == masterOffset.getObejct1()) { truncateOffset = Math.min(curEntry.getKey().getObejct2(), masterOffset.getObejct2()); break; } } ``` ### Replication process Since HA replicates logs based on stream, we cannot distinguish the boundaries of the logs (that is, a batch of transmitted logs may span multiple MasterEpochs), and the Slave cannot detect changes in MasterEpoch and cannot timely modify EpochFile. Therefore, we have made the following improvements: When the Master transfers logs, it ensures that a batch of logs sent at a time is in the same epoch, but not spanning multiple epochs. We can add two variables in WriteSocketService: - currentTransferEpoch: represents which epoch WriteSocketService.nextTransferFromWhere belongs to - currentTransferEpochEndOffset: corresponds to the end offset of currentTransferEpoch. If currentTransferEpoch == MaxEpoch, then currentTransferEpochEndOffset= -1, indicating no boundary. When WriteSocketService transfers the next batch of logs (assuming the total size of this batch is size), if it finds that nextTransferFromWhere + size > currentTransferEpochEndOffset, it sets selectMappedBufferResult limit to currentTransferEpochEndOffset. Finally, modify currentTransferEpoch and currentTransferEpochEndOffset to the next epoch. Correspondingly, when the Slave receives logs, if it finds a change in epoch from the header, it records it in the local epoch file. ### Replication protocol According to the above, we can know the AutoSwitchHaService protocol divides log replication into multiple stages. Below is the protocol for the HaService. #### Handshake stage 1.AutoSwitchHaClient (Slave) will send a HandShake packet to the Master as follows: ![示意图](../image/controller/controller_design_3.png) `current state(4byte) + Two flags(4byte) + slaveBrokerId(8byte)` - `Current state` represents the current HAConnectionState, which is HANDSHAKE. - Two flags are two status flags, where `isSyncFromLastFile` indicates whether to start copying from the Master's last file, and `isAsyncLearner` indicates whether the Slave is an asynchronous copy and joins the Master as a Learner. - `slaveBrokerId` represent the brokerId of the Slave, which will be used later to join the SyncStateSet. 2.AutoSwitchHaConnection (Master) will send a HandShake packet back to the Slave as follows: ![示意图](../image/controller/controller_design_4.png) `current state(4byte) + body size(4byte) + offset(8byte) + epoch(4byte) + body` - `Current state` represents the current HAConnectionState, which is HANDSHAKE. - `Body size` represents the length of the body. - `Offset` represents the maximum offset of the log on the Master side. - `Epoch` represents the Master's Epoch. - The Body contains the EpochEntryList on the Master side. After the Slave receives the packet sent back by the Master, it will perform the log truncation process described above locally. #### Transfer stage 1.AutoSwitchHaConnection (Master) will continually send log packets to the Slave as follows: ![示意图](../image/controller/controller_design_5.png) `current state(4byte) + body size(4byte) + offset(8byte) + epoch(4byte) + epochStartOffset(8byte) + additionalInfo(confirmOffset) (8byte)+ body` - `Current state`: represents the current HAConnectionState, which is Transfer. - `Body size`: represents the length of the body. - `Offset`: the starting offset of the current batch of logs. - `Epoch`: represents the MasterEpoch to which the current batch of logs belongs. - `epochStartOffset`: represents the StartOffset of the MasterEpoch corresponding to the current batch of logs. - `confirmOffset`: represents the minimum offset among replicas in SyncStateSet. - `Body`: logs. 2.AutoSwitchHaClient (Slave) will send an ACK packet to the Master: ![示意图](../image/controller/controller_design_6.png) ` current state(4byte) + maxOffset(8byte)` - `Current state`: represents the current HAConnectionState, which is Transfer. - `MaxOffset`: represents the current maximum log offset of the Slave. ## Elect Master ### Basic process ELectMaster mainly selects a new Master from the SyncStateSet list when the Master of a Broker replica group is offline or inaccessible. This event is initiated by the Controller itself or through the `electMaster` operation command. Whether the Controller is deployed independently or embedded in Namesrv, it listens to the connection channels of each Broker. If a Broker channel becomes inactive, it checks whether the Broker is the Master, and if so, it triggers the Master election process. The process of electing a Master is relatively simple. We just need to select one from the SyncStateSet list corresponding to the group of Brokers and make it the new Master, and apply the result to the in-memory metadata through the DLedger consensus. Finally, the result is notified to the corresponding Broker replica group. ### SyncStateSet change SyncStateSet is an important basis for electing a Master. Changes to the SyncStateSet list are mainly initiated by the Master Broker. The Master completes the Shrink and Expand of SyncStateSet through a periodic task and initiates an Alter SyncStateSet request to the election component Controller during the synchronization process. #### Shrink Shrink SyncStateSet refers to the removal of replicas from the SyncStateSet replica set that are significantly behind the Master, based on the following criteria: - Increase the haMaxTimeSlaveNotCatchUp parameter. - HaConnection records the last time the Slave caught up with the Master's timestamp, lastCaughtUpTimeMs, which means: every time the Master sends data (transferData) to the Slave, it records its current MaxOffset as lastMasterMaxOffset and the current timestamp lastTransferTimeMs. - When ReadSocketService receives slaveAckOffset, if slaveAckOffset >= lastMasterMaxOffset, it updates lastCaughtUpTimeMs to lastTransferTimeMs. - The Master scans each HaConnection through a periodic task and if (cur_time - connection.lastCaughtUpTimeMs) > haMaxTimeSlaveNotCatchUp, the Slave is Out-of-sync. - If a Slave is detected to be out of sync, the master immediately reports SyncStateSet to the Controller, thereby shrinking SyncStateSet. #### Expand If a Slave replica catches up with the Master, the Master needs to timely alter SyncStateSet with the Controller. The condition for adding to SyncStateSet is slaveAckOffset >= ConfirmOffset (the minimum value of MaxOffset among all replicas in the current SyncStateSet). ## Reference [RIP-44](https://github.com/apache/rocketmq/wiki/RIP-44-Support-DLedger-Controller) ================================================ FILE: docs/en/controller/persistent_unique_broker_id.md ================================================ # Persistent unique BrokerId ## Current Issue Currently, `BrokerAddress` is used as the unique identifier for the Broker in Controller mode, which causes the following problems: * In a container environment, each restart or upgrade of the Broker may result in an IP address change, making it impossible to associate the previous `BrokerAddress` records with the restarted Broker, such as `ReplicaInfo`, `SyncStateSet`, and other data. ## Improvement Plan In the Controller side, `BrokerName:BrokerId` is used as the unique identifier instead of `BrokerAddress`. Also, `BrokerId` needs to be persistently stored. Since `ClusterName` and `BrokerName` are both configured in the configuration file when starting up, only the allocation and persistence of `BrokerId` need to be addressed.When the Broker first comes online, only the `ClusterName`, `BrokerName`, and its own `BrokerAddress` configured in the configuration file are available. Therefore, a unique identifier, `BrokerId`, that is determined throughout the lifecycle of the entire cluster needs to be negotiated with the Controller. The `BrokerId` is assigned starting from 1. When the Broker is selected as the Master, it will be re-registered in the Name Server, and at this point, to be compatible with the previous non-HA Master-Slave architecture, the `BrokerId` needs to be temporarily changed to 0 (where id 0 previously represented that the Broker was a Master). ### Online Process ![register process](../image/controller/persistent_unique_broker_id/register_process.png) #### 1. GetNextBrokerId Request Send a GetNextBrokerId request to the Controller to obtain the next available BrokerId (allocated starting from 1). #### 1.1 ReadFromDLedger Upon receiving the request, the Controller uses DLedger to retrieve the NextBrokerId data from the state machine. #### 2. GetNextBrokerId Response The Controller returns the NextBrokerId to the Broker. #### 2.1 CreateTempMetaFile After receiving the NextBrokerId, the Broker creates a temporary file .broker.meta.temp, which records the NextBrokerId (the expected BrokerId to be applied) and generates a RegisterCode (used for subsequent identity verification), which is also persisted to the temporary file. #### 3. ApplyBrokerId Request The Broker sends an ApplyBrokerId request to the Controller, carrying its basic data (ClusterName, BrokerName, and BrokerAddress) and the expected BrokerId and RegisterCode. #### 3.1 CASApplyBrokerId The Controller writes this event to DLedger. When the event (log) is applied to the state machine, it checks whether the BrokerId can be applied (if the BrokerId has already been allocated and is not assigned to the Broker, the application fails). It also records the relationship between the BrokerId and RegisterCode. #### 4. ApplyBrokerId Response If the previous step successfully applies the BrokerId, the Controller returns success to the Broker; otherwise, it returns the current NextBrokerId. #### 4.1 CreateMetaFileFromTemp If the BrokerId is successfully applied in the previous step, it can be considered as successfully allocated on the Broker side. At this point, the information of this BrokerId needs to be persisted. This is achieved by atomically deleting the .broker.meta.temp file and creating a .broker.meta file. These two steps need to be atomic operations. > After the above process, the Broker and Controller that come online for the first time successfully negotiate a BrokerId that both sides agree on and persist it. #### 5. RegisterBrokerToController Request The previous steps have correctly negotiated the BrokerId, but at this point, it is possible that the BrokerAddress saved on the Controller side is the BrokerAddress when the last Broker came online. Therefore, the BrokerAddress needs to be updated now by sending a RegisterBrokerToController request with the current BrokerAddress. #### 5.1 UpdateBrokerAddress The Controller compares the BrokerAddress currently saved in the Controller state machine for this Broker. If it does not match the BrokerAddress carried in the request, it updates it to the BrokerAddress in the request. #### 6. RegisterBrokerToController Response After updating the BrokerAddress, the Controller can return the master-slave information of the Broker-set where the Broker is located, to notify the Broker to perform the corresponding identity transformation. ### Registration status rotation ![register state transfer](../image/controller/persistent_unique_broker_id/register_state_transfer.png) ### Fault tolerance > If various crashes occur during the normal online process, the following process ensures the correct allocation of BrokerId. #### Node online after normal restart If it is a normal restart, then a unique BrokerId has already been negotiated by both sides, and the broker.meta already has the data for that BrokerId. Therefore, the registration process is not necessary and the subsequent process can be continued directly. That is, continue to come online from RegisterBrokerToController. ![restart normally](../image/controller/persistent_unique_broker_id/normal_restart.png) #### CreateTempMetaFile Failure ![fail at creating temp metadata file](../image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png) If the process shown in the figure fails, then after the Broker restarts, the Controller's state machine has not allocated any BrokerId. The Broker itself has not saved any data. Therefore, just restart the process from the beginning as described above. #### CreateTempMetaFile success,ApplyBrokerId fail If the Controller already considers the ApplyBrokerId request to be incorrect (i.e., requesting to allocate a BrokerId that has already been allocated and the RegisterCode is not equal), and at this time returns the current NextBrokerId to the Broker, then the Broker directly deletes the .broker.meta.temp file and goes back to step 2 to restart the process and subsequent steps. ![fail at applying broker id](../image/controller/persistent_unique_broker_id/fail_apply_broker_id.png) #### ApplyBrokerId success,CreateMetaFileFromTemp fail The above situation can occur in the ApplyResult loss, and in the CAS deletion and creation of broker.meta failure processes. After restart, the Controller side thinks that our ApplyBrokerId process has succeeded and has already modified the BrokerId allocation data in the state machine. So at this point, we can directly start step 3 again, which is to send the ApplyBrokerId request. ![fail at create metadata file](../image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png) Since we have the .broker.meta.temp file, we can retrieve the BrokerId and RegisterCode that were successfully applied on the Controller side, and send them directly to the Controller. If the BrokerId exists in the Controller and the RegisterCode is equal to the one in the request, it is considered successful. ### After successful registration, use the BrokerId as the unique identifier. After successful registration, all subsequent requests and state records for the Broker are identified by BrokerId. The recording of heartbeats and other data is also identified by BrokerId. At the same time, the Controller side will also record the BrokerAddress of the current BrokerId, which will be used to notify the Broker of changes in state such as switching between master and slave. ## Upgrade plan To upgrade to version 4.x, follow the 5.0 upgrade documentation process. For upgrading from the non-persistent BrokerId version in 5.0.0 or 5.1.0 to the persistent BrokerId version 5.1.1 or above, follow the following steps: ### Upgrade Controller 1. Shut down the old version of the Controller group. 2. Clear the Controller data, i.e., the data files located by default in `~/DLedgerController`. 3. Bring up the new version of the Controller group. > During the above Controller upgrade process, the Broker can still run normally but cannot be switched. ### Upgrade Broker 1. Shut down the Broker slave node. 2. Shut down the Broker master node. 3. Delete all the Epoch files for all Brokers, i.e., the ones located at `~/store/epochFileCheckpoint` and `~/store/epochFileCheckpoint.bak` by default. 4. Bring up the original master Broker and wait for it to be elected as the new master (you can use the `getSyncStateSet` command in the `admin` tool to check). 5. Bring up all the original slave Brokers. > It is recommended to shut down the slave Brokers before shutting down the master Broker and bring up the original master Broker before bringing up the original slave Brokers. This will ensure that the original master-slave relationship is maintained. If you need to change the master-slave relationship after the upgrade, you need to make sure that the CommitLog of the old master and slave Brokers are aligned before shutting them down, otherwise data may be truncated and lost. ### Compatibility | | Controller for version 5.1.0 and below | Controller for version 5.1.1 and above | |------------------------------------|--------------------------------| ------------------------------------------------------------ | | Broker for version 5.1.0 and below | Normal operation and switch. | Normal operation and no switch if the master-slave relationship is already determined. The Broker cannot be brought up if it is restarted. | | Broker for version 5.1.1 and above | Cannot be brought up normally. | Normal operation and switch. | ================================================ FILE: docs/en/controller/quick_start.md ================================================ # Master-Slave automatic switch Quick start ## Introduction ![架构图](../image/controller/controller_design_2.png) This document mainly introduces how to quickly build a RocketMQ cluster that supports automatic master-slave switch, as shown in the above diagram. The main addition is the Controller component, which can be deployed independently or embedded in the NameServer. For detailed design ideas, please refer to [Design ideas](design.md). For detailed guidelines on new cluster deployment and old cluster upgrades, please refer to [Deployment guide](deploy.md). ## Compile RocketMQ source code ```shell $ git clone https://github.com/apache/rocketmq.git $ cd rocketmq $ mvn -Prelease-all -DskipTests clean install -U ``` ## Quick deployment After successful build ```shell #{rocketmq-version} replace with rocketmq actual version. example: 5.0.0-SNAPSHOT $ cd distribution/target/rocketmq-{rocketmq-version}/rocketmq-{rocketmq-version}/ $ sh bin/controller/fast-try.sh start ``` If the above steps are successful, you can view the status of the Controller using the operation and maintenance command. ```shell $ sh bin/mqadmin getControllerMetaData -a localhost:9878 ``` `-a` represents the address of any controller in the cluster At this point, you can send and receive messages in the cluster and perform switch testing. If you need to shut down the cluster quickly , you can execute: ```shell $ sh bin/controller/fast-try.sh stop ``` For quick deployment, the default configuration is in `conf/controller/quick-start`, the default storage path is `/tmp/rmqstore`, and a controller (embedded in Namesrv) and two brokers will be started. ### Query SyncStateSet Use the operation and maintenance tool to query SyncStateSet: ```shell $ sh bin/mqadmin getSyncStateSet -a localhost:9878 -b broker-a ``` `-a` represents the address of any controller If successful, you should see the following content: ![image-20220605205259913](../image/controller/quick-start/syncstateset.png) ### Query BrokerEpoch Use the operation and maintenance tool to query BrokerEpochEntry: ```shell $ sh bin/mqadmin getBrokerEpoch -n localhost:9876 -b broker-a ``` `-n` represents the address of any Namesrv If successful, you should see the following content: ![image-20220605205247476](../image/controller/quick-start/epoch.png) ## Switch After successful deployment, try to perform a master switch now. First, kill the process of the original master, in the example above, it is the process using port 30911: ```shell #query port: $ ps -ef|grep java|grep BrokerStartup|grep ./conf/controller/quick-start/broker-n0.conf|grep -v grep|awk '{print $2}' #kill master: $ kill -9 PID ``` Next,use `SyncStateSet admin` script to query: ```shell $ sh bin/mqadmin getSyncStateSet -a localhost:9878 -b broker-a ``` The master has switched. ![image-20220605211244128](../image/controller/quick-start/changemaster.png) ## Deploying controller embedded in Nameserver cluster The Controller component is embedded in the Nameserver cluster (consisting of 3 nodes) and quickly started through the plugin mode: ```shell $ sh bin/controller/fast-try-namesrv-plugin.sh start ``` Alternatively, it can be started separately through a command: ```shell $ nohup bin/mqnamesrv -c ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf & $ nohup bin/mqnamesrv -c ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf & $ nohup bin/mqnamesrv -c ./conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf & ``` If the above steps are successful, you can check the status of the Controller cluster through operational commands: ```shell $ sh bin/mqadmin getControllerMetaData -a localhost:9878 ``` `-a` represents the address of any Controller nodes If the Controller starts successfully, you can see the following content: ``` #ControllerGroup group1 #ControllerLeaderId n0 #ControllerLeaderAddress 127.0.0.1:9878 #Peer: n0:127.0.0.1:9878 #Peer: n1:127.0.0.1:9868 #Peer: n2:127.0.0.1:9858 ``` After the successful start, the broker controller mode deployment can use the controller cluster. If you need to quickly stop the cluster: ```shell $ sh bin/controller/fast-try-namesrv-plugin.sh stop ``` The `fast-try-namesrv-plugin.sh` script is used for quick deployment with default configurations in the `conf/controller/cluster-3n-namesrv-plugin` directory, and it will start 3 Nameservers and 3 controllers (embedded in Nameserver). ## Deploying Controller in independent cluster The Controller component is deployed in an independent cluster (consisting of 3 nodes) and quickly started.: ```shell $ sh bin/controller/fast-try-independent-deployment.sh start ``` Alternatively, it can be started separately through a command: ```shell $ nohup bin/mqcontroller -c ./conf/controller/cluster-3n-independent/controller-n0.conf & $ nohup bin/mqcontroller -c ./conf/controller/cluster-3n-independent/controller-n1.conf & $ nohup bin/mqcontroller -c ./conf/controller/cluster-3n-independent/controller-n2.conf & ``` If the previous steps are successful, you can check the status of the Controller cluster using the operational command. ```shell $ sh bin/mqadmin getControllerMetaData -a localhost:9878 ``` `-a` represents the address of any controller. If the controller starts successfully, you will see the following content: ``` #ControllerGroup group1 #ControllerLeaderId n1 #ControllerLeaderAddress 127.0.0.1:9868 #Peer: n0:127.0.0.1:9878 #Peer: n1:127.0.0.1:9868 #Peer: n2:127.0.0.1:9858 ``` After starting successfully, the broker controller mode deployment can use the controller cluster. If you need to quickly stop the cluster: ```shell $ sh bin/controller/fast-try-independent-deployment.sh stop ``` Use the `fast-try-independent-deployment.sh` script to quickly deploy, the default configuration is in `conf/controller/cluster-3n-independent` and it will start 3 controllers (independent deployment) to form a cluster. ## Note - If you want to ensure that the Controller has fault tolerance, the Controller deployment requires at least three copies (in accordance with the majority protocol of Raft). - In the controller deployment configuration file, the IP addresses configured in the `controllerDLegerPeers` parameter should be configured as IPs that can be accessed by other nodes. This is especially important when deploying on multiple machines. The example is for reference only and needs to be modified and adjusted according to the actual situation. ================================================ FILE: docs/en/design.md ================================================ ## Design ### 1 Message Store ![](../cn/image/rocketmq_design_1.png) #### 1.1 The Architecture of Message Store #### 1.2 PageCache and Memory-Map(Mmap) #### 1.3 Message Flush ![](../cn/image/rocketmq_design_2.png) ### 2 Communication Mechanism #### 2.1 The class diagram of Remoting module ![](../cn/image/rocketmq_design_3.png) #### 2.2 The design of protocol and encode/decode ![](../cn/image/rocketmq_design_4.png) #### 2.3 The three ways and process of message communication ![](../cn/image/rocketmq_design_5.png) #### 2.4 The multi-thread design of Reactor ![](../cn/image/rocketmq_design_6.png) ### 3 Message Filter ![](../cn/image/rocketmq_design_7.png) ### 4 LoadBalancing #### 4.1 The loadBalance of Producer #### 4.2 The loadBalance of Consumer ![](../cn/image/rocketmq_design_8.png) ![](../cn/image/rocketmq_design_9.png) ### 5 Transactional Message Apache RocketMQ supports distributed transactional message from version 4.3.0. RocketMQ implements transactional message by using the protocol of 2PC(two-phase commit), in addition adding a compensation logic to handle timeout-case or failure-case of commit-phase, as shown below. ![](../cn/image/rocketmq_design_10.png) #### 5.1 The Process of RocketMQ Transactional Message The picture above shows the overall architecture of transactional message, including the sending of message(commit-request phase), the sending of commit/rollback(commit phase) and the compensation process. 1. The sending of message and Commit/Rollback. (1) Sending the message(named Half message in RocketMQ) (2) The server responds the writing result(success or failure) of Half message. (3) Handle local transaction according to the result(local transaction won't be executed when the result is failure). (4) Sending Commit/Rollback to broker according to the result of local transaction(Commit will generate message index and make the message visible to consumers). 2. Compensation process (1) For a transactional message without a Commit/Rollback (means the message in the pending status), a "back-check" request is initiated from the broker. (2) The Producer receives the "back-check" request and checks the status of the local transaction corresponding to the "back-check" message. (3) Redo Commit or Rollback based on local transaction status. The compensation phase is used to resolve the timeout or failure case of the message Commit or Rollback. #### 5.2 The design of RocketMQ Transactional Message 1. Transactional message is invisible to users in first phase(commit-request phase) Upon on the main process of transactional message, the message of first phase is invisible to the user. This is also the biggest difference from normal message. So how do we write the message while making it invisible to the user? And below is the solution of RocketMQ: if the message is a Half message, the topic and queueId of the original message will be backed up, and then changes the topic to RMQ_SYS_TRANS_HALF_TOPIC. Since the consumer group does not subscribe to the topic, the consumer cannot consume the Half message. Then RocketMQ starts a timing task, pulls the message for RMQ_SYS_TRANS_HALF_TOPIC, obtains a channel according to producer group and sends a back-check to query local transaction status, and decide whether to submit or roll back the message according to the status. In RocketMQ, the storage structure of the message in the broker is as follows. Each message has corresponding index information. The Consumer reads the content of the message through the secondary index of the ConsumeQueue. The flow is as follows: ![](../cn/image/rocketmq_design_11.png) The specific implementation strategy of RocketMQ is: if the transactional message is written, topic and queueId of the message are replaced, and the original topic and queueId are stored in the properties of the message. Because the replace of the topic, the message will not be forwarded to the Consumer Queue of the original topic, and the consumer cannot perceive the existence of the message and will not consume it. In fact, changing the topic is the conventional method of RocketMQ(just recall the implementation mechanism of the delay message). 2. Commit/Rollback operation and introduction of Op message After finishing writing a message that is invisible to the user in the first phase, here comes two cases in the second phase. One is Commit operation, after which the message needs to be visible to the user; the other one is Rollback operation, after which the first phase message(Half message) needs to be revoked. For the case of Rollback, since first-phase message itself is invisible to the user, there is no need to actually revoke the message (in fact, RocketMQ can't actually delete a message because it is a sequential-write file). But still some operation needs to be done to identity the final status of the message, to differ it from pending status message. To do this, the concept of "Op message" is introduced, which means the message has a certain status(Commit or Rollback). If a transactional message does not have a corresponding Op message, the status of the transaction is still undetermined (probably the second-phase failed). By introducing the Op message, the RocketMQ records an Op message for every Half message regardless it is Commit or Rollback. The only difference between Commit and Rollback is that when it comes to Commit, the index of the Half message is created before the Op message is written. 3. How Op message stored and the correspondence between Op message and Half message RocketMQ writes the Op message to a specific system topic(RMQ_SYS_TRANS_OP_HALF_TOPIC) which will be created via the method - TransactionalMessageUtil.buildOpTopic(); this topic is an internal Topic (like the topic of RMQ_SYS_TRANS_HALF_TOPIC) and will not be consumed by the user. The content of the Op message is the physical offset of the corresponding Half message. Through the Op message we can index to the Half message for subsequent check-back operation. ![](../cn/image/rocketmq_design_12.png) 4. Index construction of Half messages When performing Commit operation of the second phase, the index of the Half message needs to be built. Since the Half message is written to a special topic(RMQ_SYS_TRANS_HALF_TOPIC) in the first phase of 2PC, so it needs to be read out from the special topic when building index, and replace the topic and queueId with the real target topic and queueId, and then write through a normal message that is visible to the user. Therefore, in conclusion, the second phase recovers a complete normal message using the content of the Half message stored in the first phase, and then goes through the message-writing process. 5. How to handle the message failed in the second phase? If commit/rollback phase fails, for example, a network problem causes the Commit to fail when you do Commit. Then certain strategy is required to make sure the message finally commit. RocketMQ uses a compensation mechanism called "back-check". The broker initiates a back-check request for the message in pending status, and sends the request to the corresponding producer side (the same producer group as the producer group who sent the Half message). The producer checks the status of local transaction and redo Commit or Rollback. The broker performs the back-check by comparing the RMQ_SYS_TRANS_HALF_TOPIC messages and the RMQ_SYS_TRANS_OP_HALF_TOPIC messages and advances the checkpoint(recording those transactional messages that the status are certain). RocketMQ does not back-check the status of transactional messages endlessly. The default time is 15. If the transaction status is still unknown after 15 times, RocketMQ will roll back the message by default. ### 6 Message Query #### 6.1 Query messages by messageId #### 6.2 Query messages by message key ![](../cn/image/rocketmq_design_13.png) ================================================ FILE: docs/en/dledger/deploy_guide.md ================================================ # Dledger cluster deployment --- ## preface This document introduces how to deploy auto failover RocketMQ-on-DLedger Group. RocketMQ-on-DLedger Group is a broker group with **same name**, needs at least 3 nodes, elect a Leader by Raft algorithm automatically, the others as Follower, replicating data between Leader and Follower for system high available. RocketMQ-on-DLedger Group can failover automatically, and maintains consistent. RocketMQ-on-DLedger Group can scale up horizontal, that is, can deploy any RocketMQ-on-DLedger Groups providing services external. ## 1. New cluster deployment #### 1.1 Write the configuration each RocketMQ-on-DLedger Group needs at least 3 machines.(assuming 3 in this document) write 3 configuration files, advising refer to the directory of conf/dledger 's example configuration file. key configuration items: | name | meaning | example | | --- | --- | --- | | enableDLegerCommitLog | whether enable DLedger  | true | | dLegerGroup | DLedger Raft Group's name, advising maintain consistent to brokerName | RaftNode00 | | dLegerPeers | DLedger Group's nodes port infos, each node's configuration stay consistent in the same group. | n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 | | dLegerSelfId | node id, must belongs to dLegerPeers; each node is unique in the same group. | n0 | | sendMessageThreadPoolNums | the count of sending thread, advising set equal to the cpu cores. | 16 | the following presents an example configuration conf/dledger/broker-n0.conf. ``` brokerClusterName = RaftCluster brokerName=RaftNode00 listenPort=30911 namesrvAddr=127.0.0.1:9876 storePathRootDir=/tmp/rmqstore/node00 storePathCommitLog=/tmp/rmqstore/node00/commitlog enableDLegerCommitLog=true dLegerGroup=RaftNode00 dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 ## must be unique dLegerSelfId=n0 sendMessageThreadPoolNums=16 ``` ### 1.2 Start Broker Startup stays consistent with the old version. `nohup sh bin/mqbroker -c conf/dledger/xxx-n0.conf & ` `nohup sh bin/mqbroker -c conf/dledger/xxx-n1.conf & ` `nohup sh bin/mqbroker -c conf/dledger/xxx-n2.conf & ` ## 2. Upgrade old cluster If old cluster deployed in Master mode, then each Master needs to be transformed into a RocketMQ-on-DLedger Group. If old cluster deployed in Master-Slave mode, then each Master-Slave group needs to be transformed into a RocketMQ-on-DLedger Group. ### 2.1 Kill old Broker execute kill command, or call `bin/mqshutdown broker`. ### 2.2 Check old Commitlog Each node in RocketMQ-on-DLedger group is compatible with old Commitlog, but Raft replicating process works on the adding message only. So, to avoid occurring exceptions, old Commitlog must be consistent. If old cluster deployed in Master-Slave mode, it maybe inconsistent after shutdown. Advising use md5sum to check at least 2 recently Commitlog file, if occur inconsistent, maintain consistent by copy. Although RocketMQ-on-DLedger Group can deployed with 2 nodes, it lacks failover ability(at least 3 nodes can tolerate one node fail). Make sure that both Master and Slave's Commitlog is consistent, then prepare 3 machines, copy old Commitlog from Master to this 3 machines(BTW, copy the config directory). Then, go ahead to set configurations. ### 2.3 Modify configuration Refer to New cluster deployment. ### 2.4 Restart Broker Refer to New cluster deployment. ================================================ FILE: docs/en/dledger/quick_start.md ================================================ # Dledger Quick Deployment --- ### preface This document is mainly introduced for how to build and deploy auto failover RocketMQ cluster based on DLedger. For detailed new cluster deployment and old cluster upgrade document, please refer to [Deployment Guide](deploy_guide.md). ### 1. Build from source code Build phase contains two parts, first, build DLedger, then build RocketMQ. #### 1.1 Build DLedger ```shell $ git clone https://github.com/openmessaging/dledger.git $ cd dledger $ mvn clean install -DskipTests ``` #### 1.2 Build RocketMQ ```shell $ git clone https://github.com/apache/rocketmq.git $ cd rocketmq $ git checkout -b store_with_dledger origin/store_with_dledger $ mvn -Prelease-all -DskipTests clean install -U ``` ### 2. Quick Deployment after build successful ```shell #{rocketmq-version} replace with rocketmq actual version. example: 5.0.0-SNAPSHOT $ cd distribution/target/rocketmq-{rocketmq-version}/rocketmq-{rocketmq-version} $ sh bin/dledger/fast-try.sh start ``` if the above commands executed successfully, then check cluster status by using mqadmin operation commands. ```shell $ sh bin/mqadmin clusterList -n 127.0.0.1:9876 ``` If everything goes well, the following content will appear: ![ClusterList](https://img.alicdn.com/5476e8b07b923/TB11Z.ZyCzqK1RjSZFLXXcn2XXa) (BID is 0 indicate Master, the others are Follower) After startup successful, producer can produce message, and then test failover scenario. Stop cluster fastly, execute the following command: ```shell $ sh bin/dledger/fast-try.sh stop ``` Quick deployment, default configuration is in directory conf/dledger, default storage path is /tmp/rmqstore. ### 3. Failover After successful deployment, kill Leader process(as the above example, kill process that binds port 30931), about 10 seconds elapses, use clusterList command check cluster's status, Leader switch to another node. ================================================ FILE: docs/en/msg_trace/user_guide.md ================================================ # message trace ---- ## 1. Message trace data's key properties | Producer End| Consumer End| Broker End| | --- | --- | --- | | produce message | consume message | message's topic | | send message time | delivery time, delivery rounds  | message store location | | whether the message was sent successfully | whether message was consumed successfully | message's key | | send cost-time | consume cost-time | message's tag value | ## 2. Enable message trace in cluster deployment ### 2.1 Broker's configuration file following by Broker's properties file configuration that enable message trace: ``` brokerClusterName=DefaultCluster brokerName=broker-a brokerId=0 deleteWhen=04 fileReservedTime=48 brokerRole=ASYNC_MASTER flushDiskType=ASYNC_FLUSH storePathRootDir=/data/rocketmq/rootdir-a-m storePathCommitLog=/data/rocketmq/commitlog-a-m autoCreateSubscriptionGroup=true ## if msg tracing is open,the flag will be true traceTopicEnable=true listenPort=10911 brokerIP1=XX.XX.XX.XX1 namesrvAddr=XX.XX.XX.XX:9876 ``` ### 2.2 Common mode Each Broker node in RocketMQ cluster used for storing message trace data that client collected and sent. So, there is no requirements and limitations to the size of Broker node in RocketMQ cluster. ### 2.3 IO physical isolation mode For huge amounts of message trace data scenario, we can select any one Broker node in RocketMQ cluster used for storing message trace data special, thus, common message data's IO are isolated from message trace data's IO in physical, not impact each other. In this mode, RocketMQ cluster must have at least two Broker nodes, the one that defined as storing message trace data. ### 2.4 Start Broker that enable message trace `nohup sh mqbroker -c ../conf/2m-noslave/broker-a.properties &` ## 3. Save the definition of topic that with support message trace RocketMQ's message trace feature supports two types of storage. ### 3.1 System level TraceTopic Be default, message trace data is stored in system level TraceTopic(topic name: **RMQ_SYS_TRACE_TOPIC**). That topic will be created at startup of broker(As mentioned above, set **traceTopicEnable** to **true** in Broker's configuration). ### 3.2 User defined TraceTopic If user don't want to store message trace data in system level TraceTopic, he can create user defined TraceTopic used for storing message trace data(that is, create common topic for storing message trace data). The following part will introduce how client SDK support user defined TraceTopic. ## 4. Client SDK demo with message trace feature For business system adapting to use RocketMQ's message trace feature easily, in design phase, the author add a switch parameter(**enableMsgTrace**) for enable message trace; add a custom parameter(**customizedTraceTopic**) for user defined TraceTopic. ### 4.1 Enable message trace when sending messages ``` DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true); producer.setNamesrvAddr("XX.XX.XX.XX1"); producer.start(); try { { Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } } catch (Exception e) { e.printStackTrace(); } ``` ### 4.2 Enable message trace when subscribe messages ``` DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true); consumer.subscribe("TopicTest", "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.setConsumeTimestamp("20181109221800"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); ``` ### 4.3 Self-defined topic support message trace Adjusting instantiation of DefaultMQProducer and DefaultMQPushConsumer as following code to support user defined TraceTopic. ``` ##Topic_test11111 should be created by user, used for storing message trace data. DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true,"Topic_test11111"); ...... DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true,"Topic_test11111"); ...... ``` ### 4.4 Send and query message trace by mqadmin command - send message ```shell ./mqadmin sendMessage -m true --topic some-topic-name -n 127.0.0.1:9876 -p "your message content" ``` - query trace ```shell ./mqadmin QueryMsgTraceById -n 127.0.0.1:9876 -i "some-message-id" ``` - query trace result ``` RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0). RocketMQLog:WARN Please initialize the logger system properly. #Type #ProducerGroup #ClientHost #SendTime #CostTimes #Status Pub 1623305799667 xxx.xxx.xxx.xxx 2021-06-10 14:16:40 131ms success ``` ================================================ FILE: docs/en/operation.md ================================================ # Operation Management --- ### 1 Deploy cluster #### 1.1 Single Master mode This mode is risky, upon broker restart or broken down, the whole service is unavailable. It's not recommended in production environment, it can be used for local test. ##### 1)Start NameServer ```bash ### Start Name Server $ nohup sh mqnamesrv & ### check whether Name Server is successfully started $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)Start Broker ```bash ### start Broker $ nohup sh bin/mqbroker -n localhost:9876 & ### check whether Broker is successfully started, eg: Broker's IP is 192.168.1.2, Broker's name is broker-a $ tail -f ~/logs/rocketmqlogs/broker.log The broker[broker-a, 192.169.1.2:10911] boot success... ``` #### 1.2 Multi Master mode Cluster contains Master node only, no Slave node, eg: 2 Master nodes, 3 Master nodes, advantages and disadvantages of this mode are shown below: - advantages: simple configuration, single Master node broke down or restart do not impact application. Under RAID10 disk config, even if machine broken down and cannot recover, message do not get lost because of RAID10's high reliable(async flush to disk lost little message, sync to disk do not lost message), this mode get highest performance. - disadvantages: during the machine's down time, messages have not be consumed on this machine can not be subscribed before recovery. That will impacts message's instantaneity. ##### 1)Start NameServer NameServer should be started before broker. If under production environment, we recommend start 3 NameServer nodes for high available. Startup command is equal, as shown below: ```bash ### start Name Server $ nohup sh mqnamesrv & ### check whether Name Server is successfully started $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)start Broker cluster ```bash ### start the first Master on machine A, eg:NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-a.properties & ### start the second Master on machine B, eg:NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-b.properties & ... ``` The above commands only used for single NameServer. In multi NameServer cluster, multi addresses concat by semicolon followed by -n in broker start command. #### 1.3 Multi Master Multi Slave mode - async replication Each Master node is equipped with one Slave node, this mode has many Master-Slave group, using async replication for HA, slaver has a lag(ms level) behind master, advantages and disadvantages of this mode are shown below: - advantages: message lost a little, even if disk is broken; message instantaneity do not loss; Consumer can still consume from slave when master is down, this process is transparency to user, no human intervention is required; Performance is almost equal to Multi Master mode. - disadvantages: message lost a little data, when Master is down and disk broken. ##### 1)Start NameServer ```bash ### start Name Server $ nohup sh mqnamesrv & ### check whether Name Server is successfully started $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)Start Broker cluster ```bash ### start first Master on machine A, eg: NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a.properties & ### start second Master on machine B, eg: NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b.properties & ### start first Slave on machine C, eg: NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a-s.properties & ### start second Slave on machine D, eg: NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b-s.properties & ``` #### 1.4 Multi Master Multi Slave mode - synchronous double write Each Master node is equipped with one Slave node, this mode has many Master-Slave group, using synchronous double write for HA, application's write operation is successful means both master and slave write successful, advantages and disadvantages of this mode are shown below: - advantages:both data and service have no single point failure, message has no latency even if Master is down, service available and data available is very high; - disadvantages:this mode's performance is 10% lower than async replication mode, sending latency is a little high, in the current version, it do not have auto Master-Slave switch when Master is down. ##### 1)Start NameServer ```bash ### start Name Server $ nohup sh mqnamesrv & ### check whether Name Server is successfully started $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... ``` ##### 2)Start Broker cluster ```bash ### start first Master on machine A, eg:NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a.properties & ### start second Master on machine B, eg:NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b.properties & ### start first Slave on machine C, eg: NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a-s.properties & ### start second Slave on machine D, eg: NameServer's IP is 192.168.1.1 $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b-s.properties & ``` The above Broker matches Slave by specifying the same BrokerName, Master's BrokerId must be 0, Slave's BrokerId must larger than 0. Besides, a Master can have multi Slaves that each has a distinct BrokerId. $ROCKETMQ_HOME indicates RocketMQ's install directory, user needs to set this environment parameter. ### 2 mqadmin management tool > Attentions: > > 1. execute command: `./mqadmin {command} {args}` > 2. almost all commands need -n indicates NameSerer address, format is ip:port > 3. almost all commands can get help info by -h > 4. if command contains both Broker address(-b) and cluster name(-c), it's prior to use broker address. If command do not contains broker address, it will executed on all hosts in this cluster. Support only one broker host. -b format is ip:port, default port is 10911 > 5. there are many commands under tools, but not all command can be used, only commands that initialized in MQAdminStartup can be used, you can modify this class, add or self-define command. > 6. because of version update, little command do not update timely, please refer to source code directly when occur error. #### 2.1 Topic
    name meaning command items explanation
    updateTopic create or update Topic's config -b Broker address, means which Broker that topic is located, only support single Broker, address format is ip:port
    -c cluster name, which cluster that topic belongs to(query cluster info by clusterList)
    -h- print help info
    -n NameServer Service address, format is ip:port
    -p assign read write authority to new topic(W=2|R=4|WR=6)
    -r the count of queue that can be read(default is 8)
    -w the count of queue that can be wrote(default is 8)
    -t topic name(can only use characters ^[a-zA-Z0-9_-]+$ )
    deleteTopic delete Topic -c cluster name, which cluster that topic will be deleted belongs to(query cluster info by clusterList)
    -h print help info
    -n NameServer Service address, format is ip:port
    -t topic name(can only use characters ^[a-zA-Z0-9_-]+$ )
    topicList query Topic list info -h print help info
    -c return topic list only if do not contains -c, if contains -c, it will return cluster name, topic name, consumer group name
    -n NameServer Service address, format is ip:port
    topicRoute query Topic's route info -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    topicStatus query Topic's offset -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    topicClusterList query cluster list where Topic belongs to -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    updateTopicPerm update Topic's produce and consume authority -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    -b Broker address which topic belongs to, support single broker only, format is ip:port
    -p assign read and write authority to the new topic(W=2|R=4|WR=6)
    -c cluster name, which topic belongs to(query cluster info by clusterList), if do not have -b, execute command on all brokers.
    updateOrderConf create delete get specified namespace's kv config from NameServer, have not enabled at present -h print help info
    -n NameServer Service address, format is ip:port
    -t topic, key
    -v orderConf, value
    -m method, including get, put, delete
    allocateMQ calculate consumer list rebalance result by average rebalance algorithm -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    -i ipList, separate by comma, calculate which topic queue that ips will load.
    statsAll print Topic's subscribe info, TPS, size of message blocked, count of read and write at last 24h, eg. -h print help info
    -n NameServer Service address, format is ip:port
    -a only print active topic or not
    -t assign topic
    #### 2.2 Cluster
    名称 meaning command items explanation
    clusterList query cluster info, including cluster, BrokerName, BrokerId, TPS, eg. -m print more infos(eg: #InTotalYest, #OutTotalYest, #InTotalToday ,#OutTotalToday)
    -h print help info
    -n NameServer Service address, format is ip:port
    -i print interval, unit second
    clusterRT send message to detect each cluster's Broker RT. Message will be sent to ${BrokerName} Topic. -a amount, count of detection, RT = sum time / amount
    -s size of message, unit B
    -c which cluster will be detected
    -p whether print format log, split by |, default is not print
    -h print help info
    -m which machine room it belongs to, just for print
    -i send interval, unit second
    -n NameServer Service address, format is ip:port
    #### 2.3 Broker
    名称 meaning command items explanation
    updateBrokerConfig update Broker's config file, it will modify Broker.conf -b Broker address, format is ip:port
    -c cluster name
    -k key
    -v value
    -h print help info
    -n NameServer Service address, format is ip:port
    brokerStatus get Broker's statistics info, running status(including whatever you want). -b Broker address, format is ip:port
    -h print help info
    -n NameServer Service address, format is ip:port
    brokerConsumeStats Broker's consumer info, including Consume Offset, Broker Offset, Diff, Timestamp that ordered by message Queue -b Broker address, format is ip:port
    -t request timeout time
    -l diff threshold, it will print when exceed this threshold.
    -o whether is sequential topic, generally false
    -h print help info
    -n NameServer Service address, format is ip:port
    getBrokerConfig get Broker's config -b Broker address, format is ip:port
    -n NameServer Service address, format is ip:port
    wipeWritePerm revoke broker's write authority from NameServer. -b Broker address, format is ip:port
    -n NameServer Service address, format is ip:port
    -h print help info
    cleanExpiredCQ clean Broker's expired Consume Queue that maybe generated by decrease queue count. -n NameServer Service address, format is ip:port
    -h print help info
    -b Broker address, format is ip:port
    -c cluster name
    deleteExpiredCommitLog delete Broker's expired CommitLog files. -n NameServer Service address, format is ip:port
    -h print help info
    -b Broker address, format is ip:port
    -c cluster name
    cleanUnusedTopic clean Broker's unused Topic that deleted manually to release memory that Topic's Consume Queue occupied. -n NameServer Service address, format is ip:port
    -h print help info
    -b Broker address, format is ip:port
    -c cluster name
    sendMsgStatus send message to Broker, return send status and RT -n NameServer Service address, format is ip:port
    -h print help info
    -b BrokerName, is different from broker address
    -s message size, unit B
    -c send count
    #### 2.4 Message
    名称 meaning command items explanation
    queryMsgById query message by offsetMsgId. If use opensource console, it should use offsetMsgId. Please refer to QueryMsgByIdSubCommand for detail. -i msgId
    -h print help info
    -n NameServer Service address, format is ip:port
    queryMsgByKey query message by Message's Key -k msgKey
    -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    queryMsgByOffset query message by Offset -b Broker name(it's not broker address, can query Broker name by clusterList).
    -i query queue id
    -o offset value
    -t topic name
    -h print help info
    -n NameServer Service address, format is ip:port
    queryMsgByUniqueKey query by msgId, msgId is different from offsetMsgId, please refer to Frequently asked questions about operations for details. Use -g and -d to let specified consumer return consume result. -h print help info
    -n NameServer Service address, format is ip:port
    -i unique msg id
    -g consumerGroup
    -d clientId
    -t topic name
    checkMsgSendRT detect RT of sending a message to a topic, similar to clusterRT -h print help info
    -n NameServer Service address, format is ip:port
    -t topic name
    -a detection count
    -s size of the message
    sendMessage send a message, also can send to a specified Message Queue. -h print help info
    -n NameServer Service address, format is ip:port
    -t topic name
    -p body, message entity
    -k keys
    -c tags
    -b BrokerName
    -i queueId
    consumeMessage consume message. Different consume logic depends on offset, start & end timestamp, message queue, please refer to ConsumeMessageCommand for details. -h print help info
    -n NameServer Service address, format is ip:port
    -t topic name
    -b BrokerName
    -o offset that consumer start consume
    -i queueId
    -g consumer group
    -s timestamp at start, refer to -h to get format开
    -d timestamp at the end
    -c size of message that consumed
    printMsg consume and print messages from broker, support a time range -h print help info
    -n NameServer Service address, format is ip:port
    -t topic name
    -c charset, eg: UTF-8
    -s subExpress, filter expression
    -b timestamp at start, refer to -h to get format
    -e timestamp at the end
    -d whether print message entity or not
    printMsgByQueue similar to printMsg, but it need specified Message Queue -h print help info
    -n NameServer Service address, format is ip:port
    -t topic name
    -i queueId
    -a BrokerName
    -c charset, eg: UTF-8
    -s subExpress, filter expression
    -b timestamp at start, refer to -h to get format
    -e timestamp at the end
    -p whether print message or not
    -d whether print message entity or not
    -f whether count and print tag or not
    resetOffsetByTime reset offset by timestamp, Broker and consumer will all be reset -h print help info
    -n NameServer Service address, format is ip:port
    -g consumer group
    -t topic name
    -s reset offset corresponding to this timestamp
    -f whether enforce to reset or not, if set false, only can reset offset, if set true, it omit the relationship between timestamp and consumer offset.
    -c whether reset c++ sdk's offset or not
    #### 2.5 Consumer, Consumer Group
    name meaning command items explanation
    consumerProgress query subscribe status, can get blocking counts of a concrete client ip. -g consumer group name
    -s whether print client IP or not
    -h print help info
    -n NameServer Service address, format is ip:port
    consumerStatus query consumer status, including message blocking, and consumer's jstack result(please refer to ConsumerStatusSubCommand) -h print help info
    -n NameServer Service address, format is ip:port
    -g consumer group
    -i clientId
    -s whether execute jstack or not
    updateSubGroup create or update subscribe info -n NameServer Service address, format is ip:port
    -h print help info
    -b Broker address
    -c cluster name
    -g consumer group name
    -s consumer group is allowed to consume or not
    -m start consume from minimal offset or not
    -d broadcast mode or not
    -q capacity of retry queue
    -r max retry count
    -i It works when slaveReadEnable enabled, and that not consumed from slave. Suggesting that consume from slave node by specify slave id.
    -w If broker consume from slave, which slave node depends on this config that defined by BrokerId, eg: 1.
    -a whether notify other consumers to rebalance or not when the count of consumer changes
    deleteSubGroup delete subscribe info from Broker -n NameServer Service address, format is ip:port
    -h print help info
    -b Broker address
    -c cluster name
    -g consumer group name
    cloneGroupOffset use source group's offset at target group -n NameServer Service address, format is ip:port
    -h print help info
    -s source consumer group
    -d target consumer group
    -t topic name
    -o not used at present
    #### 2.6 Connection
    name meaning command items explanation
    consumerConnection query Consumer's connection -g consumer group name
    -n NameServer Service address, format is ip:port
    -h print help info
    producerConnection query Producer's connection -g producer group name
    -t topic name
    -n NameServer Service address, format is ip:port
    -h print help info
    #### 2.7 NameServer
    name meaning command items explanation
    updateKvConfig update NameServer's kv config, not used at present -s namespace
    -k key
    -v value
    -n NameServer Service address, format is ip:port
    -h print help info
    deleteKvConfig delete NameServer's kv config -s namespace
    -k key
    -n NameServer Service address, format is ip:port
    -h print help info
    getNamesrvConfig get NameServer's config -n NameServer Service address, format is ip:port
    -h print help info
    updateNamesrvConfig modify NameServer's config -n NameServer Service address, format is ip:port
    -h print help info
    -k key
    -v value
    #### 2.8 Other
    name meaning command items explanation
    startMonitoring Start the monitoring process, monitor message deletion and the number of retried messages in the queue -n NameServer Service address, format is ip:port
    -h print help info
    ### 3 Frequently asked questions about operations #### 3.1 RocketMQ's mqadmin command error > question description: execute mqadmin occur below exception after deploy RocketMQ cluster. > > ```java > org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to failed > ``` Solution: execute command `export NAMESRV_ADDR=ip:9876` (ip is NameServer's ip address), then execute mqadmin commands. #### 3.2 RocketMQ consumer cannot consume, because of different version of producer and consumer. > question description: one producer produce message, consumer A can consume, consume B cannot consume, RocketMQ console print: > > ```java > Not found the consumer group consume stats, because return offset table is empty, maybe the consumer not consume any message. > ``` Solution: make sure that producer and consumer has the same version of rocketmq-client. #### 3.3 Consumer cannot consume oldest message, when a new consumer group is added. > question description: when a new consumer group start, it consumes from current offset, do not fetch oldest message. Solution: rocketmq's default policy is consume from latest, that is skip oldest message. If you want consume oldest message, you need to set `org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#setConsumeFromWhere`. The following is three common configurations: - default configuration, a new consumer group consume from latest position at first startup, then consume from last time's offset at next startup, that is skip oldest message; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); ``` - a new consumer group consume from oldest position at first startup, then consume from last time's offset at next startup, that is consume the unexpired message; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); ``` - a new consumer group consume from specified timestamp at first startup, then consume from last time's offset at next startup, cooperate with consumer.setConsumeTimestamp(), default is half an hour before; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); ``` #### 3.4 How to enable consume from Slave In some cases, consumer need reset offset to a day or two before, if Master Broker has limited memory, it's CommitLog will have a high IO load, then it will impact other message's read and write that on this broker. When `slaveReadEnable=true` is set, and consumer's offset exceeds `accessMessageInMemoryMaxRatio=40%`, Master Broker will recommend consumer consume from Slave Broker to lower Master Broker IO. #### 3.5 Performance tuning A spin lock is recommended for asynchronous disk flush, a reentrant lock is recommended for synchronous disk flush, configuration item is `useReentrantLockWhenPutMessage`, default is false; Enable `TransientStorePoolEnable` is recommended when use asynchronous disk flush; Recommend to close `transferMsgByHeap` to improve fetch efficiency; Set a little larger `sendMessageThreadPoolNums`, when use synchronous disk flush. #### 3.6 The meaning and difference between msgId and offsetMsgId in RocketMQ You will usually see the following log print message after sending message by using RocketMQ sdk. ```java SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD0000, offsetMsgId=0A42333A00002A9F000000000134F1F5, messageQueue=MessageQueue [topic=topicTest1, BrokerName=mac.local, queueId=3], queueOffset=4] ``` - msgId, is generated by producer sdk. In particular, call method `MessageClientIDSetter.createUniqIDBuffer()` to generate unique Id; - offsetMsgId, offsetMsgId is generated by Broker server(format is "Ip Address + port + CommitLog offset"). offsetMsgId is messageId that is RocketMQ console's input. ================================================ FILE: docs/en/proxy/deploy_guide.md ================================================ # RocketMQ Proxy Deployment Guide ## Overview RocketMQ Proxy supports two deployment modes: `Local` and `Cluster`. ## Configuration The configuration file applies to both `Cluster` and `Local` mode, whose default path is distribution/conf/rmq-proxy.json. ## `Cluster` Mode * Set configuration field `nameSrvAddr`. * Set configuration field `proxyMode` to `cluster` (case insensitive). Run the command below. ```shell nohup sh mqproxy & ``` The command will only launch the `Proxy` component itself. It assumes that `Namesrv` nodes are already running at the address specified `nameSrvAddr`, and broker nodes, registering themselves with `nameSrvAddr`, are running too. ## `Local` Mode * Set configuration field `nameSrvAddr`. * Set configuration field `proxyMode` to `local` (case insensitive). Run the command below. ```shell nohup sh mqproxy & ``` The previous command will launch the `Proxy`, with `Broker` in the same process. It assumes `Namesrv` nodes are running at the address specified by `nameSrvAddr`. ================================================ FILE: example/pom.xml ================================================ rocketmq-all org.apache.rocketmq ${revision} 4.0.0 jar rocketmq-example rocketmq-example ${project.version} ${basedir}/.. ${project.groupId} rocketmq-client ${project.groupId} rocketmq-tools ${project.groupId} rocketmq-remoting ${project.groupId} rocketmq-srvutil ${project.groupId} rocketmq-openmessaging ${project.groupId} rocketmq-auth org.javassist javassist io.jaegertracing jaeger-core io.jaegertracing jaeger-client io.jaegertracing jaeger-thrift commons-cli commons-cli ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/batch/SimpleBatchProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.batch; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; public class SimpleBatchProducer { public static final String PRODUCER_GROUP = "BatchProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "BatchTest"; public static final String TAG = "Tag"; public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); //If you just send messages of no more than 1MiB at a time, it is easy to use batch //Messages of the same batch should have: same topic, same waitStoreMsgOK and no schedule support List messages = new ArrayList<>(); messages.add(new Message(TOPIC, TAG, "OrderID001", "Hello world 0".getBytes(StandardCharsets.UTF_8))); messages.add(new Message(TOPIC, TAG, "OrderID002", "Hello world 1".getBytes(StandardCharsets.UTF_8))); messages.add(new Message(TOPIC, TAG, "OrderID003", "Hello world 2".getBytes(StandardCharsets.UTF_8))); SendResult sendResult = producer.send(messages); System.out.printf("%s", sendResult); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/batch/SplitBatchProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.batch; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; public class SplitBatchProducer { public static final String PRODUCER_GROUP = "BatchProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final int MESSAGE_COUNT = 100 * 1000; public static final String TOPIC = "BatchTest"; public static final String TAG = "Tag"; public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); //large batch List messages = new ArrayList<>(MESSAGE_COUNT); for (int i = 0; i < MESSAGE_COUNT; i++) { messages.add(new Message(TOPIC, TAG, "OrderID" + i, ("Hello world " + i).getBytes(StandardCharsets.UTF_8))); } //split the large batch into small ones: ListSplitter splitter = new ListSplitter(messages); while (splitter.hasNext()) { List listItem = splitter.next(); SendResult sendResult = producer.send(listItem); System.out.printf("%s", sendResult); } } } class ListSplitter implements Iterator> { private static final int SIZE_LIMIT = 1000 * 1000; private final List messages; private int currIndex; public ListSplitter(List messages) { this.messages = messages; } @Override public boolean hasNext() { return currIndex < messages.size(); } @Override public List next() { int nextIndex = currIndex; int totalSize = 0; for (; nextIndex < messages.size(); nextIndex++) { Message message = messages.get(nextIndex); int tmpSize = message.getTopic().length() + message.getBody().length; Map properties = message.getProperties(); for (Map.Entry entry : properties.entrySet()) { tmpSize += entry.getKey().length() + entry.getValue().length(); } //for log overhead tmpSize = tmpSize + 20; if (tmpSize > SIZE_LIMIT) { //it is unexpected that single message exceeds the sizeLimit //here just let it go, otherwise it will block the splitting process if (nextIndex - currIndex == 0) { //if the next sublist has no element, add this one and then break, otherwise just break nextIndex++; } break; } if (tmpSize + totalSize > SIZE_LIMIT) { break; } else { totalSize += tmpSize; } } List subList = messages.subList(currIndex, nextIndex); currIndex = nextIndex; return subList; } @Override public void remove() { throw new UnsupportedOperationException("Not allowed to remove"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/AclClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.remoting.RPCHook; public class AclClient { public static final String ACL_ACCESS_KEY = "rocketmq2"; public static final String ACL_SECRET_KEY = "12345678"; public static RPCHook getAclRPCHook() { return getAclRPCHook(ACL_ACCESS_KEY, ACL_SECRET_KEY); } public static RPCHook getAclRPCHook(String ak, String sk) { return new AclClientRPCHook(new SessionCredentials(ak, sk)); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/BatchProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.compression.CompressionType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.SerializeType; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.srvutil.ServerUtil; public class BatchProducer { private static byte[] msgBody; public static void main(String[] args) throws MQClientException { System.setProperty(RemotingCommand.SERIALIZE_TYPE_PROPERTY, SerializeType.ROCKETMQ.name()); Options options = ServerUtil.buildCommandlineOptions(new Options()); CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkBatchProducer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } final String namesrv = getOptionValue(commandLine, 'n', "127.0.0.1:9876"); final String topic = getOptionValue(commandLine, 't', "BenchmarkTest"); final int threadCount = getOptionValue(commandLine, 'w', 64); final int messageSize = getOptionValue(commandLine, 's', 128); final int batchSize = getOptionValue(commandLine, 'b', 16); final boolean keyEnable = getOptionValue(commandLine, 'k', false); final int propertySize = getOptionValue(commandLine, 'p', 0); final int tagCount = getOptionValue(commandLine, 'l', 0); final boolean msgTraceEnable = getOptionValue(commandLine, 'm', false); final boolean aclEnable = getOptionValue(commandLine, 'a', false); final boolean enableCompress = commandLine.hasOption('c') && Boolean.parseBoolean(commandLine.getOptionValue('c')); final int reportInterval = commandLine.hasOption("ri") ? Integer.parseInt(commandLine.getOptionValue("ri")) : 10000; System.out.printf("topic: %s, threadCount: %d, messageSize: %d, batchSize: %d, keyEnable: %s, propertySize: %d, tagCount: %d, traceEnable: %s, " + "aclEnable: %s%n compressEnable: %s, reportInterval: %d%n", topic, threadCount, messageSize, batchSize, keyEnable, propertySize, tagCount, msgTraceEnable, aclEnable, enableCompress, reportInterval); StringBuilder sb = new StringBuilder(messageSize); for (int i = 0; i < messageSize; i++) { sb.append(RandomStringUtils.randomAlphanumeric(1)); } msgBody = sb.toString().getBytes(StandardCharsets.UTF_8); final StatsBenchmarkBatchProducer statsBenchmark = new StatsBenchmarkBatchProducer(reportInterval); statsBenchmark.start(); RPCHook rpcHook = null; if (aclEnable) { String ak = commandLine.hasOption("ak") ? String.valueOf(commandLine.getOptionValue("ak")) : AclClient.ACL_ACCESS_KEY; String sk = commandLine.hasOption("sk") ? String.valueOf(commandLine.getOptionValue("sk")) : AclClient.ACL_SECRET_KEY; rpcHook = AclClient.getAclRPCHook(ak, sk); } final DefaultMQProducer producer = initInstance(namesrv, msgTraceEnable, rpcHook); if (enableCompress) { String compressType = commandLine.hasOption("ct") ? commandLine.getOptionValue("ct").trim() : "ZLIB"; int compressLevel = commandLine.hasOption("cl") ? Integer.parseInt(commandLine.getOptionValue("cl")) : 5; int compressOverHowMuch = commandLine.hasOption("ch") ? Integer.parseInt(commandLine.getOptionValue("ch")) : 4096; producer.setCompressType(CompressionType.of(compressType)); producer.setCompressLevel(compressLevel); producer.setCompressMsgBodyOverHowmuch(compressOverHowMuch); System.out.printf("compressType: %s compressLevel: %s%n", compressType, compressLevel); } else { producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); } producer.start(); final Logger logger = LoggerFactory.getLogger(BatchProducer.class); final ExecutorService sendThreadPool = Executors.newFixedThreadPool(threadCount); for (int i = 0; i < threadCount; i++) { sendThreadPool.execute(new Runnable() { @Override public void run() { while (true) { List msgs = buildBathMessage(batchSize, topic); if (CollectionUtils.isEmpty(msgs)) { return; } try { long beginTimestamp = System.currentTimeMillis(); long sendSucCount = statsBenchmark.getSendMessageSuccessCount().longValue(); setKeys(keyEnable, msgs, String.valueOf(beginTimestamp / 1000)); setTags(tagCount, msgs, sendSucCount); setProperties(propertySize, msgs); SendResult sendResult = producer.send(msgs); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { statsBenchmark.getSendRequestSuccessCount().increment(); statsBenchmark.getSendMessageSuccessCount().add(msgs.size()); } else { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); } long currentRT = System.currentTimeMillis() - beginTimestamp; statsBenchmark.getSendMessageSuccessTimeTotal().add(currentRT); long prevMaxRT = statsBenchmark.getSendMessageMaxRT().longValue(); while (currentRT > prevMaxRT) { boolean updated = statsBenchmark.getSendMessageMaxRT().compareAndSet(prevMaxRT, currentRT); if (updated) { break; } prevMaxRT = statsBenchmark.getSendMessageMaxRT().get(); } } catch (RemotingException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); logger.error("[BENCHMARK_PRODUCER] Send Exception", e); try { Thread.sleep(3000); } catch (InterruptedException ignored) { } } catch (InterruptedException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); try { Thread.sleep(3000); } catch (InterruptedException e1) { } statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); logger.error("[BENCHMARK_PRODUCER] Send Exception", e); } catch (MQClientException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); logger.error("[BENCHMARK_PRODUCER] Send Exception", e); } catch (MQBrokerException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); logger.error("[BENCHMARK_PRODUCER] Send Exception", e); try { Thread.sleep(3000); } catch (InterruptedException ignored) { } } } } }); } } public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("w", "threadCount", true, "Thread count, Default: 64"); opt.setRequired(false); options.addOption(opt); opt = new Option("s", "messageSize", true, "Message Size, Default: 128"); opt.setRequired(false); options.addOption(opt); opt = new Option("b", "batchSize", true, "Batch Size, Default: 16"); opt.setRequired(false); options.addOption(opt); opt = new Option("k", "keyEnable", true, "Message Key Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("t", "topic", true, "Topic name, Default: BenchmarkTest"); opt.setRequired(false); options.addOption(opt); opt = new Option("l", "tagCount", true, "Tag count, Default: 0"); opt.setRequired(false); options.addOption(opt); opt = new Option("m", "msgTraceEnable", true, "Message Trace Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("a", "aclEnable", true, "Acl Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ak", "accessKey", true, "Acl Access Key, Default: rocketmq2"); opt.setRequired(false); options.addOption(opt); opt = new Option("sk", "secretKey", true, "Acl Secret Key, Default: 123456789"); opt.setRequired(false); options.addOption(opt); opt = new Option("p", "propertySize", true, "Property Size, Default: 0"); opt.setRequired(false); options.addOption(opt); opt = new Option("n", "namesrv", true, "name server, Default: 127.0.0.1:9876"); opt.setRequired(false); options.addOption(opt); opt = new Option("c", "compressEnable", true, "Enable compress msg over 4K, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ct", "compressType", true, "Message compressed type, Default: ZLIB"); opt.setRequired(false); options.addOption(opt); opt = new Option("cl", "compressLevel", true, "Message compressed level, Default: 5"); opt.setRequired(false); options.addOption(opt); opt = new Option("ch", "compressOverHowMuch", true, "Compress message when body over how much(unit Byte), Default: 4096"); opt.setRequired(false); options.addOption(opt); opt = new Option("ri", "reportInterval", true, "The number of ms between reports, Default: 10000"); opt.setRequired(false); options.addOption(opt); return options; } private static String getOptionValue(CommandLine commandLine, char key, String defaultValue) { if (commandLine.hasOption(key)) { return commandLine.getOptionValue(key).trim(); } return defaultValue; } private static int getOptionValue(CommandLine commandLine, char key, int defaultValue) { if (commandLine.hasOption(key)) { return Integer.parseInt(commandLine.getOptionValue(key).trim()); } return defaultValue; } private static boolean getOptionValue(CommandLine commandLine, char key, boolean defaultValue) { if (commandLine.hasOption(key)) { return Boolean.parseBoolean(commandLine.getOptionValue(key).trim()); } return defaultValue; } private static List buildBathMessage(final int batchSize, final String topic) { List batchMessage = new ArrayList<>(batchSize); for (int i = 0; i < batchSize; i++) { Message msg = new Message(topic, msgBody); batchMessage.add(msg); } return batchMessage; } private static void setKeys(boolean keyEnable, List msgs, String keys) { if (!keyEnable) { return; } for (Message msg : msgs) { msg.setKeys(keys); } } private static void setTags(int tagCount, List msgs, long startTagId) { if (tagCount <= 0) { return; } long tagId = startTagId % tagCount; for (Message msg : msgs) { msg.setTags(String.format("tag%d", tagId++)); } } private static void setProperties(int propertySize, List msgs) { if (propertySize <= 0) { return; } for (Message msg : msgs) { if (msg.getProperties() != null) { msg.getProperties().clear(); } int startValue = (new Random(System.currentTimeMillis())).nextInt(100); int size = 0; for (int i = 0; ; i++) { String prop1 = "prop" + i, prop1V = "hello" + startValue; msg.putUserProperty(prop1, prop1V); size += prop1.length() + prop1V.length(); if (size > propertySize) { break; } startValue++; } } } private static DefaultMQProducer initInstance(String namesrv, boolean traceEnable, RPCHook rpcHook) { final DefaultMQProducer producer = new DefaultMQProducer("benchmark_batch_producer", rpcHook, traceEnable, null); producer.setInstanceName(Long.toString(System.currentTimeMillis())); producer.setNamesrvAddr(namesrv); return producer; } } class StatsBenchmarkBatchProducer { private final LongAdder sendRequestSuccessCount = new LongAdder(); private final LongAdder sendRequestFailedCount = new LongAdder(); private final LongAdder sendMessageSuccessTimeTotal = new LongAdder(); private final AtomicLong sendMessageMaxRT = new AtomicLong(0L); private final LongAdder sendMessageSuccessCount = new LongAdder(); private final LongAdder sendMessageFailedCount = new LongAdder(); private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( "BenchmarkTimerThread", Boolean.TRUE)); private final LinkedList snapshotList = new LinkedList<>(); private final int reportInterval; public StatsBenchmarkBatchProducer(int reportInterval) { this.reportInterval = reportInterval; } public Long[] createSnapshot() { Long[] snap = new Long[] { System.currentTimeMillis(), this.sendRequestSuccessCount.longValue(), this.sendRequestFailedCount.longValue(), this.sendMessageSuccessCount.longValue(), this.sendMessageFailedCount.longValue(), this.sendMessageSuccessTimeTotal.longValue(), }; return snap; } public LongAdder getSendRequestSuccessCount() { return sendRequestSuccessCount; } public LongAdder getSendRequestFailedCount() { return sendRequestFailedCount; } public LongAdder getSendMessageSuccessTimeTotal() { return sendMessageSuccessTimeTotal; } public AtomicLong getSendMessageMaxRT() { return sendMessageMaxRT; } public LongAdder getSendMessageSuccessCount() { return sendMessageSuccessCount; } public LongAdder getSendMessageFailedCount() { return sendMessageFailedCount; } public void start() { executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { snapshotList.addLast(createSnapshot()); if (snapshotList.size() > 10) { snapshotList.removeFirst(); } } }, 1000, 1000, TimeUnit.MILLISECONDS); executorService.scheduleAtFixedRate(new Runnable() { private void printStats() { if (snapshotList.size() >= 10) { Long[] begin = snapshotList.getFirst(); Long[] end = snapshotList.getLast(); final long sendTps = (long) (((end[1] - begin[1]) / (double) (end[0] - begin[0])) * 1000L); final long sendMps = (long) (((end[3] - begin[3]) / (double) (end[0] - begin[0])) * 1000L); final double averageRT = (end[5] - begin[5]) / (double) (end[1] - begin[1]); final double averageMsgRT = (end[5] - begin[5]) / (double) (end[3] - begin[3]); System.out.printf("Current Time: %s | Send TPS: %d | Send MPS: %d | Max RT(ms): %d | Average RT(ms): %7.3f | Average Message RT(ms): %7.3f | Send Failed: %d | Send Message Failed: %d%n", UtilAll.timeMillisToHumanString2(System.currentTimeMillis()), sendTps, sendMps, getSendMessageMaxRT().longValue(), averageRT, averageMsgRT, end[2], end[4]); } } @Override public void run() { try { this.printStats(); } catch (Exception e) { e.printStackTrace(); } } }, reportInterval, reportInterval, TimeUnit.MILLISECONDS); } public void shutdown() { executorService.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.TimerTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.SerializeType; import org.apache.rocketmq.srvutil.ServerUtil; public class Consumer { public static void main(String[] args) throws MQClientException, IOException { System.setProperty(RemotingCommand.SERIALIZE_TYPE_PROPERTY, SerializeType.ROCKETMQ.name()); Options options = ServerUtil.buildCommandlineOptions(new Options()); CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkConsumer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } final String topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; final int threadCount = commandLine.hasOption('w') ? Integer.parseInt(commandLine.getOptionValue('w')) : 20; final String groupPrefix = commandLine.hasOption('g') ? commandLine.getOptionValue('g').trim() : "benchmark_consumer"; final String isSuffixEnable = commandLine.hasOption('p') ? commandLine.getOptionValue('p').trim() : "false"; final String filterType = commandLine.hasOption('f') ? commandLine.getOptionValue('f').trim() : null; final String expression = commandLine.hasOption('e') ? commandLine.getOptionValue('e').trim() : null; final double failRate = commandLine.hasOption('r') ? Double.parseDouble(commandLine.getOptionValue('r').trim()) : 0.0; final boolean msgTraceEnable = commandLine.hasOption('m') && Boolean.parseBoolean(commandLine.getOptionValue('m')); final boolean aclEnable = commandLine.hasOption('a') && Boolean.parseBoolean(commandLine.getOptionValue('a')); final boolean clientRebalanceEnable = commandLine.hasOption('c') ? Boolean.parseBoolean(commandLine.getOptionValue('c')) : true; final int reportInterval = commandLine.hasOption("ri") ? Integer.parseInt(commandLine.getOptionValue("ri")) : 10000; String group = groupPrefix; if (Boolean.parseBoolean(isSuffixEnable)) { group = groupPrefix + "_" + (System.currentTimeMillis() % 100); } System.out.printf("topic: %s, threadCount %d, group: %s, suffix: %s, filterType: %s, expression: %s, msgTraceEnable: %s, aclEnable: %s, reportInterval: %d%n", topic, threadCount, group, isSuffixEnable, filterType, expression, msgTraceEnable, aclEnable, reportInterval); final StatsBenchmarkConsumer statsBenchmarkConsumer = new StatsBenchmarkConsumer(); ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("BenchmarkTimerThread-%d").daemon(true).build()); final LinkedList snapshotList = new LinkedList<>(); executorService.scheduleAtFixedRate(new TimerTask() { @Override public void run() { snapshotList.addLast(statsBenchmarkConsumer.createSnapshot()); if (snapshotList.size() > 10) { snapshotList.removeFirst(); } } }, 1000, 1000, TimeUnit.MILLISECONDS); executorService.scheduleAtFixedRate(new TimerTask() { private void printStats() { if (snapshotList.size() >= 10) { Long[] begin = snapshotList.getFirst(); Long[] end = snapshotList.getLast(); final long consumeTps = (long) (((end[1] - begin[1]) / (double) (end[0] - begin[0])) * 1000L); final double averageB2CRT = (end[2] - begin[2]) / (double) (end[1] - begin[1]); final double averageS2CRT = (end[3] - begin[3]) / (double) (end[1] - begin[1]); final long failCount = end[4] - begin[4]; final long b2cMax = statsBenchmarkConsumer.getBorn2ConsumerMaxRT().get(); final long s2cMax = statsBenchmarkConsumer.getStore2ConsumerMaxRT().get(); statsBenchmarkConsumer.getBorn2ConsumerMaxRT().set(0); statsBenchmarkConsumer.getStore2ConsumerMaxRT().set(0); System.out.printf("Current Time: %s | Consume TPS: %d | AVG(B2C) RT(ms): %7.3f | AVG(S2C) RT(ms): %7.3f | MAX(B2C) RT(ms): %d | MAX(S2C) RT(ms): %d | Consume Fail: %d%n", UtilAll.timeMillisToHumanString2(System.currentTimeMillis()), consumeTps, averageB2CRT, averageS2CRT, b2cMax, s2cMax, failCount); } } @Override public void run() { try { this.printStats(); } catch (Exception e) { e.printStackTrace(); } } }, reportInterval, reportInterval, TimeUnit.MILLISECONDS); RPCHook rpcHook = null; if (aclEnable) { String ak = commandLine.hasOption("ak") ? String.valueOf(commandLine.getOptionValue("ak")) : AclClient.ACL_ACCESS_KEY; String sk = commandLine.hasOption("sk") ? String.valueOf(commandLine.getOptionValue("sk")) : AclClient.ACL_SECRET_KEY; rpcHook = AclClient.getAclRPCHook(ak, sk); } DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(group, rpcHook, new AllocateMessageQueueAveragely(), msgTraceEnable, null); if (commandLine.hasOption('n')) { String ns = commandLine.getOptionValue('n'); consumer.setNamesrvAddr(ns); } consumer.setConsumeThreadMin(threadCount); consumer.setConsumeThreadMax(threadCount); consumer.setInstanceName(Long.toString(System.currentTimeMillis())); consumer.setClientRebalance(clientRebalanceEnable); if (filterType == null || expression == null) { consumer.subscribe(topic, "*"); } else { if (ExpressionType.TAG.equals(filterType)) { String expr = MixAll.file2String(expression); System.out.printf("Expression: %s%n", expr); consumer.subscribe(topic, MessageSelector.byTag(expr)); } else if (ExpressionType.SQL92.equals(filterType)) { String expr = MixAll.file2String(expression); System.out.printf("Expression: %s%n", expr); consumer.subscribe(topic, MessageSelector.bySql(expr)); } else { throw new IllegalArgumentException("Not support filter type! " + filterType); } } consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { MessageExt msg = msgs.get(0); long now = System.currentTimeMillis(); statsBenchmarkConsumer.getReceiveMessageTotalCount().increment(); long born2ConsumerRT = now - msg.getBornTimestamp(); statsBenchmarkConsumer.getBorn2ConsumerTotalRT().add(born2ConsumerRT); long store2ConsumerRT = now - msg.getStoreTimestamp(); statsBenchmarkConsumer.getStore2ConsumerTotalRT().add(store2ConsumerRT); compareAndSetMax(statsBenchmarkConsumer.getBorn2ConsumerMaxRT(), born2ConsumerRT); compareAndSetMax(statsBenchmarkConsumer.getStore2ConsumerMaxRT(), store2ConsumerRT); if (ThreadLocalRandom.current().nextDouble() < failRate) { statsBenchmarkConsumer.getFailCount().increment(); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } else { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } }); consumer.start(); System.out.printf("Consumer Started.%n"); } public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("t", "topic", true, "Topic name, Default: BenchmarkTest"); opt.setRequired(false); options.addOption(opt); opt = new Option("w", "threadCount", true, "Thread count, Default: 20"); opt.setRequired(false); options.addOption(opt); opt = new Option("g", "group", true, "Consumer group name, Default: benchmark_consumer"); opt.setRequired(false); options.addOption(opt); opt = new Option("p", "group prefix enable", true, "Is group prefix enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("f", "filterType", true, "TAG, SQL92"); opt.setRequired(false); options.addOption(opt); opt = new Option("e", "expression", true, "filter expression content file path.ie: ./test/expr"); opt.setRequired(false); options.addOption(opt); opt = new Option("r", "fail rate", true, "consumer fail rate, default 0"); opt.setRequired(false); options.addOption(opt); opt = new Option("m", "msgTraceEnable", true, "Message Trace Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("a", "aclEnable", true, "Acl Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ak", "accessKey", true, "Acl access key, Default: 12345678"); opt.setRequired(false); options.addOption(opt); opt = new Option("sk", "secretKey", true, "Acl secret key, Default: rocketmq2"); opt.setRequired(false); options.addOption(opt); opt = new Option("ri", "reportInterval", true, "The number of ms between reports, Default: 10000"); opt.setRequired(false); options.addOption(opt); return options; } public static void compareAndSetMax(final AtomicLong target, final long value) { long prev = target.get(); while (value > prev) { boolean updated = target.compareAndSet(prev, value); if (updated) break; prev = target.get(); } } } class StatsBenchmarkConsumer { private final LongAdder receiveMessageTotalCount = new LongAdder(); private final LongAdder born2ConsumerTotalRT = new LongAdder(); private final LongAdder store2ConsumerTotalRT = new LongAdder(); private final AtomicLong born2ConsumerMaxRT = new AtomicLong(0L); private final AtomicLong store2ConsumerMaxRT = new AtomicLong(0L); private final LongAdder failCount = new LongAdder(); public Long[] createSnapshot() { Long[] snap = new Long[] { System.currentTimeMillis(), this.receiveMessageTotalCount.longValue(), this.born2ConsumerTotalRT.longValue(), this.store2ConsumerTotalRT.longValue(), this.failCount.longValue() }; return snap; } public LongAdder getReceiveMessageTotalCount() { return receiveMessageTotalCount; } public LongAdder getBorn2ConsumerTotalRT() { return born2ConsumerTotalRT; } public LongAdder getStore2ConsumerTotalRT() { return store2ConsumerTotalRT; } public AtomicLong getBorn2ConsumerMaxRT() { return born2ConsumerMaxRT; } public AtomicLong getStore2ConsumerMaxRT() { return store2ConsumerMaxRT; } public LongAdder getFailCount() { return failCount; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark; import java.nio.charset.StandardCharsets; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.LongAdder; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.compression.CompressionType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.SerializeType; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.srvutil.ServerUtil; import java.util.Arrays; import java.util.LinkedList; import java.util.Random; import java.util.TimerTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicLong; public class Producer { private static final Logger log = LoggerFactory.getLogger(Producer.class); private static byte[] msgBody; private static final int MAX_LENGTH_ASYNC_QUEUE = 10000; private static final int SLEEP_FOR_A_WHILE = 100; public static void main(String[] args) throws MQClientException { System.setProperty(RemotingCommand.SERIALIZE_TYPE_PROPERTY, SerializeType.ROCKETMQ.name()); Options options = ServerUtil.buildCommandlineOptions(new Options()); CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkProducer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } final String topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; final int messageSize = commandLine.hasOption('s') ? Integer.parseInt(commandLine.getOptionValue('s')) : 128; final boolean keyEnable = commandLine.hasOption('k') && Boolean.parseBoolean(commandLine.getOptionValue('k')); final int propertySize = commandLine.hasOption('p') ? Integer.parseInt(commandLine.getOptionValue('p')) : 0; final int tagCount = commandLine.hasOption('l') ? Integer.parseInt(commandLine.getOptionValue('l')) : 0; final boolean msgTraceEnable = commandLine.hasOption('m') && Boolean.parseBoolean(commandLine.getOptionValue('m')); final boolean aclEnable = commandLine.hasOption('a') && Boolean.parseBoolean(commandLine.getOptionValue('a')); final long messageNum = commandLine.hasOption('q') ? Long.parseLong(commandLine.getOptionValue('q')) : 0; final boolean delayEnable = commandLine.hasOption('d') && Boolean.parseBoolean(commandLine.getOptionValue('d')); final int delayLevel = commandLine.hasOption('e') ? Integer.parseInt(commandLine.getOptionValue('e')) : 1; final boolean asyncEnable = commandLine.hasOption('y') && Boolean.parseBoolean(commandLine.getOptionValue('y')); final int threadCount = asyncEnable ? 1 : commandLine.hasOption('w') ? Integer.parseInt(commandLine.getOptionValue('w')) : 64; final boolean enableCompress = commandLine.hasOption('c') && Boolean.parseBoolean(commandLine.getOptionValue('c')); final int reportInterval = commandLine.hasOption("ri") ? Integer.parseInt(commandLine.getOptionValue("ri")) : 10000; System.out.printf("topic: %s, threadCount: %d, messageSize: %d, keyEnable: %s, propertySize: %d, tagCount: %d, " + "traceEnable: %s, aclEnable: %s, messageQuantity: %d, delayEnable: %s, delayLevel: %s, " + "asyncEnable: %s%n compressEnable: %s, reportInterval: %d%n", topic, threadCount, messageSize, keyEnable, propertySize, tagCount, msgTraceEnable, aclEnable, messageNum, delayEnable, delayLevel, asyncEnable, enableCompress, reportInterval); StringBuilder sb = new StringBuilder(messageSize); for (int i = 0; i < messageSize; i++) { sb.append(RandomStringUtils.randomAlphanumeric(1)); } msgBody = sb.toString().getBytes(StandardCharsets.UTF_8); final ExecutorService sendThreadPool = Executors.newFixedThreadPool(threadCount); final StatsBenchmarkProducer statsBenchmark = new StatsBenchmarkProducer(); ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("BenchmarkTimerThread-%d").daemon(true).build()); final LinkedList snapshotList = new LinkedList<>(); final long[] msgNums = new long[threadCount]; if (messageNum > 0) { Arrays.fill(msgNums, messageNum / threadCount); long mod = messageNum % threadCount; if (mod > 0) { msgNums[0] += mod; } } executorService.scheduleAtFixedRate(new TimerTask() { @Override public void run() { snapshotList.addLast(statsBenchmark.createSnapshot()); if (snapshotList.size() > 10) { snapshotList.removeFirst(); } } }, 1000, 1000, TimeUnit.MILLISECONDS); executorService.scheduleAtFixedRate(new TimerTask() { private void printStats() { if (snapshotList.size() >= 10) { doPrintStats(snapshotList, statsBenchmark, false); } } @Override public void run() { try { this.printStats(); } catch (Exception e) { e.printStackTrace(); } } }, reportInterval, reportInterval, TimeUnit.MILLISECONDS); RPCHook rpcHook = null; if (aclEnable) { String ak = commandLine.hasOption("ak") ? String.valueOf(commandLine.getOptionValue("ak")) : AclClient.ACL_ACCESS_KEY; String sk = commandLine.hasOption("sk") ? String.valueOf(commandLine.getOptionValue("sk")) : AclClient.ACL_SECRET_KEY; rpcHook = AclClient.getAclRPCHook(ak, sk); } final DefaultMQProducer producer = new DefaultMQProducer("benchmark_producer", rpcHook, msgTraceEnable, null); producer.setInstanceName(Long.toString(System.currentTimeMillis())); if (commandLine.hasOption('n')) { String ns = commandLine.getOptionValue('n'); producer.setNamesrvAddr(ns); } if (enableCompress) { String compressType = commandLine.hasOption("ct") ? commandLine.getOptionValue("ct").trim() : "ZLIB"; int compressLevel = commandLine.hasOption("cl") ? Integer.parseInt(commandLine.getOptionValue("cl")) : 5; int compressOverHowMuch = commandLine.hasOption("ch") ? Integer.parseInt(commandLine.getOptionValue("ch")) : 4096; producer.setCompressType(CompressionType.of(compressType)); producer.setCompressLevel(compressLevel); producer.setCompressMsgBodyOverHowmuch(compressOverHowMuch); System.out.printf("compressType: %s compressLevel: %s%n", compressType, compressLevel); } else { producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); } producer.start(); for (int i = 0; i < threadCount; i++) { final long msgNumLimit = msgNums[i]; if (messageNum > 0 && msgNumLimit == 0) { break; } sendThreadPool.execute(new Runnable() { @Override public void run() { int num = 0; while (true) { try { final Message msg = buildMessage(topic); final long beginTimestamp = System.currentTimeMillis(); if (keyEnable) { msg.setKeys(String.valueOf(beginTimestamp / 1000)); } if (delayEnable) { msg.setDelayTimeLevel(delayLevel); } if (tagCount > 0) { msg.setTags(String.format("tag%d", System.currentTimeMillis() % tagCount)); } if (propertySize > 0) { if (msg.getProperties() != null) { msg.getProperties().clear(); } int i = 0; int startValue = (new Random(System.currentTimeMillis())).nextInt(100); int size = 0; while (true) { String prop1 = "prop" + i, prop1V = "hello" + startValue; String prop2 = "prop" + (i + 1), prop2V = String.valueOf(startValue); msg.putUserProperty(prop1, prop1V); msg.putUserProperty(prop2, prop2V); size += prop1.length() + prop2.length() + prop1V.length() + prop2V.length(); if (size > propertySize) { break; } i += 2; startValue += 2; } } if (asyncEnable) { ThreadPoolExecutor e = (ThreadPoolExecutor) producer.getDefaultMQProducerImpl().getAsyncSenderExecutor(); // Flow control while (e.getQueue().size() > MAX_LENGTH_ASYNC_QUEUE) { Thread.sleep(SLEEP_FOR_A_WHILE); } producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { updateStatsSuccess(statsBenchmark, beginTimestamp); } @Override public void onException(Throwable e) { statsBenchmark.getSendRequestFailedCount().increment(); } }); } else { producer.send(msg); updateStatsSuccess(statsBenchmark, beginTimestamp); } } catch (RemotingException e) { statsBenchmark.getSendRequestFailedCount().increment(); log.error("[BENCHMARK_PRODUCER] Send Exception", e); try { Thread.sleep(3000); } catch (InterruptedException ignored) { } } catch (InterruptedException e) { statsBenchmark.getSendRequestFailedCount().increment(); try { Thread.sleep(3000); } catch (InterruptedException e1) { } } catch (MQClientException e) { statsBenchmark.getSendRequestFailedCount().increment(); log.error("[BENCHMARK_PRODUCER] Send Exception", e); } catch (MQBrokerException e) { statsBenchmark.getReceiveResponseFailedCount().increment(); log.error("[BENCHMARK_PRODUCER] Send Exception", e); try { Thread.sleep(3000); } catch (InterruptedException ignored) { } } if (messageNum > 0 && ++num >= msgNumLimit) { break; } } } }); } try { sendThreadPool.shutdown(); sendThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); executorService.shutdown(); try { executorService.awaitTermination(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { } if (snapshotList.size() > 1) { doPrintStats(snapshotList, statsBenchmark, true); } else { System.out.printf("[Complete] Send Total: %d Send Failed: %d Response Failed: %d%n", statsBenchmark.getSendRequestSuccessCount().longValue() + statsBenchmark.getSendRequestFailedCount().longValue(), statsBenchmark.getSendRequestFailedCount().longValue(), statsBenchmark.getReceiveResponseFailedCount().longValue()); } producer.shutdown(); } catch (InterruptedException e) { log.error("[Exit] Thread Interrupted Exception", e); } } private static void updateStatsSuccess(StatsBenchmarkProducer statsBenchmark, long beginTimestamp) { statsBenchmark.getSendRequestSuccessCount().increment(); statsBenchmark.getReceiveResponseSuccessCount().increment(); final long currentRT = System.currentTimeMillis() - beginTimestamp; statsBenchmark.getSendMessageSuccessTimeTotal().add(currentRT); long prevMaxRT = statsBenchmark.getSendMessageMaxRT().longValue(); while (currentRT > prevMaxRT) { boolean updated = statsBenchmark.getSendMessageMaxRT().compareAndSet(prevMaxRT, currentRT); if (updated) break; prevMaxRT = statsBenchmark.getSendMessageMaxRT().longValue(); } } public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("w", "threadCount", true, "Thread count, Default: 64"); opt.setRequired(false); options.addOption(opt); opt = new Option("s", "messageSize", true, "Message Size, Default: 128"); opt.setRequired(false); options.addOption(opt); opt = new Option("k", "keyEnable", true, "Message Key Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("t", "topic", true, "Topic name, Default: BenchmarkTest"); opt.setRequired(false); options.addOption(opt); opt = new Option("l", "tagCount", true, "Tag count, Default: 0"); opt.setRequired(false); options.addOption(opt); opt = new Option("m", "msgTraceEnable", true, "Message Trace Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("a", "aclEnable", true, "Acl Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ak", "accessKey", true, "Acl access key, Default: 12345678"); opt.setRequired(false); options.addOption(opt); opt = new Option("sk", "secretKey", true, "Acl secret key, Default: rocketmq2"); opt.setRequired(false); options.addOption(opt); opt = new Option("q", "messageQuantity", true, "Send message quantity, Default: 0, running forever"); opt.setRequired(false); options.addOption(opt); opt = new Option("d", "delayEnable", true, "Delay message Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("e", "delayLevel", true, "Delay message level, Default: 1"); opt.setRequired(false); options.addOption(opt); opt = new Option("y", "asyncEnable", true, "Enable async produce, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("c", "compressEnable", true, "Enable compress msg over 4K, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ct", "compressType", true, "Message compressed type, Default: ZLIB"); opt.setRequired(false); options.addOption(opt); opt = new Option("cl", "compressLevel", true, "Message compressed level, Default: 5"); opt.setRequired(false); options.addOption(opt); opt = new Option("ch", "compressOverHowMuch", true, "Compress message when body over how much(unit Byte), Default: 4096"); opt.setRequired(false); options.addOption(opt); opt = new Option("ri", "reportInterval", true, "The number of ms between reports, Default: 10000"); opt.setRequired(false); options.addOption(opt); return options; } private static Message buildMessage(final String topic) { return new Message(topic, msgBody); } private static void doPrintStats(final LinkedList snapshotList, final StatsBenchmarkProducer statsBenchmark, boolean done) { Long[] begin = snapshotList.getFirst(); Long[] end = snapshotList.getLast(); final long sendTps = (long) (((end[3] - begin[3]) / (double) (end[0] - begin[0])) * 1000L); final double averageRT = (end[5] - begin[5]) / (double) (end[3] - begin[3]); if (done) { System.out.printf("[Complete] Send Total: %d | Send TPS: %d | Max RT(ms): %d | Average RT(ms): %7.3f | Send Failed: %d | Response Failed: %d%n", statsBenchmark.getSendRequestSuccessCount().longValue() + statsBenchmark.getSendRequestFailedCount().longValue(), sendTps, statsBenchmark.getSendMessageMaxRT().longValue(), averageRT, end[2], end[4]); } else { System.out.printf("Current Time: %s | Send TPS: %d | Max RT(ms): %d | Average RT(ms): %7.3f | Send Failed: %d | Response Failed: %d%n", UtilAll.timeMillisToHumanString2(System.currentTimeMillis()), sendTps, statsBenchmark.getSendMessageMaxRT().longValue(), averageRT, end[2], end[4]); } } } class StatsBenchmarkProducer { private final LongAdder sendRequestSuccessCount = new LongAdder(); private final LongAdder sendRequestFailedCount = new LongAdder(); private final LongAdder receiveResponseSuccessCount = new LongAdder(); private final LongAdder receiveResponseFailedCount = new LongAdder(); private final LongAdder sendMessageSuccessTimeTotal = new LongAdder(); private final AtomicLong sendMessageMaxRT = new AtomicLong(0L); public Long[] createSnapshot() { Long[] snap = new Long[] { System.currentTimeMillis(), this.sendRequestSuccessCount.longValue(), this.sendRequestFailedCount.longValue(), this.receiveResponseSuccessCount.longValue(), this.receiveResponseFailedCount.longValue(), this.sendMessageSuccessTimeTotal.longValue(), }; return snap; } public LongAdder getSendRequestSuccessCount() { return sendRequestSuccessCount; } public LongAdder getSendRequestFailedCount() { return sendRequestFailedCount; } public LongAdder getReceiveResponseSuccessCount() { return receiveResponseSuccessCount; } public LongAdder getReceiveResponseFailedCount() { return receiveResponseFailedCount; } public LongAdder getSendMessageSuccessTimeTotal() { return sendMessageSuccessTimeTotal; } public AtomicLong getSendMessageMaxRT() { return sendMessageMaxRT; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/TransactionProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.SerializeType; import org.apache.rocketmq.srvutil.ServerUtil; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; public class TransactionProducer { private static final long START_TIME = System.currentTimeMillis(); private static final LongAdder MSG_COUNT = new LongAdder(); //broker max check times should less than this value static final int MAX_CHECK_RESULT_IN_MSG = 20; public static void main(String[] args) throws MQClientException, UnsupportedEncodingException { System.setProperty(RemotingCommand.SERIALIZE_TYPE_PROPERTY, SerializeType.ROCKETMQ.name()); Options options = ServerUtil.buildCommandlineOptions(new Options()); CommandLine commandLine = ServerUtil.parseCmdLine("TransactionProducer", args, buildCommandlineOptions(options), new DefaultParser()); TxSendConfig config = new TxSendConfig(); config.topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; config.threadCount = commandLine.hasOption('w') ? Integer.parseInt(commandLine.getOptionValue('w')) : 32; config.messageSize = commandLine.hasOption('s') ? Integer.parseInt(commandLine.getOptionValue('s')) : 2048; config.sendRollbackRate = commandLine.hasOption("sr") ? Double.parseDouble(commandLine.getOptionValue("sr")) : 0.0; config.sendUnknownRate = commandLine.hasOption("su") ? Double.parseDouble(commandLine.getOptionValue("su")) : 0.0; config.checkRollbackRate = commandLine.hasOption("cr") ? Double.parseDouble(commandLine.getOptionValue("cr")) : 0.0; config.checkUnknownRate = commandLine.hasOption("cu") ? Double.parseDouble(commandLine.getOptionValue("cu")) : 0.0; config.batchId = commandLine.hasOption("b") ? Long.parseLong(commandLine.getOptionValue("b")) : System.currentTimeMillis(); config.sendInterval = commandLine.hasOption("i") ? Integer.parseInt(commandLine.getOptionValue("i")) : 0; config.aclEnable = commandLine.hasOption('a') && Boolean.parseBoolean(commandLine.getOptionValue('a')); config.msgTraceEnable = commandLine.hasOption('m') && Boolean.parseBoolean(commandLine.getOptionValue('m')); config.reportInterval = commandLine.hasOption("ri") ? Integer.parseInt(commandLine.getOptionValue("ri")) : 10000; final ExecutorService sendThreadPool = Executors.newFixedThreadPool(config.threadCount); final StatsBenchmarkTProducer statsBenchmark = new StatsBenchmarkTProducer(); ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("BenchmarkTimerThread-%d").daemon(true).build()); final LinkedList snapshotList = new LinkedList<>(); executorService.scheduleAtFixedRate(new TimerTask() { @Override public void run() { snapshotList.addLast(statsBenchmark.createSnapshot()); while (snapshotList.size() > 10) { snapshotList.removeFirst(); } } }, 1000, 1000, TimeUnit.MILLISECONDS); executorService.scheduleAtFixedRate(new TimerTask() { private void printStats() { if (snapshotList.size() >= 10) { Snapshot begin = snapshotList.getFirst(); Snapshot end = snapshotList.getLast(); final long sendCount = end.sendRequestSuccessCount - begin.sendRequestSuccessCount; final long sendTps = (sendCount * 1000L) / (end.endTime - begin.endTime); final double averageRT = (end.sendMessageTimeTotal - begin.sendMessageTimeTotal) / (double) (end.sendRequestSuccessCount - begin.sendRequestSuccessCount); final long failCount = end.sendRequestFailedCount - begin.sendRequestFailedCount; final long checkCount = end.checkCount - begin.checkCount; final long unexpectedCheck = end.unexpectedCheckCount - begin.unexpectedCheckCount; final long dupCheck = end.duplicatedCheck - begin.duplicatedCheck; System.out.printf( "Current Time: %s | Send TPS: %5d | Max RT(ms): %5d | AVG RT(ms): %3.1f | Send Failed: %d | Check: %d | UnexpectedCheck: %d | DuplicatedCheck: %d%n", UtilAll.timeMillisToHumanString2(System.currentTimeMillis()), sendTps, statsBenchmark.getSendMessageMaxRT().get(), averageRT, failCount, checkCount, unexpectedCheck, dupCheck); statsBenchmark.getSendMessageMaxRT().set(0); } } @Override public void run() { try { this.printStats(); } catch (Exception e) { e.printStackTrace(); } } }, config.reportInterval, config.reportInterval, TimeUnit.MILLISECONDS); RPCHook rpcHook = null; if (config.aclEnable) { String ak = commandLine.hasOption("ak") ? String.valueOf(commandLine.getOptionValue("ak")) : AclClient.ACL_ACCESS_KEY; String sk = commandLine.hasOption("sk") ? String.valueOf(commandLine.getOptionValue("sk")) : AclClient.ACL_SECRET_KEY; rpcHook = AclClient.getAclRPCHook(ak, sk); } final TransactionListener transactionCheckListener = new TransactionListenerImpl(statsBenchmark, config); final TransactionMQProducer producer = new TransactionMQProducer( "benchmark_transaction_producer", rpcHook, config.msgTraceEnable, null); producer.setInstanceName(Long.toString(System.currentTimeMillis())); producer.setTransactionListener(transactionCheckListener); producer.setDefaultTopicQueueNums(1000); if (commandLine.hasOption('n')) { String ns = commandLine.getOptionValue('n'); producer.setNamesrvAddr(ns); } producer.start(); for (int i = 0; i < config.threadCount; i++) { sendThreadPool.execute(new Runnable() { @Override public void run() { while (true) { boolean success = false; final long beginTimestamp = System.currentTimeMillis(); try { SendResult sendResult = producer.sendMessageInTransaction(buildMessage(config), null); success = sendResult != null && sendResult.getSendStatus() == SendStatus.SEND_OK; } catch (Throwable e) { success = false; } finally { final long currentRT = System.currentTimeMillis() - beginTimestamp; statsBenchmark.getSendMessageTimeTotal().add(currentRT); long prevMaxRT = statsBenchmark.getSendMessageMaxRT().get(); while (currentRT > prevMaxRT) { boolean updated = statsBenchmark.getSendMessageMaxRT() .compareAndSet(prevMaxRT, currentRT); if (updated) break; prevMaxRT = statsBenchmark.getSendMessageMaxRT().get(); } if (success) { statsBenchmark.getSendRequestSuccessCount().increment(); } else { statsBenchmark.getSendRequestFailedCount().increment(); } if (config.sendInterval > 0) { try { Thread.sleep(config.sendInterval); } catch (InterruptedException e) { } } } } } }); } } private static Message buildMessage(TxSendConfig config) { byte[] bs = new byte[config.messageSize]; ThreadLocalRandom r = ThreadLocalRandom.current(); r.nextBytes(bs); ByteBuffer buf = ByteBuffer.wrap(bs); buf.putLong(config.batchId); long sendMachineId = START_TIME << 32; long count = MSG_COUNT.longValue(); long msgId = sendMachineId | count; MSG_COUNT.increment(); buf.putLong(msgId); // save send tx result in message if (r.nextDouble() < config.sendRollbackRate) { buf.put((byte) LocalTransactionState.ROLLBACK_MESSAGE.ordinal()); } else if (r.nextDouble() < config.sendUnknownRate) { buf.put((byte) LocalTransactionState.UNKNOW.ordinal()); } else { buf.put((byte) LocalTransactionState.COMMIT_MESSAGE.ordinal()); } // save check tx result in message for (int i = 0; i < MAX_CHECK_RESULT_IN_MSG; i++) { if (r.nextDouble() < config.checkRollbackRate) { buf.put((byte) LocalTransactionState.ROLLBACK_MESSAGE.ordinal()); } else if (r.nextDouble() < config.checkUnknownRate) { buf.put((byte) LocalTransactionState.UNKNOW.ordinal()); } else { buf.put((byte) LocalTransactionState.COMMIT_MESSAGE.ordinal()); } } Message msg = new Message(); msg.setTopic(config.topic); msg.setBody(bs); return msg; } public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("w", "threadCount", true, "Thread count, Default: 32"); opt.setRequired(false); options.addOption(opt); opt = new Option("s", "messageSize", true, "Message Size, Default: 2048"); opt.setRequired(false); options.addOption(opt); opt = new Option("t", "topic", true, "Topic name, Default: BenchmarkTest"); opt.setRequired(false); options.addOption(opt); opt = new Option("sr", "send rollback rate", true, "Send rollback rate, Default: 0.0"); opt.setRequired(false); options.addOption(opt); opt = new Option("su", "send unknown rate", true, "Send unknown rate, Default: 0.0"); opt.setRequired(false); options.addOption(opt); opt = new Option("cr", "check rollback rate", true, "Check rollback rate, Default: 0.0"); opt.setRequired(false); options.addOption(opt); opt = new Option("cu", "check unknown rate", true, "Check unknown rate, Default: 0.0"); opt.setRequired(false); options.addOption(opt); opt = new Option("b", "test batch id", true, "test batch id, Default: System.currentMillis()"); opt.setRequired(false); options.addOption(opt); opt = new Option("i", "send interval", true, "sleep interval in millis between messages, Default: 0"); opt.setRequired(false); options.addOption(opt); opt = new Option("a", "aclEnable", true, "Acl Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ak", "accessKey", true, "Acl access key, Default: 12345678"); opt.setRequired(false); options.addOption(opt); opt = new Option("sk", "secretKey", true, "Acl secret key, Default: rocketmq2"); opt.setRequired(false); options.addOption(opt); opt = new Option("m", "msgTraceEnable", true, "Message Trace Enable, Default: false"); opt.setRequired(false); options.addOption(opt); opt = new Option("ri", "reportInterval", true, "The number of ms between reports, Default: 10000"); opt.setRequired(false); options.addOption(opt); return options; } } class TransactionListenerImpl implements TransactionListener { private StatsBenchmarkTProducer statBenchmark; private TxSendConfig sendConfig; private final LRUMap cache = new LRUMap<>(200000); private class MsgMeta { long batchId; long msgId; LocalTransactionState sendResult; List checkResult; } public TransactionListenerImpl(StatsBenchmarkTProducer statsBenchmark, TxSendConfig sendConfig) { this.statBenchmark = statsBenchmark; this.sendConfig = sendConfig; } @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { return parseFromMsg(msg).sendResult; } private MsgMeta parseFromMsg(Message msg) { byte[] bs = msg.getBody(); ByteBuffer buf = ByteBuffer.wrap(bs); MsgMeta msgMeta = new MsgMeta(); msgMeta.batchId = buf.getLong(); msgMeta.msgId = buf.getLong(); msgMeta.sendResult = LocalTransactionState.values()[buf.get()]; msgMeta.checkResult = new ArrayList<>(); for (int i = 0; i < TransactionProducer.MAX_CHECK_RESULT_IN_MSG; i++) { msgMeta.checkResult.add(LocalTransactionState.values()[buf.get()]); } return msgMeta; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { MsgMeta msgMeta = parseFromMsg(msg); if (msgMeta.batchId != sendConfig.batchId) { // message not generated in this test return LocalTransactionState.ROLLBACK_MESSAGE; } statBenchmark.getCheckCount().increment(); int times = 0; try { String checkTimes = msg.getUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES); times = Integer.parseInt(checkTimes); } catch (Exception e) { return LocalTransactionState.ROLLBACK_MESSAGE; } times = times <= 0 ? 1 : times; boolean dup; synchronized (cache) { Integer oldCheckLog = cache.get(msgMeta.msgId); Integer newCheckLog; if (oldCheckLog == null) { newCheckLog = 1 << (times - 1); } else { newCheckLog = oldCheckLog | (1 << (times - 1)); } dup = newCheckLog.equals(oldCheckLog); } if (dup) { statBenchmark.getDuplicatedCheckCount().increment(); } if (msgMeta.sendResult != LocalTransactionState.UNKNOW) { System.out.printf("%s unexpected check: msgId=%s,txId=%s,checkTimes=%s,sendResult=%s\n", new SimpleDateFormat("HH:mm:ss,SSS").format(new Date()), msg.getMsgId(), msg.getTransactionId(), msg.getUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES), msgMeta.sendResult.toString()); statBenchmark.getUnexpectedCheckCount().increment(); return msgMeta.sendResult; } for (int i = 0; i < times - 1; i++) { LocalTransactionState s = msgMeta.checkResult.get(i); if (s != LocalTransactionState.UNKNOW) { System.out.printf("%s unexpected check: msgId=%s,txId=%s,checkTimes=%s,sendResult,lastCheckResult=%s\n", new SimpleDateFormat("HH:mm:ss,SSS").format(new Date()), msg.getMsgId(), msg.getTransactionId(), msg.getUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES), s); statBenchmark.getUnexpectedCheckCount().increment(); return s; } } return msgMeta.checkResult.get(times - 1); } } class Snapshot { long endTime; long sendRequestSuccessCount; long sendRequestFailedCount; long sendMessageTimeTotal; long sendMessageMaxRT; long checkCount; long unexpectedCheckCount; long duplicatedCheck; } class StatsBenchmarkTProducer { private final LongAdder sendRequestSuccessCount = new LongAdder(); private final LongAdder sendRequestFailedCount = new LongAdder(); private final LongAdder sendMessageTimeTotal = new LongAdder(); private final AtomicLong sendMessageMaxRT = new AtomicLong(0L); private final LongAdder checkCount = new LongAdder(); private final LongAdder unexpectedCheckCount = new LongAdder(); private final LongAdder duplicatedCheckCount = new LongAdder(); public Snapshot createSnapshot() { Snapshot s = new Snapshot(); s.endTime = System.currentTimeMillis(); s.sendRequestSuccessCount = sendRequestSuccessCount.longValue(); s.sendRequestFailedCount = sendRequestFailedCount.longValue(); s.sendMessageTimeTotal = sendMessageTimeTotal.longValue(); s.sendMessageMaxRT = sendMessageMaxRT.get(); s.checkCount = checkCount.longValue(); s.unexpectedCheckCount = unexpectedCheckCount.longValue(); s.duplicatedCheck = duplicatedCheckCount.longValue(); return s; } public LongAdder getSendRequestSuccessCount() { return sendRequestSuccessCount; } public LongAdder getSendRequestFailedCount() { return sendRequestFailedCount; } public LongAdder getSendMessageTimeTotal() { return sendMessageTimeTotal; } public AtomicLong getSendMessageMaxRT() { return sendMessageMaxRT; } public LongAdder getCheckCount() { return checkCount; } public LongAdder getUnexpectedCheckCount() { return unexpectedCheckCount; } public LongAdder getDuplicatedCheckCount() { return duplicatedCheckCount; } } class TxSendConfig { String topic; int threadCount; int messageSize; double sendRollbackRate; double sendUnknownRate; double checkRollbackRate; double checkUnknownRate; long batchId; int sendInterval; boolean aclEnable; boolean msgTraceEnable; int reportInterval; } class LRUMap extends LinkedHashMap { private int maxSize; public LRUMap(int maxSize) { this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxSize; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark.timer; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.srvutil.ServerUtil; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.TimerTask; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public class TimerConsumer { private final String topic; private final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("ConsumerScheduleThread_")); private final StatsBenchmarkConsumer statsBenchmark = new StatsBenchmarkConsumer(); private final LinkedList snapshotList = new LinkedList<>(); private final DefaultMQPushConsumer consumer; public TimerConsumer(String[] args) { Options options = ServerUtil.buildCommandlineOptions(new Options()); final CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkTimerConsumer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } final String namesrvAddr = commandLine.hasOption('n') ? commandLine.getOptionValue('t').trim() : "localhost:9876"; topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; System.out.printf("namesrvAddr: %s, topic: %s%n", namesrvAddr, topic); consumer = new DefaultMQPushConsumer("benchmark_consumer"); consumer.setInstanceName(Long.toString(System.currentTimeMillis())); consumer.setNamesrvAddr(namesrvAddr); } public void startScheduleTask() { scheduledExecutor.scheduleAtFixedRate(new TimerTask() { @Override public void run() { snapshotList.addLast(statsBenchmark.createSnapshot()); if (snapshotList.size() > 10) { snapshotList.removeFirst(); } } }, 1000, 1000, TimeUnit.MILLISECONDS); scheduledExecutor.scheduleAtFixedRate(new TimerTask() { private void printStats() { if (snapshotList.size() >= 10) { Long[] begin = snapshotList.getFirst(); Long[] end = snapshotList.getLast(); final long consumeTps = (long) (((end[1] - begin[1]) / (double) (end[0] - begin[0])) * 1000L); final double avgDelayedDuration = (end[2] - begin[2]) / (double) (end[1] - begin[1]); List delayedDurationList = new ArrayList<>(TimerConsumer.this.statsBenchmark.getDelayedDurationMsSet()); if (delayedDurationList.isEmpty()) { System.out.printf("Consume TPS: %d, Avg delayedDuration: %7.3f, Max delayedDuration: 0, %n", consumeTps, avgDelayedDuration); } else { long delayedDuration25 = delayedDurationList.get((int) (delayedDurationList.size() * 0.25)); long delayedDuration50 = delayedDurationList.get((int) (delayedDurationList.size() * 0.5)); long delayedDuration80 = delayedDurationList.get((int) (delayedDurationList.size() * 0.8)); long delayedDuration90 = delayedDurationList.get((int) (delayedDurationList.size() * 0.9)); long delayedDuration99 = delayedDurationList.get((int) (delayedDurationList.size() * 0.99)); long delayedDuration999 = delayedDurationList.get((int) (delayedDurationList.size() * 0.999)); System.out.printf("Consume TPS: %d, Avg delayedDuration: %7.3f, Max delayedDuration: %d, " + "delayDuration %%25: %d, %%50: %d; %%80: %d; %%90: %d; %%99: %d; %%99.9: %d%n", consumeTps, avgDelayedDuration, delayedDurationList.get(delayedDurationList.size() - 1), delayedDuration25, delayedDuration50, delayedDuration80, delayedDuration90, delayedDuration99, delayedDuration999); } } } @Override public void run() { try { this.printStats(); } catch (Exception e) { e.printStackTrace(); } } }, 10000, 10000, TimeUnit.MILLISECONDS); } public void start() throws MQClientException { consumer.subscribe(topic, "*"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { MessageExt msg = msgs.get(0); long now = System.currentTimeMillis(); statsBenchmark.getReceiveMessageTotalCount().incrementAndGet(); long deliverTimeMs = Long.parseLong(msg.getProperty("MY_RECORD_TIMER_DELIVER_MS")); long delayedDuration = now - deliverTimeMs; statsBenchmark.getDelayedDurationMsSet().add(delayedDuration); statsBenchmark.getDelayedDurationMsTotal().addAndGet(delayedDuration); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Start receiving messages%n"); } private Options buildCommandlineOptions(Options options) { Option opt = new Option("n", "namesrvAddr", true, "Nameserver address, default: localhost:9876"); opt.setRequired(false); options.addOption(opt); opt = new Option("t", "topic", true, "Send messages to which topic, default: BenchmarkTest"); opt.setRequired(false); options.addOption(opt); return options; } public static void main(String[] args) throws MQClientException { TimerConsumer timerConsumer = new TimerConsumer(args); timerConsumer.startScheduleTask(); timerConsumer.start(); } public static class StatsBenchmarkConsumer { private final AtomicLong receiveMessageTotalCount = new AtomicLong(0L); private final AtomicLong delayedDurationMsTotal = new AtomicLong(0L); private final ConcurrentSkipListSet delayedDurationMsSet = new ConcurrentSkipListSet<>(); public Long[] createSnapshot() { return new Long[]{ System.currentTimeMillis(), this.receiveMessageTotalCount.get(), this.delayedDurationMsTotal.get(), }; } public AtomicLong getReceiveMessageTotalCount() { return receiveMessageTotalCount; } public AtomicLong getDelayedDurationMsTotal() { return delayedDurationMsTotal; } public ConcurrentSkipListSet getDelayedDurationMsSet() { return delayedDurationMsSet; } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.benchmark.timer; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.srvutil.ServerUtil; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public class TimerProducer { private static final Logger log = LoggerFactory.getLogger(TimerProducer.class); private final String topic; private final int threadCount; private final int messageSize; private final int precisionMs; private final int slotsTotal; private final int msgsTotalPerSlotThread; private final int slotDis; private final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("ProducerScheduleThread_")); private final ExecutorService sendThreadPool; private final StatsBenchmarkProducer statsBenchmark = new StatsBenchmarkProducer(); private final LinkedList snapshotList = new LinkedList<>(); private final DefaultMQProducer producer; public TimerProducer(String[] args) { Options options = ServerUtil.buildCommandlineOptions(new Options()); final CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkTimerProducer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } final String namesrvAddr = commandLine.hasOption('n') ? commandLine.getOptionValue('t').trim() : "localhost:9876"; topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; threadCount = commandLine.hasOption("tc") ? Integer.parseInt(commandLine.getOptionValue("tc")) : 16; messageSize = commandLine.hasOption("ms") ? Integer.parseInt(commandLine.getOptionValue("ms")) : 1024; precisionMs = commandLine.hasOption('p') ? Integer.parseInt(commandLine.getOptionValue("p")) : 1000; slotsTotal = commandLine.hasOption("st") ? Integer.parseInt(commandLine.getOptionValue("st")) : 100; msgsTotalPerSlotThread = commandLine.hasOption("mt") ? Integer.parseInt(commandLine.getOptionValue("mt")) : 5000; slotDis = commandLine.hasOption("sd") ? Integer.parseInt(commandLine.getOptionValue("sd")) : 1000; System.out.printf("namesrvAddr: %s, topic: %s, threadCount: %d, messageSize: %d, precisionMs: %d, slotsTotal: %d, msgsTotalPerSlotThread: %d, slotDis: %d%n", namesrvAddr, topic, threadCount, messageSize, precisionMs, slotsTotal, msgsTotalPerSlotThread, slotDis); sendThreadPool = new ThreadPoolExecutor( threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryImpl("ProducerSendMessageThread_")); producer = new DefaultMQProducer("benchmark_producer"); producer.setInstanceName(Long.toString(System.currentTimeMillis())); producer.setNamesrvAddr(namesrvAddr); producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); } public void startScheduleTask() { scheduledExecutor.scheduleAtFixedRate(new TimerTask() { @Override public void run() { snapshotList.addLast(statsBenchmark.createSnapshot()); if (snapshotList.size() > 10) { snapshotList.removeFirst(); } } }, 1000, 1000, TimeUnit.MILLISECONDS); scheduledExecutor.scheduleAtFixedRate(new TimerTask() { private void printStats() { if (snapshotList.size() >= 10) { Long[] begin = snapshotList.getFirst(); Long[] end = snapshotList.getLast(); final long sendTps = (long) (((end[3] - begin[3]) / (double) (end[0] - begin[0])) * 1000L); final double averageRT = (end[5] - begin[5]) / (double) (end[3] - begin[3]); System.out.printf("Send TPS: %d, Max RT: %d, Average RT: %7.3f, Send Failed: %d, Response Failed: %d%n", sendTps, statsBenchmark.getSendMessageMaxRT().get(), averageRT, end[2], end[4]); } } @Override public void run() { try { this.printStats(); } catch (Exception e) { e.printStackTrace(); } } }, 10000, 10000, TimeUnit.MILLISECONDS); } public void start() throws MQClientException { producer.start(); System.out.printf("Start sending messages%n"); List delayList = new ArrayList<>(); final long startDelayTime = System.currentTimeMillis() / precisionMs * precisionMs + 2 * 60 * 1000 + 10; for (int slotCnt = 0; slotCnt < slotsTotal; slotCnt++) { for (int msgCnt = 0; msgCnt < msgsTotalPerSlotThread; msgCnt++) { long delayTime = startDelayTime + slotCnt * slotDis; delayList.add(delayTime); } } Collections.shuffle(delayList); // DelayTime is from 2 minutes later. for (int i = 0; i < threadCount; i++) { sendThreadPool.execute(new Runnable() { @Override public void run() { for (int slotCnt = 0; slotCnt < slotsTotal; slotCnt++) { for (int msgCnt = 0; msgCnt < msgsTotalPerSlotThread; msgCnt++) { final long beginTimestamp = System.currentTimeMillis(); long delayTime = delayList.get(slotCnt * msgsTotalPerSlotThread + msgCnt); final Message msg; try { msg = buildMessage(messageSize, topic); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return; } msg.putUserProperty("MY_RECORD_TIMER_DELIVER_MS", String.valueOf(delayTime)); msg.getProperties().put(MessageConst.PROPERTY_TIMER_DELIVER_MS, String.valueOf(delayTime)); try { producer.send(msg); statsBenchmark.getSendRequestSuccessCount().incrementAndGet(); statsBenchmark.getReceiveResponseSuccessCount().incrementAndGet(); final long currentRT = System.currentTimeMillis() - beginTimestamp; statsBenchmark.getSendMessageSuccessTimeTotal().addAndGet(currentRT); long prevMaxRT = statsBenchmark.getSendMessageMaxRT().get(); while (currentRT > prevMaxRT) { if (statsBenchmark.getSendMessageMaxRT().compareAndSet(prevMaxRT, currentRT)) { break; } prevMaxRT = statsBenchmark.getSendMessageMaxRT().get(); } } catch (RemotingException e) { statsBenchmark.getSendRequestFailedCount().incrementAndGet(); log.error("[BENCHMARK_PRODUCER] Send Exception", e); sleep(3000); } catch (InterruptedException e) { statsBenchmark.getSendRequestFailedCount().incrementAndGet(); sleep(3000); } catch (MQClientException e) { statsBenchmark.getSendRequestFailedCount().incrementAndGet(); log.error("[BENCHMARK_PRODUCER] Send Exception", e); } catch (MQBrokerException e) { statsBenchmark.getReceiveResponseFailedCount().incrementAndGet(); log.error("[BENCHMARK_PRODUCER] Send Exception", e); sleep(3000); } } } } }); } } private Options buildCommandlineOptions(Options options) { Option opt = new Option("n", "namesrvAddr", true, "Nameserver address, default: localhost:9876"); opt.setRequired(false); options.addOption(opt); opt = new Option("t", "topic", true, "Send messages to which topic, default: BenchmarkTest"); opt.setRequired(false); options.addOption(opt); opt = new Option("tc", "threadCount", true, "Thread count, default: 64"); opt.setRequired(false); options.addOption(opt); opt = new Option("ms", "messageSize", true, "Message Size, default: 128"); opt.setRequired(false); options.addOption(opt); opt = new Option("p", "precisionMs", true, "Precision (ms) for TimerMessage, default: 1000"); opt.setRequired(false); options.addOption(opt); opt = new Option("st", "slotsTotal", true, "Send messages to how many slots, default: 100"); opt.setRequired(false); options.addOption(opt); opt = new Option("mt", "msgsTotalPerSlotThread", true, "Messages total for each slot and each thread, default: 100"); opt.setRequired(false); options.addOption(opt); opt = new Option("sd", "slotDis", true, "Time distance between two slots, default: 1000"); opt.setRequired(false); options.addOption(opt); return options; } private Message buildMessage(final int messageSize, final String topic) throws UnsupportedEncodingException { Message msg = new Message(); msg.setTopic(topic); String body = StringUtils.repeat('a', messageSize); msg.setBody(body.getBytes(RemotingHelper.DEFAULT_CHARSET)); return msg; } private void sleep(long timeMs) { try { Thread.sleep(timeMs); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws MQClientException { TimerProducer timerProducer = new TimerProducer(args); timerProducer.startScheduleTask(); timerProducer.start(); } public static class StatsBenchmarkProducer { private final AtomicLong sendRequestSuccessCount = new AtomicLong(0L); private final AtomicLong sendRequestFailedCount = new AtomicLong(0L); private final AtomicLong receiveResponseSuccessCount = new AtomicLong(0L); private final AtomicLong receiveResponseFailedCount = new AtomicLong(0L); private final AtomicLong sendMessageSuccessTimeTotal = new AtomicLong(0L); private final AtomicLong sendMessageMaxRT = new AtomicLong(0L); public Long[] createSnapshot() { return new Long[]{ System.currentTimeMillis(), this.sendRequestSuccessCount.get(), this.sendRequestFailedCount.get(), this.receiveResponseSuccessCount.get(), this.receiveResponseFailedCount.get(), this.sendMessageSuccessTimeTotal.get(), }; } public AtomicLong getSendRequestSuccessCount() { return sendRequestSuccessCount; } public AtomicLong getSendRequestFailedCount() { return sendRequestFailedCount; } public AtomicLong getReceiveResponseSuccessCount() { return receiveResponseSuccessCount; } public AtomicLong getReceiveResponseFailedCount() { return receiveResponseFailedCount; } public AtomicLong getSendMessageSuccessTimeTotal() { return sendMessageSuccessTimeTotal; } public AtomicLong getSendMessageMaxRT() { return sendMessageMaxRT; } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/broadcast/PushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.broadcast; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class PushConsumer { public static final String CONSUMER_GROUP = "please_rename_unique_group_name_1"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static final String SUB_EXPRESSION = "TagA || TagC || TagD"; public static void main(String[] args) throws InterruptedException, MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.setMessageModel(MessageModel.BROADCASTING); consumer.subscribe(TOPIC, SUB_EXPRESSION); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); System.out.printf("Broadcast Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/filter/SqlFilterConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.filter; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; public class SqlFilterConsumer { public static void main(String[] args) throws Exception { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); // Don't forget to set enablePropertyFilter=true in broker consumer.subscribe("SqlFilterTest", MessageSelector.bySql("(TAGS is not null and TAGS in ('TagA', 'TagB'))" + "and (a is not null and a between 0 and 3)")); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/filter/SqlFilterProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.filter; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class SqlFilterProducer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC"}; for (int i = 0; i < 10; i++) { Message msg = new Message("SqlFilterTest", tags[i % tags.length], ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) ); msg.putUserProperty("a", String.valueOf(i)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/filter/TagFilterConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.filter; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; public class TagFilterConsumer { public static void main(String[] args) throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); consumer.subscribe("TagFilterTest", "TagA || TagC"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/filter/TagFilterProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.filter; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class TagFilterProducer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC"}; for (int i = 0; i < 60; i++) { Message msg = new Message("TagFilterTest", tags[i % tags.length], "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/lmq/LMQProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.lmq; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.remoting.common.RemotingHelper; public class LMQProducer { public static final String PRODUCER_GROUP = "ProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicLMQParent"; public static final String TAG = "TagA"; public static final String LMQ_TOPIC_1 = MixAll.LMQ_PREFIX + "123"; public static final String LMQ_TOPIC_2 = MixAll.LMQ_PREFIX + "456"; public static void main(String[] args) throws MQClientException, InterruptedException { DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); for (int i = 0; i < 128; i++) { try { Message msg = new Message(TOPIC, TAG, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); msg.setKeys("Key" + i); msg.putUserProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH /* "INNER_MULTI_DISPATCH" */, String.join(MixAll.LMQ_DISPATCH_SEPARATOR, LMQ_TOPIC_1, LMQ_TOPIC_2) /* "%LMQ%123,%LMQ%456" */); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); } } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/lmq/LMQPullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.lmq; import java.util.Arrays; import java.util.HashSet; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; @SuppressWarnings("deprecation") public class LMQPullConsumer { public static final String BROKER_NAME = "broker-a"; public static final String CONSUMER_GROUP = "CID_LMQ_PULL_1"; public static final String TOPIC = "TopicLMQParent"; public static final String LMQ_TOPIC = MixAll.LMQ_PREFIX + "123"; public static final String NAMESRV_ADDR = "127.0.0.1:9876"; public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException { DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(CONSUMER_GROUP); consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.setRegisterTopics(new HashSet<>(Arrays.asList(TOPIC))); consumer.start(); // use parent topic to fill up broker addr table consumer.getDefaultMQPullConsumerImpl().getRebalanceImpl().getmQClientFactory() .updateTopicRouteInfoFromNameServer(TOPIC); final MessageQueue lmq = new MessageQueue(LMQ_TOPIC, BROKER_NAME, (int) MixAll.LMQ_QUEUE_ID); long offset = consumer.minOffset(lmq); consumer.pullBlockIfNotFound(lmq, "*", offset, 32, new PullCallback() { @Override public void onSuccess(PullResult pullResult) { List list = pullResult.getMsgFoundList(); if (list == null || list.isEmpty()) { return; } for (MessageExt msg : list) { System.out.printf("%s Pull New Messages: %s %n", Thread.currentThread().getName(), msg); } } @Override public void onException(Throwable e) { } }); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/lmq/LMQPushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.lmq; import com.google.common.collect.Lists; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class LMQPushConsumer { public static final String CLUSTER_NAME = "DefaultCluster"; public static final String BROKER_NAME = "broker-a"; public static final String TOPIC = "TopicLMQParent"; public static final String LMQ_TOPIC = MixAll.LMQ_PREFIX + "123"; public static final String CONSUMER_GROUP = "CID_LMQ_1"; public static final String NAMESRV_ADDR = "127.0.0.1:9876"; public static final HashMap BROKER_ADDR_MAP = new HashMap() { { put(MixAll.MASTER_ID, "127.0.0.1:10911"); } }; public static void main(String[] args) throws InterruptedException, MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.subscribe(LMQ_TOPIC, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); // use parent topic to fill up broker addr table consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(TOPIC); final TopicRouteData topicRouteData = new TopicRouteData(); final BrokerData brokerData = new BrokerData(); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME); brokerData.setBrokerAddrs(BROKER_ADDR_MAP); topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData)); // compensate LMQ topic route for MQClientInstance#findBrokerAddrByTopic consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getTopicRouteTable().put(LMQ_TOPIC, topicRouteData); // compensate for RebalanceImpl#topicSubscribeInfoTable consumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(LMQ_TOPIC, new HashSet<>(Arrays.asList(new MessageQueue(LMQ_TOPIC, BROKER_NAME, (int) MixAll.LMQ_QUEUE_ID)))); // re-balance immediately to start pulling messages consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().doRebalance(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/lmq/LMQPushPopConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.lmq; import com.google.common.collect.Lists; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; public class LMQPushPopConsumer { public static final String CLUSTER_NAME = "DefaultCluster"; public static final String BROKER_NAME = "broker-a"; public static final String TOPIC = "TopicLMQParent"; public static final String LMQ_TOPIC = MixAll.LMQ_PREFIX + "456"; public static final String NAMESRV_ADDR = "127.0.0.1:9876"; public static final String CONSUMER_GROUP = "CID_LMQ_POP_1"; public static final HashMap BROKER_ADDR_MAP = new HashMap() { { put(MixAll.MASTER_ID, "127.0.0.1:10911"); } }; public static void main(String[] args) throws Exception { switchPop(); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.subscribe(LMQ_TOPIC, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // use server side rebalance consumer.setClientRebalance(false); consumer.start(); // use parent topic to fill up broker addr table consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(TOPIC); final TopicRouteData topicRouteData = new TopicRouteData(); final BrokerData brokerData = new BrokerData(); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME); brokerData.setBrokerAddrs(BROKER_ADDR_MAP); topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData)); // compensate LMQ topic route for MQClientInstance#findBrokerAddrByTopic consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getTopicRouteTable().put(LMQ_TOPIC, topicRouteData); // re-balance immediately to start pulling messages consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().doRebalance(); System.out.printf("Consumer Started.%n"); } private static void switchPop() throws Exception { DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setNamesrvAddr(NAMESRV_ADDR); mqAdminExt.start(); List brokerDatas = mqAdminExt.examineTopicRouteInfo(TOPIC).getBrokerDatas(); for (BrokerData brokerData : brokerDatas) { Set brokerAddrs = new HashSet<>(brokerData.getBrokerAddrs().values()); for (String brokerAddr : brokerAddrs) { mqAdminExt.setMessageRequestMode(brokerAddr, LMQ_TOPIC, CONSUMER_GROUP, MessageRequestMode.POP, 8, 3_000); } } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/namespace/ProducerWithNamespace.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.namespace; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import java.nio.charset.StandardCharsets; public class ProducerWithNamespace { public static final String NAMESPACE = "InstanceTest"; public static final String PRODUCER_GROUP = "pidTest"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final int MESSAGE_COUNT = 100; public static final String TOPIC = "NAMESPACE_TOPIC"; public static final String TAG = "tagTest"; public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); producer.setNamespaceV2(NAMESPACE); producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { Message message = new Message(TOPIC, TAG, "Hello world".getBytes(StandardCharsets.UTF_8)); try { SendResult result = producer.send(message); System.out.printf("Topic:%s send success, misId is:%s%n", message.getTopic(), result.getMsgId()); } catch (Exception e) { e.printStackTrace(); } } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/namespace/PullConsumerWithNamespace.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.namespace; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.common.message.MessageQueue; public class PullConsumerWithNamespace { public static final String NAMESPACE = "InstanceTest"; public static final String CONSUMER_GROUP = "cidTest"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "NAMESPACE_TOPIC"; private static final Map OFFSET_TABLE = new HashMap<>(); public static void main(String[] args) throws Exception { DefaultMQPullConsumer pullConsumer = new DefaultMQPullConsumer(CONSUMER_GROUP); pullConsumer.setNamespaceV2(NAMESPACE); pullConsumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); pullConsumer.start(); Set mqs = pullConsumer.fetchSubscribeMessageQueues(TOPIC); for (MessageQueue mq : mqs) { System.out.printf("Consume from the topic: %s, queue: %s%n", mq.getTopic(), mq); SINGLE_MQ: while (true) { try { PullResult pullResult = pullConsumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32); System.out.printf("%s%n", pullResult); putMessageQueueOffset(mq, pullResult.getNextBeginOffset()); switch (pullResult.getPullStatus()) { case FOUND: dealWithPullResult(pullResult); break; case NO_MATCHED_MSG: break; case NO_NEW_MSG: break SINGLE_MQ; case OFFSET_ILLEGAL: break; default: break; } } catch (Exception e) { e.printStackTrace(); } } } pullConsumer.shutdown(); } private static long getMessageQueueOffset(MessageQueue mq) { Long offset = OFFSET_TABLE.get(mq); if (offset != null) { return offset; } return 0; } private static void dealWithPullResult(PullResult pullResult) { if (null == pullResult || pullResult.getMsgFoundList().isEmpty()) { return; } pullResult.getMsgFoundList().forEach( msg -> System.out.printf("Topic is:%s, msgId is:%s%n", msg.getTopic(), msg.getMsgId())); } private static void putMessageQueueOffset(MessageQueue mq, long offset) { OFFSET_TABLE.put(mq, offset); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/namespace/PushConsumerWithNamespace.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.namespace; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; public class PushConsumerWithNamespace { public static final String NAMESPACE = "InstanceTest"; public static final String CONSUMER_GROUP = "cidTest"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "NAMESPACE_TOPIC"; public static void main(String[] args) throws Exception { DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer(CONSUMER_GROUP); defaultMQPushConsumer.setNamespaceV2(NAMESPACE); defaultMQPushConsumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); defaultMQPushConsumer.subscribe(TOPIC, "*"); defaultMQPushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { msgs.forEach(msg -> System.out.printf("Msg topic is:%s, MsgId is:%s, reconsumeTimes is:%s%n", msg.getTopic(), msg.getMsgId(), msg.getReconsumeTimes())); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); defaultMQPushConsumer.start(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/openmessaging/SimpleProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.openmessaging; import io.openmessaging.Future; import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.producer.Producer; import io.openmessaging.producer.SendResult; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; public class SimpleProducer { public static final String URL = "oms:rocketmq://localhost:9876/default:default"; public static final String QUEUE = "OMS_HELLO_TOPIC"; public static void main(String[] args) { // You need to set the environment variable OMS_RMQ_DIRECT_NAME_SRV=true final MessagingAccessPoint messagingAccessPoint = OMS.getMessagingAccessPoint(URL); final Producer producer = messagingAccessPoint.createProducer(); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); producer.startup(); System.out.printf("Producer startup OK%n"); { Message message = producer.createBytesMessage(QUEUE, "OMS_HELLO_BODY".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(message); //final Void aVoid = result.get(3000L); System.out.printf("Send async message OK, msgId: %s%n", sendResult.messageId()); } final CountDownLatch countDownLatch = new CountDownLatch(1); { final Future result = producer.sendAsync(producer.createBytesMessage(QUEUE, "OMS_HELLO_BODY".getBytes(StandardCharsets.UTF_8))); result.addListener(future -> { if (future.getThrowable() != null) { System.out.printf("Send async message Failed, error: %s%n", future.getThrowable().getMessage()); } else { System.out.printf("Send async message OK, msgId: %s%n", future.get().messageId()); } countDownLatch.countDown(); }); } { producer.sendOneway(producer.createBytesMessage("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(StandardCharsets.UTF_8))); System.out.printf("Send oneway message OK%n"); } try { countDownLatch.await(); // Wait some time for one-way delivery. Thread.sleep(500); } catch (InterruptedException ignore) { } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.openmessaging; import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.PullConsumer; import io.openmessaging.producer.Producer; import io.openmessaging.producer.SendResult; import java.nio.charset.StandardCharsets; public class SimplePullConsumer { public static final String URL = "oms:rocketmq://localhost:9876/default:default"; public static final String QUEUE = "OMS_CONSUMER"; public static void main(String[] args) { // You need to set the environment variable OMS_RMQ_DIRECT_NAME_SRV=true final MessagingAccessPoint messagingAccessPoint = OMS.getMessagingAccessPoint(URL); messagingAccessPoint.startup(); final Producer producer = messagingAccessPoint.createProducer(); final PullConsumer consumer = messagingAccessPoint.createPullConsumer( OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, QUEUE)); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); final String queueName = "TopicTest"; producer.startup(); Message msg = producer.createBytesMessage(queueName, "Hello Open Messaging".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(msg); System.out.printf("Send Message OK. MsgId: %s%n", sendResult.messageId()); producer.shutdown(); consumer.attachQueue(queueName); consumer.startup(); System.out.printf("Consumer startup OK%n"); // Keep running until we find the one that has just been sent boolean stop = false; while (!stop) { Message message = consumer.receive(); if (message != null) { String msgId = message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID); System.out.printf("Received one message: %s%n", msgId); consumer.ack(msgId); if (!stop) { stop = msgId.equalsIgnoreCase(sendResult.messageId()); } } else { System.out.printf("Return without any message%n"); } } consumer.shutdown(); messagingAccessPoint.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.openmessaging; import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.PushConsumer; public class SimplePushConsumer { public static final String URL = "oms:rocketmq://localhost:9876/default:default"; public static final String QUEUE = "OMS_HELLO_TOPIC"; public static void main(String[] args) { // You need to set the environment variable OMS_RMQ_DIRECT_NAME_SRV=true final MessagingAccessPoint messagingAccessPoint = OMS .getMessagingAccessPoint(URL); final PushConsumer consumer = messagingAccessPoint. createPushConsumer(OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "OMS_CONSUMER")); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); Runtime.getRuntime().addShutdownHook(new Thread(() -> { consumer.shutdown(); messagingAccessPoint.shutdown(); })); consumer.attachQueue(QUEUE, (message, context) -> { System.out.printf("Received one message: %s%n", message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID)); context.ack(); }); consumer.startup(); System.out.printf("Consumer startup OK%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/operation/Consumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.operation; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; public class Consumer { public static void main(String[] args) throws MQClientException { CommandLine commandLine = buildCommandline(args); if (commandLine != null) { String subGroup = commandLine.getOptionValue('g'); String topic = commandLine.getOptionValue('t'); String subExpression = commandLine.getOptionValue('s'); final String returnFailedHalf = commandLine.getOptionValue('f'); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(subGroup); consumer.setInstanceName(Long.toString(System.currentTimeMillis())); consumer.subscribe(topic, subExpression); consumer.registerMessageListener(new MessageListenerConcurrently() { AtomicLong consumeTimes = new AtomicLong(0); @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { long currentTimes = this.consumeTimes.incrementAndGet(); System.out.printf("%-8d %s%n", currentTimes, msgs); if (Boolean.parseBoolean(returnFailedHalf)) { if ((currentTimes % 2) == 0) { return ConsumeConcurrentlyStatus.RECONSUME_LATER; } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); } } public static CommandLine buildCommandline(String[] args) { final Options options = new Options(); Option opt = new Option("h", "help", false, "Print help"); opt.setRequired(false); options.addOption(opt); opt = new Option("g", "consumerGroup", true, "Consumer Group Name"); opt.setRequired(true); options.addOption(opt); opt = new Option("t", "topic", true, "Topic Name"); opt.setRequired(true); options.addOption(opt); opt = new Option("s", "subscription", true, "subscription"); opt.setRequired(false); options.addOption(opt); opt = new Option("f", "returnFailedHalf", true, "return failed result, for half message"); opt.setRequired(true); options.addOption(opt); DefaultParser parser = new DefaultParser(); HelpFormatter hf = new HelpFormatter(); hf.setWidth(110); CommandLine commandLine = null; try { commandLine = parser.parse(options, args); if (commandLine.hasOption('h')) { hf.printHelp("producer", options, true); return null; } } catch (ParseException e) { hf.printHelp("producer", options, true); return null; } return commandLine; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/operation/Producer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.operation; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class Producer { public static void main(String[] args) throws MQClientException, InterruptedException { CommandLine commandLine = buildCommandline(args); if (commandLine != null) { String group = commandLine.getOptionValue('g'); String topic = commandLine.getOptionValue('t'); String tags = commandLine.getOptionValue('a'); String keys = commandLine.getOptionValue('k'); String msgCount = commandLine.getOptionValue('c'); DefaultMQProducer producer = new DefaultMQProducer(group); producer.setInstanceName(Long.toString(System.currentTimeMillis())); producer.start(); for (int i = 0; i < Integer.parseInt(msgCount); i++) { try { Message msg = new Message( topic, tags, keys, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%-8d %s%n", i, sendResult); } catch (Exception e) { e.printStackTrace(); Thread.sleep(1000); } } producer.shutdown(); } } public static CommandLine buildCommandline(String[] args) { final Options options = new Options(); Option opt = new Option("h", "help", false, "Print help"); opt.setRequired(false); options.addOption(opt); opt = new Option("g", "producerGroup", true, "Producer Group Name"); opt.setRequired(true); options.addOption(opt); opt = new Option("t", "topic", true, "Topic Name"); opt.setRequired(true); options.addOption(opt); opt = new Option("a", "tags", true, "Tags Name"); opt.setRequired(true); options.addOption(opt); opt = new Option("k", "keys", true, "Keys Name"); opt.setRequired(true); options.addOption(opt); opt = new Option("c", "msgCount", true, "Message Count"); opt.setRequired(true); options.addOption(opt); DefaultParser parser = new DefaultParser(); HelpFormatter hf = new HelpFormatter(); hf.setWidth(110); CommandLine commandLine = null; try { commandLine = parser.parse(options, args); if (commandLine.hasOption('h')) { hf.printHelp("producer", options, true); return null; } } catch (ParseException e) { hf.printHelp("producer", options, true); return null; } return commandLine; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/ordermessage/Consumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.ordermessage; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; public class Consumer { public static void main(String[] args) throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicTest", "TagA || TagC || TagD"); consumer.registerMessageListener(new MessageListenerOrderly() { AtomicLong consumeTimes = new AtomicLong(0); @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { context.setAutoCommit(true); System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); this.consumeTimes.incrementAndGet(); if ((this.consumeTimes.get() % 2) == 0) { return ConsumeOrderlyStatus.SUCCESS; } else if ((this.consumeTimes.get() % 5) == 0) { context.setSuspendCurrentQueueTimeMillis(3000); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } return ConsumeOrderlyStatus.SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/ordermessage/Producer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.ordermessage; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.common.RemotingHelper; import java.util.List; public class Producer { public static void main(String[] args) throws MQClientException { try { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 100; i++) { int orderId = i % 10; Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg, new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { Integer id = (Integer) arg; int index = id % mqs.size(); return mqs.get(index); } }, orderId); System.out.printf("%s%n", sendResult); } producer.shutdown(); } catch (Exception e) { e.printStackTrace(); throw new MQClientException(e.getMessage(), null); } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/quickstart/Consumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.quickstart; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; /** * This example shows how to subscribe and consume messages using providing {@link DefaultMQPushConsumer}. */ public class Consumer { public static final String CONSUMER_GROUP = "please_rename_unique_group_name_4"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static void main(String[] args) throws MQClientException { /* * Instantiate with specified consumer group name. */ DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); /* * Specify name server addresses. *

    * * Alternatively, you may specify name server addresses via exporting environmental variable: NAMESRV_ADDR *

             * {@code
             * consumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
             * }
             * 
    */ // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); /* * Specify where to start in case the specific consumer group is a brand-new one. */ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); /* * Subscribe one more topic to consume. */ consumer.subscribe(TOPIC, "*"); /* * Register callback to execute on arrival of messages fetched from brokers. */ consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); /* * Launch the consumer instance. */ consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.quickstart; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; /** * This class demonstrates how to send messages to brokers using provided {@link DefaultMQProducer}. */ public class Producer { /** * The number of produced messages. */ public static final int MESSAGE_COUNT = 1000; public static final String PRODUCER_GROUP = "please_rename_unique_group_name"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static final String TAG = "TagA"; public static void main(String[] args) throws MQClientException, InterruptedException { /* * Instantiate with a producer group name. */ DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); /* * Specify name server addresses. * * Alternatively, you may specify name server addresses via exporting environmental variable: NAMESRV_ADDR *
             * {@code
             *  producer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
             * }
             * 
    */ // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); /* * Launch the instance. */ producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { try { /* * Create a message instance, specifying topic, tag and message body. */ Message msg = new Message(TOPIC /* Topic */, TAG /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); /* * Call send message to deliver message to one of brokers. */ SendResult sendResult = producer.send(msg, 20 * 1000); /* * There are different ways to send message, if you don't care about the send result,you can use this way * {@code * producer.sendOneway(msg); * } */ /* * if you want to get the send result in a synchronize way, you can use this send method * {@code * SendResult sendResult = producer.send(msg); * System.out.printf("%s%n", sendResult); * } */ /* * if you want to get the send result in a asynchronize way, you can use this send method * {@code * * producer.send(msg, new SendCallback() { * @Override * public void onSuccess(SendResult sendResult) { * // do something * } * * @Override * public void onException(Throwable e) { * // do something * } *}); * *} */ System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); Thread.sleep(1000); } } /* * Shut down once the producer instance is no longer in use. */ producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/rpc/AsyncRequestProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.rpc; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.RequestCallback; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class AsyncRequestProducer { private static final Logger log = LoggerFactory.getLogger(AsyncRequestProducer.class); public static void main(String[] args) throws MQClientException, InterruptedException { String producerGroup = "please_rename_unique_group_name"; String topic = "RequestTopic"; long ttl = 3000; DefaultMQProducer producer = new DefaultMQProducer(producerGroup); producer.start(); try { Message msg = new Message(topic, "", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); long begin = System.currentTimeMillis(); producer.request(msg, new RequestCallback() { @Override public void onSuccess(Message message) { long cost = System.currentTimeMillis() - begin; System.out.printf("request to <%s> cost: %d replyMessage: %s %n", topic, cost, message); } @Override public void onException(Throwable e) { System.err.printf("request to <%s> fail.", topic); } }, ttl); } catch (Exception e) { log.warn("", e); } /* shutdown after your request callback is finished */ // producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/rpc/RequestProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.rpc; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class RequestProducer { public static void main(String[] args) throws MQClientException, InterruptedException { String producerGroup = "please_rename_unique_group_name"; String topic = "RequestTopic"; long ttl = 3000; DefaultMQProducer producer = new DefaultMQProducer(producerGroup); //You need to set namesrvAddr to the address of the local namesrv producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); try { Message msg = new Message(topic, "", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); long begin = System.currentTimeMillis(); Message retMsg = producer.request(msg, ttl); long cost = System.currentTimeMillis() - begin; System.out.printf("request to <%s> cost: %d replyMessage: %s %n", topic, cost, retMsg); } catch (Exception e) { e.printStackTrace(); } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/rpc/ResponseConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.rpc; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.utils.MessageUtil; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.exception.RemotingException; public class ResponseConsumer { public static void main(String[] args) throws InterruptedException, MQClientException { String producerGroup = "please_rename_unique_group_name"; String consumerGroup = "please_rename_unique_group_name"; String topic = "RequestTopic"; // create a producer to send reply message DefaultMQProducer replyProducer = new DefaultMQProducer(producerGroup); replyProducer.setNamesrvAddr("127.0.0.1:9876"); replyProducer.start(); // create consumer DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); consumer.setNamesrvAddr("127.0.0.1:9876"); // recommend client configs consumer.setPullTimeDelayMillsWhenException(0L); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); for (MessageExt msg : msgs) { try { System.out.printf("handle message: %s %n", msg.toString()); String replyTo = MessageUtil.getReplyToClient(msg); byte[] replyContent = "reply message contents.".getBytes(StandardCharsets.UTF_8); // create reply message with given util, do not create reply message by yourself Message replyMessage = MessageUtil.createReplyMessage(msg, replyContent); // send reply message with producer SendResult replyResult = replyProducer.send(replyMessage, 3000); System.out.printf("reply to %s , %s %n", replyTo, replyResult.toString()); } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { e.printStackTrace(); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.subscribe(topic, "*"); consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.schedule; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; public class ScheduledMessageConsumer { public static final String CONSUMER_GROUP = "ExampleConsumer"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TestTopic"; public static void main(String[] args) throws Exception { // Instantiate message consumer DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); // Subscribe topics consumer.subscribe(TOPIC, "*"); // Register message listener consumer.registerMessageListener((MessageListenerConcurrently) (messages, context) -> { for (MessageExt message : messages) { // Print approximate delay time period System.out.printf("Receive message[msgId=%s %d ms later]\n", message.getMsgId(), System.currentTimeMillis() - message.getStoreTimestamp()); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); // Launch consumer consumer.start(); //info:to see the time effect, run the consumer first , it will wait for the msg //then start the producer } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.schedule; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; public class ScheduledMessageProducer { public static final String PRODUCER_GROUP = "ExampleProducerGroup"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TestTopic"; public static void main(String[] args) throws Exception { // Instantiate a producer to send scheduled messages DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); // Launch producer producer.start(); int totalMessagesToSend = 100; for (int i = 0; i < totalMessagesToSend; i++) { Message message = new Message(TOPIC, ("Hello scheduled message " + i).getBytes(StandardCharsets.UTF_8)); // This message will be delivered to consumer 10 seconds later. message.setDelayTimeLevel(3); // Send the message SendResult result = producer.send(message); System.out.print(result); } // Shutdown producer after use. producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.schedule; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; public class TimerMessageConsumer { //Note: TimerMessage is a new feature in version 5.0, so be sure to upgrade RocketMQ to version 5.0+ before using it. public static final String CONSUMER_GROUP = "TimerMessageConsumerGroup"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TimerTopic"; public static void main(String[] args) throws Exception { // Instantiate message consumer DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); // Subscribe topics consumer.subscribe(TOPIC, "*"); // Register message listener consumer.registerMessageListener((MessageListenerConcurrently) (messages, context) -> { for (MessageExt message : messages) { // Print approximate delay time period System.out.printf("Receive message[msgId=%s %d ms later]\n", message.getMsgId(), System.currentTimeMillis() - message.getBornTimestamp()); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); // Launch consumer consumer.start(); //info:to see the time effect, run the consumer first , it will wait for the msg //then start the producer } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.schedule; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; public class TimerMessageProducer { //Note: TimerMessage is a new feature in version 5.0, so be sure to upgrade RocketMQ to version 5.0+ before using it. public static final String PRODUCER_GROUP = "TimerMessageProducerGroup"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TimerTopic"; public static void main(String[] args) throws Exception { // Instantiate a producer to send scheduled messages DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); // Launch producer producer.start(); int totalMessagesToSend = 10; for (int i = 0; i < totalMessagesToSend; i++) { Message message = new Message(TOPIC, ("Hello scheduled message " + i).getBytes(StandardCharsets.UTF_8)); // This message will be delivered to consumer 10 seconds later. //message.setDelayTimeSec(10); // The effect is the same as the above // message.setDelayTimeMs(10_000L); // Set the specific delivery time, and the effect is the same as the above message.setDeliverTimeMs(System.currentTimeMillis() + 10_000L); // Send the message SendResult result = producer.send(message); System.out.printf(result + "\n"); } // Shutdown producer after use. producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/AclClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.common.RemotingHelper; public class AclClient { private static final Map OFFSE_TABLE = new HashMap<>(); private static final String ACL_ACCESS_KEY = "RocketMQ"; private static final String ACL_SECRET_KEY = "1234567"; public static void main(String[] args) throws MQClientException, InterruptedException { producer(); pushConsumer(); pullConsumer(); } public static void producer() throws MQClientException { DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName", getAclRPCHook()); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); for (int i = 0; i < 128; i++) try { { Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } } catch (Exception e) { e.printStackTrace(); } producer.shutdown(); } public static void pushConsumer() throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_5", getAclRPCHook(), new AllocateMessageQueueAveragely()); consumer.setNamesrvAddr("127.0.0.1:9876"); consumer.subscribe("TopicTest", "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); // Wrong time format 2017_0422_221800 consumer.setConsumeTimestamp("20180422221800"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); printBody(msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); } public static void pullConsumer() throws MQClientException { DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_group_name_6", getAclRPCHook()); consumer.setNamesrvAddr("127.0.0.1:9876"); consumer.start(); Set mqs = consumer.fetchSubscribeMessageQueues("TopicTest"); for (MessageQueue mq : mqs) { System.out.printf("Consume from the queue: %s%n", mq); SINGLE_MQ: while (true) { try { PullResult pullResult = consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32); System.out.printf("%s%n", pullResult); putMessageQueueOffset(mq, pullResult.getNextBeginOffset()); printBody(pullResult); switch (pullResult.getPullStatus()) { case FOUND: break; case NO_MATCHED_MSG: break; case NO_NEW_MSG: break SINGLE_MQ; case OFFSET_ILLEGAL: break; default: break; } } catch (Exception e) { e.printStackTrace(); } } } consumer.shutdown(); } private static void printBody(PullResult pullResult) { printBody(pullResult.getMsgFoundList()); } private static void printBody(List msg) { if (msg == null || msg.size() == 0) return; for (MessageExt m : msg) { if (m != null) { System.out.printf("msgId : %s body : %s \n\r", m.getMsgId(), new String(m.getBody(), StandardCharsets.UTF_8)); } } } private static long getMessageQueueOffset(MessageQueue mq) { Long offset = OFFSE_TABLE.get(mq); if (offset != null) return offset; return 0; } private static void putMessageQueueOffset(MessageQueue mq, long offset) { OFFSE_TABLE.put(mq, offset); } static RPCHook getAclRPCHook() { return new AclClientRPCHook(new SessionCredentials(ACL_ACCESS_KEY,ACL_SECRET_KEY)); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/AsyncProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.io.UnsupportedEncodingException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class AsyncProducer { public static void main( String[] args) throws MQClientException, InterruptedException, UnsupportedEncodingException { DefaultMQProducer producer = new DefaultMQProducer("Jodie_Daily_test"); producer.start(); // suggest to on enableBackpressureForAsyncMode in heavy traffic, default is false producer.setEnableBackpressureForAsyncMode(true); producer.setRetryTimesWhenSendAsyncFailed(0); int messageCount = 100; final CountDownLatch countDownLatch = new CountDownLatch(messageCount); for (int i = 0; i < messageCount; i++) { try { final int index = i; Message msg = new Message("Jodie_topic_1023", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { countDownLatch.countDown(); System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId()); } @Override public void onException(Throwable e) { countDownLatch.countDown(); System.out.printf("%-10d Exception %s %n", index, e); e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } } countDownLatch.await(5, TimeUnit.SECONDS); producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/CachedQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.TreeMap; import org.apache.rocketmq.common.message.MessageExt; public class CachedQueue { private final TreeMap msgCachedTable = new TreeMap<>(); public TreeMap getMsgCachedTable() { return msgCachedTable; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssign.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class LitePullConsumerAssign { public static volatile boolean running = true; public static void main(String[] args) throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer("please_rename_unique_group_name"); litePullConsumer.setAutoCommit(false); litePullConsumer.start(); Collection mqSet = litePullConsumer.fetchMessageQueues("TopicTest"); List list = new ArrayList<>(mqSet); List assignList = new ArrayList<>(); for (int i = 0; i < list.size() / 2; i++) { assignList.add(list.get(i)); } litePullConsumer.assign(assignList); litePullConsumer.seek(assignList.get(0), 10); try { while (running) { List messageExts = litePullConsumer.poll(); System.out.printf("%s %n", messageExts); litePullConsumer.commit(); } } finally { litePullConsumer.shutdown(); } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssignWithSubExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class LitePullConsumerAssignWithSubExpression { public static volatile boolean running = true; public static void main(String[] args) throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer("please_rename_unique_group_name"); litePullConsumer.setAutoCommit(false); litePullConsumer.setSubExpressionForAssign("TopicTest", "TagA"); litePullConsumer.start(); Collection mqSet = litePullConsumer.fetchMessageQueues("TopicTest"); List list = new ArrayList<>(mqSet); List assignList = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { assignList.add(list.get(i)); } mqSet = litePullConsumer.fetchMessageQueues("TopicTest1"); list = new ArrayList<>(mqSet); for (int i = 0; i < list.size(); i++) { assignList.add(list.get(i)); } litePullConsumer.assign(assignList); litePullConsumer.seek(assignList.get(0), 10); try { while (running) { List messageExts = litePullConsumer.poll(); System.out.printf("%s %n", messageExts); litePullConsumer.commit(); } } finally { litePullConsumer.shutdown(); } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerSubscribe.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; public class LitePullConsumerSubscribe { public static volatile boolean running = true; public static void main(String[] args) throws Exception { DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer("lite_pull_consumer_test"); litePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); litePullConsumer.subscribe("TopicTest", "*"); litePullConsumer.start(); try { while (running) { List messageExts = litePullConsumer.poll(); System.out.printf("%s%n", messageExts); } } finally { litePullConsumer.shutdown(); } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/OnewayProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; import java.nio.charset.StandardCharsets; public class OnewayProducer { public static void main(String[] args) throws Exception { //Instantiate with a producer group name. DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses. producer.setNamesrvAddr("localhost:9876"); //Launch the instance. producer.start(); for (int i = 0; i < 100; i++) { //Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(StandardCharsets.UTF_8) /* Message body */ ); //Call send message to deliver message to one of brokers. producer.sendOneway(msg); } //Wait for sending to complete Thread.sleep(5000); producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/PopConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; public class PopConsumer { public static final String TOPIC = "TopicTest"; public static final String CONSUMER_GROUP = "CID_JODIE_1"; public static final String NAMESRV_ADDR = "127.0.0.1:9876"; public static void main(String[] args) throws Exception { switchPop(); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); consumer.subscribe(TOPIC, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.setClientRebalance(false); consumer.start(); System.out.printf("Consumer Started.%n"); } private static void switchPop() throws Exception { DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); // mqAdminExt.setNamesrvAddr(NAMESRV_ADDR); mqAdminExt.start(); List brokerDatas = mqAdminExt.examineTopicRouteInfo(TOPIC).getBrokerDatas(); for (BrokerData brokerData : brokerDatas) { Set brokerAddrs = new HashSet<>(brokerData.getBrokerAddrs().values()); for (String brokerAddr : brokerAddrs) { mqAdminExt.setMessageRequestMode(brokerAddr, TOPIC, CONSUMER_GROUP, MessageRequestMode.POP, 8, 3_000); } } } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/Producer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import java.nio.charset.StandardCharsets; public class Producer { public static final String PRODUCER_GROUP = "ProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static final String TAG = "TagA"; public static void main(String[] args) throws MQClientException, InterruptedException { DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address //producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); for (int i = 0; i < 128; i++) { try { Message msg = new Message(TOPIC, TAG, "OrderID188", "Hello world".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); } } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/PullConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; @SuppressWarnings("deprecation") public class PullConsumer { public static void main(String[] args) throws MQClientException { DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_group_name_5"); consumer.setNamesrvAddr("127.0.0.1:9876"); Set topics = new HashSet<>(); //You would be better to register topics,It will use in rebalance when starting topics.add("TopicTest"); consumer.setRegisterTopics(topics); consumer.start(); ExecutorService executors = Executors.newFixedThreadPool(topics.size(), new ThreadFactoryImpl("PullConsumerThread")); for (String topic : consumer.getRegisterTopics()) { executors.execute(new Runnable() { public void doSomething(List msgs) { //do your business } @Override public void run() { while (true) { try { Set messageQueues = consumer.fetchMessageQueuesInBalance(topic); if (messageQueues == null || messageQueues.isEmpty()) { Thread.sleep(1000); continue; } PullResult pullResult = null; for (MessageQueue messageQueue : messageQueues) { try { long offset = this.consumeFromOffset(messageQueue); pullResult = consumer.pull(messageQueue, "*", offset, 32); switch (pullResult.getPullStatus()) { case FOUND: List msgs = pullResult.getMsgFoundList(); if (msgs != null && !msgs.isEmpty()) { this.doSomething(msgs); //update offset to local memory, eventually to broker consumer.updateConsumeOffset(messageQueue, pullResult.getNextBeginOffset()); //print pull tps this.incPullTPS(topic, pullResult.getMsgFoundList().size()); } break; case OFFSET_ILLEGAL: consumer.updateConsumeOffset(messageQueue, pullResult.getNextBeginOffset()); break; case NO_NEW_MSG: Thread.sleep(1); consumer.updateConsumeOffset(messageQueue, pullResult.getNextBeginOffset()); break; case NO_MATCHED_MSG: consumer.updateConsumeOffset(messageQueue, pullResult.getNextBeginOffset()); break; default: } } catch (RemotingException e) { e.printStackTrace(); } catch (MQBrokerException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } catch (MQClientException e) { //reblance error e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } public long consumeFromOffset(MessageQueue messageQueue) throws MQClientException { //-1 when started long offset = consumer.getOffsetStore().readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY); if (offset < 0) { //query from broker offset = consumer.getOffsetStore().readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE); } if (offset < 0) { //first time start from last offset offset = consumer.maxOffset(messageQueue); } //make sure if (offset < 0) { offset = 0; } return offset; } public void incPullTPS(String topic, int pullSize) { consumer.getDefaultMQPullConsumerImpl().getRebalanceImpl().getmQClientFactory() .getConsumerStatsManager().incPullTPS(consumer.getConsumerGroup(), topic, pullSize); } }); } // executors.shutdown(); // consumer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/PullScheduleService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import org.apache.rocketmq.client.consumer.MQPullConsumer; import org.apache.rocketmq.client.consumer.MQPullConsumerScheduleService; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullTaskCallback; import org.apache.rocketmq.client.consumer.PullTaskContext; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class PullScheduleService { public static void main(String[] args) throws MQClientException { final MQPullConsumerScheduleService scheduleService = new MQPullConsumerScheduleService("GroupName1"); scheduleService.setMessageModel(MessageModel.CLUSTERING); scheduleService.registerPullTaskCallback("TopicTest", new PullTaskCallback() { @Override public void doPullTask(MessageQueue mq, PullTaskContext context) { MQPullConsumer consumer = context.getPullConsumer(); try { long offset = consumer.fetchConsumeOffset(mq, false); if (offset < 0) offset = 0; PullResult pullResult = consumer.pull(mq, "*", offset, 32); System.out.printf("%s%n", offset + "\t" + mq + "\t" + pullResult); switch (pullResult.getPullStatus()) { case FOUND: break; case NO_MATCHED_MSG: break; case NO_NEW_MSG: case OFFSET_ILLEGAL: break; default: break; } consumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset()); context.setPullNextDelayTimeMillis(100); } catch (Exception e) { e.printStackTrace(); } } }); scheduleService.start(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/PushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; public class PushConsumer { public static final String TOPIC = "TopicTest"; public static final String CONSUMER_GROUP = "CID_JODIE_1"; public static final String NAMESRV_ADDR = "127.0.0.1:9876"; public static void main(String[] args) throws InterruptedException, MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.subscribe(TOPIC, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); //wrong time format 2017_0422_221800 consumer.setConsumeTimestamp("20181109221800"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/simple/RandomAsyncCommit.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.simple; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class RandomAsyncCommit { private final ConcurrentHashMap mqCachedTable = new ConcurrentHashMap<>(); public void putMessages(final MessageQueue mq, final List msgs) { CachedQueue cachedQueue = this.mqCachedTable.get(mq); if (null == cachedQueue) { cachedQueue = new CachedQueue(); this.mqCachedTable.put(mq, cachedQueue); } for (MessageExt msg : msgs) { cachedQueue.getMsgCachedTable().put(msg.getQueueOffset(), msg); } } public void removeMessage(final MessageQueue mq, long offset) { CachedQueue cachedQueue = this.mqCachedTable.get(mq); if (null != cachedQueue) { cachedQueue.getMsgCachedTable().remove(offset); } } public long commitableOffset(final MessageQueue mq) { CachedQueue cachedQueue = this.mqCachedTable.get(mq); if (null != cachedQueue) { return cachedQueue.getMsgCachedTable().firstKey(); } return -1; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.tracemessage; import io.jaegertracing.Configuration; import io.jaegertracing.internal.samplers.ConstSampler; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.trace.hook.SendMessageOpenTracingHookImpl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class OpenTracingProducer { public static final String PRODUCER_GROUP = "ProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static final String TAG = "TagA"; public static final String KEY = "OrderID188"; public static void main(String[] args) throws MQClientException { Tracer tracer = initTracer(); DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.getDefaultMQProducerImpl().registerSendMessageHook(new SendMessageOpenTracingHookImpl(tracer)); producer.start(); try { Message msg = new Message(TOPIC, TAG, KEY, "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); } producer.shutdown(); } private static Tracer initTracer() { Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv() .withType(ConstSampler.TYPE) .withParam(1); Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv() .withLogSpans(true); Configuration config = new Configuration("rocketmq") .withSampler(samplerConfig) .withReporter(reporterConfig); GlobalTracer.registerIfAbsent(config.getTracer()); return config.getTracer(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingPushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.tracemessage; import io.jaegertracing.Configuration; import io.jaegertracing.internal.samplers.ConstSampler; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.trace.hook.ConsumeMessageOpenTracingHookImpl; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; public class OpenTracingPushConsumer { public static final String CONSUMER_GROUP = "CID_JODIE_1"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static void main(String[] args) throws InterruptedException, MQClientException { Tracer tracer = initTracer(); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); consumer.registerConsumeMessageHook(new ConsumeMessageOpenTracingHookImpl(tracer)); consumer.subscribe(TOPIC, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.setConsumeTimestamp("20181109221800"); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); System.out.printf("Consumer Started.%n"); } private static Tracer initTracer() { Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv() .withType(ConstSampler.TYPE) .withParam(1); Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv() .withLogSpans(true); Configuration config = new Configuration("rocketmq") .withSampler(samplerConfig) .withReporter(reporterConfig); GlobalTracer.registerIfAbsent(config.getTracer()); return config.getTracer(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.tracemessage; import io.jaegertracing.Configuration; import io.jaegertracing.internal.samplers.ConstSampler; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.client.trace.hook.EndTransactionOpenTracingHookImpl; import org.apache.rocketmq.client.trace.hook.SendMessageOpenTracingHookImpl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.common.RemotingHelper; import java.io.UnsupportedEncodingException; public class OpenTracingTransactionProducer { public static final String PRODUCER_GROUP = "please_rename_unique_group_name"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static final String TAG = "Tag"; public static final String KEY = "KEY"; public static final int MESSAGE_COUNT = 100000; public static void main(String[] args) throws MQClientException, InterruptedException { Tracer tracer = initTracer(); TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.getDefaultMQProducerImpl().registerSendMessageHook(new SendMessageOpenTracingHookImpl(tracer)); producer.getDefaultMQProducerImpl().registerEndTransactionHook(new EndTransactionOpenTracingHookImpl(tracer)); producer.setTransactionListener(new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { return LocalTransactionState.COMMIT_MESSAGE; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { return LocalTransactionState.COMMIT_MESSAGE; } }); producer.start(); try { Message msg = new Message(TOPIC, TAG, KEY, "Hello RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); } catch (MQClientException | UnsupportedEncodingException e) { e.printStackTrace(); } for (int i = 0; i < MESSAGE_COUNT; i++) { Thread.sleep(1000); } producer.shutdown(); } private static Tracer initTracer() { Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv() .withType(ConstSampler.TYPE) .withParam(1); Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv() .withLogSpans(true); Configuration config = new Configuration("rocketmq") .withSampler(samplerConfig) .withReporter(reporterConfig); GlobalTracer.registerIfAbsent(config.getTracer()); return config.getTracer(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/tracemessage/TraceProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.tracemessage; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class TraceProducer { public static final String PRODUCER_GROUP = "ProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static final String TAG = "TagA"; public static final String KEY = "OrderID188"; public static final int MESSAGE_COUNT = 128; public static void main(String[] args) throws MQClientException, InterruptedException { DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP, true, null); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { try { { Message msg = new Message(TOPIC, TAG, KEY, "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } } catch (Exception e) { e.printStackTrace(); } } producer.shutdown(); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/tracemessage/TracePushConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.tracemessage; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; public class TracePushConsumer { public static final String CONSUMER_GROUP = "ProducerGroupName"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest"; public static void main(String[] args) throws InterruptedException, MQClientException { // Here,we use the default message track trace topic name DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP, true, null); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); consumer.subscribe(TOPIC, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); // Wrong time format 2017_0422_221800 consumer.setConsumeTimestamp("20181109221800"); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); System.out.printf("Consumer Started.%n"); } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/transaction/TransactionListenerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.transaction; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; public class TransactionListenerImpl implements TransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); private ConcurrentHashMap localTrans = new ConcurrentHashMap<>(); @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { int value = transactionIndex.getAndIncrement(); int status = value % 3; localTrans.put(msg.getTransactionId(), status); return LocalTransactionState.UNKNOW; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { Integer status = localTrans.get(msg.getTransactionId()); if (null != status) { switch (status) { case 0: return LocalTransactionState.UNKNOW; case 1: return LocalTransactionState.COMMIT_MESSAGE; case 2: return LocalTransactionState.ROLLBACK_MESSAGE; default: return LocalTransactionState.COMMIT_MESSAGE; } } return LocalTransactionState.COMMIT_MESSAGE; } } ================================================ FILE: example/src/main/java/org/apache/rocketmq/example/transaction/TransactionProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.example.transaction; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TransactionProducer { public static final String PRODUCER_GROUP = "please_rename_unique_group_name"; public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876"; public static final String TOPIC = "TopicTest1234"; public static final int MESSAGE_COUNT = 10; public static void main(String[] args) throws MQClientException, InterruptedException { TransactionListener transactionListener = new TransactionListenerImpl(); TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP, Arrays.asList(TOPIC)); // Uncomment the following line while debugging, namesrvAddr should be set to your local address // producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), r -> { Thread thread = new Thread(r); thread.setName("client-transaction-msg-check-thread"); return thread; }); producer.setExecutorService(executorService); producer.setTransactionListener(transactionListener); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < MESSAGE_COUNT; i++) { try { Message msg = new Message(TOPIC, tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); Thread.sleep(10); } catch (MQClientException | UnsupportedEncodingException e) { e.printStackTrace(); } } for (int i = 0; i < 100000; i++) { Thread.sleep(1000); } producer.shutdown(); } } ================================================ FILE: filter/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "filter", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_validator_commons_validator", "@maven//:com_github_luben_zstd_jni", "@maven//:org_lz4_lz4_java", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":filter", "//common", "//remoting", "//:test_deps", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_netty_netty_all", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: filter/pom.xml ================================================ rocketmq-all org.apache.rocketmq ${revision} 4.0.0 jar rocketmq-filter rocketmq-filter ${project.version} ${basedir}/.. ${project.groupId} rocketmq-common ${project.groupId} rocketmq-srvutil com.google.guava guava ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/FilterFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import java.util.HashMap; import java.util.Map; /** * Filter factory: support other filter to register. */ public class FilterFactory { public static final FilterFactory INSTANCE = new FilterFactory(); protected static final Map FILTER_SPI_HOLDER = new HashMap<>(4); static { FilterFactory.INSTANCE.register(new SqlFilter()); } /** * Register a filter. *
    * Note: *
  • 1. Filter registered will be used in broker server, so take care of it's reliability and performance.
  • */ public void register(FilterSpi filterSpi) { if (FILTER_SPI_HOLDER.containsKey(filterSpi.ofType())) { throw new IllegalArgumentException(String.format("Filter spi type(%s) already exist!", filterSpi.ofType())); } FILTER_SPI_HOLDER.put(filterSpi.ofType(), filterSpi); } /** * Un register a filter. */ public FilterSpi unRegister(String type) { return FILTER_SPI_HOLDER.remove(type); } /** * Get a filter registered, null if none exist. */ public FilterSpi get(String type) { return FILTER_SPI_HOLDER.get(type); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/FilterSpi.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.MQFilterException; /** * Filter spi interface. */ public interface FilterSpi { /** * Compile. */ Expression compile(final String expr) throws MQFilterException; /** * Which type. */ String ofType(); } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/SqlFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.MQFilterException; import org.apache.rocketmq.filter.parser.SelectorParser; /** * SQL92 Filter, just a wrapper of {@link org.apache.rocketmq.filter.parser.SelectorParser}. *

    *

    * Do not use this filter directly.Use {@link FilterFactory#get} to select a filter. *

    */ public class SqlFilter implements FilterSpi { @Override public Expression compile(final String expr) throws MQFilterException { return SelectorParser.parse(expr); } @Override public String ofType() { return ExpressionType.SQL92; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/constant/UnaryType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.constant; public enum UnaryType { NEGATE, IN, NOT, BOOLEANCAST, LIKE } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/BinaryExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * An expression which performs an operation on two expression values. *

    * This class was taken from ActiveMQ org.apache.activemq.filter.BinaryExpression, *

    */ public abstract class BinaryExpression implements Expression { protected Expression left; protected Expression right; public BinaryExpression(Expression left, Expression right) { this.left = left; this.right = right; } public Expression getLeft() { return left; } public Expression getRight() { return right; } /** * @see Object#toString() */ public String toString() { return "(" + left.toString() + " " + getExpressionSymbol() + " " + right.toString() + ")"; } /** * @see Object#hashCode() */ public int hashCode() { return toString().hashCode(); } /** * @see Object#equals(Object) */ public boolean equals(Object o) { if (o == null || !this.getClass().equals(o.getClass())) { return false; } return toString().equals(o.toString()); } /** * Returns the symbol that represents this binary expression. For example, addition is * represented by "+" */ public abstract String getExpressionSymbol(); /** * @param expression */ public void setRight(Expression expression) { right = expression; } /** * @param expression */ public void setLeft(Expression expression) { left = expression; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/BooleanConstantExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * BooleanConstantExpression */ public class BooleanConstantExpression extends ConstantExpression implements BooleanExpression { public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null); public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE); public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE); public BooleanConstantExpression(Object value) { super(value); } public boolean matches(EvaluationContext context) throws Exception { Object object = evaluate(context); return object != null && object == Boolean.TRUE; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/BooleanExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * A BooleanExpression is an expression that always * produces a Boolean result. *

    * This class was taken from ActiveMQ org.apache.activemq.filter.BooleanExpression, * but the parameter is changed to an interface. *

    * * @see org.apache.rocketmq.filter.expression.EvaluationContext */ public interface BooleanExpression extends Expression { /** * @return true if the expression evaluates to Boolean.TRUE. */ boolean matches(EvaluationContext context) throws Exception; } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; import java.util.List; /** * A filter performing a comparison of two objects *

    * This class was taken from ActiveMQ org.apache.activemq.filter.ComparisonExpression, * but: * 1. Remove LIKE expression, and related methods; * 2. Extract a new method __compare which has int return value; * 3. When create between expression, check whether left value is less or equal than right value; * 4. For string type value(can not convert to number), only equal or unequal comparison are supported. *

    */ public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression { public static final ThreadLocal CONVERT_STRING_EXPRESSIONS = new ThreadLocal<>(); boolean convertStringExpressions = false; /** * @param left * @param right */ public ComparisonExpression(Expression left, Expression right) { super(left, right); convertStringExpressions = CONVERT_STRING_EXPRESSIONS.get() != null; } public static BooleanExpression createBetween(Expression value, Expression left, Expression right) { // check if (left instanceof ConstantExpression && right instanceof ConstantExpression) { Object lv = ((ConstantExpression) left).getValue(); Object rv = ((ConstantExpression) right).getValue(); if (lv == null || rv == null) { throw new RuntimeException("Illegal values of between, values can not be null!"); } if (lv instanceof Comparable && rv instanceof Comparable) { int ret = __compare((Comparable) rv, (Comparable) lv, true); if (ret < 0) throw new RuntimeException( String.format("Illegal values of between, left value(%s) must less than or equal to right value(%s)", lv, rv) ); } } return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right)); } public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) { return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right)); } static class ContainsExpression extends UnaryExpression implements BooleanExpression { String search; public ContainsExpression(Expression right, String search) { super(right); this.search = search; } public String getExpressionSymbol() { return "CONTAINS"; } public Object evaluate(EvaluationContext message) throws Exception { if (search == null || search.length() == 0) { return Boolean.FALSE; } Object rv = this.getRight().evaluate(message); if (rv == null) { return Boolean.FALSE; } if (!(rv instanceof String)) { return Boolean.FALSE; } return ((String)rv).contains(search) ? Boolean.TRUE : Boolean.FALSE; } public boolean matches(EvaluationContext message) throws Exception { Object object = evaluate(message); return object != null && object == Boolean.TRUE; } } static class NotContainsExpression extends UnaryExpression implements BooleanExpression { String search; public NotContainsExpression(Expression right, String search) { super(right); this.search = search; } public String getExpressionSymbol() { return "NOT CONTAINS"; } public Object evaluate(EvaluationContext message) throws Exception { if (search == null || search.length() == 0) { return Boolean.FALSE; } Object rv = this.getRight().evaluate(message); if (rv == null) { return Boolean.FALSE; } if (!(rv instanceof String)) { return Boolean.FALSE; } return ((String)rv).contains(search) ? Boolean.FALSE : Boolean.TRUE; } public boolean matches(EvaluationContext message) throws Exception { Object object = evaluate(message); return object != null && object == Boolean.TRUE; } } public static BooleanExpression createContains(Expression left, String search) { return new ContainsExpression(left, search); } public static BooleanExpression createNotContains(Expression left, String search) { return new NotContainsExpression(left, search); } static class StartsWithExpression extends UnaryExpression implements BooleanExpression { String search; public StartsWithExpression(Expression right, String search) { super(right); this.search = search; } public String getExpressionSymbol() { return "STARTSWITH"; } public Object evaluate(EvaluationContext message) throws Exception { if (search == null || search.length() == 0) { return Boolean.FALSE; } Object rv = this.getRight().evaluate(message); if (rv == null) { return Boolean.FALSE; } if (!(rv instanceof String)) { return Boolean.FALSE; } return ((String)rv).startsWith(search) ? Boolean.TRUE : Boolean.FALSE; } public boolean matches(EvaluationContext message) throws Exception { Object object = evaluate(message); return object != null && object == Boolean.TRUE; } } static class NotStartsWithExpression extends UnaryExpression implements BooleanExpression { String search; public NotStartsWithExpression(Expression right, String search) { super(right); this.search = search; } public String getExpressionSymbol() { return "NOT STARTSWITH"; } public Object evaluate(EvaluationContext message) throws Exception { if (search == null || search.length() == 0) { return Boolean.FALSE; } Object rv = this.getRight().evaluate(message); if (rv == null) { return Boolean.FALSE; } if (!(rv instanceof String)) { return Boolean.FALSE; } return ((String)rv).startsWith(search) ? Boolean.FALSE : Boolean.TRUE; } public boolean matches(EvaluationContext message) throws Exception { Object object = evaluate(message); return object != null && object == Boolean.TRUE; } } public static BooleanExpression createStartsWith(Expression left, String search) { return new StartsWithExpression(left, search); } public static BooleanExpression createNotStartsWith(Expression left, String search) { return new NotStartsWithExpression(left, search); } static class EndsWithExpression extends UnaryExpression implements BooleanExpression { String search; public EndsWithExpression(Expression right, String search) { super(right); this.search = search; } public String getExpressionSymbol() { return "ENDSWITH"; } public Object evaluate(EvaluationContext message) throws Exception { if (search == null || search.length() == 0) { return Boolean.FALSE; } Object rv = this.getRight().evaluate(message); if (rv == null) { return Boolean.FALSE; } if (!(rv instanceof String)) { return Boolean.FALSE; } return ((String)rv).endsWith(search) ? Boolean.TRUE : Boolean.FALSE; } public boolean matches(EvaluationContext message) throws Exception { Object object = evaluate(message); return object != null && object == Boolean.TRUE; } } static class NotEndsWithExpression extends UnaryExpression implements BooleanExpression { String search; public NotEndsWithExpression(Expression right, String search) { super(right); this.search = search; } public String getExpressionSymbol() { return "NOT ENDSWITH"; } public Object evaluate(EvaluationContext message) throws Exception { if (search == null || search.length() == 0) { return Boolean.FALSE; } Object rv = this.getRight().evaluate(message); if (rv == null) { return Boolean.FALSE; } if (!(rv instanceof String)) { return Boolean.FALSE; } return ((String)rv).endsWith(search) ? Boolean.FALSE : Boolean.TRUE; } public boolean matches(EvaluationContext message) throws Exception { Object object = evaluate(message); return object != null && object == Boolean.TRUE; } } public static BooleanExpression createEndsWith(Expression left, String search) { return new EndsWithExpression(left, search); } public static BooleanExpression createNotEndsWith(Expression left, String search) { return new NotEndsWithExpression(left, search); } @SuppressWarnings({"rawtypes", "unchecked"}) public static BooleanExpression createInFilter(Expression left, List elements) { if (!(left instanceof PropertyExpression)) { throw new RuntimeException("Expected a property for In expression, got: " + left); } return UnaryExpression.createInExpression((PropertyExpression) left, elements, false); } @SuppressWarnings({"rawtypes", "unchecked"}) public static BooleanExpression createNotInFilter(Expression left, List elements) { if (!(left instanceof PropertyExpression)) { throw new RuntimeException("Expected a property for In expression, got: " + left); } return UnaryExpression.createInExpression((PropertyExpression) left, elements, true); } public static BooleanExpression createIsNull(Expression left) { return doCreateEqual(left, BooleanConstantExpression.NULL); } public static BooleanExpression createIsNotNull(Expression left) { return UnaryExpression.createNOT(doCreateEqual(left, BooleanConstantExpression.NULL)); } public static BooleanExpression createNotEqual(Expression left, Expression right) { return UnaryExpression.createNOT(createEqual(left, right)); } public static BooleanExpression createEqual(Expression left, Expression right) { checkEqualOperand(left); checkEqualOperand(right); checkEqualOperandCompatability(left, right); return doCreateEqual(left, right); } @SuppressWarnings({"rawtypes"}) private static BooleanExpression doCreateEqual(Expression left, Expression right) { return new ComparisonExpression(left, right) { public Object evaluate(EvaluationContext context) throws Exception { Object lv = left.evaluate(context); Object rv = right.evaluate(context); // If one of the values is null if (lv == null ^ rv == null) { if (lv == null) { return null; } return Boolean.FALSE; } if (lv == rv || lv.equals(rv)) { return Boolean.TRUE; } if (lv instanceof Comparable && rv instanceof Comparable) { return compare((Comparable) lv, (Comparable) rv); } return Boolean.FALSE; } protected boolean asBoolean(int answer) { return answer == 0; } public String getExpressionSymbol() { return "=="; } }; } public static BooleanExpression createGreaterThan(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { protected boolean asBoolean(int answer) { return answer > 0; } public String getExpressionSymbol() { return ">"; } }; } public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { protected boolean asBoolean(int answer) { return answer >= 0; } public String getExpressionSymbol() { return ">="; } }; } public static BooleanExpression createLessThan(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { protected boolean asBoolean(int answer) { return answer < 0; } public String getExpressionSymbol() { return "<"; } }; } public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { protected boolean asBoolean(int answer) { return answer <= 0; } public String getExpressionSymbol() { return "<="; } }; } /** * Only Numeric expressions can be used in >, >=, < or <= expressions.s */ public static void checkLessThanOperand(Expression expr) { if (expr instanceof ConstantExpression) { Object value = ((ConstantExpression) expr).getValue(); if (value instanceof Number) { return; } // Else it's boolean or a String.. throw new RuntimeException("Value '" + expr + "' cannot be compared."); } if (expr instanceof BooleanExpression) { throw new RuntimeException("Value '" + expr + "' cannot be compared."); } } /** * Validates that the expression can be used in == or <> expression. Cannot * not be NULL TRUE or FALSE litterals. */ public static void checkEqualOperand(Expression expr) { if (expr instanceof ConstantExpression) { Object value = ((ConstantExpression) expr).getValue(); if (value == null) { throw new RuntimeException("'" + expr + "' cannot be compared."); } } } /** * @param left * @param right */ private static void checkEqualOperandCompatability(Expression left, Expression right) { if (left instanceof ConstantExpression && right instanceof ConstantExpression) { if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) { throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); } } } @SuppressWarnings({"rawtypes", "unchecked"}) public Object evaluate(EvaluationContext context) throws Exception { Comparable lv = (Comparable) left.evaluate(context); if (lv == null) { return null; } Comparable rv = (Comparable) right.evaluate(context); if (rv == null) { return null; } if (getExpressionSymbol().equals(">=") || getExpressionSymbol().equals(">") || getExpressionSymbol().equals("<") || getExpressionSymbol().equals("<=")) { Class lc = lv.getClass(); Class rc = rv.getClass(); if (lc == rc && lc == String.class) { // Compare String is illegal // first try to convert to double try { Comparable lvC = Double.valueOf((String) (Comparable) lv); Comparable rvC = Double.valueOf((String) rv); return compare(lvC, rvC); } catch (Exception e) { throw new RuntimeException("It's illegal to compare string by '>=', '>', '<', '<='. lv=" + lv + ", rv=" + rv, e); } } } return compare(lv, rv); } @SuppressWarnings({"rawtypes", "unchecked"}) protected static int __compare(Comparable lv, Comparable rv, boolean convertStringExpressions) { Class lc = lv.getClass(); Class rc = rv.getClass(); // If the the objects are not of the same type, // try to convert up to allow the comparison. if (lc != rc) { try { if (lc == Boolean.class) { if (convertStringExpressions && rc == String.class) { lv = Boolean.valueOf((String) lv).booleanValue(); } else { return -1; } } else if (lc == Byte.class) { if (rc == Short.class) { lv = Short.valueOf(((Number) lv).shortValue()); } else if (rc == Integer.class) { lv = Integer.valueOf(((Number) lv).intValue()); } else if (rc == Long.class) { lv = Long.valueOf(((Number) lv).longValue()); } else if (rc == Float.class) { lv = new Float(((Number) lv).floatValue()); } else if (rc == Double.class) { lv = new Double(((Number) lv).doubleValue()); } else if (convertStringExpressions && rc == String.class) { rv = Byte.valueOf((String) rv); } else { return -1; } } else if (lc == Short.class) { if (rc == Integer.class) { lv = Integer.valueOf(((Number) lv).intValue()); } else if (rc == Long.class) { lv = Long.valueOf(((Number) lv).longValue()); } else if (rc == Float.class) { lv = new Float(((Number) lv).floatValue()); } else if (rc == Double.class) { lv = new Double(((Number) lv).doubleValue()); } else if (convertStringExpressions && rc == String.class) { rv = Short.valueOf((String) rv); } else { return -1; } } else if (lc == Integer.class) { if (rc == Long.class) { lv = Long.valueOf(((Number) lv).longValue()); } else if (rc == Float.class) { lv = new Float(((Number) lv).floatValue()); } else if (rc == Double.class) { lv = new Double(((Number) lv).doubleValue()); } else if (convertStringExpressions && rc == String.class) { rv = Integer.valueOf((String) rv); } else { return -1; } } else if (lc == Long.class) { if (rc == Integer.class) { rv = Long.valueOf(((Number) rv).longValue()); } else if (rc == Float.class) { lv = new Float(((Number) lv).floatValue()); } else if (rc == Double.class) { lv = new Double(((Number) lv).doubleValue()); } else if (convertStringExpressions && rc == String.class) { rv = Long.valueOf((String) rv); } else { return -1; } } else if (lc == Float.class) { if (rc == Integer.class) { rv = new Float(((Number) rv).floatValue()); } else if (rc == Long.class) { rv = new Float(((Number) rv).floatValue()); } else if (rc == Double.class) { lv = new Double(((Number) lv).doubleValue()); } else if (convertStringExpressions && rc == String.class) { rv = Float.valueOf((String) rv); } else { return -1; } } else if (lc == Double.class) { if (rc == Integer.class) { rv = new Double(((Number) rv).doubleValue()); } else if (rc == Long.class) { rv = new Double(((Number) rv).doubleValue()); } else if (rc == Float.class) { rv = new Float(((Number) rv).doubleValue()); } else if (convertStringExpressions && rc == String.class) { rv = Double.valueOf((String) rv); } else { return -1; } } else if (convertStringExpressions && lc == String.class) { if (rc == Boolean.class) { lv = Boolean.valueOf((String) lv); } else if (rc == Byte.class) { lv = Byte.valueOf((String) lv); } else if (rc == Short.class) { lv = Short.valueOf((String) lv); } else if (rc == Integer.class) { lv = Integer.valueOf((String) lv); } else if (rc == Long.class) { lv = Long.valueOf((String) lv); } else if (rc == Float.class) { lv = Float.valueOf((String) lv); } else if (rc == Double.class) { lv = Double.valueOf((String) lv); } else { return -1; } } else { return -1; } } catch (NumberFormatException e) { throw new RuntimeException(e); } } return lv.compareTo(rv); } @SuppressWarnings({"rawtypes", "unchecked"}) protected Boolean compare(Comparable lv, Comparable rv) { return asBoolean(__compare(lv, rv, convertStringExpressions)) ? Boolean.TRUE : Boolean.FALSE; } protected abstract boolean asBoolean(int answer); public boolean matches(EvaluationContext context) throws Exception { Object object = evaluate(context); return object != null && object == Boolean.TRUE; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/ConstantExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * Represents a constant expression *

    * This class was taken from ActiveMQ org.apache.activemq.filter.ConstantExpression, * but: * 1. For long type constant, the range bound by java Long type; * 2. For float type constant, the range bound by java Double type; * 3. Remove Hex and Octal expression; * 4. Add now expression to support to get current time. *

    */ public class ConstantExpression implements Expression { private Object value; public ConstantExpression(Object value) { this.value = value; } public static ConstantExpression createFromDecimal(String text) { // Strip off the 'l' or 'L' if needed. if (text.endsWith("l") || text.endsWith("L")) { text = text.substring(0, text.length() - 1); } // only support Long.MIN_VALUE ~ Long.MAX_VALUE Number value = new Long(text); long l = value.longValue(); if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) { value = value.intValue(); } return new ConstantExpression(value); } public static ConstantExpression createFloat(String text) { Double value = new Double(text); if (value > Double.MAX_VALUE) { throw new RuntimeException(text + " is greater than " + Double.MAX_VALUE); } if (value < Double.MIN_VALUE) { throw new RuntimeException(text + " is less than " + Double.MIN_VALUE); } return new ConstantExpression(value); } public static ConstantExpression createNow() { return new NowExpression(); } public Object evaluate(EvaluationContext context) throws Exception { return value; } public Object getValue() { return value; } /** * @see Object#toString() */ public String toString() { Object value = getValue(); if (value == null) { return "NULL"; } if (value instanceof Boolean) { return (Boolean) value ? "TRUE" : "FALSE"; } if (value instanceof String) { return encodeString((String) value); } return value.toString(); } /** * @see Object#hashCode() */ public int hashCode() { return toString().hashCode(); } /** * @see Object#equals(Object) */ public boolean equals(Object o) { if (o == null || !this.getClass().equals(o.getClass())) { return false; } return toString().equals(o.toString()); } /** * Encodes the value of string so that it looks like it would look like when * it was provided in a selector. */ public static String encodeString(String s) { StringBuilder builder = new StringBuilder(); builder.append('\''); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '\'') { builder.append(c); } builder.append(c); } builder.append('\''); return builder.toString(); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/EmptyEvaluationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; import java.util.Map; /** * Empty context. */ public class EmptyEvaluationContext implements EvaluationContext { @Override public Object get(String name) { return null; } @Override public Map keyValues() { return null; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/EvaluationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; import java.util.Map; /** * Context of evaluate expression. * * Compare to org.apache.activemq.filter.MessageEvaluationContext, this is just an interface. */ public interface EvaluationContext { /** * Get value by name from context */ Object get(String name); /** * Context variables. */ Map keyValues(); } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/Expression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * Interface of expression. *

    * This class was taken from ActiveMQ org.apache.activemq.filter.Expression, * but the parameter is changed to an interface. *

    * * @see org.apache.rocketmq.filter.expression.EvaluationContext */ public interface Expression { /** * Calculate express result with context. * * @param context context of evaluation * @return the value of this expression */ Object evaluate(EvaluationContext context) throws Exception; } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/LogicExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * A filter performing a comparison of two objects *

    * This class was taken from ActiveMQ org.apache.activemq.filter.LogicExpression, *

    */ public abstract class LogicExpression extends BinaryExpression implements BooleanExpression { /** * @param left * @param right */ public LogicExpression(BooleanExpression left, BooleanExpression right) { super(left, right); } public static BooleanExpression createOR(BooleanExpression lvalue, BooleanExpression rvalue) { return new LogicExpression(lvalue, rvalue) { public Object evaluate(EvaluationContext context) throws Exception { Boolean lv = (Boolean) left.evaluate(context); if (lv != null && lv.booleanValue()) { return Boolean.TRUE; } Boolean rv = (Boolean) right.evaluate(context); if (rv != null && rv.booleanValue()) { return Boolean.TRUE; } if (lv == null || rv == null) { return null; } return Boolean.FALSE; } public String getExpressionSymbol() { return "||"; } }; } public static BooleanExpression createAND(BooleanExpression lvalue, BooleanExpression rvalue) { return new LogicExpression(lvalue, rvalue) { public Object evaluate(EvaluationContext context) throws Exception { Boolean lv = (Boolean) left.evaluate(context); if (lv != null && !lv.booleanValue()) { return Boolean.FALSE; } Boolean rv = (Boolean) right.evaluate(context); if (rv != null && !rv.booleanValue()) { return Boolean.FALSE; } if (lv == null || rv == null) { return null; } return Boolean.TRUE; } public String getExpressionSymbol() { return "&&"; } }; } public abstract Object evaluate(EvaluationContext context) throws Exception; public boolean matches(EvaluationContext context) throws Exception { Object object = evaluate(context); return object != null && object == Boolean.TRUE; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/MQFilterException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * Exception. */ public class MQFilterException extends Exception { private static final long serialVersionUID = 1L; private final int responseCode; private final String errorMessage; public MQFilterException(String errorMessage, Throwable cause) { super(cause); this.responseCode = -1; this.errorMessage = errorMessage; } public MQFilterException(int responseCode, String errorMessage) { this.responseCode = responseCode; this.errorMessage = errorMessage; } public int getResponseCode() { return responseCode; } public String getErrorMessage() { return errorMessage; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/NowExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * Current time expression.Just for test. */ public class NowExpression extends ConstantExpression { public NowExpression() { super("now"); } @Override public Object evaluate(EvaluationContext context) throws Exception { return new Long(System.currentTimeMillis()); } public Object getValue() { return new Long(System.currentTimeMillis()); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/PropertyExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; /** * Represents a property expression *

    * This class was taken from ActiveMQ org.apache.activemq.filter.PropertyExpression, * but more simple and no transfer between expression and message property. *

    */ public class PropertyExpression implements Expression { private final String name; public PropertyExpression(String name) { this.name = name; } @Override public Object evaluate(EvaluationContext context) throws Exception { return context.get(name); } public String getName() { return name; } /** * @see Object#toString() */ @Override public String toString() { return name; } /** * @see Object#hashCode() */ @Override public int hashCode() { return name.hashCode(); } /** * @see Object#equals(Object) */ @Override public boolean equals(Object o) { if (o == null || !this.getClass().equals(o.getClass())) { return false; } return name.equals(((PropertyExpression) o).name); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/UnaryExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; import org.apache.rocketmq.filter.constant.UnaryType; import java.math.BigDecimal; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; /** * An expression which performs an operation on two expression values *

    * This class was taken from ActiveMQ org.apache.activemq.filter.UnaryExpression, * but: * 1. remove XPath and XQuery expression; * 2. Add constant UnaryType to distinguish different unary expression; * 3. Extract UnaryInExpression to an independent class. *

    */ public abstract class UnaryExpression implements Expression { private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE); protected Expression right; public UnaryType unaryType; public UnaryExpression(Expression left) { this.right = left; } public UnaryExpression(Expression left, UnaryType unaryType) { this.setUnaryType(unaryType); this.right = left; } public static Expression createNegate(Expression left) { return new UnaryExpression(left, UnaryType.NEGATE) { @Override public Object evaluate(EvaluationContext context) throws Exception { Object rvalue = right.evaluate(context); if (rvalue == null) { return null; } if (rvalue instanceof Number) { return negate((Number) rvalue); } return null; } @Override public String getExpressionSymbol() { return "-"; } }; } public static BooleanExpression createInExpression(PropertyExpression right, List elements, final boolean not) { // Use a HashSet if there are many elements. Collection t; if (elements.size() == 0) { t = null; } else if (elements.size() < 5) { t = elements; } else { t = new HashSet<>(elements); } final Collection inList = t; return new UnaryInExpression(right, UnaryType.IN, inList, not) { @Override public Object evaluate(EvaluationContext context) throws Exception { Object rvalue = right.evaluate(context); if (rvalue == null) { return null; } if (rvalue.getClass() != String.class) { return null; } if ((inList != null && inList.contains(rvalue)) ^ not) { return Boolean.TRUE; } else { return Boolean.FALSE; } } @Override public String toString() { StringBuilder answer = new StringBuilder(); answer.append(right); answer.append(" "); answer.append(getExpressionSymbol()); answer.append(" ( "); int count = 0; for (Iterator i = inList.iterator(); i.hasNext(); ) { Object o = (Object) i.next(); if (count != 0) { answer.append(", "); } answer.append(o); count++; } answer.append(" )"); return answer.toString(); } @Override public String getExpressionSymbol() { if (not) { return "NOT IN"; } else { return "IN"; } } }; } abstract static class BooleanUnaryExpression extends UnaryExpression implements BooleanExpression { public BooleanUnaryExpression(Expression left, UnaryType unaryType) { super(left, unaryType); } @Override public boolean matches(EvaluationContext context) throws Exception { Object object = evaluate(context); return object != null && object == Boolean.TRUE; } } public static BooleanExpression createNOT(BooleanExpression left) { return new BooleanUnaryExpression(left, UnaryType.NOT) { @Override public Object evaluate(EvaluationContext context) throws Exception { Boolean lvalue = (Boolean) right.evaluate(context); if (lvalue == null) { return null; } return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE; } @Override public String getExpressionSymbol() { return "NOT"; } }; } public static BooleanExpression createBooleanCast(Expression left) { return new BooleanUnaryExpression(left, UnaryType.BOOLEANCAST) { @Override public Object evaluate(EvaluationContext context) throws Exception { Object rvalue = right.evaluate(context); if (rvalue == null) { return null; } if (!rvalue.getClass().equals(Boolean.class)) { return Boolean.FALSE; } return ((Boolean) rvalue).booleanValue() ? Boolean.TRUE : Boolean.FALSE; } @Override public String toString() { return right.toString(); } @Override public String getExpressionSymbol() { return ""; } }; } private static Number negate(Number left) { Class clazz = left.getClass(); if (clazz == Integer.class) { return new Integer(-left.intValue()); } else if (clazz == Long.class) { return new Long(-left.longValue()); } else if (clazz == Float.class) { return new Float(-left.floatValue()); } else if (clazz == Double.class) { return new Double(-left.doubleValue()); } else if (clazz == BigDecimal.class) { // We ussually get a big deciamal when we have Long.MIN_VALUE // constant in the // Selector. Long.MIN_VALUE is too big to store in a Long as a // positive so we store it // as a Big decimal. But it gets Negated right away.. to here we try // to covert it back // to a Long. BigDecimal bd = (BigDecimal) left; bd = bd.negate(); if (BD_LONG_MIN_VALUE.compareTo(bd) == 0) { return Long.valueOf(Long.MIN_VALUE); } return bd; } else { throw new RuntimeException("Don't know how to negate: " + left); } } public Expression getRight() { return right; } public void setRight(Expression expression) { right = expression; } public UnaryType getUnaryType() { return unaryType; } public void setUnaryType(UnaryType unaryType) { this.unaryType = unaryType; } /** * @see Object#toString() */ @Override public String toString() { return "(" + getExpressionSymbol() + " " + right.toString() + ")"; } /** * @see Object#hashCode() */ @Override public int hashCode() { return toString().hashCode(); } /** * @see Object#equals(Object) */ @Override public boolean equals(Object o) { if (o == null || !this.getClass().equals(o.getClass())) { return false; } return toString().equals(o.toString()); } /** * Returns the symbol that represents this binary expression. For example, * addition is represented by "+" */ public abstract String getExpressionSymbol(); } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/expression/UnaryInExpression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.expression; import org.apache.rocketmq.filter.constant.UnaryType; import java.util.Collection; /** * In expression. */ abstract public class UnaryInExpression extends UnaryExpression implements BooleanExpression { private boolean not; private Collection inList; public UnaryInExpression(Expression left, UnaryType unaryType, Collection inList, boolean not) { super(left, unaryType); this.setInList(inList); this.setNot(not); } public boolean matches(EvaluationContext context) throws Exception { Object object = evaluate(context); return object != null && object == Boolean.TRUE; } public boolean isNot() { return not; } public void setNot(boolean not) { this.not = not; } public Collection getInList() { return inList; } public void setInList(Collection inList) { this.inList = inList; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. ParseException.java Version 5.0 */ /* JavaCCOptions:KEEP_LINE_COL=null */ package org.apache.rocketmq.filter.parser; /** * This exception is thrown when parse errors are encountered. * You can explicitly create objects of this exception type by * calling the method generateParseException in the generated * parser. *

    * You can modify this class to customize your error reporting * mechanisms so long as you retain the public fields. */ public class ParseException extends Exception { /** * The version identifier for this Serializable class. * Increment only if the serialized form of the * class changes. */ private static final long serialVersionUID = 1L; /** * This constructor is used by the method "generateParseException" * in the generated parser. Calling this constructor generates * a new object of this type with the fields "currentToken", * "expectedTokenSequences", and "TOKEN_IMAGE" set. */ public ParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal ) { super(initialise(currentTokenVal, expectedTokenSequencesVal, tokenImageVal)); currentToken = currentTokenVal; expectedTokenSequences = expectedTokenSequencesVal; tokenImage = tokenImageVal; } /** * The following constructors are for use by you for whatever * purpose you can think of. Constructing the exception in this * manner makes the exception behave in the normal way - i.e., as * documented in the class "Throwable". The fields "errorToken", * "expectedTokenSequences", and "TOKEN_IMAGE" do not contain * relevant information. The JavaCC generated code does not use * these constructors. */ public ParseException() { super(); } /** * Constructor with message. */ public ParseException(String message) { super(message); } /** * This is the last token that has been consumed successfully. If * this object has been created due to a parse error, the token * followng this token will (therefore) be the first error token. */ public Token currentToken; /** * Each entry in this array is an array of integers. Each array * of integers represents a sequence of tokens (by their ordinal * values) that is expected at this point of the parse. */ public int[][] expectedTokenSequences; /** * This is a reference to the "TOKEN_IMAGE" array of the generated * parser within which the parse error occurred. This array is * defined in the generated ...Constants interface. */ public String[] tokenImage; /** * It uses "currentToken" and "expectedTokenSequences" to generate a parse * error message and returns it. If this object has been created * due to a parse error, and you do not catch it (it gets thrown * from the parser) the correct error message * gets displayed. */ private static String initialise(Token currentToken, int[][] expectedTokenSequences, String[] tokenImage) { String eol = System.getProperty("line.separator", "\n"); StringBuilder expected = new StringBuilder(); int maxSize = 0; for (int i = 0; i < expectedTokenSequences.length; i++) { if (maxSize < expectedTokenSequences[i].length) { maxSize = expectedTokenSequences[i].length; } for (int j = 0; j < expectedTokenSequences[i].length; j++) { expected.append(tokenImage[expectedTokenSequences[i][j]]).append(' '); } if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { expected.append("..."); } expected.append(eol).append(" "); } String retval = "Encountered \""; Token tok = currentToken.next; for (int i = 0; i < maxSize; i++) { if (i != 0) { retval += " "; } if (tok.kind == 0) { retval += tokenImage[0]; break; } retval += " " + tokenImage[tok.kind]; retval += " \""; retval += add_escapes(tok.image); retval += " \""; tok = tok.next; } retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; retval += "." + eol; if (expectedTokenSequences.length == 1) { retval += "Was expecting:" + eol + " "; } else { retval += "Was expecting one of:" + eol + " "; } retval += expected.toString(); return retval; } /** * The end of line string for this machine. */ protected String eol = System.getProperty("line.separator", "\n"); /** * Used to convert raw characters to their escaped version * when these raw version cannot be used as part of an ASCII * string literal. */ static String add_escapes(String str) { StringBuilder retval = new StringBuilder(); char ch; for (int i = 0; i < str.length(); i++) { switch (str.charAt(i)) { case 0: continue; case '\b': retval.append("\\b"); continue; case '\t': retval.append("\\t"); continue; case '\n': retval.append("\\n"); continue; case '\f': retval.append("\\f"); continue; case '\r': retval.append("\\r"); continue; case '\"': retval.append("\\\""); continue; case '\'': retval.append("\\\'"); continue; case '\\': retval.append("\\\\"); continue; default: if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { String s = "0000" + Integer.toString(ch, 16); retval.append("\\u" + s.substring(s.length() - 4, s.length())); } else { retval.append(ch); } continue; } } return retval.toString(); } } /* JavaCC - OriginalChecksum=60cf9c227a487e4be49599bc903f0a6a (do not edit this line) */ ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. SelectorParser.java */ package org.apache.rocketmq.filter.parser; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.rocketmq.filter.expression.BooleanConstantExpression; import org.apache.rocketmq.filter.expression.BooleanExpression; import org.apache.rocketmq.filter.expression.ComparisonExpression; import org.apache.rocketmq.filter.expression.ConstantExpression; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.LogicExpression; import org.apache.rocketmq.filter.expression.MQFilterException; import org.apache.rocketmq.filter.expression.PropertyExpression; import org.apache.rocketmq.filter.expression.UnaryExpression; import java.io.StringReader; import java.util.ArrayList; /** * JMS Selector Parser generated by JavaCC *

    * Do not edit this .java file directly - it is autogenerated from SelectorParser.jj */ public class SelectorParser implements SelectorParserConstants { private static final Cache PARSE_CACHE = CacheBuilder.newBuilder().maximumSize(100).build(); public static BooleanExpression parse(String sql) throws MQFilterException { Object result = PARSE_CACHE.getIfPresent(sql); if (result instanceof MQFilterException) { throw (MQFilterException) result; } else if (result instanceof BooleanExpression) { return (BooleanExpression) result; } else { ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true); try { BooleanExpression e = new SelectorParser(sql).parse(); PARSE_CACHE.put(sql, e); return e; } catch (MQFilterException t) { PARSE_CACHE.put(sql, t); throw t; } finally { ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove(); } } } public static void clearCache() { PARSE_CACHE.cleanUp(); } private String sql; protected SelectorParser(String sql) { this(new StringReader(sql)); this.sql = sql; } protected BooleanExpression parse() throws MQFilterException { try { return this.JmsSelector(); } catch (Throwable e) { throw new MQFilterException("Invalid MessageSelector. ", e); } } private BooleanExpression asBooleanExpression(Expression value) throws ParseException { if (value instanceof BooleanExpression) { return (BooleanExpression) value; } if (value instanceof PropertyExpression) { return UnaryExpression.createBooleanCast(value); } throw new ParseException("Expression will not result in a boolean value: " + value); } // ---------------------------------------------------------------------------- // Grammar // ---------------------------------------------------------------------------- final public BooleanExpression JmsSelector() throws ParseException { Expression left = orExpression(); return asBooleanExpression(left); } final public Expression orExpression() throws ParseException { Expression left; Expression right; left = andExpression(); label_1: while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case OR: break; default: jjLa1[0] = jjGen; break label_1; } jj_consume_token(OR); right = andExpression(); left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right)); } return left; } final public Expression andExpression() throws ParseException { Expression left; Expression right; left = equalityExpression(); label_2: while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case AND: break; default: jjLa1[1] = jjGen; break label_2; } jj_consume_token(AND); right = equalityExpression(); left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right)); } return left; } final public Expression equalityExpression() throws ParseException { Expression left; Expression right; left = comparisonExpression(); label_3: while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case IS: case 25: case 26: break; default: jjLa1[2] = jjGen; break label_3; } switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case 25: jj_consume_token(25); right = comparisonExpression(); left = ComparisonExpression.createEqual(left, right); break; case 26: jj_consume_token(26); right = comparisonExpression(); left = ComparisonExpression.createNotEqual(left, right); break; default: jjLa1[3] = jjGen; if (jj_2_1(2)) { jj_consume_token(IS); jj_consume_token(NULL); left = ComparisonExpression.createIsNull(left); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case IS: jj_consume_token(IS); jj_consume_token(NOT); jj_consume_token(NULL); left = ComparisonExpression.createIsNotNull(left); break; default: jjLa1[4] = jjGen; jj_consume_token(-1); throw new ParseException(); } } } } return left; } final public Expression comparisonExpression() throws ParseException { Expression left; Expression right; Expression low; Expression high; String t, u; boolean not; ArrayList list; left = unaryExpr(); label_4: while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case NOT: case BETWEEN: case IN: case CONTAINS: case STARTSWITH: case ENDSWITH: case 27: case 28: case 29: case 30: break; default: jjLa1[5] = jjGen; break label_4; } switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case 27: jj_consume_token(27); right = unaryExpr(); left = ComparisonExpression.createGreaterThan(left, right); break; case 28: jj_consume_token(28); right = unaryExpr(); left = ComparisonExpression.createGreaterThanEqual(left, right); break; case 29: jj_consume_token(29); right = unaryExpr(); left = ComparisonExpression.createLessThan(left, right); break; case 30: jj_consume_token(30); right = unaryExpr(); left = ComparisonExpression.createLessThanEqual(left, right); break; case CONTAINS: jj_consume_token(CONTAINS); t = stringLitteral(); left = ComparisonExpression.createContains(left, t); break; default: jjLa1[8] = jjGen; if (jj_2_2(2)) { jj_consume_token(NOT); jj_consume_token(CONTAINS); t = stringLitteral(); left = ComparisonExpression.createNotContains(left, t); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case STARTSWITH: jj_consume_token(STARTSWITH); t = stringLitteral(); left = ComparisonExpression.createStartsWith(left, t); break; default: jjLa1[9] = jjGen; if (jj_2_3(2)) { jj_consume_token(NOT); jj_consume_token(STARTSWITH); t = stringLitteral(); left = ComparisonExpression.createNotStartsWith(left, t); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case ENDSWITH: jj_consume_token(ENDSWITH); t = stringLitteral(); left = ComparisonExpression.createEndsWith(left, t); break; default: jjLa1[10] = jjGen; if (jj_2_4(2)) { jj_consume_token(NOT); jj_consume_token(ENDSWITH); t = stringLitteral(); left = ComparisonExpression.createNotEndsWith(left, t); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case BETWEEN: jj_consume_token(BETWEEN); low = unaryExpr(); jj_consume_token(AND); high = unaryExpr(); left = ComparisonExpression.createBetween(left, low, high); break; default: jjLa1[11] = jjGen; if (jj_2_5(2)) { jj_consume_token(NOT); jj_consume_token(BETWEEN); low = unaryExpr(); jj_consume_token(AND); high = unaryExpr(); left = ComparisonExpression.createNotBetween(left, low, high); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case IN: jj_consume_token(IN); jj_consume_token(31); t = stringLitteral(); list = new ArrayList(); list.add(t); label_5: while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case 32: break; default: jjLa1[6] = jjGen; break label_5; } jj_consume_token(32); t = stringLitteral(); list.add(t); } jj_consume_token(33); left = ComparisonExpression.createInFilter(left, list); break; default: jjLa1[12] = jjGen; if (jj_2_6(2)) { jj_consume_token(NOT); jj_consume_token(IN); jj_consume_token(31); t = stringLitteral(); list = new ArrayList(); list.add(t); label_6: while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case 32: break; default: jjLa1[7] = jjGen; break label_6; } jj_consume_token(32); t = stringLitteral(); list.add(t); } jj_consume_token(33); left = ComparisonExpression.createNotInFilter(left, list); } else { jj_consume_token(-1); throw new ParseException(); } } } } } } } } } } } return left; } final public Expression unaryExpr() throws ParseException { String s = null; Expression left = null; if (jj_2_7(2147483647)) { jj_consume_token(34); left = unaryExpr(); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case 35: jj_consume_token(35); left = unaryExpr(); left = UnaryExpression.createNegate(left); break; case NOT: jj_consume_token(NOT); left = unaryExpr(); left = UnaryExpression.createNOT(asBooleanExpression(left)); break; case TRUE: case FALSE: case NULL: case DECIMAL_LITERAL: case FLOATING_POINT_LITERAL: case STRING_LITERAL: case ID: case 31: left = primaryExpr(); break; default: jjLa1[13] = jjGen; jj_consume_token(-1); throw new ParseException(); } } return left; } final public Expression primaryExpr() throws ParseException { Expression left = null; switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case TRUE: case FALSE: case NULL: case DECIMAL_LITERAL: case FLOATING_POINT_LITERAL: case STRING_LITERAL: left = literal(); break; case ID: left = variable(); break; case 31: jj_consume_token(31); left = orExpression(); jj_consume_token(33); break; default: jjLa1[14] = jjGen; jj_consume_token(-1); throw new ParseException(); } return left; } final public ConstantExpression literal() throws ParseException { Token t; String s; ConstantExpression left = null; switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case STRING_LITERAL: s = stringLitteral(); left = new ConstantExpression(s); break; case DECIMAL_LITERAL: t = jj_consume_token(DECIMAL_LITERAL); left = ConstantExpression.createFromDecimal(t.image); break; case FLOATING_POINT_LITERAL: t = jj_consume_token(FLOATING_POINT_LITERAL); left = ConstantExpression.createFloat(t.image); break; case TRUE: jj_consume_token(TRUE); left = BooleanConstantExpression.TRUE; break; case FALSE: jj_consume_token(FALSE); left = BooleanConstantExpression.FALSE; break; case NULL: jj_consume_token(NULL); left = BooleanConstantExpression.NULL; break; default: jjLa1[15] = jjGen; jj_consume_token(-1); throw new ParseException(); } return left; } final public String stringLitteral() throws ParseException { Token t; StringBuffer rc = new StringBuffer(); boolean first = true; t = jj_consume_token(STRING_LITERAL); // Decode the sting value. String image = t.image; for (int i = 1; i < image.length() - 1; i++) { char c = image.charAt(i); if (c == '\u005c'') i++; rc.append(c); } return rc.toString(); } final public PropertyExpression variable() throws ParseException { Token t; PropertyExpression left = null; t = jj_consume_token(ID); left = new PropertyExpression(t.image); return left; } private boolean jj_2_1(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_1(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(0, xla); } } private boolean jj_2_2(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_2(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(1, xla); } } private boolean jj_2_3(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_3(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(2, xla); } } private boolean jj_2_4(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_4(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(3, xla); } } private boolean jj_2_5(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_5(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(4, xla); } } private boolean jj_2_6(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_6(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(5, xla); } } private boolean jj_2_7(int xla) { jjLa = xla; jjLastpos = jjScanpos = token; try { return !jj_3_7(); } catch (LookaheadSuccess ls) { return true; } finally { jj_save(6, xla); } } private boolean jj_3R_34() { if (jj_scan_token(26)) return true; if (jj_3R_30()) return true; return false; } private boolean jj_3R_43() { if (jj_scan_token(BETWEEN)) return true; if (jj_3R_7()) return true; if (jj_scan_token(AND)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_31() { Token xsp; xsp = jjScanpos; if (jj_3R_33()) { jjScanpos = xsp; if (jj_3R_34()) { jjScanpos = xsp; if (jj_3_1()) { jjScanpos = xsp; if (jj_3R_35()) return true; } } } return false; } private boolean jj_3R_33() { if (jj_scan_token(25)) return true; if (jj_3R_30()) return true; return false; } private boolean jj_3_4() { if (jj_scan_token(NOT)) return true; if (jj_scan_token(ENDSWITH)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_15() { if (jj_scan_token(31)) return true; if (jj_3R_18()) return true; if (jj_scan_token(33)) return true; return false; } private boolean jj_3R_14() { if (jj_3R_17()) return true; return false; } private boolean jj_3R_13() { if (jj_3R_16()) return true; return false; } private boolean jj_3R_42() { if (jj_scan_token(ENDSWITH)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_17() { if (jj_scan_token(ID)) return true; return false; } private boolean jj_3R_12() { Token xsp; xsp = jjScanpos; if (jj_3R_13()) { jjScanpos = xsp; if (jj_3R_14()) { jjScanpos = xsp; if (jj_3R_15()) return true; } } return false; } private boolean jj_3R_28() { if (jj_3R_30()) return true; Token xsp; while (true) { xsp = jjScanpos; if (jj_3R_31()) { jjScanpos = xsp; break; } } return false; } private boolean jj_3_3() { if (jj_scan_token(NOT)) return true; if (jj_scan_token(STARTSWITH)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_41() { if (jj_scan_token(STARTSWITH)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_11() { if (jj_3R_12()) return true; return false; } private boolean jj_3R_29() { if (jj_scan_token(AND)) return true; if (jj_3R_28()) return true; return false; } private boolean jj_3_7() { if (jj_scan_token(34)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3_2() { if (jj_scan_token(NOT)) return true; if (jj_scan_token(CONTAINS)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_10() { if (jj_scan_token(NOT)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_40() { if (jj_scan_token(CONTAINS)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_9() { if (jj_scan_token(35)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_27() { if (jj_scan_token(STRING_LITERAL)) return true; return false; } private boolean jj_3R_25() { if (jj_3R_28()) return true; Token xsp; while (true) { xsp = jjScanpos; if (jj_3R_29()) { jjScanpos = xsp; break; } } return false; } private boolean jj_3R_8() { if (jj_scan_token(34)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_39() { if (jj_scan_token(30)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_7() { Token xsp; xsp = jjScanpos; if (jj_3R_8()) { jjScanpos = xsp; if (jj_3R_9()) { jjScanpos = xsp; if (jj_3R_10()) { jjScanpos = xsp; if (jj_3R_11()) return true; } } } return false; } private boolean jj_3R_38() { if (jj_scan_token(29)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_46() { if (jj_scan_token(32)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_26() { if (jj_scan_token(OR)) return true; if (jj_3R_25()) return true; return false; } private boolean jj_3R_37() { if (jj_scan_token(28)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_24() { if (jj_scan_token(NULL)) return true; return false; } private boolean jj_3R_36() { if (jj_scan_token(27)) return true; if (jj_3R_7()) return true; return false; } private boolean jj_3R_32() { Token xsp; xsp = jjScanpos; if (jj_3R_36()) { jjScanpos = xsp; if (jj_3R_37()) { jjScanpos = xsp; if (jj_3R_38()) { jjScanpos = xsp; if (jj_3R_39()) { jjScanpos = xsp; if (jj_3R_40()) { jjScanpos = xsp; if (jj_3_2()) { jjScanpos = xsp; if (jj_3R_41()) { jjScanpos = xsp; if (jj_3_3()) { jjScanpos = xsp; if (jj_3R_42()) { jjScanpos = xsp; if (jj_3_4()) { jjScanpos = xsp; if (jj_3R_43()) { jjScanpos = xsp; if (jj_3_5()) { jjScanpos = xsp; if (jj_3R_44()) { jjScanpos = xsp; if (jj_3_6()) return true; } } } } } } } } } } } } } return false; } private boolean jj_3R_23() { if (jj_scan_token(FALSE)) return true; return false; } private boolean jj_3R_18() { if (jj_3R_25()) return true; Token xsp; while (true) { xsp = jjScanpos; if (jj_3R_26()) { jjScanpos = xsp; break; } } return false; } private boolean jj_3R_22() { if (jj_scan_token(TRUE)) return true; return false; } private boolean jj_3_6() { if (jj_scan_token(NOT)) return true; if (jj_scan_token(IN)) return true; if (jj_scan_token(31)) return true; if (jj_3R_27()) return true; Token xsp; while (true) { xsp = jjScanpos; if (jj_3R_46()) { jjScanpos = xsp; break; } } if (jj_scan_token(33)) return true; return false; } private boolean jj_3R_45() { if (jj_scan_token(32)) return true; if (jj_3R_27()) return true; return false; } private boolean jj_3R_30() { if (jj_3R_7()) return true; Token xsp; while (true) { xsp = jjScanpos; if (jj_3R_32()) { jjScanpos = xsp; break; } } return false; } private boolean jj_3R_21() { if (jj_scan_token(FLOATING_POINT_LITERAL)) return true; return false; } private boolean jj_3R_20() { if (jj_scan_token(DECIMAL_LITERAL)) return true; return false; } private boolean jj_3R_35() { if (jj_scan_token(IS)) return true; if (jj_scan_token(NOT)) return true; if (jj_scan_token(NULL)) return true; return false; } private boolean jj_3R_44() { if (jj_scan_token(IN)) return true; if (jj_scan_token(31)) return true; if (jj_3R_27()) return true; Token xsp; while (true) { xsp = jjScanpos; if (jj_3R_45()) { jjScanpos = xsp; break; } } if (jj_scan_token(33)) return true; return false; } private boolean jj_3R_19() { if (jj_3R_27()) return true; return false; } private boolean jj_3_1() { if (jj_scan_token(IS)) return true; if (jj_scan_token(NULL)) return true; return false; } private boolean jj_3R_16() { Token xsp; xsp = jjScanpos; if (jj_3R_19()) { jjScanpos = xsp; if (jj_3R_20()) { jjScanpos = xsp; if (jj_3R_21()) { jjScanpos = xsp; if (jj_3R_22()) { jjScanpos = xsp; if (jj_3R_23()) { jjScanpos = xsp; if (jj_3R_24()) return true; } } } } } return false; } private boolean jj_3_5() { if (jj_scan_token(NOT)) return true; if (jj_scan_token(BETWEEN)) return true; if (jj_3R_7()) return true; if (jj_scan_token(AND)) return true; if (jj_3R_7()) return true; return false; } /** * Generated Token Manager. */ public SelectorParserTokenManager tokenSource; SimpleCharStream jjInputStream; /** * Current token. */ public Token token; /** * Next token. */ public Token jjNt; private int jjNtk; private Token jjScanpos, jjLastpos; private int jjLa; private int jjGen; final private int[] jjLa1 = new int[16]; static private int[] jjLa10; static private int[] jjLa11; static { jj_la1_init_0(); jj_la1_init_1(); } private static void jj_la1_init_0() { jjLa10 = new int[]{0x400, 0x200, 0x6010000, 0x6000000, 0x10000, 0x780e1900, 0x0, 0x0, 0x78020000, 0x40000, 0x80000, 0x800, 0x1000, 0x81b0e100, 0x81b0e000, 0xb0e000,}; } private static void jj_la1_init_1() { jjLa11 = new int[]{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0,}; } final private JJCalls[] jj2Rtns = new JJCalls[7]; private boolean jjRescan = false; private int jjGc = 0; /** * Constructor with InputStream. */ public SelectorParser(java.io.InputStream stream) { this(stream, null); } /** * Constructor with InputStream and supplied encoding */ public SelectorParser(java.io.InputStream stream, String encoding) { try { jjInputStream = new SimpleCharStream(stream, encoding, 1, 1); } catch (java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } tokenSource = new SelectorParserTokenManager(jjInputStream); token = new Token(); jjNtk = -1; jjGen = 0; for (int i = 0; i < 16; i++) jjLa1[i] = -1; for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** * Reinitialise. */ public void ReInit(java.io.InputStream stream) { ReInit(stream, null); } /** * Reinitialise. */ public void ReInit(java.io.InputStream stream, String encoding) { try { jjInputStream.ReInit(stream, encoding, 1, 1); } catch (java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } tokenSource.ReInit(jjInputStream); token = new Token(); jjNtk = -1; jjGen = 0; for (int i = 0; i < 16; i++) jjLa1[i] = -1; for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** * Constructor. */ public SelectorParser(java.io.Reader stream) { jjInputStream = new SimpleCharStream(stream, 1, 1); tokenSource = new SelectorParserTokenManager(jjInputStream); token = new Token(); jjNtk = -1; jjGen = 0; for (int i = 0; i < 16; i++) jjLa1[i] = -1; for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** * Reinitialise. */ public void ReInit(java.io.Reader stream) { jjInputStream.ReInit(stream, 1, 1); tokenSource.ReInit(jjInputStream); token = new Token(); jjNtk = -1; jjGen = 0; for (int i = 0; i < 16; i++) jjLa1[i] = -1; for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** * Constructor with generated Token Manager. */ public SelectorParser(SelectorParserTokenManager tm) { tokenSource = tm; token = new Token(); jjNtk = -1; jjGen = 0; for (int i = 0; i < 16; i++) jjLa1[i] = -1; for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** * Reinitialise. */ public void ReInit(SelectorParserTokenManager tm) { tokenSource = tm; token = new Token(); jjNtk = -1; jjGen = 0; for (int i = 0; i < 16; i++) jjLa1[i] = -1; for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } private Token jj_consume_token(int kind) throws ParseException { Token oldToken; if ((oldToken = token).next != null) token = token.next; else token = token.next = tokenSource.getNextToken(); jjNtk = -1; if (token.kind == kind) { jjGen++; if (++jjGc > 100) { jjGc = 0; for (int i = 0; i < jj2Rtns.length; i++) { JJCalls c = jj2Rtns[i]; while (c != null) { if (c.gen < jjGen) c.first = null; c = c.next; } } } return token; } token = oldToken; jjKind = kind; throw generateParseException(); } static private final class LookaheadSuccess extends java.lang.Error { } final private LookaheadSuccess jjLs = new LookaheadSuccess(); private boolean jj_scan_token(int kind) { if (jjScanpos == jjLastpos) { jjLa--; if (jjScanpos.next == null) { jjLastpos = jjScanpos = jjScanpos.next = tokenSource.getNextToken(); } else { jjLastpos = jjScanpos = jjScanpos.next; } } else { jjScanpos = jjScanpos.next; } if (jjRescan) { int i = 0; Token tok = token; while (tok != null && tok != jjScanpos) { i++; tok = tok.next; } if (tok != null) jj_add_error_token(kind, i); } if (jjScanpos.kind != kind) return true; if (jjLa == 0 && jjScanpos == jjLastpos) throw jjLs; return false; } /** * Get the next Token. */ final public Token getNextToken() { if (token.next != null) token = token.next; else token = token.next = tokenSource.getNextToken(); jjNtk = -1; jjGen++; return token; } /** * Get the specific Token. */ final public Token getToken(int index) { Token t = token; for (int i = 0; i < index; i++) { if (t.next != null) t = t.next; else t = t.next = tokenSource.getNextToken(); } return t; } private int jj_ntk() { if ((jjNt = token.next) == null) return jjNtk = (token.next = tokenSource.getNextToken()).kind; else return jjNtk = jjNt.kind; } private java.util.List jjExpentries = new java.util.ArrayList<>(); private int[] jjExpentry; private int jjKind = -1; private int[] jjLasttokens = new int[100]; private int jjEndpos; private void jj_add_error_token(int kind, int pos) { if (pos >= 100) return; if (pos == jjEndpos + 1) { jjLasttokens[jjEndpos++] = kind; } else if (jjEndpos != 0) { jjExpentry = new int[jjEndpos]; for (int i = 0; i < jjEndpos; i++) { jjExpentry[i] = jjLasttokens[i]; } boolean exists = false; for (java.util.Iterator it = jjExpentries.iterator(); it.hasNext(); ) { exists = true; int[] oldentry = (int[]) (it.next()); if (oldentry.length == jjExpentry.length) { for (int i = 0; i < jjExpentry.length; i++) { if (oldentry[i] != jjExpentry[i]) { exists = false; break; } } if (exists) break; } } if (!exists) jjExpentries.add(jjExpentry); if (pos != 0) jjLasttokens[(jjEndpos = pos) - 1] = kind; } } /** * Generate ParseException. */ public ParseException generateParseException() { jjExpentries.clear(); boolean[] la1tokens = new boolean[36]; if (jjKind >= 0) { la1tokens[jjKind] = true; jjKind = -1; } for (int i = 0; i < 16; i++) { if (jjLa1[i] == jjGen) { for (int j = 0; j < 32; j++) { if ((jjLa10[i] & (1 << j)) != 0) { la1tokens[j] = true; } if ((jjLa11[i] & (1 << j)) != 0) { la1tokens[32 + j] = true; } } } } for (int i = 0; i < 36; i++) { if (la1tokens[i]) { jjExpentry = new int[1]; jjExpentry[0] = i; jjExpentries.add(jjExpentry); } } jjEndpos = 0; jj_rescan_token(); jj_add_error_token(0, 0); int[][] exptokseq = new int[jjExpentries.size()][]; for (int i = 0; i < jjExpentries.size(); i++) { exptokseq[i] = jjExpentries.get(i); } return new ParseException(token, exptokseq, TOKEN_IMAGE); } /** * Enable tracing. */ final public void enable_tracing() { } /** * Disable tracing. */ final public void disable_tracing() { } private void jj_rescan_token() { jjRescan = true; for (int i = 0; i < 7; i++) { try { JJCalls p = jj2Rtns[i]; do { if (p.gen > jjGen) { jjLa = p.arg; jjLastpos = jjScanpos = p.first; switch (i) { case 0: jj_3_1(); break; case 1: jj_3_2(); break; case 2: jj_3_3(); break; case 3: jj_3_4(); break; case 4: jj_3_5(); break; case 5: jj_3_6(); break; case 6: jj_3_7(); break; } } p = p.next; } while (p != null); } catch (LookaheadSuccess ls) { } } jjRescan = false; } private void jj_save(int index, int xla) { JJCalls p = jj2Rtns[index]; while (p.gen > jjGen) { if (p.next == null) { p = p.next = new JJCalls(); break; } p = p.next; } p.gen = jjGen + xla - jjLa; p.first = token; p.arg = xla; } static final class JJCalls { int gen; Token first; int arg; JJCalls next; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * This file was taken from ActiveMQ activemq-client/src/main/grammar/SelectorParser.jj. * * There are some modifications: * 1. Convert string expressions default; * 2. HEX_LITERAL and OCTAL_LITERAL were removed; * 3. LIKE, ESCAPE, XPATH and XQUERY were removed; * 4. Computation expressions were removed; */ // ---------------------------------------------------------------------------- // OPTIONS // ---------------------------------------------------------------------------- options { STATIC = false; UNICODE_INPUT = true; //ERROR_REPORTING = false; } // ---------------------------------------------------------------------------- // PARSER // ---------------------------------------------------------------------------- PARSER_BEGIN(SelectorParser) package org.apache.rocketmq.filter.parser; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.rocketmq.filter.expression.BooleanExpression; import org.apache.rocketmq.filter.expression.ComparisonExpression; import org.apache.rocketmq.filter.expression.ConstantExpression; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.LogicExpression; import org.apache.rocketmq.filter.expression.MQFilterException; import org.apache.rocketmq.filter.expression.PropertyExpression; import org.apache.rocketmq.filter.expression.UnaryExpression; import java.io.StringReader; import java.util.ArrayList; /** * JMS Selector Parser generated by JavaCC * * Do not edit this .java file directly - it is autogenerated from SelectorParser.jj */ public class SelectorParser { private static final Cache PARSE_CACHE = CacheBuilder.newBuilder().maximumSize(100).build(); // private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = "convert_string_expressions:"; public static BooleanExpression parse(String sql) throws MQFilterException { // sql = "("+sql+")"; Object result = PARSE_CACHE.getIfPresent(sql); if (result instanceof MQFilterException) { throw (MQFilterException) result; } else if (result instanceof BooleanExpression) { return (BooleanExpression) result; } else { // boolean convertStringExpressions = false; // if( sql.startsWith(CONVERT_STRING_EXPRESSIONS_PREFIX)) { // convertStringExpressions = true; // sql = sql.substring(CONVERT_STRING_EXPRESSIONS_PREFIX.length()); // } // if( convertStringExpressions ) { // ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true); // } ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true); try { BooleanExpression e = new SelectorParser(sql).parse(); PARSE_CACHE.put(sql, e); return e; } catch (MQFilterException t) { PARSE_CACHE.put(sql, t); throw t; } finally { ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove(); // if( convertStringExpressions ) { // ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove(); // } } } } public static void clearCache() { PARSE_CACHE.cleanUp(); } private String sql; protected SelectorParser(String sql) { this(new StringReader(sql)); this.sql = sql; } protected BooleanExpression parse() throws MQFilterException { try { return this.JmsSelector(); } catch (Throwable e) { throw new MQFilterException("Invalid MessageSelector. ", e); } } private BooleanExpression asBooleanExpression(Expression value) throws ParseException { if (value instanceof BooleanExpression) { return (BooleanExpression) value; } if (value instanceof PropertyExpression) { return UnaryExpression.createBooleanCast( value ); } throw new ParseException("Expression will not result in a boolean value: " + value); } } PARSER_END(SelectorParser) // ---------------------------------------------------------------------------- // Tokens // ---------------------------------------------------------------------------- /* White Space */ SPECIAL_TOKEN : { " " | "\t" | "\n" | "\r" | "\f" } /* Comments */ SKIP: { } SKIP: { } /* Reserved Words */ TOKEN [IGNORE_CASE] : { < NOT : "NOT"> | < AND : "AND"> | < OR : "OR"> | < BETWEEN : "BETWEEN"> | < IN : "IN"> | < TRUE : "TRUE" > | < FALSE : "FALSE" > | < NULL : "NULL" > | < IS : "IS" > | < CONTAINS : "CONTAINS"> | < STARTSWITH : "STARTSWITH"> | < ENDSWITH : "ENDSWITH"> } /* Literals */ TOKEN [IGNORE_CASE] : { < DECIMAL_LITERAL: "0" | ["1"-"9"] (["0"-"9"])* (["l","L"])? > | < FLOATING_POINT_LITERAL: (["0"-"9"])+ "." (["0"-"9"])* ()? // matches: 5.5 or 5. or 5.5E10 or 5.E10 | "." (["0"-"9"])+ ()? // matches: .5 or .5E10 | (["0"-"9"])+ // matches: 5E10 > | < #EXPONENT: "E" (["+","-"])? (["0"-"9"])+ > | < STRING_LITERAL: "'" ( ("''") | ~["'"] )* "'" > } TOKEN [IGNORE_CASE] : { < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* > } // ---------------------------------------------------------------------------- // Grammar // ---------------------------------------------------------------------------- BooleanExpression JmsSelector() : { Expression left=null; } { ( left = orExpression() ) { return asBooleanExpression(left); } } Expression orExpression() : { Expression left; Expression right; } { ( left = andExpression() ( right = andExpression() { left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right)); } )* ) { return left; } } Expression andExpression() : { Expression left; Expression right; } { ( left = equalityExpression() ( right = equalityExpression() { left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right)); } )* ) { return left; } } Expression equalityExpression() : { Expression left; Expression right; } { ( left = comparisonExpression() ( "=" right = comparisonExpression() { left = ComparisonExpression.createEqual(left, right); } | "<>" right = comparisonExpression() { left = ComparisonExpression.createNotEqual(left, right); } | LOOKAHEAD(2) { left = ComparisonExpression.createIsNull(left); } | { left = ComparisonExpression.createIsNotNull(left); } )* ) { return left; } } Expression comparisonExpression() : { Expression left; Expression right; Expression low; Expression high; String t, u; boolean not; ArrayList list; } { ( left = unaryExpr() ( ">" right = unaryExpr() { left = ComparisonExpression.createGreaterThan(left, right); } | ">=" right = unaryExpr() { left = ComparisonExpression.createGreaterThanEqual(left, right); } | "<" right = unaryExpr() { left = ComparisonExpression.createLessThan(left, right); } | "<=" right = unaryExpr() { left = ComparisonExpression.createLessThanEqual(left, right); } | t = stringLitteral() { left = ComparisonExpression.createContains(left, t); } | LOOKAHEAD(2) t = stringLitteral() { left = ComparisonExpression.createNotContains(left, t); } | t = stringLitteral() { left = ComparisonExpression.createStartsWith(left, t); } | LOOKAHEAD(2) t = stringLitteral() { left = ComparisonExpression.createNotStartsWith(left, t); } | t = stringLitteral() { left = ComparisonExpression.createEndsWith(left, t); } | LOOKAHEAD(2) t = stringLitteral() { left = ComparisonExpression.createNotEndsWith(left, t); } | low = unaryExpr() high = unaryExpr() { left = ComparisonExpression.createBetween(left, low, high); } | LOOKAHEAD(2) low = unaryExpr() high = unaryExpr() { left = ComparisonExpression.createNotBetween(left, low, high); } | "(" t = stringLitteral() { list = new ArrayList(); list.add( t ); } ( "," t = stringLitteral() { list.add( t ); } )* ")" { left = ComparisonExpression.createInFilter(left, list); } | LOOKAHEAD(2) "(" t = stringLitteral() { list = new ArrayList(); list.add( t ); } ( "," t = stringLitteral() { list.add( t ); } )* ")" { left = ComparisonExpression.createNotInFilter(left, list); } )* ) { return left; } } Expression unaryExpr() : { String s=null; Expression left=null; } { ( LOOKAHEAD( "+" unaryExpr() ) "+" left=unaryExpr() | "-" left=unaryExpr() { left = UnaryExpression.createNegate(left); } | left=unaryExpr() { left = UnaryExpression.createNOT( asBooleanExpression(left) ); } | left = primaryExpr() ) { return left; } } Expression primaryExpr() : { Expression left=null; } { ( left = literal() | left = variable() | "(" left = orExpression() ")" ) { return left; } } ConstantExpression literal() : { Token t; String s; ConstantExpression left=null; } { ( ( s = stringLitteral() { left = new ConstantExpression(s); } ) | ( t = { left = ConstantExpression.createFromDecimal(t.image); } ) | ( t = { left = ConstantExpression.createFloat(t.image); } ) | ( { left = ConstantExpression.TRUE; } ) | ( { left = ConstantExpression.FALSE; } ) | ( { left = ConstantExpression.NULL; } ) ) { return left; } } String stringLitteral() : { Token t; StringBuffer rc = new StringBuffer(); boolean first=true; } { t = { // Decode the sting value. String image = t.image; for( int i=1; i < image.length()-1; i++ ) { char c = image.charAt(i); if( c == '\'' ) i++; rc.append(c); } return rc.toString(); } } PropertyExpression variable() : { Token t; PropertyExpression left=null; } { ( t = { left = new PropertyExpression(t.image); } ) { return left; } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. SelectorParserConstants.java */ package org.apache.rocketmq.filter.parser; /** * Token literal values and constants. * Generated by org.javacc.parser.OtherFilesGen#start() */ public interface SelectorParserConstants { /** * End of File. */ int EOF = 0; /** * RegularExpression Id. */ int LINE_COMMENT = 6; /** * RegularExpression Id. */ int BLOCK_COMMENT = 7; /** * RegularExpression Id. */ int NOT = 8; /** * RegularExpression Id. */ int AND = 9; /** * RegularExpression Id. */ int OR = 10; /** * RegularExpression Id. */ int BETWEEN = 11; /** * RegularExpression Id. */ int IN = 12; /** * RegularExpression Id. */ int TRUE = 13; /** * RegularExpression Id. */ int FALSE = 14; /** * RegularExpression Id. */ int NULL = 15; /** * RegularExpression Id. */ int IS = 16; /** * RegularExpression Id. */ int CONTAINS = 17; /** * RegularExpression Id. */ int STARTSWITH = 18; /** * RegularExpression Id. */ int ENDSWITH = 19; /** * RegularExpression Id. */ int DECIMAL_LITERAL = 20; /** * RegularExpression Id. */ int FLOATING_POINT_LITERAL = 21; /** * RegularExpression Id. */ int EXPONENT = 22; /** * RegularExpression Id. */ int STRING_LITERAL = 23; /** * RegularExpression Id. */ int ID = 24; /** * Lexical state. */ int DEFAULT = 0; /** * Literal token values. */ String[] TOKEN_IMAGE = { "", "\" \"", "\"\\t\"", "\"\\n\"", "\"\\r\"", "\"\\f\"", "", "", "\"NOT\"", "\"AND\"", "\"OR\"", "\"BETWEEN\"", "\"IN\"", "\"TRUE\"", "\"FALSE\"", "\"NULL\"", "\"IS\"", "\"CONTAINS\"", "\"STARTSWITH\"", "\"ENDSWITH\"", "", "", "", "", "", "\"=\"", "\"<>\"", "\">\"", "\">=\"", "\"<\"", "\"<=\"", "\"(\"", "\",\"", "\")\"", "\"+\"", "\"-\"", }; } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. SelectorParserTokenManager.java */ package org.apache.rocketmq.filter.parser; /** * Token Manager. */ public class SelectorParserTokenManager implements SelectorParserConstants { /** * Debug output. */ public java.io.PrintStream debugStream = System.out; /** * Set debug output. */ public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } private int jjStopAtPos(int pos, int kind) { jjmatchedKind = kind; jjmatchedPos = pos; return pos + 1; } private int jjMoveStringLiteralDfa0_0() { switch (curChar) { case 9: jjmatchedKind = 2; return jjMoveNfa_0(5, 0); case 10: jjmatchedKind = 3; return jjMoveNfa_0(5, 0); case 12: jjmatchedKind = 5; return jjMoveNfa_0(5, 0); case 13: jjmatchedKind = 4; return jjMoveNfa_0(5, 0); case 32: jjmatchedKind = 1; return jjMoveNfa_0(5, 0); case 40: jjmatchedKind = 31; return jjMoveNfa_0(5, 0); case 41: jjmatchedKind = 33; return jjMoveNfa_0(5, 0); case 43: jjmatchedKind = 34; return jjMoveNfa_0(5, 0); case 44: jjmatchedKind = 32; return jjMoveNfa_0(5, 0); case 45: jjmatchedKind = 35; return jjMoveNfa_0(5, 0); case 60: jjmatchedKind = 29; return jjMoveStringLiteralDfa1_0(0x44000000L); case 61: jjmatchedKind = 25; return jjMoveNfa_0(5, 0); case 62: jjmatchedKind = 27; return jjMoveStringLiteralDfa1_0(0x10000000L); case 65: return jjMoveStringLiteralDfa1_0(0x200L); case 66: return jjMoveStringLiteralDfa1_0(0x800L); case 67: return jjMoveStringLiteralDfa1_0(0x20000L); case 69: return jjMoveStringLiteralDfa1_0(0x80000L); case 70: return jjMoveStringLiteralDfa1_0(0x4000L); case 73: return jjMoveStringLiteralDfa1_0(0x11000L); case 78: return jjMoveStringLiteralDfa1_0(0x8100L); case 79: return jjMoveStringLiteralDfa1_0(0x400L); case 83: return jjMoveStringLiteralDfa1_0(0x40000L); case 84: return jjMoveStringLiteralDfa1_0(0x2000L); case 97: return jjMoveStringLiteralDfa1_0(0x200L); case 98: return jjMoveStringLiteralDfa1_0(0x800L); case 99: return jjMoveStringLiteralDfa1_0(0x20000L); case 101: return jjMoveStringLiteralDfa1_0(0x80000L); case 102: return jjMoveStringLiteralDfa1_0(0x4000L); case 105: return jjMoveStringLiteralDfa1_0(0x11000L); case 110: return jjMoveStringLiteralDfa1_0(0x8100L); case 111: return jjMoveStringLiteralDfa1_0(0x400L); case 115: return jjMoveStringLiteralDfa1_0(0x40000L); case 116: return jjMoveStringLiteralDfa1_0(0x2000L); default: return jjMoveNfa_0(5, 0); } } private int jjMoveStringLiteralDfa1_0(long active0) { try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 0); } switch (curChar) { case 61: if ((active0 & 0x10000000L) != 0L) { jjmatchedKind = 28; jjmatchedPos = 1; } else if ((active0 & 0x40000000L) != 0L) { jjmatchedKind = 30; jjmatchedPos = 1; } break; case 62: if ((active0 & 0x4000000L) != 0L) { jjmatchedKind = 26; jjmatchedPos = 1; } break; case 65: return jjMoveStringLiteralDfa2_0(active0, 0x4000L); case 69: return jjMoveStringLiteralDfa2_0(active0, 0x800L); case 78: if ((active0 & 0x1000L) != 0L) { jjmatchedKind = 12; jjmatchedPos = 1; } return jjMoveStringLiteralDfa2_0(active0, 0x80200L); case 79: return jjMoveStringLiteralDfa2_0(active0, 0x20100L); case 82: if ((active0 & 0x400L) != 0L) { jjmatchedKind = 10; jjmatchedPos = 1; } return jjMoveStringLiteralDfa2_0(active0, 0x2000L); case 83: if ((active0 & 0x10000L) != 0L) { jjmatchedKind = 16; jjmatchedPos = 1; } break; case 84: return jjMoveStringLiteralDfa2_0(active0, 0x40000L); case 85: return jjMoveStringLiteralDfa2_0(active0, 0x8000L); case 97: return jjMoveStringLiteralDfa2_0(active0, 0x4000L); case 101: return jjMoveStringLiteralDfa2_0(active0, 0x800L); case 110: if ((active0 & 0x1000L) != 0L) { jjmatchedKind = 12; jjmatchedPos = 1; } return jjMoveStringLiteralDfa2_0(active0, 0x80200L); case 111: return jjMoveStringLiteralDfa2_0(active0, 0x20100L); case 114: if ((active0 & 0x400L) != 0L) { jjmatchedKind = 10; jjmatchedPos = 1; } return jjMoveStringLiteralDfa2_0(active0, 0x2000L); case 115: if ((active0 & 0x10000L) != 0L) { jjmatchedKind = 16; jjmatchedPos = 1; } break; case 116: return jjMoveStringLiteralDfa2_0(active0, 0x40000L); case 117: return jjMoveStringLiteralDfa2_0(active0, 0x8000L); default: break; } return jjMoveNfa_0(5, 1); } private int jjMoveStringLiteralDfa2_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 1); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 1); } switch (curChar) { case 65: return jjMoveStringLiteralDfa3_0(active0, 0x40000L); case 68: if ((active0 & 0x200L) != 0L) { jjmatchedKind = 9; jjmatchedPos = 2; } return jjMoveStringLiteralDfa3_0(active0, 0x80000L); case 76: return jjMoveStringLiteralDfa3_0(active0, 0xc000L); case 78: return jjMoveStringLiteralDfa3_0(active0, 0x20000L); case 84: if ((active0 & 0x100L) != 0L) { jjmatchedKind = 8; jjmatchedPos = 2; } return jjMoveStringLiteralDfa3_0(active0, 0x800L); case 85: return jjMoveStringLiteralDfa3_0(active0, 0x2000L); case 97: return jjMoveStringLiteralDfa3_0(active0, 0x40000L); case 100: if ((active0 & 0x200L) != 0L) { jjmatchedKind = 9; jjmatchedPos = 2; } return jjMoveStringLiteralDfa3_0(active0, 0x80000L); case 108: return jjMoveStringLiteralDfa3_0(active0, 0xc000L); case 110: return jjMoveStringLiteralDfa3_0(active0, 0x20000L); case 116: if ((active0 & 0x100L) != 0L) { jjmatchedKind = 8; jjmatchedPos = 2; } return jjMoveStringLiteralDfa3_0(active0, 0x800L); case 117: return jjMoveStringLiteralDfa3_0(active0, 0x2000L); default: break; } return jjMoveNfa_0(5, 2); } private int jjMoveStringLiteralDfa3_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 2); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 2); } switch (curChar) { case 69: if ((active0 & 0x2000L) != 0L) { jjmatchedKind = 13; jjmatchedPos = 3; } break; case 76: if ((active0 & 0x8000L) != 0L) { jjmatchedKind = 15; jjmatchedPos = 3; } break; case 82: return jjMoveStringLiteralDfa4_0(active0, 0x40000L); case 83: return jjMoveStringLiteralDfa4_0(active0, 0x84000L); case 84: return jjMoveStringLiteralDfa4_0(active0, 0x20000L); case 87: return jjMoveStringLiteralDfa4_0(active0, 0x800L); case 101: if ((active0 & 0x2000L) != 0L) { jjmatchedKind = 13; jjmatchedPos = 3; } break; case 108: if ((active0 & 0x8000L) != 0L) { jjmatchedKind = 15; jjmatchedPos = 3; } break; case 114: return jjMoveStringLiteralDfa4_0(active0, 0x40000L); case 115: return jjMoveStringLiteralDfa4_0(active0, 0x84000L); case 116: return jjMoveStringLiteralDfa4_0(active0, 0x20000L); case 119: return jjMoveStringLiteralDfa4_0(active0, 0x800L); default: break; } return jjMoveNfa_0(5, 3); } private int jjMoveStringLiteralDfa4_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 3); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 3); } switch (curChar) { case 65: return jjMoveStringLiteralDfa5_0(active0, 0x20000L); case 69: if ((active0 & 0x4000L) != 0L) { jjmatchedKind = 14; jjmatchedPos = 4; } return jjMoveStringLiteralDfa5_0(active0, 0x800L); case 84: return jjMoveStringLiteralDfa5_0(active0, 0x40000L); case 87: return jjMoveStringLiteralDfa5_0(active0, 0x80000L); case 97: return jjMoveStringLiteralDfa5_0(active0, 0x20000L); case 101: if ((active0 & 0x4000L) != 0L) { jjmatchedKind = 14; jjmatchedPos = 4; } return jjMoveStringLiteralDfa5_0(active0, 0x800L); case 116: return jjMoveStringLiteralDfa5_0(active0, 0x40000L); case 119: return jjMoveStringLiteralDfa5_0(active0, 0x80000L); default: break; } return jjMoveNfa_0(5, 4); } private int jjMoveStringLiteralDfa5_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 4); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 4); } switch (curChar) { case 69: return jjMoveStringLiteralDfa6_0(active0, 0x800L); case 73: return jjMoveStringLiteralDfa6_0(active0, 0xa0000L); case 83: return jjMoveStringLiteralDfa6_0(active0, 0x40000L); case 101: return jjMoveStringLiteralDfa6_0(active0, 0x800L); case 105: return jjMoveStringLiteralDfa6_0(active0, 0xa0000L); case 115: return jjMoveStringLiteralDfa6_0(active0, 0x40000L); default: break; } return jjMoveNfa_0(5, 5); } private int jjMoveStringLiteralDfa6_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 5); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 5); } switch (curChar) { case 78: if ((active0 & 0x800L) != 0L) { jjmatchedKind = 11; jjmatchedPos = 6; } return jjMoveStringLiteralDfa7_0(active0, 0x20000L); case 84: return jjMoveStringLiteralDfa7_0(active0, 0x80000L); case 87: return jjMoveStringLiteralDfa7_0(active0, 0x40000L); case 110: if ((active0 & 0x800L) != 0L) { jjmatchedKind = 11; jjmatchedPos = 6; } return jjMoveStringLiteralDfa7_0(active0, 0x20000L); case 116: return jjMoveStringLiteralDfa7_0(active0, 0x80000L); case 119: return jjMoveStringLiteralDfa7_0(active0, 0x40000L); default: break; } return jjMoveNfa_0(5, 6); } private int jjMoveStringLiteralDfa7_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 6); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 6); } switch (curChar) { case 72: if ((active0 & 0x80000L) != 0L) { jjmatchedKind = 19; jjmatchedPos = 7; } break; case 73: return jjMoveStringLiteralDfa8_0(active0, 0x40000L); case 83: if ((active0 & 0x20000L) != 0L) { jjmatchedKind = 17; jjmatchedPos = 7; } break; case 104: if ((active0 & 0x80000L) != 0L) { jjmatchedKind = 19; jjmatchedPos = 7; } break; case 105: return jjMoveStringLiteralDfa8_0(active0, 0x40000L); case 115: if ((active0 & 0x20000L) != 0L) { jjmatchedKind = 17; jjmatchedPos = 7; } break; default: break; } return jjMoveNfa_0(5, 7); } private int jjMoveStringLiteralDfa8_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 7); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 7); } switch (curChar) { case 84: return jjMoveStringLiteralDfa9_0(active0, 0x40000L); case 116: return jjMoveStringLiteralDfa9_0(active0, 0x40000L); default: break; } return jjMoveNfa_0(5, 8); } private int jjMoveStringLiteralDfa9_0(long old0, long active0) { if (((active0 &= old0)) == 0L) return jjMoveNfa_0(5, 8); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { return jjMoveNfa_0(5, 8); } switch (curChar) { case 72: if ((active0 & 0x40000L) != 0L) { jjmatchedKind = 18; jjmatchedPos = 9; } break; case 104: if ((active0 & 0x40000L) != 0L) { jjmatchedKind = 18; jjmatchedPos = 9; } break; default: break; } return jjMoveNfa_0(5, 9); } static final long[] JJ_BIT_VEC_0 = { 0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL }; static final long[] JJ_BIT_VEC_2 = { 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL }; private int jjMoveNfa_0(int startState, int curPos) { int strKind = jjmatchedKind; int strPos = jjmatchedPos; int seenUpto; inputStream.backup(seenUpto = curPos + 1); try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { throw new Error("Internal Error"); } curPos = 0; int startsAt = 0; jjnewStateCnt = 40; int i = 1; jjstateSet[0] = startState; int kind = 0x7fffffff; for (; ; ) { if (++jjround == 0x7fffffff) ReInitRounds(); if (curChar < 64) { long l = 1L << curChar; do { switch (jjstateSet[--i]) { case 5: if ((0x3ff000000000000L & l) != 0L) jjCheckNAddStates(0, 3); else if (curChar == 36) { if (kind > 24) kind = 24; jjCheckNAdd(28); } else if (curChar == 39) jjCheckNAddStates(4, 6); else if (curChar == 46) jjCheckNAdd(18); else if (curChar == 47) jjstateSet[jjnewStateCnt++] = 6; else if (curChar == 45) jjstateSet[jjnewStateCnt++] = 0; if ((0x3fe000000000000L & l) != 0L) { if (kind > 20) kind = 20; jjCheckNAddTwoStates(15, 16); } else if (curChar == 48) { if (kind > 20) kind = 20; } break; case 0: if (curChar == 45) jjCheckNAddStates(7, 9); break; case 1: if ((0xffffffffffffdbffL & l) != 0L) jjCheckNAddStates(7, 9); break; case 2: if ((0x2400L & l) != 0L && kind > 6) kind = 6; break; case 3: if (curChar == 10 && kind > 6) kind = 6; break; case 4: if (curChar == 13) jjstateSet[jjnewStateCnt++] = 3; break; case 6: if (curChar == 42) jjCheckNAddTwoStates(7, 8); break; case 7: if ((0xfffffbffffffffffL & l) != 0L) jjCheckNAddTwoStates(7, 8); break; case 8: if (curChar == 42) jjCheckNAddStates(10, 12); break; case 9: if ((0xffff7bffffffffffL & l) != 0L) jjCheckNAddTwoStates(10, 8); break; case 10: if ((0xfffffbffffffffffL & l) != 0L) jjCheckNAddTwoStates(10, 8); break; case 11: if (curChar == 47 && kind > 7) kind = 7; break; case 12: if (curChar == 47) jjstateSet[jjnewStateCnt++] = 6; break; case 13: if (curChar == 48 && kind > 20) kind = 20; break; case 14: if ((0x3fe000000000000L & l) == 0L) break; if (kind > 20) kind = 20; jjCheckNAddTwoStates(15, 16); break; case 15: if ((0x3ff000000000000L & l) == 0L) break; if (kind > 20) kind = 20; jjCheckNAddTwoStates(15, 16); break; case 17: if (curChar == 46) jjCheckNAdd(18); break; case 18: if ((0x3ff000000000000L & l) == 0L) break; if (kind > 21) kind = 21; jjCheckNAddTwoStates(18, 19); break; case 20: if ((0x280000000000L & l) != 0L) jjCheckNAdd(21); break; case 21: if ((0x3ff000000000000L & l) == 0L) break; if (kind > 21) kind = 21; jjCheckNAdd(21); break; case 22: case 23: if (curChar == 39) jjCheckNAddStates(4, 6); break; case 24: if (curChar == 39) jjstateSet[jjnewStateCnt++] = 23; break; case 25: if ((0xffffff7fffffffffL & l) != 0L) jjCheckNAddStates(4, 6); break; case 26: if (curChar == 39 && kind > 23) kind = 23; break; case 27: if (curChar != 36) break; if (kind > 24) kind = 24; jjCheckNAdd(28); break; case 28: if ((0x3ff001000000000L & l) == 0L) break; if (kind > 24) kind = 24; jjCheckNAdd(28); break; case 29: if ((0x3ff000000000000L & l) != 0L) jjCheckNAddStates(0, 3); break; case 30: if ((0x3ff000000000000L & l) != 0L) jjCheckNAddTwoStates(30, 31); break; case 31: if (curChar != 46) break; if (kind > 21) kind = 21; jjCheckNAddTwoStates(32, 33); break; case 32: if ((0x3ff000000000000L & l) == 0L) break; if (kind > 21) kind = 21; jjCheckNAddTwoStates(32, 33); break; case 34: if ((0x280000000000L & l) != 0L) jjCheckNAdd(35); break; case 35: if ((0x3ff000000000000L & l) == 0L) break; if (kind > 21) kind = 21; jjCheckNAdd(35); break; case 36: if ((0x3ff000000000000L & l) != 0L) jjCheckNAddTwoStates(36, 37); break; case 38: if ((0x280000000000L & l) != 0L) jjCheckNAdd(39); break; case 39: if ((0x3ff000000000000L & l) == 0L) break; if (kind > 21) kind = 21; jjCheckNAdd(39); break; default: break; } } while (i != startsAt); } else if (curChar < 128) { long l = 1L << (curChar & 077); do { switch (jjstateSet[--i]) { case 5: case 28: if ((0x7fffffe87fffffeL & l) == 0L) break; if (kind > 24) kind = 24; jjCheckNAdd(28); break; case 1: jjAddStates(7, 9); break; case 7: jjCheckNAddTwoStates(7, 8); break; case 9: case 10: jjCheckNAddTwoStates(10, 8); break; case 16: if ((0x100000001000L & l) != 0L && kind > 20) kind = 20; break; case 19: if ((0x2000000020L & l) != 0L) jjAddStates(13, 14); break; case 25: jjAddStates(4, 6); break; case 33: if ((0x2000000020L & l) != 0L) jjAddStates(15, 16); break; case 37: if ((0x2000000020L & l) != 0L) jjAddStates(17, 18); break; default: break; } } while (i != startsAt); } else { int hiByte = (int) (curChar >> 8); int i1 = hiByte >> 6; long l1 = 1L << (hiByte & 077); int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); do { switch (jjstateSet[--i]) { case 1: if (jjCanMove_0(hiByte, i1, i2, l1, l2)) jjAddStates(7, 9); break; case 7: if (jjCanMove_0(hiByte, i1, i2, l1, l2)) jjCheckNAddTwoStates(7, 8); break; case 9: case 10: if (jjCanMove_0(hiByte, i1, i2, l1, l2)) jjCheckNAddTwoStates(10, 8); break; case 25: if (jjCanMove_0(hiByte, i1, i2, l1, l2)) jjAddStates(4, 6); break; default: break; } } while (i != startsAt); } if (kind != 0x7fffffff) { jjmatchedKind = kind; jjmatchedPos = curPos; kind = 0x7fffffff; } ++curPos; if ((i = jjnewStateCnt) == (startsAt = 40 - (jjnewStateCnt = startsAt))) break; try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { break; } } if (jjmatchedPos > strPos) return curPos; int toRet = Math.max(curPos, seenUpto); if (curPos < toRet) for (i = toRet - Math.min(curPos, seenUpto); i-- > 0; ) try { curChar = inputStream.readChar(); } catch (java.io.IOException e) { throw new Error("Internal Error : Please send a bug report."); } if (jjmatchedPos < strPos) { jjmatchedKind = strKind; jjmatchedPos = strPos; } else if (jjmatchedPos == strPos && jjmatchedKind > strKind) jjmatchedKind = strKind; return toRet; } static final int[] JJ_NEXT_STATES = { 30, 31, 36, 37, 24, 25, 26, 1, 2, 4, 8, 9, 11, 20, 21, 34, 35, 38, 39, }; private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, long l2) { switch (hiByte) { case 0: return (JJ_BIT_VEC_2[i2] & l2) != 0L; default: if ((JJ_BIT_VEC_0[i1] & l1) != 0L) return true; return false; } } /** * Token literal values. */ public static final String[] JJ_STR_LITERAL_IMAGES = { "", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "\75", "\74\76", "\76", "\76\75", "\74", "\74\75", "\50", "\54", "\51", "\53", "\55",}; /** * Lexer state names. */ public static final String[] LEX_STATE_NAMES = { "DEFAULT", }; static final long[] JJ_TO_TOKEN = { 0xfffbfff01L, }; static final long[] JJ_TO_SKIP = { 0xfeL, }; static final long[] JJ_TO_SPECIAL = { 0x3eL, }; protected SimpleCharStream inputStream; private final int[] jjrounds = new int[40]; private final int[] jjstateSet = new int[80]; protected char curChar; /** * Constructor. */ public SelectorParserTokenManager(SimpleCharStream stream) { if (SimpleCharStream.STATIC_FLAG) throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); inputStream = stream; } /** * Constructor. */ public SelectorParserTokenManager(SimpleCharStream stream, int lexState) { this(stream); SwitchTo(lexState); } /** * Reinitialise parser. */ public void ReInit(SimpleCharStream stream) { jjmatchedPos = jjnewStateCnt = 0; curLexState = defaultLexState; inputStream = stream; ReInitRounds(); } private void ReInitRounds() { int i; jjround = 0x80000001; for (i = 40; i-- > 0; ) jjrounds[i] = 0x80000000; } /** * Reinitialise parser. */ public void ReInit(SimpleCharStream stream, int lexState) { ReInit(stream); SwitchTo(lexState); } /** * Switch to specified lex state. */ public void SwitchTo(int lexState) { if (lexState >= 1 || lexState < 0) throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); else curLexState = lexState; } protected Token jjFillToken() { final Token t; final String curTokenImage; final int beginLine; final int endLine; final int beginColumn; final int endColumn; String im = JJ_STR_LITERAL_IMAGES[jjmatchedKind]; curTokenImage = (im == null) ? inputStream.GetImage() : im; beginLine = inputStream.getBeginLine(); beginColumn = inputStream.getBeginColumn(); endLine = inputStream.getEndLine(); endColumn = inputStream.getEndColumn(); t = Token.newToken(jjmatchedKind, curTokenImage); t.beginLine = beginLine; t.endLine = endLine; t.beginColumn = beginColumn; t.endColumn = endColumn; return t; } int curLexState = 0; int defaultLexState = 0; int jjnewStateCnt; int jjround; int jjmatchedPos; int jjmatchedKind; /** * Get the next Token. */ public Token getNextToken() { Token specialToken = null; Token matchedToken; int curPos = 0; EOFLoop: for (; ; ) { try { curChar = inputStream.BeginToken(); } catch (java.io.IOException e) { jjmatchedKind = 0; matchedToken = jjFillToken(); matchedToken.specialToken = specialToken; return matchedToken; } jjmatchedKind = 0x7fffffff; jjmatchedPos = 0; curPos = jjMoveStringLiteralDfa0_0(); if (jjmatchedKind != 0x7fffffff) { if (jjmatchedPos + 1 < curPos) inputStream.backup(curPos - jjmatchedPos - 1); if ((JJ_TO_TOKEN[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) { matchedToken = jjFillToken(); matchedToken.specialToken = specialToken; return matchedToken; } else { if ((JJ_TO_SPECIAL[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) { matchedToken = jjFillToken(); if (specialToken == null) specialToken = matchedToken; else { matchedToken.specialToken = specialToken; specialToken = specialToken.next = matchedToken; } } continue EOFLoop; } } int errorLine = inputStream.getEndLine(); int errorColumn = inputStream.getEndColumn(); String errorAfter = null; boolean eofSeen = false; try { inputStream.readChar(); inputStream.backup(1); } catch (java.io.IOException e1) { eofSeen = true; errorAfter = curPos <= 1 ? "" : inputStream.GetImage(); if (curChar == '\n' || curChar == '\r') { errorLine++; errorColumn = 0; } else errorColumn++; } if (!eofSeen) { inputStream.backup(1); errorAfter = curPos <= 1 ? "" : inputStream.GetImage(); } throw new TokenMgrError(eofSeen, curLexState, errorLine, errorColumn, errorAfter, curChar, TokenMgrError.LEXICAL_ERROR); } } private void jjCheckNAdd(int state) { if (jjrounds[state] != jjround) { jjstateSet[jjnewStateCnt++] = state; jjrounds[state] = jjround; } } private void jjAddStates(int start, int end) { do { jjstateSet[jjnewStateCnt++] = JJ_NEXT_STATES[start]; } while (start++ != end); } private void jjCheckNAddTwoStates(int state1, int state2) { jjCheckNAdd(state1); jjCheckNAdd(state2); } private void jjCheckNAddStates(int start, int end) { do { jjCheckNAdd(JJ_NEXT_STATES[start]); } while (start++ != end); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ /* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.rocketmq.filter.parser; import java.nio.charset.StandardCharsets; /** * An implementation of interface CharStream, where the stream is assumed to * contain only ASCII characters (without unicode processing). */ public class SimpleCharStream { /** * Whether parser is static. */ public static final boolean STATIC_FLAG = false; int bufsize; int available; int tokenBegin; /** * Position in buffer. */ public int bufpos = -1; protected int[] bufline; protected int[] bufcolumn; protected int column = 0; protected int line = 1; protected boolean prevCharIsCR = false; protected boolean prevCharIsLF = false; protected java.io.Reader inputStream; protected char[] buffer; protected int maxNextCharInd = 0; protected int inBuf = 0; protected int tabSize = 8; protected void setTabSize(int i) { tabSize = i; } protected int getTabSize(int i) { return tabSize; } protected void ExpandBuff(boolean wrapAround) { char[] newbuffer = new char[bufsize + 2048]; int[] newbufline = new int[bufsize + 2048]; int[] newbufcolumn = new int[bufsize + 2048]; try { if (wrapAround) { System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); buffer = newbuffer; System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); bufline = newbufline; System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); bufcolumn = newbufcolumn; maxNextCharInd = bufpos += bufsize - tokenBegin; } else { System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); buffer = newbuffer; System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); bufline = newbufline; System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); bufcolumn = newbufcolumn; maxNextCharInd = bufpos -= tokenBegin; } } catch (Throwable t) { throw new Error(t.getMessage()); } bufsize += 2048; available = bufsize; tokenBegin = 0; } protected void FillBuff() throws java.io.IOException { if (maxNextCharInd == available) { if (available == bufsize) { if (tokenBegin > 2048) { bufpos = maxNextCharInd = 0; available = tokenBegin; } else if (tokenBegin < 0) bufpos = maxNextCharInd = 0; else ExpandBuff(false); } else if (available > tokenBegin) available = bufsize; else if ((tokenBegin - available) < 2048) ExpandBuff(true); else available = tokenBegin; } int i; try { if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) { inputStream.close(); throw new java.io.IOException(); } else maxNextCharInd += i; return; } catch (java.io.IOException e) { --bufpos; backup(0); if (tokenBegin == -1) tokenBegin = bufpos; throw e; } } /** * Start. */ public char BeginToken() throws java.io.IOException { tokenBegin = -1; char c = readChar(); tokenBegin = bufpos; return c; } protected void UpdateLineColumn(char c) { column++; if (prevCharIsLF) { prevCharIsLF = false; line += column = 1; } else if (prevCharIsCR) { prevCharIsCR = false; if (c == '\n') { prevCharIsLF = true; } else line += column = 1; } switch (c) { case '\r': prevCharIsCR = true; break; case '\n': prevCharIsLF = true; break; case '\t': column--; column += tabSize - (column % tabSize); break; default: break; } bufline[bufpos] = line; bufcolumn[bufpos] = column; } /** * Read a character. */ public char readChar() throws java.io.IOException { if (inBuf > 0) { --inBuf; if (++bufpos == bufsize) bufpos = 0; return buffer[bufpos]; } if (++bufpos >= maxNextCharInd) FillBuff(); char c = buffer[bufpos]; UpdateLineColumn(c); return c; } @Deprecated /** * @deprecated * @see #getEndColumn */ public int getColumn() { return bufcolumn[bufpos]; } @Deprecated /** * @deprecated * @see #getEndLine */ public int getLine() { return bufline[bufpos]; } /** * Get token end column number. */ public int getEndColumn() { return bufcolumn[bufpos]; } /** * Get token end line number. */ public int getEndLine() { return bufline[bufpos]; } /** * Get token beginning column number. */ public int getBeginColumn() { return bufcolumn[tokenBegin]; } /** * Get token beginning line number. */ public int getBeginLine() { return bufline[tokenBegin]; } /** * Backup a number of characters. */ public void backup(int amount) { inBuf += amount; if ((bufpos -= amount) < 0) bufpos += bufsize; } /** * Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { inputStream = dstream; line = startline; column = startcolumn - 1; available = bufsize = buffersize; buffer = new char[buffersize]; bufline = new int[buffersize]; bufcolumn = new int[buffersize]; } /** * Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn) { this(dstream, startline, startcolumn, 4096); } /** * Constructor. */ public SimpleCharStream(java.io.Reader dstream) { this(dstream, 1, 1, 4096); } /** * Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { inputStream = dstream; line = startline; column = startcolumn - 1; if (buffer == null || buffersize != buffer.length) { available = bufsize = buffersize; buffer = new char[buffersize]; bufline = new int[buffersize]; bufcolumn = new int[buffersize]; } prevCharIsLF = prevCharIsCR = false; tokenBegin = inBuf = maxNextCharInd = 0; bufpos = -1; } /** * Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn) { ReInit(dstream, startline, startcolumn, 4096); } /** * Reinitialise. */ public void ReInit(java.io.Reader dstream) { ReInit(dstream, 1, 1, 4096); } /** * Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { this(encoding == null ? new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } /** * Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { this(new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8), startline, startcolumn, buffersize); } /** * Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { this(dstream, encoding, startline, startcolumn, 4096); } /** * Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn) { this(dstream, startline, startcolumn, 4096); } /** * Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { this(dstream, encoding, 1, 1, 4096); } /** * Constructor. */ public SimpleCharStream(java.io.InputStream dstream) { this(dstream, 1, 1, 4096); } /** * Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { ReInit(encoding == null ? new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } /** * Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { ReInit(new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8), startline, startcolumn, buffersize); } /** * Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { ReInit(dstream, encoding, 1, 1, 4096); } /** * Reinitialise. */ public void ReInit(java.io.InputStream dstream) { ReInit(dstream, 1, 1, 4096); } /** * Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { ReInit(dstream, encoding, startline, startcolumn, 4096); } /** * Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn) { ReInit(dstream, startline, startcolumn, 4096); } /** * Get token literal value. */ public String GetImage() { if (bufpos >= tokenBegin) return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); else return new String(buffer, tokenBegin, bufsize - tokenBegin) + new String(buffer, 0, bufpos + 1); } /** * Get the suffix. */ public char[] GetSuffix(int len) { char[] ret = new char[len]; if ((bufpos + 1) >= len) System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); else { System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, len - bufpos - 1); System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); } return ret; } /** * Reset buffer when finished. */ public void Done() { buffer = null; bufline = null; bufcolumn = null; } /** * Method to adjust line and column numbers for the start of a token. */ public void adjustBeginLineColumn(int newLine, int newCol) { int start = tokenBegin; int len; if (bufpos >= tokenBegin) { len = bufpos - tokenBegin + inBuf + 1; } else { len = bufsize - tokenBegin + bufpos + 1 + inBuf; } int i = 0, j = 0, k = 0; int nextColDiff = 0, columnDiff = 0; while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) { bufline[j] = newLine; nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; bufcolumn[j] = newCol + columnDiff; columnDiff = nextColDiff; i++; } if (i < len) { bufline[j] = newLine++; bufcolumn[j] = newCol + columnDiff; while (i++ < len) { if (bufline[j = start % bufsize] != bufline[++start % bufsize]) bufline[j] = newLine++; else bufline[j] = newLine; } } line = bufline[j]; column = bufcolumn[j]; } } /* JavaCC - OriginalChecksum=ea3493f692d4975c1ad70c4a750107d3 (do not edit this line) */ ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ /* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.rocketmq.filter.parser; /** * Describes the input token stream. */ public class Token implements java.io.Serializable { /** * The version identifier for this Serializable class. * Increment only if the serialized form of the * class changes. */ private static final long serialVersionUID = 1L; /** * An integer that describes the kind of this token. This numbering * system is determined by JavaCCParser, and a table of these numbers is * stored in the file ...Constants.java. */ public int kind; /** * The line number of the first character of this Token. */ public int beginLine; /** * The column number of the first character of this Token. */ public int beginColumn; /** * The line number of the last character of this Token. */ public int endLine; /** * The column number of the last character of this Token. */ public int endColumn; /** * The string image of the token. */ public String image; /** * A reference to the next regular (non-special) token from the input * stream. If this is the last token from the input stream, or if the * token manager has not read tokens beyond this one, this field is * set to null. This is true only if this token is also a regular * token. Otherwise, see below for a description of the contents of * this field. */ public Token next; /** * This field is used to access special tokens that occur prior to this * token, but after the immediately preceding regular (non-special) token. * If there are no such special tokens, this field is set to null. * When there are more than one such special token, this field refers * to the last of these special tokens, which in turn refers to the next * previous special token through its specialToken field, and so on * until the first special token (whose specialToken field is null). * The next fields of special tokens refer to other special tokens that * immediately follow it (without an intervening regular token). If there * is no such token, this field is null. */ public Token specialToken; /** * An optional attribute value of the Token. * Tokens which are not used as syntactic sugar will often contain * meaningful values that will be used later on by the compiler or * interpreter. This attribute value is often different from the image. * Any subclass of Token that actually wants to return a non-null value can * override this method as appropriate. */ public Object getValue() { return null; } /** * No-argument constructor */ public Token() { } /** * Constructs a new token for the specified Image. */ public Token(int kind) { this(kind, null); } /** * Constructs a new token for the specified Image and Kind. */ public Token(int kind, String image) { this.kind = kind; this.image = image; } /** * Returns the image. */ public String toString() { return image; } /** * Returns a new Token object, by default. However, if you want, you * can create and return subclass objects based on the value of ofKind. * Simply add the cases to the switch for all those special cases. * For example, if you have a subclass of Token called IDToken that * you want to create if ofKind is ID, simply add something like : *

    * case MyParserConstants.ID : return new IDToken(ofKind, image); *

    * to the following switch statement. Then you can cast matchedToken * variable to the appropriate type and use sit in your lexical actions. */ public static Token newToken(int ofKind, String image) { switch (ofKind) { default: return new Token(ofKind, image); } } public static Token newToken(int ofKind) { return newToken(ofKind, null); } } /* JavaCC - OriginalChecksum=20094f1ccfbf423c6d9e770d6a7a0188 (do not edit this line) */ ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ /* JavaCCOptions: */ package org.apache.rocketmq.filter.parser; /** * Token Manager Error. */ public class TokenMgrError extends Error { /** * The version identifier for this Serializable class. * Increment only if the serialized form of the * class changes. */ private static final long serialVersionUID = 1L; /* * Ordinals for various reasons why an Error of this type can be thrown. */ /** * Lexical error occurred. */ static final int LEXICAL_ERROR = 0; /** * An attempt was made to create a second instance of a static token manager. */ static final int STATIC_LEXER_ERROR = 1; /** * Tried to change to an invalid lexical state. */ static final int INVALID_LEXICAL_STATE = 2; /** * Detected (and bailed out of) an infinite loop in the token manager. */ static final int LOOP_DETECTED = 3; /** * Indicates the reason why the exception is thrown. It will have * one of the above 4 values. */ int errorCode; /** * Replaces unprintable characters by their escaped (or unicode escaped) * equivalents in the given string */ protected static final String addEscapes(String str) { StringBuilder retval = new StringBuilder(); char ch; for (int i = 0; i < str.length(); i++) { switch (str.charAt(i)) { case 0: continue; case '\b': retval.append("\\b"); continue; case '\t': retval.append("\\t"); continue; case '\n': retval.append("\\n"); continue; case '\f': retval.append("\\f"); continue; case '\r': retval.append("\\r"); continue; case '\"': retval.append("\\\""); continue; case '\'': retval.append("\\\'"); continue; case '\\': retval.append("\\\\"); continue; default: if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { String s = "0000" + Integer.toString(ch, 16); retval.append("\\u" + s.substring(s.length() - 4, s.length())); } else { retval.append(ch); } continue; } } return retval.toString(); } /** * Returns a detailed message for the Error when it is thrown by the * token manager to indicate a lexical error. * Parameters : * eofSeen : indicates if EOF caused the lexical error * curLexState : lexical state in which this error occurred * errorLine : line number when the error occurred * errorColumn : column number when the error occurred * errorAfter : prefix that was seen before this error occurred * curchar : the offending character * Note: You can customize the lexical error message by modifying this method. */ protected static String LexicalError(boolean eofSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { return "Lexical error at line " + errorLine + ", column " + errorColumn + ". Encountered: " + (eofSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int) curChar + "), ") + "after : \"" + addEscapes(errorAfter) + "\""; } /** * You can also modify the body of this method to customize your error messages. * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not * of end-users concern, so you can return something like : *

    * "Internal Error : Please file a bug report .... " *

    * from this method for such cases in the release version of your parser. */ @Override public String getMessage() { return super.getMessage(); } /* * Constructors of various flavors follow. */ /** * No arg constructor. */ public TokenMgrError() { } /** * Constructor with message and reason. */ public TokenMgrError(String message, int reason) { super(message); errorCode = reason; } /** * Full Constructor. */ public TokenMgrError(boolean eofSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { this(LexicalError(eofSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); } } /* JavaCC - OriginalChecksum=de79709675790dcbad2e0d728aa630d1 (do not edit this line) */ ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/util/BitsArray.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.util; /** * Wrapper of bytes array, in order to operate single bit easily. */ public class BitsArray implements Cloneable { private byte[] bytes; private int bitLength; public static BitsArray create(int bitLength) { return new BitsArray(bitLength); } public static BitsArray create(byte[] bytes, int bitLength) { return new BitsArray(bytes, bitLength); } public static BitsArray create(byte[] bytes) { return new BitsArray(bytes); } private BitsArray(int bitLength) { this.bitLength = bitLength; // init bytes int temp = bitLength / Byte.SIZE; if (bitLength % Byte.SIZE > 0) { temp++; } bytes = new byte[temp]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) 0x00; } } private BitsArray(byte[] bytes, int bitLength) { if (bytes == null || bytes.length < 1) { throw new IllegalArgumentException("Bytes is empty!"); } if (bitLength < 1) { throw new IllegalArgumentException("Bit is less than 1."); } if (bitLength < bytes.length * Byte.SIZE) { throw new IllegalArgumentException("BitLength is less than bytes.length() * " + Byte.SIZE); } this.bytes = new byte[bytes.length]; System.arraycopy(bytes, 0, this.bytes, 0, this.bytes.length); this.bitLength = bitLength; } private BitsArray(byte[] bytes) { if (bytes == null || bytes.length < 1) { throw new IllegalArgumentException("Bytes is empty!"); } this.bitLength = bytes.length * Byte.SIZE; this.bytes = new byte[bytes.length]; System.arraycopy(bytes, 0, this.bytes, 0, this.bytes.length); } public int bitLength() { return this.bitLength; } public int byteLength() { return this.bytes.length; } public byte[] bytes() { return this.bytes; } public void xor(final BitsArray other) { checkInitialized(this); checkInitialized(other); int minByteLength = Math.min(this.byteLength(), other.byteLength()); for (int i = 0; i < minByteLength; i++) { this.bytes[i] = (byte) (this.bytes[i] ^ other.getByte(i)); } } public void xor(int bitPos, boolean set) { checkBitPosition(bitPos, this); boolean value = getBit(bitPos); if (value ^ set) { setBit(bitPos, true); } else { setBit(bitPos, false); } } public void or(final BitsArray other) { checkInitialized(this); checkInitialized(other); int minByteLength = Math.min(this.byteLength(), other.byteLength()); for (int i = 0; i < minByteLength; i++) { this.bytes[i] = (byte) (this.bytes[i] | other.getByte(i)); } } public void or(int bitPos, boolean set) { checkBitPosition(bitPos, this); if (set) { setBit(bitPos, true); } } public void and(final BitsArray other) { checkInitialized(this); checkInitialized(other); int minByteLength = Math.min(this.byteLength(), other.byteLength()); for (int i = 0; i < minByteLength; i++) { this.bytes[i] = (byte) (this.bytes[i] & other.getByte(i)); } } public void and(int bitPos, boolean set) { checkBitPosition(bitPos, this); if (!set) { setBit(bitPos, false); } } public void not(int bitPos) { checkBitPosition(bitPos, this); setBit(bitPos, !getBit(bitPos)); } public void setBit(int bitPos, boolean set) { checkBitPosition(bitPos, this); int sub = subscript(bitPos); int pos = position(bitPos); if (set) { this.bytes[sub] = (byte) (this.bytes[sub] | pos); } else { this.bytes[sub] = (byte) (this.bytes[sub] & ~pos); } } public void setByte(int bytePos, byte set) { checkBytePosition(bytePos, this); this.bytes[bytePos] = set; } public boolean getBit(int bitPos) { checkBitPosition(bitPos, this); return (this.bytes[subscript(bitPos)] & position(bitPos)) != 0; } public byte getByte(int bytePos) { checkBytePosition(bytePos, this); return this.bytes[bytePos]; } protected int subscript(int bitPos) { return bitPos / Byte.SIZE; } protected int position(int bitPos) { return 1 << bitPos % Byte.SIZE; } protected void checkBytePosition(int bytePos, BitsArray bitsArray) { checkInitialized(bitsArray); if (bytePos > bitsArray.byteLength()) { throw new IllegalArgumentException("BytePos is greater than " + bytes.length); } if (bytePos < 0) { throw new IllegalArgumentException("BytePos is less than 0"); } } protected void checkBitPosition(int bitPos, BitsArray bitsArray) { checkInitialized(bitsArray); if (bitPos > bitsArray.bitLength()) { throw new IllegalArgumentException("BitPos is greater than " + bitLength); } if (bitPos < 0) { throw new IllegalArgumentException("BitPos is less than 0"); } } protected void checkInitialized(BitsArray bitsArray) { if (bitsArray.bytes() == null) { throw new RuntimeException("Not initialized!"); } } public BitsArray clone() { byte[] clone = new byte[this.byteLength()]; System.arraycopy(this.bytes, 0, clone, 0, this.byteLength()); return create(clone, bitLength()); } @Override public String toString() { if (this.bytes == null) { return "null"; } StringBuilder stringBuilder = new StringBuilder(this.bytes.length * Byte.SIZE); for (int i = this.bytes.length - 1; i >= 0; i--) { int j = Byte.SIZE - 1; if (i == this.bytes.length - 1 && this.bitLength % Byte.SIZE > 0) { // not full byte j = this.bitLength % Byte.SIZE; } for (; j >= 0; j--) { byte mask = (byte) (1 << j); if ((this.bytes[i] & mask) == mask) { stringBuilder.append("1"); } else { stringBuilder.append("0"); } } if (i % 8 == 0) { stringBuilder.append("\n"); } } return stringBuilder.toString(); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/util/BloomFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.util; import com.google.common.hash.Hashing; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * Simple implement of bloom filter. */ public class BloomFilter { public static final Charset UTF_8 = StandardCharsets.UTF_8; // as error rate, 10/100 = 0.1 private int f = 10; private int n = 128; // hash function num, by calculation. private int k; // bit count, by calculation. private int m; /** * Create bloom filter by error rate and mapping num. * * @param f error rate * @param n num will mapping to bit */ public static BloomFilter createByFn(int f, int n) { return new BloomFilter(f, n); } /** * Constructor. * * @param f error rate * @param n num will mapping to bit */ private BloomFilter(int f, int n) { if (f < 1 || f >= 100) { throw new IllegalArgumentException("f must be greater or equal than 1 and less than 100"); } if (n < 1) { throw new IllegalArgumentException("n must be greater than 0"); } this.f = f; this.n = n; // set p = e^(-kn/m) // f = (1 - p)^k = e^(kln(1-p)) // when p = 0.5, k = ln2 * (m/n), f = (1/2)^k = (0.618)^(m/n) double errorRate = f / 100.0; this.k = (int) Math.ceil(logMN(0.5, errorRate)); if (this.k < 1) { throw new IllegalArgumentException("Hash function num is less than 1, maybe you should change the value of error rate or bit num!"); } // m >= n*log2(1/f)*log2(e) this.m = (int) Math.ceil(this.n * logMN(2, 1 / errorRate) * logMN(2, Math.E)); // m%8 = 0 this.m = (int) (Byte.SIZE * Math.ceil(this.m / (Byte.SIZE * 1.0))); } /** * Calculate bit positions of {@code str}. *

    * See "Less Hashing, Same Performance: Building a Better Bloom Filter" by Adam Kirsch and Michael * Mitzenmacher. *

    */ public int[] calcBitPositions(String str) { int[] bitPositions = new int[this.k]; long hash64 = Hashing.murmur3_128().hashString(str, UTF_8).asLong(); int hash1 = (int) hash64; int hash2 = (int) (hash64 >>> 32); for (int i = 1; i <= this.k; i++) { int combinedHash = hash1 + (i * hash2); // Flip all the bits if it's negative (guaranteed positive number) if (combinedHash < 0) { combinedHash = ~combinedHash; } bitPositions[i - 1] = combinedHash % this.m; } return bitPositions; } /** * Calculate bit positions of {@code str} to construct {@code BloomFilterData} */ public BloomFilterData generate(String str) { int[] bitPositions = calcBitPositions(str); return new BloomFilterData(bitPositions, this.m); } /** * Calculate bit positions of {@code str}, then set the related {@code bits} positions to 1. */ public void hashTo(String str, BitsArray bits) { hashTo(calcBitPositions(str), bits); } /** * Set the related {@code bits} positions to 1. */ public void hashTo(int[] bitPositions, BitsArray bits) { check(bits); for (int i : bitPositions) { bits.setBit(i, true); } } /** * Extra check: *
  • 1. check {@code filterData} belong to this bloom filter.
  • *

    * Then set the related {@code bits} positions to 1. *

    */ public void hashTo(BloomFilterData filterData, BitsArray bits) { if (!isValid(filterData)) { throw new IllegalArgumentException( String.format("Bloom filter data may not belong to this filter! %s, %s", filterData, this) ); } hashTo(filterData.getBitPos(), bits); } /** * Calculate bit positions of {@code str}, then check all the related {@code bits} positions is 1. * * @return true: all the related {@code bits} positions is 1 */ public boolean isHit(String str, BitsArray bits) { return isHit(calcBitPositions(str), bits); } /** * Check all the related {@code bits} positions is 1. * * @return true: all the related {@code bits} positions is 1 */ public boolean isHit(int[] bitPositions, BitsArray bits) { check(bits); boolean ret = bits.getBit(bitPositions[0]); for (int i = 1; i < bitPositions.length; i++) { ret &= bits.getBit(bitPositions[i]); } return ret; } /** * Check all the related {@code bits} positions is 1. * * @return true: all the related {@code bits} positions is 1 */ public boolean isHit(BloomFilterData filterData, BitsArray bits) { if (!isValid(filterData)) { throw new IllegalArgumentException( String.format("Bloom filter data may not belong to this filter! %s, %s", filterData, this) ); } return isHit(filterData.getBitPos(), bits); } /** * Check whether one of {@code bitPositions} has been occupied. * * @return true: if all positions have been occupied. */ public boolean checkFalseHit(int[] bitPositions, BitsArray bits) { for (int j = 0; j < bitPositions.length; j++) { int pos = bitPositions[j]; // check position of bits has been set. // that mean no one occupy the position. if (!bits.getBit(pos)) { return false; } } return true; } protected void check(BitsArray bits) { if (bits.bitLength() != this.m) { throw new IllegalArgumentException( String.format("Length(%d) of bits in BitsArray is not equal to %d!", bits.bitLength(), this.m) ); } } /** * Check {@code BloomFilterData} is valid, and belong to this bloom filter. *
  • 1. not null
  • *
  • 2. {@link org.apache.rocketmq.filter.util.BloomFilterData#getBitNum} must be equal to {@code m}
  • *
  • 3. {@link org.apache.rocketmq.filter.util.BloomFilterData#getBitPos} is not null
  • *
  • 4. {@link org.apache.rocketmq.filter.util.BloomFilterData#getBitPos}'s length is equal to {@code k}
  • */ public boolean isValid(BloomFilterData filterData) { if (filterData == null || filterData.getBitNum() != this.m || filterData.getBitPos() == null || filterData.getBitPos().length != this.k) { return false; } return true; } /** * error rate. */ public int getF() { return f; } /** * expect mapping num. */ public int getN() { return n; } /** * hash function num. */ public int getK() { return k; } /** * total bit num. */ public int getM() { return m; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BloomFilter)) return false; BloomFilter that = (BloomFilter) o; if (f != that.f) return false; if (k != that.k) return false; if (m != that.m) return false; if (n != that.n) return false; return true; } @Override public int hashCode() { int result = f; result = 31 * result + n; result = 31 * result + k; result = 31 * result + m; return result; } @Override public String toString() { return String.format("f: %d, n: %d, k: %d, m: %d", f, n, k, m); } protected double logMN(double m, double n) { return Math.log(n) / Math.log(m); } } ================================================ FILE: filter/src/main/java/org/apache/rocketmq/filter/util/BloomFilterData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter.util; import java.util.Arrays; /** * Data generated by bloom filter, include: *
  • 1. Bit positions allocated to requester;
  • *
  • 2. Total bit num when allocating;
  • */ public class BloomFilterData { private int[] bitPos; private int bitNum; public BloomFilterData() { } public BloomFilterData(int[] bitPos, int bitNum) { this.bitPos = bitPos; this.bitNum = bitNum; } public int[] getBitPos() { return bitPos; } public int getBitNum() { return bitNum; } public void setBitPos(final int[] bitPos) { this.bitPos = bitPos; } public void setBitNum(final int bitNum) { this.bitNum = bitNum; } @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof BloomFilterData)) return false; final BloomFilterData that = (BloomFilterData) o; if (bitNum != that.bitNum) return false; if (!Arrays.equals(bitPos, that.bitPos)) return false; return true; } @Override public int hashCode() { int result = bitPos != null ? Arrays.hashCode(bitPos) : 0; result = 31 * result + bitNum; return result; } @Override public String toString() { return "BloomFilterData{" + "bitPos=" + Arrays.toString(bitPos) + ", bitNum=" + bitNum + '}'; } } ================================================ FILE: filter/src/test/java/org/apache/rocketmq/filter/BitsArrayTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.filter.util.BitsArray; import org.junit.Test; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; public class BitsArrayTest { BitsArray gen(int bitCount) { BitsArray bitsArray = BitsArray.create(bitCount); for (int i = 0; i < bitCount / Byte.SIZE; i++) { bitsArray.setByte(i, (byte) (new Random(System.currentTimeMillis())).nextInt(0xff)); try { Thread.sleep(2); } catch (InterruptedException e) { } } return bitsArray; } int bitLength = Byte.SIZE; @Test public void testConstructor() { BitsArray bitsArray = BitsArray.create(8); assertThat(bitsArray.byteLength() == 1 && bitsArray.bitLength() == 8).isTrue(); bitsArray = BitsArray.create(9); assertThat(bitsArray.byteLength() == 2 && bitsArray.bitLength() == 9).isTrue(); bitsArray = BitsArray.create(7); assertThat(bitsArray.byteLength() == 1 && bitsArray.bitLength() == 7).isTrue(); } @Test public void testSet() { BitsArray bitsArray = gen(bitLength); BitsArray backUp = bitsArray.clone(); boolean val = bitsArray.getBit(2); bitsArray.setBit(2, !val); bitsArray.xor(backUp); assertThat(bitsArray.getBit(2)).isTrue(); } @Test public void testAndOr() { BitsArray bitsArray = gen(bitLength); boolean val = bitsArray.getBit(2); if (val) { bitsArray.and(2, false); assertThat(!bitsArray.getBit(2)).isTrue(); } else { bitsArray.or(2, true); assertThat(bitsArray.getBit(2)).isTrue(); } } @Test public void testXor() { BitsArray bitsArray = gen(bitLength); boolean val = bitsArray.getBit(2); bitsArray.xor(2, !val); assertThat(bitsArray.getBit(2)).isTrue(); } @Test public void testNot() { BitsArray bitsArray = gen(bitLength); BitsArray backUp = bitsArray.clone(); bitsArray.not(2); bitsArray.xor(backUp); assertThat(bitsArray.getBit(2)).isTrue(); } @Test public void testOr() { BitsArray b1 = BitsArray.create(new byte[] {(byte) 0xff, 0x00}); BitsArray b2 = BitsArray.create(new byte[] {0x00, (byte) 0xff}); b1.or(b2); for (int i = 0; i < b1.bitLength(); i++) { assertThat(b1.getBit(i)).isTrue(); } } } ================================================ FILE: filter/src/test/java/org/apache/rocketmq/filter/BloomFilterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.filter.util.BitsArray; import org.apache.rocketmq.filter.util.BloomFilter; import org.apache.rocketmq.filter.util.BloomFilterData; import org.junit.Test; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; public class BloomFilterTest { @Test public void testEquals() { BloomFilter a = BloomFilter.createByFn(10, 20); BloomFilter b = BloomFilter.createByFn(10, 20); BloomFilter c = BloomFilter.createByFn(12, 20); BloomFilter d = BloomFilter.createByFn(10, 30); assertThat(a).isEqualTo(b); assertThat(a).isNotEqualTo(c); assertThat(a).isNotEqualTo(d); assertThat(d).isNotEqualTo(c); assertThat(a.hashCode()).isEqualTo(b.hashCode()); assertThat(a.hashCode()).isNotEqualTo(c.hashCode()); assertThat(a.hashCode()).isNotEqualTo(d.hashCode()); assertThat(c.hashCode()).isNotEqualTo(d.hashCode()); } @Test public void testHashTo() { String cid = "CID_abc_efg"; BloomFilter bloomFilter = BloomFilter.createByFn(10, 20); BitsArray bits = BitsArray.create(bloomFilter.getM()); int[] bitPos = bloomFilter.calcBitPositions(cid); bloomFilter.hashTo(cid, bits); for (int bit : bitPos) { assertThat(bits.getBit(bit)).isTrue(); } } @Test public void testCalcBitPositions() { String cid = "CID_abc_efg"; BloomFilter bloomFilter = BloomFilter.createByFn(10, 20); int[] bitPos = bloomFilter.calcBitPositions(cid); assertThat(bitPos).isNotNull(); assertThat(bitPos.length).isEqualTo(bloomFilter.getK()); int[] bitPos2 = bloomFilter.calcBitPositions(cid); assertThat(bitPos2).isNotNull(); assertThat(bitPos2.length).isEqualTo(bloomFilter.getK()); assertThat(bitPos).isEqualTo(bitPos2); } @Test public void testIsHit() { String cid = "CID_abc_efg"; String cid2 = "CID_abc_123"; BloomFilter bloomFilter = BloomFilter.createByFn(10, 20); BitsArray bits = BitsArray.create(bloomFilter.getM()); bloomFilter.hashTo(cid, bits); assertThat(bloomFilter.isHit(cid, bits)).isTrue(); assertThat(!bloomFilter.isHit(cid2, bits)).isTrue(); bloomFilter.hashTo(cid2, bits); assertThat(bloomFilter.isHit(cid, bits)).isTrue(); assertThat(bloomFilter.isHit(cid2, bits)).isTrue(); } @Test public void testBloomFilterData() { BloomFilterData bloomFilterData = new BloomFilterData(new int[] {1, 2, 3}, 128); BloomFilterData bloomFilterData1 = new BloomFilterData(new int[] {1, 2, 3}, 128); BloomFilterData bloomFilterData2 = new BloomFilterData(new int[] {1, 2, 3}, 129); assertThat(bloomFilterData).isEqualTo(bloomFilterData1); assertThat(bloomFilterData2).isNotEqualTo(bloomFilterData); assertThat(bloomFilterData2).isNotEqualTo(bloomFilterData1); assertThat(bloomFilterData.hashCode()).isEqualTo(bloomFilterData1.hashCode()); assertThat(bloomFilterData2.hashCode()).isNotEqualTo(bloomFilterData.hashCode()); assertThat(bloomFilterData2.hashCode()).isNotEqualTo(bloomFilterData1.hashCode()); assertThat(bloomFilterData.getBitPos()).isEqualTo(bloomFilterData2.getBitPos()); assertThat(bloomFilterData.getBitNum()).isEqualTo(bloomFilterData1.getBitNum()); assertThat(bloomFilterData.getBitNum()).isNotEqualTo(bloomFilterData2.getBitNum()); bloomFilterData2.setBitNum(128); assertThat(bloomFilterData).isEqualTo(bloomFilterData2); bloomFilterData2.setBitPos(new int[] {1, 2, 3, 4}); assertThat(bloomFilterData).isNotEqualTo(bloomFilterData2); BloomFilterData nullData = new BloomFilterData(); assertThat(nullData.getBitNum()).isEqualTo(0); assertThat(nullData.getBitPos()).isNull(); BloomFilter bloomFilter = BloomFilter.createByFn(1, 300); assertThat(bloomFilter).isNotNull(); assertThat(bloomFilter.isValid(bloomFilterData)).isFalse(); } @Test public void testCheckFalseHit() { BloomFilter bloomFilter = BloomFilter.createByFn(1, 300); BitsArray bits = BitsArray.create(bloomFilter.getM()); int falseHit = 0; for (int i = 0; i < bloomFilter.getN(); i++) { String str = randomString((new Random(System.nanoTime())).nextInt(127) + 10); int[] bitPos = bloomFilter.calcBitPositions(str); if (bloomFilter.checkFalseHit(bitPos, bits)) { falseHit++; } bloomFilter.hashTo(bitPos, bits); } assertThat(falseHit).isLessThanOrEqualTo(bloomFilter.getF() * bloomFilter.getN() / 100); } private String randomString(int length) { StringBuilder stringBuilder = new StringBuilder(length); for (int i = 0; i < length; i++) { stringBuilder.append((char) ((new Random(System.nanoTime())).nextInt(123 - 97) + 97)); } return stringBuilder.toString(); } } ================================================ FILE: filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.filter.expression.ComparisonExpression; import org.apache.rocketmq.filter.expression.ConstantExpression; import org.apache.rocketmq.filter.expression.EvaluationContext; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.MQFilterException; import org.apache.rocketmq.filter.expression.PropertyExpression; import org.apache.rocketmq.filter.parser.SelectorParser; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; public class ExpressionTest { private static String andExpression = "a=3 and b<>4 And c>5 AND d<=4"; private static String orExpression = "a=3 or b<>4 Or c>5 OR d<=4"; private static String inExpression = "a in ('3', '4', '5')"; private static String notInExpression = "a not in ('3', '4', '5')"; private static String betweenExpression = "a between 2 and 10"; private static String notBetweenExpression = "a not between 2 and 10"; private static String isNullExpression = "a is null"; private static String isNotNullExpression = "a is not null"; private static String equalExpression = "a is not null and a='hello'"; private static String booleanExpression = "a=TRUE OR b=FALSE"; private static String nullOrExpression = "a is null OR a='hello'"; private static String stringHasString = "TAGS is not null and TAGS='''''tag'''''"; @Test public void testContains_StartsWith_EndsWith_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(genExp("value contains 'x'"), context, Boolean.TRUE); eval(genExp("value startswith 'ax'"), context, Boolean.TRUE); eval(genExp("value endswith 'xb'"), context, Boolean.TRUE); } @Test public void test_notContains_notStartsWith_notEndsWith_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(genExp("value not contains 'x'"), context, Boolean.FALSE); eval(genExp("value not startswith 'ax'"), context, Boolean.FALSE); eval(genExp("value not endswith 'xb'"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_has_not() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", "abb") ); eval(genExp("value contains 'x'"), context, Boolean.FALSE); eval(genExp("value startswith 'x'"), context, Boolean.FALSE); eval(genExp("value endswith 'x'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_has_not() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", "abb") ); eval(genExp("value not contains 'x'"), context, Boolean.TRUE); eval(genExp("value not startswith 'x'"), context, Boolean.TRUE); eval(genExp("value not endswith 'x'"), context, Boolean.TRUE); } @Test public void testContains_StartsWith_EndsWith_hasEmpty() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(genExp("value contains ''"), context, Boolean.FALSE); eval(genExp("value startswith ''"), context, Boolean.FALSE); eval(genExp("value endswith ''"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_hasEmpty() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(genExp("value not contains ''"), context, Boolean.FALSE); eval(genExp("value not startswith ''"), context, Boolean.FALSE); eval(genExp("value not endswith ''"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_null_has_1() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", null) ); eval(genExp("value contains 'x'"), context, Boolean.FALSE); eval(genExp("value startswith 'x'"), context, Boolean.FALSE); eval(genExp("value endswith 'x'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_null_has_1() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", null) ); eval(genExp("value not contains 'x'"), context, Boolean.FALSE); eval(genExp("value not startswith 'x'"), context, Boolean.FALSE); eval(genExp("value not endswith 'x'"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_null_has_2() throws Exception { EvaluationContext context = genContext( // KeyValue.c("value", null) ); eval(genExp("value contains 'x'"), context, Boolean.FALSE); eval(genExp("value startswith 'x'"), context, Boolean.FALSE); eval(genExp("value endswith 'x'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_null_has_2() throws Exception { EvaluationContext context = genContext( // KeyValue.c("value", null) ); eval(genExp("value not contains 'x'"), context, Boolean.FALSE); eval(genExp("value not startswith 'x'"), context, Boolean.FALSE); eval(genExp("value not endswith 'x'"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_number_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", 1.23) ); eval(genExp("value contains 'x'"), context, Boolean.FALSE); eval(genExp("value startswith 'x'"), context, Boolean.FALSE); eval(genExp("value endswith 'x'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_number_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", 1.23) ); eval(genExp("value not contains 'x'"), context, Boolean.FALSE); eval(genExp("value not startswith 'x'"), context, Boolean.FALSE); eval(genExp("value not endswith 'x'"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_boolean_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", Boolean.TRUE) ); eval(genExp("value contains 'x'"), context, Boolean.FALSE); eval(genExp("value startswith 'x'"), context, Boolean.FALSE); eval(genExp("value endswith 'x'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_boolean_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", Boolean.TRUE) ); eval(genExp("value not contains 'x'"), context, Boolean.FALSE); eval(genExp("value not startswith 'x'"), context, Boolean.FALSE); eval(genExp("value not endswith 'x'"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_object_has() throws Exception { EvaluationContext context = genContext( KeyValue.c("value", new Object()) ); eval(genExp("value contains 'x'"), context, Boolean.FALSE); eval(genExp("value startswith 'x'"), context, Boolean.FALSE); eval(genExp("value endswith 'x'"), context, Boolean.FALSE); } @Test public void testContains_has_not_string_1() throws Exception { try { Expression expr = genExp("value contains x"); // will throw parse exception. EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(expr, context, Boolean.FALSE); } catch (Throwable e) { } } @Test public void test_notContains_has_not_string_1() throws Exception { try { Expression expr = genExp("value not contains x"); // will throw parse exception. EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(expr, context, Boolean.FALSE); } catch (Throwable e) { } } @Test public void testContains_has_not_string_2() throws Exception { try { Expression expr = genExp("value contains 123"); // will throw parse exception. EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(expr, context, Boolean.FALSE); } catch (Throwable e) { } } @Test public void test_notContains_has_not_string_2() throws Exception { try { Expression expr = genExp("value not contains 123"); // will throw parse exception. EvaluationContext context = genContext( KeyValue.c("value", "axb") ); eval(expr, context, Boolean.FALSE); } catch (Throwable e) { } } @Test public void testContains_StartsWith_EndsWith_string_has_string() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' contains 'x'"), context, Boolean.TRUE); eval(genExp("'axb' startswith 'ax'"), context, Boolean.TRUE); eval(genExp("'axb' endswith 'xb'"), context, Boolean.TRUE); } @Test public void test_notContains_notStartsWith_notEndsWith_string_has_string() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' not contains 'x'"), context, Boolean.FALSE); eval(genExp("'axb' not startswith 'ax'"), context, Boolean.FALSE); eval(genExp("'axb' not endswith 'xb'"), context, Boolean.FALSE); } @Test public void testContains_startsWith_endsWith_string_has_not_string() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' contains 'u'"), context, Boolean.FALSE); eval(genExp("'axb' startswith 'u'"), context, Boolean.FALSE); eval(genExp("'axb' endswith 'u'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_string_has_not_string() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' not contains 'u'"), context, Boolean.TRUE); eval(genExp("'axb' not startswith 'u'"), context, Boolean.TRUE); eval(genExp("'axb' not endswith 'u'"), context, Boolean.TRUE); } @Test public void testContains_StartsWith_EndsWith_string_has_empty() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' contains ''"), context, Boolean.FALSE); eval(genExp("'axb' startswith ''"), context, Boolean.FALSE); eval(genExp("'axb' endswith ''"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_string_has_empty() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' not contains ''"), context, Boolean.FALSE); eval(genExp("'axb' not startswith ''"), context, Boolean.FALSE); eval(genExp("'axb' not endswith ''"), context, Boolean.FALSE); } @Test public void testContains_StartsWith_EndsWith_string_has_space() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("' ' contains ' '"), context, Boolean.TRUE); eval(genExp("' ' startswith ' '"), context, Boolean.TRUE); eval(genExp("' ' endswith ' '"), context, Boolean.TRUE); } @Test public void test_notContains_notStartsWith_notEndsWith_string_has_space() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("' ' not contains ' '"), context, Boolean.FALSE); eval(genExp("' ' not startswith ' '"), context, Boolean.FALSE); eval(genExp("' ' not endswith ' '"), context, Boolean.FALSE); } @Test public void testContains_string_has_nothing() throws Exception { try { Expression expr = genExp("'axb' contains "); // will throw parse exception. EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(expr, context, Boolean.TRUE); } catch (Throwable e) { } } @Test public void test_notContains_string_has_nothing() throws Exception { try { Expression expr = genExp("'axb' not contains "); // will throw parse exception. EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(expr, context, Boolean.TRUE); } catch (Throwable e) { } } @Test public void testContains_StartsWith_EndsWith_string_has_special_1() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' contains '.'"), context, Boolean.FALSE); eval(genExp("'axb' startswith '.'"), context, Boolean.FALSE); eval(genExp("'axb' endswith '.'"), context, Boolean.FALSE); } @Test public void test_notContains_notStartsWith_notEndsWith_string_has_special_1() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'axb' not contains '.'"), context, Boolean.TRUE); eval(genExp("'axb' not startswith '.'"), context, Boolean.TRUE); eval(genExp("'axb' not endswith '.'"), context, Boolean.TRUE); } @Test public void testContains_StartsWith_EndsWith_string_has_special_2() throws Exception { EvaluationContext context = genContext( KeyValue.c("whatever", "whatever") ); eval(genExp("'s' contains '\\'"), context, Boolean.FALSE); eval(genExp("'s' startswith '\\'"), context, Boolean.FALSE); eval(genExp("'s' endswith '\\'"), context, Boolean.FALSE); } @Test public void testContainsAllInOne() throws Exception { Expression expr = genExp("a not in ('4', '4', '5') and b between 3 and 10 and c not contains 'axbc'"); EvaluationContext context = genContext( KeyValue.c("a", "3"), KeyValue.c("b", 3), KeyValue.c("c", "axbdc") ); eval(expr, context, Boolean.TRUE); } @Test public void testStartsWithAllInOne() throws Exception { Expression expr = genExp("a not in ('4', '4', '5') and b between 3 and 10 and c not startswith 'axbc'"); EvaluationContext context = genContext( KeyValue.c("a", "3"), KeyValue.c("b", 3), KeyValue.c("c", "axbdc") ); eval(expr, context, Boolean.TRUE); } @Test public void testEndsWithAllInOne() throws Exception { Expression expr = genExp("a not in ('4', '4', '5') and b between 3 and 10 and c not endswith 'axbc'"); EvaluationContext context = genContext( KeyValue.c("a", "3"), KeyValue.c("b", 3), KeyValue.c("c", "axbdc") ); eval(expr, context, Boolean.TRUE); } @Test public void testEvaluate_stringHasString() throws Exception { Expression expr = genExp(stringHasString); EvaluationContext context = genContext( KeyValue.c("TAGS", "''tag''") ); eval(expr, context, Boolean.TRUE); } @Test public void testEvaluate_now() throws Exception { EvaluationContext context = genContext( KeyValue.c("a", System.currentTimeMillis()) ); Expression nowExpression = ConstantExpression.createNow(); Expression propertyExpression = new PropertyExpression("a"); Expression expression = ComparisonExpression.createLessThanEqual(propertyExpression, nowExpression); eval(expression, context, Boolean.TRUE); } @Test(expected = RuntimeException.class) public void testEvaluate_stringCompare() throws Exception { Expression expression = genExp("a between up and low"); EvaluationContext context = genContext( KeyValue.c("a", "3.14") ); eval(expression, context, Boolean.FALSE); { context = genContext( KeyValue.c("a", "3.14"), KeyValue.c("up", "up"), KeyValue.c("low", "low") ); eval(expression, context, Boolean.FALSE); } { expression = genExp("key is not null and key between 0 and 100"); context = genContext( KeyValue.c("key", "con") ); eval(expression, context, Boolean.FALSE); } { expression = genExp("a between 0 and 100"); context = genContext( KeyValue.c("a", "abc") ); eval(expression, context, Boolean.FALSE); } { expression = genExp("a=b"); context = genContext( KeyValue.c("a", "3.14"), KeyValue.c("b", "3.14") ); eval(expression, context, Boolean.TRUE); } { expression = genExp("a<>b"); context = genContext( KeyValue.c("a", "3.14"), KeyValue.c("b", "3.14") ); eval(expression, context, Boolean.FALSE); } { expression = genExp("a<>b"); context = genContext( KeyValue.c("a", "3.14"), KeyValue.c("b", "3.141") ); eval(expression, context, Boolean.TRUE); } } @Test public void testEvaluate_exponent() throws Exception { Expression expression = genExp("a > 3.1E10"); EvaluationContext context = genContext( KeyValue.c("a", String.valueOf(3.1415 * Math.pow(10, 10))) ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_floatNumber() throws Exception { Expression expression = genExp("a > 3.14"); EvaluationContext context = genContext( KeyValue.c("a", String.valueOf(3.1415)) ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_twoVariable() throws Exception { Expression expression = genExp("a > b"); EvaluationContext context = genContext( KeyValue.c("a", String.valueOf(10)), KeyValue.c("b", String.valueOf(20)) ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_twoVariableGt() throws Exception { Expression expression = genExp("a > b"); EvaluationContext context = genContext( KeyValue.c("b", String.valueOf(10)), KeyValue.c("a", String.valueOf(20)) ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_nullOr() throws Exception { Expression expression = genExp(nullOrExpression); EvaluationContext context = genContext( ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "hello") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "abc") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_boolean() throws Exception { Expression expression = genExp(booleanExpression); EvaluationContext context = genContext( KeyValue.c("a", "true"), KeyValue.c("b", "false") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "false"), KeyValue.c("b", "true") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_equal() throws Exception { Expression expression = genExp(equalExpression); EvaluationContext context = genContext( KeyValue.c("a", "hello") ); eval(expression, context, Boolean.TRUE); context = genContext( ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_andTrue() throws Exception { Expression expression = genExp(andExpression); EvaluationContext context = genContext( KeyValue.c("a", 3), KeyValue.c("b", 5), KeyValue.c("c", 6), KeyValue.c("d", 1) ); for (int i = 0; i < 500; i++) { eval(expression, context, Boolean.TRUE); } long start = System.currentTimeMillis(); for (int j = 0; j < 100; j++) { for (int i = 0; i < 1000; i++) { eval(expression, context, Boolean.TRUE); } } // use string context = genContext( KeyValue.c("a", "3"), KeyValue.c("b", "5"), KeyValue.c("c", "6"), KeyValue.c("d", "1") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_andFalse() throws Exception { Expression expression = genExp(andExpression); EvaluationContext context = genContext( KeyValue.c("a", 4), KeyValue.c("b", 5), KeyValue.c("c", 6), KeyValue.c("d", 1) ); eval(expression, context, Boolean.FALSE); // use string context = genContext( KeyValue.c("a", "4"), KeyValue.c("b", "5"), KeyValue.c("c", "6"), KeyValue.c("d", "1") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_orTrue() throws Exception { Expression expression = genExp(orExpression); // first EvaluationContext context = genContext( KeyValue.c("a", 3) ); eval(expression, context, Boolean.TRUE); // second context = genContext( KeyValue.c("a", 4), KeyValue.c("b", 5) ); eval(expression, context, Boolean.TRUE); // third context = genContext( KeyValue.c("a", 4), KeyValue.c("b", 4), KeyValue.c("c", 6) ); eval(expression, context, Boolean.TRUE); // forth context = genContext( KeyValue.c("a", 4), KeyValue.c("b", 4), KeyValue.c("c", 3), KeyValue.c("d", 2) ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_orFalse() throws Exception { Expression expression = genExp(orExpression); // forth EvaluationContext context = genContext( KeyValue.c("a", 4), KeyValue.c("b", 4), KeyValue.c("c", 3), KeyValue.c("d", 10) ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_inTrue() throws Exception { Expression expression = genExp(inExpression); EvaluationContext context = genContext( KeyValue.c("a", "3") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "4") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "5") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_inFalse() throws Exception { Expression expression = genExp(inExpression); EvaluationContext context = genContext( KeyValue.c("a", "8") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_notInTrue() throws Exception { Expression expression = genExp(notInExpression); EvaluationContext context = genContext( KeyValue.c("a", "8") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_notInFalse() throws Exception { Expression expression = genExp(notInExpression); EvaluationContext context = genContext( KeyValue.c("a", "3") ); eval(expression, context, Boolean.FALSE); context = genContext( KeyValue.c("a", "4") ); eval(expression, context, Boolean.FALSE); context = genContext( KeyValue.c("a", "5") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_betweenTrue() throws Exception { Expression expression = genExp(betweenExpression); EvaluationContext context = genContext( KeyValue.c("a", "2") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "10") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "3") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_betweenFalse() throws Exception { Expression expression = genExp(betweenExpression); EvaluationContext context = genContext( KeyValue.c("a", "1") ); eval(expression, context, Boolean.FALSE); context = genContext( KeyValue.c("a", "11") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_notBetweenTrue() throws Exception { Expression expression = genExp(notBetweenExpression); EvaluationContext context = genContext( KeyValue.c("a", "1") ); eval(expression, context, Boolean.TRUE); context = genContext( KeyValue.c("a", "11") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_notBetweenFalse() throws Exception { Expression expression = genExp(notBetweenExpression); EvaluationContext context = genContext( KeyValue.c("a", "2") ); eval(expression, context, Boolean.FALSE); context = genContext( KeyValue.c("a", "10") ); eval(expression, context, Boolean.FALSE); context = genContext( KeyValue.c("a", "3") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_isNullTrue() throws Exception { Expression expression = genExp(isNullExpression); EvaluationContext context = genContext( KeyValue.c("abc", "2") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_isNullFalse() throws Exception { Expression expression = genExp(isNullExpression); EvaluationContext context = genContext( KeyValue.c("a", "2") ); eval(expression, context, Boolean.FALSE); } @Test public void testEvaluate_isNotNullTrue() throws Exception { Expression expression = genExp(isNotNullExpression); EvaluationContext context = genContext( KeyValue.c("a", "2") ); eval(expression, context, Boolean.TRUE); } @Test public void testEvaluate_isNotNullFalse() throws Exception { Expression expression = genExp(isNotNullExpression); EvaluationContext context = genContext( KeyValue.c("abc", "2") ); eval(expression, context, Boolean.FALSE); } protected void eval(Expression expression, EvaluationContext context, Boolean result) throws Exception { Object ret = expression.evaluate(context); if (ret == null || !(ret instanceof Boolean)) { assertThat(result).isFalse(); } else { assertThat(result).isEqualTo(ret); } } protected EvaluationContext genContext(KeyValue... keyValues) { if (keyValues == null || keyValues.length < 1) { return new PropertyContext(); } PropertyContext context = new PropertyContext(); for (KeyValue keyValue : keyValues) { context.properties.put(keyValue.key, keyValue.value); } return context; } protected Expression genExp(String exp) { Expression expression = null; try { expression = SelectorParser.parse(exp); assertThat(expression).isNotNull(); } catch (MQFilterException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } return expression; } static class KeyValue { public static KeyValue c(String key, Object value) { return new KeyValue(key, value); } public KeyValue(String key, Object value) { this.key = key; this.value = value; } public String key; public Object value; } class PropertyContext implements EvaluationContext { public Map properties = new HashMap<>(8); @Override public Object get(final String name) { return properties.get(name); } @Override public Map keyValues() { return properties; } } } ================================================ FILE: filter/src/test/java/org/apache/rocketmq/filter/FilterSpiTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.filter.expression.EmptyEvaluationContext; import org.apache.rocketmq.filter.expression.EvaluationContext; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.MQFilterException; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class FilterSpiTest { static class NothingExpression implements Expression { @Override public Object evaluate(final EvaluationContext context) throws Exception { return Boolean.TRUE; } } static class NothingFilter implements FilterSpi { @Override public Expression compile(final String expr) throws MQFilterException { return new NothingExpression(); } @Override public String ofType() { return "Nothing"; } } @Test public void testRegister() { FilterFactory.INSTANCE.register(new NothingFilter()); Expression expr = null; try { expr = FilterFactory.INSTANCE.get("Nothing").compile("abc"); } catch (MQFilterException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } assertThat(expr).isNotNull(); try { assertThat((Boolean) expr.evaluate(new EmptyEvaluationContext())).isTrue(); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } FilterFactory.INSTANCE.unRegister("Nothing"); } @Test public void testGet() { try { assertThat((Boolean) FilterFactory.INSTANCE.get(ExpressionType.SQL92).compile("a is not null and a > 0") .evaluate(new EmptyEvaluationContext())).isFalse(); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } } ================================================ FILE: filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.filter; import org.apache.rocketmq.filter.expression.Expression; import org.apache.rocketmq.filter.expression.MQFilterException; import org.apache.rocketmq.filter.parser.SelectorParser; import org.junit.Test; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; public class ParserTest { private static String andExpression = "a=3 and b<>4 And c>5 AND d<=4"; private static String andExpressionHasBlank = "a=3 and b<>4 And c>5 AND d<=4"; private static String orExpression = "a=3 or b<>4 Or c>5 OR d<=4"; private static String inExpression = "a in ('3', '4', '5')"; private static String notInExpression = "(a not in ('6', '4', '5')) or (b in ('3', '4', '5'))"; private static String betweenExpression = "(a between 2 and 10) AND (b not between 6 and 9)"; private static String equalNullExpression = "a is null"; private static String notEqualNullExpression = "a is not null"; private static String nowExpression = "a <= now"; private static String containsExpression = "a=3 and b contains 'xxx' and c not contains 'xxx'"; private static String invalidExpression = "a and between 2 and 10"; private static String illegalBetween = " a between 10 and 0"; @Test public void testParse_valid() { for (String expr : Arrays.asList( andExpression, orExpression, inExpression, notInExpression, betweenExpression, equalNullExpression, notEqualNullExpression, nowExpression, containsExpression )) { try { Expression expression = SelectorParser.parse(expr); assertThat(expression).isNotNull(); } catch (MQFilterException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } } @Test public void testParse_invalid() { try { SelectorParser.parse(invalidExpression); assertThat(Boolean.TRUE).isFalse(); } catch (MQFilterException e) { } } @Test public void testParse_decimalOverFlow() { try { String str = "100000000000000000000000"; SelectorParser.parse("a > " + str); assertThat(Boolean.TRUE).isFalse(); } catch (Exception e) { } } @Test public void testParse_floatOverFlow() { try { StringBuilder sb = new StringBuilder(210000); sb.append("1"); for (int i = 0; i < 2048; i ++) { sb.append("111111111111111111111111111111111111111111111111111"); } sb.append("."); for (int i = 0; i < 2048; i ++) { sb.append("111111111111111111111111111111111111111111111111111"); } String str = sb.toString(); SelectorParser.parse("a > " + str); assertThat(Boolean.TRUE).isFalse(); } catch (Exception e) { } } @Test public void testParse_illegalBetween() { try { SelectorParser.parse(illegalBetween); assertThat(Boolean.TRUE).isFalse(); } catch (Exception e) { } } @Test public void testEquals() { try { Expression expr1 = SelectorParser.parse(andExpression); Expression expr2 = SelectorParser.parse(andExpressionHasBlank); Expression expr3 = SelectorParser.parse(orExpression); assertThat(expr1).isEqualTo(expr2); assertThat(expr1).isNotEqualTo(expr3); } catch (MQFilterException e) { e.printStackTrace(); assertThat(Boolean.TRUE).isFalse(); } } } ================================================ FILE: filter/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: namesrv/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "namesrv", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//remoting", "//srvutil", "//tools", "//client", "//common", "//controller", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_validator_commons_validator", "@maven//:com_github_luben_zstd_jni", "@maven//:org_lz4_lz4_java", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:io_netty_netty_all", "@maven//:ch_qos_logback_logback_classic", "@maven//:ch_qos_logback_logback_core", "@maven//:org_slf4j_slf4j_api", "@maven//:org_bouncycastle_bcpkix_jdk15on", "@maven//:commons_cli_commons_cli", "@maven//:com_google_guava_guava", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":namesrv", "//remoting", "//srvutil", "//tools", "//client", "//common", "//:test_deps", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_cli_commons_cli", "@maven//:io_netty_netty_all", "@maven//:com_google_guava_guava", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: namesrv/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-namesrv rocketmq-namesrv ${project.version} ${basedir}/.. ${project.groupId} rocketmq-controller ${project.groupId} rocketmq-client ${project.groupId} rocketmq-tools ${project.groupId} rocketmq-srvutil org.openjdk.jmh jmh-core 1.19 test org.openjdk.jmh jmh-generator-annprocess 1.19 test org.bouncycastle bcpkix-jdk18on ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv; import java.util.Collections; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.future.FutureTaskExt; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.kvconfig.KVConfigManager; import org.apache.rocketmq.namesrv.processor.ClientRequestProcessor; import org.apache.rocketmq.namesrv.processor.ClusterTestRequestProcessor; import org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor; import org.apache.rocketmq.namesrv.route.ZoneRouteRPCHook; import org.apache.rocketmq.namesrv.routeinfo.BrokerHousekeepingService; import org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager; import org.apache.rocketmq.remoting.Configuration; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.srvutil.FileWatchService; public class NamesrvController { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private static final Logger WATER_MARK_LOG = LoggerFactory.getLogger(LoggerName.NAMESRV_WATER_MARK_LOGGER_NAME); private final NamesrvConfig namesrvConfig; private final NettyServerConfig nettyServerConfig; private final NettyClientConfig nettyClientConfig; private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("NSScheduledThread").daemon(true).build()); private final ScheduledExecutorService scanExecutorService = ThreadUtils.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("NSScanScheduledThread").daemon(true).build()); private final KVConfigManager kvConfigManager; private final RouteInfoManager routeInfoManager; private RemotingClient remotingClient; private RemotingServer remotingServer; private final BrokerHousekeepingService brokerHousekeepingService; private ExecutorService defaultExecutor; private ExecutorService clientRequestExecutor; private BlockingQueue defaultThreadPoolQueue; private BlockingQueue clientRequestThreadPoolQueue; private final Configuration configuration; private FileWatchService fileWatchService; public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig) { this(namesrvConfig, nettyServerConfig, new NettyClientConfig()); } public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig, NettyClientConfig nettyClientConfig) { this.namesrvConfig = namesrvConfig; this.nettyServerConfig = nettyServerConfig; this.nettyClientConfig = nettyClientConfig; this.kvConfigManager = new KVConfigManager(this); this.brokerHousekeepingService = new BrokerHousekeepingService(this); this.routeInfoManager = new RouteInfoManager(namesrvConfig, this); this.configuration = new Configuration(LOGGER, this.namesrvConfig, this.nettyServerConfig); this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath"); } public boolean initialize() { loadConfig(); initiateNetworkComponents(); initiateThreadExecutors(); registerProcessor(); startScheduleService(); initiateSslContext(); initiateRpcHooks(); return true; } private void loadConfig() { this.kvConfigManager.load(); } private void startScheduleService() { this.scanExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5000, this.namesrvConfig.getScanNotActiveBrokerInterval(), TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically, 1, 10, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { NamesrvController.this.printWaterMark(); } catch (Throwable e) { LOGGER.error("printWaterMark error.", e); } }, 10, 1, TimeUnit.SECONDS); } private void initiateNetworkComponents() { this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService); this.remotingClient = new NettyRemotingClient(this.nettyClientConfig); } private void initiateThreadExecutors() { this.defaultThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getDefaultThreadPoolQueueCapacity()); this.defaultExecutor = ThreadUtils.newThreadPoolExecutor(this.namesrvConfig.getDefaultThreadPoolNums(), this.namesrvConfig.getDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.defaultThreadPoolQueue, new ThreadFactoryImpl("RemotingExecutorThread_")); this.clientRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getClientRequestThreadPoolQueueCapacity()); this.clientRequestExecutor = ThreadUtils.newThreadPoolExecutor(this.namesrvConfig.getClientRequestThreadPoolNums(), this.namesrvConfig.getClientRequestThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientRequestThreadPoolQueue, new ThreadFactoryImpl("ClientRequestExecutorThread_")); } private void initiateSslContext() { if (TlsSystemConfig.tlsMode == TlsMode.DISABLED) { return; } String[] watchFiles = {TlsSystemConfig.tlsServerCertPath, TlsSystemConfig.tlsServerKeyPath, TlsSystemConfig.tlsServerTrustCertPath}; FileWatchService.Listener listener = new FileWatchService.Listener() { boolean certChanged, keyChanged = false; @Override public void onChanged(String path) { if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) { LOGGER.info("The trust certificate changed, reload the ssl context"); ((NettyRemotingServer) remotingServer).loadSslContext(); } if (path.equals(TlsSystemConfig.tlsServerCertPath)) { certChanged = true; } if (path.equals(TlsSystemConfig.tlsServerKeyPath)) { keyChanged = true; } if (certChanged && keyChanged) { LOGGER.info("The certificate and private key changed, reload the ssl context"); certChanged = keyChanged = false; ((NettyRemotingServer) remotingServer).loadSslContext(); } } }; try { fileWatchService = new FileWatchService(watchFiles, listener); } catch (Exception e) { LOGGER.warn("FileWatchService created error, can't load the certificate dynamically"); } } private void printWaterMark() { WATER_MARK_LOG.info("[WATERMARK] ClientQueueSize:{} ClientQueueSlowTime:{} " + "DefaultQueueSize:{} DefaultQueueSlowTime:{}", this.clientRequestThreadPoolQueue.size(), headSlowTimeMills(this.clientRequestThreadPoolQueue), this.defaultThreadPoolQueue.size(), headSlowTimeMills(this.defaultThreadPoolQueue)); } private long headSlowTimeMills(BlockingQueue q) { long slowTimeMills = 0; final Runnable firstRunnable = q.peek(); if (firstRunnable instanceof FutureTaskExt) { final Runnable inner = ((FutureTaskExt) firstRunnable).getRunnable(); if (inner instanceof RequestTask) { slowTimeMills = System.currentTimeMillis() - ((RequestTask) inner).getCreateTimestamp(); } } if (slowTimeMills < 0) { slowTimeMills = 0; } return slowTimeMills; } private void registerProcessor() { if (namesrvConfig.isClusterTest()) { this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()), this.defaultExecutor); } else { // Support get route info only temporarily ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(this); this.remotingServer.registerProcessor(RequestCode.GET_ROUTEINFO_BY_TOPIC, clientRequestProcessor, this.clientRequestExecutor); this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.defaultExecutor); } } private void initiateRpcHooks() { this.remotingServer.registerRPCHook(new ZoneRouteRPCHook()); } public void start() throws Exception { this.remotingServer.start(); // In test scenarios where it is up to OS to pick up an available port, set the listening port back to config if (0 == nettyServerConfig.getListenPort()) { nettyServerConfig.setListenPort(this.remotingServer.localListenPort()); } this.remotingClient.updateNameServerAddressList(Collections.singletonList(NetworkUtil.getLocalAddress() + ":" + nettyServerConfig.getListenPort())); this.remotingClient.start(); if (this.fileWatchService != null) { this.fileWatchService.start(); } this.routeInfoManager.start(); } public void shutdown() { this.remotingClient.shutdown(); this.remotingServer.shutdown(); this.defaultExecutor.shutdown(); this.clientRequestExecutor.shutdown(); this.scheduledExecutorService.shutdown(); this.scanExecutorService.shutdown(); this.routeInfoManager.shutdown(); if (this.fileWatchService != null) { this.fileWatchService.shutdown(); } } public NamesrvConfig getNamesrvConfig() { return namesrvConfig; } public NettyServerConfig getNettyServerConfig() { return nettyServerConfig; } public KVConfigManager getKvConfigManager() { return kvConfigManager; } public RouteInfoManager getRouteInfoManager() { return routeInfoManager; } public RemotingServer getRemotingServer() { return remotingServer; } public RemotingClient getRemotingClient() { return remotingClient; } public void setRemotingServer(RemotingServer remotingServer) { this.remotingServer = remotingServer; } public Configuration getConfiguration() { return configuration; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv; import java.io.BufferedInputStream; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Properties; import java.util.concurrent.Callable; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.JraftConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.controller.ControllerManager; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.srvutil.ShutdownHookThread; public class NamesrvStartup { private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private static final Logger logConsole = LoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_LOGGER_NAME); private static Properties properties = null; private static NamesrvConfig namesrvConfig = null; private static NettyServerConfig nettyServerConfig = null; private static NettyClientConfig nettyClientConfig = null; private static ControllerConfig controllerConfig = null; public static void main(String[] args) { main0(args); controllerManagerMain(); } public static NamesrvController main0(String[] args) { try { parseCommandlineAndConfigFile(args); NamesrvController controller = createAndStartNamesrvController(); return controller; } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } return null; } public static ControllerManager controllerManagerMain() { try { if (namesrvConfig.isEnableControllerInNamesrv()) { return createAndStartControllerManager(); } } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } return null; } public static void parseCommandlineAndConfigFile(String[] args) throws Exception { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); Options options = ServerUtil.buildCommandlineOptions(new Options()); CommandLine commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); return; } namesrvConfig = new NamesrvConfig(); nettyServerConfig = new NettyServerConfig(); nettyClientConfig = new NettyClientConfig(); nettyServerConfig.setListenPort(9876); if (commandLine.hasOption('c')) { String file = commandLine.getOptionValue('c'); if (file != null) { InputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(file))); properties = new Properties(); properties.load(in); MixAll.properties2Object(properties, namesrvConfig); MixAll.properties2Object(properties, nettyServerConfig); MixAll.properties2Object(properties, nettyClientConfig); if (namesrvConfig.isEnableControllerInNamesrv()) { controllerConfig = new ControllerConfig(); JraftConfig jraftConfig = new JraftConfig(); controllerConfig.setJraftConfig(jraftConfig); MixAll.properties2Object(properties, controllerConfig); MixAll.properties2Object(properties, jraftConfig); } namesrvConfig.setConfigStorePath(file); System.out.printf("load config properties file OK, %s%n", file); in.close(); } } MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig); if (commandLine.hasOption('p')) { MixAll.printObjectProperties(logConsole, namesrvConfig); MixAll.printObjectProperties(logConsole, nettyServerConfig); MixAll.printObjectProperties(logConsole, nettyClientConfig); if (namesrvConfig.isEnableControllerInNamesrv()) { MixAll.printObjectProperties(logConsole, controllerConfig); } System.exit(0); } 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); } MixAll.printObjectProperties(log, namesrvConfig); MixAll.printObjectProperties(log, nettyServerConfig); } public static NamesrvController createAndStartNamesrvController() throws Exception { NamesrvController controller = createNamesrvController(); start(controller); NettyServerConfig serverConfig = controller.getNettyServerConfig(); String tip = String.format("The Name Server boot success. serializeType=%s, address %s:%d", RemotingCommand.getSerializeTypeConfigInThisServer(), serverConfig.getBindAddress(), serverConfig.getListenPort()); log.info(tip); System.out.printf("%s%n", tip); return controller; } public static NamesrvController createNamesrvController() { final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig, nettyClientConfig); // remember all configs to prevent discard controller.getConfiguration().registerConfig(properties); return controller; } public static NamesrvController start(final NamesrvController controller) throws Exception { if (null == controller) { throw new IllegalArgumentException("NamesrvController is null"); } boolean initResult = controller.initialize(); if (!initResult) { controller.shutdown(); System.exit(-3); } Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable) () -> { controller.shutdown(); return null; })); controller.start(); return controller; } public static ControllerManager createAndStartControllerManager() throws Exception { ControllerManager controllerManager = createControllerManager(); start(controllerManager); String tip = "The ControllerManager boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer(); log.info(tip); System.out.printf("%s%n", tip); return controllerManager; } public static ControllerManager createControllerManager() throws Exception { NettyServerConfig controllerNettyServerConfig = (NettyServerConfig) nettyServerConfig.clone(); ControllerManager controllerManager = new ControllerManager(controllerConfig, controllerNettyServerConfig, nettyClientConfig); // remember all configs to prevent discard controllerManager.getConfiguration().registerConfig(properties); return controllerManager; } public static ControllerManager start(final ControllerManager controllerManager) throws Exception { if (null == controllerManager) { throw new IllegalArgumentException("ControllerManager is null"); } boolean initResult = controllerManager.initialize(); if (!initResult) { controllerManager.shutdown(); System.exit(-3); } Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable) () -> { controllerManager.shutdown(); return null; })); controllerManager.start(); return controllerManager; } public static void shutdown(final NamesrvController controller) { controller.shutdown(); } public static void shutdown(final ControllerManager controllerManager) { controllerManager.shutdown(); } public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("c", "configFile", true, "Name server config properties file"); opt.setRequired(false); options.addOption(opt); opt = new Option("p", "printConfigItem", false, "Print all config items"); opt.setRequired(false); options.addOption(opt); return options; } public static Properties getProperties() { return properties; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.kvconfig; import java.io.IOException; import java.util.HashMap; import java.util.Map.Entry; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.protocol.body.KVTable; public class KVConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private final NamesrvController namesrvController; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final HashMap> configTable = new HashMap<>(); public KVConfigManager(NamesrvController namesrvController) { this.namesrvController = namesrvController; } public void load() { String content = null; try { content = MixAll.file2String(this.namesrvController.getNamesrvConfig().getKvConfigPath()); } catch (IOException e) { log.warn("Load KV config table exception", e); } if (content != null) { KVConfigSerializeWrapper kvConfigSerializeWrapper = KVConfigSerializeWrapper.fromJson(content, KVConfigSerializeWrapper.class); if (null != kvConfigSerializeWrapper) { this.configTable.putAll(kvConfigSerializeWrapper.getConfigTable()); log.info("load KV config table OK"); } } } public void putKVConfig(final String namespace, final String key, final String value) { try { this.lock.writeLock().lockInterruptibly(); try { HashMap kvTable = this.configTable.get(namespace); if (null == kvTable) { kvTable = new HashMap<>(); this.configTable.put(namespace, kvTable); log.info("putKVConfig create new Namespace {}", namespace); } final String prev = kvTable.put(key, value); if (null != prev) { log.info("putKVConfig update config item, Namespace: {} Key: {} Value: {}", namespace, key, value); } else { log.info("putKVConfig create new config item, Namespace: {} Key: {} Value: {}", namespace, key, value); } } finally { this.lock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("putKVConfig InterruptedException", e); } this.persist(); } public void persist() { try { this.lock.readLock().lockInterruptibly(); try { KVConfigSerializeWrapper kvConfigSerializeWrapper = new KVConfigSerializeWrapper(); kvConfigSerializeWrapper.setConfigTable(this.configTable); String content = kvConfigSerializeWrapper.toJson(); if (null != content) { MixAll.string2File(content, this.namesrvController.getNamesrvConfig().getKvConfigPath()); } } catch (IOException e) { log.error("persist kvconfig Exception, " + this.namesrvController.getNamesrvConfig().getKvConfigPath(), e); } finally { this.lock.readLock().unlock(); } } catch (InterruptedException e) { log.error("persist InterruptedException", e); } } public void deleteKVConfig(final String namespace, final String key) { try { this.lock.writeLock().lockInterruptibly(); try { HashMap kvTable = this.configTable.get(namespace); if (null != kvTable) { String value = kvTable.remove(key); log.info("deleteKVConfig delete a config item, Namespace: {} Key: {} Value: {}", namespace, key, value); } } finally { this.lock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("deleteKVConfig InterruptedException", e); } this.persist(); } public byte[] getKVListByNamespace(final String namespace) { try { this.lock.readLock().lockInterruptibly(); try { HashMap kvTable = this.configTable.get(namespace); if (null != kvTable) { KVTable table = new KVTable(); table.setTable(kvTable); return table.encode(); } } finally { this.lock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getKVListByNamespace InterruptedException", e); } return null; } public String getKVConfig(final String namespace, final String key) { try { this.lock.readLock().lockInterruptibly(); try { HashMap kvTable = this.configTable.get(namespace); if (null != kvTable) { return kvTable.get(key); } } finally { this.lock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getKVConfig InterruptedException", e); } return null; } public void printAllPeriodically() { try { this.lock.readLock().lockInterruptibly(); try { log.info("--------------------------------------------------------"); { log.info("configTable SIZE: {}", this.configTable.size()); for (Entry> next : this.configTable.entrySet()) { for (Entry nextSub : next.getValue().entrySet()) { log.info("configTable NS: {} Key: {} Value: {}", next.getKey(), nextSub.getKey(), nextSub.getValue()); } } } } finally { this.lock.readLock().unlock(); } } catch (InterruptedException e) { log.error("printAllPeriodically InterruptedException", e); } } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.kvconfig; import java.util.HashMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class KVConfigSerializeWrapper extends RemotingSerializable { private HashMap> configTable; public HashMap> getConfigTable() { return configTable; } public void setConfigTable(HashMap> configTable) { this.configTable = configTable; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClientRequestProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.processor; import com.alibaba.fastjson2.JSONWriter; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.namesrv.NamesrvUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import java.util.Optional; import java.util.concurrent.TimeUnit; public class ClientRequestProcessor implements NettyRequestProcessor { private static Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); protected NamesrvController namesrvController; private long startupTimeMillis; public ClientRequestProcessor(final NamesrvController namesrvController) { this.namesrvController = namesrvController; this.startupTimeMillis = System.currentTimeMillis(); } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, final RemotingCommand request) throws Exception { return this.getRouteInfoByTopic(ctx, request); } public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetRouteInfoRequestHeader requestHeader = (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); boolean namesrvReady = System.currentTimeMillis() - startupTimeMillis >= TimeUnit.SECONDS.toMillis(namesrvController.getNamesrvConfig().getWaitSecondsForService()); if (namesrvController.getNamesrvConfig().isNeedWaitForService() && !namesrvReady) { log.warn("name server not ready. request code {} ", request.getCode()); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("name server not ready"); return response; } 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; Boolean standardJsonOnly = Optional.ofNullable(requestHeader.getAcceptStandardJsonOnly()).orElse(false); if (request.getVersion() >= MQVersion.Version.V4_9_4.ordinal() || standardJsonOnly) { content = topicRouteData.encode(JSONWriter.Feature.BrowserCompatible, JSONWriter.Feature.MapSortField); } else { 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; } @Override public boolean rejectRequest() { return false; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.processor; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.namesrv.NamesrvUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; public class ClusterTestRequestProcessor extends ClientRequestProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private final DefaultMQAdminExt adminExt; private final String productEnvName; public ClusterTestRequestProcessor(NamesrvController namesrvController, String productEnvName) { super(namesrvController); this.productEnvName = productEnvName; adminExt = new DefaultMQAdminExt(); adminExt.setInstanceName("CLUSTER_TEST_NS_INS_" + productEnvName); adminExt.setUnitName(productEnvName); try { adminExt.start(); } catch (MQClientException e) { log.error("Failed to start processor", e); } } @Override public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetRouteInfoRequestHeader requestHeader = (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic()); if (topicRouteData != null) { String orderTopicConf = this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, requestHeader.getTopic()); topicRouteData.setOrderTopicConf(orderTopicConf); } else { try { topicRouteData = adminExt.examineTopicRouteInfo(requestHeader.getTopic()); } catch (Exception e) { log.info("get route info by topic from product environment failed. envName={},", productEnvName); } } if (topicRouteData != null) { 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; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.processor; import io.netty.channel.ChannelHandlerContext; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MQVersion.Version; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.namesrv.NamesrvUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.GetBrokerMemberGroupResponseBody; import org.apache.rocketmq.remoting.protocol.body.RegisterBrokerBody; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.header.GetBrokerMemberGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetTopicsByClusterRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.BrokerHeartbeatRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVListByNamespaceRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.PutKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.QueryDataVersionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.QueryDataVersionResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterTopicRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.UnRegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class DefaultRequestProcessor implements NettyRequestProcessor { private static Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); protected final NamesrvController namesrvController; protected Set configBlackList = new HashSet<>(); public DefaultRequestProcessor(NamesrvController namesrvController) { this.namesrvController = namesrvController; initConfigBlackList(); } private void initConfigBlackList() { configBlackList.add("configBlackList"); configBlackList.add("configStorePath"); configBlackList.add("kvConfigPath"); configBlackList.add("rocketmqHome"); String[] configArray = namesrvController.getNamesrvConfig().getConfigBlackList().split(";"); configBlackList.addAll(Arrays.asList(configArray)); } @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 this.queryBrokerTopicConfig(ctx, request); case RequestCode.REGISTER_BROKER: return this.registerBroker(ctx, request); case RequestCode.UNREGISTER_BROKER: return this.unregisterBroker(ctx, request); case RequestCode.BROKER_HEARTBEAT: return this.brokerHeartbeat(ctx, request); case RequestCode.GET_BROKER_MEMBER_GROUP: return this.getBrokerMemberGroup(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.ADD_WRITE_PERM_OF_BROKER: return this.addWritePermOfBroker(ctx, request); case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER: return this.getAllTopicListFromNameserver(ctx, request); case RequestCode.DELETE_TOPIC_IN_NAMESRV: return this.deleteTopicInNamesrv(ctx, request); case RequestCode.REGISTER_TOPIC_IN_NAMESRV: return this.registerTopicToNamesrv(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: String error = " request type " + request.getCode() + " not supported"; return RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); } } @Override public boolean rejectRequest() { return false; } public RemotingCommand putKVConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final PutKVConfigRequestHeader requestHeader = (PutKVConfigRequestHeader) request.decodeCommandCustomHeader(PutKVConfigRequestHeader.class); if (requestHeader.getNamespace() == null || requestHeader.getKey() == null) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("namespace or key is null"); return response; } this.namesrvController.getKvConfigManager().putKVConfig( requestHeader.getNamespace(), requestHeader.getKey(), requestHeader.getValue() ); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand getKVConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(GetKVConfigResponseHeader.class); final GetKVConfigResponseHeader responseHeader = (GetKVConfigResponseHeader) response.readCustomHeader(); final GetKVConfigRequestHeader requestHeader = (GetKVConfigRequestHeader) request.decodeCommandCustomHeader(GetKVConfigRequestHeader.class); String value = this.namesrvController.getKvConfigManager().getKVConfig( requestHeader.getNamespace(), requestHeader.getKey() ); if (value != null) { responseHeader.setValue(value); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } response.setCode(ResponseCode.QUERY_NOT_FOUND); response.setRemark("No config item, Namespace: " + requestHeader.getNamespace() + " Key: " + requestHeader.getKey()); return response; } public RemotingCommand deleteKVConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final DeleteKVConfigRequestHeader requestHeader = (DeleteKVConfigRequestHeader) request.decodeCommandCustomHeader(DeleteKVConfigRequestHeader.class); this.namesrvController.getKvConfigManager().deleteKVConfig( requestHeader.getNamespace(), requestHeader.getKey() ); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand registerBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class); final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader(); final RegisterBrokerRequestHeader requestHeader = (RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class); if (!checksum(ctx, request, requestHeader)) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("crc32 not match"); return response; } TopicConfigSerializeWrapper topicConfigWrapper = null; List filterServerList = null; Version brokerVersion = MQVersion.value2Version(request.getVersion()); if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) { final RegisterBrokerBody registerBrokerBody = extractRegisterBrokerBodyFromRequest(request, requestHeader); topicConfigWrapper = registerBrokerBody.getTopicConfigSerializeWrapper(); filterServerList = registerBrokerBody.getFilterServerList(); } else { // RegisterBrokerBody of old version only contains TopicConfig. topicConfigWrapper = extractRegisterTopicConfigFromRequest(request); } RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker( requestHeader.getClusterName(), requestHeader.getBrokerAddr(), requestHeader.getBrokerName(), requestHeader.getBrokerId(), requestHeader.getHaServerAddr(), request.getExtFields().get(MixAll.ZONE_NAME), requestHeader.getHeartbeatTimeoutMillis(), requestHeader.getEnableActingMaster(), topicConfigWrapper, filterServerList, ctx.channel() ); if (result == null) { // Register single topic route info should be after the broker completes the first registration. response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("register broker failed"); return response; } responseHeader.setHaServerAddr(result.getHaServerAddr()); responseHeader.setMasterAddr(result.getMasterAddr()); if (this.namesrvController.getNamesrvConfig().isReturnOrderTopicConfigToBroker()) { byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG); response.setBody(jsonValue); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private TopicConfigSerializeWrapper extractRegisterTopicConfigFromRequest(final RemotingCommand request) { TopicConfigSerializeWrapper topicConfigWrapper; if (request.getBody() != null) { topicConfigWrapper = TopicConfigSerializeWrapper.decode(request.getBody(), TopicConfigSerializeWrapper.class); } else { topicConfigWrapper = new TopicConfigSerializeWrapper(); topicConfigWrapper.getDataVersion().setCounter(new AtomicLong(0)); topicConfigWrapper.getDataVersion().setTimestamp(0L); topicConfigWrapper.getDataVersion().setStateVersion(0L); } return topicConfigWrapper; } private RegisterBrokerBody extractRegisterBrokerBodyFromRequest(RemotingCommand request, RegisterBrokerRequestHeader requestHeader) throws RemotingCommandException { RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody(); if (request.getBody() != null) { try { Version brokerVersion = MQVersion.value2Version(request.getVersion()); registerBrokerBody = RegisterBrokerBody.decode(request.getBody(), requestHeader.isCompressed(), brokerVersion); } catch (Exception e) { throw new RemotingCommandException("Failed to decode RegisterBrokerBody", e); } } else { registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setCounter(new AtomicLong(0)); registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setTimestamp(0L); registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setStateVersion(0L); } return registerBrokerBody; } private RemotingCommand getBrokerMemberGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { GetBrokerMemberGroupRequestHeader requestHeader = (GetBrokerMemberGroupRequestHeader) request.decodeCommandCustomHeader(GetBrokerMemberGroupRequestHeader.class); BrokerMemberGroup memberGroup = this.namesrvController.getRouteInfoManager() .getBrokerMemberGroup(requestHeader.getClusterName(), requestHeader.getBrokerName()); GetBrokerMemberGroupResponseBody responseBody = new GetBrokerMemberGroupResponseBody(); responseBody.setBrokerMemberGroup(memberGroup); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setBody(responseBody.encode()); return response; } private boolean checksum(ChannelHandlerContext ctx, RemotingCommand request, RegisterBrokerRequestHeader requestHeader) { if (requestHeader.getBodyCrc32() != 0) { final int crc32 = UtilAll.crc32(request.getBody()); if (crc32 != requestHeader.getBodyCrc32()) { log.warn(String.format("receive registerBroker request,crc32 not match,from %s", RemotingHelper.parseChannelRemoteAddr(ctx.channel()))); return false; } } return true; } public RemotingCommand queryBrokerTopicConfig(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(QueryDataVersionResponseHeader.class); final QueryDataVersionResponseHeader responseHeader = (QueryDataVersionResponseHeader) response.readCustomHeader(); final QueryDataVersionRequestHeader requestHeader = (QueryDataVersionRequestHeader) request.decodeCommandCustomHeader(QueryDataVersionRequestHeader.class); DataVersion dataVersion = DataVersion.decode(request.getBody(), DataVersion.class); String clusterName = requestHeader.getClusterName(); String brokerAddr = requestHeader.getBrokerAddr(); Boolean changed = this.namesrvController.getRouteInfoManager().isBrokerTopicConfigChanged(clusterName, brokerAddr, dataVersion); this.namesrvController.getRouteInfoManager().updateBrokerInfoUpdateTimestamp(clusterName, brokerAddr); DataVersion nameSeverDataVersion = this.namesrvController.getRouteInfoManager().queryBrokerTopicConfig(clusterName, brokerAddr); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); if (nameSeverDataVersion != null) { response.setBody(nameSeverDataVersion.encode()); } responseHeader.setChanged(changed); return response; } public RemotingCommand unregisterBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final UnRegisterBrokerRequestHeader requestHeader = (UnRegisterBrokerRequestHeader) request.decodeCommandCustomHeader(UnRegisterBrokerRequestHeader.class); if (!this.namesrvController.getRouteInfoManager().submitUnRegisterBrokerRequest(requestHeader)) { log.warn("Couldn't submit the unregister broker request to handler, broker info: {}", requestHeader); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(null); return response; } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } public RemotingCommand brokerHeartbeat(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final BrokerHeartbeatRequestHeader requestHeader = (BrokerHeartbeatRequestHeader) request.decodeCommandCustomHeader(BrokerHeartbeatRequestHeader.class); this.namesrvController.getRouteInfoManager().updateBrokerInfoUpdateTimestamp(requestHeader.getClusterName(), requestHeader.getBrokerAddr()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getBrokerClusterInfo(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); byte[] content = this.namesrvController.getRouteInfoManager().getAllClusterInfo().encode(); response.setBody(content); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand wipeWritePermOfBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(WipeWritePermOfBrokerResponseHeader.class); final WipeWritePermOfBrokerResponseHeader responseHeader = (WipeWritePermOfBrokerResponseHeader) response.readCustomHeader(); final WipeWritePermOfBrokerRequestHeader requestHeader = (WipeWritePermOfBrokerRequestHeader) request.decodeCommandCustomHeader(WipeWritePermOfBrokerRequestHeader.class); int wipeTopicCnt = this.namesrvController.getRouteInfoManager().wipeWritePermOfBrokerByLock(requestHeader.getBrokerName()); if (ctx != null) { log.info("wipe write perm of broker[{}], client: {}, {}", requestHeader.getBrokerName(), RemotingHelper.parseChannelRemoteAddr(ctx.channel()), wipeTopicCnt); } responseHeader.setWipeTopicCount(wipeTopicCnt); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand addWritePermOfBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(AddWritePermOfBrokerResponseHeader.class); final AddWritePermOfBrokerResponseHeader responseHeader = (AddWritePermOfBrokerResponseHeader) response.readCustomHeader(); final AddWritePermOfBrokerRequestHeader requestHeader = (AddWritePermOfBrokerRequestHeader) request.decodeCommandCustomHeader(AddWritePermOfBrokerRequestHeader.class); int addTopicCnt = this.namesrvController.getRouteInfoManager().addWritePermOfBrokerByLock(requestHeader.getBrokerName()); log.info("add write perm of broker[{}], client: {}, {}", requestHeader.getBrokerName(), RemotingHelper.parseChannelRemoteAddr(ctx.channel()), addTopicCnt); responseHeader.setAddTopicCount(addTopicCnt); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getAllTopicListFromNameserver(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); boolean enableAllTopicList = namesrvController.getNamesrvConfig().isEnableAllTopicList(); log.warn("getAllTopicListFromNameserver {} enable {}", ctx.channel().remoteAddress(), enableAllTopicList); if (enableAllTopicList) { byte[] body = this.namesrvController.getRouteInfoManager().getAllTopicList().encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("disable"); } return response; } private RemotingCommand registerTopicToNamesrv(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final RegisterTopicRequestHeader requestHeader = (RegisterTopicRequestHeader) request.decodeCommandCustomHeader(RegisterTopicRequestHeader.class); TopicRouteData topicRouteData = TopicRouteData.decode(request.getBody(), TopicRouteData.class); if (topicRouteData != null && topicRouteData.getQueueDatas() != null && !topicRouteData.getQueueDatas().isEmpty()) { this.namesrvController.getRouteInfoManager().registerTopic(requestHeader.getTopic(), topicRouteData.getQueueDatas()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand deleteTopicInNamesrv(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final DeleteTopicFromNamesrvRequestHeader requestHeader = (DeleteTopicFromNamesrvRequestHeader) request.decodeCommandCustomHeader(DeleteTopicFromNamesrvRequestHeader.class); if (requestHeader.getClusterName() != null && !requestHeader.getClusterName().isEmpty()) { this.namesrvController.getRouteInfoManager().deleteTopic(requestHeader.getTopic(), requestHeader.getClusterName()); } else { this.namesrvController.getRouteInfoManager().deleteTopic(requestHeader.getTopic()); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getKVListByNamespace(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetKVListByNamespaceRequestHeader requestHeader = (GetKVListByNamespaceRequestHeader) request.decodeCommandCustomHeader(GetKVListByNamespaceRequestHeader.class); byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace( requestHeader.getNamespace()); if (null != jsonValue) { response.setBody(jsonValue); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } response.setCode(ResponseCode.QUERY_NOT_FOUND); response.setRemark("No config item, Namespace: " + requestHeader.getNamespace()); return response; } private RemotingCommand getTopicsByCluster(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); boolean enableTopicList = namesrvController.getNamesrvConfig().isEnableTopicList(); if (!enableTopicList) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("disable"); return response; } final GetTopicsByClusterRequestHeader requestHeader = (GetTopicsByClusterRequestHeader) request.decodeCommandCustomHeader(GetTopicsByClusterRequestHeader.class); TopicList topicsByCluster = this.namesrvController.getRouteInfoManager().getTopicsByCluster(requestHeader.getCluster()); byte[] body = topicsByCluster.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getSystemTopicListFromNs(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); TopicList systemTopicList = this.namesrvController.getRouteInfoManager().getSystemTopicList(); byte[] body = systemTopicList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getUnitTopicList(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); boolean enableTopicList = namesrvController.getNamesrvConfig().isEnableTopicList(); if (enableTopicList) { TopicList unitTopicList = this.namesrvController.getRouteInfoManager().getUnitTopics(); byte[] body = unitTopicList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("disable"); } return response; } private RemotingCommand getHasUnitSubTopicList(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); boolean enableTopicList = namesrvController.getNamesrvConfig().isEnableTopicList(); if (enableTopicList) { TopicList hasUnitSubTopicList = this.namesrvController.getRouteInfoManager().getHasUnitSubTopicList(); byte[] body = hasUnitSubTopicList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("disable"); } return response; } private RemotingCommand getHasUnitSubUnUnitTopicList(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); boolean enableTopicList = namesrvController.getNamesrvConfig().isEnableTopicList(); if (enableTopicList) { TopicList hasUnitSubUnUnitTopicList = this.namesrvController.getRouteInfoManager().getHasUnitSubUnUnitTopicList(); byte[] body = hasUnitSubUnUnitTopicList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("disable"); } return response; } private RemotingCommand updateConfig(ChannelHandlerContext ctx, RemotingCommand request) { if (ctx != null) { log.info("updateConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); } final RemotingCommand response = RemotingCommand.createResponseCommand(null); byte[] body = request.getBody(); if (body != null) { String bodyStr; try { bodyStr = new String(body, MixAll.DEFAULT_CHARSET); } catch (UnsupportedEncodingException e) { log.error("updateConfig byte array to string error: ", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } Properties properties = MixAll.string2Properties(bodyStr); if (properties == null) { log.error("updateConfig MixAll.string2Properties error {}", bodyStr); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string2Properties error"); return response; } if (validateBlackListConfigExist(properties)) { response.setCode(ResponseCode.NO_PERMISSION); response.setRemark("Can not update config in black list."); return response; } this.namesrvController.getConfiguration().update(properties); } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private RemotingCommand getConfig(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); String content = this.namesrvController.getConfiguration().getAllConfigsFormatString(); if (StringUtils.isNotBlank(content)) { try { content = MixAll.adjustConfigForPlatform(content); response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { log.error("getConfig error, ", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } private boolean validateBlackListConfigExist(Properties properties) { for (String blackConfig : configBlackList) { if (properties.containsKey(blackConfig)) { return true; } } return false; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/route/ZoneRouteRPCHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.route; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class ZoneRouteRPCHook implements RPCHook { @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { if (RequestCode.GET_ROUTEINFO_BY_TOPIC != request.getCode()) { return; } if (response == null || response.getBody() == null || ResponseCode.SUCCESS != response.getCode()) { return; } boolean zoneMode = Boolean.parseBoolean(request.getExtFields().get(MixAll.ZONE_MODE)); if (!zoneMode) { return; } String zoneName = request.getExtFields().get(MixAll.ZONE_NAME); if (StringUtils.isBlank(zoneName)) { return; } TopicRouteData topicRouteData = RemotingSerializable.decode(response.getBody(), TopicRouteData.class); response.setBody(filterByZoneName(topicRouteData, zoneName).encode()); } private TopicRouteData filterByZoneName(TopicRouteData topicRouteData, String zoneName) { List brokerDataReserved = new ArrayList<>(); Map brokerDataRemoved = new HashMap<>(); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { if (brokerData.getBrokerAddrs() == null) { continue; } //master down, consume from slave. break nearby route rule. if (brokerData.getBrokerAddrs().get(MixAll.MASTER_ID) == null || StringUtils.equalsIgnoreCase(brokerData.getZoneName(), zoneName)) { brokerDataReserved.add(brokerData); } else { brokerDataRemoved.put(brokerData.getBrokerName(), brokerData); } } topicRouteData.setBrokerDatas(brokerDataReserved); List queueDataReserved = new ArrayList<>(); for (QueueData queueData : topicRouteData.getQueueDatas()) { if (!brokerDataRemoved.containsKey(queueData.getBrokerName())) { queueDataReserved.add(queueData); } } topicRouteData.setQueueDatas(queueDataReserved); // remove filter server table by broker address if (topicRouteData.getFilterServerTable() != null && !topicRouteData.getFilterServerTable().isEmpty()) { for (Entry entry : brokerDataRemoved.entrySet()) { BrokerData brokerData = entry.getValue(); brokerData.getBrokerAddrs().values() .forEach(brokerAddr -> topicRouteData.getFilterServerTable().remove(brokerAddr)); } } return topicRouteData; } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BatchUnregistrationService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import java.util.HashSet; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.header.namesrv.UnRegisterBrokerRequestHeader; /** * BatchUnregistrationService provides a mechanism to unregister brokers in batch manner, which speeds up broker-offline * process. */ public class BatchUnregistrationService extends ServiceThread { private final RouteInfoManager routeInfoManager; private BlockingQueue unregistrationQueue; private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); public BatchUnregistrationService(RouteInfoManager routeInfoManager, NamesrvConfig namesrvConfig) { this.routeInfoManager = routeInfoManager; this.unregistrationQueue = new LinkedBlockingQueue<>(namesrvConfig.getUnRegisterBrokerQueueCapacity()); } /** * Submits an unregister request to this queue. * * @param unRegisterRequest the request to submit * @return {@code true} if the request was added to this queue, else {@code false} */ public boolean submit(UnRegisterBrokerRequestHeader unRegisterRequest) { return unregistrationQueue.offer(unRegisterRequest); } @Override public String getServiceName() { return BatchUnregistrationService.class.getName(); } @Override public void run() { while (!this.isStopped()) { try { final UnRegisterBrokerRequestHeader request = unregistrationQueue.take(); Set unregistrationRequests = new HashSet<>(); unregistrationQueue.drainTo(unregistrationRequests); // Add polled request unregistrationRequests.add(request); this.routeInfoManager.unRegisterBroker(unregistrationRequests); } catch (Throwable e) { log.error("Handle unregister broker request failed", e); } } } // For test only int queueLength() { return this.unregistrationQueue.size(); } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BrokerHousekeepingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import io.netty.channel.Channel; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.ChannelEventListener; public class BrokerHousekeepingService implements ChannelEventListener { private final NamesrvController namesrvController; public BrokerHousekeepingService(NamesrvController namesrvController) { this.namesrvController = namesrvController; } @Override public void onChannelConnect(String remoteAddr, Channel channel) { } @Override public void onChannelClose(String remoteAddr, Channel channel) { this.namesrvController.getRouteInfoManager().onChannelDestroy(channel); } @Override public void onChannelException(String remoteAddr, Channel channel) { this.namesrvController.getRouteInfoManager().onChannelDestroy(channel); } @Override public void onChannelIdle(String remoteAddr, Channel channel) { this.namesrvController.getRouteInfoManager().onChannelDestroy(channel); } @Override public void onChannelActive(String remoteAddr, Channel channel) { } } ================================================ FILE: namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import com.google.common.collect.Sets; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.common.sysflag.TopicSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.header.NotifyMinBrokerIdChangeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.UnRegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; public class RouteInfoManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private static final long DEFAULT_BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Map> topicQueueTable; private final Map brokerAddrTable; private final Map> clusterAddrTable; private final Map brokerLiveTable; private final Map/* Filter Server */> filterServerTable; private final Map> topicQueueMappingInfoTable; private final BatchUnregistrationService unRegisterService; private final NamesrvController namesrvController; private final NamesrvConfig namesrvConfig; public RouteInfoManager(final NamesrvConfig namesrvConfig, NamesrvController namesrvController) { this.topicQueueTable = new ConcurrentHashMap<>(1024); this.brokerAddrTable = new ConcurrentHashMap<>(128); this.clusterAddrTable = new ConcurrentHashMap<>(32); this.brokerLiveTable = new ConcurrentHashMap<>(256); this.filterServerTable = new ConcurrentHashMap<>(256); this.topicQueueMappingInfoTable = new ConcurrentHashMap<>(1024); this.unRegisterService = new BatchUnregistrationService(this, namesrvConfig); this.namesrvConfig = namesrvConfig; this.namesrvController = namesrvController; } public void start() { this.unRegisterService.start(); } public void shutdown() { this.unRegisterService.shutdown(true); } public boolean submitUnRegisterBrokerRequest(UnRegisterBrokerRequestHeader unRegisterRequest) { return this.unRegisterService.submit(unRegisterRequest); } // For test only int blockedUnRegisterRequests() { return this.unRegisterService.queueLength(); } public ClusterInfo getAllClusterInfo() { ClusterInfo clusterInfoSerializeWrapper = new ClusterInfo(); clusterInfoSerializeWrapper.setBrokerAddrTable(this.brokerAddrTable); clusterInfoSerializeWrapper.setClusterAddrTable(this.clusterAddrTable); return clusterInfoSerializeWrapper; } public void registerTopic(final String topic, List queueDatas) { if (queueDatas == null || queueDatas.isEmpty()) { return; } try { this.lock.writeLock().lockInterruptibly(); if (this.topicQueueTable.containsKey(topic)) { Map queueDataMap = this.topicQueueTable.get(topic); for (QueueData queueData : queueDatas) { if (!this.brokerAddrTable.containsKey(queueData.getBrokerName())) { log.warn("Register topic contains illegal broker, {}, {}", topic, queueData); return; } queueDataMap.put(queueData.getBrokerName(), queueData); } log.info("Topic route already exist.{}, {}", topic, this.topicQueueTable.get(topic)); } else { // check and construct queue data map Map queueDataMap = new HashMap<>(); for (QueueData queueData : queueDatas) { if (!this.brokerAddrTable.containsKey(queueData.getBrokerName())) { log.warn("Register topic contains illegal broker, {}, {}", topic, queueData); return; } queueDataMap.put(queueData.getBrokerName(), queueData); } this.topicQueueTable.put(topic, queueDataMap); log.info("Register topic route:{}, {}", topic, queueDatas); } } catch (Exception e) { log.error("registerTopic Exception", e); } finally { this.lock.writeLock().unlock(); } } public void deleteTopic(final String topic) { try { this.lock.writeLock().lockInterruptibly(); this.topicQueueTable.remove(topic); } catch (Exception e) { log.error("deleteTopic Exception", e); } finally { this.lock.writeLock().unlock(); } } public void deleteTopic(final String topic, final String clusterName) { try { this.lock.writeLock().lockInterruptibly(); //get all the brokerNames fot the specified cluster Set brokerNames = this.clusterAddrTable.get(clusterName); if (brokerNames == null || brokerNames.isEmpty()) { return; } //get the store information for single topic Map queueDataMap = this.topicQueueTable.get(topic); if (queueDataMap != null) { for (String brokerName : brokerNames) { final QueueData removedQD = queueDataMap.remove(brokerName); if (removedQD != null) { log.info("deleteTopic, remove one broker's topic {} {} {}", brokerName, topic, removedQD); } } if (queueDataMap.isEmpty()) { log.info("deleteTopic, remove the topic all queue {} {}", clusterName, topic); this.topicQueueTable.remove(topic); } } } catch (Exception e) { log.error("deleteTopic Exception", e); } finally { this.lock.writeLock().unlock(); } } public TopicList getAllTopicList() { TopicList topicList = new TopicList(); try { this.lock.readLock().lockInterruptibly(); topicList.getTopicList().addAll(this.topicQueueTable.keySet()); } catch (Exception e) { log.error("getAllTopicList Exception", e); } finally { this.lock.readLock().unlock(); } return topicList; } public RegisterBrokerResult registerBroker( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final String haServerAddr, final String zoneName, final Long timeoutMillis, final TopicConfigSerializeWrapper topicConfigWrapper, final List filterServerList, final Channel channel) { return registerBroker(clusterName, brokerAddr, brokerName, brokerId, haServerAddr, zoneName, timeoutMillis, false, topicConfigWrapper, filterServerList, channel); } public RegisterBrokerResult registerBroker( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final String haServerAddr, final String zoneName, final Long timeoutMillis, final Boolean enableActingMaster, final TopicConfigSerializeWrapper topicConfigWrapper, final List filterServerList, final Channel channel) { RegisterBrokerResult result = new RegisterBrokerResult(); try { this.lock.writeLock().lockInterruptibly(); //init or update the cluster info Set brokerNames = ConcurrentHashMapUtils.computeIfAbsent((ConcurrentHashMap>) this.clusterAddrTable, clusterName, k -> new HashSet<>()); brokerNames.add(brokerName); boolean registerFirst = false; BrokerData brokerData = this.brokerAddrTable.get(brokerName); if (null == brokerData) { registerFirst = true; brokerData = new BrokerData(clusterName, brokerName, new HashMap<>()); this.brokerAddrTable.put(brokerName, brokerData); } boolean isOldVersionBroker = enableActingMaster == null; brokerData.setEnableActingMaster(!isOldVersionBroker && enableActingMaster); brokerData.setZoneName(zoneName); Map brokerAddrsMap = brokerData.getBrokerAddrs(); boolean isMinBrokerIdChanged = false; long prevMinBrokerId = 0; if (!brokerAddrsMap.isEmpty()) { prevMinBrokerId = Collections.min(brokerAddrsMap.keySet()); } if (brokerId < prevMinBrokerId) { isMinBrokerIdChanged = true; } //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 brokerAddrsMap.entrySet().removeIf(item -> null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()); //If Local brokerId stateVersion bigger than the registering one, String oldBrokerAddr = brokerAddrsMap.get(brokerId); if (null != oldBrokerAddr && !oldBrokerAddr.equals(brokerAddr)) { BrokerLiveInfo oldBrokerInfo = brokerLiveTable.get(new BrokerAddrInfo(clusterName, oldBrokerAddr)); if (null != oldBrokerInfo) { long oldStateVersion = oldBrokerInfo.getDataVersion().getStateVersion(); long newStateVersion = topicConfigWrapper.getDataVersion().getStateVersion(); if (oldStateVersion > newStateVersion) { log.warn("Registering Broker conflicts with the existed one, just ignore.: Cluster:{}, BrokerName:{}, BrokerId:{}, " + "Old BrokerAddr:{}, Old Version:{}, New BrokerAddr:{}, New Version:{}.", clusterName, brokerName, brokerId, oldBrokerAddr, oldStateVersion, brokerAddr, newStateVersion); //Remove the rejected brokerAddr from brokerLiveTable. brokerLiveTable.remove(new BrokerAddrInfo(clusterName, brokerAddr)); return result; } } } if (!brokerAddrsMap.containsKey(brokerId) && topicConfigWrapper.getTopicConfigTable().size() == 1) { log.warn("Can't register topicConfigWrapper={} because broker[{}]={} has not registered.", topicConfigWrapper.getTopicConfigTable(), brokerId, brokerAddr); return null; } String oldAddr = brokerAddrsMap.put(brokerId, brokerAddr); registerFirst = registerFirst || (StringUtils.isEmpty(oldAddr)); boolean isMaster = MixAll.MASTER_ID == brokerId; boolean isPrimeSlave = !isOldVersionBroker && !isMaster && brokerId == Collections.min(brokerAddrsMap.keySet()); if (null != topicConfigWrapper && (isMaster || isPrimeSlave)) { ConcurrentMap tcTable = topicConfigWrapper.getTopicConfigTable(); if (tcTable != null) { TopicConfigAndMappingSerializeWrapper mappingSerializeWrapper = TopicConfigAndMappingSerializeWrapper.from(topicConfigWrapper); Map topicQueueMappingInfoMap = mappingSerializeWrapper.getTopicQueueMappingInfoMap(); // Delete the topics that don't exist in tcTable from the current broker // Static topic is not supported currently if (namesrvConfig.isDeleteTopicWithBrokerRegistration() && topicQueueMappingInfoMap.isEmpty()) { final Set oldTopicSet = topicSetOfBrokerName(brokerName); final Set newTopicSet = tcTable.keySet(); final Sets.SetView toDeleteTopics = Sets.difference(oldTopicSet, newTopicSet); for (final String toDeleteTopic : toDeleteTopics) { Map queueDataMap = topicQueueTable.get(toDeleteTopic); final QueueData removedQD = queueDataMap.remove(brokerName); if (removedQD != null) { log.info("deleteTopic, remove one broker's topic {} {} {}", brokerName, toDeleteTopic, removedQD); } if (queueDataMap.isEmpty()) { log.info("deleteTopic, remove the topic all queue {}", toDeleteTopic); topicQueueTable.remove(toDeleteTopic); } } } for (Map.Entry entry : tcTable.entrySet()) { if (registerFirst || this.isTopicConfigChanged(clusterName, brokerAddr, topicConfigWrapper.getDataVersion(), brokerName, entry.getValue().getTopicName())) { final TopicConfig topicConfig = entry.getValue(); // In Slave Acting Master mode, Namesrv will regard the surviving Slave with the smallest brokerId as the "agent" Master, and modify the brokerPermission to read-only. if (isPrimeSlave && brokerData.isEnableActingMaster()) { // Wipe write perm for prime slave topicConfig.setPerm(topicConfig.getPerm() & (~PermName.PERM_WRITE)); } this.createAndUpdateQueueData(brokerName, topicConfig); } } if (this.isBrokerTopicConfigChanged(clusterName, brokerAddr, topicConfigWrapper.getDataVersion()) || registerFirst) { //the topicQueueMappingInfoMap should never be null, but can be empty for (Map.Entry entry : topicQueueMappingInfoMap.entrySet()) { if (!topicQueueMappingInfoTable.containsKey(entry.getKey())) { topicQueueMappingInfoTable.put(entry.getKey(), new HashMap<>()); } //Note asset brokerName equal entry.getValue().getBname() //here use the mappingDetail.bname topicQueueMappingInfoTable.get(entry.getKey()).put(entry.getValue().getBname(), entry.getValue()); } } } } BrokerAddrInfo brokerAddrInfo = new BrokerAddrInfo(clusterName, brokerAddr); BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddrInfo, new BrokerLiveInfo( System.currentTimeMillis(), timeoutMillis == null ? DEFAULT_BROKER_CHANNEL_EXPIRED_TIME : timeoutMillis, topicConfigWrapper == null ? new DataVersion() : topicConfigWrapper.getDataVersion(), channel, haServerAddr)); if (null == prevBrokerLiveInfo) { log.info("new broker registered, {} HAService: {}", brokerAddrInfo, haServerAddr); } if (filterServerList != null) { if (filterServerList.isEmpty()) { this.filterServerTable.remove(brokerAddrInfo); } else { this.filterServerTable.put(brokerAddrInfo, filterServerList); } } if (MixAll.MASTER_ID != brokerId) { String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID); if (masterAddr != null) { BrokerAddrInfo masterAddrInfo = new BrokerAddrInfo(clusterName, masterAddr); BrokerLiveInfo masterLiveInfo = this.brokerLiveTable.get(masterAddrInfo); if (masterLiveInfo != null) { result.setHaServerAddr(masterLiveInfo.getHaServerAddr()); result.setMasterAddr(masterAddr); } } } if (isMinBrokerIdChanged && namesrvConfig.isNotifyMinBrokerIdChanged()) { notifyMinBrokerIdChanged(brokerAddrsMap, null, this.brokerLiveTable.get(brokerAddrInfo).getHaServerAddr()); } } catch (Exception e) { log.error("registerBroker Exception", e); } finally { this.lock.writeLock().unlock(); } return result; } private Set topicSetOfBrokerName(final String brokerName) { Set topicOfBroker = new HashSet<>(); for (final Entry> entry : this.topicQueueTable.entrySet()) { if (entry.getValue().containsKey(brokerName)) { topicOfBroker.add(entry.getKey()); } } return topicOfBroker; } public BrokerMemberGroup getBrokerMemberGroup(String clusterName, String brokerName) { BrokerMemberGroup groupMember = new BrokerMemberGroup(clusterName, brokerName); try { try { this.lock.readLock().lockInterruptibly(); final BrokerData brokerData = this.brokerAddrTable.get(brokerName); if (brokerData != null) { groupMember.getBrokerAddrs().putAll(brokerData.getBrokerAddrs()); } } finally { this.lock.readLock().unlock(); } } catch (Exception e) { log.error("Get broker member group exception", e); } return groupMember; } public boolean isBrokerTopicConfigChanged(final String clusterName, final String brokerAddr, final DataVersion dataVersion) { DataVersion prev = queryBrokerTopicConfig(clusterName, brokerAddr); return null == prev || !prev.equals(dataVersion); } public boolean isTopicConfigChanged(final String clusterName, final String brokerAddr, final DataVersion dataVersion, String brokerName, String topic) { boolean isChange = isBrokerTopicConfigChanged(clusterName, brokerAddr, dataVersion); if (isChange) { return true; } final Map queueDataMap = this.topicQueueTable.get(topic); if (queueDataMap == null || queueDataMap.isEmpty()) { return true; } // The topicQueueTable already contains the broker return !queueDataMap.containsKey(brokerName); } public DataVersion queryBrokerTopicConfig(final String clusterName, final String brokerAddr) { BrokerAddrInfo addrInfo = new BrokerAddrInfo(clusterName, brokerAddr); BrokerLiveInfo prev = this.brokerLiveTable.get(addrInfo); if (prev != null) { return prev.getDataVersion(); } return null; } public void updateBrokerInfoUpdateTimestamp(final String clusterName, final String brokerAddr) { BrokerAddrInfo addrInfo = new BrokerAddrInfo(clusterName, brokerAddr); BrokerLiveInfo prev = this.brokerLiveTable.get(addrInfo); if (prev != null) { prev.setLastUpdateTimestamp(System.currentTimeMillis()); } } private void createAndUpdateQueueData(final String brokerName, final TopicConfig topicConfig) { QueueData queueData = new QueueData(); queueData.setBrokerName(brokerName); queueData.setWriteQueueNums(topicConfig.getWriteQueueNums()); queueData.setReadQueueNums(topicConfig.getReadQueueNums()); queueData.setPerm(topicConfig.getPerm()); queueData.setTopicSysFlag(topicConfig.getTopicSysFlag()); Map queueDataMap = this.topicQueueTable.get(topicConfig.getTopicName()); if (null == queueDataMap) { queueDataMap = new HashMap<>(); queueDataMap.put(brokerName, queueData); this.topicQueueTable.put(topicConfig.getTopicName(), queueDataMap); log.info("new topic registered, {} {}", topicConfig.getTopicName(), queueData); } else { final QueueData existedQD = queueDataMap.get(brokerName); if (existedQD == null) { queueDataMap.put(brokerName, queueData); } else if (!existedQD.equals(queueData)) { log.info("topic changed, {} OLD: {} NEW: {}", topicConfig.getTopicName(), existedQD, queueData); queueDataMap.put(brokerName, queueData); } } } public int wipeWritePermOfBrokerByLock(final String brokerName) { try { try { this.lock.writeLock().lockInterruptibly(); return operateWritePermOfBroker(brokerName, RequestCode.WIPE_WRITE_PERM_OF_BROKER); } finally { this.lock.writeLock().unlock(); } } catch (Exception e) { log.error("wipeWritePermOfBrokerByLock Exception", e); } return 0; } public int addWritePermOfBrokerByLock(final String brokerName) { try { try { this.lock.writeLock().lockInterruptibly(); return operateWritePermOfBroker(brokerName, RequestCode.ADD_WRITE_PERM_OF_BROKER); } finally { this.lock.writeLock().unlock(); } } catch (Exception e) { log.error("addWritePermOfBrokerByLock Exception", e); } return 0; } private int operateWritePermOfBroker(final String brokerName, final int requestCode) { int topicCnt = 0; for (Entry> entry : this.topicQueueTable.entrySet()) { Map qdMap = entry.getValue(); final QueueData qd = qdMap.get(brokerName); if (qd == null) { continue; } int perm = qd.getPerm(); switch (requestCode) { case RequestCode.WIPE_WRITE_PERM_OF_BROKER: perm &= ~PermName.PERM_WRITE; break; case RequestCode.ADD_WRITE_PERM_OF_BROKER: perm = PermName.PERM_READ | PermName.PERM_WRITE; break; } qd.setPerm(perm); topicCnt++; } return topicCnt; } public void unregisterBroker( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId) { UnRegisterBrokerRequestHeader unRegisterBrokerRequest = new UnRegisterBrokerRequestHeader(); unRegisterBrokerRequest.setClusterName(clusterName); unRegisterBrokerRequest.setBrokerAddr(brokerAddr); unRegisterBrokerRequest.setBrokerName(brokerName); unRegisterBrokerRequest.setBrokerId(brokerId); unRegisterBroker(Sets.newHashSet(unRegisterBrokerRequest)); } public void unRegisterBroker(Set unRegisterRequests) { try { Set removedBroker = new HashSet<>(); Set reducedBroker = new HashSet<>(); Map needNotifyBrokerMap = new HashMap<>(); this.lock.writeLock().lockInterruptibly(); for (final UnRegisterBrokerRequestHeader unRegisterRequest : unRegisterRequests) { final String brokerName = unRegisterRequest.getBrokerName(); final String clusterName = unRegisterRequest.getClusterName(); final String brokerAddr = unRegisterRequest.getBrokerAddr(); BrokerAddrInfo brokerAddrInfo = new BrokerAddrInfo(clusterName, brokerAddr); BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.remove(brokerAddrInfo); log.info("unregisterBroker, remove from brokerLiveTable {}, {}", brokerLiveInfo != null ? "OK" : "Failed", brokerAddrInfo ); this.filterServerTable.remove(brokerAddrInfo); boolean removeBrokerName = false; boolean isMinBrokerIdChanged = false; BrokerData brokerData = this.brokerAddrTable.get(brokerName); if (null != brokerData) { if (!brokerData.getBrokerAddrs().isEmpty() && unRegisterRequest.getBrokerId().equals(Collections.min(brokerData.getBrokerAddrs().keySet()))) { isMinBrokerIdChanged = true; } boolean removed = brokerData.getBrokerAddrs().entrySet().removeIf(item -> item.getValue().equals(brokerAddr)); log.info("unregisterBroker, remove addr from brokerAddrTable {}, {}", removed ? "OK" : "Failed", brokerAddrInfo ); if (brokerData.getBrokerAddrs().isEmpty()) { this.brokerAddrTable.remove(brokerName); log.info("unregisterBroker, remove name from brokerAddrTable OK, {}", brokerName ); removeBrokerName = true; } else if (isMinBrokerIdChanged) { needNotifyBrokerMap.put(brokerName, new BrokerStatusChangeInfo( brokerData.getBrokerAddrs(), brokerAddr, null)); } } if (removeBrokerName) { Set nameSet = this.clusterAddrTable.get(clusterName); if (nameSet != null) { boolean removed = nameSet.remove(brokerName); log.info("unregisterBroker, remove name from clusterAddrTable {}, {}", removed ? "OK" : "Failed", brokerName); if (nameSet.isEmpty()) { this.clusterAddrTable.remove(clusterName); log.info("unregisterBroker, remove cluster from clusterAddrTable {}", clusterName ); } } removedBroker.add(brokerName); } else { reducedBroker.add(brokerName); } } cleanTopicByUnRegisterRequests(removedBroker, reducedBroker); if (!needNotifyBrokerMap.isEmpty() && namesrvConfig.isNotifyMinBrokerIdChanged()) { notifyMinBrokerIdChanged(needNotifyBrokerMap); } } catch (Exception e) { log.error("unregisterBroker Exception", e); } finally { this.lock.writeLock().unlock(); } } private void cleanTopicByUnRegisterRequests(Set removedBroker, Set reducedBroker) { Iterator>> itMap = this.topicQueueTable.entrySet().iterator(); while (itMap.hasNext()) { Entry> entry = itMap.next(); String topic = entry.getKey(); Map queueDataMap = entry.getValue(); for (final String brokerName : removedBroker) { final QueueData removedQD = queueDataMap.remove(brokerName); if (removedQD != null) { log.debug("removeTopicByBrokerName, remove one broker's topic {} {}", topic, removedQD); } } if (queueDataMap.isEmpty()) { log.debug("removeTopicByBrokerName, remove the topic all queue {}", topic); itMap.remove(); } for (final String brokerName : reducedBroker) { final QueueData queueData = queueDataMap.get(brokerName); if (queueData != null) { if (this.brokerAddrTable.get(brokerName).isEnableActingMaster()) { // Master has been unregistered, wipe the write perm if (isNoMasterExists(brokerName)) { queueData.setPerm(queueData.getPerm() & (~PermName.PERM_WRITE)); } } } } } } private boolean isNoMasterExists(String brokerName) { final BrokerData brokerData = this.brokerAddrTable.get(brokerName); if (brokerData == null) { return true; } if (brokerData.getBrokerAddrs().size() == 0) { return true; } return Collections.min(brokerData.getBrokerAddrs().keySet()) > 0; } public TopicRouteData pickupTopicRouteData(final String topic) { TopicRouteData topicRouteData = new TopicRouteData(); boolean foundQueueData = false; boolean foundBrokerData = false; List brokerDataList = new LinkedList<>(); topicRouteData.setBrokerDatas(brokerDataList); HashMap> filterServerMap = new HashMap<>(); topicRouteData.setFilterServerTable(filterServerMap); try { this.lock.readLock().lockInterruptibly(); Map queueDataMap = this.topicQueueTable.get(topic); if (queueDataMap != null) { topicRouteData.setQueueDatas(new ArrayList<>(queueDataMap.values())); foundQueueData = true; Set brokerNameSet = new HashSet<>(queueDataMap.keySet()); for (String brokerName : brokerNameSet) { BrokerData brokerData = this.brokerAddrTable.get(brokerName); if (null == brokerData) { continue; } BrokerData brokerDataClone = new BrokerData(brokerData); brokerDataList.add(brokerDataClone); foundBrokerData = true; if (filterServerTable.isEmpty()) { continue; } for (final String brokerAddr : brokerDataClone.getBrokerAddrs().values()) { BrokerAddrInfo brokerAddrInfo = new BrokerAddrInfo(brokerDataClone.getCluster(), brokerAddr); List filterServerList = this.filterServerTable.get(brokerAddrInfo); filterServerMap.put(brokerAddr, filterServerList); } } } } catch (Exception e) { log.error("pickupTopicRouteData Exception", e); } finally { this.lock.readLock().unlock(); } log.debug("pickupTopicRouteData {} {}", topic, topicRouteData); if (foundBrokerData && foundQueueData) { topicRouteData.setTopicQueueMappingByBroker(this.topicQueueMappingInfoTable.get(topic)); if (!namesrvConfig.isSupportActingMaster()) { return topicRouteData; } if (topic.startsWith(TopicValidator.SYNC_BROKER_MEMBER_GROUP_PREFIX)) { return topicRouteData; } if (topicRouteData.getBrokerDatas().size() == 0 || topicRouteData.getQueueDatas().size() == 0) { return topicRouteData; } boolean needActingMaster = false; for (final BrokerData brokerData : topicRouteData.getBrokerDatas()) { if (brokerData.getBrokerAddrs().size() != 0 && !brokerData.getBrokerAddrs().containsKey(MixAll.MASTER_ID)) { needActingMaster = true; break; } } if (!needActingMaster) { return topicRouteData; } for (final BrokerData brokerData : topicRouteData.getBrokerDatas()) { final HashMap brokerAddrs = brokerData.getBrokerAddrs(); if (brokerAddrs.size() == 0 || brokerAddrs.containsKey(MixAll.MASTER_ID) || !brokerData.isEnableActingMaster()) { continue; } // No master for (final QueueData queueData : topicRouteData.getQueueDatas()) { if (queueData.getBrokerName().equals(brokerData.getBrokerName())) { if (!PermName.isWriteable(queueData.getPerm())) { final Long minBrokerId = Collections.min(brokerAddrs.keySet()); final String actingMasterAddr = brokerAddrs.remove(minBrokerId); brokerAddrs.put(MixAll.MASTER_ID, actingMasterAddr); } break; } } } return topicRouteData; } return null; } public void scanNotActiveBroker() { try { log.info("start scanNotActiveBroker"); for (Entry next : this.brokerLiveTable.entrySet()) { long last = next.getValue().getLastUpdateTimestamp(); long timeoutMillis = next.getValue().getHeartbeatTimeoutMillis(); if ((last + timeoutMillis) < System.currentTimeMillis()) { RemotingHelper.closeChannel(next.getValue().getChannel()); log.warn("The broker channel expired, {} {}ms", next.getKey(), timeoutMillis); this.onChannelDestroy(next.getKey()); } } } catch (Exception e) { log.error("scanNotActiveBroker exception", e); } } public void onChannelDestroy(BrokerAddrInfo brokerAddrInfo) { UnRegisterBrokerRequestHeader unRegisterRequest = new UnRegisterBrokerRequestHeader(); boolean needUnRegister = false; if (brokerAddrInfo != null) { try { try { this.lock.readLock().lockInterruptibly(); needUnRegister = setupUnRegisterRequest(unRegisterRequest, brokerAddrInfo); } finally { this.lock.readLock().unlock(); } } catch (Exception e) { log.error("onChannelDestroy Exception", e); } } if (needUnRegister) { boolean result = this.submitUnRegisterBrokerRequest(unRegisterRequest); log.info("the broker's channel destroyed, submit the unregister request at once, " + "broker info: {}, submit result: {}", unRegisterRequest, result); } } public void onChannelDestroy(Channel channel) { UnRegisterBrokerRequestHeader unRegisterRequest = new UnRegisterBrokerRequestHeader(); BrokerAddrInfo brokerAddrFound = null; boolean needUnRegister = false; if (channel != null) { try { try { this.lock.readLock().lockInterruptibly(); for (Entry entry : this.brokerLiveTable.entrySet()) { if (entry.getValue().getChannel() == channel) { brokerAddrFound = entry.getKey(); break; } } if (brokerAddrFound != null) { needUnRegister = setupUnRegisterRequest(unRegisterRequest, brokerAddrFound); } } finally { this.lock.readLock().unlock(); } } catch (Exception e) { log.error("onChannelDestroy Exception", e); } } if (needUnRegister) { boolean result = this.submitUnRegisterBrokerRequest(unRegisterRequest); log.info("the broker's channel destroyed, submit the unregister request at once, " + "broker info: {}, submit result: {}", unRegisterRequest, result); } } private boolean setupUnRegisterRequest(UnRegisterBrokerRequestHeader unRegisterRequest, BrokerAddrInfo brokerAddrInfo) { unRegisterRequest.setClusterName(brokerAddrInfo.getClusterName()); unRegisterRequest.setBrokerAddr(brokerAddrInfo.getBrokerAddr()); for (Entry stringBrokerDataEntry : this.brokerAddrTable.entrySet()) { BrokerData brokerData = stringBrokerDataEntry.getValue(); if (!brokerAddrInfo.getClusterName().equals(brokerData.getCluster())) { continue; } for (Entry entry : brokerData.getBrokerAddrs().entrySet()) { Long brokerId = entry.getKey(); String brokerAddr = entry.getValue(); if (brokerAddr.equals(brokerAddrInfo.getBrokerAddr())) { unRegisterRequest.setBrokerName(brokerData.getBrokerName()); unRegisterRequest.setBrokerId(brokerId); return true; } } } return false; } private void notifyMinBrokerIdChanged(Map needNotifyBrokerMap) throws InterruptedException, RemotingConnectException, RemotingTimeoutException, RemotingSendRequestException, RemotingTooMuchRequestException { for (String brokerName : needNotifyBrokerMap.keySet()) { BrokerStatusChangeInfo brokerStatusChangeInfo = needNotifyBrokerMap.get(brokerName); BrokerData brokerData = brokerAddrTable.get(brokerName); if (brokerData != null && brokerData.isEnableActingMaster()) { notifyMinBrokerIdChanged(brokerStatusChangeInfo.getBrokerAddrs(), brokerStatusChangeInfo.getOfflineBrokerAddr(), brokerStatusChangeInfo.getHaBrokerAddr()); } } } private void notifyMinBrokerIdChanged(Map brokerAddrMap, String offlineBrokerAddr, String haBrokerAddr) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException, RemotingTooMuchRequestException, RemotingConnectException { if (brokerAddrMap == null || brokerAddrMap.isEmpty() || this.namesrvController == null) { return; } NotifyMinBrokerIdChangeRequestHeader requestHeader = new NotifyMinBrokerIdChangeRequestHeader(); long minBrokerId = Collections.min(brokerAddrMap.keySet()); requestHeader.setMinBrokerId(minBrokerId); requestHeader.setMinBrokerAddr(brokerAddrMap.get(minBrokerId)); requestHeader.setOfflineBrokerAddr(offlineBrokerAddr); requestHeader.setHaBrokerAddr(haBrokerAddr); List brokerAddrsNotify = chooseBrokerAddrsToNotify(brokerAddrMap, offlineBrokerAddr); log.info("min broker id changed to {}, notify {}, offline broker addr {}", minBrokerId, brokerAddrsNotify, offlineBrokerAddr); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFY_MIN_BROKER_ID_CHANGE, requestHeader); for (String brokerAddr : brokerAddrsNotify) { this.namesrvController.getRemotingClient().invokeOneway(brokerAddr, request, 300); } } private List chooseBrokerAddrsToNotify(Map brokerAddrMap, String offlineBrokerAddr) { if (offlineBrokerAddr != null || brokerAddrMap.size() == 1) { // notify the reset brokers. return new ArrayList<>(brokerAddrMap.values()); } // new broker registered, notify previous brokers. long minBrokerId = Collections.min(brokerAddrMap.keySet()); List brokerAddrList = new ArrayList<>(); for (Long brokerId : brokerAddrMap.keySet()) { if (brokerId != minBrokerId) { brokerAddrList.add(brokerAddrMap.get(brokerId)); } } return brokerAddrList; } // For test only public void printAllPeriodically() { try { try { this.lock.readLock().lockInterruptibly(); log.info("--------------------------------------------------------"); { log.info("topicQueueTable SIZE: {}", this.topicQueueTable.size()); for (Entry> next : this.topicQueueTable.entrySet()) { log.info("topicQueueTable Topic: {} {}", next.getKey(), next.getValue()); } } { log.info("brokerAddrTable SIZE: {}", this.brokerAddrTable.size()); for (Entry next : this.brokerAddrTable.entrySet()) { log.info("brokerAddrTable brokerName: {} {}", next.getKey(), next.getValue()); } } { log.info("brokerLiveTable SIZE: {}", this.brokerLiveTable.size()); for (Entry next : this.brokerLiveTable.entrySet()) { log.info("brokerLiveTable brokerAddr: {} {}", next.getKey(), next.getValue()); } } { log.info("clusterAddrTable SIZE: {}", this.clusterAddrTable.size()); for (Entry> next : this.clusterAddrTable.entrySet()) { log.info("clusterAddrTable clusterName: {} {}", next.getKey(), next.getValue()); } } } finally { this.lock.readLock().unlock(); } } catch (Exception e) { log.error("printAllPeriodically Exception", e); } } public TopicList getSystemTopicList() { TopicList topicList = new TopicList(); try { this.lock.readLock().lockInterruptibly(); for (Map.Entry> entry : clusterAddrTable.entrySet()) { topicList.getTopicList().add(entry.getKey()); topicList.getTopicList().addAll(entry.getValue()); } if (!brokerAddrTable.isEmpty()) { for (String s : brokerAddrTable.keySet()) { BrokerData bd = brokerAddrTable.get(s); HashMap brokerAddrs = bd.getBrokerAddrs(); if (brokerAddrs != null && !brokerAddrs.isEmpty()) { Iterator it2 = brokerAddrs.keySet().iterator(); topicList.setBrokerAddr(brokerAddrs.get(it2.next())); break; } } } } catch (Exception e) { log.error("getSystemTopicList Exception", e); } finally { this.lock.readLock().unlock(); } return topicList; } public TopicList getTopicsByCluster(String cluster) { TopicList topicList = new TopicList(); try { try { this.lock.readLock().lockInterruptibly(); Set brokerNameSet = this.clusterAddrTable.get(cluster); for (String brokerName : brokerNameSet) { for (Entry> topicEntry : this.topicQueueTable.entrySet()) { String topic = topicEntry.getKey(); Map queueDataMap = topicEntry.getValue(); final QueueData qd = queueDataMap.get(brokerName); if (qd != null) { topicList.getTopicList().add(topic); } } } } finally { this.lock.readLock().unlock(); } } catch (Exception e) { log.error("getTopicsByCluster Exception", e); } return topicList; } public TopicList getUnitTopics() { TopicList topicList = new TopicList(); try { this.lock.readLock().lockInterruptibly(); for (Entry> topicEntry : this.topicQueueTable.entrySet()) { String topic = topicEntry.getKey(); Map queueDatas = topicEntry.getValue(); if (queueDatas != null && queueDatas.size() > 0 && TopicSysFlag.hasUnitFlag(queueDatas.values().iterator().next().getTopicSysFlag())) { topicList.getTopicList().add(topic); } } } catch (Exception e) { log.error("getUnitTopics Exception", e); } finally { this.lock.readLock().unlock(); } return topicList; } public TopicList getHasUnitSubTopicList() { TopicList topicList = new TopicList(); try { this.lock.readLock().lockInterruptibly(); for (Entry> topicEntry : this.topicQueueTable.entrySet()) { String topic = topicEntry.getKey(); Map queueDatas = topicEntry.getValue(); if (queueDatas != null && queueDatas.size() > 0 && TopicSysFlag.hasUnitSubFlag(queueDatas.values().iterator().next().getTopicSysFlag())) { topicList.getTopicList().add(topic); } } } catch (Exception e) { log.error("getHasUnitSubTopicList Exception", e); } finally { this.lock.readLock().unlock(); } return topicList; } public TopicList getHasUnitSubUnUnitTopicList() { TopicList topicList = new TopicList(); try { this.lock.readLock().lockInterruptibly(); for (Entry> topicEntry : this.topicQueueTable.entrySet()) { String topic = topicEntry.getKey(); Map queueDatas = topicEntry.getValue(); if (queueDatas != null && queueDatas.size() > 0 && !TopicSysFlag.hasUnitFlag(queueDatas.values().iterator().next().getTopicSysFlag()) && TopicSysFlag.hasUnitSubFlag(queueDatas.values().iterator().next().getTopicSysFlag())) { topicList.getTopicList().add(topic); } } } catch (Exception e) { log.error("getHasUnitSubUnUnitTopicList Exception", e); } finally { this.lock.readLock().unlock(); } return topicList; } } /** * broker address information */ class BrokerAddrInfo { private String clusterName; private String brokerAddr; private int hash; public BrokerAddrInfo(String clusterName, String brokerAddr) { this.clusterName = clusterName; this.brokerAddr = brokerAddr; } public String getClusterName() { return clusterName; } public String getBrokerAddr() { return brokerAddr; } public boolean isEmpty() { return clusterName.isEmpty() && brokerAddr.isEmpty(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof BrokerAddrInfo) { BrokerAddrInfo addr = (BrokerAddrInfo) obj; return clusterName.equals(addr.clusterName) && brokerAddr.equals(addr.brokerAddr); } return false; } @Override public int hashCode() { int h = hash; if (h == 0 && clusterName.length() + brokerAddr.length() > 0) { for (int i = 0; i < clusterName.length(); i++) { h = 31 * h + clusterName.charAt(i); } h = 31 * h + '_'; for (int i = 0; i < brokerAddr.length(); i++) { h = 31 * h + brokerAddr.charAt(i); } hash = h; } return h; } @Override public String toString() { return "BrokerIdentityInfo [clusterName=" + clusterName + ", brokerAddr=" + brokerAddr + "]"; } } class BrokerLiveInfo { private long lastUpdateTimestamp; private long heartbeatTimeoutMillis; private DataVersion dataVersion; private Channel channel; private String haServerAddr; public BrokerLiveInfo(long lastUpdateTimestamp, long heartbeatTimeoutMillis, DataVersion dataVersion, Channel channel, String haServerAddr) { this.lastUpdateTimestamp = lastUpdateTimestamp; this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; this.dataVersion = dataVersion; this.channel = channel; this.haServerAddr = haServerAddr; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } public long getHeartbeatTimeoutMillis() { return heartbeatTimeoutMillis; } public void setHeartbeatTimeoutMillis(long heartbeatTimeoutMillis) { this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } public Channel getChannel() { return channel; } public void setChannel(Channel channel) { this.channel = channel; } public String getHaServerAddr() { return haServerAddr; } public void setHaServerAddr(String haServerAddr) { this.haServerAddr = haServerAddr; } @Override public String toString() { return "BrokerLiveInfo [lastUpdateTimestamp=" + lastUpdateTimestamp + ", dataVersion=" + dataVersion + ", channel=" + channel + ", haServerAddr=" + haServerAddr + "]"; } } class BrokerStatusChangeInfo { Map brokerAddrs; String offlineBrokerAddr; String haBrokerAddr; public BrokerStatusChangeInfo(Map brokerAddrs, String offlineBrokerAddr, String haBrokerAddr) { this.brokerAddrs = brokerAddrs; this.offlineBrokerAddr = offlineBrokerAddr; this.haBrokerAddr = haBrokerAddr; } public Map getBrokerAddrs() { return brokerAddrs; } public void setBrokerAddrs(Map brokerAddrs) { this.brokerAddrs = brokerAddrs; } public String getOfflineBrokerAddr() { return offlineBrokerAddr; } public void setOfflineBrokerAddr(String offlineBrokerAddr) { this.offlineBrokerAddr = offlineBrokerAddr; } public String getHaBrokerAddr() { return haBrokerAddr; } public void setHaBrokerAddr(String haBrokerAddr) { this.haBrokerAddr = haBrokerAddr; } } ================================================ FILE: namesrv/src/main/resources/rmq.namesrv.logback.xml ================================================ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}namesrv_default.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}namesrv_default.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}namesrv.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}namesrv.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 0 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}namesrv_traffic.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}namesrv_traffic.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class NameServerInstanceTest { protected NamesrvController nameSrvController = null; protected NettyServerConfig nettyServerConfig = new NettyServerConfig(); protected NamesrvConfig namesrvConfig = new NamesrvConfig(); @Before public void startup() throws Exception { nettyServerConfig.setListenPort(9876); nameSrvController = new NamesrvController(namesrvConfig, nettyServerConfig); boolean initResult = nameSrvController.initialize(); assertThat(initResult).isTrue(); nameSrvController.start(); } /** * Add a placeholder to make Bazel happy. */ @Test public void itWorks() { } @After public void shutdown() throws Exception { if (nameSrvController != null) { nameSrvController.shutdown(); } //maybe need to clean the file store. But we do not suggest deleting anything. } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvControllerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.namesrv.kvconfig.KVConfigManager; import org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager; import org.apache.rocketmq.remoting.Configuration; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class NamesrvControllerTest { @Mock private NettyServerConfig nettyServerConfig; @Mock private RemotingServer remotingServer; private NamesrvController namesrvController; @Before public void setUp() throws Exception { NamesrvConfig namesrvConfig = new NamesrvConfig(); namesrvController = new NamesrvController(namesrvConfig, nettyServerConfig); } @Test public void getNamesrvConfig() { NamesrvConfig namesrvConfig = namesrvController.getNamesrvConfig(); Assert.assertNotNull(namesrvConfig); } @Test public void getNettyServerConfig() { NettyServerConfig nettyServerConfig = namesrvController.getNettyServerConfig(); Assert.assertNotNull(nettyServerConfig); } @Test public void getKvConfigManager() { KVConfigManager manager = namesrvController.getKvConfigManager(); Assert.assertNotNull(manager); } @Test public void getRouteInfoManager() { RouteInfoManager manager = namesrvController.getRouteInfoManager(); Assert.assertNotNull(manager); } @Test public void getRemotingServer() { RemotingServer server = namesrvController.getRemotingServer(); Assert.assertNull(server); } @Test public void setRemotingServer() { namesrvController.setRemotingServer(remotingServer); RemotingServer server = namesrvController.getRemotingServer(); Assert.assertEquals(remotingServer, server); } @Test public void getConfiguration() { Configuration configuration = namesrvController.getConfiguration(); Assert.assertNotNull(configuration); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvStartupTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv; import java.util.Properties; import org.apache.commons.cli.Options; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class NamesrvStartupTest { @Mock private NamesrvController namesrvController; @Mock private Options options; @Before public void setUp() throws Exception { Mockito.when(namesrvController.initialize()).thenReturn(true); } @Test public void testStart() throws Exception { NamesrvController controller = NamesrvStartup.start(namesrvController); Assert.assertNotNull(controller); } @Test public void testShutdown() { NamesrvStartup.shutdown(namesrvController); Mockito.verify(namesrvController).shutdown(); } @Test public void testBuildCommandlineOptions() { Options options = NamesrvStartup.buildCommandlineOptions(this.options); Assert.assertNotNull(options); } @Test public void testGetProperties() { Properties properties = NamesrvStartup.getProperties(); Assert.assertNull(properties); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.kvconfig; import org.apache.rocketmq.common.namesrv.NamesrvUtil; import org.apache.rocketmq.namesrv.NameServerInstanceTest; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class KVConfigManagerTest extends NameServerInstanceTest { private KVConfigManager kvConfigManager; @Before public void setup() throws Exception { kvConfigManager = new KVConfigManager(nameSrvController); } @Test public void testPutKVConfig() { kvConfigManager.putKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, "UnitTest", "test"); byte[] kvConfig = kvConfigManager.getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG); assertThat(kvConfig).isNotNull(); String value = kvConfigManager.getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, "UnitTest"); assertThat(value).isEqualTo("test"); } @Test public void testDeleteKVConfig() { kvConfigManager.deleteKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, "UnitTest"); byte[] kvConfig = kvConfigManager.getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG); assertThat(kvConfig).isNull(); Assert.assertTrue(kvConfig == null); String value = kvConfigManager.getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, "UnitTest"); assertThat(value).isNull(); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigSerializeWrapperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.kvconfig; import java.util.HashMap; import org.apache.rocketmq.common.namesrv.NamesrvUtil; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class KVConfigSerializeWrapperTest { private KVConfigSerializeWrapper kvConfigSerializeWrapper; @Before public void setup() throws Exception { kvConfigSerializeWrapper = new KVConfigSerializeWrapper(); } @Test public void testEncodeAndDecode() { HashMap> result = new HashMap<>(); HashMap kvs = new HashMap<>(); kvs.put("broker-name", "default-broker"); kvs.put("topic-name", "default-topic"); kvs.put("cid", "default-consumer-name"); result.put(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, kvs); kvConfigSerializeWrapper.setConfigTable(result); byte[] serializeByte = KVConfigSerializeWrapper.encode(kvConfigSerializeWrapper); assertThat(serializeByte).isNotNull(); KVConfigSerializeWrapper deserializeObject = KVConfigSerializeWrapper.decode(serializeByte, KVConfigSerializeWrapper.class); assertThat(deserializeObject.getConfigTable()).containsKey(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG); assertThat(deserializeObject.getConfigTable().get(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG).get("broker-name")).isEqualTo("default-broker"); assertThat(deserializeObject.getConfigTable().get(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG).get("topic-name")).isEqualTo("default-topic"); assertThat(deserializeObject.getConfigTable().get(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG).get("cid")).isEqualTo("default-consumer-name"); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/ClientRequestProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.processor; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ClientRequestProcessorTest { @Mock private NamesrvController namesrvController; @Mock private RouteInfoManager routeInfoManager; @Mock private NamesrvConfig namesrvConfig; @Mock private ChannelHandlerContext ctx; private ClientRequestProcessor clientRequestProcessor; @Before public void setup() throws NoSuchFieldException, IllegalAccessException { when(namesrvController.getRouteInfoManager()).thenReturn(routeInfoManager); when(namesrvController.getNamesrvConfig()).thenReturn(namesrvConfig); when(namesrvConfig.getWaitSecondsForService()).thenReturn(0); when(namesrvConfig.isNeedWaitForService()).thenReturn(true); clientRequestProcessor = new ClientRequestProcessor(namesrvController); Field startupTimeMillisField = ClientRequestProcessor.class.getDeclaredField("startupTimeMillis"); startupTimeMillisField.setAccessible(true); startupTimeMillisField.set(clientRequestProcessor, System.currentTimeMillis() - 60000); } @Test public void testGetRouteInfoByTopicWithHighVersionClient() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, null); request.setVersion(MQVersion.Version.V4_9_4.ordinal()); GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic("TestTopic"); RemotingCommand spyRequest = spy(request); doReturn(requestHeader).when(spyRequest).decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); TopicRouteData topicRouteData = createMockTopicRouteData(); when(routeInfoManager.pickupTopicRouteData("TestTopic")).thenReturn(topicRouteData); RemotingCommand response = clientRequestProcessor.getRouteInfoByTopic(ctx, spyRequest); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); } @Test public void testGetRouteInfoByTopicWithLowVersionClientAndNoStandardJsonFlag() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, null); request.setVersion(MQVersion.Version.V4_9_3.ordinal()); GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic("TestTopic"); requestHeader.setAcceptStandardJsonOnly(false); RemotingCommand spyRequest = spy(request); doReturn(requestHeader).when(spyRequest).decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); TopicRouteData topicRouteData = createMockTopicRouteData(); when(routeInfoManager.pickupTopicRouteData("TestTopic")).thenReturn(topicRouteData); RemotingCommand response = clientRequestProcessor.getRouteInfoByTopic(ctx, spyRequest); assertEquals(ResponseCode.SUCCESS, response.getCode()); assertNotNull(response.getBody()); } @Test public void testGetRouteInfoByTopicWithNameServerNotReady() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, null); GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic("TestTopic"); RemotingCommand spyRequest = spy(request); doReturn(requestHeader).when(spyRequest).decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); when(namesrvConfig.getWaitSecondsForService()).thenReturn(60); when(namesrvConfig.isNeedWaitForService()).thenReturn(true); try { Field startupTimeMillisField = ClientRequestProcessor.class.getDeclaredField("startupTimeMillis"); startupTimeMillisField.setAccessible(true); startupTimeMillisField.set(clientRequestProcessor, System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } RemotingCommand response = clientRequestProcessor.getRouteInfoByTopic(ctx, spyRequest); assertEquals(ResponseCode.SYSTEM_ERROR, response.getCode()); assertEquals("name server not ready", response.getRemark()); } @Test public void testGetRouteInfoByTopicWithTopicNotExist() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, null); GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic("NonExistentTopic"); RemotingCommand spyRequest = spy(request); doReturn(requestHeader).when(spyRequest).decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); when(routeInfoManager.pickupTopicRouteData("NonExistentTopic")).thenReturn(null); RemotingCommand response = clientRequestProcessor.getRouteInfoByTopic(ctx, spyRequest); assertEquals(ResponseCode.TOPIC_NOT_EXIST, response.getCode()); assertNotNull(response.getRemark()); } private TopicRouteData createMockTopicRouteData() { TopicRouteData result = new TopicRouteData(); List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-a"); queueData.setReadQueueNums(4); queueData.setWriteQueueNums(4); queueData.setPerm(6); queueData.setTopicSysFlag(0); queueDataList.add(queueData); result.setQueueDatas(queueDataList); List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("broker-a"); brokerData.setCluster("default-cluster"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDataList.add(brokerData); result.setBrokerDatas(brokerDataList); return result; } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.processor; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.MQClientManager; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.admin.DefaultMQAdminExtImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ClusterTestRequestProcessorTest { private ClusterTestRequestProcessor clusterTestProcessor; private DefaultMQAdminExtImpl defaultMQAdminExtImpl; private MQClientInstance mqClientInstance = MQClientManager.getInstance() .getOrCreateMQClientInstance(new ClientConfig()); private MQClientAPIImpl mQClientAPIImpl; private ChannelHandlerContext ctx; @Before public void init() throws NoSuchFieldException, IllegalAccessException, RemotingException, MQClientException, InterruptedException { NamesrvController namesrvController = new NamesrvController( new NamesrvConfig(), new NettyServerConfig()); clusterTestProcessor = new ClusterTestRequestProcessor(namesrvController, "default-producer"); mQClientAPIImpl = mock(MQClientAPIImpl.class); DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt(); defaultMQAdminExtImpl = new DefaultMQAdminExtImpl(defaultMQAdminExt, 1000); ctx = mock(ChannelHandlerContext.class); Field field = DefaultMQAdminExtImpl.class.getDeclaredField("mqClientInstance"); field.setAccessible(true); field.set(defaultMQAdminExtImpl, mqClientInstance); field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); field.setAccessible(true); field.set(mqClientInstance, mQClientAPIImpl); field = ClusterTestRequestProcessor.class.getDeclaredField("adminExt"); field.setAccessible(true); field.set(clusterTestProcessor, defaultMQAdminExt); TopicRouteData topicRouteData = new TopicRouteData(); List brokerDatas = new ArrayList<>(); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(1234L, "127.0.0.1:10911"); BrokerData brokerData = new BrokerData(); brokerData.setCluster("default-cluster"); brokerData.setBrokerName("default-broker"); brokerData.setBrokerAddrs(brokerAddrs); brokerDatas.add(brokerData); topicRouteData.setBrokerDatas(brokerDatas); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(topicRouteData); } @After public void terminate() { } @Test public void testGetRouteInfoByTopic() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(12, new CommandCustomHeader() { @Override public void checkFields() throws RemotingCommandException { } }); RemotingCommand remoting = clusterTestProcessor.getRouteInfoByTopic(ctx, request); assertThat(remoting.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); assertThat(remoting.getBody()).isNull(); assertThat(remoting.getRemark()).isNotNull(); } @Test public void testNamesrvReady() throws Exception { String topicName = "rocketmq-topic-test-ready"; RouteInfoManager routeInfoManager = mockRouteInfoManager(); NamesrvController namesrvController = mockNamesrvController(routeInfoManager, true, -1,true); ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(namesrvController); GetRouteInfoRequestHeader routeInfoRequestHeader = mockRouteInfoRequestHeader(topicName); RemotingCommand remotingCommand = mockTopicRouteCommand(routeInfoRequestHeader); RemotingCommand response = clientRequestProcessor.processRequest(mock(ChannelHandlerContext.class), remotingCommand); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testNamesrvNoNeedWaitForService() throws Exception { String topicName = "rocketmq-topic-test-ready"; RouteInfoManager routeInfoManager = mockRouteInfoManager(); NamesrvController namesrvController = mockNamesrvController(routeInfoManager, true, 45,false); ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(namesrvController); GetRouteInfoRequestHeader routeInfoRequestHeader = mockRouteInfoRequestHeader(topicName); RemotingCommand remotingCommand = mockTopicRouteCommand(routeInfoRequestHeader); RemotingCommand response = clientRequestProcessor.processRequest(mock(ChannelHandlerContext.class), remotingCommand); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testNamesrvNotReady() throws Exception { String topicName = "rocketmq-topic-test"; RouteInfoManager routeInfoManager = mockRouteInfoManager(); NamesrvController namesrvController = mockNamesrvController(routeInfoManager, false, 45,true); GetRouteInfoRequestHeader routeInfoRequestHeader = mockRouteInfoRequestHeader(topicName); RemotingCommand remotingCommand = mockTopicRouteCommand(routeInfoRequestHeader); ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(namesrvController); RemotingCommand response = clientRequestProcessor.processRequest(mock(ChannelHandlerContext.class), remotingCommand); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testNamesrv() throws Exception { int waitSecondsForService = 3; String topicName = "rocketmq-topic-test"; RouteInfoManager routeInfoManager = mockRouteInfoManager(); NamesrvController namesrvController = mockNamesrvController(routeInfoManager, false, waitSecondsForService,true); GetRouteInfoRequestHeader routeInfoRequestHeader = mockRouteInfoRequestHeader(topicName); RemotingCommand remotingCommand = mockTopicRouteCommand(routeInfoRequestHeader); ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(namesrvController); RemotingCommand response = clientRequestProcessor.processRequest(mock(ChannelHandlerContext.class), remotingCommand); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); TimeUnit.SECONDS.sleep(waitSecondsForService + 1); response = clientRequestProcessor.processRequest(mock(ChannelHandlerContext.class), remotingCommand); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } private RemotingCommand mockTopicRouteCommand( GetRouteInfoRequestHeader routeInfoRequestHeader) throws RemotingCommandException { RemotingCommand remotingCommand = mock(RemotingCommand.class); when(remotingCommand.decodeCommandCustomHeader(any())).thenReturn(routeInfoRequestHeader); when(remotingCommand.getCode()).thenReturn(RequestCode.GET_ROUTEINFO_BY_TOPIC); return remotingCommand; } public NamesrvController mockNamesrvController(RouteInfoManager routeInfoManager, boolean ready, int waitSecondsForService,boolean needWaitForService) { NamesrvConfig namesrvConfig = mock(NamesrvConfig.class); when(namesrvConfig.isNeedWaitForService()).thenReturn(needWaitForService); when(namesrvConfig.getUnRegisterBrokerQueueCapacity()).thenReturn(10); when(namesrvConfig.getWaitSecondsForService()).thenReturn(ready ? 0 : waitSecondsForService); NamesrvController namesrvController = mock(NamesrvController.class); when(namesrvController.getNamesrvConfig()).thenReturn(namesrvConfig); when(namesrvController.getRouteInfoManager()).thenReturn(routeInfoManager); return namesrvController; } public RouteInfoManager mockRouteInfoManager() { RouteInfoManager routeInfoManager = mock(RouteInfoManager.class); TopicRouteData topicRouteData = mock(TopicRouteData.class); when(routeInfoManager.pickupTopicRouteData(any())).thenReturn(topicRouteData); return routeInfoManager; } public GetRouteInfoRequestHeader mockRouteInfoRequestHeader(String topicName) { GetRouteInfoRequestHeader routeInfoRequestHeader = mock(GetRouteInfoRequestHeader.class); when(routeInfoRequestHeader.getTopic()).thenReturn(topicName); return routeInfoRequestHeader; } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/RequestProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.processor; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.RegisterBrokerBody; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetKVConfigResponseHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.PutKVConfigRequestHeader; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.assertj.core.util.Maps; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class RequestProcessorTest { private DefaultRequestProcessor defaultRequestProcessor; private ClientRequestProcessor clientRequestProcessor; private NamesrvController namesrvController; private NamesrvConfig namesrvConfig; private NettyServerConfig nettyServerConfig; private RouteInfoManager routeInfoManager; private Logger logger; @Before public void init() throws Exception { namesrvConfig = new NamesrvConfig(); namesrvConfig.setEnableAllTopicList(true); nettyServerConfig = new NettyServerConfig(); routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); namesrvController = new NamesrvController(namesrvConfig, nettyServerConfig); Field field = NamesrvController.class.getDeclaredField("routeInfoManager"); field.setAccessible(true); field.set(namesrvController, routeInfoManager); defaultRequestProcessor = new DefaultRequestProcessor(namesrvController); clientRequestProcessor = new ClientRequestProcessor(namesrvController); registerRouteInfoManager(); logger = mock(Logger.class); setFinalStatic(DefaultRequestProcessor.class.getDeclaredField("log"), logger); } @Test public void testProcessRequest_PutKVConfig() throws RemotingCommandException { PutKVConfigRequestHeader header = new PutKVConfigRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PUT_KV_CONFIG, header); request.addExtField("namespace", "namespace"); request.addExtField("key", "key"); request.addExtField("value", "value"); RemotingCommand response = defaultRequestProcessor.processRequest(null, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); assertThat(namesrvController.getKvConfigManager().getKVConfig("namespace", "key")) .isEqualTo("value"); } @Test public void testProcessRequest_GetKVConfigReturnNotNull() throws RemotingCommandException { namesrvController.getKvConfigManager().putKVConfig("namespace", "key", "value"); GetKVConfigRequestHeader header = new GetKVConfigRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_KV_CONFIG, header); request.addExtField("namespace", "namespace"); request.addExtField("key", "key"); RemotingCommand response = defaultRequestProcessor.processRequest(null, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); GetKVConfigResponseHeader responseHeader = (GetKVConfigResponseHeader) response .readCustomHeader(); assertThat(responseHeader.getValue()).isEqualTo("value"); } @Test public void testProcessRequest_GetKVConfigReturnNull() throws RemotingCommandException { GetKVConfigRequestHeader header = new GetKVConfigRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_KV_CONFIG, header); request.addExtField("namespace", "namespace"); request.addExtField("key", "key"); RemotingCommand response = defaultRequestProcessor.processRequest(null, request); assertThat(response.getCode()).isEqualTo(ResponseCode.QUERY_NOT_FOUND); assertThat(response.getRemark()).isEqualTo("No config item, Namespace: namespace Key: key"); GetKVConfigResponseHeader responseHeader = (GetKVConfigResponseHeader) response .readCustomHeader(); assertThat(responseHeader.getValue()).isNull(); } @Test public void testProcessRequest_DeleteKVConfig() throws RemotingCommandException { namesrvController.getKvConfigManager().putKVConfig("namespace", "key", "value"); DeleteKVConfigRequestHeader header = new DeleteKVConfigRequestHeader(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_KV_CONFIG, header); request.addExtField("namespace", "namespace"); request.addExtField("key", "key"); RemotingCommand response = defaultRequestProcessor.processRequest(null, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); assertThat(namesrvController.getKvConfigManager().getKVConfig("namespace", "key")) .isNull(); } @Test public void testProcessRequest_UnSupportedRequest() throws RemotingCommandException { final RemotingCommand unSupportedRequest = RemotingCommand.createRequestCommand(99999, null); final RemotingCommand response = defaultRequestProcessor.processRequest(null, unSupportedRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.REQUEST_CODE_NOT_SUPPORTED); } @Test public void testProcessRequest_UpdateConfigPath() throws RemotingCommandException { final RemotingCommand updateConfigRequest = RemotingCommand.createRequestCommand(RequestCode.UPDATE_NAMESRV_CONFIG, null); Properties properties = new Properties(); // Update allowed value properties.setProperty("enableTopicList", "true"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); RemotingCommand response = defaultRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); //update disallowed value properties.clear(); properties.setProperty("configStorePath", "test/path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = defaultRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list."); //update disallowed values properties.clear(); properties.setProperty("kvConfigPath", "test/path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = defaultRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list"); //update disallowed values properties.clear(); properties.setProperty("configBlackList", "test;path"); updateConfigRequest.setBody(MixAll.properties2String(properties).getBytes(StandardCharsets.UTF_8)); response = defaultRequestProcessor.processRequest(null, updateConfigRequest); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); assertThat(response.getRemark()).contains("Can not update config in black list"); } @Test public void testProcessRequest_RegisterBroker() throws RemotingCommandException, NoSuchFieldException, IllegalAccessException { RemotingCommand request = genSampleRegisterCmd(true); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); RouteInfoManager routes = namesrvController.getRouteInfoManager(); Field brokerAddrTable = RouteInfoManager.class.getDeclaredField("brokerAddrTable"); brokerAddrTable.setAccessible(true); BrokerData broker = new BrokerData(); broker.setBrokerName("broker"); broker.setBrokerAddrs((HashMap) Maps.newHashMap(new Long(2333), "10.10.1.1")); assertThat((Map) brokerAddrTable.get(routes)) .contains(new HashMap.SimpleEntry("broker", broker)); } /*@Test public void testProcessRequest_RegisterBrokerLogicalQueue() throws Exception { String cluster = "cluster"; String broker1Name = "broker1"; String broker1Addr = "10.10.1.1"; String broker2Name = "broker2"; String broker2Addr = "10.10.1.2"; String topic = "foobar"; LogicalQueueRouteData queueRouteData1 = new LogicalQueueRouteData(0, 0, new MessageQueue(topic, broker1Name, 0), MessageQueueRouteState.ReadOnly, 0, 10, 100, 100, broker1Addr); { RegisterBrokerRequestHeader header = new RegisterBrokerRequestHeader(); header.setBrokerName(broker1Name); RemotingCommand request = RemotingCommand.createRequestCommand( RequestCode.REGISTER_BROKER, header); request.addExtField("brokerName", broker1Name); request.addExtField("brokerAddr", broker1Addr); request.addExtField("clusterName", cluster); request.addExtField("haServerAddr", "10.10.2.1"); request.addExtField("brokerId", String.valueOf(MixAll.MASTER_ID)); request.setVersion(MQVersion.CURRENT_VERSION); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(new ConcurrentHashMap<>(Collections.singletonMap(topic, new TopicConfig(topic)))); topicConfigSerializeWrapper.setLogicalQueuesInfoMap(Maps.newHashMap(topic, new LogicalQueuesInfo(Collections.singletonMap(0, Lists.newArrayList( queueRouteData1 ))))); topicConfigSerializeWrapper.setDataVersion(new DataVersion()); RegisterBrokerBody requestBody = new RegisterBrokerBody(); requestBody.setTopicConfigSerializeWrapper(topicConfigSerializeWrapper); requestBody.setFilterServerList(Lists.newArrayList()); request.setBody(requestBody.encode()); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); } LogicalQueueRouteData queueRouteData2 = new LogicalQueueRouteData(0, 100, new MessageQueue(topic, broker2Name, 0), MessageQueueRouteState.Normal, 0, -1, -1, -1, broker2Addr); LogicalQueueRouteData queueRouteData3 = new LogicalQueueRouteData(1, 100, new MessageQueue(topic, broker2Name, 0), MessageQueueRouteState.Normal, 0, -1, -1, -1, broker2Addr); { RegisterBrokerRequestHeader header = new RegisterBrokerRequestHeader(); header.setBrokerName(broker2Name); RemotingCommand request = RemotingCommand.createRequestCommand( RequestCode.REGISTER_BROKER, header); request.addExtField("brokerName", broker2Name); request.addExtField("brokerAddr", broker2Addr); request.addExtField("clusterName", cluster); request.addExtField("haServerAddr", "10.10.2.1"); request.addExtField("brokerId", String.valueOf(MixAll.MASTER_ID)); request.setVersion(MQVersion.CURRENT_VERSION); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(new ConcurrentHashMap<>(Collections.singletonMap(topic, new TopicConfig(topic)))); topicConfigSerializeWrapper.setLogicalQueuesInfoMap(Maps.newHashMap(topic, new LogicalQueuesInfo(ImmutableMap.of( 0, Collections.singletonList(queueRouteData2), 1, Collections.singletonList(queueRouteData3) )))); topicConfigSerializeWrapper.setDataVersion(new DataVersion()); RegisterBrokerBody requestBody = new RegisterBrokerBody(); requestBody.setTopicConfigSerializeWrapper(topicConfigSerializeWrapper); requestBody.setFilterServerList(Lists.newArrayList()); request.setBody(requestBody.encode()); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); } { GetRouteInfoRequestHeader header = new GetRouteInfoRequestHeader(); header.setTopic(topic); header.setSysFlag(MessageSysFlag.LOGICAL_QUEUE_FLAG); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, header); request.makeCustomHeaderToNet(); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); TopicRouteDataNameSrv topicRouteDataNameSrv = JSON.parseObject(response.getBody(), TopicRouteDataNameSrv.class); assertThat(topicRouteDataNameSrv).isNotNull(); LogicalQueuesInfoUnordered logicalQueuesInfoUnordered = new LogicalQueuesInfoUnordered(); logicalQueuesInfoUnordered.put(0, ImmutableMap.of( new LogicalQueuesInfoUnordered.Key(queueRouteData1.getBrokerName(), queueRouteData1.getQueueId(), queueRouteData1.getOffsetDelta()), queueRouteData1, new LogicalQueuesInfoUnordered.Key(queueRouteData2.getBrokerName(), queueRouteData2.getQueueId(), queueRouteData2.getOffsetDelta()), queueRouteData2 )); logicalQueuesInfoUnordered.put(1, ImmutableMap.of(new LogicalQueuesInfoUnordered.Key(queueRouteData3.getBrokerName(), queueRouteData3.getQueueId(), queueRouteData3.getOffsetDelta()), queueRouteData3)); assertThat(topicRouteDataNameSrv.getLogicalQueuesInfoUnordered()).isEqualTo(logicalQueuesInfoUnordered); } } */ @Test public void testProcessRequest_RegisterBrokerWithFilterServer() throws RemotingCommandException, NoSuchFieldException, IllegalAccessException { RemotingCommand request = genSampleRegisterCmd(true); // version >= MQVersion.Version.V3_0_11.ordinal() to register with filter server request.setVersion(100); ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); RouteInfoManager routes = namesrvController.getRouteInfoManager(); Field brokerAddrTable = RouteInfoManager.class.getDeclaredField("brokerAddrTable"); brokerAddrTable.setAccessible(true); BrokerData broker = new BrokerData(); broker.setBrokerName("broker"); broker.setBrokerAddrs((HashMap) Maps.newHashMap(new Long(2333), "10.10.1.1")); assertThat((Map) brokerAddrTable.get(routes)) .contains(new HashMap.SimpleEntry("broker", broker)); } @Test public void testProcessRequest_UnregisterBroker() throws RemotingCommandException, NoSuchFieldException, IllegalAccessException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); //Register broker RemotingCommand regRequest = genSampleRegisterCmd(true); defaultRequestProcessor.processRequest(ctx, regRequest); //Unregister broker RemotingCommand unregRequest = genSampleRegisterCmd(false); RemotingCommand unregResponse = defaultRequestProcessor.processRequest(ctx, unregRequest); assertThat(unregResponse.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(unregResponse.getRemark()).isNull(); RouteInfoManager routes = namesrvController.getRouteInfoManager(); Field brokerAddrTable = RouteInfoManager.class.getDeclaredField("brokerAddrTable"); brokerAddrTable.setAccessible(true); assertThat((Map) brokerAddrTable.get(routes)).isNotEmpty(); } @Test public void testGetAllTopicList() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); Channel channel = mock(Channel.class); when(channel.remoteAddress()).thenReturn(null); when(ctx.channel()).thenReturn(channel); namesrvController.getNamesrvConfig().setEnableAllTopicList(true); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER, null); RemotingCommand response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(response.getRemark()).isNull(); namesrvController.getNamesrvConfig().setEnableAllTopicList(false); response = defaultRequestProcessor.processRequest(ctx, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @Test public void testGetRouteInfoByTopic() throws Exception { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC); RemotingCommand remotingCommandSuccess = clientRequestProcessor.processRequest(ctx, request); assertThat(remotingCommandSuccess.getCode()).isEqualTo(ResponseCode.SUCCESS); request.getExtFields().put("topic", "test"); RemotingCommand remotingCommandNoTopicRouteInfo = clientRequestProcessor.processRequest(ctx, request); assertThat(remotingCommandNoTopicRouteInfo.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); } @Test public void testGetBrokerClusterInfo() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_BROKER_CLUSTER_INFO); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testQueryDataVersion()throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.QUERY_DATA_VERSION); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetBrokerMemberBroker() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_BROKER_MEMBER_GROUP); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testBrokerHeartBeat() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.BROKER_HEARTBEAT); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testAddWritePermOfBroker() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.ADD_WRITE_PERM_OF_BROKER); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testWipeWritePermOfBroker() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.WIPE_WRITE_PERM_OF_BROKER); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetAllTopicListFromNameserver() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(mock(Channel.class)); when(ctx.channel().remoteAddress()).thenReturn(new InetSocketAddress(123)); RemotingCommand request = getRemotingCommand(RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testDeleteTopicInNamesrv() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.DELETE_TOPIC_IN_NAMESRV); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetKVListByNamespace() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_KVLIST_BY_NAMESPACE); request.addExtField("namespace", "default-namespace-1"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.QUERY_NOT_FOUND); namesrvController.getKvConfigManager().putKVConfig("default-namespace-1", "key", "value"); RemotingCommand remotingCommandSuccess = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommandSuccess.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetTopicsByCluster() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_TOPICS_BY_CLUSTER); request.addExtField("cluster", "default-cluster"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetSystemTopicListFromNs() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS); request.addExtField("cluster", "default-cluster"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetUnitTopicList() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_UNIT_TOPIC_LIST); request.addExtField("cluster", "default-cluster"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetHasUnitSubTopicList() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST); request.addExtField("cluster", "default-cluster"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetHasUnitSubUnUnitTopicList() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST); request.addExtField("cluster", "default-cluster"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testUpdateConfig() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.UPDATE_NAMESRV_CONFIG); request.addExtField("cluster", "default-cluster"); Map propertiesMap = new HashMap<>(); propertiesMap.put("key", "value"); request.setBody(propertiesMap.toString().getBytes()); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testGetConfig() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); when(ctx.channel()).thenReturn(null); RemotingCommand request = getRemotingCommand(RequestCode.GET_NAMESRV_CONFIG); request.addExtField("cluster", "default-cluster"); RemotingCommand remotingCommand = defaultRequestProcessor.processRequest(ctx, request); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.SUCCESS); } private RemotingCommand getRemotingCommand(int code) { RegisterBrokerRequestHeader header = new RegisterBrokerRequestHeader(); header.setBrokerName("broker"); RemotingCommand request = RemotingCommand.createRequestCommand(code, header); request.addExtField("brokerName", "broker"); request.addExtField("brokerAddr", "10.10.1.1"); request.addExtField("clusterName", "cluster"); request.addExtField("haServerAddr", "10.10.2.1"); request.addExtField("brokerId", "2333"); request.addExtField("topic", "unit-test0"); return request; } private static RemotingCommand genSampleRegisterCmd(boolean reg) { RegisterBrokerRequestHeader header = new RegisterBrokerRequestHeader(); byte[] body = null; if (reg) { TopicConfigAndMappingSerializeWrapper topicConfigWrapper = new TopicConfigAndMappingSerializeWrapper(); topicConfigWrapper.getTopicConfigTable().put("unit-test1", new TopicConfig()); topicConfigWrapper.getTopicConfigTable().put("unit-test2", new TopicConfig()); RegisterBrokerBody requestBody = new RegisterBrokerBody(); requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper); body = requestBody.encode(false); final int bodyCrc32 = UtilAll.crc32(body); header.setBodyCrc32(bodyCrc32); } header.setBrokerName("broker"); RemotingCommand request = RemotingCommand.createRequestCommand( reg ? RequestCode.REGISTER_BROKER : RequestCode.UNREGISTER_BROKER, header); request.addExtField("brokerName", "broker"); request.addExtField("brokerAddr", "10.10.1.1"); request.addExtField("clusterName", "cluster"); request.addExtField("haServerAddr", "10.10.2.1"); request.addExtField("brokerId", "2333"); request.setBody(body); return request; } private static void setFinalStatic(Field field, Object newValue) throws Exception { field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); } private void registerRouteInfoManager() { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); for (int i = 0; i < 2; i++) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName("unit-test" + i); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigConcurrentHashMap.put(topicConfig.getTopicName(), topicConfig); } topicConfigSerializeWrapper.setTopicConfigTable(topicConfigConcurrentHashMap); Channel channel = mock(Channel.class); RegisterBrokerResult registerBrokerResult = routeInfoManager.registerBroker("default-cluster", "127.0.0.1:10911", "default-broker", 1234, "127.0.0.1:1001", "", null, topicConfigSerializeWrapper, new ArrayList<>(), channel); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/route/ZoneRouteRPCHookMoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.route; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class ZoneRouteRPCHookMoreTest { private ZoneRouteRPCHook zoneRouteRPCHook; @Before public void setUp() { zoneRouteRPCHook = new ZoneRouteRPCHook(); } @Test public void testFilterByZoneName_ValidInput_ShouldFilterCorrectly() { // Arrange TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setBrokerDatas(generateBrokerDataList()); topicRouteData.setQueueDatas(generateQueueDataList()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC,null); request.setExtFields(createExtFields("true","ZoneA")); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "remark"); // Act zoneRouteRPCHook.doAfterResponse("127.0.0.1", request, response); TopicRouteData decodedResponse = RemotingSerializable.decode(response.getBody(), TopicRouteData.class); // Assert assertNull(decodedResponse); } @Test public void testFilterByZoneName_NoZoneName_ShouldNotFilter() { // Arrange TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setBrokerDatas(generateBrokerDataList()); topicRouteData.setQueueDatas(generateQueueDataList()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC,null); HashMap extFields = new HashMap<>(); extFields.put(MixAll.ZONE_MODE, "true"); request.setExtFields(extFields); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); // Act zoneRouteRPCHook.doAfterResponse("127.0.0.1", request, response); TopicRouteData decodedResponse = RemotingSerializable.decode(response.getBody(), TopicRouteData.class); // Assert assertEquals(topicRouteData.getBrokerDatas().size(), 2); assertEquals(topicRouteData.getQueueDatas().size(), 2); } @Test public void testFilterByZoneName_ZoneModeFalse_ShouldNotFilter() { // Arrange TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setBrokerDatas(generateBrokerDataList()); topicRouteData.setQueueDatas(generateQueueDataList()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC,null); request.setExtFields(createExtFields("false","ZoneA")); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS ,null); // Act zoneRouteRPCHook.doAfterResponse("127.0.0.1", request, response); TopicRouteData decodedResponse = RemotingSerializable.decode(response.getBody(), TopicRouteData.class); // Assert assertEquals(topicRouteData.getBrokerDatas().size(), 2); assertEquals(topicRouteData.getQueueDatas().size(), 2); } private List generateBrokerDataList() { List brokerDataList = new ArrayList<>(); BrokerData brokerData1 = new BrokerData(); brokerData1.setBrokerName("BrokerA"); brokerData1.setZoneName("ZoneA"); Map brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, "127.0.0.1:10911"); brokerData1.setBrokerAddrs((HashMap) brokerAddrs); brokerDataList.add(brokerData1); BrokerData brokerData2 = new BrokerData(); brokerData2.setBrokerName("BrokerB"); brokerData2.setZoneName("ZoneB"); brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, "127.0.0.1:10912"); brokerData2.setBrokerAddrs((HashMap) brokerAddrs); brokerDataList.add(brokerData2); return brokerDataList; } private List generateQueueDataList() { List queueDataList = new ArrayList<>(); QueueData queueData1 = new QueueData(); queueData1.setBrokerName("BrokerA"); queueDataList.add(queueData1); QueueData queueData2 = new QueueData(); queueData2.setBrokerName("BrokerB"); queueDataList.add(queueData2); return queueDataList; } private HashMap createExtFields(String zoneMode, String zoneName) { HashMap extFields = new HashMap<>(); extFields.put(MixAll.ZONE_MODE, zoneMode); extFields.put(MixAll.ZONE_NAME, zoneName); return extFields; } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/route/ZoneRouteRPCHookTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.route; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class ZoneRouteRPCHookTest { private ZoneRouteRPCHook zoneRouteRPCHook; @Before public void setup() { zoneRouteRPCHook = new ZoneRouteRPCHook(); } @Test public void testDoAfterResponseWithNoZoneMode() { RemotingCommand request1 = RemotingCommand.createRequestCommand(106,null); zoneRouteRPCHook.doAfterResponse("", request1, null); HashMap extFields = new HashMap<>(); extFields.put(MixAll.ZONE_MODE, "false"); RemotingCommand request = RemotingCommand.createRequestCommand(105,null); request.setExtFields(extFields); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setBody(RemotingSerializable.encode(createSampleTopicRouteData())); zoneRouteRPCHook.doAfterResponse("", request, response); } @Test public void testDoAfterResponseWithNoZoneName() { HashMap extFields = new HashMap<>(); extFields.put(MixAll.ZONE_MODE, "true"); RemotingCommand request = RemotingCommand.createRequestCommand(105,null); request.setExtFields(extFields); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setBody(RemotingSerializable.encode(createSampleTopicRouteData())); zoneRouteRPCHook.doAfterResponse("", request, response); } @Test public void testDoAfterResponseWithNoResponse() { HashMap extFields = new HashMap<>(); extFields.put(MixAll.ZONE_MODE, "true"); RemotingCommand request = RemotingCommand.createRequestCommand(105,null); request.setExtFields(extFields); zoneRouteRPCHook.doAfterResponse("", request, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); zoneRouteRPCHook.doAfterResponse("", request, response); response.setBody(RemotingSerializable.encode(createSampleTopicRouteData())); response.setCode(ResponseCode.NO_PERMISSION); zoneRouteRPCHook.doAfterResponse("", request, response); } @Test public void testDoAfterResponseWithValidZoneFiltering() throws Exception { HashMap extFields = new HashMap<>(); extFields.put(MixAll.ZONE_MODE, "true"); extFields.put(MixAll.ZONE_NAME,"zone1"); RemotingCommand request = RemotingCommand.createRequestCommand(105,null); request.setExtFields(extFields); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); TopicRouteData topicRouteData = createSampleTopicRouteData(); response.setBody(RemotingSerializable.encode(topicRouteData)); zoneRouteRPCHook.doAfterResponse("", request, response); HashMap brokeraddrs = new HashMap<>(); brokeraddrs.put(MixAll.MASTER_ID,"127.0.0.1:10911"); topicRouteData.getBrokerDatas().get(0).setBrokerAddrs(brokeraddrs); response.setBody(RemotingSerializable.encode(topicRouteData)); zoneRouteRPCHook.doAfterResponse("", request, response); topicRouteData.getQueueDatas().add(createQueueData("BrokerB")); HashMap brokeraddrsB = new HashMap<>(); brokeraddrsB.put(MixAll.MASTER_ID,"127.0.0.1:10912"); BrokerData brokerData1 = createBrokerData("BrokerB","zone2",brokeraddrsB); BrokerData brokerData2 = createBrokerData("BrokerC","zone1",null); topicRouteData.getBrokerDatas().add(brokerData1); topicRouteData.getBrokerDatas().add(brokerData2); response.setBody(RemotingSerializable.encode(topicRouteData)); zoneRouteRPCHook.doAfterResponse("", request, response); topicRouteData.getFilterServerTable().put("127.0.0.1:10911",new ArrayList<>()); response.setBody(RemotingSerializable.encode(topicRouteData)); zoneRouteRPCHook.doAfterResponse("", request, response); Assert.assertEquals(1,RemotingSerializable .decode(response.getBody(), TopicRouteData.class) .getFilterServerTable() .size()); topicRouteData.getFilterServerTable().put("127.0.0.1:10912",new ArrayList<>()); response.setBody(RemotingSerializable.encode(topicRouteData)); zoneRouteRPCHook.doAfterResponse("", request, response); Assert.assertEquals(1,RemotingSerializable .decode(response.getBody(), TopicRouteData.class) .getFilterServerTable() .size()); } private TopicRouteData createSampleTopicRouteData() { TopicRouteData topicRouteData = new TopicRouteData(); List brokerDatas = new ArrayList<>(); BrokerData brokerData = createBrokerData("BrokerA","zone1",new HashMap<>()); List queueDatas = new ArrayList<>(); QueueData queueData = createQueueData("BrokerA"); queueDatas.add(queueData); brokerDatas.add(brokerData); topicRouteData.setBrokerDatas(brokerDatas); topicRouteData.setQueueDatas(queueDatas); return topicRouteData; } private BrokerData createBrokerData(String brokerName,String zoneName,HashMap brokerAddrs) { BrokerData brokerData = new BrokerData(); brokerData.setBrokerName(brokerName); brokerData.setZoneName(zoneName); brokerData.setBrokerAddrs(brokerAddrs); return brokerData; } private QueueData createQueueData(String brokerName) { QueueData queueData = new QueueData(); queueData.setBrokerName(brokerName); queueData.setReadQueueNums(8); queueData.setWriteQueueNums(8); queueData.setPerm(6); return queueData; } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/BrokerHousekeepingServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; public class BrokerHousekeepingServiceTest { private static BrokerHousekeepingService brokerHousekeepingService; @BeforeClass public static void setup() { NamesrvController namesrvController = new NamesrvController( new NamesrvConfig(), new NettyServerConfig() ); brokerHousekeepingService = new BrokerHousekeepingService(namesrvController); } @AfterClass public static void terminate() { } @Test public void testOnChannelClose() { brokerHousekeepingService.onChannelClose("127.0.0.1:9876", null); } @Test public void testOnChannelException() { brokerHousekeepingService.onChannelException("127.0.0.1:9876", null); } @Test public void testOnChannelIdle() { brokerHousekeepingService.onChannelException("127.0.0.1:9876", null); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/GetRouteInfoBenchmark.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import static org.mockito.Mockito.mock; @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class GetRouteInfoBenchmark { private RouteInfoManager routeInfoManager; private String[] topicList = new String[40000]; private ExecutorService es = Executors.newCachedThreadPool(); @Setup public void setup() throws InterruptedException { routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); // Init 4 clusters and 8 brokers in each cluster // Each cluster has 10000 topics for (int i = 0; i < 40000; i++) { final String topic = RandomStringUtils.randomAlphabetic(32) + i; topicList[i] = topic; } for (int i = 0; i < 4; i++) { // Cluster iteration final String clusterName = "Default-Cluster-" + i; for (int j = 0; j < 8; j++) { // broker iteration final int startTopicIndex = i * 10000; final String brokerName = "Default-Broker-" + j; final String brokerAddr = "127.0.0.1:500" + i * j; es.submit(new Runnable() { @Override public void run() { DataVersion dataVersion = new DataVersion(); dataVersion.setCounter(new AtomicLong(10L)); dataVersion.setTimestamp(100L); ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); for (int k = startTopicIndex; k < startTopicIndex + 10000; k++) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topicList[k]); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigConcurrentHashMap.put(topicList[k], topicConfig); } while (true) { try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(100)); } catch (InterruptedException ignored) { } dataVersion.nextVersion(); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(dataVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigConcurrentHashMap); Channel channel = mock(Channel.class); routeInfoManager.registerBroker(clusterName, brokerAddr, brokerName, 0, brokerAddr, "", null, topicConfigSerializeWrapper, new ArrayList<>(), channel); } } }); } } // Wait threads startup TimeUnit.SECONDS.sleep(3); } @TearDown public void tearDown() { ThreadUtils.shutdownGracefully(es, 3, TimeUnit.SECONDS); } @Benchmark @Fork(value = 2) @Measurement(iterations = 10, time = 10) @Warmup(iterations = 10, time = 1) @Threads(4) // Assume we have 128 clients try to pick up route data concurrently public void pickupTopicRouteData() { routeInfoManager.pickupTopicRouteData(topicList[new Random().nextInt(40000)]); } public static void main(String[] args) throws Exception { org.openjdk.jmh.Main.main(args); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RegisterBrokerBenchmark.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import static org.mockito.Mockito.mock; @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class RegisterBrokerBenchmark { private RouteInfoManager routeInfoManager; private String[] topicList = new String[40000]; private ConcurrentHashMap[] topicConfigMaps = new ConcurrentHashMap[32]; private DataVersion[] dataVersions = new DataVersion[32]; private ExecutorService es = Executors.newCachedThreadPool(); private AtomicLong brokerIndex = new AtomicLong(0); @Setup public void setup() throws InterruptedException { routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); // Init 4 clusters and 8 brokers in each cluster // Each cluster has 10000 topics for (int i = 0; i < 40000; i++) { final String topic = RandomStringUtils.randomAlphabetic(32) + i; topicList[i] = topic; } for (int i = 0; i < 4; i++) { // Cluster iteration final String clusterName = "Default-Cluster-" + i; for (int j = 0; j < 8; j++) { // broker iteration final int startTopicIndex = i * 10000; final String brokerName = "Default-Broker-" + j; final String brokerAddr = "127.0.0.1:500" + (i * 8 + j); topicConfigMaps[i * 8 + j] = new ConcurrentHashMap<>(); for (int k = startTopicIndex; k < startTopicIndex + 10000; k++) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topicList[k]); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigMaps[i * 8 + j].put(topicList[k], topicConfig); } DataVersion dataVersion = new DataVersion(); dataVersion.setCounter(new AtomicLong(10L)); dataVersion.setTimestamp(100L); dataVersions[i * 8 + j] = dataVersion; } } // Init 32 threads to pick up route info for (int i = 0; i < 32; i++) { es.submit(new Runnable() { @Override public void run() { routeInfoManager.pickupTopicRouteData(topicList[new Random().nextInt(40000)]); try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10)); } catch (InterruptedException ignored) { } } }); } } @TearDown public void tearDown() { ThreadUtils.shutdownGracefully(es, 3, TimeUnit.SECONDS); } @Benchmark @Fork(value = 2) @Measurement(iterations = 10, time = 10) @Warmup(iterations = 10, time = 1) @Threads(32) // Assume we have 128 clients try to pick up route data concurrently public void registerBroker() { final long index = Math.abs(brokerIndex.getAndIncrement() % 32); dataVersions[(int) index].nextVersion(); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(dataVersions[(int) index]); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigMaps[(int) index]); Channel channel = mock(Channel.class); routeInfoManager.registerBroker("DefaultCluster" + index, "127.0.0.1:500" + index, "DefaultBroker" + index, 0, "127.0.0.1:400" + index, "", null, topicConfigSerializeWrapper, new ArrayList<>(), channel); } @Benchmark @Fork(value = 2) @Measurement(iterations = 10, time = 10) @Warmup(iterations = 10, time = 1) @Threads(32) // Assume we have 128 clients try to pick up route data concurrently @BenchmarkMode(Mode.Throughput) public void registerBroker_Throughput() { final long index = Math.abs(brokerIndex.getAndIncrement() % 32); dataVersions[(int) index].nextVersion(); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(dataVersions[(int) index]); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigMaps[(int) index]); Channel channel = mock(Channel.class); routeInfoManager.registerBroker("DefaultCluster" + index, "127.0.0.1:500" + index, "DefaultBroker" + index, 0, "127.0.0.1:400" + index, "", null, topicConfigSerializeWrapper, new ArrayList<>(), channel); } public static void main(String[] args) throws Exception { org.openjdk.jmh.Main.main(args); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerPermTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class RouteInfoManagerBrokerPermTest extends RouteInfoManagerTestBase { private static RouteInfoManager routeInfoManager; public static String clusterName = "cluster"; public static String brokerPrefix = "broker"; public static String topicPrefix = "topic"; public static RouteInfoManagerTestBase.Cluster cluster; @Before public void setup() { routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); cluster = registerCluster(routeInfoManager, clusterName, brokerPrefix, 3, 3, topicPrefix, 10); } @After public void terminate() { routeInfoManager.printAllPeriodically(); for (BrokerData bd : cluster.brokerDataMap.values()) { unregisterBrokerAll(routeInfoManager, bd); } } @Test public void testAddWritePermOfBrokerByLock() throws Exception { String brokerName = getBrokerName(brokerPrefix, 0); String topicName = getTopicName(topicPrefix, 0); QueueData qd = new QueueData(); qd.setPerm(PermName.PERM_READ); qd.setBrokerName(brokerName); HashMap> topicQueueTable = new HashMap<>(); Map queueDataMap = new HashMap<>(); queueDataMap.put(brokerName, qd); topicQueueTable.put(topicName, queueDataMap); Field filed = RouteInfoManager.class.getDeclaredField("topicQueueTable"); filed.setAccessible(true); filed.set(routeInfoManager, topicQueueTable); int addTopicCnt = routeInfoManager.addWritePermOfBrokerByLock(brokerName); assertThat(addTopicCnt).isEqualTo(1); assertThat(qd.getPerm()).isEqualTo(PermName.PERM_READ | PermName.PERM_WRITE); } @Test public void testWipeWritePermOfBrokerByLock() throws Exception { String brokerName = getBrokerName(brokerPrefix, 0); String topicName = getTopicName(topicPrefix, 0); QueueData qd = new QueueData(); qd.setPerm(PermName.PERM_READ); qd.setBrokerName(brokerName); HashMap> topicQueueTable = new HashMap<>(); Map queueDataMap = new HashMap<>(); queueDataMap.put(brokerName, qd); topicQueueTable.put(topicName, queueDataMap); Field filed = RouteInfoManager.class.getDeclaredField("topicQueueTable"); filed.setAccessible(true); filed.set(routeInfoManager, topicQueueTable); int addTopicCnt = routeInfoManager.wipeWritePermOfBrokerByLock(brokerName); assertThat(addTopicCnt).isEqualTo(1); assertThat(qd.getPerm()).isEqualTo(PermName.PERM_READ); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerRegisterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import java.util.ArrayList; import java.util.HashMap; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; public class RouteInfoManagerBrokerRegisterTest extends RouteInfoManagerTestBase { private static RouteInfoManager routeInfoManager; public static String clusterName = "cluster"; public static String brokerPrefix = "broker"; public static String topicPrefix = "topic"; public static int brokerPerName = 3; public static int brokerNameNumber = 3; public static RouteInfoManagerTestBase.Cluster cluster; @Before public void setup() { routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); cluster = registerCluster(routeInfoManager, clusterName, brokerPrefix, brokerNameNumber, brokerPerName, topicPrefix, 10); } @After public void terminate() { routeInfoManager.printAllPeriodically(); for (BrokerData bd : cluster.brokerDataMap.values()) { unregisterBrokerAll(routeInfoManager, bd); } } // @Test // public void testScanNotActiveBroker() { // for (int j = 0; j < brokerNameNumber; j++) { // String brokerName = getBrokerName(brokerPrefix, j); // // for (int i = 0; i < brokerPerName; i++) { // String brokerAddr = getBrokerAddr(clusterName, brokerName, i); // // // set not active // routeInfoManager.updateBrokerInfoUpdateTimestamp(brokerAddr, 0); // // assertEquals(1, routeInfoManager.scanNotActiveBroker()); // } // } // // } @Test public void testMasterChangeFromSlave() { String topicName = getTopicName(topicPrefix, 0); String brokerName = getBrokerName(brokerPrefix, 0); String originMasterAddr = getBrokerAddr(clusterName, brokerName, MixAll.MASTER_ID); TopicRouteData topicRouteData = routeInfoManager.pickupTopicRouteData(topicName); BrokerData brokerDataOrigin = findBrokerDataByBrokerName(topicRouteData.getBrokerDatas(), brokerName); // check origin master address Assert.assertEquals(brokerDataOrigin.getBrokerAddrs().get(MixAll.MASTER_ID), originMasterAddr); // master changed String newMasterAddr = getBrokerAddr(clusterName, brokerName, 1); registerBrokerWithTopicConfig(routeInfoManager, clusterName, newMasterAddr, brokerName, MixAll.MASTER_ID, newMasterAddr, cluster.topicConfig, new ArrayList<>()); topicRouteData = routeInfoManager.pickupTopicRouteData(topicName); brokerDataOrigin = findBrokerDataByBrokerName(topicRouteData.getBrokerDatas(), brokerName); // check new master address assertEquals(brokerDataOrigin.getBrokerAddrs().get(MixAll.MASTER_ID), newMasterAddr); } @Test public void testUnregisterBroker() { String topicName = getTopicName(topicPrefix, 0); String brokerName = getBrokerName(brokerPrefix, 0); long unregisterBrokerId = 2; unregisterBroker(routeInfoManager, cluster.brokerDataMap.get(brokerName), unregisterBrokerId); TopicRouteData topicRouteData = routeInfoManager.pickupTopicRouteData(topicName); HashMap brokerAddrs = findBrokerDataByBrokerName(topicRouteData.getBrokerDatas(), brokerName).getBrokerAddrs(); assertFalse(brokerAddrs.containsKey(unregisterBrokerId)); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerNewTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import com.google.common.collect.Sets; import io.netty.channel.Channel; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.header.namesrv.UnRegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Spy; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; public class RouteInfoManagerNewTest { private RouteInfoManager routeInfoManager; private static final String DEFAULT_CLUSTER = "Default_Cluster"; private static final String DEFAULT_BROKER = "Default_Broker"; private static final String DEFAULT_ADDR_PREFIX = "127.0.0.1:"; private static final String DEFAULT_ADDR = DEFAULT_ADDR_PREFIX + "10911"; // Synced from RouteInfoManager private static final int BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2; @Spy private static NamesrvConfig config = spy(new NamesrvConfig()); @Before public void setup() { config.setSupportActingMaster(true); routeInfoManager = new RouteInfoManager(config, null); routeInfoManager.start(); } @After public void tearDown() throws Exception { routeInfoManager.shutdown(); } @Test public void getAllClusterInfo() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker() .cluster("AnotherCluster") .name("AnotherBroker") .addr(DEFAULT_ADDR_PREFIX + 30911), "TestTopic1"); final byte[] content = routeInfoManager.getAllClusterInfo().encode(); final ClusterInfo clusterInfo = ClusterInfo.decode(content, ClusterInfo.class); assertThat(clusterInfo.retrieveAllClusterNames()).contains(DEFAULT_CLUSTER, "AnotherCluster"); assertThat(clusterInfo.getBrokerAddrTable().keySet()).contains(DEFAULT_BROKER, "AnotherBroker"); final List addrList = Arrays.asList(clusterInfo.getBrokerAddrTable().get(DEFAULT_BROKER).getBrokerAddrs().get(0L), clusterInfo.getBrokerAddrTable().get("AnotherBroker").getBrokerAddrs().get(0L)); assertThat(addrList).contains(DEFAULT_ADDR, DEFAULT_ADDR_PREFIX + 30911); } @Test public void deleteTopic() { String testTopic = "TestTopic"; registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); routeInfoManager.deleteTopic(testTopic); assertThat(routeInfoManager.pickupTopicRouteData(testTopic)).isNull(); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker().cluster("AnotherCluster").name("AnotherBroker"), testTopic); assertThat(routeInfoManager.pickupTopicRouteData(testTopic).getBrokerDatas().size()).isEqualTo(2); routeInfoManager.deleteTopic(testTopic, DEFAULT_CLUSTER); assertThat(routeInfoManager.pickupTopicRouteData(testTopic).getBrokerDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData(testTopic).getBrokerDatas().get(0).getBrokerName()).isEqualTo("AnotherBroker"); } @Test public void getAllTopicList() { byte[] content = routeInfoManager.getAllTopicList().encode(); TopicList topicList = TopicList.decode(content, TopicList.class); assertThat(topicList.getTopicList()).isEmpty(); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic", "TestTopic1", "TestTopic2"); content = routeInfoManager.getAllTopicList().encode(); topicList = TopicList.decode(content, TopicList.class); assertThat(topicList.getTopicList()).contains("TestTopic", "TestTopic1", "TestTopic2"); } @Test public void registerBroker() { // Register master broker final RegisterBrokerResult masterResult = registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); assertThat(masterResult).isNotNull(); assertThat(masterResult.getHaServerAddr()).isNull(); assertThat(masterResult.getMasterAddr()).isNull(); // Register slave broker final BrokerBasicInfo slaveBroker = BrokerBasicInfo.defaultBroker() .id(1).addr(DEFAULT_ADDR_PREFIX + 30911).haAddr(DEFAULT_ADDR_PREFIX + 40911); final RegisterBrokerResult slaveResult = registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); assertThat(slaveResult).isNotNull(); assertThat(slaveResult.getHaServerAddr()).isEqualTo(DEFAULT_ADDR_PREFIX + 20911); assertThat(slaveResult.getMasterAddr()).isEqualTo(DEFAULT_ADDR); } @Test public void unregisterBroker() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic", "TestTopic1", "TestTopic2"); routeInfoManager.unregisterBroker(DEFAULT_CLUSTER, DEFAULT_ADDR, DEFAULT_BROKER, 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic2")).isNull(); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic", "TestTopic1", "TestTopic2"); routeInfoManager.submitUnRegisterBrokerRequest(BrokerBasicInfo.defaultBroker().unRegisterRequest()); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic2")).isNull(); } @Test public void registerSlaveBroker() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsKeys(0L); registerBrokerWithNormalTopic(BrokerBasicInfo.slaveBroker(), "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsKeys(0L, 1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(BrokerBasicInfo.defaultBroker().brokerAddr, BrokerBasicInfo.slaveBroker().brokerAddr); } @Test public void createNewTopic() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); registerBrokerWithNormalTopic(BrokerBasicInfo.slaveBroker(), "TestTopic"); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic", "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1").getBrokerDatas().get(0).getBrokerAddrs()).containsKeys(0L, 1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(BrokerBasicInfo.defaultBroker().brokerAddr, BrokerBasicInfo.slaveBroker().brokerAddr); } @Test public void switchBrokerRole() { final BrokerBasicInfo masterBroker = BrokerBasicInfo.defaultBroker().enableActingMaster(false); final BrokerBasicInfo slaveBroker = BrokerBasicInfo.slaveBroker().enableActingMaster(false); registerBrokerWithNormalTopic(masterBroker, "TestTopic"); registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); // Master Down routeInfoManager.unregisterBroker(masterBroker.clusterName, masterBroker.brokerAddr, masterBroker.brokerName, 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsOnlyKeys(1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(slaveBroker.brokerAddr); // Switch slave to master slaveBroker.id(0).dataVersion.nextVersion(); registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsOnlyKeys(0L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(slaveBroker.brokerAddr); // Old master switch to slave masterBroker.id(1).dataVersion.nextVersion(); registerBrokerWithNormalTopic(masterBroker, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsKeys(0L, 1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(BrokerBasicInfo.defaultBroker().brokerAddr, BrokerBasicInfo.slaveBroker().brokerAddr); } @Test public void unRegisterSlaveBroker() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); final BrokerBasicInfo slaveBroker = BrokerBasicInfo.slaveBroker(); registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); routeInfoManager.unregisterBroker(slaveBroker.clusterName, slaveBroker.brokerAddr, slaveBroker.brokerName, 1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0) .getBrokerAddrs().get(0L)).isEqualTo(BrokerBasicInfo.defaultBroker().brokerAddr); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); routeInfoManager.submitUnRegisterBrokerRequest(slaveBroker.unRegisterRequest()); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0) .getBrokerAddrs().get(0L)).isEqualTo(BrokerBasicInfo.defaultBroker().brokerAddr); } @Test public void unRegisterMasterBroker() { final BrokerBasicInfo masterBroker = BrokerBasicInfo.defaultBroker(); masterBroker.enableActingMaster = true; registerBrokerWithNormalTopic(masterBroker, "TestTopic"); final BrokerBasicInfo slaveBroker = BrokerBasicInfo.slaveBroker(); registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); routeInfoManager.unregisterBroker(masterBroker.clusterName, masterBroker.brokerAddr, masterBroker.brokerName, 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0) .getBrokerAddrs().get(0L)).isEqualTo(slaveBroker.brokerAddr); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getQueueDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getQueueDatas().get(0).getPerm()).isEqualTo(PermName.PERM_READ); } @Test public void unRegisterMasterBrokerOldVersion() { final BrokerBasicInfo masterBroker = BrokerBasicInfo.defaultBroker(); masterBroker.enableActingMaster = false; registerBrokerWithNormalTopic(masterBroker, "TestTopic"); final BrokerBasicInfo slaveBroker = BrokerBasicInfo.slaveBroker(); slaveBroker.enableActingMaster = false; registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); routeInfoManager.unregisterBroker(masterBroker.clusterName, masterBroker.brokerAddr, masterBroker.brokerName, 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0) .getBrokerAddrs().get(0L)).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getQueueDatas().size()).isEqualTo(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getQueueDatas().get(0).getPerm()).isEqualTo(PermName.PERM_READ | PermName.PERM_WRITE); } @Test public void submitMultiUnRegisterRequests() { final BrokerBasicInfo master1 = BrokerBasicInfo.defaultBroker(); final BrokerBasicInfo master2 = BrokerBasicInfo.defaultBroker().name(DEFAULT_BROKER + 1).addr(DEFAULT_ADDR + 9); registerBrokerWithNormalTopic(master1, "TestTopic1"); registerBrokerWithNormalTopic(master2, "TestTopic2"); routeInfoManager.submitUnRegisterBrokerRequest(master1.unRegisterRequest()); routeInfoManager.submitUnRegisterBrokerRequest(master2.unRegisterRequest()); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic2")).isNull(); } @Test public void isBrokerTopicConfigChanged() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); final DataVersion dataVersion = routeInfoManager.queryBrokerTopicConfig(DEFAULT_CLUSTER, DEFAULT_ADDR); assertThat(routeInfoManager.isBrokerTopicConfigChanged(DEFAULT_CLUSTER, DEFAULT_ADDR, dataVersion)).isFalse(); DataVersion newVersion = new DataVersion(); newVersion.setTimestamp(System.currentTimeMillis() + 1000); newVersion.setCounter(new AtomicLong(dataVersion.getCounter().get())); assertThat(routeInfoManager.isBrokerTopicConfigChanged(DEFAULT_CLUSTER, DEFAULT_ADDR, newVersion)).isTrue(); newVersion = new DataVersion(); newVersion.setTimestamp(dataVersion.getTimestamp()); newVersion.setCounter(new AtomicLong(dataVersion.getCounter().get() + 1)); assertThat(routeInfoManager.isBrokerTopicConfigChanged(DEFAULT_CLUSTER, DEFAULT_ADDR, newVersion)).isTrue(); } @Test public void isTopicConfigChanged() { final BrokerBasicInfo brokerInfo = BrokerBasicInfo.defaultBroker(); assertThat(routeInfoManager.isTopicConfigChanged(DEFAULT_CLUSTER, DEFAULT_ADDR, brokerInfo.dataVersion, DEFAULT_BROKER, "TestTopic")).isTrue(); registerBrokerWithNormalTopic(brokerInfo, "TestTopic"); assertThat(routeInfoManager.isTopicConfigChanged(DEFAULT_CLUSTER, DEFAULT_ADDR, brokerInfo.dataVersion, DEFAULT_BROKER, "TestTopic")).isFalse(); assertThat(routeInfoManager.isTopicConfigChanged(DEFAULT_CLUSTER, DEFAULT_ADDR, brokerInfo.dataVersion, DEFAULT_BROKER, "TestTopic1")).isTrue(); } @Test public void queryBrokerTopicConfig() { final BrokerBasicInfo basicInfo = BrokerBasicInfo.defaultBroker(); registerBrokerWithNormalTopic(basicInfo, "TestTopic"); final DataVersion dataVersion = routeInfoManager.queryBrokerTopicConfig(DEFAULT_CLUSTER, DEFAULT_ADDR); assertThat(basicInfo.dataVersion.equals(dataVersion)).isTrue(); } @Test public void wipeWritePermOfBrokerByLock() { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getQueueDatas().get(0).getPerm()).isEqualTo(6); routeInfoManager.wipeWritePermOfBrokerByLock(DEFAULT_BROKER); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getQueueDatas().get(0).getPerm()).isEqualTo(4); } @Test public void pickupTopicRouteData() { String testTopic = "TestTopic"; registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); TopicRouteData data = routeInfoManager.pickupTopicRouteData(testTopic); assertThat(data.getBrokerDatas().size()).isEqualTo(1); assertThat(data.getBrokerDatas().get(0).getBrokerName()).isEqualTo(DEFAULT_BROKER); assertThat(data.getBrokerDatas().get(0).getBrokerAddrs().get(0L)).isEqualTo(DEFAULT_ADDR); assertThat(data.getQueueDatas().size()).isEqualTo(1); assertThat(data.getQueueDatas().get(0).getBrokerName()).isEqualTo(DEFAULT_BROKER); assertThat(data.getQueueDatas().get(0).getReadQueueNums()).isEqualTo(8); assertThat(data.getQueueDatas().get(0).getWriteQueueNums()).isEqualTo(8); assertThat(data.getQueueDatas().get(0).getPerm()).isEqualTo(6); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker().name("AnotherBroker"), testTopic); data = routeInfoManager.pickupTopicRouteData(testTopic); assertThat(data.getBrokerDatas().size()).isEqualTo(2); assertThat(data.getQueueDatas().size()).isEqualTo(2); List brokerList = Arrays.asList(data.getBrokerDatas().get(0).getBrokerName(), data.getBrokerDatas().get(1).getBrokerName()); assertThat(brokerList).contains(DEFAULT_BROKER, "AnotherBroker"); brokerList = Arrays.asList(data.getQueueDatas().get(0).getBrokerName(), data.getQueueDatas().get(1).getBrokerName()); assertThat(brokerList).contains(DEFAULT_BROKER, "AnotherBroker"); } @Test public void pickupTopicRouteDataWithSlave() { String testTopic = "TestTopic"; registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); registerBrokerWithNormalTopic(BrokerBasicInfo.slaveBroker(), testTopic); TopicRouteData routeData = routeInfoManager.pickupTopicRouteData(testTopic); assertThat(routeData.getBrokerDatas().get(0).getBrokerAddrs()).hasSize(2); assertThat(PermName.isWriteable(routeData.getQueueDatas().get(0).getPerm())).isTrue(); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.defaultBroker().unRegisterRequest())); routeData = routeInfoManager.pickupTopicRouteData(testTopic); assertThat(routeData.getBrokerDatas().get(0).getBrokerAddrs()).hasSize(1); assertThat(PermName.isWriteable(routeData.getQueueDatas().get(0).getPerm())).isFalse(); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); routeData = routeInfoManager.pickupTopicRouteData(testTopic); assertThat(routeData.getBrokerDatas().get(0).getBrokerAddrs()).hasSize(2); assertThat(PermName.isWriteable(routeData.getQueueDatas().get(0).getPerm())).isTrue(); } @Test public void scanNotActiveBroker() throws InterruptedException { registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); routeInfoManager.scanNotActiveBroker(); registerBrokerWithNormalTopicAndExpire(BrokerBasicInfo.defaultBroker(),"TestTopic"); Thread.sleep(30000); routeInfoManager.scanNotActiveBroker(); } @Test public void pickupPartitionOrderTopicRouteData() { String orderTopic = "PartitionOrderTopicTest"; // Case 1: Register global order topic with slave registerBrokerWithOrderTopic(BrokerBasicInfo.slaveBroker(), orderTopic); TopicRouteData orderRoute = routeInfoManager.pickupTopicRouteData(orderTopic); // Acting master check assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsOnlyKeys(MixAll.MASTER_ID); assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsValue(BrokerBasicInfo.slaveBroker().brokerAddr); assertThat(PermName.isWriteable(orderRoute.getQueueDatas().get(0).getPerm())).isFalse(); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.slaveBroker().unRegisterRequest())); // Case 2: Register global order topic with master and slave, then unregister master registerBrokerWithOrderTopic(BrokerBasicInfo.slaveBroker(), orderTopic); registerBrokerWithOrderTopic(BrokerBasicInfo.defaultBroker(), orderTopic); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.defaultBroker().unRegisterRequest())); orderRoute = routeInfoManager.pickupTopicRouteData(orderTopic); // Acting master check assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsOnlyKeys(MixAll.MASTER_ID); assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsValue(BrokerBasicInfo.slaveBroker().brokerAddr); assertThat(PermName.isWriteable(orderRoute.getQueueDatas().get(0).getPerm())).isFalse(); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.slaveBroker().unRegisterRequest())); // Case 3: Register two broker groups, only one group enable acting master registerBrokerWithOrderTopic(BrokerBasicInfo.slaveBroker(), orderTopic); registerBrokerWithOrderTopic(BrokerBasicInfo.defaultBroker(), orderTopic); final BrokerBasicInfo master1 = BrokerBasicInfo.defaultBroker().name(DEFAULT_BROKER + "_ANOTHER"); final BrokerBasicInfo slave1 = BrokerBasicInfo.slaveBroker().name(DEFAULT_BROKER + "_ANOTHER"); registerBrokerWithOrderTopic(master1, orderTopic); registerBrokerWithOrderTopic(slave1, orderTopic); orderRoute = routeInfoManager.pickupTopicRouteData(orderTopic); assertThat(orderRoute.getBrokerDatas()).hasSize(2); assertThat(orderRoute.getQueueDatas()).hasSize(2); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.defaultBroker().unRegisterRequest())); orderRoute = routeInfoManager.pickupTopicRouteData(orderTopic); assertThat(orderRoute.getBrokerDatas()).hasSize(2); assertThat(orderRoute.getQueueDatas()).hasSize(2); for (final BrokerData brokerData : orderRoute.getBrokerDatas()) { if (brokerData.getBrokerAddrs().size() == 1) { assertThat(brokerData.getBrokerAddrs()).containsOnlyKeys(MixAll.MASTER_ID); assertThat(brokerData.getBrokerAddrs()).containsValue(BrokerBasicInfo.slaveBroker().brokerAddr); } else if (brokerData.getBrokerAddrs().size() == 2) { assertThat(brokerData.getBrokerAddrs()).containsKeys(MixAll.MASTER_ID, (long) slave1.brokerId); assertThat(brokerData.getBrokerAddrs()).containsValues(master1.brokerAddr, slave1.brokerAddr); } else { throw new RuntimeException("Shouldn't reach here"); } } } @Test public void pickupGlobalOrderTopicRouteData() { String orderTopic = "GlobalOrderTopicTest"; // Case 1: Register global order topic with slave registerBrokerWithGlobalOrderTopic(BrokerBasicInfo.slaveBroker(), orderTopic); TopicRouteData orderRoute = routeInfoManager.pickupTopicRouteData(orderTopic); // Acting master check assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsOnlyKeys(MixAll.MASTER_ID); assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsValue(BrokerBasicInfo.slaveBroker().brokerAddr); assertThat(PermName.isWriteable(orderRoute.getQueueDatas().get(0).getPerm())).isFalse(); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.slaveBroker().unRegisterRequest())); // Case 2: Register global order topic with master and slave, then unregister master registerBrokerWithGlobalOrderTopic(BrokerBasicInfo.slaveBroker(), orderTopic); registerBrokerWithGlobalOrderTopic(BrokerBasicInfo.defaultBroker(), orderTopic); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.defaultBroker().unRegisterRequest())); // Acting master check assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsOnlyKeys(MixAll.MASTER_ID); assertThat(orderRoute.getBrokerDatas().get(0).getBrokerAddrs()) .containsValue(BrokerBasicInfo.slaveBroker().brokerAddr); assertThat(PermName.isWriteable(orderRoute.getQueueDatas().get(0).getPerm())).isFalse(); } @Test public void registerOnlySlaveBroker() { final String testTopic = "TestTopic"; // Case 1: Only slave broker registerBrokerWithNormalTopic(BrokerBasicInfo.slaveBroker(), testTopic); assertThat(routeInfoManager.pickupTopicRouteData(testTopic)).isNotNull(); int topicPerm = routeInfoManager.pickupTopicRouteData(testTopic).getQueueDatas().get(0).getPerm(); assertThat(PermName.isWriteable(topicPerm)).isFalse(); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.slaveBroker().unRegisterRequest())); // Case 2: Register master, and slave, then unregister master, finally recover master registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); registerBrokerWithNormalTopic(BrokerBasicInfo.slaveBroker(), testTopic); assertThat(routeInfoManager.pickupTopicRouteData(testTopic)).isNotNull(); topicPerm = routeInfoManager.pickupTopicRouteData(testTopic).getQueueDatas().get(0).getPerm(); assertThat(PermName.isWriteable(topicPerm)).isTrue(); routeInfoManager.unRegisterBroker(Sets.newHashSet(BrokerBasicInfo.defaultBroker().unRegisterRequest())); assertThat(routeInfoManager.pickupTopicRouteData(testTopic)).isNotNull(); topicPerm = routeInfoManager.pickupTopicRouteData(testTopic).getQueueDatas().get(0).getPerm(); assertThat(PermName.isWriteable(topicPerm)).isFalse(); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), testTopic); assertThat(routeInfoManager.pickupTopicRouteData(testTopic)).isNotNull(); topicPerm = routeInfoManager.pickupTopicRouteData(testTopic).getQueueDatas().get(0).getPerm(); assertThat(PermName.isWriteable(topicPerm)).isTrue(); } @Test public void onChannelDestroy() { Channel channel = mock(Channel.class); registerBroker(BrokerBasicInfo.defaultBroker(), channel, null, "TestTopic", "TestTopic1"); routeInfoManager.onChannelDestroy(channel); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); final BrokerBasicInfo masterBroker = BrokerBasicInfo.defaultBroker().enableActingMaster(false); final BrokerBasicInfo slaveBroker = BrokerBasicInfo.slaveBroker().enableActingMaster(false); Channel masterChannel = mock(Channel.class); Channel slaveChannel = mock(Channel.class); registerBroker(masterBroker, masterChannel, null, "TestTopic"); registerBroker(slaveBroker, slaveChannel, null, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsKeys(0L, 1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(masterBroker.brokerAddr, slaveBroker.brokerAddr); routeInfoManager.onChannelDestroy(masterChannel); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsOnlyKeys(1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(slaveBroker.brokerAddr); routeInfoManager.onChannelDestroy(slaveChannel); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); } @Test public void onChannelDestroyByBrokerInfo() { registerBroker(BrokerBasicInfo.defaultBroker(), mock(Channel.class), null, "TestTopic", "TestTopic1"); BrokerAddrInfo brokerAddrInfo = new BrokerAddrInfo(DEFAULT_CLUSTER, DEFAULT_ADDR); routeInfoManager.onChannelDestroy(brokerAddrInfo); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); } @Test public void switchBrokerRole_ChannelDestroy() { final BrokerBasicInfo masterBroker = BrokerBasicInfo.defaultBroker().enableActingMaster(false); final BrokerBasicInfo slaveBroker = BrokerBasicInfo.slaveBroker().enableActingMaster(false); Channel masterChannel = mock(Channel.class); Channel slaveChannel = mock(Channel.class); registerBroker(masterBroker, masterChannel, null, "TestTopic"); registerBroker(slaveBroker, slaveChannel, null, "TestTopic"); // Master Down routeInfoManager.onChannelDestroy(masterChannel); await().atMost(Duration.ofSeconds(5)).until(() -> routeInfoManager.blockedUnRegisterRequests() == 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsOnlyKeys(1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(slaveBroker.brokerAddr); // Switch slave to master slaveBroker.id(0).dataVersion.nextVersion(); registerBrokerWithNormalTopic(slaveBroker, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsOnlyKeys(0L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(slaveBroker.brokerAddr); // Old master switch to slave masterBroker.id(1).dataVersion.nextVersion(); registerBrokerWithNormalTopic(masterBroker, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()).containsKeys(0L, 1L); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerAddrs()) .containsValues(BrokerBasicInfo.defaultBroker().brokerAddr, BrokerBasicInfo.slaveBroker().brokerAddr); } @Test public void keepTopicWithBrokerRegistration() { RegisterBrokerResult masterResult = registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic", "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNotNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); masterResult = registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNotNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); } @Test public void deleteTopicWithBrokerRegistration() { config.setDeleteTopicWithBrokerRegistration(true); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic", "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNotNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); } @Test public void deleteTopicWithBrokerRegistration2() { // Register two brokers and delete a specific one by one config.setDeleteTopicWithBrokerRegistration(true); final BrokerBasicInfo master1 = BrokerBasicInfo.defaultBroker(); final BrokerBasicInfo master2 = BrokerBasicInfo.defaultBroker().name(DEFAULT_BROKER + 1).addr(DEFAULT_ADDR + 9); registerBrokerWithNormalTopic(master1, "TestTopic", "TestTopic1"); registerBrokerWithNormalTopic(master2, "TestTopic", "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas()).hasSize(2); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1").getBrokerDatas()).hasSize(2); registerBrokerWithNormalTopic(master1, "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas()).hasSize(1); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic").getBrokerDatas().get(0).getBrokerName()) .isEqualTo(master2.brokerName); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1").getBrokerDatas()).hasSize(2); registerBrokerWithNormalTopic(master2, "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1").getBrokerDatas()).hasSize(2); } @Test public void registerSingleTopicWithBrokerRegistration() { config.setDeleteTopicWithBrokerRegistration(true); final BrokerBasicInfo master1 = BrokerBasicInfo.defaultBroker(); registerSingleTopicWithBrokerName(master1.brokerName, "TestTopic"); // Single topic registration failed because there is no broker connection exists assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); // Register broker with TestTopic first and then register single topic TestTopic1 registerBrokerWithNormalTopic(master1, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNotNull(); registerSingleTopicWithBrokerName(master1.brokerName, "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); // Register the two topics to keep the route info registerBrokerWithNormalTopic(master1, "TestTopic", "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNotNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); // Cancel the TestTopic1 with broker registration registerBrokerWithNormalTopic(master1, "TestTopic"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNotNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); // Add TestTopic1 and cancel all the topics with broker un-registration registerSingleTopicWithBrokerName(master1.brokerName, "TestTopic1"); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNotNull(); routeInfoManager.unregisterBroker(master1.clusterName, master1.brokerAddr, master1.brokerName, 0); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic")).isNull(); assertThat(routeInfoManager.pickupTopicRouteData("TestTopic1")).isNull(); } private RegisterBrokerResult registerBrokerWithNormalTopic(BrokerBasicInfo brokerInfo, String... topics) { ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); TopicConfig baseTopic = new TopicConfig("baseTopic"); topicConfigConcurrentHashMap.put(baseTopic.getTopicName(), baseTopic); for (final String topic : topics) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topic); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigConcurrentHashMap.put(topic, topicConfig); } return registerBroker(brokerInfo, mock(Channel.class), topicConfigConcurrentHashMap, topics); } private RegisterBrokerResult registerBrokerWithNormalTopicAndExpire(BrokerBasicInfo brokerInfo, String... topics) { ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); TopicConfig baseTopic = new TopicConfig("baseTopic"); topicConfigConcurrentHashMap.put(baseTopic.getTopicName(), baseTopic); for (final String topic : topics) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topic); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigConcurrentHashMap.put(topic, topicConfig); } return registerBrokerWithExpiredTime(brokerInfo, mock(Channel.class), topicConfigConcurrentHashMap, topics); } private RegisterBrokerResult registerBrokerWithOrderTopic(BrokerBasicInfo brokerBasicInfo, String... topics) { ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); TopicConfig baseTopic = new TopicConfig("baseTopic"); baseTopic.setOrder(true); topicConfigConcurrentHashMap.put(baseTopic.getTopicName(), baseTopic); for (final String topic : topics) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topic); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(true); topicConfigConcurrentHashMap.put(topic, topicConfig); } return registerBroker(brokerBasicInfo, mock(Channel.class), topicConfigConcurrentHashMap, topics); } private RegisterBrokerResult registerBrokerWithGlobalOrderTopic(BrokerBasicInfo brokerBasicInfo, String... topics) { ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); TopicConfig baseTopic = new TopicConfig("baseTopic", 1, 1); baseTopic.setOrder(true); topicConfigConcurrentHashMap.put(baseTopic.getTopicName(), baseTopic); for (final String topic : topics) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(1); topicConfig.setTopicName(topic); topicConfig.setPerm(6); topicConfig.setReadQueueNums(1); topicConfig.setOrder(true); topicConfigConcurrentHashMap.put(topic, topicConfig); } return registerBroker(brokerBasicInfo, mock(Channel.class), topicConfigConcurrentHashMap, topics); } private RegisterBrokerResult registerBroker(BrokerBasicInfo brokerInfo, Channel channel, ConcurrentMap topicConfigConcurrentHashMap, String... topics) { if (topicConfigConcurrentHashMap == null) { topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); TopicConfig baseTopic = new TopicConfig("baseTopic"); topicConfigConcurrentHashMap.put(baseTopic.getTopicName(), baseTopic); for (final String topic : topics) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topic); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigConcurrentHashMap.put(topic, topicConfig); } } TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(brokerInfo.dataVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigConcurrentHashMap); return routeInfoManager.registerBroker( brokerInfo.clusterName, brokerInfo.brokerAddr, brokerInfo.brokerName, brokerInfo.brokerId, brokerInfo.haAddr, "", null, brokerInfo.enableActingMaster, topicConfigSerializeWrapper, new ArrayList<>(), channel); } private RegisterBrokerResult registerBrokerWithExpiredTime(BrokerBasicInfo brokerInfo, Channel channel, ConcurrentMap topicConfigConcurrentHashMap, String... topics) { if (topicConfigConcurrentHashMap == null) { topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); TopicConfig baseTopic = new TopicConfig("baseTopic"); topicConfigConcurrentHashMap.put(baseTopic.getTopicName(), baseTopic); for (final String topic : topics) { TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topic); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigConcurrentHashMap.put(topic, topicConfig); } } TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(brokerInfo.dataVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigConcurrentHashMap); return routeInfoManager.registerBroker( brokerInfo.clusterName, brokerInfo.brokerAddr, brokerInfo.brokerName, brokerInfo.brokerId, brokerInfo.haAddr, "", 30000L, brokerInfo.enableActingMaster, topicConfigSerializeWrapper, new ArrayList<>(), channel); } private void registerSingleTopicWithBrokerName(String brokerName, String... topics) { for (final String topic : topics) { QueueData queueData = new QueueData(); queueData.setBrokerName(brokerName); queueData.setReadQueueNums(8); queueData.setWriteQueueNums(8); queueData.setPerm(6); routeInfoManager.registerTopic(topic, Collections.singletonList(queueData)); } } static class BrokerBasicInfo { String clusterName; String brokerName; String brokerAddr; String haAddr; int brokerId; boolean enableActingMaster; DataVersion dataVersion; static BrokerBasicInfo defaultBroker() { BrokerBasicInfo basicInfo = new BrokerBasicInfo(); DataVersion dataVersion = new DataVersion(); dataVersion.setCounter(new AtomicLong(1)); dataVersion.setTimestamp(System.currentTimeMillis()); basicInfo.dataVersion = dataVersion; return basicInfo.id(0) .name(DEFAULT_BROKER) .cluster(DEFAULT_CLUSTER) .addr(DEFAULT_ADDR) .haAddr(DEFAULT_ADDR_PREFIX + "20911") .enableActingMaster(true); } UnRegisterBrokerRequestHeader unRegisterRequest() { UnRegisterBrokerRequestHeader unRegisterBrokerRequest = new UnRegisterBrokerRequestHeader(); unRegisterBrokerRequest.setBrokerAddr(brokerAddr); unRegisterBrokerRequest.setBrokerName(brokerName); unRegisterBrokerRequest.setClusterName(clusterName); unRegisterBrokerRequest.setBrokerId((long) brokerId); return unRegisterBrokerRequest; } static BrokerBasicInfo slaveBroker() { final BrokerBasicInfo slaveBroker = defaultBroker(); return slaveBroker .id(1) .addr(DEFAULT_ADDR_PREFIX + "30911") .haAddr(DEFAULT_ADDR_PREFIX + "40911") .enableActingMaster(true); } BrokerBasicInfo name(String name) { this.brokerName = name; return this; } BrokerBasicInfo cluster(String name) { this.clusterName = name; return this; } BrokerBasicInfo addr(String addr) { this.brokerAddr = addr; return this; } BrokerBasicInfo id(int id) { this.brokerId = id; return this; } BrokerBasicInfo haAddr(String addr) { this.haAddr = addr; return this; } BrokerBasicInfo enableActingMaster(boolean enableActingMaster) { this.enableActingMaster = enableActingMaster; return this; } } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerStaticRegisterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.body.TopicList; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class RouteInfoManagerStaticRegisterTest extends RouteInfoManagerTestBase { private static RouteInfoManager routeInfoManager; public static String clusterName = "cluster"; public static String brokerPrefix = "broker"; public static String topicPrefix = "topic"; public static RouteInfoManagerTestBase.Cluster cluster; @Before public void setup() { routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); cluster = registerCluster(routeInfoManager, clusterName, brokerPrefix, 3, 3, topicPrefix, 10); } @After public void terminate() { routeInfoManager.printAllPeriodically(); for (BrokerData bd : cluster.brokerDataMap.values()) { unregisterBrokerAll(routeInfoManager, bd); } } @Test public void testGetAllClusterInfo() { ClusterInfo clusterInfo = routeInfoManager.getAllClusterInfo(); Map> clusterAddrTable = clusterInfo.getClusterAddrTable(); assertEquals(1, clusterAddrTable.size()); assertEquals(cluster.getAllBrokerName(), clusterAddrTable.get(clusterName)); } @Test public void testGetAllTopicList() { TopicList topicInfo = routeInfoManager.getAllTopicList(); assertEquals(cluster.getAllTopicName(), topicInfo.getTopicList()); } @Test public void testGetTopicsByCluster() { TopicList topicList = routeInfoManager.getTopicsByCluster(clusterName); assertEquals(cluster.getAllTopicName(), topicList.getTopicList()); } @Test public void testPickupTopicRouteData() { String topic = getTopicName(topicPrefix, 0); TopicRouteData topicRouteData = routeInfoManager.pickupTopicRouteData(topic); TopicConfig topicConfig = cluster.topicConfig.get(topic); // check broker data Collections.sort(topicRouteData.getBrokerDatas()); List ans = new ArrayList<>(cluster.brokerDataMap.values()); Collections.sort(ans); assertEquals(topicRouteData.getBrokerDatas(), ans); // check queue data HashSet allBrokerNameInQueueData = new HashSet<>(); for (QueueData queueData : topicRouteData.getQueueDatas()) { allBrokerNameInQueueData.add(queueData.getBrokerName()); assertEquals(queueData.getWriteQueueNums(), topicConfig.getWriteQueueNums()); assertEquals(queueData.getReadQueueNums(), topicConfig.getReadQueueNums()); assertEquals(queueData.getPerm(), topicConfig.getPerm()); assertEquals(queueData.getTopicSysFlag(), topicConfig.getTopicSysFlag()); } assertEquals(allBrokerNameInQueueData, new HashSet<>(cluster.getAllBrokerName())); } @Test public void testDeleteTopic() { String topic = getTopicName(topicPrefix, 0); routeInfoManager.deleteTopic(topic); assertNull(routeInfoManager.pickupTopicRouteData(topic)); } @Test public void testGetSystemTopicList() { TopicList topicList = routeInfoManager.getSystemTopicList(); assertThat(topicList).isNotNull(); } @Test public void testGetUnitTopics() { TopicList topicList = routeInfoManager.getUnitTopics(); assertThat(topicList).isNotNull(); } @Test public void testGetHasUnitSubTopicList() { TopicList topicList = routeInfoManager.getHasUnitSubTopicList(); assertThat(topicList).isNotNull(); } @Test public void testGetHasUnitSubUnUnitTopicList() { TopicList topicList = routeInfoManager.getHasUnitSubUnUnitTopicList(); assertThat(topicList).isNotNull(); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import io.netty.channel.Channel; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; public class RouteInfoManagerTest { private static RouteInfoManager routeInfoManager; @Before public void setup() { routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); routeInfoManager.start(); testRegisterBroker(); } @After public void terminate() { routeInfoManager.shutdown(); routeInfoManager.printAllPeriodically(); routeInfoManager.unregisterBroker("default-cluster", "127.0.0.1:10911", "default-broker", 1234); } @Test public void testGetAllClusterInfo() { byte[] clusterInfo = routeInfoManager.getAllClusterInfo().encode(); assertThat(clusterInfo).isNotNull(); } @Test public void testQueryBrokerTopicConfig() { { DataVersion targetVersion = new DataVersion(); targetVersion.setCounter(new AtomicLong(10L)); targetVersion.setTimestamp(100L); DataVersion dataVersion = routeInfoManager.queryBrokerTopicConfig("default-cluster", "127.0.0.1:10911"); assertThat(dataVersion.equals(targetVersion)).isTrue(); } { // register broker default-cluster-1 with the same addr, then test DataVersion targetVersion = new DataVersion(); targetVersion.setCounter(new AtomicLong(20L)); targetVersion.setTimestamp(200L); ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); topicConfigConcurrentHashMap.put("unit-test-0", new TopicConfig("unit-test-0")); topicConfigConcurrentHashMap.put("unit-test-1", new TopicConfig("unit-test-1")); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(targetVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigConcurrentHashMap); Channel channel = mock(Channel.class); RegisterBrokerResult registerBrokerResult = routeInfoManager.registerBroker("default-cluster-1", "127.0.0.1:10911", "default-broker-1", 1234, "127.0.0.1:1001", "", null, topicConfigSerializeWrapper, new ArrayList<>(), channel); assertThat(registerBrokerResult).isNotNull(); DataVersion dataVersion0 = routeInfoManager.queryBrokerTopicConfig("default-cluster", "127.0.0.1:10911"); assertThat(targetVersion.equals(dataVersion0)).isFalse(); DataVersion dataVersion1 = routeInfoManager.queryBrokerTopicConfig("default-cluster-1", "127.0.0.1:10911"); assertThat(targetVersion.equals(dataVersion1)).isTrue(); } // unregister broker default-cluster-1, then test { routeInfoManager.unregisterBroker("default-cluster-1", "127.0.0.1:10911", "default-broker-1", 1234); assertThat(null != routeInfoManager.queryBrokerTopicConfig("default-cluster", "127.0.0.1:10911")).isTrue(); assertThat(null == routeInfoManager.queryBrokerTopicConfig("default-cluster-1", "127.0.0.1:10911")).isTrue(); } } @Test public void testGetAllTopicList() { byte[] topicInfo = routeInfoManager.getAllTopicList().encode(); Assert.assertTrue(topicInfo != null); assertThat(topicInfo).isNotNull(); } @Test public void testRegisterBroker() { DataVersion dataVersion = new DataVersion(); dataVersion.setCounter(new AtomicLong(10L)); dataVersion.setTimestamp(100L); ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); topicConfigConcurrentHashMap.put("unit-test0", new TopicConfig("unit-test0")); topicConfigConcurrentHashMap.put("unit-test1", new TopicConfig("unit-test1")); TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setDataVersion(dataVersion); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigConcurrentHashMap); Channel channel = mock(Channel.class); RegisterBrokerResult registerBrokerResult = routeInfoManager.registerBroker("default-cluster", "127.0.0.1:10911", "default-broker", 1234, "127.0.0.1:1001", "", null, topicConfigSerializeWrapper, new ArrayList<>(), channel); assertThat(registerBrokerResult).isNotNull(); } @Test public void testWipeWritePermOfBrokerByLock() throws Exception { Map qdMap = new HashMap<>(); QueueData qd = new QueueData(); qd.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); qd.setBrokerName("broker-a"); qdMap.put("broker-a",qd); HashMap> topicQueueTable = new HashMap<>(); topicQueueTable.put("topic-a", qdMap); Field filed = RouteInfoManager.class.getDeclaredField("topicQueueTable"); filed.setAccessible(true); filed.set(routeInfoManager, topicQueueTable); int addTopicCnt = routeInfoManager.wipeWritePermOfBrokerByLock("broker-a"); assertThat(addTopicCnt).isEqualTo(1); assertThat(qd.getPerm()).isEqualTo(PermName.PERM_READ); } @Test public void testPickupTopicRouteData() { TopicRouteData result = routeInfoManager.pickupTopicRouteData("unit_test"); assertThat(result).isNull(); } @Test public void testGetSystemTopicList() { byte[] topicList = routeInfoManager.getSystemTopicList().encode(); assertThat(topicList).isNotNull(); } @Test public void testGetTopicsByCluster() { byte[] topicList = routeInfoManager.getTopicsByCluster("default-cluster").encode(); assertThat(topicList).isNotNull(); } @Test public void testGetUnitTopics() { byte[] topicList = routeInfoManager.getUnitTopics().encode(); assertThat(topicList).isNotNull(); } @Test public void testGetHasUnitSubTopicList() { byte[] topicList = routeInfoManager.getHasUnitSubTopicList().encode(); assertThat(topicList).isNotNull(); } @Test public void testGetHasUnitSubUnUnitTopicList() { byte[] topicList = routeInfoManager.getHasUnitSubUnUnitTopicList().encode(); assertThat(topicList).isNotNull(); } @Test public void testAddWritePermOfBrokerByLock() throws Exception { Map qdMap = new HashMap<>(); QueueData qd = new QueueData(); qd.setPerm(PermName.PERM_READ); qd.setBrokerName("broker-a"); qdMap.put("broker-a",qd); HashMap> topicQueueTable = new HashMap<>(); topicQueueTable.put("topic-a", qdMap); Field filed = RouteInfoManager.class.getDeclaredField("topicQueueTable"); filed.setAccessible(true); filed.set(routeInfoManager, topicQueueTable); int addTopicCnt = routeInfoManager.addWritePermOfBrokerByLock("broker-a"); assertThat(addTopicCnt).isEqualTo(1); assertThat(qd.getPerm()).isEqualTo(PermName.PERM_READ | PermName.PERM_WRITE); } } ================================================ FILE: namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.namesrv.routeinfo; import io.netty.channel.Channel; import io.netty.channel.embedded.EmbeddedChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult; import org.apache.rocketmq.remoting.protocol.route.BrokerData; public class RouteInfoManagerTestBase { protected static class Cluster { ConcurrentMap topicConfig; Map brokerDataMap; public Cluster(ConcurrentMap topicConfig, Map brokerData) { this.topicConfig = topicConfig; this.brokerDataMap = brokerData; } public Set getAllBrokerName() { return brokerDataMap.keySet(); } public Set getAllTopicName() { return topicConfig.keySet(); } } protected Cluster registerCluster(RouteInfoManager routeInfoManager, String cluster, String brokerNamePrefix, int brokerNameNumber, int brokerPerName, String topicPrefix, int topicNumber) { Map brokerDataMap = new HashMap<>(); // no filterServer address List filterServerAddr = new ArrayList<>(); ConcurrentMap topicConfig = genTopicConfig(topicPrefix, topicNumber); for (int i = 0; i < brokerNameNumber; i++) { String brokerName = getBrokerName(brokerNamePrefix, i); BrokerData brokerData = genBrokerData(cluster, brokerName, brokerPerName, true); // avoid object reference copy ConcurrentMap topicConfigForBroker = genTopicConfig(topicPrefix, topicNumber); registerBrokerWithTopicConfig(routeInfoManager, brokerData, topicConfigForBroker, filterServerAddr); // avoid object reference copy brokerDataMap.put(brokerData.getBrokerName(), genBrokerData(cluster, brokerName, brokerPerName, true)); } return new Cluster(topicConfig, brokerDataMap); } protected String getBrokerAddr(String cluster, String brokerName, long brokerNumber) { return cluster + "-" + brokerName + ":" + brokerNumber; } protected BrokerData genBrokerData(String clusterName, String brokerName, long totalBrokerNumber, boolean hasMaster) { HashMap brokerAddrMap = new HashMap<>(); long startId = 0; if (hasMaster) { brokerAddrMap.put(MixAll.MASTER_ID, getBrokerAddr(clusterName, brokerName, MixAll.MASTER_ID)); startId = 1; } for (long i = startId; i < totalBrokerNumber; i++) { brokerAddrMap.put(i, getBrokerAddr(clusterName, brokerName, i)); } return new BrokerData(clusterName, brokerName, brokerAddrMap); } protected void registerBrokerWithTopicConfig(RouteInfoManager routeInfoManager, BrokerData brokerData, ConcurrentMap topicConfigTable, List filterServerAddr) { brokerData.getBrokerAddrs().forEach((brokerId, brokerAddr) -> { registerBrokerWithTopicConfig(routeInfoManager, brokerData.getCluster(), brokerAddr, brokerData.getBrokerName(), brokerId, brokerAddr, // set ha server address the same as brokerAddr new ConcurrentHashMap<>(topicConfigTable), new ArrayList<>(filterServerAddr)); }); } protected void unregisterBrokerAll(RouteInfoManager routeInfoManager, BrokerData brokerData) { for (Map.Entry entry : brokerData.getBrokerAddrs().entrySet()) { routeInfoManager.unregisterBroker(brokerData.getCluster(), entry.getValue(), brokerData.getBrokerName(), entry.getKey()); } } protected void unregisterBroker(RouteInfoManager routeInfoManager, BrokerData brokerData, long brokerId) { HashMap brokerAddrs = brokerData.getBrokerAddrs(); if (brokerAddrs.containsKey(brokerId)) { String address = brokerAddrs.remove(brokerId); routeInfoManager.unregisterBroker(brokerData.getCluster(), address, brokerData.getBrokerName(), brokerId); } } protected RegisterBrokerResult registerBrokerWithTopicConfig(RouteInfoManager routeInfoManager, String clusterName, String brokerAddr, String brokerName, long brokerId, String haServerAddr, ConcurrentMap topicConfigTable, List filterServerAddr) { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(topicConfigTable); Channel channel = new EmbeddedChannel(); return routeInfoManager.registerBroker(clusterName, brokerAddr, brokerName, brokerId, "", haServerAddr, null, topicConfigSerializeWrapper, filterServerAddr, channel); } protected String getTopicName(String topicPrefix, int topicNumber) { return topicPrefix + "-" + topicNumber; } protected ConcurrentMap genTopicConfig(String topicPrefix, int topicNumber) { ConcurrentMap topicConfigMap = new ConcurrentHashMap<>(); for (int i = 0; i < topicNumber; i++) { String topicName = getTopicName(topicPrefix, i); TopicConfig topicConfig = new TopicConfig(); topicConfig.setWriteQueueNums(8); topicConfig.setTopicName(topicName); topicConfig.setPerm(6); topicConfig.setReadQueueNums(8); topicConfig.setOrder(false); topicConfigMap.put(topicName, topicConfig); } return topicConfigMap; } protected String getBrokerName(String brokerNamePrefix, long brokerNameNumber) { return brokerNamePrefix + "-" + brokerNameNumber; } protected BrokerData findBrokerDataByBrokerName(List data, String brokerName) { return data.stream().filter(bd -> bd.getBrokerName().equals(brokerName)).findFirst().orElse(null); } } ================================================ FILE: namesrv/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: openmessaging/pom.xml ================================================ rocketmq-all org.apache.rocketmq ${revision} 4.0.0 rocketmq-openmessaging rocketmq-openmessaging ${project.version} ${basedir}/.. io.openmessaging openmessaging-api ${project.groupId} rocketmq-client ${project.version} ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/MessagingAccessPointImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq; import io.openmessaging.KeyValue; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.ResourceManager; import io.openmessaging.consumer.PullConsumer; import io.openmessaging.consumer.PushConsumer; import io.openmessaging.consumer.StreamingConsumer; import io.openmessaging.exception.OMSNotSupportedException; import io.openmessaging.producer.Producer; import io.openmessaging.rocketmq.consumer.PullConsumerImpl; import io.openmessaging.rocketmq.consumer.PushConsumerImpl; import io.openmessaging.rocketmq.producer.ProducerImpl; import io.openmessaging.rocketmq.utils.OMSUtil; public class MessagingAccessPointImpl implements MessagingAccessPoint { private final KeyValue accessPointProperties; public MessagingAccessPointImpl(final KeyValue accessPointProperties) { this.accessPointProperties = accessPointProperties; } @Override public KeyValue attributes() { return accessPointProperties; } @Override public String implVersion() { return "0.3.0"; } @Override public Producer createProducer() { return new ProducerImpl(this.accessPointProperties); } @Override public Producer createProducer(KeyValue properties) { return new ProducerImpl(OMSUtil.buildKeyValue(this.accessPointProperties, properties)); } @Override public PushConsumer createPushConsumer() { return new PushConsumerImpl(accessPointProperties); } @Override public PushConsumer createPushConsumer(KeyValue properties) { return new PushConsumerImpl(OMSUtil.buildKeyValue(this.accessPointProperties, properties)); } @Override public PullConsumer createPullConsumer() { return new PullConsumerImpl(accessPointProperties); } @Override public PullConsumer createPullConsumer(KeyValue attributes) { return new PullConsumerImpl(OMSUtil.buildKeyValue(this.accessPointProperties, attributes)); } @Override public StreamingConsumer createStreamingConsumer() { return null; } @Override public StreamingConsumer createStreamingConsumer(KeyValue attributes) { return null; } @Override public ResourceManager resourceManager() { throw new OMSNotSupportedException("-1", "ResourceManager is not supported in current version."); } @Override public void startup() { //Ignore } @Override public void shutdown() { //Ignore } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/config/ClientConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.config; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.rocketmq.domain.NonStandardKeys; public class ClientConfig implements OMSBuiltinKeys, NonStandardKeys { private String driverImpl; private String accessPoints; private String namespace; private String producerId; private String consumerId; private int operationTimeout = 5000; private String region; private String routingSource; private String routingDestination; private String routingExpression; private String rmqConsumerGroup; private String rmqProducerGroup = "__OMS_PRODUCER_DEFAULT_GROUP"; private int rmqMaxRedeliveryTimes = 16; private int rmqMessageConsumeTimeout = 15; //In minutes private int rmqMaxConsumeThreadNums = 64; private int rmqMinConsumeThreadNums = 20; private String rmqMessageDestination; private int rmqPullMessageBatchNums = 32; private int rmqPullMessageCacheCapacity = 1000; public String getDriverImpl() { return driverImpl; } public void setDriverImpl(final String driverImpl) { this.driverImpl = driverImpl; } public String getAccessPoints() { return accessPoints; } public void setAccessPoints(final String accessPoints) { this.accessPoints = accessPoints; } public String getNamespace() { return namespace; } public void setNamespace(final String namespace) { this.namespace = namespace; } public String getProducerId() { return producerId; } public void setProducerId(final String producerId) { this.producerId = producerId; } public String getConsumerId() { return consumerId; } public void setConsumerId(final String consumerId) { this.consumerId = consumerId; } public int getOperationTimeout() { return operationTimeout; } public void setOperationTimeout(final int operationTimeout) { this.operationTimeout = operationTimeout; } public String getRoutingSource() { return routingSource; } public void setRoutingSource(final String routingSource) { this.routingSource = routingSource; } public String getRmqConsumerGroup() { return rmqConsumerGroup; } public void setRmqConsumerGroup(final String rmqConsumerGroup) { this.rmqConsumerGroup = rmqConsumerGroup; } public String getRmqProducerGroup() { return rmqProducerGroup; } public void setRmqProducerGroup(final String rmqProducerGroup) { this.rmqProducerGroup = rmqProducerGroup; } public int getRmqMaxRedeliveryTimes() { return rmqMaxRedeliveryTimes; } public void setRmqMaxRedeliveryTimes(final int rmqMaxRedeliveryTimes) { this.rmqMaxRedeliveryTimes = rmqMaxRedeliveryTimes; } public int getRmqMessageConsumeTimeout() { return rmqMessageConsumeTimeout; } public void setRmqMessageConsumeTimeout(final int rmqMessageConsumeTimeout) { this.rmqMessageConsumeTimeout = rmqMessageConsumeTimeout; } public int getRmqMaxConsumeThreadNums() { return rmqMaxConsumeThreadNums; } public void setRmqMaxConsumeThreadNums(final int rmqMaxConsumeThreadNums) { this.rmqMaxConsumeThreadNums = rmqMaxConsumeThreadNums; } public int getRmqMinConsumeThreadNums() { return rmqMinConsumeThreadNums; } public void setRmqMinConsumeThreadNums(final int rmqMinConsumeThreadNums) { this.rmqMinConsumeThreadNums = rmqMinConsumeThreadNums; } public String getRmqMessageDestination() { return rmqMessageDestination; } public void setRmqMessageDestination(final String rmqMessageDestination) { this.rmqMessageDestination = rmqMessageDestination; } public int getRmqPullMessageBatchNums() { return rmqPullMessageBatchNums; } public void setRmqPullMessageBatchNums(final int rmqPullMessageBatchNums) { this.rmqPullMessageBatchNums = rmqPullMessageBatchNums; } public int getRmqPullMessageCacheCapacity() { return rmqPullMessageCacheCapacity; } public void setRmqPullMessageCacheCapacity(final int rmqPullMessageCacheCapacity) { this.rmqPullMessageCacheCapacity = rmqPullMessageCacheCapacity; } public String getRegion() { return region; } public void setRegion(String region) { this.region = region; } public String getRoutingDestination() { return routingDestination; } public void setRoutingDestination(String routingDestination) { this.routingDestination = routingDestination; } public String getRoutingExpression() { return routingExpression; } public void setRoutingExpression(String routingExpression) { this.routingExpression = routingExpression; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/LocalMessageCache.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.consumer; import io.openmessaging.KeyValue; import io.openmessaging.Message; import io.openmessaging.ServiceLifecycle; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.ConsumeRequest; import java.util.Collections; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; class LocalMessageCache implements ServiceLifecycle { private static final Logger log = LoggerFactory.getLogger(LocalMessageCache.class); private final BlockingQueue consumeRequestCache; private final Map consumedRequest; private final ConcurrentHashMap pullOffsetTable; private final DefaultMQPullConsumer rocketmqPullConsumer; private final ClientConfig clientConfig; private final ScheduledExecutorService cleanExpireMsgExecutors; LocalMessageCache(final DefaultMQPullConsumer rocketmqPullConsumer, final ClientConfig clientConfig) { consumeRequestCache = new LinkedBlockingQueue<>(clientConfig.getRmqPullMessageCacheCapacity()); this.consumedRequest = new ConcurrentHashMap<>(); this.pullOffsetTable = new ConcurrentHashMap<>(); this.rocketmqPullConsumer = rocketmqPullConsumer; this.clientConfig = clientConfig; this.cleanExpireMsgExecutors = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( "OMS_CleanExpireMsgScheduledThread_")); } int nextPullBatchNums() { return Math.min(clientConfig.getRmqPullMessageBatchNums(), consumeRequestCache.remainingCapacity()); } long nextPullOffset(MessageQueue remoteQueue) { if (!pullOffsetTable.containsKey(remoteQueue)) { try { pullOffsetTable.putIfAbsent(remoteQueue, rocketmqPullConsumer.fetchConsumeOffset(remoteQueue, false)); } catch (MQClientException e) { log.error("An error occurred in fetch consume offset process.", e); } } return pullOffsetTable.get(remoteQueue); } void updatePullOffset(MessageQueue remoteQueue, long nextPullOffset) { pullOffsetTable.put(remoteQueue, nextPullOffset); } void submitConsumeRequest(ConsumeRequest consumeRequest) { try { consumeRequestCache.put(consumeRequest); } catch (InterruptedException ignore) { } } MessageExt poll() { return poll(clientConfig.getOperationTimeout()); } MessageExt poll(final KeyValue properties) { int currentPollTimeout = clientConfig.getOperationTimeout(); if (properties.containsKey(Message.BuiltinKeys.TIMEOUT)) { currentPollTimeout = properties.getInt(Message.BuiltinKeys.TIMEOUT); } return poll(currentPollTimeout); } private MessageExt poll(long timeout) { try { ConsumeRequest consumeRequest = consumeRequestCache.poll(timeout, TimeUnit.MILLISECONDS); if (consumeRequest != null) { MessageExt messageExt = consumeRequest.getMessageExt(); consumeRequest.setStartConsumeTimeMillis(System.currentTimeMillis()); MessageAccessor.setConsumeStartTimeStamp(messageExt, String.valueOf(consumeRequest.getStartConsumeTimeMillis())); consumedRequest.put(messageExt.getMsgId(), consumeRequest); return messageExt; } } catch (InterruptedException ignore) { } return null; } void ack(final String messageId) { ConsumeRequest consumeRequest = consumedRequest.remove(messageId); if (consumeRequest != null) { long offset = consumeRequest.getProcessQueue().removeMessage(Collections.singletonList(consumeRequest.getMessageExt())); try { rocketmqPullConsumer.updateConsumeOffset(consumeRequest.getMessageQueue(), offset); } catch (MQClientException e) { log.error("An error occurred in update consume offset process.", e); } } } void ack(final MessageQueue messageQueue, final ProcessQueue processQueue, final MessageExt messageExt) { consumedRequest.remove(messageExt.getMsgId()); long offset = processQueue.removeMessage(Collections.singletonList(messageExt)); try { rocketmqPullConsumer.updateConsumeOffset(messageQueue, offset); } catch (MQClientException e) { log.error("An error occurred in update consume offset process.", e); } } @Override public void startup() { this.cleanExpireMsgExecutors.scheduleAtFixedRate(new Runnable() { @Override public void run() { cleanExpireMsg(); } }, clientConfig.getRmqMessageConsumeTimeout(), clientConfig.getRmqMessageConsumeTimeout(), TimeUnit.MINUTES); } @Override public void shutdown() { ThreadUtils.shutdownGracefully(cleanExpireMsgExecutors, 5000, TimeUnit.MILLISECONDS); } private void cleanExpireMsg() { for (final Map.Entry next : rocketmqPullConsumer.getDefaultMQPullConsumerImpl() .getRebalanceImpl().getProcessQueueTable().entrySet()) { ProcessQueue pq = next.getValue(); MessageQueue mq = next.getKey(); ReadWriteLock lockTreeMap = getLockInProcessQueue(pq); if (lockTreeMap == null) { log.error("Gets tree map lock in process queue error, may be has compatibility issue"); return; } TreeMap msgTreeMap = pq.getMsgTreeMap(); int loop = msgTreeMap.size(); for (int i = 0; i < loop; i++) { MessageExt msg = null; try { lockTreeMap.readLock().lockInterruptibly(); try { if (!msgTreeMap.isEmpty()) { msg = msgTreeMap.firstEntry().getValue(); if (System.currentTimeMillis() - Long.parseLong(MessageAccessor.getConsumeStartTimeStamp(msg)) > clientConfig.getRmqMessageConsumeTimeout() * 60 * 1000) { //Expired, ack and remove it. } else { break; } } else { break; } } finally { lockTreeMap.readLock().unlock(); } } catch (InterruptedException e) { log.error("Gets expired message exception", e); } try { rocketmqPullConsumer.sendMessageBack(msg, 3); log.info("Send expired msg back. topic={}, msgId={}, storeHost={}, queueId={}, queueOffset={}", msg.getTopic(), msg.getMsgId(), msg.getStoreHost(), msg.getQueueId(), msg.getQueueOffset()); ack(mq, pq, msg); } catch (Exception e) { log.error("Send back expired msg exception", e); } } } } private ReadWriteLock getLockInProcessQueue(ProcessQueue pq) { try { return (ReadWriteLock) FieldUtils.readDeclaredField(pq, "lockTreeMap", true); } catch (IllegalAccessException e) { return null; } } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PullConsumerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.consumer; import io.openmessaging.KeyValue; import io.openmessaging.Message; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.PullConsumer; import io.openmessaging.exception.OMSRuntimeException; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.ConsumeRequest; import io.openmessaging.rocketmq.utils.BeanUtils; import io.openmessaging.rocketmq.utils.OMSUtil; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.MQPullConsumer; import org.apache.rocketmq.client.consumer.MQPullConsumerScheduleService; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullTaskCallback; import org.apache.rocketmq.client.consumer.PullTaskContext; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class PullConsumerImpl implements PullConsumer { private static final Logger log = LoggerFactory.getLogger(PullConsumerImpl.class); private final DefaultMQPullConsumer rocketmqPullConsumer; private final KeyValue properties; private boolean started = false; private final MQPullConsumerScheduleService pullConsumerScheduleService; private final LocalMessageCache localMessageCache; private final ClientConfig clientConfig; public PullConsumerImpl(final KeyValue properties) { this.properties = properties; this.clientConfig = BeanUtils.populate(properties, ClientConfig.class); String consumerGroup = clientConfig.getConsumerId(); if (null == consumerGroup || consumerGroup.isEmpty()) { throw new OMSRuntimeException("-1", "Consumer Group is necessary for RocketMQ, please set it."); } pullConsumerScheduleService = new MQPullConsumerScheduleService(consumerGroup); this.rocketmqPullConsumer = pullConsumerScheduleService.getDefaultMQPullConsumer(); if ("true".equalsIgnoreCase(System.getenv("OMS_RMQ_DIRECT_NAME_SRV"))) { String accessPoints = clientConfig.getAccessPoints(); if (accessPoints == null || accessPoints.isEmpty()) { throw new OMSRuntimeException("-1", "OMS AccessPoints is null or empty."); } this.rocketmqPullConsumer.setNamesrvAddr(accessPoints.replace(',', ';')); } this.rocketmqPullConsumer.setConsumerGroup(consumerGroup); int maxReDeliveryTimes = clientConfig.getRmqMaxRedeliveryTimes(); this.rocketmqPullConsumer.setMaxReconsumeTimes(maxReDeliveryTimes); String consumerId = OMSUtil.buildInstanceName(); this.rocketmqPullConsumer.setInstanceName(consumerId); properties.put(OMSBuiltinKeys.CONSUMER_ID, consumerId); this.rocketmqPullConsumer.setLanguage(LanguageCode.OMS); this.localMessageCache = new LocalMessageCache(this.rocketmqPullConsumer, clientConfig); } @Override public KeyValue attributes() { return properties; } @Override public PullConsumer attachQueue(String queueName) { registerPullTaskCallback(queueName); return this; } @Override public PullConsumer attachQueue(String queueName, KeyValue attributes) { registerPullTaskCallback(queueName); return this; } @Override public PullConsumer detachQueue(String queueName) { this.rocketmqPullConsumer.getRegisterTopics().remove(queueName); return this; } @Override public Message receive() { MessageExt rmqMsg = localMessageCache.poll(); return rmqMsg == null ? null : OMSUtil.msgConvert(rmqMsg); } @Override public Message receive(final KeyValue properties) { MessageExt rmqMsg = localMessageCache.poll(properties); return rmqMsg == null ? null : OMSUtil.msgConvert(rmqMsg); } @Override public void ack(final String messageId) { localMessageCache.ack(messageId); } @Override public void ack(final String messageId, final KeyValue properties) { localMessageCache.ack(messageId); } @Override public synchronized void startup() { if (!started) { try { this.pullConsumerScheduleService.start(); this.localMessageCache.startup(); } catch (MQClientException e) { throw new OMSRuntimeException("-1", e); } } this.started = true; } private void registerPullTaskCallback(final String targetQueueName) { this.pullConsumerScheduleService.registerPullTaskCallback(targetQueueName, new PullTaskCallback() { @Override public void doPullTask(final MessageQueue mq, final PullTaskContext context) { MQPullConsumer consumer = context.getPullConsumer(); try { long offset = localMessageCache.nextPullOffset(mq); PullResult pullResult = consumer.pull(mq, "*", offset, localMessageCache.nextPullBatchNums()); ProcessQueue pq = rocketmqPullConsumer.getDefaultMQPullConsumerImpl().getRebalanceImpl() .getProcessQueueTable().get(mq); switch (pullResult.getPullStatus()) { case FOUND: if (pq != null) { pq.putMessage(pullResult.getMsgFoundList()); for (final MessageExt messageExt : pullResult.getMsgFoundList()) { localMessageCache.submitConsumeRequest(new ConsumeRequest(messageExt, mq, pq)); } } break; default: break; } localMessageCache.updatePullOffset(mq, pullResult.getNextBeginOffset()); } catch (Exception e) { log.error("An error occurred in pull message process.", e); } } }); } @Override public synchronized void shutdown() { if (this.started) { this.localMessageCache.shutdown(); this.pullConsumerScheduleService.shutdown(); this.rocketmqPullConsumer.shutdown(); } this.started = false; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PushConsumerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.consumer; import io.openmessaging.BytesMessage; import io.openmessaging.KeyValue; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.MessageListener; import io.openmessaging.consumer.PushConsumer; import io.openmessaging.exception.OMSRuntimeException; import io.openmessaging.interceptor.ConsumerInterceptor; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.NonStandardKeys; import io.openmessaging.rocketmq.utils.BeanUtils; import io.openmessaging.rocketmq.utils.OMSUtil; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.protocol.LanguageCode; public class PushConsumerImpl implements PushConsumer { private final DefaultMQPushConsumer rocketmqPushConsumer; private final KeyValue properties; private boolean started = false; private final Map subscribeTable = new ConcurrentHashMap<>(); private final ClientConfig clientConfig; public PushConsumerImpl(final KeyValue properties) { this.rocketmqPushConsumer = new DefaultMQPushConsumer(); this.properties = properties; this.clientConfig = BeanUtils.populate(properties, ClientConfig.class); if ("true".equalsIgnoreCase(System.getenv("OMS_RMQ_DIRECT_NAME_SRV"))) { String accessPoints = clientConfig.getAccessPoints(); if (accessPoints == null || accessPoints.isEmpty()) { throw new OMSRuntimeException("-1", "OMS AccessPoints is null or empty."); } this.rocketmqPushConsumer.setNamesrvAddr(accessPoints.replace(',', ';')); } String consumerGroup = clientConfig.getConsumerId(); if (null == consumerGroup || consumerGroup.isEmpty()) { throw new OMSRuntimeException("-1", "Consumer Group is necessary for RocketMQ, please set it."); } this.rocketmqPushConsumer.setConsumerGroup(consumerGroup); this.rocketmqPushConsumer.setMaxReconsumeTimes(clientConfig.getRmqMaxRedeliveryTimes()); this.rocketmqPushConsumer.setConsumeTimeout(clientConfig.getRmqMessageConsumeTimeout()); this.rocketmqPushConsumer.setConsumeThreadMax(clientConfig.getRmqMaxConsumeThreadNums()); this.rocketmqPushConsumer.setConsumeThreadMin(clientConfig.getRmqMinConsumeThreadNums()); String consumerId = OMSUtil.buildInstanceName(); this.rocketmqPushConsumer.setInstanceName(consumerId); properties.put(OMSBuiltinKeys.CONSUMER_ID, consumerId); this.rocketmqPushConsumer.setLanguage(LanguageCode.OMS); this.rocketmqPushConsumer.registerMessageListener(new MessageListenerImpl()); } @Override public KeyValue attributes() { return properties; } @Override public void resume() { this.rocketmqPushConsumer.resume(); } @Override public void suspend() { this.rocketmqPushConsumer.suspend(); } @Override public void suspend(long timeout) { } @Override public boolean isSuspended() { return this.rocketmqPushConsumer.isPause(); } @Override public PushConsumer attachQueue(final String queueName, final MessageListener listener) { this.subscribeTable.put(queueName, listener); try { this.rocketmqPushConsumer.subscribe(queueName, "*"); } catch (MQClientException e) { throw new OMSRuntimeException("-1", String.format("RocketMQ push consumer can't attach to %s.", queueName)); } return this; } @Override public PushConsumer attachQueue(String queueName, MessageListener listener, KeyValue attributes) { return this.attachQueue(queueName, listener); } @Override public PushConsumer detachQueue(String queueName) { this.subscribeTable.remove(queueName); try { this.rocketmqPushConsumer.unsubscribe(queueName); } catch (Exception e) { throw new OMSRuntimeException("-1", String.format("RocketMQ push consumer fails to unsubscribe topic: %s", queueName)); } return null; } @Override public void addInterceptor(ConsumerInterceptor interceptor) { } @Override public void removeInterceptor(ConsumerInterceptor interceptor) { } @Override public synchronized void startup() { if (!started) { try { this.rocketmqPushConsumer.start(); } catch (MQClientException e) { throw new OMSRuntimeException("-1", e); } } this.started = true; } @Override public synchronized void shutdown() { if (this.started) { this.rocketmqPushConsumer.shutdown(); } this.started = false; } class MessageListenerImpl implements MessageListenerConcurrently { @Override public ConsumeConcurrentlyStatus consumeMessage(List rmqMsgList, ConsumeConcurrentlyContext contextRMQ) { MessageExt rmqMsg = rmqMsgList.get(0); BytesMessage omsMsg = OMSUtil.msgConvert(rmqMsg); MessageListener listener = PushConsumerImpl.this.subscribeTable.get(rmqMsg.getTopic()); if (listener == null) { throw new OMSRuntimeException("-1", String.format("The topic/queue %s isn't attached to this consumer", rmqMsg.getTopic())); } final KeyValue contextProperties = OMS.newKeyValue(); final CountDownLatch sync = new CountDownLatch(1); contextProperties.put(NonStandardKeys.MESSAGE_CONSUME_STATUS, ConsumeConcurrentlyStatus.RECONSUME_LATER.name()); MessageListener.Context context = new MessageListener.Context() { @Override public KeyValue attributes() { return contextProperties; } @Override public void ack() { sync.countDown(); contextProperties.put(NonStandardKeys.MESSAGE_CONSUME_STATUS, ConsumeConcurrentlyStatus.CONSUME_SUCCESS.name()); } }; long begin = System.currentTimeMillis(); listener.onReceived(omsMsg, context); long costs = System.currentTimeMillis() - begin; long timeoutMills = clientConfig.getRmqMessageConsumeTimeout() * 60 * 1000; try { sync.await(Math.max(0, timeoutMills - costs), TimeUnit.MILLISECONDS); } catch (InterruptedException ignore) { } return ConsumeConcurrentlyStatus.valueOf(contextProperties.getString(NonStandardKeys.MESSAGE_CONSUME_STATUS)); } } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/domain/BytesMessageImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.domain; import io.openmessaging.BytesMessage; import io.openmessaging.KeyValue; import io.openmessaging.Message; import io.openmessaging.OMS; import io.openmessaging.exception.OMSMessageFormatException; import org.apache.commons.lang3.builder.ToStringBuilder; public class BytesMessageImpl implements BytesMessage { private KeyValue sysHeaders; private KeyValue userHeaders; private byte[] body; public BytesMessageImpl() { this.sysHeaders = OMS.newKeyValue(); this.userHeaders = OMS.newKeyValue(); } @Override public T getBody(Class type) throws OMSMessageFormatException { if (type == byte[].class) { return (T)body; } throw new OMSMessageFormatException("", "Cannot assign byte[] to " + type.getName()); } @Override public BytesMessage setBody(final byte[] body) { this.body = body; return this; } @Override public KeyValue sysHeaders() { return sysHeaders; } @Override public KeyValue userHeaders() { return userHeaders; } @Override public Message putSysHeaders(String key, int value) { sysHeaders.put(key, value); return this; } @Override public Message putSysHeaders(String key, long value) { sysHeaders.put(key, value); return this; } @Override public Message putSysHeaders(String key, double value) { sysHeaders.put(key, value); return this; } @Override public Message putSysHeaders(String key, String value) { sysHeaders.put(key, value); return this; } @Override public Message putUserHeaders(String key, int value) { userHeaders.put(key, value); return this; } @Override public Message putUserHeaders(String key, long value) { userHeaders.put(key, value); return this; } @Override public Message putUserHeaders(String key, double value) { userHeaders.put(key, value); return this; } @Override public Message putUserHeaders(String key, String value) { userHeaders.put(key, value); return this; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/domain/ConsumeRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.domain; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class ConsumeRequest { private final MessageExt messageExt; private final MessageQueue messageQueue; private final ProcessQueue processQueue; private long startConsumeTimeMillis; public ConsumeRequest(final MessageExt messageExt, final MessageQueue messageQueue, final ProcessQueue processQueue) { this.messageExt = messageExt; this.messageQueue = messageQueue; this.processQueue = processQueue; } public MessageExt getMessageExt() { return messageExt; } public MessageQueue getMessageQueue() { return messageQueue; } public ProcessQueue getProcessQueue() { return processQueue; } public long getStartConsumeTimeMillis() { return startConsumeTimeMillis; } public void setStartConsumeTimeMillis(final long startConsumeTimeMillis) { this.startConsumeTimeMillis = startConsumeTimeMillis; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/domain/NonStandardKeys.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.domain; public interface NonStandardKeys { String CONSUMER_GROUP = "rmq.consumer.group"; String PRODUCER_GROUP = "rmq.producer.group"; String MAX_REDELIVERY_TIMES = "rmq.max.redelivery.times"; String MESSAGE_CONSUME_TIMEOUT = "rmq.message.consume.timeout"; String MAX_CONSUME_THREAD_NUMS = "rmq.max.consume.thread.nums"; String MIN_CONSUME_THREAD_NUMS = "rmq.min.consume.thread.nums"; String MESSAGE_CONSUME_STATUS = "rmq.message.consume.status"; String MESSAGE_DESTINATION = "rmq.message.destination"; String PULL_MESSAGE_BATCH_NUMS = "rmq.pull.message.batch.nums"; String PULL_MESSAGE_CACHE_CAPACITY = "rmq.pull.message.cache.capacity"; } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/domain/RocketMQConstants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.domain; public interface RocketMQConstants { /** * Key of scheduled message delivery time */ String START_DELIVER_TIME = "__STARTDELIVERTIME"; } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/domain/SendResultImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.domain; import io.openmessaging.KeyValue; import io.openmessaging.producer.SendResult; public class SendResultImpl implements SendResult { private String messageId; private KeyValue properties; public SendResultImpl(final String messageId, final KeyValue properties) { this.messageId = messageId; this.properties = properties; } @Override public String messageId() { return messageId; } public KeyValue properties() { return properties; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/producer/AbstractOMSProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.producer; import io.openmessaging.BytesMessage; import io.openmessaging.KeyValue; import io.openmessaging.Message; import io.openmessaging.MessageFactory; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.ServiceLifecycle; import io.openmessaging.exception.OMSMessageFormatException; import io.openmessaging.exception.OMSNotSupportedException; import io.openmessaging.exception.OMSRuntimeException; import io.openmessaging.exception.OMSTimeOutException; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.BytesMessageImpl; import io.openmessaging.rocketmq.utils.BeanUtils; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import static io.openmessaging.rocketmq.utils.OMSUtil.buildInstanceName; abstract class AbstractOMSProducer implements ServiceLifecycle, MessageFactory { final KeyValue properties; final DefaultMQProducer rocketmqProducer; private boolean started = false; private final ClientConfig clientConfig; AbstractOMSProducer(final KeyValue properties) { this.properties = properties; this.rocketmqProducer = new DefaultMQProducer(); this.clientConfig = BeanUtils.populate(properties, ClientConfig.class); if ("true".equalsIgnoreCase(System.getenv("OMS_RMQ_DIRECT_NAME_SRV"))) { String accessPoints = clientConfig.getAccessPoints(); if (accessPoints == null || accessPoints.isEmpty()) { throw new OMSRuntimeException("-1", "OMS AccessPoints is null or empty."); } this.rocketmqProducer.setNamesrvAddr(accessPoints.replace(',', ';')); } this.rocketmqProducer.setProducerGroup(clientConfig.getRmqProducerGroup()); String producerId = buildInstanceName(); this.rocketmqProducer.setSendMsgTimeout(clientConfig.getOperationTimeout()); this.rocketmqProducer.setInstanceName(producerId); this.rocketmqProducer.setMaxMessageSize(1024 * 1024 * 4); this.rocketmqProducer.setLanguage(LanguageCode.OMS); properties.put(OMSBuiltinKeys.PRODUCER_ID, producerId); } @Override public synchronized void startup() { if (!started) { try { this.rocketmqProducer.start(); } catch (MQClientException e) { throw new OMSRuntimeException("-1", e); } } this.started = true; } @Override public synchronized void shutdown() { if (this.started) { this.rocketmqProducer.shutdown(); } this.started = false; } OMSRuntimeException checkProducerException(String topic, String msgId, Throwable e) { if (e instanceof MQClientException) { if (e.getCause() != null) { if (e.getCause() instanceof RemotingTimeoutException) { return new OMSTimeOutException("-1", String.format("Send message to broker timeout, %dms, Topic=%s, msgId=%s", this.rocketmqProducer.getSendMsgTimeout(), topic, msgId), e); } else if (e.getCause() instanceof MQBrokerException || e.getCause() instanceof RemotingConnectException) { if (e.getCause() instanceof MQBrokerException) { MQBrokerException brokerException = (MQBrokerException) e.getCause(); return new OMSRuntimeException("-1", String.format("Received a broker exception, Topic=%s, msgId=%s, %s", topic, msgId, brokerException.getErrorMessage()), e); } if (e.getCause() instanceof RemotingConnectException) { RemotingConnectException connectException = (RemotingConnectException)e.getCause(); return new OMSRuntimeException("-1", String.format("Network connection experiences failures. Topic=%s, msgId=%s, %s", topic, msgId, connectException.getMessage()), e); } } } // Exception thrown by local. else { MQClientException clientException = (MQClientException) e; if (-1 == clientException.getResponseCode()) { return new OMSRuntimeException("-1", String.format("Topic does not exist, Topic=%s, msgId=%s", topic, msgId), e); } else if (ResponseCode.MESSAGE_ILLEGAL == clientException.getResponseCode()) { return new OMSMessageFormatException("-1", String.format("A illegal message for RocketMQ, Topic=%s, msgId=%s", topic, msgId), e); } } } return new OMSRuntimeException("-1", "Send message to RocketMQ broker failed.", e); } protected void checkMessageType(Message message) { if (!(message instanceof BytesMessage)) { throw new OMSNotSupportedException("-1", "Only BytesMessage is supported."); } } @Override public BytesMessage createBytesMessage(String queue, byte[] body) { BytesMessage message = new BytesMessageImpl(); message.setBody(body); message.sysHeaders().put(Message.BuiltinKeys.DESTINATION, queue); return message; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/producer/ProducerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.producer; import io.openmessaging.BytesMessage; import io.openmessaging.KeyValue; import io.openmessaging.Message; import io.openmessaging.Promise; import io.openmessaging.exception.OMSRuntimeException; import io.openmessaging.interceptor.ProducerInterceptor; import io.openmessaging.producer.BatchMessageSender; import io.openmessaging.producer.LocalTransactionExecutor; import io.openmessaging.producer.Producer; import io.openmessaging.producer.SendResult; import io.openmessaging.rocketmq.promise.DefaultPromise; import io.openmessaging.rocketmq.utils.OMSUtil; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import static io.openmessaging.rocketmq.utils.OMSUtil.msgConvert; public class ProducerImpl extends AbstractOMSProducer implements Producer { private static final Logger log = LoggerFactory.getLogger(ProducerImpl.class); public ProducerImpl(final KeyValue properties) { super(properties); } @Override public KeyValue attributes() { return properties; } @Override public SendResult send(final Message message) { return send(message, this.rocketmqProducer.getSendMsgTimeout()); } @Override public SendResult send(final Message message, final KeyValue properties) { long timeout = properties.containsKey(Message.BuiltinKeys.TIMEOUT) ? properties.getInt(Message.BuiltinKeys.TIMEOUT) : this.rocketmqProducer.getSendMsgTimeout(); return send(message, timeout); } @Override public SendResult send(Message message, LocalTransactionExecutor branchExecutor, KeyValue attributes) { return null; } private SendResult send(final Message message, long timeout) { checkMessageType(message); org.apache.rocketmq.common.message.Message rmqMessage = msgConvert((BytesMessage) message); try { org.apache.rocketmq.client.producer.SendResult rmqResult = this.rocketmqProducer.send(rmqMessage, timeout); if (!rmqResult.getSendStatus().equals(SendStatus.SEND_OK)) { log.error(String.format("Send message to RocketMQ failed, %s", message)); throw new OMSRuntimeException("-1", "Send message to RocketMQ broker failed."); } message.sysHeaders().put(Message.BuiltinKeys.MESSAGE_ID, rmqResult.getMsgId()); return OMSUtil.sendResultConvert(rmqResult); } catch (Exception e) { log.error(String.format("Send message to RocketMQ failed, %s", message), e); throw checkProducerException(rmqMessage.getTopic(), message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID), e); } } @Override public Promise sendAsync(final Message message) { return sendAsync(message, this.rocketmqProducer.getSendMsgTimeout()); } @Override public Promise sendAsync(final Message message, final KeyValue properties) { long timeout = properties.containsKey(Message.BuiltinKeys.TIMEOUT) ? properties.getInt(Message.BuiltinKeys.TIMEOUT) : this.rocketmqProducer.getSendMsgTimeout(); return sendAsync(message, timeout); } private Promise sendAsync(final Message message, long timeout) { checkMessageType(message); org.apache.rocketmq.common.message.Message rmqMessage = msgConvert((BytesMessage) message); final Promise promise = new DefaultPromise<>(); try { this.rocketmqProducer.send(rmqMessage, new SendCallback() { @Override public void onSuccess(final org.apache.rocketmq.client.producer.SendResult rmqResult) { message.sysHeaders().put(Message.BuiltinKeys.MESSAGE_ID, rmqResult.getMsgId()); promise.set(OMSUtil.sendResultConvert(rmqResult)); } @Override public void onException(final Throwable e) { promise.setFailure(e); } }, timeout); } catch (Exception e) { promise.setFailure(e); } return promise; } @Override public void sendOneway(final Message message) { checkMessageType(message); org.apache.rocketmq.common.message.Message rmqMessage = msgConvert((BytesMessage) message); try { this.rocketmqProducer.sendOneway(rmqMessage); } catch (Exception ignore) { //Ignore the oneway exception. } } @Override public void sendOneway(final Message message, final KeyValue properties) { sendOneway(message); } @Override public BatchMessageSender createBatchMessageSender() { return null; } @Override public void addInterceptor(ProducerInterceptor interceptor) { } @Override public void removeInterceptor(ProducerInterceptor interceptor) { } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/promise/DefaultPromise.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.promise; import io.openmessaging.Promise; import io.openmessaging.FutureListener; import io.openmessaging.exception.OMSRuntimeException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; public class DefaultPromise implements Promise { private static final Logger LOG = LoggerFactory.getLogger(DefaultPromise.class); private final Object lock = new Object(); private volatile FutureState state = FutureState.DOING; private V result = null; private long timeout; private long createTime; private Throwable exception = null; private List> promiseListenerList; public DefaultPromise() { createTime = System.currentTimeMillis(); promiseListenerList = new ArrayList<>(); timeout = 5000; } @Override public boolean cancel(final boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return state.isCancelledState(); } @Override public boolean isDone() { return state.isDoneState(); } @Override public V get() { return result; } @Override public V get(final long timeout) { synchronized (lock) { if (!isDoing()) { return getValueOrThrowable(); } if (timeout <= 0) { try { lock.wait(); } catch (Exception e) { cancel(e); } return getValueOrThrowable(); } else { long waitTime = timeout - (System.currentTimeMillis() - createTime); if (waitTime > 0) { for (; ; ) { try { lock.wait(waitTime); } catch (InterruptedException e) { LOG.error("promise get value interrupted,exception:{}", e.getMessage()); } if (!isDoing()) { break; } else { waitTime = timeout - (System.currentTimeMillis() - createTime); if (waitTime <= 0) { break; } } } } if (isDoing()) { timeoutSoCancel(); } } return getValueOrThrowable(); } } @Override public boolean set(final V value) { if (value == null) return false; this.result = value; return done(); } @Override public boolean setFailure(final Throwable cause) { if (cause == null) return false; this.exception = cause; return done(); } @Override public void addListener(final FutureListener listener) { if (listener == null) { throw new NullPointerException("FutureListener is null"); } boolean notifyNow = false; synchronized (lock) { if (!isDoing()) { notifyNow = true; } else { if (promiseListenerList == null) { promiseListenerList = new ArrayList<>(); } promiseListenerList.add(listener); } } if (notifyNow) { notifyListener(listener); } } @Override public Throwable getThrowable() { return exception; } private void notifyListeners() { if (promiseListenerList != null) { for (FutureListener listener : promiseListenerList) { notifyListener(listener); } } } private boolean isSuccess() { return isDone() && exception == null; } private void timeoutSoCancel() { synchronized (lock) { if (!isDoing()) { return; } state = FutureState.CANCELLED; exception = new RuntimeException("Get request result is timeout or interrupted"); lock.notifyAll(); } notifyListeners(); } private V getValueOrThrowable() { if (exception != null) { Throwable e = exception.getCause() != null ? exception.getCause() : exception; throw new OMSRuntimeException("-1", e); } notifyListeners(); return result; } private boolean isDoing() { return state.isDoingState(); } private boolean done() { synchronized (lock) { if (!isDoing()) { return false; } state = FutureState.DONE; lock.notifyAll(); } notifyListeners(); return true; } private void notifyListener(final FutureListener listener) { try { listener.operationComplete(this); } catch (Throwable t) { LOG.error("notifyListener {} Error:{}", listener.getClass().getSimpleName(), t); } } private boolean cancel(Exception e) { synchronized (lock) { if (!isDoing()) { return false; } state = FutureState.CANCELLED; exception = e; lock.notifyAll(); } notifyListeners(); return true; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/promise/FutureState.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.promise; public enum FutureState { /** * the task is doing **/ DOING(0), /** * the task is done **/ DONE(1), /** * ths task is cancelled **/ CANCELLED(2); public final int value; private FutureState(int value) { this.value = value; } public boolean isCancelledState() { return this == CANCELLED; } public boolean isDoneState() { return this == DONE; } public boolean isDoingState() { return this == DOING; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/utils/BeanUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.utils; import io.openmessaging.KeyValue; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public final class BeanUtils { private static final Logger log = LoggerFactory.getLogger(BeanUtils.class); /** * Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. */ private static Map, Class> primitiveWrapperMap = new HashMap<>(); static { primitiveWrapperMap.put(Boolean.TYPE, Boolean.class); primitiveWrapperMap.put(Byte.TYPE, Byte.class); primitiveWrapperMap.put(Character.TYPE, Character.class); primitiveWrapperMap.put(Short.TYPE, Short.class); primitiveWrapperMap.put(Integer.TYPE, Integer.class); primitiveWrapperMap.put(Long.TYPE, Long.class); primitiveWrapperMap.put(Double.TYPE, Double.class); primitiveWrapperMap.put(Float.TYPE, Float.class); primitiveWrapperMap.put(Void.TYPE, Void.TYPE); } private static Map, Class> wrapperMap = new HashMap<>(); static { for (Entry, Class> primitiveClass : primitiveWrapperMap.entrySet()) { final Class wrapperClass = primitiveClass.getValue(); if (!primitiveClass.getKey().equals(wrapperClass)) { wrapperMap.put(wrapperClass, primitiveClass.getKey()); } } wrapperMap.put(String.class, String.class); } /** *

    Populate the JavaBeans properties of the specified bean, based on * the specified name/value pairs. This method uses Java reflection APIs * to identify corresponding "property setter" method names, and deals * with setter arguments of type String, boolean, * int, long, float, and * double.

    * *

    The particular setter method to be called for each property is * determined using the usual JavaBeans introspection mechanisms. Thus, * you may identify custom setter methods using a BeanInfo class that is * associated with the class of the bean itself. If no such BeanInfo * class is available, the standard method name conversion ("set" plus * the capitalized name of the property in question) is used.

    * *

    NOTE: It is contrary to the JavaBeans Specification * to have more than one setter method (with different argument * signatures) for the same property.

    * * @param clazz JavaBean class whose properties are being populated * @param properties Map keyed by property name, with the corresponding (String or String[]) value(s) to be set * @param Class type * @return Class instance */ public static T populate(final Properties properties, final Class clazz) { T obj = null; try { obj = clazz.getDeclaredConstructor().newInstance(); return populate(properties, obj); } catch (Throwable e) { log.warn("Error occurs !", e); } return obj; } public static T populate(final KeyValue properties, final Class clazz) { T obj = null; try { obj = clazz.getDeclaredConstructor().newInstance(); return populate(properties, obj); } catch (Throwable e) { log.warn("Error occurs !", e); } return obj; } public static Class getMethodClass(Class clazz, String methodName) { Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getName().equalsIgnoreCase(methodName)) { return method.getParameterTypes()[0]; } } return null; } public static void setProperties(Class clazz, Object obj, String methodName, Object value) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class parameterClass = getMethodClass(clazz, methodName); Method setterMethod = clazz.getMethod(methodName, parameterClass); if (parameterClass == Boolean.TYPE) { setterMethod.invoke(obj, Boolean.valueOf(value.toString())); } else if (parameterClass == Integer.TYPE) { setterMethod.invoke(obj, Integer.valueOf(value.toString())); } else if (parameterClass == Double.TYPE) { setterMethod.invoke(obj, Double.valueOf(value.toString())); } else if (parameterClass == Float.TYPE) { setterMethod.invoke(obj, Float.valueOf(value.toString())); } else if (parameterClass == Long.TYPE) { setterMethod.invoke(obj, Long.valueOf(value.toString())); } else setterMethod.invoke(obj, value); } public static T populate(final Properties properties, final T obj) { Class clazz = obj.getClass(); try { Set> entries = properties.entrySet(); for (Map.Entry entry : entries) { String entryKey = entry.getKey().toString(); String[] keyGroup = entryKey.split("\\."); for (int i = 0; i < keyGroup.length; i++) { keyGroup[i] = keyGroup[i].toLowerCase(); keyGroup[i] = StringUtils.capitalize(keyGroup[i]); } String beanFieldNameWithCapitalization = StringUtils.join(keyGroup); try { setProperties(clazz, obj, "set" + beanFieldNameWithCapitalization, entry.getValue()); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { //ignored... } } } catch (RuntimeException e) { log.warn("Error occurs !", e); } return obj; } public static T populate(final KeyValue properties, final T obj) { Class clazz = obj.getClass(); try { final Set keySet = properties.keySet(); for (String key : keySet) { String[] keyGroup = key.split("[\\._]"); for (int i = 0; i < keyGroup.length; i++) { keyGroup[i] = keyGroup[i].toLowerCase(); keyGroup[i] = StringUtils.capitalize(keyGroup[i]); } String beanFieldNameWithCapitalization = StringUtils.join(keyGroup); try { setProperties(clazz, obj, "set" + beanFieldNameWithCapitalization, properties.getString(key)); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { //ignored... } } } catch (RuntimeException e) { log.warn("Error occurs !", e); } return obj; } } ================================================ FILE: openmessaging/src/main/java/io/openmessaging/rocketmq/utils/OMSUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.utils; import io.openmessaging.BytesMessage; import io.openmessaging.KeyValue; import io.openmessaging.Message.BuiltinKeys; import io.openmessaging.OMS; import io.openmessaging.producer.SendResult; import io.openmessaging.rocketmq.domain.BytesMessageImpl; import io.openmessaging.rocketmq.domain.RocketMQConstants; import io.openmessaging.rocketmq.domain.SendResultImpl; import java.lang.reflect.Field; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageAccessor; public class OMSUtil { /** * Builds a OMS client instance name. * * @return a unique instance name */ public static String buildInstanceName() { return Integer.toString(UtilAll.getPid()) + "%OpenMessaging" + "%" + System.nanoTime(); } public static org.apache.rocketmq.common.message.Message msgConvert(BytesMessage omsMessage) { org.apache.rocketmq.common.message.Message rmqMessage = new org.apache.rocketmq.common.message.Message(); rmqMessage.setBody(omsMessage.getBody(byte[].class)); KeyValue sysHeaders = omsMessage.sysHeaders(); KeyValue userHeaders = omsMessage.userHeaders(); //All destinations in RocketMQ use Topic rmqMessage.setTopic(sysHeaders.getString(BuiltinKeys.DESTINATION)); if (sysHeaders.containsKey(BuiltinKeys.START_TIME)) { long deliverTime = sysHeaders.getLong(BuiltinKeys.START_TIME, 0); if (deliverTime > 0) { rmqMessage.putUserProperty(RocketMQConstants.START_DELIVER_TIME, String.valueOf(deliverTime)); } } for (String key : userHeaders.keySet()) { MessageAccessor.putProperty(rmqMessage, key, userHeaders.getString(key)); } //System headers has a high priority for (String key : sysHeaders.keySet()) { MessageAccessor.putProperty(rmqMessage, key, sysHeaders.getString(key)); } return rmqMessage; } public static BytesMessage msgConvert(org.apache.rocketmq.common.message.MessageExt rmqMsg) { BytesMessage omsMsg = new BytesMessageImpl(); omsMsg.setBody(rmqMsg.getBody()); KeyValue headers = omsMsg.sysHeaders(); KeyValue properties = omsMsg.userHeaders(); final Set> entries = rmqMsg.getProperties().entrySet(); for (final Map.Entry entry : entries) { if (isOMSHeader(entry.getKey())) { headers.put(entry.getKey(), entry.getValue()); } else { properties.put(entry.getKey(), entry.getValue()); } } omsMsg.putSysHeaders(BuiltinKeys.MESSAGE_ID, rmqMsg.getMsgId()); omsMsg.putSysHeaders(BuiltinKeys.DESTINATION, rmqMsg.getTopic()); omsMsg.putSysHeaders(BuiltinKeys.SEARCH_KEYS, rmqMsg.getKeys()); omsMsg.putSysHeaders(BuiltinKeys.BORN_HOST, String.valueOf(rmqMsg.getBornHost())); omsMsg.putSysHeaders(BuiltinKeys.BORN_TIMESTAMP, rmqMsg.getBornTimestamp()); omsMsg.putSysHeaders(BuiltinKeys.STORE_HOST, String.valueOf(rmqMsg.getStoreHost())); omsMsg.putSysHeaders(BuiltinKeys.STORE_TIMESTAMP, rmqMsg.getStoreTimestamp()); return omsMsg; } public static boolean isOMSHeader(String value) { for (Field field : BuiltinKeys.class.getDeclaredFields()) { try { if (field.get(BuiltinKeys.class).equals(value)) { return true; } } catch (IllegalAccessException e) { return false; } } return false; } /** * Convert a RocketMQ SEND_OK SendResult instance to a OMS SendResult. */ public static SendResult sendResultConvert(org.apache.rocketmq.client.producer.SendResult rmqResult) { assert rmqResult.getSendStatus().equals(SendStatus.SEND_OK); return new SendResultImpl(rmqResult.getMsgId(), OMS.newKeyValue()); } public static KeyValue buildKeyValue(KeyValue... keyValues) { KeyValue keyValue = OMS.newKeyValue(); for (KeyValue properties : keyValues) { for (String key : properties.keySet()) { keyValue.put(key, properties.getString(key)); } } return keyValue; } /** * Returns an iterator that cycles indefinitely over the elements of {@code Iterable}. */ public static Iterator cycle(final Iterable iterable) { return new Iterator() { Iterator iterator = new Iterator() { @Override public synchronized boolean hasNext() { return false; } @Override public synchronized T next() { throw new NoSuchElementException(); } @Override public synchronized void remove() { //Ignore } }; @Override public synchronized boolean hasNext() { return iterator.hasNext() || iterable.iterator().hasNext(); } @Override public synchronized T next() { if (!iterator.hasNext()) { iterator = iterable.iterator(); if (!iterator.hasNext()) { throw new NoSuchElementException(); } } return iterator.next(); } @Override public synchronized void remove() { iterator.remove(); } }; } } ================================================ FILE: openmessaging/src/test/java/io/openmessaging/rocketmq/consumer/LocalMessageCacheTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.consumer; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.ConsumeRequest; import io.openmessaging.rocketmq.domain.NonStandardKeys; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LocalMessageCacheTest { private LocalMessageCache localMessageCache; @Mock private DefaultMQPullConsumer rocketmqPullConsume; @Mock private ConsumeRequest consumeRequest; @Before public void init() { ClientConfig clientConfig = new ClientConfig(); clientConfig.setRmqPullMessageBatchNums(512); clientConfig.setRmqPullMessageCacheCapacity(1024); localMessageCache = new LocalMessageCache(rocketmqPullConsume, clientConfig); } @Test public void testNextPullBatchNums() throws Exception { assertThat(localMessageCache.nextPullBatchNums()).isEqualTo(512); for (int i = 0; i < 513; i++) { localMessageCache.submitConsumeRequest(consumeRequest); } assertThat(localMessageCache.nextPullBatchNums()).isEqualTo(511); } @Test public void testNextPullOffset() throws Exception { MessageQueue messageQueue = new MessageQueue(); when(rocketmqPullConsume.fetchConsumeOffset(any(MessageQueue.class), anyBoolean())) .thenReturn(123L); assertThat(localMessageCache.nextPullOffset(new MessageQueue())).isEqualTo(123L); } @Test public void testUpdatePullOffset() throws Exception { MessageQueue messageQueue = new MessageQueue(); localMessageCache.updatePullOffset(messageQueue, 124L); assertThat(localMessageCache.nextPullOffset(messageQueue)).isEqualTo(124L); } @Test public void testSubmitConsumeRequest() throws Exception { byte[] body = new byte[] {'1', '2', '3'}; MessageExt consumedMsg = new MessageExt(); consumedMsg.setMsgId("NewMsgId"); consumedMsg.setBody(body); consumedMsg.putUserProperty(NonStandardKeys.MESSAGE_DESTINATION, "TOPIC"); consumedMsg.setTopic("HELLO_QUEUE"); when(consumeRequest.getMessageExt()).thenReturn(consumedMsg); localMessageCache.submitConsumeRequest(consumeRequest); assertThat(localMessageCache.poll()).isEqualTo(consumedMsg); } } ================================================ FILE: openmessaging/src/test/java/io/openmessaging/rocketmq/consumer/PullConsumerImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.consumer; import io.openmessaging.BytesMessage; import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.PullConsumer; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.NonStandardKeys; import java.lang.reflect.Field; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.common.message.MessageExt; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PullConsumerImplTest { private PullConsumer consumer; private String queueName = "HELLO_QUEUE"; @Mock private DefaultMQPullConsumer rocketmqPullConsumer; private LocalMessageCache localMessageCache = null; @Before public void init() throws NoSuchFieldException, IllegalAccessException { final MessagingAccessPoint messagingAccessPoint = OMS .getMessagingAccessPoint("oms:rocketmq://IP1:9876,IP2:9876/namespace"); consumer = messagingAccessPoint.createPullConsumer(OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "TestGroup")); consumer.attachQueue(queueName); Field field = PullConsumerImpl.class.getDeclaredField("rocketmqPullConsumer"); field.setAccessible(true); field.set(consumer, rocketmqPullConsumer); //Replace ClientConfig clientConfig = new ClientConfig(); clientConfig.setOperationTimeout(200); localMessageCache = spy(new LocalMessageCache(rocketmqPullConsumer, clientConfig)); field = PullConsumerImpl.class.getDeclaredField("localMessageCache"); field.setAccessible(true); field.set(consumer, localMessageCache); messagingAccessPoint.startup(); consumer.startup(); } @Test public void testPoll() { final byte[] testBody = new byte[] {'a', 'b'}; MessageExt consumedMsg = new MessageExt(); consumedMsg.setMsgId("NewMsgId"); consumedMsg.setBody(testBody); consumedMsg.putUserProperty(NonStandardKeys.MESSAGE_DESTINATION, "TOPIC"); consumedMsg.setTopic(queueName); when(localMessageCache.poll()).thenReturn(consumedMsg); Message message = consumer.receive(); assertThat(message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID)).isEqualTo("NewMsgId"); assertThat(((BytesMessage) message).getBody(byte[].class)).isEqualTo(testBody); } @Test public void testPoll_WithTimeout() { //There is a default timeout value, @see ClientConfig#omsOperationTimeout. Message message = consumer.receive(); assertThat(message).isNull(); message = consumer.receive(OMS.newKeyValue().put(Message.BuiltinKeys.TIMEOUT, 100)); assertThat(message).isNull(); } } ================================================ FILE: openmessaging/src/test/java/io/openmessaging/rocketmq/consumer/PushConsumerImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.consumer; import io.openmessaging.BytesMessage; import io.openmessaging.Message; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.consumer.MessageListener; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.consumer.PushConsumer; import io.openmessaging.rocketmq.domain.NonStandardKeys; import java.lang.reflect.Field; import java.util.Collections; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PushConsumerImplTest { private PushConsumer consumer; @Mock private DefaultMQPushConsumer rocketmqPushConsumer; @Before public void init() throws NoSuchFieldException, IllegalAccessException { final MessagingAccessPoint messagingAccessPoint = OMS .getMessagingAccessPoint("oms:rocketmq://IP1:9876,IP2:9876/namespace"); consumer = messagingAccessPoint.createPushConsumer( OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "TestGroup")); Field field = PushConsumerImpl.class.getDeclaredField("rocketmqPushConsumer"); field.setAccessible(true); DefaultMQPushConsumer innerConsumer = (DefaultMQPushConsumer) field.get(consumer); field.set(consumer, rocketmqPushConsumer); //Replace when(rocketmqPushConsumer.getMessageListener()).thenReturn(innerConsumer.getMessageListener()); messagingAccessPoint.startup(); consumer.startup(); } @Test public void testConsumeMessage() { final byte[] testBody = new byte[] {'a', 'b'}; MessageExt consumedMsg = new MessageExt(); consumedMsg.setMsgId("NewMsgId"); consumedMsg.setBody(testBody); consumedMsg.putUserProperty(NonStandardKeys.MESSAGE_DESTINATION, "TOPIC"); consumedMsg.setTopic("HELLO_QUEUE"); consumer.attachQueue("HELLO_QUEUE", new MessageListener() { @Override public void onReceived(Message message, Context context) { assertThat(message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID)).isEqualTo("NewMsgId"); assertThat(((BytesMessage) message).getBody(byte[].class)).isEqualTo(testBody); context.ack(); } }); ((MessageListenerConcurrently) rocketmqPushConsumer .getMessageListener()).consumeMessage(Collections.singletonList(consumedMsg), null); } } ================================================ FILE: openmessaging/src/test/java/io/openmessaging/rocketmq/producer/ProducerImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.producer; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.exception.OMSRuntimeException; import io.openmessaging.producer.Producer; import java.lang.reflect.Field; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.exception.RemotingException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ProducerImplTest { private Producer producer; @Mock private DefaultMQProducer rocketmqProducer; @Before public void init() throws NoSuchFieldException, IllegalAccessException { final MessagingAccessPoint messagingAccessPoint = OMS .getMessagingAccessPoint("oms:rocketmq://IP1:9876,IP2:9876/namespace"); producer = messagingAccessPoint.createProducer(); Field field = AbstractOMSProducer.class.getDeclaredField("rocketmqProducer"); field.setAccessible(true); field.set(producer, rocketmqProducer); messagingAccessPoint.startup(); producer.startup(); } @Test public void testSend_OK() throws InterruptedException, RemotingException, MQClientException, MQBrokerException { SendResult sendResult = new SendResult(); sendResult.setMsgId("TestMsgID"); sendResult.setSendStatus(SendStatus.SEND_OK); when(rocketmqProducer.send(any(Message.class), anyLong())).thenReturn(sendResult); io.openmessaging.producer.SendResult omsResult = producer.send(producer.createBytesMessage("HELLO_TOPIC", new byte[] {'a'})); assertThat(omsResult.messageId()).isEqualTo("TestMsgID"); } @Test public void testSend_Not_OK() throws InterruptedException, RemotingException, MQClientException, MQBrokerException { SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.FLUSH_DISK_TIMEOUT); when(rocketmqProducer.send(any(Message.class), anyLong())).thenReturn(sendResult); try { producer.send(producer.createBytesMessage("HELLO_TOPIC", new byte[] {'a'})); failBecauseExceptionWasNotThrown(OMSRuntimeException.class); } catch (Exception e) { assertThat(e).hasMessageContaining("Send message to RocketMQ broker failed."); } } @Test public void testSend_WithException() throws InterruptedException, RemotingException, MQClientException, MQBrokerException { when(rocketmqProducer.send(any(Message.class), anyLong())).thenThrow(MQClientException.class); try { producer.send(producer.createBytesMessage("HELLO_TOPIC", new byte[] {'a'})); failBecauseExceptionWasNotThrown(OMSRuntimeException.class); } catch (Exception e) { assertThat(e).hasMessageContaining("Send message to RocketMQ broker failed."); } } } ================================================ FILE: openmessaging/src/test/java/io/openmessaging/rocketmq/promise/DefaultPromiseTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.promise; import io.openmessaging.Future; import io.openmessaging.FutureListener; import io.openmessaging.Promise; import io.openmessaging.exception.OMSRuntimeException; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; public class DefaultPromiseTest { private Promise promise; @Before public void init() { promise = new DefaultPromise<>(); } @Test public void testIsCancelled() throws Exception { assertThat(promise.isCancelled()).isEqualTo(false); } @Test public void testIsDone() throws Exception { assertThat(promise.isDone()).isEqualTo(false); promise.set("Done"); assertThat(promise.isDone()).isEqualTo(true); } @Test public void testGet() throws Exception { promise.set("Done"); assertThat(promise.get()).isEqualTo("Done"); } @Test public void testGet_WithTimeout() throws Exception { try { promise.get(100); failBecauseExceptionWasNotThrown(OMSRuntimeException.class); } catch (OMSRuntimeException e) { assertThat(e).hasMessageContaining("Get request result is timeout or interrupted"); } } @Test public void testAddListener() throws Exception { promise.addListener(new FutureListener() { @Override public void operationComplete(Future future) { assertThat(promise.get()).isEqualTo("Done"); } }); promise.set("Done"); } @Test public void testAddListener_ListenerAfterSet() throws Exception { promise.set("Done"); promise.addListener(new FutureListener() { @Override public void operationComplete(Future future) { assertThat(future.get()).isEqualTo("Done"); } }); } @Test public void testAddListener_WithException_ListenerAfterSet() throws Exception { final Throwable exception = new OMSRuntimeException("-1", "Test Error"); promise.setFailure(exception); promise.addListener(new FutureListener() { @Override public void operationComplete(Future future) { assertThat(promise.getThrowable()).isEqualTo(exception); } }); } @Test public void testAddListener_WithException() throws Exception { final Throwable exception = new OMSRuntimeException("-1", "Test Error"); promise.addListener(new FutureListener() { @Override public void operationComplete(Future future) { assertThat(promise.getThrowable()).isEqualTo(exception); } }); promise.setFailure(exception); } @Test public void getThrowable() throws Exception { assertThat(promise.getThrowable()).isNull(); Throwable exception = new OMSRuntimeException("-1", "Test Error"); promise.setFailure(exception); assertThat(promise.getThrowable()).isEqualTo(exception); } } ================================================ FILE: openmessaging/src/test/java/io/openmessaging/rocketmq/utils/BeanUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.rocketmq.utils; import io.openmessaging.KeyValue; import io.openmessaging.OMS; import io.openmessaging.rocketmq.config.ClientConfig; import io.openmessaging.rocketmq.domain.NonStandardKeys; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class BeanUtilsTest { private KeyValue properties = OMS.newKeyValue(); public static class CustomizedConfig extends ClientConfig { final static String STRING_TEST = "string.test"; String stringTest = "foobar"; final static String DOUBLE_TEST = "double.test"; double doubleTest = 123.0; final static String LONG_TEST = "long.test"; long longTest = 123L; String getStringTest() { return stringTest; } public void setStringTest(String stringTest) { this.stringTest = stringTest; } double getDoubleTest() { return doubleTest; } public void setDoubleTest(final double doubleTest) { this.doubleTest = doubleTest; } long getLongTest() { return longTest; } public void setLongTest(final long longTest) { this.longTest = longTest; } CustomizedConfig() { } } @Before public void init() { properties.put(NonStandardKeys.MAX_REDELIVERY_TIMES, 120); properties.put(CustomizedConfig.STRING_TEST, "kaka"); properties.put(NonStandardKeys.CONSUMER_GROUP, "Default_Consumer_Group"); properties.put(NonStandardKeys.MESSAGE_CONSUME_TIMEOUT, 101); properties.put(CustomizedConfig.LONG_TEST, 1234567890L); properties.put(CustomizedConfig.DOUBLE_TEST, 10.234); } @Test public void testPopulate() { CustomizedConfig config = BeanUtils.populate(properties, CustomizedConfig.class); //RemotingConfig config = BeanUtils.populate(properties, RemotingConfig.class); Assert.assertEquals(config.getRmqMaxRedeliveryTimes(), 120); Assert.assertEquals(config.getStringTest(), "kaka"); Assert.assertEquals(config.getRmqConsumerGroup(), "Default_Consumer_Group"); Assert.assertEquals(config.getRmqMessageConsumeTimeout(), 101); Assert.assertEquals(config.getLongTest(), 1234567890L); Assert.assertEquals(config.getDoubleTest(), 10.234, 0.000001); } @Test public void testPopulate_ExistObj() { CustomizedConfig config = new CustomizedConfig(); config.setConsumerId("NewConsumerId"); Assert.assertEquals(config.getConsumerId(), "NewConsumerId"); config = BeanUtils.populate(properties, config); //RemotingConfig config = BeanUtils.populate(properties, RemotingConfig.class); Assert.assertEquals(config.getRmqMaxRedeliveryTimes(), 120); Assert.assertEquals(config.getStringTest(), "kaka"); Assert.assertEquals(config.getRmqConsumerGroup(), "Default_Consumer_Group"); Assert.assertEquals(config.getRmqMessageConsumeTimeout(), 101); Assert.assertEquals(config.getLongTest(), 1234567890L); Assert.assertEquals(config.getDoubleTest(), 10.234, 0.000001); } } ================================================ FILE: openmessaging/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: pom.xml ================================================ org.apache apache 18 4.0.0 2012 org.apache.rocketmq rocketmq-all ${revision} pom Apache RocketMQ ${project.version} http://rocketmq.apache.org/ git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git HEAD Development List dev-subscribe@rocketmq.apache.org dev-unsubscribe@rocketmq.apache.org dev@rocketmq.apache.org User List users-subscribe@rocketmq.apache.org users-unsubscribe@rocketmq.apache.org users@rocketmq.apache.org Commits List commits-subscribe@rocketmq.apache.org commits-unsubscribe@rocketmq.apache.org commits@rocketmq.apache.org Apache RocketMQ Apache RocketMQ of ASF https://rocketmq.apache.org/ Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 repo Apache Software Foundation http://www.apache.org jira https://issues.apache.org/jira/browse/RocketMQ 5.4.0 UTF-8 UTF-8 ${basedir} false true 1.8 1.8 1.5.0 4.1.130.Final 2.0.53.Final 1.83 1.2.83 2.0.59 3.20.0-GA 4.2.2 3.20.0 2.14.0 32.0.1-jre 2.9.0 0.3.1-alpha 2.0 1.13 1.0.1 2.0.3 1.0.0 1.10.0 1.5.2-2 1.10.3 0.33.0 1.8.1 0.3.2 6.0.53 1.0-beta-4 1.4.2 2.1.1 1.53.0 3.20.1 1.2.10 0.9.11 2.9.3 5.3.27 3.4.0 1.44.1 1.44.1-alpha 2.0.6 2.20.29 1.0.6 2.13.4.2 1.3.14 4.13.2 3.22.0 3.10.0 4.11.0 2.0.9 4.1.0 0.30 2.11.0 5.0.5 1.7.2 2.2 1.0.2 2.7 1.4.1 3.5.1 3.0.1 2.2 3.2.0 0.12 3.0.2 4.3.0 0.8.5 2.19.1 3.0.2 4.2.2 3.4.2 2.10.4 2.19.1 3.2.4 jacoco ${project.basedir}/../test/target/jacoco-it.exec file:**/generated-sources/**,**/test/** client common broker tools store namesrv remoting srvutil filter test distribution openmessaging auth example container controller proxy tieredstore org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} true flatten process-resources flatten true oss remove remove flatten-clean clean clean org.codehaus.mojo versions-maven-plugin ${versions-maven-plugin.version} com.github.vongosling dependency-mediator-maven-plugin ${dependency-mediator-maven-plugin.version} org.codehaus.mojo clirr-maven-plugin ${clirr-maven-plugin.version} maven-enforcer-plugin ${maven-enforcer-plugin.version} enforce-ban-circular-dependencies enforce true org.codehaus.mojo extra-enforcer-rules ${extra-enforcer-rules.version} maven-compiler-plugin ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} ${maven.compiler.source} true true maven-source-plugin ${maven-source-plugin.version} attach-sources jar maven-help-plugin ${maven-help-plugin.version} generate-effective-dependencies-pom generate-resources ${project.build.directory}/effective-pom/effective-dependencies.xml maven-checkstyle-plugin ${maven-checkstyle-plugin.version} validate validate style/rmq_checkstyle.xml UTF-8 true true true **/generated*/**/* check org.apache.rat apache-rat-plugin ${apache-rat-plugin.version} .gitignore .travis.yml README.md CONTRIBUTING.md bin/README.md .github/** src/test/resources/** src/test/resources/certs/* src/test/**/*.log src/test/resources/META-INF/services/* src/main/resources/META-INF/services/* */target/** */*.iml docs/** localbin/** conf/rmq-proxy.json .bazelversion maven-resources-plugin ${maven-resources-plugin.version} ${project.build.sourceEncoding} org.eluder.coveralls coveralls-maven-plugin ${coveralls-maven-plugin.version} org.jacoco jacoco-maven-plugin ${jacoco-maven-plugin.version} default-prepare-agent prepare-agent ${project.build.directory}/jacoco.exec report test report default-prepare-agent-integration pre-integration-test prepare-agent-integration ${project.build.directory}/jacoco-it.exec failsafeArgLine default-report report default-report-integration report-integration maven-surefire-plugin ${maven-surefire-plugin.version} 1 1 true **/IT*.java org.sonarsource.scanner.maven sonar-maven-plugin ${sonar-maven-plugin.version} com.github.spotbugs spotbugs-maven-plugin ${spotbugs-plugin.version} check compile check true false true ${project.root}/style/spotbugs-suppressions.xml High Max maven-assembly-plugin ${maven-assembly-plugin.version} jdk8 [1.8,) maven-javadoc-plugin ${maven-javadoc-plugin.version} -Xdoclint:none maven-javadoc-plugin ${maven-javadoc-plugin.version} -Xdoclint:none release-sign-artifacts performRelease true maven-gpg-plugin 1.6 sign-artifacts verify sign it-test maven-failsafe-plugin ${maven-failsafe-plugin.version} @{failsafeArgLine} **/NormalMsgDelayIT.java integration-test verify sonar-apache https://builds.apache.org/analysis skip-unit-tests maven-surefire-plugin ${maven-surefire-plugin.version} true true org.apache.rocketmq rocketmq-auth ${project.version} org.apache.rocketmq rocketmq-broker ${project.version} org.apache.rocketmq rocketmq-client ${project.version} org.apache.rocketmq rocketmq-common ${project.version} org.apache.rocketmq rocketmq-container ${project.version} org.apache.rocketmq rocketmq-controller ${project.version} org.apache.rocketmq rocketmq-example ${project.version} org.apache.rocketmq rocketmq-filter ${project.version} org.apache.rocketmq rocketmq-namesrv ${project.version} org.apache.rocketmq rocketmq-openmessaging ${project.version} org.apache.rocketmq rocketmq-proxy ${project.version} org.apache.rocketmq rocketmq-remoting ${project.version} org.apache.rocketmq rocketmq-srvutil ${project.version} org.apache.rocketmq rocketmq-store ${project.version} org.apache.rocketmq rocketmq-tiered-store ${project.version} org.apache.rocketmq rocketmq-test ${project.version} org.apache.rocketmq rocketmq-tools ${project.version} ${project.groupId} rocketmq-proto ${rocketmq-proto.version} * * commons-cli commons-cli ${commons-cli.version} io.netty netty-all ${netty.version} org.bouncycastle bcpkix-jdk18on runtime jar ${bcpkix-jdk18on.version} com.alibaba fastjson ${fastjson.version} com.alibaba.fastjson2 fastjson2 ${fastjson2.version} org.javassist javassist ${javassist.version} net.java.dev.jna jna ${jna.version} org.apache.commons commons-lang3 ${commons-lang3.version} commons-io commons-io ${commons-io.version} com.google.guava guava ${guava.version} com.google.errorprone error_prone_annotations com.google.code gson ${gson.version} com.googlecode.concurrentlinkedhashmap concurrentlinkedhashmap-lru ${concurrentlinkedhashmap-lru.version} io.openmessaging openmessaging-api ${openmessaging.version} org.yaml snakeyaml ${snakeyaml.version} commons-codec commons-codec ${commons-codec.version} org.slf4j slf4j-api ${slf4j-api.version} org.apache.rocketmq rocketmq-rocksdb ${rocksdb.version} io.github.aliyunmq rocketmq-shaded-slf4j-api-bridge ${rocketmq-shaded-slf4j-api-bridge.version} io.github.aliyunmq rocketmq-slf4j-api ${rocketmq-logging.version} io.github.aliyunmq rocketmq-logback-classic ${rocketmq-logging.version} commons-validator commons-validator ${commons-validator.version} com.github.luben zstd-jni ${zstd-jni.version} at.yawk.lz4 lz4-java ${lz4-java.version} io.opentracing opentracing-api ${opentracing.version} provided io.opentracing opentracing-mock ${opentracing.version} test io.jaegertracing jaeger-core ${jaeger.version} com.google.code.gson gson io.jaegertracing jaeger-thrift ${jaeger.version} com.squareup.okhttp3 okhttp io.jaegertracing jaeger-client ${jaeger.version} org.jetbrains.kotlin kotlin-stdlib io.openmessaging.storage dledger ${dleger.version} org.slf4j slf4j-api org.apache.tomcat annotations-api ${annotations-api.version} junit junit ${junit.version} test org.assertj assertj-core ${assertj-core.version} test org.mockito mockito-core ${mockito-core.version} test org.mockito mockito-junit-jupiter ${mockito-junit-jupiter.version} test org.awaitility awaitility ${awaitility.version} com.google.truth truth ${truth.version} com.google.errorprone error_prone_annotations org.reflections reflections ${org.relection.version} io.grpc grpc-netty-shaded ${grpc.version} io.grpc grpc-protobuf ${grpc.version} com.google.protobuf protobuf-java io.grpc grpc-stub ${grpc.version} io.grpc grpc-services ${grpc.version} io.grpc grpc-testing ${grpc.version} test com.google.protobuf protobuf-java-util ${protobuf.version} com.google.errorprone error_prone_annotations com.google.code.gson gson com.google.j2objc j2objc-annotations io.github.aliyunmq rocketmq-grpc-netty-codec-haproxy 1.0.0 com.conversantmedia disruptor ${disruptor.version} org.slf4j slf4j-api com.github.ben-manes.caffeine caffeine ${caffeine.version} com.google.errorprone error_prone_annotations io.netty netty-tcnative-boringssl-static ${netty.tcnative.version} org.springframework spring-core ${spring.version} test com.squareup.okio okio-jvm ${okio-jvm.version} org.jetbrains.kotlin kotlin-stdlib org.jetbrains.kotlin kotlin-stdlib-common org.jetbrains.kotlin kotlin-stdlib-jdk8 io.opentelemetry opentelemetry-exporter-otlp ${opentelemetry.version} io.opentelemetry opentelemetry-exporter-prometheus ${opentelemetry-exporter-prometheus.version} io.opentelemetry opentelemetry-exporter-logging ${opentelemetry.version} io.opentelemetry opentelemetry-sdk ${opentelemetry.version} io.opentelemetry opentelemetry-exporter-logging-otlp ${opentelemetry.version} org.slf4j jul-to-slf4j ${jul-to-slf4j.version} software.amazon.awssdk s3 ${s3.version} com.fasterxml.jackson.core jackson-databind ${jackson-databind.version} com.alipay.sofa jraft-core ${sofa-jraft.version} org.ow2.asm asm com.google.protobuf protobuf-java org.rocksdb rocksdbjni com.adobe.testing s3mock-junit4 ${s3mock-junit4.version} test annotations software.amazon.awssdk commons-logging commons-logging http-client-spi software.amazon.awssdk json-utils software.amazon.awssdk profiles software.amazon.awssdk regions software.amazon.awssdk sdk-core software.amazon.awssdk utils software.amazon.awssdk jackson-dataformat-cbor com.fasterxml.jackson.dataformat jakarta.annotation jakarta.annotation-api 1.3.5 junit junit ${junit.version} test org.assertj assertj-core ${assertj-core.version} test org.mockito mockito-core ${mockito-core.version} test org.mockito mockito-junit-jupiter ${mockito-junit-jupiter.version} test org.awaitility awaitility ${awaitility.version} org.powermock powermock-module-junit4 ${powermock-version} test org.objenesis objenesis net.bytebuddy byte-buddy net.bytebuddy byte-buddy-agent org.powermock powermock-api-mockito2 ${powermock-version} test ================================================ FILE: proxy/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "proxy", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//auth", "//broker", "//client", "//common", "//remoting", "//srvutil", "@maven//:ch_qos_logback_logback_classic", "@maven//:ch_qos_logback_logback_core", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_github_ben_manes_caffeine_caffeine", "@maven//:com_github_luben_zstd_jni", "@maven//:com_google_code_findbugs_jsr305", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:commons_cli_commons_cli", "@maven//:commons_codec_commons_codec", "@maven//:commons_collections_commons_collections", "@maven//:commons_validator_commons_validator", "@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_context", "@maven//:io_grpc_grpc_netty_shaded", "@maven//:io_grpc_grpc_services", "@maven//:io_grpc_grpc_stub", "@maven//:io_netty_netty_all", "@maven//:io_github_aliyunmq_rocketmq_grpc_netty_codec_haproxy", "@maven//:io_openmessaging_storage_dledger", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_exporter_logging", "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:org_apache_commons_commons_lang3", "@maven//:org_apache_rocketmq_rocketmq_proto", "@maven//:org_checkerframework_checker_qual", "@maven//:org_lz4_lz4_java", "@maven//:org_slf4j_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:org_slf4j_jul_to_slf4j", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), resources = [ "src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker", "src/test/resources/rmq-proxy-home/conf/broker.conf", "src/test/resources/rmq-proxy-home/conf/logback_proxy.xml", "src/test/resources/rmq-proxy-home/conf/rmq-proxy.json", ] + glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]), visibility = ["//visibility:public"], deps = [ "//auth", ":proxy", "//:test_deps", "//broker", "//client", "//common", "//srvutil", "//remoting", "@maven//:ch_qos_logback_logback_core", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_github_ben_manes_caffeine_caffeine", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_context", "@maven//:io_grpc_grpc_netty_shaded", "@maven//:io_grpc_grpc_stub", "@maven//:io_netty_netty_all", "@maven//:io_github_aliyunmq_rocketmq_grpc_netty_codec_haproxy", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:org_apache_rocketmq_rocketmq_proto", "@maven//:org_checkerframework_checker_qual", "@maven//:org_slf4j_slf4j_api", "@maven//:org_springframework_spring_core", "@maven//:org_jetbrains_annotations", "@maven//:org_slf4j_jul_to_slf4j", "@maven//:io_netty_netty_tcnative_boringssl_static", "@maven//:commons_codec_commons_codec", ], ) GenTestRules( name = "GeneratedTestRules", exclude_tests = [ "src/test/java/org/apache/rocketmq/proxy/config/InitConfigTest", "src/test/java/org/apache/rocketmq/proxy/service/cert/TlsCertificateManagerTest", ], test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: proxy/README.md ================================================ rocketmq-proxy -------- ## Introduction `RocketMQ Proxy` is a stateless component that makes full use of the newly introduced `pop` consumption mechanism to achieve stateless consumption behavior. `gRPC` protocol is supported by `Proxy` now and all the message types including `normal`, `fifo`, `transaction` and `delay` are supported via `pop` consumption mode. `Proxy` will translate incoming traffic into customized `Remoting` protocol to access `Broker` and `Namesrv`. `Proxy` also handles SSL, authorization/authentication and logging/tracing/metrics and is in charge of connection management and traffic governance. ### Multi-language support. `gRPC` combined with `Protocol Buffer` makes it easy to implement clients with both `java` and other programming languages while the server side doesn't need extra work to support different programming languages. See [rocketmq-clients](https://github.com/apache/rocketmq-clients) for more information. ### Multi-protocol support. With `Proxy` served as a traffic interface, it's convenient to implement multiple protocols upon proxy. `gRPC` protocol is implemented first and the customized `Remoting` protocol will be implemented later. HTTP/1.1 will also be taken into consideration. ## Architecture `RocketMQ Proxy` has two deployment modes: `Cluster` mode and `Local` mode. With both modes, `Pop` mode is natively supported in `Proxy`. ### `Cluster` mode While in `Cluster` mode, `Proxy` is an independent cluster that communicates with `Broker` with remote procedure call. In this scenario, `Proxy` acts as a stateless computing component while `Broker` is a stateful component with local storage. This form of deployment introduces the architecture of separation of computing and storage for RocketMQ. Due to the separation of computing and storage, `RocketMQ Proxy` can be scaled out indefinitely in `Cluster` mode to handle traffic peak while `Broker` can focus on storage engine and high availability. ![](../docs/en/images/rocketmq_proxy_cluster_mode.png) ### `Local` mode `Proxy` in `Local` mode has more similarity with `RocketMQ` 4.x version, which is easily deployed or upgraded for current RocketMQ users. With `Local` mode, `Proxy` deployed with `Broker` in the same process with inter-process communication so the network overhead is reduced compared to `Cluster` mode. ![](../docs/en/images/rocketmq_proxy_local_mode.png) ## Deploy guide See [Proxy Deployment](../docs/en/proxy/deploy_guide.md) ## Related * [rocketmq-apis](https://github.com/apache/rocketmq-apis): Common communication protocol between server and client. * [rocketmq-clients](https://github.com/apache/rocketmq-clients): Collection of Polyglot Clients for Apache RocketMQ. * [RIP-37: New and Unified APIs](https://shimo.im/docs/m5kv92OeRRU8olqX): RocketMQ proposal of new and unified APIs crossing different languages. * [RIP-39: Support gRPC protocol](https://shimo.im/docs/gXqmeEPYgdUw5bqo): RocketMQ proposal of gRPC protocol support. ================================================ FILE: proxy/pom.xml ================================================ rocketmq-all org.apache.rocketmq ${revision} 4.0.0 jar rocketmq-proxy rocketmq-proxy ${project.version} 8 8 ${basedir}/.. org.apache.rocketmq rocketmq-proto org.apache.rocketmq rocketmq-broker org.apache.rocketmq rocketmq-common org.apache.rocketmq rocketmq-client org.apache.rocketmq rocketmq-auth io.grpc grpc-netty-shaded io.grpc grpc-protobuf io.grpc grpc-stub io.grpc grpc-services com.google.protobuf protobuf-java-util io.github.aliyunmq rocketmq-grpc-netty-codec-haproxy org.apache.commons commons-lang3 io.github.aliyunmq rocketmq-slf4j-api io.github.aliyunmq rocketmq-logback-classic com.github.ben-manes.caffeine caffeine org.checkerframework checker-qual io.netty netty-tcnative-boringssl-static org.springframework spring-core test org.slf4j jul-to-slf4j ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/CommandLineArgument.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy; public class CommandLineArgument { private String namesrvAddr; private String brokerConfigPath; private String proxyConfigPath; private String proxyMode; public String getNamesrvAddr() { return namesrvAddr; } public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; } public String getBrokerConfigPath() { return brokerConfigPath; } public void setBrokerConfigPath(String brokerConfigPath) { this.brokerConfigPath = brokerConfigPath; } public String getProxyConfigPath() { return proxyConfigPath; } public void setProxyConfigPath(String proxyConfigPath) { this.proxyConfigPath = proxyConfigPath; } public String getProxyMode() { return proxyMode; } public void setProxyMode(String proxyMode) { this.proxyMode = proxyMode; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy; public enum ProxyMode { LOCAL("LOCAL"), CLUSTER("CLUSTER"); private final String mode; ProxyMode(String mode) { this.mode = mode; } public static boolean isClusterMode(String mode) { if (mode == null) { return false; } return CLUSTER.mode.equals(mode.toUpperCase()); } public static boolean isClusterMode(ProxyMode mode) { if (mode == null) { return false; } return CLUSTER.equals(mode); } public static boolean isLocalMode(String mode) { if (mode == null) { return false; } return LOCAL.mode.equals(mode.toUpperCase()); } public static boolean isLocalMode(ProxyMode mode) { if (mode == null) { return false; } return LOCAL.equals(mode); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy; import com.google.common.collect.Lists; import io.grpc.protobuf.services.ChannelzService; import io.grpc.protobuf.services.ProtoReflectionService; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerStartup; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.Configuration; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.GrpcServer; import org.apache.rocketmq.proxy.grpc.GrpcServerBuilder; import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingApplication; import org.apache.rocketmq.proxy.metrics.ProxyMetricsManager; import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.RemotingProtocolServer; import org.apache.rocketmq.proxy.service.cert.TlsCertificateManager; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.srvutil.ServerUtil; import java.util.Date; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ProxyStartup { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final ProxyStartAndShutdown PROXY_START_AND_SHUTDOWN = new ProxyStartAndShutdown(); private static class ProxyStartAndShutdown extends AbstractStartAndShutdown { @Override public void appendStartAndShutdown(StartAndShutdown startAndShutdown) { super.appendStartAndShutdown(startAndShutdown); } } public static void main(String[] args) { try { // parse argument from command line CommandLineArgument commandLineArgument = parseCommandLineArgument(args); initConfiguration(commandLineArgument); // init thread pool monitor for proxy. initThreadPoolMonitor(); ThreadPoolExecutor executor = createServerExecutor(); MessagingProcessor messagingProcessor = createMessagingProcessor(); // tls cert update TlsCertificateManager tlsCertificateManager = new TlsCertificateManager(); PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(tlsCertificateManager); // create grpcServer GrpcServer grpcServer = GrpcServerBuilder.newBuilder(executor, ConfigurationManager.getProxyConfig().getGrpcServerPort(), tlsCertificateManager) .addService(createServiceProcessor(messagingProcessor)) .addService(ChannelzService.newInstance(100)) .addService(ProtoReflectionService.newInstance()) .configInterceptor() .shutdownTime(ConfigurationManager.getProxyConfig().getGrpcShutdownTimeSeconds(), TimeUnit.SECONDS) .build(); PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(grpcServer); RemotingProtocolServer remotingServer = new RemotingProtocolServer(messagingProcessor, tlsCertificateManager); PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(remotingServer); // start servers one by one. PROXY_START_AND_SHUTDOWN.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { log.info("try to shutdown server"); try { PROXY_START_AND_SHUTDOWN.preShutdown(); PROXY_START_AND_SHUTDOWN.shutdown(); } catch (Exception e) { log.error("err when shutdown rocketmq-proxy", e); } })); } catch (Exception e) { e.printStackTrace(); log.error("find an unexpect err.", e); System.exit(1); } System.out.printf("%s%n", new Date() + " rocketmq-proxy startup successfully"); log.info(new Date() + " rocketmq-proxy startup successfully"); } protected static void initConfiguration(CommandLineArgument commandLineArgument) throws Exception { if (StringUtils.isNotBlank(commandLineArgument.getProxyConfigPath())) { System.setProperty(Configuration.CONFIG_PATH_PROPERTY, commandLineArgument.getProxyConfigPath()); } ConfigurationManager.initEnv(); ConfigurationManager.initConfig(); setConfigFromCommandLineArgument(commandLineArgument); log.info("Current configuration: " + ConfigurationManager.formatProxyConfig()); } protected static CommandLineArgument parseCommandLineArgument(String[] args) { CommandLine commandLine = ServerUtil.parseCmdLine("mqproxy", args, buildCommandlineOptions(), new DefaultParser()); if (commandLine == null) { throw new RuntimeException("parse command line argument failed"); } CommandLineArgument commandLineArgument = new CommandLineArgument(); MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), commandLineArgument); return commandLineArgument; } private static Options buildCommandlineOptions() { Options options = ServerUtil.buildCommandlineOptions(new Options()); Option opt = new Option("bc", "brokerConfigPath", true, "Broker config file path for local mode"); opt.setRequired(false); options.addOption(opt); opt = new Option("pc", "proxyConfigPath", true, "Proxy config file path"); opt.setRequired(false); options.addOption(opt); opt = new Option("pm", "proxyMode", true, "Proxy run in local or cluster mode"); opt.setRequired(false); options.addOption(opt); return options; } private static void setConfigFromCommandLineArgument(CommandLineArgument commandLineArgument) { if (StringUtils.isNotBlank(commandLineArgument.getNamesrvAddr())) { ConfigurationManager.getProxyConfig().setNamesrvAddr(commandLineArgument.getNamesrvAddr()); } if (StringUtils.isNotBlank(commandLineArgument.getBrokerConfigPath())) { ConfigurationManager.getProxyConfig().setBrokerConfigPath(commandLineArgument.getBrokerConfigPath()); } if (StringUtils.isNotBlank(commandLineArgument.getProxyMode())) { ConfigurationManager.getProxyConfig().setProxyMode(commandLineArgument.getProxyMode()); } } protected static MessagingProcessor createMessagingProcessor() { String proxyModeStr = ConfigurationManager.getProxyConfig().getProxyMode(); MessagingProcessor messagingProcessor; if (ProxyMode.isClusterMode(proxyModeStr)) { messagingProcessor = DefaultMessagingProcessor.createForClusterMode(); ProxyMetricsManager proxyMetricsManager = ProxyMetricsManager.initClusterMode(ConfigurationManager.getProxyConfig()); PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(proxyMetricsManager); } else if (ProxyMode.isLocalMode(proxyModeStr)) { BrokerController brokerController = createBrokerController(); ProxyMetricsManager.initLocalMode(brokerController.getBrokerMetricsManager(), ConfigurationManager.getProxyConfig()); StartAndShutdown brokerControllerWrapper = new StartAndShutdown() { @Override public void start() throws Exception { brokerController.start(); String tip = "The broker[" + brokerController.getBrokerConfig().getBrokerName() + ", " + brokerController.getBrokerAddr() + "] boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer(); if (null != brokerController.getBrokerConfig().getNamesrvAddr()) { tip += " and name server is " + brokerController.getBrokerConfig().getNamesrvAddr(); } log.info(tip); } @Override public void shutdown() throws Exception { brokerController.shutdown(); } }; PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(brokerControllerWrapper); messagingProcessor = DefaultMessagingProcessor.createForLocalMode(brokerController); } else { throw new IllegalArgumentException("try to start grpc server with wrong mode, use 'local' or 'cluster'"); } PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(messagingProcessor); return messagingProcessor; } private static GrpcMessagingApplication createServiceProcessor(MessagingProcessor messagingProcessor) { GrpcMessagingApplication application = GrpcMessagingApplication.create(messagingProcessor); PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(application); return application; } protected static BrokerController createBrokerController() { ProxyConfig config = ConfigurationManager.getProxyConfig(); List brokerStartupArgList = Lists.newArrayList("-c", config.getBrokerConfigPath()); if (StringUtils.isNotBlank(config.getNamesrvAddr())) { brokerStartupArgList.add("-n"); brokerStartupArgList.add(config.getNamesrvAddr()); } String[] brokerStartupArgs = brokerStartupArgList.toArray(new String[0]); return BrokerStartup.createBrokerController(brokerStartupArgs); } public static ThreadPoolExecutor createServerExecutor() { ProxyConfig config = ConfigurationManager.getProxyConfig(); int threadPoolNums = config.getGrpcThreadPoolNums(); int threadPoolQueueCapacity = config.getGrpcThreadPoolQueueCapacity(); ThreadPoolExecutor executor = ThreadPoolMonitor.createAndMonitor( threadPoolNums, threadPoolNums, 1, TimeUnit.MINUTES, "GrpcRequestExecutorThread", threadPoolQueueCapacity ); PROXY_START_AND_SHUTDOWN.appendShutdown(executor::shutdown); return executor; } public static void initThreadPoolMonitor() { ProxyConfig config = ConfigurationManager.getProxyConfig(); ThreadPoolMonitor.config( LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME), LoggerFactory.getLogger(LoggerName.PROXY_WATER_MARK_LOGGER_NAME), config.isEnablePrintJstack(), config.getPrintJstackInMillis(), config.getPrintThreadPoolStatusInMillis()); ThreadPoolMonitor.init(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/auth/ProxyAuthenticationMetadataProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.auth; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.proxy.service.metadata.MetadataService; public class ProxyAuthenticationMetadataProvider implements AuthenticationMetadataProvider { protected AuthConfig authConfig; protected MetadataService metadataService; @Override public void initialize(AuthConfig authConfig, Supplier metadataService) { this.authConfig = authConfig; if (metadataService != null) { this.metadataService = (MetadataService) metadataService.get(); } } @Override public void shutdown() { } @Override public CompletableFuture createUser(User user) { return null; } @Override public CompletableFuture deleteUser(String username) { return null; } @Override public CompletableFuture updateUser(User user) { return null; } @Override public CompletableFuture getUser(String username) { return this.metadataService.getUser(null, username); } @Override public CompletableFuture> listUser(String filter) { return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/auth/ProxyAuthorizationMetadataProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.auth; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.proxy.service.metadata.MetadataService; public class ProxyAuthorizationMetadataProvider implements AuthorizationMetadataProvider { protected AuthConfig authConfig; protected MetadataService metadataService; @Override public void initialize(AuthConfig authConfig, Supplier metadataService) { this.authConfig = authConfig; if (metadataService != null) { this.metadataService = (MetadataService) metadataService.get(); } } @Override public void shutdown() { } @Override public CompletableFuture createAcl(Acl acl) { return null; } @Override public CompletableFuture deleteAcl(Subject subject) { return null; } @Override public CompletableFuture updateAcl(Acl acl) { return null; } @Override public CompletableFuture getAcl(Subject subject) { return this.metadataService.getAcl(null, subject); } @Override public CompletableFuture> listAcl(String subjectFilter, String resourceFilter) { return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.cache.CacheLoader; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; import java.util.concurrent.ThreadPoolExecutor; import javax.annotation.Nonnull; public abstract class AbstractCacheLoader extends CacheLoader { private final ThreadPoolExecutor cacheRefreshExecutor; public AbstractCacheLoader(ThreadPoolExecutor cacheRefreshExecutor) { this.cacheRefreshExecutor = cacheRefreshExecutor; } @Override public ListenableFuture reload(@Nonnull K key, @Nonnull V oldValue) throws Exception { ListenableFutureTask task = ListenableFutureTask.create(() -> { try { return getDirectly(key); } catch (Exception e) { onErr(key, e); return oldValue; } }); cacheRefreshExecutor.execute(task); return task; } @Override public V load(@Nonnull K key) throws Exception { return getDirectly(key); } protected abstract V getDirectly(K key) throws Exception; protected abstract void onErr(K key, Exception e); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.net.HostAndPort; import java.util.Objects; import org.apache.rocketmq.common.utils.IPAddressUtils; public class Address { public enum AddressScheme { IPv4, IPv6, DOMAIN_NAME, UNRECOGNIZED } private AddressScheme addressScheme; private HostAndPort hostAndPort; public Address(HostAndPort hostAndPort) { this.addressScheme = buildScheme(hostAndPort); this.hostAndPort = hostAndPort; } public Address(AddressScheme addressScheme, HostAndPort hostAndPort) { this.addressScheme = addressScheme; this.hostAndPort = hostAndPort; } public AddressScheme getAddressScheme() { return addressScheme; } public void setAddressScheme(AddressScheme addressScheme) { this.addressScheme = addressScheme; } public HostAndPort getHostAndPort() { return hostAndPort; } public void setHostAndPort(HostAndPort hostAndPort) { this.hostAndPort = hostAndPort; } private AddressScheme buildScheme(HostAndPort hostAndPort) { if (hostAndPort == null) { return AddressScheme.UNRECOGNIZED; } String address = hostAndPort.getHost(); if (IPAddressUtils.isValidIPv4(address)) { return AddressScheme.IPv4; } if (IPAddressUtils.isValidIPv6(address)) { return AddressScheme.IPv6; } return AddressScheme.DOMAIN_NAME; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Address address = (Address) o; return addressScheme == address.addressScheme && Objects.equals(hostAndPort, address.hostAndPort); } @Override public int hashCode() { return Objects.hash(addressScheme, hostAndPort); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; public class ContextVariable { public static final String REMOTE_ADDRESS = "remote-address"; public static final String LOCAL_ADDRESS = "local-address"; public static final String CLIENT_ID = "client-id"; public static final String CHANNEL = "channel"; public static final String LANGUAGE = "language"; public static final String CLIENT_VERSION = "client-version"; public static final String REMAINING_MS = "remaining-ms"; public static final String ACTION = "action"; public static final String PROTOCOL_TYPE = "protocol-type"; public static final String NAMESPACE = "namespace"; } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.consumer.ReceiptHandle; public class MessageReceiptHandle { private final String group; private final String topic; private final int queueId; private final String messageId; private final long queueOffset; private final String originalReceiptHandleStr; private final ReceiptHandle originalReceiptHandle; private final int reconsumeTimes; private final AtomicInteger renewRetryTimes = new AtomicInteger(0); private final AtomicInteger renewTimes = new AtomicInteger(0); private final long consumeTimestamp; private String liteTopic; private volatile String receiptHandleStr; public MessageReceiptHandle(String group, String topic, int queueId, String receiptHandleStr, String messageId, long queueOffset, int reconsumeTimes) { this(group, topic, queueId, receiptHandleStr, messageId, queueOffset, reconsumeTimes, null); } public MessageReceiptHandle(String group, String topic, int queueId, String receiptHandleStr, String messageId, long queueOffset, int reconsumeTimes, String liteTopic) { this.originalReceiptHandle = ReceiptHandle.decode(receiptHandleStr); this.group = group; this.topic = topic; this.queueId = queueId; this.receiptHandleStr = receiptHandleStr; this.originalReceiptHandleStr = receiptHandleStr; this.messageId = messageId; this.queueOffset = queueOffset; this.reconsumeTimes = reconsumeTimes; this.consumeTimestamp = originalReceiptHandle.getRetrieveTime(); this.liteTopic = liteTopic; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MessageReceiptHandle handle = (MessageReceiptHandle) o; return queueId == handle.queueId && queueOffset == handle.queueOffset && consumeTimestamp == handle.consumeTimestamp && reconsumeTimes == handle.reconsumeTimes && Objects.equal(group, handle.group) && Objects.equal(topic, handle.topic) && Objects.equal(messageId, handle.messageId) && Objects.equal(originalReceiptHandleStr, handle.originalReceiptHandleStr) && Objects.equal(receiptHandleStr, handle.receiptHandleStr); } @Override public int hashCode() { return Objects.hashCode(group, topic, queueId, messageId, queueOffset, originalReceiptHandleStr, consumeTimestamp, reconsumeTimes, receiptHandleStr); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("group", group) .add("topic", topic) .add("queueId", queueId) .add("messageId", messageId) .add("queueOffset", queueOffset) .add("originalReceiptHandleStr", originalReceiptHandleStr) .add("reconsumeTimes", reconsumeTimes) .add("renewRetryTimes", renewRetryTimes) .add("firstConsumeTimestamp", consumeTimestamp) .add("receiptHandleStr", receiptHandleStr) .add("liteTopic", liteTopic) .omitNullValues() .toString(); } public String getGroup() { return group; } public String getTopic() { return topic; } public int getQueueId() { return queueId; } public String getReceiptHandleStr() { return receiptHandleStr; } public String getOriginalReceiptHandleStr() { return originalReceiptHandleStr; } public String getMessageId() { return messageId; } public long getQueueOffset() { return queueOffset; } public int getReconsumeTimes() { return reconsumeTimes; } public long getConsumeTimestamp() { return consumeTimestamp; } public void updateReceiptHandle(String receiptHandleStr) { this.receiptHandleStr = receiptHandleStr; } public int incrementAndGetRenewRetryTimes() { return this.renewRetryTimes.incrementAndGet(); } public int incrementRenewTimes() { return this.renewTimes.incrementAndGet(); } public int getRenewTimes() { return this.renewTimes.get(); } public void resetRenewRetryTimes() { this.renewRetryTimes.set(0); } public int getRenewRetryTimes() { return this.renewRetryTimes.get(); } public ReceiptHandle getOriginalReceiptHandle() { return originalReceiptHandle; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import io.netty.channel.Channel; import java.util.HashMap; import java.util.Map; public class ProxyContext { public static final String INNER_ACTION_PREFIX = "Inner"; private final Map value = new HashMap<>(); public static ProxyContext create() { return new ProxyContext(); } public static ProxyContext createForInner(String actionName) { return create().setAction(INNER_ACTION_PREFIX + actionName); } public static ProxyContext createForInner(Class clazz) { return createForInner(clazz.getSimpleName()); } public Map getValue() { return this.value; } public ProxyContext withVal(String key, Object val) { this.value.put(key, val); return this; } public T getVal(String key) { return (T) this.value.get(key); } public ProxyContext setLocalAddress(String localAddress) { this.withVal(ContextVariable.LOCAL_ADDRESS, localAddress); return this; } public String getLocalAddress() { return this.getVal(ContextVariable.LOCAL_ADDRESS); } public ProxyContext setRemoteAddress(String remoteAddress) { this.withVal(ContextVariable.REMOTE_ADDRESS, remoteAddress); return this; } public String getRemoteAddress() { return this.getVal(ContextVariable.REMOTE_ADDRESS); } public ProxyContext setClientID(String clientID) { this.withVal(ContextVariable.CLIENT_ID, clientID); return this; } public String getClientID() { return this.getVal(ContextVariable.CLIENT_ID); } public ProxyContext setChannel(Channel channel) { this.withVal(ContextVariable.CHANNEL, channel); return this; } public Channel getChannel() { return this.getVal(ContextVariable.CHANNEL); } public ProxyContext setLanguage(String language) { this.withVal(ContextVariable.LANGUAGE, language); return this; } public String getLanguage() { return this.getVal(ContextVariable.LANGUAGE); } public ProxyContext setClientVersion(String clientVersion) { this.withVal(ContextVariable.CLIENT_VERSION, clientVersion); return this; } public String getClientVersion() { return this.getVal(ContextVariable.CLIENT_VERSION); } public ProxyContext setRemainingMs(Long remainingMs) { this.withVal(ContextVariable.REMAINING_MS, remainingMs); return this; } public Long getRemainingMs() { return this.getVal(ContextVariable.REMAINING_MS); } public ProxyContext setAction(String action) { this.withVal(ContextVariable.ACTION, action); return this; } public String getAction() { return this.getVal(ContextVariable.ACTION); } public ProxyContext setProtocolType(String protocol) { this.withVal(ContextVariable.PROTOCOL_TYPE, protocol); return this; } public String getProtocolType() { return this.getVal(ContextVariable.PROTOCOL_TYPE); } public ProxyContext setNamespace(String namespace) { this.withVal(ContextVariable.NAMESPACE, namespace); return this; } public String getNamespace() { return this.getVal(ContextVariable.NAMESPACE); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; public class ProxyException extends RuntimeException { private final ProxyExceptionCode code; public ProxyException(ProxyExceptionCode code, String message) { super(message); this.code = code; } public ProxyException(ProxyExceptionCode code, String message, Throwable cause) { super(message, cause); this.code = code; } public ProxyExceptionCode getCode() { return code; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; public enum ProxyExceptionCode { INVALID_BROKER_NAME, TRANSACTION_DATA_NOT_FOUND, FORBIDDEN, MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, INVALID_RECEIPT_HANDLE, INTERNAL_SERVER_ERROR, } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; public class ReceiptHandleGroup { protected final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); // The messages having the same messageId will be deduplicated based on the parameters of broker, queueId, and offset protected final Map> receiptHandleMap = new ConcurrentHashMap<>(); public static class HandleKey { private final String originalHandle; private final String broker; private final int queueId; private final long offset; public HandleKey(String handle) { this(ReceiptHandle.decode(handle)); } public HandleKey(ReceiptHandle receiptHandle) { this.originalHandle = receiptHandle.getReceiptHandle(); this.broker = receiptHandle.getBrokerName(); this.queueId = receiptHandle.getQueueId(); this.offset = receiptHandle.getOffset(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HandleKey key = (HandleKey) o; return queueId == key.queueId && offset == key.offset && Objects.equal(broker, key.broker); } @Override public int hashCode() { return Objects.hashCode(broker, queueId, offset); } @Override public String toString() { return new ToStringBuilder(this) .append("originalHandle", originalHandle) .append("broker", broker) .append("queueId", queueId) .append("offset", offset) .toString(); } public String getOriginalHandle() { return originalHandle; } public String getBroker() { return broker; } public int getQueueId() { return queueId; } public long getOffset() { return offset; } } public static class HandleData { private final Semaphore semaphore = new Semaphore(1); private final AtomicLong lastLockTimeMs = new AtomicLong(-1L); private volatile boolean needRemove = false; private volatile MessageReceiptHandle messageReceiptHandle; public HandleData(MessageReceiptHandle messageReceiptHandle) { this.messageReceiptHandle = messageReceiptHandle; } public Long lock(long timeoutMs) { try { boolean result = this.semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); long currentTimeMs = System.currentTimeMillis(); if (result) { this.lastLockTimeMs.set(currentTimeMs); return currentTimeMs; } else { // if the lock is expired, can be acquired again long expiredTimeMs = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup() * 3; if (currentTimeMs - this.lastLockTimeMs.get() > expiredTimeMs) { synchronized (this) { if (currentTimeMs - this.lastLockTimeMs.get() > expiredTimeMs) { log.warn("HandleData lock expired, acquire lock success and reset lock time. " + "MessageReceiptHandle={}, lockTime={}", messageReceiptHandle, currentTimeMs); this.lastLockTimeMs.set(currentTimeMs); return currentTimeMs; } } } } return null; } catch (InterruptedException e) { return null; } } public void unlock(long lockTimeMs) { // if the lock is expired, we don't need to unlock it if (System.currentTimeMillis() - lockTimeMs > ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup() * 2) { log.warn("HandleData lock expired, unlock fail. MessageReceiptHandle={}, lockTime={}, now={}", messageReceiptHandle, lockTimeMs, System.currentTimeMillis()); return; } this.semaphore.release(); } public MessageReceiptHandle getMessageReceiptHandle() { return messageReceiptHandle; } @Override public boolean equals(Object o) { return this == o; } @Override public int hashCode() { return Objects.hashCode(semaphore, needRemove, messageReceiptHandle); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("semaphore", semaphore) .add("needRemove", needRemove) .add("messageReceiptHandle", messageReceiptHandle) .toString(); } } public void put(String msgID, MessageReceiptHandle value) { long timeout = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup(); Map handleMap = ConcurrentHashMapUtils.computeIfAbsent((ConcurrentHashMap>) this.receiptHandleMap, msgID, msgIDKey -> new ConcurrentHashMap<>()); handleMap.compute(new HandleKey(value.getOriginalReceiptHandle()), (handleKey, handleData) -> { if (handleData == null || handleData.needRemove) { return new HandleData(value); } Long lockTimeMs = handleData.lock(timeout); if (lockTimeMs == null) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "try to put handle failed"); } try { if (handleData.needRemove) { return new HandleData(value); } handleData.messageReceiptHandle = value; } finally { handleData.unlock(lockTimeMs); } return handleData; }); } public boolean isEmpty() { return this.receiptHandleMap.isEmpty(); } public long getHandleNum() { long handleNum = 0L; for (Map.Entry> entry : receiptHandleMap.entrySet()) { handleNum += entry.getValue().size(); } return handleNum; } public int getMsgCount() { return this.receiptHandleMap.size(); } public MessageReceiptHandle get(String msgID, String handle) { Map handleMap = this.receiptHandleMap.get(msgID); if (handleMap == null) { return null; } long timeout = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup(); AtomicReference res = new AtomicReference<>(); handleMap.computeIfPresent(new HandleKey(handle), (handleKey, handleData) -> { Long lockTimeMs = handleData.lock(timeout); if (lockTimeMs == null) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "try to get handle failed"); } try { if (handleData.needRemove) { return null; } res.set(handleData.messageReceiptHandle); } finally { handleData.unlock(lockTimeMs); } return handleData; }); return res.get(); } public MessageReceiptHandle remove(String msgID, String handle) { Map handleMap = this.receiptHandleMap.get(msgID); if (handleMap == null) { return null; } long timeout = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup(); AtomicReference res = new AtomicReference<>(); handleMap.computeIfPresent(new HandleKey(handle), (handleKey, handleData) -> { Long lockTimeMs = handleData.lock(timeout); if (lockTimeMs == null) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "try to remove and get handle failed"); } try { if (!handleData.needRemove) { handleData.needRemove = true; res.set(handleData.messageReceiptHandle); } return null; } finally { handleData.unlock(lockTimeMs); } }); removeHandleMapKeyIfNeed(msgID); return res.get(); } public MessageReceiptHandle removeOne(String msgID) { Map handleMap = this.receiptHandleMap.get(msgID); if (handleMap == null) { return null; } Set keys = handleMap.keySet(); for (HandleKey key : keys) { MessageReceiptHandle res = this.remove(msgID, key.originalHandle); if (res != null) { return res; } } return null; } public void computeIfPresent(String msgID, String handle, Function> function) { long timeout = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup(); computeIfPresent(msgID, handle, function, timeout); } public void computeIfPresent(String msgID, String handle, Function> function, long lockTimeout) { Map handleMap = this.receiptHandleMap.get(msgID); if (handleMap == null) { return; } handleMap.computeIfPresent(new HandleKey(handle), (handleKey, handleData) -> { Long lockTimeMs = handleData.lock(lockTimeout); if (lockTimeMs == null) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "try to compute failed"); } CompletableFuture future = function.apply(handleData.messageReceiptHandle); future.whenComplete((messageReceiptHandle, throwable) -> { try { if (throwable != null) { return; } if (messageReceiptHandle == null) { handleData.needRemove = true; } else { handleData.messageReceiptHandle = messageReceiptHandle; } } finally { handleData.unlock(lockTimeMs); } if (handleData.needRemove) { handleMap.remove(handleKey, handleData); } removeHandleMapKeyIfNeed(msgID); }); return handleData; }); } protected void removeHandleMapKeyIfNeed(String msgID) { this.receiptHandleMap.computeIfPresent(msgID, (msgIDKey, handleMap) -> { if (handleMap.isEmpty()) { return null; } return handleMap; }); } public interface DataScanner { void onData(String msgID, String handle, MessageReceiptHandle receiptHandle); } public void scan(DataScanner scanner) { this.receiptHandleMap.forEach((msgID, handleMap) -> { handleMap.forEach((handleKey, v) -> { scanner.onData(msgID, handleKey.originalHandle, v.messageReceiptHandle); }); }); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("receiptHandleMap", receiptHandleMap) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroupKey.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import io.netty.channel.Channel; public class ReceiptHandleGroupKey { protected final Channel channel; protected final String group; public ReceiptHandleGroupKey(Channel channel, String group) { this.channel = channel; this.group = group; } protected String getChannelId() { return channel.id().asLongText(); } public String getGroup() { return group; } public Channel getChannel() { return channel; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ReceiptHandleGroupKey key = (ReceiptHandleGroupKey) o; return Objects.equal(getChannelId(), key.getChannelId()) && Objects.equal(group, key.group); } @Override public int hashCode() { return Objects.hashCode(getChannelId(), group); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("channelId", getChannelId()) .add("group", group) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/RenewEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.consumer.AckResult; public class RenewEvent { protected ReceiptHandleGroupKey key; protected MessageReceiptHandle messageReceiptHandle; protected long renewTime; protected EventType eventType; protected CompletableFuture future; public enum EventType { RENEW, STOP_RENEW, CLEAR_GROUP } public RenewEvent(ReceiptHandleGroupKey key, MessageReceiptHandle messageReceiptHandle, long renewTime, EventType eventType, CompletableFuture future) { this.key = key; this.messageReceiptHandle = messageReceiptHandle; this.renewTime = renewTime; this.eventType = eventType; this.future = future; } public ReceiptHandleGroupKey getKey() { return key; } public MessageReceiptHandle getMessageReceiptHandle() { return messageReceiptHandle; } public long getRenewTime() { return renewTime; } public EventType getEventType() { return eventType; } public CompletableFuture getFuture() { return future; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.protocol.subscription.RetryPolicy; import java.util.concurrent.TimeUnit; public class RenewStrategyPolicy implements RetryPolicy { // 1m 3m 5m 6m 10m 30m 1h private long[] next = new long[]{ TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(3), TimeUnit.MINUTES.toMillis(5), TimeUnit.MINUTES.toMillis(10), TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1) }; public RenewStrategyPolicy() { } public RenewStrategyPolicy(long[] next) { this.next = next; } public long[] getNext() { return next; } public void setNext(long[] next) { this.next = next; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("next", next) .toString(); } @Override public long nextDelayDuration(int renewTimes) { if (renewTimes < 0) { renewTimes = 0; } int index = renewTimes; if (index >= next.length) { index = next.length - 1; } return next[index]; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/channel/ChannelHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common.channel; import io.netty.channel.Channel; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; import org.apache.rocketmq.proxy.processor.channel.RemoteChannel; import org.apache.rocketmq.proxy.remoting.channel.RemotingChannel; public class ChannelHelper { /** * judge channel is sync from other proxy or not * * @param channel channel * @return true if is sync from other proxy */ public static boolean isRemote(Channel channel) { return channel instanceof RemoteChannel; } public static ChannelProtocolType getChannelProtocolType(Channel channel) { if (channel instanceof GrpcClientChannel) { return ChannelProtocolType.GRPC_V2; } else if (channel instanceof RemotingChannel) { return ChannelProtocolType.REMOTING; } else if (channel instanceof RemoteChannel) { RemoteChannel remoteChannel = (RemoteChannel) channel; return remoteChannel.getType(); } return ChannelProtocolType.UNKNOWN; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common.utils; import java.util.Set; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class FilterUtils { /** * Whether the message's tag matches consumerGroup's SubscriptionData * * @param tagsSet, tagSet in {@link SubscriptionData}, tagSet empty means SubscriptionData.SUB_ALL(*) * @param tags, message's tags, null means not tag attached to the message. */ public static boolean isTagMatched(Set tagsSet, String tags) { if (tagsSet.isEmpty()) { return true; } if (tags == null) { return false; } return tagsSet.contains(tags); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/GrpcUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common.utils; import io.grpc.Attributes; import io.grpc.Metadata; import io.grpc.ServerCall; public class GrpcUtils { private GrpcUtils() { } public static void putHeaderIfNotExist(Metadata headers, Metadata.Key key, T value) { if (headers == null) { return; } if (!headers.containsKey(key) && value != null) { headers.put(key, value); } } public static T getAttribute(ServerCall call, Attributes.Key key) { Attributes attributes = call.getAttributes(); if (attributes == null) { return null; } return attributes.get(key); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common.utils; public class ProxyUtils { public static final int MAX_MSG_NUMS_FOR_POP_REQUEST = 32; public static final String BROKER_ADDR = "brokerAddr"; } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; public interface ConfigFile { void initData(); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import com.alibaba.fastjson2.JSON; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class Configuration { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final AtomicReference proxyConfigReference = new AtomicReference<>(); private final AtomicReference authConfigReference = new AtomicReference<>(); public static final String CONFIG_PATH_PROPERTY = "com.rocketmq.proxy.configPath"; public void init() throws Exception { String proxyConfigData = loadJsonConfig(); ProxyConfig proxyConfig = JSON.parseObject(proxyConfigData, ProxyConfig.class); proxyConfig.initData(); setProxyConfig(proxyConfig); AuthConfig authConfig = JSON.parseObject(proxyConfigData, AuthConfig.class); setAuthConfig(authConfig); authConfig.setConfigName(proxyConfig.getProxyName()); authConfig.setClusterName(proxyConfig.getRocketMQClusterName()); } private String loadJsonConfig() throws Exception { String configFileName = ProxyConfig.DEFAULT_CONFIG_FILE_NAME; String filePath = System.getProperty(CONFIG_PATH_PROPERTY); if (StringUtils.isBlank(filePath)) { final String testResource = "rmq-proxy-home/conf/" + configFileName; try (InputStream inputStream = Configuration.class.getClassLoader().getResourceAsStream(testResource)) { if (null != inputStream) { return CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); } } filePath = new File(ConfigurationManager.getProxyHome() + File.separator + "conf", configFileName).toString(); } File file = new File(filePath); log.info("The current configuration file path is {}", filePath); if (!file.exists()) { String msg = String.format("the config file %s not exist", filePath); log.warn(msg); throw new RuntimeException(msg); } long fileLength = file.length(); if (fileLength <= 0) { String msg = String.format("the config file %s length is zero", filePath); log.warn(msg); throw new RuntimeException(msg); } return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); } public ProxyConfig getProxyConfig() { return proxyConfigReference.get(); } public void setProxyConfig(ProxyConfig proxyConfig) { proxyConfigReference.set(proxyConfig); } public AuthConfig getAuthConfig() { return authConfigReference.get(); } public void setAuthConfig(AuthConfig authConfig) { authConfigReference.set(authConfig); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter.Feature; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.MixAll; public class ConfigurationManager { public static final String RMQ_PROXY_HOME = "RMQ_PROXY_HOME"; protected static final String DEFAULT_RMQ_PROXY_HOME = MixAll.ROCKETMQ_HOME_DIR; protected static String proxyHome; protected static Configuration configuration; public static void initEnv() { proxyHome = System.getenv(RMQ_PROXY_HOME); if (StringUtils.isEmpty(proxyHome)) { proxyHome = System.getProperty(RMQ_PROXY_HOME, DEFAULT_RMQ_PROXY_HOME); } if (proxyHome == null) { proxyHome = "./"; } } public static void initConfig() throws Exception { configuration = new Configuration(); configuration.init(); } public static String getProxyHome() { return proxyHome; } public static ProxyConfig getProxyConfig() { return configuration.getProxyConfig(); } public static AuthConfig getAuthConfig() { return configuration.getAuthConfig(); } public static String formatProxyConfig() { return JSON.toJSONString(ConfigurationManager.getProxyConfig(), Feature.PrettyFormat, Feature.WriteMapNullValue, Feature.WriteNullListAsEmpty); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; public enum MetricCollectorMode { /** * Do not collect the metric from clients. */ OFF("off"), /** * Collect the metric from clients to the given address. */ ON("on"), /** * Collect the metric by the proxy itself. */ PROXY("proxy"); private final String modeString; MetricCollectorMode(String modeString) { this.modeString = modeString; } public String getModeString() { return modeString; } public static MetricCollectorMode getEnumByString(String modeString) { for (MetricCollectorMode mode : MetricCollectorMode.values()) { if (mode.modeString.equals(modeString.toLowerCase())) { return mode; } } return OFF; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import java.net.InetAddress; import java.net.UnknownHostException; import java.time.Duration; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.metrics.MetricsExporterType; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.ProxyMode; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; public class ProxyConfig implements ConfigFile { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); public final static String DEFAULT_CONFIG_FILE_NAME = "rmq-proxy.json"; private static final int PROCESSOR_NUMBER = Runtime.getRuntime().availableProcessors(); private static final String DEFAULT_CLUSTER_NAME = "DefaultCluster"; private static String localHostName; static { try { localHostName = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { log.error("Failed to obtain the host name", e); } } private String rocketMQClusterName = DEFAULT_CLUSTER_NAME; private String proxyClusterName = DEFAULT_CLUSTER_NAME; private String proxyName = StringUtils.isEmpty(localHostName) ? "DEFAULT_PROXY" : localHostName; private String localServeAddr = ""; private String heartbeatSyncerTopicClusterName = ""; private int heartbeatSyncerThreadPoolNums = 4; private int heartbeatSyncerThreadPoolQueueCapacity = 100; private String heartbeatSyncerTopicName = "DefaultHeartBeatSyncerTopic"; /** * configuration for ThreadPoolMonitor */ private boolean enablePrintJstack = true; private long printJstackInMillis = Duration.ofSeconds(60).toMillis(); private long printThreadPoolStatusInMillis = Duration.ofSeconds(3).toMillis(); private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); private String namesrvDomain = ""; private String namesrvDomainSubgroup = ""; /** * TLS */ private boolean tlsTestModeEnable = true; private String tlsKeyPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.key"; private String tlsKeyPassword = ""; private String tlsCertPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.crt"; private int tlsCertWatchIntervalMs = 60 * 60 * 1000; // 1 hour /** * gRPC */ private String proxyMode = ProxyMode.CLUSTER.name(); private Integer grpcServerPort = 8081; private long grpcShutdownTimeSeconds = 30; private int grpcBossLoopNum = 1; private int grpcWorkerLoopNum = PROCESSOR_NUMBER * 2; private boolean enableGrpcEpoll = false; private int grpcThreadPoolNums = 16 + PROCESSOR_NUMBER * 2; private int grpcThreadPoolQueueCapacity = 100000; private String brokerConfigPath = ConfigurationManager.getProxyHome() + "/conf/broker.conf"; /** * gRPC max message size * 130M = 4M * 32 messages + 2M attributes */ private int grpcMaxInboundMessageSize = 130 * 1024 * 1024; /** * max message body size, 0 or negative number means no limit for proxy */ private int maxMessageSize = 4 * 1024 * 1024; /** * if true, proxy will check message body size and reject msg if it's body is empty */ private boolean enableMessageBodyEmptyCheck = true; /** * max user property size, 0 or negative number means no limit for proxy */ private int maxUserPropertySize = 16 * 1024; private int userPropertyMaxNum = 128; /** * max message group size, 0 or negative number means no limit for proxy */ private int maxMessageGroupSize = 64; /** * max lite topic size */ private int maxLiteTopicSize = 64; private int maxLiteRenewNumPerChannel = 100; // syncLiteSubscription request rate limit per proxy private int maxSyncLiteSubscriptionRate = 5000; /** * When a message pops, the message is invisible by default */ private long defaultInvisibleTimeMills = Duration.ofSeconds(60).toMillis(); private long minInvisibleTimeMillsForRecv = Duration.ofSeconds(10).toMillis(); private long maxInvisibleTimeMills = Duration.ofHours(12).toMillis(); private long maxDelayTimeMills = Duration.ofDays(1).toMillis(); private long maxTransactionRecoverySecond = Duration.ofHours(1).getSeconds(); private boolean enableTopicMessageTypeCheck = true; private int grpcClientProducerMaxAttempts = 3; private long grpcClientProducerBackoffInitialMillis = 10; private long grpcClientProducerBackoffMaxMillis = 1000; private int grpcClientProducerBackoffMultiplier = 2; private long grpcClientConsumerMinLongPollingTimeoutMillis = Duration.ofSeconds(5).toMillis(); private long grpcClientConsumerMaxLongPollingTimeoutMillis = Duration.ofSeconds(20).toMillis(); private int grpcClientConsumerLongPollingBatchSize = 32; private long grpcClientIdleTimeMills = Duration.ofSeconds(120).toMillis(); private int channelExpiredInSeconds = 60; private int contextExpiredInSeconds = 30; private int rocketmqMQClientNum = 6; private long grpcProxyRelayRequestTimeoutInSeconds = 5; private int grpcProducerThreadPoolNums = PROCESSOR_NUMBER; private int grpcProducerThreadQueueCapacity = 10000; private int grpcConsumerThreadPoolNums = PROCESSOR_NUMBER; private int grpcConsumerThreadQueueCapacity = 10000; private int grpcRouteThreadPoolNums = PROCESSOR_NUMBER; private int grpcRouteThreadQueueCapacity = 10000; private int grpcClientManagerThreadPoolNums = PROCESSOR_NUMBER; private int grpcClientManagerThreadQueueCapacity = 10000; private int grpcTransactionThreadPoolNums = PROCESSOR_NUMBER; private int grpcTransactionThreadQueueCapacity = 10000; private int producerProcessorThreadPoolNums = PROCESSOR_NUMBER; private int producerProcessorThreadPoolQueueCapacity = 10000; private int consumerProcessorThreadPoolNums = PROCESSOR_NUMBER; private int consumerProcessorThreadPoolQueueCapacity = 10000; private boolean useEndpointPortFromRequest = false; private int topicRouteServiceCacheExpiredSeconds = 300; private int topicRouteServiceCacheRefreshSeconds = 20; private int topicRouteServiceCacheMaxNum = 20000; private int topicRouteServiceThreadPoolNums = PROCESSOR_NUMBER; private int topicRouteServiceThreadPoolQueueCapacity = 5000; private int topicConfigCacheExpiredSeconds = 300; private int topicConfigCacheRefreshSeconds = 20; private int topicConfigCacheMaxNum = 20000; private int subscriptionGroupConfigCacheExpiredSeconds = 300; private int subscriptionGroupConfigCacheRefreshSeconds = 20; private int subscriptionGroupConfigCacheMaxNum = 20000; private int userCacheExpiredSeconds = 300; private int userCacheRefreshSeconds = 20; private int userCacheMaxNum = 20000; private int aclCacheExpiredSeconds = 300; private int aclCacheRefreshSeconds = 20; private int aclCacheMaxNum = 20000; private int metadataThreadPoolNums = 3; private int metadataThreadPoolQueueCapacity = 100000; private int transactionHeartbeatThreadPoolNums = 20; private int transactionHeartbeatThreadPoolQueueCapacity = 200; private int transactionHeartbeatPeriodSecond = 20; private int transactionHeartbeatBatchNum = 100; private long transactionDataExpireScanPeriodMillis = Duration.ofSeconds(10).toMillis(); private long transactionDataMaxWaitClearMillis = Duration.ofSeconds(30).toMillis(); private long transactionDataExpireMillis = Duration.ofSeconds(30).toMillis(); private int transactionDataMaxNum = 15; private long longPollingReserveTimeInMillis = 100; private long invisibleTimeMillisWhenClear = 1000L; private boolean enableProxyAutoRenew = true; private int maxRenewRetryTimes = 3; private int renewThreadPoolNums = 2; private int renewMaxThreadPoolNums = 4; private int renewThreadPoolQueueCapacity = 300; private long lockTimeoutMsInHandleGroup = TimeUnit.SECONDS.toMillis(3); private long renewAheadTimeMillis = TimeUnit.SECONDS.toMillis(10); private long renewMaxTimeMillis = TimeUnit.HOURS.toMillis(3); private long renewSchedulePeriodMillis = TimeUnit.SECONDS.toMillis(5); private int returnHandleGroupThreadPoolNums = 2; private boolean enableAclRpcHookForClusterMode = false; private boolean useDelayLevel = false; private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; private transient ConcurrentSkipListMap delayLevelTable = new ConcurrentSkipListMap<>(); private String metricCollectorMode = MetricCollectorMode.OFF.getModeString(); // Example address: 127.0.0.1:1234 private String metricCollectorAddress = ""; private String regionId = ""; private boolean traceOn = false; private MetricsExporterType metricsExporterType = MetricsExporterType.DISABLE; private String metricsGrpcExporterTarget = ""; private String metricsGrpcExporterHeader = ""; private long metricGrpcExporterTimeOutInMills = 3 * 1000; private long metricGrpcExporterIntervalInMills = 60 * 1000; private long metricLoggingExporterIntervalInMills = 10 * 1000; private int metricsPromExporterPort = 5557; private String metricsPromExporterHost = ""; // Label pairs in CSV. Each label follows pattern of Key:Value. eg: instance_id:xxx,uid:xxx private String metricsLabel = ""; private boolean metricsInDelta = false; private long channelExpiredTimeout = 1000 * 120; // remoting private boolean enableRemotingLocalProxyGrpc = true; private int localProxyConnectTimeoutMs = 3000; private String remotingAccessAddr = ""; private int remotingListenPort = 8080; // related to proxy's send strategy in cluster mode. private boolean sendLatencyEnable = false; private boolean startDetectorEnable = false; private int detectTimeout = 200; private int detectInterval = 2 * 1000; private int remotingHeartbeatThreadPoolNums = 2 * PROCESSOR_NUMBER; private int remotingTopicRouteThreadPoolNums = 2 * PROCESSOR_NUMBER; private int remotingSendMessageThreadPoolNums = 4 * PROCESSOR_NUMBER; private int remotingPullMessageThreadPoolNums = 4 * PROCESSOR_NUMBER; private int remotingUpdateOffsetThreadPoolNums = 4 * PROCESSOR_NUMBER; private int remotingDefaultThreadPoolNums = 4 * PROCESSOR_NUMBER; private int remotingHeartbeatThreadPoolQueueCapacity = 50000; private int remotingTopicRouteThreadPoolQueueCapacity = 50000; private int remotingSendThreadPoolQueueCapacity = 10000; private int remotingPullThreadPoolQueueCapacity = 50000; private int remotingUpdateOffsetThreadPoolQueueCapacity = 10000; private int remotingDefaultThreadPoolQueueCapacity = 50000; private long remotingWaitTimeMillsInSendQueue = 3 * 1000; private long remotingWaitTimeMillsInPullQueue = 5 * 1000; private long remotingWaitTimeMillsInHeartbeatQueue = 31 * 1000; private long remotingWaitTimeMillsInUpdateOffsetQueue = 3 * 1000; private long remotingWaitTimeMillsInTopicRouteQueue = 3 * 1000; private long remotingWaitTimeMillsInDefaultQueue = 3 * 1000; private boolean enableBatchAck = false; @Override public void initData() { parseDelayLevel(); if (StringUtils.isEmpty(localServeAddr)) { this.localServeAddr = NetworkUtil.getLocalAddress(); } if (StringUtils.isBlank(localServeAddr)) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "get local serve ip failed"); } if (StringUtils.isBlank(remotingAccessAddr)) { this.remotingAccessAddr = this.localServeAddr; } if (StringUtils.isBlank(heartbeatSyncerTopicClusterName)) { this.heartbeatSyncerTopicClusterName = this.rocketMQClusterName; } } public int computeDelayLevel(long timeMillis) { long intervalMillis = timeMillis - System.currentTimeMillis(); List> sortedLevels = delayLevelTable.entrySet().stream().sorted(Comparator.comparingLong(Map.Entry::getValue)).collect(Collectors.toList()); for (Map.Entry entry : sortedLevels) { if (entry.getValue() > intervalMillis) { return entry.getKey(); } } return sortedLevels.get(sortedLevels.size() - 1).getKey(); } public void parseDelayLevel() { this.delayLevelTable = new ConcurrentSkipListMap<>(); Map timeUnitTable = new HashMap<>(); timeUnitTable.put("s", 1000L); timeUnitTable.put("m", 1000L * 60); timeUnitTable.put("h", 1000L * 60 * 60); timeUnitTable.put("d", 1000L * 60 * 60 * 24); String levelString = this.getMessageDelayLevel(); try { String[] levelArray = levelString.split(" "); for (int i = 0; i < levelArray.length; i++) { String value = levelArray[i]; String ch = value.substring(value.length() - 1); Long tu = timeUnitTable.get(ch); int level = i + 1; long num = Long.parseLong(value.substring(0, value.length() - 1)); long delayTimeMillis = tu * num; this.delayLevelTable.put(level, delayTimeMillis); } } catch (Exception e) { log.error("parse delay level failed. messageDelayLevel:{}", messageDelayLevel, e); } } public int getTlsCertWatchIntervalMs() { return tlsCertWatchIntervalMs; } public void setTlsCertWatchIntervalMs(int tlsCertWatchIntervalMs) { this.tlsCertWatchIntervalMs = tlsCertWatchIntervalMs; } public String getRocketMQClusterName() { return rocketMQClusterName; } public void setRocketMQClusterName(String rocketMQClusterName) { this.rocketMQClusterName = rocketMQClusterName; } public String getProxyClusterName() { return proxyClusterName; } public void setProxyClusterName(String proxyClusterName) { this.proxyClusterName = proxyClusterName; } public String getProxyName() { return proxyName; } public void setProxyName(String proxyName) { this.proxyName = proxyName; } public String getLocalServeAddr() { return localServeAddr; } public void setLocalServeAddr(String localServeAddr) { this.localServeAddr = localServeAddr; } public String getHeartbeatSyncerTopicClusterName() { return heartbeatSyncerTopicClusterName; } public void setHeartbeatSyncerTopicClusterName(String heartbeatSyncerTopicClusterName) { this.heartbeatSyncerTopicClusterName = heartbeatSyncerTopicClusterName; } public int getHeartbeatSyncerThreadPoolNums() { return heartbeatSyncerThreadPoolNums; } public void setHeartbeatSyncerThreadPoolNums(int heartbeatSyncerThreadPoolNums) { this.heartbeatSyncerThreadPoolNums = heartbeatSyncerThreadPoolNums; } public int getHeartbeatSyncerThreadPoolQueueCapacity() { return heartbeatSyncerThreadPoolQueueCapacity; } public void setHeartbeatSyncerThreadPoolQueueCapacity(int heartbeatSyncerThreadPoolQueueCapacity) { this.heartbeatSyncerThreadPoolQueueCapacity = heartbeatSyncerThreadPoolQueueCapacity; } public String getHeartbeatSyncerTopicName() { return heartbeatSyncerTopicName; } public void setHeartbeatSyncerTopicName(String heartbeatSyncerTopicName) { this.heartbeatSyncerTopicName = heartbeatSyncerTopicName; } public boolean isEnablePrintJstack() { return enablePrintJstack; } public void setEnablePrintJstack(boolean enablePrintJstack) { this.enablePrintJstack = enablePrintJstack; } public long getPrintJstackInMillis() { return printJstackInMillis; } public void setPrintJstackInMillis(long printJstackInMillis) { this.printJstackInMillis = printJstackInMillis; } public long getPrintThreadPoolStatusInMillis() { return printThreadPoolStatusInMillis; } public void setPrintThreadPoolStatusInMillis(long printThreadPoolStatusInMillis) { this.printThreadPoolStatusInMillis = printThreadPoolStatusInMillis; } public String getNamesrvAddr() { return namesrvAddr; } public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; } public String getNamesrvDomain() { return namesrvDomain; } public void setNamesrvDomain(String namesrvDomain) { this.namesrvDomain = namesrvDomain; } public String getNamesrvDomainSubgroup() { return namesrvDomainSubgroup; } public void setNamesrvDomainSubgroup(String namesrvDomainSubgroup) { this.namesrvDomainSubgroup = namesrvDomainSubgroup; } public String getProxyMode() { return proxyMode; } public void setProxyMode(String proxyMode) { this.proxyMode = proxyMode; } public Integer getGrpcServerPort() { return grpcServerPort; } public void setGrpcServerPort(Integer grpcServerPort) { this.grpcServerPort = grpcServerPort; } public long getGrpcShutdownTimeSeconds() { return grpcShutdownTimeSeconds; } public void setGrpcShutdownTimeSeconds(long grpcShutdownTimeSeconds) { this.grpcShutdownTimeSeconds = grpcShutdownTimeSeconds; } public boolean isUseEndpointPortFromRequest() { return useEndpointPortFromRequest; } public void setUseEndpointPortFromRequest(boolean useEndpointPortFromRequest) { this.useEndpointPortFromRequest = useEndpointPortFromRequest; } public boolean isTlsTestModeEnable() { return tlsTestModeEnable; } public void setTlsTestModeEnable(boolean tlsTestModeEnable) { this.tlsTestModeEnable = tlsTestModeEnable; } public String getTlsKeyPath() { return tlsKeyPath; } public void setTlsKeyPath(String tlsKeyPath) { this.tlsKeyPath = tlsKeyPath; } public String getTlsKeyPassword() { return tlsKeyPassword; } public void setTlsKeyPassword(String tlsKeyPassword) { this.tlsKeyPassword = tlsKeyPassword; } public String getTlsCertPath() { return tlsCertPath; } public void setTlsCertPath(String tlsCertPath) { this.tlsCertPath = tlsCertPath; } public int getGrpcBossLoopNum() { return grpcBossLoopNum; } public void setGrpcBossLoopNum(int grpcBossLoopNum) { this.grpcBossLoopNum = grpcBossLoopNum; } public int getGrpcWorkerLoopNum() { return grpcWorkerLoopNum; } public void setGrpcWorkerLoopNum(int grpcWorkerLoopNum) { this.grpcWorkerLoopNum = grpcWorkerLoopNum; } public boolean isEnableGrpcEpoll() { return enableGrpcEpoll; } public void setEnableGrpcEpoll(boolean enableGrpcEpoll) { this.enableGrpcEpoll = enableGrpcEpoll; } public int getGrpcThreadPoolNums() { return grpcThreadPoolNums; } public void setGrpcThreadPoolNums(int grpcThreadPoolNums) { this.grpcThreadPoolNums = grpcThreadPoolNums; } public int getGrpcThreadPoolQueueCapacity() { return grpcThreadPoolQueueCapacity; } public void setGrpcThreadPoolQueueCapacity(int grpcThreadPoolQueueCapacity) { this.grpcThreadPoolQueueCapacity = grpcThreadPoolQueueCapacity; } public String getBrokerConfigPath() { return brokerConfigPath; } public void setBrokerConfigPath(String brokerConfigPath) { this.brokerConfigPath = brokerConfigPath; } public int getGrpcMaxInboundMessageSize() { return grpcMaxInboundMessageSize; } public void setGrpcMaxInboundMessageSize(int grpcMaxInboundMessageSize) { this.grpcMaxInboundMessageSize = grpcMaxInboundMessageSize; } public int getMaxMessageSize() { return maxMessageSize; } public void setMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; } public int getMaxUserPropertySize() { return maxUserPropertySize; } public void setMaxUserPropertySize(int maxUserPropertySize) { this.maxUserPropertySize = maxUserPropertySize; } public int getUserPropertyMaxNum() { return userPropertyMaxNum; } public void setUserPropertyMaxNum(int userPropertyMaxNum) { this.userPropertyMaxNum = userPropertyMaxNum; } public int getMaxMessageGroupSize() { return maxMessageGroupSize; } public void setMaxMessageGroupSize(int maxMessageGroupSize) { this.maxMessageGroupSize = maxMessageGroupSize; } public long getMinInvisibleTimeMillsForRecv() { return minInvisibleTimeMillsForRecv; } public void setMinInvisibleTimeMillsForRecv(long minInvisibleTimeMillsForRecv) { this.minInvisibleTimeMillsForRecv = minInvisibleTimeMillsForRecv; } public long getDefaultInvisibleTimeMills() { return defaultInvisibleTimeMills; } public void setDefaultInvisibleTimeMills(long defaultInvisibleTimeMills) { this.defaultInvisibleTimeMills = defaultInvisibleTimeMills; } public long getMaxInvisibleTimeMills() { return maxInvisibleTimeMills; } public void setMaxInvisibleTimeMills(long maxInvisibleTimeMills) { this.maxInvisibleTimeMills = maxInvisibleTimeMills; } public long getMaxDelayTimeMills() { return maxDelayTimeMills; } public void setMaxDelayTimeMills(long maxDelayTimeMills) { this.maxDelayTimeMills = maxDelayTimeMills; } public long getMaxTransactionRecoverySecond() { return maxTransactionRecoverySecond; } public void setMaxTransactionRecoverySecond(long maxTransactionRecoverySecond) { this.maxTransactionRecoverySecond = maxTransactionRecoverySecond; } public int getGrpcClientProducerMaxAttempts() { return grpcClientProducerMaxAttempts; } public void setGrpcClientProducerMaxAttempts(int grpcClientProducerMaxAttempts) { this.grpcClientProducerMaxAttempts = grpcClientProducerMaxAttempts; } public long getGrpcClientProducerBackoffInitialMillis() { return grpcClientProducerBackoffInitialMillis; } public void setGrpcClientProducerBackoffInitialMillis(long grpcClientProducerBackoffInitialMillis) { this.grpcClientProducerBackoffInitialMillis = grpcClientProducerBackoffInitialMillis; } public long getGrpcClientProducerBackoffMaxMillis() { return grpcClientProducerBackoffMaxMillis; } public void setGrpcClientProducerBackoffMaxMillis(long grpcClientProducerBackoffMaxMillis) { this.grpcClientProducerBackoffMaxMillis = grpcClientProducerBackoffMaxMillis; } public int getGrpcClientProducerBackoffMultiplier() { return grpcClientProducerBackoffMultiplier; } public void setGrpcClientProducerBackoffMultiplier(int grpcClientProducerBackoffMultiplier) { this.grpcClientProducerBackoffMultiplier = grpcClientProducerBackoffMultiplier; } public long getGrpcClientConsumerMinLongPollingTimeoutMillis() { return grpcClientConsumerMinLongPollingTimeoutMillis; } public void setGrpcClientConsumerMinLongPollingTimeoutMillis(long grpcClientConsumerMinLongPollingTimeoutMillis) { this.grpcClientConsumerMinLongPollingTimeoutMillis = grpcClientConsumerMinLongPollingTimeoutMillis; } public long getGrpcClientConsumerMaxLongPollingTimeoutMillis() { return grpcClientConsumerMaxLongPollingTimeoutMillis; } public void setGrpcClientConsumerMaxLongPollingTimeoutMillis(long grpcClientConsumerMaxLongPollingTimeoutMillis) { this.grpcClientConsumerMaxLongPollingTimeoutMillis = grpcClientConsumerMaxLongPollingTimeoutMillis; } public int getGrpcClientConsumerLongPollingBatchSize() { return grpcClientConsumerLongPollingBatchSize; } public void setGrpcClientConsumerLongPollingBatchSize(int grpcClientConsumerLongPollingBatchSize) { this.grpcClientConsumerLongPollingBatchSize = grpcClientConsumerLongPollingBatchSize; } public int getChannelExpiredInSeconds() { return channelExpiredInSeconds; } public void setChannelExpiredInSeconds(int channelExpiredInSeconds) { this.channelExpiredInSeconds = channelExpiredInSeconds; } public int getContextExpiredInSeconds() { return contextExpiredInSeconds; } public void setContextExpiredInSeconds(int contextExpiredInSeconds) { this.contextExpiredInSeconds = contextExpiredInSeconds; } public int getRocketmqMQClientNum() { return rocketmqMQClientNum; } public void setRocketmqMQClientNum(int rocketmqMQClientNum) { this.rocketmqMQClientNum = rocketmqMQClientNum; } public long getGrpcProxyRelayRequestTimeoutInSeconds() { return grpcProxyRelayRequestTimeoutInSeconds; } public void setGrpcProxyRelayRequestTimeoutInSeconds(long grpcProxyRelayRequestTimeoutInSeconds) { this.grpcProxyRelayRequestTimeoutInSeconds = grpcProxyRelayRequestTimeoutInSeconds; } public int getGrpcProducerThreadPoolNums() { return grpcProducerThreadPoolNums; } public void setGrpcProducerThreadPoolNums(int grpcProducerThreadPoolNums) { this.grpcProducerThreadPoolNums = grpcProducerThreadPoolNums; } public int getGrpcProducerThreadQueueCapacity() { return grpcProducerThreadQueueCapacity; } public void setGrpcProducerThreadQueueCapacity(int grpcProducerThreadQueueCapacity) { this.grpcProducerThreadQueueCapacity = grpcProducerThreadQueueCapacity; } public int getGrpcConsumerThreadPoolNums() { return grpcConsumerThreadPoolNums; } public void setGrpcConsumerThreadPoolNums(int grpcConsumerThreadPoolNums) { this.grpcConsumerThreadPoolNums = grpcConsumerThreadPoolNums; } public int getGrpcConsumerThreadQueueCapacity() { return grpcConsumerThreadQueueCapacity; } public void setGrpcConsumerThreadQueueCapacity(int grpcConsumerThreadQueueCapacity) { this.grpcConsumerThreadQueueCapacity = grpcConsumerThreadQueueCapacity; } public int getGrpcRouteThreadPoolNums() { return grpcRouteThreadPoolNums; } public void setGrpcRouteThreadPoolNums(int grpcRouteThreadPoolNums) { this.grpcRouteThreadPoolNums = grpcRouteThreadPoolNums; } public int getGrpcRouteThreadQueueCapacity() { return grpcRouteThreadQueueCapacity; } public void setGrpcRouteThreadQueueCapacity(int grpcRouteThreadQueueCapacity) { this.grpcRouteThreadQueueCapacity = grpcRouteThreadQueueCapacity; } public int getGrpcClientManagerThreadPoolNums() { return grpcClientManagerThreadPoolNums; } public void setGrpcClientManagerThreadPoolNums(int grpcClientManagerThreadPoolNums) { this.grpcClientManagerThreadPoolNums = grpcClientManagerThreadPoolNums; } public int getGrpcClientManagerThreadQueueCapacity() { return grpcClientManagerThreadQueueCapacity; } public void setGrpcClientManagerThreadQueueCapacity(int grpcClientManagerThreadQueueCapacity) { this.grpcClientManagerThreadQueueCapacity = grpcClientManagerThreadQueueCapacity; } public int getGrpcTransactionThreadPoolNums() { return grpcTransactionThreadPoolNums; } public void setGrpcTransactionThreadPoolNums(int grpcTransactionThreadPoolNums) { this.grpcTransactionThreadPoolNums = grpcTransactionThreadPoolNums; } public int getGrpcTransactionThreadQueueCapacity() { return grpcTransactionThreadQueueCapacity; } public void setGrpcTransactionThreadQueueCapacity(int grpcTransactionThreadQueueCapacity) { this.grpcTransactionThreadQueueCapacity = grpcTransactionThreadQueueCapacity; } public int getProducerProcessorThreadPoolNums() { return producerProcessorThreadPoolNums; } public void setProducerProcessorThreadPoolNums(int producerProcessorThreadPoolNums) { this.producerProcessorThreadPoolNums = producerProcessorThreadPoolNums; } public int getProducerProcessorThreadPoolQueueCapacity() { return producerProcessorThreadPoolQueueCapacity; } public void setProducerProcessorThreadPoolQueueCapacity(int producerProcessorThreadPoolQueueCapacity) { this.producerProcessorThreadPoolQueueCapacity = producerProcessorThreadPoolQueueCapacity; } public int getConsumerProcessorThreadPoolNums() { return consumerProcessorThreadPoolNums; } public void setConsumerProcessorThreadPoolNums(int consumerProcessorThreadPoolNums) { this.consumerProcessorThreadPoolNums = consumerProcessorThreadPoolNums; } public int getConsumerProcessorThreadPoolQueueCapacity() { return consumerProcessorThreadPoolQueueCapacity; } public void setConsumerProcessorThreadPoolQueueCapacity(int consumerProcessorThreadPoolQueueCapacity) { this.consumerProcessorThreadPoolQueueCapacity = consumerProcessorThreadPoolQueueCapacity; } public int getTopicRouteServiceCacheExpiredSeconds() { return topicRouteServiceCacheExpiredSeconds; } public void setTopicRouteServiceCacheExpiredSeconds(int topicRouteServiceCacheExpiredSeconds) { this.topicRouteServiceCacheExpiredSeconds = topicRouteServiceCacheExpiredSeconds; } public int getTopicRouteServiceCacheRefreshSeconds() { return topicRouteServiceCacheRefreshSeconds; } public void setTopicRouteServiceCacheRefreshSeconds(int topicRouteServiceCacheRefreshSeconds) { this.topicRouteServiceCacheRefreshSeconds = topicRouteServiceCacheRefreshSeconds; } public int getTopicRouteServiceCacheMaxNum() { return topicRouteServiceCacheMaxNum; } public void setTopicRouteServiceCacheMaxNum(int topicRouteServiceCacheMaxNum) { this.topicRouteServiceCacheMaxNum = topicRouteServiceCacheMaxNum; } public int getTopicRouteServiceThreadPoolNums() { return topicRouteServiceThreadPoolNums; } public void setTopicRouteServiceThreadPoolNums(int topicRouteServiceThreadPoolNums) { this.topicRouteServiceThreadPoolNums = topicRouteServiceThreadPoolNums; } public int getTopicRouteServiceThreadPoolQueueCapacity() { return topicRouteServiceThreadPoolQueueCapacity; } public void setTopicRouteServiceThreadPoolQueueCapacity(int topicRouteServiceThreadPoolQueueCapacity) { this.topicRouteServiceThreadPoolQueueCapacity = topicRouteServiceThreadPoolQueueCapacity; } public int getTopicConfigCacheRefreshSeconds() { return topicConfigCacheRefreshSeconds; } public void setTopicConfigCacheRefreshSeconds(int topicConfigCacheRefreshSeconds) { this.topicConfigCacheRefreshSeconds = topicConfigCacheRefreshSeconds; } public int getTopicConfigCacheExpiredSeconds() { return topicConfigCacheExpiredSeconds; } public void setTopicConfigCacheExpiredSeconds(int topicConfigCacheExpiredSeconds) { this.topicConfigCacheExpiredSeconds = topicConfigCacheExpiredSeconds; } public int getTopicConfigCacheMaxNum() { return topicConfigCacheMaxNum; } public void setTopicConfigCacheMaxNum(int topicConfigCacheMaxNum) { this.topicConfigCacheMaxNum = topicConfigCacheMaxNum; } public int getSubscriptionGroupConfigCacheRefreshSeconds() { return subscriptionGroupConfigCacheRefreshSeconds; } public void setSubscriptionGroupConfigCacheRefreshSeconds(int subscriptionGroupConfigCacheRefreshSeconds) { this.subscriptionGroupConfigCacheRefreshSeconds = subscriptionGroupConfigCacheRefreshSeconds; } public int getSubscriptionGroupConfigCacheExpiredSeconds() { return subscriptionGroupConfigCacheExpiredSeconds; } public void setSubscriptionGroupConfigCacheExpiredSeconds(int subscriptionGroupConfigCacheExpiredSeconds) { this.subscriptionGroupConfigCacheExpiredSeconds = subscriptionGroupConfigCacheExpiredSeconds; } public int getSubscriptionGroupConfigCacheMaxNum() { return subscriptionGroupConfigCacheMaxNum; } public void setSubscriptionGroupConfigCacheMaxNum(int subscriptionGroupConfigCacheMaxNum) { this.subscriptionGroupConfigCacheMaxNum = subscriptionGroupConfigCacheMaxNum; } public int getUserCacheExpiredSeconds() { return userCacheExpiredSeconds; } public void setUserCacheExpiredSeconds(int userCacheExpiredSeconds) { this.userCacheExpiredSeconds = userCacheExpiredSeconds; } public int getUserCacheRefreshSeconds() { return userCacheRefreshSeconds; } public void setUserCacheRefreshSeconds(int userCacheRefreshSeconds) { this.userCacheRefreshSeconds = userCacheRefreshSeconds; } public int getUserCacheMaxNum() { return userCacheMaxNum; } public void setUserCacheMaxNum(int userCacheMaxNum) { this.userCacheMaxNum = userCacheMaxNum; } public int getAclCacheExpiredSeconds() { return aclCacheExpiredSeconds; } public void setAclCacheExpiredSeconds(int aclCacheExpiredSeconds) { this.aclCacheExpiredSeconds = aclCacheExpiredSeconds; } public int getAclCacheRefreshSeconds() { return aclCacheRefreshSeconds; } public void setAclCacheRefreshSeconds(int aclCacheRefreshSeconds) { this.aclCacheRefreshSeconds = aclCacheRefreshSeconds; } public int getAclCacheMaxNum() { return aclCacheMaxNum; } public void setAclCacheMaxNum(int aclCacheMaxNum) { this.aclCacheMaxNum = aclCacheMaxNum; } public int getMetadataThreadPoolNums() { return metadataThreadPoolNums; } public void setMetadataThreadPoolNums(int metadataThreadPoolNums) { this.metadataThreadPoolNums = metadataThreadPoolNums; } public int getMetadataThreadPoolQueueCapacity() { return metadataThreadPoolQueueCapacity; } public void setMetadataThreadPoolQueueCapacity(int metadataThreadPoolQueueCapacity) { this.metadataThreadPoolQueueCapacity = metadataThreadPoolQueueCapacity; } public int getTransactionHeartbeatThreadPoolNums() { return transactionHeartbeatThreadPoolNums; } public void setTransactionHeartbeatThreadPoolNums(int transactionHeartbeatThreadPoolNums) { this.transactionHeartbeatThreadPoolNums = transactionHeartbeatThreadPoolNums; } public int getTransactionHeartbeatThreadPoolQueueCapacity() { return transactionHeartbeatThreadPoolQueueCapacity; } public void setTransactionHeartbeatThreadPoolQueueCapacity(int transactionHeartbeatThreadPoolQueueCapacity) { this.transactionHeartbeatThreadPoolQueueCapacity = transactionHeartbeatThreadPoolQueueCapacity; } public int getTransactionHeartbeatPeriodSecond() { return transactionHeartbeatPeriodSecond; } public void setTransactionHeartbeatPeriodSecond(int transactionHeartbeatPeriodSecond) { this.transactionHeartbeatPeriodSecond = transactionHeartbeatPeriodSecond; } public int getTransactionHeartbeatBatchNum() { return transactionHeartbeatBatchNum; } public void setTransactionHeartbeatBatchNum(int transactionHeartbeatBatchNum) { this.transactionHeartbeatBatchNum = transactionHeartbeatBatchNum; } public long getTransactionDataExpireScanPeriodMillis() { return transactionDataExpireScanPeriodMillis; } public void setTransactionDataExpireScanPeriodMillis(long transactionDataExpireScanPeriodMillis) { this.transactionDataExpireScanPeriodMillis = transactionDataExpireScanPeriodMillis; } public long getTransactionDataMaxWaitClearMillis() { return transactionDataMaxWaitClearMillis; } public void setTransactionDataMaxWaitClearMillis(long transactionDataMaxWaitClearMillis) { this.transactionDataMaxWaitClearMillis = transactionDataMaxWaitClearMillis; } public long getTransactionDataExpireMillis() { return transactionDataExpireMillis; } public void setTransactionDataExpireMillis(long transactionDataExpireMillis) { this.transactionDataExpireMillis = transactionDataExpireMillis; } public int getTransactionDataMaxNum() { return transactionDataMaxNum; } public void setTransactionDataMaxNum(int transactionDataMaxNum) { this.transactionDataMaxNum = transactionDataMaxNum; } public long getLongPollingReserveTimeInMillis() { return longPollingReserveTimeInMillis; } public void setLongPollingReserveTimeInMillis(long longPollingReserveTimeInMillis) { this.longPollingReserveTimeInMillis = longPollingReserveTimeInMillis; } public boolean isEnableAclRpcHookForClusterMode() { return enableAclRpcHookForClusterMode; } public void setEnableAclRpcHookForClusterMode(boolean enableAclRpcHookForClusterMode) { this.enableAclRpcHookForClusterMode = enableAclRpcHookForClusterMode; } public boolean isEnableTopicMessageTypeCheck() { return enableTopicMessageTypeCheck; } public void setEnableTopicMessageTypeCheck(boolean enableTopicMessageTypeCheck) { this.enableTopicMessageTypeCheck = enableTopicMessageTypeCheck; } public long getInvisibleTimeMillisWhenClear() { return invisibleTimeMillisWhenClear; } public void setInvisibleTimeMillisWhenClear(long invisibleTimeMillisWhenClear) { this.invisibleTimeMillisWhenClear = invisibleTimeMillisWhenClear; } public boolean isEnableProxyAutoRenew() { return enableProxyAutoRenew; } public void setEnableProxyAutoRenew(boolean enableProxyAutoRenew) { this.enableProxyAutoRenew = enableProxyAutoRenew; } public int getMaxRenewRetryTimes() { return maxRenewRetryTimes; } public void setMaxRenewRetryTimes(int maxRenewRetryTimes) { this.maxRenewRetryTimes = maxRenewRetryTimes; } public int getRenewThreadPoolNums() { return renewThreadPoolNums; } public void setRenewThreadPoolNums(int renewThreadPoolNums) { this.renewThreadPoolNums = renewThreadPoolNums; } public int getRenewMaxThreadPoolNums() { return renewMaxThreadPoolNums; } public void setRenewMaxThreadPoolNums(int renewMaxThreadPoolNums) { this.renewMaxThreadPoolNums = renewMaxThreadPoolNums; } public int getRenewThreadPoolQueueCapacity() { return renewThreadPoolQueueCapacity; } public void setRenewThreadPoolQueueCapacity(int renewThreadPoolQueueCapacity) { this.renewThreadPoolQueueCapacity = renewThreadPoolQueueCapacity; } public long getLockTimeoutMsInHandleGroup() { return lockTimeoutMsInHandleGroup; } public void setLockTimeoutMsInHandleGroup(long lockTimeoutMsInHandleGroup) { this.lockTimeoutMsInHandleGroup = lockTimeoutMsInHandleGroup; } public long getRenewAheadTimeMillis() { return renewAheadTimeMillis; } public void setRenewAheadTimeMillis(long renewAheadTimeMillis) { this.renewAheadTimeMillis = renewAheadTimeMillis; } public long getRenewMaxTimeMillis() { return renewMaxTimeMillis; } public void setRenewMaxTimeMillis(long renewMaxTimeMillis) { this.renewMaxTimeMillis = renewMaxTimeMillis; } public long getRenewSchedulePeriodMillis() { return renewSchedulePeriodMillis; } public void setRenewSchedulePeriodMillis(long renewSchedulePeriodMillis) { this.renewSchedulePeriodMillis = renewSchedulePeriodMillis; } public String getMetricCollectorMode() { return metricCollectorMode; } public void setMetricCollectorMode(String metricCollectorMode) { this.metricCollectorMode = metricCollectorMode; } public String getMetricCollectorAddress() { return metricCollectorAddress; } public void setMetricCollectorAddress(String metricCollectorAddress) { this.metricCollectorAddress = metricCollectorAddress; } public boolean isUseDelayLevel() { return useDelayLevel; } public void setUseDelayLevel(boolean useDelayLevel) { this.useDelayLevel = useDelayLevel; } public String getMessageDelayLevel() { return messageDelayLevel; } public void setMessageDelayLevel(String messageDelayLevel) { this.messageDelayLevel = messageDelayLevel; } public ConcurrentSkipListMap getDelayLevelTable() { return delayLevelTable; } public long getGrpcClientIdleTimeMills() { return grpcClientIdleTimeMills; } public void setGrpcClientIdleTimeMills(final long grpcClientIdleTimeMills) { this.grpcClientIdleTimeMills = grpcClientIdleTimeMills; } public String getRegionId() { return regionId; } public void setRegionId(String regionId) { this.regionId = regionId; } public boolean isTraceOn() { return traceOn; } public void setTraceOn(boolean traceOn) { this.traceOn = traceOn; } public String getRemotingAccessAddr() { return remotingAccessAddr; } public void setRemotingAccessAddr(String remotingAccessAddr) { this.remotingAccessAddr = remotingAccessAddr; } public MetricsExporterType getMetricsExporterType() { return metricsExporterType; } public void setMetricsExporterType(MetricsExporterType metricsExporterType) { this.metricsExporterType = metricsExporterType; } public void setMetricsExporterType(String metricsExporterType) { this.metricsExporterType = MetricsExporterType.valueOf(metricsExporterType); } public String getMetricsGrpcExporterTarget() { return metricsGrpcExporterTarget; } public void setMetricsGrpcExporterTarget(String metricsGrpcExporterTarget) { this.metricsGrpcExporterTarget = metricsGrpcExporterTarget; } public String getMetricsGrpcExporterHeader() { return metricsGrpcExporterHeader; } public void setMetricsGrpcExporterHeader(String metricsGrpcExporterHeader) { this.metricsGrpcExporterHeader = metricsGrpcExporterHeader; } public long getMetricGrpcExporterTimeOutInMills() { return metricGrpcExporterTimeOutInMills; } public void setMetricGrpcExporterTimeOutInMills(long metricGrpcExporterTimeOutInMills) { this.metricGrpcExporterTimeOutInMills = metricGrpcExporterTimeOutInMills; } public long getMetricGrpcExporterIntervalInMills() { return metricGrpcExporterIntervalInMills; } public void setMetricGrpcExporterIntervalInMills(long metricGrpcExporterIntervalInMills) { this.metricGrpcExporterIntervalInMills = metricGrpcExporterIntervalInMills; } public long getMetricLoggingExporterIntervalInMills() { return metricLoggingExporterIntervalInMills; } public void setMetricLoggingExporterIntervalInMills(long metricLoggingExporterIntervalInMills) { this.metricLoggingExporterIntervalInMills = metricLoggingExporterIntervalInMills; } public int getMetricsPromExporterPort() { return metricsPromExporterPort; } public void setMetricsPromExporterPort(int metricsPromExporterPort) { this.metricsPromExporterPort = metricsPromExporterPort; } public String getMetricsPromExporterHost() { return metricsPromExporterHost; } public void setMetricsPromExporterHost(String metricsPromExporterHost) { this.metricsPromExporterHost = metricsPromExporterHost; } public String getMetricsLabel() { return metricsLabel; } public void setMetricsLabel(String metricsLabel) { this.metricsLabel = metricsLabel; } public boolean isMetricsInDelta() { return metricsInDelta; } public void setMetricsInDelta(boolean metricsInDelta) { this.metricsInDelta = metricsInDelta; } public long getChannelExpiredTimeout() { return channelExpiredTimeout; } public boolean isEnableRemotingLocalProxyGrpc() { return enableRemotingLocalProxyGrpc; } public void setChannelExpiredTimeout(long channelExpiredTimeout) { this.channelExpiredTimeout = channelExpiredTimeout; } public void setEnableRemotingLocalProxyGrpc(boolean enableRemotingLocalProxyGrpc) { this.enableRemotingLocalProxyGrpc = enableRemotingLocalProxyGrpc; } public int getLocalProxyConnectTimeoutMs() { return localProxyConnectTimeoutMs; } public void setLocalProxyConnectTimeoutMs(int localProxyConnectTimeoutMs) { this.localProxyConnectTimeoutMs = localProxyConnectTimeoutMs; } public int getRemotingListenPort() { return remotingListenPort; } public void setRemotingListenPort(int remotingListenPort) { this.remotingListenPort = remotingListenPort; } public int getRemotingHeartbeatThreadPoolNums() { return remotingHeartbeatThreadPoolNums; } public void setRemotingHeartbeatThreadPoolNums(int remotingHeartbeatThreadPoolNums) { this.remotingHeartbeatThreadPoolNums = remotingHeartbeatThreadPoolNums; } public int getRemotingTopicRouteThreadPoolNums() { return remotingTopicRouteThreadPoolNums; } public void setRemotingTopicRouteThreadPoolNums(int remotingTopicRouteThreadPoolNums) { this.remotingTopicRouteThreadPoolNums = remotingTopicRouteThreadPoolNums; } public int getRemotingSendMessageThreadPoolNums() { return remotingSendMessageThreadPoolNums; } public void setRemotingSendMessageThreadPoolNums(int remotingSendMessageThreadPoolNums) { this.remotingSendMessageThreadPoolNums = remotingSendMessageThreadPoolNums; } public int getRemotingPullMessageThreadPoolNums() { return remotingPullMessageThreadPoolNums; } public void setRemotingPullMessageThreadPoolNums(int remotingPullMessageThreadPoolNums) { this.remotingPullMessageThreadPoolNums = remotingPullMessageThreadPoolNums; } public int getRemotingUpdateOffsetThreadPoolNums() { return remotingUpdateOffsetThreadPoolNums; } public void setRemotingUpdateOffsetThreadPoolNums(int remotingUpdateOffsetThreadPoolNums) { this.remotingUpdateOffsetThreadPoolNums = remotingUpdateOffsetThreadPoolNums; } public int getRemotingDefaultThreadPoolNums() { return remotingDefaultThreadPoolNums; } public void setRemotingDefaultThreadPoolNums(int remotingDefaultThreadPoolNums) { this.remotingDefaultThreadPoolNums = remotingDefaultThreadPoolNums; } public int getRemotingHeartbeatThreadPoolQueueCapacity() { return remotingHeartbeatThreadPoolQueueCapacity; } public void setRemotingHeartbeatThreadPoolQueueCapacity(int remotingHeartbeatThreadPoolQueueCapacity) { this.remotingHeartbeatThreadPoolQueueCapacity = remotingHeartbeatThreadPoolQueueCapacity; } public int getRemotingTopicRouteThreadPoolQueueCapacity() { return remotingTopicRouteThreadPoolQueueCapacity; } public void setRemotingTopicRouteThreadPoolQueueCapacity(int remotingTopicRouteThreadPoolQueueCapacity) { this.remotingTopicRouteThreadPoolQueueCapacity = remotingTopicRouteThreadPoolQueueCapacity; } public int getRemotingSendThreadPoolQueueCapacity() { return remotingSendThreadPoolQueueCapacity; } public void setRemotingSendThreadPoolQueueCapacity(int remotingSendThreadPoolQueueCapacity) { this.remotingSendThreadPoolQueueCapacity = remotingSendThreadPoolQueueCapacity; } public int getRemotingPullThreadPoolQueueCapacity() { return remotingPullThreadPoolQueueCapacity; } public void setRemotingPullThreadPoolQueueCapacity(int remotingPullThreadPoolQueueCapacity) { this.remotingPullThreadPoolQueueCapacity = remotingPullThreadPoolQueueCapacity; } public int getRemotingUpdateOffsetThreadPoolQueueCapacity() { return remotingUpdateOffsetThreadPoolQueueCapacity; } public void setRemotingUpdateOffsetThreadPoolQueueCapacity(int remotingUpdateOffsetThreadPoolQueueCapacity) { this.remotingUpdateOffsetThreadPoolQueueCapacity = remotingUpdateOffsetThreadPoolQueueCapacity; } public int getRemotingDefaultThreadPoolQueueCapacity() { return remotingDefaultThreadPoolQueueCapacity; } public void setRemotingDefaultThreadPoolQueueCapacity(int remotingDefaultThreadPoolQueueCapacity) { this.remotingDefaultThreadPoolQueueCapacity = remotingDefaultThreadPoolQueueCapacity; } public long getRemotingWaitTimeMillsInSendQueue() { return remotingWaitTimeMillsInSendQueue; } public void setRemotingWaitTimeMillsInSendQueue(long remotingWaitTimeMillsInSendQueue) { this.remotingWaitTimeMillsInSendQueue = remotingWaitTimeMillsInSendQueue; } public long getRemotingWaitTimeMillsInPullQueue() { return remotingWaitTimeMillsInPullQueue; } public void setRemotingWaitTimeMillsInPullQueue(long remotingWaitTimeMillsInPullQueue) { this.remotingWaitTimeMillsInPullQueue = remotingWaitTimeMillsInPullQueue; } public long getRemotingWaitTimeMillsInHeartbeatQueue() { return remotingWaitTimeMillsInHeartbeatQueue; } public void setRemotingWaitTimeMillsInHeartbeatQueue(long remotingWaitTimeMillsInHeartbeatQueue) { this.remotingWaitTimeMillsInHeartbeatQueue = remotingWaitTimeMillsInHeartbeatQueue; } public long getRemotingWaitTimeMillsInUpdateOffsetQueue() { return remotingWaitTimeMillsInUpdateOffsetQueue; } public void setRemotingWaitTimeMillsInUpdateOffsetQueue(long remotingWaitTimeMillsInUpdateOffsetQueue) { this.remotingWaitTimeMillsInUpdateOffsetQueue = remotingWaitTimeMillsInUpdateOffsetQueue; } public long getRemotingWaitTimeMillsInTopicRouteQueue() { return remotingWaitTimeMillsInTopicRouteQueue; } public void setRemotingWaitTimeMillsInTopicRouteQueue(long remotingWaitTimeMillsInTopicRouteQueue) { this.remotingWaitTimeMillsInTopicRouteQueue = remotingWaitTimeMillsInTopicRouteQueue; } public long getRemotingWaitTimeMillsInDefaultQueue() { return remotingWaitTimeMillsInDefaultQueue; } public void setRemotingWaitTimeMillsInDefaultQueue(long remotingWaitTimeMillsInDefaultQueue) { this.remotingWaitTimeMillsInDefaultQueue = remotingWaitTimeMillsInDefaultQueue; } public boolean isSendLatencyEnable() { return sendLatencyEnable; } public boolean isStartDetectorEnable() { return startDetectorEnable; } public void setStartDetectorEnable(boolean startDetectorEnable) { this.startDetectorEnable = startDetectorEnable; } public void setSendLatencyEnable(boolean sendLatencyEnable) { this.sendLatencyEnable = sendLatencyEnable; } public boolean getStartDetectorEnable() { return this.startDetectorEnable; } public boolean getSendLatencyEnable() { return this.sendLatencyEnable; } public int getDetectTimeout() { return detectTimeout; } public void setDetectTimeout(int detectTimeout) { this.detectTimeout = detectTimeout; } public int getDetectInterval() { return detectInterval; } public void setDetectInterval(int detectInterval) { this.detectInterval = detectInterval; } public boolean isEnableBatchAck() { return enableBatchAck; } public void setEnableBatchAck(boolean enableBatchAck) { this.enableBatchAck = enableBatchAck; } public boolean isEnableMessageBodyEmptyCheck() { return enableMessageBodyEmptyCheck; } public void setEnableMessageBodyEmptyCheck(boolean enableMessageBodyEmptyCheck) { this.enableMessageBodyEmptyCheck = enableMessageBodyEmptyCheck; } public int getMaxLiteTopicSize() { return maxLiteTopicSize; } public void setMaxLiteTopicSize(int maxLiteTopicSize) { this.maxLiteTopicSize = maxLiteTopicSize; } public int getMaxLiteRenewNumPerChannel() { return maxLiteRenewNumPerChannel; } public void setMaxLiteRenewNumPerChannel(int maxLiteRenewNumPerChannel) { this.maxLiteRenewNumPerChannel = maxLiteRenewNumPerChannel; } public int getMaxSyncLiteSubscriptionRate() { return maxSyncLiteSubscriptionRate; } public void setMaxSyncLiteSubscriptionRate(int maxSyncLiteSubscriptionRate) { this.maxSyncLiteSubscriptionRate = maxSyncLiteSubscriptionRate; } public int getReturnHandleGroupThreadPoolNums() { return returnHandleGroupThreadPoolNums; } public void setReturnHandleGroupThreadPoolNums(int returnHandleGroupThreadPoolNums) { this.returnHandleGroupThreadPoolNums = returnHandleGroupThreadPoolNums; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc; import com.google.common.annotations.VisibleForTesting; import io.grpc.Server; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.service.cert.TlsCertificateManager; import java.io.IOException; import java.security.cert.CertificateException; import java.util.concurrent.TimeUnit; public class GrpcServer implements StartAndShutdown { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final Server server; private final long timeout; private final TimeUnit unit; private final TlsCertificateManager tlsCertificateManager; @VisibleForTesting final GrpcTlsReloadHandler tlsReloadHandler; protected GrpcServer(Server server, long timeout, TimeUnit unit, TlsCertificateManager tlsCertificateManager) throws Exception { this.server = server; this.timeout = timeout; this.unit = unit; this.tlsCertificateManager = tlsCertificateManager; this.tlsReloadHandler = new GrpcTlsReloadHandler(); } public void start() throws Exception { // Register the TLS context reload handler tlsCertificateManager.registerReloadListener(this.tlsReloadHandler); this.server.start(); log.info("grpc server start successfully."); } public void shutdown() { try { // Unregister the TLS context reload handler tlsCertificateManager.unregisterReloadListener(this.tlsReloadHandler); this.server.shutdown().awaitTermination(timeout, unit); log.info("grpc server shutdown successfully."); } catch (Exception e) { e.printStackTrace(); log.error("Failed to shutdown grpc server", e); } } @VisibleForTesting class GrpcTlsReloadHandler implements TlsCertificateManager.TlsContextReloadListener { @Override public void onTlsContextReload() { try { ProxyAndTlsProtocolNegotiator.loadSslContext(); log.info("SslContext reloaded for grpc server"); } catch (CertificateException | IOException e) { log.error("Failed to reload SslContext for server", e); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc; import io.grpc.BindableService; import io.grpc.ServerInterceptor; import io.grpc.ServerServiceDefinition; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup; import io.grpc.netty.shaded.io.netty.channel.epoll.EpollServerSocketChannel; import io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoopGroup; import io.grpc.netty.shaded.io.netty.channel.socket.nio.NioServerSocketChannel; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.interceptor.ContextInterceptor; import org.apache.rocketmq.proxy.grpc.interceptor.GlobalExceptionInterceptor; import org.apache.rocketmq.proxy.grpc.interceptor.HeaderInterceptor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.proxy.service.cert.TlsCertificateManager; public class GrpcServerBuilder { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected NettyServerBuilder serverBuilder; protected long time = 30; protected TimeUnit unit = TimeUnit.SECONDS; protected TlsCertificateManager tlsCertificateManager; public static GrpcServerBuilder newBuilder(ThreadPoolExecutor executor, int port, TlsCertificateManager tlsCertificateManager) { return new GrpcServerBuilder(executor, port, tlsCertificateManager); } protected GrpcServerBuilder(ThreadPoolExecutor executor, int port, TlsCertificateManager tlsCertificateManager) { this.tlsCertificateManager = tlsCertificateManager; serverBuilder = NettyServerBuilder.forPort(port); serverBuilder.protocolNegotiator(new ProxyAndTlsProtocolNegotiator()); // build server int bossLoopNum = ConfigurationManager.getProxyConfig().getGrpcBossLoopNum(); int workerLoopNum = ConfigurationManager.getProxyConfig().getGrpcWorkerLoopNum(); int maxInboundMessageSize = ConfigurationManager.getProxyConfig().getGrpcMaxInboundMessageSize(); long idleTimeMills = ConfigurationManager.getProxyConfig().getGrpcClientIdleTimeMills(); if (ConfigurationManager.getProxyConfig().isEnableGrpcEpoll()) { serverBuilder.bossEventLoopGroup(new EpollEventLoopGroup(bossLoopNum)) .workerEventLoopGroup(new EpollEventLoopGroup(workerLoopNum)) .channelType(EpollServerSocketChannel.class) .executor(executor); } else { serverBuilder.bossEventLoopGroup(new NioEventLoopGroup(bossLoopNum)) .workerEventLoopGroup(new NioEventLoopGroup(workerLoopNum)) .channelType(NioServerSocketChannel.class) .executor(executor); } serverBuilder.maxInboundMessageSize(maxInboundMessageSize) .maxConnectionIdle(idleTimeMills, TimeUnit.MILLISECONDS); log.info("grpc server has built. port: {}, bossLoopNum: {}, workerLoopNum: {}, maxInboundMessageSize: {}", port, bossLoopNum, workerLoopNum, maxInboundMessageSize); } public GrpcServerBuilder shutdownTime(long time, TimeUnit unit) { this.time = time; this.unit = unit; return this; } public GrpcServerBuilder addService(BindableService service) { this.serverBuilder.addService(service); return this; } public GrpcServerBuilder addService(ServerServiceDefinition service) { this.serverBuilder.addService(service); return this; } public GrpcServerBuilder appendInterceptor(ServerInterceptor interceptor) { this.serverBuilder.intercept(interceptor); return this; } public GrpcServer build() throws Exception { return new GrpcServer(this.serverBuilder.build(), time, unit, tlsCertificateManager); } public GrpcServerBuilder configInterceptor() { this.serverBuilder .intercept(new GlobalExceptionInterceptor()) .intercept(new ContextInterceptor()) .intercept(new HeaderInterceptor()); return this; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc; import io.grpc.Attributes; import io.grpc.netty.shaded.io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.InternalProtocolNegotiationEvent; import io.grpc.netty.shaded.io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.shaded.io.grpc.netty.InternalProtocolNegotiators; import io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiationEvent; import io.grpc.netty.shaded.io.netty.buffer.ByteBuf; import io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil; import io.grpc.netty.shaded.io.netty.channel.ChannelHandler; import io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext; import io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter; import io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder; import io.grpc.netty.shaded.io.netty.handler.codec.ProtocolDetectionResult; import io.grpc.netty.shaded.io.netty.handler.codec.ProtocolDetectionState; import io.grpc.netty.shaded.io.netty.handler.codec.haproxy.HAProxyMessage; import io.grpc.netty.shaded.io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.grpc.netty.shaded.io.netty.handler.codec.haproxy.HAProxyProtocolVersion; import io.grpc.netty.shaded.io.netty.handler.codec.haproxy.HAProxyTLV; import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth; import io.grpc.netty.shaded.io.netty.handler.ssl.OpenSsl; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.grpc.netty.shaded.io.netty.handler.ssl.SslHandler; import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.grpc.netty.shaded.io.netty.handler.ssl.util.SelfSignedCertificate; import io.grpc.netty.shaded.io.netty.util.AsciiString; import io.grpc.netty.shaded.io.netty.util.CharsetUtil; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.security.cert.CertificateException; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.HAProxyConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.BinaryUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.constant.AttributeKeys; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; public class ProxyAndTlsProtocolNegotiator implements InternalProtocolNegotiator.ProtocolNegotiator { protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final String HA_PROXY_DECODER = "HAProxyDecoder"; private static final String HA_PROXY_HANDLER = "HAProxyHandler"; private static final String TLS_MODE_HANDLER = "TlsModeHandler"; /** * the length of the ssl record header (in bytes) */ private static final int SSL_RECORD_HEADER_LENGTH = 5; private static SslContext sslContext; public ProxyAndTlsProtocolNegotiator() { try { loadSslContext(); log.info("SslContext created for proxy server"); } catch (IOException | CertificateException e) { log.error("SslContext init error", e); throw new RuntimeException(e); } } @Override public AsciiString scheme() { return AsciiString.of("https"); } @Override public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { return new ProxyAndTlsProtocolHandler(grpcHandler); } @Override public void close() { } public static void loadSslContext() throws CertificateException, IOException { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); SslProvider provider; if (OpenSsl.isAvailable()) { provider = SslProvider.OPENSSL; log.info("Using OpenSSL provider"); } else { provider = SslProvider.JDK; log.info("Using JDK SSL provider"); } if (proxyConfig.isTlsTestModeEnable()) { SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); sslContext = GrpcSslContexts.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) .sslProvider(provider) .trustManager(InsecureTrustManagerFactory.INSTANCE) .clientAuth(ClientAuth.NONE) .build(); } else { String tlsCertPath = ConfigurationManager.getProxyConfig().getTlsCertPath(); String tlsKeyPath = ConfigurationManager.getProxyConfig().getTlsKeyPath(); String tlsKeyPassword = ConfigurationManager.getProxyConfig().getTlsKeyPassword(); try (InputStream serverKeyInputStream = Files.newInputStream( Paths.get(tlsKeyPath)); InputStream serverCertificateStream = Files.newInputStream( Paths.get(tlsCertPath))) { sslContext = GrpcSslContexts.forServer(serverCertificateStream, serverKeyInputStream, StringUtils.isNotBlank(tlsKeyPassword) ? tlsKeyPassword : null) .trustManager(InsecureTrustManagerFactory.INSTANCE) .clientAuth(ClientAuth.NONE) .build(); } } } private class ProxyAndTlsProtocolHandler extends ByteToMessageDecoder { private final GrpcHttp2ConnectionHandler grpcHandler; private ProtocolNegotiationEvent pne = InternalProtocolNegotiationEvent.getDefault(); public ProxyAndTlsProtocolHandler(GrpcHttp2ConnectionHandler grpcHandler) { this.grpcHandler = grpcHandler; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { try { ProtocolDetectionResult ha = HAProxyMessageDecoder.detectProtocol(in); if (ha.state() == ProtocolDetectionState.NEEDS_MORE_DATA) { return; } if (ha.state() == ProtocolDetectionState.DETECTED) { ctx.pipeline().addAfter(ctx.name(), HA_PROXY_DECODER, new HAProxyMessageDecoder()) .addAfter(HA_PROXY_DECODER, HA_PROXY_HANDLER, new HAProxyMessageHandler()) .addAfter(HA_PROXY_HANDLER, TLS_MODE_HANDLER, new TlsModeHandler(grpcHandler)); } else { ctx.pipeline().addAfter(ctx.name(), TLS_MODE_HANDLER, new TlsModeHandler(grpcHandler)); } Attributes.Builder builder = InternalProtocolNegotiationEvent.getAttributes(pne).toBuilder(); builder.set(AttributeKeys.CHANNEL_ID, ctx.channel().id().asLongText()); ctx.fireUserEventTriggered(InternalProtocolNegotiationEvent.withAttributes(pne, builder.build())); ctx.pipeline().remove(this); } catch (Exception e) { log.error("process proxy protocol negotiator failed.", e); throw e; } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof ProtocolNegotiationEvent) { pne = (ProtocolNegotiationEvent) evt; } else { super.userEventTriggered(ctx, evt); } } } private class HAProxyMessageHandler extends ChannelInboundHandlerAdapter { private ProtocolNegotiationEvent pne = InternalProtocolNegotiationEvent.getDefault(); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HAProxyMessage) { handleWithMessage((HAProxyMessage) msg); ctx.fireUserEventTriggered(pne); } else { super.channelRead(ctx, msg); } ctx.pipeline().remove(this); } /** * The definition of key refers to the implementation of nginx * ngx_http_core_module * * @param msg */ private void handleWithMessage(HAProxyMessage msg) { try { Attributes.Builder builder = InternalProtocolNegotiationEvent.getAttributes(pne).toBuilder(); if (StringUtils.isNotBlank(msg.sourceAddress())) { builder.set(AttributeKeys.PROXY_PROTOCOL_ADDR, msg.sourceAddress()); } if (msg.sourcePort() > 0) { builder.set(AttributeKeys.PROXY_PROTOCOL_PORT, String.valueOf(msg.sourcePort())); } if (StringUtils.isNotBlank(msg.destinationAddress())) { builder.set(AttributeKeys.PROXY_PROTOCOL_SERVER_ADDR, msg.destinationAddress()); } if (msg.destinationPort() > 0) { builder.set(AttributeKeys.PROXY_PROTOCOL_SERVER_PORT, String.valueOf(msg.destinationPort())); } if (CollectionUtils.isNotEmpty(msg.tlvs())) { msg.tlvs().forEach(tlv -> handleHAProxyTLV(tlv, builder)); } pne = InternalProtocolNegotiationEvent .withAttributes(InternalProtocolNegotiationEvent.getDefault(), builder.build()); } finally { msg.release(); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof ProtocolNegotiationEvent) { pne = (ProtocolNegotiationEvent) evt; } else { super.userEventTriggered(ctx, evt); } } } protected void handleHAProxyTLV(HAProxyTLV tlv, Attributes.Builder builder) { byte[] valueBytes = ByteBufUtil.getBytes(tlv.content()); if (!BinaryUtil.isAscii(valueBytes)) { return; } Attributes.Key key = AttributeKeys.valueOf( HAProxyConstants.PROXY_PROTOCOL_TLV_PREFIX + String.format("%02x", tlv.typeByteValue())); builder.set(key, new String(valueBytes, CharsetUtil.UTF_8)); } private class TlsModeHandler extends ByteToMessageDecoder { private ProtocolNegotiationEvent pne = InternalProtocolNegotiationEvent.getDefault(); private final ChannelHandler ssl; private final ChannelHandler plaintext; public TlsModeHandler(GrpcHttp2ConnectionHandler grpcHandler) { this.ssl = InternalProtocolNegotiators.serverTls(sslContext) .newHandler(grpcHandler); this.plaintext = InternalProtocolNegotiators.serverPlaintext() .newHandler(grpcHandler); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { try { TlsMode tlsMode = TlsSystemConfig.tlsMode; if (TlsMode.ENFORCING.equals(tlsMode)) { ctx.pipeline().addAfter(ctx.name(), null, this.ssl); } else if (TlsMode.DISABLED.equals(tlsMode)) { ctx.pipeline().addAfter(ctx.name(), null, this.plaintext); } else { // in SslHandler.isEncrypted, it needs at least 5 bytes to judge is encrypted or not if (in.readableBytes() < SSL_RECORD_HEADER_LENGTH) { return; } if (SslHandler.isEncrypted(in)) { ctx.pipeline().addAfter(ctx.name(), null, this.ssl); } else { ctx.pipeline().addAfter(ctx.name(), null, this.plaintext); } } ctx.fireUserEventTriggered(pne); ctx.pipeline().remove(this); } catch (Exception e) { log.error("process ssl protocol negotiator failed.", e); throw e; } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof ProtocolNegotiationEvent) { pne = (ProtocolNegotiationEvent) evt; } else { super.userEventTriggered(ctx, evt); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/constant/AttributeKeys.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.constant; import io.grpc.Attributes; import org.apache.rocketmq.common.constant.HAProxyConstants; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class AttributeKeys { public static final Attributes.Key CHANNEL_ID = Attributes.Key.create(HAProxyConstants.CHANNEL_ID); public static final Attributes.Key PROXY_PROTOCOL_ADDR = Attributes.Key.create(HAProxyConstants.PROXY_PROTOCOL_ADDR); public static final Attributes.Key PROXY_PROTOCOL_PORT = Attributes.Key.create(HAProxyConstants.PROXY_PROTOCOL_PORT); public static final Attributes.Key PROXY_PROTOCOL_SERVER_ADDR = Attributes.Key.create(HAProxyConstants.PROXY_PROTOCOL_SERVER_ADDR); public static final Attributes.Key PROXY_PROTOCOL_SERVER_PORT = Attributes.Key.create(HAProxyConstants.PROXY_PROTOCOL_SERVER_PORT); private static final Map> ATTRIBUTES_KEY_MAP = new ConcurrentHashMap<>(); public static Attributes.Key valueOf(String name) { return ATTRIBUTES_KEY_MAP.computeIfAbsent(name, key -> Attributes.Key.create(name)); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.interceptor; import io.grpc.Context; import io.grpc.Contexts; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import org.apache.rocketmq.common.constant.GrpcConstants; public class ContextInterceptor implements ServerInterceptor { @Override public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next ) { Context context = Context.current().withValue(GrpcConstants.METADATA, headers); return Contexts.interceptCall(context, call, headers, next); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.interceptor; import io.grpc.ForwardingServerCall; import io.grpc.ForwardingServerCallListener; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.Status; import io.grpc.StatusRuntimeException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class GlobalExceptionInterceptor implements ServerInterceptor { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); @Override public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next ) { final ServerCall serverCall = new ClosableServerCall<>(call); ServerCall.Listener delegate = next.startCall(serverCall, headers); return new ForwardingServerCallListener.SimpleForwardingServerCallListener(delegate) { @Override public void onMessage(R message) { try { super.onMessage(message); } catch (Throwable e) { closeWithException(e); } } @Override public void onHalfClose() { try { super.onHalfClose(); } catch (Throwable e) { closeWithException(e); } } @Override public void onCancel() { try { super.onCancel(); } catch (Throwable e) { closeWithException(e); } } @Override public void onComplete() { try { super.onComplete(); } catch (Throwable e) { closeWithException(e); } } @Override public void onReady() { try { super.onReady(); } catch (Throwable e) { closeWithException(e); } } private void closeWithException(Throwable t) { Metadata trailers = new Metadata(); Status status = Status.INTERNAL.withDescription(t.getMessage()); boolean printLog = true; if (t instanceof StatusRuntimeException) { trailers = ((StatusRuntimeException) t).getTrailers(); status = ((StatusRuntimeException) t).getStatus(); // no error stack for permission denied. if (status.getCode().value() == Status.PERMISSION_DENIED.getCode().value()) { printLog = false; } } if (printLog) { log.error("grpc server has exception. errorMsg:{}, e:", t.getMessage(), t); } serverCall.close(status, trailers); } }; } private static class ClosableServerCall extends ForwardingServerCall.SimpleForwardingServerCall { private boolean closeCalled = false; ClosableServerCall(ServerCall delegate) { super(delegate); } @Override public synchronized void close(final Status status, final Metadata trailers) { if (!closeCalled) { closeCalled = true; ClosableServerCall.super.close(status, trailers); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.interceptor; import com.google.common.net.HostAndPort; import io.grpc.Attributes; import io.grpc.Grpc; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.HAProxyConstants; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.proxy.common.utils.GrpcUtils; import org.apache.rocketmq.proxy.grpc.constant.AttributeKeys; import java.net.InetSocketAddress; import java.net.SocketAddress; public class HeaderInterceptor implements ServerInterceptor { @Override public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next ) { String remoteAddress = getProxyProtocolAddress(call.getAttributes()); if (StringUtils.isBlank(remoteAddress)) { SocketAddress remoteSocketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); remoteAddress = parseSocketAddress(remoteSocketAddress); } GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.REMOTE_ADDRESS, remoteAddress); SocketAddress localSocketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR); String localAddress = parseSocketAddress(localSocketAddress); GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.LOCAL_ADDRESS, localAddress); for (Attributes.Key key : call.getAttributes().keys()) { if (!StringUtils.startsWith(key.toString(), HAProxyConstants.PROXY_PROTOCOL_PREFIX)) { continue; } Metadata.Key headerKey = Metadata.Key.of(key.toString(), Metadata.ASCII_STRING_MARSHALLER); String headerValue = String.valueOf(call.getAttributes().get(key)); GrpcUtils.putHeaderIfNotExist(headers, headerKey, headerValue); } String channelId = call.getAttributes().get(AttributeKeys.CHANNEL_ID); if (StringUtils.isNotBlank(channelId)) { GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.CHANNEL_ID, channelId); } return next.startCall(call, headers); } private String parseSocketAddress(SocketAddress socketAddress) { if (socketAddress instanceof InetSocketAddress) { InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; return HostAndPort.fromParts( inetSocketAddress.getAddress() .getHostAddress(), inetSocketAddress.getPort() ).toString(); } return ""; } private String getProxyProtocolAddress(Attributes attributes) { String proxyProtocolAddr = attributes.get(AttributeKeys.PROXY_PROTOCOL_ADDR); String proxyProtocolPort = attributes.get(AttributeKeys.PROXY_PROTOCOL_PORT); if (StringUtils.isBlank(proxyProtocolAddr) || StringUtils.isBlank(proxyProtocolPort)) { return null; } return proxyProtocolAddr + ":" + proxyProtocolPort; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.interceptor; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.SendMessageRequest; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RequestCode; public class RequestMapping { @SuppressWarnings("DoubleBraceInitialization") private final static Map REQUEST_MAP = new HashMap() { { // v2 put(QueryRouteRequest.getDescriptor().getFullName(), RequestCode.GET_ROUTEINFO_BY_TOPIC); put(HeartbeatRequest.getDescriptor().getFullName(), RequestCode.HEART_BEAT); put(SendMessageRequest.getDescriptor().getFullName(), RequestCode.SEND_MESSAGE_V2); put(RecallMessageRequest.getDescriptor().getFullName(), RequestCode.RECALL_MESSAGE); put(QueryAssignmentRequest.getDescriptor().getFullName(), RequestCode.GET_ROUTEINFO_BY_TOPIC); put(ReceiveMessageRequest.getDescriptor().getFullName(), RequestCode.PULL_MESSAGE); put(AckMessageRequest.getDescriptor().getFullName(), RequestCode.UPDATE_CONSUMER_OFFSET); put(ForwardMessageToDeadLetterQueueResponse.getDescriptor().getFullName(), RequestCode.CONSUMER_SEND_MSG_BACK); put(EndTransactionRequest.getDescriptor().getFullName(), RequestCode.END_TRANSACTION); put(NotifyClientTerminationRequest.getDescriptor().getFullName(), RequestCode.UNREGISTER_CLIENT); put(ChangeInvisibleDurationRequest.getDescriptor().getFullName(), RequestCode.CONSUMER_SEND_MSG_BACK); } }; public static int map(String rpcFullName) { if (REQUEST_MAP.containsKey(rpcFullName)) { return REQUEST_MAP.get(rpcFullName); } return RequestCode.HEART_BEAT; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.pipeline; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Context; import io.grpc.Metadata; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.utils.GrpcUtils; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public class AuthenticationPipeline implements RequestPipeline { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final AuthConfig authConfig; private final AuthenticationEvaluator authenticationEvaluator; public AuthenticationPipeline(AuthConfig authConfig, MessagingProcessor messagingProcessor) { this.authConfig = authConfig; this.authenticationEvaluator = AuthenticationFactory.getEvaluator(authConfig, messagingProcessor::getMetadataService); } @Override public void execute(ProxyContext context, Metadata headers, GeneratedMessageV3 request) { if (!authConfig.isAuthenticationEnabled()) { return; } try { Metadata metadata = GrpcConstants.METADATA.get(Context.current()); AuthenticationContext authenticationContext = newContext(context, metadata, request); authenticationEvaluator.evaluate(authenticationContext); } catch (AuthenticationException ex) { throw ex; } catch (Throwable ex) { LOGGER.error("authenticate failed, request:{}", request, ex); throw ex; } } /** * Create Context, for extension * * @param context for extension * @param headers gRPC headers * @param request * @return */ protected AuthenticationContext newContext(ProxyContext context, Metadata headers, GeneratedMessageV3 request) { AuthenticationContext result = AuthenticationFactory.newContext(authConfig, headers, request); if (result instanceof DefaultAuthenticationContext) { DefaultAuthenticationContext defaultAuthenticationContext = (DefaultAuthenticationContext) result; if (StringUtils.isNotBlank(defaultAuthenticationContext.getUsername())) { GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.AUTHORIZATION_AK, defaultAuthenticationContext.getUsername()); } } return result; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthorizationPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.pipeline; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import java.util.List; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public class AuthorizationPipeline implements RequestPipeline { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final AuthConfig authConfig; private final AuthorizationEvaluator authorizationEvaluator; public AuthorizationPipeline(AuthConfig authConfig, MessagingProcessor messagingProcessor) { this.authConfig = authConfig; this.authorizationEvaluator = AuthorizationFactory.getEvaluator(authConfig, messagingProcessor::getMetadataService); } @Override public void execute(ProxyContext context, Metadata headers, GeneratedMessageV3 request) { if (!authConfig.isAuthorizationEnabled()) { return; } try { List contexts = newContexts(context, headers, request); authorizationEvaluator.evaluate(contexts); } catch (AuthorizationException | AuthenticationException ex) { throw ex; } catch (Throwable ex) { LOGGER.error("authorize failed, request:{}", request, ex); throw ex; } } protected List newContexts(ProxyContext context, Metadata headers, GeneratedMessageV3 request) { return AuthorizationFactory.newContexts(authConfig, headers, request); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/ContextInitPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.pipeline; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Context; import io.grpc.Metadata; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; public class ContextInitPipeline implements RequestPipeline { @Override public void execute(ProxyContext context, Metadata headers, GeneratedMessageV3 request) { Context ctx = Context.current(); context.setLocalAddress(getDefaultStringMetadataInfo(headers, GrpcConstants.LOCAL_ADDRESS)) .setRemoteAddress(getDefaultStringMetadataInfo(headers, GrpcConstants.REMOTE_ADDRESS)) .setClientID(getDefaultStringMetadataInfo(headers, GrpcConstants.CLIENT_ID)) .setProtocolType(ChannelProtocolType.GRPC_V2.getName()) .setLanguage(getDefaultStringMetadataInfo(headers, GrpcConstants.LANGUAGE)) .setClientVersion(getDefaultStringMetadataInfo(headers, GrpcConstants.CLIENT_VERSION)) .setAction(getDefaultStringMetadataInfo(headers, GrpcConstants.SIMPLE_RPC_NAME)) .setNamespace(getDefaultStringMetadataInfo(headers, GrpcConstants.NAMESPACE_ID)); if (ctx.getDeadline() != null) { context.setRemainingMs(ctx.getDeadline().timeRemaining(TimeUnit.MILLISECONDS)); } } protected String getDefaultStringMetadataInfo(Metadata headers, Metadata.Key key) { return StringUtils.defaultString(headers.get(key)); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/RequestPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.pipeline; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Metadata; import org.apache.rocketmq.proxy.common.ProxyContext; public interface RequestPipeline { void execute(ProxyContext context, Metadata headers, GeneratedMessageV3 request); default RequestPipeline pipe(RequestPipeline source) { return (ctx, headers, request) -> { source.execute(ctx, headers, request); execute(ctx, headers, request); }; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessagingActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import apache.rocketmq.v2.Resource; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcValidator; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public abstract class AbstractMessagingActivity { protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final MessagingProcessor messagingProcessor; protected final GrpcClientSettingsManager grpcClientSettingsManager; protected final GrpcChannelManager grpcChannelManager; public AbstractMessagingActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { this.messagingProcessor = messagingProcessor; this.grpcClientSettingsManager = grpcClientSettingsManager; this.grpcChannelManager = grpcChannelManager; } protected void validateTopic(Resource topic) { GrpcValidator.getInstance().validateTopic(topic); } protected void validateLiteTopic(String liteTopic) { GrpcValidator.getInstance().validateLiteTopic(liteTopic); } protected void validateConsumerGroup(Resource consumerGroup) { GrpcValidator.getInstance().validateConsumerGroup(consumerGroup); } protected void validateTopicAndConsumerGroup(Resource topic, Resource consumerGroup) { GrpcValidator.getInstance().validateTopicAndConsumerGroup(topic, consumerGroup); } protected void validateInvisibleTime(long invisibleTime) { GrpcValidator.getInstance().validateInvisibleTime(invisibleTime); } protected void validateInvisibleTime(long invisibleTime, long minInvisibleTime) { GrpcValidator.getInstance().validateInvisibleTime(invisibleTime, minInvisibleTime); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/ContextStreamObserver.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import org.apache.rocketmq.proxy.common.ProxyContext; public interface ContextStreamObserver { void onNext(ProxyContext ctx, V value); void onError(Throwable t); void onCompleted(); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessagingActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.AckMessageResponse; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ChangeInvisibleDurationResponse; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.EndTransactionResponse; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.HeartbeatResponse; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.NotifyClientTerminationResponse; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryAssignmentResponse; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.RecallMessageResponse; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.SendMessageResponse; import apache.rocketmq.v2.SyncLiteSubscriptionRequest; import apache.rocketmq.v2.SyncLiteSubscriptionResponse; import apache.rocketmq.v2.TelemetryCommand; import io.grpc.stub.StreamObserver; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.client.ClientActivity; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.consumer.AckMessageActivity; import org.apache.rocketmq.proxy.grpc.v2.consumer.ChangeInvisibleDurationActivity; import org.apache.rocketmq.proxy.grpc.v2.consumer.ReceiveMessageActivity; import org.apache.rocketmq.proxy.grpc.v2.producer.ForwardMessageToDLQActivity; import org.apache.rocketmq.proxy.grpc.v2.producer.RecallMessageActivity; import org.apache.rocketmq.proxy.grpc.v2.producer.SendMessageActivity; import org.apache.rocketmq.proxy.grpc.v2.route.RouteActivity; import org.apache.rocketmq.proxy.grpc.v2.transaction.EndTransactionActivity; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public class DefaultGrpcMessagingActivity extends AbstractStartAndShutdown implements GrpcMessagingActivity { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected GrpcClientSettingsManager grpcClientSettingsManager; protected GrpcChannelManager grpcChannelManager; protected ReceiveMessageActivity receiveMessageActivity; protected AckMessageActivity ackMessageActivity; protected ChangeInvisibleDurationActivity changeInvisibleDurationActivity; protected SendMessageActivity sendMessageActivity; protected RecallMessageActivity recallMessageActivity; protected ForwardMessageToDLQActivity forwardMessageToDLQActivity; protected EndTransactionActivity endTransactionActivity; protected RouteActivity routeActivity; protected ClientActivity clientActivity; protected DefaultGrpcMessagingActivity(MessagingProcessor messagingProcessor) { this.init(messagingProcessor); } protected void init(MessagingProcessor messagingProcessor) { this.grpcClientSettingsManager = new GrpcClientSettingsManager(messagingProcessor); this.grpcChannelManager = new GrpcChannelManager(messagingProcessor.getProxyRelayService(), this.grpcClientSettingsManager); this.receiveMessageActivity = new ReceiveMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.ackMessageActivity = new AckMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.changeInvisibleDurationActivity = new ChangeInvisibleDurationActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.sendMessageActivity = new SendMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.recallMessageActivity = new RecallMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.forwardMessageToDLQActivity = new ForwardMessageToDLQActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.endTransactionActivity = new EndTransactionActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.routeActivity = new RouteActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.clientActivity = new ClientActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.appendStartAndShutdown(this.grpcClientSettingsManager); } @Override public CompletableFuture queryRoute(ProxyContext ctx, QueryRouteRequest request) { return this.routeActivity.queryRoute(ctx, request); } @Override public CompletableFuture heartbeat(ProxyContext ctx, HeartbeatRequest request) { return this.clientActivity.heartbeat(ctx, request); } @Override public CompletableFuture sendMessage(ProxyContext ctx, SendMessageRequest request) { return this.sendMessageActivity.sendMessage(ctx, request); } @Override public CompletableFuture queryAssignment(ProxyContext ctx, QueryAssignmentRequest request) { return this.routeActivity.queryAssignment(ctx, request); } @Override public void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request, StreamObserver responseObserver) { this.receiveMessageActivity.receiveMessage(ctx, request, responseObserver); } @Override public CompletableFuture ackMessage(ProxyContext ctx, AckMessageRequest request) { return this.ackMessageActivity.ackMessage(ctx, request); } @Override public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, ForwardMessageToDeadLetterQueueRequest request) { return this.forwardMessageToDLQActivity.forwardMessageToDeadLetterQueue(ctx, request); } @Override public CompletableFuture endTransaction(ProxyContext ctx, EndTransactionRequest request) { return this.endTransactionActivity.endTransaction(ctx, request); } @Override public CompletableFuture notifyClientTermination(ProxyContext ctx, NotifyClientTerminationRequest request) { return this.clientActivity.notifyClientTermination(ctx, request); } @Override public CompletableFuture changeInvisibleDuration(ProxyContext ctx, ChangeInvisibleDurationRequest request) { return this.changeInvisibleDurationActivity.changeInvisibleDuration(ctx, request); } @Override public CompletableFuture recallMessage(ProxyContext ctx, RecallMessageRequest request) { return this.recallMessageActivity.recallMessage(ctx, request); } @Override public CompletableFuture syncLiteSubscription(ProxyContext ctx, SyncLiteSubscriptionRequest request) { return this.clientActivity.syncLiteSubscription(ctx, request); } @Override public ContextStreamObserver telemetry(StreamObserver responseObserver) { return this.clientActivity.telemetry(responseObserver); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.AckMessageResponse; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ChangeInvisibleDurationResponse; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.EndTransactionResponse; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.HeartbeatResponse; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.NotifyClientTerminationResponse; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryAssignmentResponse; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.RecallMessageResponse; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.SendMessageResponse; import apache.rocketmq.v2.SyncLiteSubscriptionRequest; import apache.rocketmq.v2.SyncLiteSubscriptionResponse; import apache.rocketmq.v2.TelemetryCommand; import io.grpc.stub.StreamObserver; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.common.utils.StartAndShutdown; public interface GrpcMessagingActivity extends StartAndShutdown { CompletableFuture queryRoute(ProxyContext ctx, QueryRouteRequest request); CompletableFuture heartbeat(ProxyContext ctx, HeartbeatRequest request); CompletableFuture sendMessage(ProxyContext ctx, SendMessageRequest request); CompletableFuture queryAssignment(ProxyContext ctx, QueryAssignmentRequest request); void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request, StreamObserver responseObserver); CompletableFuture ackMessage(ProxyContext ctx, AckMessageRequest request); CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, ForwardMessageToDeadLetterQueueRequest request); CompletableFuture endTransaction(ProxyContext ctx, EndTransactionRequest request); CompletableFuture notifyClientTermination(ProxyContext ctx, NotifyClientTerminationRequest request); CompletableFuture changeInvisibleDuration(ProxyContext ctx, ChangeInvisibleDurationRequest request); CompletableFuture recallMessage(ProxyContext ctx, RecallMessageRequest request); CompletableFuture syncLiteSubscription(ProxyContext ctx, SyncLiteSubscriptionRequest request); ContextStreamObserver telemetry(StreamObserver responseObserver); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.AckMessageResponse; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ChangeInvisibleDurationResponse; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.EndTransactionResponse; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.HeartbeatResponse; import apache.rocketmq.v2.MessagingServiceGrpc; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.NotifyClientTerminationResponse; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryAssignmentResponse; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.RecallMessageResponse; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.SendMessageResponse; import apache.rocketmq.v2.Status; import apache.rocketmq.v2.SyncLiteSubscriptionRequest; import apache.rocketmq.v2.SyncLiteSubscriptionResponse; import apache.rocketmq.v2.TelemetryCommand; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Context; import io.grpc.stub.StreamObserver; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.pipeline.AuthenticationPipeline; import org.apache.rocketmq.proxy.grpc.pipeline.AuthorizationPipeline; import org.apache.rocketmq.proxy.grpc.pipeline.ContextInitPipeline; import org.apache.rocketmq.proxy.grpc.pipeline.RequestPipeline; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public class GrpcMessagingApplication extends MessagingServiceGrpc.MessagingServiceImplBase implements StartAndShutdown { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final GrpcMessagingActivity grpcMessagingActivity; protected final RequestPipeline requestPipeline; protected ThreadPoolExecutor routeThreadPoolExecutor; protected ThreadPoolExecutor producerThreadPoolExecutor; protected ThreadPoolExecutor consumerThreadPoolExecutor; protected ThreadPoolExecutor clientManagerThreadPoolExecutor; protected ThreadPoolExecutor transactionThreadPoolExecutor; protected GrpcMessagingApplication(GrpcMessagingActivity grpcMessagingActivity, RequestPipeline requestPipeline) { this.grpcMessagingActivity = grpcMessagingActivity; this.requestPipeline = requestPipeline; ProxyConfig config = ConfigurationManager.getProxyConfig(); this.routeThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor( config.getGrpcRouteThreadPoolNums(), config.getGrpcRouteThreadPoolNums(), 1, TimeUnit.MINUTES, "GrpcRouteThreadPool", config.getGrpcRouteThreadQueueCapacity() ); this.producerThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor( config.getGrpcProducerThreadPoolNums(), config.getGrpcProducerThreadPoolNums(), 1, TimeUnit.MINUTES, "GrpcProducerThreadPool", config.getGrpcProducerThreadQueueCapacity() ); this.consumerThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor( config.getGrpcConsumerThreadPoolNums(), config.getGrpcConsumerThreadPoolNums(), 1, TimeUnit.MINUTES, "GrpcConsumerThreadPool", config.getGrpcConsumerThreadQueueCapacity() ); this.clientManagerThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor( config.getGrpcClientManagerThreadPoolNums(), config.getGrpcClientManagerThreadPoolNums(), 1, TimeUnit.MINUTES, "GrpcClientManagerThreadPool", config.getGrpcClientManagerThreadQueueCapacity() ); this.transactionThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor( config.getGrpcTransactionThreadPoolNums(), config.getGrpcTransactionThreadPoolNums(), 1, TimeUnit.MINUTES, "GrpcTransactionThreadPool", config.getGrpcTransactionThreadQueueCapacity() ); this.init(); } protected void init() { GrpcTaskRejectedExecutionHandler rejectedExecutionHandler = new GrpcTaskRejectedExecutionHandler(); this.routeThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler); this.producerThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler); this.consumerThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler); this.clientManagerThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler); this.transactionThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler); } public static GrpcMessagingApplication create(MessagingProcessor messagingProcessor) { RequestPipeline pipeline = (context, headers, request) -> { }; // add pipeline // the last pipe add will execute at the first AuthConfig authConfig = ConfigurationManager.getAuthConfig(); if (authConfig != null) { pipeline = pipeline .pipe(new AuthorizationPipeline(authConfig, messagingProcessor)) .pipe(new AuthenticationPipeline(authConfig, messagingProcessor)); } pipeline = pipeline.pipe(new ContextInitPipeline()); return new GrpcMessagingApplication(new DefaultGrpcMessagingActivity(messagingProcessor), pipeline); } protected Status flowLimitStatus() { return ResponseBuilder.getInstance().buildStatus(Code.TOO_MANY_REQUESTS, "flow limit"); } protected Status convertExceptionToStatus(Throwable t) { return ResponseBuilder.getInstance().buildStatus(t); } protected void addExecutor(ExecutorService executor, ProxyContext context, V request, Runnable runnable, StreamObserver responseObserver, Function statusResponseCreator) { if (request instanceof GeneratedMessageV3) { requestPipeline.execute(context, GrpcConstants.METADATA.get(Context.current()), (GeneratedMessageV3) request); validateContext(context); } else { log.error("[BUG]grpc request pipe is not been executed"); } executor.submit(new GrpcTask<>(runnable, context, request, responseObserver, statusResponseCreator.apply(flowLimitStatus()))); } protected void writeResponse(ProxyContext context, V request, T response, StreamObserver responseObserver, Throwable t, Function errorResponseCreator) { if (t != null) { ResponseWriter.getInstance().write( responseObserver, errorResponseCreator.apply(convertExceptionToStatus(t)) ); } else { ResponseWriter.getInstance().write(responseObserver, response); } } protected ProxyContext createContext() { return ProxyContext.create(); } protected void validateContext(ProxyContext context) { if (StringUtils.isBlank(context.getClientID())) { throw new GrpcProxyException(Code.CLIENT_ID_REQUIRED, "client id cannot be empty"); } } @Override public void queryRoute(QueryRouteRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> QueryRouteResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.routeThreadPoolExecutor, context, request, () -> grpcMessagingActivity.queryRoute(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void heartbeat(HeartbeatRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> HeartbeatResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.clientManagerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.heartbeat(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void sendMessage(SendMessageRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> SendMessageResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.producerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.sendMessage(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void queryAssignment(QueryAssignmentRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> QueryAssignmentResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.routeThreadPoolExecutor, context, request, () -> grpcMessagingActivity.queryAssignment(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void receiveMessage(ReceiveMessageRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> ReceiveMessageResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.consumerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.receiveMessage(context, request, responseObserver), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void ackMessage(AckMessageRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> AckMessageResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.consumerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.ackMessage(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void forwardMessageToDeadLetterQueue(ForwardMessageToDeadLetterQueueRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> ForwardMessageToDeadLetterQueueResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.producerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.forwardMessageToDeadLetterQueue(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void endTransaction(EndTransactionRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> EndTransactionResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.transactionThreadPoolExecutor, context, request, () -> grpcMessagingActivity.endTransaction(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void notifyClientTermination(NotifyClientTerminationRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> NotifyClientTerminationResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.clientManagerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.notifyClientTermination(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void changeInvisibleDuration(ChangeInvisibleDurationRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> { ChangeInvisibleDurationResponse.Builder builder = ChangeInvisibleDurationResponse.newBuilder().setStatus(status); if (Code.TOO_MANY_REQUESTS.equals(status.getCode())) { builder.setReceiptHandle(request.getReceiptHandle()); } return builder.build(); }; ProxyContext context = createContext(); try { this.addExecutor(this.consumerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.changeInvisibleDuration(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void recallMessage(RecallMessageRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> RecallMessageResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.producerThreadPoolExecutor, // reuse producer thread pool context, request, () -> grpcMessagingActivity.recallMessage(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public void syncLiteSubscription(SyncLiteSubscriptionRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> SyncLiteSubscriptionResponse.newBuilder().setStatus(status).build(); ProxyContext context = createContext(); try { this.addExecutor(this.clientManagerThreadPoolExecutor, context, request, () -> grpcMessagingActivity.syncLiteSubscription(context, request) .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, request, null, responseObserver, t, statusResponseCreator); } } @Override public StreamObserver telemetry(StreamObserver responseObserver) { Function statusResponseCreator = status -> TelemetryCommand.newBuilder().setStatus(status).build(); ContextStreamObserver responseTelemetryCommand = grpcMessagingActivity.telemetry(responseObserver); return new StreamObserver() { @Override public void onNext(TelemetryCommand value) { ProxyContext context = createContext(); try { addExecutor(clientManagerThreadPoolExecutor, context, value, () -> responseTelemetryCommand.onNext(context, value), responseObserver, statusResponseCreator); } catch (Throwable t) { writeResponse(context, value, null, responseObserver, t, statusResponseCreator); } } @Override public void onError(Throwable t) { responseTelemetryCommand.onError(t); } @Override public void onCompleted() { responseTelemetryCommand.onCompleted(); } }; } @Override public void shutdown() throws Exception { this.grpcMessagingActivity.shutdown(); this.routeThreadPoolExecutor.shutdown(); this.producerThreadPoolExecutor.shutdown(); this.consumerThreadPoolExecutor.shutdown(); this.clientManagerThreadPoolExecutor.shutdown(); this.transactionThreadPoolExecutor.shutdown(); } @Override public void start() throws Exception { this.grpcMessagingActivity.start(); } protected static class GrpcTask implements Runnable { protected final Runnable runnable; protected final ProxyContext context; protected final V request; protected final T executeRejectResponse; protected final StreamObserver streamObserver; public GrpcTask(Runnable runnable, ProxyContext context, V request, StreamObserver streamObserver, T executeRejectResponse) { this.runnable = runnable; this.context = context; this.streamObserver = streamObserver; this.request = request; this.executeRejectResponse = executeRejectResponse; } @Override public void run() { this.runnable.run(); } } protected class GrpcTaskRejectedExecutionHandler implements RejectedExecutionHandler { public GrpcTaskRejectedExecutionHandler() { } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { if (r instanceof GrpcTask) { try { GrpcTask grpcTask = (GrpcTask) r; writeResponse(grpcTask.context, grpcTask.request, grpcTask.executeRejectResponse, grpcTask.streamObserver, null, null); } catch (Throwable t) { log.warn("write rejected error response failed", t); } } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.channel; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.remoting.protocol.ResponseCode; public class GrpcChannelManager implements StartAndShutdown { private final ProxyRelayService proxyRelayService; private final GrpcClientSettingsManager grpcClientSettingsManager; protected final ConcurrentMap clientIdChannelMap = new ConcurrentHashMap<>(); protected final AtomicLong nonceIdGenerator = new AtomicLong(0); protected final ConcurrentMap resultNonceFutureMap = new ConcurrentHashMap<>(); protected final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryImpl("GrpcChannelManager_") ); public GrpcChannelManager(ProxyRelayService proxyRelayService, GrpcClientSettingsManager grpcClientSettingsManager) { this.proxyRelayService = proxyRelayService; this.grpcClientSettingsManager = grpcClientSettingsManager; this.init(); } protected void init() { this.scheduledExecutorService.scheduleAtFixedRate( this::scanExpireResultFuture, 10, 1, TimeUnit.SECONDS ); } public GrpcClientChannel createChannel(ProxyContext ctx, String clientId) { return this.clientIdChannelMap.computeIfAbsent(clientId, k -> new GrpcClientChannel(proxyRelayService, grpcClientSettingsManager, this, ctx, clientId)); } public GrpcClientChannel getChannel(String clientId) { return clientIdChannelMap.get(clientId); } public GrpcClientChannel removeChannel(String clientId) { return this.clientIdChannelMap.remove(clientId); } public String addResponseFuture(CompletableFuture> responseFuture) { String nonce = this.nextNonce(); this.resultNonceFutureMap.put(nonce, new ResultFuture<>(responseFuture)); return nonce; } public CompletableFuture> getAndRemoveResponseFuture(String nonce) { ResultFuture resultFuture = this.resultNonceFutureMap.remove(nonce); if (resultFuture != null) { return resultFuture.future; } return null; } protected String nextNonce() { return String.valueOf(this.nonceIdGenerator.getAndIncrement()); } protected void scanExpireResultFuture() { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); long timeOutMs = TimeUnit.SECONDS.toMillis(proxyConfig.getGrpcProxyRelayRequestTimeoutInSeconds()); Set nonceSet = this.resultNonceFutureMap.keySet(); for (String nonce : nonceSet) { ResultFuture resultFuture = this.resultNonceFutureMap.get(nonce); if (resultFuture == null) { continue; } if (System.currentTimeMillis() - resultFuture.createTime > timeOutMs) { resultFuture = this.resultNonceFutureMap.remove(nonce); if (resultFuture != null) { resultFuture.future.complete(new ProxyRelayResult<>(ResponseCode.SYSTEM_BUSY, "call remote timeout", null)); } } } } @Override public void shutdown() throws Exception { this.scheduledExecutorService.shutdown(); } @Override public void start() throws Exception { } protected static class ResultFuture { public CompletableFuture> future; public long createTime = System.currentTimeMillis(); public ResultFuture(CompletableFuture> future) { this.future = future; } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.channel; import apache.rocketmq.v2.NotifyUnsubscribeLiteCommand; import apache.rocketmq.v2.PrintThreadStackTraceCommand; import apache.rocketmq.v2.RecoverOrphanedTransactionCommand; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.TelemetryCommand; import apache.rocketmq.v2.VerifyMessageCommand; import com.google.common.base.MoreObjects; import com.google.common.collect.ComparisonChain; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.TextFormat; import com.google.protobuf.util.JsonFormat; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import io.netty.channel.Channel; import io.netty.channel.ChannelId; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.channel.ChannelHelper; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter; import org.apache.rocketmq.proxy.processor.channel.ChannelExtendAttributeGetter; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; import org.apache.rocketmq.proxy.processor.channel.RemoteChannel; import org.apache.rocketmq.proxy.processor.channel.RemoteChannelConverter; import org.apache.rocketmq.proxy.service.relay.ProxyChannel; import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; public class GrpcClientChannel extends ProxyChannel implements ChannelExtendAttributeGetter, RemoteChannelConverter { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final GrpcChannelManager grpcChannelManager; private final GrpcClientSettingsManager grpcClientSettingsManager; private final AtomicReference> telemetryCommandRef = new AtomicReference<>(); private final Object telemetryWriteLock = new Object(); private final String clientId; public GrpcClientChannel(ProxyRelayService proxyRelayService, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager, ProxyContext ctx, String clientId) { super(proxyRelayService, null, new GrpcChannelId(clientId), ctx.getRemoteAddress(), ctx.getLocalAddress()); this.grpcChannelManager = grpcChannelManager; this.grpcClientSettingsManager = grpcClientSettingsManager; this.clientId = clientId; } @Override public String getChannelExtendAttribute() { Settings settings = this.grpcClientSettingsManager.getRawClientSettings(this.clientId); if (settings == null) { return null; } try { return JsonFormat.printer().print(settings); } catch (InvalidProtocolBufferException e) { log.error("convert settings to json data failed. settings:{}", settings, e); } return null; } public static Settings parseChannelExtendAttribute(Channel channel) { if (ChannelHelper.getChannelProtocolType(channel).equals(ChannelProtocolType.GRPC_V2) && channel instanceof ChannelExtendAttributeGetter) { String attr = ((ChannelExtendAttributeGetter) channel).getChannelExtendAttribute(); if (attr == null) { return null; } Settings.Builder builder = Settings.newBuilder(); try { JsonFormat.parser().merge(attr, builder); return builder.build(); } catch (InvalidProtocolBufferException e) { log.error("convert settings json data to settings failed. data:{}", attr, e); return null; } } return null; } @Override public RemoteChannel toRemoteChannel() { return new RemoteChannel( ConfigurationManager.getProxyConfig().getLocalServeAddr(), this.getRemoteAddress(), this.getLocalAddress(), ChannelProtocolType.GRPC_V2, this.getChannelExtendAttribute()); } protected static class GrpcChannelId implements ChannelId { private final String clientId; public GrpcChannelId(String clientId) { this.clientId = clientId; } @Override public String asShortText() { return this.clientId; } @Override public String asLongText() { return this.clientId; } @Override public int compareTo(ChannelId o) { if (this == o) { return 0; } if (o instanceof GrpcChannelId) { GrpcChannelId other = (GrpcChannelId) o; return ComparisonChain.start() .compare(this.clientId, other.clientId) .result(); } return asLongText().compareTo(o.asLongText()); } } public void setClientObserver(StreamObserver future) { this.telemetryCommandRef.set(future); } protected void clearClientObserver(StreamObserver future) { this.telemetryCommandRef.compareAndSet(future, null); } @Override public boolean isOpen() { return this.telemetryCommandRef.get() != null; } @Override public boolean isActive() { return this.telemetryCommandRef.get() != null; } @Override public boolean isWritable() { return this.telemetryCommandRef.get() != null; } @Override protected CompletableFuture processOtherMessage(Object msg) { if (msg instanceof TelemetryCommand) { TelemetryCommand response = (TelemetryCommand) msg; this.writeTelemetryCommand(response); } return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processCheckTransaction(CheckTransactionStateRequestHeader header, MessageExt messageExt, TransactionData transactionData, CompletableFuture> responseFuture) { CompletableFuture writeFuture = new CompletableFuture<>(); try { this.writeTelemetryCommand(TelemetryCommand.newBuilder() .setRecoverOrphanedTransactionCommand(RecoverOrphanedTransactionCommand.newBuilder() .setTransactionId(transactionData.getTransactionId()) .setMessage(GrpcConverter.getInstance().buildMessage(messageExt)) .build()) .build()); responseFuture.complete(null); writeFuture.complete(null); } catch (Throwable t) { responseFuture.completeExceptionally(t); writeFuture.completeExceptionally(t); } return writeFuture; } @Override protected CompletableFuture processNotifyUnsubscribeLite(NotifyUnsubscribeLiteRequestHeader header) { final String group = header.getConsumerGroup(); final String liteTopic = header.getLiteTopic(); NotifyUnsubscribeLiteCommand unsubscribeLiteCommand = NotifyUnsubscribeLiteCommand.newBuilder() .setLiteTopic(liteTopic) .build(); TelemetryCommand telemetryCommand = TelemetryCommand.newBuilder() .setNotifyUnsubscribeLiteCommand(unsubscribeLiteCommand) .build(); this.writeTelemetryCommand(telemetryCommand); log.info("notifyUnsubscribeLite liteTopic:{} group:{} clientId:{}", liteTopic, group, clientId); return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processGetConsumerRunningInfo(RemotingCommand command, GetConsumerRunningInfoRequestHeader header, CompletableFuture> responseFuture) { if (Objects.isNull(header) || !header.isJstackEnable()) { return CompletableFuture.completedFuture(null); } this.writeTelemetryCommand(TelemetryCommand.newBuilder() .setPrintThreadStackTraceCommand(PrintThreadStackTraceCommand.newBuilder() .setNonce(this.grpcChannelManager.addResponseFuture(responseFuture)) .build()) .build()); return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processConsumeMessageDirectly(RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header, MessageExt messageExt, CompletableFuture> responseFuture) { this.writeTelemetryCommand(TelemetryCommand.newBuilder() .setVerifyMessageCommand(VerifyMessageCommand.newBuilder() .setNonce(this.grpcChannelManager.addResponseFuture(responseFuture)) .setMessage(GrpcConverter.getInstance().buildMessage(messageExt)) .build()) .build()); return CompletableFuture.completedFuture(null); } public String getClientId() { return clientId; } public void writeTelemetryCommand(TelemetryCommand command) { StreamObserver observer = this.telemetryCommandRef.get(); if (observer == null) { log.warn("telemetry command observer is null when try to write data. command:{}, channel:{}", TextFormat.shortDebugString(command), this); return; } synchronized (this.telemetryWriteLock) { observer = this.telemetryCommandRef.get(); if (observer == null) { log.warn("telemetry command observer is null when try to write data. command:{}, channel:{}", TextFormat.shortDebugString(command), this); return; } try { observer.onNext(command); } catch (StatusRuntimeException | IllegalStateException exception) { log.warn("write telemetry failed. command:{}", command, exception); this.clearClientObserver(observer); } } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("clientId", clientId) .add("remoteAddress", getRemoteAddress()) .add("localAddress", getLocalAddress()) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.client; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.FilterExpression; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.HeartbeatResponse; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.NotifyClientTerminationResponse; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Status; import apache.rocketmq.v2.SubscriptionEntry; import apache.rocketmq.v2.SyncLiteSubscriptionRequest; import apache.rocketmq.v2.SyncLiteSubscriptionResponse; import apache.rocketmq.v2.TelemetryCommand; import apache.rocketmq.v2.ThreadStackTrace; import apache.rocketmq.v2.VerifyMessageResult; import com.google.common.collect.ImmutableSet; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import io.netty.channel.Channel; import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ProducerChangeListener; import org.apache.rocketmq.broker.client.ProducerGroupEvent; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.lite.LiteSubscriptionAction; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.lite.OffsetOption; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.channel.ChannelHelper; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.ContextStreamObserver; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ClientActivity extends AbstractMessagingActivity { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); public ClientActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); this.init(); } protected void init() { this.messagingProcessor.registerConsumerListener(new ConsumerIdsChangeListenerImpl()); this.messagingProcessor.registerProducerListener(new ProducerChangeListenerImpl()); } public CompletableFuture heartbeat(ProxyContext ctx, HeartbeatRequest request) { CompletableFuture future = new CompletableFuture<>(); try { Settings clientSettings = grpcClientSettingsManager.getClientSettings(ctx); if (clientSettings == null) { future.complete(HeartbeatResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, "cannot find client settings for this client")) .build()); return future; } switch (clientSettings.getClientType()) { case PRODUCER: { for (Resource topic : clientSettings.getPublishing().getTopicsList()) { String topicName = topic.getName(); this.registerProducer(ctx, topicName); } break; } case PUSH_CONSUMER: case LITE_PUSH_CONSUMER: case SIMPLE_CONSUMER: { validateConsumerGroup(request.getGroup()); String consumerGroup = request.getGroup().getName(); this.registerConsumer(ctx, consumerGroup, clientSettings.getClientType(), clientSettings.getSubscription().getSubscriptionsList(), false); break; } default: { future.complete(HeartbeatResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, clientSettings.getClientType().name())) .build()); return future; } } future.complete(HeartbeatResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); return future; } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture notifyClientTermination(ProxyContext ctx, NotifyClientTerminationRequest request) { CompletableFuture future = new CompletableFuture<>(); try { String clientId = ctx.getClientID(); LanguageCode languageCode = LanguageCode.valueOf(ctx.getLanguage()); Settings clientSettings = grpcClientSettingsManager.removeAndGetClientSettings(ctx); if (clientSettings == null) { future.complete(NotifyClientTerminationResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, "cannot find client settings for this client")) .build()); return future; } switch (clientSettings.getClientType()) { case PRODUCER: for (Resource topic : clientSettings.getPublishing().getTopicsList()) { String topicName = topic.getName(); GrpcClientChannel channel = this.grpcChannelManager.removeChannel(clientId); if (channel != null) { ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal()); this.messagingProcessor.unRegisterProducer(ctx, topicName, clientChannelInfo); } } break; case PUSH_CONSUMER: case LITE_PUSH_CONSUMER: case SIMPLE_CONSUMER: validateConsumerGroup(request.getGroup()); String consumerGroup = request.getGroup().getName(); GrpcClientChannel channel = this.grpcChannelManager.removeChannel(clientId); if (channel != null) { ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal()); this.messagingProcessor.unRegisterConsumer(ctx, consumerGroup, clientChannelInfo); this.grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, clientSettings); } break; default: future.complete(NotifyClientTerminationResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, clientSettings.getClientType().name())) .build()); return future; } future.complete(NotifyClientTerminationResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture syncLiteSubscription(ProxyContext ctx, SyncLiteSubscriptionRequest request) { try { validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); final LiteSubscriptionAction action = toLiteAction(request.getAction()); final Set liteTopicSet = ImmutableSet.copyOf(request.getLiteTopicSetList()); if (LiteSubscriptionAction.PARTIAL_ADD == action) { for (String liteTopic : liteTopicSet) { validateLiteTopic(liteTopic); } } final String group = request.getGroup().getName(); final String topic = request.getTopic().getName(); LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO() .setAction(action) .setClientId(ctx.getClientID()) .setGroup(group) .setTopic(topic) .setLiteTopicSet(liteTopicSet) .setVersion(request.getVersion()); if (LiteSubscriptionAction.PARTIAL_ADD == action) { if (request.hasOffsetOption()) { liteSubscriptionDTO.setOffsetOption(toOffsetOption(request.getOffsetOption())); } } return this.messagingProcessor .syncLiteSubscription(ctx, liteSubscriptionDTO, Duration.ofSeconds(2).toMillis()) .thenApply(v -> SyncLiteSubscriptionResponse .newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, null)) .build() ); } catch (Throwable t) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(t); return future; } } private static OffsetOption toOffsetOption(apache.rocketmq.v2.OffsetOption gRpcOffsetOption) { OffsetOption offsetOption = new OffsetOption(); switch (gRpcOffsetOption.getOffsetTypeCase()) { case POLICY: offsetOption.setType(OffsetOption.Type.POLICY); offsetOption.setValue(toOffsetPolicy(gRpcOffsetOption.getPolicy())); break; case OFFSET: offsetOption.setType(OffsetOption.Type.OFFSET); offsetOption.setValue(gRpcOffsetOption.getOffset()); break; case TAIL_N: offsetOption.setType(OffsetOption.Type.TAIL_N); offsetOption.setValue(gRpcOffsetOption.getTailN()); break; case TIMESTAMP: offsetOption.setType(OffsetOption.Type.TIMESTAMP); offsetOption.setValue(gRpcOffsetOption.getTimestamp()); break; default: throw new IllegalArgumentException("Unknown OffsetOption type: " + gRpcOffsetOption.getOffsetTypeCase()); } return offsetOption; } private static long toOffsetPolicy(apache.rocketmq.v2.OffsetOption.Policy policy) { switch (policy) { case LAST: return OffsetOption.POLICY_LAST_VALUE; case MIN: return OffsetOption.POLICY_MIN_VALUE; case MAX: return OffsetOption.POLICY_MAX_VALUE; } throw new IllegalArgumentException("Unknown OffsetOption.Policy value: " + policy); } public ContextStreamObserver telemetry(StreamObserver responseObserver) { return new ContextStreamObserver() { private ProxyContext proxyCtx = null; @Override public void onNext(ProxyContext ctx, TelemetryCommand request) { this.proxyCtx = ctx; try { switch (request.getCommandCase()) { case SETTINGS: { processAndWriteClientSettings(ctx, request, responseObserver); break; } case THREAD_STACK_TRACE: { reportThreadStackTrace(ctx, request.getStatus(), request.getThreadStackTrace()); break; } case VERIFY_MESSAGE_RESULT: { reportVerifyMessageResult(ctx, request.getStatus(), request.getVerifyMessageResult()); break; } } } catch (Throwable t) { processTelemetryException(request, t, responseObserver); } } @Override public void onError(Throwable t) { log.error("telemetry on error", t); handleGrpcCancel(proxyCtx, t); } @Override public void onCompleted() { responseObserver.onCompleted(); } }; } private static LiteSubscriptionAction toLiteAction(apache.rocketmq.v2.LiteSubscriptionAction gRpcAction) { switch (gRpcAction) { case PARTIAL_ADD: return LiteSubscriptionAction.PARTIAL_ADD; case PARTIAL_REMOVE: return LiteSubscriptionAction.PARTIAL_REMOVE; case COMPLETE_ADD: return LiteSubscriptionAction.COMPLETE_ADD; case COMPLETE_REMOVE: return LiteSubscriptionAction.COMPLETE_REMOVE; } throw new IllegalArgumentException("unknown LiteSubscriptionAction: " + gRpcAction); } private void handleGrpcCancel(ProxyContext ctx, Throwable t) { final String clientId = ctx.getClientID(); if (StringUtils.isBlank(clientId)) { return; } if (!(t instanceof StatusRuntimeException)) { return; } log.warn("handleGrpcCancel clientId:{}", clientId); StatusRuntimeException statusException = (StatusRuntimeException) t; if (io.grpc.Status.CANCELLED.getCode() == statusException.getStatus().getCode() || io.grpc.Status.UNAVAILABLE.getCode() == statusException.getStatus().getCode()) { this.grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, null); } } protected void processTelemetryException(TelemetryCommand request, Throwable t, StreamObserver responseObserver) { StatusRuntimeException exception = io.grpc.Status.INTERNAL .withDescription("process client telemetryCommand failed. " + t.getMessage()) .withCause(t) .asRuntimeException(); if (t instanceof GrpcProxyException) { GrpcProxyException proxyException = (GrpcProxyException) t; if (proxyException.getCode().getNumber() < Code.INTERNAL_ERROR_VALUE && proxyException.getCode().getNumber() >= Code.BAD_REQUEST_VALUE) { exception = io.grpc.Status.INVALID_ARGUMENT .withDescription("process client telemetryCommand failed. " + t.getMessage()) .withCause(t) .asRuntimeException(); } } if (exception.getStatus().getCode().equals(io.grpc.Status.Code.INTERNAL)) { log.warn("process client telemetryCommand failed. request:{}", request, t); } responseObserver.onError(exception); } protected void processAndWriteClientSettings(ProxyContext ctx, TelemetryCommand request, StreamObserver responseObserver) { GrpcClientChannel grpcClientChannel = null; Settings settings = request.getSettings(); switch (settings.getPubSubCase()) { case PUBLISHING: for (Resource topic : settings.getPublishing().getTopicsList()) { validateTopic(topic); String topicName = topic.getName(); grpcClientChannel = registerProducer(ctx, topicName); grpcClientChannel.setClientObserver(responseObserver); } break; case SUBSCRIPTION: validateConsumerGroup(settings.getSubscription().getGroup()); String groupName = settings.getSubscription().getGroup().getName(); grpcClientChannel = registerConsumer(ctx, groupName, settings.getClientType(), settings.getSubscription().getSubscriptionsList(), true); grpcClientChannel.setClientObserver(responseObserver); break; default: break; } if (Settings.PubSubCase.PUBSUB_NOT_SET.equals(settings.getPubSubCase())) { responseObserver.onError(io.grpc.Status.INVALID_ARGUMENT .withDescription("there is no publishing or subscription data in settings") .asRuntimeException()); return; } TelemetryCommand command = processClientSettings(ctx, request); if (grpcClientChannel != null) { grpcClientChannel.writeTelemetryCommand(command); } else { responseObserver.onNext(command); } } protected TelemetryCommand processClientSettings(ProxyContext ctx, TelemetryCommand request) { String clientId = ctx.getClientID(); grpcClientSettingsManager.updateClientSettings(ctx, clientId, request.getSettings()); Settings settings = grpcClientSettingsManager.getClientSettings(ctx); return TelemetryCommand.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .setSettings(settings) .build(); } protected GrpcClientChannel registerProducer(ProxyContext ctx, String topicName) { String clientId = ctx.getClientID(); LanguageCode languageCode = LanguageCode.valueOf(ctx.getLanguage()); GrpcClientChannel channel = this.grpcChannelManager.createChannel(ctx, clientId); // use topic name as producer group ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, parseClientVersion(ctx.getClientVersion())); this.messagingProcessor.registerProducer(ctx, topicName, clientChannelInfo); TopicMessageType topicMessageType = this.messagingProcessor.getMetadataService().getTopicMessageType(ctx, topicName); if (TopicMessageType.TRANSACTION.equals(topicMessageType)) { this.messagingProcessor.addTransactionSubscription(ctx, topicName, topicName); } return channel; } protected GrpcClientChannel registerConsumer(ProxyContext ctx, String consumerGroup, ClientType clientType, List subscriptionEntryList, boolean updateSubscription) { String clientId = ctx.getClientID(); LanguageCode languageCode = LanguageCode.valueOf(ctx.getLanguage()); GrpcClientChannel channel = this.grpcChannelManager.createChannel(ctx, clientId); ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, parseClientVersion(ctx.getClientVersion())); this.messagingProcessor.registerConsumer( ctx, consumerGroup, clientChannelInfo, this.buildConsumeType(clientType), this.buildMessageModel(clientType), ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, this.buildSubscriptionDataSet(subscriptionEntryList), updateSubscription ); return channel; } private int parseClientVersion(String clientVersionStr) { int clientVersion = MQVersion.CURRENT_VERSION; if (!StringUtils.isEmpty(clientVersionStr)) { try { String tmp = StringUtils.upperCase(clientVersionStr); clientVersion = MQVersion.Version.valueOf(tmp).ordinal(); } catch (Exception ignored) { } } return clientVersion; } protected void reportThreadStackTrace(ProxyContext ctx, Status status, ThreadStackTrace request) { String nonce = request.getNonce(); String threadStack = request.getThreadStackTrace(); CompletableFuture> responseFuture = this.grpcChannelManager.getAndRemoveResponseFuture(nonce); if (responseFuture != null) { try { if (status.getCode().equals(Code.OK)) { ConsumerRunningInfo runningInfo = new ConsumerRunningInfo(); runningInfo.setJstack(threadStack); responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", runningInfo)); } else if (status.getCode().equals(Code.VERIFY_FIFO_MESSAGE_UNSUPPORTED)) { responseFuture.complete(new ProxyRelayResult<>(ResponseCode.NO_PERMISSION, "forbidden to verify message", null)); } else { responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SYSTEM_ERROR, "verify message failed", null)); } } catch (Throwable t) { responseFuture.completeExceptionally(t); } } } protected void reportVerifyMessageResult(ProxyContext ctx, Status status, VerifyMessageResult request) { String nonce = request.getNonce(); CompletableFuture> responseFuture = this.grpcChannelManager.getAndRemoveResponseFuture(nonce); if (responseFuture != null) { try { ConsumeMessageDirectlyResult result = this.buildConsumeMessageDirectlyResult(status, request); responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", result)); } catch (Throwable t) { responseFuture.completeExceptionally(t); } } } protected ConsumeMessageDirectlyResult buildConsumeMessageDirectlyResult(Status status, VerifyMessageResult request) { ConsumeMessageDirectlyResult consumeMessageDirectlyResult = new ConsumeMessageDirectlyResult(); switch (status.getCode().getNumber()) { case Code.OK_VALUE: { consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_SUCCESS); break; } case Code.FAILED_TO_CONSUME_MESSAGE_VALUE: { consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_LATER); break; } case Code.MESSAGE_CORRUPTED_VALUE: { consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_RETURN_NULL); break; } } consumeMessageDirectlyResult.setRemark("from gRPC client"); return consumeMessageDirectlyResult; } protected ConsumeType buildConsumeType(ClientType clientType) { switch (clientType) { case SIMPLE_CONSUMER: return ConsumeType.CONSUME_ACTIVELY; case PUSH_CONSUMER: case LITE_PUSH_CONSUMER: return ConsumeType.CONSUME_PASSIVELY; default: throw new IllegalArgumentException("Client type is not consumer, type: " + clientType); } } protected MessageModel buildMessageModel(ClientType clientType) { if (clientType == ClientType.LITE_PUSH_CONSUMER) { return MessageModel.LITE_SELECTIVE; } return MessageModel.CLUSTERING; } protected Set buildSubscriptionDataSet(List subscriptionEntryList) { Set subscriptionDataSet = new HashSet<>(); for (SubscriptionEntry sub : subscriptionEntryList) { String topicName = sub.getTopic().getName(); FilterExpression filterExpression = sub.getExpression(); subscriptionDataSet.add(buildSubscriptionData(topicName, filterExpression)); } return subscriptionDataSet; } protected SubscriptionData buildSubscriptionData(String topicName, FilterExpression filterExpression) { String expression = filterExpression.getExpression(); String expressionType = GrpcConverter.getInstance().buildExpressionType(filterExpression.getType()); try { return FilterAPI.build(topicName, expression, expressionType); } catch (Exception e) { throw new GrpcProxyException(Code.ILLEGAL_FILTER_EXPRESSION, "expression format is not correct", e); } } protected class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener { @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { switch (event) { case CLIENT_UNREGISTER: processClientUnregister(group, args); break; case REGISTER: processClientRegister(group, args); break; default: break; } } protected void processClientUnregister(String group, Object... args) { if (args == null || args.length < 1) { return; } if (args[0] instanceof ClientChannelInfo) { ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0]; if (ChannelHelper.isRemote(clientChannelInfo.getChannel())) { return; } GrpcClientChannel removedChannel = grpcChannelManager.removeChannel(clientChannelInfo.getClientId()); log.info("remove grpc channel when client unregister. group:{}, clientChannelInfo:{}, removed:{}", group, clientChannelInfo, removedChannel != null); } } protected void processClientRegister(String group, Object... args) { if (args == null || args.length < 2) { return; } if (args[1] instanceof ClientChannelInfo) { ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[1]; Channel channel = clientChannelInfo.getChannel(); if (ChannelHelper.isRemote(channel)) { // save settings from channel sync from other proxy Settings settings = GrpcClientChannel.parseChannelExtendAttribute(channel); log.debug("save client settings sync from other proxy. group:{}, channelInfo:{}, settings:{}", group, clientChannelInfo, settings); if (settings == null) { return; } grpcClientSettingsManager.updateClientSettings( ProxyContext.createForInner(this.getClass()), clientChannelInfo.getClientId(), settings ); } } } @Override public void shutdown() { } } protected class ProducerChangeListenerImpl implements ProducerChangeListener { @Override public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) { if (event == ProducerGroupEvent.CLIENT_UNREGISTER) { grpcChannelManager.removeChannel(clientChannelInfo.getClientId()); grpcClientSettingsManager.removeAndGetRawClientSettings(clientChannelInfo.getClientId()); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.Address; import apache.rocketmq.v2.AddressScheme; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.CustomizedBackoff; import apache.rocketmq.v2.Endpoints; import apache.rocketmq.v2.ExponentialBackoff; import apache.rocketmq.v2.Metric; import apache.rocketmq.v2.Settings; import com.google.protobuf.Duration; import com.google.protobuf.util.Durations; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteSubscriptionAction; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.MetricCollectorMode; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.remoting.protocol.subscription.CustomizedRetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.ExponentialRetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.GroupRetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.GroupRetryPolicyType; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class GrpcClientSettingsManager extends ServiceThread implements StartAndShutdown { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final Map CLIENT_SETTINGS_MAP = new ConcurrentHashMap<>(); private final MessagingProcessor messagingProcessor; public GrpcClientSettingsManager(MessagingProcessor messagingProcessor) { this.messagingProcessor = messagingProcessor; } public Settings getRawClientSettings(String clientId) { return CLIENT_SETTINGS_MAP.get(clientId); } public Settings getClientSettings(ProxyContext ctx) { String clientId = ctx.getClientID(); Settings settings = getRawClientSettings(clientId); if (settings == null) { return null; } if (settings.hasPublishing()) { settings = mergeProducerData(settings); } else if (settings.hasSubscription()) { settings = mergeSubscriptionData(ctx, settings, settings.getSubscription().getGroup().getName()); } return mergeMetric(settings); } protected static Settings mergeProducerData(Settings settings) { ProxyConfig config = ConfigurationManager.getProxyConfig(); Settings.Builder builder = settings.toBuilder(); builder.getBackoffPolicyBuilder() .setMaxAttempts(config.getGrpcClientProducerMaxAttempts()) .setExponentialBackoff(ExponentialBackoff.newBuilder() .setInitial(Durations.fromMillis(config.getGrpcClientProducerBackoffInitialMillis())) .setMax(Durations.fromMillis(config.getGrpcClientProducerBackoffMaxMillis())) .setMultiplier(config.getGrpcClientProducerBackoffMultiplier()) .build()); builder.getPublishingBuilder() .setValidateMessageType(config.isEnableTopicMessageTypeCheck()) .setMaxBodySize(config.getMaxMessageSize()); return builder.build(); } protected Settings mergeSubscriptionData(ProxyContext ctx, Settings settings, String consumerGroup) { SubscriptionGroupConfig config = this.messagingProcessor.getSubscriptionGroupConfig(ctx, consumerGroup); if (config == null) { return settings; } return mergeSubscriptionData(settings, config); } protected Settings mergeMetric(Settings settings) { // Construct metric according to the proxy config final ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); final MetricCollectorMode metricCollectorMode = MetricCollectorMode.getEnumByString(proxyConfig.getMetricCollectorMode()); final String metricCollectorAddress = proxyConfig.getMetricCollectorAddress(); final Metric.Builder metricBuilder = Metric.newBuilder(); switch (metricCollectorMode) { case ON: final String[] split = metricCollectorAddress.split(":"); final String host = split[0]; final int port = Integer.parseInt(split[1]); Address address = Address.newBuilder().setHost(host).setPort(port).build(); final Endpoints endpoints = Endpoints.newBuilder().setScheme(AddressScheme.IPv4) .addAddresses(address).build(); metricBuilder.setOn(true).setEndpoints(endpoints); break; case PROXY: metricBuilder.setOn(true).setEndpoints(settings.getAccessPoint()); break; case OFF: default: metricBuilder.setOn(false); break; } Metric metric = metricBuilder.build(); return settings.toBuilder().setMetric(metric).build(); } protected static Settings mergeSubscriptionData(Settings settings, SubscriptionGroupConfig groupConfig) { Settings.Builder resultSettingsBuilder = settings.toBuilder(); ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); resultSettingsBuilder.getSubscriptionBuilder() .setReceiveBatchSize(proxyConfig.getGrpcClientConsumerLongPollingBatchSize()) .setLongPollingTimeout(Durations.fromMillis(proxyConfig.getGrpcClientConsumerMaxLongPollingTimeoutMillis())) .setFifo(groupConfig.isConsumeMessageOrderly()) // client-side lite subscription quota limit .setLiteSubscriptionQuota(groupConfig.getLiteSubClientQuota()) .setMaxLiteTopicSize(proxyConfig.getMaxLiteTopicSize()); resultSettingsBuilder.getBackoffPolicyBuilder().setMaxAttempts(groupConfig.getRetryMaxTimes() + 1); GroupRetryPolicy groupRetryPolicy = groupConfig.getGroupRetryPolicy(); if (groupRetryPolicy.getType().equals(GroupRetryPolicyType.EXPONENTIAL)) { ExponentialRetryPolicy exponentialRetryPolicy = groupRetryPolicy.getExponentialRetryPolicy(); if (exponentialRetryPolicy == null) { exponentialRetryPolicy = new ExponentialRetryPolicy(); } resultSettingsBuilder.getBackoffPolicyBuilder().setExponentialBackoff(convertToExponentialBackoff(exponentialRetryPolicy)); } else { CustomizedRetryPolicy customizedRetryPolicy = groupRetryPolicy.getCustomizedRetryPolicy(); if (customizedRetryPolicy == null) { customizedRetryPolicy = new CustomizedRetryPolicy(); } resultSettingsBuilder.getBackoffPolicyBuilder().setCustomizedBackoff(convertToCustomizedRetryPolicy(customizedRetryPolicy)); } return resultSettingsBuilder.build(); } protected static ExponentialBackoff convertToExponentialBackoff(ExponentialRetryPolicy retryPolicy) { return ExponentialBackoff.newBuilder() .setInitial(Durations.fromMillis(retryPolicy.getInitial())) .setMax(Durations.fromMillis(retryPolicy.getMax())) .setMultiplier(retryPolicy.getMultiplier()) .build(); } protected static CustomizedBackoff convertToCustomizedRetryPolicy(CustomizedRetryPolicy retryPolicy) { List durationList = Arrays.stream(retryPolicy.getNext()) .mapToObj(Durations::fromMillis).collect(Collectors.toList()); return CustomizedBackoff.newBuilder() .addAllNext(durationList) .build(); } public void updateClientSettings(ProxyContext ctx, String clientId, Settings settings) { if (settings.hasSubscription()) { settings = createDefaultConsumerSettingsBuilder().mergeFrom(settings).build(); } CLIENT_SETTINGS_MAP.put(clientId, settings); } protected Settings.Builder createDefaultConsumerSettingsBuilder() { return mergeSubscriptionData(Settings.newBuilder().getDefaultInstanceForType(), new SubscriptionGroupConfig()) .toBuilder(); } public Settings removeAndGetRawClientSettings(String clientId) { return CLIENT_SETTINGS_MAP.remove(clientId); } public Settings removeAndGetClientSettings(ProxyContext ctx) { String clientId = ctx.getClientID(); Settings settings = this.removeAndGetRawClientSettings(clientId); if (settings == null) { return null; } if (settings.hasSubscription()) { settings = mergeSubscriptionData(ctx, settings, settings.getSubscription().getGroup().getName()); } return mergeMetric(settings); } @Override public String getServiceName() { return "GrpcClientSettingsManagerCleaner"; } /** * Remove all lite subscriptions when client offline. * * @param ctx Proxy context * @param clientId Client identifier * @param settings Current client settings, if available */ public void offlineClientLiteSubscription(ProxyContext ctx, String clientId, Settings settings) { if (settings == null) { settings = getRawClientSettings(clientId); } if (settings == null || ClientType.LITE_PUSH_CONSUMER != settings.getClientType()) { return; } try { String topic = settings.getSubscription().getSubscriptions(0).getTopic().getName(); String group = settings.getSubscription().getGroup().getName(); log.info("offlineClientLiteSubscription, topic:{}, group:{}, clientId:{}", topic, group, clientId); LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO() .setAction(LiteSubscriptionAction.COMPLETE_REMOVE) .setClientId(clientId) .setGroup(group) .setTopic(topic); this.messagingProcessor.syncLiteSubscription(ctx, liteSubscriptionDTO, java.time.Duration.ofSeconds(2).toMillis()) .whenComplete((result, throwable) -> { if (throwable != null) { log.error("offlineClientLiteSubscription failed, topic:{}, group:{}, clientId:{}", topic, group, clientId, throwable); } }); } catch (Exception e) { log.error("offlineClientLiteSubscription error, clientId:{}, settings:{}", clientId, settings, e); } } @Override public void run() { while (!this.isStopped()) { this.waitForRunning(TimeUnit.SECONDS.toMillis(5)); } } @Override protected void onWaitEnd() { Set clientIdSet = CLIENT_SETTINGS_MAP.keySet(); for (String clientId : clientIdSet) { try { CLIENT_SETTINGS_MAP.computeIfPresent(clientId, (clientIdKey, settings) -> { if (!settings.getClientType().equals(ClientType.PUSH_CONSUMER) && !settings.getClientType().equals(ClientType.SIMPLE_CONSUMER) && !settings.getClientType().equals(ClientType.LITE_PUSH_CONSUMER)) { return settings; } String consumerGroup = settings.getSubscription().getGroup().getName(); ConsumerGroupInfo consumerGroupInfo = this.messagingProcessor.getConsumerGroupInfo( ProxyContext.createForInner(this.getClass()), consumerGroup ); if (consumerGroupInfo == null || consumerGroupInfo.findChannel(clientId) == null) { log.info("remove unused grpc client settings. group:{}, settings:{}", consumerGroupInfo, settings); return null; } return settings; }); } catch (Throwable t) { log.error("check expired grpc client settings failed. clientId:{}", clientId, t); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.Broker; import apache.rocketmq.v2.DeadLetterQueue; import apache.rocketmq.v2.Digest; import apache.rocketmq.v2.DigestType; import apache.rocketmq.v2.Encoding; import apache.rocketmq.v2.FilterType; import apache.rocketmq.v2.Message; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.MessageType; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.SystemProperties; import com.google.protobuf.ByteString; import com.google.protobuf.util.Timestamps; import java.net.SocketAddress; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.BinaryUtil; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class GrpcConverter { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final Object INSTANCE_CREATE_LOCK = new Object(); protected static volatile GrpcConverter instance; public static GrpcConverter getInstance() { if (instance == null) { synchronized (INSTANCE_CREATE_LOCK) { if (instance == null) { instance = new GrpcConverter(); } } } return instance; } public MessageQueue buildMessageQueue(MessageExt messageExt, String brokerName) { Broker broker = Broker.getDefaultInstance(); if (!StringUtils.isEmpty(brokerName)) { broker = Broker.newBuilder() .setName(brokerName) .setId(0) .build(); } return MessageQueue.newBuilder() .setId(messageExt.getQueueId()) .setTopic(Resource.newBuilder() .setName(NamespaceUtil.withoutNamespace(messageExt.getTopic())) .setResourceNamespace(NamespaceUtil.getNamespaceFromResource(messageExt.getTopic())) .build()) .setBroker(broker) .build(); } public String buildExpressionType(FilterType filterType) { switch (filterType) { case SQL: return ExpressionType.SQL92; case TAG: default: return ExpressionType.TAG; } } public Message buildMessage(MessageExt messageExt) { Map userProperties = buildUserAttributes(messageExt); SystemProperties systemProperties = buildSystemProperties(messageExt); Resource topic = buildResource(messageExt.getTopic()); return Message.newBuilder() .setTopic(topic) .putAllUserProperties(userProperties) .setSystemProperties(systemProperties) .setBody(ByteString.copyFrom(messageExt.getBody())) .build(); } protected Map buildUserAttributes(MessageExt messageExt) { Map userAttributes = new HashMap<>(); Map properties = messageExt.getProperties(); for (Map.Entry property : properties.entrySet()) { if (!MessageConst.STRING_HASH_SET.contains(property.getKey())) { userAttributes.put(property.getKey(), property.getValue()); } } return userAttributes; } protected SystemProperties buildSystemProperties(MessageExt messageExt) { SystemProperties.Builder systemPropertiesBuilder = SystemProperties.newBuilder(); // tag String tag = messageExt.getUserProperty(MessageConst.PROPERTY_TAGS); if (tag != null) { systemPropertiesBuilder.setTag(tag); } // keys String keys = messageExt.getKeys(); if (keys != null) { String[] keysArray = keys.split(MessageConst.KEY_SEPARATOR); systemPropertiesBuilder.addAllKeys(Arrays.asList(keysArray)); } // message_id String uniqKey = messageExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (uniqKey == null) { uniqKey = messageExt.getMsgId(); } if (uniqKey != null) { systemPropertiesBuilder.setMessageId(uniqKey); } // body_digest & body_encoding String md5Result = BinaryUtil.generateMd5(messageExt.getBody()); Digest digest = Digest.newBuilder() .setType(DigestType.MD5) .setChecksum(md5Result) .build(); systemPropertiesBuilder.setBodyDigest(digest); if ((messageExt.getSysFlag() & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { systemPropertiesBuilder.setBodyEncoding(Encoding.GZIP); } else { systemPropertiesBuilder.setBodyEncoding(Encoding.IDENTITY); } // message_type TopicMessageType topicMessageType = TopicMessageType.parseFromMessageProperty(messageExt.getProperties()); systemPropertiesBuilder.setMessageType(convertToGrpcMessageType(topicMessageType)); // born_timestamp (millis) long bornTimestamp = messageExt.getBornTimestamp(); systemPropertiesBuilder.setBornTimestamp(Timestamps.fromMillis(bornTimestamp)); // born_host String bornHostString = messageExt.getProperty(MessageConst.PROPERTY_BORN_HOST); if (StringUtils.isBlank(bornHostString)) { bornHostString = messageExt.getBornHostString(); } if (StringUtils.isNotBlank(bornHostString)) { systemPropertiesBuilder.setBornHost(bornHostString); } // store_timestamp (millis) long storeTimestamp = messageExt.getStoreTimestamp(); systemPropertiesBuilder.setStoreTimestamp(Timestamps.fromMillis(storeTimestamp)); // store_host SocketAddress storeHost = messageExt.getStoreHost(); if (storeHost != null) { systemPropertiesBuilder.setStoreHost(NetworkUtil.socketAddress2String(storeHost)); } // delivery_timestamp String deliverMsString; long deliverMs; if (messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) { long delayMs = TimeUnit.SECONDS.toMillis(Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC))); deliverMs = System.currentTimeMillis() + delayMs; systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs)); } else { deliverMsString = messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS); if (deliverMsString != null) { deliverMs = Long.parseLong(deliverMsString); systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs)); } } // priority int priority = messageExt.getPriority(); if (priority >= 0) { systemPropertiesBuilder.setPriority(priority); } // sharding key String shardingKey = messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY); if (shardingKey != null) { systemPropertiesBuilder.setMessageGroup(shardingKey); } // lite topic String liteTopic = messageExt.getProperty(MessageConst.PROPERTY_LITE_TOPIC); if (liteTopic != null) { systemPropertiesBuilder.setLiteTopic(liteTopic); } // receipt_handle && invisible_period String handle = messageExt.getProperty(MessageConst.PROPERTY_POP_CK); if (handle != null) { systemPropertiesBuilder.setReceiptHandle(handle); } // partition_id systemPropertiesBuilder.setQueueId(messageExt.getQueueId()); // partition_offset systemPropertiesBuilder.setQueueOffset(messageExt.getQueueOffset()); // delivery_attempt systemPropertiesBuilder.setDeliveryAttempt(messageExt.getReconsumeTimes() + 1); // trace context String traceContext = messageExt.getProperty(MessageConst.PROPERTY_TRACE_CONTEXT); if (traceContext != null) { systemPropertiesBuilder.setTraceContext(traceContext); } String dlqOriginTopic = messageExt.getProperty(MessageConst.PROPERTY_DLQ_ORIGIN_TOPIC); String dlqOriginMessageId = messageExt.getProperty(MessageConst.PROPERTY_DLQ_ORIGIN_MESSAGE_ID); if (dlqOriginTopic != null && dlqOriginMessageId != null) { DeadLetterQueue dlq = DeadLetterQueue.newBuilder() .setTopic(dlqOriginTopic) .setMessageId(dlqOriginMessageId) .build(); systemPropertiesBuilder.setDeadLetterQueue(dlq); } return systemPropertiesBuilder.build(); } public Resource buildResource(String resourceNameWithNamespace) { return Resource.newBuilder() .setResourceNamespace(NamespaceUtil.getNamespaceFromResource(resourceNameWithNamespace)) .setName(NamespaceUtil.withoutNamespace(resourceNameWithNamespace)) .build(); } protected MessageType convertToGrpcMessageType(TopicMessageType topicMessageType) { switch (topicMessageType) { case TRANSACTION: return MessageType.TRANSACTION; case DELAY: return MessageType.DELAY; case FIFO: return MessageType.FIFO; case PRIORITY: return MessageType.PRIORITY; case LITE: return MessageType.LITE; case NORMAL: return MessageType.NORMAL; case UNSPECIFIED: default: return MessageType.NORMAL; } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.Code; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; public class GrpcProxyException extends RuntimeException { private ProxyException proxyException; private Code code; protected static final Map CODE_MAPPING = new ConcurrentHashMap<>(); static { CODE_MAPPING.put(ProxyExceptionCode.INVALID_BROKER_NAME, Code.BAD_REQUEST); CODE_MAPPING.put(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, Code.INVALID_RECEIPT_HANDLE); CODE_MAPPING.put(ProxyExceptionCode.FORBIDDEN, Code.FORBIDDEN); CODE_MAPPING.put(ProxyExceptionCode.INTERNAL_SERVER_ERROR, Code.INTERNAL_SERVER_ERROR); CODE_MAPPING.put(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, Code.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE); } public GrpcProxyException(Code code, String message) { super(message); this.code = code; } public GrpcProxyException(Code code, String message, Throwable t) { super(message, t); this.code = code; } public GrpcProxyException(ProxyException proxyException) { super(proxyException); this.proxyException = proxyException; } public Code getCode() { if (this.code != null) { return this.code; } if (this.proxyException != null) { return CODE_MAPPING.getOrDefault(this.proxyException.getCode(), Code.INTERNAL_SERVER_ERROR); } return Code.INTERNAL_SERVER_ERROR; } public ProxyException getProxyException() { return proxyException; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Resource; import com.google.common.base.CharMatcher; import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; public class GrpcValidator { protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final Object INSTANCE_CREATE_LOCK = new Object(); protected static volatile GrpcValidator instance; public static GrpcValidator getInstance() { if (instance == null) { synchronized (INSTANCE_CREATE_LOCK) { if (instance == null) { instance = new GrpcValidator(); } } } return instance; } public void validateTopic(Resource topic) { validateTopic(topic.getName()); } public void validateTopic(String topicName) { try { Validators.checkTopic(topicName); } catch (MQClientException mqClientException) { throw new GrpcProxyException(Code.ILLEGAL_TOPIC, mqClientException.getErrorMessage()); } if (TopicValidator.isSystemTopic(topicName)) { throw new GrpcProxyException(Code.ILLEGAL_TOPIC, "cannot access system topic"); } } public void validateConsumerGroup(Resource consumerGroup) { validateConsumerGroup(consumerGroup.getName()); } public void validateConsumerGroup(String consumerGroupName) { try { Validators.checkGroup(consumerGroupName); } catch (MQClientException mqClientException) { throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, mqClientException.getErrorMessage()); } if (MixAll.isSysConsumerGroup(consumerGroupName)) { throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "cannot use system consumer group"); } } public void validateTopicAndConsumerGroup(Resource topic, Resource consumerGroup) { validateTopic(topic); validateConsumerGroup(consumerGroup); } public void validateInvisibleTime(long invisibleTime) { validateInvisibleTime(invisibleTime, 0); } public void validateInvisibleTime(long invisibleTime, long minInvisibleTime) { if (invisibleTime < minInvisibleTime) { throw new GrpcProxyException(Code.ILLEGAL_INVISIBLE_TIME, "the invisibleTime is too small. min is " + minInvisibleTime); } long maxInvisibleTime = ConfigurationManager.getProxyConfig().getMaxInvisibleTimeMills(); if (maxInvisibleTime <= 0) { return; } if (invisibleTime > maxInvisibleTime) { throw new GrpcProxyException(Code.ILLEGAL_INVISIBLE_TIME, "the invisibleTime is too large. max is " + maxInvisibleTime); } } public void validateTag(String tag) { if (StringUtils.isNotEmpty(tag)) { if (StringUtils.isBlank(tag)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_TAG, "tag cannot be the char sequence of whitespace"); } if (tag.contains("|")) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_TAG, "tag cannot contain '|'"); } if (containControlCharacter(tag)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_TAG, "tag cannot contain control character"); } } } public boolean containControlCharacter(String data) { for (int i = 0; i < data.length(); i++) { if (CharMatcher.javaIsoControl().matches(data.charAt(i))) { return true; } } return false; } public void validateLiteTopic(String liteTopic) { if (StringUtils.isBlank(liteTopic)) { throw new GrpcProxyException(Code.ILLEGAL_LITE_TOPIC, "lite topic cannot be the char sequence of whitespace"); } int maxSize = ConfigurationManager.getProxyConfig().getMaxLiteTopicSize(); if (liteTopic.getBytes(StandardCharsets.UTF_8).length > maxSize) { throw new GrpcProxyException(Code.ILLEGAL_LITE_TOPIC, "lite topic exceed the max size " + maxSize); } if (!isValidLiteTopic(liteTopic)) { throw new GrpcProxyException(Code.ILLEGAL_LITE_TOPIC, "lite topic can only contain alphanumeric characters, hyphens(-), and underscores(_)"); } } /** * alternative for regex "^[a-zA-Z0-9_-]+$" */ private boolean isValidLiteTopic(String liteTopic) { for (int i = 0; i < liteTopic.length(); i++) { char c = liteTopic.charAt(i); if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && c != '-' && c != '_') { return false; } } return true; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Status; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.client.common.ClientErrorCode; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.proxy.service.route.TopicRouteHelper; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.ResponseCode; public class ResponseBuilder { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final Map RESPONSE_CODE_MAPPING = new ConcurrentHashMap<>(); protected static final Object INSTANCE_CREATE_LOCK = new Object(); protected static volatile ResponseBuilder instance; static { RESPONSE_CODE_MAPPING.put(ResponseCode.SUCCESS, Code.OK); RESPONSE_CODE_MAPPING.put(ResponseCode.SYSTEM_BUSY, Code.TOO_MANY_REQUESTS); RESPONSE_CODE_MAPPING.put(ResponseCode.REQUEST_CODE_NOT_SUPPORTED, Code.NOT_IMPLEMENTED); RESPONSE_CODE_MAPPING.put(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST, Code.CONSUMER_GROUP_NOT_FOUND); RESPONSE_CODE_MAPPING.put(ResponseCode.LMQ_QUOTA_EXCEEDED, Code.LITE_TOPIC_QUOTA_EXCEEDED); RESPONSE_CODE_MAPPING.put(ResponseCode.LITE_SUBSCRIPTION_QUOTA_EXCEEDED, Code.LITE_SUBSCRIPTION_QUOTA_EXCEEDED); RESPONSE_CODE_MAPPING.put(ClientErrorCode.ACCESS_BROKER_TIMEOUT, Code.PROXY_TIMEOUT); } public static ResponseBuilder getInstance() { if (instance == null) { synchronized (INSTANCE_CREATE_LOCK) { if (instance == null) { instance = new ResponseBuilder(); } } } return instance; } public Status buildStatus(Throwable t) { t = ExceptionUtils.getRealException(t); if (t instanceof ProxyException) { t = new GrpcProxyException((ProxyException) t); } if (t instanceof GrpcProxyException) { GrpcProxyException grpcProxyException = (GrpcProxyException) t; return buildStatus(grpcProxyException.getCode(), grpcProxyException.getMessage()); } if (TopicRouteHelper.isTopicNotExistError(t)) { return buildStatus(Code.TOPIC_NOT_FOUND, t.getMessage()); } if (t instanceof MQBrokerException) { MQBrokerException mqBrokerException = (MQBrokerException) t; return buildStatus(buildCode(mqBrokerException.getResponseCode()), mqBrokerException.getErrorMessage()); } if (t instanceof MQClientException) { MQClientException mqClientException = (MQClientException) t; return buildStatus(buildCode(mqClientException.getResponseCode()), mqClientException.getErrorMessage()); } if (t instanceof RemotingTimeoutException) { return buildStatus(Code.PROXY_TIMEOUT, t.getMessage()); } if (t instanceof AuthenticationException || t instanceof AuthorizationException) { return buildStatus(Code.UNAUTHORIZED, t.getMessage()); } log.error("internal server error", t); return buildStatus(Code.INTERNAL_SERVER_ERROR, ExceptionUtils.getErrorDetailMessage(t)); } public Status buildStatus(Code code, String message) { return Status.newBuilder() .setCode(code) .setMessage(message != null ? message : code.name()) .build(); } public Status buildStatus(int remotingResponseCode, String remark) { String message = remark; if (message == null) { message = String.valueOf(remotingResponseCode); } return Status.newBuilder() .setCode(buildCode(remotingResponseCode)) .setMessage(message) .build(); } public Code buildCode(int remotingResponseCode) { return RESPONSE_CODE_MAPPING.getOrDefault(remotingResponseCode, Code.INTERNAL_SERVER_ERROR); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ResponseWriter { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final Object INSTANCE_CREATE_LOCK = new Object(); protected static volatile ResponseWriter instance; public static ResponseWriter getInstance() { if (instance == null) { synchronized (INSTANCE_CREATE_LOCK) { if (instance == null) { instance = new ResponseWriter(); } } } return instance; } public void write(StreamObserver observer, final T response) { if (writeResponse(observer, response)) { observer.onCompleted(); } } public boolean writeResponse(StreamObserver observer, final T response) { if (null == response) { return false; } log.debug("start to write response. response: {}", response); if (isCancelled(observer)) { log.warn("client has cancelled the request. response to write: {}", response); return false; } try { observer.onNext(response); } catch (StatusRuntimeException statusRuntimeException) { if (Status.CANCELLED.equals(statusRuntimeException.getStatus())) { log.warn("client has cancelled the request. response to write: {}", response); return false; } throw statusRuntimeException; } return true; } public boolean isCancelled(StreamObserver observer) { if (observer instanceof ServerCallStreamObserver) { final ServerCallStreamObserver serverCallStreamObserver = (ServerCallStreamObserver) observer; return serverCallStreamObserver.isCancelled(); } return false; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.AckMessageEntry; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.AckMessageResponse; import apache.rocketmq.v2.AckMessageResultEntry; import apache.rocketmq.v2.Code; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.BatchAckResult; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; public class AckMessageActivity extends AbstractMessagingActivity { public AckMessageActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture ackMessage(ProxyContext ctx, AckMessageRequest request) { CompletableFuture future = new CompletableFuture<>(); try { validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); String group = request.getGroup().getName(); String topic = request.getTopic().getName(); boolean isBatchAck = ConfigurationManager.getProxyConfig().isEnableBatchAck() && !request.getEntries(0).hasLiteTopic(); if (isBatchAck) { future = ackMessageInBatch(ctx, group, topic, request); } else { future = ackMessageOneByOne(ctx, group, topic, request); } } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected CompletableFuture ackMessageInBatch(ProxyContext ctx, String group, String topic, AckMessageRequest request) { List handleMessageList = new ArrayList<>(request.getEntriesCount()); for (AckMessageEntry ackMessageEntry : request.getEntriesList()) { String handleString = getHandleString(ctx, group, request, ackMessageEntry); handleMessageList.add(new ReceiptHandleMessage(ReceiptHandle.decode(handleString), ackMessageEntry.getMessageId())); } return this.messagingProcessor.batchAckMessage(ctx, handleMessageList, group, topic) .thenApply(batchAckResultList -> { AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder(); Set responseCodes = new HashSet<>(); for (BatchAckResult batchAckResult : batchAckResultList) { AckMessageResultEntry entry = convertToAckMessageResultEntry(batchAckResult); responseBuilder.addEntries(entry); responseCodes.add(entry.getStatus().getCode()); } setAckResponseStatus(responseBuilder, responseCodes); return responseBuilder.build(); }); } protected AckMessageResultEntry convertToAckMessageResultEntry(BatchAckResult batchAckResult) { ReceiptHandleMessage handleMessage = batchAckResult.getReceiptHandleMessage(); AckMessageResultEntry.Builder resultBuilder = AckMessageResultEntry.newBuilder() .setMessageId(handleMessage.getMessageId()) .setReceiptHandle(handleMessage.getReceiptHandle().getReceiptHandle()); if (batchAckResult.getProxyException() != null) { resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(batchAckResult.getProxyException())); } else { AckResult ackResult = batchAckResult.getAckResult(); if (AckStatus.OK.equals(ackResult.getStatus())) { resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())); } else { resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal")); } } return resultBuilder.build(); } protected CompletableFuture ackMessageOneByOne(ProxyContext ctx, String group, String topic, AckMessageRequest request) { CompletableFuture resultFuture = new CompletableFuture<>(); CompletableFuture[] futures = new CompletableFuture[request.getEntriesCount()]; for (int i = 0; i < request.getEntriesCount(); i++) { futures[i] = processAckMessage(ctx, group, topic, request, request.getEntries(i)); } CompletableFuture.allOf(futures).whenComplete((val, throwable) -> { if (throwable != null) { resultFuture.completeExceptionally(throwable); return; } Set responseCodes = new HashSet<>(); List entryList = new ArrayList<>(); for (CompletableFuture entryFuture : futures) { AckMessageResultEntry entryResult = entryFuture.join(); responseCodes.add(entryResult.getStatus().getCode()); entryList.add(entryResult); } AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder() .addAllEntries(entryList); setAckResponseStatus(responseBuilder, responseCodes); resultFuture.complete(responseBuilder.build()); }); return resultFuture; } protected CompletableFuture processAckMessage(ProxyContext ctx, String group, String topic, AckMessageRequest request, AckMessageEntry ackMessageEntry) { CompletableFuture future = new CompletableFuture<>(); try { String handleString = this.getHandleString(ctx, group, request, ackMessageEntry); CompletableFuture ackResultFuture = this.messagingProcessor.ackMessage( ctx, ReceiptHandle.decode(handleString), ackMessageEntry.getMessageId(), group, topic, ackMessageEntry.hasLiteTopic() ? ackMessageEntry.getLiteTopic() : null ); ackResultFuture.thenAccept(result -> { future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, result)); }).exceptionally(t -> { future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, t)); return null; }); } catch (Throwable t) { future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, t)); } return future; } protected AckMessageResultEntry convertToAckMessageResultEntry(ProxyContext ctx, AckMessageEntry ackMessageEntry, Throwable throwable) { return AckMessageResultEntry.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(throwable)) .setMessageId(ackMessageEntry.getMessageId()) .setReceiptHandle(ackMessageEntry.getReceiptHandle()) .build(); } protected AckMessageResultEntry convertToAckMessageResultEntry(ProxyContext ctx, AckMessageEntry ackMessageEntry, AckResult ackResult) { if (AckStatus.OK.equals(ackResult.getStatus())) { return AckMessageResultEntry.newBuilder() .setMessageId(ackMessageEntry.getMessageId()) .setReceiptHandle(ackMessageEntry.getReceiptHandle()) .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build(); } return AckMessageResultEntry.newBuilder() .setMessageId(ackMessageEntry.getMessageId()) .setReceiptHandle(ackMessageEntry.getReceiptHandle()) .setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal")) .build(); } protected void setAckResponseStatus(AckMessageResponse.Builder responseBuilder, Set responseCodes) { if (responseCodes.size() > 1) { responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.MULTIPLE_RESULTS, Code.MULTIPLE_RESULTS.name())); } else if (responseCodes.size() == 1) { Code code = responseCodes.stream().findAny().get(); responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(code, code.name())); } else { responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message result is empty")); } } protected String getHandleString(ProxyContext ctx, String group, AckMessageRequest request, AckMessageEntry ackMessageEntry) { String handleString = ackMessageEntry.getReceiptHandle(); GrpcClientChannel channel = grpcChannelManager.getChannel(ctx.getClientID()); if (channel != null) { MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, channel, group, ackMessageEntry.getMessageId(), ackMessageEntry.getReceiptHandle()); if (messageReceiptHandle != null) { handleString = messageReceiptHandle.getReceiptHandleStr(); } } return handleString; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ChangeInvisibleDurationResponse; import apache.rocketmq.v2.Code; import com.google.protobuf.util.Durations; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public class ChangeInvisibleDurationActivity extends AbstractMessagingActivity { public ChangeInvisibleDurationActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture changeInvisibleDuration(ProxyContext ctx, ChangeInvisibleDurationRequest request) { CompletableFuture future = new CompletableFuture<>(); try { validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); validateInvisibleTime(Durations.toMillis(request.getInvisibleDuration())); ReceiptHandle receiptHandle = ReceiptHandle.decode(request.getReceiptHandle()); String group = request.getGroup().getName(); MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, request.getMessageId(), receiptHandle.getReceiptHandle()); if (messageReceiptHandle != null) { receiptHandle = ReceiptHandle.decode(messageReceiptHandle.getReceiptHandleStr()); } return this.messagingProcessor.changeInvisibleTime( ctx, receiptHandle, request.getMessageId(), group, request.getTopic().getName(), Durations.toMillis(request.getInvisibleDuration()) ).thenApply(ackResult -> convertToChangeInvisibleDurationResponse(ctx, request, ackResult)); } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected ChangeInvisibleDurationResponse convertToChangeInvisibleDurationResponse(ProxyContext ctx, ChangeInvisibleDurationRequest request, AckResult ackResult) { if (AckStatus.OK.equals(ackResult.getStatus())) { return ChangeInvisibleDurationResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .setReceiptHandle(ackResult.getExtraInfo()) .build(); } return ChangeInvisibleDurationResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "changeInvisibleDuration failed: status is abnormal")) .build(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/PopMessageResultFilterImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.utils.FilterUtils; import org.apache.rocketmq.proxy.processor.PopMessageResultFilter; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class PopMessageResultFilterImpl implements PopMessageResultFilter { private final int maxAttempts; public PopMessageResultFilterImpl(int maxAttempts) { this.maxAttempts = maxAttempts; } @Override public FilterResult filterMessage(ProxyContext ctx, String consumerGroup, SubscriptionData subscriptionData, MessageExt messageExt) { if (!FilterUtils.isTagMatched(subscriptionData.getTagsSet(), messageExt.getTags())) { return FilterResult.NO_MATCH; } if (messageExt.getReconsumeTimes() >= maxAttempts) { return FilterResult.TO_DLQ; } return FilterResult.MATCH; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.FilterExpression; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Subscription; import com.google.protobuf.util.Durations; import io.grpc.stub.StreamObserver; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.QueueSelector; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueSelector; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ReceiveMessageActivity extends AbstractMessagingActivity { private static final String ILLEGAL_POLLING_TIME_INTRODUCED_CLIENT_VERSION = "5.0.3"; public ReceiveMessageActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request, StreamObserver responseObserver) { ReceiveMessageResponseStreamWriter writer = createWriter(ctx, responseObserver); try { Settings settings = this.grpcClientSettingsManager.getClientSettings(ctx); final boolean isLite = ClientType.LITE_PUSH_CONSUMER.equals(settings.getClientType()); Subscription subscription = settings.getSubscription(); boolean fifo = subscription.getFifo(); int maxAttempts = settings.getBackoffPolicy().getMaxAttempts(); ProxyConfig config = ConfigurationManager.getProxyConfig(); Long timeRemaining = ctx.getRemainingMs(); long pollingTime; if (request.hasLongPollingTimeout()) { pollingTime = Durations.toMillis(request.getLongPollingTimeout()); } else { pollingTime = timeRemaining - Durations.toMillis(settings.getRequestTimeout()) / 2; } if (pollingTime < config.getGrpcClientConsumerMinLongPollingTimeoutMillis()) { pollingTime = config.getGrpcClientConsumerMinLongPollingTimeoutMillis(); } if (pollingTime > config.getGrpcClientConsumerMaxLongPollingTimeoutMillis()) { pollingTime = config.getGrpcClientConsumerMaxLongPollingTimeoutMillis(); } if (pollingTime > timeRemaining) { if (timeRemaining >= config.getGrpcClientConsumerMinLongPollingTimeoutMillis()) { pollingTime = timeRemaining; } else { final String clientVersion = ctx.getClientVersion(); Code code = null == clientVersion || ILLEGAL_POLLING_TIME_INTRODUCED_CLIENT_VERSION.compareTo(clientVersion) > 0 ? Code.BAD_REQUEST : Code.ILLEGAL_POLLING_TIME; writer.writeAndComplete(ctx, code, "The deadline time remaining is not enough" + " for polling, please check network condition"); return; } } validateTopicAndConsumerGroup(request.getMessageQueue().getTopic(), request.getGroup()); String topic = request.getMessageQueue().getTopic().getName(); String group = request.getGroup().getName(); long actualInvisibleTime = Durations.toMillis(request.getInvisibleDuration()); ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); if (proxyConfig.isEnableProxyAutoRenew() && request.getAutoRenew()) { actualInvisibleTime = proxyConfig.getDefaultInvisibleTimeMills(); } else { validateInvisibleTime(actualInvisibleTime, ConfigurationManager.getProxyConfig().getMinInvisibleTimeMillsForRecv()); } FilterExpression filterExpression = request.getFilterExpression(); SubscriptionData subscriptionData; try { subscriptionData = FilterAPI.build(topic, filterExpression.getExpression(), GrpcConverter.getInstance().buildExpressionType(filterExpression.getType())); } catch (Exception e) { writer.writeAndComplete(ctx, Code.ILLEGAL_FILTER_EXPRESSION, e.getMessage()); return; } CompletableFuture popFuture; if (isLite) { GrpcClientChannel clientChannel = grpcChannelManager.getChannel(ctx.getClientID()); if (clientChannel == null) { writer.writeAndComplete(ctx, Code.BAD_REQUEST, String.format("The client [%s] is disconnected.", ctx.getClientID())); return; } // check lite consumer max unacked messages int unackedMessageCount = messagingProcessor.getUnackedMessageCount(ctx, clientChannel, group); if (proxyConfig.getMaxLiteRenewNumPerChannel() < unackedMessageCount) { writer.writeAndComplete(ctx, Code.FORBIDDEN, String.format("The client [%s] has too many unacked messages. Unacked count: %d", ctx.getClientID(), unackedMessageCount)); return; } popFuture = this.messagingProcessor.popLiteMessage( ctx, new ReceiveMessageQueueSelector( request.getMessageQueue().getBroker().getName() ), group, topic, request.getBatchSize(), actualInvisibleTime, pollingTime, subscriptionData, new PopMessageResultFilterImpl(maxAttempts), request.hasAttemptId() ? request.getAttemptId() : null, timeRemaining ); } else { popFuture = this.messagingProcessor.popMessage( ctx, new ReceiveMessageQueueSelector( request.getMessageQueue().getBroker().getName() ), group, topic, request.getBatchSize(), actualInvisibleTime, pollingTime, ConsumeInitMode.MAX, subscriptionData, fifo, new PopMessageResultFilterImpl(maxAttempts), request.hasAttemptId() ? request.getAttemptId() : null, timeRemaining ); } final boolean autoRenew = proxyConfig.isEnableProxyAutoRenew() && request.getAutoRenew(); popFuture.thenAccept(popResult -> { Runnable doAfterWrite = null; if (autoRenew) { doAfterWrite = handleAutoRenew(ctx, request, group, topic, popResult, writer); } writer.writeAndComplete(ctx, request, popResult, doAfterWrite); }).exceptionally(t -> { writer.writeAndComplete(ctx, request, t); return null; }); } catch (Throwable t) { writer.writeAndComplete(ctx, request, t); } } private Runnable handleAutoRenew(ProxyContext ctx, ReceiveMessageRequest request, String group, String topic, PopResult popResult, ReceiveMessageResponseStreamWriter writer ) { if (!PopStatus.FOUND.equals(popResult.getPopStatus())) { return null; } GrpcClientChannel clientChannel = grpcChannelManager.getChannel(ctx.getClientID()); if (clientChannel == null) { GrpcProxyException e = new GrpcProxyException(Code.MESSAGE_NOT_FOUND, String.format("The client [%s] is disconnected.", ctx.getClientID())); popResult.getMsgFoundList().forEach(messageExt -> writer.processThrowableWhenWriteMessage(e, ctx, request, messageExt)); throw e; } return () -> { List messageExtList = popResult.getMsgFoundList(); for (MessageExt messageExt : messageExtList) { String receiptHandle = messageExt.getProperty(MessageConst.PROPERTY_POP_CK); if (receiptHandle != null) { MessageReceiptHandle messageReceiptHandle = new MessageReceiptHandle(group, topic, messageExt.getQueueId(), receiptHandle, messageExt.getMsgId(), messageExt.getQueueOffset(), messageExt.getReconsumeTimes()); messagingProcessor.addReceiptHandle(ctx, clientChannel, group, messageExt.getMsgId(), messageReceiptHandle); } } }; } protected ReceiveMessageResponseStreamWriter createWriter(ProxyContext ctx, StreamObserver responseObserver) { return new ReceiveMessageResponseStreamWriter( this.messagingProcessor, responseObserver ); } protected static class ReceiveMessageQueueSelector implements QueueSelector { private final String brokerName; public ReceiveMessageQueueSelector(String brokerName) { this.brokerName = brokerName; } @Override public AddressableMessageQueue select(ProxyContext ctx, MessageQueueView messageQueueView) { try { AddressableMessageQueue addressableMessageQueue = null; MessageQueueSelector messageQueueSelector = messageQueueView.getReadSelector(); if (StringUtils.isNotBlank(brokerName)) { addressableMessageQueue = messageQueueSelector.getQueueByBrokerName(brokerName); } if (addressableMessageQueue == null) { addressableMessageQueue = messageQueueSelector.selectOne(true); } return addressableMessageQueue; } catch (Throwable t) { return null; } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Message; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import com.google.protobuf.util.Timestamps; import io.grpc.stub.StreamObserver; import java.time.Duration; import java.util.Iterator; import java.util.List; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter; import org.apache.rocketmq.proxy.processor.MessagingProcessor; public class ReceiveMessageResponseStreamWriter { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final long NACK_INVISIBLE_TIME = Duration.ofSeconds(1).toMillis(); protected final MessagingProcessor messagingProcessor; protected final StreamObserver streamObserver; public ReceiveMessageResponseStreamWriter( MessagingProcessor messagingProcessor, StreamObserver observer) { this.messagingProcessor = messagingProcessor; this.streamObserver = observer; } public void writeAndComplete(ProxyContext ctx, ReceiveMessageRequest request, PopResult popResult) { writeAndComplete(ctx, request, popResult, null); } public void writeAndComplete(ProxyContext ctx, ReceiveMessageRequest request, PopResult popResult, Runnable doAfterWrite) { PopStatus status = popResult.getPopStatus(); List messageFoundList = popResult.getMsgFoundList(); try { switch (status) { case FOUND: if (messageFoundList.isEmpty()) { streamObserver.onNext(ReceiveMessageResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.MESSAGE_NOT_FOUND, "no match message")) .build()); } else { try { streamObserver.onNext(ReceiveMessageResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); } catch (Throwable t) { messageFoundList.forEach(messageExt -> this.processThrowableWhenWriteMessage(t, ctx, request, messageExt)); throw t; } Iterator messageIterator = messageFoundList.iterator(); while (messageIterator.hasNext()) { MessageExt curMessageExt = messageIterator.next(); Message curMessage = convertToMessage(curMessageExt); try { streamObserver.onNext(ReceiveMessageResponse.newBuilder() .setMessage(curMessage) .build()); } catch (Throwable t) { this.processThrowableWhenWriteMessage(t, ctx, request, curMessageExt); messageIterator.forEachRemaining(messageExt -> this.processThrowableWhenWriteMessage(t, ctx, request, messageExt)); return; } } } break; case POLLING_FULL: streamObserver.onNext(ReceiveMessageResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.TOO_MANY_REQUESTS, "polling full")) .build()); break; case NO_NEW_MSG: case POLLING_NOT_FOUND: default: streamObserver.onNext(ReceiveMessageResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.MESSAGE_NOT_FOUND, "no new message")) .build()); break; } if (doAfterWrite != null) { doAfterWrite.run(); } } catch (Throwable t) { writeResponseWithErrorIgnore( ReceiveMessageResponse.newBuilder().setStatus(ResponseBuilder.getInstance().buildStatus(t)).build()); } finally { onComplete(); } } protected Message convertToMessage(MessageExt messageExt) { return GrpcConverter.getInstance().buildMessage(messageExt); } protected void processThrowableWhenWriteMessage(Throwable throwable, ProxyContext ctx, ReceiveMessageRequest request, MessageExt messageExt) { String handle = messageExt.getProperty(MessageConst.PROPERTY_POP_CK); if (handle == null) { return; } this.messagingProcessor.changeInvisibleTime( ctx, ReceiptHandle.decode(handle), messageExt.getMsgId(), request.getGroup().getName(), request.getMessageQueue().getTopic().getName(), NACK_INVISIBLE_TIME, null, MessagingProcessor.DEFAULT_TIMEOUT_MILLS, true ); } public void writeAndComplete(ProxyContext ctx, Code code, String message) { writeResponseWithErrorIgnore( ReceiveMessageResponse.newBuilder().setStatus(ResponseBuilder.getInstance().buildStatus(code, message)).build()); onComplete(); } public void writeAndComplete(ProxyContext ctx, ReceiveMessageRequest request, Throwable throwable) { writeResponseWithErrorIgnore( ReceiveMessageResponse.newBuilder().setStatus(ResponseBuilder.getInstance().buildStatus(throwable)).build()); onComplete(); } protected void writeResponseWithErrorIgnore(ReceiveMessageResponse response) { try { ResponseWriter.getInstance().writeResponse(streamObserver, response); } catch (Exception e) { log.error("err when write receive message response", e); } } protected void onComplete() { writeResponseWithErrorIgnore(ReceiveMessageResponse.newBuilder() .setDeliveryTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .build()); try { streamObserver.onCompleted(); } catch (Exception e) { log.error("err when complete receive message response", e); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.producer; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class ForwardMessageToDLQActivity extends AbstractMessagingActivity { public ForwardMessageToDLQActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, ForwardMessageToDeadLetterQueueRequest request) { CompletableFuture future = new CompletableFuture<>(); try { validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); String group = request.getGroup().getName(); String handleString = request.getReceiptHandle(); MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, request.getMessageId(), request.getReceiptHandle()); if (messageReceiptHandle != null) { handleString = messageReceiptHandle.getReceiptHandleStr(); } ReceiptHandle receiptHandle = ReceiptHandle.decode(handleString); String liteTopic = request.hasLiteTopic() ? request.getLiteTopic() : null; return this.messagingProcessor.forwardMessageToDeadLetterQueue( ctx, receiptHandle, request.getMessageId(), request.getGroup().getName(), request.getTopic().getName(), liteTopic ).thenApply(result -> convertToForwardMessageToDeadLetterQueueResponse(ctx, result)); } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected ForwardMessageToDeadLetterQueueResponse convertToForwardMessageToDeadLetterQueueResponse(ProxyContext ctx, RemotingCommand result) { return ForwardMessageToDeadLetterQueueResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(result.getCode(), result.getRemark())) .build(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/RecallMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.producer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.RecallMessageResponse; import apache.rocketmq.v2.Resource; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import java.time.Duration; import java.util.concurrent.CompletableFuture; public class RecallMessageActivity extends AbstractMessagingActivity { public RecallMessageActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture recallMessage(ProxyContext ctx, RecallMessageRequest request) { CompletableFuture future = new CompletableFuture<>(); try { Resource topic = request.getTopic(); validateTopic(topic); future = this.messagingProcessor.recallMessage( ctx, topic.getName(), request.getRecallHandle(), Duration.ofSeconds(2).toMillis() ).thenApply(result -> RecallMessageResponse.newBuilder() .setMessageId(result) .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); } catch (Throwable t) { future.completeExceptionally(t); } return future; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.producer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Encoding; import apache.rocketmq.v2.MessageType; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.SendMessageResponse; import apache.rocketmq.v2.SendResultEntry; import com.google.common.collect.Maps; import com.google.common.hash.Hashing; import com.google.protobuf.ByteString; import com.google.protobuf.Timestamp; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcValidator; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.QueueSelector; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueView; public class SendMessageActivity extends AbstractMessagingActivity { public SendMessageActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture sendMessage(ProxyContext ctx, SendMessageRequest request) { CompletableFuture future = new CompletableFuture<>(); try { if (request.getMessagesCount() <= 0) { throw new GrpcProxyException(Code.MESSAGE_CORRUPTED, "no message to send"); } List messageList = request.getMessagesList(); apache.rocketmq.v2.Message message = messageList.get(0); Resource topic = message.getTopic(); validateTopic(topic); future = this.messagingProcessor.sendMessage( ctx, new SendMessageQueueSelector(request), topic.getName(), buildSysFlag(message), buildMessage(ctx, request.getMessagesList(), topic) ).thenApply(result -> convertToSendMessageResponse(ctx, request, result)); } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected List buildMessage(ProxyContext context, List protoMessageList, Resource topic) { String topicName = topic.getName(); List messageExtList = new ArrayList<>(); for (apache.rocketmq.v2.Message protoMessage : protoMessageList) { if (!protoMessage.getTopic().equals(topic)) { throw new GrpcProxyException(Code.MESSAGE_CORRUPTED, "topic in message is not same"); } // here use topicName as producerGroup for transactional checker. messageExtList.add(buildMessage(context, protoMessage, topicName)); } return messageExtList; } protected Message buildMessage(ProxyContext context, apache.rocketmq.v2.Message protoMessage, String producerGroup) { String topicName = protoMessage.getTopic().getName(); validateMessageBodySize(protoMessage.getBody()); Message messageExt = new Message(); messageExt.setTopic(topicName); messageExt.setBody(protoMessage.getBody().toByteArray()); Map messageProperty = this.buildMessageProperty(context, protoMessage, producerGroup); MessageAccessor.setProperties(messageExt, messageProperty); return messageExt; } protected int buildSysFlag(apache.rocketmq.v2.Message protoMessage) { // sysFlag (body encoding & message type) int sysFlag = 0; Encoding bodyEncoding = protoMessage.getSystemProperties().getBodyEncoding(); if (bodyEncoding.equals(Encoding.GZIP)) { sysFlag |= MessageSysFlag.COMPRESSED_FLAG; } // transaction MessageType messageType = protoMessage.getSystemProperties().getMessageType(); if (messageType.equals(MessageType.TRANSACTION)) { sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE; } return sysFlag; } protected void validateMessageBodySize(ByteString body) { if (ConfigurationManager.getProxyConfig().isEnableMessageBodyEmptyCheck()) { if (body.isEmpty()) { throw new GrpcProxyException(Code.MESSAGE_BODY_EMPTY, "message body cannot be empty"); } } int max = ConfigurationManager.getProxyConfig().getMaxMessageSize(); if (max <= 0) { return; } if (body.size() > max) { throw new GrpcProxyException(Code.MESSAGE_BODY_TOO_LARGE, "message body cannot exceed the max " + max); } } protected void validateMessageKey(String key) { if (StringUtils.isNotEmpty(key)) { if (StringUtils.isBlank(key)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_KEY, "key cannot be the char sequence of whitespace"); } if (GrpcValidator.getInstance().containControlCharacter(key)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_KEY, "key cannot contain control character"); } } } protected void validateMessageGroup(String messageGroup) { if (StringUtils.isNotEmpty(messageGroup)) { if (StringUtils.isBlank(messageGroup)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_GROUP, "message group cannot be the char sequence of whitespace"); } int maxSize = ConfigurationManager.getProxyConfig().getMaxMessageGroupSize(); if (maxSize <= 0) { return; } if (messageGroup.getBytes(StandardCharsets.UTF_8).length >= maxSize) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_GROUP, "message group exceed the max size " + maxSize); } if (GrpcValidator.getInstance().containControlCharacter(messageGroup)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_GROUP, "message group cannot contain control character"); } } } protected void validateDelayTime(long deliveryTimestampMs) { long maxDelay = ConfigurationManager.getProxyConfig().getMaxDelayTimeMills(); if (maxDelay <= 0) { return; } if (deliveryTimestampMs - System.currentTimeMillis() > maxDelay) { throw new GrpcProxyException(Code.ILLEGAL_DELIVERY_TIME, "the max delay time of message is too large, max is " + maxDelay); } } protected void validateTransactionRecoverySecond(long transactionRecoverySecond) { long maxTransactionRecoverySecond = ConfigurationManager.getProxyConfig().getMaxTransactionRecoverySecond(); if (maxTransactionRecoverySecond <= 0) { return; } if (transactionRecoverySecond > maxTransactionRecoverySecond) { throw new GrpcProxyException(Code.BAD_REQUEST, "the max transaction recovery time of message is too large, max is " + maxTransactionRecoverySecond); } } protected Map buildMessageProperty(ProxyContext context, apache.rocketmq.v2.Message message, String producerGroup) { long userPropertySize = 0; ProxyConfig config = ConfigurationManager.getProxyConfig(); org.apache.rocketmq.common.message.Message messageWithHeader = new org.apache.rocketmq.common.message.Message(); // set user properties Map userProperties = message.getUserPropertiesMap(); if (userProperties.size() > config.getUserPropertyMaxNum()) { throw new GrpcProxyException(Code.MESSAGE_PROPERTIES_TOO_LARGE, "too many user properties, max is " + config.getUserPropertyMaxNum()); } for (Map.Entry userPropertiesEntry : userProperties.entrySet()) { if (MessageConst.STRING_HASH_SET.contains(userPropertiesEntry.getKey())) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, "property is used by system: " + userPropertiesEntry.getKey()); } if (GrpcValidator.getInstance().containControlCharacter(userPropertiesEntry.getKey())) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, "the key of property cannot contain control character"); } if (GrpcValidator.getInstance().containControlCharacter(userPropertiesEntry.getValue())) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, "the value of property cannot contain control character"); } userPropertySize += userPropertiesEntry.getKey().getBytes(StandardCharsets.UTF_8).length; userPropertySize += userPropertiesEntry.getValue().getBytes(StandardCharsets.UTF_8).length; } MessageAccessor.setProperties(messageWithHeader, Maps.newHashMap(userProperties)); // set tag String tag = message.getSystemProperties().getTag(); GrpcValidator.getInstance().validateTag(tag); messageWithHeader.setTags(tag); userPropertySize += tag.getBytes(StandardCharsets.UTF_8).length; // set keys List keysList = message.getSystemProperties().getKeysList(); for (String key : keysList) { validateMessageKey(key); userPropertySize += key.getBytes(StandardCharsets.UTF_8).length; } if (keysList.size() > 0) { messageWithHeader.setKeys(keysList); } if (userPropertySize > config.getMaxUserPropertySize()) { throw new GrpcProxyException(Code.MESSAGE_PROPERTIES_TOO_LARGE, "the total size of user property is too large, max is " + config.getMaxUserPropertySize()); } // set message id String messageId = message.getSystemProperties().getMessageId(); if (StringUtils.isBlank(messageId)) { throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_ID, "message id cannot be empty"); } MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, messageId); // set transaction property MessageType messageType = message.getSystemProperties().getMessageType(); if (messageType.equals(MessageType.TRANSACTION)) { MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); if (message.getSystemProperties().hasOrphanedTransactionRecoveryDuration()) { long transactionRecoverySecond = Durations.toSeconds(message.getSystemProperties().getOrphanedTransactionRecoveryDuration()); validateTransactionRecoverySecond(transactionRecoverySecond); MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS, String.valueOf(transactionRecoverySecond)); } } // set delay level or deliver timestamp fillDelayMessageProperty(message, messageWithHeader); // set priority if (message.getSystemProperties().hasPriority()) { int priority = message.getSystemProperties().getPriority(); messageWithHeader.setPriority(priority); } // set reconsume times int reconsumeTimes = message.getSystemProperties().getDeliveryAttempt(); MessageAccessor.setReconsumeTime(messageWithHeader, String.valueOf(reconsumeTimes)); // set producer group MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_PRODUCER_GROUP, producerGroup); // set message group String messageGroup = message.getSystemProperties().getMessageGroup(); if (StringUtils.isNotEmpty(messageGroup)) { validateMessageGroup(messageGroup); MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_SHARDING_KEY, messageGroup); } // set lite topic String liteTopic = message.getSystemProperties().getLiteTopic(); if (StringUtils.isNotEmpty(liteTopic)) { validateLiteTopic(liteTopic); MessageAccessor.setLiteTopic(messageWithHeader, liteTopic); } // set trace context String traceContext = message.getSystemProperties().getTraceContext(); if (!traceContext.isEmpty()) { MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TRACE_CONTEXT, traceContext); } String bornHost = message.getSystemProperties().getBornHost(); if (StringUtils.isBlank(bornHost)) { bornHost = context.getRemoteAddress(); } if (StringUtils.isNotBlank(bornHost)) { MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_BORN_HOST, bornHost); } Timestamp bornTimestamp = message.getSystemProperties().getBornTimestamp(); if (Timestamps.isValid(bornTimestamp)) { MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_BORN_TIMESTAMP, String.valueOf(Timestamps.toMillis(bornTimestamp))); } return messageWithHeader.getProperties(); } protected void fillDelayMessageProperty(apache.rocketmq.v2.Message message, org.apache.rocketmq.common.message.Message messageWithHeader) { if (message.getSystemProperties().hasDeliveryTimestamp()) { Timestamp deliveryTimestamp = message.getSystemProperties().getDeliveryTimestamp(); long deliveryTimestampMs = Timestamps.toMillis(deliveryTimestamp); validateDelayTime(deliveryTimestampMs); ProxyConfig config = ConfigurationManager.getProxyConfig(); if (config.isUseDelayLevel()) { int delayLevel = config.computeDelayLevel(deliveryTimestampMs); MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_DELAY_TIME_LEVEL, String.valueOf(delayLevel)); } String timestampString = String.valueOf(deliveryTimestampMs); MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TIMER_DELIVER_MS, timestampString); } } protected SendMessageResponse convertToSendMessageResponse(ProxyContext ctx, SendMessageRequest request, List resultList) { SendMessageResponse.Builder builder = SendMessageResponse.newBuilder(); Set responseCodes = new HashSet<>(); for (SendResult result : resultList) { SendResultEntry resultEntry; switch (result.getSendStatus()) { case FLUSH_DISK_TIMEOUT: resultEntry = SendResultEntry.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.MASTER_PERSISTENCE_TIMEOUT, "send message failed, sendStatus=" + result.getSendStatus())) .build(); break; case FLUSH_SLAVE_TIMEOUT: resultEntry = SendResultEntry.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.SLAVE_PERSISTENCE_TIMEOUT, "send message failed, sendStatus=" + result.getSendStatus())) .build(); break; case SLAVE_NOT_AVAILABLE: resultEntry = SendResultEntry.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.HA_NOT_AVAILABLE, "send message failed, sendStatus=" + result.getSendStatus())) .build(); break; case SEND_OK: resultEntry = SendResultEntry.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .setOffset(result.getQueueOffset()) .setMessageId(StringUtils.defaultString(result.getMsgId())) .setTransactionId(StringUtils.defaultString(result.getTransactionId())) .setRecallHandle(StringUtils.defaultString(result.getRecallHandle())) .build(); break; default: resultEntry = SendResultEntry.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "send message failed, sendStatus=" + result.getSendStatus())) .build(); break; } builder.addEntries(resultEntry); responseCodes.add(resultEntry.getStatus().getCode()); } if (responseCodes.size() > 1) { builder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.MULTIPLE_RESULTS, Code.MULTIPLE_RESULTS.name())); } else if (responseCodes.size() == 1) { Code code = responseCodes.stream().findAny().get(); builder.setStatus(ResponseBuilder.getInstance().buildStatus(code, code.name())); } else { builder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "send status is empty")); } return builder.build(); } protected static class SendMessageQueueSelector implements QueueSelector { private final SendMessageRequest request; public SendMessageQueueSelector(SendMessageRequest request) { this.request = request; } @Override public AddressableMessageQueue select(ProxyContext ctx, MessageQueueView messageQueueView) { try { apache.rocketmq.v2.Message message = request.getMessages(0); String shardingKey = null; if (request.getMessagesCount() == 1) { shardingKey = message.getSystemProperties().getMessageGroup(); // lite topic if (StringUtils.isBlank(shardingKey)) { shardingKey = message.getSystemProperties().getLiteTopic(); } } AddressableMessageQueue targetMessageQueue; if (StringUtils.isNotEmpty(shardingKey)) { // With shardingKey List writeQueues = messageQueueView.getWriteSelector().getQueues(); int bucket = Hashing.consistentHash(shardingKey.hashCode(), writeQueues.size()); targetMessageQueue = writeQueues.get(bucket); } else { targetMessageQueue = messageQueueView.getWriteSelector().selectOneByPipeline(false); } return targetMessageQueue; } catch (Exception e) { return null; } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.route; import apache.rocketmq.v2.Address; import apache.rocketmq.v2.AddressScheme; import apache.rocketmq.v2.Assignment; import apache.rocketmq.v2.Broker; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Endpoints; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.MessageType; import apache.rocketmq.v2.Permission; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryAssignmentResponse; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.Resource; import com.google.common.net.HostAndPort; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class RouteActivity extends AbstractMessagingActivity { public RouteActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture queryRoute(ProxyContext ctx, QueryRouteRequest request) { CompletableFuture future = new CompletableFuture<>(); try { validateTopic(request.getTopic()); List addressList = this.convertToAddressList(ctx, request.getEndpoints()); String topicName = request.getTopic().getName(); ProxyTopicRouteData proxyTopicRouteData = this.messagingProcessor.getTopicRouteDataForProxy( ctx, addressList, topicName); List messageQueueList = new ArrayList<>(); Map> brokerMap = buildBrokerMap(proxyTopicRouteData.getBrokerDatas()); TopicMessageType topicMessageType = messagingProcessor.getMetadataService().getTopicMessageType(ctx, topicName); for (QueueData queueData : proxyTopicRouteData.getQueueDatas()) { String brokerName = queueData.getBrokerName(); Map brokerIdMap = brokerMap.get(brokerName); if (brokerIdMap == null) { break; } for (Broker broker : brokerIdMap.values()) { messageQueueList.addAll(this.genMessageQueueFromQueueData(queueData, request.getTopic(), topicMessageType, broker)); } } QueryRouteResponse response = QueryRouteResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .addAllMessageQueues(messageQueueList) .build(); future.complete(response); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture queryAssignment(ProxyContext ctx, QueryAssignmentRequest request) { CompletableFuture future = new CompletableFuture<>(); try { validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); List addressList = this.convertToAddressList(ctx, request.getEndpoints()); ProxyTopicRouteData proxyTopicRouteData = this.messagingProcessor.getTopicRouteDataForProxy( ctx, addressList, request.getTopic().getName()); boolean isFifo = false; boolean isLite = false; SubscriptionGroupConfig groupConfig = this.messagingProcessor .getSubscriptionGroupConfig(ctx, request.getGroup().getName()); if (groupConfig != null) { isFifo = groupConfig.isConsumeMessageOrderly(); isLite = StringUtils.isNotEmpty(groupConfig.getLiteBindTopic()); } List assignments = new ArrayList<>(); Map> brokerMap = buildBrokerMap(proxyTopicRouteData.getBrokerDatas()); for (QueueData queueData : proxyTopicRouteData.getQueueDatas()) { if (PermName.isReadable(queueData.getPerm()) && queueData.getReadQueueNums() > 0) { Map brokerIdMap = brokerMap.get(queueData.getBrokerName()); if (brokerIdMap != null) { Broker broker = brokerIdMap.get(MixAll.MASTER_ID); Permission permission = this.convertToPermission(queueData.getPerm()); if (isFifo && !isLite) { for (int i = 0; i < queueData.getReadQueueNums(); i++) { MessageQueue defaultMessageQueue = MessageQueue.newBuilder() .setTopic(request.getTopic()) .setId(i) .setPermission(permission) .setBroker(broker) .build(); assignments.add(Assignment.newBuilder() .setMessageQueue(defaultMessageQueue) .build()); } } else { MessageQueue defaultMessageQueue = MessageQueue.newBuilder() .setTopic(request.getTopic()) .setId(-1) .setPermission(permission) .setBroker(broker) .build(); assignments.add(Assignment.newBuilder() .setMessageQueue(defaultMessageQueue) .build()); } } } } QueryAssignmentResponse response; if (assignments.isEmpty()) { response = QueryAssignmentResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.FORBIDDEN, "no readable queue")) .build(); } else { response = QueryAssignmentResponse.newBuilder() .addAllAssignments(assignments) .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build(); } future.complete(response); } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected Permission convertToPermission(int perm) { boolean isReadable = PermName.isReadable(perm); boolean isWriteable = PermName.isWriteable(perm); if (isReadable && isWriteable) { return Permission.READ_WRITE; } if (isReadable) { return Permission.READ; } if (isWriteable) { return Permission.WRITE; } return Permission.NONE; } protected List convertToAddressList(ProxyContext ctx, Endpoints endpoints) { boolean useEndpointPort = ConfigurationManager.getProxyConfig().isUseEndpointPortFromRequest(); List addressList = new ArrayList<>(); for (Address address : endpoints.getAddressesList()) { int port = ConfigurationManager.getProxyConfig().getGrpcServerPort(); if (useEndpointPort) { port = address.getPort(); } addressList.add(new org.apache.rocketmq.proxy.common.Address( org.apache.rocketmq.proxy.common.Address.AddressScheme.valueOf(endpoints.getScheme().name()), HostAndPort.fromParts(address.getHost(), port))); } log.debug("gRPC build address. clientId={}, addressList={}", ctx.getClientID(), addressList); return addressList; } protected Map> buildBrokerMap( List brokerDataList) { Map> brokerMap = new HashMap<>(); for (ProxyTopicRouteData.ProxyBrokerData brokerData : brokerDataList) { Map brokerIdMap = new HashMap<>(); String brokerName = brokerData.getBrokerName(); for (Map.Entry> entry : brokerData.getBrokerAddrs().entrySet()) { Long brokerId = entry.getKey(); List
    addressList = new ArrayList<>(); AddressScheme addressScheme = AddressScheme.IPv4; for (org.apache.rocketmq.proxy.common.Address address : entry.getValue()) { addressScheme = AddressScheme.valueOf(address.getAddressScheme().name()); addressList.add(Address.newBuilder() .setHost(address.getHostAndPort().getHost()) .setPort(address.getHostAndPort().getPort()) .build()); } Broker broker = Broker.newBuilder() .setName(brokerName) .setId(Math.toIntExact(brokerId)) .setEndpoints(Endpoints.newBuilder() .setScheme(addressScheme) .addAllAddresses(addressList) .build()) .build(); brokerIdMap.put(brokerId, broker); } brokerMap.put(brokerName, brokerIdMap); } return brokerMap; } protected List genMessageQueueFromQueueData(QueueData queueData, Resource topic, TopicMessageType topicMessageType, Broker broker) { List messageQueueList = new ArrayList<>(); int r = 0; int w = 0; int rw = 0; int n = 0; if (PermName.isWriteable(queueData.getPerm()) && PermName.isReadable(queueData.getPerm())) { rw = Math.min(queueData.getWriteQueueNums(), queueData.getReadQueueNums()); r = queueData.getReadQueueNums() - rw; w = queueData.getWriteQueueNums() - rw; } else if (PermName.isWriteable(queueData.getPerm())) { w = queueData.getWriteQueueNums(); } else if (PermName.isReadable(queueData.getPerm())) { r = queueData.getReadQueueNums(); } else if (!PermName.isAccessible(queueData.getPerm())) { n = Math.max(1, Math.max(queueData.getWriteQueueNums(), queueData.getReadQueueNums())); } // r here means readOnly queue nums, w means writeOnly queue nums, while rw means both readable and writable queue nums. int queueIdIndex = 0; for (int i = 0; i < r; i++) { MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic) .setId(queueIdIndex++) .setPermission(Permission.READ) .addAllAcceptMessageTypes(parseTopicMessageType(topicMessageType)) .build(); messageQueueList.add(messageQueue); } for (int i = 0; i < w; i++) { MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic) .setId(queueIdIndex++) .setPermission(Permission.WRITE) .addAllAcceptMessageTypes(parseTopicMessageType(topicMessageType)) .build(); messageQueueList.add(messageQueue); } for (int i = 0; i < rw; i++) { MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic) .setId(queueIdIndex++) .setPermission(Permission.READ_WRITE) .addAllAcceptMessageTypes(parseTopicMessageType(topicMessageType)) .build(); messageQueueList.add(messageQueue); } for (int i = 0; i < n; i++) { MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic) .setId(queueIdIndex++) .setPermission(Permission.NONE) .addAllAcceptMessageTypes(parseTopicMessageType(topicMessageType)) .build(); messageQueueList.add(messageQueue); } return messageQueueList; } private List parseTopicMessageType(TopicMessageType topicMessageType) { switch (topicMessageType) { case NORMAL: return Collections.singletonList(MessageType.NORMAL); case FIFO: return Collections.singletonList(MessageType.FIFO); case LITE: return Collections.singletonList(MessageType.LITE); case TRANSACTION: return Collections.singletonList(MessageType.TRANSACTION); case DELAY: return Collections.singletonList(MessageType.DELAY); case PRIORITY: return Collections.singletonList(MessageType.PRIORITY); case MIXED: return Arrays.asList(MessageType.NORMAL, MessageType.FIFO, MessageType.DELAY, MessageType.TRANSACTION); default: return Collections.singletonList(MessageType.MESSAGE_TYPE_UNSPECIFIED); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.transaction; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.EndTransactionResponse; import apache.rocketmq.v2.TransactionResolution; import apache.rocketmq.v2.TransactionSource; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.AbstractMessagingActivity; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.TransactionStatus; public class EndTransactionActivity extends AbstractMessagingActivity { public EndTransactionActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } public CompletableFuture endTransaction(ProxyContext ctx, EndTransactionRequest request) { CompletableFuture future = new CompletableFuture<>(); try { validateTopic(request.getTopic()); if (StringUtils.isBlank(request.getTransactionId())) { throw new GrpcProxyException(Code.INVALID_TRANSACTION_ID, "transaction id cannot be empty"); } TransactionStatus transactionStatus = TransactionStatus.UNKNOWN; TransactionResolution transactionResolution = request.getResolution(); switch (transactionResolution) { case COMMIT: transactionStatus = TransactionStatus.COMMIT; break; case ROLLBACK: transactionStatus = TransactionStatus.ROLLBACK; break; default: break; } future = this.messagingProcessor.endTransaction( ctx, request.getTopic().getName(), request.getTransactionId(), request.getMessageId(), request.getTopic().getName(), transactionStatus, request.getSource().equals(TransactionSource.SOURCE_SERVER_CHECK)) .thenApply(r -> EndTransactionResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); } catch (Throwable t) { future.completeExceptionally(t); } return future; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsConstant.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.metrics; public class ProxyMetricsConstant { public static final String GAUGE_PROXY_UP = "rocketmq_proxy_up"; public static final String LABEL_PROXY_MODE = "proxy_mode"; public static final String NODE_TYPE_PROXY = "proxy"; } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.metrics; import com.google.common.base.Splitter; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.metrics.MetricsExporterType; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.slf4j.bridge.SLF4JBridgeHandler; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.AGGREGATION_DELTA; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_AGGREGATION; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_CLUSTER_NAME; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_NODE_ID; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_NODE_TYPE; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.OPEN_TELEMETRY_METER_NAME; import static org.apache.rocketmq.proxy.metrics.ProxyMetricsConstant.GAUGE_PROXY_UP; import static org.apache.rocketmq.proxy.metrics.ProxyMetricsConstant.LABEL_PROXY_MODE; import static org.apache.rocketmq.proxy.metrics.ProxyMetricsConstant.NODE_TYPE_PROXY; public class ProxyMetricsManager implements StartAndShutdown { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static ProxyConfig proxyConfig; private final static Map LABEL_MAP = new HashMap<>(); public static Supplier attributesBuilderSupplier; private OtlpGrpcMetricExporter metricExporter; private PeriodicMetricReader periodicMetricReader; private PrometheusHttpServer prometheusHttpServer; private MetricExporter loggingMetricExporter; public static ObservableLongGauge proxyUp = null; public static void initLocalMode(BrokerMetricsManager brokerMetricsManager, ProxyConfig proxyConfig) { if (proxyConfig.getMetricsExporterType() == MetricsExporterType.DISABLE) { return; } ProxyMetricsManager.proxyConfig = proxyConfig; LABEL_MAP.put(LABEL_NODE_TYPE, NODE_TYPE_PROXY); LABEL_MAP.put(LABEL_CLUSTER_NAME, proxyConfig.getProxyClusterName()); LABEL_MAP.put(LABEL_NODE_ID, proxyConfig.getProxyName()); LABEL_MAP.put(LABEL_PROXY_MODE, proxyConfig.getProxyMode().toLowerCase()); initMetrics(brokerMetricsManager.getBrokerMeter(), brokerMetricsManager::newAttributesBuilder); } public static ProxyMetricsManager initClusterMode(ProxyConfig proxyConfig) { ProxyMetricsManager.proxyConfig = proxyConfig; return new ProxyMetricsManager(); } public static AttributesBuilder newAttributesBuilder() { AttributesBuilder attributesBuilder; if (attributesBuilderSupplier == null) { attributesBuilder = Attributes.builder(); LABEL_MAP.forEach(attributesBuilder::put); return attributesBuilder; } attributesBuilder = attributesBuilderSupplier.get(); LABEL_MAP.forEach(attributesBuilder::put); return attributesBuilder; } private static void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { ProxyMetricsManager.attributesBuilderSupplier = attributesBuilderSupplier; proxyUp = meter.gaugeBuilder(GAUGE_PROXY_UP) .setDescription("proxy status") .ofLongs() .buildWithCallback(measurement -> measurement.record(1, newAttributesBuilder().build())); } public ProxyMetricsManager() { } private boolean checkConfig() { if (proxyConfig == null) { return false; } MetricsExporterType exporterType = proxyConfig.getMetricsExporterType(); if (!exporterType.isEnable()) { return false; } switch (exporterType) { case OTLP_GRPC: return StringUtils.isNotBlank(proxyConfig.getMetricsGrpcExporterTarget()); case PROM: return true; case LOG: return true; } return false; } @Override public void start() throws Exception { MetricsExporterType metricsExporterType = proxyConfig.getMetricsExporterType(); if (metricsExporterType == MetricsExporterType.DISABLE) { return; } if (!checkConfig()) { log.error("check metrics config failed, will not export metrics"); return; } String labels = proxyConfig.getMetricsLabel(); if (StringUtils.isNotBlank(labels)) { List kvPairs = Splitter.on(',').omitEmptyStrings().splitToList(labels); for (String item : kvPairs) { String[] split = item.split(":"); if (split.length != 2) { log.warn("metricsLabel is not valid: {}", labels); continue; } LABEL_MAP.put(split[0], split[1]); } } if (proxyConfig.isMetricsInDelta()) { LABEL_MAP.put(LABEL_AGGREGATION, AGGREGATION_DELTA); } LABEL_MAP.put(LABEL_NODE_TYPE, NODE_TYPE_PROXY); LABEL_MAP.put(LABEL_CLUSTER_NAME, proxyConfig.getProxyClusterName()); LABEL_MAP.put(LABEL_NODE_ID, proxyConfig.getProxyName()); LABEL_MAP.put(LABEL_PROXY_MODE, proxyConfig.getProxyMode().toLowerCase()); SdkMeterProviderBuilder providerBuilder = SdkMeterProvider.builder() .setResource(Resource.empty()); if (metricsExporterType == MetricsExporterType.OTLP_GRPC) { String endpoint = proxyConfig.getMetricsGrpcExporterTarget(); if (!endpoint.startsWith("http")) { endpoint = "https://" + endpoint; } OtlpGrpcMetricExporterBuilder metricExporterBuilder = OtlpGrpcMetricExporter.builder() .setEndpoint(endpoint) .setTimeout(proxyConfig.getMetricGrpcExporterTimeOutInMills(), TimeUnit.MILLISECONDS) .setAggregationTemporalitySelector(type -> { if (proxyConfig.isMetricsInDelta() && (type == InstrumentType.COUNTER || type == InstrumentType.OBSERVABLE_COUNTER || type == InstrumentType.HISTOGRAM)) { return AggregationTemporality.DELTA; } return AggregationTemporality.CUMULATIVE; }); String headers = proxyConfig.getMetricsGrpcExporterHeader(); if (StringUtils.isNotBlank(headers)) { Map headerMap = new HashMap<>(); List kvPairs = Splitter.on(',').omitEmptyStrings().splitToList(headers); for (String item : kvPairs) { String[] split = item.split(":"); if (split.length != 2) { log.warn("metricsGrpcExporterHeader is not valid: {}", headers); continue; } headerMap.put(split[0], split[1]); } headerMap.forEach(metricExporterBuilder::addHeader); } metricExporter = metricExporterBuilder.build(); periodicMetricReader = PeriodicMetricReader.builder(metricExporter) .setInterval(proxyConfig.getMetricGrpcExporterIntervalInMills(), TimeUnit.MILLISECONDS) .build(); providerBuilder.registerMetricReader(periodicMetricReader); } if (metricsExporterType == MetricsExporterType.PROM) { String promExporterHost = proxyConfig.getMetricsPromExporterHost(); if (StringUtils.isBlank(promExporterHost)) { promExporterHost = "0.0.0.0"; } prometheusHttpServer = PrometheusHttpServer.builder() .setHost(promExporterHost) .setPort(proxyConfig.getMetricsPromExporterPort()) .build(); providerBuilder.registerMetricReader(prometheusHttpServer); } if (metricsExporterType == MetricsExporterType.LOG) { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); loggingMetricExporter = OtlpJsonLoggingMetricExporter.create(proxyConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE); java.util.logging.Logger.getLogger(OtlpJsonLoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST); periodicMetricReader = PeriodicMetricReader.builder(loggingMetricExporter) .setInterval(proxyConfig.getMetricLoggingExporterIntervalInMills(), TimeUnit.MILLISECONDS) .build(); providerBuilder.registerMetricReader(periodicMetricReader); } Meter proxyMeter = OpenTelemetrySdk.builder() .setMeterProvider(providerBuilder.build()) .build() .getMeter(OPEN_TELEMETRY_METER_NAME); initMetrics(proxyMeter, null); } @Override public void shutdown() throws Exception { if (proxyConfig.getMetricsExporterType() == MetricsExporterType.OTLP_GRPC) { periodicMetricReader.forceFlush(); periodicMetricReader.shutdown(); metricExporter.shutdown(); } if (proxyConfig.getMetricsExporterType() == MetricsExporterType.PROM) { prometheusHttpServer.forceFlush(); prometheusHttpServer.shutdown(); } if (proxyConfig.getMetricsExporterType() == MetricsExporterType.LOG) { periodicMetricReader.forceFlush(); periodicMetricReader.shutdown(); loggingMetricExporter.shutdown(); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.service.ServiceManager; public abstract class AbstractProcessor extends AbstractStartAndShutdown { protected MessagingProcessor messagingProcessor; protected ServiceManager serviceManager; protected static final ProxyException EXPIRED_HANDLE_PROXY_EXCEPTION = new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired"); public AbstractProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager) { this.messagingProcessor = messagingProcessor; this.serviceManager = serviceManager; } protected void validateReceiptHandle(ReceiptHandle handle) { if (handle.isExpired()) { throw EXPIRED_HANDLE_PROXY_EXCEPTION; } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; public class BatchAckResult { private final ReceiptHandleMessage receiptHandleMessage; private AckResult ackResult; private ProxyException proxyException; public BatchAckResult(ReceiptHandleMessage receiptHandleMessage, AckResult ackResult) { this.receiptHandleMessage = receiptHandleMessage; this.ackResult = ackResult; } public BatchAckResult(ReceiptHandleMessage receiptHandleMessage, ProxyException proxyException) { this.receiptHandleMessage = receiptHandleMessage; this.proxyException = proxyException; } public ReceiptHandleMessage getReceiptHandleMessage() { return receiptHandleMessage; } public AckResult getAckResult() { return ackResult; } public ProxyException getProxyException() { return proxyException; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/ClientProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import apache.rocketmq.v2.Code; import com.google.common.util.concurrent.RateLimiter; import io.netty.channel.Channel; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ProducerChangeListener; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.lite.LiteSubscriptionAction; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; @SuppressWarnings("UnstableApiUsage") public class ClientProcessor extends AbstractProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final RateLimiter syncLiteSubscriptionRateLimiter; public ClientProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager) { super(messagingProcessor, serviceManager); ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); this.syncLiteSubscriptionRateLimiter = RateLimiter.create(proxyConfig.getMaxSyncLiteSubscriptionRate()); } public void registerProducer( ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo ) { this.serviceManager.getProducerManager().registerProducer(producerGroup, clientChannelInfo); } public void unRegisterProducer( ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo ) { this.serviceManager.getProducerManager().unregisterProducer(producerGroup, clientChannelInfo); } public Channel findProducerChannel( ProxyContext ctx, String producerGroup, String clientId ) { return this.serviceManager.getProducerManager().findChannel(clientId); } public void registerProducerChangeListener(ProducerChangeListener listener) { this.serviceManager.getProducerManager().appendProducerChangeListener(listener); } public void registerConsumer( ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set subList, boolean updateSubscription ) { validateLiteMode(ctx, consumerGroup, messageModel); if (MessageModel.LITE_SELECTIVE == messageModel) { validateLiteSubTopic(ctx, consumerGroup, subList); } this.serviceManager.getConsumerManager().registerConsumer( consumerGroup, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList, false, updateSubscription); } public CompletableFuture syncLiteSubscription(ProxyContext ctx, LiteSubscriptionDTO liteSubscriptionDTO, long timeoutMillis ) { try { validateLiteBindTopic(ctx, liteSubscriptionDTO.getGroup(), liteSubscriptionDTO.getTopic()); if (CollectionUtils.isNotEmpty(liteSubscriptionDTO.getLiteTopicSet())) { validateLiteSubscriptionQuota(ctx, liteSubscriptionDTO.getGroup(), liteSubscriptionDTO.getLiteTopicSet().size()); } if (LiteSubscriptionAction.PARTIAL_ADD == liteSubscriptionDTO.getAction()) { if (!syncLiteSubscriptionRateLimiter.tryAcquire()) { String msg = String.format("Too many syncLiteSubscription requests, topic=%s, group=%s, clientId=%s", liteSubscriptionDTO.getTopic(), liteSubscriptionDTO.getGroup(), ctx.getClientID()); log.warn(msg); throw new GrpcProxyException(Code.TOO_MANY_REQUESTS, msg); } } return this.serviceManager .getLiteSubscriptionService() .syncLiteSubscription(ctx, liteSubscriptionDTO, timeoutMillis); } catch (Throwable t) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(t); return future; } } public ClientChannelInfo findConsumerChannel( ProxyContext ctx, String consumerGroup, Channel channel ) { return this.serviceManager.getConsumerManager().findChannel(consumerGroup, channel); } public void unRegisterConsumer( ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo ) { this.serviceManager.getConsumerManager().unregisterConsumer(consumerGroup, clientChannelInfo, false); } public void doChannelCloseEvent(String remoteAddr, Channel channel) { this.serviceManager.getConsumerManager().doChannelCloseEvent(remoteAddr, channel); this.serviceManager.getProducerManager().doChannelCloseEvent(remoteAddr, channel); } public void registerConsumerIdsChangeListener(ConsumerIdsChangeListener listener) { this.serviceManager.getConsumerManager().appendConsumerIdsChangeListener(listener); } public ConsumerGroupInfo getConsumerGroupInfo(ProxyContext ctx, String consumerGroup) { return this.serviceManager.getConsumerManager().getConsumerGroupInfo(consumerGroup); } /** * Validates the message model for a given consumer group. * Ensures that regular groups do not use LITE mode and LITE groups use LITE mode. * * @param ctx the proxy context * @param group the consumer group name * @param messageModel the message model to validate */ protected void validateLiteMode(ProxyContext ctx, String group, MessageModel messageModel) { String bindTopic = getGroupOrException(ctx, group).getLiteBindTopic(); if (StringUtils.isEmpty(bindTopic)) { // regular group if (MessageModel.LITE_SELECTIVE == messageModel) { throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "regular group cannot use LITE mode: " + group); } } else { // lite group if (MessageModel.LITE_SELECTIVE != messageModel) { throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "lite group must use LITE mode: " + group); } } } protected void validateLiteSubTopic(ProxyContext ctx, String group, Set subList) { if (CollectionUtils.isEmpty(subList)) { return; } // check bindTopic for sub list validateLiteBindTopic(ctx, group, subList.iterator().next().getTopic()); } protected void validateLiteBindTopic(ProxyContext ctx, String group, String bindTopic) { String expectedBindTopic = getGroupOrException(ctx, group).getLiteBindTopic(); if (!Objects.equals(expectedBindTopic, bindTopic)) { throw new GrpcProxyException(Code.ILLEGAL_TOPIC, String.format("lite group %s is expected to bind topic %s, but actual is %s", group, expectedBindTopic, bindTopic)); } } protected void validateLiteSubscriptionQuota(ProxyContext ctx, String group, int actual) { int quota = getGroupOrException(ctx, group).getLiteSubClientQuota(); int quotaBuffer = 300; if (actual > quota + quotaBuffer) { throw new GrpcProxyException(Code.LITE_SUBSCRIPTION_QUOTA_EXCEEDED, "lite subscription quota exceeded: " + quota); } } protected SubscriptionGroupConfig getGroupOrException(ProxyContext ctx, String group) { SubscriptionGroupConfig groupConfig = this.messagingProcessor.getSubscriptionGroupConfig(ctx, group); if (groupConfig == null) { throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "group not found: " + group); } return groupConfig; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.common.utils.ProxyUtils; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ConsumerProcessor extends AbstractProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final ExecutorService executor; public ConsumerProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager, ExecutorService executor) { super(messagingProcessor, serviceManager); this.executor = executor; } public CompletableFuture popMessage( ProxyContext ctx, QueueSelector queueSelector, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, int initMode, SubscriptionData subscriptionData, boolean fifo, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue messageQueue = queueSelector.select(ctx, this.serviceManager.getTopicRouteService().getCurrentMessageQueueView(ctx, topic)); if (messageQueue == null) { throw new ProxyException(ProxyExceptionCode.FORBIDDEN, "no readable queue"); } return popMessage(ctx, messageQueue, consumerGroup, topic, maxMsgNums, invisibleTime, pollTime, initMode, subscriptionData, fifo, popMessageResultFilter, attemptId, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture popMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, int initMode, SubscriptionData subscriptionData, boolean fifo, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { if (maxMsgNums > ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST) { log.warn("change maxNums from {} to {} for pop request, with info: topic:{}, group:{}", maxMsgNums, ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST, topic, consumerGroup); maxMsgNums = ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST; } PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(topic); requestHeader.setQueueId(messageQueue.getQueueId()); requestHeader.setMaxMsgNums(maxMsgNums); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setPollTime(pollTime); requestHeader.setInitMode(initMode); requestHeader.setExpType(subscriptionData.getExpressionType()); requestHeader.setExp(subscriptionData.getSubString()); requestHeader.setOrder(fifo); requestHeader.setAttemptId(attemptId); future = this.serviceManager.getMessageService().popMessage( ctx, messageQueue, requestHeader, timeoutMillis) .thenApplyAsync(popResult -> filterPopResult(ctx, popResult, requestHeader, consumerGroup, topic, subscriptionData, popMessageResultFilter), this.executor); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } private PopResult filterPopResult(ProxyContext ctx, PopResult popResult, CommandCustomHeader requestHeader, String consumerGroup, String topic, SubscriptionData subscriptionData, PopMessageResultFilter popMessageResultFilter) { if (PopStatus.FOUND.equals(popResult.getPopStatus()) && !CollectionUtils.isEmpty(popResult.getMsgFoundList()) && popMessageResultFilter != null) { List messageExtList = new ArrayList<>(); for (MessageExt messageExt : popResult.getMsgFoundList()) { try { fillUniqIDIfNeed(messageExt); String handleString = createHandle(messageExt.getProperty(MessageConst.PROPERTY_POP_CK), messageExt.getCommitLogOffset()); if (handleString == null) { log.error("[BUG] pop message from broker but handle is empty. requestHeader:{}, msg:{}", requestHeader, messageExt); messageExtList.add(messageExt); continue; } MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_POP_CK, handleString); String liteTopic = messageExt.getProperty(MessageConst.PROPERTY_LITE_TOPIC); PopMessageResultFilter.FilterResult filterResult = popMessageResultFilter.filterMessage(ctx, consumerGroup, subscriptionData, messageExt); switch (filterResult) { case NO_MATCH: this.messagingProcessor.ackMessage( ctx, ReceiptHandle.decode(handleString), messageExt.getMsgId(), consumerGroup, topic, liteTopic, MessagingProcessor.DEFAULT_TIMEOUT_MILLS); break; case TO_DLQ: this.messagingProcessor.forwardMessageToDeadLetterQueue( ctx, ReceiptHandle.decode(handleString), messageExt.getMsgId(), consumerGroup, topic, liteTopic, MessagingProcessor.DEFAULT_TIMEOUT_MILLS); break; case TO_RETURN: this.messagingProcessor.changeInvisibleTime( ctx, ReceiptHandle.decode(handleString), messageExt.getMsgId(), consumerGroup, topic, MessagingProcessor.INVISIBLE_TIME_MS, null, MessagingProcessor.DEFAULT_TIMEOUT_MILLS, true); break; case MATCH: default: messageExtList.add(messageExt); break; } } catch (Throwable t) { log.error("process filterMessage failed. requestHeader:{}, msg:{}", requestHeader, messageExt, t); messageExtList.add(messageExt); } } popResult.setMsgFoundList(messageExtList); } return popResult; } public CompletableFuture popLiteMessage( ProxyContext ctx, QueueSelector queueSelector, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, SubscriptionData subscriptionData, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue messageQueue = queueSelector.select(ctx, this.serviceManager.getTopicRouteService().getCurrentMessageQueueView(ctx, topic)); if (messageQueue == null) { throw new ProxyException(ProxyExceptionCode.FORBIDDEN, "no readable queue"); } return doPopLiteMessage(ctx, messageQueue, consumerGroup, topic, maxMsgNums, invisibleTime, pollTime, subscriptionData, popMessageResultFilter, attemptId, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return future; } private CompletableFuture doPopLiteMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, SubscriptionData subscriptionData, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { if (maxMsgNums > ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST) { log.warn("change maxNums from {} to {} for pop request, with info: topic:{}, group:{}", maxMsgNums, ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST, topic, consumerGroup); maxMsgNums = ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST; } PopLiteMessageRequestHeader requestHeader = new PopLiteMessageRequestHeader(); requestHeader.setClientId(ctx.getClientID()); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(topic); requestHeader.setMaxMsgNum(maxMsgNums); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setPollTime(pollTime); requestHeader.setAttemptId(attemptId); requestHeader.setBornTime(System.currentTimeMillis()); future = this.serviceManager.getMessageService().popLiteMessage( ctx, messageQueue, requestHeader, timeoutMillis) .thenApplyAsync(popResult -> filterPopResult(ctx, popResult, requestHeader, consumerGroup, topic, subscriptionData, popMessageResultFilter), this.executor); } catch (Throwable t) { future.completeExceptionally(t); FutureUtils.addExecutor(future, this.executor); } return future; } private void fillUniqIDIfNeed(MessageExt messageExt) { if (StringUtils.isBlank(MessageClientIDSetter.getUniqID(messageExt))) { if (messageExt instanceof MessageClientExt) { MessageClientExt clientExt = (MessageClientExt) messageExt; MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, clientExt.getOffsetMsgId()); } } } public CompletableFuture ackMessage( ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic, String liteTopic, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { this.validateReceiptHandle(handle); AckMessageRequestHeader ackMessageRequestHeader = new AckMessageRequestHeader(); ackMessageRequestHeader.setConsumerGroup(consumerGroup); ackMessageRequestHeader.setTopic(handle.getRealTopic(topic, consumerGroup)); ackMessageRequestHeader.setQueueId(handle.getQueueId()); ackMessageRequestHeader.setExtraInfo(handle.getReceiptHandle()); ackMessageRequestHeader.setOffset(handle.getOffset()); ackMessageRequestHeader.setLiteTopic(liteTopic); future = this.serviceManager.getMessageService().ackMessage( ctx, handle, messageId, ackMessageRequestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture> batchAckMessage( ProxyContext ctx, List handleMessageList, String consumerGroup, String topic, long timeoutMillis ) { CompletableFuture> future = new CompletableFuture<>(); try { List batchAckResultList = new ArrayList<>(handleMessageList.size()); Map> brokerHandleListMap = new HashMap<>(); for (ReceiptHandleMessage handleMessage : handleMessageList) { if (handleMessage.getReceiptHandle().isExpired()) { batchAckResultList.add(new BatchAckResult(handleMessage, EXPIRED_HANDLE_PROXY_EXCEPTION)); continue; } List brokerHandleList = brokerHandleListMap.computeIfAbsent(handleMessage.getReceiptHandle().getBrokerName(), key -> new ArrayList<>()); brokerHandleList.add(handleMessage); } if (brokerHandleListMap.isEmpty()) { return FutureUtils.addExecutor(CompletableFuture.completedFuture(batchAckResultList), this.executor); } Set>> brokerHandleListMapEntrySet = brokerHandleListMap.entrySet(); CompletableFuture>[] futures = new CompletableFuture[brokerHandleListMapEntrySet.size()]; int futureIndex = 0; for (Map.Entry> entry : brokerHandleListMapEntrySet) { futures[futureIndex++] = processBrokerHandle(ctx, consumerGroup, topic, entry.getValue(), timeoutMillis); } CompletableFuture.allOf(futures).whenComplete((val, throwable) -> { if (throwable != null) { future.completeExceptionally(throwable); } for (CompletableFuture> resultFuture : futures) { batchAckResultList.addAll(resultFuture.join()); } future.complete(batchAckResultList); }); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } protected CompletableFuture> processBrokerHandle(ProxyContext ctx, String consumerGroup, String topic, List handleMessageList, long timeoutMillis) { return this.serviceManager.getMessageService().batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis) .thenApply(result -> { List results = new ArrayList<>(); for (ReceiptHandleMessage handleMessage : handleMessageList) { results.add(new BatchAckResult(handleMessage, result)); } return results; }) .exceptionally(throwable -> { List results = new ArrayList<>(); for (ReceiptHandleMessage handleMessage : handleMessageList) { results.add(new BatchAckResult(handleMessage, new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, throwable.getMessage(), throwable))); } return results; }); } public CompletableFuture changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long invisibleTime, String liteTopic, long timeoutMillis, boolean suspend) { CompletableFuture future = new CompletableFuture<>(); try { this.validateReceiptHandle(handle); ChangeInvisibleTimeRequestHeader changeInvisibleTimeRequestHeader = new ChangeInvisibleTimeRequestHeader(); changeInvisibleTimeRequestHeader.setConsumerGroup(groupName); changeInvisibleTimeRequestHeader.setTopic(handle.getRealTopic(topicName, groupName)); changeInvisibleTimeRequestHeader.setQueueId(handle.getQueueId()); changeInvisibleTimeRequestHeader.setExtraInfo(handle.getReceiptHandle()); changeInvisibleTimeRequestHeader.setOffset(handle.getOffset()); changeInvisibleTimeRequestHeader.setInvisibleTime(invisibleTime); changeInvisibleTimeRequestHeader.setLiteTopic(liteTopic); changeInvisibleTimeRequestHeader.setSuspend(suspend); long commitLogOffset = handle.getCommitLogOffset(); future = this.serviceManager.getMessageService().changeInvisibleTime( ctx, handle, messageId, changeInvisibleTimeRequestHeader, timeoutMillis) .thenApplyAsync(ackResult -> { if (StringUtils.isNotBlank(ackResult.getExtraInfo())) { AckResult result = new AckResult(); result.setStatus(ackResult.getStatus()); result.setPopTime(result.getPopTime()); result.setExtraInfo(createHandle(ackResult.getExtraInfo(), commitLogOffset)); return result; } else { return ackResult; } }, this.executor); } catch (Throwable t) { future.completeExceptionally(t); FutureUtils.addExecutor(future, this.executor); } return future; } protected String createHandle(String handleString, long commitLogOffset) { if (handleString == null) { return null; } return handleString + MessageConst.KEY_SEPARATOR + commitLogOffset; } public CompletableFuture pullMessage(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long queueOffset, int maxMsgNums, int sysFlag, long commitOffset, long suspendTimeoutMillis, SubscriptionData subscriptionData, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue addressableMessageQueue = serviceManager.getTopicRouteService() .buildAddressableMessageQueue(ctx, messageQueue); PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(addressableMessageQueue.getTopic()); requestHeader.setQueueId(addressableMessageQueue.getQueueId()); requestHeader.setQueueOffset(queueOffset); requestHeader.setMaxMsgNums(maxMsgNums); requestHeader.setSysFlag(sysFlag); requestHeader.setCommitOffset(commitOffset); requestHeader.setSuspendTimeoutMillis(suspendTimeoutMillis); requestHeader.setSubscription(subscriptionData.getSubString()); requestHeader.setExpressionType(subscriptionData.getExpressionType()); future = serviceManager.getMessageService().pullMessage(ctx, addressableMessageQueue, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture updateConsumerOffset(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long commitOffset, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue addressableMessageQueue = serviceManager.getTopicRouteService() .buildAddressableMessageQueue(ctx, messageQueue); UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(addressableMessageQueue.getTopic()); requestHeader.setQueueId(addressableMessageQueue.getQueueId()); requestHeader.setCommitOffset(commitOffset); future = serviceManager.getMessageService().updateConsumerOffset(ctx, addressableMessageQueue, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture updateConsumerOffsetAsync(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long commitOffset, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue addressableMessageQueue = serviceManager.getTopicRouteService() .buildAddressableMessageQueue(ctx, messageQueue); UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(addressableMessageQueue.getTopic()); requestHeader.setQueueId(addressableMessageQueue.getQueueId()); requestHeader.setCommitOffset(commitOffset); future = serviceManager.getMessageService().updateConsumerOffsetAsync(ctx, addressableMessageQueue, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture queryConsumerOffset(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue addressableMessageQueue = serviceManager.getTopicRouteService() .buildAddressableMessageQueue(ctx, messageQueue); QueryConsumerOffsetRequestHeader requestHeader = new QueryConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(addressableMessageQueue.getTopic()); requestHeader.setQueueId(addressableMessageQueue.getQueueId()); future = serviceManager.getMessageService().queryConsumerOffset(ctx, addressableMessageQueue, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture> lockBatchMQ(ProxyContext ctx, Set mqSet, String consumerGroup, String clientId, long timeoutMillis) { CompletableFuture> future = new CompletableFuture<>(); try { Set successSet = new CopyOnWriteArraySet<>(); Set addressableMessageQueueSet = buildAddressableSet(ctx, mqSet); Map> messageQueueSetMap = buildAddressableMapByBrokerName(addressableMessageQueueSet); List> futureList = new ArrayList<>(); messageQueueSetMap.forEach((k, v) -> { LockBatchRequestBody requestBody = new LockBatchRequestBody(); requestBody.setConsumerGroup(consumerGroup); requestBody.setClientId(clientId); requestBody.setMqSet(v.stream().map(AddressableMessageQueue::getMessageQueue).collect(Collectors.toSet())); CompletableFuture future0 = serviceManager.getMessageService() .lockBatchMQ(ctx, v.get(0), requestBody, timeoutMillis) .thenAccept(successSet::addAll); futureList.add(FutureUtils.addExecutor(future0, this.executor)); }); CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).whenComplete((v, t) -> { if (t != null) { log.error("LockBatchMQ failed, group={}", consumerGroup, t); } future.complete(successSet); }); } catch (Throwable t) { log.error("LockBatchMQ exception, group={}", consumerGroup, t); future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture unlockBatchMQ(ProxyContext ctx, Set mqSet, String consumerGroup, String clientId, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { Set addressableMessageQueueSet = buildAddressableSet(ctx, mqSet); Map> messageQueueSetMap = buildAddressableMapByBrokerName(addressableMessageQueueSet); List> futureList = new ArrayList<>(); messageQueueSetMap.forEach((k, v) -> { UnlockBatchRequestBody requestBody = new UnlockBatchRequestBody(); requestBody.setConsumerGroup(consumerGroup); requestBody.setClientId(clientId); requestBody.setMqSet(v.stream().map(AddressableMessageQueue::getMessageQueue).collect(Collectors.toSet())); CompletableFuture future0 = serviceManager.getMessageService().unlockBatchMQ(ctx, v.get(0), requestBody, timeoutMillis); futureList.add(FutureUtils.addExecutor(future0, this.executor)); }); CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).whenComplete((v, t) -> { if (t != null) { log.error("UnlockBatchMQ failed, group={}", consumerGroup, t); } future.complete(null); }); } catch (Throwable t) { log.error("UnlockBatchMQ exception, group={}", consumerGroup, t); future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture getMaxOffset(ProxyContext ctx, MessageQueue messageQueue, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue addressableMessageQueue = serviceManager.getTopicRouteService() .buildAddressableMessageQueue(ctx, messageQueue); GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader(); requestHeader.setTopic(addressableMessageQueue.getTopic()); requestHeader.setQueueId(addressableMessageQueue.getQueueId()); future = serviceManager.getMessageService().getMaxOffset(ctx, addressableMessageQueue, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } public CompletableFuture getMinOffset(ProxyContext ctx, MessageQueue messageQueue, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { AddressableMessageQueue addressableMessageQueue = serviceManager.getTopicRouteService() .buildAddressableMessageQueue(ctx, messageQueue); GetMinOffsetRequestHeader requestHeader = new GetMinOffsetRequestHeader(); requestHeader.setTopic(addressableMessageQueue.getTopic()); requestHeader.setQueueId(addressableMessageQueue.getQueueId()); future = serviceManager.getMessageService().getMinOffset(ctx, addressableMessageQueue, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } protected Set buildAddressableSet(ProxyContext ctx, Set mqSet) { Set addressableMessageQueueSet = new HashSet<>(mqSet.size()); for (MessageQueue mq : mqSet) { try { addressableMessageQueueSet.add(serviceManager.getTopicRouteService().buildAddressableMessageQueue(ctx, mq)); } catch (Exception e) { log.error("build addressable message queue fail, messageQueue = {}", mq, e); } } return addressableMessageQueueSet; } protected HashMap> buildAddressableMapByBrokerName( final Set mqSet) { HashMap> result = new HashMap<>(); if (mqSet == null) { return result; } for (AddressableMessageQueue mq : mqSet) { if (mq == null) { continue; } List mqs = result.computeIfAbsent(mq.getBrokerName(), k -> new ArrayList<>()); mqs.add(mq); } return result; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ProducerChangeListener; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.ServiceManagerFactory; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class DefaultMessagingProcessor extends AbstractStartAndShutdown implements MessagingProcessor { protected ServiceManager serviceManager; protected ProducerProcessor producerProcessor; protected ConsumerProcessor consumerProcessor; protected TransactionProcessor transactionProcessor; protected ClientProcessor clientProcessor; protected RequestBrokerProcessor requestBrokerProcessor; protected ReceiptHandleProcessor receiptHandleProcessor; protected ThreadPoolExecutor producerProcessorExecutor; protected ThreadPoolExecutor consumerProcessorExecutor; protected static final String ROCKETMQ_HOME = MixAll.ROCKETMQ_HOME_DIR; protected DefaultMessagingProcessor(ServiceManager serviceManager) { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); this.producerProcessorExecutor = ThreadPoolMonitor.createAndMonitor( proxyConfig.getProducerProcessorThreadPoolNums(), proxyConfig.getProducerProcessorThreadPoolNums(), 1, TimeUnit.MINUTES, "ProducerProcessorExecutor", proxyConfig.getProducerProcessorThreadPoolQueueCapacity() ); this.consumerProcessorExecutor = ThreadPoolMonitor.createAndMonitor( proxyConfig.getConsumerProcessorThreadPoolNums(), proxyConfig.getConsumerProcessorThreadPoolNums(), 1, TimeUnit.MINUTES, "ConsumerProcessorExecutor", proxyConfig.getConsumerProcessorThreadPoolQueueCapacity() ); this.serviceManager = serviceManager; this.producerProcessor = new ProducerProcessor(this, serviceManager, this.producerProcessorExecutor); this.consumerProcessor = new ConsumerProcessor(this, serviceManager, this.consumerProcessorExecutor); this.transactionProcessor = new TransactionProcessor(this, serviceManager); this.clientProcessor = new ClientProcessor(this, serviceManager); this.requestBrokerProcessor = new RequestBrokerProcessor(this, serviceManager); this.receiptHandleProcessor = new ReceiptHandleProcessor(this, serviceManager); this.init(); } public static DefaultMessagingProcessor createForLocalMode(BrokerController brokerController) { return createForLocalMode(brokerController, null); } public static DefaultMessagingProcessor createForLocalMode(BrokerController brokerController, RPCHook rpcHook) { return new DefaultMessagingProcessor(ServiceManagerFactory.createForLocalMode(brokerController, rpcHook)); } public static DefaultMessagingProcessor createForClusterMode() { RPCHook rpcHook = null; if (!ConfigurationManager.getProxyConfig().isEnableAclRpcHookForClusterMode()) { return createForClusterMode(rpcHook); } AuthConfig authConfig = ConfigurationManager.getAuthConfig(); if (StringUtils.isNotBlank(authConfig.getInnerClientAuthenticationCredentials())) { SessionCredentials sessionCredentials = JSON.parseObject(authConfig.getInnerClientAuthenticationCredentials(), SessionCredentials.class); if (StringUtils.isNotBlank(sessionCredentials.getAccessKey()) && StringUtils.isNotBlank(sessionCredentials.getSecretKey())) { rpcHook = new AclClientRPCHook(sessionCredentials); } } else { rpcHook = AclUtils.getAclRPCHook(ROCKETMQ_HOME + MixAll.ACL_CONF_TOOLS_FILE); } return createForClusterMode(rpcHook); } public static DefaultMessagingProcessor createForClusterMode(RPCHook rpcHook) { return new DefaultMessagingProcessor(ServiceManagerFactory.createForClusterMode(rpcHook)); } protected void init() { this.appendStartAndShutdown(this.serviceManager); this.appendStartAndShutdown(this.receiptHandleProcessor); this.appendShutdown(this.producerProcessorExecutor::shutdown); this.appendShutdown(this.consumerProcessorExecutor::shutdown); } @Override public SubscriptionGroupConfig getSubscriptionGroupConfig(ProxyContext ctx, String consumerGroupName) { return this.serviceManager.getMetadataService().getSubscriptionGroupConfig(ctx, consumerGroupName); } @Override public ProxyTopicRouteData getTopicRouteDataForProxy(ProxyContext ctx, List
    requestHostAndPortList, String topicName) throws Exception { return this.serviceManager.getTopicRouteService().getTopicRouteForProxy(ctx, requestHostAndPortList, topicName); } @Override public CompletableFuture> sendMessage(ProxyContext ctx, QueueSelector queueSelector, String producerGroup, int sysFlag, List msg, long timeoutMillis) { return this.producerProcessor.sendMessage(ctx, queueSelector, producerGroup, sysFlag, msg, timeoutMillis); } @Override public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long timeoutMillis) { return this.producerProcessor.forwardMessageToDeadLetterQueue(ctx, handle, messageId, groupName, topicName, null, timeoutMillis); } @Override public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, String liteTopic, long timeoutMillis) { return this.producerProcessor.forwardMessageToDeadLetterQueue(ctx, handle, messageId, groupName, topicName, liteTopic, timeoutMillis); } @Override public CompletableFuture endTransaction(ProxyContext ctx, String topic, String transactionId, String messageId, String producerGroup, TransactionStatus transactionStatus, boolean fromTransactionCheck, long timeoutMillis) { return this.transactionProcessor.endTransaction(ctx, topic, transactionId, messageId, producerGroup, transactionStatus, fromTransactionCheck, timeoutMillis); } @Override public CompletableFuture popMessage( ProxyContext ctx, QueueSelector queueSelector, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, int initMode, SubscriptionData subscriptionData, boolean fifo, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ) { return this.consumerProcessor.popMessage(ctx, queueSelector, consumerGroup, topic, maxMsgNums, invisibleTime, pollTime, initMode, subscriptionData, fifo, popMessageResultFilter, attemptId, timeoutMillis); } @Override public CompletableFuture popLiteMessage(ProxyContext ctx, QueueSelector queueSelector, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, SubscriptionData subscriptionData, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis) { return this.consumerProcessor.popLiteMessage(ctx, queueSelector, consumerGroup, topic, maxMsgNums, invisibleTime, pollTime, subscriptionData, popMessageResultFilter, attemptId, timeoutMillis); } @Override public CompletableFuture ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic, long timeoutMillis) { return this.consumerProcessor.ackMessage(ctx, handle, messageId, consumerGroup, topic, null, timeoutMillis); } @Override public CompletableFuture ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic, String liteTopic, long timeoutMillis) { return this.consumerProcessor.ackMessage(ctx, handle, messageId, consumerGroup, topic, liteTopic, timeoutMillis); } @Override public CompletableFuture> batchAckMessage(ProxyContext ctx, List handleMessageList, String consumerGroup, String topic, long timeoutMillis) { return this.consumerProcessor.batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis); } @Override public CompletableFuture changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long invisibleTime, String liteTopic, long timeoutMillis, boolean suspend) { return this.consumerProcessor.changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, liteTopic, timeoutMillis, suspend); } @Override public CompletableFuture pullMessage(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long queueOffset, int maxMsgNums, int sysFlag, long commitOffset, long suspendTimeoutMillis, SubscriptionData subscriptionData, long timeoutMillis) { return this.consumerProcessor.pullMessage(ctx, messageQueue, consumerGroup, queueOffset, maxMsgNums, sysFlag, commitOffset, suspendTimeoutMillis, subscriptionData, timeoutMillis); } @Override public CompletableFuture updateConsumerOffset(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long commitOffset, long timeoutMillis) { return this.consumerProcessor.updateConsumerOffset(ctx, messageQueue, consumerGroup, commitOffset, timeoutMillis); } @Override public CompletableFuture updateConsumerOffsetAsync(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long commitOffset, long timeoutMillis) { return this.consumerProcessor.updateConsumerOffsetAsync(ctx, messageQueue, consumerGroup, commitOffset, timeoutMillis); } @Override public CompletableFuture queryConsumerOffset(ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long timeoutMillis) { return this.consumerProcessor.queryConsumerOffset(ctx, messageQueue, consumerGroup, timeoutMillis); } @Override public CompletableFuture> lockBatchMQ(ProxyContext ctx, Set mqSet, String consumerGroup, String clientId, long timeoutMillis) { return this.consumerProcessor.lockBatchMQ(ctx, mqSet, consumerGroup, clientId, timeoutMillis); } @Override public CompletableFuture unlockBatchMQ(ProxyContext ctx, Set mqSet, String consumerGroup, String clientId, long timeoutMillis) { return this.consumerProcessor.unlockBatchMQ(ctx, mqSet, consumerGroup, clientId, timeoutMillis); } @Override public CompletableFuture getMaxOffset(ProxyContext ctx, MessageQueue messageQueue, long timeoutMillis) { return this.consumerProcessor.getMaxOffset(ctx, messageQueue, timeoutMillis); } @Override public CompletableFuture getMinOffset(ProxyContext ctx, MessageQueue messageQueue, long timeoutMillis) { return this.consumerProcessor.getMinOffset(ctx, messageQueue, timeoutMillis); } @Override public CompletableFuture recallMessage(ProxyContext ctx, String topic, String recallHandle, long timeoutMillis) { return this.producerProcessor.recallMessage(ctx, topic, recallHandle, timeoutMillis); } @Override public CompletableFuture syncLiteSubscription(ProxyContext ctx, LiteSubscriptionDTO liteSubscriptionDTO, long timeoutMillis) { return this.clientProcessor.syncLiteSubscription(ctx, liteSubscriptionDTO, timeoutMillis); } @Override public CompletableFuture request(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { int originalRequestOpaque = request.getOpaque(); request.setOpaque(RemotingCommand.createNewRequestId()); return this.requestBrokerProcessor.request(ctx, brokerName, request, timeoutMillis).thenApply(r -> { request.setOpaque(originalRequestOpaque); return r; }); } @Override public CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { int originalRequestOpaque = request.getOpaque(); request.setOpaque(RemotingCommand.createNewRequestId()); return this.requestBrokerProcessor.requestOneway(ctx, brokerName, request, timeoutMillis).thenApply(r -> { request.setOpaque(originalRequestOpaque); return r; }); } @Override public void registerProducer(ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo) { this.clientProcessor.registerProducer(ctx, producerGroup, clientChannelInfo); } @Override public void unRegisterProducer(ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo) { this.clientProcessor.unRegisterProducer(ctx, producerGroup, clientChannelInfo); } @Override public Channel findProducerChannel(ProxyContext ctx, String producerGroup, String clientId) { return this.clientProcessor.findProducerChannel(ctx, producerGroup, clientId); } @Override public void registerProducerListener(ProducerChangeListener producerChangeListener) { this.clientProcessor.registerProducerChangeListener(producerChangeListener); } @Override public void registerConsumer(ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set subList, boolean updateSubscription) { this.clientProcessor.registerConsumer(ctx, consumerGroup, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList, updateSubscription); } @Override public ClientChannelInfo findConsumerChannel(ProxyContext ctx, String consumerGroup, Channel channel) { return this.clientProcessor.findConsumerChannel(ctx, consumerGroup, channel); } @Override public void unRegisterConsumer(ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo) { this.clientProcessor.unRegisterConsumer(ctx, consumerGroup, clientChannelInfo); } @Override public void registerConsumerListener(ConsumerIdsChangeListener consumerIdsChangeListener) { this.clientProcessor.registerConsumerIdsChangeListener(consumerIdsChangeListener); } @Override public void doChannelCloseEvent(String remoteAddr, Channel channel) { this.clientProcessor.doChannelCloseEvent(remoteAddr, channel); } @Override public ConsumerGroupInfo getConsumerGroupInfo(ProxyContext ctx, String consumerGroup) { return this.clientProcessor.getConsumerGroupInfo(ctx, consumerGroup); } @Override public void addTransactionSubscription(ProxyContext ctx, String producerGroup, String topic) { this.transactionProcessor.addTransactionSubscription(ctx, producerGroup, topic); } @Override public ProxyRelayService getProxyRelayService() { return this.serviceManager.getProxyRelayService(); } @Override public MetadataService getMetadataService() { return this.serviceManager.getMetadataService(); } @Override public void addReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, MessageReceiptHandle messageReceiptHandle) { receiptHandleProcessor.addReceiptHandle(ctx, channel, group, msgID, messageReceiptHandle); } @Override public MessageReceiptHandle removeReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, String receiptHandle) { return receiptHandleProcessor.removeReceiptHandle(ctx, channel, group, msgID, receiptHandle); } @Override public int getUnackedMessageCount(ProxyContext ctx, Channel channel, String group) { return receiptHandleProcessor.getUnackedMessageCount(ctx, channel, group); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import io.netty.channel.Channel; import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ProducerChangeListener; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public interface MessagingProcessor extends StartAndShutdown { long DEFAULT_TIMEOUT_MILLS = Duration.ofSeconds(2).toMillis(); long INVISIBLE_TIME_MS = Duration.ofSeconds(1).toMillis(); SubscriptionGroupConfig getSubscriptionGroupConfig( ProxyContext ctx, String consumerGroupName ); ProxyTopicRouteData getTopicRouteDataForProxy( ProxyContext ctx, List
    requestHostAndPortList, String topicName ) throws Exception; default CompletableFuture> sendMessage( ProxyContext ctx, QueueSelector queueSelector, String producerGroup, int sysFlag, List msg ) { return sendMessage(ctx, queueSelector, producerGroup, sysFlag, msg, DEFAULT_TIMEOUT_MILLS); } CompletableFuture> sendMessage( ProxyContext ctx, QueueSelector queueSelector, String producerGroup, int sysFlag, List msg, long timeoutMillis ); default CompletableFuture forwardMessageToDeadLetterQueue( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName ) { return forwardMessageToDeadLetterQueue(ctx, handle, messageId, groupName, topicName, DEFAULT_TIMEOUT_MILLS); } CompletableFuture forwardMessageToDeadLetterQueue( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long timeoutMillis ); default CompletableFuture forwardMessageToDeadLetterQueue( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, String liteTopic ) { return forwardMessageToDeadLetterQueue(ctx, handle, messageId, groupName, topicName, liteTopic, DEFAULT_TIMEOUT_MILLS); } CompletableFuture forwardMessageToDeadLetterQueue( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, String liteTopic, long timeoutMillis ); default CompletableFuture endTransaction( ProxyContext ctx, String topic, String transactionId, String messageId, String producerGroup, TransactionStatus transactionStatus, boolean fromTransactionCheck ) { return endTransaction(ctx, topic, transactionId, messageId, producerGroup, transactionStatus, fromTransactionCheck, DEFAULT_TIMEOUT_MILLS); } CompletableFuture endTransaction( ProxyContext ctx, String topic, String transactionId, String messageId, String producerGroup, TransactionStatus transactionStatus, boolean fromTransactionCheck, long timeoutMillis ); CompletableFuture popMessage( ProxyContext ctx, QueueSelector queueSelector, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, int initMode, SubscriptionData subscriptionData, boolean fifo, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ); CompletableFuture popLiteMessage( ProxyContext ctx, QueueSelector queueSelector, String consumerGroup, String topic, int maxMsgNums, long invisibleTime, long pollTime, SubscriptionData subscriptionData, PopMessageResultFilter popMessageResultFilter, String attemptId, long timeoutMillis ); default CompletableFuture ackMessage( ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic ) { return ackMessage(ctx, handle, messageId, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS); } CompletableFuture ackMessage( ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic, long timeoutMillis ); default CompletableFuture ackMessage( ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic, String liteTopic ) { return ackMessage(ctx, handle, messageId, consumerGroup, topic, liteTopic, DEFAULT_TIMEOUT_MILLS); } CompletableFuture ackMessage( ProxyContext ctx, ReceiptHandle handle, String messageId, String consumerGroup, String topic, String liteTopic, long timeoutMillis ); default CompletableFuture> batchAckMessage( ProxyContext ctx, List handleMessageList, String consumerGroup, String topic ) { return batchAckMessage(ctx, handleMessageList, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS); } CompletableFuture> batchAckMessage( ProxyContext ctx, List handleMessageList, String consumerGroup, String topic, long timeoutMillis ); default CompletableFuture changeInvisibleTime( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long invisibleTime ) { return changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, DEFAULT_TIMEOUT_MILLS); } default CompletableFuture changeInvisibleTime( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis ) { return changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, null, timeoutMillis, false); } default CompletableFuture changeInvisibleTime( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long invisibleTime, String liteTopic ) { return changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, liteTopic, DEFAULT_TIMEOUT_MILLS, false); } CompletableFuture changeInvisibleTime( ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, long invisibleTime, String liteTopic, long timeoutMillis, boolean suspend ); CompletableFuture pullMessage( ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long queueOffset, int maxMsgNums, int sysFlag, long commitOffset, long suspendTimeoutMillis, SubscriptionData subscriptionData, long timeoutMillis ); CompletableFuture updateConsumerOffset( ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long commitOffset, long timeoutMillis ); CompletableFuture updateConsumerOffsetAsync( ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long commitOffset, long timeoutMillis ); CompletableFuture queryConsumerOffset( ProxyContext ctx, MessageQueue messageQueue, String consumerGroup, long timeoutMillis ); CompletableFuture> lockBatchMQ( ProxyContext ctx, Set mqSet, String consumerGroup, String clientId, long timeoutMillis ); CompletableFuture unlockBatchMQ( ProxyContext ctx, Set mqSet, String consumerGroup, String clientId, long timeoutMillis ); CompletableFuture getMaxOffset( ProxyContext ctx, MessageQueue messageQueue, long timeoutMillis ); CompletableFuture getMinOffset( ProxyContext ctx, MessageQueue messageQueue, long timeoutMillis ); CompletableFuture recallMessage( ProxyContext ctx, String topic, String recallHandle, long timeoutMillis ); CompletableFuture syncLiteSubscription( ProxyContext ctx, LiteSubscriptionDTO liteSubscriptionDTO, long timeoutMillis ); CompletableFuture request(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis); CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis); void registerProducer( ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo ); void unRegisterProducer( ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo ); Channel findProducerChannel( ProxyContext ctx, String producerGroup, String clientId ); void registerProducerListener( ProducerChangeListener producerChangeListener ); void registerConsumer( ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set subList, boolean updateSubscription ); ClientChannelInfo findConsumerChannel( ProxyContext ctx, String consumerGroup, Channel channel ); void unRegisterConsumer( ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo ); void registerConsumerListener( ConsumerIdsChangeListener consumerIdsChangeListener ); void doChannelCloseEvent(String remoteAddr, Channel channel); ConsumerGroupInfo getConsumerGroupInfo(ProxyContext ctx, String consumerGroup); void addTransactionSubscription( ProxyContext ctx, String producerGroup, String topic ); ProxyRelayService getProxyRelayService(); MetadataService getMetadataService(); void addReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, MessageReceiptHandle messageReceiptHandle); MessageReceiptHandle removeReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, String receiptHandle); int getUnackedMessageCount(ProxyContext ctx, Channel channel, String group); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/PopMessageResultFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public interface PopMessageResultFilter { enum FilterResult { TO_DLQ, NO_MATCH, MATCH, TO_RETURN } FilterResult filterMessage(ProxyContext ctx, String consumerGroup, SubscriptionData subscriptionData, MessageExt messageExt); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import org.apache.commons.codec.DecoderException; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageId; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.processor.validator.DefaultTopicMessageTypeValidator; import org.apache.rocketmq.proxy.processor.validator.TopicMessageTypeValidator; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; public class ProducerProcessor extends AbstractProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final ExecutorService executor; private final TopicMessageTypeValidator topicMessageTypeValidator; public ProducerProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager, ExecutorService executor) { super(messagingProcessor, serviceManager); this.executor = executor; this.topicMessageTypeValidator = new DefaultTopicMessageTypeValidator(); } public CompletableFuture> sendMessage(ProxyContext ctx, QueueSelector queueSelector, String producerGroup, int sysFlag, List messageList, long timeoutMillis) { CompletableFuture> future = new CompletableFuture<>(); long beginTimestampFirst = System.currentTimeMillis(); AddressableMessageQueue messageQueue = null; try { Message message = messageList.get(0); String topic = message.getTopic(); if (isNeedCheckTopicMessageType(message)) { if (topicMessageTypeValidator != null) { // Do not check retry or dlq topic if (!NamespaceUtil.isRetryTopic(topic) && !NamespaceUtil.isDLQTopic(topic)) { TopicMessageType topicMessageType = serviceManager.getMetadataService().getTopicMessageType(ctx, topic); TopicMessageType messageType = TopicMessageType.parseFromMessageProperty(message.getProperties()); topicMessageTypeValidator.validate(topicMessageType, messageType); } } } messageQueue = queueSelector.select(ctx, this.serviceManager.getTopicRouteService().getCurrentMessageQueueView(ctx, topic)); if (messageQueue == null) { throw new ProxyException(ProxyExceptionCode.FORBIDDEN, "no writable queue"); } for (Message msg : messageList) { MessageClientIDSetter.setUniqID(msg); } SendMessageRequestHeader requestHeader = buildSendMessageRequestHeader(messageList, producerGroup, sysFlag, messageQueue.getQueueId()); AddressableMessageQueue finalMessageQueue = messageQueue; future = this.serviceManager.getMessageService().sendMessage( ctx, messageQueue, messageList, requestHeader, timeoutMillis) .whenCompleteAsync((sendResultList, throwable) -> { long endTimestamp = System.currentTimeMillis(); if (throwable == null) { for (SendResult sendResult : sendResultList) { int tranType = MessageSysFlag.getTransactionValue(requestHeader.getSysFlag()); if (SendStatus.SEND_OK.equals(sendResult.getSendStatus()) && tranType == MessageSysFlag.TRANSACTION_PREPARED_TYPE && StringUtils.isNotBlank(sendResult.getTransactionId())) { fillTransactionData(ctx, producerGroup, finalMessageQueue, sendResult, messageList); } } this.serviceManager.getTopicRouteService().updateFaultItem(finalMessageQueue.getBrokerName(), endTimestamp - beginTimestampFirst, false, true); } else { this.serviceManager.getTopicRouteService().updateFaultItem(finalMessageQueue.getBrokerName(), endTimestamp - beginTimestampFirst, true, false); } }, this.executor); } catch (Throwable t) { future.completeExceptionally(t); FutureUtils.addExecutor(future, this.executor); } return future; } public CompletableFuture recallMessage(ProxyContext ctx, String topic, String recallHandle, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { if (ConfigurationManager.getProxyConfig().isEnableTopicMessageTypeCheck()) { TopicMessageType messageType = serviceManager.getMetadataService().getTopicMessageType(ctx, topic); topicMessageTypeValidator.validate(messageType, TopicMessageType.DELAY); } RecallMessageHandle.HandleV1 handleEntity; try { handleEntity = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(recallHandle); } catch (DecoderException e) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, e.getMessage()); } String brokerName = handleEntity.getBrokerName(); RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setRecallHandle(recallHandle); requestHeader.setBrokerName(brokerName); future = serviceManager.getMessageService().recallMessage(ctx, brokerName, requestHeader, timeoutMillis); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } protected void fillTransactionData(ProxyContext ctx, String producerGroup, AddressableMessageQueue messageQueue, SendResult sendResult, List messageList) { try { MessageId id; if (sendResult.getOffsetMsgId() != null) { id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId()); } else { id = MessageDecoder.decodeMessageId(sendResult.getMsgId()); } this.serviceManager.getTransactionService().addTransactionDataByBrokerName( ctx, messageQueue.getBrokerName(), messageList.get(0).getTopic(), producerGroup, sendResult.getQueueOffset(), id.getOffset(), sendResult.getTransactionId(), messageList.get(0) ); } catch (Throwable t) { log.warn("fillTransactionData failed. messageQueue: {}, sendResult: {}", messageQueue, sendResult, t); } } protected SendMessageRequestHeader buildSendMessageRequestHeader(List messageList, String producerGroup, int sysFlag, int queueId) { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); Message message = messageList.get(0); requestHeader.setProducerGroup(producerGroup); requestHeader.setTopic(message.getTopic()); requestHeader.setDefaultTopic(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC); requestHeader.setDefaultTopicQueueNums(4); requestHeader.setQueueId(queueId); requestHeader.setSysFlag(sysFlag); /* In RocketMQ 4.0, org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader.bornTimestamp represents the timestamp when the message was born. In RocketMQ 5.0, the bornTimestamp of the message is a message attribute, that is, the timestamp when message was constructed, and there is no bornTimestamp in the SendMessageRequest of RocketMQ 5.0. Note: When using grpc sendMessage to send multiple messages, the bornTimestamp in the requestHeader is set to the bornTimestamp of the first message, which may not be accurate. When a bornTimestamp is required, the bornTimestamp of the message property should be used. * */ try { requestHeader.setBornTimestamp(Long.parseLong(message.getProperty(MessageConst.PROPERTY_BORN_TIMESTAMP))); } catch (Exception e) { log.warn("parse born time error, with value:{}", message.getProperty(MessageConst.PROPERTY_BORN_TIMESTAMP)); requestHeader.setBornTimestamp(System.currentTimeMillis()); } requestHeader.setFlag(message.getFlag()); requestHeader.setProperties(MessageDecoder.messageProperties2String(message.getProperties())); requestHeader.setReconsumeTimes(0); if (messageList.size() > 1) { requestHeader.setBatch(true); } if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { String reconsumeTimes = MessageAccessor.getReconsumeTime(message); if (reconsumeTimes != null) { requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes)); MessageAccessor.clearProperty(message, MessageConst.PROPERTY_RECONSUME_TIME); } String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(message); if (maxReconsumeTimes != null) { requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes)); MessageAccessor.clearProperty(message, MessageConst.PROPERTY_MAX_RECONSUME_TIMES); } } return requestHeader; } public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, ReceiptHandle handle, String messageId, String groupName, String topicName, String liteTopic, long timeoutMillis ) { CompletableFuture future = new CompletableFuture<>(); try { if (handle.getCommitLogOffset() < 0) { throw new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "commit log offset is empty"); } ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader(); consumerSendMsgBackRequestHeader.setOffset(handle.getCommitLogOffset()); consumerSendMsgBackRequestHeader.setGroup(groupName); consumerSendMsgBackRequestHeader.setDelayLevel(-1); consumerSendMsgBackRequestHeader.setOriginMsgId(messageId); consumerSendMsgBackRequestHeader.setOriginTopic(handle.getRealTopic(topicName, groupName)); consumerSendMsgBackRequestHeader.setMaxReconsumeTimes(0); future = this.serviceManager.getMessageService().sendMessageBack( ctx, handle, messageId, consumerSendMsgBackRequestHeader, timeoutMillis ).whenCompleteAsync((remotingCommand, t) -> { if (t == null && remotingCommand.getCode() == ResponseCode.SUCCESS) { this.messagingProcessor.ackMessage(ctx, handle, messageId, groupName, topicName, liteTopic, timeoutMillis); } }, this.executor); } catch (Throwable t) { future.completeExceptionally(t); } return FutureUtils.addExecutor(future, this.executor); } private boolean isNeedCheckTopicMessageType(Message message) { return ConfigurationManager.getProxyConfig().isEnableTopicMessageTypeCheck() && !message.hasProperty(MessageConst.PROPERTY_TRANSFER_FLAG); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/QueueSelector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueView; public interface QueueSelector { AddressableMessageQueue select(ProxyContext ctx, MessageQueueView messageQueueView); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import io.netty.channel.Channel; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.state.StateEventListener; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.RenewEvent; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.receipt.DefaultReceiptHandleManager; public class ReceiptHandleProcessor extends AbstractProcessor { protected final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected DefaultReceiptHandleManager receiptHandleManager; public ReceiptHandleProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager) { super(messagingProcessor, serviceManager); StateEventListener eventListener = event -> { ProxyContext context = createContext(event.getEventType().name()) .setChannel(event.getKey().getChannel()); MessageReceiptHandle messageReceiptHandle = event.getMessageReceiptHandle(); ReceiptHandle handle = ReceiptHandle.decode(messageReceiptHandle.getReceiptHandleStr()); messagingProcessor.changeInvisibleTime(context, handle, messageReceiptHandle.getMessageId(), messageReceiptHandle.getGroup(), messageReceiptHandle.getTopic(), event.getRenewTime(), messageReceiptHandle.getLiteTopic()) .whenComplete((v, t) -> { if (t != null) { event.getFuture().completeExceptionally(t); return; } event.getFuture().complete(v); }); }; this.receiptHandleManager = new DefaultReceiptHandleManager(serviceManager.getMetadataService(), serviceManager.getConsumerManager(), eventListener); this.appendStartAndShutdown(receiptHandleManager); } protected ProxyContext createContext(String actionName) { return ProxyContext.createForInner(this.getClass().getSimpleName() + actionName); } public void addReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, MessageReceiptHandle messageReceiptHandle) { receiptHandleManager.addReceiptHandle(ctx, channel, group, msgID, messageReceiptHandle); } public MessageReceiptHandle removeReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, String receiptHandle) { return receiptHandleManager.removeReceiptHandle(ctx, channel, group, msgID, receiptHandle); } public int getUnackedMessageCount(ProxyContext ctx, Channel channel, String group) { return receiptHandleManager.getUnackedMessageCount(ctx, channel, group); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/RequestBrokerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class RequestBrokerProcessor extends AbstractProcessor { public RequestBrokerProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager) { super(messagingProcessor, serviceManager); } CompletableFuture request(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { return serviceManager.getMessageService().request(ctx, brokerName, request, timeoutMillis); } CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { return serviceManager.getMessageService().requestOneway(ctx, brokerName, request, timeoutMillis); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.transaction.EndTransactionRequestData; public class TransactionProcessor extends AbstractProcessor { public TransactionProcessor(MessagingProcessor messagingProcessor, ServiceManager serviceManager) { super(messagingProcessor, serviceManager); } public CompletableFuture endTransaction(ProxyContext ctx, String topic, String transactionId, String messageId, String producerGroup, TransactionStatus transactionStatus, boolean fromTransactionCheck, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { EndTransactionRequestData headerData = serviceManager.getTransactionService().genEndTransactionRequestHeader( ctx, topic, producerGroup, buildCommitOrRollback(transactionStatus), fromTransactionCheck, messageId, transactionId ); if (headerData == null) { future.completeExceptionally(new ProxyException(ProxyExceptionCode.TRANSACTION_DATA_NOT_FOUND, "cannot found transaction data")); return future; } return this.serviceManager.getMessageService().endTransactionOneway( ctx, headerData.getBrokerName(), headerData.getRequestHeader(), timeoutMillis ); } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected int buildCommitOrRollback(TransactionStatus transactionStatus) { switch (transactionStatus) { case COMMIT: return MessageSysFlag.TRANSACTION_COMMIT_TYPE; case ROLLBACK: return MessageSysFlag.TRANSACTION_ROLLBACK_TYPE; default: return MessageSysFlag.TRANSACTION_NOT_TYPE; } } public void addTransactionSubscription(ProxyContext ctx, String producerGroup, String topic) { this.serviceManager.getTransactionService().addTransactionSubscription(ctx, producerGroup, topic); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; public enum TransactionStatus { UNKNOWN, COMMIT, ROLLBACK } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelExtendAttributeGetter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.channel; public interface ChannelExtendAttributeGetter { String getChannelExtendAttribute(); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelProtocolType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.channel; public enum ChannelProtocolType { UNKNOWN("unknown"), GRPC_V2("grpc_v2"), GRPC_V1("grpc_v1"), REMOTING("remoting"); private final String name; ChannelProtocolType(String name) { this.name = name; } public String getName() { return name; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.channel; import com.google.common.base.MoreObjects; import io.netty.channel.Channel; import io.netty.channel.ChannelId; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; public class RemoteChannel extends SimpleChannel implements ChannelExtendAttributeGetter { protected final ChannelProtocolType type; protected final String remoteProxyIp; protected volatile String extendAttribute; public RemoteChannel(String remoteProxyIp, String remoteAddress, String localAddress, ChannelProtocolType type, String extendAttribute) { super(null, new RemoteChannelId(remoteProxyIp, remoteAddress, localAddress, type), remoteAddress, localAddress); this.type = type; this.remoteProxyIp = remoteProxyIp; this.extendAttribute = extendAttribute; } public static class RemoteChannelId implements ChannelId { private final String id; public RemoteChannelId(String remoteProxyIp, String remoteAddress, String localAddress, ChannelProtocolType type) { this.id = remoteProxyIp + "@" + remoteAddress + "@" + localAddress + "@" + type; } @Override public String asShortText() { return this.id; } @Override public String asLongText() { return this.id; } @Override public int compareTo(ChannelId o) { return this.id.compareTo(o.asLongText()); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("id", id) .toString(); } } @Override public boolean isWritable() { return false; } public ChannelProtocolType getType() { return type; } public String encode() { return RemoteChannelSerializer.toJson(this); } public static RemoteChannel decode(String data) { return RemoteChannelSerializer.decodeFromJson(data); } public static RemoteChannel create(Channel channel) { if (channel instanceof RemoteChannelConverter) { return ((RemoteChannelConverter) channel).toRemoteChannel(); } return null; } public String getRemoteProxyIp() { return remoteProxyIp; } public void setExtendAttribute(String extendAttribute) { this.extendAttribute = extendAttribute; } @Override public String getChannelExtendAttribute() { return this.extendAttribute; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("channelId", id()) .add("type", type) .add("remoteProxyIp", remoteProxyIp) .add("extendAttribute", extendAttribute) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.channel; public interface RemoteChannelConverter { RemoteChannel toRemoteChannel(); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelSerializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.channel; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; public class RemoteChannelSerializer { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final String REMOTE_PROXY_IP_KEY = "remoteProxyIp"; private static final String REMOTE_ADDRESS_KEY = "remoteAddress"; private static final String LOCAL_ADDRESS_KEY = "localAddress"; private static final String TYPE_KEY = "type"; private static final String EXTEND_ATTRIBUTE_KEY = "extendAttribute"; public static String toJson(RemoteChannel remoteChannel) { Map data = new HashMap<>(); data.put(REMOTE_PROXY_IP_KEY, remoteChannel.getRemoteProxyIp()); data.put(REMOTE_ADDRESS_KEY, remoteChannel.getRemoteAddress()); data.put(LOCAL_ADDRESS_KEY, remoteChannel.getLocalAddress()); data.put(TYPE_KEY, remoteChannel.getType()); data.put(EXTEND_ATTRIBUTE_KEY, remoteChannel.getChannelExtendAttribute()); return JSON.toJSONString(data); } public static RemoteChannel decodeFromJson(String jsonData) { if (StringUtils.isBlank(jsonData)) { return null; } try { JSONObject jsonObject = JSON.parseObject(jsonData); return new RemoteChannel( jsonObject.getString(REMOTE_PROXY_IP_KEY), jsonObject.getString(REMOTE_ADDRESS_KEY), jsonObject.getString(LOCAL_ADDRESS_KEY), jsonObject.getObject(TYPE_KEY, ChannelProtocolType.class), jsonObject.getObject(EXTEND_ATTRIBUTE_KEY, String.class) ); } catch (Throwable t) { log.error("decode remote channel data failed. data:{}", jsonData, t); } return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/DefaultTopicMessageTypeValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.validator; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; public class DefaultTopicMessageTypeValidator implements TopicMessageTypeValidator { public void validate(TopicMessageType expectedType, TopicMessageType actualType) { if (actualType.equals(TopicMessageType.UNSPECIFIED) || !actualType.equals(expectedType) && !expectedType.equals(TopicMessageType.MIXED)) { String errorInfo = String.format("TopicMessageType validate failed, the expected type is %s, but actual type is %s", expectedType, actualType); throw new ProxyException(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, errorInfo); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/TopicMessageTypeValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.validator; import org.apache.rocketmq.common.attribute.TopicMessageType; public interface TopicMessageTypeValidator { /** * Will throw {@link org.apache.rocketmq.proxy.common.ProxyException} if validate failed. * * @param expectedType Target topic * @param actualType Message's type */ void validate(TopicMessageType expectedType, TopicMessageType actualType); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/ClientHousekeepingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting; import io.netty.channel.Channel; import org.apache.rocketmq.proxy.remoting.activity.ClientManagerActivity; import org.apache.rocketmq.remoting.ChannelEventListener; public class ClientHousekeepingService implements ChannelEventListener { private final ClientManagerActivity clientManagerActivity; public ClientHousekeepingService(ClientManagerActivity clientManagerActivity) { this.clientManagerActivity = clientManagerActivity; } @Override public void onChannelConnect(String remoteAddr, Channel channel) { } @Override public void onChannelClose(String remoteAddr, Channel channel) { this.clientManagerActivity.doChannelCloseEvent(remoteAddr, channel); } @Override public void onChannelException(String remoteAddr, Channel channel) { this.clientManagerActivity.doChannelCloseEvent(remoteAddr, channel); } @Override public void onChannelIdle(String remoteAddr, Channel channel) { this.clientManagerActivity.doChannelCloseEvent(remoteAddr, channel); } @Override public void onChannelActive(String remoteAddr, Channel channel) { } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolRemotingServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.timeout.IdleStateHandler; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.remoting.protocol.ProtocolNegotiationHandler; import org.apache.rocketmq.proxy.remoting.protocol.http2proxy.Http2ProtocolProxyHandler; import org.apache.rocketmq.proxy.remoting.protocol.remoting.RemotingProtocolHandler; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import java.io.IOException; import java.security.cert.CertificateException; /** * support remoting and http2 protocol at one port */ public class MultiProtocolRemotingServer extends NettyRemotingServer { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final NettyServerConfig nettyServerConfig; private final RemotingProtocolHandler remotingProtocolHandler; protected Http2ProtocolProxyHandler http2ProtocolProxyHandler; public MultiProtocolRemotingServer(NettyServerConfig nettyServerConfig, ChannelEventListener channelEventListener) { super(nettyServerConfig, channelEventListener); this.nettyServerConfig = nettyServerConfig; this.remotingProtocolHandler = new RemotingProtocolHandler( this::getEncoder, this::getDistributionHandler, this::getConnectionManageHandler, this::getServerHandler); this.http2ProtocolProxyHandler = new Http2ProtocolProxyHandler(); } @Override public void loadSslContext() { TlsMode tlsMode = TlsSystemConfig.tlsMode; log.info("Server is running in TLS {} mode", tlsMode.getName()); if (tlsMode != TlsMode.DISABLED) { try { sslContext = MultiProtocolTlsHelper.buildSslContext(); log.info("SslContext created for multi protocol remoting server"); } catch (CertificateException | IOException e) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "Failed to create SslContext for server", e); } } } @Override protected ChannelPipeline configChannel(SocketChannel ch) { return ch.pipeline() .addLast(this.getDefaultEventExecutorGroup(), HANDSHAKE_HANDLER_NAME, new HandshakeHandler()) .addLast(this.getDefaultEventExecutorGroup(), new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()), new ProtocolNegotiationHandler(this.remotingProtocolHandler) .addProtocolHandler(this.http2ProtocolProxyHandler) ); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolTlsHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.security.cert.CertificateException; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.TlsHelper; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerAuthClient; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPassword; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerNeedClientAuth; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerTrustCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsTestModeEnable; public class MultiProtocolTlsHelper extends TlsHelper { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final DecryptionStrategy DECRYPTION_STRATEGY = (privateKeyEncryptPath, forClient) -> new FileInputStream(privateKeyEncryptPath); public static SslContext buildSslContext() throws IOException, CertificateException { TlsHelper.buildSslContext(false); SslProvider provider; if (OpenSsl.isAvailable()) { provider = SslProvider.OPENSSL; log.info("Using OpenSSL provider"); } else { provider = SslProvider.JDK; log.info("Using JDK SSL provider"); } SslContextBuilder sslContextBuilder; if (tlsTestModeEnable) { SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); sslContextBuilder = SslContextBuilder .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) .sslProvider(provider) .clientAuth(ClientAuth.OPTIONAL); } else { sslContextBuilder = SslContextBuilder.forServer( !StringUtils.isBlank(tlsServerCertPath) ? Files.newInputStream(Paths.get(tlsServerCertPath)) : null, !StringUtils.isBlank(tlsServerKeyPath) ? DECRYPTION_STRATEGY.decryptPrivateKey(tlsServerKeyPath, false) : null, !StringUtils.isBlank(tlsServerKeyPassword) ? tlsServerKeyPassword : null) .sslProvider(provider); if (!tlsServerAuthClient) { sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); } else { if (!StringUtils.isBlank(tlsServerTrustCertPath)) { sslContextBuilder.trustManager(new File(tlsServerTrustCertPath)); } } sslContextBuilder.clientAuth(parseClientAuthMode(tlsServerNeedClientAuth)); } sslContextBuilder.applicationProtocolConfig(new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2)); moreTlsConfig(sslContextBuilder); return sslContextBuilder.build(); } private static ClientAuth parseClientAuthMode(String authMode) { if (null == authMode || authMode.trim().isEmpty()) { return ClientAuth.NONE; } String authModeUpper = authMode.toUpperCase(); for (ClientAuth clientAuth : ClientAuth.values()) { if (clientAuth.name().equals(authModeUpper)) { return clientAuth; } } return ClientAuth.NONE; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.channel.Channel; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.future.FutureTaskExt; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.thread.ThreadPoolStatusMonitor; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.activity.AckMessageActivity; import org.apache.rocketmq.proxy.remoting.activity.ChangeInvisibleTimeActivity; import org.apache.rocketmq.proxy.remoting.activity.ClientManagerActivity; import org.apache.rocketmq.proxy.remoting.activity.ConsumerManagerActivity; import org.apache.rocketmq.proxy.remoting.activity.GetTopicRouteActivity; import org.apache.rocketmq.proxy.remoting.activity.PopMessageActivity; import org.apache.rocketmq.proxy.remoting.activity.PullMessageActivity; import org.apache.rocketmq.proxy.remoting.activity.RecallMessageActivity; import org.apache.rocketmq.proxy.remoting.activity.SendMessageActivity; import org.apache.rocketmq.proxy.remoting.activity.TransactionActivity; import org.apache.rocketmq.proxy.remoting.channel.RemotingChannelManager; import org.apache.rocketmq.proxy.remoting.pipeline.AuthenticationPipeline; import org.apache.rocketmq.proxy.remoting.pipeline.AuthorizationPipeline; import org.apache.rocketmq.proxy.remoting.pipeline.ContextInitPipeline; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.proxy.service.cert.TlsCertificateManager; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class RemotingProtocolServer implements StartAndShutdown, RemotingProxyOutClient { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final MessagingProcessor messagingProcessor; protected final RemotingChannelManager remotingChannelManager; protected final ChannelEventListener clientHousekeepingService; protected final RemotingServer defaultRemotingServer; protected final GetTopicRouteActivity getTopicRouteActivity; protected final ClientManagerActivity clientManagerActivity; protected final ConsumerManagerActivity consumerManagerActivity; protected final SendMessageActivity sendMessageActivity; protected final RecallMessageActivity recallMessageActivity; protected final TransactionActivity transactionActivity; protected final PullMessageActivity pullMessageActivity; protected final PopMessageActivity popMessageActivity; protected final AckMessageActivity ackMessageActivity; protected final ChangeInvisibleTimeActivity changeInvisibleTimeActivity; protected final ThreadPoolExecutor sendMessageExecutor; protected final ThreadPoolExecutor pullMessageExecutor; protected final ThreadPoolExecutor heartbeatExecutor; protected final ThreadPoolExecutor updateOffsetExecutor; protected final ThreadPoolExecutor topicRouteExecutor; protected final ThreadPoolExecutor defaultExecutor; protected final ScheduledExecutorService timerExecutor; protected final TlsCertificateManager tlsCertificateManager; protected final RemotingTlsReloadHandler tlsReloadHandler; public RemotingProtocolServer(MessagingProcessor messagingProcessor, TlsCertificateManager tlsCertificateManager) throws Exception { this.messagingProcessor = messagingProcessor; this.remotingChannelManager = new RemotingChannelManager(this, messagingProcessor.getProxyRelayService()); RequestPipeline pipeline = createRequestPipeline(messagingProcessor); this.getTopicRouteActivity = new GetTopicRouteActivity(pipeline, messagingProcessor); this.clientManagerActivity = new ClientManagerActivity(pipeline, messagingProcessor, remotingChannelManager); this.consumerManagerActivity = new ConsumerManagerActivity(pipeline, messagingProcessor); this.sendMessageActivity = new SendMessageActivity(pipeline, messagingProcessor); this.recallMessageActivity = new RecallMessageActivity(pipeline, messagingProcessor); this.transactionActivity = new TransactionActivity(pipeline, messagingProcessor); this.pullMessageActivity = new PullMessageActivity(pipeline, messagingProcessor); this.popMessageActivity = new PopMessageActivity(pipeline, messagingProcessor); this.ackMessageActivity = new AckMessageActivity(pipeline, messagingProcessor); this.changeInvisibleTimeActivity = new ChangeInvisibleTimeActivity(pipeline, messagingProcessor); ProxyConfig config = ConfigurationManager.getProxyConfig(); NettyServerConfig defaultServerConfig = new NettyServerConfig(); defaultServerConfig.setListenPort(config.getRemotingListenPort()); TlsSystemConfig.tlsTestModeEnable = config.isTlsTestModeEnable(); System.setProperty(TlsSystemConfig.TLS_TEST_MODE_ENABLE, Boolean.toString(config.isTlsTestModeEnable())); TlsSystemConfig.tlsServerCertPath = config.getTlsCertPath(); System.setProperty(TlsSystemConfig.TLS_SERVER_CERTPATH, config.getTlsCertPath()); TlsSystemConfig.tlsServerKeyPath = config.getTlsKeyPath(); System.setProperty(TlsSystemConfig.TLS_SERVER_KEYPATH, config.getTlsKeyPath()); TlsSystemConfig.tlsServerKeyPassword = config.getTlsKeyPassword(); System.setProperty(TlsSystemConfig.TLS_SERVER_KEYPASSWORD, config.getTlsKeyPassword()); this.tlsCertificateManager = tlsCertificateManager; this.tlsReloadHandler = new RemotingTlsReloadHandler(); this.clientHousekeepingService = new ClientHousekeepingService(this.clientManagerActivity); if (config.isEnableRemotingLocalProxyGrpc()) { this.defaultRemotingServer = new MultiProtocolRemotingServer(defaultServerConfig, this.clientHousekeepingService); } else { this.defaultRemotingServer = new NettyRemotingServer(defaultServerConfig, this.clientHousekeepingService); } this.sendMessageExecutor = ThreadPoolMonitor.createAndMonitor( config.getRemotingSendMessageThreadPoolNums(), config.getRemotingSendMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "RemotingSendMessageThread", config.getRemotingSendThreadPoolQueueCapacity(), new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInSendQueue()) ); this.pullMessageExecutor = ThreadPoolMonitor.createAndMonitor( config.getRemotingPullMessageThreadPoolNums(), config.getRemotingPullMessageThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "RemotingPullMessageThread", config.getRemotingPullThreadPoolQueueCapacity(), new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInPullQueue()) ); this.updateOffsetExecutor = ThreadPoolMonitor.createAndMonitor( config.getRemotingUpdateOffsetThreadPoolNums(), config.getRemotingUpdateOffsetThreadPoolNums(), 1, TimeUnit.MINUTES, "RemotingUpdateOffsetThread", config.getRemotingUpdateOffsetThreadPoolQueueCapacity(), new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInUpdateOffsetQueue()) ); this.heartbeatExecutor = ThreadPoolMonitor.createAndMonitor( config.getRemotingHeartbeatThreadPoolNums(), config.getRemotingHeartbeatThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "RemotingHeartbeatThread", config.getRemotingHeartbeatThreadPoolQueueCapacity(), new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInHeartbeatQueue()) ); this.topicRouteExecutor = ThreadPoolMonitor.createAndMonitor( config.getRemotingTopicRouteThreadPoolNums(), config.getRemotingTopicRouteThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "RemotingTopicRouteThread", config.getRemotingTopicRouteThreadPoolQueueCapacity(), new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInTopicRouteQueue()) ); this.defaultExecutor = ThreadPoolMonitor.createAndMonitor( config.getRemotingDefaultThreadPoolNums(), config.getRemotingDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "RemotingDefaultThread", config.getRemotingDefaultThreadPoolQueueCapacity(), new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInDefaultQueue()) ); this.timerExecutor = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("RemotingServerScheduler-%d").build() ); this.timerExecutor.scheduleAtFixedRate(this::cleanExpireRequest, 10, 10, TimeUnit.SECONDS); this.registerRemotingServer(this.defaultRemotingServer); } protected class RemotingTlsReloadHandler implements TlsCertificateManager.TlsContextReloadListener { @Override public void onTlsContextReload() { if (defaultRemotingServer instanceof NettyRemotingServer) { ((NettyRemotingServer) defaultRemotingServer).loadSslContext(); log.info("SslContext reloaded for remoting server"); } } } protected void registerRemotingServer(RemotingServer remotingServer) { remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageActivity, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageActivity, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageActivity, this.sendMessageExecutor); remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageActivity, sendMessageExecutor); remotingServer.registerProcessor(RequestCode.END_TRANSACTION, transactionActivity, sendMessageExecutor); remotingServer.registerProcessor(RequestCode.RECALL_MESSAGE, recallMessageActivity, sendMessageExecutor); remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManagerActivity, this.heartbeatExecutor); remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, pullMessageActivity, this.pullMessageExecutor); remotingServer.registerProcessor(RequestCode.LITE_PULL_MESSAGE, pullMessageActivity, this.pullMessageExecutor); remotingServer.registerProcessor(RequestCode.POP_MESSAGE, pullMessageActivity, this.pullMessageExecutor); remotingServer.registerProcessor(RequestCode.UPDATE_CONSUMER_OFFSET, consumerManagerActivity, this.updateOffsetExecutor); remotingServer.registerProcessor(RequestCode.ACK_MESSAGE, consumerManagerActivity, this.updateOffsetExecutor); remotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, consumerManagerActivity, this.updateOffsetExecutor); remotingServer.registerProcessor(RequestCode.GET_CONSUMER_CONNECTION_LIST, consumerManagerActivity, this.updateOffsetExecutor); remotingServer.registerProcessor(RequestCode.GET_CONSUMER_LIST_BY_GROUP, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.GET_MAX_OFFSET, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.GET_MIN_OFFSET, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.QUERY_CONSUMER_OFFSET, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.LOCK_BATCH_MQ, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.UNLOCK_BATCH_MQ, consumerManagerActivity, this.defaultExecutor); remotingServer.registerProcessor(RequestCode.GET_ROUTEINFO_BY_TOPIC, getTopicRouteActivity, this.topicRouteExecutor); } @Override public void shutdown() throws Exception { // Unregister the TLS context reload handler tlsCertificateManager.unregisterReloadListener(this.tlsReloadHandler); this.defaultRemotingServer.shutdown(); this.remotingChannelManager.shutdown(); this.sendMessageExecutor.shutdown(); this.pullMessageExecutor.shutdown(); this.heartbeatExecutor.shutdown(); this.updateOffsetExecutor.shutdown(); this.topicRouteExecutor.shutdown(); this.defaultExecutor.shutdown(); } @Override public void start() throws Exception { // Register the TLS context reload handler tlsCertificateManager.registerReloadListener(this.tlsReloadHandler); this.remotingChannelManager.start(); this.defaultRemotingServer.start(); } @Override public CompletableFuture invokeToClient(Channel channel, RemotingCommand request, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { this.defaultRemotingServer.invokeAsync(channel, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { future.complete(response); } @Override public void operationFail(Throwable throwable) { future.completeExceptionally(throwable); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } protected RequestPipeline createRequestPipeline(MessagingProcessor messagingProcessor) { RequestPipeline pipeline = (ctx, request, context) -> { }; // add pipeline // the last pipe add will execute at the first AuthConfig authConfig = ConfigurationManager.getAuthConfig(); if (authConfig != null) { pipeline = pipeline.pipe(new AuthorizationPipeline(authConfig, messagingProcessor)) .pipe(new AuthenticationPipeline(authConfig, messagingProcessor)); } return pipeline.pipe(new ContextInitPipeline()); } protected class ThreadPoolHeadSlowTimeMillsMonitor implements ThreadPoolStatusMonitor { private final long maxWaitTimeMillsInQueue; public ThreadPoolHeadSlowTimeMillsMonitor(long maxWaitTimeMillsInQueue) { this.maxWaitTimeMillsInQueue = maxWaitTimeMillsInQueue; } @Override public String describe() { return "headSlow"; } @Override public double value(ThreadPoolExecutor executor) { return headSlowTimeMills(executor.getQueue()); } @Override public boolean needPrintJstack(ThreadPoolExecutor executor, double value) { return value > maxWaitTimeMillsInQueue; } } protected long headSlowTimeMills(BlockingQueue q) { try { long slowTimeMills = 0; final Runnable peek = q.peek(); if (peek != null) { RequestTask rt = castRunnable(peek); slowTimeMills = rt == null ? 0 : System.currentTimeMillis() - rt.getCreateTimestamp(); } if (slowTimeMills < 0) { slowTimeMills = 0; } return slowTimeMills; } catch (Exception e) { log.error("error when headSlowTimeMills.", e); } return -1; } protected void cleanExpireRequest() { ProxyConfig config = ConfigurationManager.getProxyConfig(); cleanExpiredRequestInQueue(this.sendMessageExecutor, config.getRemotingWaitTimeMillsInSendQueue()); cleanExpiredRequestInQueue(this.pullMessageExecutor, config.getRemotingWaitTimeMillsInPullQueue()); cleanExpiredRequestInQueue(this.heartbeatExecutor, config.getRemotingWaitTimeMillsInHeartbeatQueue()); cleanExpiredRequestInQueue(this.updateOffsetExecutor, config.getRemotingWaitTimeMillsInUpdateOffsetQueue()); cleanExpiredRequestInQueue(this.topicRouteExecutor, config.getRemotingWaitTimeMillsInTopicRouteQueue()); cleanExpiredRequestInQueue(this.defaultExecutor, config.getRemotingWaitTimeMillsInDefaultQueue()); } protected void cleanExpiredRequestInQueue(ThreadPoolExecutor threadPoolExecutor, long maxWaitTimeMillsInQueue) { while (true) { try { BlockingQueue blockingQueue = threadPoolExecutor.getQueue(); if (!blockingQueue.isEmpty()) { final Runnable runnable = blockingQueue.peek(); if (null == runnable) { break; } final RequestTask rt = castRunnable(runnable); if (rt == null || rt.isStopRun()) { break; } final long behind = System.currentTimeMillis() - rt.getCreateTimestamp(); if (behind >= maxWaitTimeMillsInQueue) { if (blockingQueue.remove(runnable)) { rt.setStopRun(true); rt.returnResponse(ResponseCode.SYSTEM_BUSY, String.format("[TIMEOUT_CLEAN_QUEUE]broker busy, start flow control for a while, period in queue: %sms, size of queue: %d", behind, blockingQueue.size())); } } else { break; } } else { break; } } catch (Throwable ignored) { } } } private RequestTask castRunnable(final Runnable runnable) { try { if (runnable instanceof FutureTaskExt) { FutureTaskExt futureTaskExt = (FutureTaskExt) runnable; return (RequestTask) futureTaskExt.getRunnable(); } return null; } catch (Throwable e) { log.error("castRunnable exception. class:{}", runnable.getClass().getName(), e); } return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProxyOutClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting; import io.netty.channel.Channel; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RemotingProxyOutClient { CompletableFuture invokeToClient(Channel channel, RemotingCommand request, long timeoutMillis); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; public abstract class AbstractRemotingActivity implements NettyRequestProcessor { protected final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final MessagingProcessor messagingProcessor; protected static final String BROKER_NAME_FIELD = "bname"; protected static final String BROKER_NAME_FIELD_FOR_SEND_MESSAGE_V2 = "n"; @SuppressWarnings("DoubleBraceInitialization") private static final Map PROXY_EXCEPTION_RESPONSE_CODE_MAP = new HashMap() { { put(ProxyExceptionCode.FORBIDDEN, ResponseCode.NO_PERMISSION); put(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, ResponseCode.MESSAGE_ILLEGAL); put(ProxyExceptionCode.INTERNAL_SERVER_ERROR, ResponseCode.SYSTEM_ERROR); put(ProxyExceptionCode.TRANSACTION_DATA_NOT_FOUND, ResponseCode.SUCCESS); } }; protected final RequestPipeline requestPipeline; public AbstractRemotingActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { this.requestPipeline = requestPipeline; this.messagingProcessor = messagingProcessor; } protected RemotingCommand request(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context, long timeoutMillis) throws Exception { String brokerName; if (request.getCode() == RequestCode.SEND_MESSAGE_V2 || request.getCode() == RequestCode.SEND_BATCH_MESSAGE) { if (request.getExtFields().get(BROKER_NAME_FIELD_FOR_SEND_MESSAGE_V2) == null) { return RemotingCommand.buildErrorResponse(ResponseCode.VERSION_NOT_SUPPORTED, "Request doesn't have field bname"); } brokerName = request.getExtFields().get(BROKER_NAME_FIELD_FOR_SEND_MESSAGE_V2); } else { if (request.getExtFields().get(BROKER_NAME_FIELD) == null) { return RemotingCommand.buildErrorResponse(ResponseCode.VERSION_NOT_SUPPORTED, "Request doesn't have field bname"); } brokerName = request.getExtFields().get(BROKER_NAME_FIELD); } if (request.isOnewayRPC()) { messagingProcessor.requestOneway(context, brokerName, request, timeoutMillis); return null; } messagingProcessor.request(context, brokerName, request, timeoutMillis) .thenAccept(r -> writeResponse(ctx, context, request, r)) .exceptionally(t -> { writeErrResponse(ctx, context, request, t); return null; }); return null; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception { ProxyContext context = createContext(); try { this.requestPipeline.execute(ctx, request, context); RemotingCommand response = this.processRequest0(ctx, request, context); if (response != null) { writeResponse(ctx, context, request, response); } return null; } catch (Throwable t) { writeErrResponse(ctx, context, request, t); return null; } } @Override public boolean rejectRequest() { return false; } protected abstract RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception; protected ProxyContext createContext() { return ProxyContext.create(); } protected void writeErrResponse(ChannelHandlerContext ctx, final ProxyContext context, final RemotingCommand request, Throwable t) { t = ExceptionUtils.getRealException(t); if (t instanceof ProxyException) { ProxyException e = (ProxyException) t; writeResponse(ctx, context, request, RemotingCommand.createResponseCommand( PROXY_EXCEPTION_RESPONSE_CODE_MAP.getOrDefault(e.getCode(), ResponseCode.SYSTEM_ERROR), e.getMessage()), t); } else if (t instanceof MQClientException) { MQClientException e = (MQClientException) t; writeResponse(ctx, context, request, RemotingCommand.createResponseCommand(e.getResponseCode(), e.getErrorMessage()), t); } else if (t instanceof MQBrokerException) { MQBrokerException e = (MQBrokerException) t; writeResponse(ctx, context, request, RemotingCommand.createResponseCommand(e.getResponseCode(), e.getErrorMessage()), t); } else if (t instanceof AclException) { writeResponse(ctx, context, request, RemotingCommand.createResponseCommand(ResponseCode.NO_PERMISSION, t.getMessage()), t); } else { writeResponse(ctx, context, request, RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, t.getMessage()), t); } } protected void writeResponse(ChannelHandlerContext ctx, final ProxyContext context, final RemotingCommand request, RemotingCommand response) { writeResponse(ctx, context, request, response, null); } protected void writeResponse(ChannelHandlerContext ctx, final ProxyContext context, final RemotingCommand request, RemotingCommand response, Throwable t) { if (request.isOnewayRPC()) { return; } if (!ctx.channel().isWritable()) { return; } ProxyConfig config = ConfigurationManager.getProxyConfig(); response.setOpaque(request.getOpaque()); response.markResponseType(); response.addExtField(MessageConst.PROPERTY_MSG_REGION, config.getRegionId()); response.addExtField(MessageConst.PROPERTY_TRACE_SWITCH, String.valueOf(config.isTraceOn())); if (t != null) { response.setRemark(t.getMessage()); } ctx.writeAndFlush(response); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AckMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import java.time.Duration; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class AckMessageActivity extends AbstractRemotingActivity { public AckMessageActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { return request(ctx, request, context, Duration.ofSeconds(3).toMillis()); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ChangeInvisibleTimeActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import java.time.Duration; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class ChangeInvisibleTimeActivity extends AbstractRemotingActivity { public ChangeInvisibleTimeActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { return request(ctx, request, context, Duration.ofSeconds(3).toMillis()); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ClientManagerActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ProducerChangeListener; import org.apache.rocketmq.broker.client.ProducerGroupEvent; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.channel.RemotingChannel; import org.apache.rocketmq.proxy.remoting.channel.RemotingChannelManager; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.netty.AttributeKeys; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.ProducerData; import java.util.Set; public class ClientManagerActivity extends AbstractRemotingActivity { private final RemotingChannelManager remotingChannelManager; public ClientManagerActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor, RemotingChannelManager manager) { super(requestPipeline, messagingProcessor); this.remotingChannelManager = manager; this.init(); } protected void init() { this.messagingProcessor.registerConsumerListener(new ConsumerIdsChangeListenerImpl()); this.messagingProcessor.registerProducerListener(new ProducerChangeListenerImpl()); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { switch (request.getCode()) { case RequestCode.HEART_BEAT: return this.heartBeat(ctx, request, context); case RequestCode.UNREGISTER_CLIENT: return this.unregisterClient(ctx, request, context); case RequestCode.CHECK_CLIENT_CONFIG: return this.checkClientConfig(ctx, request, context); default: break; } return null; } protected RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) { HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class); String clientId = heartbeatData.getClientID(); for (ProducerData data : heartbeatData.getProducerDataSet()) { ClientChannelInfo clientChannelInfo = new ClientChannelInfo( this.remotingChannelManager.createProducerChannel(context, ctx.channel(), data.getGroupName(), clientId), clientId, request.getLanguage(), request.getVersion()); setClientPropertiesToChannelAttr(clientChannelInfo); messagingProcessor.registerProducer(context, data.getGroupName(), clientChannelInfo); } for (ConsumerData data : heartbeatData.getConsumerDataSet()) { ClientChannelInfo clientChannelInfo = new ClientChannelInfo( this.remotingChannelManager.createConsumerChannel(context, ctx.channel(), data.getGroupName(), clientId, data.getSubscriptionDataSet()), clientId, request.getLanguage(), request.getVersion()); setClientPropertiesToChannelAttr(clientChannelInfo); messagingProcessor.registerConsumer(context, data.getGroupName(), clientChannelInfo, data.getConsumeType(), data.getMessageModel(), data.getConsumeFromWhere(), data.getSubscriptionDataSet(), true); } RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setRemark(""); return response; } private void setClientPropertiesToChannelAttr(final ClientChannelInfo clientChannelInfo) { Channel channel = clientChannelInfo.getChannel(); if (channel instanceof RemotingChannel) { RemotingChannel remotingChannel = (RemotingChannel) channel; Channel parent = remotingChannel.parent(); RemotingHelper.setPropertyToAttr(parent, AttributeKeys.CLIENT_ID_KEY, clientChannelInfo.getClientId()); RemotingHelper.setPropertyToAttr(parent, AttributeKeys.LANGUAGE_CODE_KEY, clientChannelInfo.getLanguage()); RemotingHelper.setPropertyToAttr(parent, AttributeKeys.VERSION_KEY, clientChannelInfo.getVersion()); } } protected RemotingCommand unregisterClient(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(UnregisterClientResponseHeader.class); final UnregisterClientRequestHeader requestHeader = (UnregisterClientRequestHeader) request.decodeCommandCustomHeader(UnregisterClientRequestHeader.class); final String producerGroup = requestHeader.getProducerGroup(); if (producerGroup != null) { RemotingChannel channel = this.remotingChannelManager.removeProducerChannel(context, producerGroup, ctx.channel()); if (channel != null) { ClientChannelInfo clientChannelInfo = new ClientChannelInfo( channel, requestHeader.getClientID(), request.getLanguage(), request.getVersion()); this.messagingProcessor.unRegisterProducer(context, producerGroup, clientChannelInfo); } else { log.warn("unregister producer failed, channel not exist, may has been removed, producerGroup={}, channel={}", producerGroup, ctx.channel()); } } final String consumerGroup = requestHeader.getConsumerGroup(); if (consumerGroup != null) { RemotingChannel channel = this.remotingChannelManager.removeConsumerChannel(context, consumerGroup, ctx.channel()); if (channel != null) { ClientChannelInfo clientChannelInfo = new ClientChannelInfo( channel, requestHeader.getClientID(), request.getLanguage(), request.getVersion()); this.messagingProcessor.unRegisterConsumer(context, consumerGroup, clientChannelInfo); } else { log.warn("unregister consumer failed, channel not exist, may has been removed, consumerGroup={}, channel={}", consumerGroup, ctx.channel()); } } response.setCode(ResponseCode.SUCCESS); response.setRemark(""); return response; } protected RemotingCommand checkClientConfig(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setRemark(""); return response; } public void doChannelCloseEvent(String remoteAddr, Channel channel) { Set remotingChannelSet = this.remotingChannelManager.removeChannel(channel); for (RemotingChannel remotingChannel : remotingChannelSet) { this.messagingProcessor.doChannelCloseEvent(remoteAddr, remotingChannel); } } protected class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener { @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) { if (args == null || args.length < 1) { return; } if (args[0] instanceof ClientChannelInfo) { ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0]; remotingChannelManager.removeConsumerChannel(ProxyContext.createForInner(this.getClass()), group, clientChannelInfo.getChannel()); log.info("remove remoting channel when client unregister. clientChannelInfo:{}", clientChannelInfo); } } } @Override public void shutdown() { } } protected class ProducerChangeListenerImpl implements ProducerChangeListener { @Override public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) { if (event == ProducerGroupEvent.CLIENT_UNREGISTER) { remotingChannelManager.removeProducerChannel(ProxyContext.createForInner(this.getClass()), group, clientChannelInfo.getChannel()); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ConsumerManagerActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.time.Duration; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.Connection; import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseHeader; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class ConsumerManagerActivity extends AbstractRemotingActivity { public ConsumerManagerActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { switch (request.getCode()) { case RequestCode.GET_CONSUMER_LIST_BY_GROUP: { return getConsumerListByGroup(ctx, request, context); } case RequestCode.LOCK_BATCH_MQ: { return lockBatchMQ(ctx, request, context); } case RequestCode.UNLOCK_BATCH_MQ: { return unlockBatchMQ(ctx, request, context); } case RequestCode.UPDATE_CONSUMER_OFFSET: case RequestCode.QUERY_CONSUMER_OFFSET: case RequestCode.SEARCH_OFFSET_BY_TIMESTAMP: case RequestCode.GET_MIN_OFFSET: case RequestCode.GET_MAX_OFFSET: case RequestCode.GET_EARLIEST_MSG_STORETIME: { return request(ctx, request, context, Duration.ofSeconds(3).toMillis()); } case RequestCode.GET_CONSUMER_CONNECTION_LIST: { return getConsumerConnectionList(ctx, request, context); } default: break; } return null; } protected RemotingCommand getConsumerListByGroup(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { RemotingCommand response = RemotingCommand.createResponseCommand(GetConsumerListByGroupResponseHeader.class); GetConsumerListByGroupRequestHeader header = (GetConsumerListByGroupRequestHeader) request.decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class); ConsumerGroupInfo consumerGroupInfo = messagingProcessor.getConsumerGroupInfo(context, header.getConsumerGroup()); List clientIds = consumerGroupInfo.getAllClientId(); GetConsumerListByGroupResponseBody body = new GetConsumerListByGroupResponseBody(); body.setConsumerIdList(clientIds); response.setBody(body.encode()); response.setCode(ResponseCode.SUCCESS); return response; } protected RemotingCommand getConsumerConnectionList(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { RemotingCommand response = RemotingCommand.createResponseCommand(null); GetConsumerConnectionListRequestHeader header = (GetConsumerConnectionListRequestHeader) request.decodeCommandCustomHeader(GetConsumerConnectionListRequestHeader.class); ConsumerGroupInfo consumerGroupInfo = messagingProcessor.getConsumerGroupInfo(context, header.getConsumerGroup()); if (consumerGroupInfo != null) { ConsumerConnection bodydata = new ConsumerConnection(); bodydata.setConsumeFromWhere(consumerGroupInfo.getConsumeFromWhere()); bodydata.setConsumeType(consumerGroupInfo.getConsumeType()); bodydata.setMessageModel(consumerGroupInfo.getMessageModel()); bodydata.getSubscriptionTable().putAll(consumerGroupInfo.getSubscriptionTable()); Iterator> it = consumerGroupInfo.getChannelInfoTable().entrySet().iterator(); while (it.hasNext()) { ClientChannelInfo info = it.next().getValue(); Connection connection = new Connection(); connection.setClientId(info.getClientId()); connection.setLanguage(info.getLanguage()); connection.setVersion(info.getVersion()); connection.setClientAddr(RemotingHelper.parseChannelRemoteAddr(info.getChannel())); bodydata.getConnectionSet().add(connection); } byte[] body = bodydata.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } response.setCode(ResponseCode.CONSUMER_NOT_ONLINE); response.setRemark("the consumer group[" + header.getConsumerGroup() + "] not online"); return response; } protected RemotingCommand lockBatchMQ(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { final RemotingCommand response = RemotingCommand.createResponseCommand(null); LockBatchRequestBody requestBody = LockBatchRequestBody.decode(request.getBody(), LockBatchRequestBody.class); Set mqSet = requestBody.getMqSet(); if (mqSet.isEmpty()) { response.setBody(requestBody.encode()); response.setRemark("MessageQueue set is empty"); return response; } String brokerName = new ArrayList<>(mqSet).get(0).getBrokerName(); messagingProcessor.request(context, brokerName, request, Duration.ofSeconds(3).toMillis()) .thenAccept(r -> writeResponse(ctx, context, request, r)) .exceptionally(t -> { writeErrResponse(ctx, context, request, t); return null; }); return null; } protected RemotingCommand unlockBatchMQ(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { final RemotingCommand response = RemotingCommand.createResponseCommand(null); UnlockBatchRequestBody requestBody = UnlockBatchRequestBody.decode(request.getBody(), UnlockBatchRequestBody.class); Set mqSet = requestBody.getMqSet(); if (mqSet.isEmpty()) { response.setBody(requestBody.encode()); response.setRemark("MessageQueue set is empty"); return response; } String brokerName = new ArrayList<>(mqSet).get(0).getBrokerName(); messagingProcessor.request(context, brokerName, request, Duration.ofSeconds(3).toMillis()) .thenAccept(r -> writeResponse(ctx, context, request, r)) .exceptionally(t -> { writeErrResponse(ctx, context, request, t); return null; }); return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/GetTopicRouteActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import com.alibaba.fastjson2.JSONWriter; import com.google.common.net.HostAndPort; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import java.util.ArrayList; import java.util.List; public class GetTopicRouteActivity extends AbstractRemotingActivity { public GetTopicRouteActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetRouteInfoRequestHeader requestHeader = (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class); List
    addressList = new ArrayList<>(); // AddressScheme is just a placeholder and will not affect topic route result in this case. addressList.add(new Address(HostAndPort.fromParts(proxyConfig.getRemotingAccessAddr(), proxyConfig.getRemotingListenPort()))); ProxyTopicRouteData proxyTopicRouteData = messagingProcessor.getTopicRouteDataForProxy(context, addressList, requestHeader.getTopic()); TopicRouteData topicRouteData = proxyTopicRouteData.buildTopicRouteData(); byte[] content; Boolean standardJsonOnly = requestHeader.getAcceptStandardJsonOnly(); if (request.getVersion() >= MQVersion.Version.V4_9_4.ordinal() || null != standardJsonOnly && standardJsonOnly) { content = topicRouteData.encode(JSONWriter.Feature.BrowserCompatible, JSONWriter.Feature.MapSortField); } else { content = topicRouteData.encode(); } response.setBody(content); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PopMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import java.time.Duration; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class PopMessageActivity extends AbstractRemotingActivity { public PopMessageActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { PopMessageRequestHeader popMessageRequestHeader = (PopMessageRequestHeader) request.decodeCommandCustomHeader(PopMessageRequestHeader.class); long timeoutMillis = popMessageRequestHeader.getPollTime(); return request(ctx, request, context, timeoutMillis + Duration.ofSeconds(10).toMillis()); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import java.time.Duration; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class PullMessageActivity extends AbstractRemotingActivity { public PullMessageActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { PullMessageRequestHeader requestHeader = (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class); int sysFlag = requestHeader.getSysFlag(); if (!PullSysFlag.hasSubscriptionFlag(sysFlag)) { ConsumerGroupInfo consumerInfo = messagingProcessor.getConsumerGroupInfo(context, requestHeader.getConsumerGroup()); if (consumerInfo == null) { return RemotingCommand.buildErrorResponse(ResponseCode.SUBSCRIPTION_NOT_LATEST, "the consumer's subscription not latest"); } SubscriptionData subscriptionData = consumerInfo.findSubscriptionData(requestHeader.getTopic()); if (subscriptionData == null) { return RemotingCommand.buildErrorResponse(ResponseCode.SUBSCRIPTION_NOT_EXIST, "the consumer's subscription not exist"); } requestHeader.setSysFlag(PullSysFlag.buildSysFlagWithSubscription(sysFlag)); requestHeader.setSubscription(subscriptionData.getSubString()); requestHeader.setExpressionType(subscriptionData.getExpressionType()); request.writeCustomHeader(requestHeader); request.makeCustomHeaderToNet(); } long timeoutMillis = requestHeader.getSuspendTimeoutMillis() + Duration.ofSeconds(10).toMillis(); return request(ctx, request, context, timeoutMillis); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/RecallMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.validator.DefaultTopicMessageTypeValidator; import org.apache.rocketmq.proxy.processor.validator.TopicMessageTypeValidator; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import java.time.Duration; public class RecallMessageActivity extends AbstractRemotingActivity { TopicMessageTypeValidator topicMessageTypeValidator; public RecallMessageActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); this.topicMessageTypeValidator = new DefaultTopicMessageTypeValidator(); } @Override public RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { RecallMessageRequestHeader requestHeader = request.decodeCommandCustomHeader(RecallMessageRequestHeader.class); String topic = requestHeader.getTopic(); if (ConfigurationManager.getProxyConfig().isEnableTopicMessageTypeCheck()) { TopicMessageType messageType = messagingProcessor.getMetadataService().getTopicMessageType(context, topic); topicMessageTypeValidator.validate(messageType, TopicMessageType.DELAY); } return request(ctx, request, context, Duration.ofSeconds(2).toMillis()); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import java.time.Duration; import java.util.Map; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.validator.DefaultTopicMessageTypeValidator; import org.apache.rocketmq.proxy.processor.validator.TopicMessageTypeValidator; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; public class SendMessageActivity extends AbstractRemotingActivity { TopicMessageTypeValidator topicMessageTypeValidator; public SendMessageActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); this.topicMessageTypeValidator = new DefaultTopicMessageTypeValidator(); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { switch (request.getCode()) { case RequestCode.SEND_MESSAGE: case RequestCode.SEND_MESSAGE_V2: case RequestCode.SEND_BATCH_MESSAGE: { return sendMessage(ctx, request, context); } case RequestCode.CONSUMER_SEND_MSG_BACK: { return consumerSendMessage(ctx, request, context); } default: break; } return null; } protected RemotingCommand sendMessage(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { SendMessageRequestHeader requestHeader = SendMessageRequestHeader.parseRequestHeader(request); String topic = requestHeader.getTopic(); Map property = MessageDecoder.string2messageProperties(requestHeader.getProperties()); TopicMessageType messageType = TopicMessageType.parseFromMessageProperty(property); if (isNeedCheckTopicMessageType(property)) { if (topicMessageTypeValidator != null) { // Do not check retry or dlq topic if (!NamespaceUtil.isRetryTopic(topic) && !NamespaceUtil.isDLQTopic(topic)) { TopicMessageType topicMessageType = messagingProcessor.getMetadataService().getTopicMessageType(context, topic); topicMessageTypeValidator.validate(topicMessageType, messageType); } } } if (!NamespaceUtil.isRetryTopic(topic) && !NamespaceUtil.isDLQTopic(topic)) { if (TopicMessageType.TRANSACTION.equals(messageType)) { messagingProcessor.addTransactionSubscription(context, requestHeader.getProducerGroup(), requestHeader.getTopic()); } } return request(ctx, request, context, Duration.ofSeconds(3).toMillis()); } protected RemotingCommand consumerSendMessage(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { return request(ctx, request, context, Duration.ofSeconds(3).toMillis()); } private boolean isNeedCheckTopicMessageType(Map property) { return ConfigurationManager.getProxyConfig().isEnableTopicMessageTypeCheck() && !property.containsKey(MessageConst.PROPERTY_TRANSFER_FLAG); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/TransactionActivity.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.TransactionStatus; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class TransactionActivity extends AbstractRemotingActivity { public TransactionActivity(RequestPipeline requestPipeline, MessagingProcessor messagingProcessor) { super(requestPipeline, messagingProcessor); } @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); final EndTransactionRequestHeader requestHeader = (EndTransactionRequestHeader) request.decodeCommandCustomHeader(EndTransactionRequestHeader.class); TransactionStatus transactionStatus = TransactionStatus.UNKNOWN; switch (requestHeader.getCommitOrRollback()) { case MessageSysFlag.TRANSACTION_COMMIT_TYPE: transactionStatus = TransactionStatus.COMMIT; break; case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: transactionStatus = TransactionStatus.ROLLBACK; break; default: break; } this.messagingProcessor.endTransaction( context, requestHeader.getTopic(), requestHeader.getTransactionId(), requestHeader.getMsgId(), requestHeader.getProducerGroup(), transactionStatus, requestHeader.getFromTransactionCheck() ); return response; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.channel; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; import com.google.common.base.MoreObjects; import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelMetadata; import java.time.Duration; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.NotImplementedException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.channel.ChannelHelper; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.processor.channel.ChannelExtendAttributeGetter; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; import org.apache.rocketmq.proxy.processor.channel.RemoteChannel; import org.apache.rocketmq.proxy.processor.channel.RemoteChannelConverter; import org.apache.rocketmq.proxy.remoting.RemotingProxyOutClient; import org.apache.rocketmq.proxy.remoting.common.RemotingConverter; import org.apache.rocketmq.proxy.service.relay.ProxyChannel; import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class RemotingChannel extends ProxyChannel implements RemoteChannelConverter, ChannelExtendAttributeGetter { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final long DEFAULT_MQ_CLIENT_TIMEOUT = Duration.ofSeconds(3).toMillis(); private final String clientId; private final String remoteAddress; private final String localAddress; private final RemotingProxyOutClient remotingProxyOutClient; private final Set subscriptionData; public RemotingChannel(RemotingProxyOutClient remotingProxyOutClient, ProxyRelayService proxyRelayService, Channel parent, String clientId, Set subscriptionData) { super(proxyRelayService, parent, parent.id(), NetworkUtil.socketAddress2String(parent.remoteAddress()), NetworkUtil.socketAddress2String(parent.localAddress())); this.remotingProxyOutClient = remotingProxyOutClient; this.clientId = clientId; this.remoteAddress = NetworkUtil.socketAddress2String(parent.remoteAddress()); this.localAddress = NetworkUtil.socketAddress2String(parent.localAddress()); this.subscriptionData = subscriptionData; } @Override public boolean isOpen() { return this.parent().isOpen(); } @Override public boolean isActive() { return this.parent().isActive(); } @Override public boolean isWritable() { return this.parent().isWritable(); } @Override public ChannelFuture close() { return this.parent().close(); } @Override public ChannelConfig config() { return this.parent().config(); } @Override public ChannelMetadata metadata() { return this.parent().metadata(); } @Override protected CompletableFuture processOtherMessage(Object msg) { this.parent().writeAndFlush(msg); return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processCheckTransaction(CheckTransactionStateRequestHeader header, MessageExt messageExt, TransactionData transactionData, CompletableFuture> responseFuture) { CompletableFuture writeFuture = new CompletableFuture<>(); try { CheckTransactionStateRequestHeader requestHeader = new CheckTransactionStateRequestHeader(); requestHeader.setTopic(messageExt.getTopic()); requestHeader.setCommitLogOffset(transactionData.getCommitLogOffset()); requestHeader.setTranStateTableOffset(transactionData.getTranStateTableOffset()); requestHeader.setTransactionId(transactionData.getTransactionId()); requestHeader.setMsgId(header.getMsgId()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHECK_TRANSACTION_STATE, requestHeader); request.setBody(RemotingConverter.getInstance().convertMsgToBytes(messageExt)); this.parent().writeAndFlush(request).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { responseFuture.complete(null); writeFuture.complete(null); } else { Exception e = new RemotingException("write and flush data failed"); responseFuture.completeExceptionally(e); writeFuture.completeExceptionally(e); } }); } catch (Throwable t) { responseFuture.completeExceptionally(t); writeFuture.completeExceptionally(t); } return writeFuture; } @Override protected CompletableFuture processGetConsumerRunningInfo(RemotingCommand command, GetConsumerRunningInfoRequestHeader header, CompletableFuture> responseFuture) { try { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_RUNNING_INFO, header); this.remotingProxyOutClient.invokeToClient(this.parent(), request, DEFAULT_MQ_CLIENT_TIMEOUT) .thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ConsumerRunningInfo consumerRunningInfo = ConsumerRunningInfo.decode(response.getBody(), ConsumerRunningInfo.class); responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", consumerRunningInfo)); } else { String errMsg = String.format("get consumer running info failed, code:%s remark:%s", response.getCode(), response.getRemark()); RuntimeException e = new RuntimeException(errMsg); responseFuture.completeExceptionally(e); } }) .exceptionally(t -> { responseFuture.completeExceptionally(ExceptionUtils.getRealException(t)); return null; }); return CompletableFuture.completedFuture(null); } catch (Throwable t) { responseFuture.completeExceptionally(t); return FutureUtils.completeExceptionally(t); } } @Override protected CompletableFuture processNotifyUnsubscribeLite(NotifyUnsubscribeLiteRequestHeader header) { throw new NotImplementedException(); } @Override protected CompletableFuture processConsumeMessageDirectly(RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header, MessageExt messageExt, CompletableFuture> responseFuture) { try { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONSUME_MESSAGE_DIRECTLY, header); request.setBody(RemotingConverter.getInstance().convertMsgToBytes(messageExt)); this.remotingProxyOutClient.invokeToClient(this.parent(), request, DEFAULT_MQ_CLIENT_TIMEOUT) .thenAccept(response -> { if (response.getCode() == ResponseCode.SUCCESS) { ConsumeMessageDirectlyResult result = ConsumeMessageDirectlyResult.decode(response.getBody(), ConsumeMessageDirectlyResult.class); responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", result)); } else { String errMsg = String.format("consume message directly failed, code:%s remark:%s", response.getCode(), response.getRemark()); RuntimeException e = new RuntimeException(errMsg); responseFuture.completeExceptionally(e); } }) .exceptionally(t -> { responseFuture.completeExceptionally(ExceptionUtils.getRealException(t)); return null; }); return CompletableFuture.completedFuture(null); } catch (Throwable t) { responseFuture.completeExceptionally(t); return FutureUtils.completeExceptionally(t); } } public String getClientId() { return clientId; } @Override public String getChannelExtendAttribute() { if (this.subscriptionData == null) { return null; } return JSON.toJSONString(this.subscriptionData); } public static Set parseChannelExtendAttribute(Channel channel) { if (ChannelHelper.getChannelProtocolType(channel).equals(ChannelProtocolType.REMOTING) && channel instanceof ChannelExtendAttributeGetter) { String attr = ((ChannelExtendAttributeGetter) channel).getChannelExtendAttribute(); if (attr == null) { return null; } try { return JSON.parseObject(attr, new TypeReference>() { }); } catch (Exception e) { log.error("convert remoting extend attribute to subscriptionDataSet failed. data:{}", attr, e); return null; } } return null; } @Override public RemoteChannel toRemoteChannel() { return new RemoteChannel( ConfigurationManager.getProxyConfig().getLocalServeAddr(), this.getRemoteAddress(), this.getLocalAddress(), ChannelProtocolType.REMOTING, this.getChannelExtendAttribute()); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("parent", parent()) .add("clientId", clientId) .add("remoteAddress", remoteAddress) .add("localAddress", localAddress) .add("subscriptionData", subscriptionData) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.channel; import io.netty.channel.Channel; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.remoting.RemotingProxyOutClient; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class RemotingChannelManager implements StartAndShutdown { protected final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final ProxyRelayService proxyRelayService; protected final ConcurrentMap> groupChannelMap = new ConcurrentHashMap<>(); private final RemotingProxyOutClient remotingProxyOutClient; public RemotingChannelManager(RemotingProxyOutClient remotingProxyOutClient, ProxyRelayService proxyRelayService) { this.remotingProxyOutClient = remotingProxyOutClient; this.proxyRelayService = proxyRelayService; } protected String buildProducerKey(String group) { return buildKey("p", group); } protected String buildConsumerKey(String group) { return buildKey("c", group); } protected String buildKey(String prefix, String group) { return prefix + group; } public RemotingChannel createProducerChannel(ProxyContext ctx, Channel channel, String group, String clientId) { return createChannel(channel, buildProducerKey(group), clientId, Collections.emptySet()); } public RemotingChannel createConsumerChannel(ProxyContext ctx, Channel channel, String group, String clientId, Set subscriptionData) { return createChannel(channel, buildConsumerKey(group), clientId, subscriptionData); } protected RemotingChannel createChannel(Channel channel, String group, String clientId, Set subscriptionData) { this.groupChannelMap.compute(group, (groupKey, clientIdMap) -> { if (clientIdMap == null) { clientIdMap = new ConcurrentHashMap<>(); } clientIdMap.computeIfAbsent(channel, clientIdKey -> new RemotingChannel(remotingProxyOutClient, proxyRelayService, channel, clientId, subscriptionData)); return clientIdMap; }); return getChannel(group, channel); } protected RemotingChannel getChannel(String group, Channel channel) { Map clientIdChannelMap = this.groupChannelMap.get(group); if (clientIdChannelMap == null) { return null; } return clientIdChannelMap.get(channel); } public Set removeChannel(Channel channel) { Set removedChannelSet = new HashSet<>(); Set groupKeySet = groupChannelMap.keySet(); for (String group : groupKeySet) { RemotingChannel remotingChannel = removeChannel(group, channel); if (remotingChannel != null) { removedChannelSet.add(remotingChannel); } } return removedChannelSet; } public RemotingChannel removeProducerChannel(ProxyContext ctx, String group, Channel channel) { return removeChannel(buildProducerKey(group), channel); } public RemotingChannel removeConsumerChannel(ProxyContext ctx, String group, Channel channel) { return removeChannel(buildConsumerKey(group), channel); } protected RemotingChannel removeChannel(String group, Channel channel) { AtomicReference channelRef = new AtomicReference<>(); this.groupChannelMap.computeIfPresent(group, (groupKey, channelMap) -> { channelRef.set(channelMap.remove(getOrgRawChannel(channel))); if (channelMap.isEmpty()) { return null; } return channelMap; }); return channelRef.get(); } /** * to get the org channel pass by nettyRemotingServer * @param channel * @return */ protected Channel getOrgRawChannel(Channel channel) { if (channel instanceof RemotingChannel) { return channel.parent(); } return channel; } @Override public void shutdown() throws Exception { } @Override public void start() throws Exception { } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/common/RemotingConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.common; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class RemotingConverter { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected static final Object INSTANCE_CREATE_LOCK = new Object(); protected static volatile RemotingConverter instance; public static RemotingConverter getInstance() { if (instance == null) { synchronized (INSTANCE_CREATE_LOCK) { if (instance == null) { instance = new RemotingConverter(); } } } return instance; } public byte[] convertMsgToBytes(final MessageExt msg) throws Exception { // change to 0 for recalculate storeSize msg.setStoreSize(0); if (msg.getTopic().length() > Byte.MAX_VALUE) { log.warn("Topic length is too long, topic: {}", msg.getTopic()); } return MessageDecoder.encode(msg, false); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/AuthenticationPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.pipeline; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator; import org.apache.rocketmq.auth.authentication.context.AuthenticationContext; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class AuthenticationPipeline implements RequestPipeline { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final AuthConfig authConfig; private final AuthenticationEvaluator authenticationEvaluator; public AuthenticationPipeline(AuthConfig authConfig, MessagingProcessor messagingProcessor) { this.authConfig = authConfig; this.authenticationEvaluator = AuthenticationFactory.getEvaluator(authConfig, messagingProcessor::getMetadataService); } @Override public void execute(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { if (!authConfig.isAuthenticationEnabled()) { return; } try { AuthenticationContext authenticationContext = newContext(ctx, request, context); authenticationEvaluator.evaluate(authenticationContext); } catch (AuthenticationException ex) { throw ex; } catch (Throwable ex) { LOGGER.error("authenticate failed, request:{}", request, ex); throw ex; } } protected AuthenticationContext newContext(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) { return AuthenticationFactory.newContext(authConfig, ctx, request); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/AuthorizationPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.pipeline; import io.netty.channel.ChannelHandlerContext; import java.util.List; import org.apache.rocketmq.auth.authentication.exception.AuthenticationException; import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator; import org.apache.rocketmq.auth.authorization.context.AuthorizationContext; import org.apache.rocketmq.auth.authorization.exception.AuthorizationException; import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory; import org.apache.rocketmq.auth.config.AuthConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class AuthorizationPipeline implements RequestPipeline { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final AuthConfig authConfig; private final AuthorizationEvaluator authorizationEvaluator; public AuthorizationPipeline(AuthConfig authConfig, MessagingProcessor messagingProcessor) { this.authConfig = authConfig; this.authorizationEvaluator = AuthorizationFactory.getEvaluator(authConfig, messagingProcessor::getMetadataService); } @Override public void execute(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { if (!authConfig.isAuthorizationEnabled()) { return; } try { List contexts = newContexts(request, ctx, context); authorizationEvaluator.evaluate(contexts); } catch (AuthorizationException | AuthenticationException ex) { throw ex; } catch (Throwable ex) { LOGGER.error("authorize failed, request:{}", request, ex); throw ex; } } protected List newContexts(RemotingCommand request, ChannelHandlerContext ctx, ProxyContext context) { return AuthorizationFactory.newContexts(authConfig, ctx, request); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/ContextInitPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.pipeline; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.netty.AttributeKeys; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class ContextInitPipeline implements RequestPipeline { @Override public void execute(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { Channel channel = ctx.channel(); LanguageCode languageCode = RemotingHelper.getAttributeValue(AttributeKeys.LANGUAGE_CODE_KEY, channel); String clientId = RemotingHelper.getAttributeValue(AttributeKeys.CLIENT_ID_KEY, channel); Integer version = RemotingHelper.getAttributeValue(AttributeKeys.VERSION_KEY, channel); context.setAction(RemotingHelper.getRequestCodeDesc(request.getCode())) .setProtocolType(ChannelProtocolType.REMOTING.getName()) .setChannel(channel) .setLocalAddress(NetworkUtil.socketAddress2String(ctx.channel().localAddress())) .setRemoteAddress(RemotingHelper.parseChannelRemoteAddr(ctx.channel())); if (languageCode != null) { context.setLanguage(languageCode.name()); } if (clientId != null) { context.setClientID(clientId); } if (version != null) { context.setClientVersion(MQVersion.getVersionDesc(version)); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/RequestPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.pipeline; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RequestPipeline { void execute(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception; default RequestPipeline pipe(RequestPipeline source) { return (ctx, request, context) -> { source.execute(ctx, request, context); execute(ctx, request, context); }; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; public interface ProtocolHandler { boolean match(ByteBuf msg); void config(final ChannelHandlerContext ctx, final ByteBuf msg); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolNegotiationHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.util.ArrayList; import java.util.List; public class ProtocolNegotiationHandler extends ByteToMessageDecoder { private final List protocolHandlerList = new ArrayList(); private final ProtocolHandler fallbackProtocolHandler; public ProtocolNegotiationHandler(ProtocolHandler fallbackProtocolHandler) { this.fallbackProtocolHandler = fallbackProtocolHandler; } public ProtocolNegotiationHandler addProtocolHandler(ProtocolHandler protocolHandler) { protocolHandlerList.add(protocolHandler); return this; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { // use 4 bytes to judge protocol if (in.readableBytes() < 4) { return; } ProtocolHandler protocolHandler = null; for (ProtocolHandler curProtocolHandler : protocolHandlerList) { if (curProtocolHandler.match(in)) { protocolHandler = curProtocolHandler; break; } } if (protocolHandler == null) { protocolHandler = fallbackProtocolHandler; } protocolHandler.config(ctx, in); ctx.pipeline().remove(this); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/HAProxyMessageForwarder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.http2proxy; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.haproxy.HAProxyCommand; import io.netty.handler.codec.haproxy.HAProxyMessage; import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; import io.netty.handler.codec.haproxy.HAProxyTLV; import io.netty.util.Attribute; import io.netty.util.DefaultAttributeMap; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.common.constant.CommonConstants; import org.apache.rocketmq.common.constant.HAProxyConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.netty.AttributeKeys; public class HAProxyMessageForwarder extends ChannelInboundHandlerAdapter { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final Field FIELD_ATTRIBUTE = FieldUtils.getField(DefaultAttributeMap.class, "attributes", true); private final Channel outboundChannel; public HAProxyMessageForwarder(final Channel outboundChannel) { this.outboundChannel = outboundChannel; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { forwardHAProxyMessage(ctx.channel(), outboundChannel); ctx.fireChannelRead(msg); } catch (Exception e) { log.error("Forward HAProxyMessage from Remoting to gRPC server error.", e); throw e; } finally { ctx.pipeline().remove(this); } } private void forwardHAProxyMessage(Channel inboundChannel, Channel outboundChannel) throws Exception { if (!(inboundChannel instanceof DefaultAttributeMap)) { return; } HAProxyMessage message = buildHAProxyMessage(inboundChannel); if (message == null) { return; } outboundChannel.writeAndFlush(message).sync(); } protected HAProxyMessage buildHAProxyMessage(Channel inboundChannel) throws IllegalAccessException, DecoderException { String sourceAddress = null, destinationAddress = null; int sourcePort = 0, destinationPort = 0; if (inboundChannel.hasAttr(AttributeKeys.PROXY_PROTOCOL_ADDR)) { Attribute[] attributes = (Attribute[]) FieldUtils.readField(FIELD_ATTRIBUTE, inboundChannel); if (ArrayUtils.isEmpty(attributes)) { return null; } for (Attribute attribute : attributes) { String attributeKey = attribute.key().name(); if (!StringUtils.startsWith(attributeKey, HAProxyConstants.PROXY_PROTOCOL_PREFIX)) { continue; } String attributeValue = (String) attribute.get(); if (StringUtils.isEmpty(attributeValue)) { continue; } if (attribute.key() == AttributeKeys.PROXY_PROTOCOL_ADDR) { sourceAddress = attributeValue; } if (attribute.key() == AttributeKeys.PROXY_PROTOCOL_PORT) { sourcePort = Integer.parseInt(attributeValue); } if (attribute.key() == AttributeKeys.PROXY_PROTOCOL_SERVER_ADDR) { destinationAddress = attributeValue; } if (attribute.key() == AttributeKeys.PROXY_PROTOCOL_SERVER_PORT) { destinationPort = Integer.parseInt(attributeValue); } } } else { String remoteAddr = RemotingHelper.parseChannelRemoteAddr(inboundChannel); sourceAddress = StringUtils.substringBeforeLast(remoteAddr, CommonConstants.COLON); sourcePort = Integer.parseInt(StringUtils.substringAfterLast(remoteAddr, CommonConstants.COLON)); String localAddr = RemotingHelper.parseChannelLocalAddr(inboundChannel); destinationAddress = StringUtils.substringBeforeLast(localAddr, CommonConstants.COLON); destinationPort = Integer.parseInt(StringUtils.substringAfterLast(localAddr, CommonConstants.COLON)); } HAProxyProxiedProtocol proxiedProtocol = AclUtils.isColon(sourceAddress) ? HAProxyProxiedProtocol.TCP6 : HAProxyProxiedProtocol.TCP4; List haProxyTLVs = buildHAProxyTLV(inboundChannel); return new HAProxyMessage(HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol, sourceAddress, destinationAddress, sourcePort, destinationPort, haProxyTLVs); } protected List buildHAProxyTLV(Channel inboundChannel) throws IllegalAccessException, DecoderException { List result = new ArrayList<>(); if (!inboundChannel.hasAttr(AttributeKeys.PROXY_PROTOCOL_ADDR)) { return result; } Attribute[] attributes = (Attribute[]) FieldUtils.readField(FIELD_ATTRIBUTE, inboundChannel); if (ArrayUtils.isEmpty(attributes)) { return result; } for (Attribute attribute : attributes) { String attributeKey = attribute.key().name(); if (!StringUtils.startsWith(attributeKey, HAProxyConstants.PROXY_PROTOCOL_TLV_PREFIX)) { continue; } String attributeValue = (String) attribute.get(); HAProxyTLV haProxyTLV = buildHAProxyTLV(attributeKey, attributeValue); if (haProxyTLV != null) { result.add(haProxyTLV); } } return result; } protected HAProxyTLV buildHAProxyTLV(String attributeKey, String attributeValue) throws DecoderException { String typeString = StringUtils.substringAfter(attributeKey, HAProxyConstants.PROXY_PROTOCOL_TLV_PREFIX); ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeBytes(attributeValue.getBytes(Charset.defaultCharset())); return new HAProxyTLV(Hex.decodeHex(typeString)[0], byteBuf); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProtocolProxyHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.http2proxy; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.handler.codec.haproxy.HAProxyMessageEncoder; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import javax.net.ssl.SSLException; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.remoting.protocol.ProtocolHandler; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; public class Http2ProtocolProxyHandler implements ProtocolHandler { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final String LOCAL_HOST = "127.0.0.1"; /** * The int value of "PRI ". Now use 4 bytes to judge protocol, may be has potential risks if there is a new protocol * which start with "PRI " in the future *

    * The full HTTP/2 connection preface is "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" *

    * ref: https://datatracker.ietf.org/doc/html/rfc7540#section-3.5 */ private static final int PRI_INT = 0x50524920; private final SslContext sslContext; public Http2ProtocolProxyHandler() { try { TlsMode tlsMode = TlsSystemConfig.tlsMode; if (TlsMode.DISABLED.equals(tlsMode)) { sslContext = null; } else { sslContext = SslContextBuilder .forClient() .sslProvider(SslProvider.OPENSSL) .trustManager(InsecureTrustManagerFactory.INSTANCE) .applicationProtocolConfig(new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2)) .build(); } } catch (SSLException e) { log.error("Failed to create SslContext for Http2ProtocolProxyHandler", e); throw new RuntimeException("Failed to create SslContext for Http2ProtocolProxyHandler", e); } } @Override public boolean match(ByteBuf in) { if (!ConfigurationManager.getProxyConfig().isEnableRemotingLocalProxyGrpc()) { return false; } // If starts with 'PRI ' return in.getInt(in.readerIndex()) == PRI_INT; } @Override public void config(final ChannelHandlerContext ctx, final ByteBuf msg) { // proxy channel to http2 server final Channel inboundChannel = ctx.channel(); ProxyConfig config = ConfigurationManager.getProxyConfig(); // Start the connection attempt. Bootstrap b = new Bootstrap(); b.group(inboundChannel.eventLoop()) .channel(ctx.channel().getClass()) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(null, Http2ProxyBackendHandler.HANDLER_NAME, new Http2ProxyBackendHandler(inboundChannel)); } }) .option(ChannelOption.AUTO_READ, false) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getLocalProxyConnectTimeoutMs()); ChannelFuture f; try { f = b.connect(LOCAL_HOST, config.getGrpcServerPort()).sync(); } catch (Exception e) { log.error("connect http2 server failed. port:{}", config.getGrpcServerPort(), e); inboundChannel.close(); return; } final Channel outboundChannel = f.channel(); configPipeline(inboundChannel, outboundChannel); SslHandler sslHandler = null; if (sslContext != null) { sslHandler = sslContext.newHandler(outboundChannel.alloc(), LOCAL_HOST, config.getGrpcServerPort()); } ctx.pipeline().addLast(new Http2ProxyFrontendHandler(outboundChannel, sslHandler)); } protected void configPipeline(Channel inboundChannel, Channel outboundChannel) { inboundChannel.pipeline().addLast(new HAProxyMessageForwarder(outboundChannel)); outboundChannel.pipeline().addFirst(HAProxyMessageEncoder.INSTANCE); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyBackendHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.http2proxy; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class Http2ProxyBackendHandler extends ChannelInboundHandlerAdapter { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); public static final String HANDLER_NAME = "Http2ProxyBackendHandler"; private final Channel inboundChannel; public Http2ProxyBackendHandler(Channel inboundChannel) { this.inboundChannel = inboundChannel; } @Override public void channelActive(ChannelHandlerContext ctx) { ctx.read(); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) { inboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.isSuccess()) { ctx.channel().read(); } else { future.channel().close(); } } }); } @Override public void channelInactive(ChannelHandlerContext ctx) { Http2ProxyFrontendHandler.closeOnFlush(inboundChannel); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error("Http2ProxyBackendHandler#exceptionCaught", cause); Http2ProxyFrontendHandler.closeOnFlush(ctx.channel()); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyFrontendHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.http2proxy; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.ssl.SslHandler; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class Http2ProxyFrontendHandler extends ChannelInboundHandlerAdapter { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); public static final String HANDLER_NAME = "SslHandler"; // As we use inboundChannel.eventLoop() when building the Bootstrap this does not need to be volatile as // the outboundChannel will use the same EventLoop (and therefore Thread) as the inboundChannel. private final Channel outboundChannel; private final SslHandler sslHandler; public Http2ProxyFrontendHandler(final Channel outboundChannel, final SslHandler sslHandler) { this.outboundChannel = outboundChannel; this.sslHandler = sslHandler; } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) { if (outboundChannel.isActive()) { if (sslHandler != null && outboundChannel.pipeline().get(HANDLER_NAME) == null) { outboundChannel.pipeline().addBefore(Http2ProxyBackendHandler.HANDLER_NAME, HANDLER_NAME, sslHandler); } outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { // was able to flush out data, start to read the next chunk ctx.channel().read(); } else { future.channel().close(); } }); } } @Override public void channelInactive(ChannelHandlerContext ctx) { if (outboundChannel != null) { closeOnFlush(outboundChannel); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error("Http2ProxyFrontendHandler#exceptionCaught", cause); closeOnFlush(ctx.channel()); } /** * Closes the specified channel after all queued write requests are flushed. */ static void closeOnFlush(Channel ch) { if (ch.isActive()) { ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/remoting/RemotingProtocolHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.remoting; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import java.util.function.Supplier; import org.apache.rocketmq.proxy.remoting.protocol.ProtocolHandler; import org.apache.rocketmq.remoting.netty.NettyDecoder; import org.apache.rocketmq.remoting.netty.NettyEncoder; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.RemotingCodeDistributionHandler; public class RemotingProtocolHandler implements ProtocolHandler { private final Supplier encoderSupplier; private final Supplier remotingCodeDistributionHandlerSupplier; private final Supplier connectionManageHandlerSupplier; private final Supplier serverHandlerSupplier; public RemotingProtocolHandler(Supplier encoderSupplier, Supplier remotingCodeDistributionHandlerSupplier, Supplier connectionManageHandlerSupplier, Supplier serverHandlerSupplier) { this.encoderSupplier = encoderSupplier; this.remotingCodeDistributionHandlerSupplier = remotingCodeDistributionHandlerSupplier; this.connectionManageHandlerSupplier = connectionManageHandlerSupplier; this.serverHandlerSupplier = serverHandlerSupplier; } @Override public boolean match(ByteBuf in) { return true; } @Override public void config(ChannelHandlerContext ctx, ByteBuf msg) { ctx.pipeline().addLast( this.encoderSupplier.get(), new NettyDecoder(), this.remotingCodeDistributionHandlerSupplier.get(), this.connectionManageHandlerSupplier.get(), this.serverHandlerSupplier.get() ); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.client.ProducerChangeListener; import org.apache.rocketmq.broker.client.ProducerGroupEvent; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.client.common.NameserverAccessConfig; import org.apache.rocketmq.client.impl.mqclient.DoNothingClientRemotingProcessor; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.ObjectCreator; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.proxy.service.admin.DefaultAdminService; import org.apache.rocketmq.proxy.service.client.ClusterConsumerManager; import org.apache.rocketmq.proxy.service.client.ProxyClientRemotingProcessor; import org.apache.rocketmq.proxy.service.lite.LiteSubscriptionService; import org.apache.rocketmq.proxy.service.message.ClusterMessageService; import org.apache.rocketmq.proxy.service.message.MessageService; import org.apache.rocketmq.proxy.service.metadata.ClusterMetadataService; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.ClusterProxyRelayService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.ClusterTopicRouteService; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.proxy.service.transaction.ClusterTransactionService; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingClient; public class ClusterServiceManager extends AbstractStartAndShutdown implements ServiceManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected ClusterTransactionService clusterTransactionService; protected ProducerManager producerManager; protected ClusterConsumerManager consumerManager; protected TopicRouteService topicRouteService; protected MessageService messageService; protected ProxyRelayService proxyRelayService; protected ClusterMetadataService metadataService; protected AdminService adminService; protected LiteSubscriptionService liteSubscriptionService; protected ScheduledExecutorService scheduledExecutorService; protected MQClientAPIFactory messagingClientAPIFactory; protected MQClientAPIFactory operationClientAPIFactory; protected MQClientAPIFactory transactionClientAPIFactory; protected MQClientAPIFactory liteSubscriptionAPIFactory; public ClusterServiceManager(RPCHook rpcHook) { this(rpcHook, null); } public ClusterServiceManager(RPCHook rpcHook, ObjectCreator remotingClientCreator) { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); NameserverAccessConfig nameserverAccessConfig = new NameserverAccessConfig(proxyConfig.getNamesrvAddr(), proxyConfig.getNamesrvDomain(), proxyConfig.getNamesrvDomainSubgroup()); this.scheduledExecutorService = ThreadUtils.newScheduledThreadPool(3); this.messagingClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "ClusterMQClient_", proxyConfig.getRocketmqMQClientNum(), new DoNothingClientRemotingProcessor(null), rpcHook, scheduledExecutorService, remotingClientCreator ); this.operationClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "OperationClient_", 1, new DoNothingClientRemotingProcessor(null), rpcHook, this.scheduledExecutorService, remotingClientCreator ); this.topicRouteService = new ClusterTopicRouteService(operationClientAPIFactory); this.messageService = new ClusterMessageService(this.topicRouteService, this.messagingClientAPIFactory); this.metadataService = new ClusterMetadataService(topicRouteService, operationClientAPIFactory); this.adminService = new DefaultAdminService(this.operationClientAPIFactory); this.producerManager = new ProducerManager(); this.consumerManager = new ClusterConsumerManager(this.topicRouteService, this.adminService, this.operationClientAPIFactory, new ConsumerIdsChangeListenerImpl(), proxyConfig.getChannelExpiredTimeout(), rpcHook); this.transactionClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "ClusterTransaction_", 1, new ProxyClientRemotingProcessor(producerManager, consumerManager), rpcHook, scheduledExecutorService, remotingClientCreator ); this.clusterTransactionService = new ClusterTransactionService(this.topicRouteService, this.producerManager, this.transactionClientAPIFactory); this.proxyRelayService = new ClusterProxyRelayService(this.clusterTransactionService); // Lite subscriptions use a separate channel this.liteSubscriptionAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "LiteSubscription_", 1, new ProxyClientRemotingProcessor(producerManager, consumerManager), rpcHook, scheduledExecutorService); this.liteSubscriptionService = new LiteSubscriptionService(this.topicRouteService, this.liteSubscriptionAPIFactory); this.init(); } protected void init() { this.producerManager.appendProducerChangeListener(new ProducerChangeListenerImpl()); this.scheduledExecutorService.scheduleAtFixedRate(() -> { try { producerManager.scanNotActiveChannel(); consumerManager.scanNotActiveChannel(); } catch (Throwable e) { log.error("Error occurred when scan not active client channels.", e); } }, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS); this.appendShutdown(scheduledExecutorService::shutdown); this.appendStartAndShutdown(this.messagingClientAPIFactory); this.appendStartAndShutdown(this.operationClientAPIFactory); this.appendStartAndShutdown(this.transactionClientAPIFactory); this.appendStartAndShutdown(this.liteSubscriptionAPIFactory); this.appendStartAndShutdown(this.topicRouteService); this.appendStartAndShutdown(this.clusterTransactionService); this.appendStartAndShutdown(this.metadataService); this.appendStartAndShutdown(this.consumerManager); } @Override public MessageService getMessageService() { return this.messageService; } @Override public TopicRouteService getTopicRouteService() { return topicRouteService; } @Override public ProducerManager getProducerManager() { return this.producerManager; } @Override public ConsumerManager getConsumerManager() { return this.consumerManager; } @Override public TransactionService getTransactionService() { return this.clusterTransactionService; } @Override public ProxyRelayService getProxyRelayService() { return this.proxyRelayService; } @Override public MetadataService getMetadataService() { return this.metadataService; } @Override public AdminService getAdminService() { return this.adminService; } @Override public LiteSubscriptionService getLiteSubscriptionService() { return liteSubscriptionService; } protected static class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener { @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { } @Override public void shutdown() { } } protected class ProducerChangeListenerImpl implements ProducerChangeListener { @Override public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) { if (event == ProducerGroupEvent.GROUP_UNREGISTER) { getTransactionService().unSubscribeAllTransactionTopic(ProxyContext.createForInner(this.getClass()), group); } } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.client.common.NameserverAccessConfig; import org.apache.rocketmq.client.impl.mqclient.DoNothingClientRemotingProcessor; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.proxy.service.admin.DefaultAdminService; import org.apache.rocketmq.proxy.service.channel.ChannelManager; import org.apache.rocketmq.proxy.service.lite.LiteSubscriptionService; import org.apache.rocketmq.proxy.service.message.LocalMessageService; import org.apache.rocketmq.proxy.service.message.MessageService; import org.apache.rocketmq.proxy.service.metadata.LocalMetadataService; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.LocalProxyRelayService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.LocalTopicRouteService; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.proxy.service.transaction.LocalTransactionService; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.RPCHook; public class LocalServiceManager extends AbstractStartAndShutdown implements ServiceManager { private final BrokerController brokerController; private final TopicRouteService topicRouteService; private final MessageService messageService; private final TransactionService transactionService; private final ProxyRelayService proxyRelayService; private final MetadataService metadataService; private final AdminService adminService; private final MQClientAPIFactory mqClientAPIFactory; private final ChannelManager channelManager; private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryImpl("LocalServiceManagerScheduledThread")); public LocalServiceManager(BrokerController brokerController, RPCHook rpcHook) { this.brokerController = brokerController; this.channelManager = new ChannelManager(); this.messageService = new LocalMessageService(brokerController, channelManager, rpcHook); ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); NameserverAccessConfig nameserverAccessConfig = new NameserverAccessConfig(proxyConfig.getNamesrvAddr(), proxyConfig.getNamesrvDomain(), proxyConfig.getNamesrvDomainSubgroup()); this.mqClientAPIFactory = new MQClientAPIFactory( nameserverAccessConfig, "LocalMQClient_", 1, new DoNothingClientRemotingProcessor(null), rpcHook, scheduledExecutorService ); this.topicRouteService = new LocalTopicRouteService(brokerController, mqClientAPIFactory); this.transactionService = new LocalTransactionService(brokerController.getBrokerConfig()); this.proxyRelayService = new LocalProxyRelayService(brokerController, this.transactionService); this.metadataService = new LocalMetadataService(brokerController); this.adminService = new DefaultAdminService(this.mqClientAPIFactory); this.init(); } protected void init() { this.appendStartAndShutdown(this.mqClientAPIFactory); this.appendStartAndShutdown(this.topicRouteService); this.appendStartAndShutdown(new LocalServiceManagerStartAndShutdown()); } @Override public MessageService getMessageService() { return this.messageService; } @Override public TopicRouteService getTopicRouteService() { return this.topicRouteService; } @Override public ProducerManager getProducerManager() { return this.brokerController.getProducerManager(); } @Override public ConsumerManager getConsumerManager() { return this.brokerController.getConsumerManager(); } @Override public TransactionService getTransactionService() { return this.transactionService; } @Override public ProxyRelayService getProxyRelayService() { return this.proxyRelayService; } @Override public MetadataService getMetadataService() { return this.metadataService; } @Override public AdminService getAdminService() { return this.adminService; } @Override public LiteSubscriptionService getLiteSubscriptionService() { return null; } private class LocalServiceManagerStartAndShutdown implements StartAndShutdown { @Override public void start() throws Exception { LocalServiceManager.this.scheduledExecutorService.scheduleWithFixedDelay(channelManager::scanAndCleanChannels, 5, 5, TimeUnit.MINUTES); } @Override public void shutdown() throws Exception { LocalServiceManager.this.scheduledExecutorService.shutdown(); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.proxy.service.lite.LiteSubscriptionService; import org.apache.rocketmq.proxy.service.message.MessageService; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.proxy.service.transaction.TransactionService; public interface ServiceManager extends StartAndShutdown { MessageService getMessageService(); TopicRouteService getTopicRouteService(); ProducerManager getProducerManager(); ConsumerManager getConsumerManager(); TransactionService getTransactionService(); ProxyRelayService getProxyRelayService(); MetadataService getMetadataService(); AdminService getAdminService(); LiteSubscriptionService getLiteSubscriptionService(); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManagerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ObjectCreator; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RemotingClient; public class ServiceManagerFactory { public static ServiceManager createForLocalMode(BrokerController brokerController) { return createForLocalMode(brokerController, null); } public static ServiceManager createForLocalMode(BrokerController brokerController, RPCHook rpcHook) { return new LocalServiceManager(brokerController, rpcHook); } public static ServiceManager createForClusterMode() { return createForClusterMode(null, null); } public static ServiceManager createForClusterMode(RPCHook rpcHook) { return createForClusterMode(rpcHook, null); } public static ServiceManager createForClusterMode(RPCHook rpcHook, ObjectCreator remotingClientCreator) { return new ClusterServiceManager(rpcHook, remotingClientCreator); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/AdminService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.admin; import java.util.List; import org.apache.rocketmq.remoting.protocol.route.BrokerData; public interface AdminService { boolean topicExist(String topic); boolean createTopicOnTopicBrokerIfNotExist(String createTopic, String sampleTopic, int wQueueNum, int rQueueNum, boolean examineTopic, int retryCheckCount); boolean createTopicOnBroker(String topic, int wQueueNum, int rQueueNum, List curBrokerDataList, List sampleBrokerDataList, boolean examineTopic, int retryCheckCount) throws Exception; } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.admin; import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.proxy.service.route.TopicRouteHelper; public class DefaultAdminService implements AdminService { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final MQClientAPIFactory mqClientAPIFactory; public DefaultAdminService(MQClientAPIFactory mqClientAPIFactory) { this.mqClientAPIFactory = mqClientAPIFactory; } @Override public boolean topicExist(String topic) { boolean topicExist; TopicRouteData topicRouteData; try { topicRouteData = this.getTopicRouteDataDirectlyFromNameServer(topic); topicExist = topicRouteData != null; } catch (Throwable e) { topicExist = false; } return topicExist; } @Override public boolean createTopicOnTopicBrokerIfNotExist(String createTopic, String sampleTopic, int wQueueNum, int rQueueNum, boolean examineTopic, int retryCheckCount) { TopicRouteData curTopicRouteData = new TopicRouteData(); try { curTopicRouteData = this.getTopicRouteDataDirectlyFromNameServer(createTopic); } catch (Exception e) { if (!TopicRouteHelper.isTopicNotExistError(e)) { log.error("get cur topic route {} failed.", createTopic, e); return false; } } TopicRouteData sampleTopicRouteData = null; try { sampleTopicRouteData = this.getTopicRouteDataDirectlyFromNameServer(sampleTopic); } catch (Exception e) { log.error("create topic {} failed.", createTopic, e); return false; } if (sampleTopicRouteData == null || sampleTopicRouteData.getBrokerDatas().isEmpty()) { return false; } try { return this.createTopicOnBroker(createTopic, wQueueNum, rQueueNum, curTopicRouteData.getBrokerDatas(), sampleTopicRouteData.getBrokerDatas(), examineTopic, retryCheckCount); } catch (Exception e) { log.error("create topic {} failed.", createTopic, e); } return false; } @Override public boolean createTopicOnBroker(String topic, int wQueueNum, int rQueueNum, List curBrokerDataList, List sampleBrokerDataList, boolean examineTopic, int retryCheckCount) throws Exception { Set curBrokerAddr = new HashSet<>(); if (curBrokerDataList != null) { for (BrokerData brokerData : curBrokerDataList) { curBrokerAddr.add(brokerData.getBrokerAddrs().get(MixAll.MASTER_ID)); } } TopicConfig topicConfig = new TopicConfig(); topicConfig.setTopicName(topic); topicConfig.setWriteQueueNums(wQueueNum); topicConfig.setReadQueueNums(rQueueNum); topicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); for (BrokerData brokerData : sampleBrokerDataList) { String addr = brokerData.getBrokerAddrs() == null ? null : brokerData.getBrokerAddrs().get(MixAll.MASTER_ID); if (addr == null) { continue; } if (curBrokerAddr.contains(addr)) { continue; } try { this.getClient().createTopic(addr, TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC, topicConfig, Duration.ofSeconds(3).toMillis()); } catch (Exception e) { log.error("create topic on broker failed. topic:{}, broker:{}", topicConfig, addr, e); } } if (examineTopic) { // examine topic exist. int count = retryCheckCount; while (count-- > 0) { if (this.topicExist(topic)) { return true; } } } else { return true; } return false; } protected TopicRouteData getTopicRouteDataDirectlyFromNameServer(String topic) throws Exception { return this.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); } protected MQClientAPIExt getClient() { return this.mqClientAPIFactory.getClient(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/cert/TlsCertificateManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.cert; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import org.apache.rocketmq.srvutil.FileWatchService; import java.util.ArrayList; import java.util.List; public class TlsCertificateManager implements StartAndShutdown { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final FileWatchService fileWatchService; private final List reloadListeners = new ArrayList<>(); public TlsCertificateManager() { try { this.fileWatchService = new FileWatchService( new String[] { ConfigurationManager.getProxyConfig().getTlsCertPath(), ConfigurationManager.getProxyConfig().getTlsKeyPath() }, new CertKeyFileWatchListener(), ConfigurationManager.getProxyConfig().getTlsCertWatchIntervalMs() ); } catch (Exception e) { log.error("Failed to initialize TLS certificate watch service", e); throw new RuntimeException("Failed to initialize TLS certificate manager", e); } } public FileWatchService getFileWatchService() { return this.fileWatchService; } public void registerReloadListener(TlsContextReloadListener listener) { if (listener != null) { this.reloadListeners.add(listener); } } public void unregisterReloadListener(TlsContextReloadListener listener) { if (listener != null) { this.reloadListeners.remove(listener); } } public List getReloadListeners() { return this.reloadListeners; } @Override public void start() throws Exception { this.fileWatchService.start(); log.info("TLS certificate manager started successfully, start watching: {} {}", ConfigurationManager.getProxyConfig().getTlsCertPath(), ConfigurationManager.getProxyConfig().getTlsKeyPath() ); } @Override public void shutdown() throws Exception { this.fileWatchService.shutdown(); log.info("TLS certificate manager shutdown successfully"); } private class CertKeyFileWatchListener implements FileWatchService.Listener { private boolean certChanged = false; private boolean keyChanged = false; @Override public void onChanged(String path) { log.info("File changed: {}", path); if (path.equals(TlsSystemConfig.tlsServerCertPath)) { certChanged = true; } else if (path.equals(TlsSystemConfig.tlsServerKeyPath)) { keyChanged = true; } if (certChanged && keyChanged) { log.info("The certificate and private key changed, reload the ssl context"); notifyContextReload(); certChanged = false; keyChanged = false; } } private void notifyContextReload() { for (TlsContextReloadListener listener : reloadListeners) { try { listener.onTlsContextReload(); } catch (Throwable e) { log.error("Failed to notify TLS context reload to listener: " + listener, e); } } } } // Interface for listeners interested in TLS context reload events public interface TlsContextReloadListener { void onTlsContextReload(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/ChannelManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.channel; import com.google.common.base.Strings; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class ChannelManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final ConcurrentMap clientIdChannelMap = new ConcurrentHashMap<>(); public SimpleChannel createChannel(ProxyContext context) { final String clientId = anonymousChannelId(context); if (Strings.isNullOrEmpty(clientId)) { log.warn("ClientId is unexpected null or empty"); return createChannelInner(context); } SimpleChannel channel = ConcurrentHashMapUtils.computeIfAbsent(this.clientIdChannelMap,clientId, k -> createChannelInner(context)); channel.updateLastAccessTime(); return channel; } public SimpleChannel createInvocationChannel(ProxyContext context) { final String clientId = anonymousChannelId(InvocationChannel.class.getName(), context); final String clientHost = context.getRemoteAddress(); final String localAddress = context.getLocalAddress(); if (Strings.isNullOrEmpty(clientId)) { log.warn("ClientId is unexpected null or empty"); return new InvocationChannel(clientHost, localAddress); } SimpleChannel channel = clientIdChannelMap.computeIfAbsent(clientId, k -> new InvocationChannel(clientHost, localAddress)); channel.updateLastAccessTime(); return channel; } private String anonymousChannelId(ProxyContext context) { final String clientHost = context.getRemoteAddress(); final String localAddress = context.getLocalAddress(); return clientHost + "@" + localAddress; } private String anonymousChannelId(String key, ProxyContext context) { final String clientHost = context.getRemoteAddress(); final String localAddress = context.getLocalAddress(); return key + "@" + clientHost + "@" + localAddress; } private SimpleChannel createChannelInner(ProxyContext context) { return new SimpleChannel(context.getRemoteAddress(), context.getLocalAddress()); } public void scanAndCleanChannels() { try { Iterator> iterator = clientIdChannelMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (!entry.getValue().isActive()) { iterator.remove(); } else { entry.getValue().clearExpireContext(); } } } catch (Throwable e) { log.error("Unexpected exception", e); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.channel; import io.netty.channel.ChannelFuture; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class InvocationChannel extends SimpleChannel { protected final ConcurrentMap inFlightRequestMap; public InvocationChannel(String remoteAddress, String localAddress) { super(remoteAddress, localAddress); this.inFlightRequestMap = new ConcurrentHashMap<>(); } @Override public ChannelFuture writeAndFlush(Object msg) { if (msg instanceof RemotingCommand) { RemotingCommand responseCommand = (RemotingCommand) msg; InvocationContextInterface context = inFlightRequestMap.remove(responseCommand.getOpaque()); if (null != context) { context.handle(responseCommand); } inFlightRequestMap.remove(responseCommand.getOpaque()); } return super.writeAndFlush(msg); } @Override public boolean isWritable() { return inFlightRequestMap.size() > 0; } @Override public void registerInvocationContext(int opaque, InvocationContextInterface context) { inFlightRequestMap.put(opaque, context); } @Override public void eraseInvocationContext(int opaque) { inFlightRequestMap.remove(opaque); } @Override public void clearExpireContext() { Iterator> iterator = inFlightRequestMap.entrySet().iterator(); int count = 0; while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (entry.getValue().expired(ConfigurationManager.getProxyConfig().getChannelExpiredInSeconds())) { iterator.remove(); count++; log.debug("An expired request is found, request: {}", entry.getValue()); } } if (count > 0) { log.warn("[BUG] {} expired in-flight requests is cleaned.", count); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.channel; import java.time.Duration; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class InvocationContext implements InvocationContextInterface { private final CompletableFuture response; private final long timestamp = System.currentTimeMillis(); public InvocationContext(CompletableFuture resp) { this.response = resp; } public boolean expired(long expiredTimeSec) { return System.currentTimeMillis() - timestamp >= Duration.ofSeconds(expiredTimeSec).toMillis(); } public CompletableFuture getResponse() { return response; } public void handle(RemotingCommand remotingCommand) { response.complete(remotingCommand); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContextInterface.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.channel; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface InvocationContextInterface { void handle(RemotingCommand remotingCommand); boolean expired(long expiredTimeSec); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.channel; import com.google.common.base.Strings; import io.netty.channel.AbstractChannel; import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelId; import io.netty.channel.ChannelMetadata; import io.netty.channel.ChannelOutboundBuffer; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.EventLoop; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; import java.net.SocketAddress; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * SimpleChannel is used to handle writeAndFlush situation in processor * * @see io.netty.channel.ChannelHandlerContext#writeAndFlush * @see io.netty.channel.Channel#writeAndFlush */ public class SimpleChannel extends AbstractChannel { protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final String remoteAddress; protected final String localAddress; protected long lastAccessTime; protected ChannelHandlerContext channelHandlerContext; /** * Creates a new instance. * * @param parent the parent of this channel. {@code null} if there's no parent. * @param remoteAddress Remote address * @param localAddress Local address */ public SimpleChannel(Channel parent, String remoteAddress, String localAddress) { this(parent, null, remoteAddress, localAddress); } public SimpleChannel(Channel parent, ChannelId id, String remoteAddress, String localAddress) { super(parent, id); lastAccessTime = System.currentTimeMillis(); this.remoteAddress = remoteAddress; this.localAddress = localAddress; this.channelHandlerContext = new SimpleChannelHandlerContext(this); } public SimpleChannel(String remoteAddress, String localAddress) { this(null, remoteAddress, localAddress); } @Override protected AbstractUnsafe newUnsafe() { return null; } @Override protected boolean isCompatible(EventLoop loop) { return false; } private static SocketAddress parseSocketAddress(String address) { if (Strings.isNullOrEmpty(address)) { return null; } String[] segments = address.split(":"); if (2 == segments.length) { return new InetSocketAddress(segments[0], Integer.parseInt(segments[1])); } return null; } @Override protected SocketAddress localAddress0() { return parseSocketAddress(localAddress); } @Override public SocketAddress localAddress() { return localAddress0(); } @Override public SocketAddress remoteAddress() { return remoteAddress0(); } @Override protected SocketAddress remoteAddress0() { return parseSocketAddress(remoteAddress); } @Override protected void doBind(SocketAddress localAddress) throws Exception { } @Override protected void doDisconnect() throws Exception { } @Override public ChannelFuture close() { DefaultChannelPromise promise = new DefaultChannelPromise(this, GlobalEventExecutor.INSTANCE); promise.setSuccess(); return promise; } @Override protected void doClose() throws Exception { } @Override protected void doBeginRead() throws Exception { } @Override protected void doWrite(ChannelOutboundBuffer in) throws Exception { } @Override public ChannelConfig config() { return null; } @Override public boolean isOpen() { return true; } @Override public boolean isActive() { return (System.currentTimeMillis() - lastAccessTime) <= 120L * 1000; } @Override public ChannelMetadata metadata() { return null; } @Override public ChannelFuture writeAndFlush(Object msg) { DefaultChannelPromise promise = new DefaultChannelPromise(this, GlobalEventExecutor.INSTANCE); promise.setSuccess(); return promise; } @Override public boolean isWritable() { return true; } public void updateLastAccessTime() { this.lastAccessTime = System.currentTimeMillis(); } public void registerInvocationContext(int opaque, InvocationContextInterface context) { } public void eraseInvocationContext(int opaque) { } public void clearExpireContext() { } public String getRemoteAddress() { return remoteAddress; } public String getLocalAddress() { return localAddress; } public ChannelHandlerContext getChannelHandlerContext() { return channelHandlerContext; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannelHandlerContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.channel; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelProgressivePromise; import io.netty.channel.ChannelPromise; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import io.netty.util.concurrent.EventExecutor; import java.net.SocketAddress; import org.apache.commons.lang3.NotImplementedException; public class SimpleChannelHandlerContext implements ChannelHandlerContext { private final Channel channel; public SimpleChannelHandlerContext(Channel channel) { this.channel = channel; } @Override public Channel channel() { return channel; } @Override public EventExecutor executor() { throw new NotImplementedException("Not implemented"); } @Override public String name() { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandler handler() { throw new NotImplementedException("Not implemented"); } @Override public boolean isRemoved() { return false; } @Override public ChannelHandlerContext fireChannelRegistered() { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireChannelUnregistered() { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireChannelActive() { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireChannelInactive() { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireExceptionCaught(Throwable cause) { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireUserEventTriggered(Object evt) { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireChannelRead(Object msg) { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireChannelReadComplete() { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext fireChannelWritabilityChanged() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture bind(SocketAddress localAddress) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture connect(SocketAddress remoteAddress) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture disconnect() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture close() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture deregister() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture disconnect(ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture close(ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture deregister(ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext read() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture write(Object msg) { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture write(Object msg, ChannelPromise promise) { throw new NotImplementedException("Not implemented"); } @Override public ChannelHandlerContext flush() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { return channel.writeAndFlush(msg, promise); } @Override public ChannelFuture writeAndFlush(Object msg) { return channel.writeAndFlush(msg); } @Override public ChannelPipeline pipeline() { throw new NotImplementedException("Not implemented"); } @Override public ByteBufAllocator alloc() { throw new NotImplementedException("Not implemented"); } @Override public ChannelPromise newPromise() { throw new NotImplementedException("Not implemented"); } @Override public ChannelProgressivePromise newProgressivePromise() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture newSucceededFuture() { throw new NotImplementedException("Not implemented"); } @Override public ChannelFuture newFailedFuture(Throwable cause) { throw new NotImplementedException("Not implemented"); } @Override public ChannelPromise voidPromise() { throw new NotImplementedException("Not implemented"); } @Override public Attribute attr(AttributeKey key) { throw new NotImplementedException("Not implemented"); } @Override public boolean hasAttr(AttributeKey attributeKey) { return false; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ClusterConsumerManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.client; import java.util.Set; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.proxy.service.sysmessage.HeartbeatSyncer; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ClusterConsumerManager extends ConsumerManager implements StartAndShutdown { protected HeartbeatSyncer heartbeatSyncer; public ClusterConsumerManager(TopicRouteService topicRouteService, AdminService adminService, MQClientAPIFactory mqClientAPIFactory, ConsumerIdsChangeListener consumerIdsChangeListener, long channelExpiredTimeout, RPCHook rpcHook) { super(consumerIdsChangeListener, channelExpiredTimeout); this.heartbeatSyncer = new HeartbeatSyncer(topicRouteService, adminService, this, mqClientAPIFactory, rpcHook); } @Override public boolean registerConsumer(String group, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set subList, boolean isNotifyConsumerIdsChangedEnable, boolean updateSubscription) { this.heartbeatSyncer.onConsumerRegister(group, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList); return super.registerConsumer(group, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList, isNotifyConsumerIdsChangedEnable, updateSubscription); } @Override public void unregisterConsumer(String group, ClientChannelInfo clientChannelInfo, boolean isNotifyConsumerIdsChangedEnable) { this.heartbeatSyncer.onConsumerUnRegister(group, clientChannelInfo); super.unregisterConsumer(group, clientChannelInfo, isNotifyConsumerIdsChangedEnable); } @Override public void shutdown() throws Exception { this.heartbeatSyncer.shutdown(); } @Override public void start() throws Exception { this.heartbeatSyncer.start(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ProxyClientRemotingProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.client; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import java.nio.ByteBuffer; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.utils.ProxyUtils; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; public class ProxyClientRemotingProcessor extends ClientRemotingProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final ProducerManager producerManager; private final ClusterConsumerManager consumerManager; public ProxyClientRemotingProcessor(ProducerManager producerManager, ClusterConsumerManager consumerManager) { super(null); this.producerManager = producerManager; this.consumerManager = consumerManager; } @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { if (request.getCode() == RequestCode.CHECK_TRANSACTION_STATE) { return this.checkTransactionState(ctx, request); } else if (request.getCode() == RequestCode.NOTIFY_UNSUBSCRIBE_LITE) { return this.notifyUnsubscribeLite(ctx, request); } return null; } @Override public RemotingCommand checkTransactionState(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ByteBuffer byteBuffer = ByteBuffer.wrap(request.getBody()); final MessageExt messageExt = MessageDecoder.decode(byteBuffer, true, false, false); if (messageExt != null) { final String group = messageExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); if (group != null) { CheckTransactionStateRequestHeader requestHeader = (CheckTransactionStateRequestHeader) request.decodeCommandCustomHeader(CheckTransactionStateRequestHeader.class); request.writeCustomHeader(requestHeader); request.addExtField(ProxyUtils.BROKER_ADDR, NetworkUtil.socketAddress2String(ctx.channel().remoteAddress())); Channel channel = this.producerManager.getAvailableChannel(group); if (channel != null) { channel.writeAndFlush(request); } else { log.warn("check transaction failed, channel is empty. groupId={}, requestHeader:{}", group, requestHeader); } } } return null; } /** * one way, return null response */ public RemotingCommand notifyUnsubscribeLite(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { NotifyUnsubscribeLiteRequestHeader requestHeader = request.decodeCommandCustomHeader(NotifyUnsubscribeLiteRequestHeader.class); request.writeCustomHeader(requestHeader); final String clientId = requestHeader.getClientId(); final String group = requestHeader.getConsumerGroup(); if (StringUtils.isBlank(clientId) || StringUtils.isBlank(group)) { log.warn("notifyUnsubscribeLite clientId or group is null. {}", requestHeader); return null; } ClientChannelInfo channelInfo = consumerManager.findChannel(group, clientId); if (channelInfo == null) { log.warn("notifyUnsubscribeLite channelInfo is null. {}", requestHeader); return null; } Channel channel = channelInfo.getChannel(); if (channel == null) { log.warn("notifyUnsubscribeLite channel is null. {}", requestHeader); return null; } channel.writeAndFlush(request); return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/lite/LiteSubscriptionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.lite; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.proxy.service.route.TopicRouteService; public class LiteSubscriptionService { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final TopicRouteService topicRouteService; protected final MQClientAPIFactory mqClientAPIFactory; public LiteSubscriptionService(TopicRouteService topicRouteService, MQClientAPIFactory mqClientAPIFactory) { this.topicRouteService = topicRouteService; this.mqClientAPIFactory = mqClientAPIFactory; } public CompletableFuture syncLiteSubscription(ProxyContext ctx, LiteSubscriptionDTO liteSubscriptionDTO, long timeoutMillis) { final String topic = liteSubscriptionDTO.getTopic(); List readQueues; try { MessageQueueView messageQueueView = topicRouteService.getAllMessageQueueView(ctx, topic); // Send subscriptions to all readable brokers. readQueues = messageQueueView.getReadSelector().getBrokerActingQueues(); } catch (Exception e) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(e); return future; } return CompletableFuture.allOf( readQueues .stream() .map(writeQ -> mqClientAPIFactory.getClient().syncLiteSubscriptionAsync( writeQ.getBrokerAddr(), liteSubscriptionDTO, timeoutMillis )) .toArray(CompletableFuture[]::new) ); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import com.google.common.collect.Lists; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; public class ClusterMessageService implements MessageService { protected final TopicRouteService topicRouteService; protected final MQClientAPIFactory mqClientAPIFactory; public ClusterMessageService(TopicRouteService topicRouteService, MQClientAPIFactory mqClientAPIFactory) { this.topicRouteService = topicRouteService; this.mqClientAPIFactory = mqClientAPIFactory; } @Override public CompletableFuture> sendMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, List msgList, SendMessageRequestHeader requestHeader, long timeoutMillis) { CompletableFuture> future; if (msgList.size() == 1) { future = this.mqClientAPIFactory.getClient().sendMessageAsync( messageQueue.getBrokerAddr(), messageQueue.getBrokerName(), msgList.get(0), requestHeader, timeoutMillis) .thenApply(Lists::newArrayList); } else { future = this.mqClientAPIFactory.getClient().sendMessageAsync( messageQueue.getBrokerAddr(), messageQueue.getBrokerName(), msgList, requestHeader, timeoutMillis) .thenApply(Lists::newArrayList); } return future; } @Override public CompletableFuture sendMessageBack(ProxyContext ctx, ReceiptHandle handle, String messageId, ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().sendMessageBackAsync( this.resolveBrokerAddrInReceiptHandle(ctx, handle), requestHeader, timeoutMillis ); } @Override public CompletableFuture endTransactionOneway(ProxyContext ctx, String brokerName, EndTransactionRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { this.mqClientAPIFactory.getClient().endTransactionOneway( this.resolveBrokerAddr(ctx, brokerName), requestHeader, "end transaction from proxy", timeoutMillis ); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } return future; } @Override public CompletableFuture popMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, PopMessageRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().popMessageAsync( messageQueue.getBrokerAddr(), messageQueue.getBrokerName(), requestHeader, timeoutMillis ); } @Override public CompletableFuture popLiteMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, PopLiteMessageRequestHeader requestHeader, long timeoutMillis ) { return this.mqClientAPIFactory.getClient().popLiteMessageAsync( messageQueue.getBrokerAddr(), messageQueue.getBrokerName(), requestHeader, timeoutMillis ); } @Override public CompletableFuture changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId, ChangeInvisibleTimeRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().changeInvisibleTimeAsync( this.resolveBrokerAddrInReceiptHandle(ctx, handle), handle.getBrokerName(), requestHeader, timeoutMillis ); } @Override public CompletableFuture ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId, AckMessageRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().ackMessageAsync( this.resolveBrokerAddrInReceiptHandle(ctx, handle), requestHeader, timeoutMillis ); } @Override public CompletableFuture batchAckMessage(ProxyContext ctx, List handleList, String consumerGroup, String topic, long timeoutMillis) { List extraInfoList = handleList.stream().map(message -> message.getReceiptHandle().getReceiptHandle()).collect(Collectors.toList()); return this.mqClientAPIFactory.getClient().batchAckMessageAsync( this.resolveBrokerAddrInReceiptHandle(ctx, handleList.get(0).getReceiptHandle()), topic, consumerGroup, extraInfoList, timeoutMillis ); } @Override public CompletableFuture pullMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, PullMessageRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().pullMessageAsync( messageQueue.getBrokerAddr(), requestHeader, timeoutMillis ); } @Override public CompletableFuture queryConsumerOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, QueryConsumerOffsetRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().queryConsumerOffsetWithFuture( messageQueue.getBrokerAddr(), requestHeader, timeoutMillis ); } @Override public CompletableFuture updateConsumerOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, UpdateConsumerOffsetRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().updateConsumerOffsetOneWay( messageQueue.getBrokerAddr(), requestHeader, timeoutMillis ); } @Override public CompletableFuture updateConsumerOffsetAsync(ProxyContext ctx, AddressableMessageQueue messageQueue, UpdateConsumerOffsetRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().updateConsumerOffsetAsync( messageQueue.getBrokerAddr(), requestHeader, timeoutMillis ); } @Override public CompletableFuture> lockBatchMQ(ProxyContext ctx, AddressableMessageQueue messageQueue, LockBatchRequestBody requestBody, long timeoutMillis) { return this.mqClientAPIFactory.getClient().lockBatchMQWithFuture( messageQueue.getBrokerAddr(), requestBody, timeoutMillis ); } @Override public CompletableFuture unlockBatchMQ(ProxyContext ctx, AddressableMessageQueue messageQueue, UnlockBatchRequestBody requestBody, long timeoutMillis) { return this.mqClientAPIFactory.getClient().unlockBatchMQOneway( messageQueue.getBrokerAddr(), requestBody, timeoutMillis ); } @Override public CompletableFuture getMaxOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, GetMaxOffsetRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().getMaxOffset( messageQueue.getBrokerAddr(), requestHeader, timeoutMillis ); } @Override public CompletableFuture getMinOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, GetMinOffsetRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().getMinOffset( messageQueue.getBrokerAddr(), requestHeader, timeoutMillis ); } @Override public CompletableFuture recallMessage(ProxyContext ctx, String brokerName, RecallMessageRequestHeader requestHeader, long timeoutMillis) { return this.mqClientAPIFactory.getClient().recallMessageAsync( this.resolveBrokerAddr(ctx, brokerName), requestHeader, timeoutMillis ); } @Override public CompletableFuture request(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { try { String brokerAddress = topicRouteService.getBrokerAddr(ctx, brokerName); return mqClientAPIFactory.getClient().invoke(brokerAddress, request, timeoutMillis); } catch (Throwable t) { return FutureUtils.completeExceptionally(t); } } @Override public CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { try { String brokerAddress = topicRouteService.getBrokerAddr(ctx, brokerName); return mqClientAPIFactory.getClient().invokeOneway(brokerAddress, request, timeoutMillis); } catch (Throwable t) { return FutureUtils.completeExceptionally(t); } } protected String resolveBrokerAddrInReceiptHandle(ProxyContext ctx, ReceiptHandle handle) { try { return this.topicRouteService.getBrokerAddr(ctx, handle.getBrokerName()); } catch (Throwable t) { throw new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "cannot find broker " + handle.getBrokerName(), t); } } protected String resolveBrokerAddr(ProxyContext ctx, String brokerName) { try { return this.topicRouteService.getBrokerAddr(ctx, brokerName); } catch (Throwable t) { throw new ProxyException(ProxyExceptionCode.INVALID_BROKER_NAME, "cannot find broker " + brokerName, t); } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import io.netty.channel.ChannelHandlerContext; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.NotImplementedException; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.service.channel.ChannelManager; import org.apache.rocketmq.proxy.service.channel.InvocationContext; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.BatchAck; import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class LocalMessageService implements MessageService { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final BrokerController brokerController; private final ChannelManager channelManager; public LocalMessageService(BrokerController brokerController, ChannelManager channelManager, RPCHook rpcHook) { this.brokerController = brokerController; this.channelManager = channelManager; } @Override public CompletableFuture> sendMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, List msgList, SendMessageRequestHeader requestHeader, long timeoutMillis) { byte[] body; String messageId; if (msgList.size() > 1) { requestHeader.setBatch(true); MessageBatch msgBatch = MessageBatch.generateFromList(msgList); MessageClientIDSetter.setUniqID(msgBatch); body = msgBatch.encode(); msgBatch.setBody(body); messageId = MessageClientIDSetter.getUniqID(msgBatch); } else { Message message = msgList.get(0); body = message.getBody(); messageId = MessageClientIDSetter.getUniqID(message); } RemotingCommand request = LocalRemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader, ctx.getLanguage()); request.setBody(body); CompletableFuture future = new CompletableFuture<>(); SimpleChannel channel = channelManager.createInvocationChannel(ctx); InvocationContext invocationContext = new InvocationContext(future); channel.registerInvocationContext(request.getOpaque(), invocationContext); ChannelHandlerContext simpleChannelHandlerContext = channel.getChannelHandlerContext(); try { RemotingCommand response = brokerController.getSendMessageProcessor().processRequest(simpleChannelHandlerContext, request); if (response != null) { invocationContext.handle(response); channel.eraseInvocationContext(request.getOpaque()); } } catch (Exception e) { future.completeExceptionally(e); channel.eraseInvocationContext(request.getOpaque()); log.error("Failed to process sendMessage command", e); } return future.thenApply(r -> { SendResult sendResult = new SendResult(); SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) r.readCustomHeader(); SendStatus sendStatus; switch (r.getCode()) { case ResponseCode.FLUSH_DISK_TIMEOUT: { sendStatus = SendStatus.FLUSH_DISK_TIMEOUT; break; } case ResponseCode.FLUSH_SLAVE_TIMEOUT: { sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT; break; } case ResponseCode.SLAVE_NOT_AVAILABLE: { sendStatus = SendStatus.SLAVE_NOT_AVAILABLE; break; } case ResponseCode.SUCCESS: { sendStatus = SendStatus.SEND_OK; break; } default: { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, r.getRemark()); } } sendResult.setSendStatus(sendStatus); sendResult.setMsgId(messageId); sendResult.setMessageQueue(new MessageQueue(requestHeader.getTopic(), brokerController.getBrokerConfig().getBrokerName(), requestHeader.getQueueId())); sendResult.setQueueOffset(responseHeader.getQueueOffset()); sendResult.setTransactionId(responseHeader.getTransactionId()); sendResult.setOffsetMsgId(responseHeader.getMsgId()); sendResult.setRecallHandle(responseHeader.getRecallHandle()); return Collections.singletonList(sendResult); }); } @Override public CompletableFuture sendMessageBack(ProxyContext ctx, ReceiptHandle handle, String messageId, ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis) { SimpleChannel channel = channelManager.createChannel(ctx); ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext(); RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, requestHeader, ctx.getLanguage()); CompletableFuture future = new CompletableFuture<>(); try { RemotingCommand response = brokerController.getSendMessageProcessor() .processRequest(channelHandlerContext, command); future.complete(response); } catch (Exception e) { log.error("Fail to process sendMessageBack command", e); future.completeExceptionally(e); } return future; } @Override public CompletableFuture endTransactionOneway(ProxyContext ctx, String brokerName, EndTransactionRequestHeader requestHeader, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); SimpleChannel channel = channelManager.createChannel(ctx); ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext(); RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader, ctx.getLanguage()); try { brokerController.getEndTransactionProcessor() .processRequest(channelHandlerContext, command); future.complete(null); } catch (Exception e) { future.completeExceptionally(e); } return future; } @Override public CompletableFuture popLiteMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, PopLiteMessageRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException(); } @Override public CompletableFuture popMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, PopMessageRequestHeader requestHeader, long timeoutMillis) { RemotingCommand request = LocalRemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader, ctx.getLanguage()); CompletableFuture future = new CompletableFuture<>(); SimpleChannel channel = channelManager.createInvocationChannel(ctx); InvocationContext invocationContext = new InvocationContext(future); channel.registerInvocationContext(request.getOpaque(), invocationContext); ChannelHandlerContext simpleChannelHandlerContext = channel.getChannelHandlerContext(); try { RemotingCommand response = brokerController.getPopMessageProcessor().processRequest(simpleChannelHandlerContext, request); if (response != null) { invocationContext.handle(response); channel.eraseInvocationContext(request.getOpaque()); } } catch (Exception e) { future.completeExceptionally(e); channel.eraseInvocationContext(request.getOpaque()); log.error("Failed to process popMessage command", e); } return future.thenApply(r -> { PopStatus popStatus; List messageExtList = new ArrayList<>(); switch (r.getCode()) { case ResponseCode.SUCCESS: popStatus = PopStatus.FOUND; ByteBuffer byteBuffer = ByteBuffer.wrap(r.getBody()); messageExtList = MessageDecoder.decodesBatch( byteBuffer, true, false, true ); break; case ResponseCode.POLLING_FULL: popStatus = PopStatus.POLLING_FULL; break; case ResponseCode.POLLING_TIMEOUT: case ResponseCode.PULL_NOT_FOUND: popStatus = PopStatus.POLLING_NOT_FOUND; break; default: throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, r.getRemark()); } PopResult popResult = new PopResult(popStatus, messageExtList); PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) r.readCustomHeader(); if (popStatus == PopStatus.FOUND) { Map startOffsetInfo; Map> msgOffsetInfo; Map orderCountInfo; popResult.setInvisibleTime(responseHeader.getInvisibleTime()); popResult.setPopTime(responseHeader.getPopTime()); startOffsetInfo = ExtraInfoUtil.parseStartOffsetInfo(responseHeader.getStartOffsetInfo()); msgOffsetInfo = ExtraInfoUtil.parseMsgOffsetInfo(responseHeader.getMsgOffsetInfo()); orderCountInfo = ExtraInfoUtil.parseOrderCountInfo(responseHeader.getOrderCountInfo()); // Map> sortMap = new HashMap<>(16); for (MessageExt messageExt : messageExtList) { // Value of POP_CK is used to determine whether it is a pop retry, // cause topic could be rewritten by broker. String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getProperty(MessageConst.PROPERTY_POP_CK), messageExt.getQueueId()); if (!sortMap.containsKey(key)) { sortMap.put(key, new ArrayList<>(4)); } sortMap.get(key).add(messageExt.getQueueOffset()); } Map map = new HashMap<>(5); for (MessageExt messageExt : messageExtList) { if (startOffsetInfo == null) { // we should set the check point info to extraInfo field , if the command is popMsg // find pop ck offset String key = messageExt.getTopic() + messageExt.getQueueId(); if (!map.containsKey(messageExt.getTopic() + messageExt.getQueueId())) { map.put(key, ExtraInfoUtil.buildExtraInfo(messageExt.getQueueOffset(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), messageExt.getTopic(), messageQueue.getBrokerName(), messageExt.getQueueId())); } messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, map.get(key) + MessageConst.KEY_SEPARATOR + messageExt.getQueueOffset()); } else { if (messageExt.getProperty(MessageConst.PROPERTY_POP_CK) == null) { String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getQueueId()); int index = sortMap.get(key).indexOf(messageExt.getQueueOffset()); Long msgQueueOffset = msgOffsetInfo.get(key).get(index); if (msgQueueOffset != messageExt.getQueueOffset()) { log.warn("Queue offset [{}] of msg is strange, not equal to the stored in msg, {}", msgQueueOffset, messageExt); } messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, ExtraInfoUtil.buildExtraInfo(startOffsetInfo.get(key), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(), messageExt.getTopic(), messageQueue.getBrokerName(), messageExt.getQueueId(), msgQueueOffset) ); if (requestHeader.isOrder() && orderCountInfo != null) { Integer count = orderCountInfo.get(key); if (count != null && count > 0) { messageExt.setReconsumeTimes(count); } } } } messageExt.getProperties().computeIfAbsent(MessageConst.PROPERTY_FIRST_POP_TIME, k -> String.valueOf(responseHeader.getPopTime())); messageExt.setBrokerName(messageExt.getBrokerName()); messageExt.setTopic(messageQueue.getTopic()); } } return popResult; }); } @Override public CompletableFuture changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId, ChangeInvisibleTimeRequestHeader requestHeader, long timeoutMillis) { SimpleChannel channel = channelManager.createChannel(ctx); ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext(); RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader, ctx.getLanguage()); CompletableFuture future = new CompletableFuture<>(); try { future = brokerController.getChangeInvisibleTimeProcessor() .processRequestAsync(channelHandlerContext.channel(), command, true); } catch (Exception e) { log.error("Fail to process changeInvisibleTime command", e); future.completeExceptionally(e); } return future.thenApply(r -> { ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) r.readCustomHeader(); AckResult ackResult = new AckResult(); if (ResponseCode.SUCCESS == r.getCode()) { ackResult.setStatus(AckStatus.OK); } else { ackResult.setStatus(AckStatus.NO_EXIST); } ackResult.setPopTime(responseHeader.getPopTime()); ackResult.setExtraInfo(ReceiptHandle.builder() .startOffset(handle.getStartOffset()) .retrieveTime(responseHeader.getPopTime()) .invisibleTime(responseHeader.getInvisibleTime()) .reviveQueueId(responseHeader.getReviveQid()) .topicType(handle.getTopicType()) .brokerName(handle.getBrokerName()) .queueId(handle.getQueueId()) .offset(handle.getOffset()) .build() .encode()); return ackResult; }); } @Override public CompletableFuture ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId, AckMessageRequestHeader requestHeader, long timeoutMillis) { SimpleChannel channel = channelManager.createChannel(ctx); ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext(); RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader, ctx.getLanguage()); CompletableFuture future = new CompletableFuture<>(); try { RemotingCommand response = brokerController.getAckMessageProcessor() .processRequest(channelHandlerContext, command); future.complete(response); } catch (Exception e) { log.error("Fail to process ackMessage command", e); future.completeExceptionally(e); } return future.thenApply(r -> { AckResult ackResult = new AckResult(); if (ResponseCode.SUCCESS == r.getCode()) { ackResult.setStatus(AckStatus.OK); } else { ackResult.setStatus(AckStatus.NO_EXIST); } return ackResult; }); } @Override public CompletableFuture batchAckMessage(ProxyContext ctx, List handleList, String consumerGroup, String topic, long timeoutMillis) { SimpleChannel channel = channelManager.createChannel(ctx); ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext(); RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null); Map batchAckMap = new HashMap<>(); for (ReceiptHandleMessage receiptHandleMessage : handleList) { String extraInfo = receiptHandleMessage.getReceiptHandle().getReceiptHandle(); String[] extraInfoData = ExtraInfoUtil.split(extraInfo); String mergeKey = ExtraInfoUtil.getRetry(extraInfoData) + "@" + ExtraInfoUtil.getQueueId(extraInfoData) + "@" + ExtraInfoUtil.getCkQueueOffset(extraInfoData) + "@" + ExtraInfoUtil.getPopTime(extraInfoData); BatchAck bAck = batchAckMap.computeIfAbsent(mergeKey, k -> { BatchAck newBatchAck = new BatchAck(); newBatchAck.setConsumerGroup(consumerGroup); newBatchAck.setTopic(topic); newBatchAck.setRetry(ExtraInfoUtil.getRetry(extraInfoData)); newBatchAck.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfoData)); newBatchAck.setQueueId(ExtraInfoUtil.getQueueId(extraInfoData)); newBatchAck.setReviveQueueId(ExtraInfoUtil.getReviveQid(extraInfoData)); newBatchAck.setPopTime(ExtraInfoUtil.getPopTime(extraInfoData)); newBatchAck.setInvisibleTime(ExtraInfoUtil.getInvisibleTime(extraInfoData)); newBatchAck.setBitSet(new BitSet()); return newBatchAck; }); bAck.getBitSet().set((int) (ExtraInfoUtil.getQueueOffset(extraInfoData) - ExtraInfoUtil.getCkQueueOffset(extraInfoData))); } BatchAckMessageRequestBody requestBody = new BatchAckMessageRequestBody(); requestBody.setBrokerName(brokerController.getBrokerConfig().getBrokerName()); requestBody.setAcks(new ArrayList<>(batchAckMap.values())); command.setBody(requestBody.encode()); CompletableFuture future = new CompletableFuture<>(); try { RemotingCommand response = brokerController.getAckMessageProcessor() .processRequest(channelHandlerContext, command); future.complete(response); } catch (Exception e) { log.error("Fail to process batchAckMessage command", e); future.completeExceptionally(e); } return future.thenApply(r -> { AckResult ackResult = new AckResult(); if (ResponseCode.SUCCESS == r.getCode()) { ackResult.setStatus(AckStatus.OK); } else { ackResult.setStatus(AckStatus.NO_EXIST); } return ackResult; }); } @Override public CompletableFuture pullMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, PullMessageRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException("pullMessage is not implemented in LocalMessageService"); } @Override public CompletableFuture queryConsumerOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, QueryConsumerOffsetRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException("queryConsumerOffset is not implemented in LocalMessageService"); } @Override public CompletableFuture updateConsumerOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, UpdateConsumerOffsetRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException("updateConsumerOffset is not implemented in LocalMessageService"); } @Override public CompletableFuture updateConsumerOffsetAsync(ProxyContext ctx, AddressableMessageQueue messageQueue, UpdateConsumerOffsetRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException("updateConsumerOffsetAsync is not implemented in LocalMessageService"); } @Override public CompletableFuture> lockBatchMQ(ProxyContext ctx, AddressableMessageQueue messageQueue, LockBatchRequestBody requestBody, long timeoutMillis) { throw new NotImplementedException("lockBatchMQ is not implemented in LocalMessageService"); } @Override public CompletableFuture unlockBatchMQ(ProxyContext ctx, AddressableMessageQueue messageQueue, UnlockBatchRequestBody requestBody, long timeoutMillis) { throw new NotImplementedException("unlockBatchMQ is not implemented in LocalMessageService"); } @Override public CompletableFuture getMaxOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, GetMaxOffsetRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException("getMaxOffset is not implemented in LocalMessageService"); } @Override public CompletableFuture getMinOffset(ProxyContext ctx, AddressableMessageQueue messageQueue, GetMinOffsetRequestHeader requestHeader, long timeoutMillis) { throw new NotImplementedException("getMinOffset is not implemented in LocalMessageService"); } @Override public CompletableFuture recallMessage(ProxyContext ctx, String brokerName, RecallMessageRequestHeader requestHeader, long timeoutMillis) { SimpleChannel channel = channelManager.createChannel(ctx); ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext(); RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, requestHeader, ctx.getLanguage()); CompletableFuture future = new CompletableFuture<>(); try { RemotingCommand response = brokerController.getRecallMessageProcessor() .processRequest(channelHandlerContext, command); future.complete(response); } catch (Exception e) { log.error("Fail to process recallMessage command", e); future.completeExceptionally(e); } return future.thenApply(r -> { switch (r.getCode()) { case ResponseCode.SUCCESS: return ((RecallMessageResponseHeader) r.readCustomHeader()).getMsgId(); default: throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, r.getRemark()); } }); } @Override public CompletableFuture request(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { throw new NotImplementedException("request is not implemented in LocalMessageService"); } @Override public CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis) { throw new NotImplementedException("requestOneway is not implemented in LocalMessageService"); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalRemotingCommand.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import java.util.HashMap; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class LocalRemotingCommand extends RemotingCommand { public static LocalRemotingCommand createRequestCommand(int code, CommandCustomHeader customHeader, String language) { LocalRemotingCommand cmd = new LocalRemotingCommand(); cmd.setCode(code); cmd.setLanguage(LanguageCode.getCode(language)); cmd.writeCustomHeader(customHeader); cmd.setExtFields(new HashMap<>()); setCmdVersion(cmd); cmd.makeCustomHeaderToNet(); return cmd; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; public interface MessageService { CompletableFuture> sendMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, List msgList, SendMessageRequestHeader requestHeader, long timeoutMillis ); CompletableFuture sendMessageBack( ProxyContext ctx, ReceiptHandle handle, String messageId, ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis ); CompletableFuture endTransactionOneway( ProxyContext ctx, String brokerName, EndTransactionRequestHeader requestHeader, long timeoutMillis ); CompletableFuture popMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, PopMessageRequestHeader requestHeader, long timeoutMillis ); CompletableFuture popLiteMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, PopLiteMessageRequestHeader requestHeader, long timeoutMillis ); CompletableFuture changeInvisibleTime( ProxyContext ctx, ReceiptHandle handle, String messageId, ChangeInvisibleTimeRequestHeader requestHeader, long timeoutMillis ); CompletableFuture ackMessage( ProxyContext ctx, ReceiptHandle handle, String messageId, AckMessageRequestHeader requestHeader, long timeoutMillis ); CompletableFuture batchAckMessage( ProxyContext ctx, List handleList, String consumerGroup, String topic, long timeoutMillis ); CompletableFuture pullMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, PullMessageRequestHeader requestHeader, long timeoutMillis ); CompletableFuture queryConsumerOffset( ProxyContext ctx, AddressableMessageQueue messageQueue, QueryConsumerOffsetRequestHeader requestHeader, long timeoutMillis ); CompletableFuture updateConsumerOffset( ProxyContext ctx, AddressableMessageQueue messageQueue, UpdateConsumerOffsetRequestHeader requestHeader, long timeoutMillis ); CompletableFuture updateConsumerOffsetAsync( ProxyContext ctx, AddressableMessageQueue messageQueue, UpdateConsumerOffsetRequestHeader requestHeader, long timeoutMillis ); CompletableFuture> lockBatchMQ( ProxyContext ctx, AddressableMessageQueue messageQueue, LockBatchRequestBody requestBody, long timeoutMillis ); CompletableFuture unlockBatchMQ( ProxyContext ctx, AddressableMessageQueue messageQueue, UnlockBatchRequestBody requestBody, long timeoutMillis ); CompletableFuture getMaxOffset( ProxyContext ctx, AddressableMessageQueue messageQueue, GetMaxOffsetRequestHeader requestHeader, long timeoutMillis ); CompletableFuture getMinOffset( ProxyContext ctx, AddressableMessageQueue messageQueue, GetMinOffsetRequestHeader requestHeader, long timeoutMillis ); CompletableFuture recallMessage( ProxyContext ctx, String brokerName, RecallMessageRequestHeader requestHeader, long timeoutMillis ); CompletableFuture request(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis); CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, long timeoutMillis); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import org.apache.rocketmq.common.consumer.ReceiptHandle; public class ReceiptHandleMessage { private final ReceiptHandle receiptHandle; private final String messageId; public ReceiptHandleMessage(ReceiptHandle receiptHandle, String messageId) { this.receiptHandle = receiptHandle; this.messageId = messageId; } public ReceiptHandle getReceiptHandle() { return receiptHandle; } public String getMessageId() { return messageId; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.metadata; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.broker.auth.converter.AclConverter; import org.apache.rocketmq.broker.auth.converter.UserConverter; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.AbstractCacheLoader; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.route.TopicRouteHelper; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.body.AclInfo; import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class ClusterMetadataService extends AbstractStartAndShutdown implements MetadataService { protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final long DEFAULT_TIMEOUT = 3000; private final TopicRouteService topicRouteService; private final MQClientAPIFactory mqClientAPIFactory; protected final ThreadPoolExecutor cacheRefreshExecutor; protected final LoadingCache topicConfigCache; protected final static TopicConfigAndQueueMapping EMPTY_TOPIC_CONFIG = new TopicConfigAndQueueMapping(); protected final LoadingCache subscriptionGroupConfigCache; protected final static SubscriptionGroupConfig EMPTY_SUBSCRIPTION_GROUP_CONFIG = new SubscriptionGroupConfig(); protected final LoadingCache userCache; protected final static User EMPTY_USER = new User(); protected final LoadingCache aclCache; protected final static Acl EMPTY_ACL = new Acl(); protected final Random random = new Random(); public ClusterMetadataService(TopicRouteService topicRouteService, MQClientAPIFactory mqClientAPIFactory) { this.topicRouteService = topicRouteService; this.mqClientAPIFactory = mqClientAPIFactory; ProxyConfig config = ConfigurationManager.getProxyConfig(); this.cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor( config.getMetadataThreadPoolNums(), config.getMetadataThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "MetadataCacheRefresh", config.getMetadataThreadPoolQueueCapacity() ); this.topicConfigCache = CacheBuilder.newBuilder() .maximumSize(config.getTopicConfigCacheMaxNum()) .expireAfterAccess(config.getTopicConfigCacheExpiredSeconds(), TimeUnit.SECONDS) .refreshAfterWrite(config.getTopicConfigCacheRefreshSeconds(), TimeUnit.SECONDS) .build(new ClusterTopicConfigCacheLoader()); this.subscriptionGroupConfigCache = CacheBuilder.newBuilder() .maximumSize(config.getSubscriptionGroupConfigCacheMaxNum()) .expireAfterAccess(config.getSubscriptionGroupConfigCacheExpiredSeconds(), TimeUnit.SECONDS) .refreshAfterWrite(config.getSubscriptionGroupConfigCacheRefreshSeconds(), TimeUnit.SECONDS) .build(new ClusterSubscriptionGroupConfigCacheLoader()); this.userCache = CacheBuilder.newBuilder() .maximumSize(config.getUserCacheMaxNum()) .expireAfterAccess(config.getUserCacheExpiredSeconds(), TimeUnit.SECONDS) .refreshAfterWrite(config.getUserCacheRefreshSeconds(), TimeUnit.SECONDS) .build(new ClusterUserCacheLoader()); this.aclCache = CacheBuilder.newBuilder() .maximumSize(config.getAclCacheMaxNum()) .expireAfterAccess(config.getAclCacheExpiredSeconds(), TimeUnit.SECONDS) .refreshAfterWrite(config.getAclCacheRefreshSeconds(), TimeUnit.SECONDS) .build(new ClusterAclCacheLoader()); this.init(); } protected void init() { this.appendShutdown(this.cacheRefreshExecutor::shutdown); } @Override public TopicMessageType getTopicMessageType(ProxyContext ctx, String topic) { TopicConfigAndQueueMapping topicConfigAndQueueMapping; try { topicConfigAndQueueMapping = topicConfigCache.get(topic); } catch (Exception e) { return TopicMessageType.UNSPECIFIED; } if (topicConfigAndQueueMapping.equals(EMPTY_TOPIC_CONFIG)) { return TopicMessageType.UNSPECIFIED; } return topicConfigAndQueueMapping.getTopicMessageType(); } @Override public SubscriptionGroupConfig getSubscriptionGroupConfig(ProxyContext ctx, String group) { SubscriptionGroupConfig config; try { config = this.subscriptionGroupConfigCache.get(group); } catch (Exception e) { return null; } if (config == EMPTY_SUBSCRIPTION_GROUP_CONFIG) { return null; } return config; } @Override public CompletableFuture getUser(ProxyContext ctx, String username) { CompletableFuture result = new CompletableFuture<>(); try { User user = this.userCache.get(username); if (user == EMPTY_USER) { user = null; } result.complete(user); } catch (Exception e) { result.completeExceptionally(e); } return result; } @Override public CompletableFuture getAcl(ProxyContext ctx, Subject subject) { CompletableFuture result = new CompletableFuture<>(); try { Acl acl = this.aclCache.get(subject.getSubjectKey()); if (acl == EMPTY_ACL) { acl = null; } result.complete(acl); } catch (Exception e) { result.completeExceptionally(e); } return result; } protected class ClusterSubscriptionGroupConfigCacheLoader extends AbstractCacheLoader { public ClusterSubscriptionGroupConfigCacheLoader() { super(cacheRefreshExecutor); } @Override protected SubscriptionGroupConfig getDirectly(String consumerGroup) throws Exception { ProxyConfig config = ConfigurationManager.getProxyConfig(); String clusterName = config.getRocketMQClusterName(); Optional brokerDataOptional = findOneBroker(clusterName); if (brokerDataOptional.isPresent()) { String brokerAddress = brokerDataOptional.get().selectBrokerAddr(); return mqClientAPIFactory.getClient().getSubscriptionGroupConfig(brokerAddress, consumerGroup, DEFAULT_TIMEOUT); } return EMPTY_SUBSCRIPTION_GROUP_CONFIG; } @Override protected void onErr(String consumerGroup, Exception e) { log.error("load subscription config failed. consumerGroup:{}", consumerGroup, e); } } protected class ClusterTopicConfigCacheLoader extends AbstractCacheLoader { public ClusterTopicConfigCacheLoader() { super(cacheRefreshExecutor); } @Override protected TopicConfigAndQueueMapping getDirectly(String topic) throws Exception { Optional brokerDataOptional = findOneBroker(topic); if (brokerDataOptional.isPresent()) { String brokerAddress = brokerDataOptional.get().selectBrokerAddr(); return mqClientAPIFactory.getClient().getTopicConfig(brokerAddress, topic, DEFAULT_TIMEOUT); } return EMPTY_TOPIC_CONFIG; } @Override protected void onErr(String key, Exception e) { log.error("load topic config failed. topic:{}", key, e); } } protected class ClusterUserCacheLoader extends AbstractCacheLoader { public ClusterUserCacheLoader() { super(cacheRefreshExecutor); } @Override protected User getDirectly(String username) throws Exception { ProxyConfig config = ConfigurationManager.getProxyConfig(); String clusterName = config.getRocketMQClusterName(); Optional brokerDataOptional = findOneBroker(clusterName); if (brokerDataOptional.isPresent()) { String brokerAddress = brokerDataOptional.get().selectBrokerAddr(); UserInfo userInfo = mqClientAPIFactory.getClient().getUser(brokerAddress, username, DEFAULT_TIMEOUT); if (userInfo == null) { return EMPTY_USER; } return UserConverter.convertUser(userInfo); } return EMPTY_USER; } @Override protected void onErr(String key, Exception e) { log.error("load user failed. username:{}", key, e); } } protected class ClusterAclCacheLoader extends AbstractCacheLoader { public ClusterAclCacheLoader() { super(cacheRefreshExecutor); } @Override protected Acl getDirectly(String subject) throws Exception { ProxyConfig config = ConfigurationManager.getProxyConfig(); String clusterName = config.getRocketMQClusterName(); Optional brokerDataOptional = findOneBroker(clusterName); if (brokerDataOptional.isPresent()) { String brokerAddress = brokerDataOptional.get().selectBrokerAddr(); AclInfo aclInfo = mqClientAPIFactory.getClient().getAcl(brokerAddress, subject, DEFAULT_TIMEOUT); if (aclInfo == null) { return EMPTY_ACL; } return AclConverter.convertAcl(aclInfo); } return EMPTY_ACL; } @Override protected void onErr(String key, Exception e) { log.error("load acl failed. subject:{}", key, e); } } protected Optional findOneBroker(String topic) throws Exception { try { List brokerDatas = topicRouteService.getAllMessageQueueView(ProxyContext.createForInner(this.getClass()), topic).getTopicRouteData().getBrokerDatas(); int skipNum = random.nextInt(brokerDatas.size()); return brokerDatas.stream().skip(skipNum).findFirst(); } catch (Exception e) { if (TopicRouteHelper.isTopicNotExistError(e)) { return Optional.empty(); } throw e; } } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/LocalMetadataService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.metadata; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class LocalMetadataService implements MetadataService { private final BrokerController brokerController; public LocalMetadataService(BrokerController brokerController) { this.brokerController = brokerController; } @Override public TopicMessageType getTopicMessageType(ProxyContext ctx, String topic) { TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic); if (topicConfig == null) { return TopicMessageType.UNSPECIFIED; } return topicConfig.getTopicMessageType(); } @Override public SubscriptionGroupConfig getSubscriptionGroupConfig(ProxyContext ctx, String group) { return this.brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().get(group); } @Override public CompletableFuture getUser(ProxyContext ctx, String username) { return this.brokerController.getAuthenticationMetadataManager().getUser(username); } @Override public CompletableFuture getAcl(ProxyContext ctx, Subject subject) { return this.brokerController.getAuthorizationMetadataManager().getAcl(subject); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/MetadataService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.metadata; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.auth.authentication.model.Subject; import org.apache.rocketmq.auth.authentication.model.User; import org.apache.rocketmq.auth.authorization.model.Acl; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public interface MetadataService { TopicMessageType getTopicMessageType(ProxyContext ctx, String topic); SubscriptionGroupConfig getSubscriptionGroupConfig(ProxyContext ctx, String group); CompletableFuture getUser(ProxyContext ctx, String username); CompletableFuture getAcl(ProxyContext ctx, Subject subject); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/receipt/DefaultReceiptHandleManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.receipt; import com.google.common.base.Stopwatch; import io.netty.channel.Channel; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.state.StateEventListener; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.common.ReceiptHandleGroup; import org.apache.rocketmq.proxy.common.ReceiptHandleGroupKey; import org.apache.rocketmq.proxy.common.RenewEvent; import org.apache.rocketmq.proxy.common.RenewStrategyPolicy; import org.apache.rocketmq.proxy.common.channel.ChannelHelper; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.remoting.protocol.subscription.RetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class DefaultReceiptHandleManager extends AbstractStartAndShutdown implements ReceiptHandleManager { protected final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final MetadataService metadataService; protected final ConsumerManager consumerManager; protected final ConcurrentMap receiptHandleGroupMap; protected final StateEventListener eventListener; protected final static RetryPolicy RENEW_POLICY = new RenewStrategyPolicy(); protected final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RenewalScheduledThread_")); protected final ThreadPoolExecutor renewalWorkerService; protected final ThreadPoolExecutor returnHandleGroupWorkerService; public DefaultReceiptHandleManager(MetadataService metadataService, ConsumerManager consumerManager, StateEventListener eventListener) { this.metadataService = metadataService; this.consumerManager = consumerManager; this.eventListener = eventListener; ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); this.renewalWorkerService = ThreadPoolMonitor.createAndMonitor( proxyConfig.getRenewThreadPoolNums(), proxyConfig.getRenewMaxThreadPoolNums(), 1, TimeUnit.MINUTES, "RenewalWorkerThread", proxyConfig.getRenewThreadPoolQueueCapacity() ); this.returnHandleGroupWorkerService = ThreadPoolMonitor.createAndMonitor( proxyConfig.getReturnHandleGroupThreadPoolNums(), proxyConfig.getReturnHandleGroupThreadPoolNums() * 2, 1, TimeUnit.MINUTES, "ReturnHandleGroupWorkerThread", proxyConfig.getRenewThreadPoolQueueCapacity() ); consumerManager.appendConsumerIdsChangeListener(new ConsumerIdsChangeListener() { @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { if (ConsumerGroupEvent.CLIENT_UNREGISTER.equals(event)) { if (args == null || args.length < 1) { return; } if (args[0] instanceof ClientChannelInfo) { ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0]; if (ChannelHelper.isRemote(clientChannelInfo.getChannel())) { // if the channel sync from other proxy is expired, not to clear data of connect to current proxy return; } clearGroup(new ReceiptHandleGroupKey(clientChannelInfo.getChannel(), group)); log.info("clear handle of this client when client unregister. group:{}, clientChannelInfo:{}", group, clientChannelInfo); } } } @Override public void shutdown() { } }); this.receiptHandleGroupMap = new ConcurrentHashMap<>(); this.renewalWorkerService.setRejectedExecutionHandler((r, executor) -> log.warn("add renew task failed. queueSize:{}", executor.getQueue().size())); this.appendStartAndShutdown(new StartAndShutdown() { @Override public void start() throws Exception { scheduledExecutorService.scheduleWithFixedDelay(() -> scheduleRenewTask(), 0, ConfigurationManager.getProxyConfig().getRenewSchedulePeriodMillis(), TimeUnit.MILLISECONDS); } @Override public void shutdown() throws Exception { scheduledExecutorService.shutdown(); clearAllHandle(); } }); } public void addReceiptHandle(ProxyContext context, Channel channel, String group, String msgID, MessageReceiptHandle messageReceiptHandle) { ConcurrentHashMapUtils.computeIfAbsent(this.receiptHandleGroupMap, new ReceiptHandleGroupKey(channel, group), k -> new ReceiptHandleGroup()).put(msgID, messageReceiptHandle); } public MessageReceiptHandle removeReceiptHandle(ProxyContext context, Channel channel, String group, String msgID, String receiptHandle) { ReceiptHandleGroup handleGroup = receiptHandleGroupMap.get(new ReceiptHandleGroupKey(channel, group)); if (handleGroup == null) { return null; } return handleGroup.remove(msgID, receiptHandle); } public int getUnackedMessageCount(ProxyContext context, Channel channel, String group) { ReceiptHandleGroup handleGroup = receiptHandleGroupMap.get(new ReceiptHandleGroupKey(channel, group)); return handleGroup == null ? 0 : handleGroup.getMsgCount(); } protected boolean clientIsOffline(ReceiptHandleGroupKey groupKey) { return this.consumerManager.findChannel(groupKey.getGroup(), groupKey.getChannel()) == null; } protected void scheduleRenewTask() { Stopwatch stopwatch = Stopwatch.createStarted(); try { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); for (Map.Entry entry : receiptHandleGroupMap.entrySet()) { ReceiptHandleGroupKey key = entry.getKey(); if (clientIsOffline(key)) { clearGroup(key); continue; } ReceiptHandleGroup group = entry.getValue(); group.scan((msgID, handleStr, v) -> { long current = System.currentTimeMillis(); ReceiptHandle handle = ReceiptHandle.decode(v.getReceiptHandleStr()); if (handle.getNextVisibleTime() - current > proxyConfig.getRenewAheadTimeMillis()) { return; } renewalWorkerService.submit(() -> renewMessage(createContext("RenewMessage"), key, group, msgID, handleStr)); }); } } catch (Exception e) { log.error("unexpect error when schedule renew task", e); } log.debug("scan for renewal done. cost:{}ms", stopwatch.elapsed().toMillis()); } protected void renewMessage(ProxyContext context, ReceiptHandleGroupKey key, ReceiptHandleGroup group, String msgID, String handleStr) { try { group.computeIfPresent(msgID, handleStr, messageReceiptHandle -> startRenewMessage(context, key, messageReceiptHandle), 0); } catch (Exception e) { log.error("error when renew message. msgID:{}, handleStr:{}", msgID, handleStr, e); } } protected CompletableFuture startRenewMessage(ProxyContext context, ReceiptHandleGroupKey key, MessageReceiptHandle messageReceiptHandle) { CompletableFuture resFuture = new CompletableFuture<>(); ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); long current = System.currentTimeMillis(); try { if (messageReceiptHandle.getRenewRetryTimes() >= proxyConfig.getMaxRenewRetryTimes()) { log.warn("handle has exceed max renewRetryTimes. handle:{}", messageReceiptHandle); return CompletableFuture.completedFuture(null); } if (current - messageReceiptHandle.getConsumeTimestamp() < proxyConfig.getRenewMaxTimeMillis()) { CompletableFuture future = new CompletableFuture<>(); eventListener.fireEvent(new RenewEvent(key, messageReceiptHandle, RENEW_POLICY.nextDelayDuration(messageReceiptHandle.getRenewTimes()), RenewEvent.EventType.RENEW, future)); future.whenComplete((ackResult, throwable) -> { if (throwable != null) { log.error("error when renew. handle:{}", messageReceiptHandle, throwable); if (renewExceptionNeedRetry(throwable)) { messageReceiptHandle.incrementAndGetRenewRetryTimes(); resFuture.complete(messageReceiptHandle); } else { resFuture.complete(null); } } else if (AckStatus.OK.equals(ackResult.getStatus())) { messageReceiptHandle.updateReceiptHandle(ackResult.getExtraInfo()); messageReceiptHandle.resetRenewRetryTimes(); messageReceiptHandle.incrementRenewTimes(); resFuture.complete(messageReceiptHandle); } else { log.error("renew response is not ok. result:{}, handle:{}", ackResult, messageReceiptHandle); resFuture.complete(null); } }); } else { SubscriptionGroupConfig subscriptionGroupConfig = metadataService.getSubscriptionGroupConfig(context, messageReceiptHandle.getGroup()); if (subscriptionGroupConfig == null) { log.error("group's subscriptionGroupConfig is null when renew. handle: {}", messageReceiptHandle); return CompletableFuture.completedFuture(null); } RetryPolicy retryPolicy = subscriptionGroupConfig.getGroupRetryPolicy().getRetryPolicy(); CompletableFuture future = new CompletableFuture<>(); eventListener.fireEvent(new RenewEvent(key, messageReceiptHandle, retryPolicy.nextDelayDuration(messageReceiptHandle.getReconsumeTimes()), RenewEvent.EventType.STOP_RENEW, future)); future.whenComplete((ackResult, throwable) -> { if (throwable != null) { log.error("error when nack in renew. handle:{}", messageReceiptHandle, throwable); } resFuture.complete(null); }); } } catch (Throwable t) { log.error("unexpect error when renew message, stop to renew it. handle:{}", messageReceiptHandle, t); resFuture.complete(null); } return resFuture; } protected void clearGroup(ReceiptHandleGroupKey key) { if (key == null) { return; } ReceiptHandleGroup handleGroup = receiptHandleGroupMap.remove(key); returnHandleGroupWorkerService.submit(() -> returnHandleGroup(key, handleGroup)); } // There is no longer any waiting for lock, and only the locked handles will be processed immediately, // while the handles that cannot be acquired will be kept waiting for the next scheduling. private void returnHandleGroup(ReceiptHandleGroupKey key, ReceiptHandleGroup handleGroup) { if (handleGroup == null || handleGroup.isEmpty()) { return; } ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); handleGroup.scan((msgID, handle, v) -> { try { handleGroup.computeIfPresent(msgID, handle, messageReceiptHandle -> { CompletableFuture future = new CompletableFuture<>(); eventListener.fireEvent(new RenewEvent(key, messageReceiptHandle, proxyConfig.getInvisibleTimeMillisWhenClear(), RenewEvent.EventType.CLEAR_GROUP, future)); return CompletableFuture.completedFuture(null); }, 0); } catch (Exception e) { log.error("error when clear handle for group. key:{}", key, e); } }); // scheduleRenewTask will trigger cleanup again if (!handleGroup.isEmpty()) { log.warn("The handle cannot be completely cleared, the remaining quantity is {}, key:{}", handleGroup.getHandleNum(), key); receiptHandleGroupMap.putIfAbsent(key, handleGroup); } } protected void clearAllHandle() { log.info("start clear all handle in receiptHandleProcessor"); Set keySet = receiptHandleGroupMap.keySet(); for (ReceiptHandleGroupKey key : keySet) { clearGroup(key); } log.info("clear all handle in receiptHandleProcessor done"); } protected boolean renewExceptionNeedRetry(Throwable t) { t = ExceptionUtils.getRealException(t); if (t instanceof ProxyException) { ProxyException proxyException = (ProxyException) t; if (ProxyExceptionCode.INVALID_BROKER_NAME.equals(proxyException.getCode()) || ProxyExceptionCode.INVALID_RECEIPT_HANDLE.equals(proxyException.getCode())) { return false; } } return true; } protected ProxyContext createContext(String actionName) { return ProxyContext.createForInner(this.getClass().getSimpleName() + actionName); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/receipt/ReceiptHandleManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.receipt; import io.netty.channel.Channel; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; public interface ReceiptHandleManager { void addReceiptHandle(ProxyContext context, Channel channel, String group, String msgID, MessageReceiptHandle messageReceiptHandle); MessageReceiptHandle removeReceiptHandle(ProxyContext context, Channel channel, String group, String msgID, String receiptHandle); int getUnackedMessageCount(ProxyContext context, Channel channel, String group); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/AbstractProxyRelayService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.common.utils.ProxyUtils; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; public abstract class AbstractProxyRelayService implements ProxyRelayService { protected final TransactionService transactionService; public AbstractProxyRelayService(TransactionService transactionService) { this.transactionService = transactionService; } @Override public RelayData processCheckTransactionState(ProxyContext context, RemotingCommand command, CheckTransactionStateRequestHeader header, MessageExt messageExt) { CompletableFuture> future = new CompletableFuture<>(); String group = messageExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); TransactionData transactionData = transactionService.addTransactionDataByBrokerAddr( context, command.getExtFields().get(ProxyUtils.BROKER_ADDR), messageExt.getTopic(), group, header.getTranStateTableOffset(), header.getCommitLogOffset(), header.getTransactionId(), messageExt); if (transactionData == null) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, String.format("add transaction data failed. request:%s, message:%s", command, messageExt)); } future.exceptionally(throwable -> { this.transactionService.onSendCheckTransactionStateFailed(context, group, transactionData); return null; }); return new RelayData<>(transactionData, future); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ClusterProxyRelayService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; /** * not implement yet */ public class ClusterProxyRelayService extends AbstractProxyRelayService { public ClusterProxyRelayService(TransactionService transactionService) { super(transactionService); } @Override public CompletableFuture> processGetConsumerRunningInfo( ProxyContext context, RemotingCommand command, GetConsumerRunningInfoRequestHeader header) { return null; } @Override public CompletableFuture> processConsumeMessageDirectly( ProxyContext context, RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header) { return null; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; public class LocalProxyRelayService extends AbstractProxyRelayService { private final BrokerController brokerController; public LocalProxyRelayService(BrokerController brokerController, TransactionService transactionService) { super(transactionService); this.brokerController = brokerController; } @Override public CompletableFuture> processGetConsumerRunningInfo( ProxyContext context, RemotingCommand command, GetConsumerRunningInfoRequestHeader header) { CompletableFuture> future = new CompletableFuture<>(); future.thenAccept(proxyOutResult -> { RemotingServer remotingServer = this.brokerController.getRemotingServer(); if (remotingServer instanceof NettyRemotingAbstract) { NettyRemotingAbstract nettyRemotingAbstract = (NettyRemotingAbstract) remotingServer; RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(null); remotingCommand.setOpaque(command.getOpaque()); remotingCommand.setCode(proxyOutResult.getCode()); remotingCommand.setRemark(proxyOutResult.getRemark()); if (proxyOutResult.getCode() == ResponseCode.SUCCESS && proxyOutResult.getResult() != null) { ConsumerRunningInfo consumerRunningInfo = proxyOutResult.getResult(); remotingCommand.setBody(consumerRunningInfo.encode()); } SimpleChannel simpleChannel = new SimpleChannel(context.getRemoteAddress(), context.getLocalAddress()); nettyRemotingAbstract.processResponseCommand(simpleChannel.getChannelHandlerContext(), remotingCommand); } }); return future; } @Override public CompletableFuture> processConsumeMessageDirectly( ProxyContext context, RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header) { CompletableFuture> future = new CompletableFuture<>(); future.thenAccept(proxyOutResult -> { RemotingServer remotingServer = this.brokerController.getRemotingServer(); if (remotingServer instanceof NettyRemotingAbstract) { NettyRemotingAbstract nettyRemotingAbstract = (NettyRemotingAbstract) remotingServer; RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(null); remotingCommand.setOpaque(command.getOpaque()); remotingCommand.setCode(proxyOutResult.getCode()); remotingCommand.setRemark(proxyOutResult.getRemark()); if (proxyOutResult.getCode() == ResponseCode.SUCCESS && proxyOutResult.getResult() != null) { ConsumeMessageDirectlyResult consumeMessageDirectlyResult = proxyOutResult.getResult(); remotingCommand.setBody(consumeMessageDirectlyResult.encode()); } SimpleChannel simpleChannel = new SimpleChannel(context.getRemoteAddress(), context.getLocalAddress()); nettyRemotingAbstract.processResponseCommand(simpleChannel.getChannelHandlerContext(), remotingCommand); } }); return future; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelId; import io.netty.channel.ChannelMetadata; import io.netty.channel.ChannelOutboundBuffer; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.EventLoop; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; public abstract class ProxyChannel extends SimpleChannel { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final SocketAddress remoteSocketAddress; protected final SocketAddress localSocketAddress; protected final ProxyRelayService proxyRelayService; protected ProxyChannel(ProxyRelayService proxyRelayService, Channel parent, String remoteAddress, String localAddress) { super(parent, remoteAddress, localAddress); this.proxyRelayService = proxyRelayService; this.remoteSocketAddress = NetworkUtil.string2SocketAddress(remoteAddress); this.localSocketAddress = NetworkUtil.string2SocketAddress(localAddress); } protected ProxyChannel(ProxyRelayService proxyRelayService, Channel parent, ChannelId id, String remoteAddress, String localAddress) { super(parent, id, remoteAddress, localAddress); this.proxyRelayService = proxyRelayService; this.remoteSocketAddress = NetworkUtil.string2SocketAddress(remoteAddress); this.localSocketAddress = NetworkUtil.string2SocketAddress(localAddress); } @Override public ChannelFuture writeAndFlush(Object msg) { CompletableFuture processFuture = new CompletableFuture<>(); try { if (msg instanceof RemotingCommand) { ProxyContext context = ProxyContext.createForInner(this.getClass()) .setRemoteAddress(remoteAddress) .setLocalAddress(localAddress); RemotingCommand command = (RemotingCommand) msg; if (command.getExtFields() == null) { command.setExtFields(new HashMap<>()); } switch (command.getCode()) { case RequestCode.CHECK_TRANSACTION_STATE: { CheckTransactionStateRequestHeader header = (CheckTransactionStateRequestHeader) command.readCustomHeader(); MessageExt messageExt = MessageDecoder.decode(ByteBuffer.wrap(command.getBody()), true, false, false); RelayData relayData = this.proxyRelayService.processCheckTransactionState(context, command, header, messageExt); processFuture = this.processCheckTransaction(header, messageExt, relayData.getProcessResult(), relayData.getRelayFuture()); break; } case RequestCode.GET_CONSUMER_RUNNING_INFO: { GetConsumerRunningInfoRequestHeader header = (GetConsumerRunningInfoRequestHeader) command.readCustomHeader(); CompletableFuture> relayFuture = this.proxyRelayService.processGetConsumerRunningInfo(context, command, header); processFuture = this.processGetConsumerRunningInfo(command, header, relayFuture); break; } case RequestCode.CONSUME_MESSAGE_DIRECTLY: { ConsumeMessageDirectlyResultRequestHeader header = (ConsumeMessageDirectlyResultRequestHeader) command.readCustomHeader(); MessageExt messageExt = MessageDecoder.decode(ByteBuffer.wrap(command.getBody()), true, false, false); processFuture = this.processConsumeMessageDirectly(command, header, messageExt, this.proxyRelayService.processConsumeMessageDirectly(context, command, header)); break; } case RequestCode.NOTIFY_UNSUBSCRIBE_LITE: { NotifyUnsubscribeLiteRequestHeader header = (NotifyUnsubscribeLiteRequestHeader) command.readCustomHeader(); processFuture = this.processNotifyUnsubscribeLite(header); break; } default: break; } } else { processFuture = processOtherMessage(msg); } } catch (Throwable t) { log.error("process failed. msg:{}", msg, t); processFuture.completeExceptionally(t); } DefaultChannelPromise promise = new DefaultChannelPromise(this, GlobalEventExecutor.INSTANCE); processFuture.thenAccept(ignore -> promise.setSuccess()) .exceptionally(t -> { promise.setFailure(t); return null; }); return promise; } protected abstract CompletableFuture processOtherMessage(Object msg); protected abstract CompletableFuture processCheckTransaction( CheckTransactionStateRequestHeader header, MessageExt messageExt, TransactionData transactionData, CompletableFuture> responseFuture); protected abstract CompletableFuture processNotifyUnsubscribeLite(NotifyUnsubscribeLiteRequestHeader header); protected abstract CompletableFuture processGetConsumerRunningInfo( RemotingCommand command, GetConsumerRunningInfoRequestHeader header, CompletableFuture> responseFuture); protected abstract CompletableFuture processConsumeMessageDirectly( RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header, MessageExt messageExt, CompletableFuture> responseFuture); @Override public ChannelConfig config() { return null; } @Override public ChannelMetadata metadata() { return null; } @Override protected AbstractUnsafe newUnsafe() { return null; } @Override protected boolean isCompatible(EventLoop loop) { return false; } @Override protected void doBind(SocketAddress localAddress) throws Exception { } @Override protected void doDisconnect() throws Exception { } @Override protected void doClose() throws Exception { } @Override protected void doBeginRead() throws Exception { } @Override protected void doWrite(ChannelOutboundBuffer in) throws Exception { } @Override protected SocketAddress localAddress0() { return this.localSocketAddress; } @Override protected SocketAddress remoteAddress0() { return this.remoteSocketAddress; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; public class ProxyRelayResult { private int code; private String remark; private T result; public ProxyRelayResult(int code, String remark, T result) { this.code = code; this.remark = remark; this.result = result; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public T getResult() { return result; } public void setResult(T result) { this.result = result; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; public interface ProxyRelayService { CompletableFuture> processGetConsumerRunningInfo( ProxyContext context, RemotingCommand command, GetConsumerRunningInfoRequestHeader header ); CompletableFuture> processConsumeMessageDirectly( ProxyContext context, RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header ); RelayData processCheckTransactionState( ProxyContext context, RemotingCommand command, CheckTransactionStateRequestHeader header, MessageExt messageExt ); } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/RelayData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import java.util.concurrent.CompletableFuture; public class RelayData { private T processResult; private CompletableFuture> relayFuture; public RelayData(T processResult, CompletableFuture> relayFuture) { this.processResult = processResult; this.relayFuture = relayFuture; } public CompletableFuture> getRelayFuture() { return relayFuture; } public void setRelayFuture( CompletableFuture> relayFuture) { this.relayFuture = relayFuture; } public T getProcessResult() { return processResult; } public void setProcessResult(T processResult) { this.processResult = processResult; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/AddressableMessageQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.message.MessageQueue; public class AddressableMessageQueue extends MessageQueue { private final String brokerAddr; public AddressableMessageQueue(MessageQueue messageQueue, String brokerAddr) { super(messageQueue); this.brokerAddr = brokerAddr; } public String getBrokerAddr() { return brokerAddr; } public MessageQueue getMessageQueue() { return new MessageQueue(getTopic(), getBrokerName(), getQueueId()); } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AddressableMessageQueue)) { return false; } return super.equals(o); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("messageQueue", super.toString()) .add("brokerAddr", brokerAddr) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import java.util.List; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class ClusterTopicRouteService extends TopicRouteService { public ClusterTopicRouteService(MQClientAPIFactory mqClientAPIFactory) { super(mqClientAPIFactory); } @Override public MessageQueueView getCurrentMessageQueueView(ProxyContext ctx, String topicName) throws Exception { return getAllMessageQueueView(ctx, topicName); } @Override public ProxyTopicRouteData getTopicRouteForProxy(ProxyContext ctx, List

    requestHostAndPortList, String topicName) throws Exception { TopicRouteData topicRouteData = getAllMessageQueueView(ctx, topicName).getTopicRouteData(); return new ProxyTopicRouteData(topicRouteData, requestHostAndPortList); } @Override public String getBrokerAddr(ProxyContext ctx, String brokerName) throws Exception { TopicRouteWrapper topicRouteWrapper = getAllMessageQueueView(ctx, brokerName).getTopicRouteWrapper(); return topicRouteWrapper.getMasterAddr(brokerName); } @Override public AddressableMessageQueue buildAddressableMessageQueue(ProxyContext ctx, MessageQueue messageQueue) throws Exception { String brokerAddress = getBrokerAddr(ctx, messageQueue.getBrokerName()); return new AddressableMessageQueue(messageQueue, brokerAddress); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/DefaultMessageQueuePriorityProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; public class DefaultMessageQueuePriorityProvider implements MessageQueuePriorityProvider { @Override public int priorityOf(AddressableMessageQueue queue) { return 0; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.google.common.collect.Lists; import java.util.HashMap; import java.util.List; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class LocalTopicRouteService extends TopicRouteService { private final BrokerController brokerController; private final List brokerDataList; private final int grpcPort; public LocalTopicRouteService(BrokerController brokerController, MQClientAPIFactory mqClientAPIFactory) { super(mqClientAPIFactory); this.brokerController = brokerController; BrokerConfig brokerConfig = this.brokerController.getBrokerConfig(); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, this.brokerController.getBrokerAddr()); this.brokerDataList = Lists.newArrayList( new BrokerData(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerAddrs) ); this.grpcPort = ConfigurationManager.getProxyConfig().getGrpcServerPort(); } @Override public MessageQueueView getCurrentMessageQueueView(ProxyContext ctx, String topic) throws Exception { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().getTopicConfigTable().get(topic); return new MessageQueueView(topic, toTopicRouteData(topicConfig), null); } @Override public ProxyTopicRouteData getTopicRouteForProxy(ProxyContext ctx, List
    requestHostAndPortList, String topicName) throws Exception { MessageQueueView messageQueueView = getAllMessageQueueView(ctx, topicName); TopicRouteData topicRouteData = messageQueueView.getTopicRouteData(); return new ProxyTopicRouteData(topicRouteData, grpcPort); } @Override public String getBrokerAddr(ProxyContext ctx, String brokerName) throws Exception { return this.brokerController.getBrokerAddr(); } @Override public AddressableMessageQueue buildAddressableMessageQueue(ProxyContext ctx, MessageQueue messageQueue) throws Exception { String brokerAddress = getBrokerAddr(ctx, messageQueue.getBrokerName()); return new AddressableMessageQueue(messageQueue, brokerAddress); } protected TopicRouteData toTopicRouteData(TopicConfig topicConfig) { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setBrokerDatas(brokerDataList); QueueData queueData = new QueueData(); queueData.setPerm(topicConfig.getPerm()); queueData.setReadQueueNums(topicConfig.getReadQueueNums()); queueData.setWriteQueueNums(topicConfig.getWriteQueueNums()); queueData.setTopicSysFlag(topicConfig.getTopicSysFlag()); queueData.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); topicRouteData.setQueueDatas(Lists.newArrayList(queueData)); return topicRouteData; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueuePenalizer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.tuple.Pair; import org.apache.rocketmq.common.message.MessageQueue; @FunctionalInterface public interface MessageQueuePenalizer { /** * Returns the penalty value for the given MessageQueue; lower is better. */ int penaltyOf(Q messageQueue); /** * Aggregates penalties from multiple penalizers for the same MessageQueue (by summing them up). */ static int evaluatePenalty(Q messageQueue, List> penalizers) { Objects.requireNonNull(messageQueue, "messageQueue"); if (penalizers == null || penalizers.isEmpty()) { return 0; } int sum = 0; for (MessageQueuePenalizer p : penalizers) { sum += p.penaltyOf(messageQueue); } return sum; } /** * Selects the queue with the lowest evaluated penalty from the given queue list. * *

    The method iterates through all queues exactly once, but starts from a rotating index * derived from {@code startIndex} (round-robin) to avoid always scanning from position 0 .

    * *

    For each queue, it computes a penalty via {@link #evaluatePenalty} using * the provided {@code penalizers}. The queue with the smallest penalty is selected.

    * *

    Short-circuit rule: if any queue has a {@code penalty<= 0}, it is returned immediately, * since no better result than 0 is expected.

    * * @param queues candidate queues to select from * @param penalizers penalty evaluators applied to each queue * @param startIndex atomic counter used to determine the rotating start position (round-robin) * @param queue type * @return a {@code Pair} of (selected queue, penalty), or {@code null} if {@code queues} is null/empty */ static Pair selectLeastPenalty(List queues, List> penalizers, AtomicInteger startIndex) { if (queues == null || queues.isEmpty()) { return null; } Q bestQueue = null; int bestPenalty = Integer.MAX_VALUE; for (int i = 0; i < queues.size(); i++) { int index = Math.floorMod(startIndex.getAndIncrement(), queues.size()); Q messageQueue = queues.get(index); int penalty = evaluatePenalty(messageQueue, penalizers); // Short-circuit: cannot do better than 0 if (penalty <= 0) { return Pair.of(messageQueue, penalty); } if (penalty < bestPenalty) { bestPenalty = penalty; bestQueue = messageQueue; } } return Pair.of(bestQueue, bestPenalty); } /** * Selects a queue with the lowest computed penalty from multiple priority groups. * *

    The input {@code queuesWithPriority} is a list of queue groups ordered by priority. * For each priority group, this method delegates to {@link #selectLeastPenalty} to pick the best queue * within that group and obtain its penalty.

    * *

    Short-circuit rule: if any priority group yields a queue whose {@code penalty <= 0}, * that result is returned immediately.

    * *

    Otherwise, it returns the queue with the smallest positive penalty among all groups. * If multiple groups produce the same minimum penalty, the first encountered one wins.

    * * @param queuesWithPriority priority-ordered groups of queues; each inner list represents one priority level * @param penalizers penalty calculators used by {@code selectLeastPenalty} to score queues * @param startIndex round-robin start index forwarded to {@code selectLeastPenalty} to reduce contention/hotspots * @param queue type * @return a {@code Pair} of (selected queue, penalty), or {@code null} if {@code queuesWithPriority} is null/empty */ static Pair selectLeastPenaltyWithPriority(List> queuesWithPriority, List> penalizers, AtomicInteger startIndex) { if (queuesWithPriority == null || queuesWithPriority.isEmpty()) { return null; } if (queuesWithPriority.size() == 1) { return selectLeastPenalty(queuesWithPriority.get(0), penalizers, startIndex); } Q bestQueue = null; int bestPenalty = Integer.MAX_VALUE; for (List queues : queuesWithPriority) { Pair queueAndPenalty = selectLeastPenalty(queues, penalizers, startIndex); int penalty = queueAndPenalty.getRight(); if (queueAndPenalty.getRight() <= 0) { return queueAndPenalty; } if (penalty < bestPenalty) { bestPenalty = penalty; bestQueue = queueAndPenalty.getLeft(); } } return Pair.of(bestQueue, bestPenalty); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueuePriorityProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.apache.rocketmq.common.message.MessageQueue; /** * A functional interface for providing priority values for message queues. * This interface allows custom priority determination logic to be applied to message queues, * enabling queue selection and routing based on priority levels. *

    * The priority value follows the convention that smaller numeric values indicate higher priority. * For example, priority 0 is higher than priority 1. *

    * * @param the type of message queue, must extend {@link MessageQueue} */ @FunctionalInterface public interface MessageQueuePriorityProvider { /** * Determines the priority value of the given message queue. *

    * Smaller values indicate higher priority. For example: *

      *
    • Priority 0: Highest priority
    • *
    • Priority 1: Medium priority
    • *
    • Priority 2: Lower priority
    • *
    *

    * * @param q the message queue to evaluate * @return the priority value, where smaller values indicate higher priority */ int priorityOf(Q q); /** * Groups message queues by their priority levels and returns them in priority order. *

    * This static utility method takes a list of message queues and a priority provider, * then organizes the queues into groups based on their priority values. * The returned list is ordered from highest priority to lowest priority. *

    * * @param the type of message queue, must extend {@link MessageQueue} * @param queues the list of message queues to group by priority, can be null or empty * @param provider the priority provider to determine the priority of each queue * @return a list of lists, where each inner list contains queues of the same priority level, * ordered from highest priority (smallest value) to lowest priority (largest value). * Returns an empty list if the input queues are null or empty. */ static List> buildPriorityGroups(List queues, MessageQueuePriorityProvider provider) { if (queues == null || queues.isEmpty()) { return Collections.emptyList(); } Map> buckets = new TreeMap<>(); for (Q q : queues) { int p = provider.priorityOf(q); buckets.computeIfAbsent(p, k -> new ArrayList<>()).add(q); } return new ArrayList<>(buckets.values()); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.google.common.base.MoreObjects; import com.google.common.math.IntMath; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.route.QueueData; import static org.apache.rocketmq.proxy.service.route.MessageQueuePenalizer.selectLeastPenaltyWithPriority; import static org.apache.rocketmq.proxy.service.route.MessageQueuePriorityProvider.buildPriorityGroups; public class MessageQueueSelector { private static final int BROKER_ACTING_QUEUE_ID = -1; // multiple queues for brokers with queueId : normal private final List queues = new ArrayList<>(); // one queue for brokers with queueId : -1 private final List brokerActingQueues = new ArrayList<>(); private final Map brokerNameQueueMap = new ConcurrentHashMap<>(); private final AtomicInteger queueIndex; private final AtomicInteger brokerIndex; private final List> penalizers = new ArrayList<>(); // ordered by priority asc (smaller => higher priority) private final List> queuesWithPriority; private final List> brokerActingQueuesWithPriority; public MessageQueueSelector(TopicRouteWrapper topicRouteWrapper, boolean read) { this(topicRouteWrapper, read, null); } public MessageQueueSelector(TopicRouteWrapper topicRouteWrapper, boolean read, MessageQueuePriorityProvider priorityProvider) { if (read) { this.queues.addAll(buildRead(topicRouteWrapper)); } else { this.queues.addAll(buildWrite(topicRouteWrapper)); } buildBrokerActingQueues(topicRouteWrapper.getTopicName(), this.queues); Random random = new Random(); this.queueIndex = new AtomicInteger(random.nextInt()); this.brokerIndex = new AtomicInteger(random.nextInt()); if (priorityProvider == null) { priorityProvider = new DefaultMessageQueuePriorityProvider(); } this.queuesWithPriority = buildPriorityGroups(queues, priorityProvider); this.brokerActingQueuesWithPriority = buildPriorityGroups(brokerActingQueues, priorityProvider); } private static List buildRead(TopicRouteWrapper topicRoute) { Set queueSet = new HashSet<>(); List qds = topicRoute.getQueueDatas(); if (qds == null) { return new ArrayList<>(); } for (QueueData qd : qds) { if (PermName.isReadable(qd.getPerm())) { String brokerAddr = topicRoute.getMasterAddrPrefer(qd.getBrokerName()); if (brokerAddr == null) { continue; } for (int i = 0; i < qd.getReadQueueNums(); i++) { AddressableMessageQueue mq = new AddressableMessageQueue( new MessageQueue(topicRoute.getTopicName(), qd.getBrokerName(), i), brokerAddr); queueSet.add(mq); } } } return queueSet.stream().sorted().collect(Collectors.toList()); } private static List buildWrite(TopicRouteWrapper topicRoute) { Set queueSet = new HashSet<>(); // order topic route. if (StringUtils.isNotBlank(topicRoute.getOrderTopicConf())) { String[] brokers = topicRoute.getOrderTopicConf().split(";"); for (String broker : brokers) { String[] item = broker.split(":"); String brokerName = item[0]; String brokerAddr = topicRoute.getMasterAddr(brokerName); if (brokerAddr == null) { continue; } int nums = Integer.parseInt(item[1]); for (int i = 0; i < nums; i++) { AddressableMessageQueue mq = new AddressableMessageQueue( new MessageQueue(topicRoute.getTopicName(), brokerName, i), brokerAddr); queueSet.add(mq); } } } else { List qds = topicRoute.getQueueDatas(); if (qds == null) { return new ArrayList<>(); } for (QueueData qd : qds) { if (PermName.isWriteable(qd.getPerm())) { String brokerAddr = topicRoute.getMasterAddr(qd.getBrokerName()); if (brokerAddr == null) { continue; } for (int i = 0; i < qd.getWriteQueueNums(); i++) { AddressableMessageQueue mq = new AddressableMessageQueue( new MessageQueue(topicRoute.getTopicName(), qd.getBrokerName(), i), brokerAddr); queueSet.add(mq); } } } } return queueSet.stream().sorted().collect(Collectors.toList()); } private void buildBrokerActingQueues(String topic, List normalQueues) { for (AddressableMessageQueue mq : normalQueues) { AddressableMessageQueue brokerActingQueue = new AddressableMessageQueue( new MessageQueue(topic, mq.getBrokerName(), BROKER_ACTING_QUEUE_ID), mq.getBrokerAddr()); if (!brokerActingQueues.contains(brokerActingQueue)) { brokerActingQueues.add(brokerActingQueue); brokerNameQueueMap.put(brokerActingQueue.getBrokerName(), brokerActingQueue); } } Collections.sort(brokerActingQueues); } public AddressableMessageQueue getQueueByBrokerName(String brokerName) { return this.brokerNameQueueMap.get(brokerName); } public AddressableMessageQueue selectOne(boolean onlyBroker) { int nextIndex = onlyBroker ? brokerIndex.getAndIncrement() : queueIndex.getAndIncrement(); return selectOneByIndex(nextIndex, onlyBroker); } public AddressableMessageQueue selectOneByPipeline(boolean onlyBroker) { if (CollectionUtils.isNotEmpty(penalizers)) { Pair queueAndPenalty; if (onlyBroker) { queueAndPenalty = selectLeastPenaltyWithPriority(brokerActingQueuesWithPriority, penalizers, brokerIndex); } else { queueAndPenalty = selectLeastPenaltyWithPriority(queuesWithPriority, penalizers, queueIndex); } if (queueAndPenalty != null && queueAndPenalty.getLeft() != null) { return queueAndPenalty.getLeft(); } } // SendLatency is not enabled, or no queue is selected, then select by index. return selectOne(onlyBroker); } public AddressableMessageQueue selectNextOne(AddressableMessageQueue last) { boolean onlyBroker = last.getQueueId() < 0; AddressableMessageQueue newOne = last; int count = onlyBroker ? brokerActingQueues.size() : queues.size(); for (int i = 0; i < count; i++) { newOne = selectOne(onlyBroker); if (!newOne.getBrokerName().equals(last.getBrokerName()) || newOne.getQueueId() != last.getQueueId()) { break; } } return newOne; } public AddressableMessageQueue selectOneByIndex(int index, boolean onlyBroker) { if (onlyBroker) { if (brokerActingQueues.isEmpty()) { return null; } return brokerActingQueues.get(IntMath.mod(index, brokerActingQueues.size())); } if (queues.isEmpty()) { return null; } return queues.get(IntMath.mod(index, queues.size())); } public List getQueues() { return queues; } public List getBrokerActingQueues() { return brokerActingQueues; } public void addPenalizer(MessageQueuePenalizer penalizer) { if (penalizer != null) { this.penalizers.add(penalizer); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof MessageQueueSelector)) { return false; } MessageQueueSelector queue = (MessageQueueSelector) o; return Objects.equals(queues, queue.queues) && Objects.equals(brokerActingQueues, queue.brokerActingQueues); } @Override public int hashCode() { return Objects.hash(queues, brokerActingQueues); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("queues", queues) .add("brokerActingQueues", brokerActingQueues) .add("brokerNameQueueMap", brokerNameQueueMap) .add("queueIndex", queueIndex) .add("brokerIndex", brokerIndex) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.google.common.base.MoreObjects; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class MessageQueueView { public static final MessageQueueView WRAPPED_EMPTY_QUEUE = new MessageQueueView("", new TopicRouteData(), null); private final MessageQueueSelector readSelector; private final MessageQueueSelector writeSelector; private final TopicRouteWrapper topicRouteWrapper; public MessageQueueView(String topic, TopicRouteData topicRouteData, List> penalizer) { this(topic, topicRouteData, penalizer, null); } public MessageQueueView(String topic, TopicRouteData topicRouteData, List> penalizer, MessageQueuePriorityProvider priorityProvider) { this.topicRouteWrapper = new TopicRouteWrapper(topicRouteData, topic); this.readSelector = new MessageQueueSelector(topicRouteWrapper, true, priorityProvider); this.writeSelector = new MessageQueueSelector(topicRouteWrapper, false, priorityProvider); if (CollectionUtils.isNotEmpty(penalizer)) { for (MessageQueuePenalizer p : penalizer) { this.readSelector.addPenalizer(p); this.writeSelector.addPenalizer(p); } } } public TopicRouteData getTopicRouteData() { return topicRouteWrapper.getTopicRouteData(); } public TopicRouteWrapper getTopicRouteWrapper() { return topicRouteWrapper; } public String getTopicName() { return topicRouteWrapper.getTopicName(); } public boolean isEmptyCachedQueue() { return this == WRAPPED_EMPTY_QUEUE; } public MessageQueueSelector getReadSelector() { return readSelector; } public MessageQueueSelector getWriteSelector() { return writeSelector; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("readSelector", readSelector) .add("writeSelector", writeSelector) .add("topicRouteWrapper", topicRouteWrapper) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ProxyTopicRouteData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.google.common.collect.Lists; import com.google.common.net.HostAndPort; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class ProxyTopicRouteData { public ProxyTopicRouteData() { } public ProxyTopicRouteData(TopicRouteData topicRouteData) { this.queueDatas = topicRouteData.getQueueDatas(); this.brokerDatas = new ArrayList<>(); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { ProxyTopicRouteData.ProxyBrokerData proxyBrokerData = new ProxyTopicRouteData.ProxyBrokerData(); proxyBrokerData.setCluster(brokerData.getCluster()); proxyBrokerData.setBrokerName(brokerData.getBrokerName()); brokerData.getBrokerAddrs().forEach((brokerId, brokerAddr) -> { HostAndPort brokerHostAndPort = HostAndPort.fromString(brokerAddr); proxyBrokerData.getBrokerAddrs().put(brokerId, Lists.newArrayList(new Address(brokerHostAndPort))); }); this.brokerDatas.add(proxyBrokerData); } } public ProxyTopicRouteData(TopicRouteData topicRouteData, int port) { this.queueDatas = topicRouteData.getQueueDatas(); this.brokerDatas = new ArrayList<>(); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { ProxyTopicRouteData.ProxyBrokerData proxyBrokerData = new ProxyTopicRouteData.ProxyBrokerData(); proxyBrokerData.setCluster(brokerData.getCluster()); proxyBrokerData.setBrokerName(brokerData.getBrokerName()); brokerData.getBrokerAddrs().forEach((brokerId, brokerAddr) -> { HostAndPort brokerHostAndPort = HostAndPort.fromString(brokerAddr); HostAndPort proxyHostAndPort = HostAndPort.fromParts(brokerHostAndPort.getHost(), port); proxyBrokerData.getBrokerAddrs().put(brokerId, Lists.newArrayList(new Address(proxyHostAndPort))); }); this.brokerDatas.add(proxyBrokerData); } } public ProxyTopicRouteData(TopicRouteData topicRouteData, List
    requestHostAndPortList) { this.queueDatas = topicRouteData.getQueueDatas(); this.brokerDatas = new ArrayList<>(); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { ProxyTopicRouteData.ProxyBrokerData proxyBrokerData = new ProxyTopicRouteData.ProxyBrokerData(); proxyBrokerData.setCluster(brokerData.getCluster()); proxyBrokerData.setBrokerName(brokerData.getBrokerName()); for (Long brokerId : brokerData.getBrokerAddrs().keySet()) { proxyBrokerData.getBrokerAddrs().put(brokerId, requestHostAndPortList); } this.brokerDatas.add(proxyBrokerData); } } public static class ProxyBrokerData { private String cluster; private String brokerName; private Map/* broker address */> brokerAddrs = new HashMap<>(); public String getCluster() { return cluster; } public void setCluster(String cluster) { this.cluster = cluster; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public Map> getBrokerAddrs() { return brokerAddrs; } public void setBrokerAddrs(Map> brokerAddrs) { this.brokerAddrs = brokerAddrs; } public BrokerData buildBrokerData() { BrokerData brokerData = new BrokerData(); brokerData.setCluster(cluster); brokerData.setBrokerName(brokerName); HashMap buildBrokerAddress = new HashMap<>(); brokerAddrs.forEach((k, v) -> { if (!v.isEmpty()) { buildBrokerAddress.put(k, v.get(0).getHostAndPort().toString()); } }); brokerData.setBrokerAddrs(buildBrokerAddress); return brokerData; } } private List queueDatas = new ArrayList<>(); private List brokerDatas = new ArrayList<>(); public List getQueueDatas() { return queueDatas; } public void setQueueDatas(List queueDatas) { this.queueDatas = queueDatas; } public List getBrokerDatas() { return brokerDatas; } public void setBrokerDatas(List brokerDatas) { this.brokerDatas = brokerDatas; } public TopicRouteData buildTopicRouteData() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setQueueDatas(queueDatas); topicRouteData.setBrokerDatas(brokerDatas.stream() .map(ProxyBrokerData::buildBrokerData) .collect(Collectors.toList())); return topicRouteData; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import org.apache.rocketmq.client.common.ClientErrorCode; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.remoting.protocol.ResponseCode; public class TopicRouteHelper { public static boolean isTopicNotExistError(Throwable e) { if (e instanceof MQBrokerException) { if (((MQBrokerException) e).getResponseCode() == ResponseCode.TOPIC_NOT_EXIST) { return true; } } if (e instanceof MQClientException) { int code = ((MQClientException) e).getResponseCode(); if (code == ResponseCode.TOPIC_NOT_EXIST || code == ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION) { return true; } Throwable cause = e.getCause(); if (cause instanceof MQClientException) { int causeCode = ((MQClientException) cause).getResponseCode(); return causeCode == ResponseCode.TOPIC_NOT_EXIST || causeCode == ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION; } } return false; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.client.latency.MQFaultStrategy; import org.apache.rocketmq.client.latency.Resolver; import org.apache.rocketmq.client.latency.ServiceDetector; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; public abstract class TopicRouteService extends AbstractStartAndShutdown { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final MQFaultStrategy mqFaultStrategy; protected final LoadingCache topicCache; protected final ThreadPoolExecutor cacheRefreshExecutor; protected final List> penalizers = new ArrayList<>(); protected MessageQueuePriorityProvider priorityProvider = new DefaultMessageQueuePriorityProvider(); public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { ProxyConfig config = ConfigurationManager.getProxyConfig(); this.cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor( config.getTopicRouteServiceThreadPoolNums(), config.getTopicRouteServiceThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, "TopicRouteCacheRefresh", config.getTopicRouteServiceThreadPoolQueueCapacity() ); this.topicCache = Caffeine.newBuilder().maximumSize(config.getTopicRouteServiceCacheMaxNum()) .expireAfterAccess(config.getTopicRouteServiceCacheExpiredSeconds(), TimeUnit.SECONDS) .refreshAfterWrite(config.getTopicRouteServiceCacheRefreshSeconds(), TimeUnit.SECONDS) .executor(cacheRefreshExecutor) .build(new CacheLoader() { @Override public @Nullable MessageQueueView load(String topic) throws Exception { try { TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); return buildMessageQueueView(topic, topicRouteData); } catch (Exception e) { if (TopicRouteHelper.isTopicNotExistError(e)) { return MessageQueueView.WRAPPED_EMPTY_QUEUE; } throw e; } } @Override public @Nullable MessageQueueView reload(@NonNull String key, @NonNull MessageQueueView oldValue) throws Exception { try { return load(key); } catch (Exception e) { log.warn(String.format("reload topic route from namesrv. topic: %s", key), e); return oldValue; } } }); ServiceDetector serviceDetector = new ServiceDetector() { @Override public boolean detect(String endpoint, long timeoutMillis) { Optional candidateTopic = pickTopic(); if (!candidateTopic.isPresent()) { return false; } try { GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader(); requestHeader.setTopic(candidateTopic.get()); requestHeader.setQueueId(0); Long maxOffset = mqClientAPIFactory.getClient().getMaxOffset(endpoint, requestHeader, timeoutMillis).get(); return true; } catch (Exception e) { return false; } } }; mqFaultStrategy = new MQFaultStrategy(extractClientConfigFromProxyConfig(config), new Resolver() { @Override public String resolve(String name) { try { String brokerAddr = getBrokerAddr(ProxyContext.createForInner("MQFaultStrategy"), name); return brokerAddr; } catch (Exception e) { return null; } } }, serviceDetector); this.penalizers.addAll(buildPenalizerByMQFaultStrategy(mqFaultStrategy)); this.init(); } // pickup one topic in the topic cache private Optional pickTopic() { if (topicCache.asMap().isEmpty()) { return Optional.empty(); } return Optional.of(topicCache.asMap().keySet().iterator().next()); } protected void init() { this.appendStartAndShutdown(this.mqFaultStrategy); } public ClientConfig extractClientConfigFromProxyConfig(ProxyConfig proxyConfig) { ClientConfig tempClientConfig = new ClientConfig(); tempClientConfig.setSendLatencyEnable(proxyConfig.getSendLatencyEnable()); tempClientConfig.setStartDetectorEnable(proxyConfig.getStartDetectorEnable()); tempClientConfig.setDetectTimeout(proxyConfig.getDetectTimeout()); tempClientConfig.setDetectInterval(proxyConfig.getDetectInterval()); return tempClientConfig; } public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation, boolean reachable) { checkSendFaultToleranceEnable(); this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation, reachable); } public void checkSendFaultToleranceEnable() { boolean hotLatencySwitch = ConfigurationManager.getProxyConfig().isSendLatencyEnable(); boolean hotDetectorSwitch = ConfigurationManager.getProxyConfig().isStartDetectorEnable(); this.mqFaultStrategy.setSendLatencyFaultEnable(hotLatencySwitch); this.mqFaultStrategy.setStartDetectorEnable(hotDetectorSwitch); } public MQFaultStrategy getMqFaultStrategy() { return this.mqFaultStrategy; } public MessageQueueView getAllMessageQueueView(ProxyContext ctx, String topicName) throws Exception { return getCacheMessageQueueWrapper(this.topicCache, topicName); } public abstract MessageQueueView getCurrentMessageQueueView(ProxyContext ctx, String topicName) throws Exception; public abstract ProxyTopicRouteData getTopicRouteForProxy(ProxyContext ctx, List
    requestHostAndPortList, String topicName) throws Exception; public abstract String getBrokerAddr(ProxyContext ctx, String brokerName) throws Exception; public abstract AddressableMessageQueue buildAddressableMessageQueue(ProxyContext ctx, MessageQueue messageQueue) throws Exception; protected static MessageQueueView getCacheMessageQueueWrapper(LoadingCache topicCache, String key) throws Exception { MessageQueueView res = topicCache.get(key); if (res != null && res.isEmptyCachedQueue()) { throw new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "No topic route info in name server for the topic: " + key); } return res; } protected static boolean isTopicRouteValid(TopicRouteData routeData) { return routeData != null && routeData.getQueueDatas() != null && !routeData.getQueueDatas().isEmpty() && routeData.getBrokerDatas() != null && !routeData.getBrokerDatas().isEmpty(); } protected MessageQueueView buildMessageQueueView(String topic, TopicRouteData topicRouteData) { if (isTopicRouteValid(topicRouteData)) { MessageQueueView tmp = new MessageQueueView(topic, topicRouteData, this.penalizers, this.priorityProvider); log.debug("load topic route from namesrv. topic: {}, queue: {}", topic, tmp); return tmp; } return MessageQueueView.WRAPPED_EMPTY_QUEUE; } public void setPriorityProvider(MessageQueuePriorityProvider priorityProvider) { this.priorityProvider = priorityProvider; } public void addPenalizer(MessageQueuePenalizer penalizer) { this.penalizers.add(penalizer); } @VisibleForTesting public static List> buildPenalizerByMQFaultStrategy(MQFaultStrategy mqFaultStrategy) { List> penalizers = new ArrayList<>(); penalizers.add(messageQueue -> { if (!mqFaultStrategy.isSendLatencyFaultEnable() || mqFaultStrategy.getAvailableFilter().filter(messageQueue)) { return 0; } return 10; }); penalizers.add(messageQueue -> { if (!mqFaultStrategy.isSendLatencyFaultEnable() || mqFaultStrategy.getReachableFilter().filter(messageQueue)) { return 0; } return 100; }); return penalizers; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class TopicRouteWrapper { private final TopicRouteData topicRouteData; private final String topicName; private final Map brokerNameRouteData = new HashMap<>(); public TopicRouteWrapper(TopicRouteData topicRouteData, String topicName) { this.topicRouteData = topicRouteData; this.topicName = topicName; if (this.topicRouteData.getBrokerDatas() != null) { for (BrokerData brokerData : this.topicRouteData.getBrokerDatas()) { this.brokerNameRouteData.put(brokerData.getBrokerName(), brokerData); } } } public String getMasterAddr(String brokerName) { return this.brokerNameRouteData.get(brokerName).getBrokerAddrs().get(MixAll.MASTER_ID); } public String getMasterAddrPrefer(String brokerName) { HashMap brokerAddr = brokerNameRouteData.get(brokerName).getBrokerAddrs(); String addr = brokerAddr.get(MixAll.MASTER_ID); if (addr == null) { Optional optional = brokerAddr.keySet().stream().findFirst(); return optional.map(brokerAddr::get).orElse(null); } return addr; } public String getTopicName() { return topicName; } public TopicRouteData getTopicRouteData() { return topicRouteData; } public List getQueueDatas() { return this.topicRouteData.getQueueDatas(); } public String getOrderTopicConf() { return this.topicRouteData.getOrderTopicConf(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/AbstractSystemMessageSyncer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.sysmessage; import com.alibaba.fastjson2.JSON; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import java.nio.charset.StandardCharsets; import java.time.Duration; public abstract class AbstractSystemMessageSyncer implements StartAndShutdown, MessageListenerConcurrently { protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final TopicRouteService topicRouteService; protected final AdminService adminService; protected final MQClientAPIFactory mqClientAPIFactory; protected final RPCHook rpcHook; protected DefaultMQPushConsumer defaultMQPushConsumer; public AbstractSystemMessageSyncer(TopicRouteService topicRouteService, AdminService adminService, MQClientAPIFactory mqClientAPIFactory, RPCHook rpcHook) { this.topicRouteService = topicRouteService; this.adminService = adminService; this.mqClientAPIFactory = mqClientAPIFactory; this.rpcHook = rpcHook; } protected String getSystemMessageProducerId() { return "PID_" + getBroadcastTopicName(); } protected String getSystemMessageConsumerId() { return "CID_" + getBroadcastTopicName(); } protected String getBroadcastTopicName() { return ConfigurationManager.getProxyConfig().getHeartbeatSyncerTopicName(); } protected String getSubTag() { return "*"; } protected String getBroadcastTopicClusterName() { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); return proxyConfig.getHeartbeatSyncerTopicClusterName(); } protected int getBroadcastTopicQueueNum() { return 1; } public RPCHook getRpcHook() { return rpcHook; } protected void sendSystemMessage(Object data) { String targetTopic = this.getBroadcastTopicName(); try { Message message = new Message( targetTopic, JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8) ); AddressableMessageQueue messageQueue = this.topicRouteService.getAllMessageQueueView(ProxyContext.createForInner(this.getClass()), targetTopic) .getWriteSelector().selectOne(true); this.mqClientAPIFactory.getClient().sendMessageAsync( messageQueue.getBrokerAddr(), messageQueue.getBrokerName(), message, buildSendMessageRequestHeader(message, this.getSystemMessageProducerId(), messageQueue.getQueueId()), Duration.ofSeconds(3).toMillis() ).whenCompleteAsync((result, throwable) -> { if (throwable != null) { log.error("send system message failed. data: {}, topic: {}", data, getBroadcastTopicName(), throwable); return; } if (SendStatus.SEND_OK != result.getSendStatus()) { log.error("send system message failed. data: {}, topic: {}, sendResult:{}", data, getBroadcastTopicName(), result); } }); } catch (Throwable t) { log.error("send system message failed. data: {}, topic: {}", data, targetTopic, t); } } protected SendMessageRequestHeader buildSendMessageRequestHeader(Message message, String producerGroup, int queueId) { SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setProducerGroup(producerGroup); requestHeader.setTopic(message.getTopic()); requestHeader.setDefaultTopic(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC); requestHeader.setDefaultTopicQueueNums(0); requestHeader.setQueueId(queueId); requestHeader.setSysFlag(0); requestHeader.setBornTimestamp(System.currentTimeMillis()); requestHeader.setFlag(message.getFlag()); requestHeader.setProperties(MessageDecoder.messageProperties2String(message.getProperties())); requestHeader.setReconsumeTimes(0); requestHeader.setBatch(false); return requestHeader; } @Override public void start() throws Exception { this.createSysTopic(); RPCHook rpcHook = this.getRpcHook(); this.defaultMQPushConsumer = new DefaultMQPushConsumer(this.getSystemMessageConsumerId(), rpcHook); this.defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); this.defaultMQPushConsumer.setMessageModel(MessageModel.BROADCASTING); try { this.defaultMQPushConsumer.subscribe(this.getBroadcastTopicName(), this.getSubTag()); } catch (MQClientException e) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "subscribe to broadcast topic " + this.getBroadcastTopicName() + " failed. " + e.getMessage()); } this.defaultMQPushConsumer.registerMessageListener(this); this.defaultMQPushConsumer.start(); } protected void createSysTopic() { String clusterName = this.getBroadcastTopicClusterName(); if (StringUtils.isEmpty(clusterName)) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "system topic cluster cannot be empty"); } boolean createSuccess = this.adminService.createTopicOnTopicBrokerIfNotExist( this.getBroadcastTopicName(), clusterName, this.getBroadcastTopicQueueNum(), this.getBroadcastTopicQueueNum(), true, 3 ); if (!createSuccess) { throw new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, "create system broadcast topic " + this.getBroadcastTopicName() + " failed on cluster " + clusterName); } } @Override public void shutdown() throws Exception { this.defaultMQPushConsumer.shutdown(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.sysmessage; import com.alibaba.fastjson2.JSON; import io.netty.channel.Channel; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.proxy.common.channel.ChannelHelper; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.channel.RemoteChannel; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class HeartbeatSyncer extends AbstractSystemMessageSyncer { protected ThreadPoolExecutor threadPoolExecutor; protected ConsumerManager consumerManager; protected final Map remoteChannelMap = new ConcurrentHashMap<>(); protected String localProxyId; public HeartbeatSyncer(TopicRouteService topicRouteService, AdminService adminService, ConsumerManager consumerManager, MQClientAPIFactory mqClientAPIFactory, RPCHook rpcHook) { super(topicRouteService, adminService, mqClientAPIFactory, rpcHook); this.consumerManager = consumerManager; this.localProxyId = buildLocalProxyId(); this.init(); } protected void init() { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); this.threadPoolExecutor = ThreadPoolMonitor.createAndMonitor( proxyConfig.getHeartbeatSyncerThreadPoolNums(), proxyConfig.getHeartbeatSyncerThreadPoolNums(), 1, TimeUnit.MINUTES, "HeartbeatSyncer", proxyConfig.getHeartbeatSyncerThreadPoolQueueCapacity() ); this.consumerManager.appendConsumerIdsChangeListener(new ConsumerIdsChangeListener() { @Override public void handle(ConsumerGroupEvent event, String group, Object... args) { processConsumerGroupEvent(event, group, args); } @Override public void shutdown() { } }); } @Override public void shutdown() throws Exception { this.threadPoolExecutor.shutdown(); super.shutdown(); } protected void processConsumerGroupEvent(ConsumerGroupEvent event, String group, Object... args) { if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) { if (args == null || args.length < 1) { return; } if (args[0] instanceof ClientChannelInfo) { ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0]; remoteChannelMap.remove(buildKey(group, clientChannelInfo.getChannel())); } } } public void onConsumerRegister(String consumerGroup, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set subList) { if (clientChannelInfo == null || ChannelHelper.isRemote(clientChannelInfo.getChannel())) { return; } try { this.threadPoolExecutor.submit(() -> { try { RemoteChannel remoteChannel = RemoteChannel.create(clientChannelInfo.getChannel()); if (remoteChannel == null) { return; } HeartbeatSyncerData data = new HeartbeatSyncerData( HeartbeatType.REGISTER, clientChannelInfo.getClientId(), clientChannelInfo.getLanguage(), clientChannelInfo.getVersion(), consumerGroup, consumeType, messageModel, consumeFromWhere, localProxyId, remoteChannel.encode() ); data.setSubscriptionDataSet(subList); log.debug("sync register heart beat. topic:{}, data:{}", this.getBroadcastTopicName(), data); this.sendSystemMessage(data); } catch (Throwable t) { log.error("heartbeat register broadcast failed. group:{}, clientChannelInfo:{}, consumeType:{}, messageModel:{}, consumeFromWhere:{}, subList:{}", consumerGroup, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList, t); } }); } catch (Throwable t) { log.error("heartbeat submit register broadcast failed. group:{}, clientChannelInfo:{}, consumeType:{}, messageModel:{}, consumeFromWhere:{}, subList:{}", consumerGroup, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList, t); } } public void onConsumerUnRegister(String consumerGroup, ClientChannelInfo clientChannelInfo) { if (clientChannelInfo == null || ChannelHelper.isRemote(clientChannelInfo.getChannel())) { return; } try { this.threadPoolExecutor.submit(() -> { try { RemoteChannel remoteChannel = RemoteChannel.create(clientChannelInfo.getChannel()); if (remoteChannel == null) { return; } HeartbeatSyncerData data = new HeartbeatSyncerData( HeartbeatType.UNREGISTER, clientChannelInfo.getClientId(), clientChannelInfo.getLanguage(), clientChannelInfo.getVersion(), consumerGroup, null, null, null, localProxyId, remoteChannel.encode() ); log.debug("sync unregister heart beat. topic:{}, data:{}", this.getBroadcastTopicName(), data); this.sendSystemMessage(data); } catch (Throwable t) { log.error("heartbeat unregister broadcast failed. group:{}, clientChannelInfo:{}, consumeType:{}", consumerGroup, clientChannelInfo, t); } }); } catch (Throwable t) { log.error("heartbeat submit unregister broadcast failed. group:{}, clientChannelInfo:{}, consumeType:{}", consumerGroup, clientChannelInfo, t); } } @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { if (msgs == null || msgs.isEmpty()) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } for (MessageExt msg : msgs) { try { HeartbeatSyncerData data = JSON.parseObject(new String(msg.getBody(), StandardCharsets.UTF_8), HeartbeatSyncerData.class); if (data.getLocalProxyId().equals(localProxyId)) { continue; } RemoteChannel decodedChannel = RemoteChannel.decode(data.getChannelData()); RemoteChannel channel = remoteChannelMap.computeIfAbsent(buildKey(data.getGroup(), decodedChannel), key -> decodedChannel); channel.setExtendAttribute(decodedChannel.getChannelExtendAttribute()); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( channel, data.getClientId(), data.getLanguage(), data.getVersion() ); log.debug("start process remote channel. data:{}, clientChannelInfo:{}", data, clientChannelInfo); if (data.getHeartbeatType().equals(HeartbeatType.REGISTER)) { this.consumerManager.registerConsumer( data.getGroup(), clientChannelInfo, data.getConsumeType(), data.getMessageModel(), data.getConsumeFromWhere(), data.getSubscriptionDataSet(), false ); } else { this.consumerManager.unregisterConsumer( data.getGroup(), clientChannelInfo, false ); } } catch (Throwable t) { log.error("heartbeat consume message failed. msg:{}, data:{}", msg, new String(msg.getBody(), StandardCharsets.UTF_8), t); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } private String buildLocalProxyId() { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); // use local address, remoting port and grpc port to build unique local proxy Id return proxyConfig.getLocalServeAddr() + "%" + proxyConfig.getRemotingListenPort() + "%" + proxyConfig.getGrpcServerPort(); } private static String buildKey(String group, Channel channel) { return group + "@" + channel.id().asLongText(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.sysmessage; import com.google.common.base.MoreObjects; import java.util.Set; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.LanguageCode; public class HeartbeatSyncerData { private HeartbeatType heartbeatType; private String clientId; private LanguageCode language; private int version; private long lastUpdateTimestamp = System.currentTimeMillis(); private Set subscriptionDataSet; private String group; private ConsumeType consumeType; private MessageModel messageModel; private ConsumeFromWhere consumeFromWhere; private String localProxyId; private String channelData; public HeartbeatSyncerData() { } public HeartbeatSyncerData(HeartbeatType heartbeatType, String clientId, LanguageCode language, int version, String group, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, String localProxyId, String channelData) { this.heartbeatType = heartbeatType; this.clientId = clientId; this.language = language; this.version = version; this.group = group; this.consumeType = consumeType; this.messageModel = messageModel; this.consumeFromWhere = consumeFromWhere; this.localProxyId = localProxyId; this.channelData = channelData; } public HeartbeatType getHeartbeatType() { return heartbeatType; } public void setHeartbeatType(HeartbeatType heartbeatType) { this.heartbeatType = heartbeatType; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public LanguageCode getLanguage() { return language; } public void setLanguage(LanguageCode language) { this.language = language; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } public Set getSubscriptionDataSet() { return subscriptionDataSet; } public void setSubscriptionDataSet( Set subscriptionDataSet) { this.subscriptionDataSet = subscriptionDataSet; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public ConsumeType getConsumeType() { return consumeType; } public void setConsumeType(ConsumeType consumeType) { this.consumeType = consumeType; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public void setConsumeFromWhere(ConsumeFromWhere consumeFromWhere) { this.consumeFromWhere = consumeFromWhere; } public String getLocalProxyId() { return localProxyId; } public void setLocalProxyId(String localProxyId) { this.localProxyId = localProxyId; } public String getChannelData() { return channelData; } public void setChannelData(String channelData) { this.channelData = channelData; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("heartbeatType", heartbeatType) .add("clientId", clientId) .add("language", language) .add("version", version) .add("lastUpdateTimestamp", lastUpdateTimestamp) .add("subscriptionDataSet", subscriptionDataSet) .add("group", group) .add("consumeType", consumeType) .add("messageModel", messageModel) .add("consumeFromWhere", consumeFromWhere) .add("connectProxyIp", localProxyId) .add("channelData", channelData) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.sysmessage; public enum HeartbeatType { REGISTER, UNREGISTER; } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; public abstract class AbstractTransactionService implements TransactionService, StartAndShutdown { protected TransactionDataManager transactionDataManager = new TransactionDataManager(); @Override public TransactionData addTransactionDataByBrokerAddr(ProxyContext ctx, String brokerAddr, String topic, String producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, Message message) { return this.addTransactionDataByBrokerName(ctx, this.getBrokerNameByAddr(brokerAddr), topic, producerGroup, tranStateTableOffset, commitLogOffset, transactionId, message); } @Override public TransactionData addTransactionDataByBrokerName(ProxyContext ctx, String brokerName, String topic, String producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, Message message) { if (StringUtils.isBlank(brokerName)) { return null; } TransactionData transactionData = new TransactionData( brokerName, topic, tranStateTableOffset, commitLogOffset, transactionId, System.currentTimeMillis(), ConfigurationManager.getProxyConfig().getTransactionDataExpireMillis()); this.transactionDataManager.addTransactionData( producerGroup, transactionId, transactionData ); return transactionData; } @Override public EndTransactionRequestData genEndTransactionRequestHeader(ProxyContext ctx, String topic, String producerGroup, Integer commitOrRollback, boolean fromTransactionCheck, String msgId, String transactionId) { TransactionData transactionData = this.transactionDataManager.pollNoExpireTransactionData(producerGroup, transactionId); if (transactionData == null) { return null; } EndTransactionRequestHeader header = new EndTransactionRequestHeader(); header.setTopic(topic); header.setProducerGroup(producerGroup); header.setCommitOrRollback(commitOrRollback); header.setFromTransactionCheck(fromTransactionCheck); header.setMsgId(msgId); header.setTransactionId(transactionId); header.setTranStateTableOffset(transactionData.getTranStateTableOffset()); header.setCommitLogOffset(transactionData.getCommitLogOffset()); return new EndTransactionRequestData(transactionData.getBrokerName(), header); } @Override public void onSendCheckTransactionStateFailed(ProxyContext context, String producerGroup, TransactionData transactionData) { this.transactionDataManager.removeTransactionData(producerGroup, transactionData.getTransactionId(), transactionData); } protected abstract String getBrokerNameByAddr(String brokerAddr); @Override public void shutdown() throws Exception { this.transactionDataManager.shutdown(); } @Override public void start() throws Exception { this.transactionDataManager.start(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import com.google.common.collect.Sets; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.ProducerData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; public class ClusterTransactionService extends AbstractTransactionService { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private static final String TRANS_HEARTBEAT_CLIENT_ID = "rmq-proxy-producer-client"; private final MQClientAPIFactory mqClientAPIFactory; private final TopicRouteService topicRouteService; private final ProducerManager producerManager; private ThreadPoolExecutor heartbeatExecutors; private final Map/* cluster list */> groupClusterData = new ConcurrentHashMap<>(); private final AtomicReference> brokerAddrNameMapRef = new AtomicReference<>(); private TxHeartbeatServiceThread txHeartbeatServiceThread; public ClusterTransactionService(TopicRouteService topicRouteService, ProducerManager producerManager, MQClientAPIFactory mqClientAPIFactory) { this.topicRouteService = topicRouteService; this.producerManager = producerManager; this.mqClientAPIFactory = mqClientAPIFactory; } @Override public void addTransactionSubscription(ProxyContext ctx, String group, List topicList) { for (String topic : topicList) { addTransactionSubscription(ctx, group, topic); } } @Override public void addTransactionSubscription(ProxyContext ctx, String group, String topic) { try { groupClusterData.compute(group, (groupName, clusterDataSet) -> { if (clusterDataSet == null) { clusterDataSet = Sets.newHashSet(); } clusterDataSet.addAll(getClusterDataFromTopic(ctx, topic)); return clusterDataSet; }); } catch (Exception e) { log.error("add producer group err in txHeartBeat. groupId: {}, err: {}", group, e); } } @Override public void replaceTransactionSubscription(ProxyContext ctx, String group, List topicList) { Set clusterDataSet = new HashSet<>(); for (String topic : topicList) { clusterDataSet.addAll(getClusterDataFromTopic(ctx, topic)); } groupClusterData.put(group, clusterDataSet); } private Set getClusterDataFromTopic(ProxyContext ctx, String topic) { try { MessageQueueView messageQueue = this.topicRouteService.getAllMessageQueueView(ctx, topic); List brokerDataList = messageQueue.getTopicRouteData().getBrokerDatas(); if (brokerDataList == null) { return Collections.emptySet(); } Set res = Sets.newHashSet(); for (BrokerData brokerData : brokerDataList) { res.add(new ClusterData(brokerData.getCluster())); } return res; } catch (Throwable t) { log.error("get cluster data failed in txHeartBeat. topic: {}, err: {}", topic, t); } return Collections.emptySet(); } @Override public void unSubscribeAllTransactionTopic(ProxyContext ctx, String group) { groupClusterData.remove(group); } public void scanProducerHeartBeat() { Set groupSet = groupClusterData.keySet(); Map> clusterHeartbeatData = new HashMap<>(); for (String group : groupSet) { groupClusterData.computeIfPresent(group, (groupName, clusterDataSet) -> { if (clusterDataSet.isEmpty()) { return null; } if (!this.producerManager.groupOnline(groupName)) { return null; } ProducerData producerData = new ProducerData(); producerData.setGroupName(groupName); for (ClusterData clusterData : clusterDataSet) { List heartbeatDataList = clusterHeartbeatData.get(clusterData.cluster); if (heartbeatDataList == null) { heartbeatDataList = new ArrayList<>(); } HeartbeatData heartbeatData; if (heartbeatDataList.isEmpty()) { heartbeatData = new HeartbeatData(); heartbeatData.setClientID(TRANS_HEARTBEAT_CLIENT_ID); heartbeatDataList.add(heartbeatData); } else { heartbeatData = heartbeatDataList.get(heartbeatDataList.size() - 1); if (heartbeatData.getProducerDataSet().size() >= ConfigurationManager.getProxyConfig().getTransactionHeartbeatBatchNum()) { heartbeatData = new HeartbeatData(); heartbeatData.setClientID(TRANS_HEARTBEAT_CLIENT_ID); heartbeatDataList.add(heartbeatData); } } heartbeatData.getProducerDataSet().add(producerData); clusterHeartbeatData.put(clusterData.cluster, heartbeatDataList); } if (clusterDataSet.isEmpty()) { return null; } return clusterDataSet; }); } if (clusterHeartbeatData.isEmpty()) { return; } Map brokerAddrNameMap = new ConcurrentHashMap<>(); Set>> clusterEntry = clusterHeartbeatData.entrySet(); for (Map.Entry> entry : clusterEntry) { sendHeartBeatToCluster(entry.getKey(), entry.getValue(), brokerAddrNameMap); } this.brokerAddrNameMapRef.set(brokerAddrNameMap); } public Map> getGroupClusterData() { return groupClusterData; } protected void sendHeartBeatToCluster(String clusterName, List heartbeatDataList, Map brokerAddrNameMap) { if (heartbeatDataList == null) { return; } for (HeartbeatData heartbeatData : heartbeatDataList) { sendHeartBeatToCluster(clusterName, heartbeatData, brokerAddrNameMap); } this.brokerAddrNameMapRef.set(brokerAddrNameMap); } protected void sendHeartBeatToCluster(String clusterName, HeartbeatData heartbeatData, Map brokerAddrNameMap) { try { MessageQueueView messageQueue = this.topicRouteService.getAllMessageQueueView(ProxyContext.createForInner(this.getClass()), clusterName); List brokerDataList = messageQueue.getTopicRouteData().getBrokerDatas(); if (brokerDataList == null) { return; } for (BrokerData brokerData : brokerDataList) { brokerAddrNameMap.put(brokerData.selectBrokerAddr(), brokerData.getBrokerName()); heartbeatExecutors.submit(() -> { String brokerAddr = brokerData.selectBrokerAddr(); this.mqClientAPIFactory.getClient() .sendHeartbeatOneway(brokerAddr, heartbeatData, Duration.ofSeconds(3).toMillis()) .exceptionally(t -> { log.error("Send transactionHeartbeat to broker err. brokerAddr: {}", brokerAddr, t); return null; }); }); } } catch (Exception e) { log.error("get broker add in cluster failed in tx. clusterName: {}", clusterName, e); } } @Override protected String getBrokerNameByAddr(String brokerAddr) { if (StringUtils.isBlank(brokerAddr)) { return null; } return brokerAddrNameMapRef.get().get(brokerAddr); } static class ClusterData { private final String cluster; public ClusterData(String cluster) { this.cluster = cluster; } public String getCluster() { return cluster; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof ClusterData)) { return super.equals(obj); } ClusterData other = (ClusterData) obj; return cluster.equals(other.cluster); } @Override public int hashCode() { return cluster.hashCode(); } } class TxHeartbeatServiceThread extends ServiceThread { @Override public String getServiceName() { return TxHeartbeatServiceThread.class.getName(); } @Override public void run() { while (!this.isStopped()) { this.waitForRunning(TimeUnit.SECONDS.toMillis(ConfigurationManager.getProxyConfig().getTransactionHeartbeatPeriodSecond())); } } @Override protected void onWaitEnd() { scanProducerHeartBeat(); } } @Override public void start() throws Exception { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); txHeartbeatServiceThread = new TxHeartbeatServiceThread(); super.start(); txHeartbeatServiceThread.start(); heartbeatExecutors = ThreadPoolMonitor.createAndMonitor( proxyConfig.getTransactionHeartbeatThreadPoolNums(), proxyConfig.getTransactionHeartbeatThreadPoolNums(), 0L, TimeUnit.MILLISECONDS, "TransactionHeartbeatRegisterThread", proxyConfig.getTransactionHeartbeatThreadPoolQueueCapacity() ); } @Override public void shutdown() throws Exception { txHeartbeatServiceThread.shutdown(); heartbeatExecutors.shutdown(); super.shutdown(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/EndTransactionRequestData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; public class EndTransactionRequestData { private String brokerName; private EndTransactionRequestHeader requestHeader; public EndTransactionRequestData(String brokerName, EndTransactionRequestHeader requestHeader) { this.brokerName = brokerName; this.requestHeader = requestHeader; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public EndTransactionRequestHeader getRequestHeader() { return requestHeader; } public void setRequestHeader(EndTransactionRequestHeader requestHeader) { this.requestHeader = requestHeader; } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/LocalTransactionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import java.util.List; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.proxy.common.ProxyContext; /** * no need to implements, because the channel of producer will put into the broker's producerManager */ public class LocalTransactionService extends AbstractTransactionService { protected final BrokerConfig brokerConfig; public LocalTransactionService(BrokerConfig brokerConfig) { this.brokerConfig = brokerConfig; } @Override public void addTransactionSubscription(ProxyContext ctx, String group, List topicList) { } @Override public void addTransactionSubscription(ProxyContext ctx, String group, String topic) { } @Override public void replaceTransactionSubscription(ProxyContext ctx, String group, List topicList) { } @Override public void unSubscribeAllTransactionTopic(ProxyContext ctx, String group) { } @Override protected String getBrokerNameByAddr(String brokerAddr) { return this.brokerConfig.getBrokerName(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.collect.ComparisonChain; public class TransactionData implements Comparable { private final String brokerName; private final String topic; private final long tranStateTableOffset; private final long commitLogOffset; private final String transactionId; private final long checkTimestamp; private final long expireMs; public TransactionData(String brokerName, String topic, long tranStateTableOffset, long commitLogOffset, String transactionId, long checkTimestamp, long expireMs) { this.brokerName = brokerName; this.topic = topic; this.tranStateTableOffset = tranStateTableOffset; this.commitLogOffset = commitLogOffset; this.transactionId = transactionId; this.checkTimestamp = checkTimestamp; this.expireMs = expireMs; } public String getBrokerName() { return brokerName; } public String getTopic() { return topic; } public long getTranStateTableOffset() { return tranStateTableOffset; } public long getCommitLogOffset() { return commitLogOffset; } public String getTransactionId() { return transactionId; } public long getCheckTimestamp() { return checkTimestamp; } public long getExpireMs() { return expireMs; } public long getExpireTime() { return checkTimestamp + expireMs; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TransactionData data = (TransactionData) o; return tranStateTableOffset == data.tranStateTableOffset && commitLogOffset == data.commitLogOffset && getExpireTime() == data.getExpireTime() && Objects.equal(brokerName, data.brokerName) && Objects.equal(transactionId, data.transactionId); } @Override public int hashCode() { return Objects.hashCode(brokerName, transactionId, tranStateTableOffset, commitLogOffset, getExpireTime()); } @Override public int compareTo(TransactionData o) { return ComparisonChain.start() .compare(getExpireTime(), o.getExpireTime()) .compare(brokerName, o.brokerName) .compare(commitLogOffset, o.commitLogOffset) .compare(tranStateTableOffset, o.tranStateTableOffset) .compare(transactionId, o.transactionId) .result(); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("brokerName", brokerName) .add("tranStateTableOffset", tranStateTableOffset) .add("commitLogOffset", commitLogOffset) .add("transactionId", transactionId) .add("checkTimestamp", checkTimestamp) .add("expireMs", expireMs) .toString(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import java.util.Iterator; import java.util.Map; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.proxy.config.ConfigurationManager; public class TransactionDataManager implements StartAndShutdown { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); protected final AtomicLong maxTransactionDataExpireTime = new AtomicLong(System.currentTimeMillis()); protected final Map> transactionIdDataMap = new ConcurrentHashMap<>(); protected final TransactionDataCleaner transactionDataCleaner = new TransactionDataCleaner(); protected String buildKey(String producerGroup, String transactionId) { return producerGroup + "@" + transactionId; } public void addTransactionData(String producerGroup, String transactionId, TransactionData transactionData) { this.transactionIdDataMap.compute(buildKey(producerGroup, transactionId), (key, dataSet) -> { if (dataSet == null) { dataSet = new ConcurrentSkipListSet<>(); } dataSet.add(transactionData); if (dataSet.size() > ConfigurationManager.getProxyConfig().getTransactionDataMaxNum()) { dataSet.pollFirst(); } return dataSet; }); } public TransactionData pollNoExpireTransactionData(String producerGroup, String transactionId) { AtomicReference res = new AtomicReference<>(); long currTimestamp = System.currentTimeMillis(); this.transactionIdDataMap.computeIfPresent(buildKey(producerGroup, transactionId), (key, dataSet) -> { TransactionData data = dataSet.pollLast(); while (data != null && data.getExpireTime() < currTimestamp) { data = dataSet.pollLast(); } if (data != null) { res.set(data); } if (dataSet.isEmpty()) { return null; } return dataSet; }); return res.get(); } public void removeTransactionData(String producerGroup, String transactionId, TransactionData transactionData) { this.transactionIdDataMap.computeIfPresent(buildKey(producerGroup, transactionId), (key, dataSet) -> { dataSet.remove(transactionData); if (dataSet.isEmpty()) { return null; } return dataSet; }); } protected void cleanExpireTransactionData() { long currTimestamp = System.currentTimeMillis(); Set transactionIdSet = this.transactionIdDataMap.keySet(); for (String transactionId : transactionIdSet) { this.transactionIdDataMap.computeIfPresent(transactionId, (transactionIdKey, dataSet) -> { Iterator iterator = dataSet.iterator(); while (iterator.hasNext()) { try { TransactionData data = iterator.next(); if (data.getExpireTime() < currTimestamp) { iterator.remove(); } else { break; } } catch (NoSuchElementException ignore) { break; } } if (dataSet.isEmpty()) { return null; } try { TransactionData maxData = dataSet.last(); maxTransactionDataExpireTime.set(Math.max(maxTransactionDataExpireTime.get(), maxData.getExpireTime())); } catch (NoSuchElementException ignore) { } return dataSet; }); } } protected class TransactionDataCleaner extends ServiceThread { @Override public String getServiceName() { return "TransactionDataCleaner"; } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { this.waitForRunning(ConfigurationManager.getProxyConfig().getTransactionDataExpireScanPeriodMillis()); } log.info(this.getServiceName() + " service stopped"); } @Override protected void onWaitEnd() { cleanExpireTransactionData(); } } protected void waitTransactionDataClear() throws InterruptedException { this.cleanExpireTransactionData(); long waitMs = Math.max(this.maxTransactionDataExpireTime.get() - System.currentTimeMillis(), 0); waitMs = Math.min(waitMs, ConfigurationManager.getProxyConfig().getTransactionDataMaxWaitClearMillis()); if (waitMs > 0) { TimeUnit.MILLISECONDS.sleep(waitMs); } } @Override public void shutdown() throws Exception { this.transactionDataCleaner.shutdown(); this.waitTransactionDataClear(); } @Override public void start() throws Exception { this.transactionDataCleaner.start(); } } ================================================ FILE: proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import java.util.List; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.proxy.common.ProxyContext; public interface TransactionService { void addTransactionSubscription(ProxyContext ctx, String group, List topicList); void addTransactionSubscription(ProxyContext ctx, String group, String topic); void replaceTransactionSubscription(ProxyContext ctx, String group, List topicList); void unSubscribeAllTransactionTopic(ProxyContext ctx, String group); TransactionData addTransactionDataByBrokerAddr(ProxyContext ctx, String brokerAddr, String topic, String producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, Message message); TransactionData addTransactionDataByBrokerName(ProxyContext ctx, String brokerName, String topic, String producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, Message message); EndTransactionRequestData genEndTransactionRequestHeader(ProxyContext ctx, String topic, String producerGroup, Integer commitOrRollback, boolean fromTransactionCheck, String msgId, String transactionId); void onSendCheckTransactionStateFailed(ProxyContext context, String producerGroup, TransactionData transactionData); } ================================================ FILE: proxy/src/main/resources/rmq.proxy.logback.xml ================================================ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}proxy.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}proxy.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}proxy_watermark.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}proxy_watermark.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker_default.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker_default.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker.%i.log.gz 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}protection.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}protection.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}watermark.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}watermark.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}store.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}store.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}remoting.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}remoting.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}storeerror.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}storeerror.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}transaction.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}transaction.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}lock.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}lock.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}filter.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}filter.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}stats.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}stats.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}commercial.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}commercial.%i.log.gz 1 10 500MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}pop.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}pop.%i.log 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}proxy_metric.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}proxy_metric.%i.log.gz 1 10 500MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}auth_audit.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}auth_audit.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}proxy_traffic.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}proxy_traffic.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/ProxyStartupTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import io.opentelemetry.sdk.OpenTelemetrySdk; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; import java.util.UUID; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerStartup; import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.proxy.config.Configuration; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import static org.apache.rocketmq.proxy.config.ConfigurationManager.RMQ_PROXY_HOME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; public class ProxyStartupTest { private File proxyHome; @Before public void before() throws Throwable { proxyHome = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString().replace('-', '_')); if (!proxyHome.exists()) { proxyHome.mkdirs(); } String folder = "rmq-proxy-home"; PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader()); Resource[] resources = resolver.getResources(String.format("classpath:%s/**/*", folder)); for (Resource resource : resources) { if (!resource.isReadable()) { continue; } String description = resource.getDescription(); int start = description.indexOf('['); int end = description.lastIndexOf(']'); String path = description.substring(start + 1, end); try (InputStream inputStream = resource.getInputStream()) { copyTo(path, inputStream, proxyHome, folder); } } System.setProperty(RMQ_PROXY_HOME, proxyHome.getAbsolutePath()); } private void copyTo(String path, InputStream src, File dstDir, String flag) throws IOException { Preconditions.checkNotNull(flag); Iterator iterator = Splitter.on(File.separatorChar).split(path).iterator(); boolean found = false; File dir = dstDir; while (iterator.hasNext()) { String current = iterator.next(); if (!found && flag.equals(current)) { found = true; continue; } if (found) { if (!iterator.hasNext()) { dir = new File(dir, current); } else { dir = new File(dir, current); if (!dir.exists()) { Assert.assertTrue(dir.mkdir()); } } } } Assert.assertTrue(dir.createNewFile()); byte[] buffer = new byte[4096]; BufferedInputStream bis = new BufferedInputStream(src); int len = 0; try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(dir.toPath()))) { while ((len = bis.read(buffer)) > 0) { bos.write(buffer, 0, len); } } } private void recursiveDelete(File file) { if (file.isFile()) { file.delete(); return; } File[] files = file.listFiles(); for (File f : files) { recursiveDelete(f); } file.delete(); } @After public void after() { System.clearProperty(RMQ_PROXY_HOME); System.clearProperty(MixAll.NAMESRV_ADDR_PROPERTY); System.clearProperty(Configuration.CONFIG_PATH_PROPERTY); recursiveDelete(proxyHome); } @Test public void testParseAndInitCommandLineArgument() throws Exception { Path configFilePath = Files.createTempFile("testParseAndInitCommandLineArgument", ".json"); String configData = "{}"; Files.write(configFilePath, configData.getBytes(StandardCharsets.UTF_8)); String brokerConfigPath = "brokerConfigPath"; String proxyConfigPath = configFilePath.toAbsolutePath().toString(); String proxyMode = "LOCAL"; String namesrvAddr = "namesrvAddr"; CommandLineArgument commandLineArgument = ProxyStartup.parseCommandLineArgument(new String[] { "-bc", brokerConfigPath, "-pc", proxyConfigPath, "-pm", proxyMode, "-n", namesrvAddr }); assertEquals(brokerConfigPath, commandLineArgument.getBrokerConfigPath()); assertEquals(proxyConfigPath, commandLineArgument.getProxyConfigPath()); assertEquals(proxyMode, commandLineArgument.getProxyMode()); assertEquals(namesrvAddr, commandLineArgument.getNamesrvAddr()); ProxyStartup.initConfiguration(commandLineArgument); ProxyConfig config = ConfigurationManager.getProxyConfig(); assertEquals(brokerConfigPath, config.getBrokerConfigPath()); assertEquals(proxyMode, config.getProxyMode()); assertEquals(namesrvAddr, config.getNamesrvAddr()); } @Test public void testLocalModeWithNameSrvAddrByProperty() throws Exception { String namesrvAddr = "namesrvAddr"; System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, namesrvAddr); CommandLineArgument commandLineArgument = ProxyStartup.parseCommandLineArgument(new String[] { "-pm", "local" }); ProxyStartup.initConfiguration(commandLineArgument); ProxyConfig config = ConfigurationManager.getProxyConfig(); assertEquals(namesrvAddr, config.getNamesrvAddr()); validateBrokerCreateArgsWithNamsrvAddr(config, namesrvAddr); } private void validateBrokerCreateArgsWithNamsrvAddr(ProxyConfig config, String namesrvAddr) { try (MockedStatic brokerStartupMocked = mockStatic(BrokerStartup.class); MockedStatic messagingProcessorMocked = mockStatic(DefaultMessagingProcessor.class)) { ArgumentCaptor args = ArgumentCaptor.forClass(Object.class); BrokerController brokerControllerMocked = mock(BrokerController.class); BrokerMetricsManager brokerMetricsManagerMocked = mock(BrokerMetricsManager.class); Mockito.when(brokerMetricsManagerMocked.getBrokerMeter()).thenReturn(OpenTelemetrySdk.builder().build().getMeter("test")); Mockito.when(brokerControllerMocked.getBrokerMetricsManager()).thenReturn(brokerMetricsManagerMocked); brokerStartupMocked.when(() -> BrokerStartup.createBrokerController((String[]) args.capture())) .thenReturn(brokerControllerMocked); messagingProcessorMocked.when(() -> DefaultMessagingProcessor.createForLocalMode(any(), any())) .thenReturn(mock(DefaultMessagingProcessor.class)); ProxyStartup.createMessagingProcessor(); String[] passArgs = (String[]) args.getValue(); assertEquals("-c", passArgs[0]); assertEquals(config.getBrokerConfigPath(), passArgs[1]); assertEquals("-n", passArgs[2]); assertEquals(namesrvAddr, passArgs[3]); assertEquals(4, passArgs.length); } } @Test public void testLocalModeWithNameSrvAddrByConfigFile() throws Exception { String namesrvAddr = "namesrvAddr"; System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, "foo"); Path configFilePath = Files.createTempFile("testLocalModeWithNameSrvAddrByConfigFile", ".json"); String configData = "{\n" + " \"namesrvAddr\": \"namesrvAddr\"\n" + "}"; Files.write(configFilePath, configData.getBytes(StandardCharsets.UTF_8)); CommandLineArgument commandLineArgument = ProxyStartup.parseCommandLineArgument(new String[] { "-pm", "local", "-pc", configFilePath.toAbsolutePath().toString() }); ProxyStartup.initConfiguration(commandLineArgument); ProxyConfig config = ConfigurationManager.getProxyConfig(); assertEquals(namesrvAddr, config.getNamesrvAddr()); validateBrokerCreateArgsWithNamsrvAddr(config, namesrvAddr); } @Test public void testLocalModeWithNameSrvAddrByCommandLine() throws Exception { String namesrvAddr = "namesrvAddr"; System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, "foo"); Path configFilePath = Files.createTempFile("testLocalModeWithNameSrvAddrByCommandLine", ".json"); String configData = "{\n" + " \"namesrvAddr\": \"foo\"\n" + "}"; Files.write(configFilePath, configData.getBytes(StandardCharsets.UTF_8)); CommandLineArgument commandLineArgument = ProxyStartup.parseCommandLineArgument(new String[] { "-pm", "local", "-pc", configFilePath.toAbsolutePath().toString(), "-n", namesrvAddr }); ProxyStartup.initConfiguration(commandLineArgument); ProxyConfig config = ConfigurationManager.getProxyConfig(); assertEquals(namesrvAddr, config.getNamesrvAddr()); validateBrokerCreateArgsWithNamsrvAddr(config, namesrvAddr); } @Test public void testLocalModeWithAllArgs() throws Exception { String namesrvAddr = "namesrvAddr"; System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, "foo"); Path configFilePath = Files.createTempFile("testLocalMode", ".json"); String configData = "{\n" + " \"namesrvAddr\": \"foo\"\n" + "}"; Files.write(configFilePath, configData.getBytes(StandardCharsets.UTF_8)); Path brokerConfigFilePath = Files.createTempFile("testLocalModeBrokerConfig", ".json"); CommandLineArgument commandLineArgument = ProxyStartup.parseCommandLineArgument(new String[] { "-pm", "local", "-pc", configFilePath.toAbsolutePath().toString(), "-n", namesrvAddr, "-bc", brokerConfigFilePath.toAbsolutePath().toString() }); ProxyStartup.initConfiguration(commandLineArgument); ProxyConfig config = ConfigurationManager.getProxyConfig(); assertEquals(namesrvAddr, config.getNamesrvAddr()); assertEquals(brokerConfigFilePath.toAbsolutePath().toString(), config.getBrokerConfigPath()); validateBrokerCreateArgsWithNamsrvAddr(config, namesrvAddr); } @Test public void testClusterMode() throws Exception { CommandLineArgument commandLineArgument = ProxyStartup.parseCommandLineArgument(new String[] { "-pm", "cluster" }); ProxyStartup.initConfiguration(commandLineArgument); try (MockedStatic messagingProcessorMocked = mockStatic(DefaultMessagingProcessor.class)) { DefaultMessagingProcessor processor = mock(DefaultMessagingProcessor.class); messagingProcessorMocked.when(DefaultMessagingProcessor::createForClusterMode) .thenReturn(processor); assertSame(processor, ProxyStartup.createMessagingProcessor()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/common/AddressTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import com.google.common.net.HostAndPort; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class AddressTest { @Test public void testConstructorWithIPv4() { HostAndPort hostAndPort = HostAndPort.fromString("192.168.1.1:8080"); Address address = new Address(hostAndPort); assertEquals(Address.AddressScheme.IPv4, address.getAddressScheme()); assertEquals(hostAndPort, address.getHostAndPort()); } @Test public void testConstructorWithIPv6() { HostAndPort hostAndPort = HostAndPort.fromString("[2001:db8::1]:8080"); Address address = new Address(hostAndPort); assertEquals(Address.AddressScheme.IPv6, address.getAddressScheme()); assertEquals(hostAndPort, address.getHostAndPort()); } @Test public void testConstructorWithDomainName() { HostAndPort hostAndPort = HostAndPort.fromString("example.com:8080"); Address address = new Address(hostAndPort); assertEquals(Address.AddressScheme.DOMAIN_NAME, address.getAddressScheme()); assertEquals(hostAndPort, address.getHostAndPort()); } @Test public void testConstructorWithNullHostAndPort() { Address address = new Address(null); assertEquals(Address.AddressScheme.UNRECOGNIZED, address.getAddressScheme()); assertNull(address.getHostAndPort()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroupTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import java.time.Duration; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class ReceiptHandleGroupTest extends InitConfigTest { private static final String TOPIC = "topic"; private static final String GROUP = "group"; private ReceiptHandleGroup receiptHandleGroup; private String msgID; private final Random random = new Random(); @Before public void before() throws Throwable { super.before(); receiptHandleGroup = new ReceiptHandleGroup(); msgID = MessageClientIDSetter.createUniqID(); } protected String createHandle() { return ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis()) .invisibleTime(3000) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName("brokerName") .queueId(random.nextInt(10)) .offset(random.nextInt(10)) .commitLogOffset(0L) .build().encode(); } @Test public void testAddDuplicationHandle() { String handle1 = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis()) .invisibleTime(3000) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName("brokerName") .queueId(1) .offset(123) .commitLogOffset(0L) .build().encode(); String handle2 = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() + 1000) .invisibleTime(3000) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName("brokerName") .queueId(1) .offset(123) .commitLogOffset(0L) .build().encode(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle2, msgID)); assertEquals(1, receiptHandleGroup.receiptHandleMap.get(msgID).size()); } @Test public void testGetWhenComputeIfPresent() { String handle1 = createHandle(); String handle2 = createHandle(); AtomicReference getHandleRef = new AtomicReference<>(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); CountDownLatch latch = new CountDownLatch(2); Thread getThread = new Thread(() -> { try { latch.countDown(); latch.await(); getHandleRef.set(receiptHandleGroup.get(msgID, handle1)); } catch (Exception ignored) { } }, "getThread"); Thread computeThread = new Thread(() -> { try { receiptHandleGroup.computeIfPresent(msgID, handle1, messageReceiptHandle -> { try { latch.countDown(); latch.await(); } catch (Exception ignored) { } messageReceiptHandle.updateReceiptHandle(handle2); return FutureUtils.addExecutor(CompletableFuture.completedFuture(messageReceiptHandle), Executors.newCachedThreadPool()); }); } catch (Exception ignored) { } }, "computeThread"); getThread.start(); computeThread.start(); await().atMost(Duration.ofSeconds(1)).until(() -> getHandleRef.get() != null); assertEquals(handle2, getHandleRef.get().getReceiptHandleStr()); assertFalse(receiptHandleGroup.isEmpty()); } @Test public void testGetWhenComputeIfPresentReturnNull() { String handle1 = createHandle(); AtomicBoolean getCalled = new AtomicBoolean(false); AtomicReference getHandleRef = new AtomicReference<>(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); CountDownLatch latch = new CountDownLatch(2); Thread getThread = new Thread(() -> { try { latch.countDown(); latch.await(); getHandleRef.set(receiptHandleGroup.get(msgID, handle1)); getCalled.set(true); } catch (Exception ignored) { } }, "getThread"); Thread computeThread = new Thread(() -> { try { receiptHandleGroup.computeIfPresent(msgID, handle1, messageReceiptHandle -> { try { latch.countDown(); latch.await(); } catch (Exception ignored) { } return FutureUtils.addExecutor(CompletableFuture.completedFuture(null), Executors.newCachedThreadPool()); }); } catch (Exception ignored) { } }, "computeThread"); getThread.start(); computeThread.start(); await().atMost(Duration.ofSeconds(1)).until(getCalled::get); assertNull(getHandleRef.get()); assertTrue(receiptHandleGroup.isEmpty()); } @Test public void testRemoveWhenComputeIfPresent() { String handle1 = createHandle(); String handle2 = createHandle(); AtomicReference removeHandleRef = new AtomicReference<>(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); CountDownLatch latch = new CountDownLatch(2); Thread removeThread = new Thread(() -> { try { latch.countDown(); latch.await(); removeHandleRef.set(receiptHandleGroup.remove(msgID, handle1)); } catch (Exception ignored) { } }, "removeThread"); Thread computeThread = new Thread(() -> { try { receiptHandleGroup.computeIfPresent(msgID, handle1, messageReceiptHandle -> { try { latch.countDown(); latch.await(); } catch (Exception ignored) { } messageReceiptHandle.updateReceiptHandle(handle2); return FutureUtils.addExecutor(CompletableFuture.completedFuture(messageReceiptHandle), Executors.newCachedThreadPool()); }); } catch (Exception ignored) { } }, "computeThread"); removeThread.start(); computeThread.start(); await().atMost(Duration.ofSeconds(1)).until(() -> removeHandleRef.get() != null); assertEquals(handle2, removeHandleRef.get().getReceiptHandleStr()); assertTrue(receiptHandleGroup.isEmpty()); } @Test public void testRemoveWhenComputeIfPresentReturnNull() { String handle1 = createHandle(); AtomicBoolean removeCalled = new AtomicBoolean(false); AtomicReference removeHandleRef = new AtomicReference<>(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); CountDownLatch latch = new CountDownLatch(2); Thread removeThread = new Thread(() -> { try { latch.countDown(); latch.await(); removeHandleRef.set(receiptHandleGroup.remove(msgID, handle1)); removeCalled.set(true); } catch (Exception ignored) { } }, "removeThread"); Thread computeThread = new Thread(() -> { try { receiptHandleGroup.computeIfPresent(msgID, handle1, messageReceiptHandle -> { try { latch.countDown(); latch.await(); } catch (Exception ignored) { } return FutureUtils.addExecutor(CompletableFuture.completedFuture(null), Executors.newCachedThreadPool()); }); } catch (Exception ignored) { } }, "computeThread"); removeThread.start(); computeThread.start(); await().atMost(Duration.ofSeconds(1)).until(removeCalled::get); assertNull(removeHandleRef.get()); assertTrue(receiptHandleGroup.isEmpty()); } @Test public void testRemoveMultiThread() { String handle1 = createHandle(); AtomicReference removeHandleRef = new AtomicReference<>(); AtomicInteger count = new AtomicInteger(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); int threadNum = Math.max(Runtime.getRuntime().availableProcessors(), 3); CountDownLatch latch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { Thread thread = new Thread(() -> { try { latch.countDown(); latch.await(); MessageReceiptHandle handle = receiptHandleGroup.remove(msgID, handle1); if (handle != null) { removeHandleRef.set(handle); count.incrementAndGet(); } } catch (Exception ignored) { } }); thread.start(); } await().atMost(Duration.ofSeconds(1)).untilAsserted(() -> assertEquals(1, count.get())); assertEquals(handle1, removeHandleRef.get().getReceiptHandleStr()); assertTrue(receiptHandleGroup.isEmpty()); } @Test public void testRemoveOne() { String handle1 = createHandle(); AtomicReference removeHandleRef = new AtomicReference<>(); AtomicInteger count = new AtomicInteger(); receiptHandleGroup.put(msgID, createMessageReceiptHandle(handle1, msgID)); int threadNum = Math.max(Runtime.getRuntime().availableProcessors(), 3); CountDownLatch latch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { Thread thread = new Thread(() -> { try { latch.countDown(); latch.await(); MessageReceiptHandle handle = receiptHandleGroup.removeOne(msgID); if (handle != null) { removeHandleRef.set(handle); count.incrementAndGet(); } } catch (Exception ignored) { } }); thread.start(); } await().atMost(Duration.ofSeconds(1)).untilAsserted(() -> assertEquals(1, count.get())); assertEquals(handle1, removeHandleRef.get().getReceiptHandleStr()); assertTrue(receiptHandleGroup.isEmpty()); } private MessageReceiptHandle createMessageReceiptHandle(String handle, String msgID) { return new MessageReceiptHandle(GROUP, TOPIC, 0, handle, msgID, 0, 0); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common; import org.apache.rocketmq.remoting.protocol.subscription.RetryPolicy; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; public class RenewStrategyPolicyTest { private RetryPolicy retryPolicy; private final AtomicInteger times = new AtomicInteger(0); @Before public void before() throws Throwable { this.retryPolicy = new RenewStrategyPolicy(); } @Test public void testNextDelayDuration() { long value = this.retryPolicy.nextDelayDuration(times.getAndIncrement()); assertEquals(value, TimeUnit.MINUTES.toMillis(1)); value = this.retryPolicy.nextDelayDuration(times.getAndIncrement()); assertEquals(value, TimeUnit.MINUTES.toMillis(3)); value = this.retryPolicy.nextDelayDuration(times.getAndIncrement()); assertEquals(value, TimeUnit.MINUTES.toMillis(5)); value = this.retryPolicy.nextDelayDuration(times.getAndIncrement()); assertEquals(value, TimeUnit.MINUTES.toMillis(10)); value = this.retryPolicy.nextDelayDuration(times.getAndIncrement()); assertEquals(value, TimeUnit.MINUTES.toMillis(30)); value = this.retryPolicy.nextDelayDuration(times.getAndIncrement()); assertEquals(value, TimeUnit.HOURS.toMillis(1)); } @After public void after() { } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/common/utils/FilterUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.common.utils; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class FilterUtilTest { @Test public void testTagMatched() throws Exception { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData("", "tagA"); assertThat(FilterUtils.isTagMatched(subscriptionData.getTagsSet(), "tagA")).isTrue(); } @Test public void testTagNotMatched() throws Exception { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData("", "tagA"); assertThat(FilterUtils.isTagMatched(subscriptionData.getTagsSet(), "tagB")).isFalse(); } @Test public void testTagMatchedStar() throws Exception { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData("", "*"); assertThat(FilterUtils.isTagMatched(subscriptionData.getTagsSet(), "tagA")).isTrue(); } @Test public void testTagNotMatchedNull() throws Exception { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData("", "tagA"); assertThat(FilterUtils.isTagMatched(subscriptionData.getTagsSet(), null)).isFalse(); } @Test public void testBuildSubscriptionData() throws Exception { // Test case 1: expressionType is null, will use TAG as default. String topic = "topic"; String subString = "substring"; String expressionType = null; SubscriptionData result = FilterAPI.buildSubscriptionData(topic, subString, expressionType); assertThat(result).isNotNull(); assertThat(topic).isEqualTo(result.getTopic()); assertThat(subString).isEqualTo(result.getSubString()); assertThat(result.getExpressionType()).isEqualTo("TAG"); assertThat(result.getCodeSet().size()).isEqualTo(1); // Test case 2: expressionType is not null topic = "topic"; subString = "substring1||substring2"; expressionType = "SQL92"; result = FilterAPI.buildSubscriptionData(topic, subString, expressionType); assertThat(result).isNotNull(); assertThat(topic).isEqualTo(result.getTopic()); assertThat(subString).isEqualTo(result.getSubString()); assertThat(result.getExpressionType()).isEqualTo(expressionType); assertThat(result.getCodeSet().size()).isEqualTo(2); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/config/ConfigurationManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import org.apache.rocketmq.proxy.ProxyMode; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class ConfigurationManagerTest extends InitConfigTest { @Test public void testInitConfig() { assertThat(ConfigurationManager.getProxyConfig()).isNotNull(); assertThat(ConfigurationManager.getProxyConfig().getProxyMode()).isEqualToIgnoringCase(ProxyMode.CLUSTER.toString()); String brokerConfig = ConfigurationManager.getProxyConfig().getBrokerConfigPath(); assertThat(brokerConfig).isEqualTo(ConfigurationManager.getProxyHome() + "/conf/broker.conf"); } @Test public void testGetProxyHome() { // test configured proxy home assertThat(ConfigurationManager.getProxyHome()).isIn(mockProxyHome, "./"); } @Test public void testGetProxyConfig() { assertThat(ConfigurationManager.getProxyConfig()).isNotNull(); } @Test public void testFormatProxyConfig() { String actual = ConfigurationManager.formatProxyConfig(); assertNotNull(actual); ProxyConfig expected = ConfigurationManager.getProxyConfig(); assertTrue(actual.contains(expected.getProxyMode())); assertTrue(actual.contains(expected.getProxyName())); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/config/ConfigurationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import org.apache.rocketmq.auth.config.AuthConfig; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.spy; public class ConfigurationTest { private Configuration configuration; @Before public void init() { configuration = spy(new Configuration()); } @Test public void testInit() throws Exception { configuration.init(); ProxyConfig loadedProxyConfig = configuration.getProxyConfig(); assertNotNull(loadedProxyConfig); AuthConfig loadedAuthConfig = configuration.getAuthConfig(); assertNotNull(loadedAuthConfig); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import java.net.URL; import org.assertj.core.util.Strings; import org.junit.After; import org.junit.Before; import static org.apache.rocketmq.proxy.config.ConfigurationManager.RMQ_PROXY_HOME; public class InitConfigTest { public static String mockProxyHome = "/mock/rmq/proxy/home"; @Before public void before() throws Throwable { URL mockProxyHomeURL = getClass().getClassLoader().getResource("rmq-proxy-home"); if (mockProxyHomeURL != null) { mockProxyHome = mockProxyHomeURL.toURI().getPath(); } if (!Strings.isNullOrEmpty(mockProxyHome)) { System.setProperty(RMQ_PROXY_HOME, mockProxyHome); } ConfigurationManager.initEnv(); ConfigurationManager.initConfig(); } @After public void after() { System.clearProperty(RMQ_PROXY_HOME); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/config/MetricCollectorModeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.config; import org.junit.Assert; import org.junit.Test; public class MetricCollectorModeTest { @Test public void testGetEnumByOrdinal() { Assert.assertEquals(MetricCollectorMode.OFF, MetricCollectorMode.getEnumByString("off")); Assert.assertEquals(MetricCollectorMode.ON, MetricCollectorMode.getEnumByString("on")); Assert.assertEquals(MetricCollectorMode.PROXY, MetricCollectorMode.getEnumByString("proxy")); Assert.assertEquals(MetricCollectorMode.OFF, MetricCollectorMode.getEnumByString("OFF")); Assert.assertEquals(MetricCollectorMode.ON, MetricCollectorMode.getEnumByString("ON")); Assert.assertEquals(MetricCollectorMode.PROXY, MetricCollectorMode.getEnumByString("PROXY")); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc; import io.grpc.Attributes; import io.grpc.netty.shaded.io.netty.buffer.ByteBuf; import io.grpc.netty.shaded.io.netty.buffer.Unpooled; import io.grpc.netty.shaded.io.netty.handler.codec.haproxy.HAProxyTLV; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ProxyAndTlsProtocolNegotiatorTest { private ProxyAndTlsProtocolNegotiator negotiator; @Before public void setUp() throws Exception { ConfigurationManager.initConfig(); ConfigurationManager.getProxyConfig().setTlsTestModeEnable(true); negotiator = new ProxyAndTlsProtocolNegotiator(); } @After public void tearDown() { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); proxyConfig.setTlsTestModeEnable(true); proxyConfig.setTlsKeyPath(""); proxyConfig.setTlsCertPath(""); proxyConfig.setTlsKeyPassword(""); } @Test public void handleHAProxyTLV() { ByteBuf content = Unpooled.buffer(); content.writeBytes("xxxx".getBytes(StandardCharsets.UTF_8)); HAProxyTLV haProxyTLV = new HAProxyTLV((byte) 0xE1, content); negotiator.handleHAProxyTLV(haProxyTLV, Attributes.newBuilder()); } @Test public void testLoadSslContextWithUnencryptedKey() throws Exception { configureTls("server.key", "server.pem", ""); ProxyAndTlsProtocolNegotiator.loadSslContext(); } @Test public void testLoadSslContextWithEncryptedKey() throws Exception { // "1234" is the password of certs/client.key, inherited from remoting module test resources configureTls("client.key", "client.pem", "1234"); ProxyAndTlsProtocolNegotiator.loadSslContext(); } @Test(expected = IllegalArgumentException.class) public void testLoadSslContextWithWrongPassword() throws Exception { configureTls("client.key", "client.pem", "wrong_password"); ProxyAndTlsProtocolNegotiator.loadSslContext(); } private void configureTls(String keyFile, String certFile, String password) throws IOException { ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); proxyConfig.setTlsTestModeEnable(false); proxyConfig.setTlsKeyPath(getCertsPath(keyFile)); proxyConfig.setTlsCertPath(getCertsPath(certFile)); proxyConfig.setTlsKeyPassword(password); } private static String getCertsPath(String fileName) throws IOException { File tempFile = File.createTempFile(fileName, null); tempFile.deleteOnExit(); try (InputStream is = ProxyAndTlsProtocolNegotiatorTest.class .getClassLoader().getResourceAsStream("certs/" + fileName)) { Files.copy(is, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } return tempFile.getAbsolutePath(); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessagingActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import apache.rocketmq.v2.Resource; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertThrows; public class AbstractMessagingActivityTest extends InitConfigTest { public static class MockMessagingActivity extends AbstractMessagingActivity { public MockMessagingActivity(MessagingProcessor messagingProcessor, GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } } private AbstractMessagingActivity messagingActivity; @Before public void before() throws Throwable { super.before(); this.messagingActivity = new MockMessagingActivity(null, null, null); } @Test public void testValidateTopic() { assertThrows(GrpcProxyException.class, () -> messagingActivity.validateTopic(Resource.newBuilder().build())); assertThrows(GrpcProxyException.class, () -> messagingActivity.validateTopic(Resource.newBuilder().setName(TopicValidator.RMQ_SYS_TRACE_TOPIC).build())); assertThrows(GrpcProxyException.class, () -> messagingActivity.validateTopic(Resource.newBuilder().setName("@").build())); assertThrows(GrpcProxyException.class, () -> messagingActivity.validateTopic(Resource.newBuilder().setName(createString(128)).build())); messagingActivity.validateTopic(Resource.newBuilder().setName(createString(127)).build()); } @Test public void testValidateConsumer() { assertThrows(GrpcProxyException.class, () -> messagingActivity.validateConsumerGroup(Resource.newBuilder().build())); assertThrows(GrpcProxyException.class, () -> messagingActivity.validateConsumerGroup(Resource.newBuilder().setName(MixAll.CID_SYS_RMQ_TRANS).build())); assertThrows(GrpcProxyException.class, () -> messagingActivity.validateConsumerGroup(Resource.newBuilder().setName("@").build())); assertThrows(GrpcProxyException.class, () -> messagingActivity.validateConsumerGroup(Resource.newBuilder().setName(createString(256)).build())); messagingActivity.validateConsumerGroup(Resource.newBuilder().setName(createString(120)).build()); } private static String createString(int len) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i++) { sb.append('a'); } return sb.toString(); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/BaseActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import io.grpc.Metadata; import java.time.Duration; import java.util.Random; import java.util.UUID; import org.apache.rocketmq.proxy.common.ContextVariable; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.junit.Ignore; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @Ignore @RunWith(MockitoJUnitRunner.Silent.class) public class BaseActivityTest extends InitConfigTest { protected static final Random RANDOM = new Random(); protected MessagingProcessor messagingProcessor; protected GrpcClientSettingsManager grpcClientSettingsManager; protected GrpcChannelManager grpcChannelManager; protected ProxyRelayService proxyRelayService; protected ReceiptHandleProcessor receiptHandleProcessor; protected MetadataService metadataService; protected static final String REMOTE_ADDR = "192.168.0.1:8080"; protected static final String LOCAL_ADDR = "127.0.0.1:8080"; protected Metadata metadata = new Metadata(); protected static final String CLIENT_ID = "client-id" + UUID.randomUUID(); protected static final String JAVA = "JAVA"; public void before() throws Throwable { super.before(); messagingProcessor = mock(MessagingProcessor.class); grpcClientSettingsManager = mock(GrpcClientSettingsManager.class); proxyRelayService = mock(ProxyRelayService.class); receiptHandleProcessor = mock(ReceiptHandleProcessor.class); metadataService = mock(MetadataService.class); metadata.put(GrpcConstants.CLIENT_ID, CLIENT_ID); metadata.put(GrpcConstants.LANGUAGE, JAVA); metadata.put(GrpcConstants.REMOTE_ADDRESS, REMOTE_ADDR); metadata.put(GrpcConstants.LOCAL_ADDRESS, LOCAL_ADDR); when(messagingProcessor.getProxyRelayService()).thenReturn(proxyRelayService); when(messagingProcessor.getMetadataService()).thenReturn(metadataService); grpcChannelManager = new GrpcChannelManager(messagingProcessor.getProxyRelayService(), grpcClientSettingsManager); } protected ProxyContext createContext() { return ProxyContext.create() .withVal(ContextVariable.CLIENT_ID, CLIENT_ID) .withVal(ContextVariable.LANGUAGE, JAVA) .withVal(ContextVariable.REMOTE_ADDRESS, REMOTE_ADDR) .withVal(ContextVariable.LOCAL_ADDRESS, LOCAL_ADDR) .withVal(ContextVariable.REMAINING_MS, Duration.ofSeconds(10).toMillis()); } protected static String buildReceiptHandle(String topic, long popTime, long invisibleTime) { return ExtraInfoUtil.buildExtraInfo( RANDOM.nextInt(Integer.MAX_VALUE), popTime, invisibleTime, 0, topic, "brokerName", RANDOM.nextInt(8), RANDOM.nextInt(Integer.MAX_VALUE) ); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2; import apache.rocketmq.v2.Address; import apache.rocketmq.v2.AddressScheme; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Endpoints; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.Resource; import io.grpc.Context; import io.grpc.Metadata; import io.grpc.stub.StreamObserver; import java.util.UUID; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.grpc.pipeline.ContextInitPipeline; import org.apache.rocketmq.proxy.grpc.pipeline.RequestPipeline; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; @RunWith(MockitoJUnitRunner.class) public class GrpcMessagingApplicationTest extends InitConfigTest { protected static final String REMOTE_ADDR = "192.168.0.1:8080"; protected static final String LOCAL_ADDR = "127.0.0.1:8080"; protected static final String CLIENT_ID = "client-id" + UUID.randomUUID(); protected static final String JAVA = "JAVA"; @Mock StreamObserver queryRouteResponseStreamObserver; @Mock GrpcMessagingActivity grpcMessagingActivity; GrpcMessagingApplication grpcMessagingApplication; private static final String TOPIC = "topic"; private static Endpoints grpcEndpoints = Endpoints.newBuilder() .setScheme(AddressScheme.IPv4) .addAddresses(Address.newBuilder().setHost("127.0.0.1").setPort(8080).build()) .addAddresses(Address.newBuilder().setHost("127.0.0.2").setPort(8080).build()) .build(); @Before public void setUp() throws Throwable { super.before(); RequestPipeline pipeline = (context, headers, request) -> { }; pipeline = pipeline.pipe(new ContextInitPipeline()); grpcMessagingApplication = new GrpcMessagingApplication(grpcMessagingActivity, pipeline); } @Test public void testQueryRoute() { Metadata metadata = new Metadata(); metadata.put(GrpcConstants.CLIENT_ID, CLIENT_ID); metadata.put(GrpcConstants.LANGUAGE, JAVA); metadata.put(GrpcConstants.REMOTE_ADDRESS, REMOTE_ADDR); metadata.put(GrpcConstants.LOCAL_ADDRESS, LOCAL_ADDR); Assert.assertNotNull(Context.current() .withValue(GrpcConstants.METADATA, metadata) .attach()); CompletableFuture future = new CompletableFuture<>(); QueryRouteRequest request = QueryRouteRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .build(); Mockito.when(grpcMessagingActivity.queryRoute(Mockito.any(ProxyContext.class), Mockito.eq(request))) .thenReturn(future); QueryRouteResponse response = QueryRouteResponse.newBuilder() .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .addMessageQueues(MessageQueue.getDefaultInstance()) .build(); grpcMessagingApplication.queryRoute(request, queryRouteResponseStreamObserver); future.complete(response); await().untilAsserted(() -> { Mockito.verify(queryRouteResponseStreamObserver, Mockito.times(1)).onNext(Mockito.same(response)); }); } @Test public void testQueryRouteWithBadClientID() { Metadata metadata = new Metadata(); metadata.put(GrpcConstants.LANGUAGE, JAVA); metadata.put(GrpcConstants.REMOTE_ADDRESS, REMOTE_ADDR); metadata.put(GrpcConstants.LOCAL_ADDRESS, LOCAL_ADDR); Assert.assertNotNull(Context.current() .withValue(GrpcConstants.METADATA, metadata) .attach()); QueryRouteRequest request = QueryRouteRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .build(); grpcMessagingApplication.queryRoute(request, queryRouteResponseStreamObserver); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(QueryRouteResponse.class); await().untilAsserted(() -> { Mockito.verify(queryRouteResponseStreamObserver, Mockito.times(1)).onNext(responseArgumentCaptor.capture()); }); assertEquals(Code.CLIENT_ID_REQUIRED, responseArgumentCaptor.getValue().getStatus().getCode()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannelTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.channel; import apache.rocketmq.v2.Publishing; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.Settings; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; import org.apache.rocketmq.proxy.processor.channel.RemoteChannel; import org.apache.rocketmq.proxy.remoting.channel.RemotingChannel; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class GrpcClientChannelTest extends InitConfigTest { @Mock private ProxyRelayService proxyRelayService; @Mock private GrpcClientSettingsManager grpcClientSettingsManager; @Mock private GrpcChannelManager grpcChannelManager; private String clientId; private GrpcClientChannel grpcClientChannel; @Before public void before() throws Throwable { super.before(); this.clientId = RandomStringUtils.randomAlphabetic(10); this.grpcClientChannel = new GrpcClientChannel(proxyRelayService, grpcClientSettingsManager, grpcChannelManager, ProxyContext.create().setRemoteAddress("10.152.39.53:9768").setLocalAddress("11.193.0.1:1210"), this.clientId); } @Test public void testChannelExtendAttributeParse() { Settings clientSettings = Settings.newBuilder() .setPublishing(Publishing.newBuilder() .addTopics(Resource.newBuilder() .setName("topic") .build()) .build()) .build(); when(grpcClientSettingsManager.getRawClientSettings(eq(clientId))).thenReturn(clientSettings); RemoteChannel remoteChannel = this.grpcClientChannel.toRemoteChannel(); assertEquals(ChannelProtocolType.GRPC_V2, remoteChannel.getType()); assertEquals(clientSettings, GrpcClientChannel.parseChannelExtendAttribute(remoteChannel)); assertEquals(clientSettings, GrpcClientChannel.parseChannelExtendAttribute(this.grpcClientChannel)); assertNull(GrpcClientChannel.parseChannelExtendAttribute(mock(RemotingChannel.class))); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.client; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.FilterExpression; import apache.rocketmq.v2.FilterType; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.HeartbeatResponse; import apache.rocketmq.v2.LiteSubscriptionAction; import apache.rocketmq.v2.NotifyClientTerminationRequest; import apache.rocketmq.v2.NotifyClientTerminationResponse; import apache.rocketmq.v2.Publishing; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Subscription; import apache.rocketmq.v2.SubscriptionEntry; import apache.rocketmq.v2.SyncLiteSubscriptionRequest; import apache.rocketmq.v2.SyncLiteSubscriptionResponse; import apache.rocketmq.v2.TelemetryCommand; import apache.rocketmq.v2.ThreadStackTrace; import apache.rocketmq.v2.VerifyMessageResult; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.grpc.v2.ContextStreamObserver; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcValidator; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ClientActivityTest extends BaseActivityTest { private static final String TOPIC = "topic"; private static final String CONSUMER_GROUP = "consumerGroup"; private ClientActivity clientActivity; @Mock private GrpcChannelManager grpcChannelManagerMock; @Mock private CompletableFuture> runningInfoFutureMock; @Captor ArgumentCaptor> runningInfoArgumentCaptor; @Mock private CompletableFuture> resultFutureMock; @Captor ArgumentCaptor> resultArgumentCaptor; @Before public void before() throws Throwable { super.before(); this.clientActivity = new ClientActivity(this.messagingProcessor, this.grpcClientSettingsManager, grpcChannelManager); } protected TelemetryCommand sendProducerTelemetry(ProxyContext context) throws Throwable { return this.sendClientTelemetry( context, Settings.newBuilder() .setClientType(ClientType.PRODUCER) .setPublishing(Publishing.newBuilder() .addTopics(Resource.newBuilder().setName(TOPIC).build()) .build()) .build()).get(); } protected HeartbeatResponse sendProducerHeartbeat(ProxyContext context) throws Throwable { return this.clientActivity.heartbeat(context, HeartbeatRequest.newBuilder() .setClientType(ClientType.PRODUCER) .build()).get(); } @Test public void testProducerHeartbeat() throws Throwable { ProxyContext context = createContext(); this.sendProducerTelemetry(context); ArgumentCaptor registerProducerGroupArgumentCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor channelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doNothing().when(this.messagingProcessor).registerProducer(any(), registerProducerGroupArgumentCaptor.capture(), channelInfoArgumentCaptor.capture()); ArgumentCaptor txProducerGroupArgumentCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor txProducerTopicArgumentCaptor = ArgumentCaptor.forClass(String.class); doNothing().when(this.messagingProcessor).addTransactionSubscription(any(), txProducerGroupArgumentCaptor.capture(), txProducerTopicArgumentCaptor.capture() ); when(this.metadataService.getTopicMessageType(any(), anyString())).thenReturn(TopicMessageType.TRANSACTION); HeartbeatResponse response = this.sendProducerHeartbeat(context); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(Lists.newArrayList(TOPIC), registerProducerGroupArgumentCaptor.getAllValues()); ClientChannelInfo clientChannelInfo = channelInfoArgumentCaptor.getValue(); assertClientChannelInfo(clientChannelInfo, TOPIC); assertEquals(Lists.newArrayList(TOPIC), txProducerGroupArgumentCaptor.getAllValues()); assertEquals(Lists.newArrayList(TOPIC), txProducerTopicArgumentCaptor.getAllValues()); } protected TelemetryCommand sendConsumerTelemetry(ProxyContext context) throws Throwable { return this.sendClientTelemetry( context, Settings.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .setSubscription(Subscription.newBuilder() .setGroup(Resource.newBuilder().setName("Group").build()) .addSubscriptions(SubscriptionEntry.newBuilder() .setExpression(FilterExpression.newBuilder() .setExpression("tag") .setType(FilterType.TAG) .build()) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .build()) .build()) .build()).get(); } protected HeartbeatResponse sendConsumerHeartbeat(ProxyContext context) throws Throwable { return this.clientActivity.heartbeat(context, HeartbeatRequest.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .build()).get(); } @Test public void testConsumerHeartbeat() throws Throwable { ProxyContext context = createContext(); this.sendConsumerTelemetry(context); ArgumentCaptor> subscriptionDatasArgumentCaptor = ArgumentCaptor.forClass(Set.class); ArgumentCaptor channelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doNothing().when(this.messagingProcessor).registerConsumer(any(), anyString(), channelInfoArgumentCaptor.capture(), any(), any(), any(), subscriptionDatasArgumentCaptor.capture(), anyBoolean() ); HeartbeatResponse response = this.sendConsumerHeartbeat(context); assertEquals(Code.OK, response.getStatus().getCode()); ClientChannelInfo clientChannelInfo = channelInfoArgumentCaptor.getValue(); assertClientChannelInfo(clientChannelInfo, CONSUMER_GROUP); SubscriptionData data = subscriptionDatasArgumentCaptor.getValue().stream().findAny().get(); assertEquals("TAG", data.getExpressionType()); assertEquals("tag", data.getSubString()); } protected void assertClientChannelInfo(ClientChannelInfo clientChannelInfo, String group) { assertEquals(LanguageCode.JAVA, clientChannelInfo.getLanguage()); assertEquals(CLIENT_ID, clientChannelInfo.getClientId()); assertTrue(clientChannelInfo.getChannel() instanceof GrpcClientChannel); GrpcClientChannel channel = (GrpcClientChannel) clientChannelInfo.getChannel(); assertEquals(REMOTE_ADDR, channel.getRemoteAddress()); assertEquals(LOCAL_ADDR, channel.getLocalAddress()); } @Test public void testProducerNotifyClientTermination() throws Throwable { ProxyContext context = createContext(); when(this.grpcClientSettingsManager.removeAndGetClientSettings(any())).thenReturn(Settings.newBuilder() .setClientType(ClientType.PRODUCER) .setPublishing(Publishing.newBuilder() .addTopics(Resource.newBuilder().setName(TOPIC).build()) .build()) .build()); ArgumentCaptor channelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doNothing().when(this.messagingProcessor).unRegisterProducer(any(), anyString(), channelInfoArgumentCaptor.capture()); when(this.metadataService.getTopicMessageType(any(), anyString())).thenReturn(TopicMessageType.NORMAL); this.sendProducerTelemetry(context); this.sendProducerHeartbeat(context); NotifyClientTerminationResponse response = this.clientActivity.notifyClientTermination( context, NotifyClientTerminationRequest.newBuilder() .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); ClientChannelInfo clientChannelInfo = channelInfoArgumentCaptor.getValue(); assertClientChannelInfo(clientChannelInfo, TOPIC); } @Test public void testConsumerNotifyClientTermination() throws Throwable { ProxyContext context = createContext(); when(this.grpcClientSettingsManager.removeAndGetClientSettings(any())).thenReturn(Settings.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .build()); ArgumentCaptor channelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doNothing().when(this.messagingProcessor).unRegisterConsumer(any(), anyString(), channelInfoArgumentCaptor.capture()); this.sendConsumerTelemetry(context); this.sendConsumerHeartbeat(context); NotifyClientTerminationResponse response = this.clientActivity.notifyClientTermination( context, NotifyClientTerminationRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); ClientChannelInfo clientChannelInfo = channelInfoArgumentCaptor.getValue(); assertClientChannelInfo(clientChannelInfo, CONSUMER_GROUP); } @Test public void testErrorConsumerGroupName() throws Throwable { ProxyContext context = createContext(); try { this.sendClientTelemetry( context, Settings.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .setSubscription(Subscription.newBuilder() .addSubscriptions(SubscriptionEntry.newBuilder() .setExpression(FilterExpression.newBuilder() .setExpression("tag") .setType(FilterType.TAG) .build()) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .build()) .build()) .build()).get(); fail(); } catch (ExecutionException e) { StatusRuntimeException exception = (StatusRuntimeException) e.getCause(); assertEquals(Status.Code.INVALID_ARGUMENT, exception.getStatus().getCode()); } } @Test public void testErrorProducerConfig() throws Throwable { ProxyContext context = createContext(); try { this.sendClientTelemetry( context, Settings.newBuilder() .setClientType(ClientType.PRODUCER) .setPublishing(Publishing.newBuilder() .addTopics(Resource.newBuilder().setName("()").build()) .build()) .build()).get(); fail(); } catch (ExecutionException e) { StatusRuntimeException exception = (StatusRuntimeException) e.getCause(); assertEquals(Status.Code.INVALID_ARGUMENT, exception.getStatus().getCode()); } } @Test public void testEmptySettings() throws Throwable { ProxyContext context = createContext(); try { this.sendClientTelemetry( context, Settings.getDefaultInstance()).get(); fail(); } catch (ExecutionException e) { StatusRuntimeException exception = (StatusRuntimeException) e.getCause(); assertEquals(Status.Code.INVALID_ARGUMENT, exception.getStatus().getCode()); } } @Test public void testEmptyProducerSettings() throws Throwable { ProxyContext context = createContext(); TelemetryCommand command = this.sendClientTelemetry( context, Settings.newBuilder() .setClientType(ClientType.PRODUCER) .setPublishing(Publishing.getDefaultInstance()) .build()).get(); assertTrue(command.hasSettings()); assertTrue(command.getSettings().hasPublishing()); } @Test public void testReportThreadStackTrace() { this.clientActivity = new ClientActivity(this.messagingProcessor, this.grpcClientSettingsManager, grpcChannelManagerMock); String jstack = "jstack"; String nonce = "123"; when(grpcChannelManagerMock.getAndRemoveResponseFuture(anyString())).thenReturn((CompletableFuture) runningInfoFutureMock); ProxyContext context = createContext(); ContextStreamObserver streamObserver = clientActivity.telemetry(new StreamObserver() { @Override public void onNext(TelemetryCommand value) { } @Override public void onError(Throwable t) { } @Override public void onCompleted() { } }); streamObserver.onNext(context, TelemetryCommand.newBuilder() .setThreadStackTrace(ThreadStackTrace.newBuilder() .setThreadStackTrace(jstack) .setNonce(nonce) .build()) .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); verify(runningInfoFutureMock, times(1)).complete(runningInfoArgumentCaptor.capture()); ProxyRelayResult result = runningInfoArgumentCaptor.getValue(); assertThat(result.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(result.getResult().getJstack()).isEqualTo(jstack); } @Test public void testReportVerifyMessageResult() { this.clientActivity = new ClientActivity(this.messagingProcessor, this.grpcClientSettingsManager, grpcChannelManagerMock); String nonce = "123"; when(grpcChannelManagerMock.getAndRemoveResponseFuture(anyString())).thenReturn((CompletableFuture) resultFutureMock); ProxyContext context = createContext(); ContextStreamObserver streamObserver = clientActivity.telemetry(new StreamObserver() { @Override public void onNext(TelemetryCommand value) { } @Override public void onError(Throwable t) { } @Override public void onCompleted() { } }); streamObserver.onNext(context, TelemetryCommand.newBuilder() .setVerifyMessageResult(VerifyMessageResult.newBuilder() .setNonce(nonce) .build()) .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) .build()); verify(resultFutureMock, times(1)).complete(resultArgumentCaptor.capture()); ProxyRelayResult result = resultArgumentCaptor.getValue(); assertThat(result.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(result.getResult().getConsumeResult()).isEqualTo(CMResult.CR_SUCCESS); } protected CompletableFuture sendClientTelemetry(ProxyContext ctx, Settings settings) { when(grpcClientSettingsManager.getClientSettings(any())).thenReturn(settings); CompletableFuture future = new CompletableFuture<>(); StreamObserver responseObserver = new StreamObserver() { @Override public void onNext(TelemetryCommand value) { future.complete(value); } @Override public void onError(Throwable t) { future.completeExceptionally(t); } @Override public void onCompleted() { } }; ContextStreamObserver requestObserver = this.clientActivity.telemetry(responseObserver); requestObserver.onNext(ctx, TelemetryCommand.newBuilder() .setSettings(settings) .build()); return future; } @Test public void testSyncLiteSubscription_Success() { ProxyContext proxyContext = createContext(); proxyContext.setClientID("client-id"); Resource topic = Resource.newBuilder().setName("test-topic").build(); Resource group = Resource.newBuilder().setName("test-group").build(); SyncLiteSubscriptionRequest request = SyncLiteSubscriptionRequest.newBuilder() .setTopic(topic) .setGroup(group) .setAction(LiteSubscriptionAction.PARTIAL_ADD) .addAllLiteTopicSet(java.util.Collections.emptyList()) .setVersion(1L) .build(); when(messagingProcessor.syncLiteSubscription(any(), any(LiteSubscriptionDTO.class), anyLong())) .thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture future = clientActivity.syncLiteSubscription(proxyContext, request); SyncLiteSubscriptionResponse response = future.join(); assertEquals(Code.OK, response.getStatus().getCode()); } @Test public void testSyncLiteSubscription_ValidationFailure() { ProxyContext proxyContext = createContext(); Resource topic = Resource.newBuilder().setName("test-topic").build(); Resource group = Resource.newBuilder().setName("test-group").build(); SyncLiteSubscriptionRequest request = SyncLiteSubscriptionRequest.newBuilder() .setTopic(topic) .setGroup(group) .build(); // Mock the GrpcValidator singleton GrpcValidator mockValidator = mock(GrpcValidator.class); try (MockedStatic mocked = mockStatic(GrpcValidator.class)) { mocked.when(GrpcValidator::getInstance).thenReturn(mockValidator); doThrow(new IllegalArgumentException("Invalid topic")) .when(mockValidator).validateTopicAndConsumerGroup(topic, group); CompletableFuture future = clientActivity.syncLiteSubscription(proxyContext, request); assertTrue(future.isCompletedExceptionally()); } } @Test public void testSyncLiteSubscription_ProcessingFailure() { ProxyContext proxyContext = createContext(); proxyContext.setClientID("client-id"); Resource topic = Resource.newBuilder().setName("test-topic").build(); Resource group = Resource.newBuilder().setName("test-group").build(); SyncLiteSubscriptionRequest request = SyncLiteSubscriptionRequest.newBuilder() .setTopic(topic) .setGroup(group) .setAction(LiteSubscriptionAction.PARTIAL_ADD) .addAllLiteTopicSet(java.util.Collections.emptyList()) .setVersion(1L) .build(); CompletableFuture failedFuture = new CompletableFuture<>(); failedFuture.completeExceptionally(new RuntimeException("Processing failed")); when(messagingProcessor.syncLiteSubscription(any(), any(LiteSubscriptionDTO.class), anyLong())) .thenReturn(failedFuture); CompletableFuture future = clientActivity.syncLiteSubscription(proxyContext, request); assertTrue(future.isCompletedExceptionally()); } @Test public void testSyncLiteSubscription_NullContext() { Resource topic = Resource.newBuilder().setName("test-topic").build(); Resource group = Resource.newBuilder().setName("test-group").build(); SyncLiteSubscriptionRequest request = SyncLiteSubscriptionRequest.newBuilder() .setTopic(topic) .setGroup(group) .build(); CompletableFuture future = clientActivity.syncLiteSubscription(null, request); assertTrue(future.isCompletedExceptionally()); } @Test public void testSyncLiteSubscription_NullRequest() { ProxyContext proxyContext = createContext(); CompletableFuture future = clientActivity.syncLiteSubscription(proxyContext, null); assertTrue(future.isCompletedExceptionally()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.CustomizedBackoff; import apache.rocketmq.v2.ExponentialBackoff; import apache.rocketmq.v2.Publishing; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.RetryPolicy; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Subscription; import apache.rocketmq.v2.SubscriptionEntry; import com.google.protobuf.util.Durations; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.proxy.common.ContextVariable; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.remoting.protocol.subscription.CustomizedRetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.ExponentialRetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.GroupRetryPolicyType; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class GrpcClientSettingsManagerTest extends BaseActivityTest { private final ProxyContext ctx = ProxyContext.create(); private final String clientId = "testClientId"; @Before public void before() throws Throwable { super.before(); grpcClientSettingsManager = spy(new GrpcClientSettingsManager(messagingProcessor)); } @Test public void testGetProducerData() { ProxyContext context = ProxyContext.create().withVal(ContextVariable.CLIENT_ID, CLIENT_ID); this.grpcClientSettingsManager.updateClientSettings(context, CLIENT_ID, Settings.newBuilder() .setBackoffPolicy(RetryPolicy.getDefaultInstance()) .setPublishing(Publishing.getDefaultInstance()) .build()); Settings settings = this.grpcClientSettingsManager.getClientSettings(context); assertNotEquals(settings.getBackoffPolicy(), settings.getBackoffPolicy().getDefaultInstanceForType()); assertNotEquals(settings.getPublishing(), settings.getPublishing().getDefaultInstanceForType()); } @Test public void testGetSubscriptionData() { ProxyContext context = ProxyContext.create().withVal(ContextVariable.CLIENT_ID, CLIENT_ID); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); when(this.messagingProcessor.getSubscriptionGroupConfig(any(), any())) .thenReturn(subscriptionGroupConfig); this.grpcClientSettingsManager.updateClientSettings(context, CLIENT_ID, Settings.newBuilder() .setSubscription(Subscription.newBuilder() .setGroup(Resource.newBuilder().setName("group").build()) .build()) .build()); Settings settings = this.grpcClientSettingsManager.getClientSettings(context); assertEquals(settings.getBackoffPolicy(), this.grpcClientSettingsManager.createDefaultConsumerSettingsBuilder().build().getBackoffPolicy()); subscriptionGroupConfig.setRetryMaxTimes(3); subscriptionGroupConfig.getGroupRetryPolicy().setType(GroupRetryPolicyType.CUSTOMIZED); subscriptionGroupConfig.getGroupRetryPolicy().setCustomizedRetryPolicy(new CustomizedRetryPolicy(new long[] {1000})); settings = this.grpcClientSettingsManager.getClientSettings(context); assertEquals(RetryPolicy.newBuilder() .setMaxAttempts(4) .setCustomizedBackoff(CustomizedBackoff.newBuilder() .addNext(Durations.fromSeconds(1)) .build()) .build(), settings.getBackoffPolicy()); subscriptionGroupConfig.setRetryMaxTimes(10); subscriptionGroupConfig.getGroupRetryPolicy().setType(GroupRetryPolicyType.EXPONENTIAL); subscriptionGroupConfig.getGroupRetryPolicy().setExponentialRetryPolicy(new ExponentialRetryPolicy(1000, 2000, 3)); settings = this.grpcClientSettingsManager.getClientSettings(context); assertEquals(RetryPolicy.newBuilder() .setMaxAttempts(11) .setExponentialBackoff(ExponentialBackoff.newBuilder() .setMax(Durations.fromSeconds(2)) .setInitial(Durations.fromSeconds(1)) .setMultiplier(3) .build()) .build(), settings.getBackoffPolicy()); Settings settings1 = this.grpcClientSettingsManager.removeAndGetClientSettings(context); assertEquals(settings, settings1); assertNull(this.grpcClientSettingsManager.getClientSettings(context)); assertNull(this.grpcClientSettingsManager.removeAndGetClientSettings(context)); } @Test public void testOfflineClientLiteSubscription_SettingsNullAndNoCachedSettings() { doReturn(null).when(grpcClientSettingsManager).getRawClientSettings(anyString()); grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, null); verify(messagingProcessor, never()).syncLiteSubscription(any(), any(), anyLong()); } @Test public void testOfflineClientLiteSubscription_SettingsNull_CachedSettingsNotLite() { Settings cachedSettings = Settings.newBuilder() .setClientType(ClientType.PRODUCER) .build(); doReturn(cachedSettings).when(grpcClientSettingsManager).getRawClientSettings(anyString()); grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, null); verify(messagingProcessor, never()).syncLiteSubscription(any(), any(), anyLong()); } @Test public void testOfflineClientLiteSubscription_SettingsNotNull_NotLiteConsumer() { Settings settings = Settings.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .build(); grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, settings); verify(messagingProcessor, never()).syncLiteSubscription(any(), any(), anyLong()); } @Test public void testOfflineClientLiteSubscription_ValidLiteConsumer_Success() { Subscription subscription = Subscription.newBuilder() .setGroup(Resource.newBuilder().setName("testGroup").build()) .addSubscriptions(SubscriptionEntry.newBuilder() .setTopic(Resource.newBuilder().setName("testTopic").build()) .build()) .build(); Settings settings = Settings.newBuilder() .setClientType(ClientType.LITE_PUSH_CONSUMER) .setSubscription(subscription) .build(); when(messagingProcessor.syncLiteSubscription(any(), any(LiteSubscriptionDTO.class), anyLong())) .thenReturn(CompletableFuture.completedFuture(null)); grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, settings); verify(messagingProcessor, times(1)).syncLiteSubscription(any(), any(LiteSubscriptionDTO.class), anyLong()); } @Test public void testOfflineClientLiteSubscription_ValidLiteConsumer_SyncThrowsException() { Subscription subscription = Subscription.newBuilder() .setGroup(Resource.newBuilder().setName("testGroup").build()) .addSubscriptions(SubscriptionEntry.newBuilder() .setTopic(Resource.newBuilder().setName("testTopic").build()) .build()) .build(); Settings settings = Settings.newBuilder() .setClientType(ClientType.LITE_PUSH_CONSUMER) .setSubscription(subscription) .build(); CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new RuntimeException("Simulated error")); when(messagingProcessor.syncLiteSubscription(any(), any(LiteSubscriptionDTO.class), anyLong())) .thenReturn(future); grpcClientSettingsManager.offlineClientLiteSubscription(ctx, clientId, settings); verify(messagingProcessor, times(1)).syncLiteSubscription(any(), any(LiteSubscriptionDTO.class), anyLong()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.MessageType; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageExt; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class GrpcConverterTest { @Test public void testBuildMessageQueue() { String topic = "topic"; String brokerName = "brokerName"; int queueId = 1; MessageExt messageExt = new MessageExt(); messageExt.setQueueId(queueId); messageExt.setTopic(topic); MessageQueue messageQueue = GrpcConverter.getInstance().buildMessageQueue(messageExt, brokerName); assertThat(messageQueue.getTopic().getName()).isEqualTo(topic); assertThat(messageQueue.getBroker().getName()).isEqualTo(brokerName); assertThat(messageQueue.getId()).isEqualTo(queueId); } @Test public void testBuildMessageWithLiteTopic() { final String topic = "test-topic"; final String liteTopic = "test-lite-topic"; // Build a message with lite topic properties MessageExt messageExt = new MessageExt(); messageExt.setTopic(topic); messageExt.setBody("test-body".getBytes(StandardCharsets.UTF_8)); messageExt.setQueueId(1); messageExt.setQueueOffset(100L); messageExt.setBornTimestamp(System.currentTimeMillis()); messageExt.setStoreTimestamp(System.currentTimeMillis()); messageExt.setBornHost(new InetSocketAddress("127.0.0.1", 1234)); messageExt.setStoreHost(new InetSocketAddress("127.0.0.1", 5678)); messageExt.setReconsumeTimes(0); messageExt.setMsgId("test-msg-id"); // Set lite topic property MessageAccessor.setLiteTopic(messageExt, liteTopic); // Convert message GrpcConverter grpcConverter = GrpcConverter.getInstance(); apache.rocketmq.v2.Message grpcMessage = grpcConverter.buildMessage(messageExt); // Verify basic properties assertNotNull(grpcMessage); assertEquals(topic, grpcMessage.getTopic().getName()); assertEquals("test-body", grpcMessage.getBody().toString(StandardCharsets.UTF_8)); // Verify lite topic in system properties assertNotNull(grpcMessage.getSystemProperties()); assertTrue(grpcMessage.getSystemProperties().hasLiteTopic()); assertEquals(liteTopic, grpcMessage.getSystemProperties().getLiteTopic()); // Verify message type is LITE assertEquals(MessageType.LITE, grpcMessage.getSystemProperties().getMessageType()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.common; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; public class GrpcValidatorTest { private GrpcValidator grpcValidator; @Before public void before() { this.grpcValidator = GrpcValidator.getInstance(); } @Test public void testValidateTopic() { assertThrows(GrpcProxyException.class, () -> grpcValidator.validateTopic("")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateTopic("rmq_sys_xxxx")); grpcValidator.validateTopic("topicName"); } @Test public void testValidateConsumerGroup() { assertThrows(GrpcProxyException.class, () -> grpcValidator.validateConsumerGroup("")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateConsumerGroup("CID_RMQ_SYS_xxxx")); grpcValidator.validateConsumerGroup("consumerGroupName"); } @Test public void testValidateLiteTopic_Null() { assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic(null)); } @Test public void testValidateLiteTopic_Blank() { assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic(" ")); } @Test public void testValidateLiteTopic_TooLong() { try (MockedStatic mockedConfig = mockStatic(ConfigurationManager.class)) { ProxyConfig proxyConfig = mock(ProxyConfig.class); when(proxyConfig.getMaxLiteTopicSize()).thenReturn(5); mockedConfig.when(ConfigurationManager::getProxyConfig).thenReturn(proxyConfig); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("toolongtopic")); } } @Test public void testValidateLiteTopic_IllegalCharacter() { try (MockedStatic mockedConfig = mockStatic(ConfigurationManager.class)) { ProxyConfig proxyConfig = mock(ProxyConfig.class); when(proxyConfig.getMaxLiteTopicSize()).thenReturn(100); mockedConfig.when(ConfigurationManager::getProxyConfig).thenReturn(proxyConfig); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("invalid@topic")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("invalid$topic")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("invalid%topic")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("invalid\ttopic")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("invalid\ntopic")); assertThrows(GrpcProxyException.class, () -> grpcValidator.validateLiteTopic("invalid\0topic")); } } @Test public void testValidateLiteTopic_Valid() { try (MockedStatic mockedConfig = mockStatic(ConfigurationManager.class)) { ProxyConfig proxyConfig = mock(ProxyConfig.class); when(proxyConfig.getMaxLiteTopicSize()).thenReturn(64); mockedConfig.when(ConfigurationManager::getProxyConfig).thenReturn(proxyConfig); grpcValidator.validateLiteTopic("Valid_Topic-123"); grpcValidator.validateLiteTopic(RandomStringUtils.randomAlphanumeric(64)); grpcValidator.validateLiteTopic(RandomStringUtils.randomAlphanumeric(63)); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.AckMessageEntry; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.AckMessageResponse; import apache.rocketmq.v2.AckMessageResultEntry; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Resource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.processor.BatchAckResult; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; import org.junit.Before; import org.junit.Test; import org.mockito.stubbing.Answer; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; public class AckMessageActivityTest extends BaseActivityTest { private AckMessageActivity ackMessageActivity; private static final String TOPIC = "topic"; private static final String GROUP = "group"; @Before public void before() throws Throwable { super.before(); this.ackMessageActivity = new AckMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testAckMessage() throws Throwable { ConfigurationManager.getProxyConfig().setEnableBatchAck(false); String msg1 = "msg1"; String msg2 = "msg2"; String msg3 = "msg3"; when(this.messagingProcessor.ackMessage(any(), any(), eq(msg1), anyString(), anyString(), any())) .thenThrow(new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired")); AckResult msg2AckResult = new AckResult(); msg2AckResult.setStatus(AckStatus.OK); when(this.messagingProcessor.ackMessage(any(), any(), eq(msg2), anyString(), anyString(), any())) .thenReturn(CompletableFuture.completedFuture(msg2AckResult)); AckResult msg3AckResult = new AckResult(); msg3AckResult.setStatus(AckStatus.NO_EXIST); when(this.messagingProcessor.ackMessage(any(), any(), eq(msg3), anyString(), anyString(), any())) .thenReturn(CompletableFuture.completedFuture(msg3AckResult)); { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(msg1) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000)) .build()) .build() ).get(); assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getStatus().getCode()); } { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(msg2) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000)) .build()) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); } { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(msg3) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000)) .build()) .build() ).get(); assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode()); } { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(msg1) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000)) .build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(msg2) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(msg3) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .build() ).get(); assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode()); assertEquals(3, response.getEntriesCount()); assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getEntries(0).getStatus().getCode()); assertEquals(Code.OK, response.getEntries(1).getStatus().getCode()); assertEquals(Code.INTERNAL_SERVER_ERROR, response.getEntries(2).getStatus().getCode()); } } @Test public void testAckMessageInBatch() throws Throwable { ConfigurationManager.getProxyConfig().setEnableBatchAck(true); String successMessageId = "msg1"; String notOkMessageId = "msg2"; String exceptionMessageId = "msg3"; doAnswer((Answer>>) invocation -> { List receiptHandleMessageList = invocation.getArgument(1, List.class); List batchAckResultList = new ArrayList<>(); for (ReceiptHandleMessage receiptHandleMessage : receiptHandleMessageList) { BatchAckResult batchAckResult; if (receiptHandleMessage.getMessageId().equals(successMessageId)) { AckResult ackResult = new AckResult(); ackResult.setStatus(AckStatus.OK); batchAckResult = new BatchAckResult(receiptHandleMessage, ackResult); } else if (receiptHandleMessage.getMessageId().equals(notOkMessageId)) { AckResult ackResult = new AckResult(); ackResult.setStatus(AckStatus.NO_EXIST); batchAckResult = new BatchAckResult(receiptHandleMessage, ackResult); } else { batchAckResult = new BatchAckResult(receiptHandleMessage, new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "")); } batchAckResultList.add(batchAckResult); } return CompletableFuture.completedFuture(batchAckResultList); }).when(this.messagingProcessor).batchAckMessage(any(), anyList(), anyString(), anyString()); { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(successMessageId) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); } { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(notOkMessageId) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .build() ).get(); assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode()); } { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(exceptionMessageId) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .build() ).get(); assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getStatus().getCode()); } { AckMessageResponse response = this.ackMessageActivity.ackMessage( createContext(), AckMessageRequest.newBuilder() .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(GROUP).build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(successMessageId) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(notOkMessageId) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .addEntries(AckMessageEntry.newBuilder() .setMessageId(exceptionMessageId) .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build()) .build() ).get(); assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode()); assertEquals(3, response.getEntriesCount()); Map msgCode = new HashMap<>(); for (AckMessageResultEntry entry : response.getEntriesList()) { msgCode.put(entry.getMessageId(), entry.getStatus().getCode()); } assertEquals(Code.OK, msgCode.get(successMessageId)); assertEquals(Code.INTERNAL_SERVER_ERROR, msgCode.get(notOkMessageId)); assertEquals(Code.INVALID_RECEIPT_HANDLE, msgCode.get(exceptionMessageId)); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ChangeInvisibleDurationResponse; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Resource; import com.google.protobuf.util.Durations; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; public class ChangeInvisibleDurationActivityTest extends BaseActivityTest { private static final String TOPIC = "topic"; private static final String CONSUMER_GROUP = "consumerGroup"; private ChangeInvisibleDurationActivity changeInvisibleDurationActivity; @Before public void before() throws Throwable { super.before(); this.changeInvisibleDurationActivity = new ChangeInvisibleDurationActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testChangeInvisibleDurationActivity() throws Throwable { String newHandle = "newHandle"; ArgumentCaptor invisibleTimeArgumentCaptor = ArgumentCaptor.forClass(Long.class); AckResult ackResult = new AckResult(); ackResult.setExtraInfo(newHandle); ackResult.setStatus(AckStatus.OK); when(this.messagingProcessor.changeInvisibleTime( any(), any(), anyString(), anyString(), anyString(), invisibleTimeArgumentCaptor.capture() )).thenReturn(CompletableFuture.completedFuture(ackResult)); ChangeInvisibleDurationResponse response = this.changeInvisibleDurationActivity.changeInvisibleDuration( createContext(), ChangeInvisibleDurationRequest.newBuilder() .setInvisibleDuration(Durations.fromSeconds(3)) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageId("msgId") .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(TimeUnit.SECONDS.toMillis(3), invisibleTimeArgumentCaptor.getValue().longValue()); assertEquals(newHandle, response.getReceiptHandle()); } @Test public void testChangeInvisibleDurationActivityWhenHasMappingHandle() throws Throwable { String newHandle = "newHandle"; ArgumentCaptor invisibleTimeArgumentCaptor = ArgumentCaptor.forClass(Long.class); AckResult ackResult = new AckResult(); ackResult.setExtraInfo(newHandle); ackResult.setStatus(AckStatus.OK); String savedHandleStr = buildReceiptHandle("topic", System.currentTimeMillis(),3000); ArgumentCaptor receiptHandleCaptor = ArgumentCaptor.forClass(ReceiptHandle.class); when(this.messagingProcessor.changeInvisibleTime( any(), receiptHandleCaptor.capture(), anyString(), anyString(), anyString(), invisibleTimeArgumentCaptor.capture() )).thenReturn(CompletableFuture.completedFuture(ackResult)); when(messagingProcessor.removeReceiptHandle(any(), any(), anyString(), anyString(), anyString())) .thenReturn(new MessageReceiptHandle("group", "topic", 0, savedHandleStr, "msgId", 0, 0)); ChangeInvisibleDurationResponse response = this.changeInvisibleDurationActivity.changeInvisibleDuration( createContext(), ChangeInvisibleDurationRequest.newBuilder() .setInvisibleDuration(Durations.fromSeconds(3)) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageId("msgId") .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(TimeUnit.SECONDS.toMillis(3), invisibleTimeArgumentCaptor.getValue().longValue()); assertEquals(savedHandleStr, receiptHandleCaptor.getValue().getReceiptHandle()); assertEquals(newHandle, response.getReceiptHandle()); } @Test public void testChangeInvisibleDurationActivityFailed() throws Throwable { ArgumentCaptor invisibleTimeArgumentCaptor = ArgumentCaptor.forClass(Long.class); AckResult ackResult = new AckResult(); ackResult.setStatus(AckStatus.NO_EXIST); when(this.messagingProcessor.changeInvisibleTime( any(), any(), anyString(), anyString(), anyString(), invisibleTimeArgumentCaptor.capture() )).thenReturn(CompletableFuture.completedFuture(ackResult)); ChangeInvisibleDurationResponse response = this.changeInvisibleDurationActivity.changeInvisibleDuration( createContext(), ChangeInvisibleDurationRequest.newBuilder() .setInvisibleDuration(Durations.fromSeconds(3)) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageId("msgId") .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build() ).get(); assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode()); assertEquals(TimeUnit.SECONDS.toMillis(3), invisibleTimeArgumentCaptor.getValue().longValue()); } @Test public void testChangeInvisibleDurationInvisibleTimeTooSmall() throws Throwable { try { this.changeInvisibleDurationActivity.changeInvisibleDuration( createContext(), ChangeInvisibleDurationRequest.newBuilder() .setInvisibleDuration(Durations.fromSeconds(-1)) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageId("msgId") .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build() ).get(); } catch (ExecutionException executionException) { GrpcProxyException exception = (GrpcProxyException) executionException.getCause(); assertEquals(Code.ILLEGAL_INVISIBLE_TIME, exception.getCode()); } } @Test public void testChangeInvisibleDurationInvisibleTimeTooLarge() throws Throwable { try { this.changeInvisibleDurationActivity.changeInvisibleDuration( createContext(), ChangeInvisibleDurationRequest.newBuilder() .setInvisibleDuration(Durations.fromDays(7)) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageId("msgId") .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000)) .build() ).get(); } catch (ExecutionException executionException) { GrpcProxyException exception = (GrpcProxyException) executionException.getCause(); assertEquals(Code.ILLEGAL_INVISIBLE_TIME, exception.getCode()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.FilterExpression; import apache.rocketmq.v2.FilterType; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.Settings; import com.google.protobuf.Duration; import com.google.protobuf.util.Durations; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class ReceiveMessageActivityTest extends BaseActivityTest { protected static final String BROKER_NAME = "broker"; protected static final String CLUSTER_NAME = "cluster"; protected static final String BROKER_ADDR = "127.0.0.1:10911"; private static final String TOPIC = "topic"; private static final String CONSUMER_GROUP = "consumerGroup"; private ReceiveMessageActivity receiveMessageActivity; @Before public void before() throws Throwable { super.before(); ConfigurationManager.getProxyConfig().setGrpcClientConsumerMinLongPollingTimeoutMillis(0); this.receiveMessageActivity = new ReceiveMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testReceiveMessagePollingTime() { StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor.capture()); ArgumentCaptor pollTimeCaptor = ArgumentCaptor.forClass(Long.class); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder() .setRequestTimeout(Durations.fromSeconds(3)) .build()); when(this.messagingProcessor.popMessage(any(), any(), anyString(), anyString(), anyInt(), anyLong(), pollTimeCaptor.capture(), anyInt(), any(), anyBoolean(), any(), isNull(), anyLong())) .thenReturn(CompletableFuture.completedFuture(new PopResult(PopStatus.NO_NEW_MSG, Collections.emptyList()))); ProxyContext context = createContext(); context.setRemainingMs(1L); this.receiveMessageActivity.receiveMessage( context, ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(true) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("*") .build()) .build(), receiveStreamObserver ); assertEquals(Code.MESSAGE_NOT_FOUND, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor.getAllValues())); assertEquals(0L, pollTimeCaptor.getValue().longValue()); } @Test public void testReceiveMessageWithIllegalPollingTime() { StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); ArgumentCaptor responseArgumentCaptor0 = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor0.capture()); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder().getDefaultInstanceForType()); final ProxyContext context = createContext(); context.setClientVersion("5.0.2"); context.setRemainingMs(-1L); final ReceiveMessageRequest request = ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(false) .setLongPollingTimeout(Duration.newBuilder().setSeconds(20).build()) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("*") .build()) .build(); this.receiveMessageActivity.receiveMessage( context, request, receiveStreamObserver ); assertEquals(Code.BAD_REQUEST, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor0.getAllValues())); ArgumentCaptor responseArgumentCaptor1 = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor1.capture()); context.setClientVersion("5.0.3"); this.receiveMessageActivity.receiveMessage( context, request, receiveStreamObserver ); assertEquals(Code.ILLEGAL_POLLING_TIME, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor1.getAllValues())); } @Test public void testReceiveMessageIllegalFilter() { StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor.capture()); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder().getDefaultInstanceForType()); this.receiveMessageActivity.receiveMessage( createContext(), ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(true) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.SQL) .setExpression("") .build()) .build(), receiveStreamObserver ); assertEquals(Code.ILLEGAL_FILTER_EXPRESSION, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor.getAllValues())); } @Test public void testReceiveMessageIllegalInvisibleTimeTooSmall() { StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor.capture()); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder().getDefaultInstanceForType()); this.receiveMessageActivity.receiveMessage( createContext(), ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(false) .setInvisibleDuration(Durations.fromSeconds(0)) .build(), receiveStreamObserver ); assertEquals(Code.ILLEGAL_INVISIBLE_TIME, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor.getAllValues())); } @Test public void testReceiveMessageIllegalInvisibleTimeTooLarge() { StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor.capture()); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder().getDefaultInstanceForType()); this.receiveMessageActivity.receiveMessage( createContext(), ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(false) .setInvisibleDuration(Durations.fromDays(7)) .build(), receiveStreamObserver ); assertEquals(Code.ILLEGAL_INVISIBLE_TIME, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor.getAllValues())); } @Test public void testReceiveMessageAddReceiptHandle() { ConfigurationManager.getProxyConfig().setEnableProxyAutoRenew(true); StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); doNothing().when(receiveStreamObserver).onNext(any()); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder().getDefaultInstanceForType()); MessageExt messageExt1 = new MessageExt(); String msgId1 = "msgId1"; String popCk1 = "0 0 60000 0 0 broker 0 0 0"; messageExt1.setTopic(TOPIC); messageExt1.setMsgId(msgId1); MessageAccessor.putProperty(messageExt1, MessageConst.PROPERTY_POP_CK, popCk1); messageExt1.setBody("body1".getBytes()); MessageExt messageExt2 = new MessageExt(); String msgId2 = "msgId2"; String popCk2 = "0 0 60000 0 0 broker 0 1 1000"; messageExt2.setTopic(TOPIC); messageExt2.setMsgId(msgId2); MessageAccessor.putProperty(messageExt2, MessageConst.PROPERTY_POP_CK, popCk2); messageExt2.setBody("body2".getBytes()); PopResult popResult = new PopResult(PopStatus.FOUND, Arrays.asList(messageExt1, messageExt2)); when(this.messagingProcessor.popMessage( any(), any(), anyString(), anyString(), anyInt(), anyLong(), anyLong(), anyInt(), any(), anyBoolean(), any(), isNull(), anyLong())).thenReturn(CompletableFuture.completedFuture(popResult)); ArgumentCaptor msgIdCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor receiptHandleCaptor = ArgumentCaptor.forClass(ReceiptHandle.class); when(this.messagingProcessor.changeInvisibleTime( any(), receiptHandleCaptor.capture(), msgIdCaptor.capture(), anyString(), anyString(), anyLong(), any(), anyLong(), anyBoolean())).thenReturn(CompletableFuture.completedFuture(new AckResult())); // normal ProxyContext ctx = createContext(); this.grpcChannelManager.createChannel(ctx, ctx.getClientID()); ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(true) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("*") .build()) .build(); this.receiveMessageActivity.receiveMessage(ctx, receiveMessageRequest, receiveStreamObserver); verify(this.messagingProcessor, times(0)).changeInvisibleTime( any(), any(), anyString(), anyString(), anyString(), anyLong()); // abnormal this.grpcChannelManager.removeChannel(ctx.getClientID()); this.receiveMessageActivity.receiveMessage(ctx, receiveMessageRequest, receiveStreamObserver); verify(this.messagingProcessor, times(2)).changeInvisibleTime( any(), any(), anyString(), anyString(), anyString(), anyLong(), any(), anyLong(), anyBoolean()); assertEquals(Arrays.asList(msgId1, msgId2), msgIdCaptor.getAllValues()); assertEquals(Arrays.asList(popCk1, popCk2), receiptHandleCaptor.getAllValues().stream().map(ReceiptHandle::encode).collect(Collectors.toList())); } @Test public void testReceiveMessage() { StreamObserver receiveStreamObserver = mock(ServerCallStreamObserver.class); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(receiveStreamObserver).onNext(responseArgumentCaptor.capture()); when(this.grpcClientSettingsManager.getClientSettings(any())).thenReturn(Settings.newBuilder().getDefaultInstanceForType()); PopResult popResult = new PopResult(PopStatus.NO_NEW_MSG, new ArrayList<>()); when(this.messagingProcessor.popMessage( any(), any(), anyString(), anyString(), anyInt(), anyLong(), anyLong(), anyInt(), any(), anyBoolean(), any(), isNull(), anyLong())).thenReturn(CompletableFuture.completedFuture(popResult)); this.receiveMessageActivity.receiveMessage( createContext(), ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setAutoRenew(true) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("*") .build()) .build(), receiveStreamObserver ); assertEquals(Code.MESSAGE_NOT_FOUND, getResponseCodeFromReceiveMessageResponseList(responseArgumentCaptor.getAllValues())); } private Code getResponseCodeFromReceiveMessageResponseList(List responseList) { for (ReceiveMessageResponse response : responseList) { if (response.hasStatus()) { return response.getStatus().getCode(); } } return null; } @Test public void testReceiveMessageQueueSelector() throws Exception { TopicRouteData topicRouteData = new TopicRouteData(); List queueDatas = new ArrayList<>(); for (int i = 0; i < 2; i++) { QueueData queueData = new QueueData(); queueData.setBrokerName(BROKER_NAME + i); queueData.setReadQueueNums(1); queueData.setPerm(PermName.PERM_READ); queueDatas.add(queueData); } topicRouteData.setQueueDatas(queueDatas); List brokerDatas = new ArrayList<>(); for (int i = 0; i < 2; i++) { BrokerData brokerData = new BrokerData(); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME + i); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR); brokerData.setBrokerAddrs(brokerAddrs); brokerDatas.add(brokerData); } topicRouteData.setBrokerDatas(brokerDatas); MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null); ReceiveMessageActivity.ReceiveMessageQueueSelector selector = new ReceiveMessageActivity.ReceiveMessageQueueSelector(""); AddressableMessageQueue firstSelect = selector.select(ProxyContext.create(), messageQueueView); AddressableMessageQueue secondSelect = selector.select(ProxyContext.create(), messageQueueView); AddressableMessageQueue thirdSelect = selector.select(ProxyContext.create(), messageQueueView); assertEquals(firstSelect, thirdSelect); assertNotEquals(firstSelect, secondSelect); for (int i = 0; i < 2; i++) { ReceiveMessageActivity.ReceiveMessageQueueSelector selectorBrokerName = new ReceiveMessageActivity.ReceiveMessageQueueSelector(BROKER_NAME + i); assertEquals(BROKER_NAME + i, selectorBrokerName.select(ProxyContext.create(), messageQueueView).getBrokerName()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.consumer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.FilterExpression; import apache.rocketmq.v2.FilterType; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.Resource; import io.grpc.stub.StreamObserver; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import java.lang.reflect.Method; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class ReceiveMessageResponseStreamWriterTest extends BaseActivityTest { private static final String TOPIC = "topic"; private static final String CONSUMER_GROUP = "consumerGroup"; private ReceiveMessageResponseStreamWriter writer; private StreamObserver streamObserver; @Before public void before() throws Throwable { super.before(); this.streamObserver = mock(StreamObserver.class); this.writer = new ReceiveMessageResponseStreamWriter(this.messagingProcessor, this.streamObserver); } @Test public void testWriteMessage() { ArgumentCaptor changeInvisibleTimeMsgIdCaptor = ArgumentCaptor.forClass(String.class); doReturn(CompletableFuture.completedFuture(mock(AckResult.class))).when(this.messagingProcessor) .changeInvisibleTime(any(), any(), changeInvisibleTimeMsgIdCaptor.capture(), anyString(), anyString(), anyLong(), any(), anyLong(), anyBoolean()); ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); AtomicInteger onNextCallNum = new AtomicInteger(0); doAnswer(mock -> { if (onNextCallNum.incrementAndGet() > 2) { throw new RuntimeException(); } return null; }).when(streamObserver).onNext(responseArgumentCaptor.capture()); List messageExtList = new ArrayList<>(); messageExtList.add(createMessageExt(TOPIC, "tag")); messageExtList.add(createMessageExt(TOPIC, "tag")); PopResult popResult = new PopResult(PopStatus.FOUND, messageExtList); ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("*") .build()) .build(); writer.writeAndComplete( ProxyContext.create(), receiveMessageRequest, popResult ); verify(streamObserver, times(1)).onCompleted(); verify(streamObserver, times(4)).onNext(any()); verify(this.messagingProcessor, times(1)) .changeInvisibleTime(any(), any(), anyString(), anyString(), anyString(), anyLong(), any(), anyLong(), eq(true)); assertTrue(responseArgumentCaptor.getAllValues().get(0).hasStatus()); assertEquals(Code.OK, responseArgumentCaptor.getAllValues().get(0).getStatus().getCode()); assertTrue(responseArgumentCaptor.getAllValues().get(1).hasMessage()); assertEquals(messageExtList.get(0).getMsgId(), responseArgumentCaptor.getAllValues().get(1).getMessage().getSystemProperties().getMessageId()); assertEquals(messageExtList.get(1).getMsgId(), changeInvisibleTimeMsgIdCaptor.getValue()); // case: fail to write response status at first step doThrow(new RuntimeException()).when(streamObserver).onNext(any()); writer.writeAndComplete( ProxyContext.create(), receiveMessageRequest, popResult ); verify(this.messagingProcessor, times(3)) .changeInvisibleTime(any(), any(), anyString(), anyString(), anyString(), anyLong(), any(), anyLong(), eq(true)); } @Test public void testPollingFull() { ArgumentCaptor responseArgumentCaptor = ArgumentCaptor.forClass(ReceiveMessageResponse.class); doNothing().when(streamObserver).onNext(responseArgumentCaptor.capture()); PopResult popResult = new PopResult(PopStatus.POLLING_FULL, new ArrayList<>()); writer.writeAndComplete( ProxyContext.create(), ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .setFilterExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("*") .build()) .build(), popResult ); ReceiveMessageResponse response = responseArgumentCaptor.getAllValues().stream().filter(ReceiveMessageResponse::hasStatus) .findFirst().get(); assertEquals(Code.TOO_MANY_REQUESTS, response.getStatus().getCode()); } @Test public void testNackMessageWithSuspendTrue() { ArgumentCaptor changeInvisibleTimeMsgIdCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor changeInvisibleTimeGroupCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor changeInvisibleTimeTopicCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor changeInvisibleTimeInvisibleTimeCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor changeInvisibleTimeSuspendCaptor = ArgumentCaptor.forClass(Boolean.class); doReturn(CompletableFuture.completedFuture(mock(AckResult.class))).when(this.messagingProcessor) .changeInvisibleTime(any(), any(), changeInvisibleTimeMsgIdCaptor.capture(), changeInvisibleTimeGroupCaptor.capture(), changeInvisibleTimeTopicCaptor.capture(), changeInvisibleTimeInvisibleTimeCaptor.capture(), any(), anyLong(), changeInvisibleTimeSuspendCaptor.capture()); MessageExt messageExt = createMessageExt(TOPIC, "tag"); ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder().setName(CONSUMER_GROUP).build()) .setMessageQueue(MessageQueue.newBuilder().setTopic(Resource.newBuilder().setName(TOPIC).build()).build()) .build(); // Simulate nack by calling processThrowableWhenWriteMessage using reflection // This is called when an exception occurs during message processing try { Method method = ReceiveMessageResponseStreamWriter.class.getDeclaredMethod( "processThrowableWhenWriteMessage", Throwable.class, ProxyContext.class, ReceiveMessageRequest.class, MessageExt.class); method.setAccessible(true); method.invoke(writer, new RuntimeException("Test exception"), ProxyContext.create(), receiveMessageRequest, messageExt); } catch (Exception e) { throw new RuntimeException(e); } // Verify that changeInvisibleTime was called with suspend=true verify(this.messagingProcessor, times(1)) .changeInvisibleTime(any(), any(), eq(messageExt.getMsgId()), eq(CONSUMER_GROUP), eq(TOPIC), eq(ReceiveMessageResponseStreamWriter.NACK_INVISIBLE_TIME), eq(null), eq(org.apache.rocketmq.proxy.processor.MessagingProcessor.DEFAULT_TIMEOUT_MILLS), eq(true)); assertEquals(messageExt.getMsgId(), changeInvisibleTimeMsgIdCaptor.getValue()); assertEquals(CONSUMER_GROUP, changeInvisibleTimeGroupCaptor.getValue()); assertEquals(TOPIC, changeInvisibleTimeTopicCaptor.getValue()); assertEquals(ReceiveMessageResponseStreamWriter.NACK_INVISIBLE_TIME, changeInvisibleTimeInvisibleTimeCaptor.getValue().longValue()); assertTrue("Suspend should be true for nack", changeInvisibleTimeSuspendCaptor.getValue()); } private static MessageExt createMessageExt(String topic, String tags) { String msgId = MessageClientIDSetter.createUniqID(); MessageExt messageExt = new MessageExt(); messageExt.setTopic(topic); messageExt.setTags(tags); messageExt.setBody(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); messageExt.setMsgId(msgId); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, msgId); messageExt.setCommitLogOffset(RANDOM.nextInt(Integer.MAX_VALUE)); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_POP_CK, ExtraInfoUtil.buildExtraInfo(RANDOM.nextInt(Integer.MAX_VALUE), System.currentTimeMillis(), 3000, RANDOM.nextInt(Integer.MAX_VALUE), topic, "mockBroker", RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE))); return messageExt; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.producer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest; import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse; import apache.rocketmq.v2.Resource; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; public class ForwardMessageToDLQActivityTest extends BaseActivityTest { private ForwardMessageToDLQActivity forwardMessageToDLQActivity; @Before public void before() throws Throwable { super.before(); this.forwardMessageToDLQActivity = new ForwardMessageToDLQActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testForwardMessageToDeadLetterQueue() throws Throwable { ArgumentCaptor receiptHandleCaptor = ArgumentCaptor.forClass(ReceiptHandle.class); when(this.messagingProcessor.forwardMessageToDeadLetterQueue(any(), receiptHandleCaptor.capture(), anyString(), anyString(), anyString(), any())) .thenReturn(CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""))); String handleStr = buildReceiptHandle("topic", System.currentTimeMillis(), 3000); ForwardMessageToDeadLetterQueueResponse response = this.forwardMessageToDLQActivity.forwardMessageToDeadLetterQueue( createContext(), ForwardMessageToDeadLetterQueueRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setGroup(Resource.newBuilder().setName("group").build()) .setMessageId(MessageClientIDSetter.createUniqID()) .setReceiptHandle(handleStr) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(handleStr, receiptHandleCaptor.getValue().getReceiptHandle()); } @Test public void testForwardMessageToDeadLetterQueueWhenHasMappingHandle() throws Throwable { ArgumentCaptor receiptHandleCaptor = ArgumentCaptor.forClass(ReceiptHandle.class); when(this.messagingProcessor.forwardMessageToDeadLetterQueue(any(), receiptHandleCaptor.capture(), anyString(), anyString(), anyString(), any())) .thenReturn(CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""))); String savedHandleStr = buildReceiptHandle("topic", System.currentTimeMillis(),3000); when(messagingProcessor.removeReceiptHandle(any(), any(), anyString(), anyString(), anyString())) .thenReturn(new MessageReceiptHandle("group", "topic", 0, savedHandleStr, "msgId", 0, 0)); ForwardMessageToDeadLetterQueueResponse response = this.forwardMessageToDLQActivity.forwardMessageToDeadLetterQueue( createContext(), ForwardMessageToDeadLetterQueueRequest.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setGroup(Resource.newBuilder().setName("group").build()) .setMessageId(MessageClientIDSetter.createUniqID()) .setReceiptHandle(buildReceiptHandle("topic", System.currentTimeMillis(), 3000)) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(savedHandleStr, receiptHandleCaptor.getValue().getReceiptHandle()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/RecallMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.producer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.RecallMessageResponse; import apache.rocketmq.v2.Resource; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; public class RecallMessageActivityTest extends BaseActivityTest { private RecallMessageActivity recallMessageActivity; @Before public void before() throws Throwable { super.before(); this.recallMessageActivity = new RecallMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testRecallMessage_success() { when(this.messagingProcessor.recallMessage(any(), any(), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture("msgId")); RecallMessageResponse response = this.recallMessageActivity.recallMessage( createContext(), RecallMessageRequest.newBuilder() .setRecallHandle("handle") .setTopic(Resource.newBuilder().setResourceNamespace("ns").setName("topic")) .build() ).join(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals("msgId", response.getMessageId()); } @Test public void testRecallMessage_fail() { CompletableFuture exceptionFuture = new CompletableFuture(); when(this.messagingProcessor.recallMessage(any(), any(), any(), anyLong())).thenReturn(exceptionFuture); exceptionFuture.completeExceptionally( new ProxyException(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, "info")); CompletionException exception = Assert.assertThrows(CompletionException.class, () -> { this.recallMessageActivity.recallMessage( createContext(), RecallMessageRequest.newBuilder() .setRecallHandle("handle") .setTopic(Resource.newBuilder().setResourceNamespace("ns").setName("topic")) .build() ).join(); }); Assert.assertTrue(exception.getCause() instanceof ProxyException); ProxyException cause = (ProxyException) exception.getCause(); Assert.assertEquals(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, cause.getCode()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.producer; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Encoding; import apache.rocketmq.v2.Message; import apache.rocketmq.v2.MessageType; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.SendMessageResponse; import apache.rocketmq.v2.SystemProperties; import com.google.protobuf.ByteString; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.latency.MQFaultStrategy; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import static org.apache.rocketmq.proxy.service.route.TopicRouteService.buildPenalizerByMQFaultStrategy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SendMessageActivityTest extends BaseActivityTest { protected static final String BROKER_NAME = "broker"; protected static final String BROKER_NAME2 = "broker2"; protected static final String CLUSTER_NAME = "cluster"; protected static final String BROKER_ADDR = "127.0.0.1:10911"; protected static final String BROKER_ADDR2 = "127.0.0.1:10912"; private static final String TOPIC = "topic"; private static final String CONSUMER_GROUP = "consumerGroup"; MQFaultStrategy mqFaultStrategy; private SendMessageActivity sendMessageActivity; @Before public void before() throws Throwable { super.before(); this.sendMessageActivity = new SendMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void sendMessage() throws Exception { String msgId = MessageClientIDSetter.createUniqID(); SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.SEND_OK); sendResult.setMsgId(msgId); when(this.messagingProcessor.sendMessage(any(), any(), anyString(), anyInt(), any())) .thenReturn(CompletableFuture.completedFuture(Lists.newArrayList(sendResult))); SendMessageResponse response = this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(msgId) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build()) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(msgId, response.getEntries(0).getMessageId()); } @Test public void testConvertToSendMessageResponse() { { SendMessageResponse response = this.sendMessageActivity.convertToSendMessageResponse( ProxyContext.create(), SendMessageRequest.newBuilder().build(), Lists.newArrayList(new SendResult(SendStatus.FLUSH_DISK_TIMEOUT, null, null, null, 0)) ); assertEquals(Code.MASTER_PERSISTENCE_TIMEOUT, response.getStatus().getCode()); assertEquals(Code.MASTER_PERSISTENCE_TIMEOUT, response.getEntries(0).getStatus().getCode()); } { SendMessageResponse response = this.sendMessageActivity.convertToSendMessageResponse( ProxyContext.create(), SendMessageRequest.newBuilder().build(), Lists.newArrayList(new SendResult(SendStatus.FLUSH_SLAVE_TIMEOUT, null, null, null, 0)) ); assertEquals(Code.SLAVE_PERSISTENCE_TIMEOUT, response.getStatus().getCode()); assertEquals(Code.SLAVE_PERSISTENCE_TIMEOUT, response.getEntries(0).getStatus().getCode()); } { SendMessageResponse response = this.sendMessageActivity.convertToSendMessageResponse( ProxyContext.create(), SendMessageRequest.newBuilder().build(), Lists.newArrayList(new SendResult(SendStatus.SLAVE_NOT_AVAILABLE, null, null, null, 0)) ); assertEquals(Code.HA_NOT_AVAILABLE, response.getStatus().getCode()); assertEquals(Code.HA_NOT_AVAILABLE, response.getEntries(0).getStatus().getCode()); } { SendMessageResponse response = this.sendMessageActivity.convertToSendMessageResponse( ProxyContext.create(), SendMessageRequest.newBuilder().build(), Lists.newArrayList(new SendResult(SendStatus.SEND_OK, null, null, null, 0)) ); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(Code.OK, response.getEntries(0).getStatus().getCode()); } { SendMessageResponse response = this.sendMessageActivity.convertToSendMessageResponse( ProxyContext.create(), SendMessageRequest.newBuilder().build(), Lists.newArrayList( new SendResult(SendStatus.SEND_OK, null, null, null, 0), new SendResult(SendStatus.SLAVE_NOT_AVAILABLE, null, null, null, 0) ) ); assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode()); } } @Test(expected = GrpcProxyException.class) public void testBuildErrorMessage() { this.sendMessageActivity.buildMessage(null, Lists.newArrayList( Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(MessageClientIDSetter.createUniqID()) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build(), Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC + 2) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(MessageClientIDSetter.createUniqID()) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build() ), Resource.newBuilder().setName(TOPIC).build()); } @Test public void testBuildMessage() { long deliveryTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(5); ConfigurationManager.getProxyConfig().setMessageDelayLevel("1s 5s"); ConfigurationManager.getProxyConfig().initData(); String msgId = MessageClientIDSetter.createUniqID(); org.apache.rocketmq.common.message.Message messageExt = this.sendMessageActivity.buildMessage(null, Lists.newArrayList( Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(msgId) .setQueueId(0) .setMessageType(MessageType.DELAY) .setDeliveryTimestamp(Timestamps.fromMillis(deliveryTime)) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build() ), Resource.newBuilder().setName(TOPIC).build()).get(0); assertEquals(MessageClientIDSetter.getUniqID(messageExt), msgId); assertEquals(deliveryTime, Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS))); } @Test public void testBuildMessageWithLiteTopic() { String msgId = MessageClientIDSetter.createUniqID(); String liteTopic = "build-test-lite-topic"; String topic = "build-test-topic"; org.apache.rocketmq.common.message.Message messageExt = this.sendMessageActivity.buildMessage( ProxyContext.create(), Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(msgId) .setQueueId(0) .setMessageType(MessageType.LITE) .setLiteTopic(liteTopic) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("test body")) .build(), "test-producer-group" ); assertEquals(liteTopic, messageExt.getProperty(MessageConst.PROPERTY_LITE_TOPIC)); assertNull(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)); } @Test public void testTxMessage() { String msgId = MessageClientIDSetter.createUniqID(); Message message = Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(msgId) .setQueueId(0) .setMessageType(MessageType.TRANSACTION) .setOrphanedTransactionRecoveryDuration(Durations.fromSeconds(30)) .setBodyEncoding(Encoding.GZIP) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build(); org.apache.rocketmq.common.message.Message messageExt = this.sendMessageActivity.buildMessage(null, Lists.newArrayList( message ), Resource.newBuilder().setName(TOPIC).build()).get(0); assertEquals(MessageClientIDSetter.getUniqID(messageExt), msgId); assertEquals(MessageSysFlag.TRANSACTION_PREPARED_TYPE | MessageSysFlag.COMPRESSED_FLAG, sendMessageActivity.buildSysFlag(message)); } @Test public void testPriorityMessage() { String msgId = MessageClientIDSetter.createUniqID(); Message message = Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(msgId) .setQueueId(0) .setMessageType(MessageType.PRIORITY) .setPriority(5) .setBodyEncoding(Encoding.GZIP) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build(); org.apache.rocketmq.common.message.Message messageExt = this.sendMessageActivity.buildMessage(null, Lists.newArrayList( message ), Resource.newBuilder().setName(TOPIC).build()).get(0); assertEquals(MessageClientIDSetter.getUniqID(messageExt), msgId); assertEquals(5, messageExt.getPriority()); } @Test public void testSendOrderMessageQueueSelector() throws Exception { TopicRouteData topicRouteData = new TopicRouteData(); QueueData queueData = new QueueData(); BrokerData brokerData = new BrokerData(); queueData.setBrokerName(BROKER_NAME); queueData.setWriteQueueNums(8); queueData.setPerm(PermName.PERM_WRITE); topicRouteData.setQueueDatas(Lists.newArrayList(queueData)); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR); brokerData.setBrokerAddrs(brokerAddrs); topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData)); MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null); SendMessageActivity.SendMessageQueueSelector selector1 = new SendMessageActivity.SendMessageQueueSelector( SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setSystemProperties(SystemProperties.newBuilder() .setMessageGroup(String.valueOf(1)) .build()) .build()) .build() ); TopicRouteService topicRouteService = mock(TopicRouteService.class); MQFaultStrategy mqFaultStrategy = mock(MQFaultStrategy.class); when(topicRouteService.getAllMessageQueueView(any(), any())).thenReturn(messageQueueView); when(topicRouteService.getMqFaultStrategy()).thenReturn(mqFaultStrategy); when(mqFaultStrategy.isSendLatencyFaultEnable()).thenReturn(false); SendMessageActivity.SendMessageQueueSelector selector2 = new SendMessageActivity.SendMessageQueueSelector( SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setSystemProperties(SystemProperties.newBuilder() .setMessageGroup(String.valueOf(1)) .build()) .build()) .build() ); SendMessageActivity.SendMessageQueueSelector selector3 = new SendMessageActivity.SendMessageQueueSelector( SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setSystemProperties(SystemProperties.newBuilder() .setMessageGroup(String.valueOf(2)) .build()) .build()) .build() ); assertEquals(selector1.select(ProxyContext.create(), messageQueueView), selector2.select(ProxyContext.create(), messageQueueView)); assertNotEquals(selector1.select(ProxyContext.create(), messageQueueView), selector3.select(ProxyContext.create(), messageQueueView)); } @Test public void testSendNormalMessageQueueSelector() { TopicRouteData topicRouteData = new TopicRouteData(); QueueData queueData = new QueueData(); BrokerData brokerData = new BrokerData(); queueData.setBrokerName(BROKER_NAME); queueData.setWriteQueueNums(2); queueData.setPerm(PermName.PERM_WRITE); topicRouteData.setQueueDatas(Lists.newArrayList(queueData)); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR); brokerData.setBrokerAddrs(brokerAddrs); topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData)); SendMessageActivity.SendMessageQueueSelector selector = new SendMessageActivity.SendMessageQueueSelector( SendMessageRequest.newBuilder() .addMessages(Message.newBuilder().build()) .build() ); TopicRouteService topicRouteService = mock(TopicRouteService.class); MQFaultStrategy mqFaultStrategy = mock(MQFaultStrategy.class); when(topicRouteService.getMqFaultStrategy()).thenReturn(mqFaultStrategy); when(mqFaultStrategy.isSendLatencyFaultEnable()).thenReturn(false); MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null); AddressableMessageQueue firstSelect = selector.select(ProxyContext.create(), messageQueueView); AddressableMessageQueue secondSelect = selector.select(ProxyContext.create(), messageQueueView); AddressableMessageQueue thirdSelect = selector.select(ProxyContext.create(), messageQueueView); assertEquals(firstSelect, thirdSelect); assertNotEquals(firstSelect, secondSelect); } @Test public void testSendNormalMessageQueueSelectorPipeLine() throws Exception { TopicRouteData topicRouteData = new TopicRouteData(); int queueNums = 2; QueueData queueData = createQueueData(BROKER_NAME, queueNums); QueueData queueData2 = createQueueData(BROKER_NAME2, queueNums); topicRouteData.setQueueDatas(Lists.newArrayList(queueData,queueData2)); BrokerData brokerData = createBrokerData(CLUSTER_NAME, BROKER_NAME, BROKER_ADDR); BrokerData brokerData2 = createBrokerData(CLUSTER_NAME, BROKER_NAME2, BROKER_ADDR2); topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData, brokerData2)); SendMessageActivity.SendMessageQueueSelector selector = new SendMessageActivity.SendMessageQueueSelector( SendMessageRequest.newBuilder() .addMessages(Message.newBuilder().build()) .build() ); ClientConfig cc = new ClientConfig(); this.mqFaultStrategy = new MQFaultStrategy(cc, null, null); mqFaultStrategy.setSendLatencyFaultEnable(true); mqFaultStrategy.updateFaultItem(BROKER_NAME2, 1000, true, true); mqFaultStrategy.updateFaultItem(BROKER_NAME, 1000, true, false); MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, buildPenalizerByMQFaultStrategy(mqFaultStrategy)); AddressableMessageQueue firstSelect = selector.select(ProxyContext.create(), messageQueueView); assertEquals(firstSelect.getBrokerName(), BROKER_NAME2); mqFaultStrategy.updateFaultItem(BROKER_NAME2, 1000, true, false); mqFaultStrategy.updateFaultItem(BROKER_NAME, 1000, true, true); AddressableMessageQueue secondSelect = selector.select(ProxyContext.create(), messageQueueView); assertEquals(secondSelect.getBrokerName(), BROKER_NAME); } @Test public void testParameterValidate() { // too large message body assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[4 * 1024 * 1024 + 1])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.MESSAGE_BODY_TOO_LARGE, e.getCode()); throw e; } }); // black tag assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setTag(" ") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_TAG, e.getCode()); throw e; } }); // tag with '|' assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setTag("|") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_TAG, e.getCode()); throw e; } }); // tag with \t assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setTag("\t") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_TAG, e.getCode()); throw e; } }); // blank message key assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .addKeys(" ") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_KEY, e.getCode()); throw e; } }); // blank message with \t assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .addKeys("\t") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_KEY, e.getCode()); throw e; } }); // blank message group assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageGroup(" ") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_GROUP, e.getCode()); throw e; } }); // long message group assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageGroup(createStr(ConfigurationManager.getProxyConfig().getMaxMessageGroupSize() + 1)) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_GROUP, e.getCode()); throw e; } }); // message group with \t assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageGroup("\t") .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_GROUP, e.getCode()); throw e; } }); // too large message property assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .putUserProperties("key", createStr(16 * 1024 + 1)) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.MESSAGE_PROPERTIES_TOO_LARGE, e.getCode()); throw e; } }); // too large message property assertThrows(GrpcProxyException.class, () -> { Map p = new HashMap<>(); for (int i = 0; i <= ConfigurationManager.getProxyConfig().getUserPropertyMaxNum(); i++) { p.put(String.valueOf(i), String.valueOf(i)); } try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .putAllUserProperties(p) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.MESSAGE_PROPERTIES_TOO_LARGE, e.getCode()); throw e; } }); // set system properties assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .putUserProperties(MessageConst.PROPERTY_TRACE_SWITCH, "false") .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, e.getCode()); throw e; } }); // set the key of user property with control character assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .putUserProperties("\u0000", "hello") .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, e.getCode()); throw e; } }); // set the value of user property with control character assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("msgId") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .putUserProperties("p", "\u0000") .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, e.getCode()); throw e; } }); // empty message id assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(" ") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_MESSAGE_ID, e.getCode()); throw e; } }); // delay time assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("id") .setDeliveryTimestamp( Timestamps.fromMillis(System.currentTimeMillis() + Duration.ofDays(1).toMillis() + Duration.ofSeconds(10).toMillis())) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.ILLEGAL_DELIVERY_TIME, e.getCode()); throw e; } }); // transactionRecoverySecond assertThrows(GrpcProxyException.class, () -> { try { this.sendMessageActivity.sendMessage( createContext(), SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId("id") .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .setOrphanedTransactionRecoveryDuration(Durations.fromHours(2)) .setMessageType(MessageType.TRANSACTION) .build()) .setBody(ByteString.copyFrom(new byte[3])) .build()) .build() ).get(); } catch (ExecutionException t) { GrpcProxyException e = (GrpcProxyException) t.getCause(); assertEquals(Code.BAD_REQUEST, e.getCode()); throw e; } }); } private static String createStr(int len) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i++) { sb.append("a"); } return sb.toString(); } private static QueueData createQueueData(String brokerName, int writeQueueNums) { QueueData queueData = new QueueData(); queueData.setBrokerName(brokerName); queueData.setWriteQueueNums(writeQueueNums); queueData.setPerm(PermName.PERM_WRITE); return queueData; } private static BrokerData createBrokerData(String clusterName, String brokerName, String brokerAddrs) { BrokerData brokerData = new BrokerData(); brokerData.setCluster(clusterName); brokerData.setBrokerName(brokerName); HashMap brokerAddrsMap = new HashMap<>(); brokerAddrsMap.put(MixAll.MASTER_ID, brokerAddrs); brokerData.setBrokerAddrs(brokerAddrsMap); return brokerData; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.route; import apache.rocketmq.v2.Address; import apache.rocketmq.v2.AddressScheme; import apache.rocketmq.v2.Broker; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Endpoints; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.MessageType; import apache.rocketmq.v2.Permission; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryAssignmentResponse; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.Resource; import com.google.common.net.HostAndPort; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.proxy.service.metadata.LocalMetadataService; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; public class RouteActivityTest extends BaseActivityTest { private RouteActivity routeActivity; private static final String CLUSTER = "cluster"; private static final String TOPIC = "topic"; private static final String GROUP = "group"; private static final String BROKER_NAME = "brokerName"; private static final Broker GRPC_BROKER = Broker.newBuilder().setName(BROKER_NAME).build(); private static final Resource GRPC_TOPIC = Resource.newBuilder() .setName(TOPIC) .build(); private static final Resource GRPC_GROUP = Resource.newBuilder() .setName(GROUP) .build(); private static Endpoints grpcEndpoints = Endpoints.newBuilder() .setScheme(AddressScheme.IPv4) .addAddresses(Address.newBuilder().setHost("127.0.0.1").setPort(8080).build()) .addAddresses(Address.newBuilder().setHost("127.0.0.2").setPort(8080).build()) .build(); private static List addressArrayList = new ArrayList<>(); static { addressArrayList.add(new org.apache.rocketmq.proxy.common.Address( org.apache.rocketmq.proxy.common.Address.AddressScheme.IPv4, HostAndPort.fromParts("127.0.0.1", 8080))); addressArrayList.add(new org.apache.rocketmq.proxy.common.Address( org.apache.rocketmq.proxy.common.Address.AddressScheme.IPv4, HostAndPort.fromParts("127.0.0.2", 8080))); } @Before public void before() throws Throwable { super.before(); this.routeActivity = new RouteActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testQueryRoute() throws Throwable { ConfigurationManager.getProxyConfig().setGrpcServerPort(8080); ArgumentCaptor> addressListCaptor = ArgumentCaptor.forClass(List.class); when(this.messagingProcessor.getTopicRouteDataForProxy(any(), addressListCaptor.capture(), anyString())) .thenReturn(createProxyTopicRouteData(2, 2, 6)); MetadataService metadataService = Mockito.mock(LocalMetadataService.class); when(this.messagingProcessor.getMetadataService()).thenReturn(metadataService); when(metadataService.getTopicMessageType(any(), anyString())).thenReturn(TopicMessageType.NORMAL); QueryRouteResponse response = this.routeActivity.queryRoute( createContext(), QueryRouteRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(Resource.newBuilder().setName(TOPIC).build()) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(4, response.getMessageQueuesCount()); for (MessageQueue messageQueue : response.getMessageQueuesList()) { assertEquals(grpcEndpoints, messageQueue.getBroker().getEndpoints()); assertEquals(Permission.READ_WRITE, messageQueue.getPermission()); } } @Test public void testQueryRouteTopicExist() throws Throwable { when(this.messagingProcessor.getTopicRouteDataForProxy(any(), any(), anyString())) .thenThrow(new MQBrokerException(ResponseCode.TOPIC_NOT_EXIST, "")); try { this.routeActivity.queryRoute( createContext(), QueryRouteRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(GRPC_TOPIC) .build() ).get(); } catch (Throwable t) { assertEquals(Code.TOPIC_NOT_FOUND, ResponseBuilder.getInstance().buildStatus(t).getCode()); return; } fail(); } @Test public void testQueryAssignmentWithNoReadPerm() throws Throwable { when(this.messagingProcessor.getTopicRouteDataForProxy(any(), any(), anyString())) .thenReturn(createProxyTopicRouteData(2, 2, PermName.PERM_WRITE)); QueryAssignmentResponse response = this.routeActivity.queryAssignment( createContext(), QueryAssignmentRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(GRPC_TOPIC) .setGroup(GRPC_GROUP) .build() ).get(); assertEquals(Code.FORBIDDEN, response.getStatus().getCode()); } @Test public void testQueryAssignmentWithNoReadQueue() throws Throwable { when(this.messagingProcessor.getTopicRouteDataForProxy(any(), any(), anyString())) .thenReturn(createProxyTopicRouteData(0, 2, 6)); QueryAssignmentResponse response = this.routeActivity.queryAssignment( createContext(), QueryAssignmentRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(GRPC_TOPIC) .setGroup(GRPC_GROUP) .build() ).get(); assertEquals(Code.FORBIDDEN, response.getStatus().getCode()); } @Test public void testQueryAssignment() throws Throwable { when(this.messagingProcessor.getTopicRouteDataForProxy(any(), any(), anyString())) .thenReturn(createProxyTopicRouteData(2, 2, 6)); QueryAssignmentResponse response = this.routeActivity.queryAssignment( createContext(), QueryAssignmentRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(GRPC_TOPIC) .setGroup(GRPC_GROUP) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(1, response.getAssignmentsCount()); assertEquals(grpcEndpoints, response.getAssignments(0).getMessageQueue().getBroker().getEndpoints()); } @Test public void testQueryFifoAssignment() throws Throwable { when(this.messagingProcessor.getTopicRouteDataForProxy(any(), any(), anyString())) .thenReturn(createProxyTopicRouteData(2, 2, 6)); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setConsumeMessageOrderly(true); when(this.messagingProcessor.getSubscriptionGroupConfig(any(), anyString())).thenReturn(subscriptionGroupConfig); QueryAssignmentResponse response = this.routeActivity.queryAssignment( createContext(), QueryAssignmentRequest.newBuilder() .setEndpoints(grpcEndpoints) .setTopic(GRPC_TOPIC) .setGroup(GRPC_GROUP) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(2, response.getAssignmentsCount()); assertEquals(grpcEndpoints, response.getAssignments(0).getMessageQueue().getBroker().getEndpoints()); } private static ProxyTopicRouteData createProxyTopicRouteData(int r, int w, int p) { ProxyTopicRouteData proxyTopicRouteData = new ProxyTopicRouteData(); proxyTopicRouteData.getQueueDatas().add(createQueueData(r, w, p)); ProxyTopicRouteData.ProxyBrokerData proxyBrokerData = new ProxyTopicRouteData.ProxyBrokerData(); proxyBrokerData.setCluster(CLUSTER); proxyBrokerData.setBrokerName(BROKER_NAME); proxyBrokerData.getBrokerAddrs().put(0L, addressArrayList); proxyBrokerData.getBrokerAddrs().put(1L, addressArrayList); proxyTopicRouteData.getBrokerDatas().add(proxyBrokerData); return proxyTopicRouteData; } @Test public void testGenPartitionFromQueueData() throws Exception { // test queueData with 8 read queues, 8 write queues, and rw permission, expect 8 rw queues. QueueData queueDataWith8R8WPermRW = createQueueData(8, 8, PermName.PERM_READ | PermName.PERM_WRITE); List partitionWith8R8WPermRW = this.routeActivity.genMessageQueueFromQueueData(queueDataWith8R8WPermRW, GRPC_TOPIC, TopicMessageType.NORMAL, GRPC_BROKER); assertEquals(8, partitionWith8R8WPermRW.size()); assertEquals(8, partitionWith8R8WPermRW.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.NORMAL.getNumber()).count()); assertEquals(8, partitionWith8R8WPermRW.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith8R8WPermRW.stream().filter(a -> a.getPermission() == Permission.READ).count()); assertEquals(0, partitionWith8R8WPermRW.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); // test queueData with 8 read queues, 8 write queues, and read only permission, expect 8 read only queues. QueueData queueDataWith8R8WPermR = createQueueData(8, 8, PermName.PERM_READ); List partitionWith8R8WPermR = this.routeActivity.genMessageQueueFromQueueData(queueDataWith8R8WPermR, GRPC_TOPIC, TopicMessageType.FIFO, GRPC_BROKER); assertEquals(8, partitionWith8R8WPermR.size()); assertEquals(8, partitionWith8R8WPermR.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.FIFO.getNumber()).count()); assertEquals(8, partitionWith8R8WPermR.stream().filter(a -> a.getPermission() == Permission.READ).count()); assertEquals(0, partitionWith8R8WPermR.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith8R8WPermR.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); // test queueData with 8 read queues, 8 write queues, and write only permission, expect 8 write only queues. QueueData queueDataWith8R8WPermW = createQueueData(8, 8, PermName.PERM_WRITE); List partitionWith8R8WPermW = this.routeActivity.genMessageQueueFromQueueData(queueDataWith8R8WPermW, GRPC_TOPIC, TopicMessageType.TRANSACTION, GRPC_BROKER); assertEquals(8, partitionWith8R8WPermW.size()); assertEquals(8, partitionWith8R8WPermW.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.TRANSACTION.getNumber()).count()); assertEquals(8, partitionWith8R8WPermW.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); assertEquals(0, partitionWith8R8WPermW.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith8R8WPermW.stream().filter(a -> a.getPermission() == Permission.READ).count()); // test queueData with 8 read queues, 0 write queues, and rw permission, expect 8 read only queues. QueueData queueDataWith8R0WPermRW = createQueueData(8, 0, PermName.PERM_READ | PermName.PERM_WRITE); List partitionWith8R0WPermRW = this.routeActivity.genMessageQueueFromQueueData(queueDataWith8R0WPermRW, GRPC_TOPIC, TopicMessageType.DELAY, GRPC_BROKER); assertEquals(8, partitionWith8R0WPermRW.size()); assertEquals(8, partitionWith8R0WPermRW.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.DELAY.getNumber()).count()); assertEquals(8, partitionWith8R0WPermRW.stream().filter(a -> a.getPermission() == Permission.READ).count()); assertEquals(0, partitionWith8R0WPermRW.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith8R0WPermRW.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); // test queueData with 4 read queues, 8 write queues, and rw permission, expect 4 rw queues and 4 write only queues. QueueData queueDataWith4R8WPermRW = createQueueData(4, 8, PermName.PERM_READ | PermName.PERM_WRITE); List partitionWith4R8WPermRW = this.routeActivity.genMessageQueueFromQueueData(queueDataWith4R8WPermRW, GRPC_TOPIC, TopicMessageType.UNSPECIFIED, GRPC_BROKER); assertEquals(8, partitionWith4R8WPermRW.size()); assertEquals(8, partitionWith4R8WPermRW.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.MESSAGE_TYPE_UNSPECIFIED.getNumber()).count()); assertEquals(4, partitionWith4R8WPermRW.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); assertEquals(4, partitionWith4R8WPermRW.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith4R8WPermRW.stream().filter(a -> a.getPermission() == Permission.READ).count()); // test queueData with 2 read queues, 2 write queues, and no permission, expect 2 no permission queues. QueueData queueDataWith2R2WNoPerm = createQueueData(2, 2, 0); List partitionWith2R2WNoPerm = this.routeActivity.genMessageQueueFromQueueData(queueDataWith2R2WNoPerm, GRPC_TOPIC, TopicMessageType.UNSPECIFIED, GRPC_BROKER); assertEquals(2, partitionWith2R2WNoPerm.size()); assertEquals(2, partitionWith2R2WNoPerm.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.MESSAGE_TYPE_UNSPECIFIED.getNumber()).count()); assertEquals(2, partitionWith2R2WNoPerm.stream().filter(a -> a.getPermission() == Permission.NONE).count()); assertEquals(0, partitionWith2R2WNoPerm.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); assertEquals(0, partitionWith2R2WNoPerm.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith2R2WNoPerm.stream().filter(a -> a.getPermission() == Permission.READ).count()); // test queueData with 0 read queues, 0 write queues, and no permission, expect 1 no permission queue. QueueData queueDataWith0R0WNoPerm = createQueueData(0, 0, 0); List partitionWith0R0WNoPerm = this.routeActivity.genMessageQueueFromQueueData(queueDataWith0R0WNoPerm, GRPC_TOPIC, TopicMessageType.UNSPECIFIED, GRPC_BROKER); assertEquals(1, partitionWith0R0WNoPerm.size()); assertEquals(1, partitionWith0R0WNoPerm.stream().filter(a -> a.getAcceptMessageTypesValue(0) == MessageType.MESSAGE_TYPE_UNSPECIFIED.getNumber()).count()); assertEquals(1, partitionWith0R0WNoPerm.stream().filter(a -> a.getPermission() == Permission.NONE).count()); assertEquals(0, partitionWith0R0WNoPerm.stream().filter(a -> a.getPermission() == Permission.WRITE).count()); assertEquals(0, partitionWith0R0WNoPerm.stream().filter(a -> a.getPermission() == Permission.READ_WRITE).count()); assertEquals(0, partitionWith0R0WNoPerm.stream().filter(a -> a.getPermission() == Permission.READ).count()); } private static QueueData createQueueData(int r, int w, int perm) { QueueData queueData = new QueueData(); queueData.setBrokerName(BROKER_NAME); queueData.setReadQueueNums(r); queueData.setWriteQueueNums(w); queueData.setPerm(perm); return queueData; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.grpc.v2.transaction; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.EndTransactionResponse; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.TransactionResolution; import apache.rocketmq.v2.TransactionSource; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; import org.apache.rocketmq.proxy.processor.TransactionStatus; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @RunWith(Parameterized.class) public class EndTransactionActivityTest extends BaseActivityTest { private EndTransactionActivity endTransactionActivity; private TransactionResolution resolution; private TransactionSource source; private TransactionStatus transactionStatus; private Boolean fromTransactionCheck; public EndTransactionActivityTest(TransactionResolution resolution, TransactionSource source, TransactionStatus transactionStatus, Boolean fromTransactionCheck) { this.resolution = resolution; this.source = source; this.transactionStatus = transactionStatus; this.fromTransactionCheck = fromTransactionCheck; } @Before public void before() throws Throwable { super.before(); this.endTransactionActivity = new EndTransactionActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } @Test public void testEndTransaction() throws Throwable { ArgumentCaptor transactionStatusCaptor = ArgumentCaptor.forClass(TransactionStatus.class); ArgumentCaptor fromTransactionCheckCaptor = ArgumentCaptor.forClass(Boolean.class); when(this.messagingProcessor.endTransaction(any(), any(), anyString(), anyString(), anyString(), transactionStatusCaptor.capture(), fromTransactionCheckCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null)); EndTransactionResponse response = this.endTransactionActivity.endTransaction( createContext(), EndTransactionRequest.newBuilder() .setResolution(resolution) .setTopic(Resource.newBuilder().setName("topic").build()) .setMessageId(MessageClientIDSetter.createUniqID()) .setTransactionId(MessageClientIDSetter.createUniqID()) .setSource(source) .build() ).get(); assertEquals(Code.OK, response.getStatus().getCode()); assertEquals(transactionStatus, transactionStatusCaptor.getValue()); assertEquals(fromTransactionCheck, fromTransactionCheckCaptor.getValue()); } @Parameterized.Parameters public static Collection parameters() { Object[][] p = new Object[][] { {TransactionResolution.COMMIT, TransactionSource.SOURCE_CLIENT, TransactionStatus.COMMIT, false}, {TransactionResolution.ROLLBACK, TransactionSource.SOURCE_SERVER_CHECK, TransactionStatus.ROLLBACK, true}, {TransactionResolution.TRANSACTION_RESOLUTION_UNSPECIFIED, TransactionSource.SOURCE_SERVER_CHECK, TransactionStatus.UNKNOWN, true}, }; return Arrays.asList(p); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.UUID; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.message.MessageService; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.junit.Ignore; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.when; @Ignore @RunWith(MockitoJUnitRunner.Silent.class) public class BaseProcessorTest extends InitConfigTest { protected static final Random RANDOM = new Random(); @Mock protected MessagingProcessor messagingProcessor; @Mock protected ServiceManager serviceManager; @Mock protected MessageService messageService; @Mock protected TopicRouteService topicRouteService; @Mock protected ProducerManager producerManager; @Mock protected ConsumerManager consumerManager; @Mock protected TransactionService transactionService; @Mock protected ProxyRelayService proxyRelayService; @Mock protected MetadataService metadataService; public void before() throws Throwable { super.before(); when(serviceManager.getMessageService()).thenReturn(messageService); when(serviceManager.getTopicRouteService()).thenReturn(topicRouteService); when(serviceManager.getProducerManager()).thenReturn(producerManager); when(serviceManager.getConsumerManager()).thenReturn(consumerManager); when(serviceManager.getTransactionService()).thenReturn(transactionService); when(serviceManager.getProxyRelayService()).thenReturn(proxyRelayService); when(serviceManager.getMetadataService()).thenReturn(metadataService); when(messagingProcessor.getMetadataService()).thenReturn(metadataService); } protected static ProxyContext createContext() { return ProxyContext.create(); } protected static MessageExt createMessageExt(String topic, String tags, int reconsumeTimes, long invisibleTime) { return createMessageExt(topic, tags, reconsumeTimes, invisibleTime, System.currentTimeMillis(), RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE), "mockBroker"); } protected static MessageExt createMessageExt(String topic, String tags, int reconsumeTimes, long invisibleTime, long popTime, long startOffset, int reviveQid, int queueId, long queueOffset, String brokerName) { MessageExt messageExt = new MessageExt(); messageExt.setTopic(topic); messageExt.setTags(tags); messageExt.setReconsumeTimes(reconsumeTimes); messageExt.setBody(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); messageExt.setMsgId(MessageClientIDSetter.createUniqID()); messageExt.setCommitLogOffset(RANDOM.nextInt(Integer.MAX_VALUE)); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_POP_CK, ExtraInfoUtil.buildExtraInfo(startOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId, queueOffset)); return messageExt; } protected static ReceiptHandle create(MessageExt messageExt) { String ckInfo = messageExt.getProperty(MessageConst.PROPERTY_POP_CK); if (ckInfo == null) { return null; } return ReceiptHandle.decode(ckInfo + MessageConst.KEY_SEPARATOR + messageExt.getCommitLogOffset()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/ClientProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ClientProcessorTest { @Mock private MessagingProcessor messagingProcessor; @Mock private ServiceManager serviceManager; @Mock private ProxyContext ctx; @Mock private SubscriptionGroupConfig groupConfig; private ClientProcessor clientProcessor; @Before public void setUp() throws Exception { ConfigurationManager.initConfig(); clientProcessor = new ClientProcessor(messagingProcessor, serviceManager); } @Test public void testValidateLiteMode_regularGroupWithLiteMode_throwsException() { String group = "regularGroup"; when(groupConfig.getLiteBindTopic()).thenReturn(""); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); GrpcProxyException exception = assertThrows(GrpcProxyException.class, () -> { clientProcessor.validateLiteMode(ctx, group, MessageModel.LITE_SELECTIVE); }); assertEquals("regular group cannot use LITE mode: " + group, exception.getMessage()); } @Test public void testValidateLiteMode_liteGroupWithoutLiteMode_throwsException() { String group = "liteGroup"; when(groupConfig.getLiteBindTopic()).thenReturn("topic1"); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); GrpcProxyException exception = assertThrows(GrpcProxyException.class, () -> { clientProcessor.validateLiteMode(ctx, group, MessageModel.CLUSTERING); }); assertEquals("lite group must use LITE mode: " + group, exception.getMessage()); } @Test public void testValidateLiteMode_regularGroupWithoutLiteMode_noException() { String group = "regularGroup"; when(groupConfig.getLiteBindTopic()).thenReturn(""); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); assertDoesNotThrow(() -> { clientProcessor.validateLiteMode(ctx, group, MessageModel.CLUSTERING); }); } @Test public void testValidateLiteMode_liteGroupWithLiteMode_noException() { String group = "liteGroup"; when(groupConfig.getLiteBindTopic()).thenReturn("topic1"); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); assertDoesNotThrow(() -> { clientProcessor.validateLiteMode(ctx, group, MessageModel.LITE_SELECTIVE); }); } @Test public void testValidateLiteSubTopic_emptySubList_noException() { String group = "group"; Set subList = new HashSet<>(); assertDoesNotThrow(() -> { clientProcessor.validateLiteSubTopic(ctx, group, subList); }); } @Test public void testValidateLiteSubTopic_validSubList_noException() { String group = "group"; String topic = "topic1"; SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); Set subList = new HashSet<>(); subList.add(subscriptionData); when(groupConfig.getLiteBindTopic()).thenReturn(topic); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); assertDoesNotThrow(() -> { clientProcessor.validateLiteSubTopic(ctx, group, subList); }); } @Test public void testValidateLiteBindTopic_matchingTopics_noException() { String group = "group"; String bindTopic = "topic1"; when(groupConfig.getLiteBindTopic()).thenReturn(bindTopic); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); assertDoesNotThrow(() -> { clientProcessor.validateLiteBindTopic(ctx, group, bindTopic); }); } @Test public void testValidateLiteBindTopic_mismatchedTopics_throwsException() { String group = "group"; String expectedTopic = "expectedTopic"; String actualTopic = "actualTopic"; when(groupConfig.getLiteBindTopic()).thenReturn(expectedTopic); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); GrpcProxyException exception = assertThrows(GrpcProxyException.class, () -> { clientProcessor.validateLiteBindTopic(ctx, group, actualTopic); }); assertTrue(exception.getMessage().contains("expected to bind topic")); } @Test public void testValidateLiteSubscriptionQuota_withinQuota_noException() { String group = "group"; int quota = 10; int actual = 5; when(groupConfig.getLiteSubClientQuota()).thenReturn(quota); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); assertDoesNotThrow(() -> { clientProcessor.validateLiteSubscriptionQuota(ctx, group, actual); }); } @Test public void testValidateLiteSubscriptionQuota_exceedsQuota_throwsException() { String group = "group"; int quota = 10; int actual = 15 + 300 /*quota buffer*/; when(groupConfig.getLiteSubClientQuota()).thenReturn(quota); when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); GrpcProxyException exception = assertThrows(GrpcProxyException.class, () -> { clientProcessor.validateLiteSubscriptionQuota(ctx, group, actual); }); assertTrue(exception.getMessage().contains("lite subscription quota exceeded")); } @Test public void testGetGroupOrException_groupExists_returnsConfig() { String group = "group"; when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(groupConfig); SubscriptionGroupConfig result = clientProcessor.getGroupOrException(ctx, group); assertEquals(groupConfig, result); } @Test public void testGetGroupOrException_groupNotExists_throwsException() { String group = "nonExistentGroup"; when(messagingProcessor.getSubscriptionGroupConfig(ctx, group)).thenReturn(null); GrpcProxyException exception = assertThrows(GrpcProxyException.class, () -> { clientProcessor.getGroupOrException(ctx, group); }); assertEquals("group not found: " + group, exception.getMessage()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import com.google.common.collect.Sets; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.proxy.common.utils.ProxyUtils; import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class ConsumerProcessorTest extends BaseProcessorTest { private static final String CONSUMER_GROUP = "consumerGroup"; private static final String TOPIC = "topic"; private static final String CLIENT_ID = "clientId"; private ConsumerProcessor consumerProcessor; @Before public void before() throws Throwable { super.before(); this.consumerProcessor = new ConsumerProcessor(messagingProcessor, serviceManager, Executors.newCachedThreadPool()); } @Test public void testPopMessage() throws Throwable { final String tag = "tag"; final long invisibleTime = Duration.ofSeconds(15).toMillis(); ArgumentCaptor messageQueueArgumentCaptor = ArgumentCaptor.forClass(AddressableMessageQueue.class); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(PopMessageRequestHeader.class); List messageExtList = new ArrayList<>(); messageExtList.add(createMessageExt(TOPIC, "noMatch", 0, invisibleTime)); messageExtList.add(createMessageExt(TOPIC, tag, 0, invisibleTime)); messageExtList.add(createMessageExt(TOPIC, tag, 1, invisibleTime)); PopResult innerPopResult = new PopResult(PopStatus.FOUND, messageExtList); when(this.messageService.popMessage(any(), messageQueueArgumentCaptor.capture(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(innerPopResult)); when(this.topicRouteService.getCurrentMessageQueueView(any(), anyString())) .thenReturn(mock(MessageQueueView.class)); ArgumentCaptor ackMessageIdArgumentCaptor = ArgumentCaptor.forClass(String.class); when(this.messagingProcessor.ackMessage(any(), any(), ackMessageIdArgumentCaptor.capture(), anyString(), anyString(), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(mock(AckResult.class))); ArgumentCaptor toDLQMessageIdArgumentCaptor = ArgumentCaptor.forClass(String.class); when(this.messagingProcessor.forwardMessageToDeadLetterQueue(any(), any(), toDLQMessageIdArgumentCaptor.capture(), anyString(), anyString(), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(mock(RemotingCommand.class))); AddressableMessageQueue messageQueue = mock(AddressableMessageQueue.class); PopResult popResult = this.consumerProcessor.popMessage( createContext(), (ctx, messageQueueView) -> messageQueue, CONSUMER_GROUP, TOPIC, 60, invisibleTime, Duration.ofSeconds(3).toMillis(), ConsumeInitMode.MAX, FilterAPI.build(TOPIC, tag, ExpressionType.TAG), false, (ctx, consumerGroup, subscriptionData, messageExt) -> { if (!messageExt.getTags().equals(tag)) { return PopMessageResultFilter.FilterResult.NO_MATCH; } if (messageExt.getReconsumeTimes() > 0) { return PopMessageResultFilter.FilterResult.TO_DLQ; } return PopMessageResultFilter.FilterResult.MATCH; }, null, Duration.ofSeconds(3).toMillis() ).get(); assertSame(messageQueue, messageQueueArgumentCaptor.getValue()); assertEquals(CONSUMER_GROUP, requestHeaderArgumentCaptor.getValue().getConsumerGroup()); assertEquals(TOPIC, requestHeaderArgumentCaptor.getValue().getTopic()); assertEquals(ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST, requestHeaderArgumentCaptor.getValue().getMaxMsgNums()); assertEquals(tag, requestHeaderArgumentCaptor.getValue().getExp()); assertEquals(ExpressionType.TAG, requestHeaderArgumentCaptor.getValue().getExpType()); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); assertEquals(1, popResult.getMsgFoundList().size()); assertEquals(messageExtList.get(1), popResult.getMsgFoundList().get(0)); assertEquals(messageExtList.get(0).getMsgId(), ackMessageIdArgumentCaptor.getValue()); assertEquals(messageExtList.get(2).getMsgId(), toDLQMessageIdArgumentCaptor.getValue()); } @Test public void testAckMessage() throws Throwable { ReceiptHandle handle = create(createMessageExt(MixAll.RETRY_GROUP_TOPIC_PREFIX + TOPIC, "", 0, 3000)); assertNotNull(handle); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(AckMessageRequestHeader.class); AckResult innerAckResult = new AckResult(); innerAckResult.setStatus(AckStatus.OK); when(this.messageService.ackMessage(any(), any(), anyString(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(innerAckResult)); AckResult ackResult = this.consumerProcessor.ackMessage(createContext(), handle, MessageClientIDSetter.createUniqID(), CONSUMER_GROUP, TOPIC, null, 3000).get(); assertEquals(AckStatus.OK, ackResult.getStatus()); assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP, new BrokerConfig().isEnableRetryTopicV2()), requestHeaderArgumentCaptor.getValue().getTopic()); assertEquals(CONSUMER_GROUP, requestHeaderArgumentCaptor.getValue().getConsumerGroup()); assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo()); } @Test public void testBatchAckExpireMessage() throws Throwable { String brokerName1 = "brokerName1"; List receiptHandleMessageList = new ArrayList<>(); for (int i = 0; i < 3; i++) { MessageExt expireMessage = createMessageExt(TOPIC, "", 0, 3000, System.currentTimeMillis() - 10000, 0, 0, 0, i, brokerName1); ReceiptHandle expireHandle = create(expireMessage); receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId())); } List batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get(); verify(this.messageService, never()).batchAckMessage(any(), anyList(), anyString(), anyString(), anyLong()); assertEquals(receiptHandleMessageList.size(), batchAckResultList.size()); for (BatchAckResult batchAckResult : batchAckResultList) { assertNull(batchAckResult.getAckResult()); assertNotNull(batchAckResult.getProxyException()); assertNotNull(batchAckResult.getReceiptHandleMessage()); } } @Test public void testBatchAckMessage() throws Throwable { String brokerName1 = "brokerName1"; String brokerName2 = "brokerName2"; String errThrowBrokerName = "errThrowBrokerName"; MessageExt expireMessage = createMessageExt(TOPIC, "", 0, 3000, System.currentTimeMillis() - 10000, 0, 0, 0, 0, brokerName1); ReceiptHandle expireHandle = create(expireMessage); List receiptHandleMessageList = new ArrayList<>(); receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId())); List broker1Msg = new ArrayList<>(); List broker2Msg = new ArrayList<>(); long now = System.currentTimeMillis(); int msgNum = 3; for (int i = 0; i < msgNum; i++) { MessageExt brokerMessage = createMessageExt(TOPIC, "", 0, 3000, now, 0, 0, 0, i + 1, brokerName1); ReceiptHandle brokerHandle = create(brokerMessage); receiptHandleMessageList.add(new ReceiptHandleMessage(brokerHandle, brokerMessage.getMsgId())); broker1Msg.add(brokerMessage.getMsgId()); } for (int i = 0; i < msgNum; i++) { MessageExt brokerMessage = createMessageExt(TOPIC, "", 0, 3000, now, 0, 0, 0, i + 1, brokerName2); ReceiptHandle brokerHandle = create(brokerMessage); receiptHandleMessageList.add(new ReceiptHandleMessage(brokerHandle, brokerMessage.getMsgId())); broker2Msg.add(brokerMessage.getMsgId()); } // for this message, will throw exception in batchAckMessage MessageExt errThrowMessage = createMessageExt(TOPIC, "", 0, 3000, now, 0, 0, 0, 0, errThrowBrokerName); ReceiptHandle errThrowHandle = create(errThrowMessage); receiptHandleMessageList.add(new ReceiptHandleMessage(errThrowHandle, errThrowMessage.getMsgId())); Collections.shuffle(receiptHandleMessageList); doAnswer((Answer>) invocation -> { List handleMessageList = invocation.getArgument(1, List.class); AckResult ackResult = new AckResult(); String brokerName = handleMessageList.get(0).getReceiptHandle().getBrokerName(); if (brokerName.equals(brokerName1)) { ackResult.setStatus(AckStatus.OK); } else if (brokerName.equals(brokerName2)) { ackResult.setStatus(AckStatus.NO_EXIST); } else { return FutureUtils.completeExceptionally(new RuntimeException()); } return CompletableFuture.completedFuture(ackResult); }).when(this.messageService).batchAckMessage(any(), anyList(), anyString(), anyString(), anyLong()); List batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get(); assertEquals(receiptHandleMessageList.size(), batchAckResultList.size()); // check ackResult for each msg Map msgBatchAckResult = new HashMap<>(); for (BatchAckResult batchAckResult : batchAckResultList) { msgBatchAckResult.put(batchAckResult.getReceiptHandleMessage().getMessageId(), batchAckResult); } for (String msgId : broker1Msg) { assertEquals(AckStatus.OK, msgBatchAckResult.get(msgId).getAckResult().getStatus()); assertNull(msgBatchAckResult.get(msgId).getProxyException()); } for (String msgId : broker2Msg) { assertEquals(AckStatus.NO_EXIST, msgBatchAckResult.get(msgId).getAckResult().getStatus()); assertNull(msgBatchAckResult.get(msgId).getProxyException()); } assertNotNull(msgBatchAckResult.get(expireMessage.getMsgId()).getProxyException()); assertEquals(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, msgBatchAckResult.get(expireMessage.getMsgId()).getProxyException().getCode()); assertNull(msgBatchAckResult.get(expireMessage.getMsgId()).getAckResult()); assertNotNull(msgBatchAckResult.get(errThrowMessage.getMsgId()).getProxyException()); assertEquals(ProxyExceptionCode.INTERNAL_SERVER_ERROR, msgBatchAckResult.get(errThrowMessage.getMsgId()).getProxyException().getCode()); assertNull(msgBatchAckResult.get(errThrowMessage.getMsgId()).getAckResult()); } @Test public void testChangeInvisibleTime() throws Throwable { ReceiptHandle handle = create(createMessageExt(MixAll.RETRY_GROUP_TOPIC_PREFIX + TOPIC, "", 0, 3000)); assertNotNull(handle); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(ChangeInvisibleTimeRequestHeader.class); AckResult innerAckResult = new AckResult(); innerAckResult.setStatus(AckStatus.OK); when(this.messageService.changeInvisibleTime(any(), any(), anyString(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(innerAckResult)); AckResult ackResult = this.consumerProcessor.changeInvisibleTime(createContext(), handle, MessageClientIDSetter.createUniqID(), CONSUMER_GROUP, TOPIC, 1000, null, 3000, true).get(); assertEquals(AckStatus.OK, ackResult.getStatus()); assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP, new BrokerConfig().isEnableRetryTopicV2()), requestHeaderArgumentCaptor.getValue().getTopic()); assertEquals(CONSUMER_GROUP, requestHeaderArgumentCaptor.getValue().getConsumerGroup()); assertEquals(1000, requestHeaderArgumentCaptor.getValue().getInvisibleTime().longValue()); assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo()); } @Test public void testLockBatch() throws Throwable { Set mqSet = new HashSet<>(); MessageQueue mq1 = new MessageQueue(TOPIC, "broker1", 0); AddressableMessageQueue addressableMessageQueue1 = new AddressableMessageQueue(mq1, "127.0.0.1"); MessageQueue mq2 = new MessageQueue(TOPIC, "broker2", 0); AddressableMessageQueue addressableMessageQueue2 = new AddressableMessageQueue(mq2, "127.0.0.1"); mqSet.add(mq1); mqSet.add(mq2); when(this.topicRouteService.buildAddressableMessageQueue(any(), any())).thenAnswer(i -> new AddressableMessageQueue((MessageQueue) i.getArguments()[1], "127.0.0.1")); when(this.messageService.lockBatchMQ(any(), eq(addressableMessageQueue1), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Sets.newHashSet(mq1))); when(this.messageService.lockBatchMQ(any(), eq(addressableMessageQueue2), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Sets.newHashSet(mq2))); Set result = this.consumerProcessor.lockBatchMQ(ProxyContext.create(), mqSet, CONSUMER_GROUP, CLIENT_ID, 1000) .get(); assertThat(result).isEqualTo(mqSet); } @Test public void testLockBatchPartialSuccess() throws Throwable { Set mqSet = new HashSet<>(); MessageQueue mq1 = new MessageQueue(TOPIC, "broker1", 0); AddressableMessageQueue addressableMessageQueue1 = new AddressableMessageQueue(mq1, "127.0.0.1"); MessageQueue mq2 = new MessageQueue(TOPIC, "broker2", 0); AddressableMessageQueue addressableMessageQueue2 = new AddressableMessageQueue(mq2, "127.0.0.1"); mqSet.add(mq1); mqSet.add(mq2); when(this.topicRouteService.buildAddressableMessageQueue(any(), any())).thenAnswer(i -> new AddressableMessageQueue((MessageQueue) i.getArguments()[1], "127.0.0.1")); when(this.messageService.lockBatchMQ(any(), eq(addressableMessageQueue1), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Sets.newHashSet(mq1))); when(this.messageService.lockBatchMQ(any(), eq(addressableMessageQueue2), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Sets.newHashSet())); Set result = this.consumerProcessor.lockBatchMQ(ProxyContext.create(), mqSet, CONSUMER_GROUP, CLIENT_ID, 1000) .get(); assertThat(result).isEqualTo(Sets.newHashSet(mq1)); } @Test public void testLockBatchPartialSuccessWithException() throws Throwable { Set mqSet = new HashSet<>(); MessageQueue mq1 = new MessageQueue(TOPIC, "broker1", 0); AddressableMessageQueue addressableMessageQueue1 = new AddressableMessageQueue(mq1, "127.0.0.1"); MessageQueue mq2 = new MessageQueue(TOPIC, "broker2", 0); AddressableMessageQueue addressableMessageQueue2 = new AddressableMessageQueue(mq2, "127.0.0.1"); mqSet.add(mq1); mqSet.add(mq2); when(this.topicRouteService.buildAddressableMessageQueue(any(), any())).thenAnswer(i -> new AddressableMessageQueue((MessageQueue) i.getArguments()[1], "127.0.0.1")); when(this.messageService.lockBatchMQ(any(), eq(addressableMessageQueue1), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Sets.newHashSet(mq1))); CompletableFuture> future = new CompletableFuture<>(); future.completeExceptionally(new MQBrokerException(1, "err")); when(this.messageService.lockBatchMQ(any(), eq(addressableMessageQueue2), any(), anyLong())) .thenReturn(future); Set result = this.consumerProcessor.lockBatchMQ(ProxyContext.create(), mqSet, CONSUMER_GROUP, CLIENT_ID, 1000) .get(); assertThat(result).isEqualTo(Sets.newHashSet(mq1)); } @Test public void testPopMessageWithToReturnFilter() throws Throwable { final String tag = "tag"; final long invisibleTime = Duration.ofSeconds(15).toMillis(); ArgumentCaptor messageQueueArgumentCaptor = ArgumentCaptor.forClass(AddressableMessageQueue.class); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(PopMessageRequestHeader.class); List messageExtList = new ArrayList<>(); messageExtList.add(createMessageExt(TOPIC, tag, 0, invisibleTime)); PopResult innerPopResult = new PopResult(PopStatus.FOUND, messageExtList); when(this.messageService.popMessage(any(), messageQueueArgumentCaptor.capture(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(innerPopResult)); when(this.topicRouteService.getCurrentMessageQueueView(any(), anyString())) .thenReturn(mock(MessageQueueView.class)); ArgumentCaptor ackMessageIdArgumentCaptor = ArgumentCaptor.forClass(String.class); when(this.messagingProcessor.ackMessage(any(), any(), ackMessageIdArgumentCaptor.capture(), anyString(), anyString(), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture(mock(AckResult.class))); ArgumentCaptor changeInvisibleTimeMessageIdArgumentCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor changeInvisibleTimeInvisibleTimeArgumentCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor changeInvisibleTimeSuspendArgumentCaptor = ArgumentCaptor.forClass(Boolean.class); when(this.messagingProcessor.changeInvisibleTime(any(), any(), changeInvisibleTimeMessageIdArgumentCaptor.capture(), anyString(), anyString(), changeInvisibleTimeInvisibleTimeArgumentCaptor.capture(), any(), anyLong(), changeInvisibleTimeSuspendArgumentCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(mock(AckResult.class))); AddressableMessageQueue messageQueue = mock(AddressableMessageQueue.class); PopResult popResult = this.consumerProcessor.popMessage( createContext(), (ctx, messageQueueView) -> messageQueue, CONSUMER_GROUP, TOPIC, 60, invisibleTime, Duration.ofSeconds(3).toMillis(), ConsumeInitMode.MAX, FilterAPI.build(TOPIC, tag, ExpressionType.TAG), false, (ctx, consumerGroup, subscriptionData, messageExt) -> { // Return TO_RETURN for the message return PopMessageResultFilter.FilterResult.TO_RETURN; }, null, Duration.ofSeconds(3).toMillis() ).get(); // Verify that changeInvisibleTime was called with suspend=true verify(this.messagingProcessor).changeInvisibleTime(any(), any(), eq(messageExtList.get(0).getMsgId()), eq(CONSUMER_GROUP), eq(TOPIC), eq(Duration.ofSeconds(1).toMillis()), eq(null), eq(MessagingProcessor.DEFAULT_TIMEOUT_MILLS), eq(true)); // Verify that the message was NOT added to the result list assertEquals(PopStatus.FOUND, popResult.getPopStatus()); assertEquals(0, popResult.getMsgFoundList().size()); } @Test public void testChangeInvisibleTimeWithSuspendFalse() throws Throwable { ReceiptHandle handle = create(createMessageExt(MixAll.RETRY_GROUP_TOPIC_PREFIX + TOPIC, "", 0, 3000)); assertNotNull(handle); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(ChangeInvisibleTimeRequestHeader.class); AckResult innerAckResult = new AckResult(); innerAckResult.setStatus(AckStatus.OK); when(this.messageService.changeInvisibleTime(any(), any(), anyString(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(innerAckResult)); AckResult ackResult = this.consumerProcessor.changeInvisibleTime(createContext(), handle, MessageClientIDSetter.createUniqID(), CONSUMER_GROUP, TOPIC, 1000, null, 3000, false).get(); assertEquals(AckStatus.OK, ackResult.getStatus()); assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP, new BrokerConfig().isEnableRetryTopicV2()), requestHeaderArgumentCaptor.getValue().getTopic()); assertEquals(CONSUMER_GROUP, requestHeaderArgumentCaptor.getValue().getConsumerGroup()); assertEquals(1000, requestHeaderArgumentCaptor.getValue().getInvisibleTime().longValue()); assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo()); assertFalse("Suspend should be false", requestHeaderArgumentCaptor.getValue().isSuspend()); } @Test public void testChangeInvisibleTimeWithSuspendTrue() throws Throwable { ReceiptHandle handle = create(createMessageExt(MixAll.RETRY_GROUP_TOPIC_PREFIX + TOPIC, "", 0, 3000)); assertNotNull(handle); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(ChangeInvisibleTimeRequestHeader.class); AckResult innerAckResult = new AckResult(); innerAckResult.setStatus(AckStatus.OK); when(this.messageService.changeInvisibleTime(any(), any(), anyString(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(innerAckResult)); AckResult ackResult = this.consumerProcessor.changeInvisibleTime(createContext(), handle, MessageClientIDSetter.createUniqID(), CONSUMER_GROUP, TOPIC, 1000, null, 3000, true).get(); assertEquals(AckStatus.OK, ackResult.getStatus()); assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP, new BrokerConfig().isEnableRetryTopicV2()), requestHeaderArgumentCaptor.getValue().getTopic()); assertEquals(CONSUMER_GROUP, requestHeaderArgumentCaptor.getValue().getConsumerGroup()); assertEquals(1000, requestHeaderArgumentCaptor.getValue().getInvisibleTime().longValue()); assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo()); assertTrue("Suspend should be true", requestHeaderArgumentCaptor.getValue().isSuspend()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/ProducerProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executors; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.assertj.core.util.Lists; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ProducerProcessorTest extends BaseProcessorTest { private static final String PRODUCER_GROUP = "producerGroup"; private static final String CONSUMER_GROUP = "consumerGroup"; private static final String TOPIC = "topic"; private ProducerProcessor producerProcessor; @Before public void before() throws Throwable { super.before(); this.producerProcessor = new ProducerProcessor(this.messagingProcessor, this.serviceManager, Executors.newCachedThreadPool()); } @Test public void testSendMessage() throws Throwable { when(metadataService.getTopicMessageType(any(), eq(TOPIC))).thenReturn(TopicMessageType.NORMAL); String txId = MessageClientIDSetter.createUniqID(); String msgId = MessageClientIDSetter.createUniqID(); long commitLogOffset = 1000L; long queueOffset = 100L; SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.SEND_OK); sendResult.setTransactionId(txId); sendResult.setMsgId(msgId); sendResult.setOffsetMsgId(createOffsetMsgId(commitLogOffset)); sendResult.setQueueOffset(queueOffset); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(SendMessageRequestHeader.class); when(this.messageService.sendMessage(any(), any(), any(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Lists.newArrayList(sendResult))); List messageList = new ArrayList<>(); Message messageExt = createMessageExt(TOPIC, "tag", 0, 0); messageList.add(messageExt); AddressableMessageQueue messageQueue = mock(AddressableMessageQueue.class); when(messageQueue.getBrokerName()).thenReturn("mockBroker"); ArgumentCaptor brokerNameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor tranStateTableOffsetCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor commitLogOffsetCaptor = ArgumentCaptor.forClass(Long.class); when(transactionService.addTransactionDataByBrokerName( any(), brokerNameCaptor.capture(), anyString(), anyString(), tranStateTableOffsetCaptor.capture(), commitLogOffsetCaptor.capture(), anyString(), any())).thenReturn(mock(TransactionData.class)); List sendResultList = this.producerProcessor.sendMessage( createContext(), (ctx, messageQueueView) -> messageQueue, PRODUCER_GROUP, MessageSysFlag.TRANSACTION_PREPARED_TYPE, messageList, 3000 ).get(); assertNotNull(sendResultList); assertEquals("mockBroker", brokerNameCaptor.getValue()); assertEquals(queueOffset, tranStateTableOffsetCaptor.getValue().longValue()); assertEquals(commitLogOffset, commitLogOffsetCaptor.getValue().longValue()); SendMessageRequestHeader requestHeader = requestHeaderArgumentCaptor.getValue(); assertEquals(PRODUCER_GROUP, requestHeader.getProducerGroup()); assertEquals(TOPIC, requestHeader.getTopic()); } @Test public void testSendRetryMessage() throws Throwable { String txId = MessageClientIDSetter.createUniqID(); String msgId = MessageClientIDSetter.createUniqID(); long commitLogOffset = 1000L; long queueOffset = 100L; SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.SEND_OK); sendResult.setTransactionId(txId); sendResult.setMsgId(msgId); sendResult.setOffsetMsgId(createOffsetMsgId(commitLogOffset)); sendResult.setQueueOffset(queueOffset); ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(SendMessageRequestHeader.class); when(this.messageService.sendMessage(any(), any(), any(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(Lists.newArrayList(sendResult))); List messageExtList = new ArrayList<>(); Message messageExt = createMessageExt(MixAll.getRetryTopic(CONSUMER_GROUP), "tag", 0, 0); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_RECONSUME_TIME, "1"); MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_MAX_RECONSUME_TIMES, "16"); messageExtList.add(messageExt); AddressableMessageQueue messageQueue = mock(AddressableMessageQueue.class); when(messageQueue.getBrokerName()).thenReturn("mockBroker"); ArgumentCaptor brokerNameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor tranStateTableOffsetCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor commitLogOffsetCaptor = ArgumentCaptor.forClass(Long.class); when(transactionService.addTransactionDataByBrokerName( any(), brokerNameCaptor.capture(), anyString(), anyString(), tranStateTableOffsetCaptor.capture(), commitLogOffsetCaptor.capture(), anyString(), any())).thenReturn(mock(TransactionData.class)); List sendResultList = this.producerProcessor.sendMessage( createContext(), (ctx, messageQueueView) -> messageQueue, PRODUCER_GROUP, MessageSysFlag.TRANSACTION_PREPARED_TYPE, messageExtList, 3000 ).get(); assertNotNull(sendResultList); assertEquals("mockBroker", brokerNameCaptor.getValue()); assertEquals(queueOffset, tranStateTableOffsetCaptor.getValue().longValue()); assertEquals(commitLogOffset, commitLogOffsetCaptor.getValue().longValue()); SendMessageRequestHeader requestHeader = requestHeaderArgumentCaptor.getValue(); assertEquals(PRODUCER_GROUP, requestHeader.getProducerGroup()); assertEquals(MixAll.getRetryTopic(CONSUMER_GROUP), requestHeader.getTopic()); assertEquals(1, requestHeader.getReconsumeTimes().intValue()); assertEquals(16, requestHeader.getMaxReconsumeTimes().intValue()); } @Test public void testForwardMessageToDeadLetterQueue() throws Throwable { ArgumentCaptor requestHeaderArgumentCaptor = ArgumentCaptor.forClass(ConsumerSendMsgBackRequestHeader.class); when(this.messageService.sendMessageBack(any(), any(), anyString(), requestHeaderArgumentCaptor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(mock(RemotingCommand.class))); MessageExt messageExt = createMessageExt(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP, new BrokerConfig().isEnableRetryTopicV2()), "", 16, 3000); RemotingCommand remotingCommand = this.producerProcessor.forwardMessageToDeadLetterQueue( createContext(), create(messageExt), messageExt.getMsgId(), CONSUMER_GROUP, TOPIC, null, 3000 ).get(); assertNotNull(remotingCommand); ConsumerSendMsgBackRequestHeader requestHeader = requestHeaderArgumentCaptor.getValue(); assertEquals(messageExt.getTopic(), requestHeader.getOriginTopic()); assertEquals(messageExt.getMsgId(), requestHeader.getOriginMsgId()); assertEquals(CONSUMER_GROUP, requestHeader.getGroup()); } @Test public void testRecallMessage_notDelayMessage() { when(metadataService.getTopicMessageType(any(), any())).thenReturn(TopicMessageType.NORMAL); CompletionException exception = Assert.assertThrows(CompletionException.class, () -> { producerProcessor.recallMessage(createContext(), TOPIC, "handle", 3000).join(); }); assertTrue(exception.getCause() instanceof ProxyException); ProxyException cause = (ProxyException) exception.getCause(); assertEquals(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, cause.getCode()); } @Test public void testRecallMessage_invalidRecallHandle() { when(metadataService.getTopicMessageType(any(), any())).thenReturn(TopicMessageType.DELAY); CompletionException exception = Assert.assertThrows(CompletionException.class, () -> { producerProcessor.recallMessage(createContext(), TOPIC, "handle", 3000).join(); }); assertTrue(exception.getCause() instanceof ProxyException); ProxyException cause = (ProxyException) exception.getCause(); assertEquals("recall handle is invalid", cause.getMessage()); } @Test public void testRecallMessage_success() { when(metadataService.getTopicMessageType(any(), any())).thenReturn(TopicMessageType.DELAY); when(this.messageService.recallMessage(any(), any(), any(), anyLong())) .thenReturn(CompletableFuture.completedFuture("msgId")); String handle = RecallMessageHandle.HandleV1.buildHandle(TOPIC, "brokerName", "timestampStr", "whateverId"); String msgId = producerProcessor.recallMessage(createContext(), TOPIC, handle, 3000).join(); assertEquals("msgId", msgId); } private static String createOffsetMsgId(long commitLogOffset) { int msgIDLength = 4 + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); return MessageDecoder.createMessageId(byteBufferMsgId, MessageExt.socketAddress2ByteBuffer(NetworkUtil.string2SocketAddress("127.0.0.1:10911")), commitLogOffset); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import io.netty.channel.local.LocalChannel; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.proxy.common.ContextVariable; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.service.ServiceManager; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ReceiptHandleProcessorTest extends InitConfigTest { @Mock protected MessagingProcessor messagingProcessor; @Mock protected ServiceManager serviceManager; @Mock protected ConsumerManager consumerManager; @Mock protected MetadataService metadataService; private static final ProxyContext PROXY_CONTEXT = ProxyContext.create(); private static final String CONSUMER_GROUP = "consumerGroup"; private static final String TOPIC = "topic"; private static final String BROKER_NAME = "broker"; private static final int QUEUE_ID = 1; private static final String MESSAGE_ID = "messageId"; private static final long OFFSET = 123L; private static final long INVISIBLE_TIME = 60000L; private static final int RECONSUME_TIMES = 1; private static final String MSG_ID = MessageClientIDSetter.createUniqID(); private MessageReceiptHandle messageReceiptHandle; private ReceiptHandleProcessor receiptHandleProcessor; @Before public void before() throws Throwable { super.before(); when(serviceManager.getConsumerManager()).thenReturn(consumerManager); when(serviceManager.getMetadataService()).thenReturn(metadataService); this.receiptHandleProcessor = new ReceiptHandleProcessor(this.messagingProcessor, this.serviceManager); ProxyConfig config = ConfigurationManager.getProxyConfig(); String receiptHandle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() - INVISIBLE_TIME + config.getRenewAheadTimeMillis() - 5) .invisibleTime(INVISIBLE_TIME) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build().encode(); PROXY_CONTEXT.withVal(ContextVariable.CLIENT_ID, "channel-id"); PROXY_CONTEXT.withVal(ContextVariable.CHANNEL, new LocalChannel()); messageReceiptHandle = new MessageReceiptHandle(CONSUMER_GROUP, TOPIC, QUEUE_ID, receiptHandle, MESSAGE_ID, OFFSET, RECONSUME_TIMES); } @Test public void testStart() throws Exception { receiptHandleProcessor.start(); receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, PROXY_CONTEXT.getChannel(), CONSUMER_GROUP, MSG_ID, messageReceiptHandle); Mockito.when(consumerManager.findChannel(Mockito.eq(CONSUMER_GROUP), Mockito.eq(PROXY_CONTEXT.getChannel()))).thenReturn(Mockito.mock(ClientChannelInfo.class)); Mockito.verify(messagingProcessor, Mockito.timeout(10000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(CONSUMER_GROUP), Mockito.eq(TOPIC), Mockito.eq(ConfigurationManager.getProxyConfig().getDefaultInvisibleTimeMills()), Mockito.eq(null)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/TransactionProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.proxy.service.transaction.EndTransactionRequestData; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; public class TransactionProcessorTest extends BaseProcessorTest { private static final String PRODUCER_GROUP = "producerGroup"; private TransactionProcessor transactionProcessor; @Before public void before() throws Throwable { super.before(); this.transactionProcessor = new TransactionProcessor(this.messagingProcessor, this.serviceManager); } @Test public void testEndTransaction() throws Throwable { testEndTransaction(MessageSysFlag.TRANSACTION_COMMIT_TYPE, TransactionStatus.COMMIT); testEndTransaction(MessageSysFlag.TRANSACTION_NOT_TYPE, TransactionStatus.UNKNOWN); testEndTransaction(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE, TransactionStatus.ROLLBACK); } protected void testEndTransaction(int sysFlag, TransactionStatus transactionStatus) throws Throwable { when(this.messageService.endTransactionOneway(any(), any(), any(), anyLong())).thenReturn(CompletableFuture.completedFuture(null)); ArgumentCaptor commitOrRollbackCaptor = ArgumentCaptor.forClass(Integer.class); when(transactionService.genEndTransactionRequestHeader(any(), anyString(), anyString(), commitOrRollbackCaptor.capture(), anyBoolean(), anyString(), anyString())) .thenReturn(new EndTransactionRequestData("brokerName", new EndTransactionRequestHeader())); this.transactionProcessor.endTransaction( createContext(), "topic", "transactionId", "msgId", PRODUCER_GROUP, transactionStatus, true, 3000 ); assertEquals(sysFlag, commitOrRollbackCaptor.getValue().intValue()); reset(this.messageService); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.processor.channel; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class RemoteChannelTest { @Test public void testEncodeAndDecode() { String remoteProxyIp = "11.193.0.1"; String remoteAddress = "10.152.39.53:9768"; String localAddress = "11.193.0.1:1210"; ChannelProtocolType type = ChannelProtocolType.REMOTING; String extendAttribute = RandomStringUtils.randomAlphabetic(10); RemoteChannel remoteChannel = new RemoteChannel(remoteProxyIp, remoteAddress, localAddress, type, extendAttribute); String encodedData = remoteChannel.encode(); assertNotNull(encodedData); RemoteChannel decodedChannel = RemoteChannel.decode(encodedData); assertEquals(remoteProxyIp, decodedChannel.remoteProxyIp); assertEquals(remoteAddress, decodedChannel.getRemoteAddress()); assertEquals(localAddress, decodedChannel.getLocalAddress()); assertEquals(type, decodedChannel.type); assertEquals(extendAttribute, decodedChannel.extendAttribute); assertNull(RemoteChannel.decode("")); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.netty.AttributeKeys; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class AbstractRemotingActivityTest extends InitConfigTest { private static final String CLIENT_ID = "test@clientId"; AbstractRemotingActivity remotingActivity; @Mock MessagingProcessor messagingProcessorMock; @Spy ChannelHandlerContext ctx = new SimpleChannelHandlerContext(new SimpleChannel(null, "0.0.0.0:0", "1.1.1.1:1")) { @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { return null; } }; @Before public void setup() { remotingActivity = new AbstractRemotingActivity(null, messagingProcessorMock) { @Override protected RemotingCommand processRequest0(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { return null; } }; Channel channel = ctx.channel(); RemotingHelper.setPropertyToAttr(channel, AttributeKeys.CLIENT_ID_KEY, CLIENT_ID); RemotingHelper.setPropertyToAttr(channel, AttributeKeys.LANGUAGE_CODE_KEY, LanguageCode.JAVA); RemotingHelper.setPropertyToAttr(channel, AttributeKeys.VERSION_KEY, MQVersion.CURRENT_VERSION); } @Test public void testRequest() throws Exception { String brokerName = "broker"; RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "remark"); when(messagingProcessorMock.request(any(), eq(brokerName), any(), anyLong())).thenReturn(CompletableFuture.completedFuture( response )); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(ctx, times(1)).writeAndFlush(response); } @Test public void testRequestOneway() throws Exception { String brokerName = "broker"; RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.markOnewayRPC(); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(messagingProcessorMock, times(1)).requestOneway(any(), eq(brokerName), any(), anyLong()); } @Test public void testRequestInvalid() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField("test", "test"); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand.getCode()).isEqualTo(ResponseCode.VERSION_NOT_SUPPORTED); verify(ctx, never()).writeAndFlush(any()); } @Test public void testRequestProxyException() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); String brokerName = "broker"; String remark = "exception"; CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new ProxyException(ProxyExceptionCode.FORBIDDEN, remark)); when(messagingProcessorMock.request(any(), eq(brokerName), any(), anyLong())).thenReturn(future); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(ctx, times(1)).writeAndFlush(captor.capture()); assertThat(captor.getValue().getCode()).isEqualTo(ResponseCode.NO_PERMISSION); } @Test public void testRequestClientException() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); String brokerName = "broker"; String remark = "exception"; CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new MQClientException(remark, null)); when(messagingProcessorMock.request(any(), eq(brokerName), any(), anyLong())).thenReturn(future); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(ctx, times(1)).writeAndFlush(captor.capture()); assertThat(captor.getValue().getCode()).isEqualTo(-1); } @Test public void testRequestBrokerException() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); String brokerName = "broker"; String remark = "exception"; CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new MQBrokerException(ResponseCode.FLUSH_DISK_TIMEOUT, remark)); when(messagingProcessorMock.request(any(), eq(brokerName), any(), anyLong())).thenReturn(future); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(ctx, times(1)).writeAndFlush(captor.capture()); assertThat(captor.getValue().getCode()).isEqualTo(ResponseCode.FLUSH_DISK_TIMEOUT); } @Test public void testRequestAclException() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); String brokerName = "broker"; String remark = "exception"; CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new AclException(remark, ResponseCode.MESSAGE_ILLEGAL)); when(messagingProcessorMock.request(any(), eq(brokerName), any(), anyLong())).thenReturn(future); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(ctx, times(1)).writeAndFlush(captor.capture()); assertThat(captor.getValue().getCode()).isEqualTo(ResponseCode.NO_PERMISSION); } @Test public void testRequestDefaultException() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); String brokerName = "broker"; String remark = "exception"; CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new Exception(remark)); when(messagingProcessorMock.request(any(), eq(brokerName), any(), anyLong())).thenReturn(future); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, brokerName); RemotingCommand remotingCommand = remotingActivity.request(ctx, request, null, 10000); assertThat(remotingCommand).isNull(); verify(ctx, times(1)).writeAndFlush(captor.capture()); assertThat(captor.getValue().getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/GetTopicRouteActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class GetTopicRouteActivityTest { @Mock private RequestPipeline requestPipeline; @Mock private MessagingProcessor messagingProcessor; private GetTopicRouteActivity getTopicRouteActivity; private ChannelHandlerContext ctx; private ProxyContext context; @Before public void setup() throws Exception { getTopicRouteActivity = new GetTopicRouteActivity(requestPipeline, messagingProcessor); ConfigurationManager.initEnv(); ConfigurationManager.initConfig(); Channel channel = new SimpleChannel(null, "0.0.0.0:0", "1.1.1.1:1"); ctx = new SimpleChannelHandlerContext(channel); context = ProxyContext.create(); } @Test public void testProcessRequest0_HighVersion_SerializeWithFeatures() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, null); request.setVersion(MQVersion.Version.V4_9_4.ordinal()); GetRouteInfoRequestHeader header = new GetRouteInfoRequestHeader(); header.setTopic("TestTopic"); header.setAcceptStandardJsonOnly(false); request.writeCustomHeader(header); TopicRouteData topicRouteData = prepareTopicRouteData(); TopicRouteData spyTopicRouteData = Mockito.spy(topicRouteData); ProxyTopicRouteData proxyTopicRouteData = mock(ProxyTopicRouteData.class); when(proxyTopicRouteData.buildTopicRouteData()).thenReturn(spyTopicRouteData); when(messagingProcessor.getTopicRouteDataForProxy(any(ProxyContext.class), anyList(), any())) .thenReturn(proxyTopicRouteData); RemotingCommand response = getTopicRouteActivity.processRequest0(ctx, request, context); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(spyTopicRouteData).encode( JSONWriter.Feature.BrowserCompatible, JSONWriter.Feature.MapSortField ); TopicRouteData deserializedData = JSON.parseObject(response.getBody(), TopicRouteData.class); assertEquals(topicRouteData.getOrderTopicConf(), deserializedData.getOrderTopicConf()); assertEquals(topicRouteData.getQueueDatas().size(), deserializedData.getQueueDatas().size()); } @Test public void testProcessRequest0_LowVersion_StandardJsonOnly_SerializeWithFeatures() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, null); request.setVersion(MQVersion.Version.V4_9_3.ordinal()); GetRouteInfoRequestHeader header = new GetRouteInfoRequestHeader(); header.setTopic("TestTopic"); header.setAcceptStandardJsonOnly(true); request.writeCustomHeader(header); TopicRouteData topicRouteData = prepareTopicRouteData(); TopicRouteData spyTopicRouteData = Mockito.spy(topicRouteData); ProxyTopicRouteData proxyTopicRouteData = mock(ProxyTopicRouteData.class); when(proxyTopicRouteData.buildTopicRouteData()).thenReturn(spyTopicRouteData); when(messagingProcessor.getTopicRouteDataForProxy(any(ProxyContext.class), anyList(), any())) .thenReturn(proxyTopicRouteData); RemotingCommand response = getTopicRouteActivity.processRequest0(ctx, request, context); assertNotNull(response); assertEquals(ResponseCode.SUCCESS, response.getCode()); verify(spyTopicRouteData).encode(); } private TopicRouteData prepareTopicRouteData() { TopicRouteData result = new TopicRouteData(); result.setOrderTopicConf("orderTopicConf"); List queueDatas = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-a"); queueData.setPerm(6); queueData.setReadQueueNums(4); queueData.setWriteQueueNums(4); queueData.setTopicSysFlag(0); queueDatas.add(queueData); result.setQueueDatas(queueDatas); List brokerDatas = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("broker-a"); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddrs); brokerDatas.add(brokerData); result.setBrokerDatas(brokerDatas); return result; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.client.ConsumerGroupInfo; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class PullMessageActivityTest extends InitConfigTest { PullMessageActivity pullMessageActivity; @Mock MessagingProcessor messagingProcessorMock; @Mock ConsumerGroupInfo consumerGroupInfoMock; String topic = "topic"; String group = "group"; String brokerName = "brokerName"; String subString = "sub"; String type = "type"; @Spy ChannelHandlerContext ctx = new SimpleChannelHandlerContext(new SimpleChannel(null, "1", "2")) { @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { return null; } }; @Before public void setup() throws Exception { pullMessageActivity = new PullMessageActivity(null, messagingProcessorMock); } @Test public void testPullMessageWithoutSub() throws Exception { when(messagingProcessorMock.getConsumerGroupInfo(any(), eq(group))) .thenReturn(consumerGroupInfoMock); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setSubString(subString); subscriptionData.setExpressionType(type); when(consumerGroupInfoMock.findSubscriptionData(eq(topic))) .thenReturn(subscriptionData); PullMessageRequestHeader header = new PullMessageRequestHeader(); header.setTopic(topic); header.setConsumerGroup(group); header.setQueueId(0); header.setQueueOffset(0L); header.setMaxMsgNums(16); header.setSysFlag(PullSysFlag.buildSysFlag(true, false, false, false)); header.setCommitOffset(0L); header.setSuspendTimeoutMillis(1000L); header.setSubVersion(0L); header.setBrokerName(brokerName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, header); request.makeCustomHeaderToNet(); RemotingCommand expectResponse = RemotingCommand.createResponseCommand(ResponseCode.NO_MESSAGE, "success"); PullMessageRequestHeader newHeader = new PullMessageRequestHeader(); newHeader.setTopic(topic); newHeader.setConsumerGroup(group); newHeader.setQueueId(0); newHeader.setQueueOffset(0L); newHeader.setMaxMsgNums(16); newHeader.setSysFlag(PullSysFlag.buildSysFlag(true, false, true, false)); newHeader.setCommitOffset(0L); newHeader.setSuspendTimeoutMillis(1000L); newHeader.setSubVersion(0L); newHeader.setBrokerName(brokerName); newHeader.setSubscription(subString); newHeader.setExpressionType(type); RemotingCommand matchRequest = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, newHeader); matchRequest.setOpaque(request.getOpaque()); matchRequest.makeCustomHeaderToNet(); ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); when(messagingProcessorMock.request(any(), eq(brokerName), captor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(expectResponse)); RemotingCommand response = pullMessageActivity.processRequest0(ctx, request, null); assertThat(captor.getValue().getExtFields()).isEqualTo(matchRequest.getExtFields()); assertThat(response).isNull(); verify(ctx, times(1)).writeAndFlush(eq(expectResponse)); } @Test public void testPullMessageWithSub() throws Exception { when(messagingProcessorMock.getConsumerGroupInfo(any(), eq(group))) .thenReturn(consumerGroupInfoMock); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setSubString(subString); subscriptionData.setExpressionType(type); when(consumerGroupInfoMock.findSubscriptionData(eq(topic))) .thenReturn(subscriptionData); PullMessageRequestHeader header = new PullMessageRequestHeader(); header.setTopic(topic); header.setConsumerGroup(group); header.setQueueId(0); header.setQueueOffset(0L); header.setMaxMsgNums(16); header.setSysFlag(PullSysFlag.buildSysFlag(true, true, false, false)); header.setCommitOffset(0L); header.setSuspendTimeoutMillis(1000L); header.setSubVersion(0L); header.setBrokerName(brokerName); header.setSubscription(subString); header.setExpressionType(type); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, header); request.makeCustomHeaderToNet(); RemotingCommand expectResponse = RemotingCommand.createResponseCommand(ResponseCode.NO_MESSAGE, "success"); ArgumentCaptor captor = ArgumentCaptor.forClass(RemotingCommand.class); when(messagingProcessorMock.request(any(), eq(brokerName), captor.capture(), anyLong())) .thenReturn(CompletableFuture.completedFuture(expectResponse)); RemotingCommand response = pullMessageActivity.processRequest0(ctx, request, null); assertThat(captor.getValue().getExtFields()).isEqualTo(request.getExtFields()); assertThat(response).isNull(); verify(ctx, times(1)).writeAndFlush(eq(expectResponse)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/RecallMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.util.concurrent.CompletableFuture; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RecallMessageActivityTest extends InitConfigTest { private static final String TOPIC = "topic"; private static final String GROUP = "group"; private static final String BROKER_NAME = "brokerName"; private RecallMessageActivity recallMessageActivity; @Mock private MessagingProcessor messagingProcessor; @Mock private MetadataService metadataService; @Spy private ChannelHandlerContext ctx = new SimpleChannelHandlerContext(new SimpleChannel(null, "1", "2")) { @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { return null; } }; @Before public void init() { recallMessageActivity = new RecallMessageActivity(null, messagingProcessor); when(messagingProcessor.getMetadataService()).thenReturn(metadataService); } @Test public void testRecallMessage_notDelayMessage() { when(metadataService.getTopicMessageType(any(), eq(TOPIC))).thenReturn(TopicMessageType.NORMAL); ProxyException exception = Assert.assertThrows(ProxyException.class, () -> { recallMessageActivity.processRequest0(ctx, mockRequest(), null); }); Assert.assertEquals(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, exception.getCode()); } @Test public void testRecallMessage_success() throws Exception { when(metadataService.getTopicMessageType(any(), eq(TOPIC))).thenReturn(TopicMessageType.DELAY); RemotingCommand request = mockRequest(); RemotingCommand expectResponse = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); when(messagingProcessor.request(any(), eq(BROKER_NAME), eq(request), anyLong())) .thenReturn(CompletableFuture.completedFuture(expectResponse)); RemotingCommand response = recallMessageActivity.processRequest0(ctx, request, null); Assert.assertNull(response); verify(ctx, times(1)).writeAndFlush(eq(expectResponse)); } private RemotingCommand mockRequest() { RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); requestHeader.setProducerGroup(GROUP); requestHeader.setTopic(TOPIC); requestHeader.setRecallHandle("handle"); requestHeader.setBrokerName(BROKER_NAME); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, requestHeader); request.makeCustomHeaderToNet(); return request; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivityTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.activity; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class SendMessageActivityTest extends InitConfigTest { SendMessageActivity sendMessageActivity; @Mock MessagingProcessor messagingProcessorMock; @Mock MetadataService metadataServiceMock; String topic = "topic"; String producerGroup = "group"; String brokerName = "brokerName"; @Spy ChannelHandlerContext ctx = new SimpleChannelHandlerContext(new SimpleChannel(null, "1", "2")) { @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { return null; } }; @Before public void setup() { sendMessageActivity = new SendMessageActivity(null, messagingProcessorMock); when(messagingProcessorMock.getMetadataService()).thenReturn(metadataServiceMock); } @Test public void testSendMessage() throws Exception { when(metadataServiceMock.getTopicMessageType(any(), eq(topic))).thenReturn(TopicMessageType.NORMAL); Message message = new Message(topic, "123".getBytes()); message.putUserProperty("a", "b"); SendMessageRequestHeader sendMessageRequestHeader = new SendMessageRequestHeader(); sendMessageRequestHeader.setTopic(topic); sendMessageRequestHeader.setProducerGroup(producerGroup); sendMessageRequestHeader.setDefaultTopic(""); sendMessageRequestHeader.setDefaultTopicQueueNums(0); sendMessageRequestHeader.setQueueId(0); sendMessageRequestHeader.setSysFlag(0); sendMessageRequestHeader.setBrokerName(brokerName); sendMessageRequestHeader.setProperties(MessageDecoder.messageProperties2String(message.getProperties())); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader); remotingCommand.setBody(message.getBody()); remotingCommand.makeCustomHeaderToNet(); RemotingCommand expectResponse = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "success"); when(messagingProcessorMock.request(any(), eq(brokerName), eq(remotingCommand), anyLong())) .thenReturn(CompletableFuture.completedFuture(expectResponse)); RemotingCommand response = sendMessageActivity.processRequest0(ctx, remotingCommand, null); assertThat(response).isNull(); verify(ctx, times(1)).writeAndFlush(eq(expectResponse)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.channel; import io.netty.channel.Channel; import io.netty.channel.ChannelId; import java.util.HashSet; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.remoting.RemotingProxyOutClient; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class RemotingChannelManagerTest { @Mock private RemotingProxyOutClient remotingProxyOutClient; @Mock private ProxyRelayService proxyRelayService; private final String remoteAddress = "10.152.39.53:9768"; private final String localAddress = "11.193.0.1:1210"; private RemotingChannelManager remotingChannelManager; private final ProxyContext ctx = ProxyContext.createForInner(this.getClass()); @Before public void before() { this.remotingChannelManager = new RemotingChannelManager(this.remotingProxyOutClient, this.proxyRelayService); } @Test public void testCreateChannel() { String group = "group"; String clientId = RandomStringUtils.randomAlphabetic(10); Channel producerChannel = createMockChannel(); RemotingChannel producerRemotingChannel = this.remotingChannelManager.createProducerChannel(ctx, producerChannel, group, clientId); assertNotNull(producerRemotingChannel); assertSame(producerRemotingChannel, this.remotingChannelManager.createProducerChannel(ctx, producerChannel, group, clientId)); Channel consumerChannel = createMockChannel(); RemotingChannel consumerRemotingChannel = this.remotingChannelManager.createConsumerChannel(ctx, consumerChannel, group, clientId, new HashSet<>()); assertSame(consumerRemotingChannel, this.remotingChannelManager.createConsumerChannel(ctx, consumerChannel, group, clientId, new HashSet<>())); assertNotNull(consumerRemotingChannel); assertNotSame(producerRemotingChannel, consumerRemotingChannel); } @Test public void testRemoveProducerChannel() { String group = "group"; String clientId = RandomStringUtils.randomAlphabetic(10); { Channel producerChannel = createMockChannel(); RemotingChannel producerRemotingChannel = this.remotingChannelManager.createProducerChannel(ctx, producerChannel, group, clientId); assertSame(producerRemotingChannel, this.remotingChannelManager.removeProducerChannel(ctx, group, producerRemotingChannel)); assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); } { Channel producerChannel = createMockChannel(); RemotingChannel producerRemotingChannel = this.remotingChannelManager.createProducerChannel(ctx, producerChannel, group, clientId); assertSame(producerRemotingChannel, this.remotingChannelManager.removeProducerChannel(ctx, group, producerChannel)); assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); } } @Test public void testRemoveConsumerChannel() { String group = "group"; String clientId = RandomStringUtils.randomAlphabetic(10); { Channel consumerChannel = createMockChannel(); RemotingChannel consumerRemotingChannel = this.remotingChannelManager.createConsumerChannel(ctx, consumerChannel, group, clientId, new HashSet<>()); assertSame(consumerRemotingChannel, this.remotingChannelManager.removeConsumerChannel(ctx, group, consumerRemotingChannel)); assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); } { Channel consumerChannel = createMockChannel(); RemotingChannel consumerRemotingChannel = this.remotingChannelManager.createConsumerChannel(ctx, consumerChannel, group, clientId, new HashSet<>()); assertSame(consumerRemotingChannel, this.remotingChannelManager.removeConsumerChannel(ctx, group, consumerChannel)); assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); } } @Test public void testRemoveChannel() { String consumerGroup = "consumerGroup"; String producerGroup = "producerGroup"; String clientId = RandomStringUtils.randomAlphabetic(10); Channel consumerChannel = createMockChannel(); RemotingChannel consumerRemotingChannel = this.remotingChannelManager.createConsumerChannel(ctx, consumerChannel, consumerGroup, clientId, new HashSet<>()); Channel producerChannel = createMockChannel(); RemotingChannel producerRemotingChannel = this.remotingChannelManager.createProducerChannel(ctx, producerChannel, producerGroup, clientId); assertSame(consumerRemotingChannel, this.remotingChannelManager.removeChannel(consumerChannel).stream().findFirst().get()); assertSame(producerRemotingChannel, this.remotingChannelManager.removeChannel(producerChannel).stream().findFirst().get()); assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); } private Channel createMockChannel() { return new MockChannel(RandomStringUtils.randomAlphabetic(10)); } private class MockChannel extends SimpleChannel { public MockChannel(String channelId) { super(null, new MockChannelId(channelId), RemotingChannelManagerTest.this.remoteAddress, RemotingChannelManagerTest.this.localAddress); } } private static class MockChannelId implements ChannelId { private final String channelId; public MockChannelId(String channelId) { this.channelId = channelId; } @Override public String asShortText() { return channelId; } @Override public String asLongText() { return channelId; } @Override public int compareTo(@NotNull ChannelId o) { return this.channelId.compareTo(o.asLongText()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.channel; import io.netty.channel.Channel; import java.util.HashSet; import java.util.Set; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; import org.apache.rocketmq.proxy.processor.channel.RemoteChannel; import org.apache.rocketmq.proxy.remoting.RemotingProxyOutClient; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RemotingChannelTest extends InitConfigTest { @Mock private RemotingProxyOutClient remotingProxyOutClient; @Mock private ProxyRelayService proxyRelayService; @Mock private Channel parent; private String clientId; private Set subscriptionData; private RemotingChannel remotingChannel; private final String remoteAddress = "10.152.39.53:9768"; private final String localAddress = "11.193.0.1:1210"; @Before public void before() throws Throwable { super.before(); this.clientId = RandomStringUtils.randomAlphabetic(10); when(parent.remoteAddress()).thenReturn(NetworkUtil.string2SocketAddress(remoteAddress)); when(parent.localAddress()).thenReturn(NetworkUtil.string2SocketAddress(localAddress)); this.subscriptionData = new HashSet<>(); this.subscriptionData.add(FilterAPI.buildSubscriptionData("topic", "subTag")); this.remotingChannel = new RemotingChannel(remotingProxyOutClient, proxyRelayService, parent, clientId, subscriptionData); } @Test public void testChannelExtendAttributeParse() { RemoteChannel remoteChannel = this.remotingChannel.toRemoteChannel(); assertEquals(ChannelProtocolType.REMOTING, remoteChannel.getType()); assertEquals(subscriptionData, RemotingChannel.parseChannelExtendAttribute(remoteChannel)); assertEquals(subscriptionData, RemotingChannel.parseChannelExtendAttribute(this.remotingChannel)); assertNull(RemotingChannel.parseChannelExtendAttribute(mock(GrpcClientChannel.class))); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/HAProxyMessageForwarderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.http2proxy; import io.netty.channel.Channel; import io.netty.handler.codec.haproxy.HAProxyTLV; import org.apache.commons.codec.DecoderException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class HAProxyMessageForwarderTest { private HAProxyMessageForwarder haProxyMessageForwarder; @Mock private Channel outboundChannel; @Before public void setUp() throws Exception { haProxyMessageForwarder = new HAProxyMessageForwarder(outboundChannel); } @Test public void buildHAProxyTLV() throws DecoderException { HAProxyTLV haProxyTLV = haProxyMessageForwarder.buildHAProxyTLV("proxy_protocol_tlv_0xe1", "xxxx"); assert haProxyTLV != null; assert haProxyTLV.typeByteValue() == (byte) 0xe1; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProtocolProxyHandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.remoting.protocol.http2proxy; import io.netty.channel.Channel; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.haproxy.HAProxyMessageEncoder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class Http2ProtocolProxyHandlerTest { private Http2ProtocolProxyHandler http2ProtocolProxyHandler; @Mock private Channel inboundChannel; @Mock private ChannelPipeline inboundPipeline; @Mock private Channel outboundChannel; @Mock private ChannelPipeline outboundPipeline; @Before public void setUp() throws Exception { http2ProtocolProxyHandler = new Http2ProtocolProxyHandler(); } @Test public void configPipeline() { when(inboundChannel.pipeline()).thenReturn(inboundPipeline); when(inboundPipeline.addLast(any(HAProxyMessageForwarder.class))).thenReturn(inboundPipeline); when(outboundChannel.pipeline()).thenReturn(outboundPipeline); when(outboundPipeline.addFirst(any(HAProxyMessageEncoder.class))).thenReturn(outboundPipeline); http2ProtocolProxyHandler.configPipeline(inboundChannel, outboundChannel); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service; import java.util.HashMap; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Ignore; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @Ignore @RunWith(MockitoJUnitRunner.Silent.class) public class BaseServiceTest extends InitConfigTest { protected TopicRouteService topicRouteService; protected MQClientAPIFactory mqClientAPIFactory; protected MQClientAPIExt mqClientAPIExt; protected static final String ERR_TOPIC = "errTopic"; protected static final String TOPIC = "topic"; protected static final String GROUP = "group"; protected static final String BROKER_NAME = "broker"; protected static final String CLUSTER_NAME = "cluster"; protected static final String BROKER_ADDR = "127.0.0.1:10911"; protected final TopicRouteData topicRouteData = new TopicRouteData(); protected final QueueData queueData = new QueueData(); protected final BrokerData brokerData = new BrokerData(); @Before public void before() throws Throwable { super.before(); topicRouteService = mock(TopicRouteService.class); mqClientAPIFactory = mock(MQClientAPIFactory.class); mqClientAPIExt = mock(MQClientAPIExt.class); when(mqClientAPIFactory.getClient()).thenReturn(mqClientAPIExt); queueData.setBrokerName(BROKER_NAME); topicRouteData.setQueueDatas(Lists.newArrayList(queueData)); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR); brokerData.setBrokerAddrs(brokerAddrs); topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData)); when(this.topicRouteService.getAllMessageQueueView(any(), eq(ERR_TOPIC))).thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "")); when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(TOPIC, topicRouteData, null)); when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, topicRouteData, null)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.admin; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultAdminServiceTest { @Mock private MQClientAPIFactory mqClientAPIFactory; @Mock private MQClientAPIExt mqClientAPIExt; private DefaultAdminService defaultAdminService; @Before public void before() { when(mqClientAPIFactory.getClient()).thenReturn(mqClientAPIExt); defaultAdminService = new DefaultAdminService(mqClientAPIFactory); } @Test public void testCreateTopic() throws Exception { when(mqClientAPIExt.getTopicRouteInfoFromNameServer(eq("createTopic"), anyLong())) .thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "")) .thenReturn(createTopicRouteData(1)); when(mqClientAPIExt.getTopicRouteInfoFromNameServer(eq("sampleTopic"), anyLong())) .thenReturn(createTopicRouteData(2)); ArgumentCaptor addrArgumentCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor topicConfigArgumentCaptor = ArgumentCaptor.forClass(TopicConfig.class); doNothing().when(mqClientAPIExt).createTopic(addrArgumentCaptor.capture(), anyString(), topicConfigArgumentCaptor.capture(), anyLong()); assertTrue(defaultAdminService.createTopicOnTopicBrokerIfNotExist( "createTopic", "sampleTopic", 7, 8, true, 1 )); assertEquals(2, addrArgumentCaptor.getAllValues().size()); Set createAddr = new HashSet<>(addrArgumentCaptor.getAllValues()); assertTrue(createAddr.contains("127.0.0.1:10911")); assertTrue(createAddr.contains("127.0.0.2:10911")); assertEquals("createTopic", topicConfigArgumentCaptor.getValue().getTopicName()); assertEquals(7, topicConfigArgumentCaptor.getValue().getWriteQueueNums()); assertEquals(8, topicConfigArgumentCaptor.getValue().getReadQueueNums()); } private TopicRouteData createTopicRouteData(int brokerNum) { TopicRouteData topicRouteData = new TopicRouteData(); for (int i = 0; i < brokerNum; i++) { BrokerData brokerData = new BrokerData(); HashMap addrMap = new HashMap<>(); addrMap.put(0L, "127.0.0." + (i + 1) + ":10911"); brokerData.setBrokerAddrs(addrMap); brokerData.setBrokerName("broker-" + i); brokerData.setCluster("cluster"); topicRouteData.getBrokerDatas().add(brokerData); } return topicRouteData; } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/cert/TlsCertificateManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.cert; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; import org.apache.rocketmq.srvutil.FileWatchService; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class TlsCertificateManagerTest { @Rule public TemporaryFolder tempDir = new TemporaryFolder(); private TlsCertificateManager manager; @Mock private TlsCertificateManager.TlsContextReloadListener listener1; @Mock private TlsCertificateManager.TlsContextReloadListener listener2; private File certFile; private File keyFile; private FileWatchService.Listener fileWatchListener; private Field configField; private ProxyConfig originalConfig; @Before public void setUp() throws Exception { ConfigurationManager.initEnv(); ConfigurationManager.initConfig(); // Create temporary certificate and key files certFile = tempDir.newFile("server.crt"); keyFile = tempDir.newFile("server.key"); try (FileWriter certWriter = new FileWriter(certFile); FileWriter keyWriter = new FileWriter(keyFile)) { certWriter.write("test certificate content"); keyWriter.write("test key content"); } // Set TlsSystemConfig paths TlsSystemConfig.tlsServerCertPath = certFile.getAbsolutePath(); TlsSystemConfig.tlsServerKeyPath = keyFile.getAbsolutePath(); // Create the TlsCertificateManager manager = new TlsCertificateManager(); // Extract the file watch listener using reflection fileWatchListener = extractFileWatchListener(manager); } @After public void tearDown() throws Exception { // Restore the original config if (configField != null && originalConfig != null) { configField.set(null, originalConfig); } } private FileWatchService.Listener extractFileWatchListener(TlsCertificateManager manager) throws Exception { Field fileWatchServiceField = TlsCertificateManager.class.getDeclaredField("fileWatchService"); fileWatchServiceField.setAccessible(true); FileWatchService fileWatchService = (FileWatchService) fileWatchServiceField.get(manager); Field listenerField = FileWatchService.class.getDeclaredField("listener"); listenerField.setAccessible(true); return (FileWatchService.Listener) listenerField.get(fileWatchService); } @Test public void testConstructor() { // The constructor should initialize the FileWatchService with the correct paths assertNotNull(manager); } @Test public void testStartAndShutdown() throws Exception { TlsCertificateManager managerSpy = spy(manager); Field watchServiceField = TlsCertificateManager.class.getDeclaredField("fileWatchService"); watchServiceField.setAccessible(true); FileWatchService watchService = (FileWatchService) watchServiceField.get(managerSpy); FileWatchService watchServiceSpy = spy(watchService); watchServiceField.set(managerSpy, watchServiceSpy); managerSpy.start(); verify(watchServiceSpy).start(); managerSpy.shutdown(); verify(watchServiceSpy).shutdown(); } @Test public void testRegisterAndUnregisterListener() { manager.registerReloadListener(listener1); List listeners = manager.getReloadListeners(); assertEquals(1, listeners.size()); assertTrue(listeners.contains(listener1)); manager.registerReloadListener(listener2); assertEquals(2, listeners.size()); assertTrue(listeners.contains(listener2)); manager.unregisterReloadListener(listener1); assertEquals(1, listeners.size()); assertFalse(listeners.contains(listener1)); assertTrue(listeners.contains(listener2)); manager.registerReloadListener(null); assertEquals(1, listeners.size()); // Should remain unchanged manager.unregisterReloadListener(null); assertEquals(1, listeners.size()); // Should remain unchanged } @Test public void testFileChangeNotification_CertOnly() throws Exception { manager.registerReloadListener(listener1); fileWatchListener.onChanged(certFile.getAbsolutePath()); verify(listener1, never()).onTlsContextReload(); } @Test public void testFileChangeNotification_KeyOnly() throws Exception { manager.registerReloadListener(listener1); fileWatchListener.onChanged(keyFile.getAbsolutePath()); verify(listener1, never()).onTlsContextReload(); } @Test public void testFileChangeNotification_BothFiles() throws Exception { manager.registerReloadListener(listener1); fileWatchListener.onChanged(certFile.getAbsolutePath()); fileWatchListener.onChanged(keyFile.getAbsolutePath()); verify(listener1, times(1)).onTlsContextReload(); } @Test public void testFileChangeNotification_MultipleListeners() throws Exception { manager.registerReloadListener(listener1); manager.registerReloadListener(listener2); fileWatchListener.onChanged(certFile.getAbsolutePath()); fileWatchListener.onChanged(keyFile.getAbsolutePath()); verify(listener1, times(1)).onTlsContextReload(); verify(listener2, times(1)).onTlsContextReload(); } @Test public void testFileChangeNotification_BothFilesReverseOrder() throws Exception { manager.registerReloadListener(listener1); fileWatchListener.onChanged(keyFile.getAbsolutePath()); fileWatchListener.onChanged(certFile.getAbsolutePath()); verify(listener1, times(1)).onTlsContextReload(); } @Test public void testFileChangeNotification_RepeatedChanges() throws Exception { manager.registerReloadListener(listener1); fileWatchListener.onChanged(certFile.getAbsolutePath()); fileWatchListener.onChanged(keyFile.getAbsolutePath()); verify(listener1, times(1)).onTlsContextReload(); fileWatchListener.onChanged(certFile.getAbsolutePath()); fileWatchListener.onChanged(keyFile.getAbsolutePath()); verify(listener1, times(2)).onTlsContextReload(); } @Test public void testFileChangeNotification_UnknownFile() throws Exception { manager.registerReloadListener(listener1); fileWatchListener.onChanged("/unknown/file/path"); verify(listener1, never()).onTlsContextReload(); } @Test public void testFileChangeNotification_ListenerThrowsException() throws Exception { TlsCertificateManager.TlsContextReloadListener exceptionListener = mock(TlsCertificateManager.TlsContextReloadListener.class); doThrow(new RuntimeException("Test exception")).when(exceptionListener).onTlsContextReload(); manager.registerReloadListener(exceptionListener); manager.registerReloadListener(listener1); fileWatchListener.onChanged(certFile.getAbsolutePath()); fileWatchListener.onChanged(keyFile.getAbsolutePath()); verify(exceptionListener, times(1)).onTlsContextReload(); verify(listener1, times(1)).onTlsContextReload(); } @Test public void testInnerCertKeyFileWatchListener() throws Exception { Class innerClass = null; for (Class clazz : TlsCertificateManager.class.getDeclaredClasses()) { if (clazz.getSimpleName().equals("CertKeyFileWatchListener")) { innerClass = clazz; break; } } assertNotNull(innerClass, "CertKeyFileWatchListener class not found"); Constructor constructor = innerClass.getDeclaredConstructor(TlsCertificateManager.class); constructor.setAccessible(true); Object innerListener = constructor.newInstance(manager); manager.registerReloadListener(listener1); Method onChangedMethod = innerClass.getDeclaredMethod("onChanged", String.class); onChangedMethod.setAccessible(true); onChangedMethod.invoke(innerListener, certFile.getAbsolutePath()); verify(listener1, never()).onTlsContextReload(); onChangedMethod.invoke(innerListener, keyFile.getAbsolutePath()); verify(listener1, times(1)).onTlsContextReload(); reset(listener1); onChangedMethod.invoke(innerListener, certFile.getAbsolutePath()); verify(listener1, never()).onTlsContextReload(); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/lite/LiteSubscriptionServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.lite; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.proxy.service.route.MessageQueueSelector; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LiteSubscriptionServiceTest { @Mock private TopicRouteService topicRouteService; @Mock private MQClientAPIFactory mqClientAPIFactory; @Mock private MQClientAPIExt mqClientAPIExt; private LiteSubscriptionService liteSubscriptionService; @Before public void setUp() { liteSubscriptionService = new LiteSubscriptionService(topicRouteService, mqClientAPIFactory); } /** * Test successful case: all brokers sync successfully */ @Test public void testSyncLiteSubscription_Success() throws Exception { ProxyContext ctx = ProxyContext.create(); LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); liteSubscriptionDTO.setTopic("testTopic"); long timeoutMillis = 3000L; MessageQueueView messageQueueView = mock(MessageQueueView.class); MessageQueueSelector readSelector = mock(MessageQueueSelector.class); when(messageQueueView.getReadSelector()).thenReturn(readSelector); AddressableMessageQueue queue1 = mock(AddressableMessageQueue.class); AddressableMessageQueue queue2 = mock(AddressableMessageQueue.class); when(queue1.getBrokerAddr()).thenReturn("broker1:10911"); when(queue2.getBrokerAddr()).thenReturn("broker2:10911"); List readQueues = Arrays.asList(queue1, queue2); when(readSelector.getBrokerActingQueues()).thenReturn(readQueues); when(topicRouteService.getAllMessageQueueView(ctx, "testTopic")).thenReturn(messageQueueView); when(mqClientAPIFactory.getClient()).thenReturn(mqClientAPIExt); when(mqClientAPIExt.syncLiteSubscriptionAsync(anyString(), any(LiteSubscriptionDTO.class), anyLong())) .thenReturn(CompletableFuture.completedFuture(null)) .thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture future = liteSubscriptionService.syncLiteSubscription(ctx, liteSubscriptionDTO, timeoutMillis); assertDoesNotThrow(() -> future.get()); verify(mqClientAPIExt, times(2)).syncLiteSubscriptionAsync(anyString(), any(LiteSubscriptionDTO.class), anyLong()); } /** * Test exception case: topicRouteService throws exception */ @Test public void testSyncLiteSubscription_TopicRouteServiceException() throws Exception { ProxyContext ctx = ProxyContext.create(); LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); liteSubscriptionDTO.setTopic("testTopic"); long timeoutMillis = 3000L; when(topicRouteService.getAllMessageQueueView(ctx, "testTopic")) .thenThrow(new RuntimeException("Topic route error")); CompletableFuture future = liteSubscriptionService.syncLiteSubscription(ctx, liteSubscriptionDTO, timeoutMillis); assertTrue(future.isCompletedExceptionally()); verify(mqClientAPIFactory, never()).getClient(); } /** * Test exception case: some broker sync fails */ @Test public void testSyncLiteSubscription_SomeBrokerFail() throws Exception { ProxyContext ctx = ProxyContext.create(); LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); liteSubscriptionDTO.setTopic("testTopic"); long timeoutMillis = 3000L; MessageQueueView messageQueueView = mock(MessageQueueView.class); MessageQueueSelector readSelector = mock(MessageQueueSelector.class); when(messageQueueView.getReadSelector()).thenReturn(readSelector); AddressableMessageQueue queue1 = mock(AddressableMessageQueue.class); AddressableMessageQueue queue2 = mock(AddressableMessageQueue.class); when(queue1.getBrokerAddr()).thenReturn("broker1:10911"); when(queue2.getBrokerAddr()).thenReturn("broker2:10911"); List readQueues = Arrays.asList(queue1, queue2); when(readSelector.getBrokerActingQueues()).thenReturn(readQueues); when(topicRouteService.getAllMessageQueueView(ctx, "testTopic")).thenReturn(messageQueueView); when(mqClientAPIFactory.getClient()).thenReturn(mqClientAPIExt); CompletableFuture failedFuture = new CompletableFuture<>(); failedFuture.completeExceptionally(new RuntimeException("Broker sync failed")); when(mqClientAPIExt.syncLiteSubscriptionAsync(anyString(), any(LiteSubscriptionDTO.class), anyLong())) .thenReturn(failedFuture); CompletableFuture future = liteSubscriptionService.syncLiteSubscription(ctx, liteSubscriptionDTO, timeoutMillis); assertTrue(future.isCompletedExceptionally()); verify(mqClientAPIExt, times(2)).syncLiteSubscriptionAsync(anyString(), any(LiteSubscriptionDTO.class), anyLong()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/message/ClusterMessageServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ClusterMessageServiceTest { private TopicRouteService topicRouteService; private ClusterMessageService clusterMessageService; @Before public void before() { this.topicRouteService = mock(TopicRouteService.class); MQClientAPIFactory mqClientAPIFactory = mock(MQClientAPIFactory.class); this.clusterMessageService = new ClusterMessageService(this.topicRouteService, mqClientAPIFactory); } @Test public void testAckMessageByInvalidBrokerNameHandle() throws Exception { when(topicRouteService.getBrokerAddr(any(), anyString())).thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "")); try { this.clusterMessageService.ackMessage( ProxyContext.create(), ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis()) .invisibleTime(3000) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName("notExistBroker") .queueId(0) .offset(123) .commitLogOffset(0L) .build(), MessageClientIDSetter.createUniqID(), new AckMessageRequestHeader(), 3000); fail(); } catch (Exception e) { assertTrue(e instanceof ProxyException); ProxyException proxyException = (ProxyException) e; assertEquals(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, proxyException.getCode()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.message; import io.netty.channel.Channel; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.processor.AckMessageProcessor; import org.apache.rocketmq.broker.processor.ChangeInvisibleTimeProcessor; import org.apache.rocketmq.broker.processor.EndTransactionProcessor; import org.apache.rocketmq.broker.processor.PopMessageProcessor; import org.apache.rocketmq.broker.processor.RecallMessageProcessor; import org.apache.rocketmq.broker.processor.SendMessageProcessor; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.proxy.common.ContextVariable; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.service.channel.ChannelManager; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowableOfType; @RunWith(MockitoJUnitRunner.class) public class LocalMessageServiceTest extends InitConfigTest { private LocalMessageService localMessageService; @Mock private SendMessageProcessor sendMessageProcessorMock; @Mock private EndTransactionProcessor endTransactionProcessorMock; @Mock private PopMessageProcessor popMessageProcessorMock; @Mock private ChangeInvisibleTimeProcessor changeInvisibleTimeProcessorMock; @Mock private AckMessageProcessor ackMessageProcessorMock; @Mock private RecallMessageProcessor recallMessageProcessorMock; @Mock private BrokerController brokerControllerMock; private ProxyContext proxyContext; private ChannelManager channelManager; private String topic = "topic"; private String brokerName = "brokerName"; private int queueId = 0; private long queueOffset = 0L; private String transactionId = "transactionId"; private String offsetMessageId = "offsetMessageId"; @Before public void setUp() throws Throwable { super.before(); ConfigurationManager.getProxyConfig().setNamesrvAddr("1.1.1.1"); channelManager = new ChannelManager(); Mockito.when(brokerControllerMock.getSendMessageProcessor()).thenReturn(sendMessageProcessorMock); Mockito.when(brokerControllerMock.getPopMessageProcessor()).thenReturn(popMessageProcessorMock); Mockito.when(brokerControllerMock.getChangeInvisibleTimeProcessor()).thenReturn(changeInvisibleTimeProcessorMock); Mockito.when(brokerControllerMock.getAckMessageProcessor()).thenReturn(ackMessageProcessorMock); Mockito.when(brokerControllerMock.getEndTransactionProcessor()).thenReturn(endTransactionProcessorMock); Mockito.when(brokerControllerMock.getRecallMessageProcessor()).thenReturn(recallMessageProcessorMock); Mockito.when(brokerControllerMock.getBrokerConfig()).thenReturn(new BrokerConfig()); localMessageService = new LocalMessageService(brokerControllerMock, channelManager, null); proxyContext = ProxyContext.create().withVal(ContextVariable.REMOTE_ADDRESS, "0.0.0.1") .withVal(ContextVariable.LOCAL_ADDRESS, "0.0.0.2"); } @Test public void testSendMessageWriteAndFlush() throws Exception { Message message = new Message(topic, "body".getBytes(StandardCharsets.UTF_8)); MessageClientIDSetter.setUniqID(message); List messagesList = Collections.singletonList(message); SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); Mockito.when(sendMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.SEND_MESSAGE; boolean second = Arrays.equals(argument.getBody(), message.getBody()); return first & second; }))).thenAnswer(invocation -> { SimpleChannelHandlerContext simpleChannelHandlerContext = invocation.getArgument(0); RemotingCommand request = invocation.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setOpaque(request.getOpaque()); response.setCode(ResponseCode.SUCCESS); response.setBody(message.getBody()); SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader(); sendMessageResponseHeader.setQueueId(queueId); sendMessageResponseHeader.setQueueOffset(queueOffset); sendMessageResponseHeader.setMsgId(offsetMessageId); sendMessageResponseHeader.setTransactionId(transactionId); simpleChannelHandlerContext.writeAndFlush(response); return null; }); CompletableFuture> future = localMessageService.sendMessage(proxyContext, null, messagesList, requestHeader, 1000L); SendResult sendResult = future.get().get(0); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getMsgId()).isEqualTo(MessageClientIDSetter.getUniqID(message)); assertThat(sendResult.getMessageQueue()) .isEqualTo(new MessageQueue(topic, brokerControllerMock.getBrokerConfig().getBrokerName(), queueId)); assertThat(sendResult.getQueueOffset()).isEqualTo(queueOffset); assertThat(sendResult.getTransactionId()).isEqualTo(transactionId); assertThat(sendResult.getOffsetMsgId()).isEqualTo(offsetMessageId); } @Test public void testSendBatchMessageWriteAndFlush() throws Exception { Message message1 = new Message(topic, "body1".getBytes(StandardCharsets.UTF_8)); Message message2 = new Message(topic, "body2".getBytes(StandardCharsets.UTF_8)); MessageClientIDSetter.setUniqID(message1); MessageClientIDSetter.setUniqID(message2); List messagesList = Arrays.asList(message1, message2); MessageBatch msgBatch = MessageBatch.generateFromList(messagesList); MessageClientIDSetter.setUniqID(msgBatch); byte[] body = msgBatch.encode(); msgBatch.setBody(body); SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); Mockito.when(sendMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.SEND_MESSAGE; boolean second = Arrays.equals(argument.getBody(), body); return first & second; }))).thenAnswer(invocation -> { SimpleChannelHandlerContext simpleChannelHandlerContext = invocation.getArgument(0); RemotingCommand request = invocation.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setOpaque(request.getOpaque()); response.setCode(ResponseCode.SUCCESS); response.setBody(body); SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader(); sendMessageResponseHeader.setQueueId(queueId); sendMessageResponseHeader.setQueueOffset(queueOffset); sendMessageResponseHeader.setMsgId(offsetMessageId); sendMessageResponseHeader.setTransactionId(transactionId); simpleChannelHandlerContext.writeAndFlush(response); return null; }); CompletableFuture> future = localMessageService.sendMessage(proxyContext, null, messagesList, requestHeader, 1000L); SendResult sendResult = future.get().get(0); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); assertThat(sendResult.getMessageQueue()) .isEqualTo(new MessageQueue(topic, brokerControllerMock.getBrokerConfig().getBrokerName(), queueId)); assertThat(sendResult.getQueueOffset()).isEqualTo(queueOffset); assertThat(sendResult.getTransactionId()).isEqualTo(transactionId); assertThat(sendResult.getOffsetMsgId()).isEqualTo(offsetMessageId); } @Test public void testSendMessageError() throws Exception { RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(ResponseCode.SYSTEM_ERROR); Message message = new Message("topic", "body".getBytes(StandardCharsets.UTF_8)); MessageClientIDSetter.setUniqID(message); List messagesList = Collections.singletonList(message); SendMessageRequestHeader sendMessageRequestHeader = new SendMessageRequestHeader(); sendMessageRequestHeader.setTopic(topic); sendMessageRequestHeader.setQueueId(queueId); Mockito.when(sendMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.any(RemotingCommand.class))) .thenReturn(response); CompletableFuture> future = localMessageService.sendMessage(proxyContext, null, messagesList, sendMessageRequestHeader, 1000L); ExecutionException exception = catchThrowableOfType(future::get, ExecutionException.class); assertThat(exception.getCause()).isInstanceOf(ProxyException.class); assertThat(((ProxyException) exception.getCause()).getCode()).isEqualTo(ProxyExceptionCode.INTERNAL_SERVER_ERROR); } @Test public void testSendMessageWithException() throws Exception { Mockito.when(sendMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.any(RemotingCommand.class))) .thenThrow(new RemotingCommandException("test")); Message message = new Message("topic", "body".getBytes(StandardCharsets.UTF_8)); MessageClientIDSetter.setUniqID(message); List messagesList = Collections.singletonList(message); SendMessageRequestHeader sendMessageRequestHeader = new SendMessageRequestHeader(); CompletableFuture> future = localMessageService.sendMessage(proxyContext, null, messagesList, sendMessageRequestHeader, 1000L); ExecutionException exception = catchThrowableOfType(future::get, ExecutionException.class); assertThat(exception.getCause()).isInstanceOf(RemotingCommandException.class); } @Test public void testSendMessageBack() throws Exception { RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); Mockito.when(sendMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.CONSUMER_SEND_MSG_BACK; boolean second = argument.readCustomHeader() instanceof ConsumerSendMsgBackRequestHeader; return first && second; }))).thenReturn(remotingCommand); ConsumerSendMsgBackRequestHeader requestHeader = new ConsumerSendMsgBackRequestHeader(); CompletableFuture future = localMessageService.sendMessageBack(proxyContext, null, null, requestHeader, 1000L); RemotingCommand response = future.get(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @Test public void testEndTransaction() throws Exception { EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader(); localMessageService.endTransactionOneway(proxyContext, null, requestHeader, 1000L); Mockito.verify(endTransactionProcessorMock, Mockito.times(1)).processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.END_TRANSACTION; boolean second = argument.readCustomHeader() instanceof EndTransactionRequestHeader; return first && second; })); } @Test public void testPopMessageWriteAndFlush() throws Exception { int reviveQueueId = 1; long popTime = System.currentTimeMillis(); long invisibleTime = 3000L; long startOffset = 100L; long restNum = 0L; StringBuilder startOffsetStringBuilder = new StringBuilder(); StringBuilder messageOffsetStringBuilder = new StringBuilder(); List messageExtList = new ArrayList<>(); List messageOffsetList = new ArrayList<>(); MessageExt message1 = buildMessageExt(topic, 0, startOffset); messageExtList.add(message1); messageOffsetList.add(startOffset); byte[] body1 = MessageDecoder.encode(message1, false); MessageExt message2 = buildMessageExt(topic, 0, startOffset + 1); messageExtList.add(message2); messageOffsetList.add(startOffset + 1); ExtraInfoUtil.buildStartOffsetInfo(startOffsetStringBuilder, topic, queueId, startOffset); ExtraInfoUtil.buildMsgOffsetInfo(messageOffsetStringBuilder, topic, queueId, messageOffsetList); byte[] body2 = MessageDecoder.encode(message2, false); ByteBuffer byteBuffer1 = ByteBuffer.wrap(body1); ByteBuffer byteBuffer2 = ByteBuffer.wrap(body2); ByteBuffer b3 = ByteBuffer.allocate(byteBuffer1.limit() + byteBuffer2.limit()); b3.put(byteBuffer1); b3.put(byteBuffer2); PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setInvisibleTime(invisibleTime); Mockito.when(popMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.POP_MESSAGE; boolean second = argument.readCustomHeader() instanceof PopMessageRequestHeader; return first && second; }))).thenAnswer(invocation -> { SimpleChannelHandlerContext simpleChannelHandlerContext = invocation.getArgument(0); RemotingCommand request = invocation.getArgument(1); RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); response.setOpaque(request.getOpaque()); response.setCode(ResponseCode.SUCCESS); response.setBody(b3.array()); PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); responseHeader.setStartOffsetInfo(startOffsetStringBuilder.toString()); responseHeader.setMsgOffsetInfo(messageOffsetStringBuilder.toString()); responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); responseHeader.setPopTime(popTime); responseHeader.setRestNum(restNum); responseHeader.setReviveQid(reviveQueueId); simpleChannelHandlerContext.writeAndFlush(response); return null; }); MessageQueue messageQueue = new MessageQueue(topic, brokerName, queueId); CompletableFuture future = localMessageService.popMessage(proxyContext, new AddressableMessageQueue(messageQueue, ""), requestHeader, 1000L); PopResult popResult = future.get(); assertThat(popResult.getPopTime()).isEqualTo(popTime); assertThat(popResult.getInvisibleTime()).isEqualTo(invisibleTime); assertThat(popResult.getPopStatus()).isEqualTo(PopStatus.FOUND); assertThat(popResult.getRestNum()).isEqualTo(restNum); assertThat(popResult.getMsgFoundList().size()).isEqualTo(messageExtList.size()); for (int i = 0; i < popResult.getMsgFoundList().size(); i++) { assertMessageExt(popResult.getMsgFoundList().get(i), messageExtList.get(i)); } } @Test public void testPopMessagePollingTimeout() throws Exception { RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ResponseCode.POLLING_TIMEOUT, ""); Mockito.when(popMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.POP_MESSAGE; boolean second = argument.readCustomHeader() instanceof PopMessageRequestHeader; return first && second; }))).thenReturn(remotingCommand); PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); CompletableFuture future = localMessageService.popMessage(proxyContext, null, requestHeader, 1000L); PopResult popResult = future.get(); assertThat(popResult.getPopStatus()).isEqualTo(PopStatus.POLLING_NOT_FOUND); } @Test public void testChangeInvisibleTime() throws Exception { String messageId = "messageId"; long popTime = System.currentTimeMillis(); long invisibleTime = 3000L; int reviveQueueId = 1; ReceiptHandle handle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(popTime) .invisibleTime(invisibleTime) .reviveQueueId(reviveQueueId) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(brokerName) .queueId(queueId) .offset(queueOffset) .build(); RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); remotingCommand.setCode(ResponseCode.SUCCESS); remotingCommand.setRemark(""); long newPopTime = System.currentTimeMillis(); long newInvisibleTime = 5000L; int newReviveQueueId = 2; ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) remotingCommand.readCustomHeader(); responseHeader.setReviveQid(newReviveQueueId); responseHeader.setInvisibleTime(newInvisibleTime); responseHeader.setPopTime(newPopTime); Mockito.when(changeInvisibleTimeProcessorMock.processRequestAsync(Mockito.any(Channel.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.CHANGE_MESSAGE_INVISIBLETIME; boolean second = argument.readCustomHeader() instanceof ChangeInvisibleTimeRequestHeader; return first && second; }), Mockito.any(Boolean.class))).thenReturn(CompletableFuture.completedFuture(remotingCommand)); ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); CompletableFuture future = localMessageService.changeInvisibleTime(proxyContext, handle, messageId, requestHeader, 1000L); AckResult ackResult = future.get(); assertThat(ackResult.getStatus()).isEqualTo(AckStatus.OK); assertThat(ackResult.getPopTime()).isEqualTo(newPopTime); assertThat(ackResult.getExtraInfo()).isEqualTo(ReceiptHandle.builder() .startOffset(0L) .retrieveTime(newPopTime) .invisibleTime(newInvisibleTime) .reviveQueueId(newReviveQueueId) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(brokerName) .queueId(queueId) .offset(queueOffset) .build() .encode()); } @Test public void testAckMessage() throws Exception { String messageId = "messageId"; long popTime = System.currentTimeMillis(); long invisibleTime = 3000L; int reviveQueueId = 1; ReceiptHandle handle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(popTime) .invisibleTime(invisibleTime) .reviveQueueId(reviveQueueId) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(brokerName) .queueId(queueId) .offset(queueOffset) .build(); RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); Mockito.when(ackMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { boolean first = argument.getCode() == RequestCode.ACK_MESSAGE; boolean second = argument.readCustomHeader() instanceof AckMessageRequestHeader; return first && second; }))).thenReturn(remotingCommand); AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); CompletableFuture future = localMessageService.ackMessage(proxyContext, handle, messageId, requestHeader, 1000L); AckResult ackResult = future.get(); assertThat(ackResult.getStatus()).isEqualTo(AckStatus.OK); } @Test public void testRecallMessage_success() throws Exception { RecallMessageResponseHeader responseHeader = new RecallMessageResponseHeader(); responseHeader.setMsgId("msgId"); RemotingCommand response = RemotingCommand.createResponseCommandWithHeader(ResponseCode.SUCCESS, responseHeader); Mockito.when(recallMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.any())).thenReturn(response); RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); String msgId = localMessageService.recallMessage(proxyContext, "brokerName", requestHeader, 1000L).join(); assertThat(msgId).isEqualTo("msgId"); } @Test public void testRecallMessage_fail() throws Exception { RecallMessageResponseHeader responseHeader = new RecallMessageResponseHeader(); RemotingCommand response = RemotingCommand.createResponseCommandWithHeader(ResponseCode.SLAVE_NOT_AVAILABLE, responseHeader); Mockito.when(recallMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.any())).thenReturn(response); RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader(); CompletionException exception = Assert.assertThrows(CompletionException.class, () -> { localMessageService.recallMessage(proxyContext, "brokerName", requestHeader, 1000L).join(); }); Assert.assertTrue(exception.getCause() instanceof ProxyException); } private MessageExt buildMessageExt(String topic, int queueId, long queueOffset) { MessageExt message1 = new MessageExt(); message1.setTopic(topic); message1.setBody("body".getBytes(StandardCharsets.UTF_8)); message1.setFlag(0); message1.setQueueId(queueId); message1.setQueueOffset(queueOffset); message1.setCommitLogOffset(1000L); message1.setSysFlag(0); message1.setBornTimestamp(0L); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 80); message1.setBornHost(inetSocketAddress); message1.setStoreHost(inetSocketAddress); message1.setReconsumeTimes(0); message1.setPreparedTransactionOffset(0L); message1.putUserProperty("K", "V"); return message1; } private void assertMessageExt(MessageExt messageExt1, MessageExt messageExt2) { assertThat(messageExt1.getBody()).isEqualTo(messageExt2.getBody()); assertThat(messageExt1.getTopic()).isEqualTo(messageExt2.getTopic()); assertThat(messageExt1.getQueueId()).isEqualTo(messageExt2.getQueueId()); assertThat(messageExt1.getQueueOffset()).isEqualTo(messageExt2.getQueueOffset()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.metadata; import java.util.HashMap; import java.util.HashSet; import java.util.Optional; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.service.BaseServiceTest; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; public class ClusterMetadataServiceTest extends BaseServiceTest { private ClusterMetadataService clusterMetadataService; protected static final String BROKER2_ADDR = "127.0.0.2:10911"; @Before public void before() throws Throwable { super.before(); ConfigurationManager.getProxyConfig().setRocketMQClusterName(CLUSTER_NAME); TopicConfigAndQueueMapping topicConfigAndQueueMapping = new TopicConfigAndQueueMapping(); topicConfigAndQueueMapping.setAttributes(new HashMap<>()); topicConfigAndQueueMapping.setTopicMessageType(TopicMessageType.NORMAL); when(this.mqClientAPIExt.getTopicConfig(anyString(), eq(TOPIC), anyLong())).thenReturn(topicConfigAndQueueMapping); when(this.mqClientAPIExt.getSubscriptionGroupConfig(anyString(), eq(GROUP), anyLong())).thenReturn(new SubscriptionGroupConfig()); this.clusterMetadataService = new ClusterMetadataService(this.topicRouteService, this.mqClientAPIFactory); BrokerData brokerData2 = new BrokerData(); brokerData2.setBrokerName("brokerName2"); HashMap addrs = new HashMap<>(); addrs.put(MixAll.MASTER_ID, BROKER2_ADDR); brokerData2.setBrokerAddrs(addrs); brokerData2.setCluster(CLUSTER_NAME); topicRouteData.getBrokerDatas().add(brokerData2); when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(CLUSTER_NAME, topicRouteData, null)); } @Test public void testGetTopicMessageType() { ProxyContext ctx = ProxyContext.create(); assertEquals(TopicMessageType.UNSPECIFIED, this.clusterMetadataService.getTopicMessageType(ctx, ERR_TOPIC)); assertEquals(1, this.clusterMetadataService.topicConfigCache.asMap().size()); assertEquals(TopicMessageType.UNSPECIFIED, this.clusterMetadataService.getTopicMessageType(ctx, ERR_TOPIC)); assertEquals(TopicMessageType.NORMAL, this.clusterMetadataService.getTopicMessageType(ctx, TOPIC)); assertEquals(2, this.clusterMetadataService.topicConfigCache.asMap().size()); } @Test public void testGetSubscriptionGroupConfig() { ProxyContext ctx = ProxyContext.create(); assertNotNull(this.clusterMetadataService.getSubscriptionGroupConfig(ctx, GROUP)); assertEquals(1, this.clusterMetadataService.subscriptionGroupConfigCache.asMap().size()); } @Test public void findOneBroker() { Set resultBrokerNames = new HashSet<>(); // run 1000 times to test the random for (int i = 0; i < 1000; i++) { Optional brokerData = null; try { brokerData = this.clusterMetadataService.findOneBroker(TOPIC); resultBrokerNames.add(brokerData.get().getBrokerName()); } catch (Exception e) { throw new RuntimeException(e); } } // we should choose two brokers assertEquals(2, resultBrokerNames.size()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.mqclient; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.mqclient.DoNothingClientRemotingProcessor; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.GetLiteTopicInfoResponseBody; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseBody; import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @RunWith(MockitoJUnitRunner.class) public class MQClientAPIExtTest { private static final String BROKER_ADDR = "127.0.0.1:10911"; private static final String BROKER_NAME = "brokerName"; private static final long TIMEOUT = 3000; private static final String CONSUMER_GROUP = "group"; private static final String TOPIC = "topic"; @Spy private final MQClientAPIExt mqClientAPI = new MQClientAPIExt(new ClientConfig(), new NettyClientConfig(), new DoNothingClientRemotingProcessor(null), null); @Mock private RemotingClient remotingClient; @Before public void init() throws Exception { Field field = MQClientAPIImpl.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(mqClientAPI, remotingClient); } @Test public void testSendHeartbeatAsync() throws Exception { CompletableFuture future = new CompletableFuture<>(); future.complete(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "")); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); assertNotNull(mqClientAPI.sendHeartbeatAsync(BROKER_ADDR, new HeartbeatData(), TIMEOUT).get()); } @Test public void testSendMessageAsync() throws Exception { AtomicReference msgIdRef = new AtomicReference<>(); CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader(); sendMessageResponseHeader.setMsgId(msgIdRef.get()); sendMessageResponseHeader.setQueueId(0); sendMessageResponseHeader.setQueueOffset(1L); response.setCode(ResponseCode.SUCCESS); response.makeCustomHeaderToNet(); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); MessageExt messageExt = createMessage(); msgIdRef.set(MessageClientIDSetter.getUniqID(messageExt)); SendResult sendResult = mqClientAPI.sendMessageAsync(BROKER_ADDR, BROKER_NAME, messageExt, new SendMessageRequestHeader(), TIMEOUT) .get(); assertNotNull(sendResult); assertEquals(msgIdRef.get(), sendResult.getMsgId()); assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); } @Test public void testSendMessageListAsync() throws Exception { CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader(); sendMessageResponseHeader.setMsgId(""); sendMessageResponseHeader.setQueueId(0); sendMessageResponseHeader.setQueueOffset(1L); response.setCode(ResponseCode.SUCCESS); response.makeCustomHeaderToNet(); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); List messageExtList = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 3; i++) { MessageExt messageExt = createMessage(); sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(messageExt)); messageExtList.add(messageExt); } SendResult sendResult = mqClientAPI.sendMessageAsync(BROKER_ADDR, BROKER_NAME, messageExtList, new SendMessageRequestHeader(), TIMEOUT) .get(); assertNotNull(sendResult); assertEquals(sb.toString(), sendResult.getMsgId()); assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); } @Test public void testSendMessageBackAsync() throws Exception { CompletableFuture future = new CompletableFuture<>(); future.complete(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "")); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); RemotingCommand remotingCommand = mqClientAPI.sendMessageBackAsync(BROKER_ADDR, new ConsumerSendMsgBackRequestHeader(), TIMEOUT) .get(); assertNotNull(remotingCommand); assertEquals(ResponseCode.SUCCESS, remotingCommand.getCode()); } @Test public void testPopMessageAsync() throws Exception { PopResult popResult = new PopResult(PopStatus.POLLING_NOT_FOUND, null); doAnswer((Answer) mock -> { PopCallback popCallback = mock.getArgument(4); popCallback.onSuccess(popResult); return null; }).when(mqClientAPI).popMessageAsync(anyString(), anyString(), any(), anyLong(), any()); assertSame(popResult, mqClientAPI.popMessageAsync(BROKER_ADDR, BROKER_NAME, new PopMessageRequestHeader(), TIMEOUT).get()); } @Test public void testPopLiteMessageAsync() throws Exception { PopResult popResult = new PopResult(PopStatus.FOUND, new ArrayList<>()); doAnswer((Answer) mock -> { PopCallback popCallback = mock.getArgument(4); popCallback.onSuccess(popResult); return null; }).when(mqClientAPI).popLiteMessageAsync(anyString(), anyString(), any(), anyLong(), any()); assertSame(popResult, mqClientAPI.popLiteMessageAsync(BROKER_ADDR, BROKER_NAME, new PopLiteMessageRequestHeader(), TIMEOUT).get()); } @Test public void testPopLiteMessageAsync_Exception() throws Exception { Throwable throwable = new RuntimeException("test exception"); doAnswer((Answer) mock -> { PopCallback popCallback = mock.getArgument(4); popCallback.onException(throwable); return null; }).when(mqClientAPI).popLiteMessageAsync(anyString(), anyString(), any(), anyLong(), any()); CompletableFuture future = mqClientAPI.popLiteMessageAsync(BROKER_ADDR, BROKER_NAME, new PopLiteMessageRequestHeader(), TIMEOUT); assertTrue(future.isCompletedExceptionally()); } @Test public void testAckMessageAsync() throws Exception { AckResult ackResult = new AckResult(); doAnswer((Answer) mock -> { AckCallback ackCallback = mock.getArgument(2); ackCallback.onSuccess(ackResult); return null; }).when(mqClientAPI).ackMessageAsync(anyString(), anyLong(), any(AckCallback.class), any()); assertSame(ackResult, mqClientAPI.ackMessageAsync(BROKER_ADDR, new AckMessageRequestHeader(), TIMEOUT).get()); } @Test public void testBatchAckMessageAsync() throws Exception { AckResult ackResult = new AckResult(); doAnswer((Answer) mock -> { AckCallback ackCallback = mock.getArgument(2); ackCallback.onSuccess(ackResult); return null; }).when(mqClientAPI).batchAckMessageAsync(anyString(), anyLong(), any(AckCallback.class), any()); assertSame(ackResult, mqClientAPI.batchAckMessageAsync(BROKER_ADDR, TOPIC, CONSUMER_GROUP, new ArrayList<>(), TIMEOUT).get()); } @Test public void testChangeInvisibleTimeAsync() throws Exception { AckResult ackResult = new AckResult(); doAnswer((Answer) mock -> { AckCallback ackCallback = mock.getArgument(4); ackCallback.onSuccess(ackResult); return null; }).when(mqClientAPI).changeInvisibleTimeAsync(anyString(), anyString(), any(), anyLong(), any(AckCallback.class)); assertSame(ackResult, mqClientAPI.changeInvisibleTimeAsync(BROKER_ADDR, BROKER_NAME, new ChangeInvisibleTimeRequestHeader(), TIMEOUT).get()); } @Test public void testPullMessageAsync() throws Exception { MessageExt msg1 = createMessage(); byte[] msg1Byte = MessageDecoder.encode(msg1, false); MessageExt msg2 = createMessage(); byte[] msg2Byte = MessageDecoder.encode(msg2, false); ByteBuffer byteBuffer = ByteBuffer.allocate(msg1Byte.length + msg2Byte.length); byteBuffer.put(msg1Byte); byteBuffer.put(msg2Byte); PullResultExt pullResultExt = new PullResultExt(PullStatus.FOUND, 0, 0, 1, null, 0, byteBuffer.array()); doAnswer((Answer) mock -> { PullCallback pullCallback = mock.getArgument(4); pullCallback.onSuccess(pullResultExt); return null; }).when(mqClientAPI).pullMessage(anyString(), any(), anyLong(), any(CommunicationMode.class), any(PullCallback.class)); PullResult pullResult = mqClientAPI.pullMessageAsync(BROKER_ADDR, new PullMessageRequestHeader(), TIMEOUT).get(); assertNotNull(pullResult); assertEquals(2, pullResult.getMsgFoundList().size()); Set msgIdSet = pullResult.getMsgFoundList().stream().map(MessageClientIDSetter::getUniqID).collect(Collectors.toSet()); assertTrue(msgIdSet.contains(MessageClientIDSetter.getUniqID(msg1))); assertTrue(msgIdSet.contains(MessageClientIDSetter.getUniqID(msg2))); } @Test public void testGetConsumerListByGroupAsync() throws Exception { List clientIds = Lists.newArrayList("clientIds"); doAnswer((Answer) mock -> { InvokeCallback invokeCallback = mock.getArgument(3); ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); RemotingCommand response = RemotingCommand.createResponseCommand(GetConsumerListByGroupResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.makeCustomHeaderToNet(); GetConsumerListByGroupResponseBody body = new GetConsumerListByGroupResponseBody(); body.setConsumerIdList(clientIds); response.setBody(body.encode()); responseFuture.putResponse(response); invokeCallback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); List res = mqClientAPI.getConsumerListByGroupAsync(BROKER_ADDR, new GetConsumerListByGroupRequestHeader(), TIMEOUT).get(); assertEquals(clientIds, res); } @Test public void testGetEmptyConsumerListByGroupAsync() throws Exception { doAnswer((Answer) mock -> { InvokeCallback invokeCallback = mock.getArgument(3); ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); RemotingCommand response = RemotingCommand.createResponseCommand(GetConsumerListByGroupRequestHeader.class); response.setCode(ResponseCode.SYSTEM_ERROR); response.makeCustomHeaderToNet(); responseFuture.putResponse(response); invokeCallback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); List res = mqClientAPI.getConsumerListByGroupAsync(BROKER_ADDR, new GetConsumerListByGroupRequestHeader(), TIMEOUT).get(); assertTrue(res.isEmpty()); } @Test public void testGetMaxOffsetAsync() throws Exception { long offset = ThreadLocalRandom.current().nextLong(); doAnswer((Answer) mock -> { InvokeCallback invokeCallback = mock.getArgument(3); ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); RemotingCommand response = RemotingCommand.createResponseCommand(GetMaxOffsetResponseHeader.class); GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(offset); response.setCode(ResponseCode.SUCCESS); response.makeCustomHeaderToNet(); responseFuture.putResponse(response); invokeCallback.operationSucceed(responseFuture.getResponseCommand()); return null; }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader(); requestHeader.setTopic(TOPIC); requestHeader.setQueueId(0); assertEquals(offset, mqClientAPI.getMaxOffset(BROKER_ADDR, requestHeader, TIMEOUT).get().longValue()); } @Test public void testSearchOffsetAsync() throws Exception { long offset = ThreadLocalRandom.current().nextLong(); CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class); SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); responseHeader.setOffset(offset); response.setCode(ResponseCode.SUCCESS); response.makeCustomHeaderToNet(); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); SearchOffsetRequestHeader requestHeader = new SearchOffsetRequestHeader(); requestHeader.setTopic(TOPIC); requestHeader.setQueueId(0); requestHeader.setTimestamp(System.currentTimeMillis()); assertEquals(offset, mqClientAPI.searchOffset(BROKER_ADDR, requestHeader, TIMEOUT).get().longValue()); } protected MessageExt createMessage() { MessageExt messageExt = new MessageExt(); messageExt.setTopic("topic"); messageExt.setBornHost(NetworkUtil.string2SocketAddress("127.0.0.2:8888")); messageExt.setStoreHost(NetworkUtil.string2SocketAddress("127.0.0.1:10911")); messageExt.setBody(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); MessageClientIDSetter.setUniqID(messageExt); return messageExt; } @Test public void testSyncLiteSubscriptionAsync_Success() throws Exception { LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); liteSubscriptionDTO.setTopic("test-topic"); liteSubscriptionDTO.setGroup("test-group"); CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture result = mqClientAPI.syncLiteSubscriptionAsync(BROKER_ADDR, liteSubscriptionDTO, TIMEOUT); assertNotNull(result); result.get(); } @Test public void testSyncLiteSubscriptionAsync_Failure() throws Exception { LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); liteSubscriptionDTO.setTopic("test-topic"); liteSubscriptionDTO.setGroup("test-group"); CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, "System error"); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture result = mqClientAPI.syncLiteSubscriptionAsync(BROKER_ADDR, liteSubscriptionDTO, TIMEOUT); assertNotNull(result); assertTrue(result.isCompletedExceptionally()); try { result.get(); } catch (Exception e) { assertTrue(e.getCause() instanceof MQBrokerException); MQBrokerException brokerException = (MQBrokerException) e.getCause(); assertEquals(ResponseCode.SYSTEM_ERROR, brokerException.getResponseCode()); } } @Test public void testSyncLiteSubscriptionAsync_Exception() throws Exception { LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); liteSubscriptionDTO.setTopic("test-topic"); liteSubscriptionDTO.setGroup("test-group"); CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new RuntimeException("Network error")); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture result = mqClientAPI.syncLiteSubscriptionAsync(BROKER_ADDR, liteSubscriptionDTO, TIMEOUT); assertNotNull(result); assertTrue(result.isCompletedExceptionally()); try { result.get(); } catch (Exception e) { assertTrue(e.getCause() instanceof RuntimeException); assertEquals("Network error", e.getCause().getMessage()); } } @Test public void testSyncLiteSubscriptionAsync_EmptySubscription() throws Exception { LiteSubscriptionDTO liteSubscriptionDTO = new LiteSubscriptionDTO(); CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture result = mqClientAPI.syncLiteSubscriptionAsync(BROKER_ADDR, liteSubscriptionDTO, TIMEOUT); assertNotNull(result); result.get(); } @Test public void testGetLiteTopicInfoAsync_Success() throws Exception { String parentTopic = "parentTopic"; String liteTopic = "liteTopic"; GetLiteTopicInfoResponseBody responseBody = new GetLiteTopicInfoResponseBody(); responseBody.setLiteTopic(liteTopic); responseBody.setParentTopic(parentTopic); CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""); response.setBody(responseBody.encode()); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture result = mqClientAPI.getLiteTopicInfoAsync(BROKER_ADDR, parentTopic, liteTopic, TIMEOUT); assertNotNull(result); GetLiteTopicInfoResponseBody actualBody = result.get(); assertNotNull(actualBody); assertEquals(liteTopic, actualBody.getLiteTopic()); assertEquals(parentTopic, actualBody.getParentTopic()); } @Test public void testGetLiteTopicInfoAsync_Failure() throws Exception { String parentTopic = "parentTopic"; String liteTopic = "liteTopic"; CompletableFuture future = new CompletableFuture<>(); RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, "System error"); future.complete(response); doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture result = mqClientAPI.getLiteTopicInfoAsync(BROKER_ADDR, parentTopic, liteTopic, TIMEOUT); assertNotNull(result); assertTrue(result.isCompletedExceptionally()); try { result.get(); } catch (Exception e) { assertTrue(e.getCause() instanceof MQBrokerException); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/ProxyClientRemotingProcessorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.mqclient; import apache.rocketmq.v2.TelemetryCommand; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.ServerCallStreamObserver; import io.netty.channel.Channel; import java.net.InetSocketAddress; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.proxy.service.client.ProxyClientRemotingProcessor; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.relay.RelayData; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ProxyClientRemotingProcessorTest { @Mock private ProducerManager producerManager; @Mock private GrpcClientSettingsManager grpcClientSettingsManager; @Mock private ProxyRelayService proxyRelayService; @Test public void testTransactionCheck() throws Exception { // Temporarily skip this test on the Mac system as it is flaky if (MixAll.isMac()) { return; } CompletableFuture> proxyRelayResultFuture = new CompletableFuture<>(); when(proxyRelayService.processCheckTransactionState(any(), any(), any(), any())) .thenReturn(new RelayData<>( new TransactionData("brokerName", "topic", 0, 0, "id", System.currentTimeMillis(), 3000), proxyRelayResultFuture)); GrpcClientChannel grpcClientChannel = new GrpcClientChannel(proxyRelayService, grpcClientSettingsManager, null, ProxyContext.create().setRemoteAddress("127.0.0.1:8888").setLocalAddress("127.0.0.1:10911"), "clientId"); when(producerManager.getAvailableChannel(anyString())) .thenReturn(grpcClientChannel); ProxyClientRemotingProcessor processor = new ProxyClientRemotingProcessor(producerManager, null); CheckTransactionStateRequestHeader requestHeader = new CheckTransactionStateRequestHeader(); RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.CHECK_TRANSACTION_STATE, requestHeader); MessageExt message = new MessageExt(); message.setQueueId(0); message.setFlag(12); message.setQueueOffset(0L); message.setCommitLogOffset(100L); message.setSysFlag(0); message.setBornTimestamp(System.currentTimeMillis()); message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); message.setStoreTimestamp(System.currentTimeMillis()); message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); message.setBody("body".getBytes()); message.setTopic("topic"); MessageAccessor.putProperty(message, MessageConst.PROPERTY_PRODUCER_GROUP, "group"); command.setBody(MessageDecoder.encode(message, false)); processor.processRequest(new MockChannelHandlerContext(null), command); ServerCallStreamObserver observer = mock(ServerCallStreamObserver.class); grpcClientChannel.setClientObserver(observer); processor.processRequest(new MockChannelHandlerContext(null), command); verify(observer, times(1)).onNext(any()); // throw exception to test clear observer doThrow(new StatusRuntimeException(Status.CANCELLED)).when(observer).onNext(any()); ExecutorService executorService = Executors.newCachedThreadPool(); AtomicInteger count = new AtomicInteger(); for (int i = 0; i < 100; i++) { executorService.submit(() -> { try { processor.processRequest(new MockChannelHandlerContext(null), command); count.incrementAndGet(); } catch (RemotingCommandException ignored) { } }); } await().atMost(Duration.ofSeconds(3)).until(() -> count.get() == 100); verify(observer, times(2)).onNext(any()); } protected static class MockChannelHandlerContext extends SimpleChannelHandlerContext { public MockChannelHandlerContext(Channel channel) { super(channel); } @Override public Channel channel() { Channel channel = mock(Channel.class); when(channel.remoteAddress()).thenReturn(NetworkUtil.string2SocketAddress("127.0.0.1:10911")); return channel; } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/receipt/DefaultReceiptHandleManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.receipt; import io.netty.channel.Channel; import io.netty.channel.local.LocalChannel; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.consumer.ReceiptHandle; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.state.StateEventListener; import org.apache.rocketmq.proxy.common.RenewEvent; import org.apache.rocketmq.proxy.common.ContextVariable; import org.apache.rocketmq.proxy.common.MessageReceiptHandle; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.common.ProxyException; import org.apache.rocketmq.proxy.common.ProxyExceptionCode; import org.apache.rocketmq.proxy.common.ReceiptHandleGroup; import org.apache.rocketmq.proxy.common.RenewStrategyPolicy; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.common.ReceiptHandleGroupKey; import org.apache.rocketmq.proxy.service.BaseServiceTest; import org.apache.rocketmq.proxy.service.metadata.MetadataService; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.subscription.RetryPolicy; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class DefaultReceiptHandleManagerTest extends BaseServiceTest { private DefaultReceiptHandleManager receiptHandleManager; @Mock protected MessagingProcessor messagingProcessor; @Mock protected MetadataService metadataService; @Mock protected ConsumerManager consumerManager; private static final ProxyContext PROXY_CONTEXT = ProxyContext.create(); private static final String GROUP = "group"; private static final String TOPIC = "topic"; private static final String BROKER_NAME = "broker"; private static final int QUEUE_ID = 1; private static final String MESSAGE_ID = "messageId"; private static final long OFFSET = 123L; private static final long INVISIBLE_TIME = 60000L; private static final int RECONSUME_TIMES = 1; private static final String MSG_ID = MessageClientIDSetter.createUniqID(); private MessageReceiptHandle messageReceiptHandle; private String receiptHandle; @Before public void setup() { receiptHandleManager = new DefaultReceiptHandleManager(metadataService, consumerManager, new StateEventListener() { @Override public void fireEvent(RenewEvent event) { MessageReceiptHandle messageReceiptHandle = event.getMessageReceiptHandle(); ReceiptHandle handle = ReceiptHandle.decode(messageReceiptHandle.getReceiptHandleStr()); messagingProcessor.changeInvisibleTime(PROXY_CONTEXT, handle, messageReceiptHandle.getMessageId(), messageReceiptHandle.getGroup(), messageReceiptHandle.getTopic(), event.getRenewTime()) .whenComplete((v, t) -> { if (t != null) { event.getFuture().completeExceptionally(t); return; } event.getFuture().complete(v); }); } }); ProxyConfig config = ConfigurationManager.getProxyConfig(); receiptHandle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() - INVISIBLE_TIME + config.getRenewAheadTimeMillis() - 5) .invisibleTime(INVISIBLE_TIME) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build().encode(); PROXY_CONTEXT.withVal(ContextVariable.CLIENT_ID, "channel-id"); PROXY_CONTEXT.withVal(ContextVariable.CHANNEL, new LocalChannel()); Mockito.doNothing().when(consumerManager).appendConsumerIdsChangeListener(Mockito.any(ConsumerIdsChangeListener.class)); messageReceiptHandle = new MessageReceiptHandle(GROUP, TOPIC, QUEUE_ID, receiptHandle, MESSAGE_ID, OFFSET, RECONSUME_TIMES); } @Test public void testAddReceiptHandle() { Channel channel = new LocalChannel(); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(new SubscriptionGroupConfig()); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(ConfigurationManager.getProxyConfig().getDefaultInvisibleTimeMills())); } @Test public void testAddDuplicationMessage() { ProxyConfig config = ConfigurationManager.getProxyConfig(); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); { String receiptHandle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() - INVISIBLE_TIME + config.getRenewAheadTimeMillis() - 1000) .invisibleTime(INVISIBLE_TIME) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build().encode(); MessageReceiptHandle messageReceiptHandle = new MessageReceiptHandle(GROUP, TOPIC, QUEUE_ID, receiptHandle, MESSAGE_ID, OFFSET, RECONSUME_TIMES); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); } receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(new SubscriptionGroupConfig()); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); receiptHandleManager.scheduleRenewTask(); ArgumentCaptor handleArgumentCaptor = ArgumentCaptor.forClass(ReceiptHandle.class); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), handleArgumentCaptor.capture(), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(ConfigurationManager.getProxyConfig().getDefaultInvisibleTimeMills())); assertEquals(receiptHandle, handleArgumentCaptor.getValue().encode()); } @Test public void testRenewReceiptHandle() { ProxyConfig config = ConfigurationManager.getProxyConfig(); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); long newInvisibleTime = 18000L; ReceiptHandle newReceiptHandleClass = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() - newInvisibleTime + config.getRenewAheadTimeMillis() - 5) .invisibleTime(newInvisibleTime) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build(); String newReceiptHandle = newReceiptHandleClass.encode(); RetryPolicy retryPolicy = new RenewStrategyPolicy(); AtomicInteger times = new AtomicInteger(0); AckResult ackResult = new AckResult(); ackResult.setStatus(AckStatus.OK); ackResult.setExtraInfo(newReceiptHandle); Mockito.when(messagingProcessor.changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(retryPolicy.nextDelayDuration(times.get())))) .thenReturn(CompletableFuture.completedFuture(ackResult)); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.argThat(r -> r.getInvisibleTime() == INVISIBLE_TIME), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(retryPolicy.nextDelayDuration(times.get()))); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.argThat(r -> r.getInvisibleTime() == newInvisibleTime), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(retryPolicy.nextDelayDuration(times.incrementAndGet()))); receiptHandleManager.scheduleRenewTask(); } @Test public void testRenewExceedMaxRenewTimes() { Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); CompletableFuture ackResultFuture = new CompletableFuture<>(); ackResultFuture.completeExceptionally(new MQClientException(0, "error")); RetryPolicy retryPolicy = new RenewStrategyPolicy(); Mockito.when(messagingProcessor.changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(retryPolicy.nextDelayDuration(messageReceiptHandle.getRenewTimes())))) .thenReturn(ackResultFuture); await().atMost(Duration.ofSeconds(3)).until(() -> { receiptHandleManager.scheduleRenewTask(); try { ReceiptHandleGroup receiptHandleGroup = receiptHandleManager.receiptHandleGroupMap.values().stream().findFirst().get(); return receiptHandleGroup.isEmpty(); } catch (Exception e) { return false; } }); Mockito.verify(messagingProcessor, Mockito.times(3)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(retryPolicy.nextDelayDuration(messageReceiptHandle.getRenewTimes()))); } @Test public void testRenewWithInvalidHandle() { Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); CompletableFuture ackResultFuture = new CompletableFuture<>(); ackResultFuture.completeExceptionally(new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "error")); Mockito.when(messagingProcessor.changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(ConfigurationManager.getProxyConfig().getDefaultInvisibleTimeMills()))) .thenReturn(ackResultFuture); await().atMost(Duration.ofSeconds(1)).until(() -> { receiptHandleManager.scheduleRenewTask(); try { ReceiptHandleGroup receiptHandleGroup = receiptHandleManager.receiptHandleGroupMap.values().stream().findFirst().get(); return receiptHandleGroup.isEmpty(); } catch (Exception e) { return false; } }); } @Test public void testRenewWithErrorThenOK() { ProxyConfig config = ConfigurationManager.getProxyConfig(); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); AtomicInteger count = new AtomicInteger(0); List> futureList = new ArrayList<>(); { CompletableFuture ackResultFuture = new CompletableFuture<>(); ackResultFuture.completeExceptionally(new MQClientException(0, "error")); futureList.add(ackResultFuture); futureList.add(ackResultFuture); } { long newInvisibleTime = 2000L; ReceiptHandle newReceiptHandleClass = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() - newInvisibleTime + config.getRenewAheadTimeMillis() - 5) .invisibleTime(newInvisibleTime) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build(); String newReceiptHandle = newReceiptHandleClass.encode(); AckResult ackResult = new AckResult(); ackResult.setStatus(AckStatus.OK); ackResult.setExtraInfo(newReceiptHandle); futureList.add(CompletableFuture.completedFuture(ackResult)); } { CompletableFuture ackResultFuture = new CompletableFuture<>(); ackResultFuture.completeExceptionally(new MQClientException(0, "error")); futureList.add(ackResultFuture); futureList.add(ackResultFuture); futureList.add(ackResultFuture); futureList.add(ackResultFuture); } RetryPolicy retryPolicy = new RenewStrategyPolicy(); AtomicInteger times = new AtomicInteger(0); for (int i = 0; i < 6; i++) { Mockito.doAnswer((Answer>) mock -> { return futureList.get(count.getAndIncrement()); }).when(messagingProcessor).changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(retryPolicy.nextDelayDuration(times.getAndIncrement()))); } await().pollDelay(Duration.ZERO).pollInterval(Duration.ofMillis(10)).atMost(Duration.ofSeconds(10)).until(() -> { receiptHandleManager.scheduleRenewTask(); try { ReceiptHandleGroup receiptHandleGroup = receiptHandleManager.receiptHandleGroupMap.values().stream().findFirst().get(); return receiptHandleGroup.isEmpty(); } catch (Exception e) { return false; } }); assertEquals(6, count.get()); } @Test public void testRenewReceiptHandleWhenTimeout() { long newInvisibleTime = 200L; long maxRenewMs = ConfigurationManager.getProxyConfig().getRenewMaxTimeMillis(); String newReceiptHandle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis() - maxRenewMs) .invisibleTime(newInvisibleTime) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build().encode(); messageReceiptHandle = new MessageReceiptHandle(GROUP, TOPIC, QUEUE_ID, newReceiptHandle, MESSAGE_ID, OFFSET, RECONSUME_TIMES); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); Mockito.when(messagingProcessor.changeInvisibleTime(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong())) .thenReturn(CompletableFuture.completedFuture(new AckResult())); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(groupConfig.getGroupRetryPolicy().getRetryPolicy().nextDelayDuration(RECONSUME_TIMES))); await().atMost(Duration.ofSeconds(1)).untilAsserted(() -> { ReceiptHandleGroup receiptHandleGroup = receiptHandleManager.receiptHandleGroupMap.values().stream().findFirst().get(); assertTrue(receiptHandleGroup.isEmpty()); }); } @Test public void testRenewReceiptHandleWhenTimeoutWithNoSubscription() { long newInvisibleTime = 0L; String newReceiptHandle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(0) .invisibleTime(newInvisibleTime) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build().encode(); messageReceiptHandle = new MessageReceiptHandle(GROUP, TOPIC, QUEUE_ID, newReceiptHandle, MESSAGE_ID, OFFSET, RECONSUME_TIMES); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(null); Mockito.when(messagingProcessor.changeInvisibleTime(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong())) .thenReturn(CompletableFuture.completedFuture(new AckResult())); receiptHandleManager.scheduleRenewTask(); await().atMost(Duration.ofSeconds(1)).until(() -> { try { ReceiptHandleGroup receiptHandleGroup = receiptHandleManager.receiptHandleGroupMap.values().stream().findFirst().get(); return receiptHandleGroup.isEmpty(); } catch (Exception e) { return false; } }); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(0)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong()); } @Test public void testRenewReceiptHandleWhenNotArrivingTime() { String newReceiptHandle = ReceiptHandle.builder() .startOffset(0L) .retrieveTime(System.currentTimeMillis()) .invisibleTime(INVISIBLE_TIME) .reviveQueueId(1) .topicType(ReceiptHandle.NORMAL_TOPIC) .brokerName(BROKER_NAME) .queueId(QUEUE_ID) .offset(OFFSET) .commitLogOffset(0L) .build().encode(); messageReceiptHandle = new MessageReceiptHandle(GROUP, TOPIC, QUEUE_ID, newReceiptHandle, MESSAGE_ID, OFFSET, RECONSUME_TIMES); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); Mockito.when(consumerManager.findChannel(Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(0)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong()); } @Test public void testRemoveReceiptHandle() { Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); receiptHandleManager.removeReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(0)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong()); } @Test public void testClearGroup() { Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); receiptHandleManager.clearGroup(new ReceiptHandleGroupKey(channel, GROUP)); SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); receiptHandleManager.scheduleRenewTask(); Mockito.verify(messagingProcessor, Mockito.timeout(1000).times(1)) .changeInvisibleTime(Mockito.any(ProxyContext.class), Mockito.any(ReceiptHandle.class), Mockito.eq(MESSAGE_ID), Mockito.eq(GROUP), Mockito.eq(TOPIC), Mockito.eq(ConfigurationManager.getProxyConfig().getInvisibleTimeMillisWhenClear())); } @Test public void testClientOffline() { ArgumentCaptor listenerArgumentCaptor = ArgumentCaptor.forClass(ConsumerIdsChangeListener.class); Mockito.verify(consumerManager, Mockito.times(1)).appendConsumerIdsChangeListener(listenerArgumentCaptor.capture()); Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); receiptHandleManager.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, messageReceiptHandle); listenerArgumentCaptor.getValue().handle(ConsumerGroupEvent.CLIENT_UNREGISTER, GROUP, new ClientChannelInfo(channel, "", LanguageCode.JAVA, 0)); assertTrue(receiptHandleManager.receiptHandleGroupMap.isEmpty()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.channel.SimpleChannelHandlerContext; import org.apache.rocketmq.proxy.service.transaction.TransactionService; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.body.CMResult; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(MockitoJUnitRunner.class) public class LocalProxyRelayServiceTest { private LocalProxyRelayService localProxyRelayService; @Mock private BrokerController brokerControllerMock; @Mock private TransactionService transactionService; @Mock private NettyRemotingServer nettyRemotingServerMock; @Before public void setUp() { localProxyRelayService = new LocalProxyRelayService(brokerControllerMock, transactionService); Mockito.when(brokerControllerMock.getRemotingServer()).thenReturn(nettyRemotingServerMock); } @Test public void testProcessGetConsumerRunningInfo() { ConsumerRunningInfo runningInfo = new ConsumerRunningInfo(); runningInfo.setJstack("jstack"); String remark = "ok"; int opaque = 123; RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_RUNNING_INFO, null); remotingCommand.setOpaque(opaque); GetConsumerRunningInfoRequestHeader requestHeader = new GetConsumerRunningInfoRequestHeader(); requestHeader.setJstackEnable(true); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(RemotingCommand.class); CompletableFuture> future = localProxyRelayService.processGetConsumerRunningInfo(ProxyContext.create(), remotingCommand, requestHeader); future.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, remark, runningInfo)); Mockito.verify(nettyRemotingServerMock, Mockito.times(1)) .processResponseCommand(Mockito.any(SimpleChannelHandlerContext.class), argumentCaptor.capture()); RemotingCommand remotingCommand1 = argumentCaptor.getValue(); assertThat(remotingCommand1.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(remotingCommand1.getRemark()).isEqualTo(remark); assertThat(remotingCommand1.getBody()).isEqualTo(runningInfo.encode()); } @Test public void testProcessConsumeMessageDirectly() { ConsumeMessageDirectlyResultRequestHeader requestHeader = new ConsumeMessageDirectlyResultRequestHeader(); String remark = "ok"; int opaque = 123; RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.CONSUME_MESSAGE_DIRECTLY, null); remotingCommand.setOpaque(opaque); ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); result.setConsumeResult(CMResult.CR_SUCCESS); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(RemotingCommand.class); CompletableFuture> future = localProxyRelayService.processConsumeMessageDirectly(ProxyContext.create(), remotingCommand, requestHeader); future.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, remark, result)); Mockito.verify(nettyRemotingServerMock, Mockito.times(1)) .processResponseCommand(Mockito.any(SimpleChannelHandlerContext.class), argumentCaptor.capture()); RemotingCommand remotingCommand1 = argumentCaptor.getValue(); assertThat(remotingCommand1.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(remotingCommand1.getRemark()).isEqualTo(remark); assertThat(remotingCommand1.getBody()).isEqualTo(result.encode()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/ProxyChannelTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.relay; import io.netty.channel.Channel; import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.NotImplementedException; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.service.transaction.TransactionData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; import org.apache.rocketmq.remoting.protocol.header.GetConsumerRunningInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.NotifyUnsubscribeLiteRequestHeader; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ProxyChannelTest { @Mock private ProxyRelayService proxyRelayService; protected abstract static class MockProxyChannel extends ProxyChannel { protected MockProxyChannel(ProxyRelayService proxyRelayService, Channel parent, String remoteAddress, String localAddress) { super(proxyRelayService, parent, remoteAddress, localAddress); } @Override public boolean isOpen() { return false; } @Override public boolean isActive() { return false; } } @Test public void testWriteAndFlush() throws Exception { when(this.proxyRelayService.processCheckTransactionState(any(), any(), any(), any())) .thenReturn(new RelayData<>(mock(TransactionData.class), new CompletableFuture<>())); ArgumentCaptor consumeMessageDirectlyArgumentCaptor = ArgumentCaptor.forClass(ConsumeMessageDirectlyResultRequestHeader.class); when(this.proxyRelayService.processConsumeMessageDirectly(any(), any(), consumeMessageDirectlyArgumentCaptor.capture())) .thenReturn(new CompletableFuture<>()); ArgumentCaptor getConsumerRunningInfoArgumentCaptor = ArgumentCaptor.forClass(GetConsumerRunningInfoRequestHeader.class); when(this.proxyRelayService.processGetConsumerRunningInfo(any(), any(), getConsumerRunningInfoArgumentCaptor.capture())) .thenReturn(new CompletableFuture<>()); CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader(); checkTransactionStateRequestHeader.setTransactionId(MessageClientIDSetter.createUniqID()); RemotingCommand checkTransactionRequest = RemotingCommand.createRequestCommand(RequestCode.CHECK_TRANSACTION_STATE, checkTransactionStateRequestHeader); MessageExt transactionMessageExt = new MessageExt(); transactionMessageExt.setTopic("topic"); transactionMessageExt.setTags("tags"); transactionMessageExt.setBornHost(NetworkUtil.string2SocketAddress("127.0.0.2:8888")); transactionMessageExt.setStoreHost(NetworkUtil.string2SocketAddress("127.0.0.1:10911")); transactionMessageExt.setBody(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); transactionMessageExt.setMsgId(MessageClientIDSetter.createUniqID()); checkTransactionRequest.setBody(MessageDecoder.encode(transactionMessageExt, false)); GetConsumerRunningInfoRequestHeader consumerRunningInfoRequestHeader = new GetConsumerRunningInfoRequestHeader(); consumerRunningInfoRequestHeader.setConsumerGroup("group"); consumerRunningInfoRequestHeader.setClientId("clientId"); RemotingCommand consumerRunningInfoRequest = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_RUNNING_INFO, consumerRunningInfoRequestHeader); ConsumeMessageDirectlyResultRequestHeader consumeMessageDirectlyResultRequestHeader = new ConsumeMessageDirectlyResultRequestHeader(); consumeMessageDirectlyResultRequestHeader.setConsumerGroup("group"); consumeMessageDirectlyResultRequestHeader.setClientId("clientId"); MessageExt consumeMessageDirectlyMessageExt = new MessageExt(); consumeMessageDirectlyMessageExt.setTopic("topic"); consumeMessageDirectlyMessageExt.setTags("tags"); consumeMessageDirectlyMessageExt.setBornHost(NetworkUtil.string2SocketAddress("127.0.0.2:8888")); consumeMessageDirectlyMessageExt.setStoreHost(NetworkUtil.string2SocketAddress("127.0.0.1:10911")); consumeMessageDirectlyMessageExt.setBody(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); consumeMessageDirectlyMessageExt.setMsgId(MessageClientIDSetter.createUniqID()); RemotingCommand consumeMessageDirectlyResult = RemotingCommand.createRequestCommand(RequestCode.CONSUME_MESSAGE_DIRECTLY, consumeMessageDirectlyResultRequestHeader); consumeMessageDirectlyResult.setBody(MessageDecoder.encode(consumeMessageDirectlyMessageExt, false)); MockProxyChannel channel = new MockProxyChannel(this.proxyRelayService, null, "127.0.0.2:8888", "127.0.0.1:10911") { @Override protected CompletableFuture processOtherMessage(Object msg) { return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processCheckTransaction(CheckTransactionStateRequestHeader header, MessageExt messageExt, TransactionData transactionData, CompletableFuture> responseFuture) { assertEquals(checkTransactionStateRequestHeader, header); assertArrayEquals(transactionMessageExt.getBody(), messageExt.getBody()); return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processGetConsumerRunningInfo(RemotingCommand command, GetConsumerRunningInfoRequestHeader header, CompletableFuture> responseFuture) { assertEquals(consumerRunningInfoRequestHeader, getConsumerRunningInfoArgumentCaptor.getValue()); assertEquals(consumerRunningInfoRequestHeader, header); return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processConsumeMessageDirectly(RemotingCommand command, ConsumeMessageDirectlyResultRequestHeader header, MessageExt messageExt, CompletableFuture> responseFuture) { assertEquals(consumeMessageDirectlyResultRequestHeader, consumeMessageDirectlyArgumentCaptor.getValue()); assertEquals(consumeMessageDirectlyResultRequestHeader, header); assertArrayEquals(consumeMessageDirectlyMessageExt.getBody(), messageExt.getBody()); return CompletableFuture.completedFuture(null); } @Override protected CompletableFuture processNotifyUnsubscribeLite(NotifyUnsubscribeLiteRequestHeader header) { throw new NotImplementedException(); } }; assertTrue(channel.writeAndFlush(checkTransactionRequest).isSuccess()); assertTrue(channel.writeAndFlush(consumerRunningInfoRequest).isSuccess()); assertTrue(channel.writeAndFlush(consumeMessageDirectlyResult).isSuccess()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.net.HostAndPort; import java.util.HashMap; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.service.BaseServiceTest; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.assertj.core.util.Lists; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowableOfType; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; public class ClusterTopicRouteServiceTest extends BaseServiceTest { private ClusterTopicRouteService topicRouteService; protected static final String BROKER2_NAME = "broker2"; protected static final String BROKER2_ADDR = "127.0.0.2:10911"; @Before public void before() throws Throwable { super.before(); this.topicRouteService = new ClusterTopicRouteService(this.mqClientAPIFactory); when(this.mqClientAPIExt.getTopicRouteInfoFromNameServer(eq(TOPIC), anyLong())).thenReturn(topicRouteData); when(this.mqClientAPIExt.getTopicRouteInfoFromNameServer(eq(ERR_TOPIC), anyLong())).thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "")); // build broker BrokerData brokerData = new BrokerData(); brokerData.setCluster(CLUSTER_NAME); brokerData.setBrokerName(BROKER_NAME); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR); brokerData.setBrokerAddrs(brokerAddrs); // build broker2 BrokerData broke2Data = new BrokerData(); broke2Data.setCluster(CLUSTER_NAME); broke2Data.setBrokerName(BROKER2_NAME); HashMap broker2Addrs = new HashMap<>(); broker2Addrs.put(MixAll.MASTER_ID, BROKER2_ADDR); broke2Data.setBrokerAddrs(broker2Addrs); // add brokers TopicRouteData brokerTopicRouteData = new TopicRouteData(); brokerTopicRouteData.setBrokerDatas(Lists.newArrayList(brokerData, broke2Data)); // add queue data QueueData queueData = new QueueData(); queueData.setBrokerName(BROKER_NAME); QueueData queue2Data = new QueueData(); queue2Data.setBrokerName(BROKER2_NAME); brokerTopicRouteData.setQueueDatas(Lists.newArrayList(queueData, queue2Data)); when(this.mqClientAPIExt.getTopicRouteInfoFromNameServer(eq(BROKER_NAME), anyLong())).thenReturn(brokerTopicRouteData); when(this.mqClientAPIExt.getTopicRouteInfoFromNameServer(eq(BROKER2_NAME), anyLong())).thenReturn(brokerTopicRouteData); } @Test public void testGetCurrentMessageQueueView() throws Throwable { ProxyContext ctx = ProxyContext.create(); MQClientException exception = catchThrowableOfType(() -> this.topicRouteService.getCurrentMessageQueueView(ctx, ERR_TOPIC), MQClientException.class); assertTrue(TopicRouteHelper.isTopicNotExistError(exception)); assertEquals(1, this.topicRouteService.topicCache.asMap().size()); assertNotNull(this.topicRouteService.getCurrentMessageQueueView(ctx, TOPIC)); assertEquals(2, this.topicRouteService.topicCache.asMap().size()); } @Test public void testGetBrokerAddr() throws Throwable { ProxyContext ctx = ProxyContext.create(); assertEquals(BROKER_ADDR, topicRouteService.getBrokerAddr(ctx, BROKER_NAME)); assertEquals(BROKER2_ADDR, topicRouteService.getBrokerAddr(ctx, BROKER2_NAME)); } @Test public void testGetTopicRouteForProxy() throws Throwable { ProxyContext ctx = ProxyContext.create(); List
    addressList = Lists.newArrayList(new Address(Address.AddressScheme.IPv4, HostAndPort.fromParts("127.0.0.1", 8888))); ProxyTopicRouteData proxyTopicRouteData = this.topicRouteService.getTopicRouteForProxy(ctx, addressList, TOPIC); assertEquals(1, proxyTopicRouteData.getBrokerDatas().size()); assertEquals(addressList, proxyTopicRouteData.getBrokerDatas().get(0).getBrokerAddrs().get(MixAll.MASTER_ID)); } @Test public void testTopicRouteCaffeineCache() throws InterruptedException { String key = "abc"; String value = key; final AtomicBoolean throwException = new AtomicBoolean(); ThreadPoolExecutor cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor( 10, 10, 30L, TimeUnit.SECONDS, "test", 10); LoadingCache topicCache = Caffeine.newBuilder().maximumSize(30). refreshAfterWrite(2, TimeUnit.SECONDS).executor(cacheRefreshExecutor).build(new CacheLoader() { @Override public @Nullable String load(@NonNull String key) throws Exception { try { if (throwException.get()) { throw new RuntimeException(); } else { throwException.set(true); return value; } } catch (Exception e) { if (TopicRouteHelper.isTopicNotExistError(e)) { return ""; } throw e; } } @Override public @Nullable String reload(@NonNull String key, @NonNull String oldValue) throws Exception { try { return load(key); } catch (Exception e) { return oldValue; } } }); assertThat(value).isEqualTo(topicCache.get(key)); TimeUnit.SECONDS.sleep(5); assertThat(value).isEqualTo(topicCache.get(key)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import com.google.common.net.HostAndPort; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.topic.TopicConfigManager; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.service.BaseServiceTest; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; public class LocalTopicRouteServiceTest extends BaseServiceTest { private static final String LOCAL_BROKER_NAME = "localBroker"; private static final String LOCAL_CLUSTER_NAME = "localCluster"; private static final String LOCAL_HOST = "127.0.0.2"; private static final int LOCAL_PORT = 10911; private static final String LOCAL_ADDR = LOCAL_HOST + ":" + LOCAL_PORT; @Mock private BrokerController brokerController; @Mock private TopicConfigManager topicConfigManager; private ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); private BrokerConfig brokerConfig = new BrokerConfig(); private LocalTopicRouteService topicRouteService; @Before public void before() throws Throwable { super.before(); this.brokerConfig.setBrokerName(LOCAL_BROKER_NAME); this.brokerConfig.setBrokerClusterName(LOCAL_CLUSTER_NAME); when(this.brokerController.getBrokerAddr()).thenReturn(LOCAL_ADDR); when(this.brokerController.getBrokerConfig()).thenReturn(this.brokerConfig); when(this.brokerController.getTopicConfigManager()).thenReturn(this.topicConfigManager); when(this.topicConfigManager.getTopicConfigTable()).thenReturn(this.topicConfigTable); this.topicRouteService = new LocalTopicRouteService(this.brokerController, this.mqClientAPIFactory); when(this.mqClientAPIExt.getTopicRouteInfoFromNameServer(eq(TOPIC), anyLong())).thenReturn(topicRouteData); when(this.mqClientAPIExt.getTopicRouteInfoFromNameServer(eq(ERR_TOPIC), anyLong())).thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "")); } @Test public void testGetCurrentMessageQueueView() throws Throwable { ProxyContext ctx = ProxyContext.create(); this.topicConfigTable.put(TOPIC, new TopicConfig(TOPIC, 3, 2, PermName.PERM_WRITE | PermName.PERM_READ)); MessageQueueView messageQueueView = this.topicRouteService.getCurrentMessageQueueView(ctx, TOPIC); assertEquals(3, messageQueueView.getReadSelector().getQueues().size()); assertEquals(2, messageQueueView.getWriteSelector().getQueues().size()); assertEquals(1, messageQueueView.getReadSelector().getBrokerActingQueues().size()); assertEquals(1, messageQueueView.getWriteSelector().getBrokerActingQueues().size()); assertEquals(LOCAL_ADDR, messageQueueView.getReadSelector().selectOne(true).getBrokerAddr()); assertEquals(LOCAL_BROKER_NAME, messageQueueView.getReadSelector().selectOne(true).getBrokerName()); assertEquals(messageQueueView.getReadSelector().selectOne(true), messageQueueView.getWriteSelector().selectOne(true)); } @Test public void testGetTopicRouteForProxy() throws Throwable { ProxyContext ctx = ProxyContext.create(); ProxyTopicRouteData proxyTopicRouteData = this.topicRouteService.getTopicRouteForProxy(ctx, new ArrayList<>(), TOPIC); assertEquals(1, proxyTopicRouteData.getBrokerDatas().size()); assertEquals( Lists.newArrayList(new Address(Address.AddressScheme.IPv4, HostAndPort.fromParts( HostAndPort.fromString(BROKER_ADDR).getHost(), ConfigurationManager.getProxyConfig().getGrpcServerPort()))), proxyTopicRouteData.getBrokerDatas().get(0).getBrokerAddrs().get(MixAll.MASTER_ID)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueuePenalizerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.tuple.Pair; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class MessageQueuePenalizerTest { /** * Test evaluatePenalty with null messageQueue should throw NullPointerException */ @Test(expected = NullPointerException.class) public void testEvaluatePenalty_NullMessageQueue() { List> penalizers = new ArrayList<>(); penalizers.add(mq -> 10); MessageQueuePenalizer.evaluatePenalty(null, penalizers); } /** * Test evaluatePenalty with null penalizers should return 0 */ @Test public void testEvaluatePenalty_NullPenalizers() { MessageQueue mq = new MessageQueue("topic", "broker", 0); int penalty = MessageQueuePenalizer.evaluatePenalty(mq, null); assertEquals(0, penalty); } /** * Test evaluatePenalty with empty penalizers should return 0 */ @Test public void testEvaluatePenalty_EmptyPenalizers() { MessageQueue mq = new MessageQueue("topic", "broker", 0); int penalty = MessageQueuePenalizer.evaluatePenalty(mq, Collections.emptyList()); assertEquals(0, penalty); } /** * Test evaluatePenalty aggregates penalties from multiple penalizers by summing them up */ @Test public void testEvaluatePenalty_MultiplePenalizers() { MessageQueue mq = new MessageQueue("topic", "broker", 0); List> penalizers = Arrays.asList( q -> 10, q -> 20, q -> 5 ); int penalty = MessageQueuePenalizer.evaluatePenalty(mq, penalizers); assertEquals(35, penalty); } /** * Test evaluatePenalty with negative penalties (sum should still work) */ @Test public void testEvaluatePenalty_NegativePenalties() { MessageQueue mq = new MessageQueue("topic", "broker", 0); List> penalizers = Arrays.asList( q -> -5, q -> 10, q -> -3 ); int penalty = MessageQueuePenalizer.evaluatePenalty(mq, penalizers); assertEquals(2, penalty); } /** * Test selectLeastPenalty with null queues should return null */ @Test public void testSelectLeastPenalty_NullQueues() { List> penalizers = Collections.singletonList(mq -> 10); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenalty(null, penalizers, startIndex); assertNull(result); } /** * Test selectLeastPenalty with empty queues should return null */ @Test public void testSelectLeastPenalty_EmptyQueues() { List> penalizers = Collections.singletonList(mq -> 10); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenalty( Collections.emptyList(), penalizers, startIndex); assertNull(result); } /** * Test selectLeastPenalty selects the queue with the lowest penalty */ @Test public void testSelectLeastPenalty_LowestPenalty() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); MessageQueue mq2 = new MessageQueue("topic", "broker", 2); List queues = Arrays.asList(mq0, mq1, mq2); // Penalizer that assigns different penalties based on queue id List> penalizers = Collections.singletonList( mq -> mq.getQueueId() == 0 ? 50 : (mq.getQueueId() == 1 ? 10 : 30) ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex); assertNotNull(result); assertEquals(mq1, result.getLeft()); assertEquals(10, result.getRight().intValue()); } /** * Test selectLeastPenalty short-circuits when penalty <= 0 */ @Test public void testSelectLeastPenalty_ShortCircuitZeroPenalty() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); MessageQueue mq2 = new MessageQueue("topic", "broker", 2); List queues = Arrays.asList(mq0, mq1, mq2); // mq1 has penalty 0, should short-circuit List> penalizers = Collections.singletonList( mq -> mq.getQueueId() == 0 ? 50 : (mq.getQueueId() == 1 ? 0 : 30) ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex); assertNotNull(result); assertEquals(mq1, result.getLeft()); assertEquals(0, result.getRight().intValue()); } /** * Test selectLeastPenalty short-circuits when penalty is negative */ @Test public void testSelectLeastPenalty_ShortCircuitNegativePenalty() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); MessageQueue mq2 = new MessageQueue("topic", "broker", 2); List queues = Arrays.asList(mq0, mq1, mq2); // mq1 has penalty -5, should short-circuit List> penalizers = Collections.singletonList( mq -> mq.getQueueId() == 0 ? 50 : (mq.getQueueId() == 1 ? -5 : 30) ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex); assertNotNull(result); assertEquals(mq1, result.getLeft()); assertEquals(-5, result.getRight().intValue()); } /** * Test selectLeastPenalty with round-robin behavior (rotating start index) * Verifies that startIndex affects the iteration order */ @Test public void testSelectLeastPenalty_RoundRobinStartIndex() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); MessageQueue mq2 = new MessageQueue("topic", "broker", 2); List queues = Arrays.asList(mq0, mq1, mq2); // All queues have penalty 0, so whichever is encountered first will be returned List> penalizers = Collections.singletonList(mq -> 0); // Starting from index 0 AtomicInteger startIndex1 = new AtomicInteger(0); Pair result1 = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex1); assertNotNull(result1); assertEquals(mq0, result1.getLeft()); // Starting from index 1 AtomicInteger startIndex2 = new AtomicInteger(1); Pair result2 = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex2); assertNotNull(result2); assertEquals(mq1, result2.getLeft()); // Starting from index 2 AtomicInteger startIndex3 = new AtomicInteger(2); Pair result3 = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex3); assertNotNull(result3); assertEquals(mq2, result3.getLeft()); } /** * Test selectLeastPenalty increments startIndex for each iteration */ @Test public void testSelectLeastPenalty_IncrementStartIndex() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); MessageQueue mq2 = new MessageQueue("topic", "broker", 2); List queues = Arrays.asList(mq0, mq1, mq2); List> penalizers = Collections.singletonList(mq -> 10); AtomicInteger startIndex = new AtomicInteger(0); MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex); // After iterating through 3 queues, startIndex should be incremented 3 times assertEquals(3, startIndex.get()); } /** * Test selectLeastPenalty handles startIndex wrapping with Math.floorMod */ @Test public void testSelectLeastPenalty_StartIndexWrapping() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); MessageQueue mq2 = new MessageQueue("topic", "broker", 2); List queues = Arrays.asList(mq0, mq1, mq2); List> penalizers = Collections.singletonList(mq -> 0); // Start with large index to test wrapping AtomicInteger startIndex = new AtomicInteger(100); Pair result = MessageQueuePenalizer.selectLeastPenalty(queues, penalizers, startIndex); assertNotNull(result); // 100 % 3 = 1, so should start from mq1 assertEquals(mq1, result.getLeft()); } /** * Test selectLeastPenaltyWithPriority with null queuesWithPriority should return null */ @Test public void testSelectLeastPenaltyWithPriority_NullQueues() { List> penalizers = Collections.singletonList(mq -> 10); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( null, penalizers, startIndex); assertNull(result); } /** * Test selectLeastPenaltyWithPriority with empty queuesWithPriority should return null */ @Test public void testSelectLeastPenaltyWithPriority_EmptyQueues() { List> penalizers = Collections.singletonList(mq -> 10); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( Collections.emptyList(), penalizers, startIndex); assertNull(result); } /** * Test selectLeastPenaltyWithPriority with single priority group delegates to selectLeastPenalty */ @Test public void testSelectLeastPenaltyWithPriority_SinglePriorityGroup() { MessageQueue mq0 = new MessageQueue("topic", "broker", 0); MessageQueue mq1 = new MessageQueue("topic", "broker", 1); List queues = Arrays.asList(mq0, mq1); List> penalizers = Collections.singletonList( mq -> mq.getQueueId() == 0 ? 20 : 10 ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( Collections.singletonList(queues), penalizers, startIndex); assertNotNull(result); assertEquals(mq1, result.getLeft()); assertEquals(10, result.getRight().intValue()); } /** * Test selectLeastPenaltyWithPriority selects queue with lowest penalty across multiple priority groups */ @Test public void testSelectLeastPenaltyWithPriority_MultiplePriorityGroups() { // Priority group 1 (higher priority) MessageQueue mq0 = new MessageQueue("topic", "broker-high", 0); MessageQueue mq1 = new MessageQueue("topic", "broker-high", 1); List highPriorityQueues = Arrays.asList(mq0, mq1); // Priority group 2 (lower priority) MessageQueue mq2 = new MessageQueue("topic", "broker-low", 0); MessageQueue mq3 = new MessageQueue("topic", "broker-low", 1); List lowPriorityQueues = Arrays.asList(mq2, mq3); List> queuesWithPriority = Arrays.asList(highPriorityQueues, lowPriorityQueues); // Assign penalties: high-priority queues have higher penalties, low-priority have lower List> penalizers = Collections.singletonList( mq -> mq.getBrokerName().equals("broker-high") ? 50 : 10 ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( queuesWithPriority, penalizers, startIndex); assertNotNull(result); // Should select from low-priority group because it has lower penalty assertTrue(result.getLeft().getBrokerName().equals("broker-low")); assertEquals(10, result.getRight().intValue()); } /** * Test selectLeastPenaltyWithPriority short-circuits when a priority group yields penalty <= 0 */ @Test public void testSelectLeastPenaltyWithPriority_ShortCircuitZeroPenalty() { // Priority group 1 MessageQueue mq0 = new MessageQueue("topic", "broker-high", 0); List highPriorityQueues = Collections.singletonList(mq0); // Priority group 2 MessageQueue mq1 = new MessageQueue("topic", "broker-low", 0); List lowPriorityQueues = Collections.singletonList(mq1); List> queuesWithPriority = Arrays.asList(highPriorityQueues, lowPriorityQueues); // First group has penalty 0, should short-circuit List> penalizers = Collections.singletonList( mq -> mq.getBrokerName().equals("broker-high") ? 0 : 100 ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( queuesWithPriority, penalizers, startIndex); assertNotNull(result); assertEquals(mq0, result.getLeft()); assertEquals(0, result.getRight().intValue()); } /** * Test selectLeastPenaltyWithPriority when first group encounters zero penalty during iteration */ @Test public void testSelectLeastPenaltyWithPriority_FirstGroupHasZeroPenalty() { // Priority group 1 MessageQueue mq0 = new MessageQueue("topic", "broker1", 0); MessageQueue mq1 = new MessageQueue("topic", "broker1", 1); List group1 = Arrays.asList(mq0, mq1); // Priority group 2 MessageQueue mq2 = new MessageQueue("topic", "broker2", 0); List group2 = Collections.singletonList(mq2); List> queuesWithPriority = Arrays.asList(group1, group2); // mq1 in first group has penalty 0 List> penalizers = Collections.singletonList( mq -> mq.getQueueId() == 1 && mq.getBrokerName().equals("broker1") ? 0 : 50 ); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( queuesWithPriority, penalizers, startIndex); assertNotNull(result); assertEquals(mq1, result.getLeft()); assertEquals(0, result.getRight().intValue()); } /** * Test selectLeastPenaltyWithPriority returns first encountered minimum when multiple groups have same minimum penalty */ @Test public void testSelectLeastPenaltyWithPriority_SameMinimumPenalty() { // Priority group 1 MessageQueue mq0 = new MessageQueue("topic", "broker1", 0); List group1 = Collections.singletonList(mq0); // Priority group 2 MessageQueue mq1 = new MessageQueue("topic", "broker2", 0); List group2 = Collections.singletonList(mq1); // Priority group 3 MessageQueue mq2 = new MessageQueue("topic", "broker3", 0); List group3 = Collections.singletonList(mq2); List> queuesWithPriority = Arrays.asList(group1, group2, group3); // All have same penalty List> penalizers = Collections.singletonList(mq -> 10); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( queuesWithPriority, penalizers, startIndex); assertNotNull(result); // Should return first encountered (from group1) assertEquals(mq0, result.getLeft()); assertEquals(10, result.getRight().intValue()); } /** * Test selectLeastPenaltyWithPriority with complex scenario: * Multiple priority groups with varying penalties */ @Test public void testSelectLeastPenaltyWithPriority_ComplexScenario() { // Priority group 1: penalties 100, 90 MessageQueue mq0 = new MessageQueue("topic", "broker1", 0); MessageQueue mq1 = new MessageQueue("topic", "broker1", 1); List group1 = Arrays.asList(mq0, mq1); // Priority group 2: penalties 50, 30 MessageQueue mq2 = new MessageQueue("topic", "broker2", 0); MessageQueue mq3 = new MessageQueue("topic", "broker2", 1); List group2 = Arrays.asList(mq2, mq3); // Priority group 3: penalties 80, 20 MessageQueue mq4 = new MessageQueue("topic", "broker3", 0); MessageQueue mq5 = new MessageQueue("topic", "broker3", 1); List group3 = Arrays.asList(mq4, mq5); List> queuesWithPriority = Arrays.asList(group1, group2, group3); List> penalizers = Collections.singletonList(mq -> { if (mq.getBrokerName().equals("broker1")) { return mq.getQueueId() == 0 ? 100 : 90; } else if (mq.getBrokerName().equals("broker2")) { return mq.getQueueId() == 0 ? 50 : 30; } else { return mq.getQueueId() == 0 ? 80 : 20; } }); AtomicInteger startIndex = new AtomicInteger(0); Pair result = MessageQueuePenalizer.selectLeastPenaltyWithPriority( queuesWithPriority, penalizers, startIndex); assertNotNull(result); // Should select mq5 from group3 with penalty 20 (the global minimum) assertEquals(mq5, result.getLeft()); assertEquals(20, result.getRight().intValue()); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueuePriorityProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class MessageQueuePriorityProviderTest { @Test public void testPriorityOfWithLambda() { // Test functional interface implementation using lambda MessageQueuePriorityProvider provider = mq -> mq.getQueueId(); MessageQueue queue1 = new MessageQueue("topic", "broker", 0); MessageQueue queue2 = new MessageQueue("topic", "broker", 5); MessageQueue queue3 = new MessageQueue("topic", "broker", 10); assertEquals(0, provider.priorityOf(queue1)); assertEquals(5, provider.priorityOf(queue2)); assertEquals(10, provider.priorityOf(queue3)); } @Test public void testPriorityOfWithConstantValue() { // Test with constant priority MessageQueuePriorityProvider constantProvider = mq -> 1; MessageQueue queue1 = new MessageQueue("topic1", "broker1", 0); MessageQueue queue2 = new MessageQueue("topic2", "broker2", 5); assertEquals(1, constantProvider.priorityOf(queue1)); assertEquals(1, constantProvider.priorityOf(queue2)); } @Test public void testPriorityOfBasedOnBrokerName() { // Test priority based on broker name hash MessageQueuePriorityProvider brokerProvider = mq -> mq.getBrokerName().hashCode() % 10; MessageQueue queue1 = new MessageQueue("topic", "broker-a", 0); MessageQueue queue2 = new MessageQueue("topic", "broker-b", 0); int priority1 = brokerProvider.priorityOf(queue1); int priority2 = brokerProvider.priorityOf(queue2); // Priorities should be deterministic for the same broker assertEquals(priority1, brokerProvider.priorityOf(queue1)); assertEquals(priority2, brokerProvider.priorityOf(queue2)); } @Test public void testBuildPriorityGroupsWithNullList() { MessageQueuePriorityProvider provider = mq -> 0; List> result = MessageQueuePriorityProvider.buildPriorityGroups(null, provider); assertNotNull(result); assertTrue(result.isEmpty()); } @Test public void testBuildPriorityGroupsWithEmptyList() { MessageQueuePriorityProvider provider = mq -> 0; List> result = MessageQueuePriorityProvider.buildPriorityGroups( Collections.emptyList(), provider); assertNotNull(result); assertTrue(result.isEmpty()); } @Test public void testBuildPriorityGroupsWithSinglePriority() { MessageQueuePriorityProvider provider = mq -> 0; List queues = Arrays.asList( new MessageQueue("topic", "broker1", 0), new MessageQueue("topic", "broker1", 1), new MessageQueue("topic", "broker1", 2) ); List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(1, result.size()); assertEquals(3, result.get(0).size()); } @Test public void testBuildPriorityGroupsWithMultiplePriorities() { // Priority based on queue ID: 0->high, 1->medium, 2->low MessageQueuePriorityProvider provider = mq -> { if (mq.getQueueId() < 2) return 0; // High priority if (mq.getQueueId() < 4) return 1; // Medium priority return 2; // Low priority }; List queues = Arrays.asList( new MessageQueue("topic", "broker", 0), // priority 0 new MessageQueue("topic", "broker", 1), // priority 0 new MessageQueue("topic", "broker", 2), // priority 1 new MessageQueue("topic", "broker", 3), // priority 1 new MessageQueue("topic", "broker", 4), // priority 2 new MessageQueue("topic", "broker", 5) // priority 2 ); List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(3, result.size()); // First group (highest priority 0) assertEquals(2, result.get(0).size()); assertEquals(0, result.get(0).get(0).getQueueId()); assertEquals(1, result.get(0).get(1).getQueueId()); // Second group (medium priority 1) assertEquals(2, result.get(1).size()); assertEquals(2, result.get(1).get(0).getQueueId()); assertEquals(3, result.get(1).get(1).getQueueId()); // Third group (low priority 2) assertEquals(2, result.get(2).size()); assertEquals(4, result.get(2).get(0).getQueueId()); assertEquals(5, result.get(2).get(1).getQueueId()); } @Test public void testBuildPriorityGroupsOrderedByPriority() { // Test that groups are ordered from high to low priority (ascending numeric value) MessageQueuePriorityProvider provider = mq -> mq.getQueueId(); List queues = Arrays.asList( new MessageQueue("topic", "broker", 5), new MessageQueue("topic", "broker", 0), new MessageQueue("topic", "broker", 3), new MessageQueue("topic", "broker", 1) ); List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(4, result.size()); // Verify order: 0, 1, 3, 5 (ascending) assertEquals(0, result.get(0).get(0).getQueueId()); assertEquals(1, result.get(1).get(0).getQueueId()); assertEquals(3, result.get(2).get(0).getQueueId()); assertEquals(5, result.get(3).get(0).getQueueId()); } @Test public void testBuildPriorityGroupsWithNegativePriorities() { // Test with negative priority values MessageQueuePriorityProvider provider = mq -> mq.getQueueId() - 5; List queues = Arrays.asList( new MessageQueue("topic", "broker", 0), // priority -5 new MessageQueue("topic", "broker", 5), // priority 0 new MessageQueue("topic", "broker", 10) // priority 5 ); List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(3, result.size()); // Verify order: -5, 0, 5 (ascending) assertEquals(0, result.get(0).get(0).getQueueId()); assertEquals(5, result.get(1).get(0).getQueueId()); assertEquals(10, result.get(2).get(0).getQueueId()); } @Test public void testBuildPriorityGroupsWithMixedBrokers() { // Priority based on broker name MessageQueuePriorityProvider provider = mq -> { if (mq.getBrokerName().equals("broker-high")) return 0; if (mq.getBrokerName().equals("broker-medium")) return 1; return 2; }; List queues = Arrays.asList( new MessageQueue("topic", "broker-high", 0), new MessageQueue("topic", "broker-low", 0), new MessageQueue("topic", "broker-medium", 0), new MessageQueue("topic", "broker-high", 1), new MessageQueue("topic", "broker-medium", 1) ); List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(3, result.size()); // High priority group assertEquals(2, result.get(0).size()); assertEquals("broker-high", result.get(0).get(0).getBrokerName()); assertEquals("broker-high", result.get(0).get(1).getBrokerName()); // Medium priority group assertEquals(2, result.get(1).size()); assertEquals("broker-medium", result.get(1).get(0).getBrokerName()); // Low priority group assertEquals(1, result.get(2).size()); assertEquals("broker-low", result.get(2).get(0).getBrokerName()); } @Test public void testBuildPriorityGroupsPreservesQueueOrder() { // Test that queues with same priority maintain their relative order MessageQueuePriorityProvider provider = mq -> 0; List queues = new ArrayList<>(); for (int i = 0; i < 10; i++) { queues.add(new MessageQueue("topic", "broker", i)); } List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(1, result.size()); assertEquals(10, result.get(0).size()); // Verify order is maintained for (int i = 0; i < 10; i++) { assertEquals(i, result.get(0).get(i).getQueueId()); } } @Test public void testBuildPriorityGroupsWithCustomMessageQueue() { // Test with extended MessageQueue type class CustomMessageQueue extends MessageQueue { private int customPriority; public CustomMessageQueue(String topic, String brokerName, int queueId, int customPriority) { super(topic, brokerName, queueId); this.customPriority = customPriority; } public int getCustomPriority() { return customPriority; } } MessageQueuePriorityProvider provider = CustomMessageQueue::getCustomPriority; List queues = Arrays.asList( new CustomMessageQueue("topic", "broker", 0, 2), new CustomMessageQueue("topic", "broker", 1, 0), new CustomMessageQueue("topic", "broker", 2, 1) ); List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(3, result.size()); // Verify order by custom priority: 0, 1, 2 assertEquals(0, result.get(0).get(0).getCustomPriority()); assertEquals(1, result.get(1).get(0).getCustomPriority()); assertEquals(2, result.get(2).get(0).getCustomPriority()); } @Test public void testBuildPriorityGroupsWithLargeNumberOfQueues() { // Test with large number of queues MessageQueuePriorityProvider provider = mq -> mq.getQueueId() % 5; List queues = new ArrayList<>(); for (int i = 0; i < 100; i++) { queues.add(new MessageQueue("topic", "broker", i)); } List> result = MessageQueuePriorityProvider.buildPriorityGroups(queues, provider); assertNotNull(result); assertEquals(5, result.size()); // 5 different priorities (0-4) // Each group should have 20 queues (100 / 5) for (List group : result) { assertEquals(20, group.size()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.route; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.proxy.service.BaseServiceTest; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class MessageQueueSelectorTest extends BaseServiceTest { @Test public void testReadMessageQueue() { queueData.setPerm(PermName.PERM_READ); queueData.setReadQueueNums(0); MessageQueueSelector messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), true); assertTrue(messageQueueSelector.getQueues().isEmpty()); queueData.setPerm(PermName.PERM_READ); queueData.setReadQueueNums(3); messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), true); assertEquals(3, messageQueueSelector.getQueues().size()); assertEquals(1, messageQueueSelector.getBrokerActingQueues().size()); for (int i = 0; i < messageQueueSelector.getQueues().size(); i++) { AddressableMessageQueue messageQueue = messageQueueSelector.getQueues().get(i); assertEquals(i, messageQueue.getQueueId()); } AddressableMessageQueue brokerQueue = messageQueueSelector.getQueueByBrokerName(BROKER_NAME); assertEquals(brokerQueue, messageQueueSelector.getBrokerActingQueues().get(0)); assertEquals(brokerQueue, messageQueueSelector.selectOne(true)); assertEquals(brokerQueue, messageQueueSelector.selectOneByIndex(3, true)); AddressableMessageQueue queue = messageQueueSelector.selectOne(false); messageQueueSelector.selectOne(false); messageQueueSelector.selectOne(false); assertEquals(queue, messageQueueSelector.selectOne(false)); } @Test public void testWriteMessageQueue() { queueData.setPerm(PermName.PERM_WRITE); queueData.setReadQueueNums(0); MessageQueueSelector messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), false); assertTrue(messageQueueSelector.getQueues().isEmpty()); queueData.setPerm(PermName.PERM_WRITE); queueData.setWriteQueueNums(3); messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), false); assertEquals(3, messageQueueSelector.getQueues().size()); assertEquals(1, messageQueueSelector.getBrokerActingQueues().size()); for (int i = 0; i < messageQueueSelector.getQueues().size(); i++) { AddressableMessageQueue messageQueue = messageQueueSelector.getQueues().get(i); assertEquals(i, messageQueue.getQueueId()); } AddressableMessageQueue brokerQueue = messageQueueSelector.getQueueByBrokerName(BROKER_NAME); assertEquals(brokerQueue, messageQueueSelector.getBrokerActingQueues().get(0)); assertEquals(brokerQueue, messageQueueSelector.selectOne(true)); assertEquals(brokerQueue, messageQueueSelector.selectOneByIndex(3, true)); AddressableMessageQueue queue = messageQueueSelector.selectOne(false); messageQueueSelector.selectOne(false); messageQueueSelector.selectOne(false); assertEquals(queue, messageQueueSelector.selectOne(false)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.sysmessage; import apache.rocketmq.v2.FilterExpression; import apache.rocketmq.v2.FilterType; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Subscription; import apache.rocketmq.v2.SubscriptionEntry; import com.google.common.collect.Sets; import io.netty.channel.Channel; import io.netty.channel.ChannelId; import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.broker.client.ClientChannelInfo; import org.apache.rocketmq.broker.client.ConsumerGroupEvent; import org.apache.rocketmq.broker.client.ConsumerManager; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager; import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel; import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager; import org.apache.rocketmq.proxy.remoting.RemotingProxyOutClient; import org.apache.rocketmq.proxy.remoting.channel.RemotingChannel; import org.apache.rocketmq.proxy.service.admin.AdminService; import org.apache.rocketmq.proxy.service.channel.SimpleChannel; import org.apache.rocketmq.proxy.service.relay.ProxyRelayService; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.proxy.service.route.TopicRouteService; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.assertj.core.util.Lists; import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class HeartbeatSyncerTest extends InitConfigTest { @Mock private TopicRouteService topicRouteService; @Mock private AdminService adminService; @Mock private ConsumerManager consumerManager; @Mock private MQClientAPIFactory mqClientAPIFactory; @Mock private MQClientAPIExt mqClientAPIExt; @Mock private ProxyRelayService proxyRelayService; private String clientId; private final String remoteAddress = "10.152.39.53:9768"; private final String localAddress = "11.193.0.1:1210"; private final String clusterName = "cluster"; private final String brokerName = "broker-01"; @Before public void before() throws Throwable { super.before(); this.clientId = RandomStringUtils.randomAlphabetic(10); when(mqClientAPIFactory.getClient()).thenReturn(mqClientAPIExt); { TopicRouteData topicRouteData = new TopicRouteData(); QueueData queueData = new QueueData(); queueData.setReadQueueNums(8); queueData.setWriteQueueNums(8); queueData.setPerm(6); queueData.setBrokerName(brokerName); topicRouteData.getQueueDatas().add(queueData); BrokerData brokerData = new BrokerData(); brokerData.setCluster(clusterName); brokerData.setBrokerName(brokerName); HashMap brokerAddr = new HashMap<>(); brokerAddr.put(0L, "127.0.0.1:10911"); brokerData.setBrokerAddrs(brokerAddr); topicRouteData.getBrokerDatas().add(brokerData); MessageQueueView messageQueueView = new MessageQueueView("foo", topicRouteData, null); when(this.topicRouteService.getAllMessageQueueView(any(), anyString())).thenReturn(messageQueueView); } } @Test public void testSyncGrpcV2Channel() throws Exception { String consumerGroup = "consumerGroup"; GrpcClientSettingsManager grpcClientSettingsManager = mock(GrpcClientSettingsManager.class); GrpcChannelManager grpcChannelManager = mock(GrpcChannelManager.class); GrpcClientChannel grpcClientChannel = new GrpcClientChannel( proxyRelayService, grpcClientSettingsManager, grpcChannelManager, ProxyContext.create().setRemoteAddress(remoteAddress).setLocalAddress(localAddress), clientId); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( grpcClientChannel, clientId, LanguageCode.JAVA, 5 ); ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.SEND_OK); doReturn(CompletableFuture.completedFuture(sendResult)).when(this.mqClientAPIExt) .sendMessageAsync(anyString(), anyString(), messageArgumentCaptor.capture(), any(), anyLong()); Settings settings = Settings.newBuilder() .setSubscription(Subscription.newBuilder() .addSubscriptions(SubscriptionEntry.newBuilder() .setTopic(Resource.newBuilder().setName("topic").build()) .setExpression(FilterExpression.newBuilder() .setType(FilterType.TAG) .setExpression("tag") .build()) .build()) .build()) .build(); when(grpcClientSettingsManager.getRawClientSettings(eq(clientId))).thenReturn(settings); HeartbeatSyncer heartbeatSyncer = new HeartbeatSyncer(topicRouteService, adminService, consumerManager, mqClientAPIFactory, null); heartbeatSyncer.onConsumerRegister( consumerGroup, clientChannelInfo, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, Sets.newHashSet(FilterAPI.buildSubscriptionData("topic", "tag")) ); await().atMost(Duration.ofSeconds(3)).until(() -> !messageArgumentCaptor.getAllValues().isEmpty()); heartbeatSyncer.consumeMessage(Lists.newArrayList(convertFromMessage(messageArgumentCaptor.getValue())), null); verify(consumerManager, never()).registerConsumer(anyString(), any(), any(), any(), any(), any(), anyBoolean()); String localServeAddr = ConfigurationManager.getProxyConfig().getLocalServeAddr(); // change local serve addr, to simulate other proxy receive messages heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10); ArgumentCaptor syncChannelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doReturn(true).when(consumerManager).registerConsumer(anyString(), syncChannelInfoArgumentCaptor.capture(), any(), any(), any(), any(), anyBoolean()); heartbeatSyncer.consumeMessage(Lists.newArrayList(convertFromMessage(messageArgumentCaptor.getValue())), null); heartbeatSyncer.consumeMessage(Lists.newArrayList(convertFromMessage(messageArgumentCaptor.getValue())), null); assertEquals(2, syncChannelInfoArgumentCaptor.getAllValues().size()); List channelInfoList = syncChannelInfoArgumentCaptor.getAllValues(); assertSame(channelInfoList.get(0).getChannel(), channelInfoList.get(1).getChannel()); assertEquals(settings, GrpcClientChannel.parseChannelExtendAttribute(channelInfoList.get(0).getChannel())); assertEquals(settings, GrpcClientChannel.parseChannelExtendAttribute(channelInfoList.get(1).getChannel())); // start test sync client unregister // reset localServeAddr ConfigurationManager.getProxyConfig().setLocalServeAddr(localServeAddr); heartbeatSyncer.onConsumerUnRegister(consumerGroup, clientChannelInfo); await().atMost(Duration.ofSeconds(3)).until(() -> messageArgumentCaptor.getAllValues().size() == 2); ArgumentCaptor syncUnRegisterChannelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doNothing().when(consumerManager).unregisterConsumer(anyString(), syncUnRegisterChannelInfoArgumentCaptor.capture(), anyBoolean()); // change local serve addr, to simulate other proxy receive messages heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10); heartbeatSyncer.consumeMessage(Lists.newArrayList(convertFromMessage(messageArgumentCaptor.getAllValues().get(1))), null); assertSame(channelInfoList.get(0).getChannel(), syncUnRegisterChannelInfoArgumentCaptor.getValue().getChannel()); } @Test public void testSyncRemotingChannel() throws Exception { String consumerGroup = "consumerGroup"; String consumerGroup2 = "consumerGroup2"; Channel channel = createMockChannel(); Set subscriptionDataSet = new HashSet<>(); subscriptionDataSet.add(FilterAPI.buildSubscriptionData("topic", "tagSub")); Set subscriptionDataSet2 = new HashSet<>(); subscriptionDataSet2.add(FilterAPI.buildSubscriptionData("topic2", "tagSub2")); RemotingProxyOutClient remotingProxyOutClient = mock(RemotingProxyOutClient.class); RemotingChannel remotingChannel = new RemotingChannel(remotingProxyOutClient, proxyRelayService, channel, clientId, subscriptionDataSet); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( remotingChannel, clientId, LanguageCode.JAVA, 4 ); RemotingChannel remotingChannel2 = new RemotingChannel(remotingProxyOutClient, proxyRelayService, channel, clientId, subscriptionDataSet2); ClientChannelInfo clientChannelInfo2 = new ClientChannelInfo( remotingChannel2, clientId, LanguageCode.JAVA, 4 ); HeartbeatSyncer heartbeatSyncer = new HeartbeatSyncer(topicRouteService, adminService, consumerManager, mqClientAPIFactory, null); SendResult okSendResult = new SendResult(); okSendResult.setSendStatus(SendStatus.SEND_OK); { ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); doReturn(CompletableFuture.completedFuture(okSendResult)).when(this.mqClientAPIExt) .sendMessageAsync(anyString(), anyString(), messageArgumentCaptor.capture(), any(), anyLong()); heartbeatSyncer.onConsumerRegister( consumerGroup, clientChannelInfo, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, subscriptionDataSet ); heartbeatSyncer.onConsumerRegister( consumerGroup2, clientChannelInfo2, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, subscriptionDataSet2 ); await().atMost(Duration.ofSeconds(3)).until(() -> messageArgumentCaptor.getAllValues().size() == 2); heartbeatSyncer.consumeMessage(convertFromMessage(messageArgumentCaptor.getAllValues()), null); verify(consumerManager, never()).registerConsumer(anyString(), any(), any(), any(), any(), any(), anyBoolean()); // change local serve addr, to simulate other proxy receive messages heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10); ArgumentCaptor syncChannelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doReturn(true).when(consumerManager).registerConsumer(anyString(), syncChannelInfoArgumentCaptor.capture(), any(), any(), any(), any(), anyBoolean()); heartbeatSyncer.consumeMessage(convertFromMessage(messageArgumentCaptor.getAllValues()), null); heartbeatSyncer.consumeMessage(convertFromMessage(messageArgumentCaptor.getAllValues()), null); /* data in syncChannelInfoArgumentCaptor will be like: 1st, data of group1 2nd, data of group2 3rd, data of group1 4th, data of group2 */ assertEquals(4, syncChannelInfoArgumentCaptor.getAllValues().size()); List channelInfoList = syncChannelInfoArgumentCaptor.getAllValues(); assertSame(channelInfoList.get(0).getChannel(), channelInfoList.get(2).getChannel()); assertNotSame(channelInfoList.get(0).getChannel(), channelInfoList.get(1).getChannel()); Set> checkSubscriptionDatas = new HashSet<>(); checkSubscriptionDatas.add(RemotingChannel.parseChannelExtendAttribute(channelInfoList.get(0).getChannel())); checkSubscriptionDatas.add(RemotingChannel.parseChannelExtendAttribute(channelInfoList.get(1).getChannel())); assertTrue(checkSubscriptionDatas.contains(subscriptionDataSet)); assertTrue(checkSubscriptionDatas.contains(subscriptionDataSet2)); } { // start test sync client unregister // reset localServeAddr ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); doReturn(CompletableFuture.completedFuture(okSendResult)).when(this.mqClientAPIExt) .sendMessageAsync(anyString(), anyString(), messageArgumentCaptor.capture(), any(), anyLong()); heartbeatSyncer.onConsumerUnRegister(consumerGroup, clientChannelInfo); heartbeatSyncer.onConsumerUnRegister(consumerGroup2, clientChannelInfo2); await().atMost(Duration.ofSeconds(3)).until(() -> messageArgumentCaptor.getAllValues().size() == 2); ArgumentCaptor syncUnRegisterChannelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doNothing().when(consumerManager).unregisterConsumer(anyString(), syncUnRegisterChannelInfoArgumentCaptor.capture(), anyBoolean()); // change local serve addr, to simulate other proxy receive messages heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10); heartbeatSyncer.consumeMessage(convertFromMessage(messageArgumentCaptor.getAllValues()), null); List channelInfoList = syncUnRegisterChannelInfoArgumentCaptor.getAllValues(); assertNotSame(channelInfoList.get(0).getChannel(), channelInfoList.get(1).getChannel()); Set> checkSubscriptionDatas = new HashSet<>(); checkSubscriptionDatas.add(RemotingChannel.parseChannelExtendAttribute(channelInfoList.get(0).getChannel())); checkSubscriptionDatas.add(RemotingChannel.parseChannelExtendAttribute(channelInfoList.get(1).getChannel())); assertTrue(checkSubscriptionDatas.contains(subscriptionDataSet)); assertTrue(checkSubscriptionDatas.contains(subscriptionDataSet2)); } } @Test public void testProcessConsumerGroupEventForRemoting() { String consumerGroup = "consumerGroup"; Channel channel = createMockChannel(); RemotingProxyOutClient remotingProxyOutClient = mock(RemotingProxyOutClient.class); RemotingChannel remotingChannel = new RemotingChannel(remotingProxyOutClient, proxyRelayService, channel, clientId, Collections.emptySet()); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( remotingChannel, clientId, LanguageCode.JAVA, 4 ); testProcessConsumerGroupEvent(consumerGroup, clientChannelInfo); } @Test public void testProcessConsumerGroupEventForGrpcV2() { String consumerGroup = "consumerGroup"; GrpcClientSettingsManager grpcClientSettingsManager = mock(GrpcClientSettingsManager.class); GrpcChannelManager grpcChannelManager = mock(GrpcChannelManager.class); GrpcClientChannel grpcClientChannel = new GrpcClientChannel( proxyRelayService, grpcClientSettingsManager, grpcChannelManager, ProxyContext.create().setRemoteAddress(remoteAddress).setLocalAddress(localAddress), clientId); ClientChannelInfo clientChannelInfo = new ClientChannelInfo( grpcClientChannel, clientId, LanguageCode.JAVA, 5 ); testProcessConsumerGroupEvent(consumerGroup, clientChannelInfo); } private void testProcessConsumerGroupEvent(String consumerGroup, ClientChannelInfo clientChannelInfo) { HeartbeatSyncer heartbeatSyncer = new HeartbeatSyncer(topicRouteService, adminService, consumerManager, mqClientAPIFactory, null); SendResult okSendResult = new SendResult(); okSendResult.setSendStatus(SendStatus.SEND_OK); ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); doReturn(CompletableFuture.completedFuture(okSendResult)).when(this.mqClientAPIExt) .sendMessageAsync(anyString(), anyString(), messageArgumentCaptor.capture(), any(), anyLong()); heartbeatSyncer.onConsumerRegister( consumerGroup, clientChannelInfo, ConsumeType.CONSUME_PASSIVELY, MessageModel.CLUSTERING, ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, Collections.emptySet() ); await().atMost(Duration.ofSeconds(3)).until(() -> messageArgumentCaptor.getAllValues().size() == 1); // change local serve addr, to simulate other proxy receive messages heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10); ArgumentCaptor channelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class); doReturn(true).when(consumerManager).registerConsumer(anyString(), channelInfoArgumentCaptor.capture(), any(), any(), any(), any(), anyBoolean()); heartbeatSyncer.consumeMessage(convertFromMessage(messageArgumentCaptor.getAllValues()), null); assertEquals(1, heartbeatSyncer.remoteChannelMap.size()); heartbeatSyncer.processConsumerGroupEvent(ConsumerGroupEvent.CLIENT_UNREGISTER, consumerGroup, channelInfoArgumentCaptor.getValue()); assertTrue(heartbeatSyncer.remoteChannelMap.isEmpty()); } private MessageExt convertFromMessage(Message message) { MessageExt messageExt = new MessageExt(); messageExt.setTopic(message.getTopic()); messageExt.setBody(message.getBody()); return messageExt; } private List convertFromMessage(List message) { return message.stream().map(this::convertFromMessage).collect(Collectors.toList()); } private Channel createMockChannel() { return new MockChannel(RandomStringUtils.randomAlphabetic(10)); } private class MockChannel extends SimpleChannel { public MockChannel(String channelId) { super(null, new MockChannelId(channelId), HeartbeatSyncerTest.this.remoteAddress, HeartbeatSyncerTest.this.localAddress); } } private static class MockChannelId implements ChannelId { private final String channelId; public MockChannelId(String channelId) { this.channelId = channelId; } @Override public String asShortText() { return channelId; } @Override public String asLongText() { return channelId; } @Override public int compareTo(@NotNull ChannelId o) { return this.channelId.compareTo(o.asLongText()); } } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import java.util.List; import java.util.Random; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class AbstractTransactionServiceTest extends InitConfigTest { private static final String BROKER_NAME = "mockBroker"; private static final String PRODUCER_GROUP = "producerGroup"; private static final Random RANDOM = new Random(); private final ProxyContext ctx = ProxyContext.createForInner(this.getClass()); public static class MockAbstractTransactionServiceTest extends AbstractTransactionService { @Override protected String getBrokerNameByAddr(String brokerAddr) { return BROKER_NAME; } @Override public void addTransactionSubscription(ProxyContext ctx, String group, List topicList) { } @Override public void addTransactionSubscription(ProxyContext ctx, String group, String topic) { } @Override public void replaceTransactionSubscription(ProxyContext ctx, String group, List topicList) { } @Override public void unSubscribeAllTransactionTopic(ProxyContext ctx, String group) { } } private TransactionService transactionService; @Before public void before() throws Throwable { super.before(); this.transactionService = new MockAbstractTransactionServiceTest(); } @Test public void testAddAndGenEndHeader() { Message message = new Message(); message.putUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS, "30"); String txId = MessageClientIDSetter.createUniqID(); TransactionData transactionData = transactionService.addTransactionDataByBrokerName( ctx, BROKER_NAME, "Topic", PRODUCER_GROUP, RANDOM.nextLong(), RANDOM.nextLong(), txId, message ); assertNotNull(transactionData); EndTransactionRequestData requestData = transactionService.genEndTransactionRequestHeader( ctx, "topic", PRODUCER_GROUP, MessageSysFlag.TRANSACTION_COMMIT_TYPE, true, txId, txId ); assertEquals(BROKER_NAME, requestData.getBrokerName()); assertEquals(BROKER_NAME, transactionData.getBrokerName()); assertEquals(transactionData.getCommitLogOffset(), requestData.getRequestHeader().getCommitLogOffset().longValue()); assertEquals(transactionData.getTranStateTableOffset(), requestData.getRequestHeader().getTranStateTableOffset().longValue()); assertNull(transactionService.genEndTransactionRequestHeader( ctx, "topic", "group", MessageSysFlag.TRANSACTION_COMMIT_TYPE, true, txId, txId )); } @Test public void testOnSendCheckTransactionStateFailedFailed() { Message message = new Message(); message.putUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS, "30"); String txId = MessageClientIDSetter.createUniqID(); TransactionData transactionData = transactionService.addTransactionDataByBrokerName( ctx, BROKER_NAME, "Topic", PRODUCER_GROUP, RANDOM.nextLong(), RANDOM.nextLong(), txId, message ); transactionService.onSendCheckTransactionStateFailed(ProxyContext.createForInner(this.getClass()), PRODUCER_GROUP, transactionData); assertNull(transactionService.genEndTransactionRequestHeader( ctx, "topic", PRODUCER_GROUP, MessageSysFlag.TRANSACTION_COMMIT_TYPE, true, txId, txId )); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import java.time.Duration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.rocketmq.broker.client.ProducerManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.service.BaseServiceTest; import org.apache.rocketmq.proxy.service.route.MessageQueueView; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.ProducerData; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.QueueData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; public class ClusterTransactionServiceTest extends BaseServiceTest { @Mock private ProducerManager producerManager; private ProxyContext ctx = ProxyContext.create(); private ClusterTransactionService clusterTransactionService; @Before public void before() throws Throwable { super.before(); this.clusterTransactionService = new ClusterTransactionService(this.topicRouteService, this.producerManager, this.mqClientAPIFactory); MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null); when(this.topicRouteService.getAllMessageQueueView(any(), anyString())) .thenReturn(messageQueueView); when(mqClientAPIFactory.getClient()).thenReturn(mqClientAPIExt); } @Test public void testAddTransactionSubscription() { this.clusterTransactionService.addTransactionSubscription(ctx, GROUP, TOPIC); assertEquals(1, this.clusterTransactionService.getGroupClusterData().size()); assertEquals(CLUSTER_NAME, this.clusterTransactionService.getGroupClusterData().get(GROUP).stream().findAny().get().getCluster()); } @Test public void testAddTransactionSubscriptionTopicList() { this.clusterTransactionService.addTransactionSubscription(ctx, GROUP, Lists.newArrayList(TOPIC + 1, TOPIC + 2)); assertEquals(1, this.clusterTransactionService.getGroupClusterData().size()); assertEquals(CLUSTER_NAME, this.clusterTransactionService.getGroupClusterData().get(GROUP).stream().findAny().get().getCluster()); } @Test public void testReplaceTransactionSubscription() { this.clusterTransactionService.addTransactionSubscription(ctx, GROUP, TOPIC); assertEquals(1, this.clusterTransactionService.getGroupClusterData().size()); assertEquals(CLUSTER_NAME, this.clusterTransactionService.getGroupClusterData().get(GROUP).stream().findAny().get().getCluster()); this.brokerData.setCluster(CLUSTER_NAME + 1); this.clusterTransactionService.replaceTransactionSubscription(ctx, GROUP, Lists.newArrayList(TOPIC + 1)); assertEquals(1, this.clusterTransactionService.getGroupClusterData().size()); assertEquals(CLUSTER_NAME + 1, this.clusterTransactionService.getGroupClusterData().get(GROUP).stream().findAny().get().getCluster()); } @Test public void testUnSubscribeAllTransactionTopic() { this.clusterTransactionService.addTransactionSubscription(ctx, GROUP, TOPIC); this.clusterTransactionService.unSubscribeAllTransactionTopic(ctx, GROUP); assertEquals(0, this.clusterTransactionService.getGroupClusterData().size()); } @Test public void testScanProducerHeartBeat() throws Exception { when(this.producerManager.groupOnline(anyString())).thenReturn(true); Mockito.reset(this.topicRouteService); String brokerName2 = "broker-2-01"; String clusterName2 = "broker-2"; String brokerAddr2 = "127.0.0.2:10911"; BrokerData brokerData = new BrokerData(); QueueData queueData = new QueueData(); queueData.setBrokerName(brokerName2); brokerData.setCluster(clusterName2); brokerData.setBrokerName(brokerName2); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, brokerName2); brokerData.setBrokerAddrs(brokerAddrs); topicRouteData.getQueueDatas().add(queueData); topicRouteData.getBrokerDatas().add(brokerData); when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(TOPIC, topicRouteData, null)); TopicRouteData clusterTopicRouteData = new TopicRouteData(); QueueData clusterQueueData = new QueueData(); BrokerData clusterBrokerData = new BrokerData(); clusterQueueData.setBrokerName(BROKER_NAME); clusterTopicRouteData.setQueueDatas(Lists.newArrayList(clusterQueueData)); clusterBrokerData.setCluster(CLUSTER_NAME); clusterBrokerData.setBrokerName(BROKER_NAME); brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR); clusterBrokerData.setBrokerAddrs(brokerAddrs); clusterTopicRouteData.setBrokerDatas(Lists.newArrayList(clusterBrokerData)); when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, clusterTopicRouteData, null)); TopicRouteData clusterTopicRouteData2 = new TopicRouteData(); QueueData clusterQueueData2 = new QueueData(); BrokerData clusterBrokerData2 = new BrokerData(); clusterQueueData2.setBrokerName(brokerName2); clusterTopicRouteData2.setQueueDatas(Lists.newArrayList(clusterQueueData2)); clusterBrokerData2.setCluster(clusterName2); clusterBrokerData2.setBrokerName(brokerName2); brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, brokerAddr2); clusterBrokerData2.setBrokerAddrs(brokerAddrs); clusterTopicRouteData2.setBrokerDatas(Lists.newArrayList(clusterBrokerData2)); when(this.topicRouteService.getAllMessageQueueView(any(), eq(clusterName2))).thenReturn(new MessageQueueView(clusterName2, clusterTopicRouteData2, null)); ConfigurationManager.getProxyConfig().setTransactionHeartbeatBatchNum(2); this.clusterTransactionService.start(); Set groupSet = new HashSet<>(); for (int i = 0; i < 3; i++) { groupSet.add(GROUP + i); this.clusterTransactionService.addTransactionSubscription(ctx, GROUP + i, TOPIC); } ArgumentCaptor brokerAddrArgumentCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor heartbeatDataArgumentCaptor = ArgumentCaptor.forClass(HeartbeatData.class); when(mqClientAPIExt.sendHeartbeatOneway( brokerAddrArgumentCaptor.capture(), heartbeatDataArgumentCaptor.capture(), anyLong() )).thenReturn(CompletableFuture.completedFuture(null)); this.clusterTransactionService.scanProducerHeartBeat(); await().atMost(Duration.ofSeconds(1)).until(() -> brokerAddrArgumentCaptor.getAllValues().size() == 4); assertEquals(Lists.newArrayList(BROKER_ADDR, BROKER_ADDR, brokerAddr2, brokerAddr2), brokerAddrArgumentCaptor.getAllValues().stream().sorted().collect(Collectors.toList())); List heartbeatDataList = heartbeatDataArgumentCaptor.getAllValues(); for (final HeartbeatData heartbeatData : heartbeatDataList) { for (ProducerData producerData : heartbeatData.getProducerDataSet()) { groupSet.remove(producerData.getGroupName()); } } assertTrue(groupSet.isEmpty()); assertEquals(brokerName2, this.clusterTransactionService.getBrokerNameByAddr(brokerAddr2)); assertEquals(BROKER_NAME, this.clusterTransactionService.getBrokerNameByAddr(BROKER_ADDR)); } } ================================================ FILE: proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.proxy.service.transaction; import java.time.Duration; import java.util.Random; import org.apache.commons.lang3.time.StopWatch; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.proxy.config.InitConfigTest; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; public class TransactionDataManagerTest extends InitConfigTest { private static final String PRODUCER_GROUP = "producerGroup"; private static final Random RANDOM = new Random(); private TransactionDataManager transactionDataManager; @Before public void before() throws Throwable { super.before(); this.transactionDataManager = new TransactionDataManager(); } @After public void after() { super.after(); } @Test public void testAddAndRemove() { TransactionData transactionData1 = createTransactionData(); TransactionData transactionData2 = createTransactionData(transactionData1.getTransactionId()); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, transactionData1.getTransactionId(), transactionData1); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, transactionData1.getTransactionId(), transactionData2); assertEquals(1, this.transactionDataManager.transactionIdDataMap.size()); assertEquals(2, this.transactionDataManager.transactionIdDataMap.get( transactionDataManager.buildKey(PRODUCER_GROUP, transactionData1.getTransactionId())).size()); this.transactionDataManager.removeTransactionData(PRODUCER_GROUP, transactionData1.getTransactionId(), transactionData1); assertEquals(1, this.transactionDataManager.transactionIdDataMap.size()); this.transactionDataManager.removeTransactionData(PRODUCER_GROUP, transactionData1.getTransactionId(), transactionData2); assertEquals(0, this.transactionDataManager.transactionIdDataMap.size()); } @Test public void testPoll() { String txId = MessageClientIDSetter.createUniqID(); TransactionData transactionData1 = createTransactionData(txId, System.currentTimeMillis() - Duration.ofMinutes(2).toMillis()); TransactionData transactionData2 = createTransactionData(txId); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, txId, transactionData1); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, txId, transactionData2); TransactionData resTransactionData = this.transactionDataManager.pollNoExpireTransactionData(PRODUCER_GROUP, txId); assertSame(transactionData2, resTransactionData); assertNull(this.transactionDataManager.pollNoExpireTransactionData(PRODUCER_GROUP, txId)); } @Test public void testCleanExpire() { String txId = MessageClientIDSetter.createUniqID(); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, txId, createTransactionData(txId, System.currentTimeMillis(), Duration.ofMillis(100).toMillis())); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, txId, createTransactionData(txId, System.currentTimeMillis(), Duration.ofMillis(500).toMillis())); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, MessageClientIDSetter.createUniqID(), createTransactionData(txId, System.currentTimeMillis(), Duration.ofMillis(1000).toMillis())); await().atMost(Duration.ofSeconds(2)).until(() -> { this.transactionDataManager.cleanExpireTransactionData(); return this.transactionDataManager.transactionIdDataMap.isEmpty(); }); } @Test public void testWaitTransactionDataClear() throws InterruptedException { // Skip this test case on Mac as it's not stable enough. Assume.assumeFalse(MixAll.isMac()); String txId = MessageClientIDSetter.createUniqID(); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, txId, createTransactionData(txId, System.currentTimeMillis(), Duration.ofMillis(100).toMillis())); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, txId, createTransactionData(txId, System.currentTimeMillis(), Duration.ofMillis(500).toMillis())); this.transactionDataManager.addTransactionData(PRODUCER_GROUP, MessageClientIDSetter.createUniqID(), createTransactionData(txId, System.currentTimeMillis(), Duration.ofMillis(1000).toMillis())); StopWatch stopWatch = new StopWatch(); stopWatch.start(); this.transactionDataManager.waitTransactionDataClear(); stopWatch.stop(); assertTrue(Math.abs(stopWatch.getTime() - 1000) <= 50); } private static TransactionData createTransactionData() { return createTransactionData(MessageClientIDSetter.createUniqID()); } private static TransactionData createTransactionData(String txId) { return createTransactionData(txId, System.currentTimeMillis()); } private static TransactionData createTransactionData(String txId, long checkTimestamp) { return createTransactionData(txId, checkTimestamp, Duration.ofMinutes(1).toMillis()); } private static TransactionData createTransactionData(String txId, long checkTimestamp, long checkImmunityTime) { return new TransactionData( "brokerName", "topicName", RANDOM.nextLong(), RANDOM.nextLong(), txId, checkTimestamp, checkImmunityTime ); } } ================================================ FILE: proxy/src/test/resources/certs/client.key ================================================ -----BEGIN ENCRYPTED PRIVATE KEY----- MIICoTAbBgkqhkiG9w0BBQMwDgQI1vtPpDhOYRcCAggABIICgMHwgw0p9fx95R/+ cWnNdEq8I3ZOOy2wDjammFvPrYXcCJzS3Xg/0GDJ8pdJRKrI7253e4u3mxf5oMuY RrvpB3KfdelU1k/5QKqOxL/N0gQafQLViN53f6JelyBEAmO1UxQtKZtkTrdZg8ZP 0u1cPPWxmgNdn1Xx3taMw+Wo05ysHjnHJhOEDQ2WT3VXigiRmFSX3H567yjYMRD+ zmvBq+qqR9JPbH9Cn7X1oRXX6c8VsZHWF/Ds0I4i+5zJxsSIuNZxjZw9XXNgXtFv 7FEFC0HDgDQQUY/FNPUbmjQUp1y0YxoOBjlyIqBIx5FWxu95p2xITS0OimQPFT0o IngaSb+EKRDhqpLxxIVEbDdkQrdRqcmmLGJioAysExTBDsDwkaEJGOp44bLDM4QW SIA9SB01omuCXgn7RjUyVXb5g0Lz+Nvsfp1YXUkPDO9hILfz3eMHDSW7/FzbB81M r8URaTagQxBZnvIoCoWszLDXn3JwEjpZEA6y55Naptps3mMRf7+XMt42lX0e4y9a ogNu5Zw/RZD9YcaTjC2z5XeKiMCs1Ymhy9iuzbo+eRGESqzvUE4VirtsiEwxJRci JHAvuAl3X4XnpTty4ahOU+DihM9lALxdU68CN9++7mx581pYuvjzrV+Z5+PuptZX AjCZmkZLDh8TCHSzWRqvP/Hcvo9BjW8l1Lq6tOa222PefSNCc6gs6Hq+jUghbabZ /ux4WuFc0Zd6bfQWAZohSvd78/ixsdJPNGm2OP+LUIrEDKIkLuH1PM0uq4wzJZNu Bo7oJ5iFWF67u3MC8oq+BqOVKDNWaCMi7iiSrN2XW8FBo/rpx4Lf/VYREL+Y0mP6 vzJrZqw= -----END ENCRYPTED PRIVATE KEY----- ================================================ FILE: proxy/src/test/resources/certs/client.pem ================================================ -----BEGIN CERTIFICATE----- MIIDATCCAekCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw YWNoZS5vcmcwIBcNMTgwMTE2MDYxNjQ0WhgPMjExNzEyMjMwNjE2NDRaMIGSMQsw CQYDVQQGEwJDTjERMA8GA1UECAwIWmhlamlhbmcxETAPBgNVBAcMCEhhbmd6aG91 MQ8wDQYDVQQKDAZhcGFjaGUxETAPBgNVBAsMCHJvY2tldG1xMRgwFgYDVQQDDA9h cGFjaGUgcm9ja2V0bXExHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcw gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjPlSjZk37XLBJBc5G/qQNsNdVD vZnEGntrqW0UuHjF2T/LPtsGOavLP5wCHvn2zwMR2eCXZwKdKIzSvk0L3XOjH/XY OLgRa3cg90lV7Wzn9UMGq3nOjFtjIODPjtz3lwYAuAt1MH+K0E+ChuCFBgFqdY9U E0suW3DX0Mt/WB3pAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFGPaZKyCZzQihKj n/7I1J0wKl1HrU7N4sOie8E+ntcpKeX9zKYAou/4Iy0qwgxgRsnucB1rDous560a +8DFDU8+FnikK9cQtKfQqu4F266IkkXolviZMSfkmB+NIsByIl95eMJlQHVlAvnX vnpGdhD/Jhs+acE1VHhO6K+8omKLA6Og8MmYGRwmnBLcxIvqoSNDlEShfQyjaECg I4bEi4ZhH3lSHE46FybJdoxDbj9IjHWqpOnjM23EOyfd1zcwOZJA7a54kfOpiTjz wrtes5yoQznun5WtGcLM8ZmyaQ+Jr3j6NyZhOwULzK1+A8YUsW6Ww39xTxQoIHEQ 7eirb54= -----END CERTIFICATE----- ================================================ FILE: proxy/src/test/resources/certs/server.key ================================================ -----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOsmp4YtrIRsBdBQ LyPImafCRynTJls3NNF4g6nZr9e0efBY830gw9kBebcm603sdZNl95fzRr2+srXi 5FJbG7Fmq1+F0xLNK/kKWirGtNMT2DubmhVdKyXYJSvInoGRkrQzbOG0MdAyzE6Q O6OjjNN+xGkmadWyCyNF6S8YqMJTAgMBAAECgYEAj0OlnOIG0Ube4+N2VN7KfqKm qJy0Ka6gx14dGUY/E7Qo9n27GujzaSq09RkJExiVKZBeIH1fBAtC5f2uDV7kpy0l uNpTpQkbw0g2EQLxDsVwaUEYbu+t9qVeXoDd1vFeoXHBuRwvI9UW1BrxVtvKODia 5StU8Lw4yjcm2lQalwECQQD/sKj56thIsIY7D9qBHk7fnFLd8aYzhnP2GsbZX4V/ T1KHRxr/8MqdNQX53DE5qcyM/Mqu95FIpTAniUtvcBujAkEA62+fAMYFTAEWj4Z4 vCmcoPqfVPWhBKFR/wo3L8uUARiIzlbYNU3LIqC2s16QO50+bLUd41oVHNw9Y+uM fxQpkQJACg/WpncSadHghmR6UchyjCQnsqo2wyJQX+fv2VAD/d2OPtqSem3sW0Fh 6dI7cax36zhrdXUyl2xAt92URV9hBwJALX93sdWSxnpbWsc449wCydVFH00MnfFz AB+ARLtJ0eBk58M+qyZqgDmgtQ8sPmkH3EgwC3SoKdiiAIJPt2s1EQJBAKnISZZr qB2F2PfAW2JJbQlrPyVzkxhv9XYdiVNOErmuxLFae3AI7nECgGuFBtvmeqzm2yRj 7RBMCmzyWG7MF3o= -----END PRIVATE KEY----- ================================================ FILE: proxy/src/test/resources/certs/server.pem ================================================ -----BEGIN CERTIFICATE----- MIIDATCCAekCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw YWNoZS5vcmcwIBcNMTgwMTE2MDYxMzQ5WhgPMjExNzEyMjMwNjEzNDlaMIGSMQsw CQYDVQQGEwJDTjERMA8GA1UECAwIWmhlamlhbmcxETAPBgNVBAcMCEhhbmd6aG91 MQ8wDQYDVQQKDAZhcGFjaGUxETAPBgNVBAsMCHJvY2tldG1xMRgwFgYDVQQDDA9h cGFjaGUgcm9ja2V0bXExHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcw gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOsmp4YtrIRsBdBQLyPImafCRynT Jls3NNF4g6nZr9e0efBY830gw9kBebcm603sdZNl95fzRr2+srXi5FJbG7Fmq1+F 0xLNK/kKWirGtNMT2DubmhVdKyXYJSvInoGRkrQzbOG0MdAyzE6QO6OjjNN+xGkm adWyCyNF6S8YqMJTAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAAzbwXyAULmXitiU +8/2vbUZQlzB/nXY52OIq7qu3F55hE5qlHkcVxG2JZjO3p5UETwOyNUpU4dpu3uT 7WSdygH4Iagl87ILpGsob9pAf0joAbaXAY4sGDhg+WjR5JInAxbmT+QWZ+4NTuLQ fSudUSJrv+HmUlmcVOvLiNStgt9rbtcgJAvpVwY+iCv0HQziFuQxmOkDv09ZLzu/ lxCMqnbgkEFYkwdntN6MVk38K3MovszedGO/n19hNOFss7nn5XDEeEnc6BqKGdck YDoy6amohY0Ds0o0gJ2rq0Y8Gjl9spQ3oeXpoNUoz84OF4KIBRTzSMv8CrmqPdFY Zd2MGjw= -----END CERTIFICATE----- ================================================ FILE: proxy/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: proxy/src/test/resources/rmq-proxy-home/conf/broker.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH ================================================ FILE: proxy/src/test/resources/rmq-proxy-home/conf/logback_proxy.xml ================================================ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}proxy.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}proxy.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}proxy_watermark.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}proxy_watermark.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8}%m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker_default.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker_default.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker.%i.log.gz 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}protection.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}protection.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}watermark.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}watermark.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}store.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}store.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}remoting.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}remoting.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}storeerror.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}storeerror.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}transaction.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}transaction.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}lock.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}lock.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}filter.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}filter.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}stats.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}stats.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}commercial.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}commercial.%i.log.gz 1 10 500MB ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}pop.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}pop.%i.log 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 true %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ================================================ FILE: proxy/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json ================================================ { "proxyMode": "cluster" } ================================================ FILE: proxy/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: remoting/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "remoting", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_google_guava_guava", "@maven//:com_google_code_findbugs_jsr305", "@maven//:com_squareup_okio_okio_jvm", "@maven//:io_netty_netty_all", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:org_apache_tomcat_annotations_api", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:commons_collections_commons_collections", "@maven//:org_reflections_reflections", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":remoting", "//common", "//:test_deps", "@maven//:org_objenesis_objenesis", "@maven//:com_alibaba_fastjson", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_google_code_gson_gson", "@maven//:com_google_guava_guava", "@maven//:com_google_code_findbugs_jsr305", "@maven//:com_squareup_okio_okio_jvm", "@maven//:io_netty_netty_all", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:org_apache_tomcat_annotations_api", "@maven//:org_apache_commons_commons_lang3", "@maven//:org_jetbrains_annotations", "@maven//:org_reflections_reflections", ], resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: remoting/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-remoting rocketmq-remoting ${project.version} ${basedir}/.. ${project.groupId} rocketmq-common org.apache.commons commons-lang3 org.reflections reflections com.google.code.gson gson 2.9.0 test ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/ChannelEventListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import io.netty.channel.Channel; public interface ChannelEventListener { void onChannelConnect(final String remoteAddr, final Channel channel); void onChannelClose(final String remoteAddr, final Channel channel); void onChannelException(final String remoteAddr, final Channel channel); void onChannelIdle(final String remoteAddr, final Channel channel); void onChannelActive(final String remoteAddr, final Channel channel); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/CommandCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; public interface CommandCallback { void accept(); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/CommandCustomHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public interface CommandCustomHeader { void checkFields() throws RemotingCommandException; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/Configuration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.remoting.protocol.DataVersion; public class Configuration { private final Logger log; private List configObjectList = new ArrayList<>(4); private String storePath; private boolean storePathFromConfig = false; private Object storePathObject; private Field storePathField; private DataVersion dataVersion = new DataVersion(); private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * All properties include configs in object and extend properties. */ private Properties allConfigs = new Properties(); public Configuration(Logger log) { this.log = log; } public Configuration(Logger log, Object... configObjects) { this.log = log; if (configObjects == null || configObjects.length == 0) { return; } for (Object configObject : configObjects) { if (configObject == null) { continue; } registerConfig(configObject); } } public Configuration(Logger log, String storePath, Object... configObjects) { this(log, configObjects); this.storePath = storePath; } /** * register config object * * @return the current Configuration object */ public Configuration registerConfig(Object configObject) { try { readWriteLock.writeLock().lockInterruptibly(); try { Properties registerProps = MixAll.object2Properties(configObject); merge(registerProps, this.allConfigs); configObjectList.add(configObject); } finally { readWriteLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("registerConfig lock error"); } return this; } /** * register config properties * * @return the current Configuration object */ public Configuration registerConfig(Properties extProperties) { if (extProperties == null) { return this; } try { readWriteLock.writeLock().lockInterruptibly(); try { merge(extProperties, this.allConfigs); } finally { readWriteLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("register lock error. {}" + extProperties); } return this; } /** * The store path will be gotten from the field of object. * * @throws java.lang.RuntimeException if the field of object is not exist. */ public void setStorePathFromConfig(Object object, String fieldName) { assert object != null; try { readWriteLock.writeLock().lockInterruptibly(); try { this.storePathFromConfig = true; this.storePathObject = object; // check this.storePathField = object.getClass().getDeclaredField(fieldName); assert this.storePathField != null && !Modifier.isStatic(this.storePathField.getModifiers()); this.storePathField.setAccessible(true); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } finally { readWriteLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("setStorePathFromConfig lock error"); } } private String getStorePath() { String realStorePath = null; try { readWriteLock.readLock().lockInterruptibly(); try { realStorePath = this.storePath; if (this.storePathFromConfig) { try { realStorePath = (String) storePathField.get(this.storePathObject); } catch (IllegalAccessException e) { log.error("getStorePath error, ", e); } } } finally { readWriteLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getStorePath lock error"); } return realStorePath; } public void setStorePath(final String storePath) { this.storePath = storePath; } public void update(Properties properties) { try { readWriteLock.writeLock().lockInterruptibly(); try { // the property must be exist when update mergeIfExist(properties, this.allConfigs); for (Object configObject : configObjectList) { // not allConfigs to update... MixAll.properties2Object(properties, configObject); } this.dataVersion.nextVersion(); } finally { readWriteLock.writeLock().unlock(); } } catch (InterruptedException e) { log.error("update lock error, {}", properties); return; } persist(); } public void persist() { try { readWriteLock.readLock().lockInterruptibly(); try { String allConfigs = getAllConfigsInternal(); MixAll.string2File(allConfigs, getStorePath()); } catch (IOException e) { log.error("persist string2File error, ", e); } finally { readWriteLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("persist lock error"); } } public String getAllConfigsFormatString() { try { readWriteLock.readLock().lockInterruptibly(); try { return getAllConfigsInternal(); } finally { readWriteLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getAllConfigsFormatString lock error"); } return null; } public String getClientConfigsFormatString(List clientKeys) { try { readWriteLock.readLock().lockInterruptibly(); try { return getClientConfigsInternal(clientKeys); } finally { readWriteLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getAllConfigsFormatString lock error"); } return null; } public String getDataVersionJson() { return this.dataVersion.toJson(); } public Properties getAllConfigs() { try { readWriteLock.readLock().lockInterruptibly(); try { return this.allConfigs; } finally { readWriteLock.readLock().unlock(); } } catch (InterruptedException e) { log.error("getAllConfigs lock error"); } return null; } private String getAllConfigsInternal() { StringBuilder stringBuilder = new StringBuilder(); // reload from config object ? for (Object configObject : this.configObjectList) { Properties properties = MixAll.object2Properties(configObject); if (properties != null) { merge(properties, this.allConfigs); } else { log.warn("getAllConfigsInternal object2Properties is null, {}", configObject.getClass()); } } { stringBuilder.append(MixAll.properties2String(this.allConfigs, true)); } return stringBuilder.toString(); } private String getClientConfigsInternal(List clientConigKeys) { StringBuilder stringBuilder = new StringBuilder(); Properties clientProperties = new Properties(); // reload from config object ? for (Object configObject : this.configObjectList) { Properties properties = MixAll.object2Properties(configObject); for (String nameNow : clientConigKeys) { if (properties.containsKey(nameNow)) { clientProperties.put(nameNow, properties.get(nameNow)); } } } stringBuilder.append(MixAll.properties2String(clientProperties)); return stringBuilder.toString(); } private void merge(Properties from, Properties to) { for (Entry next : from.entrySet()) { Object fromObj = next.getValue(), toObj = to.get(next.getKey()); if (toObj != null && !toObj.equals(fromObj)) { log.info("Replace, key: {}, value: {} -> {}", next.getKey(), toObj, fromObj); } to.put(next.getKey(), fromObj); } } private void mergeIfExist(Properties from, Properties to) { for (Entry next : from.entrySet()) { if (!to.containsKey(next.getKey())) { continue; } Object fromObj = next.getValue(), toObj = to.get(next.getKey()); if (toObj != null && !toObj.equals(fromObj)) { log.info("Replace, key: {}, value: {} -> {}", next.getKey(), toObj, fromObj); } to.put(next.getKey(), fromObj); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/InvokeCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface InvokeCallback { /** * This method is expected to be invoked after {@link #operationSucceed(RemotingCommand)} * or {@link #operationFail(Throwable)} * * @param responseFuture the returned object contains response or exception */ void operationComplete(final ResponseFuture responseFuture); default void operationSucceed(final RemotingCommand response) { } default void operationFail(final Throwable throwable) { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/RPCHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RPCHook { void doBeforeRequest(final String remoteAddr, final RemotingCommand request); void doAfterResponse(final String remoteAddr, final RemotingCommand request, final RemotingCommand response); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RemotingClient extends RemotingService { void updateNameServerAddressList(final List addrs); List getNameServerAddressList(); List getAvailableNameSrvList(); RemotingCommand invokeSync(final String addr, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException; void invokeAsync(final String addr, final RemotingCommand request, final long timeoutMillis, final InvokeCallback invokeCallback) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException; void invokeOneway(final String addr, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException; default CompletableFuture invoke(final String addr, final RemotingCommand request, final long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { future.complete(response); } @Override public void operationFail(Throwable throwable) { future.completeExceptionally(throwable); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } void registerProcessor(final int requestCode, final NettyRequestProcessor processor, final ExecutorService executor); void setCallbackExecutor(final ExecutorService callbackExecutor); boolean isChannelWritable(final String addr); boolean isAddressReachable(final String addr); void closeChannels(final List addrList); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import io.netty.channel.Channel; import java.util.concurrent.ExecutorService; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RemotingServer extends RemotingService { void registerProcessor(final int requestCode, final NettyRequestProcessor processor, final ExecutorService executor); void registerDefaultProcessor(final NettyRequestProcessor processor, final ExecutorService executor); int localListenPort(); Pair getProcessorPair(final int requestCode); Pair getDefaultProcessorPair(); RemotingServer newRemotingServer(int port); void removeRemotingServer(int port); RemotingCommand invokeSync(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException; void invokeAsync(final Channel channel, final RemotingCommand request, final long timeoutMillis, final InvokeCallback invokeCallback) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException; void invokeOneway(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException; void writeResponse(final Channel channel, final RemotingCommand request, final RemotingCommand response, final java.util.function.Consumer> callback); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/RemotingService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import org.apache.rocketmq.remoting.pipeline.RequestPipeline; public interface RemotingService { void start(); void shutdown(); void registerRPCHook(RPCHook rpcHook); void setRequestPipeline(RequestPipeline pipeline); /** * Remove all rpc hooks. */ void clearRPCHook(); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/annotation/CFNotNull.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) public @interface CFNotNull { } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/annotation/CFNullable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) public @interface CFNullable { } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/common/HeartbeatV2Result.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.common; public class HeartbeatV2Result { private int version = 0; private boolean isSubChange = false; private boolean isSupportV2 = false; public HeartbeatV2Result(int version, boolean isSubChange, boolean isSupportV2) { this.version = version; this.isSubChange = isSubChange; this.isSupportV2 = isSupportV2; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public boolean isSubChange() { return isSubChange; } public void setSubChange(boolean subChange) { isSubChange = subChange; } public boolean isSupportV2() { return isSupportV2; } public void setSupportV2(boolean supportV2) { isSupportV2 = supportV2; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.common; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.AttributeKeys; import org.apache.rocketmq.remoting.netty.NettySystemConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; @SuppressWarnings("DoubleBraceInitialization") public class RemotingHelper { public static final String DEFAULT_CHARSET = "UTF-8"; public static final String DEFAULT_CIDR_ALL = "0.0.0.0/0"; private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); public static final Map REQUEST_CODE_MAP = new HashMap() { { try { Field[] f = RequestCode.class.getFields(); for (Field field : f) { if (field.getType() == int.class) { put((int) field.get(null), field.getName().toLowerCase()); } } } catch (IllegalAccessException ignore) { } } }; public static final Map RESPONSE_CODE_MAP = new HashMap() { { try { Field[] f = ResponseCode.class.getFields(); for (Field field : f) { if (field.getType() == int.class) { put((int) field.get(null), field.getName().toLowerCase()); } } } catch (IllegalAccessException ignore) { } } }; public static T getAttributeValue(AttributeKey key, final Channel channel) { if (channel.hasAttr(key)) { Attribute attribute = channel.attr(key); return attribute.get(); } return null; } public static void setPropertyToAttr(final Channel channel, AttributeKey attributeKey, T value) { if (channel == null) { return; } channel.attr(attributeKey).set(value); } public static SocketAddress string2SocketAddress(final String addr) { int split = addr.lastIndexOf(":"); String host = addr.substring(0, split); String port = addr.substring(split + 1); InetSocketAddress isa = new InetSocketAddress(host, Integer.parseInt(port)); return isa; } public static RemotingCommand invokeSync(final String addr, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, RemotingCommandException { long beginTime = System.currentTimeMillis(); SocketAddress socketAddress = NetworkUtil.string2SocketAddress(addr); SocketChannel socketChannel = connect(socketAddress); if (socketChannel != null) { boolean sendRequestOK = false; try { socketChannel.configureBlocking(true); //bugfix http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4614802 socketChannel.socket().setSoTimeout((int) timeoutMillis); ByteBuffer byteBufferRequest = request.encode(); while (byteBufferRequest.hasRemaining()) { int length = socketChannel.write(byteBufferRequest); if (length > 0) { if (byteBufferRequest.hasRemaining()) { if ((System.currentTimeMillis() - beginTime) > timeoutMillis) { throw new RemotingSendRequestException(addr); } } } else { throw new RemotingSendRequestException(addr); } Thread.sleep(1); } sendRequestOK = true; ByteBuffer byteBufferSize = ByteBuffer.allocate(4); while (byteBufferSize.hasRemaining()) { int length = socketChannel.read(byteBufferSize); if (length > 0) { if (byteBufferSize.hasRemaining()) { if ((System.currentTimeMillis() - beginTime) > timeoutMillis) { throw new RemotingTimeoutException(addr, timeoutMillis); } } } else { throw new RemotingTimeoutException(addr, timeoutMillis); } Thread.sleep(1); } int size = byteBufferSize.getInt(0); ByteBuffer byteBufferBody = ByteBuffer.allocate(size); while (byteBufferBody.hasRemaining()) { int length = socketChannel.read(byteBufferBody); if (length > 0) { if (byteBufferBody.hasRemaining()) { if ((System.currentTimeMillis() - beginTime) > timeoutMillis) { throw new RemotingTimeoutException(addr, timeoutMillis); } } } else { throw new RemotingTimeoutException(addr, timeoutMillis); } Thread.sleep(1); } byteBufferBody.flip(); return RemotingCommand.decode(byteBufferBody); } catch (IOException e) { log.error("invokeSync failure", e); if (sendRequestOK) { throw new RemotingTimeoutException(addr, timeoutMillis); } else { throw new RemotingSendRequestException(addr); } } finally { try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } else { throw new RemotingConnectException(addr); } } public static String parseChannelRemoteAddr(final Channel channel) { if (null == channel) { return ""; } String addr = getProxyProtocolAddress(channel); if (StringUtils.isNotBlank(addr)) { return addr; } Attribute att = channel.attr(AttributeKeys.REMOTE_ADDR_KEY); if (att == null) { // mocked in unit test return parseChannelRemoteAddr0(channel); } addr = att.get(); if (addr == null) { addr = parseChannelRemoteAddr0(channel); att.set(addr); } return addr; } private static String getProxyProtocolAddress(Channel channel) { if (!channel.hasAttr(AttributeKeys.PROXY_PROTOCOL_ADDR)) { return null; } String proxyProtocolAddr = getAttributeValue(AttributeKeys.PROXY_PROTOCOL_ADDR, channel); String proxyProtocolPort = getAttributeValue(AttributeKeys.PROXY_PROTOCOL_PORT, channel); if (StringUtils.isBlank(proxyProtocolAddr) || proxyProtocolPort == null) { return null; } return proxyProtocolAddr + ":" + proxyProtocolPort; } private static String parseChannelRemoteAddr0(final Channel channel) { SocketAddress remote = channel.remoteAddress(); final String addr = remote != null ? remote.toString() : ""; if (addr.length() > 0) { int index = addr.lastIndexOf("/"); if (index >= 0) { return addr.substring(index + 1); } return addr; } return ""; } public static String parseChannelLocalAddr(final Channel channel) { SocketAddress remote = channel.localAddress(); final String addr = remote != null ? remote.toString() : ""; if (addr.length() > 0) { int index = addr.lastIndexOf("/"); if (index >= 0) { return addr.substring(index + 1); } return addr; } return ""; } public static String parseHostFromAddress(String address) { if (address == null) { return ""; } String[] addressSplits = address.split(":"); if (addressSplits.length < 1) { return ""; } return addressSplits[0]; } public static String parseSocketAddressAddr(SocketAddress socketAddress) { if (socketAddress != null) { // Default toString of InetSocketAddress is "hostName/IP:port" final String addr = socketAddress.toString(); int index = addr.lastIndexOf("/"); return (index != -1) ? addr.substring(index + 1) : addr; } return ""; } public static Integer parseSocketAddressPort(SocketAddress socketAddress) { if (socketAddress instanceof InetSocketAddress) { return ((InetSocketAddress) socketAddress).getPort(); } return -1; } public static int ipToInt(String ip) { String[] ips = ip.split("\\."); return (Integer.parseInt(ips[0]) << 24) | (Integer.parseInt(ips[1]) << 16) | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]); } public static boolean ipInCIDR(String ip, String cidr) { int ipAddr = ipToInt(ip); String[] cidrArr = cidr.split("/"); int netId = Integer.parseInt(cidrArr[1]); int mask = 0xFFFFFFFF << (32 - netId); int cidrIpAddr = ipToInt(cidrArr[0]); return (ipAddr & mask) == (cidrIpAddr & mask); } public static SocketChannel connect(SocketAddress remote) { return connect(remote, 1000 * 5); } public static SocketChannel connect(SocketAddress remote, final int timeoutMillis) { SocketChannel sc = null; try { sc = SocketChannel.open(); sc.configureBlocking(true); sc.socket().setSoLinger(false, -1); sc.socket().setTcpNoDelay(true); if (NettySystemConfig.socketSndbufSize > 0) { sc.socket().setReceiveBufferSize(NettySystemConfig.socketSndbufSize); } if (NettySystemConfig.socketRcvbufSize > 0) { sc.socket().setSendBufferSize(NettySystemConfig.socketRcvbufSize); } sc.socket().connect(remote, timeoutMillis); sc.configureBlocking(false); return sc; } catch (Exception e) { if (sc != null) { try { sc.close(); } catch (IOException e1) { e1.printStackTrace(); } } } return null; } public static void closeChannel(Channel channel) { final String addrRemote = RemotingHelper.parseChannelRemoteAddr(channel); if ("".equals(addrRemote)) { channel.close(); } else { channel.close().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { log.info("closeChannel: close the connection to remote address[{}] result: {}", addrRemote, future.isSuccess()); } }); } } public static CompletableFuture convertChannelFutureToCompletableFuture(ChannelFuture channelFuture) { CompletableFuture completableFuture = new CompletableFuture<>(); channelFuture.addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { completableFuture.complete(null); } else { completableFuture.completeExceptionally(new RemotingConnectException(channelFuture.channel().remoteAddress().toString(), future.cause())); } }); return completableFuture; } public static String getRequestCodeDesc(int code) { return REQUEST_CODE_MAP.getOrDefault(code, String.valueOf(code)); } public static String getResponseCodeDesc(int code) { return RESPONSE_CODE_MAP.getOrDefault(code, String.valueOf(code)); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/common/SemaphoreReleaseOnlyOnce.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.common; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; public class SemaphoreReleaseOnlyOnce { private final AtomicBoolean released = new AtomicBoolean(false); private final Semaphore semaphore; public SemaphoreReleaseOnlyOnce(Semaphore semaphore) { this.semaphore = semaphore; } public void release() { if (this.semaphore != null) { if (this.released.compareAndSet(false, true)) { this.semaphore.release(); } } } public Semaphore getSemaphore() { return semaphore; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/common/ServiceThread.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.common; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * Base class for background thread */ public abstract class ServiceThread implements Runnable { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final long JOIN_TIME = 90 * 1000; protected final Thread thread; protected volatile boolean hasNotified = false; protected volatile boolean stopped = false; public ServiceThread() { this.thread = new Thread(this, this.getServiceName()); } public abstract String getServiceName(); public void start() { this.thread.start(); } public void shutdown() { this.shutdown(false); } public void shutdown(final boolean interrupt) { this.stopped = true; log.info("shutdown thread " + this.getServiceName() + " interrupt " + interrupt); synchronized (this) { if (!this.hasNotified) { this.hasNotified = true; this.notify(); } } try { if (interrupt) { this.thread.interrupt(); } long beginTime = System.currentTimeMillis(); this.thread.join(this.getJointime()); long elapsedTime = System.currentTimeMillis() - beginTime; log.info("join thread " + this.getServiceName() + " elapsed time(ms) " + elapsedTime + " " + this.getJointime()); } catch (InterruptedException e) { log.error("Interrupted", e); } } public long getJointime() { return JOIN_TIME; } public boolean isStopped() { return stopped; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/common/TlsMode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.common; /** * For server, three SSL modes are supported: disabled, permissive and enforcing. *
      *
    1. disabled: SSL is not supported; any incoming SSL handshake will be rejected, causing connection closed.
    2. *
    3. permissive: SSL is optional, aka, server in this mode can serve client connections with or without SSL;
    4. *
    5. enforcing: SSL is required, aka, non SSL connection will be rejected.
    6. *
    */ public enum TlsMode { DISABLED("disabled"), PERMISSIVE("permissive"), ENFORCING("enforcing"); private String name; TlsMode(String name) { this.name = name; } public static TlsMode parse(String mode) { for (TlsMode tlsMode : TlsMode.values()) { if (tlsMode.name.equals(mode)) { return tlsMode; } } return PERMISSIVE; } public String getName() { return name; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/exception/RemotingCommandException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.exception; public class RemotingCommandException extends RemotingException { private static final long serialVersionUID = -6061365915274953096L; public RemotingCommandException(String message) { super(message, null); } public RemotingCommandException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/exception/RemotingConnectException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.exception; public class RemotingConnectException extends RemotingException { private static final long serialVersionUID = -5565366231695911316L; public RemotingConnectException(String addr) { this(addr, null); } public RemotingConnectException(String addr, Throwable cause) { super("connect to " + addr + " failed", cause); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/exception/RemotingException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.exception; public class RemotingException extends Exception { private static final long serialVersionUID = -5690687334570505110L; public RemotingException(String message) { super(message); } public RemotingException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/exception/RemotingSendRequestException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.exception; public class RemotingSendRequestException extends RemotingException { private static final long serialVersionUID = 5391285827332471674L; public RemotingSendRequestException(String addr) { this(addr, null); } public RemotingSendRequestException(String addr, Throwable cause) { super("send request to <" + addr + "> failed", cause); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/exception/RemotingTimeoutException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.exception; public class RemotingTimeoutException extends RemotingException { private static final long serialVersionUID = 4106899185095245979L; public RemotingTimeoutException(String message) { super(message); } public RemotingTimeoutException(String addr, long timeoutMillis) { this(addr, timeoutMillis, null); } public RemotingTimeoutException(String addr, long timeoutMillis, Throwable cause) { super("wait response on the channel <" + addr + "> timeout, " + timeoutMillis + "(ms)", cause); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/exception/RemotingTooMuchRequestException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.exception; public class RemotingTooMuchRequestException extends RemotingException { private static final long serialVersionUID = 4326919581254519654L; public RemotingTooMuchRequestException(String message) { super(message); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsConstant.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.metrics; public class RemotingMetricsConstant { public static final String HISTOGRAM_RPC_LATENCY = "rocketmq_rpc_latency"; public static final String LABEL_PROTOCOL_TYPE = "protocol_type"; public static final String LABEL_REQUEST_CODE = "request_code"; public static final String LABEL_RESPONSE_CODE = "response_code"; public static final String LABEL_IS_LONG_POLLING = "is_long_polling"; public static final String LABEL_RESULT = "result"; public static final String PROTOCOL_TYPE_REMOTING = "remoting"; public static final String RESULT_ONEWAY = "oneway"; public static final String RESULT_SUCCESS = "success"; public static final String RESULT_CANCELED = "cancelled"; public static final String RESULT_PROCESS_REQUEST_FAILED = "process_request_failed"; public static final String RESULT_WRITE_CHANNEL_FAILED = "write_channel_failed"; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.metrics; import com.google.common.collect.Lists; import io.netty.util.concurrent.Future; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.metrics.NopLongHistogram; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.HISTOGRAM_RPC_LATENCY; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_PROTOCOL_TYPE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.PROTOCOL_TYPE_REMOTING; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.RESULT_CANCELED; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.RESULT_SUCCESS; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.RESULT_WRITE_CHANNEL_FAILED; public class RemotingMetricsManager { private LongHistogram rpcLatency = new NopLongHistogram(); private Supplier attributesBuilderSupplier; public RemotingMetricsManager() { } public AttributesBuilder newAttributesBuilder() { if (this.attributesBuilderSupplier == null) { return Attributes.builder(); } return this.attributesBuilderSupplier.get() .put(LABEL_PROTOCOL_TYPE, PROTOCOL_TYPE_REMOTING); } public void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; this.rpcLatency = meter.histogramBuilder(HISTOGRAM_RPC_LATENCY) .setDescription("Rpc latency") .setUnit("milliseconds") .ofLongs() .build(); } public List> getMetricsView() { List rpcCostTimeBuckets = Arrays.asList( (double) Duration.ofMillis(1).toMillis(), (double) Duration.ofMillis(3).toMillis(), (double) Duration.ofMillis(5).toMillis(), (double) Duration.ofMillis(7).toMillis(), (double) Duration.ofMillis(10).toMillis(), (double) Duration.ofMillis(100).toMillis(), (double) Duration.ofSeconds(1).toMillis(), (double) Duration.ofSeconds(2).toMillis(), (double) Duration.ofSeconds(3).toMillis() ); InstrumentSelector selector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_RPC_LATENCY) .build(); ViewBuilder viewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets)); return Lists.newArrayList(new Pair<>(selector, viewBuilder)); } public String getWriteAndFlushResult(Future future) { String result = RESULT_SUCCESS; if (future.isCancelled()) { result = RESULT_CANCELED; } else if (!future.isSuccess()) { result = RESULT_WRITE_CHANNEL_FAILED; } return result; } // Getter methods for external access public LongHistogram getRpcLatency() { return rpcLatency; } public Supplier getAttributesBuilderSupplier() { return attributesBuilderSupplier; } // Setter methods for testing public void setAttributesBuilderSupplier(Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/AttributeKeys.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.util.AttributeKey; import org.apache.rocketmq.common.constant.HAProxyConstants; import org.apache.rocketmq.remoting.protocol.LanguageCode; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class AttributeKeys { public static final AttributeKey REMOTE_ADDR_KEY = AttributeKey.valueOf("RemoteAddr"); public static final AttributeKey CLIENT_ID_KEY = AttributeKey.valueOf("ClientId"); public static final AttributeKey VERSION_KEY = AttributeKey.valueOf("Version"); public static final AttributeKey LANGUAGE_CODE_KEY = AttributeKey.valueOf("LanguageCode"); public static final AttributeKey PROXY_PROTOCOL_ADDR = AttributeKey.valueOf(HAProxyConstants.PROXY_PROTOCOL_ADDR); public static final AttributeKey PROXY_PROTOCOL_PORT = AttributeKey.valueOf(HAProxyConstants.PROXY_PROTOCOL_PORT); public static final AttributeKey PROXY_PROTOCOL_SERVER_ADDR = AttributeKey.valueOf(HAProxyConstants.PROXY_PROTOCOL_SERVER_ADDR); public static final AttributeKey PROXY_PROTOCOL_SERVER_PORT = AttributeKey.valueOf(HAProxyConstants.PROXY_PROTOCOL_SERVER_PORT); private static final Map> ATTRIBUTE_KEY_MAP = new ConcurrentHashMap<>(); public static AttributeKey valueOf(String name) { return ATTRIBUTE_KEY_MAP.computeIfAbsent(name, AttributeKey::valueOf); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/FileRegionEncoder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.FileRegion; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.ssl.SslHandler; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; /** *

    * By default, file region are directly transferred to socket channel which is known as zero copy. In case we need * to encrypt transmission, data being sent should go through the {@link SslHandler}. This encoder ensures this * process. *

    */ public class FileRegionEncoder extends MessageToByteEncoder { /** * Encode a message into a {@link io.netty.buffer.ByteBuf}. This method will be called for each written message that * can be handled by this encoder. * * @param ctx the {@link io.netty.channel.ChannelHandlerContext} which this {@link * io.netty.handler.codec.MessageToByteEncoder} belongs to * @param msg the message to encode * @param out the {@link io.netty.buffer.ByteBuf} into which the encoded message will be written * @throws Exception is thrown if an error occurs */ @Override protected void encode(ChannelHandlerContext ctx, FileRegion msg, final ByteBuf out) throws Exception { WritableByteChannel writableByteChannel = new WritableByteChannel() { @Override public int write(ByteBuffer src) { int prev = out.writerIndex(); out.writeBytes(src); return out.writerIndex() - prev; } @Override public boolean isOpen() { return true; } @Override public void close() throws IOException { } }; long toTransfer = msg.count(); while (true) { long transferred = msg.transferred(); if (toTransfer - transferred <= 0) { break; } msg.transferTo(writableByteChannel, transferred); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import org.apache.rocketmq.remoting.common.TlsMode; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_ENABLE; public class NettyClientConfig { /** * Worker thread number */ private int clientWorkerThreads = NettySystemConfig.clientWorkerSize; private int clientCallbackExecutorThreads = Runtime.getRuntime().availableProcessors(); private int clientOnewaySemaphoreValue = NettySystemConfig.CLIENT_ONEWAY_SEMAPHORE_VALUE; private int clientAsyncSemaphoreValue = NettySystemConfig.CLIENT_ASYNC_SEMAPHORE_VALUE; private int connectTimeoutMillis = NettySystemConfig.connectTimeoutMillis; private long channelNotActiveInterval = 1000 * 60; private boolean isScanAvailableNameSrv = true; /** * IdleStateEvent will be triggered when neither read nor write was performed for * the specified period of this time. Specify {@code 0} to disable */ private int clientChannelMaxIdleTimeSeconds = NettySystemConfig.clientChannelMaxIdleTimeSeconds; private int clientSocketSndBufSize = NettySystemConfig.socketSndbufSize; private int clientSocketRcvBufSize = NettySystemConfig.socketRcvbufSize; private boolean clientPooledByteBufAllocatorEnable = false; private boolean clientCloseSocketIfTimeout = NettySystemConfig.clientCloseSocketIfTimeout; private boolean useTLS = Boolean.parseBoolean(System.getProperty(TLS_ENABLE, String.valueOf(TlsSystemConfig.tlsMode == TlsMode.ENFORCING))); private String socksProxyConfig = "{}"; private int writeBufferHighWaterMark = NettySystemConfig.writeBufferHighWaterMark; private int writeBufferLowWaterMark = NettySystemConfig.writeBufferLowWaterMark; private boolean disableCallbackExecutor = false; private boolean disableNettyWorkerGroup = false; private long maxReconnectIntervalTimeSeconds = 60; private boolean enableReconnectForGoAway = true; public boolean isClientCloseSocketIfTimeout() { return clientCloseSocketIfTimeout; } public void setClientCloseSocketIfTimeout(final boolean clientCloseSocketIfTimeout) { this.clientCloseSocketIfTimeout = clientCloseSocketIfTimeout; } public int getClientWorkerThreads() { return clientWorkerThreads; } public void setClientWorkerThreads(int clientWorkerThreads) { this.clientWorkerThreads = clientWorkerThreads; } public int getClientOnewaySemaphoreValue() { return clientOnewaySemaphoreValue; } public void setClientOnewaySemaphoreValue(int clientOnewaySemaphoreValue) { this.clientOnewaySemaphoreValue = clientOnewaySemaphoreValue; } public int getConnectTimeoutMillis() { return connectTimeoutMillis; } public void setConnectTimeoutMillis(int connectTimeoutMillis) { this.connectTimeoutMillis = connectTimeoutMillis; } public int getClientCallbackExecutorThreads() { return clientCallbackExecutorThreads; } public void setClientCallbackExecutorThreads(int clientCallbackExecutorThreads) { this.clientCallbackExecutorThreads = clientCallbackExecutorThreads; } public long getChannelNotActiveInterval() { return channelNotActiveInterval; } public void setChannelNotActiveInterval(long channelNotActiveInterval) { this.channelNotActiveInterval = channelNotActiveInterval; } public int getClientAsyncSemaphoreValue() { return clientAsyncSemaphoreValue; } public void setClientAsyncSemaphoreValue(int clientAsyncSemaphoreValue) { this.clientAsyncSemaphoreValue = clientAsyncSemaphoreValue; } public int getClientChannelMaxIdleTimeSeconds() { return clientChannelMaxIdleTimeSeconds; } public void setClientChannelMaxIdleTimeSeconds(int clientChannelMaxIdleTimeSeconds) { this.clientChannelMaxIdleTimeSeconds = clientChannelMaxIdleTimeSeconds; } public int getClientSocketSndBufSize() { return clientSocketSndBufSize; } public void setClientSocketSndBufSize(int clientSocketSndBufSize) { this.clientSocketSndBufSize = clientSocketSndBufSize; } public int getClientSocketRcvBufSize() { return clientSocketRcvBufSize; } public void setClientSocketRcvBufSize(int clientSocketRcvBufSize) { this.clientSocketRcvBufSize = clientSocketRcvBufSize; } public boolean isClientPooledByteBufAllocatorEnable() { return clientPooledByteBufAllocatorEnable; } public void setClientPooledByteBufAllocatorEnable(boolean clientPooledByteBufAllocatorEnable) { this.clientPooledByteBufAllocatorEnable = clientPooledByteBufAllocatorEnable; } public boolean isUseTLS() { return useTLS; } public void setUseTLS(boolean useTLS) { this.useTLS = useTLS; } public int getWriteBufferLowWaterMark() { return writeBufferLowWaterMark; } public void setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { this.writeBufferLowWaterMark = writeBufferLowWaterMark; } public int getWriteBufferHighWaterMark() { return writeBufferHighWaterMark; } public void setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { this.writeBufferHighWaterMark = writeBufferHighWaterMark; } public boolean isDisableCallbackExecutor() { return disableCallbackExecutor; } public void setDisableCallbackExecutor(boolean disableCallbackExecutor) { this.disableCallbackExecutor = disableCallbackExecutor; } public boolean isDisableNettyWorkerGroup() { return disableNettyWorkerGroup; } public void setDisableNettyWorkerGroup(boolean disableNettyWorkerGroup) { this.disableNettyWorkerGroup = disableNettyWorkerGroup; } public long getMaxReconnectIntervalTimeSeconds() { return maxReconnectIntervalTimeSeconds; } public void setMaxReconnectIntervalTimeSeconds(long maxReconnectIntervalTimeSeconds) { this.maxReconnectIntervalTimeSeconds = maxReconnectIntervalTimeSeconds; } public boolean isEnableReconnectForGoAway() { return enableReconnectForGoAway; } public void setEnableReconnectForGoAway(boolean enableReconnectForGoAway) { this.enableReconnectForGoAway = enableReconnectForGoAway; } public String getSocksProxyConfig() { return socksProxyConfig; } public void setSocksProxyConfig(String socksProxyConfig) { this.socksProxyConfig = socksProxyConfig; } public boolean isScanAvailableNameSrv() { return isScanAvailableNameSrv; } public void setScanAvailableNameSrv(boolean scanAvailableNameSrv) { this.isScanAvailableNameSrv = scanAvailableNameSrv; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyDecoder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import com.google.common.base.Stopwatch; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class NettyDecoder extends LengthFieldBasedFrameDecoder { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final int FRAME_MAX_LENGTH = Integer.parseInt(System.getProperty("com.rocketmq.remoting.frameMaxLength", "16777216")); public NettyDecoder() { super(FRAME_MAX_LENGTH, 0, 4, 0, 4); } @Override public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { ByteBuf frame = null; Stopwatch timer = Stopwatch.createStarted(); try { frame = (ByteBuf) super.decode(ctx, in); if (null == frame) { return null; } RemotingCommand cmd = RemotingCommand.decode(frame); cmd.setProcessTimer(timer); return cmd; } catch (Exception e) { log.error("decode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); RemotingHelper.closeChannel(ctx.channel()); } finally { if (null != frame) { frame.release(); } } return null; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEncoder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.RemotingCommand; @ChannelHandler.Sharable public class NettyEncoder extends MessageToByteEncoder { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); @Override public void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, ByteBuf out) throws Exception { try { remotingCommand.fastEncodeHeader(out); byte[] body = remotingCommand.getBody(); if (body != null) { out.writeBytes(body); } } catch (Exception e) { log.error("encode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); if (remotingCommand != null) { log.error(remotingCommand.toString()); } RemotingHelper.closeChannel(ctx.channel()); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.Channel; public class NettyEvent { private final NettyEventType type; private final String remoteAddr; private final Channel channel; public NettyEvent(NettyEventType type, String remoteAddr, Channel channel) { this.type = type; this.remoteAddr = remoteAddr; this.channel = channel; } public NettyEventType getType() { return type; } public String getRemoteAddr() { return remoteAddr; } public Channel getChannel() { return channel; } @Override public String toString() { return "NettyEvent [type=" + type + ", remoteAddr=" + remoteAddr + ", channel=" + channel + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEventType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; public enum NettyEventType { CONNECT, CLOSE, IDLE, EXCEPTION, ACTIVE } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyLogger.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.util.internal.logging.InternalLogLevel; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicBoolean; public class NettyLogger { private static AtomicBoolean nettyLoggerSeted = new AtomicBoolean(false); private static InternalLogLevel nettyLogLevel = InternalLogLevel.ERROR; public static void initNettyLogger() { if (!nettyLoggerSeted.get()) { try { io.netty.util.internal.logging.InternalLoggerFactory.setDefaultFactory(new NettyBridgeLoggerFactory()); } catch (Throwable e) { //ignore } nettyLoggerSeted.set(true); } } private static class NettyBridgeLoggerFactory extends io.netty.util.internal.logging.InternalLoggerFactory { @Override protected io.netty.util.internal.logging.InternalLogger newInstance(String s) { return new NettyBridgeLogger(s); } } private static class NettyBridgeLogger implements io.netty.util.internal.logging.InternalLogger { private Logger logger = null; private static final String EXCEPTION_MESSAGE = "Unexpected exception:"; public NettyBridgeLogger(String name) { logger = LoggerFactory.getLogger(name); } @Override public String name() { return logger.getName(); } @Override public boolean isEnabled(InternalLogLevel internalLogLevel) { return nettyLogLevel.ordinal() <= internalLogLevel.ordinal(); } @Override public void log(InternalLogLevel internalLogLevel, String s) { if (internalLogLevel.equals(InternalLogLevel.DEBUG)) { logger.debug(s); } if (internalLogLevel.equals(InternalLogLevel.TRACE)) { logger.trace(s); } if (internalLogLevel.equals(InternalLogLevel.INFO)) { logger.info(s); } if (internalLogLevel.equals(InternalLogLevel.WARN)) { logger.warn(s); } if (internalLogLevel.equals(InternalLogLevel.ERROR)) { logger.error(s); } } @Override public void log(InternalLogLevel internalLogLevel, String s, Object o) { if (internalLogLevel.equals(InternalLogLevel.DEBUG)) { logger.debug(s, o); } if (internalLogLevel.equals(InternalLogLevel.TRACE)) { logger.trace(s, o); } if (internalLogLevel.equals(InternalLogLevel.INFO)) { logger.info(s, o); } if (internalLogLevel.equals(InternalLogLevel.WARN)) { logger.warn(s, o); } if (internalLogLevel.equals(InternalLogLevel.ERROR)) { logger.error(s, o); } } @Override public void log(InternalLogLevel internalLogLevel, String s, Object o, Object o1) { if (internalLogLevel.equals(InternalLogLevel.DEBUG)) { logger.debug(s, o, o1); } if (internalLogLevel.equals(InternalLogLevel.TRACE)) { logger.trace(s, o, o1); } if (internalLogLevel.equals(InternalLogLevel.INFO)) { logger.info(s, o, o1); } if (internalLogLevel.equals(InternalLogLevel.WARN)) { logger.warn(s, o, o1); } if (internalLogLevel.equals(InternalLogLevel.ERROR)) { logger.error(s, o, o1); } } @Override public void log(InternalLogLevel internalLogLevel, String s, Object... objects) { if (internalLogLevel.equals(InternalLogLevel.DEBUG)) { logger.debug(s, objects); } if (internalLogLevel.equals(InternalLogLevel.TRACE)) { logger.trace(s, objects); } if (internalLogLevel.equals(InternalLogLevel.INFO)) { logger.info(s, objects); } if (internalLogLevel.equals(InternalLogLevel.WARN)) { logger.warn(s, objects); } if (internalLogLevel.equals(InternalLogLevel.ERROR)) { logger.error(s, objects); } } @Override public void log(InternalLogLevel internalLogLevel, String s, Throwable throwable) { if (internalLogLevel.equals(InternalLogLevel.DEBUG)) { logger.debug(s, throwable); } if (internalLogLevel.equals(InternalLogLevel.TRACE)) { logger.trace(s, throwable); } if (internalLogLevel.equals(InternalLogLevel.INFO)) { logger.info(s, throwable); } if (internalLogLevel.equals(InternalLogLevel.WARN)) { logger.warn(s, throwable); } if (internalLogLevel.equals(InternalLogLevel.ERROR)) { logger.error(s, throwable); } } @Override public void log(InternalLogLevel internalLogLevel, Throwable throwable) { if (internalLogLevel.equals(InternalLogLevel.DEBUG)) { logger.debug(EXCEPTION_MESSAGE, throwable); } if (internalLogLevel.equals(InternalLogLevel.TRACE)) { logger.trace(EXCEPTION_MESSAGE, throwable); } if (internalLogLevel.equals(InternalLogLevel.INFO)) { logger.info(EXCEPTION_MESSAGE, throwable); } if (internalLogLevel.equals(InternalLogLevel.WARN)) { logger.warn(EXCEPTION_MESSAGE, throwable); } if (internalLogLevel.equals(InternalLogLevel.ERROR)) { logger.error(EXCEPTION_MESSAGE, throwable); } } @Override public boolean isTraceEnabled() { return isEnabled(InternalLogLevel.TRACE); } @Override public void trace(String var1) { logger.trace(var1); } @Override public void trace(String var1, Object var2) { logger.trace(var1, var2); } @Override public void trace(String var1, Object var2, Object var3) { logger.trace(var1, var2, var3); } @Override public void trace(String var1, Object... var2) { logger.trace(var1, var2); } @Override public void trace(String var1, Throwable var2) { logger.trace(var1, var2); } @Override public void trace(Throwable var1) { logger.trace(EXCEPTION_MESSAGE, var1); } @Override public boolean isDebugEnabled() { return isEnabled(InternalLogLevel.DEBUG); } @Override public void debug(String var1) { logger.debug(var1); } @Override public void debug(String var1, Object var2) { logger.debug(var1, var2); } @Override public void debug(String var1, Object var2, Object var3) { logger.debug(var1, var2, var3); } @Override public void debug(String var1, Object... var2) { logger.debug(var1, var2); } @Override public void debug(String var1, Throwable var2) { logger.debug(var1, var2); } @Override public void debug(Throwable var1) { logger.debug(EXCEPTION_MESSAGE, var1); } @Override public boolean isInfoEnabled() { return isEnabled(InternalLogLevel.INFO); } @Override public void info(String var1) { logger.info(var1); } @Override public void info(String var1, Object var2) { logger.info(var1, var2); } @Override public void info(String var1, Object var2, Object var3) { logger.info(var1, var2, var3); } @Override public void info(String var1, Object... var2) { logger.info(var1, var2); } @Override public void info(String var1, Throwable var2) { logger.info(var1, var2); } @Override public void info(Throwable var1) { logger.info(EXCEPTION_MESSAGE, var1); } @Override public boolean isWarnEnabled() { return isEnabled(InternalLogLevel.WARN); } @Override public void warn(String var1) { logger.warn(var1); } @Override public void warn(String var1, Object var2) { logger.warn(var1, var2); } @Override public void warn(String var1, Object... var2) { logger.warn(var1, var2); } @Override public void warn(String var1, Object var2, Object var3) { logger.warn(var1, var2, var3); } @Override public void warn(String var1, Throwable var2) { logger.warn(var1, var2); } @Override public void warn(Throwable var1) { logger.warn(EXCEPTION_MESSAGE, var1); } @Override public boolean isErrorEnabled() { return isEnabled(InternalLogLevel.ERROR); } @Override public void error(String var1) { logger.error(var1); } @Override public void error(String var1, Object var2) { logger.error(var1, var2); } @Override public void error(String var1, Object var2, Object var3) { logger.error(var1, var2, var3); } @Override public void error(String var1, Object... var2) { logger.error(var1, var2); } @Override public void error(String var1, Throwable var2) { logger.error(var1, var2); } @Override public void error(Throwable var1) { logger.error(EXCEPTION_MESSAGE, var1); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.util.concurrent.Future; import io.opentelemetry.api.common.AttributesBuilder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import javax.annotation.Nullable; import org.apache.rocketmq.common.AbortProcessException; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ExceptionUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager; import org.apache.rocketmq.remoting.pipeline.RequestPipeline; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_IS_LONG_POLLING; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_REQUEST_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESPONSE_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESULT; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.RESULT_ONEWAY; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.RESULT_PROCESS_REQUEST_FAILED; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.RESULT_WRITE_CHANNEL_FAILED; public abstract class NettyRemotingAbstract { /** * Remoting logger instance. */ private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); /** * Semaphore to limit maximum number of on-going one-way requests, which protects system memory footprint. */ protected final Semaphore semaphoreOneway; /** * Semaphore to limit maximum number of on-going asynchronous requests, which protects system memory footprint. */ protected final Semaphore semaphoreAsync; /** * This map caches all on-going requests. */ protected final ConcurrentMap responseTable = new ConcurrentHashMap<>(256); /** * This container holds all processors per request code, aka, for each incoming request, we may look up the * responding processor in this map to handle the request. */ protected final HashMap> processorTable = new HashMap<>(64); /** * Executor to feed netty events to user defined {@link ChannelEventListener}. */ protected final NettyEventExecutor nettyEventExecutor = new NettyEventExecutor(); /** * The default request processor to use in case there is no exact match in {@link #processorTable} per request * code. */ protected Pair defaultRequestProcessorPair; /** * SSL context via which to create {@link SslHandler}. */ protected volatile SslContext sslContext; /** * custom rpc hooks */ protected List rpcHooks = new ArrayList<>(); protected RequestPipeline requestPipeline; protected AtomicBoolean isShuttingDown = new AtomicBoolean(false); /** * Remoting metrics manager instance for this remoting server. */ protected RemotingMetricsManager remotingMetricsManager; static { NettyLogger.initNettyLogger(); } /** * Constructor, specifying capacity of one-way and asynchronous semaphores. * * @param permitsOneway Number of permits for one-way requests. * @param permitsAsync Number of permits for asynchronous requests. */ public NettyRemotingAbstract(final int permitsOneway, final int permitsAsync) { this.semaphoreOneway = new Semaphore(permitsOneway, true); this.semaphoreAsync = new Semaphore(permitsAsync, true); } /** * Custom channel event listener. * * @return custom channel event listener if defined; null otherwise. */ public abstract ChannelEventListener getChannelEventListener(); /** * Set the remoting metrics manager for this remoting server. * * @param remotingMetricsManager the remoting metrics manager instance */ public void setRemotingMetricsManager(RemotingMetricsManager remotingMetricsManager) { this.remotingMetricsManager = remotingMetricsManager; } /** * Get the remoting metrics manager for this remoting server. * * @return the remoting metrics manager instance */ public RemotingMetricsManager getRemotingMetricsManager() { return remotingMetricsManager; } /** * Put a netty event to the executor. * * @param event Netty event instance. */ public void putNettyEvent(final NettyEvent event) { this.nettyEventExecutor.putNettyEvent(event); } /** * Entry of incoming command processing. * *

    * Note: * The incoming remoting command may be *

      *
    • An inquiry request from a remote peer component;
    • *
    • A response to a previous request issued by this very participant.
    • *
    *

    * * @param ctx Channel handler context. * @param msg incoming remoting command. */ public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) { if (msg != null) { switch (msg.getType()) { case REQUEST_COMMAND: processRequestCommand(ctx, msg); break; case RESPONSE_COMMAND: processResponseCommand(ctx, msg); break; default: break; } } } protected void doBeforeRpcHooks(String addr, RemotingCommand request) { if (rpcHooks.size() > 0) { for (RPCHook rpcHook : rpcHooks) { rpcHook.doBeforeRequest(addr, request); } } } public void doAfterRpcHooks(String addr, RemotingCommand request, RemotingCommand response) { if (rpcHooks.size() > 0) { for (RPCHook rpcHook : rpcHooks) { rpcHook.doAfterResponse(addr, request, response); } } } public static void writeResponse(Channel channel, RemotingCommand request, @Nullable RemotingCommand response, Consumer> callback, RemotingMetricsManager remotingMetricsManager) { if (response == null) { return; } final AttributesBuilder attributesBuilder; if (remotingMetricsManager != null) { attributesBuilder = remotingMetricsManager.newAttributesBuilder(); attributesBuilder.put(LABEL_IS_LONG_POLLING, request.isSuspended()) .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(response.getCode())); } else { attributesBuilder = null; } if (request.isOnewayRPC()) { if (attributesBuilder != null) { attributesBuilder.put(LABEL_RESULT, RESULT_ONEWAY); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } return; } response.setOpaque(request.getOpaque()); response.markResponseType(); try { channel.writeAndFlush(response).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { log.debug("Response[request code: {}, response code: {}, opaque: {}] is written to channel{}", request.getCode(), response.getCode(), response.getOpaque(), channel); } else { log.error("Failed to write response[request code: {}, response code: {}, opaque: {}] to channel{}", request.getCode(), response.getCode(), response.getOpaque(), channel, future.cause()); } if (remotingMetricsManager != null) { attributesBuilder.put(LABEL_RESULT, remotingMetricsManager.getWriteAndFlushResult(future)); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } if (callback != null) { callback.accept(future); } }); } catch (Throwable e) { log.error("process request over, but response failed", e); log.error(request.toString()); log.error(response.toString()); if (remotingMetricsManager != null) { attributesBuilder.put(LABEL_RESULT, RESULT_WRITE_CHANNEL_FAILED); remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } } } public void writeResponse(Channel channel, RemotingCommand request, @Nullable RemotingCommand response, Consumer> callback) { if (response == null) { return; } final AttributesBuilder attributesBuilder; if (this.remotingMetricsManager != null) { attributesBuilder = this.remotingMetricsManager.newAttributesBuilder(); attributesBuilder.put(LABEL_IS_LONG_POLLING, request.isSuspended()) .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(response.getCode())); } else { attributesBuilder = null; } if (request.isOnewayRPC()) { if (attributesBuilder != null) { attributesBuilder.put(LABEL_RESULT, RESULT_ONEWAY); this.remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } return; } response.setOpaque(request.getOpaque()); response.markResponseType(); try { channel.writeAndFlush(response).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { log.debug("Response[request code: {}, response code: {}, opaque: {}] is written to channel{}", request.getCode(), response.getCode(), response.getOpaque(), channel); } else { log.error("Failed to write response[request code: {}, response code: {}, opaque: {}] to channel{}", request.getCode(), response.getCode(), response.getOpaque(), channel, future.cause()); } if (this.remotingMetricsManager != null && attributesBuilder != null) { attributesBuilder.put(LABEL_RESULT, this.remotingMetricsManager.getWriteAndFlushResult(future)); this.remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } if (callback != null) { callback.accept(future); } }); } catch (Throwable e) { log.error("process request over, but response failed", e); log.error(request.toString()); log.error(response.toString()); if (this.remotingMetricsManager != null && attributesBuilder != null) { attributesBuilder.put(LABEL_RESULT, RESULT_WRITE_CHANNEL_FAILED); this.remotingMetricsManager.getRpcLatency().record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } } } /** * Process incoming request command issued by remote peer. * * @param ctx channel handler context. * @param cmd request command. */ public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) { final Pair matched = this.processorTable.get(cmd.getCode()); final Pair pair = null == matched ? this.defaultRequestProcessorPair : matched; final int opaque = cmd.getOpaque(); if (pair == null) { String error = " request type " + cmd.getCode() + " not supported"; final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); response.setOpaque(opaque); this.writeResponse(ctx.channel(), cmd, response, null); log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error); return; } Runnable run = buildProcessRequestHandler(ctx, cmd, pair, opaque); if (isShuttingDown.get()) { if (cmd.getVersion() > MQVersion.Version.V5_3_1.ordinal()) { final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.GO_AWAY, "please go away"); response.setOpaque(opaque); this.writeResponse(ctx.channel(), cmd, response, null); log.info("proxy is shutting down, write response GO_AWAY. channel={}, requestCode={}, opaque={}", ctx.channel(), cmd.getCode(), opaque); return; } } if (pair.getObject1().rejectRequest()) { final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY, "[REJECTREQUEST]system busy, start flow control for a while"); response.setOpaque(opaque); this.writeResponse(ctx.channel(), cmd, response, null); return; } try { final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd); //async execute task, current thread return directly pair.getObject2().submit(requestTask); } catch (RejectedExecutionException e) { if ((System.currentTimeMillis() % 10000) == 0) { log.warn(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + ", too many requests and system thread pool busy, RejectedExecutionException " + pair.getObject2().toString() + " request code: " + cmd.getCode()); } final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY, "[OVERLOAD]system busy, start flow control for a while"); response.setOpaque(opaque); this.writeResponse(ctx.channel(), cmd, response, null); } catch (Throwable e) { if (remotingMetricsManager != null) { AttributesBuilder attributesBuilder = remotingMetricsManager.newAttributesBuilder() .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(cmd.getCode())) .put(LABEL_RESULT, RESULT_PROCESS_REQUEST_FAILED); remotingMetricsManager.getRpcLatency().record(cmd.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); } } } private Runnable buildProcessRequestHandler(ChannelHandlerContext ctx, RemotingCommand cmd, Pair pair, int opaque) { return () -> { Exception exception = null; RemotingCommand response; String remoteAddr = null; try { remoteAddr = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); try { doBeforeRpcHooks(remoteAddr, cmd); } catch (AbortProcessException e) { throw e; } catch (Exception e) { exception = e; } if (this.requestPipeline != null) { this.requestPipeline.execute(ctx, cmd); } if (exception == null) { response = pair.getObject1().processRequest(ctx, cmd); } else { response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR, null); } try { doAfterRpcHooks(remoteAddr, cmd, response); } catch (AbortProcessException e) { throw e; } catch (Exception e) { exception = e; } if (exception != null) { throw exception; } this.writeResponse(ctx.channel(), cmd, response, null); } catch (AbortProcessException e) { response = RemotingCommand.createResponseCommand(e.getResponseCode(), e.getErrorMessage()); response.setOpaque(opaque); this.writeResponse(ctx.channel(), cmd, response, null); } catch (Throwable e) { log.error("process request exception, remoteAddr: {}", remoteAddr, e); log.error(cmd.toString()); if (!cmd.isOnewayRPC()) { response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR, UtilAll.exceptionSimpleDesc(e)); response.setOpaque(opaque); this.writeResponse(ctx.channel(), cmd, response, null); } } }; } /** * Process response from remote peer to the previous issued requests. * * @param ctx channel handler context. * @param cmd response command instance. */ public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) { final int opaque = cmd.getOpaque(); final ResponseFuture responseFuture = responseTable.get(opaque); if (responseFuture != null) { responseFuture.setResponseCommand(cmd); responseTable.remove(opaque); if (responseFuture.getInvokeCallback() != null) { executeInvokeCallback(responseFuture); } else { responseFuture.putResponse(cmd); responseFuture.release(); } } else { log.warn("receive response, cmd={}, but not matched any request, address={}, channelId={}", cmd, RemotingHelper.parseChannelRemoteAddr(ctx.channel()), ctx.channel().id()); } } /** * Execute callback in callback executor. If callback executor is null, run directly in current thread */ private void executeInvokeCallback(final ResponseFuture responseFuture) { boolean runInThisThread = false; ExecutorService executor = this.getCallbackExecutor(); if (executor != null && !executor.isShutdown()) { try { executor.submit(() -> { try { responseFuture.executeInvokeCallback(); } catch (Throwable e) { log.warn("execute callback in executor exception, and callback throw", e); } finally { responseFuture.release(); } }); } catch (Exception e) { runInThisThread = true; log.warn("execute callback in executor exception, maybe executor busy", e); } } else { runInThisThread = true; } if (runInThisThread) { try { responseFuture.executeInvokeCallback(); } catch (Throwable e) { log.warn("executeInvokeCallback Exception", e); } finally { responseFuture.release(); } } } /** * Custom RPC hooks. * * @return RPC hooks if specified; null otherwise. */ public List getRPCHook() { return rpcHooks; } public void registerRPCHook(RPCHook rpcHook) { if (rpcHook != null && !rpcHooks.contains(rpcHook)) { rpcHooks.add(rpcHook); } } public void setRequestPipeline(RequestPipeline pipeline) { this.requestPipeline = pipeline; } public void clearRPCHook() { rpcHooks.clear(); } /** * This method specifies thread pool to use while invoking callback methods. * * @return Dedicated thread pool instance if specified; or null if the callback is supposed to be executed in the * netty event-loop thread. */ public abstract ExecutorService getCallbackExecutor(); /** *

    * This method is periodically invoked to scan and expire deprecated request. *

    */ public void scanResponseTable() { final List rfList = new LinkedList<>(); Iterator> it = this.responseTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); ResponseFuture rep = next.getValue(); if ((rep.getBeginTimestamp() + rep.getTimeoutMillis() + 1000) <= System.currentTimeMillis()) { rep.release(); it.remove(); rfList.add(rep); log.warn("remove timeout request, " + rep); } } for (ResponseFuture rf : rfList) { try { executeInvokeCallback(rf); } catch (Throwable e) { log.warn("scanResponseTable, operationComplete Exception", e); } } } 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()); } } public CompletableFuture invokeImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis) { return invoke0(channel, request, timeoutMillis); } protected CompletableFuture invoke0(final Channel channel, final RemotingCommand request, final long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); long beginStartTime = System.currentTimeMillis(); final int opaque = request.getOpaque(); boolean acquired; try { acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS); } catch (Throwable t) { future.completeExceptionally(t); return future; } if (acquired) { final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync); long costTime = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTime) { once.release(); future.completeExceptionally(new RemotingTimeoutException("invokeAsyncImpl call timeout")); return future; } AtomicReference responseFutureReference = new AtomicReference<>(); final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, request, timeoutMillis - costTime, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { future.complete(responseFutureReference.get()); } @Override public void operationFail(Throwable throwable) { future.completeExceptionally(throwable); } }, once); responseFutureReference.set(responseFuture); this.responseTable.put(opaque, responseFuture); try { channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> { if (f.isSuccess()) { responseFuture.setSendRequestOK(true); return; } requestFail(opaque); log.warn("send a request command to channel <{}>, channelId={}, failed.", RemotingHelper.parseChannelRemoteAddr(channel), channel.id()); }); return future; } catch (Exception e) { responseTable.remove(opaque); responseFuture.release(); log.warn("send a request command to channel <{}> channelId={} Exception", RemotingHelper.parseChannelRemoteAddr(channel), channel.id(), e); future.completeExceptionally(new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e)); return future; } } else { if (timeoutMillis <= 0) { future.completeExceptionally(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); future.completeExceptionally(new RemotingTimeoutException(info)); } return future; } } public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis, final InvokeCallback invokeCallback) { invokeImpl(channel, request, timeoutMillis) .whenComplete((v, t) -> { if (t == null) { invokeCallback.operationComplete(v); } else { ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, timeoutMillis, null, null); responseFuture.setCause(t); invokeCallback.operationComplete(responseFuture); } }) .thenAccept(responseFuture -> invokeCallback.operationSucceed(responseFuture.getResponseCommand())) .exceptionally(t -> { invokeCallback.operationFail(ExceptionUtils.getRealException(t)); return null; }); } private void requestFail(final int opaque) { ResponseFuture responseFuture = responseTable.remove(opaque); if (responseFuture != null) { responseFuture.setSendRequestOK(false); responseFuture.putResponse(null); try { executeInvokeCallback(responseFuture); } catch (Throwable e) { log.warn("execute callback in requestFail, and callback throw", e); } finally { responseFuture.release(); } } } /** * mark the request of the specified channel as fail and to invoke fail callback immediately * * @param channel the channel which is close already */ protected void failFast(final Channel channel) { for (Entry entry : responseTable.entrySet()) { if (entry.getValue().getChannel() == channel) { Integer opaque = entry.getKey(); if (opaque != null) { requestFail(opaque); } } } } 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 { channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> { 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 semaphoreOnewayValue: %d", timeoutMillis, this.semaphoreOneway.getQueueLength(), this.semaphoreOneway.availablePermits() ); log.warn(info); throw new RemotingTimeoutException(info); } } } public HashMap> getProcessorTable() { return processorTable; } class NettyEventExecutor extends ServiceThread { private final LinkedBlockingQueue eventQueue = new LinkedBlockingQueue<>(); public void putNettyEvent(final NettyEvent event) { int currentSize = this.eventQueue.size(); int maxSize = 10000; if (currentSize <= maxSize) { this.eventQueue.add(event); } else { log.warn("event queue size [{}] over the limit [{}], so drop this event {}", currentSize, maxSize, event.toString()); } } @Override public void run() { log.info(this.getServiceName() + " service started"); final ChannelEventListener listener = NettyRemotingAbstract.this.getChannelEventListener(); while (!this.isStopped()) { try { NettyEvent event = this.eventQueue.poll(3000, TimeUnit.MILLISECONDS); if (event != null && listener != null) { switch (event.getType()) { case IDLE: listener.onChannelIdle(event.getRemoteAddr(), event.getChannel()); break; case CLOSE: listener.onChannelClose(event.getRemoteAddr(), event.getChannel()); break; case CONNECT: listener.onChannelConnect(event.getRemoteAddr(), event.getChannel()); break; case EXCEPTION: listener.onChannelException(event.getRemoteAddr(), event.getChannel()); break; case ACTIVE: listener.onChannelActive(event.getRemoteAddr(), event.getChannel()); break; default: break; } } } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { return NettyEventExecutor.class.getSimpleName(); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; import com.google.common.base.Stopwatch; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.proxy.Socks5ProxyHandler; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateHandler; import io.netty.resolver.NoopAddressResolverGroup; import io.netty.util.AttributeKey; import io.netty.util.HashedWheelTimer; import io.netty.util.Timeout; import io.netty.util.TimerTask; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.FutureUtils; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.proxy.SocksProxyConfig; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.apache.rocketmq.remoting.common.RemotingHelper.convertChannelFutureToCompletableFuture; public class NettyRemotingClient extends NettyRemotingAbstract implements RemotingClient { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final AttributeKey CHANNEL_WRAPPER_ATTRIBUTE_KEY = AttributeKey.valueOf( "channelWrapper"); private static final long LOCK_TIMEOUT_MILLIS = 3000; private static final long MIN_CLOSE_TIMEOUT_MILLIS = 100; protected final NettyClientConfig nettyClientConfig; private final Bootstrap bootstrap = new Bootstrap(); private final EventLoopGroup eventLoopGroupWorker; private final Lock lockChannelTables = new ReentrantLock(); private final Map proxyMap = new HashMap<>(); private final ConcurrentHashMap bootstrapMap = new ConcurrentHashMap<>(); private final ConcurrentMap channelTables = new ConcurrentHashMap<>(); private final HashedWheelTimer timer = new HashedWheelTimer(r -> new Thread(r, "ClientHouseKeepingService")); private final AtomicReference> namesrvAddrList = new AtomicReference<>(); private final ConcurrentMap availableNamesrvAddrMap = new ConcurrentHashMap<>(); private final AtomicReference namesrvAddrChoosed = new AtomicReference<>(); private final AtomicInteger namesrvIndex = new AtomicInteger(initValueIndex()); private final Lock namesrvChannelLock = new ReentrantLock(); private final ExecutorService publicExecutor; private final ExecutorService scanExecutor; /** * Invoke the callback methods in this executor when process response. */ private ExecutorService callbackExecutor; private final ChannelEventListener channelEventListener; private EventExecutorGroup defaultEventExecutorGroup; public NettyRemotingClient(final NettyClientConfig nettyClientConfig) { this(nettyClientConfig, null); } public NettyRemotingClient(final NettyClientConfig nettyClientConfig, final ChannelEventListener channelEventListener) { this(nettyClientConfig, channelEventListener, null, null); } public NettyRemotingClient(final NettyClientConfig nettyClientConfig, final ChannelEventListener channelEventListener, final EventLoopGroup eventLoopGroup, final EventExecutorGroup eventExecutorGroup) { super(nettyClientConfig.getClientOnewaySemaphoreValue(), nettyClientConfig.getClientAsyncSemaphoreValue()); this.nettyClientConfig = nettyClientConfig; this.channelEventListener = channelEventListener; this.loadSocksProxyJson(); int publicThreadNums = nettyClientConfig.getClientCallbackExecutorThreads(); if (publicThreadNums <= 0) { publicThreadNums = 4; } this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactoryImpl("NettyClientPublicExecutor_")); this.scanExecutor = ThreadUtils.newThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("NettyClientScan_thread_")); if (eventLoopGroup != null) { this.eventLoopGroupWorker = eventLoopGroup; } else { this.eventLoopGroupWorker = new NioEventLoopGroup(1, new ThreadFactoryImpl("NettyClientSelector_")); } this.defaultEventExecutorGroup = eventExecutorGroup; if (nettyClientConfig.isUseTLS()) { try { sslContext = TlsHelper.buildSslContext(true); LOGGER.info("SSL enabled for client"); } catch (IOException e) { LOGGER.error("Failed to create SslContext", e); } catch (CertificateException e) { LOGGER.error("Failed to create SslContext", e); throw new RuntimeException("Failed to create SslContext", e); } } } private static int initValueIndex() { Random r = new Random(); return r.nextInt(999); } private void loadSocksProxyJson() { Map sockProxyMap = JSON.parseObject( nettyClientConfig.getSocksProxyConfig(), new TypeReference>() { }); if (sockProxyMap != null) { proxyMap.putAll(sockProxyMap); } } @Override public void start() { if (this.defaultEventExecutorGroup == null) { this.defaultEventExecutorGroup = new DefaultEventExecutorGroup( nettyClientConfig.getClientWorkerThreads(), new ThreadFactoryImpl("NettyClientWorkerThread_")); } Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, false) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis()) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (nettyClientConfig.isUseTLS()) { if (null != sslContext) { pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc())); LOGGER.info("Prepend SSL handler"); } else { LOGGER.warn("Connections are insecure as SslContext is null!"); } } ch.pipeline().addLast( nettyClientConfig.isDisableNettyWorkerGroup() ? null : defaultEventExecutorGroup, new NettyEncoder(), new NettyDecoder(), new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()), new NettyConnectManageHandler(), new NettyClientHandler()); } }); if (nettyClientConfig.getClientSocketSndBufSize() > 0) { LOGGER.info("client set SO_SNDBUF to {}", nettyClientConfig.getClientSocketSndBufSize()); handler.option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize()); } if (nettyClientConfig.getClientSocketRcvBufSize() > 0) { LOGGER.info("client set SO_RCVBUF to {}", nettyClientConfig.getClientSocketRcvBufSize()); handler.option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize()); } if (nettyClientConfig.getWriteBufferLowWaterMark() > 0 && nettyClientConfig.getWriteBufferHighWaterMark() > 0) { LOGGER.info("client set netty WRITE_BUFFER_WATER_MARK to {},{}", nettyClientConfig.getWriteBufferLowWaterMark(), nettyClientConfig.getWriteBufferHighWaterMark()); handler.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( nettyClientConfig.getWriteBufferLowWaterMark(), nettyClientConfig.getWriteBufferHighWaterMark())); } if (nettyClientConfig.isClientPooledByteBufAllocatorEnable()) { handler.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); } nettyEventExecutor.start(); 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); } } }; this.timer.newTimeout(timerTaskScanResponseTable, 1000 * 3, TimeUnit.MILLISECONDS); if (nettyClientConfig.isScanAvailableNameSrv()) { int connectTimeoutMillis = this.nettyClientConfig.getConnectTimeoutMillis(); TimerTask timerTaskScanAvailableNameSrv = new TimerTask() { @Override public void run(Timeout timeout) { try { NettyRemotingClient.this.scanAvailableNameSrv(); } catch (Exception e) { LOGGER.error("scanAvailableNameSrv exception", e); } finally { timer.newTimeout(this, connectTimeoutMillis, TimeUnit.MILLISECONDS); } } }; this.timer.newTimeout(timerTaskScanAvailableNameSrv, 0, TimeUnit.MILLISECONDS); } } private Map.Entry getProxy(String addr) { if (StringUtils.isBlank(addr) || !addr.contains(":")) { return null; } String[] hostAndPort = this.getHostAndPort(addr); for (Map.Entry entry : proxyMap.entrySet()) { String cidr = entry.getKey(); if (RemotingHelper.DEFAULT_CIDR_ALL.equals(cidr) || RemotingHelper.ipInCIDR(hostAndPort[0], cidr)) { return entry; } } return null; } protected ChannelFuture doConnect(String addr) { String[] hostAndPort = getHostAndPort(addr); String host = hostAndPort[0]; int port = Integer.parseInt(hostAndPort[1]); return fetchBootstrap(addr).connect(host, port); } private Bootstrap fetchBootstrap(String addr) { Map.Entry proxyEntry = getProxy(addr); if (proxyEntry == null) { return bootstrap; } String cidr = proxyEntry.getKey(); SocksProxyConfig socksProxyConfig = proxyEntry.getValue(); LOGGER.info("Netty fetch bootstrap, addr: {}, cidr: {}, proxy: {}", addr, cidr, socksProxyConfig != null ? socksProxyConfig.getAddr() : ""); Bootstrap bootstrapWithProxy = bootstrapMap.get(cidr); if (bootstrapWithProxy == null) { bootstrapWithProxy = createBootstrap(socksProxyConfig); Bootstrap old = bootstrapMap.putIfAbsent(cidr, bootstrapWithProxy); if (old != null) { bootstrapWithProxy = old; } } return bootstrapWithProxy; } private Bootstrap createBootstrap(final SocksProxyConfig proxy) { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, false) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis()) .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize()) .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize()) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); if (nettyClientConfig.isUseTLS()) { if (null != sslContext) { pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc())); LOGGER.info("Prepend SSL handler"); } else { LOGGER.warn("Connections are insecure as SslContext is null!"); } } // Netty Socks5 Proxy if (proxy != null) { String[] hostAndPort = getHostAndPort(proxy.getAddr()); pipeline.addFirst(new Socks5ProxyHandler( new InetSocketAddress(hostAndPort[0], Integer.parseInt(hostAndPort[1])), proxy.getUsername(), proxy.getPassword())); } pipeline.addLast( nettyClientConfig.isDisableNettyWorkerGroup() ? null : defaultEventExecutorGroup, new NettyEncoder(), new NettyDecoder(), new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()), new NettyConnectManageHandler(), new NettyClientHandler()); } }); // Support Netty Socks5 Proxy if (proxy != null) { bootstrap.resolver(NoopAddressResolverGroup.INSTANCE); } return bootstrap; } // Do not use RemotingHelper.string2SocketAddress(), it will directly resolve the domain protected String[] getHostAndPort(String address) { int split = address.lastIndexOf(":"); return split < 0 ? new String[]{address} : new String[]{address.substring(0, split), address.substring(split + 1)}; } @Override public void shutdown() { try { this.timer.stop(); for (Map.Entry channel : this.channelTables.entrySet()) { channel.getValue().close(); } this.channelTables.clear(); this.eventLoopGroupWorker.shutdownGracefully(); if (this.nettyEventExecutor != null) { this.nettyEventExecutor.shutdown(); } if (this.defaultEventExecutorGroup != null) { this.defaultEventExecutorGroup.shutdownGracefully(); } } catch (Exception e) { LOGGER.error("NettyRemotingClient shutdown exception, ", e); } if (this.publicExecutor != null) { try { this.publicExecutor.shutdown(); } catch (Exception e) { LOGGER.error("NettyRemotingServer shutdown exception, ", e); } } if (this.scanExecutor != null) { try { this.scanExecutor.shutdown(); } catch (Exception e) { LOGGER.error("NettyRemotingServer shutdown exception, ", e); } } } public void closeChannel(final String addr, final Channel channel) { if (null == channel) { return; } final String addrRemote = null == addr ? RemotingHelper.parseChannelRemoteAddr(channel) : addr; try { if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { boolean removeItemFromTable = true; final ChannelWrapper prevCW = this.channelTables.get(addrRemote); LOGGER.info("closeChannel: begin close the channel[addr={}, id={}] Found: {}", addrRemote, channel.id(), prevCW != null); if (null == prevCW) { LOGGER.info("closeChannel: the channel[addr={}, id={}] has been removed from the channel table before", addrRemote, channel.id()); removeItemFromTable = false; } else if (prevCW.isWrapperOf(channel)) { LOGGER.info("closeChannel: the channel[addr={}, id={}] has been closed before, and has been created again, nothing to do.", addrRemote, channel.id()); removeItemFromTable = false; } if (removeItemFromTable) { ChannelWrapper channelWrapper = RemotingHelper.getAttributeValue(CHANNEL_WRAPPER_ATTRIBUTE_KEY, channel); if (channelWrapper != null && channelWrapper.tryClose(channel)) { this.channelTables.remove(addrRemote); } LOGGER.info("closeChannel: the channel[addr={}, id={}] was removed from channel table", addrRemote, channel.id()); } RemotingHelper.closeChannel(channel); } catch (Exception e) { LOGGER.error("closeChannel: close the channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { LOGGER.warn("closeChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } } catch (InterruptedException e) { LOGGER.error("closeChannel exception", e); } } public void closeChannel(final Channel channel) { if (null == channel) { return; } try { if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { boolean removeItemFromTable = true; ChannelWrapper prevCW = null; String addrRemote = null; for (Map.Entry entry : channelTables.entrySet()) { String key = entry.getKey(); ChannelWrapper prev = entry.getValue(); if (prev.isWrapperOf(channel)) { prevCW = prev; addrRemote = key; break; } } if (null == prevCW) { LOGGER.info("eventCloseChannel: the channel[addr={}, id={}] has been removed from the channel table before", RemotingHelper.parseChannelRemoteAddr(channel), channel.id()); removeItemFromTable = false; } if (removeItemFromTable) { ChannelWrapper channelWrapper = RemotingHelper.getAttributeValue(CHANNEL_WRAPPER_ATTRIBUTE_KEY, channel); if (channelWrapper != null && channelWrapper.tryClose(channel)) { this.channelTables.remove(addrRemote); } LOGGER.info("closeChannel: the channel[addr={}, id={}] was removed from channel table", addrRemote, channel.id()); RemotingHelper.closeChannel(channel); } } catch (Exception e) { LOGGER.error("closeChannel: close the channel[id={}] exception", channel.id(), e); } finally { this.lockChannelTables.unlock(); } } else { LOGGER.warn("closeChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } } catch (InterruptedException e) { LOGGER.error("closeChannel exception", e); } } @Override public void updateNameServerAddressList(List addrs) { List old = this.namesrvAddrList.get(); boolean update = false; if (!addrs.isEmpty()) { if (null == old) { update = true; } else if (addrs.size() != old.size()) { update = true; } else { for (String addr : addrs) { if (!old.contains(addr)) { update = true; break; } } } if (update) { Collections.shuffle(addrs); LOGGER.info("name server address updated. NEW : {} , OLD: {}", addrs, old); this.namesrvAddrList.set(addrs); // should close the channel if choosed addr is not exist. String chosenNameServerAddr = this.namesrvAddrChoosed.get(); if (chosenNameServerAddr != null && !addrs.contains(chosenNameServerAddr)) { namesrvAddrChoosed.compareAndSet(chosenNameServerAddr, null); for (String addr : this.channelTables.keySet()) { if (addr.contains(chosenNameServerAddr)) { ChannelWrapper channelWrapper = this.channelTables.get(addr); if (channelWrapper != null) { channelWrapper.close(); } } } } } } } @Override public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException { long beginStartTime = System.currentTimeMillis(); final Channel channel = this.getAndCreateChannel(addr); String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel); if (channel != null && channel.isActive()) { long left = timeoutMillis; try { long costTime = System.currentTimeMillis() - beginStartTime; left -= costTime; if (left <= 0) { throw new RemotingTimeoutException("invokeSync call the addr[" + channelRemoteAddr + "] timeout"); } RemotingCommand response = this.invokeSyncImpl(channel, request, left); updateChannelLastResponseTime(addr); return response; } catch (RemotingSendRequestException e) { LOGGER.warn("invokeSync: send request exception, so close the channel[addr={}, id={}]", channelRemoteAddr, channel.id()); this.closeChannel(addr, channel); throw e; } catch (RemotingTimeoutException e) { // avoid close the success channel if left timeout is small, since it may cost too much time in get the success channel, the left timeout for read is small boolean shouldClose = left > MIN_CLOSE_TIMEOUT_MILLIS || left > timeoutMillis / 4; if (nettyClientConfig.isClientCloseSocketIfTimeout() && shouldClose) { this.closeChannel(addr, channel); LOGGER.warn("invokeSync: close socket because of timeout, {}ms, channel[addr={}, id={}]", timeoutMillis, channelRemoteAddr, channel.id()); } LOGGER.warn("invokeSync: wait response timeout exception, the channel[addr={}, id={}]", channelRemoteAddr, channel.id()); throw e; } } else { this.closeChannel(addr, channel); throw new RemotingConnectException(channelRemoteAddr); } } @Override public void closeChannels(List addrList) { for (String addr : addrList) { ChannelWrapper cw = this.channelTables.get(addr); if (cw == null) { continue; } this.closeChannel(addr, cw.getChannel()); } interruptPullRequests(new HashSet<>(addrList)); } private void interruptPullRequests(Set brokerAddrSet) { for (ResponseFuture responseFuture : responseTable.values()) { RemotingCommand cmd = responseFuture.getRequestCommand(); if (cmd == null) { continue; } String remoteAddr = RemotingHelper.parseChannelRemoteAddr(responseFuture.getChannel()); // interrupt only pull message request if (brokerAddrSet.contains(remoteAddr) && (cmd.getCode() == RequestCode.PULL_MESSAGE || cmd.getCode() == RequestCode.LITE_PULL_MESSAGE)) { LOGGER.info("interrupt {}", cmd); responseFuture.interrupt(); } } } private void updateChannelLastResponseTime(final String addr) { String address = addr; if (address == null) { address = this.namesrvAddrChoosed.get(); } if (address == null) { LOGGER.warn("[updateChannelLastResponseTime] could not find address!!"); return; } ChannelWrapper channelWrapper = this.channelTables.get(address); if (channelWrapper != null && channelWrapper.isOK()) { channelWrapper.updateLastResponseTime(); } } private ChannelFuture getAndCreateChannelAsync(final String addr) throws InterruptedException { if (null == addr) { return getAndCreateNameserverChannelAsync(); } ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isOK()) { return cw.getChannelFuture(); } return this.createChannelAsync(addr); } private Channel getAndCreateChannel(final String addr) throws InterruptedException { ChannelFuture channelFuture = getAndCreateChannelAsync(addr); if (channelFuture == null) { return null; } return channelFuture.awaitUninterruptibly().channel(); } private ChannelFuture getAndCreateNameserverChannelAsync() throws InterruptedException { String addr = this.namesrvAddrChoosed.get(); if (addr != null) { ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isOK()) { return cw.getChannelFuture(); } } final List addrList = this.namesrvAddrList.get(); if (this.namesrvChannelLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { addr = this.namesrvAddrChoosed.get(); if (addr != null) { ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isOK()) { return cw.getChannelFuture(); } } if (addrList != null && !addrList.isEmpty()) { int index = this.namesrvIndex.incrementAndGet(); index = Math.abs(index); index = index % addrList.size(); String newAddr = addrList.get(index); this.namesrvAddrChoosed.set(newAddr); LOGGER.info("new name server is chosen. OLD: {} , NEW: {}. namesrvIndex = {}", addr, newAddr, namesrvIndex); return this.createChannelAsync(newAddr); } } catch (Exception e) { LOGGER.error("getAndCreateNameserverChannel: create name server channel exception", e); } finally { this.namesrvChannelLock.unlock(); } } else { LOGGER.warn("getAndCreateNameserverChannel: try to lock name server, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } return null; } private ChannelFuture createChannelAsync(final String addr) throws InterruptedException { ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isOK()) { return cw.getChannelFuture(); } if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { cw = this.channelTables.get(addr); if (cw != null) { if (cw.isOK() || !cw.getChannelFuture().isDone()) { return cw.getChannelFuture(); } else { this.channelTables.remove(addr); } } return createChannel(addr).getChannelFuture(); } catch (Exception e) { LOGGER.error("createChannel: create channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { LOGGER.warn("createChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } return null; } private ChannelWrapper createChannel(String addr) { ChannelFuture channelFuture = doConnect(addr); LOGGER.info("createChannel: begin to connect remote host[{}] asynchronously", addr); ChannelWrapper cw = new ChannelWrapper(addr, channelFuture); this.channelTables.put(addr, cw); return cw; } @Override public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { long beginStartTime = System.currentTimeMillis(); final ChannelFuture channelFuture = this.getAndCreateChannelAsync(addr); if (channelFuture == null) { invokeCallback.operationFail(new RemotingConnectException(addr)); return; } channelFuture.addListener(future -> { if (future.isSuccess()) { Channel channel = channelFuture.channel(); String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel); if (channel != null && channel.isActive()) { long costTime = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTime) { invokeCallback.operationFail(new RemotingTooMuchRequestException("invokeAsync call the addr[" + channelRemoteAddr + "] timeout")); return; } this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, new InvokeCallbackWrapper(invokeCallback, addr)); } else { this.closeChannel(addr, channel); invokeCallback.operationFail(new RemotingConnectException(addr)); } } else { invokeCallback.operationFail(new RemotingConnectException(addr)); } }); } @Override public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { final ChannelFuture channelFuture = this.getAndCreateChannelAsync(addr); if (channelFuture == null) { throw new RemotingConnectException(addr); } channelFuture.addListener(future -> { if (future.isSuccess()) { Channel channel = channelFuture.channel(); String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel); if (channel != null && channel.isActive()) { doBeforeRpcHooks(channelRemoteAddr, request); this.invokeOnewayImpl(channel, request, timeoutMillis); } else { this.closeChannel(addr, channel); } } }); } @Override public CompletableFuture invoke(String addr, RemotingCommand request, long timeoutMillis) { CompletableFuture future = new CompletableFuture<>(); try { final ChannelFuture channelFuture = this.getAndCreateChannelAsync(addr); if (channelFuture == null) { future.completeExceptionally(new RemotingConnectException(addr)); return future; } channelFuture.addListener(f -> { if (f.isSuccess()) { Channel channel = channelFuture.channel(); if (channel != null && channel.isActive()) { invokeImpl(channel, request, timeoutMillis).whenComplete((v, t) -> { if (t == null) { updateChannelLastResponseTime(addr); } }).thenApply(ResponseFuture::getResponseCommand).whenComplete((v, t) -> { if (t != null) { future.completeExceptionally(t); } else { future.complete(v); } }); } else { this.closeChannel(addr, channel); future.completeExceptionally(new RemotingConnectException(addr)); } } else { future.completeExceptionally(new RemotingConnectException(addr)); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } @Override public CompletableFuture invokeImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis) { Stopwatch stopwatch = Stopwatch.createStarted(); String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel); doBeforeRpcHooks(channelRemoteAddr, request); return super.invokeImpl(channel, request, timeoutMillis).thenCompose(responseFuture -> { RemotingCommand response = responseFuture.getResponseCommand(); if (response.getCode() == ResponseCode.GO_AWAY) { if (nettyClientConfig.isEnableReconnectForGoAway()) { LOGGER.info("Receive go away from channelId={}, channel={}", channel.id(), channel); ChannelWrapper channelWrapper = RemotingHelper.getAttributeValue(CHANNEL_WRAPPER_ATTRIBUTE_KEY, channel); if (channelWrapper != null && channelWrapper.reconnect(channel)) { LOGGER.info("Receive go away from channelId={}, channel={}, recreate the channelId={}", channel.id(), channel, channelWrapper.getChannel().id()); } if (channelWrapper != null && !channelWrapper.isWrapperOf(channel)) { RemotingCommand retryRequest = RemotingCommand.createRequestCommand(request.getCode(), request.readCustomHeader()); retryRequest.setBody(request.getBody()); retryRequest.setExtFields(request.getExtFields()); CompletableFuture future = convertChannelFutureToCompletableFuture(channelWrapper.getChannelFuture()); return future.thenCompose(v -> { long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS); stopwatch.stop(); return super.invokeImpl(channelWrapper.getChannel(), retryRequest, timeoutMillis - duration) .thenCompose(r -> { if (r.getResponseCommand().getCode() == ResponseCode.GO_AWAY) { return FutureUtils.completeExceptionally(new RemotingSendRequestException(channelRemoteAddr, new Throwable("Receive GO_AWAY twice in request from channelId=" + channel.id()))); } return CompletableFuture.completedFuture(r); }); }); } else { LOGGER.warn("invokeImpl receive GO_AWAY, channelWrapper is null or channel is the same in wrapper, channelId={}", channel.id()); } } return FutureUtils.completeExceptionally(new RemotingSendRequestException(channelRemoteAddr, new Throwable("Receive GO_AWAY from channelId=" + channel.id()))); } return CompletableFuture.completedFuture(responseFuture); }).whenComplete((v, t) -> { if (t == null) { doAfterRpcHooks(channelRemoteAddr, request, v.getResponseCommand()); } }); } @Override public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) { ExecutorService executorThis = executor; if (null == executor) { executorThis = this.publicExecutor; } Pair pair = new Pair<>(processor, executorThis); this.processorTable.put(requestCode, pair); } @Override public boolean isChannelWritable(String addr) { ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isOK()) { return cw.isWritable(); } return true; } @Override public boolean isAddressReachable(String addr) { if (addr == null || addr.isEmpty()) { return false; } try { Channel channel = getAndCreateChannel(addr); return channel != null && channel.isActive(); } catch (Exception e) { LOGGER.warn("Get and create channel of {} failed", addr, e); return false; } } @Override public List getNameServerAddressList() { return this.namesrvAddrList.get(); } @Override public List getAvailableNameSrvList() { return new ArrayList<>(this.availableNamesrvAddrMap.keySet()); } @Override public ChannelEventListener getChannelEventListener() { return channelEventListener; } @Override public ExecutorService getCallbackExecutor() { if (nettyClientConfig.isDisableCallbackExecutor()) { return null; } return callbackExecutor != null ? callbackExecutor : publicExecutor; } @Override public void setCallbackExecutor(final ExecutorService callbackExecutor) { this.callbackExecutor = callbackExecutor; } protected void scanChannelTablesOfNameServer() { List nameServerList = this.namesrvAddrList.get(); if (nameServerList == null) { LOGGER.warn("[SCAN] Addresses of name server is empty!"); return; } for (Map.Entry entry : this.channelTables.entrySet()) { String addr = entry.getKey(); ChannelWrapper channelWrapper = entry.getValue(); if (channelWrapper == null) { continue; } if ((System.currentTimeMillis() - channelWrapper.getLastResponseTime()) > this.nettyClientConfig.getChannelNotActiveInterval()) { LOGGER.warn("[SCAN] No response after {} from name server {}, so close it!", channelWrapper.getLastResponseTime(), addr); closeChannel(addr, channelWrapper.getChannel()); } } } private void scanAvailableNameSrv() { List nameServerList = this.namesrvAddrList.get(); if (nameServerList == null) { LOGGER.debug("scanAvailableNameSrv addresses of name server is null!"); return; } for (String address : NettyRemotingClient.this.availableNamesrvAddrMap.keySet()) { if (!nameServerList.contains(address)) { LOGGER.warn("scanAvailableNameSrv remove invalid address {}", address); NettyRemotingClient.this.availableNamesrvAddrMap.remove(address); } } for (final String namesrvAddr : nameServerList) { scanExecutor.execute(new Runnable() { @Override public void run() { try { Channel channel = NettyRemotingClient.this.getAndCreateChannel(namesrvAddr); if (channel != null) { NettyRemotingClient.this.availableNamesrvAddrMap.putIfAbsent(namesrvAddr, true); } else { Boolean value = NettyRemotingClient.this.availableNamesrvAddrMap.remove(namesrvAddr); if (value != null) { LOGGER.warn("scanAvailableNameSrv remove unconnected address {}", namesrvAddr); } } } catch (Exception e) { LOGGER.error("scanAvailableNameSrv get channel of {} failed, ", namesrvAddr, e); } } }); } } class ChannelWrapper { private final ReentrantReadWriteLock lock; private ChannelFuture channelFuture; // only affected by sync or async request, oneway is not included. private ChannelFuture channelToClose; private long lastResponseTime; private final String channelAddress; public ChannelWrapper(String address, ChannelFuture channelFuture) { this.lock = new ReentrantReadWriteLock(); this.channelFuture = channelFuture; this.lastResponseTime = System.currentTimeMillis(); this.channelAddress = address; RemotingHelper.setPropertyToAttr(channelFuture.channel(), CHANNEL_WRAPPER_ATTRIBUTE_KEY, this); } public boolean isOK() { return getChannel() != null && getChannel().isActive(); } public boolean isWritable() { return getChannel().isWritable(); } public boolean isWrapperOf(Channel channel) { return this.channelFuture.channel() != null && this.channelFuture.channel() == channel; } private Channel getChannel() { return getChannelFuture().channel(); } public ChannelFuture getChannelFuture() { lock.readLock().lock(); try { return this.channelFuture; } finally { lock.readLock().unlock(); } } public long getLastResponseTime() { return this.lastResponseTime; } public void updateLastResponseTime() { this.lastResponseTime = System.currentTimeMillis(); } public String getChannelAddress() { return channelAddress; } public boolean reconnect(Channel channel) { if (!isWrapperOf(channel)) { LOGGER.warn("channelWrapper has reconnect, so do nothing, now channelId={}, input channelId={}",getChannel().id(), channel.id()); return false; } if (lock.writeLock().tryLock()) { try { if (isWrapperOf(channel)) { channelToClose = channelFuture; channelFuture = doConnect(channelAddress); RemotingHelper.setPropertyToAttr(channelFuture.channel(), CHANNEL_WRAPPER_ATTRIBUTE_KEY, this); return true; } else { LOGGER.warn("channelWrapper has reconnect, so do nothing, now channelId={}, input channelId={}",getChannel().id(), channel.id()); } } catch (Throwable t) { LOGGER.error("ChannelWrapper {} reconnect error", this, t); } finally { lock.writeLock().unlock(); } } else { LOGGER.warn("channelWrapper reconnect try lock fail, now channelId={}", getChannel().id()); } return false; } public boolean tryClose(Channel channel) { try { lock.readLock().lock(); if (channelFuture != null) { if (channelFuture.channel().equals(channel)) { return true; } } } finally { lock.readLock().unlock(); } return false; } public void close() { try { lock.writeLock().lock(); if (channelFuture != null) { closeChannel(channelFuture.channel()); } if (channelToClose != null) { closeChannel(channelToClose.channel()); } } finally { lock.writeLock().unlock(); } } } class InvokeCallbackWrapper implements InvokeCallback { private final InvokeCallback invokeCallback; private final String addr; public InvokeCallbackWrapper(InvokeCallback invokeCallback, String addr) { this.invokeCallback = invokeCallback; this.addr = addr; } @Override public void operationComplete(ResponseFuture responseFuture) { this.invokeCallback.operationComplete(responseFuture); } @Override public void operationSucceed(RemotingCommand response) { updateChannelLastResponseTime(addr); this.invokeCallback.operationSucceed(response); } @Override public void operationFail(final Throwable throwable) { this.invokeCallback.operationFail(throwable); } } public class NettyClientHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception { processMessageReceived(ctx, msg); } } public class NettyConnectManageHandler extends ChannelDuplexHandler { @Override public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { final String local = localAddress == null ? NetworkUtil.getLocalAddress() : RemotingHelper.parseSocketAddressAddr(localAddress); final String remote = remoteAddress == null ? "UNKNOWN" : RemotingHelper.parseSocketAddressAddr(remoteAddress); LOGGER.info("NETTY CLIENT PIPELINE: CONNECT {} => {}", local, remote); super.connect(ctx, remoteAddress, localAddress, promise); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.CONNECT, remote, ctx.channel())); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.info("NETTY CLIENT PIPELINE: ACTIVE, {}, channelId={}", remoteAddress, ctx.channel().id()); super.channelActive(ctx); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.ACTIVE, remoteAddress, ctx.channel())); } } @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.info("NETTY CLIENT PIPELINE: DISCONNECT {}", remoteAddress); closeChannel(ctx.channel()); super.disconnect(ctx, promise); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel())); } } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.info("NETTY CLIENT PIPELINE: CLOSE channel[addr={}, id={}]", remoteAddress, ctx.channel().id()); closeChannel(ctx.channel()); super.close(ctx, promise); NettyRemotingClient.this.failFast(ctx.channel()); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel())); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.info("NETTY CLIENT PIPELINE: channelInactive, the channel[addr={}, id={}]", remoteAddress, ctx.channel().id()); closeChannel(ctx.channel()); super.channelInactive(ctx); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state().equals(IdleState.ALL_IDLE)) { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.warn("NETTY CLIENT PIPELINE: IDLE exception channel[addr={}, id={}]", remoteAddress, ctx.channel().id()); closeChannel(ctx.channel()); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this .putNettyEvent(new NettyEvent(NettyEventType.IDLE, remoteAddress, ctx.channel())); } } } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); LOGGER.warn("NETTY CLIENT PIPELINE: exceptionCaught channel[addr={}, id={}]", remoteAddress, ctx.channel().id(), cause); closeChannel(ctx.channel()); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.EXCEPTION, remoteAddress, ctx.channel())); } } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ProtocolDetectionResult; import io.netty.handler.codec.ProtocolDetectionState; import io.netty.handler.codec.haproxy.HAProxyMessage; import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; import io.netty.handler.codec.haproxy.HAProxyTLV; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.AttributeKey; import io.netty.util.CharsetUtil; import io.netty.util.HashedWheelTimer; import io.netty.util.Timeout; import io.netty.util.TimerTask; import io.netty.util.concurrent.DefaultEventExecutorGroup; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.HAProxyConstants; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.BinaryUtil; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.ChannelEventListener; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingServer; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import java.io.IOException; import java.net.InetSocketAddress; import java.security.cert.CertificateException; import java.time.Duration; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class NettyRemotingServer extends NettyRemotingAbstract implements RemotingServer { private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final Logger TRAFFIC_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_TRAFFIC_NAME); private final ServerBootstrap serverBootstrap; protected final EventLoopGroup eventLoopGroupSelector; protected final EventLoopGroup eventLoopGroupBoss; protected final NettyServerConfig nettyServerConfig; private final ExecutorService publicExecutor; private final ScheduledExecutorService scheduledExecutorService; private final ChannelEventListener channelEventListener; private final HashedWheelTimer timer = new HashedWheelTimer(r -> new Thread(r, "ServerHouseKeepingService")); private DefaultEventExecutorGroup defaultEventExecutorGroup; /** * NettyRemotingServer may hold multiple SubRemotingServer, each server will be stored in this container with a * ListenPort key. */ private final ConcurrentMap remotingServerTable = new ConcurrentHashMap<>(); public static final String HANDSHAKE_HANDLER_NAME = "handshakeHandler"; public static final String HA_PROXY_DECODER = "HAProxyDecoder"; public static final String HA_PROXY_HANDLER = "HAProxyHandler"; public static final String TLS_MODE_HANDLER = "TlsModeHandler"; public static final String TLS_HANDLER_NAME = "sslHandler"; public static final String FILE_REGION_ENCODER_NAME = "fileRegionEncoder"; // sharable handlers protected final TlsModeHandler tlsModeHandler = new TlsModeHandler(TlsSystemConfig.tlsMode); protected final NettyEncoder encoder = new NettyEncoder(); protected final NettyConnectManageHandler connectionManageHandler = new NettyConnectManageHandler(); protected final NettyServerHandler serverHandler = new NettyServerHandler(); protected final RemotingCodeDistributionHandler distributionHandler = new RemotingCodeDistributionHandler(); public NettyRemotingServer(final NettyServerConfig nettyServerConfig) { this(nettyServerConfig, null); } public NettyRemotingServer(final NettyServerConfig nettyServerConfig, final ChannelEventListener channelEventListener) { super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue()); this.serverBootstrap = new ServerBootstrap(); this.nettyServerConfig = nettyServerConfig; this.channelEventListener = channelEventListener; this.publicExecutor = buildPublicExecutor(nettyServerConfig); this.scheduledExecutorService = buildScheduleExecutor(); this.eventLoopGroupBoss = buildEventLoopGroupBoss(); this.eventLoopGroupSelector = buildEventLoopGroupSelector(); loadSslContext(); } protected EventLoopGroup buildEventLoopGroupSelector() { if (useEpoll()) { return new EpollEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactoryImpl("NettyServerEPOLLSelector_")); } else { return new NioEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactoryImpl("NettyServerNIOSelector_")); } } protected EventLoopGroup buildEventLoopGroupBoss() { if (useEpoll()) { return new EpollEventLoopGroup(1, new ThreadFactoryImpl("NettyEPOLLBoss_")); } else { return new NioEventLoopGroup(1, new ThreadFactoryImpl("NettyNIOBoss_")); } } private ExecutorService buildPublicExecutor(NettyServerConfig nettyServerConfig) { int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads(); if (publicThreadNums <= 0) { publicThreadNums = 4; } return Executors.newFixedThreadPool(publicThreadNums, new ThreadFactoryImpl("NettyServerPublicExecutor_")); } private ScheduledExecutorService buildScheduleExecutor() { return ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("NettyServerScheduler_", true), new ThreadPoolExecutor.DiscardOldestPolicy()); } public void loadSslContext() { TlsMode tlsMode = TlsSystemConfig.tlsMode; log.info("Server is running in TLS {} mode", tlsMode.getName()); if (tlsMode != TlsMode.DISABLED) { try { sslContext = TlsHelper.buildSslContext(false); log.info("SslContext created for server"); } catch (CertificateException | IOException e) { log.error("Failed to create SslContext for server", e); } } } private boolean useEpoll() { return NetworkUtil.isLinuxPlatform() && nettyServerConfig.isUseEpollNativeSelector() && Epoll.isAvailable(); } protected void initServerBootstrap(ServerBootstrap serverBootstrap) { serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector) .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.SO_REUSEADDR, true) .childOption(ChannelOption.SO_KEEPALIVE, false) .childOption(ChannelOption.TCP_NODELAY, true) .localAddress(new InetSocketAddress(this.nettyServerConfig.getBindAddress(), this.nettyServerConfig.getListenPort())) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { configChannel(ch); } }); addCustomConfig(serverBootstrap); } @Override public void start() { this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("NettyServerCodecThread_")); initServerBootstrap(serverBootstrap); try { ChannelFuture sync = serverBootstrap.bind().sync(); InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress(); if (0 == nettyServerConfig.getListenPort()) { this.nettyServerConfig.setListenPort(addr.getPort()); } log.info("RemotingServer started, listening {}:{}", this.nettyServerConfig.getBindAddress(), this.nettyServerConfig.getListenPort()); this.remotingServerTable.put(this.nettyServerConfig.getListenPort(), this); } catch (Exception e) { throw new IllegalStateException(String.format("Failed to bind to %s:%d", nettyServerConfig.getBindAddress(), nettyServerConfig.getListenPort()), e); } if (this.channelEventListener != null) { this.nettyEventExecutor.start(); } 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); } } }; this.timer.newTimeout(timerScanResponseTable, 1000 * 3, TimeUnit.MILLISECONDS); scheduledExecutorService.scheduleWithFixedDelay(() -> { try { NettyRemotingServer.this.printRemotingCodeDistribution(); } catch (Throwable e) { TRAFFIC_LOGGER.error("NettyRemotingServer print remoting code distribution exception", e); } }, 1, 1, TimeUnit.SECONDS); } /** * config channel in ChannelInitializer * * @param ch the SocketChannel needed to init * @return the initialized ChannelPipeline, sub class can use it to extent in the future */ protected ChannelPipeline configChannel(SocketChannel ch) { return ch.pipeline() .addLast(getDefaultEventExecutorGroup(), HANDSHAKE_HANDLER_NAME, new HandshakeHandler()) .addLast(getDefaultEventExecutorGroup(), encoder, new NettyDecoder(), distributionHandler, new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()), connectionManageHandler, serverHandler ); } private void addCustomConfig(ServerBootstrap childHandler) { if (nettyServerConfig.getServerSocketSndBufSize() > 0) { log.info("server set SO_SNDBUF to {}", nettyServerConfig.getServerSocketSndBufSize()); childHandler.childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize()); } if (nettyServerConfig.getServerSocketRcvBufSize() > 0) { log.info("server set SO_RCVBUF to {}", nettyServerConfig.getServerSocketRcvBufSize()); childHandler.childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize()); } if (nettyServerConfig.getWriteBufferLowWaterMark() > 0 && nettyServerConfig.getWriteBufferHighWaterMark() > 0) { log.info("server set netty WRITE_BUFFER_WATER_MARK to {},{}", nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark()); childHandler.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark())); } if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) { childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); } } @Override public void shutdown() { try { if (nettyServerConfig.isEnableShutdownGracefully() && isShuttingDown.compareAndSet(false, true)) { Thread.sleep(Duration.ofSeconds(nettyServerConfig.getShutdownWaitTimeSeconds()).toMillis()); } this.timer.stop(); this.eventLoopGroupBoss.shutdownGracefully(); this.eventLoopGroupSelector.shutdownGracefully(); this.nettyEventExecutor.shutdown(); if (this.defaultEventExecutorGroup != null) { this.defaultEventExecutorGroup.shutdownGracefully(); } } catch (Exception e) { log.error("NettyRemotingServer shutdown exception, ", e); } if (this.publicExecutor != null) { try { this.publicExecutor.shutdown(); } catch (Exception e) { log.error("NettyRemotingServer shutdown exception, ", e); } } } @Override public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) { ExecutorService executorThis = executor; if (null == executor) { executorThis = this.publicExecutor; } Pair pair = new Pair<>(processor, executorThis); this.processorTable.put(requestCode, pair); } @Override public void registerDefaultProcessor(NettyRequestProcessor processor, ExecutorService executor) { this.defaultRequestProcessorPair = new Pair<>(processor, executor); } @Override public int localListenPort() { return this.nettyServerConfig.getListenPort(); } @Override public Pair getProcessorPair(int requestCode) { return processorTable.get(requestCode); } @Override public Pair getDefaultProcessorPair() { return defaultRequestProcessorPair; } @Override public RemotingServer newRemotingServer(final int port) { SubRemotingServer remotingServer = new SubRemotingServer(port, this.nettyServerConfig.getServerOnewaySemaphoreValue(), this.nettyServerConfig.getServerAsyncSemaphoreValue()); NettyRemotingAbstract existingServer = this.remotingServerTable.putIfAbsent(port, remotingServer); if (existingServer != null) { throw new RuntimeException("The port " + port + " already in use by another RemotingServer"); } return remotingServer; } @Override public void removeRemotingServer(final int port) { this.remotingServerTable.remove(port); } @Override public RemotingCommand invokeSync(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException { return this.invokeSyncImpl(channel, request, timeoutMillis); } @Override public void invokeAsync(Channel channel, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { this.invokeAsyncImpl(channel, request, timeoutMillis, invokeCallback); } @Override public void invokeOneway(Channel channel, RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { this.invokeOnewayImpl(channel, request, timeoutMillis); } @Override public ChannelEventListener getChannelEventListener() { return channelEventListener; } @Override public ExecutorService getCallbackExecutor() { return this.publicExecutor; } private void printRemotingCodeDistribution() { if (distributionHandler != null) { String inBoundSnapshotString = distributionHandler.getInBoundSnapshotString(); if (inBoundSnapshotString != null) { TRAFFIC_LOGGER.info("Port: {}, RequestCode Distribution: {}", nettyServerConfig.getListenPort(), inBoundSnapshotString); } String outBoundSnapshotString = distributionHandler.getOutBoundSnapshotString(); if (outBoundSnapshotString != null) { TRAFFIC_LOGGER.info("Port: {}, ResponseCode Distribution: {}", nettyServerConfig.getListenPort(), outBoundSnapshotString); } } } public DefaultEventExecutorGroup getDefaultEventExecutorGroup() { return nettyServerConfig.isServerNettyWorkerGroupEnable() ? defaultEventExecutorGroup : null; } public NettyEncoder getEncoder() { return encoder; } public NettyConnectManageHandler getConnectionManageHandler() { return connectionManageHandler; } public NettyServerHandler getServerHandler() { return serverHandler; } public RemotingCodeDistributionHandler getDistributionHandler() { return distributionHandler; } public class HandshakeHandler extends ByteToMessageDecoder { public HandshakeHandler() { } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { try { ProtocolDetectionResult detectionResult = HAProxyMessageDecoder.detectProtocol(byteBuf); if (detectionResult.state() == ProtocolDetectionState.NEEDS_MORE_DATA) { return; } if (detectionResult.state() == ProtocolDetectionState.DETECTED) { ctx.pipeline().addAfter(getDefaultEventExecutorGroup(), ctx.name(), HA_PROXY_DECODER, new HAProxyMessageDecoder()) .addAfter(getDefaultEventExecutorGroup(), HA_PROXY_DECODER, HA_PROXY_HANDLER, new HAProxyMessageHandler()) .addAfter(getDefaultEventExecutorGroup(), HA_PROXY_HANDLER, TLS_MODE_HANDLER, tlsModeHandler); } else { ctx.pipeline().addAfter(getDefaultEventExecutorGroup(), ctx.name(), TLS_MODE_HANDLER, tlsModeHandler); } try { // Remove this handler ctx.pipeline().remove(this); } catch (NoSuchElementException e) { log.error("Error while removing HandshakeHandler", e); } } catch (Exception e) { log.error("process proxy protocol negotiator failed.", e); throw e; } } } @ChannelHandler.Sharable public class TlsModeHandler extends SimpleChannelInboundHandler { private final TlsMode tlsMode; private static final byte HANDSHAKE_MAGIC_CODE = 0x16; TlsModeHandler(TlsMode tlsMode) { this.tlsMode = tlsMode; } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { // Peek the current read index byte to determine if the content is starting with TLS handshake byte b = msg.getByte(msg.readerIndex()); if (b == HANDSHAKE_MAGIC_CODE) { switch (tlsMode) { case DISABLED: ctx.close(); log.warn("Clients intend to establish an SSL connection while this server is running in SSL disabled mode"); throw new UnsupportedOperationException("The NettyRemotingServer in SSL disabled mode doesn't support ssl client"); case PERMISSIVE: case ENFORCING: if (null != sslContext) { ctx.pipeline() .addAfter(getDefaultEventExecutorGroup(), TLS_MODE_HANDLER, TLS_HANDLER_NAME, sslContext.newHandler(ctx.channel().alloc())) .addAfter(getDefaultEventExecutorGroup(), TLS_HANDLER_NAME, FILE_REGION_ENCODER_NAME, new FileRegionEncoder()); log.info("Handlers prepended to channel pipeline to establish SSL connection"); } else { ctx.close(); log.error("Trying to establish an SSL connection but SslContext is null"); } break; default: log.warn("Unknown TLS mode"); break; } } else if (tlsMode == TlsMode.ENFORCING) { ctx.close(); log.warn("Clients intend to establish an insecure connection while this server is running in SSL enforcing mode"); throw new UnsupportedOperationException("The NettyRemotingServer in SSL enforcing mode doesn't support plain client"); } try { // Remove this handler ctx.pipeline().remove(this); } catch (NoSuchElementException e) { log.error("Error while removing TlsModeHandler", e); } // Hand over this message to the next . ctx.fireChannelRead(msg.retain()); } } @ChannelHandler.Sharable public class NettyServerHandler extends SimpleChannelInboundHandler { @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()); } @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); } } @ChannelHandler.Sharable public class NettyConnectManageHandler extends ChannelDuplexHandler { @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); log.info("NETTY SERVER PIPELINE: channelRegistered {}", remoteAddress); super.channelRegistered(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); log.info("NETTY SERVER PIPELINE: channelUnregistered, the channel[{}]", remoteAddress); super.channelUnregistered(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); log.info("NETTY SERVER PIPELINE: channelActive, the channel[{}]", remoteAddress); super.channelActive(ctx); if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CONNECT, remoteAddress, ctx.channel())); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); log.info("NETTY SERVER PIPELINE: channelInactive, the channel[{}]", remoteAddress); super.channelInactive(ctx); if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel())); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state().equals(IdleState.ALL_IDLE)) { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); log.warn("NETTY SERVER PIPELINE: IDLE exception [{}]", remoteAddress); RemotingHelper.closeChannel(ctx.channel()); if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this .putNettyEvent(new NettyEvent(NettyEventType.IDLE, remoteAddress, ctx.channel())); } } } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); log.warn("NETTY SERVER PIPELINE: exceptionCaught {}", remoteAddress); log.warn("NETTY SERVER PIPELINE: exceptionCaught exception.", cause); if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.EXCEPTION, remoteAddress, ctx.channel())); } RemotingHelper.closeChannel(ctx.channel()); } } /** * The NettyRemotingServer supports bind multiple ports, each port bound by a SubRemotingServer. The * SubRemotingServer will delegate all the functions to NettyRemotingServer, so the sub server can share all the * resources from its parent server. */ class SubRemotingServer extends NettyRemotingAbstract implements RemotingServer { private volatile int listenPort; private volatile Channel serverChannel; SubRemotingServer(final int port, final int permitsOnway, final int permitsAsync) { super(permitsOnway, permitsAsync); listenPort = port; } @Override public void registerProcessor(final int requestCode, final NettyRequestProcessor processor, final ExecutorService executor) { ExecutorService executorThis = executor; if (null == executor) { executorThis = NettyRemotingServer.this.publicExecutor; } Pair pair = new Pair<>(processor, executorThis); this.processorTable.put(requestCode, pair); } @Override public void registerDefaultProcessor(final NettyRequestProcessor processor, final ExecutorService executor) { this.defaultRequestProcessorPair = new Pair<>(processor, executor); } @Override public int localListenPort() { return listenPort; } @Override public Pair getProcessorPair(final int requestCode) { return this.processorTable.get(requestCode); } @Override public Pair getDefaultProcessorPair() { return this.defaultRequestProcessorPair; } @Override public RemotingServer newRemotingServer(final int port) { throw new UnsupportedOperationException("The SubRemotingServer of NettyRemotingServer " + "doesn't support new nested RemotingServer"); } @Override public void removeRemotingServer(final int port) { throw new UnsupportedOperationException("The SubRemotingServer of NettyRemotingServer " + "doesn't support remove nested RemotingServer"); } @Override public RemotingCommand invokeSync(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException { return this.invokeSyncImpl(channel, request, timeoutMillis); } @Override public void invokeAsync(final Channel channel, final RemotingCommand request, final long timeoutMillis, final InvokeCallback invokeCallback) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { this.invokeAsyncImpl(channel, request, timeoutMillis, invokeCallback); } @Override public void invokeOneway(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { this.invokeOnewayImpl(channel, request, timeoutMillis); } @Override public void start() { try { if (listenPort < 0) { listenPort = 0; } this.serverChannel = NettyRemotingServer.this.serverBootstrap.bind(listenPort).sync().channel(); if (0 == listenPort) { InetSocketAddress addr = (InetSocketAddress) this.serverChannel.localAddress(); this.listenPort = addr.getPort(); } } catch (InterruptedException e) { throw new RuntimeException("this.subRemotingServer.serverBootstrap.bind().sync() InterruptedException", e); } } @Override public void shutdown() { isShuttingDown.set(true); if (this.serverChannel != null) { try { this.serverChannel.close().await(5, TimeUnit.SECONDS); } catch (InterruptedException ignored) { } } } @Override public ChannelEventListener getChannelEventListener() { return NettyRemotingServer.this.getChannelEventListener(); } @Override public ExecutorService getCallbackExecutor() { return NettyRemotingServer.this.getCallbackExecutor(); } } public class HAProxyMessageHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HAProxyMessage) { handleWithMessage((HAProxyMessage) msg, ctx.channel()); } else { super.channelRead(ctx, msg); } ctx.pipeline().remove(this); } /** * The definition of key refers to the implementation of nginx * ngx_http_core_module * @param msg * @param channel */ private void handleWithMessage(HAProxyMessage msg, Channel channel) { try { if (StringUtils.isNotBlank(msg.sourceAddress())) { RemotingHelper.setPropertyToAttr(channel, AttributeKeys.PROXY_PROTOCOL_ADDR, msg.sourceAddress()); } if (msg.sourcePort() > 0) { RemotingHelper.setPropertyToAttr(channel, AttributeKeys.PROXY_PROTOCOL_PORT, String.valueOf(msg.sourcePort())); } if (StringUtils.isNotBlank(msg.destinationAddress())) { RemotingHelper.setPropertyToAttr(channel, AttributeKeys.PROXY_PROTOCOL_SERVER_ADDR, msg.destinationAddress()); } if (msg.destinationPort() > 0) { RemotingHelper.setPropertyToAttr(channel, AttributeKeys.PROXY_PROTOCOL_SERVER_PORT, String.valueOf(msg.destinationPort())); } if (CollectionUtils.isNotEmpty(msg.tlvs())) { msg.tlvs().forEach(tlv -> { handleHAProxyTLV(tlv, channel); }); } } finally { msg.release(); } } } protected void handleHAProxyTLV(HAProxyTLV tlv, Channel channel) { byte[] valueBytes = ByteBufUtil.getBytes(tlv.content()); if (!BinaryUtil.isAscii(valueBytes)) { return; } AttributeKey key = AttributeKeys.valueOf( HAProxyConstants.PROXY_PROTOCOL_TLV_PREFIX + String.format("%02x", tlv.typeByteValue())); RemotingHelper.setPropertyToAttr(channel, key, new String(valueBytes, CharsetUtil.UTF_8)); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRequestProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; /** * Common remoting command processor */ public interface NettyRequestProcessor { RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws Exception; boolean rejectRequest(); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; public class NettyServerConfig implements Cloneable { /** * Bind address may be hostname, IPv4 or IPv6. * By default, it's wildcard address, listening all network interfaces. */ private String bindAddress = "0.0.0.0"; private int listenPort = 0; private int serverWorkerThreads = 8; private int serverCallbackExecutorThreads = 0; private int serverSelectorThreads = 3; private int serverOnewaySemaphoreValue = 256; private int serverAsyncSemaphoreValue = 64; private int serverChannelMaxIdleTimeSeconds = 120; private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize; private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize; private int writeBufferHighWaterMark = NettySystemConfig.writeBufferHighWaterMark; private int writeBufferLowWaterMark = NettySystemConfig.writeBufferLowWaterMark; private int serverSocketBacklog = NettySystemConfig.socketBacklog; private boolean serverNettyWorkerGroupEnable = true; private boolean serverPooledByteBufAllocatorEnable = true; private boolean enableShutdownGracefully = false; private int shutdownWaitTimeSeconds = 30; /** * make install * * * ../glibc-2.10.1/configure \ --prefix=/usr \ --with-headers=/usr/include \ * --host=x86_64-linux-gnu \ --build=x86_64-pc-linux-gnu \ --without-gd */ private boolean useEpollNativeSelector = false; public String getBindAddress() { return bindAddress; } public void setBindAddress(String bindAddress) { this.bindAddress = bindAddress; } public int getListenPort() { return listenPort; } public void setListenPort(int listenPort) { this.listenPort = listenPort; } public int getServerWorkerThreads() { return serverWorkerThreads; } public void setServerWorkerThreads(int serverWorkerThreads) { this.serverWorkerThreads = serverWorkerThreads; } public int getServerSelectorThreads() { return serverSelectorThreads; } public void setServerSelectorThreads(int serverSelectorThreads) { this.serverSelectorThreads = serverSelectorThreads; } public int getServerOnewaySemaphoreValue() { return serverOnewaySemaphoreValue; } public void setServerOnewaySemaphoreValue(int serverOnewaySemaphoreValue) { this.serverOnewaySemaphoreValue = serverOnewaySemaphoreValue; } public int getServerCallbackExecutorThreads() { return serverCallbackExecutorThreads; } public void setServerCallbackExecutorThreads(int serverCallbackExecutorThreads) { this.serverCallbackExecutorThreads = serverCallbackExecutorThreads; } public int getServerAsyncSemaphoreValue() { return serverAsyncSemaphoreValue; } public void setServerAsyncSemaphoreValue(int serverAsyncSemaphoreValue) { this.serverAsyncSemaphoreValue = serverAsyncSemaphoreValue; } public int getServerChannelMaxIdleTimeSeconds() { return serverChannelMaxIdleTimeSeconds; } public void setServerChannelMaxIdleTimeSeconds(int serverChannelMaxIdleTimeSeconds) { this.serverChannelMaxIdleTimeSeconds = serverChannelMaxIdleTimeSeconds; } public int getServerSocketSndBufSize() { return serverSocketSndBufSize; } public void setServerSocketSndBufSize(int serverSocketSndBufSize) { this.serverSocketSndBufSize = serverSocketSndBufSize; } public int getServerSocketRcvBufSize() { return serverSocketRcvBufSize; } public void setServerSocketRcvBufSize(int serverSocketRcvBufSize) { this.serverSocketRcvBufSize = serverSocketRcvBufSize; } public int getServerSocketBacklog() { return serverSocketBacklog; } public void setServerSocketBacklog(int serverSocketBacklog) { this.serverSocketBacklog = serverSocketBacklog; } public boolean isServerPooledByteBufAllocatorEnable() { return serverPooledByteBufAllocatorEnable; } public void setServerPooledByteBufAllocatorEnable(boolean serverPooledByteBufAllocatorEnable) { this.serverPooledByteBufAllocatorEnable = serverPooledByteBufAllocatorEnable; } public boolean isUseEpollNativeSelector() { return useEpollNativeSelector; } public void setUseEpollNativeSelector(boolean useEpollNativeSelector) { this.useEpollNativeSelector = useEpollNativeSelector; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } public int getWriteBufferLowWaterMark() { return writeBufferLowWaterMark; } public void setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { this.writeBufferLowWaterMark = writeBufferLowWaterMark; } public int getWriteBufferHighWaterMark() { return writeBufferHighWaterMark; } public void setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { this.writeBufferHighWaterMark = writeBufferHighWaterMark; } public boolean isServerNettyWorkerGroupEnable() { return serverNettyWorkerGroupEnable; } public void setServerNettyWorkerGroupEnable(boolean serverNettyWorkerGroupEnable) { this.serverNettyWorkerGroupEnable = serverNettyWorkerGroupEnable; } public boolean isEnableShutdownGracefully() { return enableShutdownGracefully; } public void setEnableShutdownGracefully(boolean enableShutdownGracefully) { this.enableShutdownGracefully = enableShutdownGracefully; } public int getShutdownWaitTimeSeconds() { return shutdownWaitTimeSeconds; } public void setShutdownWaitTimeSeconds(int shutdownWaitTimeSeconds) { this.shutdownWaitTimeSeconds = shutdownWaitTimeSeconds; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettySystemConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; public class NettySystemConfig { public static final String COM_ROCKETMQ_REMOTING_NETTY_POOLED_BYTE_BUF_ALLOCATOR_ENABLE = "com.rocketmq.remoting.nettyPooledByteBufAllocatorEnable"; public static final String COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE = "com.rocketmq.remoting.socket.sndbuf.size"; public static final String COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE = "com.rocketmq.remoting.socket.rcvbuf.size"; public static final String COM_ROCKETMQ_REMOTING_SOCKET_BACKLOG = "com.rocketmq.remoting.socket.backlog"; public static final String COM_ROCKETMQ_REMOTING_CLIENT_ASYNC_SEMAPHORE_VALUE = "com.rocketmq.remoting.clientAsyncSemaphoreValue"; public static final String COM_ROCKETMQ_REMOTING_CLIENT_ONEWAY_SEMAPHORE_VALUE = "com.rocketmq.remoting.clientOnewaySemaphoreValue"; public static final String COM_ROCKETMQ_REMOTING_CLIENT_WORKER_SIZE = "com.rocketmq.remoting.client.worker.size"; public static final String COM_ROCKETMQ_REMOTING_CLIENT_CONNECT_TIMEOUT = "com.rocketmq.remoting.client.connect.timeout"; public static final String COM_ROCKETMQ_REMOTING_CLIENT_CHANNEL_MAX_IDLE_SECONDS = "com.rocketmq.remoting.client.channel.maxIdleTimeSeconds"; public static final String COM_ROCKETMQ_REMOTING_CLIENT_CLOSE_SOCKET_IF_TIMEOUT = "com.rocketmq.remoting.client.closeSocketIfTimeout"; public static final String COM_ROCKETMQ_REMOTING_WRITE_BUFFER_HIGH_WATER_MARK_VALUE = "com.rocketmq.remoting.write.buffer.high.water.mark"; public static final String COM_ROCKETMQ_REMOTING_WRITE_BUFFER_LOW_WATER_MARK = "com.rocketmq.remoting.write.buffer.low.water.mark"; public static final boolean NETTY_POOLED_BYTE_BUF_ALLOCATOR_ENABLE = // Boolean.parseBoolean(System.getProperty(COM_ROCKETMQ_REMOTING_NETTY_POOLED_BYTE_BUF_ALLOCATOR_ENABLE, "false")); public static final int CLIENT_ASYNC_SEMAPHORE_VALUE = // Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_CLIENT_ASYNC_SEMAPHORE_VALUE, "65535")); public static final int CLIENT_ONEWAY_SEMAPHORE_VALUE = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_CLIENT_ONEWAY_SEMAPHORE_VALUE, "65535")); public static int socketSndbufSize = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE, "0")); public static int socketRcvbufSize = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE, "0")); public static int socketBacklog = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_SOCKET_BACKLOG, "1024")); public static int clientWorkerSize = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_CLIENT_WORKER_SIZE, "4")); public static int connectTimeoutMillis = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_CLIENT_CONNECT_TIMEOUT, "3000")); public static int clientChannelMaxIdleTimeSeconds = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_CLIENT_CHANNEL_MAX_IDLE_SECONDS, "120")); public static boolean clientCloseSocketIfTimeout = Boolean.parseBoolean(System.getProperty(COM_ROCKETMQ_REMOTING_CLIENT_CLOSE_SOCKET_IF_TIMEOUT, "true")); public static int writeBufferHighWaterMark = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_WRITE_BUFFER_HIGH_WATER_MARK_VALUE, "0")); public static int writeBufferLowWaterMark = Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_WRITE_BUFFER_LOW_WATER_MARK, "0")); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.LongAdder; import org.apache.rocketmq.remoting.protocol.RemotingCommand; @ChannelHandler.Sharable public class RemotingCodeDistributionHandler extends ChannelDuplexHandler { private final ConcurrentMap inboundDistribution; private final ConcurrentMap outboundDistribution; public RemotingCodeDistributionHandler() { inboundDistribution = new ConcurrentHashMap<>(); outboundDistribution = new ConcurrentHashMap<>(); } private void countInbound(int requestCode) { LongAdder item = inboundDistribution.computeIfAbsent(requestCode, k -> new LongAdder()); item.increment(); } private void countOutbound(int responseCode) { LongAdder item = outboundDistribution.computeIfAbsent(responseCode, k -> new LongAdder()); item.increment(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof RemotingCommand) { RemotingCommand cmd = (RemotingCommand) msg; countInbound(cmd.getCode()); } ctx.fireChannelRead(msg); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof RemotingCommand) { RemotingCommand cmd = (RemotingCommand) msg; countOutbound(cmd.getCode()); } ctx.write(msg, promise); } private Map getDistributionSnapshot(Map countMap) { Map map = new HashMap<>(countMap.size()); for (Map.Entry entry : countMap.entrySet()) { map.put(entry.getKey(), entry.getValue().sumThenReset()); } return map; } private String snapshotToString(Map distribution) { if (null != distribution && !distribution.isEmpty()) { StringBuilder sb = new StringBuilder("{"); boolean first = true; for (Map.Entry entry : distribution.entrySet()) { if (0L == entry.getValue()) { continue; } sb.append(first ? "" : ", ").append(entry.getKey()).append(":").append(entry.getValue()); first = false; } if (first) { return null; } sb.append("}"); return sb.toString(); } return null; } public String getInBoundSnapshotString() { return this.snapshotToString(this.getDistributionSnapshot(this.inboundDistribution)); } public String getOutBoundSnapshotString() { return this.snapshotToString(this.getDistributionSnapshot(this.outboundDistribution)); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/RemotingResponseCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RemotingResponseCallback { void callback(RemotingCommand response); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/RequestTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.Channel; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class RequestTask implements Runnable { private final Runnable runnable; private final long createTimestamp = System.currentTimeMillis(); private final Channel channel; private final RemotingCommand request; private volatile boolean stopRun = false; public RequestTask(final Runnable runnable, final Channel channel, final RemotingCommand request) { this.runnable = runnable; this.channel = channel; this.request = request; } @Override public int hashCode() { int result = runnable != null ? runnable.hashCode() : 0; result = 31 * result + (int) (getCreateTimestamp() ^ (getCreateTimestamp() >>> 32)); result = 31 * result + (channel != null ? channel.hashCode() : 0); result = 31 * result + (request != null ? request.hashCode() : 0); result = 31 * result + (isStopRun() ? 1 : 0); return result; } @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof RequestTask)) return false; final RequestTask that = (RequestTask) o; if (getCreateTimestamp() != that.getCreateTimestamp()) return false; if (isStopRun() != that.isStopRun()) return false; if (channel != null ? !channel.equals(that.channel) : that.channel != null) return false; return request != null ? request.getOpaque() == that.request.getOpaque() : that.request == null; } public long getCreateTimestamp() { return createTimestamp; } public boolean isStopRun() { return stopRun; } public void setStopRun(final boolean stopRun) { this.stopRun = stopRun; } @Override public void run() { if (!this.stopRun) this.runnable.run(); } public void returnResponse(int code, String remark) { final RemotingCommand response = RemotingCommand.createResponseCommand(code, remark); response.setOpaque(request.getOpaque()); this.channel.writeAndFlush(response); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.Channel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class ResponseFuture { private final Channel channel; private final int opaque; private final RemotingCommand request; private final long timeoutMillis; private final InvokeCallback invokeCallback; private final long beginTimestamp = System.currentTimeMillis(); private final CountDownLatch countDownLatch = new CountDownLatch(1); private final SemaphoreReleaseOnlyOnce once; private final AtomicBoolean executeCallbackOnlyOnce = new AtomicBoolean(false); private volatile RemotingCommand responseCommand; private volatile boolean sendRequestOK = true; private volatile Throwable cause; private volatile boolean interrupted = false; public ResponseFuture(Channel channel, int opaque, long timeoutMillis, InvokeCallback invokeCallback, SemaphoreReleaseOnlyOnce once) { this(channel, opaque, null, timeoutMillis, invokeCallback, once); } public ResponseFuture(Channel channel, int opaque, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback, SemaphoreReleaseOnlyOnce once) { this.channel = channel; this.opaque = opaque; this.request = request; this.timeoutMillis = timeoutMillis; this.invokeCallback = invokeCallback; this.once = once; } public void executeInvokeCallback() { if (invokeCallback != null) { if (this.executeCallbackOnlyOnce.compareAndSet(false, true)) { RemotingCommand response = getResponseCommand(); if (response != null) { invokeCallback.operationSucceed(response); } else { if (!isSendRequestOK()) { invokeCallback.operationFail(new RemotingSendRequestException(channel.remoteAddress().toString(), getCause())); } else if (isTimeout()) { invokeCallback.operationFail(new RemotingTimeoutException(channel.remoteAddress().toString(), getTimeoutMillis(), getCause())); } else { invokeCallback.operationFail(new RemotingException(getRequestCommand().toString(), getCause())); } } invokeCallback.operationComplete(this); } } } public void interrupt() { interrupted = true; executeInvokeCallback(); } public void release() { if (this.once != null) { this.once.release(); } } public boolean isTimeout() { long diff = System.currentTimeMillis() - this.beginTimestamp; return diff > this.timeoutMillis; } public RemotingCommand waitResponse(final long timeoutMillis) throws InterruptedException { this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); return this.responseCommand; } public void putResponse(final RemotingCommand responseCommand) { this.responseCommand = responseCommand; this.countDownLatch.countDown(); } public long getBeginTimestamp() { return beginTimestamp; } public boolean isSendRequestOK() { return sendRequestOK; } public void setSendRequestOK(boolean sendRequestOK) { this.sendRequestOK = sendRequestOK; } public long getTimeoutMillis() { return timeoutMillis; } public InvokeCallback getInvokeCallback() { return invokeCallback; } public Throwable getCause() { return cause; } public void setCause(Throwable cause) { this.cause = cause; } public RemotingCommand getResponseCommand() { return responseCommand; } public void setResponseCommand(RemotingCommand responseCommand) { this.responseCommand = responseCommand; } public int getOpaque() { return opaque; } public RemotingCommand getRequestCommand() { return request; } public Channel getChannel() { return channel; } public boolean isInterrupted() { return interrupted; } @Override public String toString() { return "ResponseFuture [responseCommand=" + responseCommand + ", sendRequestOK=" + sendRequestOK + ", cause=" + cause + ", opaque=" + opaque + ", timeoutMillis=" + timeoutMillis + ", invokeCallback=" + invokeCallback + ", beginTimestamp=" + beginTimestamp + ", countDownLatch=" + countDownLatch + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.Properties; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CIPHERS; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_AUTHSERVER; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_CERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPASSWORD; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_TRUSTCERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_PROTOCOLS; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_AUTHCLIENT; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_CERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPASSWORD; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_NEED_CLIENT_AUTH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_TRUSTCERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_TEST_MODE_ENABLE; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsCiphers; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientAuthServer; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPassword; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientTrustCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsProtocols; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerAuthClient; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPassword; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerNeedClientAuth; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerTrustCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsTestModeEnable; public class TlsHelper { public interface DecryptionStrategy { /** * Decrypt the target encrpted private key file. * * @param privateKeyEncryptPath A pathname string * @param forClient tells whether it's a client-side key file * @return An input stream for a decrypted key file * @throws IOException if an I/O error has occurred */ InputStream decryptPrivateKey(String privateKeyEncryptPath, boolean forClient) throws IOException; } private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static DecryptionStrategy decryptionStrategy = new DecryptionStrategy() { @Override public InputStream decryptPrivateKey(final String privateKeyEncryptPath, final boolean forClient) throws IOException { return new FileInputStream(privateKeyEncryptPath); } }; public static void registerDecryptionStrategy(final DecryptionStrategy decryptionStrategy) { TlsHelper.decryptionStrategy = decryptionStrategy; } public static SslContext buildSslContext(boolean forClient) throws IOException, CertificateException { File configFile = new File(TlsSystemConfig.tlsConfigFile); extractTlsConfigFromFile(configFile); logTheFinalUsedTlsConfig(); SslProvider provider; if (OpenSsl.isAvailable()) { provider = SslProvider.OPENSSL; LOGGER.info("Using OpenSSL provider"); } else { provider = SslProvider.JDK; LOGGER.info("Using JDK SSL provider"); } SslContextBuilder sslContextBuilder = null; if (forClient) { if (tlsTestModeEnable) { sslContextBuilder = SslContextBuilder .forClient() .sslProvider(SslProvider.JDK) .trustManager(InsecureTrustManagerFactory.INSTANCE); } else { sslContextBuilder = SslContextBuilder.forClient().sslProvider(SslProvider.JDK); if (!tlsClientAuthServer) { sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); } else { if (!isNullOrEmpty(tlsClientTrustCertPath)) { sslContextBuilder.trustManager(new File(tlsClientTrustCertPath)); } } sslContextBuilder = sslContextBuilder.keyManager( !isNullOrEmpty(tlsClientCertPath) ? new FileInputStream(tlsClientCertPath) : null, !isNullOrEmpty(tlsClientKeyPath) ? decryptionStrategy.decryptPrivateKey(tlsClientKeyPath, true) : null, !isNullOrEmpty(tlsClientKeyPassword) ? tlsClientKeyPassword : null); } } else { if (tlsTestModeEnable) { SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); sslContextBuilder = SslContextBuilder .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) .sslProvider(provider) .clientAuth(ClientAuth.OPTIONAL); } else { sslContextBuilder = SslContextBuilder.forServer( !isNullOrEmpty(tlsServerCertPath) ? new FileInputStream(tlsServerCertPath) : null, !isNullOrEmpty(tlsServerKeyPath) ? decryptionStrategy.decryptPrivateKey(tlsServerKeyPath, false) : null, !isNullOrEmpty(tlsServerKeyPassword) ? tlsServerKeyPassword : null) .sslProvider(provider); if (!tlsServerAuthClient) { sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); } else { if (!isNullOrEmpty(tlsServerTrustCertPath)) { sslContextBuilder.trustManager(new File(tlsServerTrustCertPath)); } } sslContextBuilder.clientAuth(parseClientAuthMode(tlsServerNeedClientAuth)); } } moreTlsConfig(sslContextBuilder); return sslContextBuilder.build(); } protected static void moreTlsConfig(SslContextBuilder sslContextBuilder) { if (tlsCiphers != null) { sslContextBuilder.ciphers(Arrays.asList(tlsCiphers.split(","))); } if (tlsProtocols != null) { sslContextBuilder.protocols(tlsProtocols.split(",")); } } private static void extractTlsConfigFromFile(final File configFile) { if (!(configFile.exists() && configFile.isFile() && configFile.canRead())) { LOGGER.info("Tls config file doesn't exist, skip it"); return; } Properties properties; properties = new Properties(); InputStream inputStream = null; try { inputStream = new FileInputStream(configFile); properties.load(inputStream); } catch (IOException ignore) { } finally { if (null != inputStream) { try { inputStream.close(); } catch (IOException ignore) { } } } tlsTestModeEnable = Boolean.parseBoolean(properties.getProperty(TLS_TEST_MODE_ENABLE, String.valueOf(tlsTestModeEnable))); tlsServerNeedClientAuth = properties.getProperty(TLS_SERVER_NEED_CLIENT_AUTH, tlsServerNeedClientAuth); tlsServerKeyPath = properties.getProperty(TLS_SERVER_KEYPATH, tlsServerKeyPath); tlsServerKeyPassword = properties.getProperty(TLS_SERVER_KEYPASSWORD, tlsServerKeyPassword); tlsServerCertPath = properties.getProperty(TLS_SERVER_CERTPATH, tlsServerCertPath); tlsServerAuthClient = Boolean.parseBoolean(properties.getProperty(TLS_SERVER_AUTHCLIENT, String.valueOf(tlsServerAuthClient))); tlsServerTrustCertPath = properties.getProperty(TLS_SERVER_TRUSTCERTPATH, tlsServerTrustCertPath); tlsClientKeyPath = properties.getProperty(TLS_CLIENT_KEYPATH, tlsClientKeyPath); tlsClientKeyPassword = properties.getProperty(TLS_CLIENT_KEYPASSWORD, tlsClientKeyPassword); tlsClientCertPath = properties.getProperty(TLS_CLIENT_CERTPATH, tlsClientCertPath); tlsClientAuthServer = Boolean.parseBoolean(properties.getProperty(TLS_CLIENT_AUTHSERVER, String.valueOf(tlsClientAuthServer))); tlsClientTrustCertPath = properties.getProperty(TLS_CLIENT_TRUSTCERTPATH, tlsClientTrustCertPath); tlsCiphers = properties.getProperty(TLS_CIPHERS, tlsCiphers); tlsProtocols = properties.getProperty(TLS_PROTOCOLS, tlsProtocols); } private static void logTheFinalUsedTlsConfig() { LOGGER.info("Log the final used tls related configuration"); LOGGER.info("{} = {}", TLS_TEST_MODE_ENABLE, tlsTestModeEnable); LOGGER.debug("{} = {}", TLS_SERVER_NEED_CLIENT_AUTH, tlsServerNeedClientAuth); LOGGER.debug("{} = {}", TLS_SERVER_KEYPATH, tlsServerKeyPath); LOGGER.debug("{} = {}", TLS_SERVER_CERTPATH, tlsServerCertPath); LOGGER.debug("{} = {}", TLS_SERVER_AUTHCLIENT, tlsServerAuthClient); LOGGER.debug("{} = {}", TLS_SERVER_TRUSTCERTPATH, tlsServerTrustCertPath); LOGGER.debug("{} = {}", TLS_CLIENT_KEYPATH, tlsClientKeyPath); LOGGER.debug("{} = {}", TLS_CLIENT_CERTPATH, tlsClientCertPath); LOGGER.debug("{} = {}", TLS_CLIENT_AUTHSERVER, tlsClientAuthServer); LOGGER.debug("{} = {}", TLS_CLIENT_TRUSTCERTPATH, tlsClientTrustCertPath); LOGGER.debug("{} = {}", TLS_CIPHERS, tlsCiphers); LOGGER.debug("{} = {}", TLS_PROTOCOLS, tlsProtocols); } private static ClientAuth parseClientAuthMode(String authMode) { if (null == authMode || authMode.trim().isEmpty()) { return ClientAuth.NONE; } String authModeUpper = authMode.toUpperCase(); for (ClientAuth clientAuth : ClientAuth.values()) { if (clientAuth.name().equals(authModeUpper)) { return clientAuth; } } return ClientAuth.NONE; } /** * Determine if a string is {@code null} or {@link String#isEmpty()} returns {@code true}. */ private static boolean isNullOrEmpty(String s) { return s == null || s.isEmpty(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsSystemConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.handler.ssl.SslContext; import org.apache.rocketmq.remoting.common.TlsMode; public class TlsSystemConfig { public static final String TLS_SERVER_MODE = "tls.server.mode"; public static final String TLS_ENABLE = "tls.enable"; public static final String TLS_CONFIG_FILE = "tls.config.file"; public static final String TLS_TEST_MODE_ENABLE = "tls.test.mode.enable"; public static final String TLS_SERVER_NEED_CLIENT_AUTH = "tls.server.need.client.auth"; public static final String TLS_SERVER_KEYPATH = "tls.server.keyPath"; public static final String TLS_SERVER_KEYPASSWORD = "tls.server.keyPassword"; public static final String TLS_SERVER_CERTPATH = "tls.server.certPath"; public static final String TLS_SERVER_AUTHCLIENT = "tls.server.authClient"; public static final String TLS_SERVER_TRUSTCERTPATH = "tls.server.trustCertPath"; public static final String TLS_CLIENT_KEYPATH = "tls.client.keyPath"; public static final String TLS_CLIENT_KEYPASSWORD = "tls.client.keyPassword"; public static final String TLS_CLIENT_CERTPATH = "tls.client.certPath"; public static final String TLS_CLIENT_AUTHSERVER = "tls.client.authServer"; public static final String TLS_CLIENT_TRUSTCERTPATH = "tls.client.trustCertPath"; public static final String TLS_CIPHERS = "tls.ciphers"; public static final String TLS_PROTOCOLS = "tls.protocols"; /** * To determine whether use SSL in client-side, include SDK client and BrokerOuterAPI */ public static boolean tlsEnable = Boolean.parseBoolean(System.getProperty(TLS_ENABLE, "false")); /** * To determine whether use test mode when initialize TLS context */ public static boolean tlsTestModeEnable = Boolean.parseBoolean(System.getProperty(TLS_TEST_MODE_ENABLE, "true")); /** * Indicates the state of the {@link javax.net.ssl.SSLEngine} with respect to client authentication. * This configuration item really only applies when building the server-side {@link SslContext}, * and can be set to none, require or optional. */ public static String tlsServerNeedClientAuth = System.getProperty(TLS_SERVER_NEED_CLIENT_AUTH, "none"); /** * The store path of server-side private key */ public static String tlsServerKeyPath = System.getProperty(TLS_SERVER_KEYPATH, null); /** * The password of the server-side private key */ public static String tlsServerKeyPassword = System.getProperty(TLS_SERVER_KEYPASSWORD, null); /** * The store path of server-side X.509 certificate chain in PEM format */ public static String tlsServerCertPath = System.getProperty(TLS_SERVER_CERTPATH, null); /** * To determine whether verify the client endpoint's certificate strictly */ public static boolean tlsServerAuthClient = Boolean.parseBoolean(System.getProperty(TLS_SERVER_AUTHCLIENT, "false")); /** * The store path of trusted certificates for verifying the client endpoint's certificate */ public static String tlsServerTrustCertPath = System.getProperty(TLS_SERVER_TRUSTCERTPATH, null); /** * The store path of client-side private key */ public static String tlsClientKeyPath = System.getProperty(TLS_CLIENT_KEYPATH, null); /** * The password of the client-side private key */ public static String tlsClientKeyPassword = System.getProperty(TLS_CLIENT_KEYPASSWORD, null); /** * The store path of client-side X.509 certificate chain in PEM format */ public static String tlsClientCertPath = System.getProperty(TLS_CLIENT_CERTPATH, null); /** * To determine whether verify the server endpoint's certificate strictly */ public static boolean tlsClientAuthServer = Boolean.parseBoolean(System.getProperty(TLS_CLIENT_AUTHSERVER, "false")); /** * The store path of trusted certificates for verifying the server endpoint's certificate */ public static String tlsClientTrustCertPath = System.getProperty(TLS_CLIENT_TRUSTCERTPATH, null); /** * For server, three SSL modes are supported: disabled, permissive and enforcing. * For client, use {@link TlsSystemConfig#tlsEnable} to determine whether use SSL. *
      *
    1. disabled: SSL is not supported; any incoming SSL handshake will be rejected, causing connection closed.
    2. *
    3. permissive: SSL is optional, aka, server in this mode can serve client connections with or without SSL;
    4. *
    5. enforcing: SSL is required, aka, non SSL connection will be rejected.
    6. *
    */ public static TlsMode tlsMode = TlsMode.parse(System.getProperty(TLS_SERVER_MODE, "permissive")); /** * A config file to store the above TLS related configurations, * except {@link TlsSystemConfig#tlsMode} and {@link TlsSystemConfig#tlsEnable} */ public static String tlsConfigFile = System.getProperty(TLS_CONFIG_FILE, "/etc/rocketmq/tls.properties"); /** * The ciphers to be used in TLS *
      *
    1. If null, use the default ciphers
    2. *
    3. Otherwise, use the ciphers specified in this string, eg: -Dtls.ciphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    4. *
    */ public static String tlsCiphers = System.getProperty(TLS_CIPHERS, null); /** * The protocols to be used in TLS *
      *
    1. If null, use the default protocols
    2. *
    3. Otherwise, use the protocols specified in this string, eg: -Dtls.protocols=TLSv1.2,TLSv1.3
    4. *
    */ public static String tlsProtocols = System.getProperty(TLS_PROTOCOLS, null); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/pipeline/RequestPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.pipeline; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface RequestPipeline { void execute(ChannelHandlerContext ctx, RemotingCommand request) throws Exception; default RequestPipeline pipe(RequestPipeline source) { return (ctx, request) -> { source.execute(ctx, request); execute(ctx, request); }; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BitSetSerializerDeserializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.reader.ObjectReader; import com.alibaba.fastjson2.writer.ObjectWriter; import java.lang.reflect.Type; import java.util.Base64; import java.util.BitSet; public class BitSetSerializerDeserializer implements ObjectReader, ObjectWriter { @Override public void write(JSONWriter writer, Object object, Object fieldName, Type fieldType, long features) { if (object == null) { writer.writeBase64(null); } else { writer.writeBase64(((BitSet) object).toByteArray()); } } @Override public BitSet readObject(JSONReader reader, Type fieldType, Object fieldName, long features) { if (reader.nextIfNull()) { return null; } String base64 = reader.readString(); if (base64 == null || base64.isEmpty()) { return null; } byte[] bytes = Base64.getDecoder().decode(base64); return BitSet.valueOf(bytes); } @Override public long getFeatures() { return 0L; } @Override public Class getObjectClass() { return ObjectReader.super.getObjectClass(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BrokerSyncInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public class BrokerSyncInfo extends RemotingSerializable { /** * For slave online sync, retrieve HA address before register */ private String masterHaAddress; private long masterFlushOffset; private String masterAddress; public BrokerSyncInfo(String masterHaAddress, long masterFlushOffset, String masterAddress) { this.masterHaAddress = masterHaAddress; this.masterFlushOffset = masterFlushOffset; this.masterAddress = masterAddress; } public String getMasterHaAddress() { return masterHaAddress; } public void setMasterHaAddress(String masterHaAddress) { this.masterHaAddress = masterHaAddress; } public long getMasterFlushOffset() { return masterFlushOffset; } public void setMasterFlushOffset(long masterFlushOffset) { this.masterFlushOffset = masterFlushOffset; } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } @Override public String toString() { return "BrokerSyncInfo{" + "masterHaAddress='" + masterHaAddress + '\'' + ", masterFlushOffset=" + masterFlushOffset + ", masterAddress=" + masterAddress + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/DataVersion.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.concurrent.atomic.AtomicLong; public class DataVersion extends RemotingSerializable { private long stateVersion = 0L; private long timestamp = System.currentTimeMillis(); private AtomicLong counter = new AtomicLong(0); public void assignNewOne(final DataVersion dataVersion) { this.timestamp = dataVersion.timestamp; this.stateVersion = dataVersion.stateVersion; this.counter.set(dataVersion.counter.get()); } public void nextVersion() { this.nextVersion(0L); } public void nextVersion(long stateVersion) { this.timestamp = System.currentTimeMillis(); this.stateVersion = stateVersion; this.counter.incrementAndGet(); } public long getStateVersion() { return stateVersion; } public void setStateVersion(long stateVersion) { this.stateVersion = stateVersion; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public AtomicLong getCounter() { return counter; } public void setCounter(AtomicLong counter) { this.counter = counter; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DataVersion version = (DataVersion) o; if (getStateVersion() != version.getStateVersion()) return false; if (getTimestamp() != version.getTimestamp()) return false; if (counter != null && version.counter != null) { return counter.longValue() == version.counter.longValue(); } return null == counter && null == version.counter; } @Override public int hashCode() { int result = (int) (getStateVersion() ^ (getStateVersion() >>> 32)); result = 31 * result + (int) (getTimestamp() ^ (getTimestamp() >>> 32)); if (null != counter) { long l = counter.get(); result = 31 * result + (int) (l ^ (l >>> 32)); } return result; } @Override public String toString() { final StringBuilder sb = new StringBuilder("DataVersion["); sb.append("timestamp=").append(timestamp); sb.append(", counter=").append(counter); sb.append(']'); return sb.toString(); } public int compare(DataVersion dataVersion) { if (this.getStateVersion() > dataVersion.getStateVersion()) { return 1; } else if (this.getStateVersion() < dataVersion.getStateVersion()) { return -1; } else if (this.getCounter().get() > dataVersion.getCounter().get()) { return 1; } else if (this.getCounter().get() < dataVersion.getCounter().get()) { return -1; } else if (this.getTimestamp() > dataVersion.getTimestamp()) { return 1; } else if (this.getTimestamp() < dataVersion.getTimestamp()) { return -1; } return 0; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/EpochEntry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.Objects; public class EpochEntry extends RemotingSerializable { public static final long LAST_EPOCH_END_OFFSET = Long.MAX_VALUE; private int epoch; private long startOffset; private long endOffset = LAST_EPOCH_END_OFFSET; public EpochEntry(EpochEntry entry) { this.epoch = entry.getEpoch(); this.startOffset = entry.getStartOffset(); this.endOffset = entry.getEndOffset(); } public EpochEntry(int epoch, long startOffset) { this.epoch = epoch; this.startOffset = startOffset; } public EpochEntry(int epoch, long startOffset, long endOffset) { this.epoch = epoch; this.startOffset = startOffset; this.endOffset = endOffset; } public int getEpoch() { return epoch; } public void setEpoch(int epoch) { this.epoch = epoch; } public long getStartOffset() { return startOffset; } public void setStartOffset(long startOffset) { this.startOffset = startOffset; } public long getEndOffset() { return endOffset; } public void setEndOffset(long endOffset) { this.endOffset = endOffset; } @Override public String toString() { return "EpochEntry{" + "epoch=" + epoch + ", startOffset=" + startOffset + ", endOffset=" + endOffset + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } EpochEntry entry = (EpochEntry) o; return epoch == entry.epoch && startOffset == entry.startOffset && endOffset == entry.endOffset; } @Override public int hashCode() { return Objects.hash(epoch, startOffset, endOffset); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/FastCodesHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.HashMap; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import io.netty.buffer.ByteBuf; public interface FastCodesHeader { default String getAndCheckNotNull(HashMap fields, String field) { String value = fields.get(field); if (value == null) { String headerClass = this.getClass().getSimpleName(); RemotingCommand.log.error("the custom field {}.{} is null", headerClass, field); // no exception throws, keep compatible with RemotingCommand.decodeCommandCustomHeader } return value; } default void writeIfNotNull(ByteBuf out, String key, Object value) { if (value != null) { RocketMQSerializable.writeStr(out, true, key); RocketMQSerializable.writeStr(out, false, value.toString()); } } void encode(ByteBuf out); void decode(HashMap fields) throws RemotingCommandException; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ForbiddenType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; /** * * gives the reason for a no permission messaging pulling. * */ public interface ForbiddenType { /** * 1=forbidden by broker */ int BROKER_FORBIDDEN = 1; /** * 2=forbidden by groupId */ int GROUP_FORBIDDEN = 2; /** * 3=forbidden by topic */ int TOPIC_FORBIDDEN = 3; /** * 4=forbidden by broadcasting mode */ int BROADCASTING_DISABLE_FORBIDDEN = 4; /** * 5=forbidden for a subscription(group with a topic) */ int SUBSCRIPTION_FORBIDDEN = 5; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.Arrays; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; public enum LanguageCode { JAVA((byte) 0), CPP((byte) 1), DOTNET((byte) 2), PYTHON((byte) 3), DELPHI((byte) 4), ERLANG((byte) 5), RUBY((byte) 6), OTHER((byte) 7), HTTP((byte) 8), GO((byte) 9), PHP((byte) 10), OMS((byte) 11), RUST((byte) 12), NODE_JS((byte) 13); private byte code; LanguageCode(byte code) { this.code = code; } public static LanguageCode valueOf(byte code) { for (LanguageCode languageCode : LanguageCode.values()) { if (languageCode.getCode() == code) { return languageCode; } } return null; } public byte getCode() { return code; } private static final Map MAP = Arrays.stream(LanguageCode.values()).collect(Collectors.toMap(LanguageCode::name, Function.identity())); public static LanguageCode getCode(String language) { return MAP.get(language); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/MQProtosHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerRequestHeader; public class MQProtosHelper { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static boolean registerBrokerToNameServer(final String nsaddr, final String brokerAddr, final long timeoutMillis) { RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader); try { RemotingCommand response = RemotingHelper.invokeSync(nsaddr, request, timeoutMillis); if (response != null) { return ResponseCode.SUCCESS == response.getCode(); } } catch (Exception e) { log.error("Failed to register broker", e); } return false; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/NamespaceUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.topic.TopicValidator; public class NamespaceUtil { public static final char NAMESPACE_SEPARATOR = '%'; public static final String STRING_BLANK = ""; public static final int RETRY_PREFIX_LENGTH = MixAll.RETRY_GROUP_TOPIC_PREFIX.length(); public static final int DLQ_PREFIX_LENGTH = MixAll.DLQ_GROUP_TOPIC_PREFIX.length(); /** * Unpack namespace from resource, just like: * (1) MQ_INST_XX%Topic_XXX --> Topic_XXX * (2) %RETRY%MQ_INST_XX%GID_XXX --> %RETRY%GID_XXX * * @param resourceWithNamespace, topic/groupId with namespace. * @return topic/groupId without namespace. */ public static String withoutNamespace(String resourceWithNamespace) { if (StringUtils.isEmpty(resourceWithNamespace) || isSystemResource(resourceWithNamespace)) { return resourceWithNamespace; } StringBuilder stringBuilder = new StringBuilder(); if (isRetryTopic(resourceWithNamespace)) { stringBuilder.append(MixAll.RETRY_GROUP_TOPIC_PREFIX); } if (isDLQTopic(resourceWithNamespace)) { stringBuilder.append(MixAll.DLQ_GROUP_TOPIC_PREFIX); } String resourceWithoutRetryAndDLQ = withOutRetryAndDLQ(resourceWithNamespace); int index = resourceWithoutRetryAndDLQ.indexOf(NAMESPACE_SEPARATOR); if (index > 0) { String resourceWithoutNamespace = resourceWithoutRetryAndDLQ.substring(index + 1); return stringBuilder.append(resourceWithoutNamespace).toString(); } return resourceWithNamespace; } /** * If resource contains the namespace, unpack namespace from resource, just like: * (1) (MQ_INST_XX1%Topic_XXX1, MQ_INST_XX1) --> Topic_XXX1 * (2) (MQ_INST_XX2%Topic_XXX2, NULL) --> MQ_INST_XX2%Topic_XXX2 * (3) (%RETRY%MQ_INST_XX1%GID_XXX1, MQ_INST_XX1) --> %RETRY%GID_XXX1 * (4) (%RETRY%MQ_INST_XX2%GID_XXX2, MQ_INST_XX3) --> %RETRY%MQ_INST_XX2%GID_XXX2 * * @param resourceWithNamespace, topic/groupId with namespace. * @param namespace, namespace to be unpacked. * @return topic/groupId without namespace. */ public static String withoutNamespace(String resourceWithNamespace, String namespace) { if (StringUtils.isEmpty(resourceWithNamespace) || StringUtils.isEmpty(namespace)) { return resourceWithNamespace; } String resourceWithoutRetryAndDLQ = withOutRetryAndDLQ(resourceWithNamespace); if (resourceWithoutRetryAndDLQ.startsWith(namespace + NAMESPACE_SEPARATOR)) { return withoutNamespace(resourceWithNamespace); } return resourceWithNamespace; } public static String wrapNamespace(String namespace, String resourceWithOutNamespace) { if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(resourceWithOutNamespace)) { return resourceWithOutNamespace; } if (isSystemResource(resourceWithOutNamespace) || isAlreadyWithNamespace(resourceWithOutNamespace, namespace)) { return resourceWithOutNamespace; } String resourceWithoutRetryAndDLQ = withOutRetryAndDLQ(resourceWithOutNamespace); StringBuilder stringBuilder = new StringBuilder(); if (isRetryTopic(resourceWithOutNamespace)) { stringBuilder.append(MixAll.RETRY_GROUP_TOPIC_PREFIX); } if (isDLQTopic(resourceWithOutNamespace)) { stringBuilder.append(MixAll.DLQ_GROUP_TOPIC_PREFIX); } return stringBuilder.append(namespace).append(NAMESPACE_SEPARATOR).append(resourceWithoutRetryAndDLQ).toString(); } public static boolean isAlreadyWithNamespace(String resource, String namespace) { if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(resource) || isSystemResource(resource)) { return false; } String resourceWithoutRetryAndDLQ = withOutRetryAndDLQ(resource); return resourceWithoutRetryAndDLQ.startsWith(namespace + NAMESPACE_SEPARATOR); } public static String wrapNamespaceAndRetry(String namespace, String consumerGroup) { if (StringUtils.isEmpty(consumerGroup)) { return null; } return new StringBuilder() .append(MixAll.RETRY_GROUP_TOPIC_PREFIX) .append(wrapNamespace(namespace, consumerGroup)) .toString(); } public static String getNamespaceFromResource(String resource) { if (StringUtils.isEmpty(resource) || isSystemResource(resource)) { return STRING_BLANK; } String resourceWithoutRetryAndDLQ = withOutRetryAndDLQ(resource); int index = resourceWithoutRetryAndDLQ.indexOf(NAMESPACE_SEPARATOR); return index > 0 ? resourceWithoutRetryAndDLQ.substring(0, index) : STRING_BLANK; } public static String withOutRetryAndDLQ(String originalResource) { if (StringUtils.isEmpty(originalResource)) { return STRING_BLANK; } if (isRetryTopic(originalResource)) { return originalResource.substring(RETRY_PREFIX_LENGTH); } if (isDLQTopic(originalResource)) { return originalResource.substring(DLQ_PREFIX_LENGTH); } return originalResource; } private static boolean isSystemResource(String resource) { if (StringUtils.isEmpty(resource)) { return false; } if (TopicValidator.isSystemTopic(resource) || MixAll.isSysConsumerGroup(resource)) { return true; } return false; } public static boolean isRetryTopic(String resource) { return StringUtils.isNotBlank(resource) && resource.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX); } public static boolean isDLQTopic(String resource) { return StringUtils.isNotBlank(resource) && resource.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson2.annotation.JSONField; import com.google.common.base.Stopwatch; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.CommandCallback; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; public class RemotingCommand { public static final String SERIALIZE_TYPE_PROPERTY = "rocketmq.serialize.type"; public static final String SERIALIZE_TYPE_ENV = "ROCKETMQ_SERIALIZE_TYPE"; public static final String REMOTING_VERSION_KEY = "rocketmq.remoting.version"; static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final int RPC_TYPE = 0; // 0, REQUEST_COMMAND private static final int RPC_ONEWAY = 1; // 0, RPC private static final Map, Field[]> CLASS_HASH_MAP = new HashMap<>(); private static final Map CANONICAL_NAME_CACHE = new HashMap<>(); // 1, Oneway // 1, RESPONSE_COMMAND private static final Map NULLABLE_FIELD_CACHE = new HashMap<>(); private static final String STRING_CANONICAL_NAME = String.class.getCanonicalName(); private static final String DOUBLE_CANONICAL_NAME_1 = Double.class.getCanonicalName(); private static final String DOUBLE_CANONICAL_NAME_2 = double.class.getCanonicalName(); private static final String INTEGER_CANONICAL_NAME_1 = Integer.class.getCanonicalName(); private static final String INTEGER_CANONICAL_NAME_2 = int.class.getCanonicalName(); private static final String LONG_CANONICAL_NAME_1 = Long.class.getCanonicalName(); private static final String LONG_CANONICAL_NAME_2 = long.class.getCanonicalName(); private static final String BOOLEAN_CANONICAL_NAME_1 = Boolean.class.getCanonicalName(); private static final String BOOLEAN_CANONICAL_NAME_2 = boolean.class.getCanonicalName(); private static final String BOUNDARY_TYPE_CANONICAL_NAME = BoundaryType.class.getCanonicalName(); private static volatile int configVersion = -1; private static AtomicInteger requestId = new AtomicInteger(0); private static SerializeType serializeTypeConfigInThisServer = SerializeType.JSON; static { final String protocol = System.getProperty(SERIALIZE_TYPE_PROPERTY, System.getenv(SERIALIZE_TYPE_ENV)); if (!StringUtils.isBlank(protocol)) { try { serializeTypeConfigInThisServer = SerializeType.valueOf(protocol); } catch (IllegalArgumentException e) { throw new RuntimeException("parser specified protocol error. protocol=" + protocol, e); } } } private int code; private LanguageCode language = LanguageCode.JAVA; private int version = 0; private int opaque = requestId.getAndIncrement(); private int flag = 0; private String remark; private HashMap extFields; private transient CommandCustomHeader customHeader; private transient CommandCustomHeader cachedHeader; private SerializeType serializeTypeCurrentRPC = serializeTypeConfigInThisServer; private transient byte[] body; private boolean suspended; private transient Stopwatch processTimer; private transient List callbackList; protected RemotingCommand() { } public static RemotingCommand createRequestCommand(int code, CommandCustomHeader customHeader) { RemotingCommand cmd = new RemotingCommand(); cmd.setCode(code); cmd.customHeader = customHeader; setCmdVersion(cmd); return cmd; } public static RemotingCommand createResponseCommandWithHeader(int code, CommandCustomHeader customHeader) { RemotingCommand cmd = new RemotingCommand(); cmd.setCode(code); cmd.markResponseType(); cmd.customHeader = customHeader; setCmdVersion(cmd); return cmd; } protected static void setCmdVersion(RemotingCommand cmd) { if (configVersion >= 0) { cmd.setVersion(configVersion); } else { String v = System.getProperty(REMOTING_VERSION_KEY); if (v != null) { int value = Integer.parseInt(v); cmd.setVersion(value); configVersion = value; } } } public static RemotingCommand createResponseCommand(Class classHeader) { return createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR, "not set any response code", classHeader); } public static RemotingCommand buildErrorResponse(int code, String remark, Class classHeader) { final RemotingCommand response = RemotingCommand.createResponseCommand(classHeader); response.setCode(code); response.setRemark(remark); return response; } public static RemotingCommand buildErrorResponse(int code, String remark) { return buildErrorResponse(code, remark, null); } public static RemotingCommand createResponseCommand(int code, String remark, Class classHeader) { RemotingCommand cmd = new RemotingCommand(); cmd.markResponseType(); cmd.setCode(code); cmd.setRemark(remark); setCmdVersion(cmd); if (classHeader != null) { try { CommandCustomHeader objectHeader = classHeader.getDeclaredConstructor().newInstance(); cmd.customHeader = objectHeader; } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { return null; } catch (InvocationTargetException e) { return null; } catch (NoSuchMethodException e) { return null; } } return cmd; } public static RemotingCommand createResponseCommand(int code, String remark) { return createResponseCommand(code, remark, null); } public static RemotingCommand decode(final byte[] array) throws RemotingCommandException { ByteBuffer byteBuffer = ByteBuffer.wrap(array); return decode(byteBuffer); } public static RemotingCommand decode(final ByteBuffer byteBuffer) throws RemotingCommandException { return decode(Unpooled.wrappedBuffer(byteBuffer)); } public static RemotingCommand decode(final ByteBuf byteBuffer) throws RemotingCommandException { int length = byteBuffer.readableBytes(); int oriHeaderLen = byteBuffer.readInt(); int headerLength = getHeaderLength(oriHeaderLen); if (headerLength > length - 4) { throw new RemotingCommandException("decode error, bad header length: " + headerLength); } RemotingCommand cmd = headerDecode(byteBuffer, headerLength, getProtocolType(oriHeaderLen)); int bodyLength = length - 4 - headerLength; byte[] bodyData = null; if (bodyLength > 0) { bodyData = new byte[bodyLength]; byteBuffer.readBytes(bodyData); } cmd.body = bodyData; return cmd; } public static int getHeaderLength(int length) { return length & 0xFFFFFF; } private static RemotingCommand headerDecode(ByteBuf byteBuffer, int len, SerializeType type) throws RemotingCommandException { switch (type) { case JSON: byte[] headerData = new byte[len]; byteBuffer.readBytes(headerData); RemotingCommand resultJson = RemotingSerializable.decode(headerData, RemotingCommand.class); resultJson.setSerializeTypeCurrentRPC(type); return resultJson; case ROCKETMQ: RemotingCommand resultRMQ = RocketMQSerializable.rocketMQProtocolDecode(byteBuffer, len); resultRMQ.setSerializeTypeCurrentRPC(type); return resultRMQ; default: break; } return null; } public static SerializeType getProtocolType(int source) { return SerializeType.valueOf((byte) ((source >> 24) & 0xFF)); } public static int createNewRequestId() { return requestId.getAndIncrement(); } public static SerializeType getSerializeTypeConfigInThisServer() { return serializeTypeConfigInThisServer; } public static int markProtocolType(int source, SerializeType type) { return (type.getCode() << 24) | (source & 0x00FFFFFF); } public void markResponseType() { int bits = 1 << RPC_TYPE; this.flag |= bits; } public CommandCustomHeader readCustomHeader() { return customHeader; } public void writeCustomHeader(CommandCustomHeader customHeader) { this.customHeader = customHeader; } public T decodeCommandCustomHeader( Class classHeader) throws RemotingCommandException { return decodeCommandCustomHeader(classHeader, false); } public T decodeCommandCustomHeader( Class classHeader, boolean isCached) throws RemotingCommandException { if (isCached && cachedHeader != null) { return classHeader.cast(cachedHeader); } cachedHeader = decodeCommandCustomHeaderDirectly(classHeader, true); if (cachedHeader == null) { return null; } return classHeader.cast(cachedHeader); } public T decodeCommandCustomHeaderDirectly(Class classHeader, boolean useFastEncode) throws RemotingCommandException { T objectHeader; try { objectHeader = classHeader.getDeclaredConstructor().newInstance(); } catch (Exception e) { return null; } if (this.extFields != null) { if (objectHeader instanceof FastCodesHeader && useFastEncode) { ((FastCodesHeader) objectHeader).decode(this.extFields); objectHeader.checkFields(); return objectHeader; } Field[] fields = getClazzFields(classHeader); for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers())) { String fieldName = field.getName(); if (!fieldName.startsWith("this")) { try { String value = this.extFields.get(fieldName); if (null == value) { if (!isFieldNullable(field)) { throw new RemotingCommandException("the custom field <" + fieldName + "> is null"); } continue; } field.setAccessible(true); String type = getCanonicalName(field.getType()); Object valueParsed; if (type.equals(STRING_CANONICAL_NAME)) { valueParsed = value; } else if (type.equals(INTEGER_CANONICAL_NAME_1) || type.equals(INTEGER_CANONICAL_NAME_2)) { valueParsed = Integer.parseInt(value); } else if (type.equals(LONG_CANONICAL_NAME_1) || type.equals(LONG_CANONICAL_NAME_2)) { valueParsed = Long.parseLong(value); } else if (type.equals(BOOLEAN_CANONICAL_NAME_1) || type.equals(BOOLEAN_CANONICAL_NAME_2)) { valueParsed = Boolean.parseBoolean(value); } else if (type.equals(DOUBLE_CANONICAL_NAME_1) || type.equals(DOUBLE_CANONICAL_NAME_2)) { valueParsed = Double.parseDouble(value); } else if (type.equals(BOUNDARY_TYPE_CANONICAL_NAME)) { valueParsed = BoundaryType.getType(value); } else { throw new RemotingCommandException("the custom field <" + fieldName + "> type is not supported"); } field.set(objectHeader, valueParsed); } catch (Throwable e) { log.error("Failed field [{}] decoding", fieldName, e); } } } } objectHeader.checkFields(); } return objectHeader; } //make it able to test Field[] getClazzFields(Class classHeader) { Field[] field = CLASS_HASH_MAP.get(classHeader); if (field == null) { Set fieldList = new HashSet<>(); for (Class className = classHeader; className != Object.class; className = className.getSuperclass()) { Field[] fields = className.getDeclaredFields(); fieldList.addAll(Arrays.asList(fields)); } field = fieldList.toArray(new Field[0]); synchronized (CLASS_HASH_MAP) { CLASS_HASH_MAP.put(classHeader, field); } } return field; } private boolean isFieldNullable(Field field) { if (!NULLABLE_FIELD_CACHE.containsKey(field)) { Annotation annotation = field.getAnnotation(CFNotNull.class); synchronized (NULLABLE_FIELD_CACHE) { NULLABLE_FIELD_CACHE.put(field, annotation == null); } } return NULLABLE_FIELD_CACHE.get(field); } private String getCanonicalName(Class clazz) { String name = CANONICAL_NAME_CACHE.get(clazz); if (name == null) { name = clazz.getCanonicalName(); synchronized (CANONICAL_NAME_CACHE) { CANONICAL_NAME_CACHE.put(clazz, name); } } return name; } public ByteBuffer encode() { // 1> header length size int length = 4; // 2> header data length byte[] headerData = this.headerEncode(); length += headerData.length; // 3> body data length if (this.body != null) { length += body.length; } ByteBuffer result = ByteBuffer.allocate(4 + length); // length result.putInt(length); // header length result.putInt(markProtocolType(headerData.length, serializeTypeCurrentRPC)); // header data result.put(headerData); // body data; if (this.body != null) { result.put(this.body); } result.flip(); return result; } private byte[] headerEncode() { this.makeCustomHeaderToNet(); if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) { return RocketMQSerializable.rocketMQProtocolEncode(this); } else { return RemotingSerializable.encode(this); } } public void makeCustomHeaderToNet() { if (this.customHeader != null) { Field[] fields = getClazzFields(customHeader.getClass()); if (null == this.extFields) { this.extFields = new HashMap<>(); } for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers())) { String name = field.getName(); if (!name.startsWith("this")) { Object value = null; try { field.setAccessible(true); value = field.get(this.customHeader); } catch (Exception e) { log.error("Failed to access field [{}]", name, e); } if (value != null) { this.extFields.put(name, value.toString()); } } } } } } public void fastEncodeHeader(ByteBuf out) { int bodySize = this.body != null ? this.body.length : 0; int beginIndex = out.writerIndex(); // skip 8 bytes out.writeLong(0); int headerSize; if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) { if (customHeader != null && !(customHeader instanceof FastCodesHeader)) { this.makeCustomHeaderToNet(); } headerSize = RocketMQSerializable.rocketMQProtocolEncode(this, out); } else { this.makeCustomHeaderToNet(); byte[] header = RemotingSerializable.encode(this); headerSize = header.length; out.writeBytes(header); } out.setInt(beginIndex, 4 + headerSize + bodySize); out.setInt(beginIndex + 4, markProtocolType(headerSize, serializeTypeCurrentRPC)); } public ByteBuffer encodeHeader() { return encodeHeader(this.body != null ? this.body.length : 0); } public ByteBuffer encodeHeader(final int bodyLength) { // 1> header length size int length = 4; // 2> header data length byte[] headerData; headerData = this.headerEncode(); length += headerData.length; // 3> body data length length += bodyLength; ByteBuffer result = ByteBuffer.allocate(4 + length - bodyLength); // length result.putInt(length); // header length result.putInt(markProtocolType(headerData.length, serializeTypeCurrentRPC)); // header data result.put(headerData); ((Buffer) result).flip(); return result; } public void markOnewayRPC() { int bits = 1 << RPC_ONEWAY; this.flag |= bits; } @JSONField(serialize = false) public boolean isOnewayRPC() { int bits = 1 << RPC_ONEWAY; return (this.flag & bits) == bits; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } @JSONField(serialize = false) public RemotingCommandType getType() { if (this.isResponseType()) { return RemotingCommandType.RESPONSE_COMMAND; } return RemotingCommandType.REQUEST_COMMAND; } @JSONField(serialize = false) public boolean isResponseType() { int bits = 1 << RPC_TYPE; return (this.flag & bits) == bits; } public LanguageCode getLanguage() { return language; } public void setLanguage(LanguageCode language) { this.language = language; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public int getOpaque() { return opaque; } public void setOpaque(int opaque) { this.opaque = opaque; } public int getFlag() { return flag; } public void setFlag(int flag) { this.flag = flag; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public byte[] getBody() { return body; } public void setBody(byte[] body) { this.body = body; } @JSONField(serialize = false) public boolean isSuspended() { return suspended; } @JSONField(serialize = false) public void setSuspended(boolean suspended) { this.suspended = suspended; } public HashMap getExtFields() { return extFields; } public void setExtFields(HashMap extFields) { this.extFields = extFields; } public void addExtField(String key, String value) { if (null == extFields) { extFields = new HashMap<>(256); } extFields.put(key, value); } public void addExtFieldIfNotExist(String key, String value) { extFields.putIfAbsent(key, value); } @Override public String toString() { return "RemotingCommand [code=" + code + ", language=" + language + ", version=" + version + ", opaque=" + opaque + ", flag(B)=" + Integer.toBinaryString(flag) + ", remark=" + remark + ", extFields=" + extFields + ", serializeTypeCurrentRPC=" + serializeTypeCurrentRPC + "]"; } public SerializeType getSerializeTypeCurrentRPC() { return serializeTypeCurrentRPC; } public void setSerializeTypeCurrentRPC(SerializeType serializeTypeCurrentRPC) { this.serializeTypeCurrentRPC = serializeTypeCurrentRPC; } public Stopwatch getProcessTimer() { return processTimer; } public void setProcessTimer(Stopwatch processTimer) { this.processTimer = processTimer; } public List getCallbackList() { return callbackList; } public void setCallbackList(List callbackList) { this.callbackList = callbackList; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommandType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public enum RemotingCommandType { REQUEST_COMMAND, RESPONSE_COMMAND; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingSerializable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; public abstract class RemotingSerializable { private final static Charset CHARSET_UTF8 = StandardCharsets.UTF_8; public static byte[] encode(final Object obj) { if (obj == null) { return null; } return JSON.toJSONBytes(obj, CHARSET_UTF8); } public static String toJson(final Object obj, boolean prettyFormat) { if (prettyFormat) { return JSON.toJSONString(obj, JSONWriter.Feature.PrettyFormat); } return JSON.toJSONString(obj); } public static T decode(final byte[] data, Class classOfT) { if (data == null) { return null; } return JSON.parseObject(data, classOfT); } public static List decodeList(final byte[] data, Class classOfT) { if (data == null) { return null; } return JSON.parseArray(data, 0, data.length, CHARSET_UTF8, classOfT); } public static T fromJson(String json, Class classOfT) { return JSON.parseObject(json, classOfT); } public byte[] encode() { return JSON.toJSONBytes(this, CHARSET_UTF8); } /** * Allow call-site to apply specific features according to their requirements. * * @param features Features to apply * @return serialized data. */ public byte[] encode(JSONWriter.Feature... features) { return JSON.toJSONBytes(this, CHARSET_UTF8, features); } public String toJson() { return toJson(false); } public String toJson(final boolean prettyFormat) { return toJson(this, prettyFormat); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingSysResponseCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public class RemotingSysResponseCode { public static final int SUCCESS = 0; public static final int SYSTEM_ERROR = 1; public static final int SYSTEM_BUSY = 2; public static final int REQUEST_CODE_NOT_SUPPORTED = 3; public static final int TRANSACTION_FAILED = 4; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public class RequestCode { public static final int SEND_MESSAGE = 10; public static final int PULL_MESSAGE = 11; public static final int QUERY_MESSAGE = 12; public static final int QUERY_BROKER_OFFSET = 13; public static final int QUERY_CONSUMER_OFFSET = 14; public static final int UPDATE_CONSUMER_OFFSET = 15; public static final int UPDATE_AND_CREATE_TOPIC = 17; public static final int UPDATE_AND_CREATE_TOPIC_LIST = 18; public static final int GET_ALL_TOPIC_CONFIG = 21; public static final int GET_TOPIC_CONFIG_LIST = 22; public static final int GET_TOPIC_NAME_LIST = 23; public static final int UPDATE_BROKER_CONFIG = 25; public static final int GET_BROKER_CONFIG = 26; public static final int TRIGGER_DELETE_FILES = 27; public static final int GET_BROKER_RUNTIME_INFO = 28; public static final int SEARCH_OFFSET_BY_TIMESTAMP = 29; public static final int GET_MAX_OFFSET = 30; public static final int GET_MIN_OFFSET = 31; public static final int GET_EARLIEST_MSG_STORETIME = 32; public static final int VIEW_MESSAGE_BY_ID = 33; public static final int HEART_BEAT = 34; public static final int UNREGISTER_CLIENT = 35; public static final int CONSUMER_SEND_MSG_BACK = 36; public static final int END_TRANSACTION = 37; public static final int GET_CONSUMER_LIST_BY_GROUP = 38; public static final int CHECK_TRANSACTION_STATE = 39; public static final int NOTIFY_CONSUMER_IDS_CHANGED = 40; public static final int LOCK_BATCH_MQ = 41; public static final int UNLOCK_BATCH_MQ = 42; public static final int GET_ALL_CONSUMER_OFFSET = 43; public static final int GET_ALL_DELAY_OFFSET = 45; public static final int CHECK_CLIENT_CONFIG = 46; public static final int GET_CLIENT_CONFIG = 47; public static final int GET_TIMER_CHECK_POINT = 60; public static final int GET_TIMER_METRICS = 61; public static final int POP_MESSAGE = 200050; public static final int ACK_MESSAGE = 200051; public static final int BATCH_ACK_MESSAGE = 200151; public static final int PEEK_MESSAGE = 200052; public static final int CHANGE_MESSAGE_INVISIBLETIME = 200053; public static final int NOTIFICATION = 200054; public static final int POLLING_INFO = 200055; public static final int POP_ROLLBACK = 200056; public static final int POP_LITE_MESSAGE = 200070; public static final int LITE_SUBSCRIPTION_CTL = 200071; public static final int ACK_LITE_MESSAGE = 200072; public static final int NOTIFY_UNSUBSCRIBE_LITE = 200073; // lite admin api public static final int GET_BROKER_LITE_INFO = 200074; public static final int GET_PARENT_TOPIC_INFO = 200075; public static final int GET_LITE_TOPIC_INFO = 200076; public static final int GET_LITE_CLIENT_INFO = 200077; public static final int GET_LITE_GROUP_INFO = 200078; public static final int TRIGGER_LITE_DISPATCH = 200079; public static final int PUT_KV_CONFIG = 100; public static final int GET_KV_CONFIG = 101; public static final int DELETE_KV_CONFIG = 102; public static final int REGISTER_BROKER = 103; public static final int UNREGISTER_BROKER = 104; public static final int GET_ROUTEINFO_BY_TOPIC = 105; public static final int GET_BROKER_CLUSTER_INFO = 106; public static final int UPDATE_AND_CREATE_SUBSCRIPTIONGROUP = 200; public static final int GET_ALL_SUBSCRIPTIONGROUP_CONFIG = 201; public static final int GET_TOPIC_STATS_INFO = 202; public static final int GET_CONSUMER_CONNECTION_LIST = 203; public static final int GET_PRODUCER_CONNECTION_LIST = 204; public static final int WIPE_WRITE_PERM_OF_BROKER = 205; public static final int GET_ALL_TOPIC_LIST_FROM_NAMESERVER = 206; public static final int DELETE_SUBSCRIPTIONGROUP = 207; public static final int GET_CONSUME_STATS = 208; public static final int SUSPEND_CONSUMER = 209; public static final int RESUME_CONSUMER = 210; public static final int RESET_CONSUMER_OFFSET_IN_CONSUMER = 211; public static final int RESET_CONSUMER_OFFSET_IN_BROKER = 212; public static final int ADJUST_CONSUMER_THREAD_POOL = 213; public static final int WHO_CONSUME_THE_MESSAGE = 214; public static final int DELETE_TOPIC_IN_BROKER = 215; public static final int DELETE_TOPIC_IN_NAMESRV = 216; public static final int REGISTER_TOPIC_IN_NAMESRV = 217; public static final int GET_KVLIST_BY_NAMESPACE = 219; public static final int RESET_CONSUMER_CLIENT_OFFSET = 220; public static final int GET_CONSUMER_STATUS_FROM_CLIENT = 221; public static final int INVOKE_BROKER_TO_RESET_OFFSET = 222; public static final int INVOKE_BROKER_TO_GET_CONSUMER_STATUS = 223; public static final int QUERY_TOPIC_CONSUME_BY_WHO = 300; public static final int GET_TOPICS_BY_CLUSTER = 224; public static final int UPDATE_AND_CREATE_SUBSCRIPTIONGROUP_LIST = 225; public static final int QUERY_TOPICS_BY_CONSUMER = 343; public static final int QUERY_SUBSCRIPTION_BY_CONSUMER = 345; public static final int REGISTER_FILTER_SERVER = 301; public static final int REGISTER_MESSAGE_FILTER_CLASS = 302; public static final int QUERY_CONSUME_TIME_SPAN = 303; public static final int GET_SYSTEM_TOPIC_LIST_FROM_NS = 304; public static final int GET_SYSTEM_TOPIC_LIST_FROM_BROKER = 305; public static final int CLEAN_EXPIRED_CONSUMEQUEUE = 306; public static final int GET_CONSUMER_RUNNING_INFO = 307; public static final int QUERY_CORRECTION_OFFSET = 308; public static final int CONSUME_MESSAGE_DIRECTLY = 309; public static final int SEND_MESSAGE_V2 = 310; public static final int GET_UNIT_TOPIC_LIST = 311; public static final int GET_HAS_UNIT_SUB_TOPIC_LIST = 312; public static final int GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST = 313; public static final int CLONE_GROUP_OFFSET = 314; public static final int VIEW_BROKER_STATS_DATA = 315; public static final int CLEAN_UNUSED_TOPIC = 316; public static final int GET_BROKER_CONSUME_STATS = 317; /** * update the config of name server */ public static final int UPDATE_NAMESRV_CONFIG = 318; /** * get config from name server */ public static final int GET_NAMESRV_CONFIG = 319; public static final int SEND_BATCH_MESSAGE = 320; public static final int QUERY_CONSUME_QUEUE = 321; public static final int QUERY_DATA_VERSION = 322; /** * resume logic of checking half messages that have been put in TRANS_CHECK_MAXTIME_TOPIC before */ public static final int RESUME_CHECK_HALF_MESSAGE = 323; public static final int SEND_REPLY_MESSAGE = 324; public static final int SEND_REPLY_MESSAGE_V2 = 325; public static final int PUSH_REPLY_MESSAGE_TO_CLIENT = 326; public static final int ADD_WRITE_PERM_OF_BROKER = 327; public static final int GET_ALL_PRODUCER_INFO = 328; public static final int DELETE_EXPIRED_COMMITLOG = 329; public static final int GET_TOPIC_CONFIG = 351; public static final int GET_SUBSCRIPTIONGROUP_CONFIG = 352; public static final int UPDATE_AND_GET_GROUP_FORBIDDEN = 353; public static final int CHECK_ROCKSDB_CQ_WRITE_PROGRESS = 354; public static final int EXPORT_ROCKSDB_CONFIG_TO_JSON = 355; public static final int LITE_PULL_MESSAGE = 361; public static final int RECALL_MESSAGE = 370; public static final int QUERY_ASSIGNMENT = 400; public static final int SET_MESSAGE_REQUEST_MODE = 401; public static final int GET_ALL_MESSAGE_REQUEST_MODE = 402; public static final int UPDATE_AND_CREATE_STATIC_TOPIC = 513; public static final int GET_BROKER_MEMBER_GROUP = 901; public static final int ADD_BROKER = 902; public static final int REMOVE_BROKER = 903; public static final int BROKER_HEARTBEAT = 904; public static final int NOTIFY_MIN_BROKER_ID_CHANGE = 905; public static final int EXCHANGE_BROKER_HA_INFO = 906; public static final int GET_BROKER_HA_STATUS = 907; public static final int RESET_MASTER_FLUSH_OFFSET = 908; /** * Controller code */ public static final int CONTROLLER_ALTER_SYNC_STATE_SET = 1001; public static final int CONTROLLER_ELECT_MASTER = 1002; public static final int CONTROLLER_REGISTER_BROKER = 1003; public static final int CONTROLLER_GET_REPLICA_INFO = 1004; public static final int CONTROLLER_GET_METADATA_INFO = 1005; public static final int CONTROLLER_GET_SYNC_STATE_DATA = 1006; public static final int GET_BROKER_EPOCH_CACHE = 1007; public static final int NOTIFY_BROKER_ROLE_CHANGED = 1008; /** * update the config of controller */ public static final int UPDATE_CONTROLLER_CONFIG = 1009; /** * get config from controller */ public static final int GET_CONTROLLER_CONFIG = 1010; /** * clean broker data */ public static final int CLEAN_BROKER_DATA = 1011; public static final int CONTROLLER_GET_NEXT_BROKER_ID = 1012; public static final int CONTROLLER_APPLY_BROKER_ID = 1013; public static final short BROKER_CLOSE_CHANNEL_REQUEST = 1014; public static final short CHECK_NOT_ACTIVE_BROKER_REQUEST = 1015; public static final short GET_BROKER_LIVE_INFO_REQUEST = 1016; public static final short GET_SYNC_STATE_DATA_REQUEST = 1017; public static final short RAFT_BROKER_HEART_BEAT_EVENT_REQUEST = 1018; public static final int UPDATE_COLD_DATA_FLOW_CTR_CONFIG = 2001; public static final int REMOVE_COLD_DATA_FLOW_CTR_CONFIG = 2002; public static final int GET_COLD_DATA_FLOW_CTR_INFO = 2003; public static final int SET_COMMITLOG_READ_MODE = 2004; public static final int AUTH_CREATE_USER = 3001; public static final int AUTH_UPDATE_USER = 3002; public static final int AUTH_DELETE_USER = 3003; public static final int AUTH_GET_USER = 3004; public static final int AUTH_LIST_USER = 3005; public static final int AUTH_CREATE_ACL = 3006; public static final int AUTH_UPDATE_ACL = 3007; public static final int AUTH_DELETE_ACL = 3008; public static final int AUTH_GET_ACL = 3009; public static final int AUTH_LIST_ACL = 3010; public static final int SWITCH_TIMER_ENGINE = 5001; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestHeaderRegistry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; public class RequestHeaderRegistry { private static final String PACKAGE_NAME = "org.apache.rocketmq.remoting.protocol.header"; private final Map> requestHeaderMap = new HashMap<>(); public static RequestHeaderRegistry getInstance() { return RequestHeaderRegistryHolder.INSTANCE; } public void initialize() { Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(ClasspathHelper.forPackage(PACKAGE_NAME)) .setScanners(new SubTypesScanner(false))); Set> classes = reflections.getSubTypesOf(CommandCustomHeader.class); classes.forEach(this::registerHeader); } public Class getRequestHeader(int requestCode) { return this.requestHeaderMap.get(requestCode); } private void registerHeader(Class clazz) { if (!clazz.isAnnotationPresent(RocketMQAction.class)) { return; } RocketMQAction action = clazz.getAnnotation(RocketMQAction.class); this.requestHeaderMap.putIfAbsent(action.value(), clazz); } private static class RequestHeaderRegistryHolder { private static final RequestHeaderRegistry INSTANCE = new RequestHeaderRegistry(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestSource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public enum RequestSource { SDK(-1), PROXY_FOR_ORDER(0), PROXY_FOR_BROADCAST(1), PROXY_FOR_STREAM(2); public static final String SYSTEM_PROPERTY_KEY = "rocketmq.requestSource"; private final int value; RequestSource(int value) { this.value = value; } public int getValue() { return value; } public static boolean isValid(Integer value) { return null != value && value >= -1 && value < RequestSource.values().length - 1; } public static RequestSource parseInteger(Integer value) { if (isValid(value)) { return RequestSource.values()[value + 1]; } return SDK; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public enum RequestType { STREAM((byte) 0); private final byte code; RequestType(byte code) { this.code = code; } public static RequestType valueOf(byte code) { for (RequestType requestType : RequestType.values()) { if (requestType.getCode() == code) { return requestType; } } return null; } public byte getCode() { return code; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public class ResponseCode extends RemotingSysResponseCode { public static final int FLUSH_DISK_TIMEOUT = 10; public static final int SLAVE_NOT_AVAILABLE = 11; public static final int FLUSH_SLAVE_TIMEOUT = 12; public static final int MESSAGE_ILLEGAL = 13; public static final int SERVICE_NOT_AVAILABLE = 14; public static final int VERSION_NOT_SUPPORTED = 15; public static final int NO_PERMISSION = 16; public static final int TOPIC_NOT_EXIST = 17; public static final int TOPIC_EXIST_ALREADY = 18; public static final int PULL_NOT_FOUND = 19; public static final int PULL_RETRY_IMMEDIATELY = 20; public static final int PULL_OFFSET_MOVED = 21; public static final int QUERY_NOT_FOUND = 22; public static final int SUBSCRIPTION_PARSE_FAILED = 23; public static final int SUBSCRIPTION_NOT_EXIST = 24; public static final int SUBSCRIPTION_NOT_LATEST = 25; public static final int SUBSCRIPTION_GROUP_NOT_EXIST = 26; public static final int FILTER_DATA_NOT_EXIST = 27; public static final int FILTER_DATA_NOT_LATEST = 28; public static final int INVALID_PARAMETER = 29; public static final int TRANSACTION_SHOULD_COMMIT = 200; public static final int TRANSACTION_SHOULD_ROLLBACK = 201; public static final int TRANSACTION_STATE_UNKNOW = 202; public static final int TRANSACTION_STATE_GROUP_WRONG = 203; public static final int NO_BUYER_ID = 204; public static final int NOT_IN_CURRENT_UNIT = 205; public static final int CONSUMER_NOT_ONLINE = 206; public static final int CONSUME_MSG_TIMEOUT = 207; public static final int NO_MESSAGE = 208; public static final int POLLING_FULL = 209; public static final int POLLING_TIMEOUT = 210; public static final int BROKER_NOT_EXIST = 211; public static final int BROKER_DISPATCH_NOT_COMPLETE = 212; public static final int BROADCAST_CONSUMPTION = 213; public static final int FLOW_CONTROL = 215; public static final int NOT_LEADER_FOR_QUEUE = 501; public static final int ILLEGAL_OPERATION = 604; public static final int RPC_UNKNOWN = -1000; public static final int RPC_ADDR_IS_NULL = -1002; public static final int RPC_SEND_TO_CHANNEL_FAILED = -1004; public static final int RPC_TIME_OUT = -1006; public static final int GO_AWAY = 1500; /** * Controller response code */ public static final int CONTROLLER_FENCED_MASTER_EPOCH = 2000; public static final int CONTROLLER_FENCED_SYNC_STATE_SET_EPOCH = 2001; public static final int CONTROLLER_INVALID_MASTER = 2002; public static final int CONTROLLER_INVALID_REPLICAS = 2003; public static final int CONTROLLER_MASTER_NOT_AVAILABLE = 2004; public static final int CONTROLLER_INVALID_REQUEST = 2005; public static final int CONTROLLER_BROKER_NOT_ALIVE = 2006; public static final int CONTROLLER_NOT_LEADER = 2007; public static final int CONTROLLER_BROKER_METADATA_NOT_EXIST = 2008; public static final int CONTROLLER_INVALID_CLEAN_BROKER_METADATA = 2009; public static final int CONTROLLER_BROKER_NEED_TO_BE_REGISTERED = 2010; public static final int CONTROLLER_MASTER_STILL_EXIST = 2011; public static final int CONTROLLER_ELECT_MASTER_FAILED = 2012; public static final int CONTROLLER_ALTER_SYNC_STATE_SET_FAILED = 2013; public static final int CONTROLLER_BROKER_ID_INVALID = 2014; public static final int CONTROLLER_JRAFT_INTERNAL_ERROR = 2015; public static final int CONTROLLER_BROKER_LIVE_INFO_NOT_EXISTS = 2016; public static final int LMQ_QUOTA_EXCEEDED = 2017; public static final int LITE_SUBSCRIPTION_QUOTA_EXCEEDED = 2018; public static final int USER_NOT_EXIST = 3001; public static final int POLICY_NOT_EXIST = 3002; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import io.netty.buffer.ByteBuf; public class RocketMQSerializable { private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; public static void writeStr(ByteBuf buf, boolean useShortLength, String str) { int lenIndex = buf.writerIndex(); if (useShortLength) { buf.writeShort(0); } else { buf.writeInt(0); } int len = buf.writeCharSequence(str, StandardCharsets.UTF_8); if (useShortLength) { buf.setShort(lenIndex, len); } else { buf.setInt(lenIndex, len); } } private static String readStr(ByteBuf buf, boolean useShortLength, int limit) throws RemotingCommandException { int len = useShortLength ? buf.readShort() : buf.readInt(); if (len == 0) { return null; } if (len > limit) { throw new RemotingCommandException("string length exceed limit:" + limit); } CharSequence cs = buf.readCharSequence(len, StandardCharsets.UTF_8); return cs == null ? null : cs.toString(); } public static int rocketMQProtocolEncode(RemotingCommand cmd, ByteBuf out) { int beginIndex = out.writerIndex(); // int code(~32767) out.writeShort(cmd.getCode()); // LanguageCode language out.writeByte(cmd.getLanguage().getCode()); // int version(~32767) out.writeShort(cmd.getVersion()); // int opaque out.writeInt(cmd.getOpaque()); // int flag out.writeInt(cmd.getFlag()); // String remark String remark = cmd.getRemark(); if (remark != null && !remark.isEmpty()) { writeStr(out, false, remark); } else { out.writeInt(0); } int mapLenIndex = out.writerIndex(); out.writeInt(0); if (cmd.readCustomHeader() instanceof FastCodesHeader) { ((FastCodesHeader) cmd.readCustomHeader()).encode(out); } HashMap map = cmd.getExtFields(); if (map != null && !map.isEmpty()) { map.forEach((k, v) -> { if (k != null && v != null) { writeStr(out, true, k); writeStr(out, false, v); } }); } out.setInt(mapLenIndex, out.writerIndex() - mapLenIndex - 4); return out.writerIndex() - beginIndex; } public static byte[] rocketMQProtocolEncode(RemotingCommand cmd) { // String remark byte[] remarkBytes = null; int remarkLen = 0; if (cmd.getRemark() != null && cmd.getRemark().length() > 0) { remarkBytes = cmd.getRemark().getBytes(CHARSET_UTF8); remarkLen = remarkBytes.length; } // HashMap extFields byte[] extFieldsBytes = null; int extLen = 0; if (cmd.getExtFields() != null && !cmd.getExtFields().isEmpty()) { extFieldsBytes = mapSerialize(cmd.getExtFields()); extLen = extFieldsBytes.length; } int totalLen = calTotalLen(remarkLen, extLen); ByteBuffer headerBuffer = ByteBuffer.allocate(totalLen); // int code(~32767) headerBuffer.putShort((short) cmd.getCode()); // LanguageCode language headerBuffer.put(cmd.getLanguage().getCode()); // int version(~32767) headerBuffer.putShort((short) cmd.getVersion()); // int opaque headerBuffer.putInt(cmd.getOpaque()); // int flag headerBuffer.putInt(cmd.getFlag()); // String remark if (remarkBytes != null) { headerBuffer.putInt(remarkBytes.length); headerBuffer.put(remarkBytes); } else { headerBuffer.putInt(0); } // HashMap extFields; if (extFieldsBytes != null) { headerBuffer.putInt(extFieldsBytes.length); headerBuffer.put(extFieldsBytes); } else { headerBuffer.putInt(0); } return headerBuffer.array(); } public static byte[] mapSerialize(HashMap map) { // keySize+key+valSize+val if (null == map || map.isEmpty()) return null; int totalLength = 0; int kvLength; Iterator> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (entry.getKey() != null && entry.getValue() != null) { kvLength = // keySize + Key 2 + entry.getKey().getBytes(CHARSET_UTF8).length // valSize + val + 4 + entry.getValue().getBytes(CHARSET_UTF8).length; totalLength += kvLength; } } ByteBuffer content = ByteBuffer.allocate(totalLength); byte[] key; byte[] val; it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (entry.getKey() != null && entry.getValue() != null) { key = entry.getKey().getBytes(CHARSET_UTF8); val = entry.getValue().getBytes(CHARSET_UTF8); content.putShort((short) key.length); content.put(key); content.putInt(val.length); content.put(val); } } return content.array(); } private static int calTotalLen(int remark, int ext) { // int code(~32767) int length = 2 // LanguageCode language + 1 // int version(~32767) + 2 // int opaque + 4 // int flag + 4 // String remark + 4 + remark // HashMap extFields + 4 + ext; return length; } public static RemotingCommand rocketMQProtocolDecode(final ByteBuf headerBuffer, int headerLen) throws RemotingCommandException { RemotingCommand cmd = new RemotingCommand(); // int code(~32767) cmd.setCode(headerBuffer.readShort()); // LanguageCode language cmd.setLanguage(LanguageCode.valueOf(headerBuffer.readByte())); // int version(~32767) cmd.setVersion(headerBuffer.readShort()); // int opaque cmd.setOpaque(headerBuffer.readInt()); // int flag cmd.setFlag(headerBuffer.readInt()); // String remark cmd.setRemark(readStr(headerBuffer, false, headerLen)); // HashMap extFields int extFieldsLength = headerBuffer.readInt(); if (extFieldsLength > 0) { if (extFieldsLength > headerLen) { throw new RemotingCommandException("RocketMQ protocol decoding failed, extFields length: " + extFieldsLength + ", but header length: " + headerLen); } cmd.setExtFields(mapDeserialize(headerBuffer, extFieldsLength)); } return cmd; } public static HashMap mapDeserialize(ByteBuf byteBuffer, int len) throws RemotingCommandException { HashMap map = new HashMap<>(128); int endIndex = byteBuffer.readerIndex() + len; while (byteBuffer.readerIndex() < endIndex) { String k = readStr(byteBuffer, true, len); String v = readStr(byteBuffer, false, len); map.put(k, v); } return map; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/SerializeType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; public enum SerializeType { JSON((byte) 0), ROCKETMQ((byte) 1); private byte code; SerializeType(byte code) { this.code = code; } public static SerializeType valueOf(byte code) { for (SerializeType serializeType : SerializeType.values()) { if (serializeType.getCode() == code) { return serializeType; } } return null; } public byte getCode() { return code; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStats.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ConsumeStats extends RemotingSerializable { private Map offsetTable = new ConcurrentHashMap<>(); private double consumeTps = 0; public long computeTotalDiff() { long diffTotal = 0L; for (Entry entry : this.offsetTable.entrySet()) { diffTotal += entry.getValue().getBrokerOffset() - entry.getValue().getConsumerOffset(); } return diffTotal; } public long computeInflightTotalDiff() { long diffTotal = 0L; for (Entry entry : this.offsetTable.entrySet()) { diffTotal += entry.getValue().getPullOffset() - entry.getValue().getConsumerOffset(); } return diffTotal; } public Map getOffsetTable() { return offsetTable; } public void setOffsetTable(Map offsetTable) { this.offsetTable = offsetTable; } public double getConsumeTps() { return consumeTps; } public void setConsumeTps(double consumeTps) { this.consumeTps = consumeTps; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/OffsetWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; public class OffsetWrapper { private long brokerOffset; private long consumerOffset; private long pullOffset; private long lastTimestamp; public long getBrokerOffset() { return brokerOffset; } public void setBrokerOffset(long brokerOffset) { this.brokerOffset = brokerOffset; } public long getConsumerOffset() { return consumerOffset; } public void setConsumerOffset(long consumerOffset) { this.consumerOffset = consumerOffset; } public long getPullOffset() { return pullOffset; } public void setPullOffset(long pullOffset) { this.pullOffset = pullOffset; } public long getLastTimestamp() { return lastTimestamp; } public void setLastTimestamp(long lastTimestamp) { this.lastTimestamp = lastTimestamp; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/RollbackStats.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; public class RollbackStats { private String brokerName; private long queueId; private long brokerOffset; private long consumerOffset; private long timestampOffset; private long rollbackOffset; public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public long getQueueId() { return queueId; } public void setQueueId(long queueId) { this.queueId = queueId; } public long getBrokerOffset() { return brokerOffset; } public void setBrokerOffset(long brokerOffset) { this.brokerOffset = brokerOffset; } public long getConsumerOffset() { return consumerOffset; } public void setConsumerOffset(long consumerOffset) { this.consumerOffset = consumerOffset; } public long getTimestampOffset() { return timestampOffset; } public void setTimestampOffset(long timestampOffset) { this.timestampOffset = timestampOffset; } public long getRollbackOffset() { return rollbackOffset; } public void setRollbackOffset(long rollbackOffset) { this.rollbackOffset = rollbackOffset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicOffset.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; public class TopicOffset { private long minOffset; private long maxOffset; private long lastUpdateTimestamp; public long getMinOffset() { return minOffset; } public void setMinOffset(long minOffset) { this.minOffset = minOffset; } public long getMaxOffset() { return maxOffset; } public void setMaxOffset(long maxOffset) { this.maxOffset = maxOffset; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } @Override public String toString() { return "TopicOffset{" + "minOffset=" + minOffset + ", maxOffset=" + maxOffset + ", lastUpdateTimestamp=" + lastUpdateTimestamp + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicStatsTable extends RemotingSerializable { private double topicPutTps; private Map offsetTable = new ConcurrentHashMap<>(); public Map getOffsetTable() { return offsetTable; } public void setOffsetTable(Map offsetTable) { this.offsetTable = offsetTable; } public double getTopicPutTps() { return topicPutTps; } public void setTopicPutTps(double topicPutTps) { this.topicPutTps = topicPutTps; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/AclInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; public class AclInfo { private String subject; private List policies; public static AclInfo of(String subject, List resources, List actions, List sourceIps, String decision) { AclInfo aclInfo = new AclInfo(); aclInfo.setSubject(subject); PolicyInfo policyInfo = PolicyInfo.of(resources, actions, sourceIps, decision); aclInfo.setPolicies(Collections.singletonList(policyInfo)); return aclInfo; } public static class PolicyInfo { private String policyType; private List entries; public static PolicyInfo of(List resources, List actions, List sourceIps, String decision) { PolicyInfo policyInfo = new PolicyInfo(); List entries = resources.stream() .map(resource -> PolicyEntryInfo.of(resource, actions, sourceIps, decision)) .collect(Collectors.toList()); policyInfo.setEntries(entries); return policyInfo; } public String getPolicyType() { return policyType; } public void setPolicyType(String policyType) { this.policyType = policyType; } public List getEntries() { return entries; } public void setEntries(List entries) { this.entries = entries; } } public static class PolicyEntryInfo { private String resource; private List actions; private List sourceIps; private String decision; public static PolicyEntryInfo of(String resource, List actions, List sourceIps, String decision) { PolicyEntryInfo policyEntryInfo = new PolicyEntryInfo(); policyEntryInfo.setResource(resource); policyEntryInfo.setActions(actions); policyEntryInfo.setSourceIps(sourceIps); policyEntryInfo.setDecision(decision); return policyEntryInfo; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public List getActions() { return actions; } public void setActions(List actions) { this.actions = actions; } public List getSourceIps() { return sourceIps; } public void setSourceIps(List sourceIps) { this.sourceIps = sourceIps; } public String getDecision() { return decision; } public void setDecision(String decision) { this.decision = decision; } } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public List getPolicies() { return policies; } public void setPolicies(List policies) { this.policies = policies; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAck.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.rocketmq.remoting.protocol.BitSetSerializerDeserializer; import java.io.Serializable; import java.util.BitSet; public class BatchAck implements Serializable { @JSONField(name = "c", alternateNames = {"consumerGroup"}) private String consumerGroup; @JSONField(name = "t", alternateNames = {"topic"}) private String topic; @JSONField(name = "r", alternateNames = {"retry"}) private String retry; // "1" if is retry topic @JSONField(name = "so", alternateNames = {"startOffset"}) private long startOffset; @JSONField(name = "q", alternateNames = {"queueId"}) private int queueId; @JSONField(name = "rq", alternateNames = {"reviveQueueId"}) private int reviveQueueId; @JSONField(name = "pt", alternateNames = {"popTime"}) private long popTime; @JSONField(name = "it", alternateNames = {"invisibleTime"}) private long invisibleTime; @JSONField(name = "b", alternateNames = {"bitSet"}, serializeUsing = BitSetSerializerDeserializer.class, deserializeUsing = BitSetSerializerDeserializer.class) private BitSet bitSet; // ack offsets bitSet public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getRetry() { return retry; } public void setRetry(String retry) { this.retry = retry; } public long getStartOffset() { return startOffset; } public void setStartOffset(long startOffset) { this.startOffset = startOffset; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } public int getReviveQueueId() { return reviveQueueId; } public void setReviveQueueId(int reviveQueueId) { this.reviveQueueId = reviveQueueId; } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public BitSet getBitSet() { return bitSet; } public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } @Override public String toString() { return "BatchAck{" + "consumerGroup='" + consumerGroup + '\'' + ", topic='" + topic + '\'' + ", retry='" + retry + '\'' + ", startOffset=" + startOffset + ", queueId=" + queueId + ", reviveQueueId=" + reviveQueueId + ", popTime=" + popTime + ", invisibleTime=" + invisibleTime + ", bitSet=" + bitSet + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAckMessageRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.util.List; public class BatchAckMessageRequestBody extends RemotingSerializable { private String brokerName; private List acks; public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public List getAcks() { return acks; } public void setAcks(List acks) { this.acks = acks; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerMemberGroup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.google.common.base.Objects; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class BrokerMemberGroup extends RemotingSerializable { private String cluster; private String brokerName; private Map brokerAddrs; // Provide default constructor for serializer public BrokerMemberGroup() { this.brokerAddrs = new HashMap<>(); } public BrokerMemberGroup(final String cluster, final String brokerName) { this.cluster = cluster; this.brokerName = brokerName; this.brokerAddrs = new HashMap<>(); } public long minimumBrokerId() { if (this.brokerAddrs.isEmpty()) { return 0; } return Collections.min(brokerAddrs.keySet()); } public String getCluster() { return cluster; } public void setCluster(final String cluster) { this.cluster = cluster; } public String getBrokerName() { return brokerName; } public void setBrokerName(final String brokerName) { this.brokerName = brokerName; } public Map getBrokerAddrs() { return brokerAddrs; } public void setBrokerAddrs(final Map brokerAddrs) { this.brokerAddrs = brokerAddrs; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } BrokerMemberGroup that = (BrokerMemberGroup) o; return Objects.equal(cluster, that.cluster) && Objects.equal(brokerName, that.brokerName) && Objects.equal(brokerAddrs, that.brokerAddrs); } @Override public int hashCode() { return Objects.hashCode(cluster, brokerName, brokerAddrs); } @Override public String toString() { return "BrokerMemberGroup{" + "cluster='" + cluster + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerAddrs=" + brokerAddrs + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerReplicasInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class BrokerReplicasInfo extends RemotingSerializable { private Map replicasInfoTable; public BrokerReplicasInfo() { this.replicasInfoTable = new HashMap<>(); } public void addReplicaInfo(final String brokerName, final ReplicasInfo replicasInfo) { this.replicasInfoTable.put(brokerName, replicasInfo); } public Map getReplicasInfoTable() { return replicasInfoTable; } public void setReplicasInfoTable( Map replicasInfoTable) { this.replicasInfoTable = replicasInfoTable; } public static class ReplicasInfo extends RemotingSerializable { private Long masterBrokerId; private String masterAddress; private Integer masterEpoch; private Integer syncStateSetEpoch; private List inSyncReplicas; private List notInSyncReplicas; public ReplicasInfo(Long masterBrokerId, String masterAddress, int masterEpoch, int syncStateSetEpoch, List inSyncReplicas, List notInSyncReplicas) { this.masterBrokerId = masterBrokerId; this.masterAddress = masterAddress; this.masterEpoch = masterEpoch; this.syncStateSetEpoch = syncStateSetEpoch; this.inSyncReplicas = inSyncReplicas; this.notInSyncReplicas = notInSyncReplicas; } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } public int getMasterEpoch() { return masterEpoch; } public void setMasterEpoch(int masterEpoch) { this.masterEpoch = masterEpoch; } public int getSyncStateSetEpoch() { return syncStateSetEpoch; } public void setSyncStateSetEpoch(int syncStateSetEpoch) { this.syncStateSetEpoch = syncStateSetEpoch; } public List getInSyncReplicas() { return inSyncReplicas; } public void setInSyncReplicas( List inSyncReplicas) { this.inSyncReplicas = inSyncReplicas; } public List getNotInSyncReplicas() { return notInSyncReplicas; } public void setNotInSyncReplicas( List notInSyncReplicas) { this.notInSyncReplicas = notInSyncReplicas; } public void setMasterBrokerId(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; } public Long getMasterBrokerId() { return masterBrokerId; } public boolean isExistInSync(String brokerName, Long brokerId, String brokerAddress) { return this.getInSyncReplicas().contains(new ReplicaIdentity(brokerName, brokerId, brokerAddress)); } public boolean isExistInNotSync(String brokerName, Long brokerId, String brokerAddress) { return this.getNotInSyncReplicas().contains(new ReplicaIdentity(brokerName, brokerId, brokerAddress)); } public boolean isExistInAllReplicas(String brokerName, Long brokerId, String brokerAddress) { return this.isExistInSync(brokerName, brokerId, brokerAddress) || this.isExistInNotSync(brokerName, brokerId, brokerAddress); } } public static class ReplicaIdentity extends RemotingSerializable { private String brokerName; private Long brokerId; private String brokerAddress; private Boolean alive; public ReplicaIdentity(String brokerName, Long brokerId, String brokerAddress) { this.brokerName = brokerName; this.brokerId = brokerId; this.brokerAddress = brokerAddress; this.alive = false; } public ReplicaIdentity(String brokerName, Long brokerId, String brokerAddress, Boolean alive) { this.brokerName = brokerName; this.brokerId = brokerId; this.brokerAddress = brokerAddress; this.alive = alive; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerAddress() { return brokerAddress; } public void setBrokerAddress(String brokerAddress) { this.brokerAddress = brokerAddress; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } public Boolean getAlive() { return alive; } public void setAlive(Boolean alive) { this.alive = alive; } @Override public String toString() { return "ReplicaIdentity{" + "brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + ", brokerAddress='" + brokerAddress + '\'' + ", alive=" + alive + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ReplicaIdentity that = (ReplicaIdentity) o; return brokerName.equals(that.brokerName) && brokerId.equals(that.brokerId) && brokerAddress.equals(that.brokerAddress); } @Override public int hashCode() { return Objects.hash(brokerName, brokerId, brokerAddress); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class BrokerStatsData extends RemotingSerializable { private BrokerStatsItem statsMinute; private BrokerStatsItem statsHour; private BrokerStatsItem statsDay; public BrokerStatsItem getStatsMinute() { return statsMinute; } public void setStatsMinute(BrokerStatsItem statsMinute) { this.statsMinute = statsMinute; } public BrokerStatsItem getStatsHour() { return statsHour; } public void setStatsHour(BrokerStatsItem statsHour) { this.statsHour = statsHour; } public BrokerStatsItem getStatsDay() { return statsDay; } public void setStatsDay(BrokerStatsItem statsDay) { this.statsDay = statsDay; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; public class BrokerStatsItem { private long sum; private double tps; private double avgpt; public long getSum() { return sum; } public void setSum(long sum) { this.sum = sum; } public double getTps() { return tps; } public void setTps(double tps) { this.tps = tps; } public double getAvgpt() { return avgpt; } public void setAvgpt(double avgpt) { this.avgpt = avgpt; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CMResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; public enum CMResult { CR_SUCCESS, CR_LATER, CR_ROLLBACK, CR_COMMIT, CR_THROW_EXCEPTION, CR_RETURN_NULL, } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class CheckClientRequestBody extends RemotingSerializable { private String clientId; private String group; private SubscriptionData subscriptionData; private String namespace; public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public SubscriptionData getSubscriptionData() { return subscriptionData; } public void setSubscriptionData(SubscriptionData subscriptionData) { this.subscriptionData = subscriptionData; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.google.common.base.Objects; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.route.BrokerData; public class ClusterInfo extends RemotingSerializable { private Map brokerAddrTable; private Map> clusterAddrTable; public Map getBrokerAddrTable() { return brokerAddrTable; } public void setBrokerAddrTable(Map brokerAddrTable) { this.brokerAddrTable = brokerAddrTable; } public Map> getClusterAddrTable() { return clusterAddrTable; } public void setClusterAddrTable(Map> clusterAddrTable) { this.clusterAddrTable = clusterAddrTable; } public String[] retrieveAllAddrByCluster(String cluster) { List addrs = new ArrayList<>(); if (clusterAddrTable.containsKey(cluster)) { Set brokerNames = clusterAddrTable.get(cluster); for (String brokerName : brokerNames) { BrokerData brokerData = brokerAddrTable.get(brokerName); if (null != brokerData) { addrs.addAll(brokerData.getBrokerAddrs().values()); } } } return addrs.toArray(new String[] {}); } public String[] retrieveAllClusterNames() { return clusterAddrTable.keySet().toArray(new String[] {}); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClusterInfo info = (ClusterInfo) o; return Objects.equal(brokerAddrTable, info.brokerAddrTable) && Objects.equal(clusterAddrTable, info.clusterAddrTable); } @Override public int hashCode() { return Objects.hashCode(brokerAddrTable, clusterAddrTable); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/Connection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.LanguageCode; public class Connection { private String clientId; private String clientAddr; private LanguageCode language; private int version; public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getClientAddr() { return clientAddr; } public void setClientAddr(String clientAddr) { this.clientAddr = clientAddr; } public LanguageCode getLanguage() { return language; } public void setLanguage(LanguageCode language) { this.language = language; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeByWho.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ConsumeByWho extends RemotingSerializable { private HashSet consumedGroup = new HashSet<>(); private HashSet notConsumedGroup = new HashSet<>(); private String topic; private int queueId; private long offset; public HashSet getConsumedGroup() { return consumedGroup; } public void setConsumedGroup(HashSet consumedGroup) { this.consumedGroup = consumedGroup; } public HashSet getNotConsumedGroup() { return notConsumedGroup; } public void setNotConsumedGroup(HashSet notConsumedGroup) { this.notConsumedGroup = notConsumedGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } public long getOffset() { return offset; } public void setOffset(long offset) { this.offset = offset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ConsumeMessageDirectlyResult extends RemotingSerializable { private boolean order = false; private boolean autoCommit = true; private CMResult consumeResult; private String remark; private long spentTimeMills; public boolean isOrder() { return order; } public void setOrder(boolean order) { this.order = order; } public boolean isAutoCommit() { return autoCommit; } public void setAutoCommit(boolean autoCommit) { this.autoCommit = autoCommit; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public CMResult getConsumeResult() { return consumeResult; } public void setConsumeResult(CMResult consumeResult) { this.consumeResult = consumeResult; } public long getSpentTimeMills() { return spentTimeMills; } public void setSpentTimeMills(long spentTimeMills) { this.spentTimeMills = spentTimeMills; } @Override public String toString() { return "ConsumeMessageDirectlyResult [order=" + order + ", autoCommit=" + autoCommit + ", consumeResult=" + consumeResult + ", remark=" + remark + ", spentTimeMills=" + spentTimeMills + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeQueueData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; public class ConsumeQueueData { private long physicOffset; private int physicSize; private long tagsCode; private String extendDataJson; private String bitMap; private boolean eval; private String msg; public long getPhysicOffset() { return physicOffset; } public void setPhysicOffset(long physicOffset) { this.physicOffset = physicOffset; } public int getPhysicSize() { return physicSize; } public void setPhysicSize(int physicSize) { this.physicSize = physicSize; } public long getTagsCode() { return tagsCode; } public void setTagsCode(long tagsCode) { this.tagsCode = tagsCode; } public String getExtendDataJson() { return extendDataJson; } public void setExtendDataJson(String extendDataJson) { this.extendDataJson = extendDataJson; } public String getBitMap() { return bitMap; } public void setBitMap(String bitMap) { this.bitMap = bitMap; } public boolean isEval() { return eval; } public void setEval(boolean eval) { this.eval = eval; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @Override public String toString() { return "ConsumeQueueData{" + "physicOffset=" + physicOffset + ", physicSize=" + physicSize + ", tagsCode=" + tagsCode + ", extendDataJson='" + extendDataJson + '\'' + ", bitMap='" + bitMap + '\'' + ", eval=" + eval + ", msg='" + msg + '\'' + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsList.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; public class ConsumeStatsList extends RemotingSerializable { private List>> consumeStatsList = new ArrayList<>(); private String brokerAddr; private long totalDiff; private long totalInflightDiff; public List>> getConsumeStatsList() { return consumeStatsList; } public void setConsumeStatsList(List>> consumeStatsList) { this.consumeStatsList = consumeStatsList; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public long getTotalDiff() { return totalDiff; } public void setTotalDiff(long totalDiff) { this.totalDiff = totalDiff; } public long getTotalInflightDiff() { return totalInflightDiff; } public void setTotalInflightDiff(long totalInflightDiff) { this.totalInflightDiff = totalInflightDiff; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; public class ConsumeStatus { private double pullRT; private double pullTPS; private double consumeRT; private double consumeOKTPS; private double consumeFailedTPS; private long consumeFailedMsgs; public double getPullRT() { return pullRT; } public void setPullRT(double pullRT) { this.pullRT = pullRT; } public double getPullTPS() { return pullTPS; } public void setPullTPS(double pullTPS) { this.pullTPS = pullTPS; } public double getConsumeRT() { return consumeRT; } public void setConsumeRT(double consumeRT) { this.consumeRT = consumeRT; } public double getConsumeOKTPS() { return consumeOKTPS; } public void setConsumeOKTPS(double consumeOKTPS) { this.consumeOKTPS = consumeOKTPS; } public double getConsumeFailedTPS() { return consumeFailedTPS; } public void setConsumeFailedTPS(double consumeFailedTPS) { this.consumeFailedTPS = consumeFailedTPS; } public long getConsumeFailedMsgs() { return consumeFailedMsgs; } public void setConsumeFailedMsgs(long consumeFailedMsgs) { this.consumeFailedMsgs = consumeFailedMsgs; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ConsumerConnection extends RemotingSerializable { private HashSet connectionSet = new HashSet<>(); private ConcurrentMap subscriptionTable = new ConcurrentHashMap<>(); private ConsumeType consumeType; private MessageModel messageModel; private ConsumeFromWhere consumeFromWhere; public int computeMinVersion() { int minVersion = Integer.MAX_VALUE; for (Connection c : this.connectionSet) { if (c.getVersion() < minVersion) { minVersion = c.getVersion(); } } return minVersion; } public HashSet getConnectionSet() { return connectionSet; } public void setConnectionSet(HashSet connectionSet) { this.connectionSet = connectionSet; } public ConcurrentMap getSubscriptionTable() { return subscriptionTable; } public void setSubscriptionTable(ConcurrentHashMap subscriptionTable) { this.subscriptionTable = subscriptionTable; } public ConsumeType getConsumeType() { return consumeType; } public void setConsumeType(ConsumeType consumeType) { this.consumeType = consumeType; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public void setConsumeFromWhere(ConsumeFromWhere consumeFromWhere) { this.consumeFromWhere = consumeFromWhere; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerOffsetSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ConsumerOffsetSerializeWrapper extends RemotingSerializable { private ConcurrentMap> offsetTable = new ConcurrentHashMap<>(512); private DataVersion dataVersion; public ConcurrentMap> getOffsetTable() { return offsetTable; } public void setOffsetTable(ConcurrentMap> offsetTable) { this.offsetTable = offsetTable; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Iterator; import java.util.Map.Entry; import java.util.Properties; import java.util.TreeMap; import java.util.TreeSet; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class ConsumerRunningInfo extends RemotingSerializable { public static final String PROP_NAMESERVER_ADDR = "PROP_NAMESERVER_ADDR"; public static final String PROP_THREADPOOL_CORE_SIZE = "PROP_THREADPOOL_CORE_SIZE"; public static final String PROP_CONSUME_ORDERLY = "PROP_CONSUMEORDERLY"; public static final String PROP_CONSUME_TYPE = "PROP_CONSUME_TYPE"; public static final String PROP_CLIENT_VERSION = "PROP_CLIENT_VERSION"; public static final String PROP_CONSUMER_START_TIMESTAMP = "PROP_CONSUMER_START_TIMESTAMP"; private Properties properties = new Properties(); private TreeSet subscriptionSet = new TreeSet<>(); private TreeMap mqTable = new TreeMap<>(); private TreeMap mqPopTable = new TreeMap<>(); private TreeMap statusTable = new TreeMap<>(); private TreeMap userConsumerInfo = new TreeMap<>(); private String jstack; public static boolean analyzeSubscription(final TreeMap criTable) { ConsumerRunningInfo prev = criTable.firstEntry().getValue(); boolean push = isPushType(prev); boolean startForAWhile = false; { String property = prev.getProperties().getProperty(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP); if (property == null) { property = String.valueOf(prev.getProperties().get(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP)); } startForAWhile = (System.currentTimeMillis() - Long.parseLong(property)) > (1000 * 60 * 2); } if (push && startForAWhile) { { Iterator> it = criTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); ConsumerRunningInfo current = next.getValue(); boolean equals = current.getSubscriptionSet().equals(prev.getSubscriptionSet()); if (!equals) { // Different subscription in the same group of consumer return false; } prev = next.getValue(); } // after consumer.unsubscribe , SubscriptionSet is Empty //if (prev != null) { // // if (prev.getSubscriptionSet().isEmpty()) { // // Subscription empty! // return false; // } //} } } return true; } public static boolean isPushType(ConsumerRunningInfo consumerRunningInfo) { String property = consumerRunningInfo.getProperties().getProperty(ConsumerRunningInfo.PROP_CONSUME_TYPE); if (property == null) { property = ((ConsumeType) consumerRunningInfo.getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).name(); } return ConsumeType.valueOf(property) == ConsumeType.CONSUME_PASSIVELY; } public static boolean analyzeRebalance(final TreeMap criTable) { return true; } public static String analyzeProcessQueue(final String clientId, ConsumerRunningInfo info) { StringBuilder sb = new StringBuilder(); boolean push = false; { String property = info.getProperties().getProperty(ConsumerRunningInfo.PROP_CONSUME_TYPE); if (property == null) { property = ((ConsumeType) info.getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).name(); } push = ConsumeType.valueOf(property) == ConsumeType.CONSUME_PASSIVELY; } boolean orderMsg = false; { String property = info.getProperties().getProperty(ConsumerRunningInfo.PROP_CONSUME_ORDERLY); orderMsg = Boolean.parseBoolean(property); } if (push) { Iterator> it = info.getMqTable().entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); MessageQueue mq = next.getKey(); ProcessQueueInfo pq = next.getValue(); if (orderMsg) { if (!pq.isLocked()) { sb.append(String.format("%s %s can't lock for a while, %dms%n", clientId, mq, System.currentTimeMillis() - pq.getLastLockTimestamp())); } else { if (pq.isDroped() && pq.getTryUnlockTimes() > 0) { sb.append(String.format("%s %s unlock %d times, still failed%n", clientId, mq, pq.getTryUnlockTimes())); } } } else { long diff = System.currentTimeMillis() - pq.getLastConsumeTimestamp(); if (diff > (1000 * 60) && pq.getCachedMsgCount() > 0) { sb.append(String.format("%s %s can't consume for a while, maybe blocked, %dms%n", clientId, mq, diff)); } } } } return sb.toString(); } public Properties getProperties() { return properties; } public void setProperties(Properties properties) { this.properties = properties; } public TreeSet getSubscriptionSet() { return subscriptionSet; } public void setSubscriptionSet(TreeSet subscriptionSet) { this.subscriptionSet = subscriptionSet; } public TreeMap getMqTable() { return mqTable; } public void setMqTable(TreeMap mqTable) { this.mqTable = mqTable; } public TreeMap getStatusTable() { return statusTable; } public void setStatusTable(TreeMap statusTable) { this.statusTable = statusTable; } public TreeMap getUserConsumerInfo() { return userConsumerInfo; } public String formatString() { StringBuilder sb = new StringBuilder(); { sb.append("#Consumer Properties#\n"); Iterator> it = this.properties.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String item = String.format("%-40s: %s%n", next.getKey().toString(), next.getValue().toString()); sb.append(item); } } { sb.append("\n\n#Consumer Subscription#\n"); Iterator it = this.subscriptionSet.iterator(); int i = 0; while (it.hasNext()) { SubscriptionData next = it.next(); String item = String.format("%03d Topic: %-40s ClassFilter: %-8s SubExpression: %s%n", ++i, next.getTopic(), next.isClassFilterMode(), next.getSubString()); sb.append(item); } } { sb.append("\n\n#Consumer Offset#\n"); sb.append(String.format("%-64s %-32s %-4s %-20s%n", "#Topic", "#Broker Name", "#QID", "#Consumer Offset" )); Iterator> it = this.mqTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String item = String.format("%-32s %-32s %-4d %-20d%n", next.getKey().getTopic(), next.getKey().getBrokerName(), next.getKey().getQueueId(), next.getValue().getCommitOffset()); sb.append(item); } } { sb.append("\n\n#Consumer MQ Detail#\n"); sb.append(String.format("%-64s %-32s %-4s %-20s%n", "#Topic", "#Broker Name", "#QID", "#ProcessQueueInfo" )); Iterator> it = this.mqTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String item = String.format("%-64s %-32s %-4d %s%n", next.getKey().getTopic(), next.getKey().getBrokerName(), next.getKey().getQueueId(), next.getValue().toString()); sb.append(item); } } { sb.append("\n\n#Consumer Pop Detail#\n"); sb.append(String.format("%-32s %-32s %-4s %-20s%n", "#Topic", "#Broker Name", "#QID", "#ProcessQueueInfo" )); Iterator> it = this.mqPopTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String item = String.format("%-32s %-32s %-4d %s%n", next.getKey().getTopic(), next.getKey().getBrokerName(), next.getKey().getQueueId(), next.getValue().toString()); sb.append(item); } } { sb.append("\n\n#Consumer RT&TPS#\n"); sb.append(String.format("%-64s %14s %14s %14s %14s %18s %25s%n", "#Topic", "#Pull RT", "#Pull TPS", "#Consume RT", "#ConsumeOK TPS", "#ConsumeFailed TPS", "#ConsumeFailedMsgsInHour" )); Iterator> it = this.statusTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String item = String.format("%-32s %14.2f %14.2f %14.2f %14.2f %18.2f %25d%n", next.getKey(), next.getValue().getPullRT(), next.getValue().getPullTPS(), next.getValue().getConsumeRT(), next.getValue().getConsumeOKTPS(), next.getValue().getConsumeFailedTPS(), next.getValue().getConsumeFailedMsgs() ); sb.append(item); } } if (this.userConsumerInfo != null) { sb.append("\n\n#User Consume Info#\n"); Iterator> it = this.userConsumerInfo.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); String item = String.format("%-40s: %s%n", next.getKey(), next.getValue()); sb.append(item); } } if (this.jstack != null) { sb.append("\n\n#Consumer jstack#\n"); sb.append(this.jstack); } return sb.toString(); } public String getJstack() { return jstack; } public void setJstack(String jstack) { this.jstack = jstack; } public TreeMap getMqPopTable() { return mqPopTable; } public void setMqPopTable( TreeMap mqPopTable) { this.mqPopTable = mqPopTable; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CreateTopicListRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class CreateTopicListRequestBody extends RemotingSerializable { @CFNotNull private List topicConfigList; public CreateTopicListRequestBody() {} public CreateTopicListRequestBody(List topicConfigList) { this.topicConfigList = topicConfigList; } public List getTopicConfigList() { return topicConfigList; } public void setTopicConfigList(List topicConfigList) { this.topicConfigList = topicConfigList; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ElectMasterResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.google.common.base.Objects; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.util.HashSet; import java.util.Set; public class ElectMasterResponseBody extends RemotingSerializable { private BrokerMemberGroup brokerMemberGroup; private Set syncStateSet; // Provide default constructor for serializer public ElectMasterResponseBody() { this.syncStateSet = new HashSet(); this.brokerMemberGroup = null; } public ElectMasterResponseBody(final Set syncStateSet) { this.syncStateSet = syncStateSet; this.brokerMemberGroup = null; } public ElectMasterResponseBody(final BrokerMemberGroup brokerMemberGroup, final Set syncStateSet) { this.brokerMemberGroup = brokerMemberGroup; this.syncStateSet = syncStateSet; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ElectMasterResponseBody that = (ElectMasterResponseBody) o; return Objects.equal(brokerMemberGroup, that.brokerMemberGroup) && Objects.equal(syncStateSet, that.syncStateSet); } @Override public int hashCode() { return Objects.hashCode(brokerMemberGroup, syncStateSet); } @Override public String toString() { return "BrokerMemberGroup{" + "brokerMemberGroup='" + brokerMemberGroup.toString() + '\'' + ", syncStateSet='" + syncStateSet.toString() + '}'; } public void setBrokerMemberGroup(BrokerMemberGroup brokerMemberGroup) { this.brokerMemberGroup = brokerMemberGroup; } public BrokerMemberGroup getBrokerMemberGroup() { return brokerMemberGroup; } public void setSyncStateSet(Set syncStateSet) { this.syncStateSet = syncStateSet; } public Set getSyncStateSet() { return syncStateSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/EpochEntryCache.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.remoting.protocol.EpochEntry; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class EpochEntryCache extends RemotingSerializable { private String clusterName; private String brokerName; private long brokerId; private List epochList; private long maxOffset; public EpochEntryCache(String clusterName, String brokerName, long brokerId, List epochList, long maxOffset) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerId = brokerId; this.epochList = epochList; this.maxOffset = maxOffset; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public long getBrokerId() { return brokerId; } public void setBrokerId(long brokerId) { this.brokerId = brokerId; } public List getEpochList() { return this.epochList; } public void setEpochList(List epochList) { this.epochList = epochList; } public long getMaxOffset() { return maxOffset; } public void setMaxOffset(long maxOffset) { this.maxOffset = maxOffset; } @Override public String toString() { return "EpochEntryCache{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + ", epochList=" + epochList + ", maxOffset=" + maxOffset + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetBrokerLiteInfoResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.util.Set; import java.util.Map; public class GetBrokerLiteInfoResponseBody extends RemotingSerializable { private String storeType; private int maxLmqNum; private int currentLmqNum; private int liteSubscriptionCount; private int orderInfoCount; private int cqTableSize; private int offsetTableSize; private int eventMapSize; private Map topicMeta; private Map> groupMeta; public String getStoreType() { return storeType; } public void setStoreType(String storeType) { this.storeType = storeType; } public int getMaxLmqNum() { return maxLmqNum; } public void setMaxLmqNum(int maxLmqNum) { this.maxLmqNum = maxLmqNum; } public int getCurrentLmqNum() { return currentLmqNum; } public void setCurrentLmqNum(int currentLmqNum) { this.currentLmqNum = currentLmqNum; } public int getLiteSubscriptionCount() { return liteSubscriptionCount; } public void setLiteSubscriptionCount(int liteSubscriptionCount) { this.liteSubscriptionCount = liteSubscriptionCount; } public int getOrderInfoCount() { return orderInfoCount; } public void setOrderInfoCount(int orderInfoCount) { this.orderInfoCount = orderInfoCount; } public int getCqTableSize() { return cqTableSize; } public void setCqTableSize(int cqTableSize) { this.cqTableSize = cqTableSize; } public int getOffsetTableSize() { return offsetTableSize; } public void setOffsetTableSize(int offsetTableSize) { this.offsetTableSize = offsetTableSize; } public int getEventMapSize() { return eventMapSize; } public void setEventMapSize(int eventMapSize) { this.eventMapSize = eventMapSize; } public Map getTopicMeta() { return topicMeta; } public void setTopicMeta(Map topicMeta) { this.topicMeta = topicMeta; } public Map> getGroupMeta() { return groupMeta; } public void setGroupMeta(Map> groupMeta) { this.groupMeta = groupMeta; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetBrokerMemberGroupResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class GetBrokerMemberGroupResponseBody extends RemotingSerializable { // Contains the broker member info of the same broker group private BrokerMemberGroup brokerMemberGroup; public BrokerMemberGroup getBrokerMemberGroup() { return brokerMemberGroup; } public void setBrokerMemberGroup(final BrokerMemberGroup brokerMemberGroup) { this.brokerMemberGroup = brokerMemberGroup; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetConsumerStatusBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; @Deprecated public class GetConsumerStatusBody extends RemotingSerializable { private Map messageQueueTable = new HashMap<>(); private Map> consumerTable = new HashMap<>(); public Map getMessageQueueTable() { return messageQueueTable; } public void setMessageQueueTable(Map messageQueueTable) { this.messageQueueTable = messageQueueTable; } public Map> getConsumerTable() { return consumerTable; } public void setConsumerTable(Map> consumerTable) { this.consumerTable = consumerTable; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetLiteClientInfoResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.util.Set; public class GetLiteClientInfoResponseBody extends RemotingSerializable { private String parentTopic; private String group; private String clientId; private long lastAccessTime; private long lastConsumeTime; private int liteTopicCount; private Set liteTopicSet; public String getParentTopic() { return parentTopic; } public void setParentTopic(String parentTopic) { this.parentTopic = parentTopic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public long getLastAccessTime() { return lastAccessTime; } public void setLastAccessTime(long lastAccessTime) { this.lastAccessTime = lastAccessTime; } public long getLastConsumeTime() { return lastConsumeTime; } public void setLastConsumeTime(long lastConsumeTime) { this.lastConsumeTime = lastConsumeTime; } public int getLiteTopicCount() { return liteTopicCount; } public void setLiteTopicCount(int liteTopicCount) { this.liteTopicCount = liteTopicCount; } public Set getLiteTopicSet() { return liteTopicSet; } public void setLiteTopicSet(Set liteTopicSet) { this.liteTopicSet = liteTopicSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetLiteGroupInfoResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.common.lite.LiteLagInfo; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper; public class GetLiteGroupInfoResponseBody extends RemotingSerializable { private String group; private String parentTopic; private String liteTopic; // total log info private long earliestUnconsumedTimestamp = -1; private long totalLagCount; // lite topic detail info private OffsetWrapper liteTopicOffsetWrapper; // if lite topic specified // topK info private List lagCountTopK; private List lagTimestampTopK; public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getParentTopic() { return parentTopic; } public void setParentTopic(String parentTopic) { this.parentTopic = parentTopic; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } public long getEarliestUnconsumedTimestamp() { return earliestUnconsumedTimestamp; } public void setEarliestUnconsumedTimestamp(long earliestUnconsumedTimestamp) { this.earliestUnconsumedTimestamp = earliestUnconsumedTimestamp; } public long getTotalLagCount() { return totalLagCount; } public void setTotalLagCount(long totalLagCount) { this.totalLagCount = totalLagCount; } public OffsetWrapper getLiteTopicOffsetWrapper() { return liteTopicOffsetWrapper; } public void setLiteTopicOffsetWrapper(OffsetWrapper liteTopicOffsetWrapper) { this.liteTopicOffsetWrapper = liteTopicOffsetWrapper; } public List getLagCountTopK() { return lagCountTopK; } public void setLagCountTopK(List lagCountTopK) { this.lagCountTopK = lagCountTopK; } public List getLagTimestampTopK() { return lagTimestampTopK; } public void setLagTimestampTopK(List lagTimestampTopK) { this.lagTimestampTopK = lagTimestampTopK; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetLiteTopicInfoResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.common.entity.ClientGroup; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.admin.TopicOffset; import java.util.Set; public class GetLiteTopicInfoResponseBody extends RemotingSerializable { private String parentTopic; private String liteTopic; private Set subscriber; private TopicOffset topicOffset; private boolean shardingToBroker; public String getParentTopic() { return parentTopic; } public void setParentTopic(String parentTopic) { this.parentTopic = parentTopic; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } public Set getSubscriber() { return subscriber; } public void setSubscriber(Set subscriber) { this.subscriber = subscriber; } public TopicOffset getTopicOffset() { return topicOffset; } public void setTopicOffset(TopicOffset topicOffset) { this.topicOffset = topicOffset; } public boolean isShardingToBroker() { return shardingToBroker; } public void setShardingToBroker(boolean shardingToBroker) { this.shardingToBroker = shardingToBroker; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetParentTopicInfoResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.util.Set; public class GetParentTopicInfoResponseBody extends RemotingSerializable { private String topic; private int ttl; private Set groups; private int lmqNum; private int liteTopicCount; public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public int getTtl() { return ttl; } public void setTtl(int ttl) { this.ttl = ttl; } public Set getGroups() { return groups; } public void setGroups(Set groups) { this.groups = groups; } public int getLmqNum() { return lmqNum; } public void setLmqNum(int lmqNum) { this.lmqNum = lmqNum; } public int getLiteTopicCount() { return liteTopicCount; } public void setLiteTopicCount(int liteTopicCount) { this.liteTopicCount = liteTopicCount; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GroupList.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class GroupList extends RemotingSerializable { private HashSet groupList = new HashSet<>(); public HashSet getGroupList() { return groupList; } public void setGroupList(HashSet groupList) { this.groupList = groupList; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/HARuntimeInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class HARuntimeInfo extends RemotingSerializable { private boolean master; private long masterCommitLogMaxOffset; private int inSyncSlaveNums; private List haConnectionInfo = new ArrayList<>(); private HAClientRuntimeInfo haClientRuntimeInfo = new HAClientRuntimeInfo(); public boolean isMaster() { return this.master; } public void setMaster(boolean master) { this.master = master; } public long getMasterCommitLogMaxOffset() { return this.masterCommitLogMaxOffset; } public void setMasterCommitLogMaxOffset(long masterCommitLogMaxOffset) { this.masterCommitLogMaxOffset = masterCommitLogMaxOffset; } public int getInSyncSlaveNums() { return this.inSyncSlaveNums; } public void setInSyncSlaveNums(int inSyncSlaveNums) { this.inSyncSlaveNums = inSyncSlaveNums; } public List getHaConnectionInfo() { return this.haConnectionInfo; } public void setHaConnectionInfo(List haConnectionInfo) { this.haConnectionInfo = haConnectionInfo; } public HAClientRuntimeInfo getHaClientRuntimeInfo() { return this.haClientRuntimeInfo; } public void setHaClientRuntimeInfo(HAClientRuntimeInfo haClientRuntimeInfo) { this.haClientRuntimeInfo = haClientRuntimeInfo; } public static class HAConnectionRuntimeInfo extends RemotingSerializable { private String addr; private long slaveAckOffset; private long diff; private boolean inSync; private long transferredByteInSecond; private long transferFromWhere; public String getAddr() { return this.addr; } public void setAddr(String addr) { this.addr = addr; } public long getSlaveAckOffset() { return this.slaveAckOffset; } public void setSlaveAckOffset(long slaveAckOffset) { this.slaveAckOffset = slaveAckOffset; } public long getDiff() { return this.diff; } public void setDiff(long diff) { this.diff = diff; } public boolean isInSync() { return this.inSync; } public void setInSync(boolean inSync) { this.inSync = inSync; } public long getTransferredByteInSecond() { return this.transferredByteInSecond; } public void setTransferredByteInSecond(long transferredByteInSecond) { this.transferredByteInSecond = transferredByteInSecond; } public long getTransferFromWhere() { return transferFromWhere; } public void setTransferFromWhere(long transferFromWhere) { this.transferFromWhere = transferFromWhere; } } public static class HAClientRuntimeInfo extends RemotingSerializable { private String masterAddr; private long transferredByteInSecond; private long maxOffset; private long lastReadTimestamp; private long lastWriteTimestamp; private long masterFlushOffset; private boolean isActivated = false; public String getMasterAddr() { return this.masterAddr; } public void setMasterAddr(String masterAddr) { this.masterAddr = masterAddr; } public long getTransferredByteInSecond() { return this.transferredByteInSecond; } public void setTransferredByteInSecond(long transferredByteInSecond) { this.transferredByteInSecond = transferredByteInSecond; } public long getMaxOffset() { return this.maxOffset; } public void setMaxOffset(long maxOffset) { this.maxOffset = maxOffset; } public long getLastReadTimestamp() { return this.lastReadTimestamp; } public void setLastReadTimestamp(long lastReadTimestamp) { this.lastReadTimestamp = lastReadTimestamp; } public long getLastWriteTimestamp() { return this.lastWriteTimestamp; } public void setLastWriteTimestamp(long lastWriteTimestamp) { this.lastWriteTimestamp = lastWriteTimestamp; } public long getMasterFlushOffset() { return masterFlushOffset; } public void setMasterFlushOffset(long masterFlushOffset) { this.masterFlushOffset = masterFlushOffset; } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/KVTable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class KVTable extends RemotingSerializable { private HashMap table = new HashMap<>(); public HashMap getTable() { return table; } public void setTable(HashMap table) { this.table = table; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LiteSubscriptionCtlRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Set; import org.apache.rocketmq.common.lite.LiteSubscriptionDTO; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class LiteSubscriptionCtlRequestBody extends RemotingSerializable { private Set subscriptionSet; public void setSubscriptionSet(Set subscriptionSet) { this.subscriptionSet = subscriptionSet; } public Set getSubscriptionSet() { return subscriptionSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.google.common.base.MoreObjects; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class LockBatchRequestBody extends RemotingSerializable { private String consumerGroup; private String clientId; private boolean onlyThisBroker = false; private Set mqSet = new HashSet<>(); public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public boolean isOnlyThisBroker() { return onlyThisBroker; } public void setOnlyThisBroker(boolean onlyThisBroker) { this.onlyThisBroker = onlyThisBroker; } public Set getMqSet() { return mqSet; } public void setMqSet(Set mqSet) { this.mqSet = mqSet; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("clientId", clientId) .add("onlyThisBroker", onlyThisBroker) .add("mqSet", mqSet) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class LockBatchResponseBody extends RemotingSerializable { private Set lockOKMQSet = new HashSet<>(); public Set getLockOKMQSet() { return lockOKMQSet; } public void setLockOKMQSet(Set lockOKMQSet) { this.lockOKMQSet = lockOKMQSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class MessageRequestModeSerializeWrapper extends RemotingSerializable { private ConcurrentHashMap> messageRequestModeMap = new ConcurrentHashMap<>(); public ConcurrentHashMap> getMessageRequestModeMap() { return messageRequestModeMap; } public void setMessageRequestModeMap(ConcurrentHashMap> messageRequestModeMap) { this.messageRequestModeMap = messageRequestModeMap; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/PopProcessQueueInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; public class PopProcessQueueInfo { private int waitAckCount; private boolean droped; private long lastPopTimestamp; public int getWaitAckCount() { return waitAckCount; } public void setWaitAckCount(int waitAckCount) { this.waitAckCount = waitAckCount; } public boolean isDroped() { return droped; } public void setDroped(boolean droped) { this.droped = droped; } public long getLastPopTimestamp() { return lastPopTimestamp; } public void setLastPopTimestamp(long lastPopTimestamp) { this.lastPopTimestamp = lastPopTimestamp; } @Override public String toString() { return "PopProcessQueueInfo [waitAckCount:" + waitAckCount + ", droped:" + droped + ", lastPopTimestamp:" + lastPopTimestamp + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProcessQueueInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.common.UtilAll; public class ProcessQueueInfo { private long commitOffset; private long cachedMsgMinOffset; private long cachedMsgMaxOffset; private int cachedMsgCount; private int cachedMsgSizeInMiB; private long transactionMsgMinOffset; private long transactionMsgMaxOffset; private int transactionMsgCount; private boolean locked; private long tryUnlockTimes; private long lastLockTimestamp; private boolean droped; private long lastPullTimestamp; private long lastConsumeTimestamp; public long getCommitOffset() { return commitOffset; } public void setCommitOffset(long commitOffset) { this.commitOffset = commitOffset; } public long getCachedMsgMinOffset() { return cachedMsgMinOffset; } public void setCachedMsgMinOffset(long cachedMsgMinOffset) { this.cachedMsgMinOffset = cachedMsgMinOffset; } public long getCachedMsgMaxOffset() { return cachedMsgMaxOffset; } public void setCachedMsgMaxOffset(long cachedMsgMaxOffset) { this.cachedMsgMaxOffset = cachedMsgMaxOffset; } public int getCachedMsgCount() { return cachedMsgCount; } public void setCachedMsgCount(int cachedMsgCount) { this.cachedMsgCount = cachedMsgCount; } public long getTransactionMsgMinOffset() { return transactionMsgMinOffset; } public void setTransactionMsgMinOffset(long transactionMsgMinOffset) { this.transactionMsgMinOffset = transactionMsgMinOffset; } public long getTransactionMsgMaxOffset() { return transactionMsgMaxOffset; } public void setTransactionMsgMaxOffset(long transactionMsgMaxOffset) { this.transactionMsgMaxOffset = transactionMsgMaxOffset; } public int getTransactionMsgCount() { return transactionMsgCount; } public void setTransactionMsgCount(int transactionMsgCount) { this.transactionMsgCount = transactionMsgCount; } public boolean isLocked() { return locked; } public void setLocked(boolean locked) { this.locked = locked; } public long getTryUnlockTimes() { return tryUnlockTimes; } public void setTryUnlockTimes(long tryUnlockTimes) { this.tryUnlockTimes = tryUnlockTimes; } public long getLastLockTimestamp() { return lastLockTimestamp; } public void setLastLockTimestamp(long lastLockTimestamp) { this.lastLockTimestamp = lastLockTimestamp; } public boolean isDroped() { return droped; } public void setDroped(boolean droped) { this.droped = droped; } public long getLastPullTimestamp() { return lastPullTimestamp; } public void setLastPullTimestamp(long lastPullTimestamp) { this.lastPullTimestamp = lastPullTimestamp; } public long getLastConsumeTimestamp() { return lastConsumeTimestamp; } public void setLastConsumeTimestamp(long lastConsumeTimestamp) { this.lastConsumeTimestamp = lastConsumeTimestamp; } public int getCachedMsgSizeInMiB() { return cachedMsgSizeInMiB; } public void setCachedMsgSizeInMiB(final int cachedMsgSizeInMiB) { this.cachedMsgSizeInMiB = cachedMsgSizeInMiB; } @Override public String toString() { return "ProcessQueueInfo [commitOffset=" + commitOffset + ", cachedMsgMinOffset=" + cachedMsgMinOffset + ", cachedMsgMaxOffset=" + cachedMsgMaxOffset + ", cachedMsgCount=" + cachedMsgCount + ", cachedMsgSizeInMiB=" + cachedMsgSizeInMiB + ", transactionMsgMinOffset=" + transactionMsgMinOffset + ", transactionMsgMaxOffset=" + transactionMsgMaxOffset + ", transactionMsgCount=" + transactionMsgCount + ", locked=" + locked + ", tryUnlockTimes=" + tryUnlockTimes + ", lastLockTimestamp=" + UtilAll.timeMillisToHumanString(lastLockTimestamp) + ", droped=" + droped + ", lastPullTimestamp=" + UtilAll.timeMillisToHumanString(lastPullTimestamp) + ", lastConsumeTimestamp=" + UtilAll.timeMillisToHumanString(lastConsumeTimestamp) + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerConnection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ProducerConnection extends RemotingSerializable { private HashSet connectionSet = new HashSet<>(); public HashSet getConnectionSet() { return connectionSet; } public void setConnectionSet(HashSet connectionSet) { this.connectionSet = connectionSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ProducerInfo extends RemotingSerializable { private String clientId; private String remoteIP; private LanguageCode language; private int version; private long lastUpdateTimestamp; public ProducerInfo(String clientId, String remoteIP, LanguageCode language, int version, long lastUpdateTimestamp) { this.clientId = clientId; this.remoteIP = remoteIP; this.language = language; this.version = version; this.lastUpdateTimestamp = lastUpdateTimestamp; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getRemoteIP() { return remoteIP; } public void setRemoteIP(String remoteIP) { this.remoteIP = remoteIP; } public LanguageCode getLanguage() { return language; } public void setLanguage(LanguageCode language) { this.language = language; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public long getLastUpdateTimestamp() { return lastUpdateTimestamp; } public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } @Override public String toString() { return String.format("clientId=%s,remoteIP=%s, language=%s, version=%d, lastUpdateTimestamp=%d", clientId, remoteIP, language.name(), version, lastUpdateTimestamp); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerTableInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ProducerTableInfo extends RemotingSerializable { public ProducerTableInfo(Map> data) { this.data = data; } private Map> data; public Map> getData() { return data; } public void setData(Map> data) { this.data = data; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class QueryAssignmentRequestBody extends RemotingSerializable { private String topic; private String consumerGroup; private String clientId; private String strategyName; private MessageModel messageModel; public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getStrategyName() { return strategyName; } public void setStrategyName(String strategyName) { this.strategyName = strategyName; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueueAssignment; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class QueryAssignmentResponseBody extends RemotingSerializable { private Set messageQueueAssignments; public Set getMessageQueueAssignments() { return messageQueueAssignments; } public void setMessageQueueAssignments( Set messageQueueAssignments) { this.messageQueueAssignments = messageQueueAssignments; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class QueryConsumeQueueResponseBody extends RemotingSerializable { private SubscriptionData subscriptionData; private String filterData; private List queueData; private long maxQueueIndex; private long minQueueIndex; public SubscriptionData getSubscriptionData() { return subscriptionData; } public void setSubscriptionData(SubscriptionData subscriptionData) { this.subscriptionData = subscriptionData; } public String getFilterData() { return filterData; } public void setFilterData(String filterData) { this.filterData = filterData; } public List getQueueData() { return queueData; } public void setQueueData(List queueData) { this.queueData = queueData; } public long getMaxQueueIndex() { return maxQueueIndex; } public void setMaxQueueIndex(long maxQueueIndex) { this.maxQueueIndex = maxQueueIndex; } public long getMinQueueIndex() { return minQueueIndex; } public void setMinQueueIndex(long minQueueIndex) { this.minQueueIndex = minQueueIndex; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeTimeSpanBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class QueryConsumeTimeSpanBody extends RemotingSerializable { List consumeTimeSpanSet = new ArrayList<>(); public List getConsumeTimeSpanSet() { return consumeTimeSpanSet; } public void setConsumeTimeSpanSet(List consumeTimeSpanSet) { this.consumeTimeSpanSet = consumeTimeSpanSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class QueryCorrectionOffsetBody extends RemotingSerializable { private Map correctionOffsets = new HashMap<>(); public Map getCorrectionOffsets() { return correctionOffsets; } public void setCorrectionOffsets(Map correctionOffsets) { this.correctionOffsets = correctionOffsets; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QuerySubscriptionResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class QuerySubscriptionResponseBody extends RemotingSerializable { private SubscriptionData subscriptionData; private String group; private String topic; public SubscriptionData getSubscriptionData() { return subscriptionData; } public void setSubscriptionData(SubscriptionData subscriptionData) { this.subscriptionData = subscriptionData; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueueTimeSpan.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Date; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageQueue; public class QueueTimeSpan { private MessageQueue messageQueue; private long minTimeStamp; private long maxTimeStamp; private long consumeTimeStamp; private long delayTime; public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public long getMinTimeStamp() { return minTimeStamp; } public void setMinTimeStamp(long minTimeStamp) { this.minTimeStamp = minTimeStamp; } public long getMaxTimeStamp() { return maxTimeStamp; } public void setMaxTimeStamp(long maxTimeStamp) { this.maxTimeStamp = maxTimeStamp; } public long getConsumeTimeStamp() { return consumeTimeStamp; } public void setConsumeTimeStamp(long consumeTimeStamp) { this.consumeTimeStamp = consumeTimeStamp; } public String getMinTimeStampStr() { return UtilAll.formatDate(new Date(minTimeStamp), UtilAll.YYYY_MM_DD_HH_MM_SS_SSS); } public String getMaxTimeStampStr() { return UtilAll.formatDate(new Date(maxTimeStamp), UtilAll.YYYY_MM_DD_HH_MM_SS_SSS); } public String getConsumeTimeStampStr() { return UtilAll.formatDate(new Date(consumeTimeStamp), UtilAll.YYYY_MM_DD_HH_MM_SS_SSS); } public long getDelayTime() { return delayTime; } public void setDelayTime(long delayTime) { this.delayTime = delayTime; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RegisterBrokerBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; public class RegisterBrokerBody extends RemotingSerializable { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); private List filterServerList = new ArrayList<>(); private static final long MINIMUM_TAKE_TIME_MILLISECOND = 50; public byte[] encode(boolean compress) { if (!compress) { return super.encode(); } long start = System.currentTimeMillis(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (DeflaterOutputStream outputStream = new DeflaterOutputStream(byteArrayOutputStream, new Deflater(Deflater.BEST_COMPRESSION))) { DataVersion dataVersion = topicConfigSerializeWrapper.getDataVersion(); ConcurrentMap topicConfigTable = cloneTopicConfigTable(topicConfigSerializeWrapper.getTopicConfigTable()); assert topicConfigTable != null; byte[] buffer = dataVersion.encode(); // write data version outputStream.write(convertIntToByteArray(buffer.length)); outputStream.write(buffer); int topicNumber = topicConfigTable.size(); // write number of topic configs outputStream.write(convertIntToByteArray(topicNumber)); // write topic config entry one by one. for (ConcurrentMap.Entry next : topicConfigTable.entrySet()) { buffer = next.getValue().encode().getBytes(MixAll.DEFAULT_CHARSET); outputStream.write(convertIntToByteArray(buffer.length)); outputStream.write(buffer); } buffer = JSON.toJSONString(filterServerList).getBytes(MixAll.DEFAULT_CHARSET); // write filter server list json length outputStream.write(convertIntToByteArray(buffer.length)); // write filter server list json outputStream.write(buffer); //write the topic queue mapping Map topicQueueMappingInfoMap = topicConfigSerializeWrapper.getTopicQueueMappingInfoMap(); if (topicQueueMappingInfoMap == null) { //as the placeholder topicQueueMappingInfoMap = new ConcurrentHashMap<>(); } outputStream.write(convertIntToByteArray(topicQueueMappingInfoMap.size())); for (TopicQueueMappingInfo info: topicQueueMappingInfoMap.values()) { buffer = JSON.toJSONString(info).getBytes(MixAll.DEFAULT_CHARSET); outputStream.write(convertIntToByteArray(buffer.length)); // write filter server list json outputStream.write(buffer); } outputStream.finish(); long takeTime = System.currentTimeMillis() - start; if (takeTime > MINIMUM_TAKE_TIME_MILLISECOND) { LOGGER.info("Compressing takes {}ms", takeTime); } return byteArrayOutputStream.toByteArray(); } catch (IOException e) { LOGGER.error("Failed to compress RegisterBrokerBody object", e); } return null; } public static RegisterBrokerBody decode(byte[] data, boolean compressed, MQVersion.Version brokerVersion) throws IOException { if (!compressed) { return RegisterBrokerBody.decode(data, RegisterBrokerBody.class); } long start = System.currentTimeMillis(); try (InflaterInputStream inflaterInputStream = new InflaterInputStream(new ByteArrayInputStream(data))) { int dataVersionLength = readInt(inflaterInputStream); byte[] dataVersionBytes = readBytes(inflaterInputStream, dataVersionLength); DataVersion dataVersion = DataVersion.decode(dataVersionBytes, DataVersion.class); RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody(); registerBrokerBody.getTopicConfigSerializeWrapper().setDataVersion(dataVersion); ConcurrentMap topicConfigTable = registerBrokerBody.getTopicConfigSerializeWrapper().getTopicConfigTable(); int topicConfigNumber = readInt(inflaterInputStream); LOGGER.debug("{} topic configs to extract", topicConfigNumber); for (int i = 0; i < topicConfigNumber; i++) { int topicConfigJsonLength = readInt(inflaterInputStream); byte[] buffer = readBytes(inflaterInputStream, topicConfigJsonLength); TopicConfig topicConfig = new TopicConfig(); String topicConfigJson = new String(buffer, MixAll.DEFAULT_CHARSET); topicConfig.decode(topicConfigJson); topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } int filterServerListJsonLength = readInt(inflaterInputStream); byte[] filterServerListBuffer = readBytes(inflaterInputStream, filterServerListJsonLength); String filterServerListJson = new String(filterServerListBuffer, MixAll.DEFAULT_CHARSET); List filterServerList = new ArrayList<>(); try { filterServerList = JSON.parseArray(filterServerListJson, String.class); } catch (Exception e) { LOGGER.error("Decompressing occur Exception {}", filterServerListJson, e); } registerBrokerBody.setFilterServerList(filterServerList); if (brokerVersion.ordinal() >= MQVersion.Version.V5_0_0.ordinal()) { int topicQueueMappingNum = readInt(inflaterInputStream); Map topicQueueMappingInfoMap = new ConcurrentHashMap<>(); for (int i = 0; i < topicQueueMappingNum; i++) { int mappingJsonLen = readInt(inflaterInputStream); byte[] buffer = readBytes(inflaterInputStream, mappingJsonLen); TopicQueueMappingInfo info = TopicQueueMappingInfo.decode(buffer, TopicQueueMappingInfo.class); topicQueueMappingInfoMap.put(info.getTopic(), info); } registerBrokerBody.getTopicConfigSerializeWrapper().setTopicQueueMappingInfoMap(topicQueueMappingInfoMap); } long takeTime = System.currentTimeMillis() - start; if (takeTime > MINIMUM_TAKE_TIME_MILLISECOND) { LOGGER.info("Decompressing takes {}ms", takeTime); } return registerBrokerBody; } } private static byte[] convertIntToByteArray(int n) { ByteBuffer byteBuffer = ByteBuffer.allocate(4); byteBuffer.putInt(n); return byteBuffer.array(); } private static byte[] readBytes(InflaterInputStream inflaterInputStream, int length) throws IOException { byte[] buffer = new byte[length]; int bytesRead = 0; while (bytesRead < length) { int len = inflaterInputStream.read(buffer, bytesRead, length - bytesRead); if (len == -1) { throw new IOException("End of compressed data has reached"); } else { bytesRead += len; } } return buffer; } private static int readInt(InflaterInputStream inflaterInputStream) throws IOException { byte[] buffer = readBytes(inflaterInputStream, 4); ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); return byteBuffer.getInt(); } public TopicConfigAndMappingSerializeWrapper getTopicConfigSerializeWrapper() { return topicConfigSerializeWrapper; } public void setTopicConfigSerializeWrapper(TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper) { this.topicConfigSerializeWrapper = topicConfigSerializeWrapper; } public List getFilterServerList() { return filterServerList; } public void setFilterServerList(List filterServerList) { this.filterServerList = filterServerList; } private ConcurrentMap cloneTopicConfigTable( ConcurrentMap topicConfigConcurrentMap) { if (topicConfigConcurrentMap == null) { return null; } ConcurrentHashMap result = new ConcurrentHashMap<>(topicConfigConcurrentMap.size()); result.putAll(topicConfigConcurrentMap); return result; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ResetOffsetBody extends RemotingSerializable { private Map offsetTable; public ResetOffsetBody() { offsetTable = new HashMap<>(); } public Map getOffsetTable() { return offsetTable; } public void setOffsetTable(Map offsetTable) { this.offsetTable = offsetTable; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyForC.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.common.message.MessageQueueForC; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class ResetOffsetBodyForC extends RemotingSerializable { private List offsetTable; public List getOffsetTable() { return offsetTable; } public void setOffsetTable(List offsetTable) { this.offsetTable = offsetTable; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RoleChangeNotifyEntry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import java.util.Set; public class RoleChangeNotifyEntry { private final BrokerMemberGroup brokerMemberGroup; private final String masterAddress; private final Long masterBrokerId; private final int masterEpoch; private final int syncStateSetEpoch; private final Set syncStateSet; public RoleChangeNotifyEntry(BrokerMemberGroup brokerMemberGroup, String masterAddress, Long masterBrokerId, int masterEpoch, int syncStateSetEpoch, Set syncStateSet) { this.brokerMemberGroup = brokerMemberGroup; this.masterAddress = masterAddress; this.masterEpoch = masterEpoch; this.syncStateSetEpoch = syncStateSetEpoch; this.masterBrokerId = masterBrokerId; this.syncStateSet = syncStateSet; } public static RoleChangeNotifyEntry convert(RemotingCommand electMasterResponse) { final ElectMasterResponseHeader header = (ElectMasterResponseHeader) electMasterResponse.readCustomHeader(); BrokerMemberGroup brokerMemberGroup = null; Set syncStateSet = null; if (electMasterResponse.getBody() != null && electMasterResponse.getBody().length > 0) { ElectMasterResponseBody body = RemotingSerializable.decode(electMasterResponse.getBody(), ElectMasterResponseBody.class); brokerMemberGroup = body.getBrokerMemberGroup(); syncStateSet = body.getSyncStateSet(); } return new RoleChangeNotifyEntry(brokerMemberGroup, header.getMasterAddress(), header.getMasterBrokerId(), header.getMasterEpoch(), header.getSyncStateSetEpoch(), syncStateSet); } public BrokerMemberGroup getBrokerMemberGroup() { return brokerMemberGroup; } public String getMasterAddress() { return masterAddress; } public int getMasterEpoch() { return masterEpoch; } public int getSyncStateSetEpoch() { return syncStateSetEpoch; } public Long getMasterBrokerId() { return masterBrokerId; } public Set getSyncStateSet() { return syncStateSet; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SetMessageRequestModeRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class SetMessageRequestModeRequestBody extends RemotingSerializable { private String topic; private String consumerGroup; private MessageRequestMode mode = MessageRequestMode.PULL; /* consumer working in pop mode could share the MessageQueues assigned to the N (N = popShareQueueNum) consumers following it in the cid list */ private int popShareQueueNum = 0; public SetMessageRequestModeRequestBody() { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public MessageRequestMode getMode() { return mode; } public void setMode(MessageRequestMode mode) { this.mode = mode; } public int getPopShareQueueNum() { return popShareQueueNum; } public void setPopShareQueueNum(int popShareQueueNum) { this.popShareQueueNum = popShareQueueNum; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupList.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class SubscriptionGroupList extends RemotingSerializable { @CFNotNull private List groupConfigList; public SubscriptionGroupList() {} public SubscriptionGroupList(List groupConfigList) { this.groupConfigList = groupConfigList; } public List getGroupConfigList() { return groupConfigList; } public void setGroupConfigList(List groupConfigList) { this.groupConfigList = groupConfigList; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class SubscriptionGroupWrapper extends RemotingSerializable { private ConcurrentMap subscriptionGroupTable = new ConcurrentHashMap<>(1024); private ConcurrentMap> forbiddenTable = new ConcurrentHashMap<>(1024); private DataVersion dataVersion = new DataVersion(); public ConcurrentMap getSubscriptionGroupTable() { return subscriptionGroupTable; } public void setSubscriptionGroupTable( ConcurrentMap subscriptionGroupTable) { this.subscriptionGroupTable = subscriptionGroupTable; } public ConcurrentMap> getForbiddenTable() { return forbiddenTable; } public void setForbiddenTable(ConcurrentMap> forbiddenTable) { this.forbiddenTable = forbiddenTable; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SyncStateSet.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class SyncStateSet extends RemotingSerializable { private Set syncStateSet; private int syncStateSetEpoch; public SyncStateSet(Set syncStateSet, int syncStateSetEpoch) { this.syncStateSet = new HashSet<>(syncStateSet); this.syncStateSetEpoch = syncStateSetEpoch; } public Set getSyncStateSet() { return new HashSet<>(syncStateSet); } public void setSyncStateSet(Set syncStateSet) { this.syncStateSet = new HashSet<>(syncStateSet); } public int getSyncStateSetEpoch() { return syncStateSetEpoch; } public void setSyncStateSetEpoch(int syncStateSetEpoch) { this.syncStateSetEpoch = syncStateSetEpoch; } @Override public String toString() { return "SyncStateSet{" + "syncStateSet=" + syncStateSet + ", syncStateSetEpoch=" + syncStateSetEpoch + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigAndMappingSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; public class TopicConfigAndMappingSerializeWrapper extends TopicConfigSerializeWrapper { private Map topicQueueMappingInfoMap = new ConcurrentHashMap<>(); private Map topicQueueMappingDetailMap = new ConcurrentHashMap<>(); private DataVersion mappingDataVersion = new DataVersion(); public Map getTopicQueueMappingInfoMap() { return topicQueueMappingInfoMap; } public void setTopicQueueMappingInfoMap(Map topicQueueMappingInfoMap) { this.topicQueueMappingInfoMap = topicQueueMappingInfoMap; } public Map getTopicQueueMappingDetailMap() { return topicQueueMappingDetailMap; } public void setTopicQueueMappingDetailMap(Map topicQueueMappingDetailMap) { this.topicQueueMappingDetailMap = topicQueueMappingDetailMap; } public DataVersion getMappingDataVersion() { return mappingDataVersion; } public void setMappingDataVersion(DataVersion mappingDataVersion) { this.mappingDataVersion = mappingDataVersion; } public static TopicConfigAndMappingSerializeWrapper from(TopicConfigSerializeWrapper wrapper) { if (wrapper instanceof TopicConfigAndMappingSerializeWrapper) { return (TopicConfigAndMappingSerializeWrapper) wrapper; } TopicConfigAndMappingSerializeWrapper mappingSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); mappingSerializeWrapper.setDataVersion(wrapper.getDataVersion()); mappingSerializeWrapper.setTopicConfigTable(wrapper.getTopicConfigTable()); return mappingSerializeWrapper; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicConfigSerializeWrapper extends RemotingSerializable { private ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); private DataVersion dataVersion = new DataVersion(); public ConcurrentMap getTopicConfigTable() { return topicConfigTable; } public void setTopicConfigTable(ConcurrentMap topicConfigTable) { this.topicConfigTable = topicConfigTable; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicList.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicList extends RemotingSerializable { private Set topicList = ConcurrentHashMap.newKeySet(); private String brokerAddr; public Set getTopicList() { return topicList; } public void setTopicList(Set topicList) { this.topicList = topicList; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicQueueMappingSerializeWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Map; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail; public class TopicQueueMappingSerializeWrapper extends RemotingSerializable { private Map topicQueueMappingInfoMap; private DataVersion dataVersion = new DataVersion(); public Map getTopicQueueMappingInfoMap() { return topicQueueMappingInfoMap; } public void setTopicQueueMappingInfoMap(Map topicQueueMappingInfoMap) { this.topicQueueMappingInfoMap = topicQueueMappingInfoMap; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/UnlockBatchRequestBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.google.common.base.MoreObjects; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class UnlockBatchRequestBody extends RemotingSerializable { private String consumerGroup; private String clientId; private boolean onlyThisBroker = false; private Set mqSet = new HashSet<>(); public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public boolean isOnlyThisBroker() { return onlyThisBroker; } public void setOnlyThisBroker(boolean onlyThisBroker) { this.onlyThisBroker = onlyThisBroker; } public Set getMqSet() { return mqSet; } public void setMqSet(Set mqSet) { this.mqSet = mqSet; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("clientId", clientId) .add("onlyThisBroker", onlyThisBroker) .add("mqSet", mqSet) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/UserInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; public class UserInfo { private String username; private String password; private String userType; private String userStatus; public static UserInfo of(String username, String password, String userType) { UserInfo userInfo = new UserInfo(); userInfo.setUsername(username); userInfo.setPassword(password); userInfo.setUserType(userType); return userInfo; } public static UserInfo of(String username, String password, String userType, String userStatus) { UserInfo userInfo = new UserInfo(); userInfo.setUsername(username); userInfo.setPassword(password); userInfo.setUserType(userType); userInfo.setUserStatus(userStatus); return userInfo; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUserType() { return userType; } public void setUserType(String userType) { this.userType = userType; } public String getUserStatus() { return userStatus; } public void setUserStatus(String userStatus) { this.userStatus = userStatus; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPI.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.filter; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import java.util.Arrays; public class FilterAPI { public static SubscriptionData buildSubscriptionData(String topic, String subString) throws Exception { final SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setSubString(subString); if (StringUtils.isEmpty(subString) || subString.equals(SubscriptionData.SUB_ALL)) { subscriptionData.setSubString(SubscriptionData.SUB_ALL); return subscriptionData; } String[] tags = subString.split("\\|\\|"); if (tags.length > 0) { Arrays.stream(tags).map(String::trim).filter(tag -> !tag.isEmpty()).forEach(tag -> { subscriptionData.getTagsSet().add(tag); subscriptionData.getCodeSet().add(tag.hashCode()); }); } else { throw new Exception("subString split error"); } return subscriptionData; } public static SubscriptionData buildSubscriptionData(String topic, String subString, String expressionType) throws Exception { final SubscriptionData subscriptionData = buildSubscriptionData(topic, subString); if (StringUtils.isNotBlank(expressionType)) { subscriptionData.setExpressionType(expressionType); } return subscriptionData; } public static SubscriptionData build(final String topic, final String subString, final String type) throws Exception { if (ExpressionType.TAG.equals(type) || type == null) { return buildSubscriptionData(topic, subString); } if (StringUtils.isEmpty(subString)) { throw new IllegalArgumentException("Expression can't be null! " + type); } SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setSubString(subString); subscriptionData.setExpressionType(type); return subscriptionData; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AckMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.ACK_MESSAGE, action = Action.SUB) public class AckMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; @CFNotNull private String extraInfo; @CFNotNull private Long offset; private String liteTopic; @Override public void checkFields() throws RemotingCommandException { } public void setOffset(Long offset) { this.offset = offset; } public Long getOffset() { return offset; } public String getConsumerGroup() { return consumerGroup; } public void setExtraInfo(String extraInfo) { this.extraInfo = extraInfo; } public String getExtraInfo() { return extraInfo; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("queueId", queueId) .add("extraInfo", extraInfo) .add("offset", offset) .add("liteTopic", liteTopic) .omitNullValues() .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AddBrokerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.ADD_BROKER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class AddBrokerRequestHeader implements CommandCustomHeader { @CFNullable private String configPath; @Override public void checkFields() throws RemotingCommandException { } public String getConfigPath() { return configPath; } public void setConfigPath(String configPath) { this.configPath = configPath; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.CHANGE_MESSAGE_INVISIBLETIME, action = Action.SUB) public class ChangeInvisibleTimeRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; /** * startOffset popTime invisibleTime queueId */ @CFNotNull private String extraInfo; @CFNotNull private Long offset; @CFNotNull private Long invisibleTime; private String liteTopic; private boolean suspend = false; @Override public void checkFields() throws RemotingCommandException { } public void setOffset(Long offset) { this.offset = offset; } public Long getOffset() { return offset; } public Long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(Long invisibleTime) { this.invisibleTime = invisibleTime; } public String getConsumerGroup() { return consumerGroup; } public void setExtraInfo(String extraInfo) { this.extraInfo = extraInfo; } /** * startOffset popTime invisibleTime queueId */ public String getExtraInfo() { return extraInfo; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } public boolean isSuspend() { return suspend; } public void setSuspend(boolean suspend) { this.suspend = suspend; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("queueId", queueId) .add("extraInfo", extraInfo) .add("offset", offset) .add("invisibleTime", invisibleTime) .add("liteTopic", liteTopic) .add("suspend", suspend) .omitNullValues() .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class ChangeInvisibleTimeResponseHeader implements CommandCustomHeader { @CFNotNull private long popTime; @CFNotNull private long invisibleTime; @CFNotNull private int reviveQid; @Override public void checkFields() throws RemotingCommandException { } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public int getReviveQid() { return reviveQid; } public void setReviveQid(int reviveQid) { this.reviveQid = reviveQid; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckRocksdbCqWriteProgressRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CHECK_ROCKSDB_CQ_WRITE_PROGRESS, action = Action.GET) public class CheckRocksdbCqWriteProgressRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; private long checkStoreTime; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public long getCheckStoreTime() { return checkStoreTime; } public void setCheckStoreTime(long checkStoreTime) { this.checkStoreTime = checkStoreTime; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: EndTransactionRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.CHECK_TRANSACTION_STATE, action = Action.PUB) public class CheckTransactionStateRequestHeader extends RpcRequestHeader { @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Long tranStateTableOffset; @CFNotNull private Long commitLogOffset; private String msgId; private String transactionId; private String offsetMsgId; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Long getTranStateTableOffset() { return tranStateTableOffset; } public void setTranStateTableOffset(Long tranStateTableOffset) { this.tranStateTableOffset = tranStateTableOffset; } public Long getCommitLogOffset() { return commitLogOffset; } public void setCommitLogOffset(Long commitLogOffset) { this.commitLogOffset = commitLogOffset; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } public String getOffsetMsgId() { return offsetMsgId; } public void setOffsetMsgId(String offsetMsgId) { this.offsetMsgId = offsetMsgId; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("tranStateTableOffset", tranStateTableOffset) .add("commitLogOffset", commitLogOffset) .add("msgId", msgId) .add("transactionId", transactionId) .add("offsetMsgId", offsetMsgId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: EndTransactionResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class CheckTransactionStateResponseHeader implements CommandCustomHeader { @CFNotNull private String producerGroup; @CFNotNull private Long tranStateTableOffset; @CFNotNull private Long commitLogOffset; @CFNotNull private Integer commitOrRollback; // TRANSACTION_COMMIT_TYPE // TRANSACTION_ROLLBACK_TYPE @Override public void checkFields() throws RemotingCommandException { if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == this.commitOrRollback) { return; } if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == this.commitOrRollback) { return; } throw new RemotingCommandException("commitOrRollback field wrong"); } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public Long getTranStateTableOffset() { return tranStateTableOffset; } public void setTranStateTableOffset(Long tranStateTableOffset) { this.tranStateTableOffset = tranStateTableOffset; } public Long getCommitLogOffset() { return commitLogOffset; } public void setCommitLogOffset(Long commitLogOffset) { this.commitLogOffset = commitLogOffset; } public Integer getCommitOrRollback() { return commitOrRollback; } public void setCommitOrRollback(Integer commitOrRollback) { this.commitOrRollback = commitOrRollback; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CloneGroupOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: DeleteTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.CLONE_GROUP_OFFSET, action = Action.UPDATE) public class CloneGroupOffsetRequestHeader extends RpcRequestHeader { @CFNotNull private String srcGroup; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String destGroup; @RocketMQResource(ResourceType.TOPIC) private String topic; private boolean offline; @Override public void checkFields() throws RemotingCommandException { } public String getDestGroup() { return destGroup; } public void setDestGroup(String destGroup) { this.destGroup = destGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getSrcGroup() { return srcGroup; } public void setSrcGroup(String srcGroup) { this.srcGroup = srcGroup; } public boolean isOffline() { return offline; } public void setOffline(boolean offline) { this.offline = offline; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("srcGroup", srcGroup) .add("destGroup", destGroup) .add("topic", topic) .add("offline", offline) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.CONSUME_MESSAGE_DIRECTLY, action = Action.SUB) public class ConsumeMessageDirectlyResultRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNullable private String clientId; @CFNullable private String msgId; @CFNullable private String brokerName; @CFNullable @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNullable private Integer topicSysFlag; @CFNullable private Integer groupSysFlag; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getTopicSysFlag() { return topicSysFlag; } public void setTopicSysFlag(Integer topicSysFlag) { this.topicSysFlag = topicSysFlag; } public Integer getGroupSysFlag() { return groupSysFlag; } public void setGroupSysFlag(Integer groupSysFlag) { this.groupSysFlag = groupSysFlag; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("clientId", clientId) .add("msgId", msgId) .add("brokerName", brokerName) .add("topic", topic) .add("topicSysFlag", topicSysFlag) .add("groupSysFlag", groupSysFlag) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumerSendMsgBackRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.CONSUMER_SEND_MSG_BACK, action = Action.SUB) public class ConsumerSendMsgBackRequestHeader extends RpcRequestHeader { @CFNotNull private Long offset; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @CFNotNull private Integer delayLevel; private String originMsgId; @RocketMQResource(ResourceType.TOPIC) private String originTopic; @CFNullable private boolean unitMode = false; private Integer maxReconsumeTimes; @Override public void checkFields() throws RemotingCommandException { } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public Integer getDelayLevel() { return delayLevel; } public void setDelayLevel(Integer delayLevel) { this.delayLevel = delayLevel; } public String getOriginMsgId() { return originMsgId; } public void setOriginMsgId(String originMsgId) { this.originMsgId = originMsgId; } public String getOriginTopic() { return originTopic; } public void setOriginTopic(String originTopic) { this.originTopic = originTopic; } public boolean isUnitMode() { return unitMode; } public void setUnitMode(boolean unitMode) { this.unitMode = unitMode; } public Integer getMaxReconsumeTimes() { return maxReconsumeTimes; } public void setMaxReconsumeTimes(final Integer maxReconsumeTimes) { this.maxReconsumeTimes = maxReconsumeTimes; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("offset", offset) .add("group", group) .add("delayLevel", delayLevel) .add("originMsgId", originMsgId) .add("originTopic", originTopic) .add("unitMode", unitMode) .add("maxReconsumeTimes", maxReconsumeTimes) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateAclRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_CREATE_ACL, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class CreateAclRequestHeader implements CommandCustomHeader { private String subject; public CreateAclRequestHeader() { } public CreateAclRequestHeader(String subject) { this.subject = subject; } @Override public void checkFields() throws RemotingCommandException { } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateTopicListRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.UPDATE_AND_CREATE_TOPIC_LIST, action = Action.CREATE) public class CreateTopicListRequestHeader extends RpcRequestHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateTopicRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: CreateTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.UPDATE_AND_CREATE_TOPIC, action = Action.CREATE) public class CreateTopicRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private String defaultTopic; @CFNotNull private Integer readQueueNums; @CFNotNull private Integer writeQueueNums; @CFNotNull private Integer perm; @CFNotNull private String topicFilterType; private Integer topicSysFlag; @CFNotNull private Boolean order = false; private String attributes; @CFNullable private Boolean force = false; @Override public void checkFields() throws RemotingCommandException { try { TopicFilterType.valueOf(this.topicFilterType); } catch (Exception e) { throw new RemotingCommandException("topicFilterType = [" + topicFilterType + "] value invalid", e); } } public TopicFilterType getTopicFilterTypeEnum() { return TopicFilterType.valueOf(this.topicFilterType); } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getDefaultTopic() { return defaultTopic; } public void setDefaultTopic(String defaultTopic) { this.defaultTopic = defaultTopic; } public Integer getReadQueueNums() { return readQueueNums; } public void setReadQueueNums(Integer readQueueNums) { this.readQueueNums = readQueueNums; } public Integer getWriteQueueNums() { return writeQueueNums; } public void setWriteQueueNums(Integer writeQueueNums) { this.writeQueueNums = writeQueueNums; } public Integer getPerm() { return perm; } public void setPerm(Integer perm) { this.perm = perm; } public String getTopicFilterType() { return topicFilterType; } public void setTopicFilterType(String topicFilterType) { this.topicFilterType = topicFilterType; } public Integer getTopicSysFlag() { return topicSysFlag; } public void setTopicSysFlag(Integer topicSysFlag) { this.topicSysFlag = topicSysFlag; } public Boolean getOrder() { return order; } public void setOrder(Boolean order) { this.order = order; } public Boolean getForce() { return force; } public void setForce(Boolean force) { this.force = force; } public String getAttributes() { return attributes; } public void setAttributes(String attributes) { this.attributes = attributes; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("defaultTopic", defaultTopic) .add("readQueueNums", readQueueNums) .add("writeQueueNums", writeQueueNums) .add("perm", perm) .add("topicFilterType", topicFilterType) .add("topicSysFlag", topicSysFlag) .add("order", order) .add("attributes", attributes) .add("force", force) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateUserRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_CREATE_USER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class CreateUserRequestHeader implements CommandCustomHeader { private String username; public CreateUserRequestHeader() { } public CreateUserRequestHeader(String username) { this.username = username; } @Override public void checkFields() throws RemotingCommandException { } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteAclRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_DELETE_ACL, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class DeleteAclRequestHeader implements CommandCustomHeader { private String subject; private String policyType; private String resource; public DeleteAclRequestHeader() { } public DeleteAclRequestHeader(String subject, String resource) { this.subject = subject; this.resource = resource; } @Override public void checkFields() throws RemotingCommandException { } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getPolicyType() { return policyType; } public void setPolicyType(String policyType) { this.policyType = policyType; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteSubscriptionGroupRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.DELETE_SUBSCRIPTIONGROUP, action = Action.DELETE) public class DeleteSubscriptionGroupRequestHeader extends RpcRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String groupName; private boolean cleanOffset = false; @Override public void checkFields() throws RemotingCommandException { } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public boolean isCleanOffset() { return cleanOffset; } public void setCleanOffset(boolean cleanOffset) { this.cleanOffset = cleanOffset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteTopicRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: DeleteTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.DELETE_TOPIC_IN_BROKER, action = Action.DELETE) public class DeleteTopicRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteUserRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_DELETE_USER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class DeleteUserRequestHeader implements CommandCustomHeader { private String username; public DeleteUserRequestHeader() { } public DeleteUserRequestHeader(String username) { this.username = username; } @Override public void checkFields() throws RemotingCommandException { } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.END_TRANSACTION, action = Action.PUB) public class EndTransactionRequestHeader extends RpcRequestHeader { @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private String producerGroup; @CFNotNull private Long tranStateTableOffset; @CFNotNull private Long commitLogOffset; @CFNotNull private Integer commitOrRollback; // TRANSACTION_COMMIT_TYPE // TRANSACTION_ROLLBACK_TYPE // TRANSACTION_NOT_TYPE @CFNullable private Boolean fromTransactionCheck = false; @CFNotNull private String msgId; private String transactionId; @Override public void checkFields() throws RemotingCommandException { if (MessageSysFlag.TRANSACTION_NOT_TYPE == this.commitOrRollback) { return; } if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == this.commitOrRollback) { return; } if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == this.commitOrRollback) { return; } throw new RemotingCommandException("commitOrRollback field wrong"); } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public Long getTranStateTableOffset() { return tranStateTableOffset; } public void setTranStateTableOffset(Long tranStateTableOffset) { this.tranStateTableOffset = tranStateTableOffset; } public Long getCommitLogOffset() { return commitLogOffset; } public void setCommitLogOffset(Long commitLogOffset) { this.commitLogOffset = commitLogOffset; } public Integer getCommitOrRollback() { return commitOrRollback; } public void setCommitOrRollback(Integer commitOrRollback) { this.commitOrRollback = commitOrRollback; } public Boolean getFromTransactionCheck() { return fromTransactionCheck; } public void setFromTransactionCheck(Boolean fromTransactionCheck) { this.fromTransactionCheck = fromTransactionCheck; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("producerGroup", producerGroup) .add("tranStateTableOffset", tranStateTableOffset) .add("commitLogOffset", commitLogOffset) .add("commitOrRollback", commitOrRollback) .add("fromTransactionCheck", fromTransactionCheck) .add("msgId", msgId) .add("transactionId", transactionId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: EndTransactionResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class EndTransactionResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.EXCHANGE_BROKER_HA_INFO,resource = ResourceType.CLUSTER, action = Action.UPDATE) public class ExchangeHAInfoRequestHeader implements CommandCustomHeader { @CFNullable public String masterHaAddress; @CFNullable public Long masterFlushOffset; @CFNullable public String masterAddress; @Override public void checkFields() throws RemotingCommandException { } public String getMasterHaAddress() { return masterHaAddress; } public void setMasterHaAddress(String masterHaAddress) { this.masterHaAddress = masterHaAddress; } public Long getMasterFlushOffset() { return masterFlushOffset; } public void setMasterFlushOffset(Long masterFlushOffset) { this.masterFlushOffset = masterFlushOffset; } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class ExchangeHAInfoResponseHeader implements CommandCustomHeader { @CFNullable public String masterHaAddress; @CFNullable public Long masterFlushOffset; @CFNullable public String masterAddress; @Override public void checkFields() throws RemotingCommandException { } public String getMasterHaAddress() { return masterHaAddress; } public void setMasterHaAddress(String masterHaAddress) { this.masterHaAddress = masterHaAddress; } public Long getMasterFlushOffset() { return masterFlushOffset; } public void setMasterFlushOffset(Long masterFlushOffset) { this.masterFlushOffset = masterFlushOffset; } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExportRocksDBConfigToJsonRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.EXPORT_ROCKSDB_CONFIG_TO_JSON, resource = ResourceType.CLUSTER, action = Action.GET) public class ExportRocksDBConfigToJsonRequestHeader implements CommandCustomHeader { private static final String CONFIG_TYPE_SEPARATOR = ";"; public enum ConfigType { TOPICS("topics"), SUBSCRIPTION_GROUPS("subscriptionGroups"), CONSUMER_OFFSETS("consumerOffsets"); private final String typeName; ConfigType(String typeName) { this.typeName = typeName; } public static ConfigType getConfigTypeByName(String typeName) { for (ConfigType configType : ConfigType.values()) { if (configType.getTypeName().equalsIgnoreCase(typeName.trim())) { return configType; } } throw new IllegalArgumentException("Unknown config type: " + typeName); } public static List fromString(String ordinal) { String[] configTypeNames = StringUtils.split(ordinal, CONFIG_TYPE_SEPARATOR); List configTypes = new ArrayList<>(); for (String configTypeName : configTypeNames) { if (StringUtils.isNotEmpty(configTypeName)) { configTypes.add(getConfigTypeByName(configTypeName)); } } return configTypes; } public static String toString(List configTypes) { StringBuilder sb = new StringBuilder(); for (ConfigType configType : configTypes) { sb.append(configType.getTypeName()).append(CONFIG_TYPE_SEPARATOR); } return sb.toString(); } public String getTypeName() { return typeName; } } @CFNotNull private String configType; @Override public void checkFields() throws RemotingCommandException { } public List fetchConfigType() { return ConfigType.fromString(configType); } public void updateConfigType(List configType) { this.configType = ConfigType.toString(configType); } public String getConfigType() { return configType; } public void setConfigType(String configType) { this.configType = configType; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; public class ExtraInfoUtil { private static final String NORMAL_TOPIC = "0"; private static final String RETRY_TOPIC = "1"; private static final String RETRY_TOPIC_V2 = "2"; private static final String QUEUE_OFFSET = "qo"; public static String[] split(String extraInfo) { if (extraInfo == null) { throw new IllegalArgumentException("split extraInfo is null"); } return extraInfo.split(MessageConst.KEY_SEPARATOR); } public static Long getCkQueueOffset(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 1) { throw new IllegalArgumentException("getCkQueueOffset fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return Long.valueOf(extraInfoStrs[0]); } public static Long getPopTime(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 2) { throw new IllegalArgumentException("getPopTime fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return Long.valueOf(extraInfoStrs[1]); } public static Long getInvisibleTime(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 3) { throw new IllegalArgumentException("getInvisibleTime fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return Long.valueOf(extraInfoStrs[2]); } public static int getReviveQid(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 4) { throw new IllegalArgumentException("getReviveQid fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return Integer.parseInt(extraInfoStrs[3]); } public static String getRealTopic(String[] extraInfoStrs, String topic, String cid) { if (extraInfoStrs == null || extraInfoStrs.length < 5) { throw new IllegalArgumentException("getRealTopic fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } if (RETRY_TOPIC.equals(extraInfoStrs[4])) { return KeyBuilder.buildPopRetryTopicV1(topic, cid); } else if (RETRY_TOPIC_V2.equals(extraInfoStrs[4])) { return KeyBuilder.buildPopRetryTopicV2(topic, cid); } else { return topic; } } public static String getRealTopic(String topic, String cid, String retry) { if (retry.equals(NORMAL_TOPIC)) { return topic; } else if (retry.equals(RETRY_TOPIC)) { return KeyBuilder.buildPopRetryTopicV1(topic, cid); } else if (retry.equals(RETRY_TOPIC_V2)) { return KeyBuilder.buildPopRetryTopicV2(topic, cid); } else { throw new IllegalArgumentException("getRetry fail, format is wrong"); } } public static String getRetry(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 5) { throw new IllegalArgumentException("getRetry fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return extraInfoStrs[4]; } public static String getBrokerName(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 6) { throw new IllegalArgumentException("getBrokerName fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return extraInfoStrs[5]; } public static int getQueueId(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 7) { throw new IllegalArgumentException("getQueueId fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return Integer.parseInt(extraInfoStrs[6]); } public static long getQueueOffset(String[] extraInfoStrs) { if (extraInfoStrs == null || extraInfoStrs.length < 8) { throw new IllegalArgumentException("getQueueOffset fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); } return Long.parseLong(extraInfoStrs[7]); } public static String buildExtraInfo(long ckQueueOffset, long popTime, long invisibleTime, int reviveQid, String topic, String brokerName, int queueId) { String t = getRetry(topic); return ckQueueOffset + MessageConst.KEY_SEPARATOR + popTime + MessageConst.KEY_SEPARATOR + invisibleTime + MessageConst.KEY_SEPARATOR + reviveQid + MessageConst.KEY_SEPARATOR + t + MessageConst.KEY_SEPARATOR + brokerName + MessageConst.KEY_SEPARATOR + queueId; } public static String buildExtraInfo(long ckQueueOffset, long popTime, long invisibleTime, int reviveQid, String topic, String brokerName, int queueId, long msgQueueOffset) { String t = getRetry(topic); return ckQueueOffset + MessageConst.KEY_SEPARATOR + popTime + MessageConst.KEY_SEPARATOR + invisibleTime + MessageConst.KEY_SEPARATOR + reviveQid + MessageConst.KEY_SEPARATOR + t + MessageConst.KEY_SEPARATOR + brokerName + MessageConst.KEY_SEPARATOR + queueId + MessageConst.KEY_SEPARATOR + msgQueueOffset; } public static void buildStartOffsetInfo(StringBuilder stringBuilder, String topic, int queueId, long startOffset) { if (stringBuilder == null) { stringBuilder = new StringBuilder(64); } if (stringBuilder.length() > 0) { stringBuilder.append(";"); } stringBuilder.append(getRetry(topic)) .append(MessageConst.KEY_SEPARATOR).append(queueId) .append(MessageConst.KEY_SEPARATOR).append(startOffset); } public static void buildQueueIdOrderCountInfo(StringBuilder stringBuilder, String topic, int queueId, int orderCount) { if (stringBuilder == null) { stringBuilder = new StringBuilder(64); } if (stringBuilder.length() > 0) { stringBuilder.append(";"); } stringBuilder.append(getRetry(topic)) .append(MessageConst.KEY_SEPARATOR).append(queueId) .append(MessageConst.KEY_SEPARATOR).append(orderCount); } public static void buildQueueOffsetOrderCountInfo(StringBuilder stringBuilder, String topic, long queueId, long queueOffset, int orderCount) { if (stringBuilder == null) { stringBuilder = new StringBuilder(64); } if (stringBuilder.length() > 0) { stringBuilder.append(";"); } stringBuilder.append(getRetry(topic)) .append(MessageConst.KEY_SEPARATOR).append(getQueueOffsetKeyValueKey(queueId, queueOffset)) .append(MessageConst.KEY_SEPARATOR).append(orderCount); } public static void buildMsgOffsetInfo(StringBuilder stringBuilder, String topic, int queueId, List msgOffsets) { if (stringBuilder == null) { stringBuilder = new StringBuilder(64); } if (stringBuilder.length() > 0) { stringBuilder.append(";"); } stringBuilder.append(getRetry(topic)) .append(MessageConst.KEY_SEPARATOR).append(queueId) .append(MessageConst.KEY_SEPARATOR); for (int i = 0; i < msgOffsets.size(); i++) { stringBuilder.append(msgOffsets.get(i)); if (i < msgOffsets.size() - 1) { stringBuilder.append(","); } } } public static Map> parseMsgOffsetInfo(String msgOffsetInfo) { if (msgOffsetInfo == null || msgOffsetInfo.length() == 0) { return null; } Map> msgOffsetMap = new HashMap<>(4); String[] array; if (msgOffsetInfo.indexOf(";") < 0) { array = new String[]{msgOffsetInfo}; } else { array = msgOffsetInfo.split(";"); } for (String one : array) { String[] split = one.split(MessageConst.KEY_SEPARATOR); if (split.length != 3) { throw new IllegalArgumentException("parse msgOffsetMap error, " + msgOffsetMap); } String key = split[0] + "@" + split[1]; if (msgOffsetMap.containsKey(key)) { throw new IllegalArgumentException("parse msgOffsetMap error, duplicate, " + msgOffsetMap); } msgOffsetMap.put(key, new ArrayList<>(8)); String[] msgOffsets = split[2].split(","); for (String msgOffset : msgOffsets) { msgOffsetMap.get(key).add(Long.valueOf(msgOffset)); } } return msgOffsetMap; } public static Map parseStartOffsetInfo(String startOffsetInfo) { if (startOffsetInfo == null || startOffsetInfo.length() == 0) { return null; } Map startOffsetMap = new HashMap<>(4); String[] array; if (startOffsetInfo.indexOf(";") < 0) { array = new String[]{startOffsetInfo}; } else { array = startOffsetInfo.split(";"); } for (String one : array) { String[] split = one.split(MessageConst.KEY_SEPARATOR); if (split.length != 3) { throw new IllegalArgumentException("parse startOffsetInfo error, " + startOffsetInfo); } String key = split[0] + "@" + split[1]; if (startOffsetMap.containsKey(key)) { throw new IllegalArgumentException("parse startOffsetInfo error, duplicate, " + startOffsetInfo); } startOffsetMap.put(key, Long.valueOf(split[2])); } return startOffsetMap; } public static Map parseOrderCountInfo(String orderCountInfo) { if (orderCountInfo == null || orderCountInfo.length() == 0) { return null; } Map startOffsetMap = new HashMap<>(4); String[] array; if (orderCountInfo.indexOf(";") < 0) { array = new String[]{orderCountInfo}; } else { array = orderCountInfo.split(";"); } for (String one : array) { String[] split = one.split(MessageConst.KEY_SEPARATOR); if (split.length != 3) { throw new IllegalArgumentException("parse orderCountInfo error, " + orderCountInfo); } String key = split[0] + "@" + split[1]; if (startOffsetMap.containsKey(key)) { throw new IllegalArgumentException("parse orderCountInfo error, duplicate, " + orderCountInfo); } startOffsetMap.put(key, Integer.valueOf(split[2])); } return startOffsetMap; } public static List parseLiteOrderCountInfo(String orderCountInfo, int msgCount) { if (StringUtils.isEmpty(orderCountInfo)) { return null; } String[] infos = orderCountInfo.split(";"); if (infos.length != msgCount) { return null; } return Arrays.stream(infos).map(ExtraInfoUtil::parseLiteOrderCount).collect(Collectors.toList()); } private static int parseLiteOrderCount(String info) { if (StringUtils.isBlank(info)) { return 0; } if (!info.contains(QUEUE_OFFSET)) { return NumberUtils.toInt(info, 0); } String[] split = info.split(MessageConst.KEY_SEPARATOR); return split.length != 3 ? 0 : NumberUtils.toInt(split[2], 0); } public static String getStartOffsetInfoMapKey(String topic, long key) { return getRetry(topic) + "@" + key; } public static String getStartOffsetInfoMapKey(String topic, String popCk, long key) { return getRetry(topic, popCk) + "@" + key; } public static String getQueueOffsetKeyValueKey(long queueId, long queueOffset) { return QUEUE_OFFSET + queueId + "%" + queueOffset; } public static String getQueueOffsetMapKey(String topic, long queueId, long queueOffset) { return getRetry(topic) + "@" + getQueueOffsetKeyValueKey(queueId, queueOffset); } public static boolean isOrder(String[] extraInfo) { return ExtraInfoUtil.getReviveQid(extraInfo) == KeyBuilder.POP_ORDER_REVIVE_QUEUE; } private static String getRetry(String topic) { String t = NORMAL_TOPIC; if (KeyBuilder.isPopRetryTopicV2(topic)) { t = RETRY_TOPIC_V2; } else if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { t = RETRY_TOPIC; } return t; } private static String getRetry(String topic, String popCk) { if (popCk != null) { return getRetry(split(popCk)); } return getRetry(topic); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAclRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_GET_ACL, resource = ResourceType.CLUSTER, action = Action.GET) public class GetAclRequestHeader implements CommandCustomHeader { private String subject; public GetAclRequestHeader() { } public GetAclRequestHeader(String subject) { this.subject = subject; } @Override public void checkFields() throws RemotingCommandException { } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllProducerInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_ALL_PRODUCER_INFO, resource = ResourceType.CLUSTER, action = Action.GET) public class GetAllProducerInfoRequestHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { // To change body of implemented methods use File | Settings | File // Templates. } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllSubscriptionGroupRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, resource = ResourceType.GROUP, action = Action.GET) public class GetAllSubscriptionGroupRequestHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { // nothing } @CFNotNull private Integer groupSeq; private String dataVersion; private Integer maxGroupNum; public Integer getGroupSeq() { return groupSeq; } public void setGroupSeq(Integer groupSeq) { this.groupSeq = groupSeq; } public String getDataVersion() { return dataVersion; } public void setDataVersion(String dataVersion) { this.dataVersion = dataVersion; } public Integer getMaxGroupNum() { return maxGroupNum; } public void setMaxGroupNum(Integer maxGroupNum) { this.maxGroupNum = maxGroupNum; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllSubscriptionGroupResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, resource = ResourceType.GROUP, action = Action.LIST) public class GetAllSubscriptionGroupResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } @CFNotNull private Integer totalGroupNum; public Integer getTotalGroupNum() { return totalGroupNum; } public void setTotalGroupNum(Integer totalGroupNum) { this.totalGroupNum = totalGroupNum; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllTopicConfigRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_ALL_TOPIC_CONFIG, resource = ResourceType.TOPIC, action = Action.GET) public class GetAllTopicConfigRequestHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { // nothing } @CFNotNull private Integer topicSeq; private String dataVersion; private Integer maxTopicNum; public Integer getTopicSeq() { return topicSeq; } public void setTopicSeq(Integer topicSeq) { this.topicSeq = topicSeq; } public String getDataVersion() { return dataVersion; } public void setDataVersion(String dataVersion) { this.dataVersion = dataVersion; } public Integer getMaxTopicNum() { return maxTopicNum; } public void setMaxTopicNum(Integer maxTopicNum) { this.maxTopicNum = maxTopicNum; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllTopicConfigResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetAllTopicConfigResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_ALL_TOPIC_CONFIG, resource = ResourceType.TOPIC, action = Action.LIST) public class GetAllTopicConfigResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } private Integer totalTopicNum; public Integer getTotalTopicNum() { return totalTopicNum; } public void setTotalTopicNum(Integer totalTopicNum) { this.totalTopicNum = totalTopicNum; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerConfigResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetBrokerConfigResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetBrokerConfigResponseHeader implements CommandCustomHeader { @CFNotNull private String version; @Override public void checkFields() throws RemotingCommandException { } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerMemberGroupRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_BROKER_MEMBER_GROUP, action = Action.GET) public class GetBrokerMemberGroupRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @CFNotNull private String brokerName; public String getClusterName() { return clusterName; } public void setClusterName(final String clusterName) { this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(final String brokerName) { this.brokerName = brokerName; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsInBrokerHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_BROKER_CONSUME_STATS, resource = ResourceType.CLUSTER, action = Action.GET) public class GetConsumeStatsInBrokerHeader implements CommandCustomHeader { @CFNotNull private boolean isOrder; @Override public void checkFields() throws RemotingCommandException { } public boolean isOrder() { return isOrder; } public void setIsOrder(boolean isOrder) { this.isOrder = isOrder; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.GET_CONSUME_STATS, action = Action.GET) public class GetConsumeStatsRequestHeader extends TopicRequestHeader { private static final String TOPIC_NAME_SEPARATOR = ";"; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @RocketMQResource(ResourceType.TOPIC) private String topic; // if topicList is provided, topic will be ignored @RocketMQResource(value = ResourceType.TOPIC, splitter = TOPIC_NAME_SEPARATOR) private String topicList; @Override public void checkFields() throws RemotingCommandException { } public List fetchTopicList() { if (StringUtils.isBlank(topicList)) { return Collections.emptyList(); } return Arrays.asList(StringUtils.split(topicList, TOPIC_NAME_SEPARATOR)); } public void updateTopicList(List topicList) { if (topicList == null || topicList.isEmpty()) { return; } StringBuilder sb = new StringBuilder(); topicList.forEach(topic -> sb.append(topic).append(TOPIC_NAME_SEPARATOR)); this.setTopicList(sb.toString()); } public String getTopicList() { return topicList; } public void setTopicList(String topicList) { this.topicList = topicList; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerConnectionListRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.GET_CONSUMER_CONNECTION_LIST, action = Action.GET) public class GetConsumerConnectionListRequestHeader extends RpcRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @Override public void checkFields() throws RemotingCommandException { // To change body of implemented methods use File | Settings | File // Templates. } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_CONSUMER_LIST_BY_GROUP, action = Action.SUB) public class GetConsumerListByGroupRequestHeader extends RpcRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseBody.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class GetConsumerListByGroupResponseBody extends RemotingSerializable { private List consumerIdList; public List getConsumerIdList() { return consumerIdList; } public void setConsumerIdList(List consumerIdList) { this.consumerIdList = consumerIdList; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetConsumerListByGroupResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerRunningInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_CONSUMER_RUNNING_INFO, action = Action.GET) public class GetConsumerRunningInfoRequestHeader extends RpcRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull private String clientId; @CFNullable private boolean jstackEnable; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public boolean isJstackEnable() { return jstackEnable; } public void setJstackEnable(boolean jstackEnable) { this.jstackEnable = jstackEnable; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("clientId", clientId) .add("jstackEnable", jstackEnable) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerStatusRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.INVOKE_BROKER_TO_GET_CONSUMER_STATUS, action = Action.GET) public class GetConsumerStatusRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @CFNullable private String clientAddr; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getClientAddr() { return clientAddr; } public void setClientAddr(String clientAddr) { this.clientAddr = clientAddr; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("group", group) .add("clientAddr", clientAddr) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetEarliestMsgStoretimeRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.GET_EARLIEST_MSG_STORETIME, action = Action.GET) public class GetEarliestMsgStoretimeRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; @Override public void checkFields() throws RemotingCommandException { } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetEarliestMsgStoretimeResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetEarliestMsgStoretimeResponseHeader implements CommandCustomHeader { @CFNotNull private Long timestamp; @Override public void checkFields() throws RemotingCommandException { } public Long getTimestamp() { return timestamp; } public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetLiteClientInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetLiteClientInfoRequestHeader implements CommandCustomHeader { private String parentTopic; private String group; private String clientId; private int maxCount = 1000; @Override public void checkFields() throws RemotingCommandException { if (maxCount <= 0) { throw new RemotingCommandException("[maxCount] field invalid"); } } public String getParentTopic() { return parentTopic; } public void setParentTopic(String parentTopic) { this.parentTopic = parentTopic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public int getMaxCount() { return maxCount; } public void setMaxCount(int maxCount) { this.maxCount = maxCount; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetLiteGroupInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetLiteGroupInfoRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; private String liteTopic; private int topK; public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } public int getTopK() { return topK; } public void setTopK(int topK) { this.topK = topK; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetLiteTopicInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetLiteTopicInfoRequestHeader implements CommandCustomHeader { private String parentTopic; private String liteTopic; @Override public void checkFields() throws RemotingCommandException { } public String getParentTopic() { return parentTopic; } public void setParentTopic(String parentTopic) { this.parentTopic = parentTopic; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetMaxOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.GET_MAX_OFFSET, action = Action.GET) public class GetMaxOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; /** * A message at committed offset has been dispatched from Topic to MessageQueue, so it can be consumed immediately, * while a message at inflight offset is not visible for a consumer temporarily. * Set this flag true if the max committed offset is needed, or false if the max inflight offset is preferred. * The default value is true. */ @CFNullable private boolean committed = true; @Override public void checkFields() throws RemotingCommandException { } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public boolean isCommitted() { return committed; } public void setCommitted(final boolean committed) { this.committed = committed; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("queueId", queueId) .add("committed", committed) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetMaxOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetMaxOffsetResponseHeader implements CommandCustomHeader { @CFNotNull private Long offset; @Override public void checkFields() throws RemotingCommandException { } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetMinOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.GET_MIN_OFFSET, action = Action.GET) public class GetMinOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; @Override public void checkFields() throws RemotingCommandException { } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("queueId", queueId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetMinOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetMinOffsetResponseHeader implements CommandCustomHeader { @CFNotNull private Long offset; @Override public void checkFields() throws RemotingCommandException { } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetParentTopicInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetParentTopicInfoRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetProducerConnectionListRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.GET_PRODUCER_CONNECTION_LIST, resource = ResourceType.CLUSTER, action = Action.GET) public class GetProducerConnectionListRequestHeader extends RpcRequestHeader { @CFNotNull private String producerGroup; @Override public void checkFields() throws RemotingCommandException { // To change body of implemented methods use File | Settings | File // Templates. } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetSubscriptionGroupConfigRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetAllTopicConfigResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.GET_SUBSCRIPTIONGROUP_CONFIG, action = Action.GET) public class GetSubscriptionGroupConfigRequestHeader extends RpcRequestHeader { @Override public void checkFields() throws RemotingCommandException { } @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; /** * @return the group */ public String getGroup() { return group; } /** * @param group the group to set */ public void setGroup(String group) { this.group = group; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicConfigRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.GET_TOPIC_CONFIG, action = Action.GET) public class GetTopicConfigRequestHeader extends TopicRequestHeader { @Override public void checkFields() throws RemotingCommandException { } @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; /** * @return the topic */ public String getTopic() { return topic; } /** * @param topic the topic to set */ public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicStatsInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.GET_TOPIC_STATS_INFO, action = Action.GET) public class GetTopicStatsInfoRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicsByClusterRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_TOPICS_BY_CLUSTER, resource = ResourceType.TOPIC, action = Action.LIST) public class GetTopicsByClusterRequestHeader implements CommandCustomHeader { @CFNotNull private String cluster; @Override public void checkFields() throws RemotingCommandException { } public String getCluster() { return cluster; } public void setCluster(String cluster) { this.cluster = cluster; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetUserRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_GET_USER, resource = ResourceType.CLUSTER, action = Action.GET) public class GetUserRequestHeader implements CommandCustomHeader { private String username; public GetUserRequestHeader() { } public GetUserRequestHeader(String username) { this.username = username; } @Override public void checkFields() throws RemotingCommandException { } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/HeartbeatRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.HEART_BEAT, resource = ResourceType.GROUP, action = {Action.PUB, Action.SUB}) public class HeartbeatRequestHeader extends RpcRequestHeader { // for namespace @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/InitConsumerOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; public class InitConsumerOffsetRequestHeader extends TopicRequestHeader { private String topic; // @see ConsumeInitMode private int initMode; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public int getInitMode() { return initMode; } public void setInitMode(int initMode) { this.initMode = initMode; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ListAclsRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_LIST_ACL, resource = ResourceType.CLUSTER, action = Action.GET) public class ListAclsRequestHeader implements CommandCustomHeader { private String subjectFilter; private String resourceFilter; public ListAclsRequestHeader() { } public ListAclsRequestHeader(String subjectFilter, String resourceFilter) { this.subjectFilter = subjectFilter; this.resourceFilter = resourceFilter; } @Override public void checkFields() throws RemotingCommandException { } public String getSubjectFilter() { return subjectFilter; } public void setSubjectFilter(String subjectFilter) { this.subjectFilter = subjectFilter; } public String getResourceFilter() { return resourceFilter; } public void setResourceFilter(String resourceFilter) { this.resourceFilter = resourceFilter; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ListUsersRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_LIST_USER, resource = ResourceType.CLUSTER, action = Action.GET) public class ListUsersRequestHeader implements CommandCustomHeader { private String filter; public ListUsersRequestHeader() { } public ListUsersRequestHeader(String filter) { this.filter = filter; } @Override public void checkFields() throws RemotingCommandException { } public String getFilter() { return filter; } public void setFilter(String filter) { this.filter = filter; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/LiteSubscriptionCtlRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; public class LiteSubscriptionCtlRequestHeader extends RpcRequestHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/LockBatchMqRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.LOCK_BATCH_MQ, action = Action.SUB) public class LockBatchMqRequestHeader extends RpcRequestHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.NOTIFICATION, action = Action.SUB) public class NotificationRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private int queueId; @CFNotNull private long pollTime; @CFNotNull private long bornTime; private Boolean order = Boolean.FALSE; private String attemptId; private String expType; private String exp; @CFNotNull @Override public void checkFields() throws RemotingCommandException { } public long getPollTime() { return pollTime; } public void setPollTime(long pollTime) { this.pollTime = pollTime; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public long getBornTime() { return bornTime; } public void setBornTime(long bornTime) { this.bornTime = bornTime; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { if (queueId < 0) { return -1; } return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public Boolean getOrder() { return order; } public void setOrder(Boolean order) { this.order = order; } public String getAttemptId() { return attemptId; } public void setAttemptId(String attemptId) { this.attemptId = attemptId; } public String getExpType() { return expType; } public void setExpType(String expType) { this.expType = expType; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("queueId", queueId) .add("pollTime", pollTime) .add("bornTime", bornTime) .add("order", order) .add("attemptId", attemptId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class NotificationResponseHeader implements CommandCustomHeader { @CFNotNull private boolean hasMsg = false; private boolean pollingFull = false; public boolean isHasMsg() { return hasMsg; } public boolean isPollingFull() { return pollingFull; } public void setPollingFull(boolean pollingFull) { this.pollingFull = pollingFull; } public void setHasMsg(boolean hasMsg) { this.hasMsg = hasMsg; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyBrokerRoleChangedRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.NOTIFY_BROKER_ROLE_CHANGED, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class NotifyBrokerRoleChangedRequestHeader implements CommandCustomHeader { private String masterAddress; private Integer masterEpoch; private Integer syncStateSetEpoch; private Long masterBrokerId; public NotifyBrokerRoleChangedRequestHeader() { } public NotifyBrokerRoleChangedRequestHeader(String masterAddress, Long masterBrokerId, Integer masterEpoch, Integer syncStateSetEpoch) { this.masterAddress = masterAddress; this.masterEpoch = masterEpoch; this.syncStateSetEpoch = syncStateSetEpoch; this.masterBrokerId = masterBrokerId; } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } public Integer getMasterEpoch() { return masterEpoch; } public void setMasterEpoch(Integer masterEpoch) { this.masterEpoch = masterEpoch; } public Integer getSyncStateSetEpoch() { return syncStateSetEpoch; } public void setSyncStateSetEpoch(Integer syncStateSetEpoch) { this.syncStateSetEpoch = syncStateSetEpoch; } public Long getMasterBrokerId() { return masterBrokerId; } public void setMasterBrokerId(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; } @Override public String toString() { return "NotifyBrokerRoleChangedRequestHeader{" + "masterAddress='" + masterAddress + '\'' + ", masterEpoch=" + masterEpoch + ", syncStateSetEpoch=" + syncStateSetEpoch + ", masterBrokerId=" + masterBrokerId + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyConsumerIdsChangedRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, action = Action.SUB) public class NotifyConsumerIdsChangedRequestHeader extends RpcRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyMinBrokerIdChangeRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.NOTIFY_MIN_BROKER_ID_CHANGE, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class NotifyMinBrokerIdChangeRequestHeader implements CommandCustomHeader { @CFNullable private Long minBrokerId; @CFNullable private String brokerName; @CFNullable private String minBrokerAddr; @CFNullable private String offlineBrokerAddr; @CFNullable private String haBrokerAddr; @Override public void checkFields() throws RemotingCommandException { } public Long getMinBrokerId() { return minBrokerId; } public void setMinBrokerId(Long minBrokerId) { this.minBrokerId = minBrokerId; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getMinBrokerAddr() { return minBrokerAddr; } public void setMinBrokerAddr(String minBrokerAddr) { this.minBrokerAddr = minBrokerAddr; } public String getOfflineBrokerAddr() { return offlineBrokerAddr; } public void setOfflineBrokerAddr(String offlineBrokerAddr) { this.offlineBrokerAddr = offlineBrokerAddr; } public String getHaBrokerAddr() { return haBrokerAddr; } public void setHaBrokerAddr(String haBrokerAddr) { this.haBrokerAddr = haBrokerAddr; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyUnsubscribeLiteRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.NOTIFY_UNSUBSCRIBE_LITE, action = Action.SUB) public class NotifyUnsubscribeLiteRequestHeader extends RpcRequestHeader { @CFNotNull private String liteTopic; @RocketMQResource(ResourceType.GROUP) @CFNotNull private String consumerGroup; @CFNotNull private String clientId; @Override public void checkFields() throws RemotingCommandException { } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } @Override public String toString() { return "NotifyUnsubscribeLiteRequestHeader{" + "liteTopic='" + liteTopic + '\'' + ", consumerGroup='" + consumerGroup + '\'' + ", clientId='" + clientId + '\'' + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PeekMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.PEEK_MESSAGE, action = Action.SUB) public class PeekMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private int queueId; @CFNotNull private int maxMsgNums; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public int getMaxMsgNums() { return maxMsgNums; } public void setMaxMsgNums(int maxMsgNums) { this.maxMsgNums = maxMsgNums; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.POLLING_INFO, action = Action.GET) public class PollingInfoRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private int queueId; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { if (queueId < 0) { return -1; } return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class PollingInfoResponseHeader implements CommandCustomHeader { @CFNotNull private int pollingNum; public int getPollingNum() { return pollingNum; } public void setPollingNum(int pollingNum) { this.pollingNum = pollingNum; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopLiteMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; public class PopLiteMessageRequestHeader extends RpcRequestHeader { @CFNotNull private String clientId; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private int maxMsgNum; @CFNotNull private long invisibleTime; @CFNotNull private long pollTime; @CFNotNull private long bornTime; private String attemptId; @Override public void checkFields() throws RemotingCommandException { } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public int getMaxMsgNum() { return maxMsgNum; } public void setMaxMsgNum(int maxMsgNum) { this.maxMsgNum = maxMsgNum; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public long getPollTime() { return pollTime; } public void setPollTime(long pollTime) { this.pollTime = pollTime; } public long getBornTime() { return bornTime; } public void setBornTime(long bornTime) { this.bornTime = bornTime; } public String getAttemptId() { return attemptId; } public void setAttemptId(String attemptId) { this.attemptId = attemptId; } public boolean isTimeoutTooMuch() { return System.currentTimeMillis() - bornTime - pollTime > 500; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("maxMsgNum", maxMsgNum) .add("invisibleTime", invisibleTime) .add("pollTime", pollTime) .add("bornTime", bornTime) .add("attemptId", attemptId) .add("clientId", clientId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopLiteMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class PopLiteMessageResponseHeader implements CommandCustomHeader { @CFNotNull private long popTime; @CFNotNull private long invisibleTime; @CFNotNull private int reviveQid; // reuse current ack implementation private String startOffsetInfo; private String msgOffsetInfo; private String orderCountInfo; @Override public void checkFields() throws RemotingCommandException { } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public int getReviveQid() { return reviveQid; } public void setReviveQid(int reviveQid) { this.reviveQid = reviveQid; } public String getStartOffsetInfo() { return startOffsetInfo; } public void setStartOffsetInfo(String startOffsetInfo) { this.startOffsetInfo = startOffsetInfo; } public String getMsgOffsetInfo() { return msgOffsetInfo; } public void setMsgOffsetInfo(String msgOffsetInfo) { this.msgOffsetInfo = msgOffsetInfo; } public String getOrderCountInfo() { return orderCountInfo; } public void setOrderCountInfo(String orderCountInfo) { this.orderCountInfo = orderCountInfo; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.POP_MESSAGE, action = Action.SUB) public class PopMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private int queueId; @CFNotNull private int maxMsgNums; @CFNotNull private long invisibleTime; @CFNotNull private long pollTime; @CFNotNull private long bornTime; @CFNotNull private int initMode; private String expType; private String exp; /** * marked as order consume, if true * 1. not commit offset * 2. not pop retry, because no retry * 3. not append check point, because no retry */ private Boolean order = Boolean.FALSE; private String attemptId; @Override public void checkFields() throws RemotingCommandException { } public void setInitMode(int initMode) { this.initMode = initMode; } public int getInitMode() { return initMode; } public long getInvisibleTime() { return invisibleTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public long getPollTime() { return pollTime; } public void setPollTime(long pollTime) { this.pollTime = pollTime; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public long getBornTime() { return bornTime; } public void setBornTime(long bornTime) { this.bornTime = bornTime; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { if (queueId < 0) { return -1; } return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public int getMaxMsgNums() { return maxMsgNums; } public void setMaxMsgNums(int maxMsgNums) { this.maxMsgNums = maxMsgNums; } public boolean isTimeoutTooMuch() { return System.currentTimeMillis() - bornTime - pollTime > 500; } public String getExpType() { return expType; } public void setExpType(String expType) { this.expType = expType; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } public Boolean getOrder() { return order; } public void setOrder(Boolean order) { this.order = order; } public boolean isOrder() { return this.order != null && this.order.booleanValue(); } public String getAttemptId() { return attemptId; } public void setAttemptId(String attemptId) { this.attemptId = attemptId; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("queueId", queueId) .add("maxMsgNums", maxMsgNums) .add("invisibleTime", invisibleTime) .add("pollTime", pollTime) .add("bornTime", bornTime) .add("initMode", initMode) .add("expType", expType) .add("exp", exp) .add("order", order) .add("attemptId", attemptId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class PopMessageResponseHeader implements CommandCustomHeader { @CFNotNull private long popTime; @CFNotNull private long invisibleTime; @CFNotNull private int reviveQid; /** * the rest num in queue */ @CFNotNull private long restNum; private String startOffsetInfo; private String msgOffsetInfo; private String orderCountInfo; @Override public void checkFields() throws RemotingCommandException { } public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public long getInvisibleTime() { return invisibleTime; } public long getRestNum() { return restNum; } public void setRestNum(long restNum) { this.restNum = restNum; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public int getReviveQid() { return reviveQid; } public void setReviveQid(int reviveQid) { this.reviveQid = reviveQid; } public String getStartOffsetInfo() { return startOffsetInfo; } public void setStartOffsetInfo(String startOffsetInfo) { this.startOffsetInfo = startOffsetInfo; } public String getMsgOffsetInfo() { return msgOffsetInfo; } public void setMsgOffsetInfo(String msgOffsetInfo) { this.msgOffsetInfo = msgOffsetInfo; } public String getOrderCountInfo() { return orderCountInfo; } public void setOrderCountInfo(String orderCountInfo) { this.orderCountInfo = orderCountInfo; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: PullMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import io.netty.buffer.ByteBuf; import java.util.HashMap; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.FastCodesHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.PULL_MESSAGE, action = Action.SUB) public class PullMessageRequestHeader extends TopicQueueRequestHeader implements FastCodesHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; private String liteTopic; @CFNotNull private Integer queueId; @CFNotNull private Long queueOffset; @CFNotNull private Integer maxMsgNums; @CFNotNull private Integer sysFlag; @CFNotNull private Long commitOffset; @CFNotNull private Long suspendTimeoutMillis; @CFNullable private String subscription; @CFNotNull private Long subVersion; private String expressionType; @CFNullable private Integer maxMsgBytes; /** * mark the source of this pull request */ private Integer requestSource; /** * the real clientId when request from proxy */ private String proxyFrowardClientId; @Override public void checkFields() throws RemotingCommandException { } @Override public void encode(ByteBuf out) { writeIfNotNull(out, "consumerGroup", consumerGroup); writeIfNotNull(out, "topic", topic); writeIfNotNull(out, "liteTopic", liteTopic); writeIfNotNull(out, "queueId", queueId); writeIfNotNull(out, "queueOffset", queueOffset); writeIfNotNull(out, "maxMsgNums", maxMsgNums); writeIfNotNull(out, "sysFlag", sysFlag); writeIfNotNull(out, "commitOffset", commitOffset); writeIfNotNull(out, "suspendTimeoutMillis", suspendTimeoutMillis); writeIfNotNull(out, "subscription", subscription); writeIfNotNull(out, "subVersion", subVersion); writeIfNotNull(out, "expressionType", expressionType); writeIfNotNull(out, "maxMsgBytes", maxMsgBytes); writeIfNotNull(out, "requestSource", requestSource); writeIfNotNull(out, "proxyFrowardClientId", proxyFrowardClientId); writeIfNotNull(out, "lo", lo); writeIfNotNull(out, "ns", ns); writeIfNotNull(out, "nsd", nsd); writeIfNotNull(out, "bname", bname); writeIfNotNull(out, "oway", oway); } @Override public void decode(HashMap fields) throws RemotingCommandException { String str = getAndCheckNotNull(fields, "consumerGroup"); if (str != null) { this.consumerGroup = str; } str = getAndCheckNotNull(fields, "topic"); if (str != null) { this.topic = str; } str = fields.get("liteTopic"); if (str != null) { this.liteTopic = str; } str = getAndCheckNotNull(fields, "queueId"); if (str != null) { this.queueId = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "queueOffset"); if (str != null) { this.queueOffset = Long.parseLong(str); } str = getAndCheckNotNull(fields, "maxMsgNums"); if (str != null) { this.maxMsgNums = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "sysFlag"); if (str != null) { this.sysFlag = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "commitOffset"); if (str != null) { this.commitOffset = Long.parseLong(str); } str = getAndCheckNotNull(fields, "suspendTimeoutMillis"); if (str != null) { this.suspendTimeoutMillis = Long.parseLong(str); } str = fields.get("subscription"); if (str != null) { this.subscription = str; } str = getAndCheckNotNull(fields, "subVersion"); if (str != null) { this.subVersion = Long.parseLong(str); } str = fields.get("expressionType"); if (str != null) { this.expressionType = str; } str = fields.get("maxMsgBytes"); if (str != null) { this.maxMsgBytes = Integer.parseInt(str); } str = fields.get("requestSource"); if (str != null) { this.requestSource = Integer.parseInt(str); } str = fields.get("proxyFrowardClientId"); if (str != null) { this.proxyFrowardClientId = str; } str = fields.get("lo"); if (str != null) { this.lo = Boolean.parseBoolean(str); } str = fields.get("ns"); if (str != null) { this.ns = str; } str = fields.get("nsd"); if (str != null) { this.nsd = Boolean.parseBoolean(str); } str = fields.get("bname"); if (str != null) { this.bname = str; } str = fields.get("oway"); if (str != null) { this.oway = Boolean.parseBoolean(str); } } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public Long getQueueOffset() { return queueOffset; } public void setQueueOffset(Long queueOffset) { this.queueOffset = queueOffset; } public Integer getMaxMsgNums() { return maxMsgNums; } public void setMaxMsgNums(Integer maxMsgNums) { this.maxMsgNums = maxMsgNums; } public Integer getSysFlag() { return sysFlag; } public void setSysFlag(Integer sysFlag) { this.sysFlag = sysFlag; } public Long getCommitOffset() { return commitOffset; } public void setCommitOffset(Long commitOffset) { this.commitOffset = commitOffset; } public Long getSuspendTimeoutMillis() { return suspendTimeoutMillis; } public void setSuspendTimeoutMillis(Long suspendTimeoutMillis) { this.suspendTimeoutMillis = suspendTimeoutMillis; } public String getSubscription() { return subscription; } public void setSubscription(String subscription) { this.subscription = subscription; } public Long getSubVersion() { return subVersion; } public void setSubVersion(Long subVersion) { this.subVersion = subVersion; } public String getExpressionType() { return expressionType; } public void setExpressionType(String expressionType) { this.expressionType = expressionType; } public Integer getMaxMsgBytes() { return maxMsgBytes; } public void setMaxMsgBytes(Integer maxMsgBytes) { this.maxMsgBytes = maxMsgBytes; } public Integer getRequestSource() { return requestSource; } public void setRequestSource(Integer requestSource) { this.requestSource = requestSource; } public String getProxyFrowardClientId() { return proxyFrowardClientId; } public void setProxyFrowardClientId(String proxyFrowardClientId) { this.proxyFrowardClientId = proxyFrowardClientId; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("liteTopic", liteTopic) .add("queueId", queueId) .add("queueOffset", queueOffset) .add("maxMsgBytes", maxMsgBytes) .add("maxMsgNums", maxMsgNums) .add("sysFlag", sysFlag) .add("commitOffset", commitOffset) .add("suspendTimeoutMillis", suspendTimeoutMillis) .add("subscription", subscription) .add("subVersion", subVersion) .add("expressionType", expressionType) .add("requestSource", requestSource) .add("proxyFrowardClientId", proxyFrowardClientId) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: PullMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import io.netty.buffer.ByteBuf; import java.util.HashMap; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.FastCodesHeader; public class PullMessageResponseHeader implements CommandCustomHeader, FastCodesHeader { @CFNotNull private Long suggestWhichBrokerId; @CFNotNull private Long nextBeginOffset; @CFNotNull private Long minOffset; @CFNotNull private Long maxOffset; @CFNullable private Long offsetDelta; @CFNullable private Integer topicSysFlag; @CFNullable private Integer groupSysFlag; @CFNullable private Integer forbiddenType; @Override public void checkFields() throws RemotingCommandException { } @Override public void encode(ByteBuf out) { writeIfNotNull(out, "suggestWhichBrokerId", suggestWhichBrokerId); writeIfNotNull(out, "nextBeginOffset", nextBeginOffset); writeIfNotNull(out, "minOffset", minOffset); writeIfNotNull(out, "maxOffset", maxOffset); writeIfNotNull(out, "offsetDelta", offsetDelta); writeIfNotNull(out, "topicSysFlag", topicSysFlag); writeIfNotNull(out, "groupSysFlag", groupSysFlag); writeIfNotNull(out, "forbiddenType", forbiddenType); } @Override public void decode(HashMap fields) throws RemotingCommandException { String str = getAndCheckNotNull(fields, "suggestWhichBrokerId"); if (str != null) { this.suggestWhichBrokerId = Long.parseLong(str); } str = getAndCheckNotNull(fields, "nextBeginOffset"); if (str != null) { this.nextBeginOffset = Long.parseLong(str); } str = getAndCheckNotNull(fields, "minOffset"); if (str != null) { this.minOffset = Long.parseLong(str); } str = getAndCheckNotNull(fields, "maxOffset"); if (str != null) { this.maxOffset = Long.parseLong(str); } str = fields.get("offsetDelta"); if (str != null) { this.offsetDelta = Long.parseLong(str); } str = fields.get("topicSysFlag"); if (str != null) { this.topicSysFlag = Integer.parseInt(str); } str = fields.get("groupSysFlag"); if (str != null) { this.groupSysFlag = Integer.parseInt(str); } str = fields.get("forbiddenType"); if (str != null) { this.forbiddenType = Integer.parseInt(str); } } public Long getNextBeginOffset() { return nextBeginOffset; } public void setNextBeginOffset(Long nextBeginOffset) { this.nextBeginOffset = nextBeginOffset; } public Long getMinOffset() { return minOffset; } public void setMinOffset(Long minOffset) { this.minOffset = minOffset; } public Long getMaxOffset() { return maxOffset; } public void setMaxOffset(Long maxOffset) { this.maxOffset = maxOffset; } public Long getSuggestWhichBrokerId() { return suggestWhichBrokerId; } public void setSuggestWhichBrokerId(Long suggestWhichBrokerId) { this.suggestWhichBrokerId = suggestWhichBrokerId; } public Integer getTopicSysFlag() { return topicSysFlag; } public void setTopicSysFlag(Integer topicSysFlag) { this.topicSysFlag = topicSysFlag; } public Integer getGroupSysFlag() { return groupSysFlag; } public void setGroupSysFlag(Integer groupSysFlag) { this.groupSysFlag = groupSysFlag; } public Integer getForbiddenType() { return forbiddenType; } public void setForbiddenType(Integer forbiddenType) { this.forbiddenType = forbiddenType; } public Long getOffsetDelta() { return offsetDelta; } public void setOffsetDelta(Long offsetDelta) { this.offsetDelta = offsetDelta; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeQueueRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.QUERY_CONSUME_QUEUE, action = Action.GET) public class QueryConsumeQueueRequestHeader extends TopicQueueRequestHeader { @RocketMQResource(ResourceType.TOPIC) private String topic; private int queueId; private long index; private int count; @RocketMQResource(ResourceType.GROUP) private String consumerGroup; public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public long getIndex() { return index; } public void setIndex(long index) { this.index = index; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeTimeSpanRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.QUERY_CONSUME_TIME_SPAN, action = Action.GET) public class QueryConsumeTimeSpanRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryConsumerOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.QUERY_CONSUMER_OFFSET, action = Action.GET) public class QueryConsumerOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; private Boolean setZeroIfNotFound; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public Boolean getSetZeroIfNotFound() { return setZeroIfNotFound; } public void setSetZeroIfNotFound(Boolean setZeroIfNotFound) { this.setZeroIfNotFound = setZeroIfNotFound; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("queueId", queueId) .add("setZeroIfNotFound", setZeroIfNotFound) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryConsumerOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class QueryConsumerOffsetResponseHeader implements CommandCustomHeader { @CFNotNull private Long offset; @Override public void checkFields() throws RemotingCommandException { } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryCorrectionOffsetHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetMinOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.QUERY_CORRECTION_OFFSET, action = Action.GET) public class QueryCorrectionOffsetHeader extends TopicRequestHeader { @RocketMQResource(value = ResourceType.GROUP, splitter = ",") private String filterGroups; @CFNotNull @RocketMQResource(ResourceType.GROUP) private String compareGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getFilterGroups() { return filterGroups; } public void setFilterGroups(String filterGroups) { this.filterGroups = filterGroups; } public String getCompareGroup() { return compareGroup; } public void setCompareGroup(String compareGroup) { this.compareGroup = compareGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.QUERY_MESSAGE, action = {Action.SUB, Action.GET}) public class QueryMessageRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private String key; @CFNotNull private Integer maxNum; @CFNotNull private Long beginTimestamp; @CFNotNull private Long endTimestamp; private String indexType; private String lastKey; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Integer getMaxNum() { return maxNum; } public void setMaxNum(Integer maxNum) { this.maxNum = maxNum; } public Long getBeginTimestamp() { return beginTimestamp; } public void setBeginTimestamp(Long beginTimestamp) { this.beginTimestamp = beginTimestamp; } public Long getEndTimestamp() { return endTimestamp; } public void setEndTimestamp(Long endTimestamp) { this.endTimestamp = endTimestamp; } public String getIndexType() { return indexType; } public void setIndexType(String indexType) { this.indexType = indexType; } public String getLastKey() { return lastKey; } public void setLastKey(String lastKey) { this.lastKey = lastKey; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class QueryMessageResponseHeader implements CommandCustomHeader { @CFNotNull private Long indexLastUpdateTimestamp; @CFNotNull private Long indexLastUpdatePhyoffset; @Override public void checkFields() throws RemotingCommandException { } public Long getIndexLastUpdateTimestamp() { return indexLastUpdateTimestamp; } public void setIndexLastUpdateTimestamp(Long indexLastUpdateTimestamp) { this.indexLastUpdateTimestamp = indexLastUpdateTimestamp; } public Long getIndexLastUpdatePhyoffset() { return indexLastUpdatePhyoffset; } public void setIndexLastUpdatePhyoffset(Long indexLastUpdatePhyoffset) { this.indexLastUpdatePhyoffset = indexLastUpdatePhyoffset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QuerySubscriptionByConsumerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.QUERY_SUBSCRIPTION_BY_CONSUMER, action = Action.GET) public class QuerySubscriptionByConsumerRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @RocketMQResource(ResourceType.TOPIC) private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicConsumeByWhoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.QUERY_TOPIC_CONSUME_BY_WHO, action = Action.GET) public class QueryTopicConsumeByWhoRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicsByConsumerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.QUERY_TOPICS_BY_CONSUMER, action = Action.GET) public class QueryTopicsByConsumerRequestHeader extends RpcRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @Override public void checkFields() throws RemotingCommandException { } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/RecallMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.RECALL_MESSAGE, action = Action.PUB) public class RecallMessageRequestHeader extends TopicRequestHeader { @CFNullable private String producerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private String recallHandle; @Override public void checkFields() throws RemotingCommandException { } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getRecallHandle() { return recallHandle; } public void setRecallHandle(String recallHandle) { this.recallHandle = recallHandle; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("producerGroup", producerGroup) .add("topic", topic) .add("recallHandle", recallHandle) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/RecallMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RecallMessageResponseHeader implements CommandCustomHeader { @CFNotNull private String msgId; @Override public void checkFields() throws RemotingCommandException { } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/RemoveBrokerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.REMOVE_BROKER, resource = ResourceType.CLUSTER,action = Action.UPDATE) public class RemoveBrokerRequestHeader implements CommandCustomHeader { @CFNotNull private String brokerName; @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String brokerClusterName; @CFNotNull private Long brokerId; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerClusterName() { return brokerClusterName; } public void setBrokerClusterName(String brokerClusterName) { this.brokerClusterName = brokerClusterName; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ReplyMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT, action = Action.SUB) public class ReplyMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String producerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private String defaultTopic; @CFNotNull private Integer defaultTopicQueueNums; @CFNotNull private Integer queueId; @CFNotNull private Integer sysFlag; @CFNotNull private Long bornTimestamp; @CFNotNull private Integer flag; @CFNullable private String properties; @CFNullable private Integer reconsumeTimes; @CFNullable private boolean unitMode = false; @CFNotNull private String bornHost; @CFNotNull private String storeHost; @CFNotNull private long storeTimestamp; public void checkFields() throws RemotingCommandException { } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getDefaultTopic() { return defaultTopic; } public void setDefaultTopic(String defaultTopic) { this.defaultTopic = defaultTopic; } public Integer getDefaultTopicQueueNums() { return defaultTopicQueueNums; } public void setDefaultTopicQueueNums(Integer defaultTopicQueueNums) { this.defaultTopicQueueNums = defaultTopicQueueNums; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public Integer getSysFlag() { return sysFlag; } public void setSysFlag(Integer sysFlag) { this.sysFlag = sysFlag; } public Long getBornTimestamp() { return bornTimestamp; } public void setBornTimestamp(Long bornTimestamp) { this.bornTimestamp = bornTimestamp; } public Integer getFlag() { return flag; } public void setFlag(Integer flag) { this.flag = flag; } public String getProperties() { return properties; } public void setProperties(String properties) { this.properties = properties; } public Integer getReconsumeTimes() { return reconsumeTimes; } public void setReconsumeTimes(Integer reconsumeTimes) { this.reconsumeTimes = reconsumeTimes; } public boolean isUnitMode() { return unitMode; } public void setUnitMode(boolean unitMode) { this.unitMode = unitMode; } public String getBornHost() { return bornHost; } public void setBornHost(String bornHost) { this.bornHost = bornHost; } public String getStoreHost() { return storeHost; } public void setStoreHost(String storeHost) { this.storeHost = storeHost; } public long getStoreTimestamp() { return storeTimestamp; } public void setStoreTimestamp(long storeTimestamp) { this.storeTimestamp = storeTimestamp; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetMasterFlushOffsetHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.RESET_MASTER_FLUSH_OFFSET, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class ResetMasterFlushOffsetHeader implements CommandCustomHeader { @CFNotNull private Long masterFlushOffset; @Override public void checkFields() throws RemotingCommandException { } public Long getMasterFlushOffset() { return masterFlushOffset; } public void setMasterFlushOffset(Long masterFlushOffset) { this.masterFlushOffset = masterFlushOffset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, action = Action.UPDATE) public class ResetOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; private int queueId = -1; private Long offset; @CFNotNull private long timestamp; @CFNotNull private boolean isForce; public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public boolean isForce() { return isForce; } public void setForce(boolean isForce) { this.isForce = isForce; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.RESUME_CHECK_HALF_MESSAGE, action = Action.UPDATE) public class ResumeCheckHalfMessageRequestHeader implements CommandCustomHeader { @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNullable private String msgId; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } @Override public String toString() { return "ResumeCheckHalfMessageRequestHeader [msgId=" + msgId + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: SearchOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, action = Action.GET) public class SearchOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; private String liteTopic; @CFNotNull private Integer queueId; @CFNotNull private Long timestamp; private BoundaryType boundaryType; @Override public void checkFields() throws RemotingCommandException { } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } public String getLiteTopic() { return liteTopic; } public void setLiteTopic(String liteTopic) { this.liteTopic = liteTopic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public Long getTimestamp() { return timestamp; } public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } public BoundaryType getBoundaryType() { // default return LOWER return boundaryType == null ? BoundaryType.LOWER : boundaryType; } public void setBoundaryType(BoundaryType boundaryType) { this.boundaryType = boundaryType; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("liteTopic", liteTopic) .add("queueId", queueId) .add("timestamp", timestamp) .add("boundaryType", boundaryType.getName()) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: SearchOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class SearchOffsetResponseHeader implements CommandCustomHeader { @CFNotNull private Long offset; @Override public void checkFields() throws RemotingCommandException { } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: SendMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.SEND_MESSAGE, action = Action.PUB) public class SendMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String producerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private String defaultTopic; @CFNotNull private Integer defaultTopicQueueNums; @CFNotNull private Integer queueId; @CFNotNull private Integer sysFlag; @CFNotNull private Long bornTimestamp; @CFNotNull private Integer flag; @CFNullable private String properties; @CFNullable private Integer reconsumeTimes; @CFNullable private Boolean unitMode; @CFNullable private Boolean batch; private Integer maxReconsumeTimes; @Override public void checkFields() throws RemotingCommandException { } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } public String getDefaultTopic() { return defaultTopic; } public void setDefaultTopic(String defaultTopic) { this.defaultTopic = defaultTopic; } public Integer getDefaultTopicQueueNums() { return defaultTopicQueueNums; } public void setDefaultTopicQueueNums(Integer defaultTopicQueueNums) { this.defaultTopicQueueNums = defaultTopicQueueNums; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public Integer getSysFlag() { return sysFlag; } public void setSysFlag(Integer sysFlag) { this.sysFlag = sysFlag; } public Long getBornTimestamp() { return bornTimestamp; } public void setBornTimestamp(Long bornTimestamp) { this.bornTimestamp = bornTimestamp; } public Integer getFlag() { return flag; } public void setFlag(Integer flag) { this.flag = flag; } public String getProperties() { return properties; } public void setProperties(String properties) { this.properties = properties; } public Integer getReconsumeTimes() { if (null == reconsumeTimes) { return 0; } return reconsumeTimes; } public void setReconsumeTimes(Integer reconsumeTimes) { this.reconsumeTimes = reconsumeTimes; } public boolean isUnitMode() { if (null == unitMode) { return false; } return unitMode; } public void setUnitMode(Boolean isUnitMode) { this.unitMode = isUnitMode; } public Integer getMaxReconsumeTimes() { return maxReconsumeTimes; } public void setMaxReconsumeTimes(final Integer maxReconsumeTimes) { this.maxReconsumeTimes = maxReconsumeTimes; } public boolean isBatch() { if (null == batch) { return false; } return batch; } public void setBatch(Boolean batch) { this.batch = batch; } public static SendMessageRequestHeader parseRequestHeader(RemotingCommand request) throws RemotingCommandException { SendMessageRequestHeaderV2 requestHeaderV2 = null; SendMessageRequestHeader requestHeader = null; switch (request.getCode()) { case RequestCode.SEND_BATCH_MESSAGE: case RequestCode.SEND_MESSAGE_V2: requestHeaderV2 = request.decodeCommandCustomHeader(SendMessageRequestHeaderV2.class); case RequestCode.SEND_MESSAGE: if (null == requestHeaderV2) { requestHeader = request.decodeCommandCustomHeader(SendMessageRequestHeader.class); } else { requestHeader = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV1(requestHeaderV2); } default: break; } return requestHeader; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("producerGroup", producerGroup) .add("topic", topic) .add("defaultTopic", defaultTopic) .add("defaultTopicQueueNums", defaultTopicQueueNums) .add("queueId", queueId) .add("sysFlag", sysFlag) .add("bornTimestamp", bornTimestamp) .add("flag", flag) .add("properties", properties) .add("reconsumeTimes", reconsumeTimes) .add("unitMode", unitMode) .add("batch", batch) .add("maxReconsumeTimes", maxReconsumeTimes) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeaderV2.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import io.netty.buffer.ByteBuf; import java.util.HashMap; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.FastCodesHeader; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; /** * Use short variable name to speed up FastJson deserialization process. */ @RocketMQAction(value = RequestCode.SEND_MESSAGE_V2, action = Action.PUB) public class SendMessageRequestHeaderV2 extends TopicQueueRequestHeader implements CommandCustomHeader, FastCodesHeader { @CFNotNull private String a; // producerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String b; // topic; @CFNotNull private String c; // defaultTopic; @CFNotNull private Integer d; // defaultTopicQueueNums; @CFNotNull private Integer e; // queueId; @CFNotNull private Integer f; // sysFlag; @CFNotNull private Long g; // bornTimestamp; @CFNotNull private Integer h; // flag; @CFNullable private String i; // properties; @CFNullable private Integer j; // reconsumeTimes; @CFNullable private Boolean k; // unitMode; private Integer l; // consumeRetryTimes @CFNullable private Boolean m; //batch @CFNullable private String n; // brokerName public static SendMessageRequestHeader createSendMessageRequestHeaderV1(final SendMessageRequestHeaderV2 v2) { SendMessageRequestHeader v1 = new SendMessageRequestHeader(); v1.setProducerGroup(v2.a); v1.setTopic(v2.b); v1.setDefaultTopic(v2.c); v1.setDefaultTopicQueueNums(v2.d); v1.setQueueId(v2.e); v1.setSysFlag(v2.f); v1.setBornTimestamp(v2.g); v1.setFlag(v2.h); v1.setProperties(v2.i); v1.setReconsumeTimes(v2.j); v1.setUnitMode(v2.k); v1.setMaxReconsumeTimes(v2.l); v1.setBatch(v2.m); v1.setBrokerName(v2.n); return v1; } public static SendMessageRequestHeaderV2 createSendMessageRequestHeaderV2(final SendMessageRequestHeader v1) { SendMessageRequestHeaderV2 v2 = new SendMessageRequestHeaderV2(); v2.a = v1.getProducerGroup(); v2.b = v1.getTopic(); v2.c = v1.getDefaultTopic(); v2.d = v1.getDefaultTopicQueueNums(); v2.e = v1.getQueueId(); v2.f = v1.getSysFlag(); v2.g = v1.getBornTimestamp(); v2.h = v1.getFlag(); v2.i = v1.getProperties(); v2.j = v1.getReconsumeTimes(); v2.k = v1.isUnitMode(); v2.l = v1.getMaxReconsumeTimes(); v2.m = v1.isBatch(); v2.n = v1.getBrokerName(); return v2; } @Override public void checkFields() throws RemotingCommandException { } @Override public void encode(ByteBuf out) { writeIfNotNull(out, "a", a); writeIfNotNull(out, "b", b); writeIfNotNull(out, "c", c); writeIfNotNull(out, "d", d); writeIfNotNull(out, "e", e); writeIfNotNull(out, "f", f); writeIfNotNull(out, "g", g); writeIfNotNull(out, "h", h); writeIfNotNull(out, "i", i); writeIfNotNull(out, "j", j); writeIfNotNull(out, "k", k); writeIfNotNull(out, "l", l); writeIfNotNull(out, "m", m); writeIfNotNull(out, "n", n); } @Override public void decode(HashMap fields) throws RemotingCommandException { String str = getAndCheckNotNull(fields, "a"); if (str != null) { a = str; } str = getAndCheckNotNull(fields, "b"); if (str != null) { b = str; } str = getAndCheckNotNull(fields, "c"); if (str != null) { c = str; } str = getAndCheckNotNull(fields, "d"); if (str != null) { d = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "e"); if (str != null) { e = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "f"); if (str != null) { f = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "g"); if (str != null) { g = Long.parseLong(str); } str = getAndCheckNotNull(fields, "h"); if (str != null) { h = Integer.parseInt(str); } str = fields.get("i"); if (str != null) { i = str; } str = fields.get("j"); if (str != null) { j = Integer.parseInt(str); } str = fields.get("k"); if (str != null) { k = Boolean.parseBoolean(str); } str = fields.get("l"); if (str != null) { l = Integer.parseInt(str); } str = fields.get("m"); if (str != null) { m = Boolean.parseBoolean(str); } str = fields.get("n"); if (str != null) { n = str; } } public String getA() { return a; } public void setA(String a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public String getC() { return c; } public void setC(String c) { this.c = c; } public Integer getD() { return d; } public void setD(Integer d) { this.d = d; } public Integer getE() { return e; } public void setE(Integer e) { this.e = e; } public Integer getF() { return f; } public void setF(Integer f) { this.f = f; } public Long getG() { return g; } public void setG(Long g) { this.g = g; } public Integer getH() { return h; } public void setH(Integer h) { this.h = h; } public String getI() { return i; } public void setI(String i) { this.i = i; } public Integer getJ() { return j; } public void setJ(Integer j) { this.j = j; } public Boolean isK() { return k; } public void setK(Boolean k) { this.k = k; } public Integer getL() { return l; } public void setL(final Integer l) { this.l = l; } public Boolean isM() { return m; } public void setM(Boolean m) { this.m = m; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("a", a) .add("b", b) .add("c", c) .add("d", d) .add("e", e) .add("f", f) .add("g", g) .add("h", h) .add("i", i) .add("j", j) .add("k", k) .add("l", l) .add("m", m) .add("n", n) .toString(); } @Override public Integer getQueueId() { return e; } @Override public void setQueueId(Integer queueId) { this.e = queueId; } @Override public String getTopic() { return b; } @Override public void setTopic(String topic) { this.b = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: SendMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import io.netty.buffer.ByteBuf; import java.util.HashMap; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.FastCodesHeader; public class SendMessageResponseHeader implements CommandCustomHeader, FastCodesHeader { @CFNotNull private String msgId; @CFNotNull private Integer queueId; @CFNotNull private Long queueOffset; private String transactionId; private String batchUniqId; private String recallHandle; @Override public void checkFields() throws RemotingCommandException { } @Override public void encode(ByteBuf out) { writeIfNotNull(out, "msgId", msgId); writeIfNotNull(out, "queueId", queueId); writeIfNotNull(out, "queueOffset", queueOffset); writeIfNotNull(out, "transactionId", transactionId); writeIfNotNull(out, "batchUniqId", batchUniqId); writeIfNotNull(out, "recallHandle", recallHandle); } @Override public void decode(HashMap fields) throws RemotingCommandException { String str = getAndCheckNotNull(fields, "msgId"); if (str != null) { this.msgId = str; } str = getAndCheckNotNull(fields, "queueId"); if (str != null) { this.queueId = Integer.parseInt(str); } str = getAndCheckNotNull(fields, "queueOffset"); if (str != null) { this.queueOffset = Long.parseLong(str); } str = fields.get("transactionId"); if (str != null) { this.transactionId = str; } str = fields.get("batchUniqId"); if (str != null) { this.batchUniqId = str; } str = fields.get("recallHandle"); if (str != null) { this.recallHandle = str; } } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public Integer getQueueId() { return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public Long getQueueOffset() { return queueOffset; } public void setQueueOffset(Long queueOffset) { this.queueOffset = queueOffset; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } public String getBatchUniqId() { return batchUniqId; } public void setBatchUniqId(String batchUniqId) { this.batchUniqId = batchUniqId; } public String getRecallHandle() { return recallHandle; } public void setRecallHandle(String recallHandle) { this.recallHandle = recallHandle; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/StatisticsMessagesRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; public class StatisticsMessagesRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String consumerGroup; @CFNotNull private String topic; @CFNotNull private int queueId; private long fromTime; private long toTime; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getQueueId() { if (queueId < 0) { return -1; } return queueId; } public void setQueueId(Integer queueId) { this.queueId = queueId; } public long getFromTime() { return fromTime; } public void setFromTime(long fromTime) { this.fromTime = fromTime; } public long getToTime() { return toTime; } public void setToTime(long toTime) { this.toTime = toTime; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/TriggerLiteDispatchRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class TriggerLiteDispatchRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; private String clientId; public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnlockBatchMqRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; @RocketMQAction(value = RequestCode.UNLOCK_BATCH_MQ, action = Action.SUB) public class UnlockBatchMqRequestHeader extends RpcRequestHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.UNREGISTER_CLIENT, action = {Action.PUB, Action.SUB}) public class UnregisterClientRequestHeader extends RpcRequestHeader { @CFNotNull private String clientID; @CFNullable private String producerGroup; @CFNullable @RocketMQResource(ResourceType.GROUP) private String consumerGroup; public String getClientID() { return clientID; } public void setClientID(String clientID) { this.clientID = clientID; } public String getProducerGroup() { return producerGroup; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class UnregisterClientResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateAclRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_UPDATE_ACL, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class UpdateAclRequestHeader implements CommandCustomHeader { private String subject; public UpdateAclRequestHeader() { } public UpdateAclRequestHeader(String subject) { this.subject = subject; } @Override public void checkFields() throws RemotingCommandException { } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: UpdateConsumerOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; @RocketMQAction(value = RequestCode.UPDATE_CONSUMER_OFFSET, action = Action.SUB) public class UpdateConsumerOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String consumerGroup; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Integer queueId; @CFNotNull private Long commitOffset; @Override public void checkFields() throws RemotingCommandException { } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } @Override public String getTopic() { return topic; } @Override public void setTopic(String topic) { this.topic = topic; } @Override public Integer getQueueId() { return queueId; } @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } public Long getCommitOffset() { return commitOffset; } public void setCommitOffset(Long commitOffset) { this.commitOffset = commitOffset; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("consumerGroup", consumerGroup) .add("topic", topic) .add("queueId", queueId) .add("commitOffset", commitOffset) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: UpdateConsumerOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class UpdateConsumerOffsetResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGroupForbiddenRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: CreateTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.UPDATE_AND_GET_GROUP_FORBIDDEN, action = Action.UPDATE) public class UpdateGroupForbiddenRequestHeader extends TopicRequestHeader { @CFNotNull @RocketMQResource(ResourceType.GROUP) private String group; @CFNotNull @RocketMQResource(ResourceType.TOPIC) private String topic; private Boolean readable; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public Boolean getReadable() { return readable; } public void setReadable(Boolean readable) { this.readable = readable; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateUserRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.AUTH_UPDATE_USER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class UpdateUserRequestHeader implements CommandCustomHeader { private String username; public UpdateUserRequestHeader() { } public UpdateUserRequestHeader(String username) { this.username = username; } @Override public void checkFields() throws RemotingCommandException { } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewBrokerStatsDataRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.VIEW_BROKER_STATS_DATA, resource = ResourceType.CLUSTER, action = Action.GET) public class ViewBrokerStatsDataRequestHeader implements CommandCustomHeader { @CFNotNull private String statsName; @CFNotNull private String statsKey; @Override public void checkFields() throws RemotingCommandException { } public String getStatsName() { return statsName; } public void setStatsName(String statsName) { this.statsName = statsName; } public String getStatsKey() { return statsKey; } public void setStatsKey(String statsKey) { this.statsKey = statsKey; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: ViewMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.VIEW_MESSAGE_BY_ID, action = Action.GET) public class ViewMessageRequestHeader implements CommandCustomHeader { @RocketMQResource(ResourceType.TOPIC) private String topic; @CFNotNull private Long offset; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Long getOffset() { return offset; } public void setOffset(Long offset) { this.offset = offset; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: ViewMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class ViewMessageResponseHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CONTROLLER_ALTER_SYNC_STATE_SET, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class AlterSyncStateSetRequestHeader implements CommandCustomHeader { private String brokerName; private Long masterBrokerId; private Integer masterEpoch; private long invokeTime = System.currentTimeMillis(); public AlterSyncStateSetRequestHeader() { } public AlterSyncStateSetRequestHeader(String brokerName, Long masterBrokerId, Integer masterEpoch) { this.brokerName = brokerName; this.masterBrokerId = masterBrokerId; this.masterEpoch = masterEpoch; } public long getInvokeTime() { return invokeTime; } public void setInvokeTime(long invokeTime) { this.invokeTime = invokeTime; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public Long getMasterBrokerId() { return masterBrokerId; } public void setMasterBrokerId(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; } public Integer getMasterEpoch() { return masterEpoch; } public void setMasterEpoch(Integer masterEpoch) { this.masterEpoch = masterEpoch; } @Override public String toString() { return "AlterSyncStateSetRequestHeader{" + "brokerName='" + brokerName + '\'' + ", masterBrokerId=" + masterBrokerId + ", masterEpoch=" + masterEpoch + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class AlterSyncStateSetResponseHeader implements CommandCustomHeader { private int newSyncStateSetEpoch; public AlterSyncStateSetResponseHeader() { } public int getNewSyncStateSetEpoch() { return newSyncStateSetEpoch; } public void setNewSyncStateSetEpoch(int newSyncStateSetEpoch) { this.newSyncStateSetEpoch = newSyncStateSetEpoch; } @Override public String toString() { return "AlterSyncStateSetResponseHeader{" + "newSyncStateSetEpoch=" + newSyncStateSetEpoch + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CONTROLLER_ELECT_MASTER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class ElectMasterRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String clusterName = ""; @CFNotNull private String brokerName = ""; /** * brokerId * for brokerTrigger electMaster: this brokerId will be elected as a master when it is the first time to elect * in this broker-set * for adminTrigger electMaster: this brokerId is also named assignedBrokerId, which means we must prefer to elect * it as a new master when this broker is valid. */ @CFNotNull private Long brokerId = -1L; @CFNotNull private Boolean designateElect = false; private Long invokeTime = System.currentTimeMillis(); public ElectMasterRequestHeader() { } public ElectMasterRequestHeader(String brokerName) { this.brokerName = brokerName; } public ElectMasterRequestHeader(String clusterName, String brokerName, Long brokerId) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerId = brokerId; } public ElectMasterRequestHeader(String clusterName, String brokerName, Long brokerId, boolean designateElect) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerId = brokerId; this.designateElect = designateElect; } public static ElectMasterRequestHeader ofBrokerTrigger(String clusterName, String brokerName, Long brokerId) { return new ElectMasterRequestHeader(clusterName, brokerName, brokerId); } public static ElectMasterRequestHeader ofControllerTrigger(String brokerName) { return new ElectMasterRequestHeader(brokerName); } public static ElectMasterRequestHeader ofAdminTrigger(String clusterName, String brokerName, Long brokerId) { return new ElectMasterRequestHeader(clusterName, brokerName, brokerId, true); } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public boolean getDesignateElect() { return this.designateElect; } public Long getInvokeTime() { return invokeTime; } public void setInvokeTime(Long invokeTime) { this.invokeTime = invokeTime; } @Override public String toString() { return "ElectMasterRequestHeader{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + ", designateElect=" + designateElect + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class ElectMasterResponseHeader implements CommandCustomHeader { private Long masterBrokerId; private String masterAddress; private Integer masterEpoch; private Integer syncStateSetEpoch; public ElectMasterResponseHeader() { } public ElectMasterResponseHeader(Long masterBrokerId, String masterAddress, Integer masterEpoch, Integer syncStateSetEpoch) { this.masterBrokerId = masterBrokerId; this.masterAddress = masterAddress; this.masterEpoch = masterEpoch; this.syncStateSetEpoch = syncStateSetEpoch; } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } public Integer getMasterEpoch() { return masterEpoch; } public void setMasterEpoch(Integer masterEpoch) { this.masterEpoch = masterEpoch; } public Integer getSyncStateSetEpoch() { return syncStateSetEpoch; } public void setSyncStateSetEpoch(Integer syncStateSetEpoch) { this.syncStateSetEpoch = syncStateSetEpoch; } public void setMasterBrokerId(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; } public Long getMasterBrokerId() { return masterBrokerId; } @Override public String toString() { return "ElectMasterResponseHeader{" + "masterBrokerId=" + masterBrokerId + ", masterAddress='" + masterAddress + '\'' + ", masterEpoch=" + masterEpoch + ", syncStateSetEpoch=" + syncStateSetEpoch + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetMetaDataResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetMetaDataResponseHeader implements CommandCustomHeader { private String group; private String controllerLeaderId; private String controllerLeaderAddress; private boolean isLeader; private String peers; public GetMetaDataResponseHeader() { } public GetMetaDataResponseHeader(String group, String controllerLeaderId, String controllerLeaderAddress, boolean isLeader, String peers) { this.group = group; this.controllerLeaderId = controllerLeaderId; this.controllerLeaderAddress = controllerLeaderAddress; this.isLeader = isLeader; this.peers = peers; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getControllerLeaderId() { return controllerLeaderId; } public void setControllerLeaderId(String controllerLeaderId) { this.controllerLeaderId = controllerLeaderId; } public String getControllerLeaderAddress() { return controllerLeaderAddress; } public void setControllerLeaderAddress(String controllerLeaderAddress) { this.controllerLeaderAddress = controllerLeaderAddress; } public boolean isLeader() { return isLeader; } public void setIsLeader(boolean leader) { isLeader = leader; } public String getPeers() { return peers; } public void setPeers(String peers) { this.peers = peers; } @Override public String toString() { return "GetMetaDataResponseHeader{" + "group='" + group + '\'' + ", controllerLeaderId='" + controllerLeaderId + '\'' + ", controllerLeaderAddress='" + controllerLeaderAddress + '\'' + ", isLeader=" + isLeader + ", peers='" + peers + '\'' + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CONTROLLER_GET_REPLICA_INFO, resource = ResourceType.CLUSTER, action = Action.GET) public class GetReplicaInfoRequestHeader implements CommandCustomHeader { private String brokerName; public GetReplicaInfoRequestHeader() { } public GetReplicaInfoRequestHeader(String brokerName) { this.brokerName = brokerName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } @Override public String toString() { return "GetReplicaInfoRequestHeader{" + "brokerName='" + brokerName + '\'' + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetReplicaInfoResponseHeader implements CommandCustomHeader { private Long masterBrokerId; private String masterAddress; private Integer masterEpoch; public GetReplicaInfoResponseHeader() { } public String getMasterAddress() { return masterAddress; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } public Integer getMasterEpoch() { return masterEpoch; } public void setMasterEpoch(Integer masterEpoch) { this.masterEpoch = masterEpoch; } public Long getMasterBrokerId() { return masterBrokerId; } public void setMasterBrokerId(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; } @Override public String toString() { return "GetReplicaInfoResponseHeader{" + "masterBrokerId=" + masterBrokerId + ", masterAddress='" + masterAddress + '\'' + ", masterEpoch=" + masterEpoch + '}'; } @Override public void checkFields() throws RemotingCommandException { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/admin/CleanControllerBrokerDataRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.admin; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CLEAN_BROKER_DATA, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class CleanControllerBrokerDataRequestHeader implements CommandCustomHeader { @CFNullable @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @CFNotNull private String brokerName; @CFNullable private String brokerControllerIdsToClean; private boolean isCleanLivingBroker = false; private long invokeTime = System.currentTimeMillis(); public CleanControllerBrokerDataRequestHeader() { } public CleanControllerBrokerDataRequestHeader(String clusterName, String brokerName, String brokerIdSetToClean, boolean isCleanLivingBroker) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerControllerIdsToClean = brokerIdSetToClean; this.isCleanLivingBroker = isCleanLivingBroker; } public CleanControllerBrokerDataRequestHeader(String clusterName, String brokerName, String brokerIdSetToClean) { this(clusterName, brokerName, brokerIdSetToClean, false); } @Override public void checkFields() throws RemotingCommandException { } public long getInvokeTime() { return invokeTime; } public void setInvokeTime(long invokeTime) { this.invokeTime = invokeTime; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerControllerIdsToClean() { return brokerControllerIdsToClean; } public void setBrokerControllerIdsToClean(String brokerIdSetToClean) { this.brokerControllerIdsToClean = brokerIdSetToClean; } public boolean isCleanLivingBroker() { return isCleanLivingBroker; } public void setCleanLivingBroker(boolean cleanLivingBroker) { isCleanLivingBroker = cleanLivingBroker; } @Override public String toString() { return "CleanControllerBrokerDataRequestHeader{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerIdSetToClean='" + brokerControllerIdsToClean + '\'' + ", isCleanLivingBroker=" + isCleanLivingBroker + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.register; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CONTROLLER_APPLY_BROKER_ID, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class ApplyBrokerIdRequestHeader implements CommandCustomHeader { @RocketMQResource(ResourceType.CLUSTER) private String clusterName; private String brokerName; private Long appliedBrokerId; private String registerCheckCode; public ApplyBrokerIdRequestHeader() { } public ApplyBrokerIdRequestHeader(String clusterName, String brokerName, Long appliedBrokerId, String registerCheckCode) { this.clusterName = clusterName; this.brokerName = brokerName; this.appliedBrokerId = appliedBrokerId; this.registerCheckCode = registerCheckCode; } @Override public void checkFields() throws RemotingCommandException { } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public Long getAppliedBrokerId() { return appliedBrokerId; } public String getRegisterCheckCode() { return registerCheckCode; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public void setAppliedBrokerId(Long appliedBrokerId) { this.appliedBrokerId = appliedBrokerId; } public void setRegisterCheckCode(String registerCheckCode) { this.registerCheckCode = registerCheckCode; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.register; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class ApplyBrokerIdResponseHeader implements CommandCustomHeader { private String clusterName; private String brokerName; public ApplyBrokerIdResponseHeader() { } public ApplyBrokerIdResponseHeader(String clusterName, String brokerName) { this.clusterName = clusterName; this.brokerName = brokerName; } @Override public String toString() { return "ApplyBrokerIdResponseHeader{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + '}'; } @Override public void checkFields() throws RemotingCommandException { } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.register; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CONTROLLER_GET_NEXT_BROKER_ID, resource = ResourceType.CLUSTER, action = Action.GET) public class GetNextBrokerIdRequestHeader implements CommandCustomHeader { @RocketMQResource(ResourceType.CLUSTER) private String clusterName; private String brokerName; public GetNextBrokerIdRequestHeader() { } public GetNextBrokerIdRequestHeader(String clusterName, String brokerName) { this.clusterName = clusterName; this.brokerName = brokerName; } @Override public String toString() { return "GetNextBrokerIdRequestHeader{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + '}'; } @Override public void checkFields() throws RemotingCommandException { } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.register; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetNextBrokerIdResponseHeader implements CommandCustomHeader { private String clusterName; private String brokerName; private Long nextBrokerId; public GetNextBrokerIdResponseHeader() { } public GetNextBrokerIdResponseHeader(String clusterName, String brokerName) { this(clusterName, brokerName, null); } public GetNextBrokerIdResponseHeader(String clusterName, String brokerName, Long nextBrokerId) { this.clusterName = clusterName; this.brokerName = brokerName; this.nextBrokerId = nextBrokerId; } @Override public String toString() { return "GetNextBrokerIdResponseHeader{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", nextBrokerId=" + nextBrokerId + '}'; } @Override public void checkFields() throws RemotingCommandException { } public void setNextBrokerId(Long nextBrokerId) { this.nextBrokerId = nextBrokerId; } public Long getNextBrokerId() { return nextBrokerId; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.register; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.CONTROLLER_REGISTER_BROKER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class RegisterBrokerToControllerRequestHeader implements CommandCustomHeader { @RocketMQResource(ResourceType.CLUSTER) private String clusterName; private String brokerName; private Long brokerId; private String brokerAddress; private long invokeTime; public RegisterBrokerToControllerRequestHeader() { } public RegisterBrokerToControllerRequestHeader(String clusterName, String brokerName, Long brokerId, String brokerAddress) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerId = brokerId; this.brokerAddress = brokerAddress; this.invokeTime = System.currentTimeMillis(); } @Override public void checkFields() throws RemotingCommandException { } public long getInvokeTime() { return invokeTime; } public void setInvokeTime(long invokeTime) { this.invokeTime = invokeTime; } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public Long getBrokerId() { return brokerId; } public String getBrokerAddress() { return brokerAddress; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } public void setBrokerAddress(String brokerAddress) { this.brokerAddress = brokerAddress; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.controller.register; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RegisterBrokerToControllerResponseHeader implements CommandCustomHeader { private String clusterName; private String brokerName; private Long masterBrokerId; private String masterAddress; private Integer masterEpoch; private Integer syncStateSetEpoch; @Override public void checkFields() throws RemotingCommandException { } public RegisterBrokerToControllerResponseHeader() { } public RegisterBrokerToControllerResponseHeader(String clusterName, String brokerName) { this.clusterName = clusterName; this.brokerName = brokerName; } public void setMasterBrokerId(Long masterBrokerId) { this.masterBrokerId = masterBrokerId; } public void setMasterAddress(String masterAddress) { this.masterAddress = masterAddress; } public void setMasterEpoch(Integer masterEpoch) { this.masterEpoch = masterEpoch; } public void setSyncStateSetEpoch(Integer syncStateSetEpoch) { this.syncStateSetEpoch = syncStateSetEpoch; } public Integer getMasterEpoch() { return masterEpoch; } public Integer getSyncStateSetEpoch() { return syncStateSetEpoch; } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public Long getMasterBrokerId() { return masterBrokerId; } public String getMasterAddress() { return masterAddress; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.ADD_WRITE_PERM_OF_BROKER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class AddWritePermOfBrokerRequestHeader implements CommandCustomHeader { @CFNotNull private String brokerName; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class AddWritePermOfBrokerResponseHeader implements CommandCustomHeader { @CFNotNull private Integer addTopicCount; @Override public void checkFields() throws RemotingCommandException { } public Integer getAddTopicCount() { return addTopicCount; } public void setAddTopicCount(Integer addTopicCount) { this.addTopicCount = addTopicCount; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/BrokerHeartbeatRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.BROKER_HEARTBEAT, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class BrokerHeartbeatRequestHeader implements CommandCustomHeader { @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @CFNotNull private String brokerAddr; @CFNotNull private String brokerName; @CFNullable private Long brokerId; @CFNullable private Integer epoch; @CFNullable private Long maxOffset; @CFNullable private Long confirmOffset; @CFNullable private Long heartbeatTimeoutMills; @CFNullable private Integer electionPriority; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public Integer getEpoch() { return epoch; } public void setEpoch(Integer epoch) { this.epoch = epoch; } public Long getMaxOffset() { return maxOffset; } public void setMaxOffset(Long maxOffset) { this.maxOffset = maxOffset; } public Long getConfirmOffset() { return confirmOffset; } public void setConfirmOffset(Long confirmOffset) { this.confirmOffset = confirmOffset; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } public Long getHeartbeatTimeoutMills() { return heartbeatTimeoutMills; } public void setHeartbeatTimeoutMills(Long heartbeatTimeoutMills) { this.heartbeatTimeoutMills = heartbeatTimeoutMills; } public Integer getElectionPriority() { return electionPriority; } public void setElectionPriority(Integer electionPriority) { this.electionPriority = electionPriority; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteKVConfigRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.DELETE_KV_CONFIG, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class DeleteKVConfigRequestHeader implements CommandCustomHeader { @CFNotNull private String namespace; @CFNotNull private String key; @Override public void checkFields() throws RemotingCommandException { } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.DELETE_TOPIC_IN_NAMESRV, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class DeleteTopicFromNamesrvRequestHeader extends TopicRequestHeader { @CFNotNull private String topic; @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_KV_CONFIG, resource = ResourceType.CLUSTER, action = Action.GET) public class GetKVConfigRequestHeader implements CommandCustomHeader { @CFNotNull private String namespace; @CFNotNull private String key; @Override public void checkFields() throws RemotingCommandException { } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetKVConfigResponseHeader implements CommandCustomHeader { @CFNullable private String value; @Override public void checkFields() throws RemotingCommandException { } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.GET_KVLIST_BY_NAMESPACE, resource = ResourceType.CLUSTER, action = Action.GET) public class GetKVListByNamespaceRequestHeader implements CommandCustomHeader { @CFNotNull private String namespace; @Override public void checkFields() throws RemotingCommandException { } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetRouteInfoRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: GetRouteInfoRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; @RocketMQAction(value = RequestCode.GET_ROUTEINFO_BY_TOPIC, resource = ResourceType.CLUSTER, action = Action.GET) public class GetRouteInfoRequestHeader extends TopicRequestHeader { @CFNotNull private String topic; @CFNullable private Boolean acceptStandardJsonOnly; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Boolean getAcceptStandardJsonOnly() { return acceptStandardJsonOnly; } public void setAcceptStandardJsonOnly(Boolean acceptStandardJsonOnly) { this.acceptStandardJsonOnly = acceptStandardJsonOnly; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/PutKVConfigRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.PUT_KV_CONFIG, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class PutKVConfigRequestHeader implements CommandCustomHeader { @CFNotNull private String namespace; @CFNotNull private String key; @CFNotNull private String value; @Override public void checkFields() throws RemotingCommandException { } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.QUERY_DATA_VERSION, resource = ResourceType.CLUSTER, action = Action.GET) public class QueryDataVersionRequestHeader implements CommandCustomHeader { @CFNotNull private String brokerName; @CFNotNull private String brokerAddr; @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @CFNotNull private Long brokerId; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class QueryDataVersionResponseHeader implements CommandCustomHeader { @CFNotNull private Boolean changed; @Override public void checkFields() throws RemotingCommandException { } public Boolean getChanged() { return changed; } public void setChanged(Boolean changed) { this.changed = changed; } @Override public String toString() { final StringBuilder sb = new StringBuilder("QueryDataVersionResponseHeader{"); sb.append("changed=").append(changed); sb.append('}'); return sb.toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: RegisterBrokerRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.REGISTER_BROKER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class RegisterBrokerRequestHeader implements CommandCustomHeader { @CFNotNull private String brokerName; @CFNotNull private String brokerAddr; @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @CFNotNull private String haServerAddr; @CFNotNull private Long brokerId; @CFNullable private Long heartbeatTimeoutMillis; @CFNullable private Boolean enableActingMaster; private boolean compressed; private Integer bodyCrc32 = 0; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getHaServerAddr() { return haServerAddr; } public void setHaServerAddr(String haServerAddr) { this.haServerAddr = haServerAddr; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } public Long getHeartbeatTimeoutMillis() { return heartbeatTimeoutMillis; } public void setHeartbeatTimeoutMillis(Long heartbeatTimeoutMillis) { this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; } public boolean isCompressed() { return compressed; } public void setCompressed(boolean compressed) { this.compressed = compressed; } public Integer getBodyCrc32() { return bodyCrc32; } public void setBodyCrc32(Integer bodyCrc32) { this.bodyCrc32 = bodyCrc32; } public Boolean getEnableActingMaster() { return enableActingMaster; } public void setEnableActingMaster(Boolean enableActingMaster) { this.enableActingMaster = enableActingMaster; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RegisterBrokerResponseHeader implements CommandCustomHeader { @CFNullable private String haServerAddr; @CFNullable private String masterAddr; @Override public void checkFields() throws RemotingCommandException { } public String getHaServerAddr() { return haServerAddr; } public void setHaServerAddr(String haServerAddr) { this.haServerAddr = haServerAddr; } public String getMasterAddr() { return masterAddr; } public void setMasterAddr(String masterAddr) { this.masterAddr = masterAddr; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: RegisterOrderTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RegisterOrderTopicRequestHeader implements CommandCustomHeader { @CFNotNull private String topic; @CFNotNull private String orderTopicString; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getOrderTopicString() { return orderTopicString; } public void setOrderTopicString(String orderTopicString) { this.orderTopicString = orderTopicString; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterTopicRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.REGISTER_TOPIC_IN_NAMESRV, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class RegisterTopicRequestHeader extends TopicRequestHeader { @CFNotNull private String topic; @Override public void checkFields() throws RemotingCommandException { } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: UnRegisterBrokerRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.common.resource.RocketMQResource; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.UNREGISTER_BROKER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class UnRegisterBrokerRequestHeader implements CommandCustomHeader { @CFNotNull private String brokerName; @CFNotNull private String brokerAddr; @CFNotNull @RocketMQResource(ResourceType.CLUSTER) private String clusterName; @CFNotNull private Long brokerId; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerAddr() { return brokerAddr; } public void setBrokerAddr(String brokerAddr) { this.brokerAddr = brokerAddr; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public Long getBrokerId() { return brokerId; } public void setBrokerId(Long brokerId) { this.brokerId = brokerId; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.common.action.Action; import org.apache.rocketmq.common.action.RocketMQAction; import org.apache.rocketmq.common.resource.ResourceType; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RequestCode; @RocketMQAction(value = RequestCode.WIPE_WRITE_PERM_OF_BROKER, resource = ResourceType.CLUSTER, action = Action.UPDATE) public class WipeWritePermOfBrokerRequestHeader implements CommandCustomHeader { @CFNotNull private String brokerName; @Override public void checkFields() throws RemotingCommandException { } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class WipeWritePermOfBrokerResponseHeader implements CommandCustomHeader { @CFNotNull private Integer wipeTopicCount; @Override public void checkFields() throws RemotingCommandException { } public Integer getWipeTopicCount() { return wipeTopicCount; } public void setWipeTopicCount(Integer wipeTopicCount) { this.wipeTopicCount = wipeTopicCount; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumeType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: ConsumeType.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.heartbeat; public enum ConsumeType { CONSUME_ACTIVELY("PULL"), CONSUME_PASSIVELY("PUSH"), CONSUME_POP("POP"); private String typeCN; ConsumeType(String typeCN) { this.typeCN = typeCN; } public String getTypeCN() { return typeCN; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumerData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: ConsumerData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.heartbeat; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; public class ConsumerData { private String groupName; private ConsumeType consumeType; private MessageModel messageModel; private ConsumeFromWhere consumeFromWhere; private Set subscriptionDataSet = new HashSet<>(); private boolean unitMode; public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public ConsumeType getConsumeType() { return consumeType; } public void setConsumeType(ConsumeType consumeType) { this.consumeType = consumeType; } public MessageModel getMessageModel() { return messageModel; } public void setMessageModel(MessageModel messageModel) { this.messageModel = messageModel; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public void setConsumeFromWhere(ConsumeFromWhere consumeFromWhere) { this.consumeFromWhere = consumeFromWhere; } public Set getSubscriptionDataSet() { return subscriptionDataSet; } public void setSubscriptionDataSet(Set subscriptionDataSet) { this.subscriptionDataSet = subscriptionDataSet; } public boolean isUnitMode() { return unitMode; } public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } @Override public String toString() { return "ConsumerData [groupName=" + groupName + ", consumeType=" + consumeType + ", messageModel=" + messageModel + ", consumeFromWhere=" + consumeFromWhere + ", unitMode=" + unitMode + ", subscriptionDataSet=" + subscriptionDataSet + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/HeartbeatData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: HeartbeatData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.heartbeat; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.util.HashSet; import java.util.Set; public class HeartbeatData extends RemotingSerializable { private String clientID; private Set producerDataSet = new HashSet<>(); private Set consumerDataSet = new HashSet<>(); private int heartbeatFingerprint = 0; private boolean isWithoutSub = false; public String getClientID() { return clientID; } public void setClientID(String clientID) { this.clientID = clientID; } public Set getProducerDataSet() { return producerDataSet; } public void setProducerDataSet(Set producerDataSet) { this.producerDataSet = producerDataSet; } public Set getConsumerDataSet() { return consumerDataSet; } public void setConsumerDataSet(Set consumerDataSet) { this.consumerDataSet = consumerDataSet; } public int getHeartbeatFingerprint() { return heartbeatFingerprint; } public void setHeartbeatFingerprint(int heartbeatFingerprint) { this.heartbeatFingerprint = heartbeatFingerprint; } public boolean isWithoutSub() { return isWithoutSub; } public void setWithoutSub(boolean withoutSub) { isWithoutSub = withoutSub; } @Override public String toString() { return "HeartbeatData [clientID=" + clientID + ", producerDataSet=" + producerDataSet + ", consumerDataSet=" + consumerDataSet + "]"; } public int computeHeartbeatFingerprint() { HeartbeatData heartbeatDataCopy = JSON.parseObject(JSON.toJSONString(this, JSONWriter.Feature.ReferenceDetection), HeartbeatData.class); for (ConsumerData consumerData : heartbeatDataCopy.getConsumerDataSet()) { for (SubscriptionData subscriptionData : consumerData.getSubscriptionDataSet()) { subscriptionData.setSubVersion(0L); } } heartbeatDataCopy.setWithoutSub(false); heartbeatDataCopy.setHeartbeatFingerprint(0); heartbeatDataCopy.setClientID(""); return JSON.toJSONString(heartbeatDataCopy, JSONWriter.Feature.ReferenceDetection).hashCode(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/MessageModel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: MessageModel.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.heartbeat; /** * Message model */ public enum MessageModel { /** * broadcast */ BROADCASTING("BROADCASTING"), /** * clustering */ CLUSTERING("CLUSTERING"), /** * for lite consumer */ LITE_SELECTIVE("LITE_SELECTIVE"); private String modeCN; MessageModel(String modeCN) { this.modeCN = modeCN; } public String getModeCN() { return modeCN; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ProducerData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: ProducerData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.heartbeat; public class ProducerData { private String groupName; public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } @Override public String toString() { return "ProducerData [groupName=" + groupName + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: SubscriptionData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.heartbeat; import com.alibaba.fastjson2.annotation.JSONField; import org.apache.rocketmq.common.filter.ExpressionType; import java.util.HashSet; import java.util.Set; public class SubscriptionData implements Comparable { public final static String SUB_ALL = "*"; private boolean classFilterMode = false; private String topic; private String subString; private Set tagsSet = new HashSet<>(); private Set codeSet = new HashSet<>(); private long subVersion = System.currentTimeMillis(); private String expressionType = ExpressionType.TAG; @JSONField(serialize = false) private String filterClassSource; public SubscriptionData() { } public SubscriptionData(String topic, String subString) { super(); this.topic = topic; this.subString = subString; } public String getFilterClassSource() { return filterClassSource; } public void setFilterClassSource(String filterClassSource) { this.filterClassSource = filterClassSource; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getSubString() { return subString; } public void setSubString(String subString) { this.subString = subString; } public Set getTagsSet() { return tagsSet; } public void setTagsSet(Set tagsSet) { this.tagsSet = tagsSet; } public long getSubVersion() { return subVersion; } public void setSubVersion(long subVersion) { this.subVersion = subVersion; } public Set getCodeSet() { return codeSet; } public void setCodeSet(Set codeSet) { this.codeSet = codeSet; } public boolean isClassFilterMode() { return classFilterMode; } public void setClassFilterMode(boolean classFilterMode) { this.classFilterMode = classFilterMode; } public String getExpressionType() { return expressionType; } public void setExpressionType(String expressionType) { this.expressionType = expressionType; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (classFilterMode ? 1231 : 1237); result = prime * result + ((codeSet == null) ? 0 : codeSet.hashCode()); result = prime * result + ((subString == null) ? 0 : subString.hashCode()); result = prime * result + ((tagsSet == null) ? 0 : tagsSet.hashCode()); result = prime * result + ((topic == null) ? 0 : topic.hashCode()); result = prime * result + ((expressionType == null) ? 0 : expressionType.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SubscriptionData other = (SubscriptionData) obj; if (classFilterMode != other.classFilterMode) return false; if (codeSet == null) { if (other.codeSet != null) return false; } else if (!codeSet.equals(other.codeSet)) return false; if (subString == null) { if (other.subString != null) return false; } else if (!subString.equals(other.subString)) return false; if (subVersion != other.subVersion) return false; if (tagsSet == null) { if (other.tagsSet != null) return false; } else if (!tagsSet.equals(other.tagsSet)) return false; if (topic == null) { if (other.topic != null) return false; } else if (!topic.equals(other.topic)) return false; if (expressionType == null) { if (other.expressionType != null) return false; } else if (!expressionType.equals(other.expressionType)) return false; return true; } @Override public String toString() { return "SubscriptionData [classFilterMode=" + classFilterMode + ", topic=" + topic + ", subString=" + subString + ", tagsSet=" + tagsSet + ", codeSet=" + codeSet + ", subVersion=" + subVersion + ", expressionType=" + expressionType + "]"; } @Override public int compareTo(SubscriptionData other) { String thisValue = this.topic + "@" + this.subString; String otherValue = other.topic + "@" + other.subString; return thisValue.compareTo(otherValue); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/namesrv/RegisterBrokerResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.namesrv; import org.apache.rocketmq.remoting.protocol.body.KVTable; public class RegisterBrokerResult { private String haServerAddr; private String masterAddr; private KVTable kvTable; public String getHaServerAddr() { return haServerAddr; } public void setHaServerAddr(String haServerAddr) { this.haServerAddr = haServerAddr; } public String getMasterAddr() { return masterAddr; } public void setMasterAddr(String masterAddr) { this.masterAddr = masterAddr; } public KVTable getKvTable() { return kvTable; } public void setKvTable(KVTable kvTable) { this.kvTable = kvTable; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/BrokerData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.route; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Random; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; /** * The class describes that a typical broker cluster's (in replication) details: the cluster (in sharding) name * that it belongs to, and all the single instance information for this cluster. */ public class BrokerData implements Comparable { private String cluster; private String brokerName; /** * The container that store the all single instances for the current broker replication cluster. * The key is the brokerId, and the value is the address of the single broker instance. */ private HashMap brokerAddrs; private String zoneName; private final Random random = new Random(); /** * Enable acting master or not, used for old version HA adaption, */ private boolean enableActingMaster = false; public BrokerData() { } public BrokerData(BrokerData brokerData) { this.cluster = brokerData.cluster; this.brokerName = brokerData.brokerName; if (brokerData.brokerAddrs != null) { this.brokerAddrs = new HashMap<>(brokerData.brokerAddrs); } this.zoneName = brokerData.zoneName; this.enableActingMaster = brokerData.enableActingMaster; } public BrokerData(String cluster, String brokerName, HashMap brokerAddrs) { this.cluster = cluster; this.brokerName = brokerName; this.brokerAddrs = brokerAddrs; } public BrokerData(String cluster, String brokerName, HashMap brokerAddrs, boolean enableActingMaster) { this.cluster = cluster; this.brokerName = brokerName; this.brokerAddrs = brokerAddrs; this.enableActingMaster = enableActingMaster; } public BrokerData(String cluster, String brokerName, HashMap brokerAddrs, boolean enableActingMaster, String zoneName) { this.cluster = cluster; this.brokerName = brokerName; this.brokerAddrs = brokerAddrs; this.enableActingMaster = enableActingMaster; this.zoneName = zoneName; } /** * Selects a (preferably master) broker address from the registered list. If the master's address cannot be found, a * slave broker address is selected in a random manner. * * @return Broker address. */ public String selectBrokerAddr() { String masterAddress = this.brokerAddrs.get(MixAll.MASTER_ID); if (masterAddress == null) { List addrs = new ArrayList<>(brokerAddrs.values()); return addrs.get(random.nextInt(addrs.size())); } return masterAddress; } public HashMap getBrokerAddrs() { return brokerAddrs; } public void setBrokerAddrs(HashMap brokerAddrs) { this.brokerAddrs = brokerAddrs; } public String getCluster() { return cluster; } public void setCluster(String cluster) { this.cluster = cluster; } public boolean isEnableActingMaster() { return enableActingMaster; } public void setEnableActingMaster(boolean enableActingMaster) { this.enableActingMaster = enableActingMaster; } public String getZoneName() { return zoneName; } public void setZoneName(String zoneName) { this.zoneName = zoneName; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((brokerAddrs == null) ? 0 : brokerAddrs.hashCode()); result = prime * result + ((brokerName == null) ? 0 : brokerName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } BrokerData other = (BrokerData) obj; if (brokerAddrs == null) { if (other.brokerAddrs != null) { return false; } } else if (!brokerAddrs.equals(other.brokerAddrs)) { return false; } return StringUtils.equals(brokerName, other.brokerName); } @Override public String toString() { return "BrokerData [brokerName=" + brokerName + ", brokerAddrs=" + brokerAddrs + ", enableActingMaster=" + enableActingMaster + "]"; } @Override public int compareTo(BrokerData o) { return this.brokerName.compareTo(o.getBrokerName()); } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/MessageQueueRouteState.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.route; public enum MessageQueueRouteState { // do not change below order, since ordinal() is used Expired, ReadOnly, Normal, WriteOnly, ; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/QueueData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id: QueueData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.route; public class QueueData implements Comparable { private String brokerName; private int readQueueNums; private int writeQueueNums; private int perm; private int topicSysFlag; public QueueData() { } // Deep copy QueueData public QueueData(QueueData queueData) { this.brokerName = queueData.brokerName; this.readQueueNums = queueData.readQueueNums; this.writeQueueNums = queueData.writeQueueNums; this.perm = queueData.perm; this.topicSysFlag = queueData.topicSysFlag; } public int getReadQueueNums() { return readQueueNums; } public void setReadQueueNums(int readQueueNums) { this.readQueueNums = readQueueNums; } public int getWriteQueueNums() { return writeQueueNums; } public void setWriteQueueNums(int writeQueueNums) { this.writeQueueNums = writeQueueNums; } public int getPerm() { return perm; } public void setPerm(int perm) { this.perm = perm; } public int getTopicSysFlag() { return topicSysFlag; } public void setTopicSysFlag(int topicSysFlag) { this.topicSysFlag = topicSysFlag; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((brokerName == null) ? 0 : brokerName.hashCode()); result = prime * result + perm; result = prime * result + readQueueNums; result = prime * result + writeQueueNums; result = prime * result + topicSysFlag; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; QueueData other = (QueueData) obj; if (brokerName == null) { if (other.brokerName != null) return false; } else if (!brokerName.equals(other.brokerName)) return false; if (perm != other.perm) return false; if (readQueueNums != other.readQueueNums) return false; if (writeQueueNums != other.writeQueueNums) return false; return topicSysFlag == other.topicSysFlag; } @Override public String toString() { return "QueueData [brokerName=" + brokerName + ", readQueueNums=" + readQueueNums + ", writeQueueNums=" + writeQueueNums + ", perm=" + perm + ", topicSysFlag=" + topicSysFlag + "]"; } @Override public int compareTo(QueueData o) { return this.brokerName.compareTo(o.getBrokerName()); } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: TopicRouteData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ package org.apache.rocketmq.remoting.protocol.route; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; public class TopicRouteData extends RemotingSerializable { private String orderTopicConf; private List queueDatas; private List brokerDatas; private HashMap/* Filter Server */> filterServerTable; //It could be null or empty private Map topicQueueMappingByBroker; public TopicRouteData() { queueDatas = new ArrayList<>(); brokerDatas = new ArrayList<>(); filterServerTable = new HashMap<>(); } public TopicRouteData(TopicRouteData topicRouteData) { this.queueDatas = new ArrayList<>(); this.brokerDatas = new ArrayList<>(); this.filterServerTable = new HashMap<>(); this.orderTopicConf = topicRouteData.orderTopicConf; if (topicRouteData.queueDatas != null) { this.queueDatas.addAll(topicRouteData.queueDatas); } if (topicRouteData.brokerDatas != null) { this.brokerDatas.addAll(topicRouteData.brokerDatas); } if (topicRouteData.filterServerTable != null) { this.filterServerTable.putAll(topicRouteData.filterServerTable); } if (topicRouteData.topicQueueMappingByBroker != null) { this.topicQueueMappingByBroker = new HashMap<>(topicRouteData.topicQueueMappingByBroker); } } public TopicRouteData cloneTopicRouteData() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setQueueDatas(new ArrayList<>()); topicRouteData.setBrokerDatas(new ArrayList<>()); topicRouteData.setFilterServerTable(new HashMap<>()); topicRouteData.setOrderTopicConf(this.orderTopicConf); topicRouteData.getQueueDatas().addAll(this.queueDatas); topicRouteData.getBrokerDatas().addAll(this.brokerDatas); topicRouteData.getFilterServerTable().putAll(this.filterServerTable); if (this.topicQueueMappingByBroker != null) { Map cloneMap = new HashMap<>(this.topicQueueMappingByBroker); topicRouteData.setTopicQueueMappingByBroker(cloneMap); } return topicRouteData; } public TopicRouteData deepCloneTopicRouteData() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setOrderTopicConf(this.orderTopicConf); for (final QueueData queueData : this.queueDatas) { topicRouteData.getQueueDatas().add(new QueueData(queueData)); } for (final BrokerData brokerData : this.brokerDatas) { topicRouteData.getBrokerDatas().add(new BrokerData(brokerData)); } for (final Map.Entry> listEntry : this.filterServerTable.entrySet()) { topicRouteData.getFilterServerTable().put(listEntry.getKey(), new ArrayList<>(listEntry.getValue())); } if (this.topicQueueMappingByBroker != null) { Map cloneMap = new HashMap<>(this.topicQueueMappingByBroker.size()); for (final Map.Entry entry : this.getTopicQueueMappingByBroker().entrySet()) { TopicQueueMappingInfo topicQueueMappingInfo = new TopicQueueMappingInfo(entry.getValue().getTopic(), entry.getValue().getTotalQueues(), entry.getValue().getBname(), entry.getValue().getEpoch()); topicQueueMappingInfo.setDirty(entry.getValue().isDirty()); topicQueueMappingInfo.setScope(entry.getValue().getScope()); ConcurrentMap concurrentMap = new ConcurrentHashMap<>(entry.getValue().getCurrIdMap()); topicQueueMappingInfo.setCurrIdMap(concurrentMap); cloneMap.put(entry.getKey(), topicQueueMappingInfo); } topicRouteData.setTopicQueueMappingByBroker(cloneMap); } return topicRouteData; } public boolean topicRouteDataChanged(TopicRouteData oldData) { if (oldData == null) return true; TopicRouteData old = new TopicRouteData(oldData); TopicRouteData now = new TopicRouteData(this); Collections.sort(old.getQueueDatas()); Collections.sort(old.getBrokerDatas()); Collections.sort(now.getQueueDatas()); Collections.sort(now.getBrokerDatas()); return !old.equals(now); } public List getQueueDatas() { return queueDatas; } public void setQueueDatas(List queueDatas) { this.queueDatas = queueDatas; } public List getBrokerDatas() { return brokerDatas; } public void setBrokerDatas(List brokerDatas) { this.brokerDatas = brokerDatas; } public HashMap> getFilterServerTable() { return filterServerTable; } public void setFilterServerTable(HashMap> filterServerTable) { this.filterServerTable = filterServerTable; } public String getOrderTopicConf() { return orderTopicConf; } public void setOrderTopicConf(String orderTopicConf) { this.orderTopicConf = orderTopicConf; } public Map getTopicQueueMappingByBroker() { return topicQueueMappingByBroker; } public void setTopicQueueMappingByBroker(Map topicQueueMappingByBroker) { this.topicQueueMappingByBroker = topicQueueMappingByBroker; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((brokerDatas == null) ? 0 : brokerDatas.hashCode()); result = prime * result + ((orderTopicConf == null) ? 0 : orderTopicConf.hashCode()); result = prime * result + ((queueDatas == null) ? 0 : queueDatas.hashCode()); result = prime * result + ((filterServerTable == null) ? 0 : filterServerTable.hashCode()); result = prime * result + ((topicQueueMappingByBroker == null) ? 0 : topicQueueMappingByBroker.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TopicRouteData other = (TopicRouteData) obj; if (brokerDatas == null) { if (other.brokerDatas != null) return false; } else if (!brokerDatas.equals(other.brokerDatas)) return false; if (orderTopicConf == null) { if (other.orderTopicConf != null) return false; } else if (!orderTopicConf.equals(other.orderTopicConf)) return false; if (queueDatas == null) { if (other.queueDatas != null) return false; } else if (!queueDatas.equals(other.queueDatas)) return false; if (filterServerTable == null) { if (other.filterServerTable != null) return false; } else if (!filterServerTable.equals(other.filterServerTable)) return false; if (topicQueueMappingByBroker == null) { if (other.topicQueueMappingByBroker != null) return false; } else if (!topicQueueMappingByBroker.equals(other.topicQueueMappingByBroker)) return false; return true; } @Override public String toString() { return "TopicRouteData [orderTopicConf=" + orderTopicConf + ", queueDatas=" + queueDatas + ", brokerDatas=" + brokerDatas + ", filterServerTable=" + filterServerTable + ", topicQueueMappingInfoTable=" + topicQueueMappingByBroker + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/LogicQueueMappingItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class LogicQueueMappingItem extends RemotingSerializable { private int gen; // immutable private int queueId; //, immutable private String bname; //important, immutable private long logicOffset; // the start of the logic offset, important, can be changed by command only once private long startOffset; // the start of the physical offset, should always be 0, immutable private long endOffset = -1; // the end of the physical offset, excluded, revered -1, mutable private long timeOfStart = -1; // mutable, reserved private long timeOfEnd = -1; // mutable, reserved //make sure it has a default constructor public LogicQueueMappingItem() { } public LogicQueueMappingItem(int gen, int queueId, String bname, long logicOffset, long startOffset, long endOffset, long timeOfStart, long timeOfEnd) { this.gen = gen; this.queueId = queueId; this.bname = bname; this.logicOffset = logicOffset; this.startOffset = startOffset; this.endOffset = endOffset; this.timeOfStart = timeOfStart; this.timeOfEnd = timeOfEnd; } //should only be user in sendMessage and getMinOffset public long computeStaticQueueOffsetLoosely(long physicalQueueOffset) { //consider the newly mapped item if (logicOffset < 0) { return -1; } if (physicalQueueOffset < startOffset) { return logicOffset; } if (endOffset >= startOffset && endOffset < physicalQueueOffset) { return logicOffset + (endOffset - startOffset); } return logicOffset + (physicalQueueOffset - startOffset); } public long computeStaticQueueOffsetStrictly(long physicalQueueOffset) { assert logicOffset >= 0; if (physicalQueueOffset < startOffset) { return logicOffset; } return logicOffset + (physicalQueueOffset - startOffset); } public long computePhysicalQueueOffset(long staticQueueOffset) { return (staticQueueOffset - logicOffset) + startOffset; } public long computeMaxStaticQueueOffset() { if (endOffset >= startOffset) { return logicOffset + endOffset - startOffset; } else { return logicOffset; } } public boolean checkIfEndOffsetDecided() { //if the endOffset == startOffset, then the item should be deleted return endOffset > startOffset; } public boolean checkIfLogicoffsetDecided() { return logicOffset >= 0; } public long computeOffsetDelta() { return logicOffset - startOffset; } public int getGen() { return gen; } public int getQueueId() { return queueId; } public String getBname() { return bname; } public long getLogicOffset() { return logicOffset; } public long getStartOffset() { return startOffset; } public long getEndOffset() { return endOffset; } public long getTimeOfStart() { return timeOfStart; } public long getTimeOfEnd() { return timeOfEnd; } public void setLogicOffset(long logicOffset) { this.logicOffset = logicOffset; } public void setEndOffset(long endOffset) { this.endOffset = endOffset; } public void setTimeOfStart(long timeOfStart) { this.timeOfStart = timeOfStart; } public void setTimeOfEnd(long timeOfEnd) { this.timeOfEnd = timeOfEnd; } public void setGen(int gen) { this.gen = gen; } public void setQueueId(int queueId) { this.queueId = queueId; } public void setBname(String bname) { this.bname = bname; } public void setStartOffset(long startOffset) { this.startOffset = startOffset; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof LogicQueueMappingItem)) return false; LogicQueueMappingItem item = (LogicQueueMappingItem) o; return new EqualsBuilder() .append(gen, item.gen) .append(queueId, item.queueId) .append(logicOffset, item.logicOffset) .append(startOffset, item.startOffset) .append(endOffset, item.endOffset) .append(timeOfStart, item.timeOfStart) .append(timeOfEnd, item.timeOfEnd) .append(bname, item.bname) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(gen) .append(queueId) .append(bname) .append(logicOffset) .append(startOffset) .append(endOffset) .append(timeOfStart) .append(timeOfEnd) .toHashCode(); } @Override public String toString() { return "LogicQueueMappingItem{" + "gen=" + gen + ", queueId=" + queueId + ", bname='" + bname + '\'' + ", logicOffset=" + logicOffset + ", startOffset=" + startOffset + ", endOffset=" + endOffset + ", timeOfStart=" + timeOfStart + ", timeOfEnd=" + timeOfEnd + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.rocketmq.common.TopicConfig; public class TopicConfigAndQueueMapping extends TopicConfig { private TopicQueueMappingDetail mappingDetail; public TopicConfigAndQueueMapping() { } public TopicConfigAndQueueMapping(TopicConfig topicConfig, TopicQueueMappingDetail mappingDetail) { super(topicConfig); this.mappingDetail = mappingDetail; } public TopicQueueMappingDetail getMappingDetail() { return mappingDetail; } public void setMappingDetail(TopicQueueMappingDetail mappingDetail) { this.mappingDetail = mappingDetail; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof TopicConfigAndQueueMapping)) return false; TopicConfigAndQueueMapping that = (TopicConfigAndQueueMapping) o; return new EqualsBuilder() .appendSuper(super.equals(o)) .append(mappingDetail, that.mappingDetail) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .appendSuper(super.hashCode()) .append(mappingDetail) .toHashCode(); } @Override public String toString() { String string = super.toString(); if (StringUtils.isNotBlank(string)) { string = string.substring(0, string.length() - 1) + ", mappingDetail=" + mappingDetail + "]"; } return string; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import com.google.common.collect.ImmutableList; import java.util.List; public class TopicQueueMappingContext { private String topic; private Integer globalId; private TopicQueueMappingDetail mappingDetail; private List mappingItemList; private LogicQueueMappingItem leaderItem; private LogicQueueMappingItem currentItem; public TopicQueueMappingContext(String topic, Integer globalId, TopicQueueMappingDetail mappingDetail, List mappingItemList, LogicQueueMappingItem leaderItem) { this.topic = topic; this.globalId = globalId; this.mappingDetail = mappingDetail; this.mappingItemList = mappingItemList; this.leaderItem = leaderItem; } public boolean isLeader() { return leaderItem != null && leaderItem.getBname().equals(mappingDetail.getBname()); } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Integer getGlobalId() { return globalId; } public void setGlobalId(Integer globalId) { this.globalId = globalId; } public TopicQueueMappingDetail getMappingDetail() { return mappingDetail; } public void setMappingDetail(TopicQueueMappingDetail mappingDetail) { this.mappingDetail = mappingDetail; } public List getMappingItemList() { return mappingItemList; } public void setMappingItemList(ImmutableList mappingItemList) { this.mappingItemList = mappingItemList; } public LogicQueueMappingItem getLeaderItem() { return leaderItem; } public void setLeaderItem(LogicQueueMappingItem leaderItem) { this.leaderItem = leaderItem; } public LogicQueueMappingItem getCurrentItem() { return currentItem; } public void setCurrentItem(LogicQueueMappingItem currentItem) { this.currentItem = currentItem; } public void setMappingItemList(List mappingItemList) { this.mappingItemList = mappingItemList; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingDetail.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; public class TopicQueueMappingDetail extends TopicQueueMappingInfo { // the mapping info in current broker, do not register to nameserver // make sure this value is not null private ConcurrentMap> hostedQueues = new ConcurrentHashMap<>(); //make sure there is a default constructor public TopicQueueMappingDetail() { } public TopicQueueMappingDetail(String topic, int totalQueues, String bname, long epoch) { super(topic, totalQueues, bname, epoch); } public static boolean putMappingInfo(TopicQueueMappingDetail mappingDetail, Integer globalId, List mappingInfo) { if (mappingInfo.isEmpty()) { return true; } mappingDetail.hostedQueues.put(globalId, mappingInfo); return true; } public static List getMappingInfo(TopicQueueMappingDetail mappingDetail, Integer globalId) { return mappingDetail.hostedQueues.get(globalId); } public static ConcurrentMap buildIdMap(TopicQueueMappingDetail mappingDetail, int level) { //level 0 means current leader in this broker //level 1 means previous leader in this broker, reserved for assert level == LEVEL_0 ; if (mappingDetail.hostedQueues == null || mappingDetail.hostedQueues.isEmpty()) { return new ConcurrentHashMap<>(); } ConcurrentMap tmpIdMap = new ConcurrentHashMap<>(); for (Map.Entry> entry: mappingDetail.hostedQueues.entrySet()) { Integer globalId = entry.getKey(); List items = entry.getValue(); if (level == LEVEL_0 && items.size() >= 1) { LogicQueueMappingItem curr = items.get(items.size() - 1); if (mappingDetail.bname.equals(curr.getBname())) { tmpIdMap.put(globalId, curr.getQueueId()); } } } return tmpIdMap; } public static long computeMaxOffsetFromMapping(TopicQueueMappingDetail mappingDetail, Integer globalId) { List mappingItems = getMappingInfo(mappingDetail, globalId); if (mappingItems == null || mappingItems.isEmpty()) { return -1; } LogicQueueMappingItem item = mappingItems.get(mappingItems.size() - 1); return item.computeMaxStaticQueueOffset(); } public static TopicQueueMappingInfo cloneAsMappingInfo(TopicQueueMappingDetail mappingDetail) { TopicQueueMappingInfo topicQueueMappingInfo = new TopicQueueMappingInfo(mappingDetail.topic, mappingDetail.totalQueues, mappingDetail.bname, mappingDetail.epoch); topicQueueMappingInfo.currIdMap = TopicQueueMappingDetail.buildIdMap(mappingDetail, LEVEL_0); return topicQueueMappingInfo; } public static boolean checkIfAsPhysical(TopicQueueMappingDetail mappingDetail, Integer globalId) { List mappingItems = getMappingInfo(mappingDetail, globalId); return mappingItems == null || mappingItems.size() == 1 && mappingItems.get(0).getLogicOffset() == 0; } public ConcurrentMap> getHostedQueues() { return hostedQueues; } public void setHostedQueues(ConcurrentMap> hostedQueues) { this.hostedQueues = hostedQueues; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof TopicQueueMappingDetail)) return false; TopicQueueMappingDetail that = (TopicQueueMappingDetail) o; return new EqualsBuilder() .append(hostedQueues, that.hostedQueues) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(hostedQueues) .toHashCode(); } @Override public String toString() { return "TopicQueueMappingDetail{" + "hostedQueues=" + hostedQueues + ", topic='" + topic + '\'' + ", totalQueues=" + totalQueues + ", bname='" + bname + '\'' + ", epoch=" + epoch + ", dirty=" + dirty + ", currIdMap=" + currIdMap + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicQueueMappingInfo extends RemotingSerializable { public static final int LEVEL_0 = 0; String topic; // redundant field String scope = MixAll.METADATA_SCOPE_GLOBAL; int totalQueues; String bname; //identify the hosted broker name long epoch; //important to fence the old dirty data boolean dirty; //indicate if the data is dirty //register to broker to construct the route protected ConcurrentMap currIdMap = new ConcurrentHashMap<>(); public TopicQueueMappingInfo() { } public TopicQueueMappingInfo(String topic, int totalQueues, String bname, long epoch) { this.topic = topic; this.totalQueues = totalQueues; this.bname = bname; this.epoch = epoch; this.dirty = false; } public boolean isDirty() { return dirty; } public void setDirty(boolean dirty) { this.dirty = dirty; } public int getTotalQueues() { return totalQueues; } public String getBname() { return bname; } public String getTopic() { return topic; } public long getEpoch() { return epoch; } public void setEpoch(long epoch) { this.epoch = epoch; } public void setTotalQueues(int totalQueues) { this.totalQueues = totalQueues; } public ConcurrentMap getCurrIdMap() { return currIdMap; } public void setTopic(String topic) { this.topic = topic; } public void setBname(String bname) { this.bname = bname; } public void setCurrIdMap(ConcurrentMap currIdMap) { this.currIdMap = currIdMap; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof TopicQueueMappingInfo)) return false; TopicQueueMappingInfo info = (TopicQueueMappingInfo) o; if (totalQueues != info.totalQueues) return false; if (epoch != info.epoch) return false; if (dirty != info.dirty) return false; if (topic != null ? !topic.equals(info.topic) : info.topic != null) return false; if (scope != null ? !scope.equals(info.scope) : info.scope != null) return false; if (bname != null ? !bname.equals(info.bname) : info.bname != null) return false; return currIdMap != null ? currIdMap.equals(info.currIdMap) : info.currIdMap == null; } @Override public int hashCode() { int result = topic != null ? topic.hashCode() : 0; result = 31 * result + (scope != null ? scope.hashCode() : 0); result = 31 * result + totalQueues; result = 31 * result + (bname != null ? bname.hashCode() : 0); result = 31 * result + (int) (epoch ^ (epoch >>> 32)); result = 31 * result + (dirty ? 1 : 0); result = 31 * result + (currIdMap != null ? currIdMap.hashCode() : 0); return result; } @Override public String toString() { return "TopicQueueMappingInfo{" + "topic='" + topic + '\'' + ", scope='" + scope + '\'' + ", totalQueues=" + totalQueues + ", bname='" + bname + '\'' + ", epoch=" + epoch + ", dirty=" + dirty + ", currIdMap=" + currIdMap + '}'; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingOne.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicQueueMappingOne extends RemotingSerializable { String topic; // redundant field String bname; //identify the hosted broker name Integer globalId; List items; TopicQueueMappingDetail mappingDetail; public TopicQueueMappingOne(TopicQueueMappingDetail mappingDetail, String topic, String bname, Integer globalId, List items) { this.mappingDetail = mappingDetail; this.topic = topic; this.bname = bname; this.globalId = globalId; this.items = items; } public String getTopic() { return topic; } public String getBname() { return bname; } public Integer getGlobalId() { return globalId; } public List getItems() { return items; } public TopicQueueMappingDetail getMappingDetail() { return mappingDetail; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof TopicQueueMappingOne)) return false; TopicQueueMappingOne that = (TopicQueueMappingOne) o; if (topic != null ? !topic.equals(that.topic) : that.topic != null) return false; if (bname != null ? !bname.equals(that.bname) : that.bname != null) return false; if (globalId != null ? !globalId.equals(that.globalId) : that.globalId != null) return false; if (items != null ? !items.equals(that.items) : that.items != null) return false; return mappingDetail != null ? mappingDetail.equals(that.mappingDetail) : that.mappingDetail == null; } @Override public int hashCode() { int result = topic != null ? topic.hashCode() : 0; result = 31 * result + (bname != null ? bname.hashCode() : 0); result = 31 * result + (globalId != null ? globalId.hashCode() : 0); result = 31 * result + (items != null ? items.hashCode() : 0); result = 31 * result + (mappingDetail != null ? mappingDetail.hashCode() : 0); return result; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import java.io.File; import java.util.AbstractMap; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; public class TopicQueueMappingUtils { public static final int DEFAULT_BLOCK_SEQ_SIZE = 10000; public static class MappingAllocator { Map brokerNumMap = new HashMap<>(); Map idToBroker = new HashMap<>(); //used for remapping Map brokerNumMapBeforeRemapping; int currentIndex = 0; List leastBrokers = new ArrayList<>(); private MappingAllocator(Map idToBroker, Map brokerNumMap, Map brokerNumMapBeforeRemapping) { this.idToBroker.putAll(idToBroker); this.brokerNumMap.putAll(brokerNumMap); this.brokerNumMapBeforeRemapping = brokerNumMapBeforeRemapping; } private void freshState() { int minNum = Integer.MAX_VALUE; for (Map.Entry entry : brokerNumMap.entrySet()) { if (entry.getValue() < minNum) { leastBrokers.clear(); leastBrokers.add(entry.getKey()); minNum = entry.getValue(); } else if (entry.getValue() == minNum) { leastBrokers.add(entry.getKey()); } } //reduce the remapping if (brokerNumMapBeforeRemapping != null && !brokerNumMapBeforeRemapping.isEmpty()) { leastBrokers.sort((o1, o2) -> { int i1 = 0, i2 = 0; if (brokerNumMapBeforeRemapping.containsKey(o1)) { i1 = brokerNumMapBeforeRemapping.get(o1); } if (brokerNumMapBeforeRemapping.containsKey(o2)) { i2 = brokerNumMapBeforeRemapping.get(o2); } return i1 - i2; }); } else { //reduce the imbalance Collections.shuffle(leastBrokers); } currentIndex = leastBrokers.size() - 1; } private String nextBroker() { if (leastBrokers.isEmpty()) { freshState(); } int tmpIndex = currentIndex % leastBrokers.size(); return leastBrokers.remove(tmpIndex); } public Map getBrokerNumMap() { return brokerNumMap; } public void upToNum(int maxQueueNum) { int currSize = idToBroker.size(); if (maxQueueNum <= currSize) { return; } for (int i = currSize; i < maxQueueNum; i++) { String nextBroker = nextBroker(); if (brokerNumMap.containsKey(nextBroker)) { brokerNumMap.put(nextBroker, brokerNumMap.get(nextBroker) + 1); } else { brokerNumMap.put(nextBroker, 1); } idToBroker.put(i, nextBroker); } } public Map getIdToBroker() { return idToBroker; } } public static MappingAllocator buildMappingAllocator(Map idToBroker, Map brokerNumMap, Map brokerNumMapBeforeRemapping) { return new MappingAllocator(idToBroker, brokerNumMap, brokerNumMapBeforeRemapping); } public static Map.Entry findMaxEpochAndQueueNum(List mappingDetailList) { long epoch = -1; int queueNum = 0; for (TopicQueueMappingDetail mappingDetail : mappingDetailList) { if (mappingDetail.getEpoch() > epoch) { epoch = mappingDetail.getEpoch(); } if (mappingDetail.getTotalQueues() > queueNum) { queueNum = mappingDetail.getTotalQueues(); } } return new AbstractMap.SimpleImmutableEntry<>(epoch, queueNum); } public static List getMappingDetailFromConfig(Collection configs) { List detailList = new ArrayList<>(); for (TopicConfigAndQueueMapping configMapping : configs) { if (configMapping.getMappingDetail() != null) { detailList.add(configMapping.getMappingDetail()); } } return detailList; } public static Map.Entry checkNameEpochNumConsistence(String topic, Map brokerConfigMap) { if (brokerConfigMap == null || brokerConfigMap.isEmpty()) { return null; } //make sure it is not null long maxEpoch = -1; int maxNum = -1; String scope = null; for (Map.Entry entry : brokerConfigMap.entrySet()) { String broker = entry.getKey(); TopicConfigAndQueueMapping configMapping = entry.getValue(); if (configMapping.getMappingDetail() == null) { throw new RuntimeException("Mapping info should not be null in broker " + broker); } TopicQueueMappingDetail mappingDetail = configMapping.getMappingDetail(); if (!broker.equals(mappingDetail.getBname())) { throw new RuntimeException(String.format("The broker name is not equal %s != %s ", broker, mappingDetail.getBname())); } if (mappingDetail.isDirty()) { throw new RuntimeException("The mapping info is dirty in broker " + broker); } if (!configMapping.getTopicName().equals(mappingDetail.getTopic())) { throw new RuntimeException("The topic name is inconsistent in broker " + broker); } if (topic != null && !topic.equals(mappingDetail.getTopic())) { throw new RuntimeException("The topic name is not match for broker " + broker); } if (scope != null && !scope.equals(mappingDetail.getScope())) { throw new RuntimeException(String.format("scope does not match %s != %s in %s", mappingDetail.getScope(), scope, broker)); } else { scope = mappingDetail.getScope(); } if (maxEpoch != -1 && maxEpoch != mappingDetail.getEpoch()) { throw new RuntimeException(String.format("epoch does not match %d != %d in %s", maxEpoch, mappingDetail.getEpoch(), mappingDetail.getBname())); } else { maxEpoch = mappingDetail.getEpoch(); } if (maxNum != -1 && maxNum != mappingDetail.getTotalQueues()) { throw new RuntimeException(String.format("total queue number does not match %d != %d in %s", maxNum, mappingDetail.getTotalQueues(), mappingDetail.getBname())); } else { maxNum = mappingDetail.getTotalQueues(); } } return new AbstractMap.SimpleEntry<>(maxEpoch, maxNum); } public static String getMockBrokerName(String scope) { assert scope != null; if (scope.equals(MixAll.METADATA_SCOPE_GLOBAL)) { return MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX + scope.substring(2); } else { return MixAll.LOGICAL_QUEUE_MOCK_BROKER_PREFIX + scope; } } public static void makeSureLogicQueueMappingItemImmutable(List oldItems, List newItems, boolean epochEqual, boolean isCLean) { if (oldItems == null || oldItems.isEmpty()) { return; } if (newItems == null || newItems.isEmpty()) { throw new RuntimeException("The new item list is null or empty"); } int iold = 0, inew = 0; while (iold < oldItems.size() && inew < newItems.size()) { LogicQueueMappingItem newItem = newItems.get(inew); LogicQueueMappingItem oldItem = oldItems.get(iold); if (newItem.getGen() < oldItem.getGen()) { //the earliest item may have been deleted concurrently inew++; } else if (oldItem.getGen() < newItem.getGen()) { //in the following cases, the new item-list has fewer items than old item-list //1. the queue is mapped back to a broker which hold the logic queue before //2. The earliest item is deleted by TopicQueueMappingCleanService iold++; } else { assert oldItem.getBname().equals(newItem.getBname()); assert oldItem.getQueueId() == newItem.getQueueId(); assert oldItem.getStartOffset() == newItem.getStartOffset(); if (oldItem.getLogicOffset() != -1) { assert oldItem.getLogicOffset() == newItem.getLogicOffset(); } iold++; inew++; } } if (epochEqual) { LogicQueueMappingItem oldLeader = oldItems.get(oldItems.size() - 1); LogicQueueMappingItem newLeader = newItems.get(newItems.size() - 1); if (newLeader.getGen() != oldLeader.getGen() || !newLeader.getBname().equals(oldLeader.getBname()) || newLeader.getQueueId() != oldLeader.getQueueId() || newLeader.getStartOffset() != oldLeader.getStartOffset()) { throw new RuntimeException("The new leader is different but epoch equal"); } } } public static void checkLogicQueueMappingItemOffset(List items) { if (items == null || items.isEmpty()) { return; } int lastGen = -1; long lastOffset = -1; for (int i = items.size() - 1; i >= 0 ; i--) { LogicQueueMappingItem item = items.get(i); if (item.getStartOffset() < 0 || item.getGen() < 0 || item.getQueueId() < 0) { throw new RuntimeException("The field is illegal, should not be negative"); } if (items.size() >= 2 && i <= items.size() - 2 && items.get(i).getLogicOffset() < 0) { throw new RuntimeException("The non-latest item has negative logic offset"); } if (lastGen != -1 && item.getGen() >= lastGen) { throw new RuntimeException("The gen does not increase monotonically"); } if (item.getEndOffset() != -1 && item.getEndOffset() < item.getStartOffset()) { throw new RuntimeException("The endOffset is smaller than the start offset"); } if (lastOffset != -1 && item.getLogicOffset() != -1) { if (item.getLogicOffset() >= lastOffset) { throw new RuntimeException("The base logic offset does not increase monotonically"); } if (item.computeMaxStaticQueueOffset() >= lastOffset) { throw new RuntimeException("The max logic offset does not increase monotonically"); } } lastGen = item.getGen(); lastOffset = item.getLogicOffset(); } } public static void checkIfReusePhysicalQueue(Collection mappingOnes) { Map physicalQueueIdMap = new HashMap<>(); for (TopicQueueMappingOne mappingOne : mappingOnes) { for (LogicQueueMappingItem item: mappingOne.items) { String physicalQueueId = item.getBname() + "-" + item.getQueueId(); if (physicalQueueIdMap.containsKey(physicalQueueId)) { throw new RuntimeException(String.format("Topic %s global queue id %d and %d shared the same physical queue %s", mappingOne.topic, mappingOne.globalId, physicalQueueIdMap.get(physicalQueueId).globalId, physicalQueueId)); } else { physicalQueueIdMap.put(physicalQueueId, mappingOne); } } } } public static void checkLeaderInTargetBrokers(Collection mappingOnes, Set targetBrokers) { for (TopicQueueMappingOne mappingOne : mappingOnes) { if (!targetBrokers.contains(mappingOne.bname)) { throw new RuntimeException("The leader broker does not in target broker"); } } } public static void checkPhysicalQueueConsistence(Map brokerConfigMap) { for (Map.Entry entry : brokerConfigMap.entrySet()) { TopicConfigAndQueueMapping configMapping = entry.getValue(); assert configMapping != null; assert configMapping.getMappingDetail() != null; if (configMapping.getReadQueueNums() < configMapping.getWriteQueueNums()) { throw new RuntimeException("Read queues is smaller than write queues"); } for (List items: configMapping.getMappingDetail().getHostedQueues().values()) { for (LogicQueueMappingItem item: items) { if (item.getStartOffset() != 0) { throw new RuntimeException("The start offset does not begin from 0"); } TopicConfig topicConfig = brokerConfigMap.get(item.getBname()); if (topicConfig == null) { throw new RuntimeException("The broker of item does not exist"); } if (item.getQueueId() >= topicConfig.getWriteQueueNums()) { throw new RuntimeException("The physical queue id is overflow the write queues"); } } } } } public static Map checkAndBuildMappingItems(List mappingDetailList, boolean replace, boolean checkConsistence) { mappingDetailList.sort((o1, o2) -> (int) (o2.getEpoch() - o1.getEpoch())); int maxNum = 0; Map globalIdMap = new HashMap<>(); for (TopicQueueMappingDetail mappingDetail : mappingDetailList) { if (mappingDetail.totalQueues > maxNum) { maxNum = mappingDetail.totalQueues; } for (Map.Entry> entry : mappingDetail.getHostedQueues().entrySet()) { Integer globalid = entry.getKey(); checkLogicQueueMappingItemOffset(entry.getValue()); String leaderBrokerName = getLeaderBroker(entry.getValue()); if (!leaderBrokerName.equals(mappingDetail.getBname())) { //not the leader continue; } if (globalIdMap.containsKey(globalid)) { if (!replace) { throw new RuntimeException(String.format("The queue id is duplicated in broker %s %s", leaderBrokerName, mappingDetail.getBname())); } } else { globalIdMap.put(globalid, new TopicQueueMappingOne(mappingDetail, mappingDetail.topic, mappingDetail.bname, globalid, entry.getValue())); } } } if (checkConsistence) { if (maxNum != globalIdMap.size()) { throw new RuntimeException(String.format("The total queue number in config does not match the real hosted queues %d != %d", maxNum, globalIdMap.size())); } for (int i = 0; i < maxNum; i++) { if (!globalIdMap.containsKey(i)) { throw new RuntimeException(String.format("The queue number %s is not in globalIdMap", i)); } } } checkIfReusePhysicalQueue(globalIdMap.values()); return globalIdMap; } public static String getLeaderBroker(List items) { return getLeaderItem(items).getBname(); } public static LogicQueueMappingItem getLeaderItem(List items) { assert items.size() > 0; return items.get(items.size() - 1); } public static String writeToTemp(TopicRemappingDetailWrapper wrapper, boolean after) { String topic = wrapper.getTopic(); String data = wrapper.toJson(); String suffix = TopicRemappingDetailWrapper.SUFFIX_BEFORE; if (after) { suffix = TopicRemappingDetailWrapper.SUFFIX_AFTER; } String fileName = System.getProperty("java.io.tmpdir") + File.separator + topic + "-" + wrapper.getEpoch() + suffix; try { MixAll.string2File(data, fileName); return fileName; } catch (Exception e) { throw new RuntimeException("write file failed " + fileName,e); } } public static long blockSeqRoundUp(long offset, long blockSeqSize) { long num = offset / blockSeqSize; long left = offset % blockSeqSize; if (left < blockSeqSize / 2) { return (num + 1) * blockSeqSize; } else { return (num + 2) * blockSeqSize; } } public static void checkTargetBrokersComplete(Set targetBrokers, Map brokerConfigMap) { for (String broker : brokerConfigMap.keySet()) { if (brokerConfigMap.get(broker).getMappingDetail().getHostedQueues().isEmpty()) { continue; } if (!targetBrokers.contains(broker)) { throw new RuntimeException("The existed broker " + broker + " does not in target brokers "); } } } public static void checkNonTargetBrokers(Set targetBrokers, Set nonTargetBrokers) { for (String broker : nonTargetBrokers) { if (targetBrokers.contains(broker)) { throw new RuntimeException("The non-target broker exist in target broker"); } } } public static TopicRemappingDetailWrapper createTopicConfigMapping(String topic, int queueNum, Set targetBrokers, Map brokerConfigMap) { checkTargetBrokersComplete(targetBrokers, brokerConfigMap); Map globalIdMap = new HashMap<>(); Map.Entry maxEpochAndNum = new AbstractMap.SimpleImmutableEntry<>(System.currentTimeMillis(), queueNum); if (!brokerConfigMap.isEmpty()) { maxEpochAndNum = TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values())), false, true); checkIfReusePhysicalQueue(globalIdMap.values()); checkPhysicalQueueConsistence(brokerConfigMap); } if (queueNum < globalIdMap.size()) { throw new RuntimeException(String.format("Cannot decrease the queue num for static topic %d < %d", queueNum, globalIdMap.size())); } //check the queue number if (queueNum == globalIdMap.size()) { throw new RuntimeException("The topic queue num is equal the existed queue num, do nothing"); } //the check is ok, now do the mapping allocation Map brokerNumMap = new HashMap<>(); for (String broker: targetBrokers) { brokerNumMap.put(broker, 0); } final Map oldIdToBroker = new HashMap<>(); for (Map.Entry entry : globalIdMap.entrySet()) { String leaderbroker = entry.getValue().getBname(); oldIdToBroker.put(entry.getKey(), leaderbroker); if (!brokerNumMap.containsKey(leaderbroker)) { brokerNumMap.put(leaderbroker, 1); } else { brokerNumMap.put(leaderbroker, brokerNumMap.get(leaderbroker) + 1); } } TopicQueueMappingUtils.MappingAllocator allocator = TopicQueueMappingUtils.buildMappingAllocator(oldIdToBroker, brokerNumMap, null); allocator.upToNum(queueNum); Map newIdToBroker = allocator.getIdToBroker(); //construct the topic configAndMapping long newEpoch = Math.max(maxEpochAndNum.getKey() + 1000, System.currentTimeMillis()); for (Map.Entry e : newIdToBroker.entrySet()) { Integer queueId = e.getKey(); String broker = e.getValue(); if (globalIdMap.containsKey(queueId)) { //ignore the exited continue; } TopicConfigAndQueueMapping configMapping; if (!brokerConfigMap.containsKey(broker)) { configMapping = new TopicConfigAndQueueMapping(new TopicConfig(topic), new TopicQueueMappingDetail(topic, 0, broker, System.currentTimeMillis())); configMapping.setWriteQueueNums(1); configMapping.setReadQueueNums(1); brokerConfigMap.put(broker, configMapping); } else { configMapping = brokerConfigMap.get(broker); configMapping.setWriteQueueNums(configMapping.getWriteQueueNums() + 1); configMapping.setReadQueueNums(configMapping.getReadQueueNums() + 1); } LogicQueueMappingItem mappingItem = new LogicQueueMappingItem(0, configMapping.getWriteQueueNums() - 1, broker, 0, 0, -1, -1, -1); TopicQueueMappingDetail.putMappingInfo(configMapping.getMappingDetail(), queueId, new ArrayList<>(Collections.singletonList(mappingItem))); } // set the topic config for (Map.Entry entry : brokerConfigMap.entrySet()) { TopicConfigAndQueueMapping configMapping = entry.getValue(); configMapping.getMappingDetail().setEpoch(newEpoch); configMapping.getMappingDetail().setTotalQueues(queueNum); } //double check the config { TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(getMappingDetailFromConfig(brokerConfigMap.values()), false, true); checkIfReusePhysicalQueue(globalIdMap.values()); checkPhysicalQueueConsistence(brokerConfigMap); } return new TopicRemappingDetailWrapper(topic, TopicRemappingDetailWrapper.TYPE_CREATE_OR_UPDATE, newEpoch, brokerConfigMap, new HashSet<>(), new HashSet<>()); } public static TopicRemappingDetailWrapper remappingStaticTopic(String topic, Map brokerConfigMap, Set targetBrokers) { Map.Entry maxEpochAndNum = TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(getMappingDetailFromConfig(brokerConfigMap.values()), false, true); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); TopicQueueMappingUtils.checkIfReusePhysicalQueue(globalIdMap.values()); //the check is ok, now do the mapping allocation int maxNum = maxEpochAndNum.getValue(); Map brokerNumMap = new HashMap<>(); for (String broker: targetBrokers) { brokerNumMap.put(broker, 0); } Map brokerNumMapBeforeRemapping = new HashMap<>(); for (TopicQueueMappingOne mappingOne: globalIdMap.values()) { if (brokerNumMapBeforeRemapping.containsKey(mappingOne.bname)) { brokerNumMapBeforeRemapping.put(mappingOne.bname, brokerNumMapBeforeRemapping.get(mappingOne.bname) + 1); } else { brokerNumMapBeforeRemapping.put(mappingOne.bname, 1); } } TopicQueueMappingUtils.MappingAllocator allocator = TopicQueueMappingUtils.buildMappingAllocator(new HashMap<>(), brokerNumMap, brokerNumMapBeforeRemapping); allocator.upToNum(maxNum); Map expectedBrokerNumMap = allocator.getBrokerNumMap(); Queue waitAssignQueues = new ArrayDeque<>(); //cannot directly use the idBrokerMap from allocator, for the number of globalId maybe not in the natural order Map expectedIdToBroker = new HashMap<>(); //the following logic will make sure that, for one broker, either "map in" or "map out" //It can't both, map in some queues but also map out some queues. for (Map.Entry entry : globalIdMap.entrySet()) { Integer queueId = entry.getKey(); TopicQueueMappingOne mappingOne = entry.getValue(); String leaderBroker = mappingOne.getBname(); if (expectedBrokerNumMap.containsKey(leaderBroker)) { if (expectedBrokerNumMap.get(leaderBroker) > 0) { expectedIdToBroker.put(queueId, leaderBroker); expectedBrokerNumMap.put(leaderBroker, expectedBrokerNumMap.get(leaderBroker) - 1); } else { waitAssignQueues.add(queueId); expectedBrokerNumMap.remove(leaderBroker); } } else { waitAssignQueues.add(queueId); } } for (Map.Entry entry: expectedBrokerNumMap.entrySet()) { String broker = entry.getKey(); Integer queueNum = entry.getValue(); for (int i = 0; i < queueNum; i++) { Integer queueId = waitAssignQueues.poll(); assert queueId != null; expectedIdToBroker.put(queueId, broker); } } long newEpoch = Math.max(maxEpochAndNum.getKey() + 1000, System.currentTimeMillis()); //Now construct the remapping info Set brokersToMapOut = new HashSet<>(); Set brokersToMapIn = new HashSet<>(); for (Map.Entry mapEntry : expectedIdToBroker.entrySet()) { Integer queueId = mapEntry.getKey(); String broker = mapEntry.getValue(); TopicQueueMappingOne topicQueueMappingOne = globalIdMap.get(queueId); assert topicQueueMappingOne != null; if (topicQueueMappingOne.getBname().equals(broker)) { continue; } //remapping final String mapInBroker = broker; final String mapOutBroker = topicQueueMappingOne.getBname(); brokersToMapIn.add(mapInBroker); brokersToMapOut.add(mapOutBroker); TopicConfigAndQueueMapping mapInConfig = brokerConfigMap.get(mapInBroker); TopicConfigAndQueueMapping mapOutConfig = brokerConfigMap.get(mapOutBroker); if (mapInConfig == null) { mapInConfig = new TopicConfigAndQueueMapping(new TopicConfig(topic, 0, 0), new TopicQueueMappingDetail(topic, maxNum, mapInBroker, newEpoch)); brokerConfigMap.put(mapInBroker, mapInConfig); } mapInConfig.setWriteQueueNums(mapInConfig.getWriteQueueNums() + 1); mapInConfig.setReadQueueNums(mapInConfig.getReadQueueNums() + 1); List items = new ArrayList<>(topicQueueMappingOne.getItems()); LogicQueueMappingItem last = items.get(items.size() - 1); items.add(new LogicQueueMappingItem(last.getGen() + 1, mapInConfig.getWriteQueueNums() - 1, mapInBroker, -1, 0, -1, -1, -1)); //Use the same object TopicQueueMappingDetail.putMappingInfo(mapInConfig.getMappingDetail(), queueId, items); TopicQueueMappingDetail.putMappingInfo(mapOutConfig.getMappingDetail(), queueId, items); } for (Map.Entry entry : brokerConfigMap.entrySet()) { TopicConfigAndQueueMapping configMapping = entry.getValue(); configMapping.getMappingDetail().setEpoch(newEpoch); configMapping.getMappingDetail().setTotalQueues(maxNum); } //double check { TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(getMappingDetailFromConfig(brokerConfigMap.values()), false, true); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); TopicQueueMappingUtils.checkIfReusePhysicalQueue(globalIdMap.values()); TopicQueueMappingUtils.checkLeaderInTargetBrokers(globalIdMap.values(), targetBrokers); } return new TopicRemappingDetailWrapper(topic, TopicRemappingDetailWrapper.TYPE_REMAPPING, newEpoch, brokerConfigMap, brokersToMapIn, brokersToMapOut); } public static LogicQueueMappingItem findLogicQueueMappingItem(List mappingItems, long logicOffset, boolean ignoreNegative) { if (mappingItems == null || mappingItems.isEmpty()) { return null; } //Could use bi-search to polish performance for (int i = mappingItems.size() - 1; i >= 0; i--) { LogicQueueMappingItem item = mappingItems.get(i); if (ignoreNegative && item.getLogicOffset() < 0) { continue; } if (logicOffset >= item.getLogicOffset()) { return item; } } //if not found, maybe out of range, return the first one for (int i = 0; i < mappingItems.size(); i++) { LogicQueueMappingItem item = mappingItems.get(i); if (ignoreNegative && item.getLogicOffset() < 0) { continue; } else { return item; } } return null; } public static LogicQueueMappingItem findNext(List items, LogicQueueMappingItem currentItem, boolean ignoreNegative) { if (items == null || currentItem == null) { return null; } for (int i = 0; i < items.size(); i++) { LogicQueueMappingItem item = items.get(i); if (ignoreNegative && item.getLogicOffset() < 0) { continue; } if (item.getGen() == currentItem.getGen()) { if (i < items.size() - 1) { item = items.get(i + 1); if (ignoreNegative && item.getLogicOffset() < 0) { return null; } else { return item; } } else { return null; } } } return null; } public static boolean checkIfLeader(List items, TopicQueueMappingDetail mappingDetail) { if (items == null || mappingDetail == null || items.isEmpty()) { return false; } return items.get(items.size() - 1).getBname().equals(mappingDetail.getBname()); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicRemappingDetailWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicRemappingDetailWrapper extends RemotingSerializable { public static final String TYPE_CREATE_OR_UPDATE = "CREATE_OR_UPDATE"; public static final String TYPE_REMAPPING = "REMAPPING"; public static final String SUFFIX_BEFORE = ".before"; public static final String SUFFIX_AFTER = ".after"; private String topic; private String type; private long epoch; private Map brokerConfigMap = new HashMap<>(); private Set brokerToMapIn = new HashSet<>(); private Set brokerToMapOut = new HashSet<>(); public TopicRemappingDetailWrapper() { } public TopicRemappingDetailWrapper(String topic, String type, long epoch, Map brokerConfigMap, Set brokerToMapIn, Set brokerToMapOut) { this.topic = topic; this.type = type; this.epoch = epoch; this.brokerConfigMap = brokerConfigMap; this.brokerToMapIn = brokerToMapIn; this.brokerToMapOut = brokerToMapOut; } public String getTopic() { return topic; } public String getType() { return type; } public long getEpoch() { return epoch; } public Map getBrokerConfigMap() { return brokerConfigMap; } public Set getBrokerToMapIn() { return brokerToMapIn; } public Set getBrokerToMapOut() { return brokerToMapOut; } public void setBrokerConfigMap(Map brokerConfigMap) { this.brokerConfigMap = brokerConfigMap; } public void setBrokerToMapIn(Set brokerToMapIn) { this.brokerToMapIn = brokerToMapIn; } public void setBrokerToMapOut(Set brokerToMapOut) { this.brokerToMapOut = brokerToMapOut; } public void setTopic(String topic) { this.topic = topic; } public void setType(String type) { this.type = type; } public void setEpoch(long epoch) { this.epoch = epoch; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import com.google.common.base.MoreObjects; import java.util.concurrent.TimeUnit; /** * CustomizedRetryPolicy is aim to make group's behavior compatible with messageDelayLevel * * @see org.apache.rocketmq.store.config.MessageStoreConfig */ public class CustomizedRetryPolicy implements RetryPolicy { // 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h private long[] next = new long[] { TimeUnit.SECONDS.toMillis(1), TimeUnit.SECONDS.toMillis(5), TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(30), TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(3), TimeUnit.MINUTES.toMillis(4), TimeUnit.MINUTES.toMillis(5), TimeUnit.MINUTES.toMillis(6), TimeUnit.MINUTES.toMillis(7), TimeUnit.MINUTES.toMillis(8), TimeUnit.MINUTES.toMillis(9), TimeUnit.MINUTES.toMillis(10), TimeUnit.MINUTES.toMillis(20), TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), TimeUnit.HOURS.toMillis(2) }; public CustomizedRetryPolicy() { } public CustomizedRetryPolicy(long[] next) { this.next = next; } public long[] getNext() { return next; } public void setNext(long[] next) { this.next = next; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("next", next) .toString(); } /** * Index = reconsumeTimes + 2 is compatible logic, cause old delayLevelTable starts from index 1, * and old index is reconsumeTime + 3 * * @param reconsumeTimes Message reconsumeTimes {@link org.apache.rocketmq.common.message.MessageExt#getReconsumeTimes} * @see org.apache.rocketmq.broker.processor.AbstractSendMessageProcessor * @see org.apache.rocketmq.store.DefaultMessageStore */ @Override public long nextDelayDuration(int reconsumeTimes) { if (reconsumeTimes < 0) { reconsumeTimes = 0; } int index = reconsumeTimes + 2; if (index >= next.length) { index = next.length - 1; } return next[index]; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import com.google.common.base.MoreObjects; import java.util.concurrent.TimeUnit; public class ExponentialRetryPolicy implements RetryPolicy { private long initial = TimeUnit.SECONDS.toMillis(5); private long max = TimeUnit.HOURS.toMillis(2); private long multiplier = 2; public ExponentialRetryPolicy() { } public ExponentialRetryPolicy(long initial, long max, long multiplier) { this.initial = initial; this.max = max; this.multiplier = multiplier; } public long getInitial() { return initial; } public void setInitial(long initial) { this.initial = initial; } public long getMax() { return max; } public void setMax(long max) { this.max = max; } public long getMultiplier() { return multiplier; } public void setMultiplier(long multiplier) { this.multiplier = multiplier; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("initial", initial) .add("max", max) .add("multiplier", multiplier) .toString(); } @Override public long nextDelayDuration(int reconsumeTimes) { if (reconsumeTimes < 0) { reconsumeTimes = 0; } if (reconsumeTimes > 32) { reconsumeTimes = 32; } return Math.min(max, initial * (long) Math.pow(multiplier, reconsumeTimes)); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupForbidden.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; /** * */ public class GroupForbidden extends RemotingSerializable { private String topic; private String group; private Boolean readable; public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public Boolean getReadable() { return readable; } public void setReadable(Boolean readable) { this.readable = readable; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((group == null) ? 0 : group.hashCode()); result = prime * result + ((readable == null) ? 0 : readable.hashCode()); result = prime * result + ((topic == null) ? 0 : topic.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; GroupForbidden other = (GroupForbidden) obj; return new EqualsBuilder() .append(topic, other.topic) .append(group, other.group) .append(readable, other.readable) .isEquals(); } @Override public String toString() { return "GroupForbidden [topic=" + topic + ", group=" + group + ", readable=" + readable + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import com.alibaba.fastjson2.annotation.JSONField; import com.google.common.base.MoreObjects; public class GroupRetryPolicy { private final static RetryPolicy DEFAULT_RETRY_POLICY = new CustomizedRetryPolicy(); private GroupRetryPolicyType type = GroupRetryPolicyType.CUSTOMIZED; private ExponentialRetryPolicy exponentialRetryPolicy; private CustomizedRetryPolicy customizedRetryPolicy; public GroupRetryPolicyType getType() { return type; } public void setType(GroupRetryPolicyType type) { this.type = type; } public ExponentialRetryPolicy getExponentialRetryPolicy() { return exponentialRetryPolicy; } public void setExponentialRetryPolicy(ExponentialRetryPolicy exponentialRetryPolicy) { this.exponentialRetryPolicy = exponentialRetryPolicy; } public CustomizedRetryPolicy getCustomizedRetryPolicy() { return customizedRetryPolicy; } public void setCustomizedRetryPolicy(CustomizedRetryPolicy customizedRetryPolicy) { this.customizedRetryPolicy = customizedRetryPolicy; } @JSONField(serialize = false, deserialize = false) public RetryPolicy getRetryPolicy() { if (GroupRetryPolicyType.EXPONENTIAL.equals(type)) { if (exponentialRetryPolicy == null) { return DEFAULT_RETRY_POLICY; } return exponentialRetryPolicy; } else if (GroupRetryPolicyType.CUSTOMIZED.equals(type)) { if (customizedRetryPolicy == null) { return DEFAULT_RETRY_POLICY; } return customizedRetryPolicy; } else { return DEFAULT_RETRY_POLICY; } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("type", type) .add("exponentialRetryPolicy", exponentialRetryPolicy) .add("customizedRetryPolicy", customizedRetryPolicy) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; public enum GroupRetryPolicyType { EXPONENTIAL, CUSTOMIZED } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/RetryPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; public interface RetryPolicy { /** * Compute message's next delay duration by specify reconsumeTimes * * @param reconsumeTimes Message reconsumeTimes * @return Message's nextDelayDuration in milliseconds */ long nextDelayDuration(int reconsumeTimes); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SimpleSubscriptionData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import com.google.common.base.MoreObjects; import java.util.Objects; public class SimpleSubscriptionData { private String topic; private String expressionType; private String expression; private long version; public SimpleSubscriptionData(String topic, String expressionType, String expression, long version) { this.topic = topic; this.expressionType = expressionType; this.expression = expression; this.version = version; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getExpressionType() { return expressionType; } public void setExpressionType(String expressionType) { this.expressionType = expressionType; } public String getExpression() { return expression; } public void setExpression(String expression) { this.expression = expression; } public long getVersion() { return version; } public void setVersion(long version) { this.version = version; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SimpleSubscriptionData that = (SimpleSubscriptionData) o; return Objects.equals(topic, that.topic) && Objects.equals(expressionType, that.expressionType) && Objects.equals(expression, that.expression); } @Override public int hashCode() { return Objects.hash(topic, expressionType, expression); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("topic", topic) .add("expressionType", expressionType) .add("expression", expression) .add("version", version) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SubscriptionGroupConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import com.alibaba.fastjson2.annotation.JSONField; import com.google.common.base.MoreObjects; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.math.NumberUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.LiteSubModel; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_SUB_CLIENT_MAX_EVENT_COUNT; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_SUB_CLIENT_QUOTA_ATTRIBUTE; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_SUB_MODEL_ATTRIBUTE; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_SUB_RESET_OFFSET_EXCLUSIVE_ATTRIBUTE; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_BIND_TOPIC_ATTRIBUTE; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.LITE_SUB_RESET_OFFSET_UNSUBSCRIBE_ATTRIBUTE; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.PRIORITY_FACTOR_ATTRIBUTE; public class SubscriptionGroupConfig { private String groupName; private boolean consumeEnable = true; private boolean consumeFromMinEnable = true; private boolean consumeBroadcastEnable = true; private boolean consumeMessageOrderly = false; private int retryQueueNums = 1; private int retryMaxTimes = 16; private GroupRetryPolicy groupRetryPolicy = new GroupRetryPolicy(); private long brokerId = MixAll.MASTER_ID; private long whichBrokerWhenConsumeSlowly = 1; private boolean notifyConsumerIdsChangedEnable = true; private int groupSysFlag = 0; // Only valid for push consumer private int consumeTimeoutMinute = 15; private Set subscriptionDataSet; private Map attributes = new HashMap<>(); public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public boolean isConsumeEnable() { return consumeEnable; } public void setConsumeEnable(boolean consumeEnable) { this.consumeEnable = consumeEnable; } public boolean isConsumeFromMinEnable() { return consumeFromMinEnable; } public void setConsumeFromMinEnable(boolean consumeFromMinEnable) { this.consumeFromMinEnable = consumeFromMinEnable; } public boolean isConsumeBroadcastEnable() { return consumeBroadcastEnable; } public void setConsumeBroadcastEnable(boolean consumeBroadcastEnable) { this.consumeBroadcastEnable = consumeBroadcastEnable; } public boolean isConsumeMessageOrderly() { return consumeMessageOrderly; } public void setConsumeMessageOrderly(boolean consumeMessageOrderly) { this.consumeMessageOrderly = consumeMessageOrderly; } public int getRetryQueueNums() { return retryQueueNums; } public void setRetryQueueNums(int retryQueueNums) { this.retryQueueNums = retryQueueNums; } public int getRetryMaxTimes() { return retryMaxTimes; } public void setRetryMaxTimes(int retryMaxTimes) { this.retryMaxTimes = retryMaxTimes; } public GroupRetryPolicy getGroupRetryPolicy() { return groupRetryPolicy; } public void setGroupRetryPolicy(GroupRetryPolicy groupRetryPolicy) { this.groupRetryPolicy = groupRetryPolicy; } public long getBrokerId() { return brokerId; } public void setBrokerId(long brokerId) { this.brokerId = brokerId; } public long getWhichBrokerWhenConsumeSlowly() { return whichBrokerWhenConsumeSlowly; } public void setWhichBrokerWhenConsumeSlowly(long whichBrokerWhenConsumeSlowly) { this.whichBrokerWhenConsumeSlowly = whichBrokerWhenConsumeSlowly; } public boolean isNotifyConsumerIdsChangedEnable() { return notifyConsumerIdsChangedEnable; } public void setNotifyConsumerIdsChangedEnable(final boolean notifyConsumerIdsChangedEnable) { this.notifyConsumerIdsChangedEnable = notifyConsumerIdsChangedEnable; } public int getGroupSysFlag() { return groupSysFlag; } public void setGroupSysFlag(int groupSysFlag) { this.groupSysFlag = groupSysFlag; } public int getConsumeTimeoutMinute() { return consumeTimeoutMinute; } public void setConsumeTimeoutMinute(int consumeTimeoutMinute) { this.consumeTimeoutMinute = consumeTimeoutMinute; } public Set getSubscriptionDataSet() { return subscriptionDataSet; } public void setSubscriptionDataSet(Set subscriptionDataSet) { this.subscriptionDataSet = subscriptionDataSet; } public Map getAttributes() { return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @JSONField(serialize = false, deserialize = false) public long getPriorityFactor() { String factorStr = null == attributes ? null : attributes.get(PRIORITY_FACTOR_ATTRIBUTE.getName()); return NumberUtils.toLong(factorStr, PRIORITY_FACTOR_ATTRIBUTE.getDefaultValue()); } @JSONField(serialize = false, deserialize = false) public void setLiteBindTopic(String liteBindTopic) { if (liteBindTopic != null) { attributes.put(LITE_BIND_TOPIC_ATTRIBUTE.getName(), liteBindTopic); } } @JSONField(serialize = false, deserialize = false) public String getLiteBindTopic() { return attributes.get(LITE_BIND_TOPIC_ATTRIBUTE.getName()); } @JSONField(serialize = false, deserialize = false) public int getLiteSubClientQuota() { long quota = LITE_SUB_CLIENT_QUOTA_ATTRIBUTE.getDefaultValue(); String quotaStr = attributes.get(LITE_SUB_CLIENT_QUOTA_ATTRIBUTE.getName()); if (quotaStr != null) { quota = Long.parseLong(quotaStr); } return Math.toIntExact(quota); } @JSONField(serialize = false, deserialize = false) public boolean isLiteSubExclusive() { String subLiteModel = attributes.get(LITE_SUB_MODEL_ATTRIBUTE.getName()); return Objects.equals(LiteSubModel.Exclusive.name(), subLiteModel); } /** * Whether to reset offset in exclusive mode */ @JSONField(serialize = false, deserialize = false) public boolean isResetOffsetInExclusiveMode() { String boolStr = attributes.get(LITE_SUB_RESET_OFFSET_EXCLUSIVE_ATTRIBUTE.getName()); return Boolean.parseBoolean(boolStr); } @JSONField(serialize = false, deserialize = false) public boolean isResetOffsetOnUnsubscribe() { String boolStr = attributes.get(LITE_SUB_RESET_OFFSET_UNSUBSCRIBE_ATTRIBUTE.getName()); return Boolean.parseBoolean(boolStr); } @JSONField(serialize = false, deserialize = false) public int getMaxClientEventCount() { String content = attributes.get(LITE_SUB_CLIENT_MAX_EVENT_COUNT.getName()); if (content == null) { return -1; } return NumberUtils.toInt(content, -1); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (brokerId ^ (brokerId >>> 32)); result = prime * result + (consumeBroadcastEnable ? 1231 : 1237); result = prime * result + (consumeEnable ? 1231 : 1237); result = prime * result + (consumeFromMinEnable ? 1231 : 1237); result = prime * result + (notifyConsumerIdsChangedEnable ? 1231 : 1237); result = prime * result + (consumeMessageOrderly ? 1231 : 1237); result = prime * result + ((groupName == null) ? 0 : groupName.hashCode()); result = prime * result + retryMaxTimes; result = prime * result + retryQueueNums; result = prime * result + (int) (whichBrokerWhenConsumeSlowly ^ (whichBrokerWhenConsumeSlowly >>> 32)); result = prime * result + groupSysFlag; result = prime * result + consumeTimeoutMinute; result = prime * result + ((subscriptionDataSet == null) ? 0 : subscriptionDataSet.hashCode()); result = prime * result + attributes.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SubscriptionGroupConfig other = (SubscriptionGroupConfig) obj; return new EqualsBuilder() .append(groupName, other.groupName) .append(consumeEnable, other.consumeEnable) .append(consumeFromMinEnable, other.consumeFromMinEnable) .append(consumeBroadcastEnable, other.consumeBroadcastEnable) .append(consumeMessageOrderly, other.consumeMessageOrderly) .append(retryQueueNums, other.retryQueueNums) .append(retryMaxTimes, other.retryMaxTimes) .append(whichBrokerWhenConsumeSlowly, other.whichBrokerWhenConsumeSlowly) .append(notifyConsumerIdsChangedEnable, other.notifyConsumerIdsChangedEnable) .append(groupSysFlag, other.groupSysFlag) .append(consumeTimeoutMinute, other.consumeTimeoutMinute) .append(subscriptionDataSet, other.subscriptionDataSet) .append(attributes, other.attributes) .isEquals(); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("groupName", groupName) .add("consumeEnable", consumeEnable) .add("consumeFromMinEnable", consumeFromMinEnable) .add("consumeBroadcastEnable", consumeBroadcastEnable) .add("consumeMessageOrderly", consumeMessageOrderly) .add("retryQueueNums", retryQueueNums) .add("retryMaxTimes", retryMaxTimes) .add("groupRetryPolicy", groupRetryPolicy) .add("brokerId", brokerId) .add("whichBrokerWhenConsumeSlowly", whichBrokerWhenConsumeSlowly) .add("notifyConsumerIdsChangedEnable", notifyConsumerIdsChangedEnable) .add("groupSysFlag", groupSysFlag) .add("consumeTimeoutMinute", consumeTimeoutMinute) .add("subscriptionDataSet", subscriptionDataSet) .add("attributes", attributes) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.topic; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class OffsetMovedEvent extends RemotingSerializable { private String consumerGroup; private MessageQueue messageQueue; private long offsetRequest; private long offsetNew; public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public MessageQueue getMessageQueue() { return messageQueue; } public void setMessageQueue(MessageQueue messageQueue) { this.messageQueue = messageQueue; } public long getOffsetRequest() { return offsetRequest; } public void setOffsetRequest(long offsetRequest) { this.offsetRequest = offsetRequest; } public long getOffsetNew() { return offsetNew; } public void setOffsetNew(long offsetNew) { this.offsetNew = offsetNew; } @Override public String toString() { return "OffsetMovedEvent [consumerGroup=" + consumerGroup + ", messageQueue=" + messageQueue + ", offsetRequest=" + offsetRequest + ", offsetNew=" + offsetNew + "]"; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/proxy/SocksProxyConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.proxy; public class SocksProxyConfig { private String addr; private String username; private String password; public SocksProxyConfig() { } public SocksProxyConfig(String addr) { this.addr = addr; } public SocksProxyConfig(String addr, String username, String password) { this.addr = addr; this.username = username; this.password = password; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return String.format("SocksProxy address: %s, username: %s, password: %s", addr, username, password); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/ClientMetadata.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; public class ClientMetadata { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private final ConcurrentMap topicRouteTable = new ConcurrentHashMap<>(); private final ConcurrentMap> topicEndPointsTable = new ConcurrentHashMap<>(); private final ConcurrentMap> brokerAddrTable = new ConcurrentHashMap<>(); private final ConcurrentMap> brokerVersionTable = new ConcurrentHashMap<>(); public void freshTopicRoute(String topic, TopicRouteData topicRouteData) { if (topic == null || topicRouteData == null) { return; } TopicRouteData old = this.topicRouteTable.get(topic); if (!topicRouteData.topicRouteDataChanged(old)) { return ; } { for (BrokerData bd : topicRouteData.getBrokerDatas()) { this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs()); } } { ConcurrentMap mqEndPoints = topicRouteData2EndpointsForStaticTopic(topic, topicRouteData); if (mqEndPoints != null && !mqEndPoints.isEmpty()) { topicEndPointsTable.put(topic, mqEndPoints); } } } public String getBrokerNameFromMessageQueue(final MessageQueue mq) { if (topicEndPointsTable.get(mq.getTopic()) != null && !topicEndPointsTable.get(mq.getTopic()).isEmpty()) { return topicEndPointsTable.get(mq.getTopic()).get(mq); } return mq.getBrokerName(); } public void refreshClusterInfo(ClusterInfo clusterInfo) { if (clusterInfo == null || clusterInfo.getBrokerAddrTable() == null) { return; } for (Map.Entry entry : clusterInfo.getBrokerAddrTable().entrySet()) { brokerAddrTable.put(entry.getKey(), entry.getValue().getBrokerAddrs()); } } public String findMasterBrokerAddr(String brokerName) { if (!brokerAddrTable.containsKey(brokerName)) { return null; } return brokerAddrTable.get(brokerName).get(MixAll.MASTER_ID); } public ConcurrentMap> getBrokerAddrTable() { return brokerAddrTable; } public static ConcurrentMap topicRouteData2EndpointsForStaticTopic(final String topic, final TopicRouteData route) { if (route.getTopicQueueMappingByBroker() == null || route.getTopicQueueMappingByBroker().isEmpty()) { return new ConcurrentHashMap<>(); } ConcurrentMap mqEndPointsOfBroker = new ConcurrentHashMap<>(); Map> mappingInfosByScope = new HashMap<>(); for (Map.Entry entry : route.getTopicQueueMappingByBroker().entrySet()) { TopicQueueMappingInfo info = entry.getValue(); String scope = info.getScope(); if (scope != null) { if (!mappingInfosByScope.containsKey(scope)) { mappingInfosByScope.put(scope, new HashMap<>()); } mappingInfosByScope.get(scope).put(entry.getKey(), entry.getValue()); } } for (Map.Entry> mapEntry : mappingInfosByScope.entrySet()) { String scope = mapEntry.getKey(); Map topicQueueMappingInfoMap = mapEntry.getValue(); ConcurrentMap mqEndPoints = new ConcurrentHashMap<>(); List> mappingInfos = new ArrayList<>(topicQueueMappingInfoMap.entrySet()); mappingInfos.sort((o1, o2) -> (int) (o2.getValue().getEpoch() - o1.getValue().getEpoch())); int maxTotalNums = 0; long maxTotalNumOfEpoch = -1; for (Map.Entry entry : mappingInfos) { TopicQueueMappingInfo info = entry.getValue(); if (info.getEpoch() >= maxTotalNumOfEpoch && info.getTotalQueues() > maxTotalNums) { maxTotalNums = info.getTotalQueues(); } for (Map.Entry idEntry : entry.getValue().getCurrIdMap().entrySet()) { int globalId = idEntry.getKey(); MessageQueue mq = new MessageQueue(topic, TopicQueueMappingUtils.getMockBrokerName(info.getScope()), globalId); TopicQueueMappingInfo oldInfo = mqEndPoints.get(mq); if (oldInfo == null || oldInfo.getEpoch() <= info.getEpoch()) { mqEndPoints.put(mq, info); } } } //accomplish the static logic queues for (int i = 0; i < maxTotalNums; i++) { MessageQueue mq = new MessageQueue(topic, TopicQueueMappingUtils.getMockBrokerName(scope), i); if (!mqEndPoints.containsKey(mq)) { mqEndPointsOfBroker.put(mq, MixAll.LOGICAL_QUEUE_MOCK_BROKER_NAME_NOT_EXIST); } else { mqEndPointsOfBroker.put(mq, mqEndPoints.get(mq).getBname()); } } } return mqEndPointsOfBroker; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RequestBuilder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; public class RequestBuilder { private static Map requestCodeMap = new HashMap<>(); static { requestCodeMap.put(RequestCode.PULL_MESSAGE, PullMessageRequestHeader.class); } public static RpcRequestHeader buildCommonRpcHeader(int requestCode, String destBrokerName) { return buildCommonRpcHeader(requestCode, null, destBrokerName); } public static RpcRequestHeader buildCommonRpcHeader(int requestCode, Boolean oneway, String destBrokerName) { Class requestHeaderClass = requestCodeMap.get(requestCode); if (requestHeaderClass == null) { throw new UnsupportedOperationException("unknown " + requestCode); } try { RpcRequestHeader requestHeader = (RpcRequestHeader) requestHeaderClass.newInstance(); requestHeader.setOneway(oneway); requestHeader.setBrokerName(destBrokerName); return requestHeader; } catch (Throwable t) { throw new RuntimeException(t); } } public static TopicQueueRequestHeader buildTopicQueueRequestHeader(int requestCode, MessageQueue mq) { return buildTopicQueueRequestHeader(requestCode, null, mq.getBrokerName(), mq.getTopic(), mq.getQueueId(), null); } public static TopicQueueRequestHeader buildTopicQueueRequestHeader(int requestCode, MessageQueue mq, Boolean logic) { return buildTopicQueueRequestHeader(requestCode, null, mq.getBrokerName(), mq.getTopic(), mq.getQueueId(), logic); } public static TopicQueueRequestHeader buildTopicQueueRequestHeader(int requestCode, Boolean oneway, MessageQueue mq, Boolean logic) { return buildTopicQueueRequestHeader(requestCode, oneway, mq.getBrokerName(), mq.getTopic(), mq.getQueueId(), logic); } public static TopicQueueRequestHeader buildTopicQueueRequestHeader(int requestCode, Boolean oneway, String destBrokerName, String topic, int queueId, Boolean logic) { Class requestHeaderClass = requestCodeMap.get(requestCode); if (requestHeaderClass == null) { throw new UnsupportedOperationException("unknown " + requestCode); } try { TopicQueueRequestHeader requestHeader = (TopicQueueRequestHeader) requestHeaderClass.newInstance(); requestHeader.setOneway(oneway); requestHeader.setBrokerName(destBrokerName); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setLo(logic); return requestHeader; } catch (Throwable t) { throw new RuntimeException(t); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import java.util.concurrent.Future; import org.apache.rocketmq.common.message.MessageQueue; public interface RpcClient { //common invoke paradigm, the logic remote addr is defined in "bname" field of request //For oneway request, the sign is labeled in request, and do not need an another method named "invokeOneway" //For one Future invoke(RpcRequest request, long timeoutMs) throws RpcException; //For rocketmq, most requests are corresponded to MessageQueue //And for LogicQueue, the broker name is mocked, the physical addr could only be defined by MessageQueue Future invoke(MessageQueue mq, RpcRequest request, long timeoutMs) throws RpcException; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; public abstract class RpcClientHook { //if the return is not null, return it public abstract RpcResponse beforeRequest(RpcRequest rpcRequest) throws RpcException; //if the return is not null, return it public abstract RpcResponse afterResponse(RpcResponse rpcResponse) throws RpcException; } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; public class RpcClientImpl implements RpcClient { private ClientMetadata clientMetadata; private RemotingClient remotingClient; private List clientHookList = new ArrayList<>(); public RpcClientImpl(ClientMetadata clientMetadata, RemotingClient remotingClient) { this.clientMetadata = clientMetadata; this.remotingClient = remotingClient; } public void registerHook(RpcClientHook hook) { clientHookList.add(hook); } @Override public Future invoke(MessageQueue mq, RpcRequest request, long timeoutMs) throws RpcException { String bname = clientMetadata.getBrokerNameFromMessageQueue(mq); request.getHeader().setBrokerName(bname); return invoke(request, timeoutMs); } public Promise createResponseFuture() { return ImmediateEventExecutor.INSTANCE.newPromise(); } @Override public Future invoke(RpcRequest request, long timeoutMs) throws RpcException { if (clientHookList.size() > 0) { for (RpcClientHook rpcClientHook: clientHookList) { RpcResponse response = rpcClientHook.beforeRequest(request); if (response != null) { //For 1.6, there is not easy-to-use future impl return createResponseFuture().setSuccess(response); } } } String addr = getBrokerAddrByNameOrException(request.getHeader().bname); Promise rpcResponsePromise = null; try { switch (request.getCode()) { case RequestCode.PULL_MESSAGE: rpcResponsePromise = handlePullMessage(addr, request, timeoutMs); break; case RequestCode.GET_MIN_OFFSET: rpcResponsePromise = handleGetMinOffset(addr, request, timeoutMs); break; case RequestCode.GET_MAX_OFFSET: rpcResponsePromise = handleGetMaxOffset(addr, request, timeoutMs); break; case RequestCode.SEARCH_OFFSET_BY_TIMESTAMP: rpcResponsePromise = handleSearchOffset(addr, request, timeoutMs); break; case RequestCode.GET_EARLIEST_MSG_STORETIME: rpcResponsePromise = handleGetEarliestMsgStoretime(addr, request, timeoutMs); break; case RequestCode.QUERY_CONSUMER_OFFSET: rpcResponsePromise = handleQueryConsumerOffset(addr, request, timeoutMs); break; case RequestCode.UPDATE_CONSUMER_OFFSET: rpcResponsePromise = handleUpdateConsumerOffset(addr, request, timeoutMs); break; case RequestCode.GET_TOPIC_STATS_INFO: rpcResponsePromise = handleCommonBodyRequest(addr, request, timeoutMs, TopicStatsTable.class); break; case RequestCode.GET_TOPIC_CONFIG: rpcResponsePromise = handleCommonBodyRequest(addr, request, timeoutMs, TopicConfigAndQueueMapping.class); break; default: throw new RpcException(ResponseCode.REQUEST_CODE_NOT_SUPPORTED, "Unknown request code " + request.getCode()); } } catch (RpcException rpcException) { throw rpcException; } catch (Exception e) { throw new RpcException(ResponseCode.RPC_UNKNOWN, "error from remoting layer", e); } return rpcResponsePromise; } private String getBrokerAddrByNameOrException(String bname) throws RpcException { String addr = this.clientMetadata.findMasterBrokerAddr(bname); if (addr == null) { throw new RpcException(ResponseCode.SYSTEM_ERROR, "cannot find addr for broker " + bname); } return addr; } private void processFailedResponse(String addr, RemotingCommand requestCommand, ResponseFuture responseFuture, Promise rpcResponsePromise) { RemotingCommand responseCommand = responseFuture.getResponseCommand(); if (responseCommand != null) { //this should not happen return; } int errorCode = ResponseCode.RPC_UNKNOWN; String errorMessage = null; if (!responseFuture.isSendRequestOK()) { errorCode = ResponseCode.RPC_SEND_TO_CHANNEL_FAILED; errorMessage = "send request failed to " + addr + ". Request: " + requestCommand; } else if (responseFuture.isTimeout()) { errorCode = ResponseCode.RPC_TIME_OUT; errorMessage = "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + requestCommand; } else { errorMessage = "unknown reason. addr: " + addr + ", timeoutMillis: " + responseFuture.getTimeoutMillis() + ". Request: " + requestCommand; } rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(errorCode, errorMessage))); } public Promise handlePullMessage(final String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); final Promise rpcResponsePromise = createResponseFuture(); InvokeCallback callback = new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { try { switch (response.getCode()) { case ResponseCode.SUCCESS: case ResponseCode.PULL_NOT_FOUND: case ResponseCode.PULL_RETRY_IMMEDIATELY: case ResponseCode.PULL_OFFSET_MOVED: PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(response.getCode(), responseHeader, response.getBody())); break; default: RpcResponse rpcResponse = new RpcResponse(new RpcException(response.getCode(), "unexpected remote response code")); rpcResponsePromise.setSuccess(rpcResponse); } } catch (Exception e) { String errorMessage = "process failed. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + requestCommand; RpcResponse rpcResponse = new RpcResponse(new RpcException(ResponseCode.RPC_UNKNOWN, errorMessage, e)); rpcResponsePromise.setSuccess(rpcResponse); } } @Override public void operationFail(Throwable throwable) { String errorMessage = "process failed. addr: " + addr + ". Request: " + requestCommand; RpcResponse rpcResponse = new RpcResponse(new RpcException(ResponseCode.RPC_UNKNOWN, errorMessage, throwable)); rpcResponsePromise.setSuccess(rpcResponse); } }; this.remotingClient.invokeAsync(addr, requestCommand, timeoutMillis, callback); return rpcResponsePromise; } public Promise handleSearchOffset(String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) responseCommand.decodeCommandCustomHeader(SearchOffsetResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } public Promise handleQueryConsumerOffset(String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { QueryConsumerOffsetResponseHeader responseHeader = (QueryConsumerOffsetResponseHeader) responseCommand.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); break; } case ResponseCode.QUERY_NOT_FOUND: { rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), null, null)); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } public Promise handleUpdateConsumerOffset(String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { UpdateConsumerOffsetResponseHeader responseHeader = (UpdateConsumerOffsetResponseHeader) responseCommand.decodeCommandCustomHeader(UpdateConsumerOffsetResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } public Promise handleCommonBodyRequest(final String addr, RpcRequest rpcRequest, long timeoutMillis, Class bodyClass) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { rpcResponsePromise.setSuccess(new RpcResponse(ResponseCode.SUCCESS, null, RemotingSerializable.decode(responseCommand.getBody(), bodyClass))); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } public Promise handleGetMinOffset(String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) responseCommand.decodeCommandCustomHeader(GetMinOffsetResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } public Promise handleGetMaxOffset(String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) responseCommand.decodeCommandCustomHeader(GetMaxOffsetResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } public Promise handleGetEarliestMsgStoretime(String addr, RpcRequest rpcRequest, long timeoutMillis) throws Exception { final Promise rpcResponsePromise = createResponseFuture(); RemotingCommand requestCommand = RpcClientUtils.createCommandForRpcRequest(rpcRequest); RemotingCommand responseCommand = this.remotingClient.invokeSync(addr, requestCommand, timeoutMillis); assert responseCommand != null; switch (responseCommand.getCode()) { case ResponseCode.SUCCESS: { GetEarliestMsgStoretimeResponseHeader responseHeader = (GetEarliestMsgStoretimeResponseHeader) responseCommand.decodeCommandCustomHeader(GetEarliestMsgStoretimeResponseHeader.class); rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); break; } default: { rpcResponsePromise.setSuccess(new RpcResponse(new RpcException(responseCommand.getCode(), "unknown remote error"))); } } return rpcResponsePromise; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import java.nio.ByteBuffer; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class RpcClientUtils { public static RemotingCommand createCommandForRpcRequest(RpcRequest rpcRequest) { RemotingCommand cmd = RemotingCommand.createRequestCommand(rpcRequest.getCode(), rpcRequest.getHeader()); cmd.setBody(encodeBody(rpcRequest.getBody())); return cmd; } public static RemotingCommand createCommandForRpcResponse(RpcResponse rpcResponse) { RemotingCommand cmd = RemotingCommand.createResponseCommandWithHeader(rpcResponse.getCode(), rpcResponse.getHeader()); cmd.setRemark(rpcResponse.getException() == null ? "" : rpcResponse.getException().getMessage()); cmd.setBody(encodeBody(rpcResponse.getBody())); return cmd; } public static byte[] encodeBody(Object body) { if (body == null) { return null; } if (body instanceof byte[]) { return (byte[])body; } else if (body instanceof RemotingSerializable) { return ((RemotingSerializable) body).encode(); } else if (body instanceof ByteBuffer) { ByteBuffer buffer = (ByteBuffer)body; buffer.mark(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); buffer.reset(); return data; } else { throw new RuntimeException("Unsupported body type " + body.getClass()); } } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import org.apache.rocketmq.remoting.exception.RemotingException; public class RpcException extends RemotingException { private int errorCode; public RpcException(int errorCode, String message) { super(message); this.errorCode = errorCode; } public RpcException(int errorCode, String message, Throwable cause) { super(message, cause); this.errorCode = errorCode; } public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; public class RpcRequest { int code; private RpcRequestHeader header; private Object body; public RpcRequest(int code, RpcRequestHeader header, Object body) { this.code = code; this.header = header; this.body = body; } public RpcRequestHeader getHeader() { return header; } public Object getBody() { return body; } public int getCode() { return code; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import com.google.common.base.MoreObjects; import java.util.Objects; import org.apache.rocketmq.remoting.CommandCustomHeader; public abstract class RpcRequestHeader implements CommandCustomHeader { //the namespace name protected String ns; //if the data has been namespaced protected Boolean nsd; //the abstract remote addr name, usually the physical broker name protected String bname; //oneway protected Boolean oway; @Deprecated public String getBname() { return bname; } @Deprecated public void setBname(String brokerName) { this.bname = brokerName; } public String getBrokerName() { return bname; } public void setBrokerName(String brokerName) { this.bname = brokerName; } public String getNamespace() { return ns; } public void setNamespace(String namespace) { this.ns = namespace; } public Boolean getNamespaced() { return nsd; } public void setNamespaced(Boolean namespaced) { this.nsd = namespaced; } public Boolean getOneway() { return oway; } public void setOneway(Boolean oneway) { this.oway = oneway; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } RpcRequestHeader header = (RpcRequestHeader) o; return Objects.equals(ns, header.ns) && Objects.equals(nsd, header.nsd) && Objects.equals(bname, header.bname) && Objects.equals(oway, header.oway); } @Override public int hashCode() { return Objects.hash(ns, nsd, bname, oway); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("namespace", ns) .add("namespaced", nsd) .add("brokerName", bname) .add("oneway", oway) .toString(); } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import org.apache.rocketmq.remoting.CommandCustomHeader; public class RpcResponse { private int code; private CommandCustomHeader header; private Object body; public RpcException exception; public RpcResponse() { } public RpcResponse(int code, CommandCustomHeader header, Object body) { this.code = code; this.header = header; this.body = body; } public RpcResponse(RpcException rpcException) { this.code = rpcException.getErrorCode(); this.exception = rpcException; } public int getCode() { return code; } public CommandCustomHeader getHeader() { return header; } public void setHeader(CommandCustomHeader header) { this.header = header; } public Object getBody() { return body; } public void setBody(Object body) { this.body = body; } public RpcException getException() { return exception; } public void setException(RpcException exception) { this.exception = exception; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicQueueRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; public abstract class TopicQueueRequestHeader extends TopicRequestHeader { public abstract Integer getQueueId(); public abstract void setQueueId(Integer queueId); } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicRequestHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; public abstract class TopicRequestHeader extends RpcRequestHeader { //logical protected Boolean lo; public abstract String getTopic(); public abstract void setTopic(String topic); public Boolean getLo() { return lo; } public void setLo(Boolean lo) { this.lo = lo; } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/DynamicalExtFieldRPCHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpchook; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class DynamicalExtFieldRPCHook implements RPCHook { @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { String zoneName = System.getProperty(MixAll.ROCKETMQ_ZONE_PROPERTY, System.getenv(MixAll.ROCKETMQ_ZONE_ENV)); if (StringUtils.isNotBlank(zoneName)) { request.addExtField(MixAll.ZONE_NAME, zoneName); } String zoneMode = System.getProperty(MixAll.ROCKETMQ_ZONE_MODE_PROPERTY, System.getenv(MixAll.ROCKETMQ_ZONE_MODE_ENV)); if (StringUtils.isNotBlank(zoneMode)) { request.addExtField(MixAll.ZONE_MODE, zoneMode); } } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } } ================================================ FILE: remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/StreamTypeRPCHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpchook; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestType; public class StreamTypeRPCHook implements RPCHook { @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { request.addExtField(MixAll.REQ_T, String.valueOf(RequestType.STREAM.getCode())); } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/ProxyProtocolTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.codec.haproxy.HAProxyCommand; import io.netty.handler.codec.haproxy.HAProxyMessage; import io.netty.handler.codec.haproxy.HAProxyMessageEncoder; import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; import java.lang.reflect.Method; import java.net.Socket; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertNotNull; @RunWith(MockitoJUnitRunner.class) public class ProxyProtocolTest { private RemotingServer remotingServer; private RemotingClient remotingClient; @Before public void setUp() throws Exception { NettyClientConfig clientConfig = new NettyClientConfig(); clientConfig.setUseTLS(false); remotingServer = RemotingServerTest.createRemotingServer(); remotingClient = RemotingServerTest.createRemotingClient(clientConfig); await().pollDelay(Duration.ofMillis(10)) .pollInterval(Duration.ofMillis(10)) .atMost(20, TimeUnit.SECONDS).until(() -> isHostConnectable(getServerAddress())); } @Test public void testProxyProtocol() throws Exception { sendHAProxyMessage(remotingClient); requestThenAssertResponse(remotingClient); } private void requestThenAssertResponse(RemotingClient remotingClient) throws Exception { RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 10000 * 3); assertNotNull(response); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); assertThat(response.getExtFields().get("messageTitle")).isEqualTo("Welcome"); } private void sendHAProxyMessage(RemotingClient remotingClient) throws Exception { Method getAndCreateChannel = NettyRemotingClient.class.getDeclaredMethod("getAndCreateChannel", String.class); getAndCreateChannel.setAccessible(true); NettyRemotingClient nettyRemotingClient = (NettyRemotingClient) remotingClient; Channel channel = (Channel) getAndCreateChannel.invoke(nettyRemotingClient, getServerAddress()); HAProxyMessage message = new HAProxyMessage(HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4, "127.0.0.1", "127.0.0.2", 8000, 9000); ByteBuf byteBuf = Unpooled.directBuffer(); Method encode = HAProxyMessageEncoder.class.getDeclaredMethod("encodeV2", HAProxyMessage.class, ByteBuf.class); encode.setAccessible(true); encode.invoke(HAProxyMessageEncoder.INSTANCE, message, byteBuf); channel.writeAndFlush(byteBuf).sync(); } private static RemotingCommand createRequest() { RequestHeader requestHeader = new RequestHeader(); requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); return RemotingCommand.createRequestCommand(0, requestHeader); } private String getServerAddress() { return "localhost:" + remotingServer.localListenPort(); } private boolean isHostConnectable(String addr) { try (Socket socket = new Socket()) { socket.connect(NetworkUtil.string2SocketAddress(addr)); return true; } catch (IOException ignored) { } return false; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import io.netty.channel.ChannelHandlerContext; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import org.apache.rocketmq.remoting.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; public class RemotingServerTest { private static RemotingServer remotingServer; private static RemotingClient remotingClient; public static RemotingServer createRemotingServer() throws InterruptedException { NettyServerConfig config = new NettyServerConfig(); RemotingServer remotingServer = new NettyRemotingServer(config); remotingServer.registerProcessor(0, new NettyRequestProcessor() { @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) { request.setRemark("Hi " + ctx.channel().remoteAddress()); return request; } @Override public boolean rejectRequest() { return false; } }, Executors.newCachedThreadPool()); remotingServer.start(); return remotingServer; } public static RemotingClient createRemotingClient() { return createRemotingClient(new NettyClientConfig()); } public static RemotingClient createRemotingClient(NettyClientConfig nettyClientConfig) { RemotingClient client = new NettyRemotingClient(nettyClientConfig); client.start(); return client; } @BeforeClass public static void setup() throws InterruptedException { remotingServer = createRemotingServer(); remotingClient = createRemotingClient(); } @AfterClass public static void destroy() { remotingClient.shutdown(); remotingServer.shutdown(); } @Test public void testInvokeSync() throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException { RequestHeader requestHeader = new RequestHeader(); requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); RemotingCommand request = RemotingCommand.createRequestCommand(0, requestHeader); RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); assertNotNull(response); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); } @Test public void testInvokeOneway() throws InterruptedException, RemotingConnectException, RemotingTimeoutException, RemotingTooMuchRequestException, RemotingSendRequestException { RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setRemark("messi"); remotingClient.invokeOneway("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); } @Test public void testInvokeAsync() throws InterruptedException, RemotingConnectException, RemotingTimeoutException, RemotingTooMuchRequestException, RemotingSendRequestException { final CountDownLatch latch = new CountDownLatch(1); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setRemark("messi"); remotingClient.invokeAsync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { latch.countDown(); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); } @Override public void operationFail(Throwable throwable) { } }); latch.await(); } } class RequestHeader implements CommandCustomHeader { @CFNullable private Integer count; @CFNullable private String messageTitle; @Override public void checkFields() throws RemotingCommandException { } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } public String getMessageTitle() { return messageTitle; } public void setMessageTitle(String messageTitle) { this.messageTitle = messageTitle; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; public class SubRemotingServerTest { private static final int SUB_SERVER_PORT = 1234; private static RemotingServer remotingServer; private static RemotingClient remotingClient; private static RemotingServer subServer; @BeforeClass public static void setup() throws InterruptedException { remotingServer = RemotingServerTest.createRemotingServer(); remotingClient = RemotingServerTest.createRemotingClient(); subServer = createSubRemotingServer(remotingServer); } @AfterClass public static void destroy() { remotingClient.shutdown(); remotingServer.shutdown(); } public static RemotingServer createSubRemotingServer(RemotingServer parentServer) { RemotingServer subServer = parentServer.newRemotingServer(SUB_SERVER_PORT); subServer.registerProcessor(1, new NettyRequestProcessor() { @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, final RemotingCommand request) throws Exception { request.setRemark(String.valueOf(RemotingHelper.parseSocketAddressPort(ctx.channel().localAddress()))); return request; } @Override public boolean rejectRequest() { return false; } }, null); subServer.start(); return subServer; } @Test public void testInvokeSubRemotingServer() throws InterruptedException, RemotingTimeoutException, RemotingConnectException, RemotingSendRequestException { RequestHeader requestHeader = new RequestHeader(); requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); // Parent remoting server doesn't support RequestCode 1 RemotingCommand request = RemotingCommand.createRequestCommand(1, requestHeader); RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED); // Issue request to SubRemotingServer response = remotingClient.invokeSync("localhost:1234", request, 1000 * 3); assertThat(response).isNotNull(); assertThat(response.getExtFields()).hasSize(2); assertThat(response.getRemark()).isEqualTo(String.valueOf(SUB_SERVER_PORT)); // Issue unsupported request to SubRemotingServer request.setCode(0); response = remotingClient.invokeSync("localhost:1234", request, 1000 * 3); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED); // Issue request to a closed SubRemotingServer request.setCode(1); remotingServer.removeRemotingServer(SUB_SERVER_PORT); subServer.shutdown(); try { remotingClient.invokeSync("localhost:1234", request, 1000 * 3); failBecauseExceptionWasNotThrown(RemotingTimeoutException.class); } catch (Exception e) { assertThat(e).isInstanceOfAny(RemotingTimeoutException.class, RemotingSendRequestException.class); } } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingServer; import org.apache.rocketmq.remoting.netty.TlsHelper; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.Socket; import java.time.Duration; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_AUTHSERVER; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_CERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPASSWORD; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_TRUSTCERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_AUTHCLIENT; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_CERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPASSWORD; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_NEED_CLIENT_AUTH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_TRUSTCERTPATH; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientAuthServer; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPassword; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientTrustCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsConfigFile; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsMode; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerAuthClient; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPassword; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerNeedClientAuth; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerTrustCertPath; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsTestModeEnable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertNotNull; @RunWith(MockitoJUnitRunner.class) public class TlsTest { private RemotingServer remotingServer; private RemotingClient remotingClient; @Rule public TestName name = new TestName(); @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @Before public void setUp() throws InterruptedException { tlsMode = TlsMode.ENFORCING; tlsTestModeEnable = false; tlsServerNeedClientAuth = "require"; tlsServerKeyPath = getCertsPath("server.key"); tlsServerCertPath = getCertsPath("server.pem"); tlsServerAuthClient = true; tlsServerTrustCertPath = getCertsPath("ca.pem"); tlsClientKeyPath = getCertsPath("client.key"); tlsClientCertPath = getCertsPath("client.pem"); tlsClientAuthServer = true; tlsClientTrustCertPath = getCertsPath("ca.pem"); tlsClientKeyPassword = "1234"; tlsServerKeyPassword = ""; NettyClientConfig clientConfig = new NettyClientConfig(); clientConfig.setUseTLS(true); if ("serverRejectsUntrustedClientCert".equals(name.getMethodName())) { // Create a client. Its credentials come from a CA that the server does not trust. The client // trusts both test CAs to ensure the handshake failure is due to the server rejecting the client's cert. tlsClientKeyPath = getCertsPath("badClient.key"); tlsClientCertPath = getCertsPath("badClient.pem"); } else if ("serverAcceptsUntrustedClientCert".equals(name.getMethodName())) { tlsClientKeyPath = getCertsPath("badClient.key"); tlsClientCertPath = getCertsPath("badClient.pem"); tlsServerAuthClient = false; } else if ("noClientAuthFailure".equals(name.getMethodName())) { //Clear the client cert config to ensure produce the handshake error tlsClientKeyPath = ""; tlsClientCertPath = ""; } else if ("clientRejectsUntrustedServerCert".equals(name.getMethodName())) { tlsServerKeyPath = getCertsPath("badServer.key"); tlsServerCertPath = getCertsPath("badServer.pem"); } else if ("clientAcceptsUntrustedServerCert".equals(name.getMethodName())) { tlsServerKeyPath = getCertsPath("badServer.key"); tlsServerCertPath = getCertsPath("badServer.pem"); tlsClientAuthServer = false; } else if ("serverNotNeedClientAuth".equals(name.getMethodName())) { tlsServerNeedClientAuth = "none"; tlsClientKeyPath = ""; tlsClientCertPath = ""; } else if ("serverWantClientAuth".equals(name.getMethodName())) { tlsServerNeedClientAuth = "optional"; } else if ("serverWantClientAuth_ButClientNoCert".equals(name.getMethodName())) { tlsServerNeedClientAuth = "optional"; tlsClientKeyPath = ""; tlsClientCertPath = ""; } else if ("serverAcceptsUnAuthClient".equals(name.getMethodName())) { tlsMode = TlsMode.PERMISSIVE; tlsClientKeyPath = ""; tlsClientCertPath = ""; clientConfig.setUseTLS(false); } else if ("disabledServerRejectsSSLClient".equals(name.getMethodName())) { tlsMode = TlsMode.DISABLED; } else if ("disabledServerAcceptUnAuthClient".equals(name.getMethodName())) { tlsMode = TlsMode.DISABLED; tlsClientKeyPath = ""; tlsClientCertPath = ""; clientConfig.setUseTLS(false); } else if ("reloadSslContextForServer".equals(name.getMethodName())) { tlsClientAuthServer = false; tlsServerNeedClientAuth = "none"; } remotingServer = RemotingServerTest.createRemotingServer(); remotingClient = RemotingServerTest.createRemotingClient(clientConfig); await().pollDelay(Duration.ofMillis(10)) .pollInterval(Duration.ofMillis(10)) .atMost(20, TimeUnit.SECONDS).until(() -> isHostConnectable(getServerAddress())); } @After public void tearDown() { remotingClient.shutdown(); remotingServer.shutdown(); tlsMode = TlsMode.PERMISSIVE; } /** * Tests that a client and a server configured using two-way SSL auth can successfully * communicate with each other. */ @Test public void basicClientServerIntegrationTest() throws Exception { requestThenAssertResponse(); } @Test public void reloadSslContextForServer() throws Exception { requestThenAssertResponse(); //Use new cert and private key tlsClientKeyPath = getCertsPath("badClient.key"); tlsClientCertPath = getCertsPath("badClient.pem"); ((NettyRemotingServer) remotingServer).loadSslContext(); //Request Again requestThenAssertResponse(); //Start another client NettyClientConfig clientConfig = new NettyClientConfig(); clientConfig.setUseTLS(true); RemotingClient remotingClient = RemotingServerTest.createRemotingClient(clientConfig); requestThenAssertResponse(remotingClient); } @Test public void serverNotNeedClientAuth() throws Exception { requestThenAssertResponse(); } @Test public void serverWantClientAuth_ButClientNoCert() throws Exception { requestThenAssertResponse(); } @Test public void serverAcceptsUnAuthClient() throws Exception { requestThenAssertResponse(); } @Test public void disabledServerRejectsSSLClient() throws Exception { try { RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 5); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } } @Test public void disabledServerAcceptUnAuthClient() throws Exception { requestThenAssertResponse(); } /** * Tests that a server configured to require client authentication refuses to accept connections * from a client that has an untrusted certificate. */ @Test public void serverRejectsUntrustedClientCert() throws Exception { try { RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 5); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } } @Test public void serverAcceptsUntrustedClientCert() throws Exception { requestThenAssertResponse(); // Thread.sleep(1000000L); } /** * Tests that a server configured to require client authentication actually does require client * authentication. */ @Test public void noClientAuthFailure() throws Exception { try { RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 3); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } } /** * Tests that a client configured using GrpcSslContexts refuses to talk to a server that has an * an untrusted certificate. */ @Test public void clientRejectsUntrustedServerCert() throws Exception { try { RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 3); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } } @Test public void clientAcceptsUntrustedServerCert() throws Exception { requestThenAssertResponse(); } @Test public void testTlsConfigThroughFile() throws Exception { File file = tempFolder.newFile("tls.config"); tlsTestModeEnable = true; tlsConfigFile = file.getAbsolutePath(); StringBuilder sb = new StringBuilder(); sb.append(TLS_SERVER_NEED_CLIENT_AUTH + "=require\n"); sb.append(TLS_SERVER_KEYPATH + "=/server.key\n"); sb.append(TLS_SERVER_CERTPATH + "=/server.pem\n"); sb.append(TLS_SERVER_KEYPASSWORD + "=2345\n"); sb.append(TLS_SERVER_AUTHCLIENT + "=true\n"); sb.append(TLS_SERVER_TRUSTCERTPATH + "=/ca.pem\n"); sb.append(TLS_CLIENT_KEYPATH + "=/client.key\n"); sb.append(TLS_CLIENT_KEYPASSWORD + "=1234\n"); sb.append(TLS_CLIENT_CERTPATH + "=/client.pem\n"); sb.append(TLS_CLIENT_AUTHSERVER + "=false\n"); sb.append(TLS_CLIENT_TRUSTCERTPATH + "=/ca.pem\n"); writeStringToFile(file.getAbsolutePath(), sb.toString()); TlsHelper.buildSslContext(false); assertThat(tlsServerNeedClientAuth).isEqualTo("require"); assertThat(tlsServerKeyPath).isEqualTo("/server.key"); assertThat(tlsServerCertPath).isEqualTo("/server.pem"); assertThat(tlsServerKeyPassword).isEqualTo("2345"); assertThat(tlsServerAuthClient).isEqualTo(true); assertThat(tlsServerTrustCertPath).isEqualTo("/ca.pem"); assertThat(tlsClientKeyPath).isEqualTo("/client.key"); assertThat(tlsClientKeyPassword).isEqualTo("1234"); assertThat(tlsClientCertPath).isEqualTo("/client.pem"); assertThat(tlsClientAuthServer).isEqualTo(false); assertThat(tlsClientTrustCertPath).isEqualTo("/ca.pem"); tlsConfigFile = "/notFound"; } private static void writeStringToFile(String path, String content) { try { PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(path, true))); out.println(content); out.close(); } catch (IOException ignore) { } } private static String getCertsPath(String fileName) { ClassLoader loader = TlsTest.class.getClassLoader(); InputStream stream = loader.getResourceAsStream("certs/" + fileName); if (null == stream) { throw new RuntimeException("File: " + fileName + " is not found"); } try { String[] segments = fileName.split("\\."); File f = File.createTempFile(UUID.randomUUID().toString(), segments[1]); f.deleteOnExit(); try (BufferedInputStream bis = new BufferedInputStream(stream); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f))) { byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) > 0) { bos.write(buffer, 0, len); } } catch (IOException e) { throw new RuntimeException(e); } return f.getAbsolutePath(); } catch (IOException e) { throw new RuntimeException(e); } } private String getServerAddress() { return "localhost:" + remotingServer.localListenPort(); } private static RemotingCommand createRequest() { RequestHeader requestHeader = new RequestHeader(); requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); return RemotingCommand.createRequestCommand(0, requestHeader); } private void requestThenAssertResponse() throws Exception { requestThenAssertResponse(remotingClient); } private void requestThenAssertResponse(RemotingClient remotingClient) throws Exception { RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 3); assertNotNull(response); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); assertThat(response.getExtFields().get("messageTitle")).isEqualTo("Welcome"); } private boolean isHostConnectable(String addr) { try (Socket socket = new Socket()) { socket.connect(NetworkUtil.string2SocketAddress(addr)); return true; } catch (IOException ignored) { } return false; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/FileRegionEncoderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.buffer.ByteBuf; import io.netty.channel.DefaultFileRegion; import io.netty.channel.FileRegion; import io.netty.channel.embedded.EmbeddedChannel; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Random; import java.util.UUID; import org.junit.Assert; import org.junit.Test; public class FileRegionEncoderTest { /** * This unit test case ensures that {@link FileRegionEncoder} indeed wraps {@link FileRegion} to * {@link ByteBuf}. * @throws IOException if there is an error. */ @Test public void testEncode() throws IOException { FileRegionEncoder fileRegionEncoder = new FileRegionEncoder(); EmbeddedChannel channel = new EmbeddedChannel(fileRegionEncoder); File file = File.createTempFile(UUID.randomUUID().toString(), ".data"); file.deleteOnExit(); Random random = new Random(System.currentTimeMillis()); int dataLength = 1 << 10; byte[] data = new byte[dataLength]; random.nextBytes(data); write(file, data); FileRegion fileRegion = new DefaultFileRegion(file, 0, dataLength); Assert.assertEquals(0, fileRegion.transferred()); Assert.assertEquals(dataLength, fileRegion.count()); Assert.assertTrue(channel.writeOutbound(fileRegion)); ByteBuf out = (ByteBuf) channel.readOutbound(); byte[] arr = new byte[out.readableBytes()]; out.getBytes(0, arr); Assert.assertArrayEquals("Data should be identical", data, arr); } /** * Write byte array to the specified file. * * @param file File to write to. * @param data byte array to write. * @throws IOException in case there is an exception. */ private static void write(File file, byte[] data) throws IOException { BufferedOutputStream bufferedOutputStream = null; try { bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file, false)); bufferedOutputStream.write(data); bufferedOutputStream.flush(); } finally { if (null != bufferedOutputStream) { bufferedOutputStream.close(); } } } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.ChannelFuture; import io.netty.channel.local.LocalChannel; public class MockChannel extends LocalChannel { @Override public ChannelFuture writeAndFlush(Object msg) { return new MockChannelPromise(MockChannel.this); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannelPromise.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.channel.Channel; import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.jetbrains.annotations.NotNull; public class MockChannelPromise implements ChannelPromise { protected Channel channel; public MockChannelPromise(Channel channel) { this.channel = channel; } @Override public Channel channel() { return channel; } @Override public ChannelPromise setSuccess(Void result) { return this; } @Override public ChannelPromise setSuccess() { return this; } @Override public boolean trySuccess() { return false; } @Override public ChannelPromise setFailure(Throwable cause) { return this; } @Override public ChannelPromise addListener(GenericFutureListener> listener) { return this; } @Override public ChannelPromise addListeners(GenericFutureListener>... listeners) { return this; } @Override public ChannelPromise removeListener(GenericFutureListener> listener) { return this; } @Override public ChannelPromise removeListeners(GenericFutureListener>... listeners) { return this; } @Override public ChannelPromise sync() throws InterruptedException { return this; } @Override public ChannelPromise syncUninterruptibly() { return this; } @Override public ChannelPromise await() throws InterruptedException { return this; } @Override public ChannelPromise awaitUninterruptibly() { return this; } @Override public ChannelPromise unvoid() { return this; } @Override public boolean isVoid() { return false; } @Override public boolean trySuccess(Void result) { return false; } @Override public boolean tryFailure(Throwable cause) { return false; } @Override public boolean setUncancellable() { return false; } @Override public boolean isSuccess() { return false; } @Override public boolean isCancellable() { return false; } @Override public Throwable cause() { return null; } @Override public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return false; } @Override public boolean await(long timeoutMillis) throws InterruptedException { return false; } @Override public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { return false; } @Override public boolean awaitUninterruptibly(long timeoutMillis) { return false; } @Override public Void getNow() { return null; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return false; } @Override public Void get() throws InterruptedException, ExecutionException { return null; } @Override public Void get(long timeout, @NotNull java.util.concurrent.TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyClientConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(MockitoJUnitRunner.class) public class NettyClientConfigTest { @Test public void testChangeConfigBySystemProperty() { System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_WORKER_SIZE, "1"); System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_CONNECT_TIMEOUT, "2000"); System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_CHANNEL_MAX_IDLE_SECONDS, "60"); System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE, "16383"); System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE, "16384"); System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_CLOSE_SOCKET_IF_TIMEOUT, "false"); System.setProperty(TlsSystemConfig.TLS_ENABLE, "true"); NettySystemConfig.socketSndbufSize = Integer.parseInt(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE, "65535")); NettySystemConfig.socketRcvbufSize = Integer.parseInt(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE, "65535")); NettySystemConfig.clientWorkerSize = Integer.parseInt(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_WORKER_SIZE, "4")); NettySystemConfig.connectTimeoutMillis = Integer.parseInt(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_CONNECT_TIMEOUT, "3000")); NettySystemConfig.clientChannelMaxIdleTimeSeconds = Integer.parseInt(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_CHANNEL_MAX_IDLE_SECONDS, "120")); NettySystemConfig.clientCloseSocketIfTimeout = Boolean.parseBoolean(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_CLIENT_CLOSE_SOCKET_IF_TIMEOUT, "true")); NettyClientConfig changedConfig = new NettyClientConfig(); assertThat(changedConfig.getClientWorkerThreads()).isEqualTo(1); assertThat(changedConfig.getClientOnewaySemaphoreValue()).isEqualTo(65535); assertThat(changedConfig.getClientAsyncSemaphoreValue()).isEqualTo(65535); assertThat(changedConfig.getConnectTimeoutMillis()).isEqualTo(2000); assertThat(changedConfig.getClientChannelMaxIdleTimeSeconds()).isEqualTo(60); assertThat(changedConfig.getClientSocketSndBufSize()).isEqualTo(16383); assertThat(changedConfig.getClientSocketRcvBufSize()).isEqualTo(16384); assertThat(changedConfig.isClientCloseSocketIfTimeout()).isEqualTo(false); assertThat(changedConfig.isUseTLS()).isEqualTo(true); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import java.util.concurrent.Semaphore; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class NettyRemotingAbstractTest { @Spy private NettyRemotingAbstract remotingAbstract = new NettyRemotingClient(new NettyClientConfig()); @Test public void testProcessResponseCommand() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { assertThat(semaphore.availablePermits()).isEqualTo(0); } @Override public void operationFail(Throwable throwable) { } }, new SemaphoreReleaseOnlyOnce(semaphore)); remotingAbstract.responseTable.putIfAbsent(1, responseFuture); RemotingCommand response = RemotingCommand.createResponseCommand(0, "Foo"); response.setOpaque(1); remotingAbstract.processResponseCommand(null, response); // Acquire the release permit after call back semaphore.acquire(1); assertThat(semaphore.availablePermits()).isEqualTo(0); } @Test public void testProcessResponseCommand_NullCallBack() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, null, new SemaphoreReleaseOnlyOnce(semaphore)); remotingAbstract.responseTable.putIfAbsent(1, responseFuture); RemotingCommand response = RemotingCommand.createResponseCommand(0, "Foo"); response.setOpaque(1); remotingAbstract.processResponseCommand(null, response); assertThat(semaphore.availablePermits()).isEqualTo(1); } @Test public void testProcessResponseCommand_RunCallBackInCurrentThread() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { assertThat(semaphore.availablePermits()).isEqualTo(0); } @Override public void operationFail(Throwable throwable) { } }, new SemaphoreReleaseOnlyOnce(semaphore)); remotingAbstract.responseTable.putIfAbsent(1, responseFuture); when(remotingAbstract.getCallbackExecutor()).thenReturn(null); RemotingCommand response = RemotingCommand.createResponseCommand(0, "Foo"); response.setOpaque(1); remotingAbstract.processResponseCommand(null, response); // Acquire the release permit after call back finished in current thread semaphore.acquire(1); assertThat(semaphore.availablePermits()).isEqualTo(0); } @Test public void testScanResponseTable() { int dummyId = 1; // mock timeout ResponseFuture responseFuture = new ResponseFuture(null, dummyId, -1000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { } @Override public void operationFail(Throwable throwable) { } }, null); remotingAbstract.responseTable.putIfAbsent(dummyId, responseFuture); remotingAbstract.scanResponseTable(); assertNull(remotingAbstract.responseTable.get(dummyId)); } @Test public void testProcessRequestCommand() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); RemotingCommand request = RemotingCommand.createRequestCommand(1, null); ResponseFuture responseFuture = new ResponseFuture(null, 1, request, 3000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } @Override public void operationSucceed(RemotingCommand response) { assertThat(semaphore.availablePermits()).isEqualTo(0); } @Override public void operationFail(Throwable throwable) { } }, new SemaphoreReleaseOnlyOnce(semaphore)); remotingAbstract.responseTable.putIfAbsent(1, responseFuture); RemotingCommand response = RemotingCommand.createResponseCommand(0, "Foo"); response.setOpaque(1); remotingAbstract.processResponseCommand(null, response); // Acquire the release permit after call back semaphore.acquire(1); assertThat(semaphore.availablePermits()).isEqualTo(0); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.local.LocalChannel; import java.lang.reflect.Field; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class NettyRemotingClientTest { @Spy private NettyRemotingClient remotingClient = new NettyRemotingClient(new NettyClientConfig()); @Mock private RPCHook rpcHookMock; @Test public void testSetCallbackExecutor() { ExecutorService customized = Executors.newCachedThreadPool(); remotingClient.setCallbackExecutor(customized); assertThat(remotingClient.getCallbackExecutor()).isEqualTo(customized); } @Test public void testInvokeResponse() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); responseFuture.setResponseCommand(response); CompletableFuture future0 = new CompletableFuture<>(); future0.complete(responseFuture.getResponseCommand()); doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture future = remotingClient.invoke("0.0.0.0", request, 1000); RemotingCommand actual = future.get(); assertThat(actual).isEqualTo(response); } @Test public void testRemotingSendRequestException() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); CompletableFuture future0 = new CompletableFuture<>(); future0.completeExceptionally(new RemotingSendRequestException(null)); doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture future = remotingClient.invoke("0.0.0.0", request, 1000); Throwable thrown = catchThrowable(future::get); assertThat(thrown.getCause()).isInstanceOf(RemotingSendRequestException.class); } @Test public void testRemotingTimeoutException() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); CompletableFuture future0 = new CompletableFuture<>(); future0.completeExceptionally(new RemotingTimeoutException("")); doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture future = remotingClient.invoke("0.0.0.0", request, 1000); Throwable thrown = catchThrowable(future::get); assertThat(thrown.getCause()).isInstanceOf(RemotingTimeoutException.class); } @Test public void testRemotingException() throws Exception { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); CompletableFuture future0 = new CompletableFuture<>(); future0.completeExceptionally(new RemotingException("")); doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); CompletableFuture future = remotingClient.invoke("0.0.0.0", request, 1000); Throwable thrown = catchThrowable(future::get); assertThat(thrown.getCause()).isInstanceOf(RemotingException.class); } @Test public void testInvokeOnewayException() throws Exception { String addr = "0.0.0.0"; try { remotingClient.invokeOneway(addr, null, 1000); } catch (RemotingConnectException e) { assertThat(e.getMessage()).contains(addr); } } @Test public void testInvoke0() throws ExecutionException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); Channel channel = new MockChannel() { @Override public ChannelFuture writeAndFlush(Object msg) { ResponseFuture responseFuture = remotingClient.responseTable.get(request.getOpaque()); responseFuture.setResponseCommand(response); responseFuture.executeInvokeCallback(); return super.writeAndFlush(msg); } }; CompletableFuture future = remotingClient.invoke0(channel, request, 1000L); assertThat(future.get().getResponseCommand()).isEqualTo(response); } @Test public void testInvoke0WithException() { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); Channel channel = new MockChannel() { @Override public ChannelFuture writeAndFlush(Object msg) { ResponseFuture responseFuture = remotingClient.responseTable.get(request.getOpaque()); responseFuture.executeInvokeCallback(); return super.writeAndFlush(msg); } }; CompletableFuture future = remotingClient.invoke0(channel, request, 1000L); assertThatThrownBy(future::get).getCause().isInstanceOf(RemotingException.class); } @Test public void testInvokeSync() throws RemotingSendRequestException, RemotingTimeoutException, InterruptedException { remotingClient.registerRPCHook(rpcHookMock); Channel channel = new LocalChannel(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, 1000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } }, new SemaphoreReleaseOnlyOnce(new Semaphore(1))); responseFuture.setResponseCommand(response); CompletableFuture future = new CompletableFuture<>(); future.complete(responseFuture); doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong()); RemotingCommand actual = remotingClient.invokeSyncImpl(channel, request, 1000); assertThat(actual).isEqualTo(response); verify(rpcHookMock).doBeforeRequest(anyString(), eq(request)); verify(rpcHookMock).doAfterResponse(anyString(), eq(request), eq(response)); } @Test public void testInvokeAsync() { remotingClient.registerRPCHook(rpcHookMock); Channel channel = new LocalChannel(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, 1000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } }, new SemaphoreReleaseOnlyOnce(new Semaphore(1))); responseFuture.setResponseCommand(response); CompletableFuture future = new CompletableFuture<>(); future.complete(responseFuture); doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong()); InvokeCallback callback = mock(InvokeCallback.class); remotingClient.invokeAsyncImpl(channel, request, 1000, callback); verify(callback, times(1)).operationSucceed(eq(response)); verify(callback, times(1)).operationComplete(eq(responseFuture)); verify(callback, never()).operationFail(any()); verify(rpcHookMock).doBeforeRequest(anyString(), eq(request)); verify(rpcHookMock).doAfterResponse(anyString(), eq(request), eq(response)); } @Test public void testInvokeAsyncFail() { remotingClient.registerRPCHook(rpcHookMock); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); Channel channel = new LocalChannel(); CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new RemotingException(null)); doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong()); InvokeCallback callback = mock(InvokeCallback.class); remotingClient.invokeAsyncImpl(channel, request, 1000, callback); verify(callback, never()).operationSucceed(any()); verify(callback, times(1)).operationComplete(any()); verify(callback, times(1)).operationFail(any()); verify(rpcHookMock).doBeforeRequest(anyString(), eq(request)); verify(rpcHookMock, never()).doAfterResponse(anyString(), eq(request), any()); } @Test public void testInvokeImpl() throws ExecutionException, InterruptedException { remotingClient.registerRPCHook(rpcHookMock); Channel channel = new LocalChannel(); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, 1000, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { } }, new SemaphoreReleaseOnlyOnce(new Semaphore(1))); responseFuture.setResponseCommand(response); CompletableFuture future = new CompletableFuture<>(); future.complete(responseFuture); doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong()); CompletableFuture future0 = remotingClient.invokeImpl(channel, request, 1000); assertThat(future0.get()).isEqualTo(responseFuture); verify(rpcHookMock).doBeforeRequest(anyString(), eq(request)); verify(rpcHookMock).doAfterResponse(anyString(), eq(request), eq(response)); } @Test public void testInvokeImplFail() { remotingClient.registerRPCHook(rpcHookMock); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); Channel channel = new LocalChannel(); CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new RemotingException(null)); doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong()); assertThatThrownBy(() -> remotingClient.invokeImpl(channel, request, 1000).get()).getCause().isInstanceOf(RemotingException.class); verify(rpcHookMock).doBeforeRequest(anyString(), eq(request)); verify(rpcHookMock, never()).doAfterResponse(anyString(), eq(request), any()); } @Test public void testIsAddressReachableFail() throws NoSuchFieldException, IllegalAccessException { Bootstrap bootstrap = spy(Bootstrap.class); Field field = NettyRemotingClient.class.getDeclaredField("bootstrap"); field.setAccessible(true); field.set(remotingClient, bootstrap); assertThat(remotingClient.isAddressReachable("0.0.0.0:8080")).isFalse(); verify(bootstrap).connect(eq("0.0.0.0"), eq(8080)); assertThat(remotingClient.isAddressReachable("[fe80::]:8080")).isFalse(); verify(bootstrap).connect(eq("[fe80::]"), eq(8080)); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingServerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.codec.haproxy.HAProxyTLV; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import java.nio.charset.StandardCharsets; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class NettyRemotingServerTest { private NettyRemotingServer nettyRemotingServer; @Mock private Channel channel; @Mock private Attribute attribute; @Before public void setUp() throws Exception { NettyServerConfig nettyServerConfig = new NettyServerConfig(); nettyRemotingServer = new NettyRemotingServer(nettyServerConfig); } @Test public void handleHAProxyTLV() { when(channel.attr(any(AttributeKey.class))).thenReturn(attribute); doNothing().when(attribute).set(any()); ByteBuf content = Unpooled.buffer(); content.writeBytes("xxxx".getBytes(StandardCharsets.UTF_8)); HAProxyTLV haProxyTLV = new HAProxyTLV((byte) 0xE1, content); nettyRemotingServer.handleHAProxyTLV(haProxyTLV, channel); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyServerConfigTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(MockitoJUnitRunner.class) public class NettyServerConfigTest { @Test public void testChangeConfigBySystemProperty() { System.setProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_BACKLOG, "65535"); NettySystemConfig.socketBacklog = Integer.parseInt(System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_BACKLOG, "1024")); NettyServerConfig changedConfig = new NettyServerConfig(); assertThat(changedConfig.getServerSocketBacklog()).isEqualTo(65535); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.netty; import java.lang.reflect.Method; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.junit.Assert; import org.junit.Test; import static org.awaitility.Awaitility.await; public class RemotingCodeDistributionHandlerTest { private final RemotingCodeDistributionHandler distributionHandler = new RemotingCodeDistributionHandler(); @Test public void remotingCodeCountTest() throws Exception { Class clazz = RemotingCodeDistributionHandler.class; Method methodIn = clazz.getDeclaredMethod("countInbound", int.class); Method methodOut = clazz.getDeclaredMethod("countOutbound", int.class); methodIn.setAccessible(true); methodOut.setAccessible(true); int threadCount = 4; int count = 1000 * 1000; CountDownLatch latch = new CountDownLatch(threadCount); AtomicBoolean result = new AtomicBoolean(true); ExecutorService executorService = Executors.newFixedThreadPool(threadCount, new ThreadFactoryImpl("RemotingCodeTest_")); for (int i = 0; i < threadCount; i++) { executorService.submit(() -> { try { for (int j = 0; j < count; j++) { methodIn.invoke(distributionHandler, 1); methodOut.invoke(distributionHandler, 2); } } catch (Exception e) { result.set(false); } finally { latch.countDown(); } }); } latch.await(); Assert.assertTrue(result.get()); await().pollInterval(Duration.ofMillis(100)).atMost(Duration.ofSeconds(10)).until(() -> { boolean f1 = ("{1:" + count * threadCount + "}").equals(distributionHandler.getInBoundSnapshotString()); boolean f2 = ("{2:" + count * threadCount + "}").equals(distributionHandler.getOutBoundSnapshotString()); return f1 && f2; }); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/CheckpointFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.utils.CheckpointFile; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class CheckpointFileTest { private static final String FILE_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "store-test", "epoch.ckpt").toString(); private List entryList; private CheckpointFile checkpoint; static class EpochEntrySerializer implements CheckpointFile.CheckpointSerializer { @Override public String toLine(EpochEntry entry) { if (entry != null) { return String.format("%d-%d", entry.getEpoch(), entry.getStartOffset()); } else { return null; } } @Override public EpochEntry fromLine(String line) { final String[] arr = line.split("-"); if (arr.length == 2) { final int epoch = Integer.parseInt(arr[0]); final long startOffset = Long.parseLong(arr[1]); return new EpochEntry(epoch, startOffset); } return null; } } @Before public void init() throws IOException { this.entryList = new ArrayList<>(); entryList.add(new EpochEntry(7, 7000)); entryList.add(new EpochEntry(8, 8000)); this.checkpoint = new CheckpointFile<>(FILE_PATH, new EpochEntrySerializer()); this.checkpoint.write(entryList); } @After public void destroy() { UtilAll.deleteFile(new File(FILE_PATH)); UtilAll.deleteFile(new File(FILE_PATH + ".bak")); } @Test public void testNormalWriteAndRead() throws IOException { List listFromFile = checkpoint.read(); Assert.assertEquals(entryList, listFromFile); checkpoint.write(entryList); listFromFile = checkpoint.read(); Assert.assertEquals(entryList, listFromFile); } @Test public void testAbNormalWriteAndRead() throws IOException { this.checkpoint.write(entryList); UtilAll.deleteFile(new File(FILE_PATH)); List listFromFile = checkpoint.read(); Assert.assertEquals(entryList, listFromFile); checkpoint.write(entryList); listFromFile = checkpoint.read(); Assert.assertEquals(entryList, listFromFile); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ClusterInfoTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class ClusterInfoTest { @Test public void testFormJson() throws Exception { ClusterInfo clusterInfo = buildClusterInfo(); byte[] data = clusterInfo.encode(); ClusterInfo json = RemotingSerializable.decode(data, ClusterInfo.class); assertNotNull(json); assertNotNull(json.getClusterAddrTable()); assertTrue(json.getClusterAddrTable().containsKey("DEFAULT_CLUSTER")); assertTrue(json.getClusterAddrTable().get("DEFAULT_CLUSTER").contains("master")); assertNotNull(json.getBrokerAddrTable()); assertTrue(json.getBrokerAddrTable().containsKey("master")); assertEquals(json.getBrokerAddrTable().get("master").getBrokerName(), "master"); assertEquals(json.getBrokerAddrTable().get("master").getCluster(), "DEFAULT_CLUSTER"); assertEquals(json.getBrokerAddrTable().get("master").getBrokerAddrs().get(MixAll.MASTER_ID), MixAll.getLocalhostByNetworkInterface()); } @Test public void testRetrieveAllClusterNames() throws Exception { ClusterInfo clusterInfo = buildClusterInfo(); byte[] data = clusterInfo.encode(); ClusterInfo json = RemotingSerializable.decode(data, ClusterInfo.class); assertArrayEquals(new String[]{"DEFAULT_CLUSTER"}, json.retrieveAllClusterNames()); } @Test public void testRetrieveAllAddrByCluster() throws Exception { ClusterInfo clusterInfo = buildClusterInfo(); byte[] data = clusterInfo.encode(); ClusterInfo json = RemotingSerializable.decode(data, ClusterInfo.class); assertArrayEquals(new String[]{MixAll.getLocalhostByNetworkInterface()}, json.retrieveAllAddrByCluster("DEFAULT_CLUSTER")); } private ClusterInfo buildClusterInfo() throws Exception { ClusterInfo clusterInfo = new ClusterInfo(); HashMap brokerAddrTable = new HashMap<>(); HashMap> clusterAddrTable = new HashMap<>(); //build brokerData BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("master"); brokerData.setCluster("DEFAULT_CLUSTER"); //build brokerAddrs HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, MixAll.getLocalhostByNetworkInterface()); brokerData.setBrokerAddrs(brokerAddrs); brokerAddrTable.put("master", brokerData); Set brokerNames = new HashSet<>(); brokerNames.add("master"); clusterAddrTable.put("DEFAULT_CLUSTER", brokerNames); clusterInfo.setBrokerAddrTable(brokerAddrTable); clusterInfo.setClusterAddrTable(clusterAddrTable); return clusterInfo; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ConsumeStatusTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import org.apache.rocketmq.remoting.protocol.body.ConsumeStatus; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; public class ConsumeStatusTest { @Test public void testFromJson() throws Exception { ConsumeStatus cs = new ConsumeStatus(); cs.setConsumeFailedTPS(10); cs.setPullRT(100); cs.setPullTPS(1000); String json = RemotingSerializable.toJson(cs, true); ConsumeStatus fromJson = RemotingSerializable.fromJson(json, ConsumeStatus.class); assertThat(fromJson.getPullRT()).isCloseTo(cs.getPullRT(), within(0.0001)); assertThat(fromJson.getPullTPS()).isCloseTo(cs.getPullTPS(), within(0.0001)); assertThat(fromJson.getConsumeFailedTPS()).isCloseTo(cs.getConsumeFailedTPS(), within(0.0001)); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/DataVersionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson2.JSON; import org.junit.Test; import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class DataVersionTest { @Test public void testEquals() { DataVersion dataVersion = new DataVersion(); DataVersion other = new DataVersion(); other.setTimestamp(dataVersion.getTimestamp()); assertEquals(dataVersion, other); } @Test public void testEquals_falseWhenCounterDifferent() { DataVersion dataVersion = new DataVersion(); DataVersion other = new DataVersion(); other.setCounter(new AtomicLong(1L)); other.setTimestamp(dataVersion.getTimestamp()); assertNotEquals(dataVersion, other); } @Test public void testEquals_falseWhenCounterDifferent2() { DataVersion dataVersion = new DataVersion(); DataVersion other = new DataVersion(); other.setCounter(null); other.setTimestamp(dataVersion.getTimestamp()); assertNotEquals(dataVersion, other); } @Test public void testEquals_falseWhenCounterDifferent3() { DataVersion dataVersion = new DataVersion(); dataVersion.setCounter(null); DataVersion other = new DataVersion(); other.setTimestamp(dataVersion.getTimestamp()); assertNotEquals(dataVersion, other); } @Test public void testEquals_trueWhenCountersBothNull() { DataVersion dataVersion = new DataVersion(); dataVersion.setCounter(null); DataVersion other = new DataVersion(); other.setCounter(null); other.setTimestamp(dataVersion.getTimestamp()); assertEquals(dataVersion, other); } @Test public void testEncode() { DataVersion dataVersion = new DataVersion(); assertTrue(dataVersion.encode().length > 0); assertNotNull(dataVersion.toJson()); } @Test public void testJsonSerializationAndDeserialization() { DataVersion expected = new DataVersion(); expected.setCounter(new AtomicLong(Long.MAX_VALUE)); expected.setTimestamp(expected.getTimestamp()); String jsonStr = expected.toJson(); assertNotNull(jsonStr); DataVersion actual = JSON.parseObject(jsonStr, DataVersion.class); assertNotNull(actual); assertEquals(expected.getTimestamp(), actual.getTimestamp()); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/GroupListTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.HashSet; import java.util.UUID; import org.apache.rocketmq.remoting.protocol.body.GroupList; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Created by guoyao on 2019/2/18. */ public class GroupListTest { @Test public void testSetGet() throws Exception { HashSet fisrtUniqueSet = createUniqueNewSet(); HashSet secondUniqueSet = createUniqueNewSet(); assertThat(fisrtUniqueSet).isNotEqualTo(secondUniqueSet); GroupList gl = new GroupList(); gl.setGroupList(fisrtUniqueSet); assertThat(gl.getGroupList()).isEqualTo(fisrtUniqueSet); assertThat(gl.getGroupList()).isNotEqualTo(secondUniqueSet); gl.setGroupList(secondUniqueSet); assertThat(gl.getGroupList()).isNotEqualTo(fisrtUniqueSet); assertThat(gl.getGroupList()).isEqualTo(secondUniqueSet); } private HashSet createUniqueNewSet() { HashSet groups = new HashSet<>(); groups.add(UUID.randomUUID().toString()); return groups; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/LanguageCodeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class LanguageCodeTest { @Test public void testLanguageCodeRust() { LanguageCode code = LanguageCode.valueOf((byte) 12); assertThat(code).isEqualTo(LanguageCode.RUST); code = LanguageCode.valueOf("RUST"); assertThat(code).isEqualTo(LanguageCode.RUST); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/NamespaceUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import org.apache.rocketmq.common.MixAll; import org.junit.Assert; import org.junit.Test; /** * MQDevelopers */ public class NamespaceUtilTest { private static final String INSTANCE_ID = "MQ_INST_XXX"; private static final String INSTANCE_ID_WRONG = "MQ_INST_XXX1"; private static final String TOPIC = "TOPIC_XXX"; private static final String GROUP_ID = "GID_XXX"; private static final String SYSTEM_TOPIC = "rmq_sys_topic"; private static final String GROUP_ID_WITH_NAMESPACE = INSTANCE_ID + NamespaceUtil.NAMESPACE_SEPARATOR + GROUP_ID; private static final String TOPIC_WITH_NAMESPACE = INSTANCE_ID + NamespaceUtil.NAMESPACE_SEPARATOR + TOPIC; private static final String RETRY_TOPIC = MixAll.RETRY_GROUP_TOPIC_PREFIX + GROUP_ID; private static final String RETRY_TOPIC_WITH_NAMESPACE = MixAll.RETRY_GROUP_TOPIC_PREFIX + INSTANCE_ID + NamespaceUtil.NAMESPACE_SEPARATOR + GROUP_ID; private static final String DLQ_TOPIC = MixAll.DLQ_GROUP_TOPIC_PREFIX + GROUP_ID; private static final String DLQ_TOPIC_WITH_NAMESPACE = MixAll.DLQ_GROUP_TOPIC_PREFIX + INSTANCE_ID + NamespaceUtil.NAMESPACE_SEPARATOR + GROUP_ID; @Test public void testWithoutNamespace() { String topic = NamespaceUtil.withoutNamespace(TOPIC_WITH_NAMESPACE, INSTANCE_ID); Assert.assertEquals(topic, TOPIC); String topic1 = NamespaceUtil.withoutNamespace(TOPIC_WITH_NAMESPACE); Assert.assertEquals(topic1, TOPIC); String groupId = NamespaceUtil.withoutNamespace(GROUP_ID_WITH_NAMESPACE, INSTANCE_ID); Assert.assertEquals(groupId, GROUP_ID); String groupId1 = NamespaceUtil.withoutNamespace(GROUP_ID_WITH_NAMESPACE); Assert.assertEquals(groupId1, GROUP_ID); String consumerId = NamespaceUtil.withoutNamespace(RETRY_TOPIC_WITH_NAMESPACE, INSTANCE_ID); Assert.assertEquals(consumerId, RETRY_TOPIC); String consumerId1 = NamespaceUtil.withoutNamespace(RETRY_TOPIC_WITH_NAMESPACE); Assert.assertEquals(consumerId1, RETRY_TOPIC); String consumerId2 = NamespaceUtil.withoutNamespace(RETRY_TOPIC_WITH_NAMESPACE, INSTANCE_ID_WRONG); Assert.assertEquals(consumerId2, RETRY_TOPIC_WITH_NAMESPACE); Assert.assertNotEquals(consumerId2, RETRY_TOPIC); } @Test public void testWrapNamespace() { String topic1 = NamespaceUtil.wrapNamespace(INSTANCE_ID, TOPIC); Assert.assertEquals(topic1, TOPIC_WITH_NAMESPACE); String topicWithNamespaceAgain = NamespaceUtil.wrapNamespace(INSTANCE_ID, topic1); Assert.assertEquals(topicWithNamespaceAgain, TOPIC_WITH_NAMESPACE); //Wrap retry topic String retryTopicWithNamespace = NamespaceUtil.wrapNamespace(INSTANCE_ID, RETRY_TOPIC); Assert.assertEquals(retryTopicWithNamespace, RETRY_TOPIC_WITH_NAMESPACE); String retryTopicWithNamespaceAgain = NamespaceUtil.wrapNamespace(INSTANCE_ID, retryTopicWithNamespace); Assert.assertEquals(retryTopicWithNamespaceAgain, retryTopicWithNamespace); //Wrap DLQ topic String dlqTopicWithNamespace = NamespaceUtil.wrapNamespace(INSTANCE_ID, DLQ_TOPIC); Assert.assertEquals(dlqTopicWithNamespace, DLQ_TOPIC_WITH_NAMESPACE); String dlqTopicWithNamespaceAgain = NamespaceUtil.wrapNamespace(INSTANCE_ID, dlqTopicWithNamespace); Assert.assertEquals(dlqTopicWithNamespaceAgain, dlqTopicWithNamespace); Assert.assertEquals(dlqTopicWithNamespaceAgain, DLQ_TOPIC_WITH_NAMESPACE); //test system topic String systemTopic = NamespaceUtil.wrapNamespace(INSTANCE_ID, SYSTEM_TOPIC); Assert.assertEquals(systemTopic, SYSTEM_TOPIC); } @Test public void testGetNamespaceFromResource() { String namespaceExpectBlank = NamespaceUtil.getNamespaceFromResource(TOPIC); Assert.assertEquals(namespaceExpectBlank, NamespaceUtil.STRING_BLANK); String namespace = NamespaceUtil.getNamespaceFromResource(TOPIC_WITH_NAMESPACE); Assert.assertEquals(namespace, INSTANCE_ID); String namespaceFromRetryTopic = NamespaceUtil.getNamespaceFromResource(RETRY_TOPIC_WITH_NAMESPACE); Assert.assertEquals(namespaceFromRetryTopic, INSTANCE_ID); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/QueryConsumeTimeSpanBodyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.body.QueryConsumeTimeSpanBody; import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class QueryConsumeTimeSpanBodyTest { @Test public void testSetGet() throws Exception { QueryConsumeTimeSpanBody queryConsumeTimeSpanBody = new QueryConsumeTimeSpanBody(); List firstQueueTimeSpans = newUniqueConsumeTimeSpanSet(); List secondQueueTimeSpans = newUniqueConsumeTimeSpanSet(); queryConsumeTimeSpanBody.setConsumeTimeSpanSet(firstQueueTimeSpans); assertThat(queryConsumeTimeSpanBody.getConsumeTimeSpanSet()).isEqualTo(firstQueueTimeSpans); assertThat(queryConsumeTimeSpanBody.getConsumeTimeSpanSet()).isNotEqualTo(secondQueueTimeSpans); queryConsumeTimeSpanBody.setConsumeTimeSpanSet(secondQueueTimeSpans); assertThat(queryConsumeTimeSpanBody.getConsumeTimeSpanSet()).isEqualTo(secondQueueTimeSpans); assertThat(queryConsumeTimeSpanBody.getConsumeTimeSpanSet()).isNotEqualTo(firstQueueTimeSpans); } @Test public void testFromJson() throws Exception { QueryConsumeTimeSpanBody qctsb = new QueryConsumeTimeSpanBody(); List queueTimeSpans = new ArrayList<>(); QueueTimeSpan queueTimeSpan = new QueueTimeSpan(); queueTimeSpan.setMinTimeStamp(1550825710000L); queueTimeSpan.setMaxTimeStamp(1550825790000L); queueTimeSpan.setConsumeTimeStamp(1550825760000L); queueTimeSpan.setDelayTime(5000L); MessageQueue messageQueue = new MessageQueue("topicName", "brokerName", 1); queueTimeSpan.setMessageQueue(messageQueue); queueTimeSpans.add(queueTimeSpan); qctsb.setConsumeTimeSpanSet(queueTimeSpans); String json = RemotingSerializable.toJson(qctsb, true); QueryConsumeTimeSpanBody fromJson = RemotingSerializable.fromJson(json, QueryConsumeTimeSpanBody.class); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMaxTimeStamp()).isEqualTo(1550825790000L); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMinTimeStamp()).isEqualTo(1550825710000L); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getDelayTime()).isEqualTo(5000L); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMessageQueue()).isEqualTo(messageQueue); } @Test public void testFromJsonRandom() throws Exception { QueryConsumeTimeSpanBody origin = new QueryConsumeTimeSpanBody(); List queueTimeSpans = newUniqueConsumeTimeSpanSet(); origin.setConsumeTimeSpanSet(queueTimeSpans); String json = origin.toJson(true); QueryConsumeTimeSpanBody fromJson = RemotingSerializable.fromJson(json, QueryConsumeTimeSpanBody.class); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMinTimeStamp()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMinTimeStamp()); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMaxTimeStamp()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMaxTimeStamp()); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getConsumeTimeStamp()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getConsumeTimeStamp()); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getDelayTime()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getDelayTime()); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMessageQueue().getBrokerName()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMessageQueue().getBrokerName()); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMessageQueue().getTopic()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMessageQueue().getTopic()); assertThat(fromJson.getConsumeTimeSpanSet().get(0).getMessageQueue().getQueueId()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMessageQueue().getQueueId()); } @Test public void testEncode() throws Exception { QueryConsumeTimeSpanBody origin = new QueryConsumeTimeSpanBody(); List queueTimeSpans = newUniqueConsumeTimeSpanSet(); origin.setConsumeTimeSpanSet(queueTimeSpans); byte[] data = origin.encode(); QueryConsumeTimeSpanBody fromData = RemotingSerializable.decode(data, QueryConsumeTimeSpanBody.class); assertThat(fromData.getConsumeTimeSpanSet().get(0).getMinTimeStamp()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMinTimeStamp()); assertThat(fromData.getConsumeTimeSpanSet().get(0).getMaxTimeStamp()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMaxTimeStamp()); assertThat(fromData.getConsumeTimeSpanSet().get(0).getConsumeTimeStamp()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getConsumeTimeStamp()); assertThat(fromData.getConsumeTimeSpanSet().get(0).getDelayTime()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getDelayTime()); assertThat(fromData.getConsumeTimeSpanSet().get(0).getMessageQueue().getBrokerName()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMessageQueue().getBrokerName()); assertThat(fromData.getConsumeTimeSpanSet().get(0).getMessageQueue().getTopic()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMessageQueue().getTopic()); assertThat(fromData.getConsumeTimeSpanSet().get(0).getMessageQueue().getQueueId()).isEqualTo(origin.getConsumeTimeSpanSet().get(0).getMessageQueue().getQueueId()); } private List newUniqueConsumeTimeSpanSet() { List queueTimeSpans = new ArrayList<>(); QueueTimeSpan queueTimeSpan = new QueueTimeSpan(); queueTimeSpan.setMinTimeStamp(System.currentTimeMillis()); queueTimeSpan.setMaxTimeStamp(UtilAll.computeNextHourTimeMillis()); queueTimeSpan.setConsumeTimeStamp(UtilAll.computeNextMinutesTimeMillis()); queueTimeSpan.setDelayTime(5000L); MessageQueue messageQueue = new MessageQueue(UUID.randomUUID().toString(), UUID.randomUUID().toString(), new Random().nextInt()); queueTimeSpan.setMessageQueue(messageQueue); queueTimeSpans.add(queueTimeSpan); return queueTimeSpans; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RegisterBrokerBodyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.body.RegisterBrokerBody; import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; import org.junit.Test; import static org.junit.Assert.assertEquals; public class RegisterBrokerBodyTest { @Test public void test_encode_decode() throws IOException { RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody(); TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); registerBrokerBody.setTopicConfigSerializeWrapper(topicConfigSerializeWrapper); ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); for (int i = 0; i < 10000; i++) { topicConfigTable.put(String.valueOf(i), new TopicConfig(String.valueOf(i))); } topicConfigSerializeWrapper.setTopicConfigTable(topicConfigTable); byte[] compareEncode = registerBrokerBody.encode(true); byte[] encode2 = registerBrokerBody.encode(false); RegisterBrokerBody decodeRegisterBrokerBody = RegisterBrokerBody.decode(compareEncode, true, MQVersion.Version.V5_0_0); assertEquals(registerBrokerBody.getTopicConfigSerializeWrapper().getTopicConfigTable().size(), decodeRegisterBrokerBody.getTopicConfigSerializeWrapper().getTopicConfigTable().size()); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingCommandTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.junit.Assert; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class RemotingCommandTest { @Test public void testMarkProtocolType_JSONProtocolType() { int source = 261; SerializeType type = SerializeType.JSON; byte[] result = new byte[4]; int x = RemotingCommand.markProtocolType(source, type); result[0] = (byte) (x >> 24); result[1] = (byte) (x >> 16); result[2] = (byte) (x >> 8); result[3] = (byte) x; assertThat(result).isEqualTo(new byte[] {0, 0, 1, 5}); } @Test public void testMarkProtocolType_ROCKETMQProtocolType() { int source = 16777215; SerializeType type = SerializeType.ROCKETMQ; byte[] result = new byte[4]; int x = RemotingCommand.markProtocolType(source, type); result[0] = (byte) (x >> 24); result[1] = (byte) (x >> 16); result[2] = (byte) (x >> 8); result[3] = (byte) x; assertThat(result).isEqualTo(new byte[] {1, -1, -1, -1}); } @Test public void testCreateRequestCommand_RegisterBroker() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = 103; //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER CommandCustomHeader header = new SampleCommandCustomHeader(); RemotingCommand cmd = RemotingCommand.createRequestCommand(code, header); assertThat(cmd.getCode()).isEqualTo(code); assertThat(cmd.getVersion()).isEqualTo(2333); assertThat(cmd.getFlag() & 0x01).isEqualTo(0); //flag bit 0: 0 presents request } @Test public void testCreateResponseCommand_SuccessWithHeader() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = RemotingSysResponseCode.SUCCESS; String remark = "Sample remark"; RemotingCommand cmd = RemotingCommand.createResponseCommand(code, remark, SampleCommandCustomHeader.class); assertThat(cmd.getCode()).isEqualTo(code); assertThat(cmd.getVersion()).isEqualTo(2333); assertThat(cmd.getRemark()).isEqualTo(remark); assertThat(cmd.getFlag() & 0x01).isEqualTo(1); //flag bit 0: 1 presents response } @Test public void testCreateResponseCommand_SuccessWithoutHeader() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = RemotingSysResponseCode.SUCCESS; String remark = "Sample remark"; RemotingCommand cmd = RemotingCommand.createResponseCommand(code, remark); assertThat(cmd.getCode()).isEqualTo(code); assertThat(cmd.getVersion()).isEqualTo(2333); assertThat(cmd.getRemark()).isEqualTo(remark); assertThat(cmd.getFlag() & 0x01).isEqualTo(1); //flag bit 0: 1 presents response } @Test public void testCreateResponseCommand_FailToCreateCommand() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = RemotingSysResponseCode.SUCCESS; String remark = "Sample remark"; RemotingCommand cmd = RemotingCommand.createResponseCommand(code, remark, CommandCustomHeader.class); assertThat(cmd).isNull(); } @Test public void testCreateResponseCommand_SystemError() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); RemotingCommand cmd = RemotingCommand.createResponseCommand(SampleCommandCustomHeader.class); assertThat(cmd.getCode()).isEqualTo(RemotingSysResponseCode.SYSTEM_ERROR); assertThat(cmd.getVersion()).isEqualTo(2333); assertThat(cmd.getRemark()).contains("not set any response code"); assertThat(cmd.getFlag() & 0x01).isEqualTo(1); //flag bit 0: 1 presents response } @Test public void testEncodeAndDecode_EmptyBody() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = 103; //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER CommandCustomHeader header = new SampleCommandCustomHeader(); RemotingCommand cmd = RemotingCommand.createRequestCommand(code, header); ByteBuffer buffer = cmd.encode(); //Simulate buffer being read in NettyDecoder buffer.getInt(); byte[] bytes = new byte[buffer.limit() - 4]; buffer.get(bytes, 0, buffer.limit() - 4); buffer = ByteBuffer.wrap(bytes); RemotingCommand decodedCommand = null; try { decodedCommand = RemotingCommand.decode(buffer); assertThat(decodedCommand.getSerializeTypeCurrentRPC()).isEqualTo(SerializeType.JSON); assertThat(decodedCommand.getBody()).isNull(); } catch (RemotingCommandException e) { e.printStackTrace(); Assert.fail("Should not throw IOException"); } } @Test public void testEncodeAndDecode_FilledBody() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = 103; //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER CommandCustomHeader header = new SampleCommandCustomHeader(); RemotingCommand cmd = RemotingCommand.createRequestCommand(code, header); cmd.setBody(new byte[] {0, 1, 2, 3, 4}); ByteBuffer buffer = cmd.encode(); //Simulate buffer being read in NettyDecoder buffer.getInt(); byte[] bytes = new byte[buffer.limit() - 4]; buffer.get(bytes, 0, buffer.limit() - 4); buffer = ByteBuffer.wrap(bytes); RemotingCommand decodedCommand = null; try { decodedCommand = RemotingCommand.decode(buffer); assertThat(decodedCommand.getSerializeTypeCurrentRPC()).isEqualTo(SerializeType.JSON); assertThat(decodedCommand.getBody()).isEqualTo(new byte[] {0, 1, 2, 3, 4}); } catch (RemotingCommandException e) { e.printStackTrace(); Assert.fail("Should not throw IOException"); } } @Test public void testEncodeAndDecode_FilledBodyWithExtFields() throws RemotingCommandException { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); int code = 103; //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER CommandCustomHeader header = new ExtFieldsHeader(); RemotingCommand cmd = RemotingCommand.createRequestCommand(code, header); cmd.addExtField("key", "value"); ByteBuffer buffer = cmd.encode(); //Simulate buffer being read in NettyDecoder buffer.getInt(); byte[] bytes = new byte[buffer.limit() - 4]; buffer.get(bytes, 0, buffer.limit() - 4); buffer = ByteBuffer.wrap(bytes); RemotingCommand decodedCommand = null; try { decodedCommand = RemotingCommand.decode(buffer); assertThat(decodedCommand.getExtFields().get("stringValue")).isEqualTo("bilibili"); assertThat(decodedCommand.getExtFields().get("intValue")).isEqualTo("2333"); assertThat(decodedCommand.getExtFields().get("longValue")).isEqualTo("23333333"); assertThat(decodedCommand.getExtFields().get("booleanValue")).isEqualTo("true"); assertThat(decodedCommand.getExtFields().get("doubleValue")).isEqualTo("0.618"); assertThat(decodedCommand.getExtFields().get("key")).isEqualTo("value"); CommandCustomHeader decodedHeader = decodedCommand.decodeCommandCustomHeader(ExtFieldsHeader.class); assertThat(((ExtFieldsHeader) decodedHeader).getStringValue()).isEqualTo("bilibili"); assertThat(((ExtFieldsHeader) decodedHeader).getIntValue()).isEqualTo(2333); assertThat(((ExtFieldsHeader) decodedHeader).getLongValue()).isEqualTo(23333333L); assertThat(((ExtFieldsHeader) decodedHeader).isBooleanValue()).isEqualTo(true); assertThat(((ExtFieldsHeader) decodedHeader).getDoubleValue()).isBetween(0.617, 0.619); } catch (RemotingCommandException e) { e.printStackTrace(); Assert.fail("Should not throw IOException"); } } @Test public void testNotNullField() throws Exception { RemotingCommand remotingCommand = new RemotingCommand(); Method method = RemotingCommand.class.getDeclaredMethod("isFieldNullable", Field.class); method.setAccessible(true); Field nullString = FieldTestClass.class.getDeclaredField("nullString"); assertThat(method.invoke(remotingCommand, nullString)).isEqualTo(false); Field nullableString = FieldTestClass.class.getDeclaredField("nullable"); assertThat(method.invoke(remotingCommand, nullableString)).isEqualTo(true); Field value = FieldTestClass.class.getDeclaredField("value"); assertThat(method.invoke(remotingCommand, value)).isEqualTo(false); } @Test public void testParentField() throws Exception { SubExtFieldsHeader subExtFieldsHeader = new SubExtFieldsHeader(); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(1, subExtFieldsHeader); Field[] fields = remotingCommand.getClazzFields(subExtFieldsHeader.getClass()); Set fieldNames = new HashSet<>(); for (Field field: fields) { fieldNames.add(field.getName()); } Assert.assertTrue(fields.length >= 7); Set names = new HashSet<>(); names.add("stringValue"); names.add("intValue"); names.add("longValue"); names.add("booleanValue"); names.add("doubleValue"); names.add("name"); names.add("value"); for (String name: names) { Assert.assertTrue(fieldNames.contains(name)); } remotingCommand.makeCustomHeaderToNet(); SubExtFieldsHeader other = (SubExtFieldsHeader) remotingCommand.decodeCommandCustomHeader(subExtFieldsHeader.getClass()); Assert.assertEquals(other, subExtFieldsHeader); } } class FieldTestClass { @CFNotNull String nullString = null; String nullable = null; @CFNotNull String value = "NotNull"; } class SampleCommandCustomHeader implements CommandCustomHeader { @Override public void checkFields() throws RemotingCommandException { } } class ExtFieldsHeader implements CommandCustomHeader { private String stringValue = "bilibili"; private int intValue = 2333; private long longValue = 23333333L; private boolean booleanValue = true; private double doubleValue = 0.618; @Override public void checkFields() throws RemotingCommandException { } public String getStringValue() { return stringValue; } public int getIntValue() { return intValue; } public long getLongValue() { return longValue; } public boolean isBooleanValue() { return booleanValue; } public double getDoubleValue() { return doubleValue; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ExtFieldsHeader)) return false; ExtFieldsHeader that = (ExtFieldsHeader) o; if (intValue != that.intValue) return false; if (longValue != that.longValue) return false; if (booleanValue != that.booleanValue) return false; if (Double.compare(that.doubleValue, doubleValue) != 0) return false; return stringValue != null ? stringValue.equals(that.stringValue) : that.stringValue == null; } @Override public int hashCode() { int result; long temp; result = stringValue != null ? stringValue.hashCode() : 0; result = 31 * result + intValue; result = 31 * result + (int) (longValue ^ (longValue >>> 32)); result = 31 * result + (booleanValue ? 1 : 0); temp = Double.doubleToLongBits(doubleValue); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } class SubExtFieldsHeader extends ExtFieldsHeader { private String name = "12321"; private int value = 111; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SubExtFieldsHeader)) return false; if (!super.equals(o)) return false; SubExtFieldsHeader that = (SubExtFieldsHeader) o; if (value != that.value) return false; return name != null ? name.equals(that.name) : that.name == null; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + value; return result; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingSerializableCompatTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.remoting.protocol.body.BatchAck; import org.junit.Test; import org.objenesis.ObjenesisStd; import org.reflections.Reflections; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class RemotingSerializableCompatTest { @Test public void testCompatibilityCheck() { Reflections reflections = new Reflections("org.apache.rocketmq.remoting.protocol"); Set> subTypes = reflections.getSubTypesOf(RemotingSerializable.class); for (Class clazz : subTypes) { if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) || clazz.getSimpleName().endsWith("Test") || clazz.isAnonymousClass() || clazz.getName().contains("$")) { continue; } try { RemotingSerializable instance; try { instance = clazz.getDeclaredConstructor().newInstance(); } catch (NoSuchMethodException e) { instance = allocateInstance(clazz); } fillDefaultFields(instance, clazz); assertTrue(checkCompatible(instance, clazz)); } catch (Exception e) { System.err.printf("Class %s: incompatible, error: %s\n", clazz.getName(), e.getMessage()); } } } @Test public void testCompatibilityCheckWithBitSet() { BitSet bitSet = new BitSet(); bitSet.set(1); bitSet.set(3); bitSet.set(5); String fastjson1Str = "{\"b\":\"Kg==\",\"c\":\"DEFAULT_CONSUMER\",\"it\":5000,\"pt\":1760694281326,\"q\":1,\"r\":\"0\",\"rq\":2,\"so\":100,\"t\":\"myTopic\"}"; BatchAck batchAck = JSON.parseObject(fastjson1Str, BatchAck.class); assertEquals(bitSet, batchAck.getBitSet()); assertEquals("DEFAULT_CONSUMER", batchAck.getConsumerGroup()); assertEquals(5000, batchAck.getInvisibleTime()); assertEquals(1760694281326L, batchAck.getPopTime()); assertEquals(1, batchAck.getQueueId()); assertEquals("0", batchAck.getRetry()); assertEquals(2, batchAck.getReviveQueueId()); assertEquals(100, batchAck.getStartOffset()); assertEquals("myTopic", batchAck.getTopic()); } private void fillDefaultFields(final Object obj, final Class clazz) throws Exception { if (null == clazz || clazz == Object.class) { return; } for (Field field : clazz.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; } field.setAccessible(true); Class type = field.getType(); if (type.isArray()) { Class componentType = type.getComponentType(); Object arr = Array.newInstance(componentType, 1); Object element = createElementOrDefault(componentType); if (element != null) { Array.set(arr, 0, element); } field.set(obj, arr); } else if (Properties.class.isAssignableFrom(type)) { field.set(obj, new Properties()); } else if (type.isEnum()) { Object[] enumConstants = type.getEnumConstants(); if (enumConstants != null && enumConstants.length > 0) { field.set(obj, enumConstants[0]); } } else if (ConcurrentHashMap.KeySetView.class.isAssignableFrom(type)) { field.set(obj, ConcurrentHashMap.newKeySet()); } else if (ConcurrentHashMap.class.isAssignableFrom(type) || ConcurrentMap.class.isAssignableFrom(type)) { field.set(obj, new ConcurrentHashMap<>()); } else if (Set.class.isAssignableFrom(type)) { Set set = type.isInterface() ? new HashSet<>() : (Set) type.getDeclaredConstructor().newInstance(); Class genericType = getFirstGenericType(field); Object element = createElementOrDefault(genericType); if (element != null) set.add(element); field.set(obj, set); } else if (List.class.isAssignableFrom(type)) { List list = new ArrayList<>(); Class genericType = getFirstGenericType(field); Object element = createElementOrDefault(genericType); if (null != element) { list.add(element); } field.set(obj, list); } else if (Map.class.isAssignableFrom(type)) { Map map = type.isInterface() ? new HashMap<>() : (Map) type.getDeclaredConstructor().newInstance(); Class keyType = getGenericType(field, 0); Class valueType = getGenericType(field, 1); Object key = createElementOrDefault(keyType); Object value = createElementOrDefault(valueType); if (null != key && null != value) { map.put(key, value); } field.set(obj, map); } else if (type == AtomicLong.class) { field.set(obj, new AtomicLong(1)); } else { Object value = getDefaultValue(type); if (null != value) { field.set(obj, value); } else if (!type.isPrimitive() && !type.getName().startsWith("java.")) { Object subObj; try { subObj = type.getDeclaredConstructor().newInstance(); } catch (NoSuchMethodException e) { subObj = allocateInstance(type); } fillDefaultFields(subObj, type); field.set(obj, subObj); } } } fillDefaultFields(obj, clazz.getSuperclass()); } private Object createElementOrDefault(final Class type) throws Exception { if (null == type) { return null; } Object value = getDefaultValue(type); if (null != value) { return value; } if (type.isEnum()) { Object[] enumConstants = type.getEnumConstants(); if (null != enumConstants && enumConstants.length > 0) { return enumConstants[0]; } return null; } if (type.isArray()) { Class componentType = type.getComponentType(); Object arr = Array.newInstance(componentType, 1); Object element = createElementOrDefault(componentType); if (null != element) { Array.set(arr, 0, element); } return arr; } if (!type.isPrimitive()) { Object obj; try { obj = type.getDeclaredConstructor().newInstance(); } catch (NoSuchMethodException e) { obj = allocateInstance(type); } fillDefaultFields(obj, type); return obj; } return null; } private Class getFirstGenericType(final Field field) { return getGenericType(field, 0); } private Class getGenericType(final Field field, final int index) { try { java.lang.reflect.Type genericType = field.getGenericType(); if (genericType instanceof java.lang.reflect.ParameterizedType) { java.lang.reflect.Type[] types = ((java.lang.reflect.ParameterizedType) genericType).getActualTypeArguments(); if (types.length > index && types[index] instanceof Class) { return (Class) types[index]; } } } catch (Exception ignored) { } return null; } private Object getDefaultValue(final Class type) { if (null == type) { return null; } if (type == boolean.class || type == Boolean.class) { return false; } if (type == byte.class || type == Byte.class) { return (byte) 1; } if (type == short.class || type == Short.class) { return (short) 1; } if (type == int.class || type == Integer.class) { return 1; } if (type == long.class || type == Long.class) { return 1L; } if (type == float.class || type == Float.class) { return 1f; } if (type == double.class || type == Double.class) { return 1d; } if (type == char.class || type == Character.class) { return '\0'; } if (type == String.class) { return "test"; } return null; } private boolean checkCompatible(final Object original, final Object deserialized, final String path, final Map visited) { if (null == original && null == deserialized) { return true; } if (null == original || null == deserialized) { System.err.printf("Objects at %s incompatible: one is null\n", path); return false; } if (!isPrimitiveOrWrapper(original.getClass())) { if (visited.containsKey(original)) { return true; } visited.put(original, deserialized); } Class clazz = original.getClass(); boolean result = true; for (Field field : clazz.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; } JSONField jsonField = field.getAnnotation(JSONField.class); if (null != jsonField && !jsonField.serialize()) { continue; } if ("hash".equals(field.getName()) || "serialVersionUID".equals(field.getName())) { continue; } field.setAccessible(true); try { Object v1 = field.get(original); Object v2 = field.get(deserialized); String fieldPath = path + "." + field.getName(); if (null == v1 && null == v2) { continue; } if (v1 instanceof Random && v2 instanceof Random) { continue; } if (v1 instanceof AtomicLong && v2 instanceof AtomicLong) { if (((AtomicLong) v1).get() != ((AtomicLong) v2).get()) { result = false; System.err.printf("Field %s incompatible: original=%s, deserialized=%s\n", fieldPath, v1, v2); } continue; } if (v1 instanceof Set && v2 instanceof Set) { Set s1 = (Set) v1, s2 = (Set) v2; if (s1.size() != s2.size()) { result = false; System.err.printf("Field %s incompatible: set size original=%d, deserialized=%d\n", fieldPath, s1.size(), s2.size()); } else if (!s1.isEmpty()) { List list1 = new ArrayList<>(s1); List list2 = new ArrayList<>(s2); if (new HashSet<>(list1).equals(new HashSet<>(list2))) { continue; } boolean elementsCompatible = true; for (Object e1 : list1) { boolean foundMatch = false; for (Object e2 : list2) { if (checkCompatible(e1, e2, fieldPath + ".element", new HashMap<>(visited))) { foundMatch = true; break; } } if (!foundMatch) { elementsCompatible = false; break; } } if (!elementsCompatible) { result = false; System.err.printf("Field %s incompatible: sets have different elements\n", fieldPath); } } continue; } if (v1 instanceof List && v2 instanceof List) { List l1 = (List) v1, l2 = (List) v2; if (l1.size() != l2.size()) { result = false; System.err.printf("Field %s incompatible: list size original=%d, deserialized=%d\n", fieldPath, l1.size(), l2.size()); } else { for (int i = 0; i < l1.size(); i++) { Object e1 = l1.get(i); Object e2 = l2.get(i); if (!checkCompatible(e1, e2, fieldPath + "[" + i + "]", new HashMap<>(visited))) { result = false; } } } continue; } if (v1 instanceof Map && v2 instanceof Map) { Map m1 = (Map) v1, m2 = (Map) v2; if (!m1.keySet().equals(m2.keySet())) { result = false; System.err.printf("Field %s incompatible: map keys original=%s, deserialized=%s\n", fieldPath, m1.keySet(), m2.keySet()); } else { for (Object key : m1.keySet()) { Object val1 = m1.get(key), val2 = m2.get(key); if (val1 != null && val2 != null) { if (!checkCompatible(val1, val2, fieldPath + "[" + key + "]", new HashMap<>(visited))) { result = false; } } else if (val1 != val2) { result = false; System.err.printf("Field %s key %s incompatible: original=%s, deserialized=%s\n", fieldPath, key, val1, val2); } } } continue; } Class type = field.getType(); if (null != v1 && null != v2 && !type.isPrimitive() && !type.getName().startsWith("java.")) { if (!checkCompatible(v1, v2, fieldPath, new HashMap<>(visited))) { result = false; } continue; } if (null == v1 || null == v2 || !v1.equals(v2)) { result = false; System.err.printf("Field %s incompatible: original=%s, deserialized=%s\n", fieldPath, v1, v2); } } catch (Exception e) { result = false; System.err.printf("Field %s error: %s\n", path + "." + field.getName(), e.getMessage()); } } if (result) { System.out.printf("Class %s compatible\n", path); } return result; } private boolean isPrimitiveOrWrapper(final Class clazz) { return clazz.isPrimitive() || clazz == String.class || clazz == Boolean.class || clazz == Character.class || clazz == Byte.class || clazz == Short.class || clazz == Integer.class || clazz == Long.class || clazz == Float.class || clazz == Double.class; } private boolean checkCompatible(final Object original, final Class clazz) { String json = com.alibaba.fastjson.JSON.toJSONString(original); Object deserialized; try { deserialized = com.alibaba.fastjson2.JSON.parseObject(json, clazz); } catch (Exception e) { System.err.printf("Deserialization failed for %s: %s\n", clazz.getName(), e.getMessage()); return false; } return checkCompatible(original, deserialized, clazz.getSimpleName(), new HashMap<>()); } private T allocateInstance(final Class clazz) { return new ObjenesisStd().newInstance(clazz); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingSerializableTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson2.JSONWriter; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.TypeAdapter; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; public class RemotingSerializableTest { @Test public void testEncodeAndDecode_HeterogeneousClass() { Sample sample = new Sample(); byte[] bytes = RemotingSerializable.encode(sample); Sample decodedSample = RemotingSerializable.decode(bytes, Sample.class); assertThat(decodedSample).isEqualTo(sample); } @Test public void testToJson_normalString() { RemotingSerializable serializable = new RemotingSerializable() { private List stringList = Arrays.asList("a", "o", "e", "i", "u", "v"); public List getStringList() { return stringList; } public void setStringList(List stringList) { this.stringList = stringList; } }; String string = serializable.toJson(); assertThat(string).isEqualTo("{\"stringList\":[\"a\",\"o\",\"e\",\"i\",\"u\",\"v\"]}"); } @Test public void testToJson_prettyString() { RemotingSerializable serializable = new RemotingSerializable() { private List stringList = Arrays.asList("a", "o", "e", "i", "u", "v"); public List getStringList() { return stringList; } public void setStringList(List stringList) { this.stringList = stringList; } }; String prettyString = serializable.toJson(true); assertThat(prettyString).isEqualTo("{\n" + "\t\"stringList\":[\n" + "\t\t\"a\",\n" + "\t\t\"o\",\n" + "\t\t\"e\",\n" + "\t\t\"i\",\n" + "\t\t\"u\",\n" + "\t\t\"v\"\n" + "\t]\n" + "}"); } @Test public void testEncode() { class Foo extends RemotingSerializable { Map map = new HashMap<>(); Foo() { map.put(0L, "Test"); } public Map getMap() { return map; } } Foo foo = new Foo(); String invalid = new String(foo.encode(), Charset.defaultCharset()); String valid = new String(foo.encode(JSONWriter.Feature.BrowserCompatible, JSONWriter.Feature.MapSortField), Charset.defaultCharset()); Gson gson = new Gson(); final TypeAdapter strictAdapter = gson.getAdapter(JsonElement.class); try { strictAdapter.fromJson(invalid); Assert.fail("Should have thrown"); } catch (IOException ignore) { } try { strictAdapter.fromJson(valid); } catch (IOException ignore) { Assert.fail("Should not throw"); } } } class Sample { private String stringValue = "string"; private int intValue = 2333; private Integer integerValue = 666; private double[] doubleArray = new double[] {0.618, 1.618}; private List stringList = Arrays.asList("a", "o", "e", "i", "u", "v"); public String getStringValue() { return stringValue; } public void setStringValue(String stringValue) { this.stringValue = stringValue; } public int getIntValue() { return intValue; } public void setIntValue(int intValue) { this.intValue = intValue; } public Integer getIntegerValue() { return integerValue; } public void setIntegerValue(Integer integerValue) { this.integerValue = integerValue; } public double[] getDoubleArray() { return doubleArray; } public void setDoubleArray(double[] doubleArray) { this.doubleArray = doubleArray; } public List getStringList() { return stringList; } public void setStringList(List stringList) { this.stringList = stringList; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Sample sample = (Sample) o; if (intValue != sample.intValue) return false; if (stringValue != null ? !stringValue.equals(sample.stringValue) : sample.stringValue != null) return false; if (integerValue != null ? !integerValue.equals(sample.integerValue) : sample.integerValue != null) return false; if (!Arrays.equals(doubleArray, sample.doubleArray)) return false; return stringList != null ? stringList.equals(sample.stringList) : sample.stringList == null; } @Override public int hashCode() { int result = stringValue != null ? stringValue.hashCode() : 0; result = 31 * result + intValue; result = 31 * result + (integerValue != null ? integerValue.hashCode() : 0); result = 31 * result + Arrays.hashCode(doubleArray); result = 31 * result + (stringList != null ? stringList.hashCode() : 0); return result; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestSourceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import junit.framework.TestCase; public class RequestSourceTest extends TestCase { public void testIsValid() { assertEquals(4, RequestSource.values().length); assertTrue(RequestSource.isValid(-1)); assertTrue(RequestSource.isValid(0)); assertTrue(RequestSource.isValid(1)); assertTrue(RequestSource.isValid(2)); assertFalse(RequestSource.isValid(-2)); assertFalse(RequestSource.isValid(3)); } public void testParseInteger() { assertEquals(RequestSource.SDK, RequestSource.parseInteger(-1)); assertEquals(RequestSource.PROXY_FOR_ORDER, RequestSource.parseInteger(0)); assertEquals(RequestSource.PROXY_FOR_BROADCAST, RequestSource.parseInteger(1)); assertEquals(RequestSource.PROXY_FOR_STREAM, RequestSource.parseInteger(2)); assertEquals(RequestSource.SDK, RequestSource.parseInteger(-10)); assertEquals(RequestSource.SDK, RequestSource.parseInteger(10)); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestTypeTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class RequestTypeTest { @Test public void testValueOf() { RequestType requestType = RequestType.valueOf(RequestType.STREAM.getCode()); assertThat(requestType).isEqualTo(RequestType.STREAM); requestType = RequestType.valueOf((byte) 1); assertThat(requestType).isNull(); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializableTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import java.util.HashMap; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.junit.Assert; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class RocketMQSerializableTest { @Test public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithoutExtFields() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER int code = 103; RemotingCommand cmd = RemotingCommand.createRequestCommand(code, new SampleCommandCustomHeader()); cmd.setSerializeTypeCurrentRPC(SerializeType.ROCKETMQ); byte[] result = RocketMQSerializable.rocketMQProtocolEncode(cmd); int opaque = cmd.getOpaque(); assertThat(result).hasSize(21); assertThat(parseToShort(result, 0)).isEqualTo((short) code); //code assertThat(result[2]).isEqualTo(LanguageCode.JAVA.getCode()); //language assertThat(parseToShort(result, 3)).isEqualTo((short) 2333); //version assertThat(parseToInt(result, 9)).isEqualTo(0); //flag assertThat(parseToInt(result, 13)).isEqualTo(0); //empty remark assertThat(parseToInt(result, 17)).isEqualTo(0); //empty extFields RemotingCommand decodedCommand = null; try { decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(Unpooled.wrappedBuffer(result), result.length); assertThat(decodedCommand.getCode()).isEqualTo(code); assertThat(decodedCommand.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(decodedCommand.getVersion()).isEqualTo(2333); assertThat(decodedCommand.getOpaque()).isEqualTo(opaque); assertThat(decodedCommand.getFlag()).isEqualTo(0); assertThat(decodedCommand.getRemark()).isNull(); assertThat(decodedCommand.getExtFields()).isNull(); } catch (RemotingCommandException e) { e.printStackTrace(); Assert.fail("Should not throw IOException"); } } @Test public void testRocketMQProtocolEncodeAndDecode_WithRemarkWithoutExtFields() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER int code = 103; RemotingCommand cmd = RemotingCommand.createRequestCommand(code, new SampleCommandCustomHeader()); cmd.setSerializeTypeCurrentRPC(SerializeType.ROCKETMQ); cmd.setRemark("Sample Remark"); byte[] result = RocketMQSerializable.rocketMQProtocolEncode(cmd); int opaque = cmd.getOpaque(); assertThat(result).hasSize(34); assertThat(parseToShort(result, 0)).isEqualTo((short) code); //code assertThat(result[2]).isEqualTo(LanguageCode.JAVA.getCode()); //language assertThat(parseToShort(result, 3)).isEqualTo((short) 2333); //version assertThat(parseToInt(result, 9)).isEqualTo(0); //flag assertThat(parseToInt(result, 13)).isEqualTo(13); //remark length byte[] remarkArray = new byte[13]; System.arraycopy(result, 17, remarkArray, 0, 13); assertThat(new String(remarkArray)).isEqualTo("Sample Remark"); assertThat(parseToInt(result, 30)).isEqualTo(0); //empty extFields try { RemotingCommand decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(Unpooled.wrappedBuffer(result), result.length); assertThat(decodedCommand.getCode()).isEqualTo(code); assertThat(decodedCommand.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(decodedCommand.getVersion()).isEqualTo(2333); assertThat(decodedCommand.getOpaque()).isEqualTo(opaque); assertThat(decodedCommand.getFlag()).isEqualTo(0); assertThat(decodedCommand.getRemark()).contains("Sample Remark"); assertThat(decodedCommand.getExtFields()).isNull(); } catch (RemotingCommandException e) { e.printStackTrace(); Assert.fail("Should not throw IOException"); } } @Test public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithExtFields() throws Exception { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER int code = 103; RemotingCommand cmd = RemotingCommand.createRequestCommand(code, new SampleCommandCustomHeader()); cmd.setSerializeTypeCurrentRPC(SerializeType.ROCKETMQ); cmd.addExtField("key", "value"); byte[] result = RocketMQSerializable.rocketMQProtocolEncode(cmd); int opaque = cmd.getOpaque(); assertThat(result).hasSize(35); assertThat(parseToShort(result, 0)).isEqualTo((short) code); //code assertThat(result[2]).isEqualTo(LanguageCode.JAVA.getCode()); //language assertThat(parseToShort(result, 3)).isEqualTo((short) 2333); //version assertThat(parseToInt(result, 9)).isEqualTo(0); //flag assertThat(parseToInt(result, 13)).isEqualTo(0); //empty remark assertThat(parseToInt(result, 17)).isEqualTo(14); //extFields length byte[] extFieldsArray = new byte[14]; System.arraycopy(result, 21, extFieldsArray, 0, 14); HashMap extFields = RocketMQSerializable.mapDeserialize(Unpooled.wrappedBuffer(extFieldsArray), extFieldsArray.length); assertThat(extFields).contains(new HashMap.SimpleEntry("key", "value")); try { RemotingCommand decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(Unpooled.wrappedBuffer(result), result.length); assertThat(decodedCommand.getCode()).isEqualTo(code); assertThat(decodedCommand.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(decodedCommand.getVersion()).isEqualTo(2333); assertThat(decodedCommand.getOpaque()).isEqualTo(opaque); assertThat(decodedCommand.getFlag()).isEqualTo(0); assertThat(decodedCommand.getRemark()).isNull(); assertThat(decodedCommand.getExtFields()).contains(new HashMap.SimpleEntry("key", "value")); } catch (RemotingCommandException e) { e.printStackTrace(); Assert.fail("Should not throw IOException"); } } private short parseToShort(byte[] array, int index) { return (short) (array[index] * 256 + array[++index]); } private int parseToInt(byte[] array, int index) { return array[index] * 16777216 + array[++index] * 65536 + array[++index] * 256 + array[++index]; } public static class MyHeader1 implements CommandCustomHeader { private String str; private int num; @Override public void checkFields() throws RemotingCommandException { } public String getStr() { return str; } public void setStr(String str) { this.str = str; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } } @Test public void testFastEncode() throws Exception { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); MyHeader1 header1 = new MyHeader1(); header1.setStr("s1"); header1.setNum(100); RemotingCommand cmd = RemotingCommand.createRequestCommand(1, header1); cmd.setRemark("remark"); cmd.setOpaque(1001); cmd.setVersion(99); cmd.setLanguage(LanguageCode.JAVA); cmd.setFlag(3); cmd.makeCustomHeaderToNet(); RocketMQSerializable.rocketMQProtocolEncode(cmd, buf); RemotingCommand cmd2 = RocketMQSerializable.rocketMQProtocolDecode(buf, buf.readableBytes()); assertThat(cmd2.getRemark()).isEqualTo("remark"); assertThat(cmd2.getCode()).isEqualTo(1); assertThat(cmd2.getOpaque()).isEqualTo(1001); assertThat(cmd2.getVersion()).isEqualTo(99); assertThat(cmd2.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(cmd2.getFlag()).isEqualTo(3); MyHeader1 h2 = (MyHeader1) cmd2.decodeCommandCustomHeader(MyHeader1.class); assertThat(h2.getStr()).isEqualTo("s1"); assertThat(h2.getNum()).isEqualTo(100); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStatsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; public class ConsumeStatsTest { @Test public void testComputeTotalDiff() { ConsumeStats stats = new ConsumeStats(); MessageQueue messageQueue = Mockito.mock(MessageQueue.class); OffsetWrapper offsetWrapper = Mockito.mock(OffsetWrapper.class); Mockito.when(offsetWrapper.getConsumerOffset()).thenReturn(1L); Mockito.when(offsetWrapper.getBrokerOffset()).thenReturn(2L); stats.getOffsetTable().put(messageQueue, offsetWrapper); MessageQueue messageQueue2 = Mockito.mock(MessageQueue.class); OffsetWrapper offsetWrapper2 = Mockito.mock(OffsetWrapper.class); Mockito.when(offsetWrapper2.getConsumerOffset()).thenReturn(2L); Mockito.when(offsetWrapper2.getBrokerOffset()).thenReturn(3L); stats.getOffsetTable().put(messageQueue2, offsetWrapper2); Assert.assertEquals(2L, stats.computeTotalDiff()); } @Test public void testComputeInflightTotalDiff() { ConsumeStats stats = new ConsumeStats(); MessageQueue messageQueue = Mockito.mock(MessageQueue.class); OffsetWrapper offsetWrapper = Mockito.mock(OffsetWrapper.class); Mockito.when(offsetWrapper.getBrokerOffset()).thenReturn(3L); Mockito.when(offsetWrapper.getPullOffset()).thenReturn(2L); stats.getOffsetTable().put(messageQueue, offsetWrapper); MessageQueue messageQueue2 = Mockito.mock(MessageQueue.class); OffsetWrapper offsetWrapper2 = Mockito.mock(OffsetWrapper.class); Mockito.when(offsetWrapper.getBrokerOffset()).thenReturn(3L); Mockito.when(offsetWrapper.getPullOffset()).thenReturn(2L); stats.getOffsetTable().put(messageQueue2, offsetWrapper2); Assert.assertEquals(2L, stats.computeInflightTotalDiff()); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTableTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.admin; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class TopicStatsTableTest { private volatile TopicStatsTable topicStatsTable; private static final String TEST_TOPIC = "test_topic"; private static final String TEST_BROKER = "test_broker"; private static final int QUEUE_ID = 1; private static final long CURRENT_TIME_MILLIS = System.currentTimeMillis(); private static final long MAX_OFFSET = CURRENT_TIME_MILLIS + 100; private static final long MIN_OFFSET = CURRENT_TIME_MILLIS - 100; @Before public void buildTopicStatsTable() { HashMap offsetTableMap = new HashMap<>(); MessageQueue messageQueue = new MessageQueue(TEST_TOPIC, TEST_BROKER, QUEUE_ID); TopicOffset topicOffset = new TopicOffset(); topicOffset.setLastUpdateTimestamp(CURRENT_TIME_MILLIS); topicOffset.setMinOffset(MIN_OFFSET); topicOffset.setMaxOffset(MAX_OFFSET); offsetTableMap.put(messageQueue, topicOffset); topicStatsTable = new TopicStatsTable(); topicStatsTable.setOffsetTable(offsetTableMap); } @Test public void testGetOffsetTable() throws Exception { validateTopicStatsTable(topicStatsTable); } @Test public void testFromJson() throws Exception { String json = RemotingSerializable.toJson(topicStatsTable, true); TopicStatsTable fromJson = RemotingSerializable.fromJson(json, TopicStatsTable.class); validateTopicStatsTable(fromJson); } private static void validateTopicStatsTable(TopicStatsTable topicStatsTable) throws Exception { Map.Entry savedTopicStatsTableMap = topicStatsTable.getOffsetTable().entrySet().iterator().next(); MessageQueue savedMessageQueue = savedTopicStatsTableMap.getKey(); TopicOffset savedTopicOffset = savedTopicStatsTableMap.getValue(); Assert.assertTrue(savedMessageQueue.getTopic().equals(TEST_TOPIC)); Assert.assertTrue(savedMessageQueue.getBrokerName().equals(TEST_BROKER)); Assert.assertTrue(savedMessageQueue.getQueueId() == QUEUE_ID); Assert.assertTrue(savedTopicOffset.getLastUpdateTimestamp() == CURRENT_TIME_MILLIS); Assert.assertTrue(savedTopicOffset.getMaxOffset() == MAX_OFFSET); Assert.assertTrue(savedTopicOffset.getMinOffset() == MIN_OFFSET); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BatchAckTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import com.alibaba.fastjson2.JSON; import org.apache.rocketmq.common.MixAll; import org.junit.Test; import java.util.Arrays; import java.util.BitSet; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; public class BatchAckTest { private static String topic = "myTopic"; private static String cid = MixAll.DEFAULT_CONSUMER_GROUP; private static long startOffset = 100; private static int qId = 1; private static int rqId = 2; private static long popTime = System.currentTimeMillis(); private static long invisibleTime = 5000; @Test public void testBatchAckSerializerDeserializer() { List ackOffsetList = Arrays.asList(startOffset + 1, startOffset + 3, startOffset + 5); BatchAck batchAck = new BatchAck(); batchAck.setConsumerGroup(cid); batchAck.setTopic(topic); batchAck.setRetry("0"); batchAck.setStartOffset(startOffset); batchAck.setQueueId(qId); batchAck.setReviveQueueId(rqId); batchAck.setPopTime(popTime); batchAck.setInvisibleTime(invisibleTime); batchAck.setBitSet(new BitSet()); for (Long offset : ackOffsetList) { batchAck.getBitSet().set((int) (offset - startOffset)); } String jsonStr = JSON.toJSONString(batchAck); BatchAck bAck = JSON.parseObject(jsonStr, BatchAck.class); assertThat(bAck.getConsumerGroup()).isEqualTo(cid); assertThat(bAck.getTopic()).isEqualTo(topic); assertThat(bAck.getStartOffset()).isEqualTo(startOffset); assertThat(bAck.getQueueId()).isEqualTo(qId); assertThat(bAck.getReviveQueueId()).isEqualTo(rqId); assertThat(bAck.getPopTime()).isEqualTo(popTime); assertThat(bAck.getInvisibleTime()).isEqualTo(invisibleTime); for (int i = 0; i < bAck.getBitSet().length(); i++) { long ackOffset = startOffset + i; if (ackOffsetList.contains(ackOffset)) { assertThat(bAck.getBitSet().get(i)).isTrue(); } else { assertThat(bAck.getBitSet().get(i)).isFalse(); } } } @Test public void testWithBatchAckMessageRequestBody() { List ackOffsetList = Arrays.asList(startOffset + 1, startOffset + 3, startOffset + 5); BatchAck batchAck = new BatchAck(); batchAck.setConsumerGroup(cid); batchAck.setTopic(topic); batchAck.setRetry("0"); batchAck.setStartOffset(startOffset); batchAck.setQueueId(qId); batchAck.setReviveQueueId(rqId); batchAck.setPopTime(popTime); batchAck.setInvisibleTime(invisibleTime); batchAck.setBitSet(new BitSet()); for (Long offset : ackOffsetList) { batchAck.getBitSet().set((int) (offset - startOffset)); } BatchAckMessageRequestBody batchAckMessageRequestBody = new BatchAckMessageRequestBody(); batchAckMessageRequestBody.setAcks(Arrays.asList(batchAck)); byte[] bytes = batchAckMessageRequestBody.encode(); BatchAckMessageRequestBody reqBody = BatchAckMessageRequestBody.decode(bytes, BatchAckMessageRequestBody.class); BatchAck bAck = reqBody.getAcks().get(0); assertThat(bAck.getConsumerGroup()).isEqualTo(cid); assertThat(bAck.getTopic()).isEqualTo(topic); assertThat(bAck.getStartOffset()).isEqualTo(startOffset); assertThat(bAck.getQueueId()).isEqualTo(qId); assertThat(bAck.getReviveQueueId()).isEqualTo(rqId); assertThat(bAck.getPopTime()).isEqualTo(popTime); assertThat(bAck.getInvisibleTime()).isEqualTo(invisibleTime); for (int i = 0; i < bAck.getBitSet().length(); i++) { long ackOffset = startOffset + i; if (ackOffsetList.contains(ackOffset)) { assertThat(bAck.getBitSet().get(i)).isTrue(); } else { assertThat(bAck.getBitSet().get(i)).isFalse(); } } } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsDataTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; public class BrokerStatsDataTest { @Test public void testFromJson() throws Exception { BrokerStatsData brokerStatsData = new BrokerStatsData(); { BrokerStatsItem brokerStatsItem = new BrokerStatsItem(); brokerStatsItem.setAvgpt(10.0); brokerStatsItem.setSum(100L); brokerStatsItem.setTps(100.0); brokerStatsData.setStatsDay(brokerStatsItem); } { BrokerStatsItem brokerStatsItem = new BrokerStatsItem(); brokerStatsItem.setAvgpt(10.0); brokerStatsItem.setSum(100L); brokerStatsItem.setTps(100.0); brokerStatsData.setStatsHour(brokerStatsItem); } { BrokerStatsItem brokerStatsItem = new BrokerStatsItem(); brokerStatsItem.setAvgpt(10.0); brokerStatsItem.setSum(100L); brokerStatsItem.setTps(100.0); brokerStatsData.setStatsMinute(brokerStatsItem); } String json = RemotingSerializable.toJson(brokerStatsData, true); BrokerStatsData brokerStatsDataResult = RemotingSerializable.fromJson(json, BrokerStatsData.class); assertThat(brokerStatsDataResult.getStatsMinute().getAvgpt()).isCloseTo(brokerStatsData.getStatsMinute().getAvgpt(), within(0.0001)); assertThat(brokerStatsDataResult.getStatsMinute().getTps()).isCloseTo(brokerStatsData.getStatsMinute().getTps(), within(0.0001)); assertThat(brokerStatsDataResult.getStatsMinute().getSum()).isEqualTo(brokerStatsData.getStatsMinute().getSum()); assertThat(brokerStatsDataResult.getStatsHour().getAvgpt()).isCloseTo(brokerStatsData.getStatsHour().getAvgpt(), within(0.0001)); assertThat(brokerStatsDataResult.getStatsHour().getTps()).isCloseTo(brokerStatsData.getStatsHour().getTps(), within(0.0001)); assertThat(brokerStatsDataResult.getStatsHour().getSum()).isEqualTo(brokerStatsData.getStatsHour().getSum()); assertThat(brokerStatsDataResult.getStatsDay().getAvgpt()).isCloseTo(brokerStatsData.getStatsDay().getAvgpt(), within(0.0001)); assertThat(brokerStatsDataResult.getStatsDay().getTps()).isCloseTo(brokerStatsData.getStatsDay().getTps(), within(0.0001)); assertThat(brokerStatsDataResult.getStatsDay().getSum()).isEqualTo(brokerStatsData.getStatsDay().getSum()); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBodyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class CheckClientRequestBodyTest { @Test public void testFromJson() { SubscriptionData subscriptionData = new SubscriptionData(); String expectedClientId = "defalutId"; String expectedGroup = "defaultGroup"; CheckClientRequestBody checkClientRequestBody = new CheckClientRequestBody(); checkClientRequestBody.setClientId(expectedClientId); checkClientRequestBody.setGroup(expectedGroup); checkClientRequestBody.setSubscriptionData(subscriptionData); String json = RemotingSerializable.toJson(checkClientRequestBody, true); CheckClientRequestBody fromJson = RemotingSerializable.fromJson(json, CheckClientRequestBody.class); assertThat(fromJson.getClientId()).isEqualTo(expectedClientId); assertThat(fromJson.getGroup()).isEqualTo(expectedGroup); assertThat(fromJson.getSubscriptionData()).isEqualTo(subscriptionData); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResultTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ConsumeMessageDirectlyResultTest { @Test public void testFromJson() throws Exception { ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); boolean defaultAutoCommit = true; boolean defaultOrder = false; long defaultSpentTimeMills = 1234567L; String defaultRemark = "defaultMark"; CMResult defaultCMResult = CMResult.CR_COMMIT; result.setAutoCommit(defaultAutoCommit); result.setOrder(defaultOrder); result.setRemark(defaultRemark); result.setSpentTimeMills(defaultSpentTimeMills); result.setConsumeResult(defaultCMResult); String json = RemotingSerializable.toJson(result, true); ConsumeMessageDirectlyResult fromJson = RemotingSerializable.fromJson(json, ConsumeMessageDirectlyResult.class); assertThat(fromJson).isNotNull(); assertThat(fromJson.getRemark()).isEqualTo(defaultRemark); assertThat(fromJson.getSpentTimeMills()).isEqualTo(defaultSpentTimeMills); assertThat(fromJson.getConsumeResult()).isEqualTo(defaultCMResult); assertThat(fromJson.isOrder()).isEqualTo(defaultOrder); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsListTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ConsumeStatsListTest { @Test public void testFromJson() { ConsumeStats consumeStats = new ConsumeStats(); ArrayList consumeStatsListValue = new ArrayList<>(); consumeStatsListValue.add(consumeStats); HashMap> map = new HashMap<>(); map.put("subscriptionGroupName", consumeStatsListValue); List>> consumeStatsListValue2 = new ArrayList<>(); consumeStatsListValue2.add(map); String brokerAddr = "brokerAddr"; long totalDiff = 12352L; ConsumeStatsList consumeStatsList = new ConsumeStatsList(); consumeStatsList.setBrokerAddr(brokerAddr); consumeStatsList.setTotalDiff(totalDiff); consumeStatsList.setConsumeStatsList(consumeStatsListValue2); String toJson = RemotingSerializable.toJson(consumeStatsList, true); ConsumeStatsList fromJson = RemotingSerializable.fromJson(toJson, ConsumeStatsList.class); assertThat(fromJson.getBrokerAddr()).isEqualTo(brokerAddr); assertThat(fromJson.getTotalDiff()).isEqualTo(totalDiff); List>> fromJsonConsumeStatsList = fromJson.getConsumeStatsList(); assertThat(fromJsonConsumeStatsList).isInstanceOf(List.class); ConsumeStats fromJsonConsumeStats = fromJsonConsumeStatsList.get(0).get("subscriptionGroupName").get(0); assertThat(fromJsonConsumeStats).isExactlyInstanceOf(ConsumeStats.class); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnectionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.assertj.core.api.Assertions; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ConsumerConnectionTest { @Test public void testFromJson() { ConsumerConnection consumerConnection = new ConsumerConnection(); HashSet connections = new HashSet<>(); Connection conn = new Connection(); connections.add(conn); ConcurrentHashMap subscriptionTable = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionTable.put("topicA", subscriptionData); ConsumeType consumeType = ConsumeType.CONSUME_ACTIVELY; MessageModel messageModel = MessageModel.CLUSTERING; ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET; consumerConnection.setConnectionSet(connections); consumerConnection.setSubscriptionTable(subscriptionTable); consumerConnection.setConsumeType(consumeType); consumerConnection.setMessageModel(messageModel); consumerConnection.setConsumeFromWhere(consumeFromWhere); String json = RemotingSerializable.toJson(consumerConnection, true); ConsumerConnection fromJson = RemotingSerializable.fromJson(json, ConsumerConnection.class); assertThat(fromJson.getConsumeType()).isEqualTo(ConsumeType.CONSUME_ACTIVELY); assertThat(fromJson.getMessageModel()).isEqualTo(MessageModel.CLUSTERING); HashSet connectionSet = fromJson.getConnectionSet(); Assertions.assertThat(connectionSet).isInstanceOf(Set.class); SubscriptionData data = fromJson.getSubscriptionTable().get("topicA"); assertThat(data).isExactlyInstanceOf(SubscriptionData.class); } @Test public void testComputeMinVersion() { ConsumerConnection consumerConnection = new ConsumerConnection(); HashSet connections = new HashSet<>(); Connection conn1 = new Connection(); conn1.setVersion(1); connections.add(conn1); Connection conn2 = new Connection(); conn2.setVersion(10); connections.add(conn2); consumerConnection.setConnectionSet(connections); int version = consumerConnection.computeMinVersion(); assertThat(version).isEqualTo(1); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfoTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.Properties; import java.util.TreeMap; import java.util.TreeSet; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Before; import org.junit.Test; import static org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType.CONSUME_ACTIVELY; import static org.assertj.core.api.Assertions.assertThat; public class ConsumerRunningInfoTest { private ConsumerRunningInfo consumerRunningInfo; private TreeMap criTable; private MessageQueue messageQueue; @Before public void init() { consumerRunningInfo = new ConsumerRunningInfo(); consumerRunningInfo.setJstack("test"); TreeMap mqTable = new TreeMap<>(); messageQueue = new MessageQueue("topicA","broker", 1); mqTable.put(messageQueue, new ProcessQueueInfo()); consumerRunningInfo.setMqTable(mqTable); TreeMap statusTable = new TreeMap<>(); statusTable.put("topicA", new ConsumeStatus()); consumerRunningInfo.setStatusTable(statusTable); TreeSet subscriptionSet = new TreeSet<>(); subscriptionSet.add(new SubscriptionData()); consumerRunningInfo.setSubscriptionSet(subscriptionSet); Properties properties = new Properties(); properties.put(ConsumerRunningInfo.PROP_CONSUME_TYPE, CONSUME_ACTIVELY); properties.put(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP, System.currentTimeMillis()); consumerRunningInfo.setProperties(properties); criTable = new TreeMap<>(); criTable.put("client_id", consumerRunningInfo); } @Test public void testFromJson() { String toJson = RemotingSerializable.toJson(consumerRunningInfo, true); ConsumerRunningInfo fromJson = RemotingSerializable.fromJson(toJson, ConsumerRunningInfo.class); assertThat(fromJson.getJstack()).isEqualTo("test"); assertThat(fromJson.getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).isEqualTo(ConsumeType.CONSUME_ACTIVELY.name()); ConsumeStatus consumeStatus = fromJson.getStatusTable().get("topicA"); assertThat(consumeStatus).isExactlyInstanceOf(ConsumeStatus.class); SubscriptionData subscription = fromJson.getSubscriptionSet().first(); assertThat(subscription).isExactlyInstanceOf(SubscriptionData.class); ProcessQueueInfo processQueueInfo = fromJson.getMqTable().get(messageQueue); assertThat(processQueueInfo).isExactlyInstanceOf(ProcessQueueInfo.class); } @Test public void testAnalyzeRebalance() { boolean result = ConsumerRunningInfo.analyzeRebalance(criTable); assertThat(result).isTrue(); } @Test public void testAnalyzeProcessQueue() { String result = ConsumerRunningInfo.analyzeProcessQueue("client_id", consumerRunningInfo); assertThat(result).isEmpty(); } @Test public void testAnalyzeSubscription() { boolean result = ConsumerRunningInfo.analyzeSubscription(criTable); assertThat(result).isTrue(); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/KVTableTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class KVTableTest { @Test public void testFromJson() throws Exception { HashMap table = new HashMap<>(); table.put("key1", "value1"); table.put("key2", "value2"); KVTable kvTable = new KVTable(); kvTable.setTable(table); String json = RemotingSerializable.toJson(kvTable, true); KVTable fromJson = RemotingSerializable.fromJson(json, KVTable.class); assertThat(fromJson).isNotEqualTo(kvTable); assertThat(fromJson.getTable().get("key1")).isEqualTo(kvTable.getTable().get("key1")); assertThat(fromJson.getTable().get("key2")).isEqualTo(kvTable.getTable().get("key2")); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MessageRequestModeSerializeWrapperTest { @Test public void testFromJson() { MessageRequestModeSerializeWrapper messageRequestModeSerializeWrapper = new MessageRequestModeSerializeWrapper(); ConcurrentHashMap> messageRequestModeMap = new ConcurrentHashMap<>(); String topic = "TopicTest"; String group = "Consumer"; MessageRequestMode requestMode = MessageRequestMode.POP; int popShareQueueNum = -1; SetMessageRequestModeRequestBody requestBody = new SetMessageRequestModeRequestBody(); requestBody.setTopic(topic); requestBody.setConsumerGroup(group); requestBody.setMode(requestMode); requestBody.setPopShareQueueNum(popShareQueueNum); ConcurrentHashMap map = new ConcurrentHashMap<>(); map.put(group, requestBody); messageRequestModeMap.put(topic, map); messageRequestModeSerializeWrapper.setMessageRequestModeMap(messageRequestModeMap); String json = RemotingSerializable.toJson(messageRequestModeSerializeWrapper, true); MessageRequestModeSerializeWrapper fromJson = RemotingSerializable.fromJson(json, MessageRequestModeSerializeWrapper.class); assertThat(fromJson.getMessageRequestModeMap()).containsKey(topic); assertThat(fromJson.getMessageRequestModeMap().get(topic)).containsKey(group); assertThat(fromJson.getMessageRequestModeMap().get(topic).get(group).getTopic()).isEqualTo(topic); assertThat(fromJson.getMessageRequestModeMap().get(topic).get(group).getConsumerGroup()).isEqualTo(group); assertThat(fromJson.getMessageRequestModeMap().get(topic).get(group).getMode()).isEqualTo(requestMode); assertThat(fromJson.getMessageRequestModeMap().get(topic).get(group).getPopShareQueueNum()).isEqualTo(popShareQueueNum); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBodyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class QueryConsumeQueueResponseBodyTest { @Test public void test() { QueryConsumeQueueResponseBody body = new QueryConsumeQueueResponseBody(); SubscriptionData subscriptionData = new SubscriptionData(); ConsumeQueueData data = new ConsumeQueueData(); data.setBitMap("defaultBitMap"); data.setEval(false); data.setMsg("this is default msg"); data.setPhysicOffset(10L); data.setPhysicSize(1); data.setTagsCode(1L); List list = new ArrayList<>(); list.add(data); body.setQueueData(list); body.setFilterData("default filter data"); body.setMaxQueueIndex(100L); body.setMinQueueIndex(1L); body.setSubscriptionData(subscriptionData); String json = RemotingSerializable.toJson(body, true); QueryConsumeQueueResponseBody fromJson = RemotingSerializable.fromJson(json, QueryConsumeQueueResponseBody.class); //test ConsumeQueue ConsumeQueueData jsonData = fromJson.getQueueData().get(0); assertThat(jsonData.getMsg()).isEqualTo("this is default msg"); assertThat(jsonData.getPhysicSize()).isEqualTo(1); assertThat(jsonData.getBitMap()).isEqualTo("defaultBitMap"); assertThat(jsonData.getTagsCode()).isEqualTo(1L); assertThat(jsonData.getPhysicSize()).isEqualTo(1); //test QueryConsumeQueueResponseBody assertThat(fromJson.getFilterData()).isEqualTo("default filter data"); assertThat(fromJson.getMaxQueueIndex()).isEqualTo(100L); assertThat(fromJson.getMinQueueIndex()).isEqualTo(1L); assertThat(fromJson.getSubscriptionData()).isEqualTo(subscriptionData); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBodyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class QueryCorrectionOffsetBodyTest { @Test public void testFromJson() throws Exception { QueryCorrectionOffsetBody qcob = new QueryCorrectionOffsetBody(); Map offsetMap = new HashMap<>(); offsetMap.put(1, 100L); offsetMap.put(2, 200L); qcob.setCorrectionOffsets(offsetMap); String json = RemotingSerializable.toJson(qcob, true); QueryCorrectionOffsetBody fromJson = RemotingSerializable.fromJson(json, QueryCorrectionOffsetBody.class); assertThat(fromJson.getCorrectionOffsets().get(1)).isEqualTo(100L); assertThat(fromJson.getCorrectionOffsets().get(2)).isEqualTo(200L); assertThat(fromJson.getCorrectionOffsets().size()).isEqualTo(2); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ResetOffsetBodyTest { @Test public void testFromJson() throws Exception { ResetOffsetBody rob = new ResetOffsetBody(); Map offsetMap = new HashMap<>(); MessageQueue queue = new MessageQueue(); queue.setQueueId(1); queue.setBrokerName("brokerName"); queue.setTopic("topic"); offsetMap.put(queue, 100L); rob.setOffsetTable(offsetMap); String json = RemotingSerializable.toJson(rob, true); ResetOffsetBody fromJson = RemotingSerializable.fromJson(json, ResetOffsetBody.class); assertThat(fromJson.getOffsetTable().get(queue)).isEqualTo(100L); assertThat(fromJson.getOffsetTable().size()).isEqualTo(1); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SubscriptionGroupWrapperTest { @Test public void testFromJson() { SubscriptionGroupWrapper subscriptionGroupWrapper = new SubscriptionGroupWrapper(); ConcurrentHashMap subscriptions = new ConcurrentHashMap<>(); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setConsumeBroadcastEnable(true); subscriptionGroupConfig.setBrokerId(1234); subscriptionGroupConfig.setGroupName("Consumer-group-one"); subscriptions.put("Consumer-group-one", subscriptionGroupConfig); subscriptionGroupWrapper.setSubscriptionGroupTable(subscriptions); DataVersion dataVersion = new DataVersion(); dataVersion.nextVersion(); subscriptionGroupWrapper.setDataVersion(dataVersion); String json = RemotingSerializable.toJson(subscriptionGroupWrapper, true); SubscriptionGroupWrapper fromJson = RemotingSerializable.fromJson(json, SubscriptionGroupWrapper.class); assertThat(fromJson.getSubscriptionGroupTable()).containsKey("Consumer-group-one"); assertThat(fromJson.getSubscriptionGroupTable().get("Consumer-group-one").getGroupName()).isEqualTo("Consumer-group-one"); assertThat(fromJson.getSubscriptionGroupTable().get("Consumer-group-one").getBrokerId()).isEqualTo(1234); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPITest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.filter; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class FilterAPITest { private String topic = "FooBar"; private String group = "FooBarGroup"; private String subString = "TAG1 || Tag2 || tag3"; @Test public void testBuildSubscriptionData() throws Exception { SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subString); assertThat(subscriptionData.getTopic()).isEqualTo(topic); assertThat(subscriptionData.getSubString()).isEqualTo(subString); String[] tags = subString.split("\\|\\|"); Set tagSet = new HashSet<>(); for (String tag : tags) { tagSet.add(tag.trim()); } assertThat(subscriptionData.getTagsSet()).isEqualTo(tagSet); } @Test public void testBuildTagSome() { try { SubscriptionData subscriptionData = FilterAPI.build( "TOPIC", "A || B", ExpressionType.TAG ); assertThat(subscriptionData).isNotNull(); assertThat(subscriptionData.getTopic()).isEqualTo("TOPIC"); assertThat(subscriptionData.getSubString()).isEqualTo("A || B"); assertThat(ExpressionType.isTagType(subscriptionData.getExpressionType())).isTrue(); assertThat(subscriptionData.getTagsSet()).isNotNull(); assertThat(subscriptionData.getTagsSet()).containsExactlyInAnyOrder("A", "B"); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } @Test public void testBuildSQL() { try { SubscriptionData subscriptionData = FilterAPI.build( "TOPIC", "a is not null", ExpressionType.SQL92 ); assertThat(subscriptionData).isNotNull(); assertThat(subscriptionData.getTopic()).isEqualTo("TOPIC"); assertThat(subscriptionData.getExpressionType()).isEqualTo(ExpressionType.SQL92); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } @Test(expected = IllegalArgumentException.class) public void testBuildSQLWithNullSubString() throws Exception { FilterAPI.build("TOPIC", null, ExpressionType.SQL92); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/ExportRocksDBConfigToJsonRequestHeaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.util.ArrayList; import java.util.List; import org.junit.Assert; import org.junit.Test; public class ExportRocksDBConfigToJsonRequestHeaderTest { @Test public void configTypeTest() { List configTypes = new ArrayList<>(); configTypes.add(ExportRocksDBConfigToJsonRequestHeader.ConfigType.TOPICS); configTypes.add(ExportRocksDBConfigToJsonRequestHeader.ConfigType.SUBSCRIPTION_GROUPS); String string = ExportRocksDBConfigToJsonRequestHeader.ConfigType.toString(configTypes); List newConfigTypes = ExportRocksDBConfigToJsonRequestHeader.ConfigType.fromString(string); assert newConfigTypes.size() == 2; assert configTypes.equals(newConfigTypes); List topics = ExportRocksDBConfigToJsonRequestHeader.ConfigType.fromString("topics"); assert topics.size() == 1; assert topics.get(0).equals(ExportRocksDBConfigToJsonRequestHeader.ConfigType.TOPICS); List mix = ExportRocksDBConfigToJsonRequestHeader.ConfigType.fromString("toPics; subScriptiongroups"); assert mix.size() == 2; assert mix.get(0).equals(ExportRocksDBConfigToJsonRequestHeader.ConfigType.TOPICS); assert mix.get(1).equals(ExportRocksDBConfigToJsonRequestHeader.ConfigType.SUBSCRIPTION_GROUPS); Assert.assertThrows(IllegalArgumentException.class, () -> { ExportRocksDBConfigToJsonRequestHeader.ConfigType.fromString("topics; subscription"); }); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.util.Map; import org.junit.Test; import static org.junit.Assert.assertEquals; public class ExtraInfoUtilTest { @Test public void testOrderCountInfo() { String topic = "TOPIC"; int queueId = 0; long queueOffset = 1234; Integer queueIdCount = 1; Integer queueOffsetCount = 2; String queueIdKey = ExtraInfoUtil.getStartOffsetInfoMapKey(topic, queueId); String queueOffsetKey = ExtraInfoUtil.getQueueOffsetMapKey(topic, queueId, queueOffset); StringBuilder sb = new StringBuilder(); ExtraInfoUtil.buildQueueIdOrderCountInfo(sb, topic, queueId, queueIdCount); ExtraInfoUtil.buildQueueOffsetOrderCountInfo(sb, topic, queueId, queueOffset, queueOffsetCount); Map orderCountInfo = ExtraInfoUtil.parseOrderCountInfo(sb.toString()); assertEquals(queueIdCount, orderCountInfo.get(queueIdKey)); assertEquals(queueOffsetCount, orderCountInfo.get(queueOffsetKey)); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/FastCodesHeaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.protocol.FastCodesHeader; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.Assert; import org.junit.Test; public class FastCodesHeaderTest { @Test public void testFastDecode() throws Exception { testFastDecode(SendMessageRequestHeaderV2.class); testFastDecode(SendMessageResponseHeader.class); testFastDecode(PullMessageRequestHeader.class); testFastDecode(PullMessageResponseHeader.class); } private void testFastDecode(Class classHeader) throws Exception { Field[] declaredFields = classHeader.getDeclaredFields(); List declaredFieldsList = new ArrayList<>(); for (Field f : declaredFields) { if (f.getName().startsWith("$")) { continue; } f.setAccessible(true); declaredFieldsList.add(f); } RemotingCommand command = RemotingCommand.createRequestCommand(0, null); HashMap m = buildExtFields(declaredFieldsList); command.setExtFields(m); check(command, declaredFieldsList, classHeader); } private HashMap buildExtFields(List fields) { HashMap extFields = new HashMap<>(); for (Field f: fields) { Class c = f.getType(); if (c.equals(String.class)) { extFields.put(f.getName(), "str"); } else if (c.equals(Integer.class) || c.equals(int.class)) { extFields.put(f.getName(), "123"); } else if (c.equals(Long.class) || c.equals(long.class)) { extFields.put(f.getName(), "1234"); } else if (c.equals(Boolean.class) || c.equals(boolean.class)) { extFields.put(f.getName(), "true"); } else { throw new RuntimeException(f.getName() + ":" + f.getType().getName()); } } return extFields; } private void check(RemotingCommand command, List fields, Class classHeader) throws Exception { CommandCustomHeader o1 = command.decodeCommandCustomHeaderDirectly(classHeader, false); CommandCustomHeader o2 = classHeader.getDeclaredConstructor().newInstance(); ((FastCodesHeader)o2).decode(command.getExtFields()); for (Field f : fields) { Object value1 = f.get(o1); Object value2 = f.get(o2); if (value1 == null) { Assert.assertNull(value2); } else { Assert.assertEquals(value1, value2); } } } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsRequestHeaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class GetConsumeStatsRequestHeaderTest { private GetConsumeStatsRequestHeader header; @Before public void setUp() { header = new GetConsumeStatsRequestHeader(); } @Test public void updateTopicList_NullTopicList_DoesNotUpdate() { header.updateTopicList(null); assertNull(header.getTopicList()); } @Test public void updateTopicList_EmptyTopicList_SetsEmptyString() { header.updateTopicList(Collections.emptyList()); assertNull(header.getTopicList()); } @Test public void updateTopicList_SingleTopic_SetsSingleTopicString() { List topicList = Collections.singletonList("TopicA"); header.updateTopicList(topicList); assertEquals("TopicA;", header.getTopicList()); } @Test public void updateTopicList_MultipleTopics_SetsMultipleTopicsString() { List topicList = Arrays.asList("TopicA", "TopicB", "TopicC"); header.updateTopicList(topicList); assertEquals("TopicA;TopicB;TopicC;", header.getTopicList()); } @Test public void updateTopicList_RepeatedTopics_SetsRepeatedTopicsString() { List topicList = Arrays.asList("TopicA", "TopicA", "TopicB"); header.updateTopicList(topicList); assertEquals("TopicA;TopicA;TopicB;", header.getTopicList()); } @Test public void fetchTopicList_NullTopicList_ReturnsEmptyList() { header.setTopicList(null); List topicList = header.fetchTopicList(); assertEquals(Collections.emptyList(), topicList); header.updateTopicList(new ArrayList<>()); topicList = header.fetchTopicList(); assertEquals(Collections.emptyList(), topicList); } @Test public void fetchTopicList_EmptyTopicList_ReturnsEmptyList() { header.setTopicList(""); List topicList = header.fetchTopicList(); assertEquals(Collections.emptyList(), topicList); } @Test public void fetchTopicList_BlankTopicList_ReturnsEmptyList() { header.setTopicList(" "); List topicList = header.fetchTopicList(); assertEquals(Collections.emptyList(), topicList); } @Test public void fetchTopicList_SingleTopic_ReturnsSingleTopicList() { header.setTopicList("TopicA"); List topicList = header.fetchTopicList(); assertEquals(Collections.singletonList("TopicA"), topicList); } @Test public void fetchTopicList_MultipleTopics_ReturnsTopicList() { header.setTopicList("TopicA;TopicB;TopicC"); List topicList = header.fetchTopicList(); assertEquals(Arrays.asList("TopicA", "TopicB", "TopicC"), topicList); } @Test public void fetchTopicList_TopicListEndsWithSeparator_ReturnsTopicList() { header.setTopicList("TopicA;TopicB;"); List topicList = header.fetchTopicList(); assertEquals(Arrays.asList("TopicA", "TopicB"), topicList); } @Test public void fetchTopicList_TopicListStartsWithSeparator_ReturnsTopicList() { header.setTopicList(";TopicA;TopicB"); List topicList = header.fetchTopicList(); assertEquals(Arrays.asList("TopicA", "TopicB"), topicList); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeaderV2Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.header; import java.nio.ByteBuffer; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SendMessageRequestHeaderV2Test { SendMessageRequestHeaderV2 header = new SendMessageRequestHeaderV2(); String topic = "test"; int queueId = 5; @Test public void testEncodeDecode() throws RemotingCommandException { header.setQueueId(queueId); header.setTopic(topic); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, header); ByteBuffer buffer = remotingCommand.encode(); //Simulate buffer being read in NettyDecoder buffer.getInt(); byte[] bytes = new byte[buffer.limit() - 4]; buffer.get(bytes, 0, buffer.limit() - 4); buffer = ByteBuffer.wrap(bytes); RemotingCommand decodeRequest = RemotingCommand.decode(buffer); assertThat(decodeRequest.getExtFields().get("e")).isEqualTo(String.valueOf(queueId)); assertThat(decodeRequest.getExtFields().get("b")).isEqualTo(topic); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionDataTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.heartbeat; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.assertj.core.util.Sets; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SubscriptionDataTest { @Test public void testConstructor1() { SubscriptionData subscriptionData = new SubscriptionData(); assertThat(subscriptionData.getTopic()).isNull(); assertThat(subscriptionData.getSubString()).isNull(); assertThat(subscriptionData.getSubVersion()).isLessThanOrEqualTo(System.currentTimeMillis()); assertThat(subscriptionData.getExpressionType()).isEqualTo(ExpressionType.TAG); assertThat(subscriptionData.getFilterClassSource()).isNull(); assertThat(subscriptionData.getCodeSet()).isEmpty(); assertThat(subscriptionData.getTagsSet()).isEmpty(); assertThat(subscriptionData.isClassFilterMode()).isFalse(); } @Test public void testConstructor2() { SubscriptionData subscriptionData = new SubscriptionData("TOPICA", "*"); assertThat(subscriptionData.getTopic()).isEqualTo("TOPICA"); assertThat(subscriptionData.getSubString()).isEqualTo("*"); assertThat(subscriptionData.getSubVersion()).isLessThanOrEqualTo(System.currentTimeMillis()); assertThat(subscriptionData.getExpressionType()).isEqualTo(ExpressionType.TAG); assertThat(subscriptionData.getFilterClassSource()).isNull(); assertThat(subscriptionData.getCodeSet()).isEmpty(); assertThat(subscriptionData.getTagsSet()).isEmpty(); assertThat(subscriptionData.isClassFilterMode()).isFalse(); } @Test public void testHashCodeNotEquals() { SubscriptionData subscriptionData = new SubscriptionData("TOPICA", "*"); subscriptionData.setCodeSet(Sets.newLinkedHashSet(1, 2, 3)); subscriptionData.setTagsSet(Sets.newLinkedHashSet("TAGA", "TAGB", "TAG3")); assertThat(subscriptionData.hashCode()).isNotEqualTo(System.identityHashCode(subscriptionData)); } @Test public void testFromJson() throws Exception { SubscriptionData subscriptionData = new SubscriptionData("TOPICA", "*"); subscriptionData.setFilterClassSource("TestFilterClassSource"); subscriptionData.setCodeSet(Sets.newLinkedHashSet(1, 2, 3)); subscriptionData.setTagsSet(Sets.newLinkedHashSet("TAGA", "TAGB", "TAG3")); String json = RemotingSerializable.toJson(subscriptionData, true); SubscriptionData fromJson = RemotingSerializable.fromJson(json, SubscriptionData.class); assertThat(subscriptionData).isEqualTo(fromJson); assertThat(subscriptionData).isEqualByComparingTo(fromJson); assertThat(subscriptionData.getFilterClassSource()).isEqualTo("TestFilterClassSource"); assertThat(fromJson.getFilterClassSource()).isNull(); } @Test public void testCompareTo() { SubscriptionData subscriptionData = new SubscriptionData("TOPICA", "*"); SubscriptionData subscriptionData1 = new SubscriptionData("TOPICBA", "*"); assertThat(subscriptionData.compareTo(subscriptionData1)).isEqualTo("TOPICA@*".compareTo("TOPICB@*")); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteDataTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.route; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class TopicRouteDataTest { @Test public void testTopicRouteDataClone() throws Exception { TopicRouteData topicRouteData = new TopicRouteData(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-a"); queueData.setPerm(6); queueData.setReadQueueNums(8); queueData.setWriteQueueNums(8); queueData.setTopicSysFlag(0); List queueDataList = new ArrayList<>(); queueDataList.add(queueData); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "192.168.0.47:10911"); brokerAddrs.put(1L, "192.168.0.47:10921"); BrokerData brokerData = new BrokerData(); brokerData.setBrokerAddrs(brokerAddrs); brokerData.setBrokerName("broker-a"); brokerData.setCluster("TestCluster"); List brokerDataList = new ArrayList<>(); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); topicRouteData.setFilterServerTable(new HashMap<>()); topicRouteData.setQueueDatas(queueDataList); assertThat(new TopicRouteData(topicRouteData)).isEqualTo(topicRouteData); } @Test public void testTopicRouteDataJsonSerialize() throws Exception { TopicRouteData topicRouteData = new TopicRouteData(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-a"); queueData.setPerm(6); queueData.setReadQueueNums(8); queueData.setWriteQueueNums(8); queueData.setTopicSysFlag(0); List queueDataList = new ArrayList<>(); queueDataList.add(queueData); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "192.168.0.47:10911"); brokerAddrs.put(1L, "192.168.0.47:10921"); BrokerData brokerData = new BrokerData(); brokerData.setBrokerAddrs(brokerAddrs); brokerData.setBrokerName("broker-a"); brokerData.setCluster("TestCluster"); List brokerDataList = new ArrayList<>(); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); topicRouteData.setFilterServerTable(new HashMap<>()); topicRouteData.setQueueDatas(queueDataList); String topicRouteDataJsonStr = RemotingSerializable.toJson(topicRouteData, true); TopicRouteData topicRouteDataFromJson = RemotingSerializable.fromJson(topicRouteDataJsonStr, TopicRouteData.class); assertThat(topicRouteDataJsonStr).isNotEqualTo(topicRouteDataFromJson); assertThat(topicRouteDataFromJson.getBrokerDatas()).isEqualTo(topicRouteData.getBrokerDatas()); assertThat(topicRouteDataFromJson.getFilterServerTable()).isEqualTo(topicRouteData.getFilterServerTable()); assertThat(topicRouteDataFromJson.getQueueDatas()).isEqualTo(topicRouteData.getQueueDatas()); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.google.common.collect.ImmutableList; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Assert; import org.junit.Test; import java.util.Map; public class TopicQueueMappingTest { @Test public void testJsonSerialize() { LogicQueueMappingItem mappingItem = new LogicQueueMappingItem(1, 2, "broker01", 33333333333333333L, 44444444444444444L, 555555555555555555L, 6666666666666666L, 77777777777777777L); String mappingItemJson = JSON.toJSONString(mappingItem) ; { Map mappingItemMap = JSON.parseObject(mappingItemJson, Map.class); Assert.assertEquals(8, mappingItemMap.size()); Assert.assertEquals(mappingItemMap.get("bname"), mappingItem.getBname()); Assert.assertEquals(mappingItemMap.get("gen"), mappingItem.getGen()); Assert.assertEquals(mappingItemMap.get("logicOffset"), mappingItem.getLogicOffset()); Assert.assertEquals(mappingItemMap.get("startOffset"), mappingItem.getStartOffset()); Assert.assertEquals(mappingItemMap.get("endOffset"), mappingItem.getEndOffset()); Assert.assertEquals(mappingItemMap.get("timeOfStart"), mappingItem.getTimeOfStart()); Assert.assertEquals(mappingItemMap.get("timeOfEnd"), mappingItem.getTimeOfEnd()); } //test the decode encode { LogicQueueMappingItem mappingItemFromJson = RemotingSerializable.fromJson(mappingItemJson, LogicQueueMappingItem.class); Assert.assertEquals(mappingItem, mappingItemFromJson); Assert.assertEquals(mappingItemJson, RemotingSerializable.toJson(mappingItemFromJson, false)); } TopicQueueMappingDetail mappingDetail = new TopicQueueMappingDetail("test", 1, "broker01", System.currentTimeMillis()); TopicQueueMappingDetail.putMappingInfo(mappingDetail, 0, ImmutableList.of(mappingItem)); String mappingDetailJson = JSON.toJSONString(mappingDetail); { Map mappingDetailMap = JSON.parseObject(mappingDetailJson); Assert.assertTrue(mappingDetailMap.containsKey("currIdMap")); Assert.assertEquals(8, mappingDetailMap.size()); Assert.assertEquals(1, ((JSONObject) mappingDetailMap.get("hostedQueues")).size()); } { TopicQueueMappingDetail mappingDetailFromJson = RemotingSerializable.decode(mappingDetailJson.getBytes(), TopicQueueMappingDetail.class); Assert.assertEquals(1, mappingDetailFromJson.getHostedQueues().size()); Assert.assertEquals(1, mappingDetailFromJson.getHostedQueues().get(0).size()); Assert.assertEquals(mappingItem, mappingDetailFromJson.getHostedQueues().get(0).get(0)); Assert.assertEquals(mappingDetailJson, RemotingSerializable.toJson(mappingDetailFromJson, false)); } } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.statictopic; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import org.apache.rocketmq.common.TopicConfig; import org.junit.Assert; import org.junit.Test; public class TopicQueueMappingUtilsTest { private Set buildTargetBrokers(int num) { return buildTargetBrokers(num, ""); } private Set buildTargetBrokers(int num, String suffix) { Set brokers = new HashSet<>(); for (int i = 0; i < num; i++) { brokers.add("broker" + suffix + i); } return brokers; } private Map buildBrokerNumMap(int num) { Map map = new HashMap<>(); for (int i = 0; i < num; i++) { map.put("broker" + i, 0); } return map; } private Map buildBrokerNumMap(int num, int queues) { Map map = new HashMap<>(); int random = new Random().nextInt(num); for (int i = 0; i < num; i++) { map.put("broker" + i, queues); if (i == random) { map.put("broker" + i, queues + 1); } } return map; } private void testIdToBroker(Map idToBroker, Map brokerNumMap) { Map brokerNumOther = new HashMap<>(); for (int i = 0; i < idToBroker.size(); i++) { Assert.assertTrue(idToBroker.containsKey(i)); String broker = idToBroker.get(i); if (brokerNumOther.containsKey(broker)) { brokerNumOther.put(broker, brokerNumOther.get(broker) + 1); } else { brokerNumOther.put(broker, 1); } } Assert.assertEquals(brokerNumMap.size(), brokerNumOther.size()); for (Map.Entry entry: brokerNumOther.entrySet()) { Assert.assertEquals(entry.getValue(), brokerNumMap.get(entry.getKey())); } } @Test public void testAllocator() { //stability for (int i = 0; i < 10; i++) { int num = 3; Map brokerNumMap = buildBrokerNumMap(num); TopicQueueMappingUtils.MappingAllocator allocator = TopicQueueMappingUtils.buildMappingAllocator(new HashMap<>(), brokerNumMap, null); allocator.upToNum(num * 2); for (Map.Entry entry: allocator.getBrokerNumMap().entrySet()) { Assert.assertEquals(2L, entry.getValue().longValue()); } Assert.assertEquals(num * 2, allocator.getIdToBroker().size()); testIdToBroker(allocator.idToBroker, allocator.getBrokerNumMap()); allocator.upToNum(num * 3 - 1); for (Map.Entry entry: allocator.getBrokerNumMap().entrySet()) { Assert.assertTrue(entry.getValue() >= 2); Assert.assertTrue(entry.getValue() <= 3); } Assert.assertEquals(num * 3 - 1, allocator.getIdToBroker().size()); testIdToBroker(allocator.idToBroker, allocator.getBrokerNumMap()); } } @Test public void testRemappingAllocator() { for (int i = 0; i < 10; i++) { int num = (i + 2) * 2; Map brokerNumMap = buildBrokerNumMap(num); Map brokerNumMapBeforeRemapping = buildBrokerNumMap(num, num); TopicQueueMappingUtils.MappingAllocator allocator = TopicQueueMappingUtils.buildMappingAllocator(new HashMap<>(), brokerNumMap, brokerNumMapBeforeRemapping); allocator.upToNum(num * num + 1); Assert.assertEquals(brokerNumMapBeforeRemapping, allocator.getBrokerNumMap()); } } @Test(expected = RuntimeException.class) public void testTargetBrokersComplete() { String topic = "static"; String broker1 = "broker1"; String broker2 = "broker2"; Set targetBrokers = new HashSet<>(); targetBrokers.add(broker1); Map brokerConfigMap = new HashMap<>(); TopicQueueMappingDetail mappingDetail = new TopicQueueMappingDetail(topic, 0, broker2, 0); mappingDetail.getHostedQueues().put(1, new ArrayList<>()); brokerConfigMap.put(broker2, new TopicConfigAndQueueMapping(new TopicConfig(topic, 0, 0), mappingDetail)); TopicQueueMappingUtils.checkTargetBrokersComplete(targetBrokers, brokerConfigMap); } @Test public void testCreateStaticTopic() { String topic = "static"; int queueNum; Map brokerConfigMap = new HashMap<>(); for (int i = 1; i < 10; i++) { Set targetBrokers = buildTargetBrokers(2 * i); Set nonTargetBrokers = buildTargetBrokers(2 * i, "test"); queueNum = 10 * i; TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.createTopicConfigMapping(topic, queueNum, targetBrokers, brokerConfigMap); Assert.assertEquals(wrapper.getBrokerConfigMap(), brokerConfigMap); Assert.assertEquals(2 * i, brokerConfigMap.size()); //do the check manually Map.Entry maxEpochAndNum = TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); Assert.assertEquals(queueNum, maxEpochAndNum.getValue().longValue()); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values())), false, true); TopicQueueMappingUtils.checkIfReusePhysicalQueue(globalIdMap.values()); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); for (Map.Entry entry : brokerConfigMap.entrySet()) { TopicConfigAndQueueMapping configMapping = entry.getValue(); if (nonTargetBrokers.contains(configMapping.getMappingDetail().bname)) { Assert.assertEquals(0, configMapping.getReadQueueNums()); Assert.assertEquals(0, configMapping.getWriteQueueNums()); Assert.assertEquals(0, configMapping.getMappingDetail().getHostedQueues().size()); } else { Assert.assertEquals(5, configMapping.getReadQueueNums()); Assert.assertEquals(5, configMapping.getWriteQueueNums()); Assert.assertTrue(configMapping.getMappingDetail().epoch > System.currentTimeMillis()); for (List items: configMapping.getMappingDetail().getHostedQueues().values()) { for (LogicQueueMappingItem item: items) { Assert.assertEquals(0, item.getStartOffset()); Assert.assertEquals(0, item.getLogicOffset()); TopicConfig topicConfig = brokerConfigMap.get(item.getBname()); Assert.assertTrue(item.getQueueId() < topicConfig.getWriteQueueNums()); } } } } } } @Test public void testRemappingStaticTopic() { String topic = "static"; int queueNum = 7; Map brokerConfigMap = new HashMap<>(); Set originalBrokers = buildTargetBrokers(2); TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.createTopicConfigMapping(topic, queueNum, originalBrokers, brokerConfigMap); Assert.assertEquals(wrapper.getBrokerConfigMap(), brokerConfigMap); Assert.assertEquals(2, brokerConfigMap.size()); { //do the check manually TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values())), false, true); TopicQueueMappingUtils.checkIfReusePhysicalQueue(globalIdMap.values()); } for (int i = 0; i < 10; i++) { Set targetBrokers = buildTargetBrokers(2, "test" + i); TopicQueueMappingUtils.remappingStaticTopic(topic, brokerConfigMap, targetBrokers); //do the check manually TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values())), false, true); TopicQueueMappingUtils.checkIfReusePhysicalQueue(globalIdMap.values()); TopicQueueMappingUtils.checkLeaderInTargetBrokers(globalIdMap.values(), targetBrokers); Assert.assertEquals((i + 2) * 2, brokerConfigMap.size()); //check and complete the logicOffset for (Map.Entry entry : brokerConfigMap.entrySet()) { TopicConfigAndQueueMapping configMapping = entry.getValue(); if (!targetBrokers.contains(configMapping.getMappingDetail().bname)) { continue; } for (List items: configMapping.getMappingDetail().getHostedQueues().values()) { Assert.assertEquals(i + 2, items.size()); items.get(items.size() - 1).setLogicOffset(i + 1); } } } } @Test public void testRemappingStaticTopicStability() { String topic = "static"; int queueNum = 7; Map brokerConfigMap = new HashMap<>(); Set originalBrokers = buildTargetBrokers(2); { TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.createTopicConfigMapping(topic, queueNum, originalBrokers, brokerConfigMap); Assert.assertEquals(wrapper.getBrokerConfigMap(), brokerConfigMap); Assert.assertEquals(2, brokerConfigMap.size()); } for (int i = 0; i < 10; i++) { TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.remappingStaticTopic(topic, brokerConfigMap, originalBrokers); Assert.assertEquals(wrapper.getBrokerConfigMap(), brokerConfigMap); Assert.assertEquals(2, brokerConfigMap.size()); Assert.assertTrue(wrapper.getBrokerToMapIn().isEmpty()); Assert.assertTrue(wrapper.getBrokerToMapOut().isEmpty()); } } @Test public void testUtilsCheck() { String topic = "static"; int queueNum = 10; Map brokerConfigMap = new HashMap<>(); Set targetBrokers = buildTargetBrokers(2); TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.createTopicConfigMapping(topic, queueNum, targetBrokers, brokerConfigMap); Assert.assertEquals(wrapper.getBrokerConfigMap(), brokerConfigMap); Assert.assertEquals(2, brokerConfigMap.size()); TopicConfigAndQueueMapping configMapping = brokerConfigMap.values().iterator().next(); List items = configMapping.getMappingDetail().getHostedQueues().values().iterator().next(); Map.Entry maxEpochNum = TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); int exceptionNum = 0; try { configMapping.getMappingDetail().setTopic("xxxx"); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); } catch (RuntimeException ignore) { exceptionNum++; configMapping.getMappingDetail().setTopic(topic); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); } try { configMapping.getMappingDetail().setTotalQueues(1); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); } catch (RuntimeException ignore) { exceptionNum++; configMapping.getMappingDetail().setTotalQueues(10); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); } try { configMapping.getMappingDetail().setEpoch(0); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); } catch (RuntimeException ignore) { exceptionNum++; configMapping.getMappingDetail().setEpoch(maxEpochNum.getKey()); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); } try { configMapping.getMappingDetail().getHostedQueues().put(10000, new ArrayList<>(Collections.singletonList(new LogicQueueMappingItem(1, 1, targetBrokers.iterator().next(), 0, 0, -1, -1, -1)))); TopicQueueMappingUtils.checkAndBuildMappingItems(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values()), false, true); } catch (RuntimeException ignore) { exceptionNum++; configMapping.getMappingDetail().getHostedQueues().remove(10000); TopicQueueMappingUtils.checkAndBuildMappingItems(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values()), false, true); } try { configMapping.setWriteQueueNums(1); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); } catch (RuntimeException ignore) { exceptionNum++; configMapping.setWriteQueueNums(5); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); } try { items.add(new LogicQueueMappingItem(1, 1, targetBrokers.iterator().next(), 0, 0, -1, -1, -1)); Map map = TopicQueueMappingUtils.checkAndBuildMappingItems(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values()), false, true); TopicQueueMappingUtils.checkIfReusePhysicalQueue(map.values()); } catch (RuntimeException ignore) { exceptionNum++; items.remove(items.size() - 1); Map map = TopicQueueMappingUtils.checkAndBuildMappingItems(TopicQueueMappingUtils.getMappingDetailFromConfig(brokerConfigMap.values()), false, true); TopicQueueMappingUtils.checkIfReusePhysicalQueue(map.values()); } Assert.assertEquals(6, exceptionNum); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import java.util.concurrent.TimeUnit; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class CustomizedRetryPolicyTest { @Test public void testNextDelayDuration() { CustomizedRetryPolicy customizedRetryPolicy = new CustomizedRetryPolicy(); long actual = customizedRetryPolicy.nextDelayDuration(0); assertThat(actual).isEqualTo(TimeUnit.SECONDS.toMillis(10)); actual = customizedRetryPolicy.nextDelayDuration(10); assertThat(actual).isEqualTo(TimeUnit.MINUTES.toMillis(9)); } @Test public void testNextDelayDurationOutOfRange() { CustomizedRetryPolicy customizedRetryPolicy = new CustomizedRetryPolicy(); long actual = customizedRetryPolicy.nextDelayDuration(-1); assertThat(actual).isEqualTo(TimeUnit.SECONDS.toMillis(10)); actual = customizedRetryPolicy.nextDelayDuration(100); assertThat(actual).isEqualTo(TimeUnit.HOURS.toMillis(2)); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import java.util.concurrent.TimeUnit; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ExponentialRetryPolicyTest { @Test public void testNextDelayDuration() { ExponentialRetryPolicy exponentialRetryPolicy = new ExponentialRetryPolicy(); long actual = exponentialRetryPolicy.nextDelayDuration(0); assertThat(actual).isEqualTo(TimeUnit.SECONDS.toMillis(5)); actual = exponentialRetryPolicy.nextDelayDuration(10); assertThat(actual).isEqualTo(TimeUnit.SECONDS.toMillis(1024 * 5)); } @Test public void testNextDelayDurationOutOfRange() { ExponentialRetryPolicy exponentialRetryPolicy = new ExponentialRetryPolicy(); long actual = exponentialRetryPolicy.nextDelayDuration(-1); assertThat(actual).isEqualTo(TimeUnit.SECONDS.toMillis(5)); actual = exponentialRetryPolicy.nextDelayDuration(100); assertThat(actual).isEqualTo(TimeUnit.HOURS.toMillis(2)); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class GroupRetryPolicyTest { @Test public void testGetRetryPolicy() { GroupRetryPolicy groupRetryPolicy = new GroupRetryPolicy(); RetryPolicy retryPolicy = groupRetryPolicy.getRetryPolicy(); assertThat(retryPolicy).isInstanceOf(CustomizedRetryPolicy.class); groupRetryPolicy.setType(GroupRetryPolicyType.EXPONENTIAL); retryPolicy = groupRetryPolicy.getRetryPolicy(); assertThat(retryPolicy).isInstanceOf(CustomizedRetryPolicy.class); groupRetryPolicy.setType(GroupRetryPolicyType.CUSTOMIZED); groupRetryPolicy.setCustomizedRetryPolicy(new CustomizedRetryPolicy()); retryPolicy = groupRetryPolicy.getRetryPolicy(); assertThat(retryPolicy).isInstanceOf(CustomizedRetryPolicy.class); groupRetryPolicy.setType(GroupRetryPolicyType.EXPONENTIAL); groupRetryPolicy.setExponentialRetryPolicy(new ExponentialRetryPolicy()); retryPolicy = groupRetryPolicy.getRetryPolicy(); assertThat(retryPolicy).isInstanceOf(ExponentialRetryPolicy.class); groupRetryPolicy.setType(null); retryPolicy = groupRetryPolicy.getRetryPolicy(); assertThat(retryPolicy).isInstanceOf(CustomizedRetryPolicy.class); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/SimpleSubscriptionDataTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.subscription; import com.google.common.collect.Sets; import java.util.Set; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleSubscriptionDataTest { @Test public void testNotEqual() { String topic = "test-topic"; String expressionType = "TAG"; String expression1 = "test-expression-1"; String expression2 = "test-expression-2"; SimpleSubscriptionData simpleSubscriptionData1 = new SimpleSubscriptionData(topic, expressionType, expression1, 1); SimpleSubscriptionData simpleSubscriptionData2 = new SimpleSubscriptionData(topic, expressionType, expression2, 1); assertThat(simpleSubscriptionData1.equals(simpleSubscriptionData2)).isFalse(); } @Test public void testEqual() { String topic = "test-topic"; String expressionType = "TAG"; String expression1 = "test-expression-1"; String expression2 = "test-expression-1"; SimpleSubscriptionData simpleSubscriptionData1 = new SimpleSubscriptionData(topic, expressionType, expression1, 1); SimpleSubscriptionData simpleSubscriptionData2 = new SimpleSubscriptionData(topic, expressionType, expression2, 1); assertThat(simpleSubscriptionData1.equals(simpleSubscriptionData2)).isTrue(); } @Test public void testSetNotEqual() { String topic = "test-topic"; String expressionType = "TAG"; String expression1 = "test-expression-1"; String expression2 = "test-expression-2"; Set set1 = Sets.newHashSet(new SimpleSubscriptionData(topic, expressionType, expression1, 1)); Set set2 = Sets.newHashSet(new SimpleSubscriptionData(topic, expressionType, expression2, 1)); assertThat(set1.equals(set2)).isFalse(); } @Test public void testSetEqual() { String topic = "test-topic"; String expressionType = "TAG"; String expression1 = "test-expression-1"; String expression2 = "test-expression-1"; Set set1 = Sets.newHashSet(new SimpleSubscriptionData(topic, expressionType, expression1, 1)); Set set2 = Sets.newHashSet(new SimpleSubscriptionData(topic, expressionType, expression2, 1)); assertThat(set1.equals(set2)).isTrue(); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEventTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.protocol.topic; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class OffsetMovedEventTest { @Test public void testFromJson() throws Exception { OffsetMovedEvent event = mockOffsetMovedEvent(); String json = event.toJson(); OffsetMovedEvent fromJson = RemotingSerializable.fromJson(json, OffsetMovedEvent.class); assertEquals(event, fromJson); } @Test public void testFromBytes() throws Exception { OffsetMovedEvent event = mockOffsetMovedEvent(); byte[] encodeData = event.encode(); OffsetMovedEvent decodeData = RemotingSerializable.decode(encodeData, OffsetMovedEvent.class); assertEquals(event, decodeData); } private void assertEquals(OffsetMovedEvent srcData, OffsetMovedEvent decodeData) { assertThat(decodeData.getConsumerGroup()).isEqualTo(srcData.getConsumerGroup()); assertThat(decodeData.getMessageQueue().getTopic()) .isEqualTo(srcData.getMessageQueue().getTopic()); assertThat(decodeData.getMessageQueue().getBrokerName()) .isEqualTo(srcData.getMessageQueue().getBrokerName()); assertThat(decodeData.getMessageQueue().getQueueId()) .isEqualTo(srcData.getMessageQueue().getQueueId()); assertThat(decodeData.getOffsetRequest()).isEqualTo(srcData.getOffsetRequest()); assertThat(decodeData.getOffsetNew()).isEqualTo(srcData.getOffsetNew()); } private OffsetMovedEvent mockOffsetMovedEvent() { OffsetMovedEvent event = new OffsetMovedEvent(); event.setConsumerGroup("test-group"); event.setMessageQueue(new MessageQueue("test-topic", "test-broker", 0)); event.setOffsetRequest(3000L); event.setOffsetNew(1000L); return event; } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/rpc/ClientMetadataTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class ClientMetadataTest { private ClientMetadata clientMetadata; private final ConcurrentMap topicRouteTable = new ConcurrentHashMap<>(); private final ConcurrentMap> topicEndPointsTable = new ConcurrentHashMap<>(); private final ConcurrentMap> brokerAddrTable = new ConcurrentHashMap<>(); private final String defaultTopic = "defaultTopic"; private final String defaultBroker = "defaultBroker"; @Before public void init() throws IllegalAccessException { clientMetadata = new ClientMetadata(); FieldUtils.writeDeclaredField(clientMetadata, "topicRouteTable", topicRouteTable, true); FieldUtils.writeDeclaredField(clientMetadata, "topicEndPointsTable", topicEndPointsTable, true); FieldUtils.writeDeclaredField(clientMetadata, "brokerAddrTable", brokerAddrTable, true); } @Test public void testGetBrokerNameFromMessageQueue() { MessageQueue mq1 = new MessageQueue(defaultTopic, "broker0", 0); MessageQueue mq2 = new MessageQueue(defaultTopic, "broker1", 0); ConcurrentMap messageQueueMap = new ConcurrentHashMap<>(); messageQueueMap.put(mq1, "broker0"); messageQueueMap.put(mq2, "broker1"); topicEndPointsTable.put(defaultTopic, messageQueueMap); String actual = clientMetadata.getBrokerNameFromMessageQueue(mq1); assertEquals("broker0", actual); } @Test public void testGetBrokerNameFromMessageQueueNotFound() { MessageQueue mq = new MessageQueue("topic1", "broker0", 0); topicEndPointsTable.put(defaultTopic, new ConcurrentHashMap<>()); String actual = clientMetadata.getBrokerNameFromMessageQueue(mq); assertEquals("broker0", actual); } @Test public void testFindMasterBrokerAddrNotFound() { assertNull(clientMetadata.findMasterBrokerAddr(defaultBroker)); } @Test public void testFindMasterBrokerAddr() { String defaultBrokerAddr = "127.0.0.1:10911"; brokerAddrTable.put(defaultBroker, new HashMap<>()); brokerAddrTable.get(defaultBroker).put(0L, defaultBrokerAddr); String actual = clientMetadata.findMasterBrokerAddr(defaultBroker); assertEquals(defaultBrokerAddr, actual); } @Test public void testTopicRouteData2EndpointsForStaticTopicNotFound() { TopicRouteData topicRouteData = new TopicRouteData(); topicRouteData.setTopicQueueMappingByBroker(null); ConcurrentMap actual = ClientMetadata.topicRouteData2EndpointsForStaticTopic(defaultTopic, topicRouteData); assertTrue(actual.isEmpty()); } @Test public void testTopicRouteData2EndpointsForStaticTopic() { TopicRouteData topicRouteData = new TopicRouteData(); Map mappingInfos = new HashMap<>(); TopicQueueMappingInfo info = new TopicQueueMappingInfo(); info.setScope("scope"); info.setCurrIdMap(new ConcurrentHashMap<>()); info.getCurrIdMap().put(0, 0); info.setTotalQueues(1); info.setBname("bname"); mappingInfos.put(defaultBroker, info); topicRouteData.setTopicQueueMappingByBroker(mappingInfos); ConcurrentMap actual = ClientMetadata.topicRouteData2EndpointsForStaticTopic(defaultTopic, topicRouteData); assertEquals(1, actual.size()); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/rpc/RpcClientImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetResponseHeader; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.concurrent.Future; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class RpcClientImplTest { @Mock private RemotingClient remotingClient; @Mock private ClientMetadata clientMetadata; private RpcClientImpl rpcClient; private MessageQueue mq; @Mock private RpcRequest request; private final long defaultTimeout = 3000L; @Before public void init() throws IllegalAccessException { rpcClient = new RpcClientImpl(clientMetadata, remotingClient); String defaultBroker = "brokerName"; mq = new MessageQueue("defaultTopic", defaultBroker, 0); RpcRequestHeader header = mock(RpcRequestHeader.class); when(request.getHeader()).thenReturn(header); when(clientMetadata.getBrokerNameFromMessageQueue(mq)).thenReturn(defaultBroker); when(clientMetadata.findMasterBrokerAddr(any())).thenReturn("127.0.0.1:10911"); } @Test public void testInvoke_PULL_MESSAGE() throws Exception { when(request.getCode()).thenReturn(RequestCode.PULL_MESSAGE); doAnswer(invocation -> { InvokeCallback callback = invocation.getArgument(3); RemotingCommand response = mock(RemotingCommand.class); when(response.getBody()).thenReturn("success".getBytes()); PullMessageResponseHeader responseHeader = mock(PullMessageResponseHeader.class); when(response.decodeCommandCustomHeader(PullMessageResponseHeader.class)).thenReturn(responseHeader); callback.operationSucceed(response); return null; }).when(remotingClient).invokeAsync( any(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("success", new String((byte[]) actual.getBody())); } @Test public void testInvoke_GET_MIN_OFFSET() throws Exception { when(request.getCode()).thenReturn(RequestCode.GET_MIN_OFFSET); RemotingCommand responseCommand = mock(RemotingCommand.class); when(responseCommand.getBody()).thenReturn("1".getBytes()); GetMinOffsetResponseHeader responseHeader = mock(GetMinOffsetResponseHeader.class); when(responseCommand.decodeCommandCustomHeader(GetMinOffsetResponseHeader.class)).thenReturn(responseHeader); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("1", new String((byte[]) actual.getBody())); } @Test public void testInvoke_GET_MAX_OFFSET() throws Exception { when(request.getCode()).thenReturn(RequestCode.GET_MAX_OFFSET); RemotingCommand responseCommand = mock(RemotingCommand.class); when(responseCommand.getBody()).thenReturn("1000".getBytes()); GetMaxOffsetResponseHeader responseHeader = mock(GetMaxOffsetResponseHeader.class); when(responseCommand.decodeCommandCustomHeader(GetMaxOffsetResponseHeader.class)).thenReturn(responseHeader); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("1000", new String((byte[]) actual.getBody())); } @Test public void testInvoke_SEARCH_OFFSET_BY_TIMESTAMP() throws Exception { when(request.getCode()).thenReturn(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP); RemotingCommand responseCommand = mock(RemotingCommand.class); when(responseCommand.getBody()).thenReturn("1000".getBytes()); SearchOffsetResponseHeader responseHeader = mock(SearchOffsetResponseHeader.class); when(responseCommand.decodeCommandCustomHeader(SearchOffsetResponseHeader.class)).thenReturn(responseHeader); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("1000", new String((byte[]) actual.getBody())); } @Test public void testInvoke_GET_EARLIEST_MSG_STORETIME() throws Exception { when(request.getCode()).thenReturn(RequestCode.GET_EARLIEST_MSG_STORETIME); RemotingCommand responseCommand = mock(RemotingCommand.class); when(responseCommand.getBody()).thenReturn("10000".getBytes()); GetEarliestMsgStoretimeResponseHeader responseHeader = mock(GetEarliestMsgStoretimeResponseHeader.class); when(responseCommand.decodeCommandCustomHeader(GetEarliestMsgStoretimeResponseHeader.class)).thenReturn(responseHeader); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("10000", new String((byte[]) actual.getBody())); } @Test public void testInvoke_QUERY_CONSUMER_OFFSET() throws Exception { when(request.getCode()).thenReturn(RequestCode.QUERY_CONSUMER_OFFSET); RemotingCommand responseCommand = mock(RemotingCommand.class); when(responseCommand.getBody()).thenReturn("1000".getBytes()); QueryConsumerOffsetResponseHeader responseHeader = mock(QueryConsumerOffsetResponseHeader.class); when(responseCommand.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class)).thenReturn(responseHeader); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("1000", new String((byte[]) actual.getBody())); } @Test public void testInvoke_UPDATE_CONSUMER_OFFSET() throws Exception { when(request.getCode()).thenReturn(RequestCode.UPDATE_CONSUMER_OFFSET); RemotingCommand responseCommand = mock(RemotingCommand.class); when(responseCommand.getBody()).thenReturn("success".getBytes()); UpdateConsumerOffsetResponseHeader responseHeader = mock(UpdateConsumerOffsetResponseHeader.class); when(responseCommand.decodeCommandCustomHeader(UpdateConsumerOffsetResponseHeader.class)).thenReturn(responseHeader); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertEquals("success", new String((byte[]) actual.getBody())); } @Test public void testInvoke_GET_TOPIC_STATS_INFO() throws Exception { when(request.getCode()).thenReturn(RequestCode.GET_TOPIC_STATS_INFO); RemotingCommand responseCommand = mock(RemotingCommand.class); TopicStatsTable topicStatsTable = new TopicStatsTable(); when(responseCommand.getBody()).thenReturn(topicStatsTable.encode()); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertTrue(actual.getBody() instanceof TopicStatsTable); } @Test public void testInvoke_GET_TOPIC_CONFIG() throws Exception { when(request.getCode()).thenReturn(RequestCode.GET_TOPIC_CONFIG); RemotingCommand responseCommand = mock(RemotingCommand.class); TopicConfigAndQueueMapping topicConfigAndQueueMapping = new TopicConfigAndQueueMapping(); when(responseCommand.getBody()).thenReturn(RemotingSerializable.encode(topicConfigAndQueueMapping)); when(remotingClient.invokeSync(any(), any(RemotingCommand.class), anyLong())).thenReturn(responseCommand); Future future = rpcClient.invoke(mq, request, defaultTimeout); RpcResponse actual = future.get(); assertEquals(ResponseCode.SUCCESS, actual.getCode()); assertTrue(actual.getBody() instanceof TopicConfigAndQueueMapping); } } ================================================ FILE: remoting/src/test/java/org/apache/rocketmq/remoting/rpc/RpcRequestHeaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.remoting.rpc; import java.nio.ByteBuffer; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class RpcRequestHeaderTest { String brokerName = "brokerName1"; String namespace = "namespace1"; boolean namespaced = true; boolean oneway = false; static class TestRequestHeader extends RpcRequestHeader { @Override public void checkFields() throws RemotingCommandException { } } @Test public void testEncodeDecode() throws RemotingCommandException { TestRequestHeader requestHeader = new TestRequestHeader(); requestHeader.setBrokerName(brokerName); requestHeader.setNamespace(namespace); requestHeader.setNamespaced(namespaced); requestHeader.setOneway(oneway); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); ByteBuffer buffer = remotingCommand.encode(); //Simulate buffer being read in NettyDecoder buffer.getInt(); byte[] bytes = new byte[buffer.limit() - 4]; buffer.get(bytes, 0, buffer.limit() - 4); buffer = ByteBuffer.wrap(bytes); RemotingCommand decodeRequest = RemotingCommand.decode(buffer); assertThat(decodeRequest.getExtFields().get("bname")).isEqualTo(brokerName); assertThat(decodeRequest.getExtFields().get("nsd")).isEqualTo(String.valueOf(namespaced)); assertThat(decodeRequest.getExtFields().get("ns")).isEqualTo(namespace); assertThat(decodeRequest.getExtFields().get("oway")).isEqualTo(String.valueOf(oneway)); } } ================================================ FILE: remoting/src/test/resources/certs/badClient.key ================================================ -----BEGIN ENCRYPTED PRIVATE KEY----- MIICoTAbBgkqhkiG9w0BBQMwDgQIc2h7vaLYK6gCAggABIICgNrkvD1Xxez79Jgk WhRJg06CG8UthncfeuymR4hgp9HIneUzUHOoaf64mpxUbDWe3YOzA29REcBQsjF0 Rpv+Uyg3cyDG14TmeRoSufOxB3MWLcIenoPPyNNtxe3XXmdkJTXX2YR0j7EOzH2v qlmuxmN4A7UonV5RdGxCz0sm7bU7EyZKdLO/DwBNxlX7ukcVLxAAqsc7ondclYj0 SFJKk1nzfysCsk/Pq+q3PAVVpG6x5RFaLVS7Zt+gU6IEp+0S0eeYukkTjGh9PMPl wjCOcRiR3O+g4b3DevmW8TcoBqAZ2cFaf4lGhYlNBfa9PaQ3spJLL8l8xBbRIs8T 3UnaFIa49r9DO/ZpCwpDeUE+URCx/SpcO6lchWQhdEuFt+DnFKOPYDSCHtHJSWHf 9Z2bltjcYYPy/8nkPeqsO9vn4/r6jo+l7MYWKyWolLCW+7RYbpx5R2s4SBGtBP6w bwQOtOASbpG+mqTf7+ARpffHaZm9cKoKwobXigjDojPeaBCg5DgRuLIS1tO46Pjg USJ8sZilXifUwc6qRZ/2KiTSiJYCPMJD2ZTvK2Inkv2qzg6X3kw7CYCaW+iDL9zN e3ES7bps1wZ6D8cGq80WUQgrtpaGAXLzIv4FvM5yDoqrre/dh/XDO9l2hYfUmRVv rynKdSxjhhyHaK2ei8cX4LGEIlRNiu9ZIxSYeUAy37IJ0rVC7vtBWTh30JTeMRop iIPmygBMX2FEhQ2l/eS2lRhiybR0QXA4kCeJkVQas3aMMBGp2ThPNahLpzP82B7V f9137okQC95/KXRz/ZLYFsJtY/53206mG7gU/+dYsYI4slLAlnSe8k2sS0D9qkWJ VV9F7PM= -----END ENCRYPTED PRIVATE KEY----- ================================================ FILE: remoting/src/test/resources/certs/badClient.pem ================================================ -----BEGIN CERTIFICATE----- MIIC8zCCAdsCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw YWNoZS5vcmcwHhcNMTcxMjExMDk0NDExWhcNMTgwMTEwMDk0NDExWjCBhjELMAkG A1UEBhMCemgxCzAJBgNVBAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBh Y2hlMREwDwYDVQQLDAhyb2NrZXRtcTEWMBQGA1UEAwwNZm9vYmFyLmNsaWVudDEh MB8GCSqGSIb3DQEJARYSZm9vQGJhci5jbGllbnQuY29tMIGfMA0GCSqGSIb3DQEB AQUAA4GNADCBiQKBgQC+3bvrKGF1Y9/kN5UBtf8bXRtxn6L1W6mCRrX6aHBb+vQp BEYk3Pwu/OLd7TkOC5zwjCIPIlwV4FaYnWh0KooqpmvXuKJLAQBFa8yGWERYys73 9a/U31cu6lndnG2lZfb47NTy+KdzDYsqB4GfnASqA7PbxJHDU4Fu7wp7gN3HRQID AQABMA0GCSqGSIb3DQEBBQUAA4IBAQBsFroSKr3MbCq1HjWpCLDEz2uS4LQV6L1G smNfGNY17ELOcY9uweBBXOsfKVOEizYJJqatbJlz6FmPkIbfsGW2Wospkp1gvYMy NGL27vX3rB5vOo5vdFITaaV9/dEu53A0iWdsn3wH/FJnMsqBmynb+/3FY+Lff9d1 XBaXLr+DeBx4lrE8rWTvhWh8gqDkuNLBTygdH0+g8/xkqhQhLqjIlMCSnrG2cTfj LewizVcX/VZ6DNC2M2vjEFbCShclZHocG80N7udl5KNsLEU2jyO1F61Q0yo+VYGS 7n8dRYgbOKyCjMdu69fAfZvp4aoy1SXqtjMphDh5R7y7mhP60e0A -----END CERTIFICATE----- ================================================ FILE: remoting/src/test/resources/certs/badServer.key ================================================ -----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALBvxESq2VvSpJl1 skv8SzyPYKgU8bZx37hEOCmoeYvd9gWNfeYZuITng2/5mpWX+zuAgKsgPU66YG0v ++dT5GBQPr0Imb25IMl3xOY2eEiLeMokYiWbnA1C+pw1a27zMqk6pgbcRaMfLdh5 npusWtqBzZIxqo1TpaOGEmyQTNRlAgMBAAECgYBSigbUZOTIWxObov7lI0MDMsPx /dJSGpWhe3CWtHUgJJdKY7XpJlE3A6Nuh+N0ZiQm4ufOpodnxDMGAXOj9ZAZY16Y i7I0ayXepcpTqYqo0o0+ze2x7SECAXe26bqvLRuKG2hpUyM59vAmll9gmQM5n8z4 ZzoAzqRqkRHdo5bTxQJBAOF6SwSSfb8KEtTjWpJ48W1PO/NmKbW3QsNCWuk/w5p7 E8L2g3nwakJiFmVNCga74rUbcgbCkw7y/lLeM8yC74MCQQDIUgCN/vuHm+eT85xk QoVKhDljXzLoog6wTUf5SMtrmUFTbQqyvw5xjHYdp3TWJM/Px8IyLxOr97sSnnft l7/3AkEAukYLv6U+GRs7X4DMDIG6AjIZNwXJo4PYtfMVo+i3seHH+6MoDw8c2eaq 1dmFVPbXXgNkek04rHr2vIMxi90H/QJAAMOfUOtaFkhX986EGDXQwFoExgZE8XI8 0BtbXO4UKJLrFuBhnBDygyhgAvjyjyaQzGAcs4hOcOd/BTEpj/R2PQJBANUKa9T9 qWYhDhWN1Uj7qXhC1j2z/vTAzcYuwhpPRjt3RaVl27itI7cqiGquFhwfKZZFaOh5 pnnWHv63YbGQ2Qc= -----END PRIVATE KEY----- ================================================ FILE: remoting/src/test/resources/certs/badServer.pem ================================================ -----BEGIN CERTIFICATE----- MIIC5DCCAcwCAQEwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw YWNoZS5vcmcwHhcNMTcxMjExMDk0MzE3WhcNMTgwMTEwMDk0MzE3WjB4MQswCQYD VQQGEwJ6aDELMAkGA1UECAwCemoxCzAJBgNVBAcMAmh6MQ8wDQYDVQQKDAZhcGFj aGUxETAPBgNVBAsMCHJvY2tldG1xMQ8wDQYDVQQDDAZmb29iYXIxGjAYBgkqhkiG 9w0BCQEWC2Zvb0BiYXIuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw b8REqtlb0qSZdbJL/Es8j2CoFPG2cd+4RDgpqHmL3fYFjX3mGbiE54Nv+ZqVl/s7 gICrID1OumBtL/vnU+RgUD69CJm9uSDJd8TmNnhIi3jKJGIlm5wNQvqcNWtu8zKp OqYG3EWjHy3YeZ6brFragc2SMaqNU6WjhhJskEzUZQIDAQABMA0GCSqGSIb3DQEB BQUAA4IBAQAx+0Se3yIvUOe23oQp6UecaHtfXJCZmi1p5WbwJi7jUcYz78JB8oBj tVsa+1jftJG+cJJxqgxo2IeIAVbcEteO19xm7dc8tgfH/Bl0rxQz4WEYKb2oF/EQ eRgcvj4uZ0d9WuprAvJgA4r0Slu2ZZ0cVkzi06NevTweTBYIKFzHaPShqUWEw8ki 42V5jAtRve7sT0c4TH/01dd2fs3V4Ul3E2U3LOP6VizIfKckdht0Bh6B6/5L8wvH 4l1f4ni7w34vXGANpmTP2FGjQQ3kYjKL7GzgMphh3Kozhil6g1GLMhxvp6ccCA9W m5g0cPa3RZnjI/FoD0lZ5S1Q5s9qXbLm -----END CERTIFICATE----- ================================================ FILE: remoting/src/test/resources/certs/ca.pem ================================================ -----BEGIN CERTIFICATE----- MIIDyzCCArOgAwIBAgIJAKzXC2VLdPclMA0GCSqGSIb3DQEBBQUAMHwxCzAJBgNV BAYTAnpoMQswCQYDVQQIDAJ6ajELMAkGA1UEBwwCaHoxDzANBgNVBAoMBmFwYWNo ZTERMA8GA1UECwwIcm9ja2V0bXExDjAMBgNVBAMMBXl1a29uMR8wHQYJKoZIhvcN AQkBFhB5dWtvbkBhcGFjaGUub3JnMB4XDTE3MTIxMTA5MjUxNFoXDTE4MDExMDA5 MjUxNFowfDELMAkGA1UEBhMCemgxCzAJBgNVBAgMAnpqMQswCQYDVQQHDAJoejEP MA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhyb2NrZXRtcTEOMAwGA1UEAwwFeXVr b24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDLUZi/zPj+7sYbfTng/gJeHpvvrWZkiudNwh1t 5kxAusrJyGBkGm+xmRPJeQPZzbhfwfrz/UiQSbjlyV4K+SEZuNIHBSU80aTnXFWg wIgIAKvu3ZwYkcTjSDBvZv1DgbRkuqAB5ExsJ4vovoNqZcsLFLKsqT1G7lTAwRKU /FTKgD4g/zvhEoolonzKuk7CPivfKWFzcTpe8zRQlI0O9+j9Pq38F+5yxP7atK/b uYw36Efgt8nbkjusWIyXibpDMbAUroJNNYlFnunb+XKLpslkrIrfLGiMUq2Ru940 ooQaANYWzogRQeIofsMN6H9CCRXtVIzcgJJU3wWXGXPRuNr7AgMBAAGjUDBOMB0G A1UdDgQWBBTd3bmAcazOY2/TI/h4zaGhni+nJzAfBgNVHSMEGDAWgBTd3bmAcazO Y2/TI/h4zaGhni+nJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBp KRcnYsVtFZJejyt02+7SaMudTNRgh5SexFWsl1O7qWUc+fMVgMHHDzGRbkdevcdZ 9uKDwUoa6M1wlOeosTTfQlH9b/AwW6QT7KqdpcpMXlmoV/PNCAVt2QeVclmplvqo Rx8qUHNckvvzNZt1W6AkBG93P0BLK/3FMJDyYmxkstwnpBPf/3A+t5k2INUI7yQf B3Tqzs/4iQ3idCLqz2WhTNUYpZOREtpJMcFaOdMsGNnIF+LvkKGij0MPVd/mwJtL UvQXwbOWpCS7A73sWFqPnrSzpi4VwcvAsi8lUYXsc0H064oagb58zvYz3kXqybcb KQntj5dP4C3lLHUTTcAV -----END CERTIFICATE----- ================================================ FILE: remoting/src/test/resources/certs/client.key ================================================ -----BEGIN ENCRYPTED PRIVATE KEY----- MIICoTAbBgkqhkiG9w0BBQMwDgQI1vtPpDhOYRcCAggABIICgMHwgw0p9fx95R/+ cWnNdEq8I3ZOOy2wDjammFvPrYXcCJzS3Xg/0GDJ8pdJRKrI7253e4u3mxf5oMuY RrvpB3KfdelU1k/5QKqOxL/N0gQafQLViN53f6JelyBEAmO1UxQtKZtkTrdZg8ZP 0u1cPPWxmgNdn1Xx3taMw+Wo05ysHjnHJhOEDQ2WT3VXigiRmFSX3H567yjYMRD+ zmvBq+qqR9JPbH9Cn7X1oRXX6c8VsZHWF/Ds0I4i+5zJxsSIuNZxjZw9XXNgXtFv 7FEFC0HDgDQQUY/FNPUbmjQUp1y0YxoOBjlyIqBIx5FWxu95p2xITS0OimQPFT0o IngaSb+EKRDhqpLxxIVEbDdkQrdRqcmmLGJioAysExTBDsDwkaEJGOp44bLDM4QW SIA9SB01omuCXgn7RjUyVXb5g0Lz+Nvsfp1YXUkPDO9hILfz3eMHDSW7/FzbB81M r8URaTagQxBZnvIoCoWszLDXn3JwEjpZEA6y55Naptps3mMRf7+XMt42lX0e4y9a ogNu5Zw/RZD9YcaTjC2z5XeKiMCs1Ymhy9iuzbo+eRGESqzvUE4VirtsiEwxJRci JHAvuAl3X4XnpTty4ahOU+DihM9lALxdU68CN9++7mx581pYuvjzrV+Z5+PuptZX AjCZmkZLDh8TCHSzWRqvP/Hcvo9BjW8l1Lq6tOa222PefSNCc6gs6Hq+jUghbabZ /ux4WuFc0Zd6bfQWAZohSvd78/ixsdJPNGm2OP+LUIrEDKIkLuH1PM0uq4wzJZNu Bo7oJ5iFWF67u3MC8oq+BqOVKDNWaCMi7iiSrN2XW8FBo/rpx4Lf/VYREL+Y0mP6 vzJrZqw= -----END ENCRYPTED PRIVATE KEY----- ================================================ FILE: remoting/src/test/resources/certs/client.pem ================================================ -----BEGIN CERTIFICATE----- MIIDATCCAekCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw YWNoZS5vcmcwIBcNMTgwMTE2MDYxNjQ0WhgPMjExNzEyMjMwNjE2NDRaMIGSMQsw CQYDVQQGEwJDTjERMA8GA1UECAwIWmhlamlhbmcxETAPBgNVBAcMCEhhbmd6aG91 MQ8wDQYDVQQKDAZhcGFjaGUxETAPBgNVBAsMCHJvY2tldG1xMRgwFgYDVQQDDA9h cGFjaGUgcm9ja2V0bXExHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcw gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjPlSjZk37XLBJBc5G/qQNsNdVD vZnEGntrqW0UuHjF2T/LPtsGOavLP5wCHvn2zwMR2eCXZwKdKIzSvk0L3XOjH/XY OLgRa3cg90lV7Wzn9UMGq3nOjFtjIODPjtz3lwYAuAt1MH+K0E+ChuCFBgFqdY9U E0suW3DX0Mt/WB3pAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFGPaZKyCZzQihKj n/7I1J0wKl1HrU7N4sOie8E+ntcpKeX9zKYAou/4Iy0qwgxgRsnucB1rDous560a +8DFDU8+FnikK9cQtKfQqu4F266IkkXolviZMSfkmB+NIsByIl95eMJlQHVlAvnX vnpGdhD/Jhs+acE1VHhO6K+8omKLA6Og8MmYGRwmnBLcxIvqoSNDlEShfQyjaECg I4bEi4ZhH3lSHE46FybJdoxDbj9IjHWqpOnjM23EOyfd1zcwOZJA7a54kfOpiTjz wrtes5yoQznun5WtGcLM8ZmyaQ+Jr3j6NyZhOwULzK1+A8YUsW6Ww39xTxQoIHEQ 7eirb54= -----END CERTIFICATE----- ================================================ FILE: remoting/src/test/resources/certs/privkey.pem ================================================ -----BEGIN ENCRYPTED PRIVATE KEY----- MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIBTqUKpwFlcUCAggA MBQGCCqGSIb3DQMHBAii5M3Oni0WEwSCBMj5amhdPBva0QxgsWWrSBkfvmc3QQbl YQE5gHXzL5oaG0lbXCLQe19pr9jFckdDH8uPIi+aRie3WpZXzYLdihjsV0CgE7iy 90ac9fgzIZbJIk/WQIDgwUZm5dEYo2v+B0WwKiD5IHzlTlXM6HVv+DRdPwKjHjAt TCcCSPUgjKVxHWFtQzY7mqo8P8wcNQHGkEfoQQub6tEsUDeesjS0FoK5Z2oYsmhW d0PNuGXw3UIMbG109DmC2ILFuTf5WSc7mxI11qL9Z5wTmcFqN7KKb8+MIQEoteni HICOFfKQWn8er14lmYw9anQAyaeyF/JnYkmVB8vaHBFYs/5EFZtvznpEIIhLKCuG lve8PJQmfWuBlPdwwJhCXHrLvjfwku4jUF8febU0BHZ5HETaB195g8r9RWfdZcrG f3fMO4Kq/YoP6oSxKhMP4L2pwj57EMV9N5P87ZyDFNp9BwgIjCawDRUc1Gi9YKak rpDNabTCr0I3NW27VGGF9m/mby7BLragc01LgTH7SFWS+1D/61/V2YBDFmWn2yV4 4eKGeBkR3w0m/nWWfNXko8UzM/hjJ4P7Njq8HXdvEpnbDRZWzwdTGWTEvn/TAI3h j7vmWUHdpOQgb0WGlvEUx3V9wi2Fc1rCseHtYZgLf3KdKYHauPAMSON7KBtKaFuU 6685sUoJbhahN7ILfP3sDxM3VYjSvlPL00lgOdqDT/iO6pNXvnNnQROCCE4kcOQT uSnEu+wmFHj+QlC60ftRl6zGVqjBxf1+TGmzTEByAOfZtEQ8V/clzRI4BCxYbCAG mJSa+q1RSju8yClBkXGT2zfhUeNqJnXEIaD/uXCPVGg7hfLyCcVVSmL97aw9QAIe lBJN/4bdxXLnJaHFKyztRe9N97JAKY9HAPMKKhKtqprWB7LedTIPHtXnpoSjyTrG SEtlOTQ38s3v9bUPXqF7TYZb+ytj5bIQpl6+WqF9ZNj3gRyx7rcsILhBBg08olVQ WZwr7LlIxUcDlrbYmrwd9lsMz2nOW2CLCD7mVqJQa2Wm35l6vJHQAI0WiQlHnopC M2Y49JruWWim2lC8ZzHgTyiU54bIfkXKQxua8G5WGxpW4dDRrM1d0uYe9M1TOqvP jFxq+XEIj/LntJpY7XIZs+33wuNLIVvkee9zsap++zYNH+KIGmbXz/HUO6gYeJYw EeaBTLfXtNlgHV9TpMjj3Js6p8hMoVDx27kzPOXa3nrFbHiHmUYY39ZSBCE53Wew SKIr/FlKtthzJwJkoMNxxsZ1QcI6WgmRANSC4oP9OyM+hPnWlISJZsy9UlXRZNEJ 1lI8P/FUbdk/hsRN98j4pb/hzI0yyG1tKaj0TBipjdEsxfthKdwS2sE1wG+F2hrg A1jG+UOYmreQjCbrzEiq0J0H4wSL6/s/zN7SyXIWBG0UFalWFiehG5242mEOyX03 0Yi94mPhF/kcqYsWZ/JJo6cq/EqgIeIqEzkbKx+TOsXk13K2y6apgvCxeHDDv3yT DqQueRgFicl0319yEK8ARnREFBm8D5oqwMHjJzVzjrqhFGLW1jfQG9HEW5A+s8WF d+2OtH1o/jVdAPXoP1DnxdF+G7fNXDI4cyjejC7uhLuxHCOx648UpRE9+mCiI2IO LDM= -----END ENCRYPTED PRIVATE KEY----- ================================================ FILE: remoting/src/test/resources/certs/server.key ================================================ -----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOsmp4YtrIRsBdBQ LyPImafCRynTJls3NNF4g6nZr9e0efBY830gw9kBebcm603sdZNl95fzRr2+srXi 5FJbG7Fmq1+F0xLNK/kKWirGtNMT2DubmhVdKyXYJSvInoGRkrQzbOG0MdAyzE6Q O6OjjNN+xGkmadWyCyNF6S8YqMJTAgMBAAECgYEAj0OlnOIG0Ube4+N2VN7KfqKm qJy0Ka6gx14dGUY/E7Qo9n27GujzaSq09RkJExiVKZBeIH1fBAtC5f2uDV7kpy0l uNpTpQkbw0g2EQLxDsVwaUEYbu+t9qVeXoDd1vFeoXHBuRwvI9UW1BrxVtvKODia 5StU8Lw4yjcm2lQalwECQQD/sKj56thIsIY7D9qBHk7fnFLd8aYzhnP2GsbZX4V/ T1KHRxr/8MqdNQX53DE5qcyM/Mqu95FIpTAniUtvcBujAkEA62+fAMYFTAEWj4Z4 vCmcoPqfVPWhBKFR/wo3L8uUARiIzlbYNU3LIqC2s16QO50+bLUd41oVHNw9Y+uM fxQpkQJACg/WpncSadHghmR6UchyjCQnsqo2wyJQX+fv2VAD/d2OPtqSem3sW0Fh 6dI7cax36zhrdXUyl2xAt92URV9hBwJALX93sdWSxnpbWsc449wCydVFH00MnfFz AB+ARLtJ0eBk58M+qyZqgDmgtQ8sPmkH3EgwC3SoKdiiAIJPt2s1EQJBAKnISZZr qB2F2PfAW2JJbQlrPyVzkxhv9XYdiVNOErmuxLFae3AI7nECgGuFBtvmeqzm2yRj 7RBMCmzyWG7MF3o= -----END PRIVATE KEY----- ================================================ FILE: remoting/src/test/resources/certs/server.pem ================================================ -----BEGIN CERTIFICATE----- MIIDATCCAekCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw YWNoZS5vcmcwIBcNMTgwMTE2MDYxMzQ5WhgPMjExNzEyMjMwNjEzNDlaMIGSMQsw CQYDVQQGEwJDTjERMA8GA1UECAwIWmhlamlhbmcxETAPBgNVBAcMCEhhbmd6aG91 MQ8wDQYDVQQKDAZhcGFjaGUxETAPBgNVBAsMCHJvY2tldG1xMRgwFgYDVQQDDA9h cGFjaGUgcm9ja2V0bXExHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcw gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOsmp4YtrIRsBdBQLyPImafCRynT Jls3NNF4g6nZr9e0efBY830gw9kBebcm603sdZNl95fzRr2+srXi5FJbG7Fmq1+F 0xLNK/kKWirGtNMT2DubmhVdKyXYJSvInoGRkrQzbOG0MdAyzE6QO6OjjNN+xGkm adWyCyNF6S8YqMJTAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAAzbwXyAULmXitiU +8/2vbUZQlzB/nXY52OIq7qu3F55hE5qlHkcVxG2JZjO3p5UETwOyNUpU4dpu3uT 7WSdygH4Iagl87ILpGsob9pAf0joAbaXAY4sGDhg+WjR5JInAxbmT+QWZ+4NTuLQ fSudUSJrv+HmUlmcVOvLiNStgt9rbtcgJAvpVwY+iCv0HQziFuQxmOkDv09ZLzu/ lxCMqnbgkEFYkwdntN6MVk38K3MovszedGO/n19hNOFss7nn5XDEeEnc6BqKGdck YDoy6amohY0Ds0o0gJ2rq0Y8Gjl9spQ3oeXpoNUoz84OF4KIBRTzSMv8CrmqPdFY Zd2MGjw= -----END CERTIFICATE----- ================================================ FILE: remoting/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: srvutil/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "srvutil", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "@maven//:org_apache_commons_commons_lang3", "@maven//:commons_validator_commons_validator", "@maven//:com_github_luben_zstd_jni", "@maven//:org_lz4_lz4_java", "@maven//:io_netty_netty_all", "@maven//:commons_cli_commons_cli", "@maven//:com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru", "@maven//:com_google_guava_guava", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ ":srvutil", "//common", "//:test_deps", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_netty_netty_all", ], ) GenTestRules( name = "GeneratedTestRules", test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: srvutil/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-srvutil rocketmq-srvutil ${project.version} ${basedir}/.. ${project.groupId} rocketmq-remoting ${project.groupId} rocketmq-common commons-cli commons-cli com.google.guava guava com.googlecode.concurrentlinkedhashmap concurrentlinkedhashmap-lru ================================================ FILE: srvutil/src/main/java/org/apache/rocketmq/srvutil/FileWatchService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.srvutil; import com.google.common.base.Strings; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.common.LifecycleAwareServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class FileWatchService extends LifecycleAwareServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static final int DEFAULT_WATCH_INTERVAL = 500; private final Map currentHash = new HashMap<>(); private final Listener listener; private final int watchInterval; private final MessageDigest md = MessageDigest.getInstance("MD5"); public FileWatchService(final String[] watchFiles, final Listener listener) throws Exception { this(watchFiles, listener, DEFAULT_WATCH_INTERVAL); } public FileWatchService(final String[] watchFiles, final Listener listener, int watchInterval) throws Exception { this.listener = listener; this.watchInterval = watchInterval; for (String file : watchFiles) { if (!Strings.isNullOrEmpty(file) && new File(file).exists()) { currentHash.put(file, md5Digest(file)); } } } @Override public String getServiceName() { return "FileWatchService"; } @Override public void run0() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.waitForRunning(watchInterval); for (Map.Entry entry : currentHash.entrySet()) { String newHash = md5Digest(entry.getKey()); if (!newHash.equals(entry.getValue())) { entry.setValue(newHash); listener.onChanged(entry.getKey()); } } } catch (Exception e) { log.warn(this.getServiceName() + " service raised an unexpected exception.", e); } } log.info(this.getServiceName() + " service end"); } /** * Note: we ignore DELETE event on purpose. This is useful when application renew CA file. * When the operator delete/rename the old CA file and copy a new one, this ensures the old CA file is used during * the operation. *

    * As we know exactly what to do when file does not exist or when IO exception is raised, there is no need to * propagate the exception up. * * @param filePath Absolute path of the file to calculate its MD5 digest. * @return Hash of the file content if exists; empty string otherwise. */ private String md5Digest(String filePath) { Path path = Paths.get(filePath); if (!path.toFile().exists()) { // Reuse previous hash result return currentHash.getOrDefault(filePath, ""); } byte[] raw; try { raw = Files.readAllBytes(path); } catch (IOException e) { log.info("Failed to read content of {}", filePath); // Reuse previous hash result return currentHash.getOrDefault(filePath, ""); } md.update(raw); byte[] hash = md.digest(); return UtilAll.bytes2string(hash); } public interface Listener { /** * Will be called when the target files are changed * * @param path the changed file path */ void onChanged(String path); } } ================================================ FILE: srvutil/src/main/java/org/apache/rocketmq/srvutil/ServerUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.srvutil; import java.util.Properties; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; public class ServerUtil { public static Options buildCommandlineOptions(final Options options) { Option opt = new Option("h", "help", false, "Print help"); opt.setRequired(false); options.addOption(opt); opt = new Option("n", "namesrvAddr", true, "Name server address list, eg: '192.168.0.1:9876;192.168.0.2:9876'"); opt.setRequired(false); options.addOption(opt); return options; } public static CommandLine parseCmdLine(final String appName, String[] args, Options options, CommandLineParser parser) { HelpFormatter hf = new HelpFormatter(); hf.setWidth(110); CommandLine commandLine = null; try { commandLine = parser.parse(options, args); if (commandLine.hasOption('h')) { hf.printHelp(appName, options, true); System.exit(0); } } catch (ParseException e) { System.err.println(e.getMessage()); hf.printHelp(appName, options, true); System.exit(1); } return commandLine; } public static void printCommandLineHelp(final String appName, final Options options) { HelpFormatter hf = new HelpFormatter(); hf.setWidth(110); hf.printHelp(appName, options, true); } public static Properties commandLine2Properties(final CommandLine commandLine) { Properties properties = new Properties(); Option[] opts = commandLine.getOptions(); if (opts != null) { for (Option opt : opts) { String name = opt.getLongOpt(); String value = commandLine.getOptionValue(name); if (value != null) { properties.setProperty(name, value); } } } return properties; } } ================================================ FILE: srvutil/src/main/java/org/apache/rocketmq/srvutil/ShutdownHookThread.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.srvutil; import org.apache.rocketmq.logging.org.slf4j.Logger; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; /** * {@link ShutdownHookThread} is the standard hook for filtersrv and namesrv modules. * Through {@link Callable} interface, this hook can customization operations in anywhere. */ public class ShutdownHookThread extends Thread { private volatile boolean hasShutdown = false; private AtomicInteger shutdownTimes = new AtomicInteger(0); private final Logger log; private final Callable callback; /** * Create the standard hook thread, with a call back, by using {@link Callable} interface. * * @param log The log instance is used in hook thread. * @param callback The call back function. */ public ShutdownHookThread(Logger log, Callable callback) { super("ShutdownHook"); this.log = log; this.callback = callback; } /** * Thread run method. * Invoke when the jvm shutdown. * 1. count the invocation times. * 2. execute the {@link ShutdownHookThread#callback}, and time it. */ @Override public void run() { synchronized (this) { log.info("shutdown hook was invoked, " + this.shutdownTimes.incrementAndGet() + " times."); if (!this.hasShutdown) { this.hasShutdown = true; long beginTime = System.currentTimeMillis(); try { this.callback.call(); } catch (Exception e) { log.error("shutdown hook callback invoked failure.", e); } long consumingTimeTotal = System.currentTimeMillis() - beginTime; log.info("shutdown hook done, consuming time total(ms): " + consumingTimeTotal); } } } } ================================================ FILE: srvutil/src/test/java/org/apache/rocketmq/srvutil/FileWatchServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.srvutil; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(MockitoJUnitRunner.class) public class FileWatchServiceTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @Test public void watchSingleFile() throws Exception { final File file = tempFolder.newFile(); final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService(new String[] {file.getAbsolutePath()}, path -> { assertThat(file.getAbsolutePath()).isEqualTo(path); waitSemaphore.release(); }); fileWatchService.start(); fileWatchService.awaitStarted(1000); modifyFile(file); boolean result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); fileWatchService.shutdown(); } @Test public void watchSingleFile_FileDeleted() throws Exception { File file = tempFolder.newFile(); final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService(new String[] {file.getAbsolutePath()}, path -> waitSemaphore.release()); fileWatchService.start(); fileWatchService.awaitStarted(1000); assertThat(file.delete()).isTrue(); boolean result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isFalse(); assertThat(file.createNewFile()).isTrue(); modifyFile(file); result = waitSemaphore.tryAcquire(1, 2000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); fileWatchService.shutdown(); } @Test public void watchTwoFile_FileDeleted() throws Exception { File fileA = tempFolder.newFile(); File fileB = tempFolder.newFile(); Files.write(fileA.toPath(), "Hello, World!".getBytes(StandardCharsets.UTF_8)); Files.write(fileB.toPath(), "Hello, World!".getBytes(StandardCharsets.UTF_8)); final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService( new String[] {fileA.getAbsolutePath(), fileB.getAbsolutePath()}, path -> waitSemaphore.release()); fileWatchService.start(); fileWatchService.awaitStarted(1000); assertThat(fileA.delete()).isTrue(); boolean result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isFalse(); modifyFile(fileB); result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); assertThat(fileA.createNewFile()).isTrue(); modifyFile(fileA); result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); fileWatchService.shutdown(); } @Test public void watchTwoFiles_ModifyOne() throws Exception { final File fileA = tempFolder.newFile(); File fileB = tempFolder.newFile(); final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService( new String[] {fileA.getAbsolutePath(), fileB.getAbsolutePath()}, path -> { assertThat(path).isEqualTo(fileA.getAbsolutePath()); waitSemaphore.release(); }); fileWatchService.start(); fileWatchService.awaitStarted(1000); modifyFile(fileA); boolean result = waitSemaphore.tryAcquire(1, 2000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); fileWatchService.shutdown(); } @Test public void watchTwoFiles() throws Exception { File fileA = tempFolder.newFile(); File fileB = tempFolder.newFile(); final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService( new String[] {fileA.getAbsolutePath(), fileB.getAbsolutePath()}, path -> waitSemaphore.release()); fileWatchService.start(); fileWatchService.awaitStarted(1000); modifyFile(fileA); modifyFile(fileB); boolean result = waitSemaphore.tryAcquire(2, 1000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); } private static void modifyFile(File file) { try { PrintWriter out = new PrintWriter(file); out.println(System.nanoTime()); out.flush(); out.close(); } catch (IOException ignore) { } } } ================================================ FILE: srvutil/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: store/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "store", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "//remoting", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_conversantmedia_disruptor", "@maven//:com_google_guava_guava", "@maven//:commons_collections_commons_collections", "@maven//:commons_io_commons_io", "@maven//:io_netty_netty_all", "@maven//:io_openmessaging_storage_dledger", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:net_java_dev_jna_jna", "@maven//:org_apache_commons_commons_lang3", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", "@maven//:com_google_code_findbugs_jsr305", "@maven//:commons_validator_commons_validator", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]), visibility = ["//visibility:public"], deps = [ ":store", "//:test_deps", "//common", "//remoting", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:com_conversantmedia_disruptor", "@maven//:io_openmessaging_storage_dledger", "@maven//:org_apache_commons_commons_lang3", "@maven//:com_google_guava_guava", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", "@maven//:org_junit_jupiter_junit_jupiter_api", ], ) GenTestRules( name = "GeneratedTestRules", exclude_tests = [ # These tests are extremely slow and flaky, exclude them before they are properly fixed. "src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest", "src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest", "src/test/java/org/apache/rocketmq/store/dledger/DLedgerMultiPathTest", ], medium_tests = [ "src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest", "src/test/java/org/apache/rocketmq/store/HATest", "src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest", "src/test/java/org/apache/rocketmq/store/MappedFileQueueTest", "src/test/java/org/apache/rocketmq/store/queue/BatchConsumeMessageTest", "src/test/java/org/apache/rocketmq/store/dledger/MixCommitlogTest", "src/test/java/org/apache/rocketmq/store/RocksDBMessageStoreTest", "src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest", ], test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: store/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-store rocketmq-store ${project.version} ${basedir}/.. io.openmessaging.storage dledger org.apache.rocketmq rocketmq-remoting ${project.groupId} rocketmq-remoting net.java.dev.jna jna com.conversantmedia disruptor com.google.guava guava commons-io commons-io org.slf4j slf4j-api io.github.aliyunmq rocketmq-shaded-slf4j-api-bridge ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.io.IOException; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; /** * Create MappedFile in advance */ public class AllocateMappedFileService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static int waitTimeOut = 1000 * 5; private ConcurrentMap requestTable = new ConcurrentHashMap<>(); private PriorityBlockingQueue requestQueue = new PriorityBlockingQueue<>(); private volatile boolean hasException = false; private DefaultMessageStore messageStore; private PreprocessHandler preprocessHandler; public AllocateMappedFileService(DefaultMessageStore messageStore) { this.messageStore = messageStore; } /** * Set preprocess handler for external extension * * @param preprocessHandler the preprocess handler */ public void setPreprocessHandler(PreprocessHandler preprocessHandler) { this.preprocessHandler = preprocessHandler; } public MappedFile putRequestAndReturnMappedFile(String nextFilePath, String nextNextFilePath, int fileSize) { // Execute preprocess logic if handler is set final PreprocessHandler finalPreprocessHandler = this.preprocessHandler; if (finalPreprocessHandler != null) { try { finalPreprocessHandler.preprocess(nextFilePath, nextNextFilePath, fileSize); } catch (Throwable t) { log.warn("Preprocess handler in AllocateMappedFileService execution failed", t); } } int canSubmitRequests = 2; if (this.messageStore.isTransientStorePoolEnable()) { if (this.messageStore.getMessageStoreConfig().isFastFailIfNoBufferInStorePool() && BrokerRole.SLAVE != this.messageStore.getMessageStoreConfig().getBrokerRole()) { //if broker is slave, don't fast fail even no buffer in pool canSubmitRequests = this.messageStore.remainTransientStoreBufferNumbs() - this.requestQueue.size(); } } AllocateRequest nextReq = new AllocateRequest(nextFilePath, fileSize); boolean nextPutOK = this.requestTable.putIfAbsent(nextFilePath, nextReq) == null; if (nextPutOK) { if (canSubmitRequests <= 0) { log.warn("[NOTIFYME]TransientStorePool is not enough, so create mapped file error, " + "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.remainTransientStoreBufferNumbs()); this.requestTable.remove(nextFilePath); return null; } boolean offerOK = this.requestQueue.offer(nextReq); if (!offerOK) { log.warn("never expected here, add a request to preallocate queue failed"); } canSubmitRequests--; } AllocateRequest nextNextReq = new AllocateRequest(nextNextFilePath, fileSize); boolean nextNextPutOK = this.requestTable.putIfAbsent(nextNextFilePath, nextNextReq) == null; if (nextNextPutOK) { if (canSubmitRequests <= 0) { log.warn("[NOTIFYME]TransientStorePool is not enough, so skip preallocate mapped file, " + "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.remainTransientStoreBufferNumbs()); this.requestTable.remove(nextNextFilePath); } else { boolean offerOK = this.requestQueue.offer(nextNextReq); if (!offerOK) { log.warn("never expected here, add a request to preallocate queue failed"); } } } if (hasException) { log.warn(this.getServiceName() + " service has exception. so return null"); return null; } AllocateRequest result = this.requestTable.get(nextFilePath); try { if (result != null) { messageStore.getPerfCounter().startTick("WAIT_MAPFILE_TIME_MS"); boolean waitOK = result.getCountDownLatch().await(waitTimeOut, TimeUnit.MILLISECONDS); messageStore.getPerfCounter().endTick("WAIT_MAPFILE_TIME_MS"); if (!waitOK) { log.warn("create mmap timeout " + result.getFilePath() + " " + result.getFileSize()); return null; } else { this.requestTable.remove(nextFilePath); return result.getMappedFile(); } } else { log.error("find preallocate mmap failed, this never happen"); } } catch (InterruptedException e) { log.warn(this.getServiceName() + " service has exception. ", e); } return null; } @Override public String getServiceName() { if (messageStore != null && messageStore.getBrokerConfig().isInBrokerContainer()) { return messageStore.getBrokerIdentity().getIdentifier() + AllocateMappedFileService.class.getSimpleName(); } return AllocateMappedFileService.class.getSimpleName(); } @Override public void shutdown() { super.shutdown(true); for (AllocateRequest req : this.requestTable.values()) { if (req.mappedFile != null) { log.info("delete pre allocated mapped file, {}", req.mappedFile.getFileName()); req.mappedFile.destroy(1000); } } } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped() && this.mmapOperation()) { } log.info(this.getServiceName() + " service end"); } /** * Only interrupted by the external thread, will return false */ private boolean mmapOperation() { boolean isSuccess = false; AllocateRequest req = null; try { req = this.requestQueue.take(); AllocateRequest expectedRequest = this.requestTable.get(req.getFilePath()); if (null == expectedRequest) { log.warn("this mmap request expired, maybe cause timeout " + req.getFilePath() + " " + req.getFileSize()); return true; } if (expectedRequest != req) { log.warn("never expected here, maybe cause timeout " + req.getFilePath() + " " + req.getFileSize() + ", req:" + req + ", expectedRequest:" + expectedRequest); return true; } if (req.getMappedFile() == null) { long beginTime = System.currentTimeMillis(); MappedFile mappedFile; boolean writeWithoutMmap = messageStore.getMessageStoreConfig().isWriteWithoutMmap(); RunningFlags runningFlags = messageStore.getMessageStoreConfig().isEnableRunningFlagsInFlush() ? messageStore.getRunningFlags() : null; if (messageStore.isTransientStorePoolEnable()) { try { mappedFile = ServiceLoader.load(MappedFile.class).iterator().next(); mappedFile.init(req.getFilePath(), req.getFileSize(), runningFlags, messageStore.getTransientStorePool()); } catch (RuntimeException e) { log.warn("Use default implementation."); mappedFile = new DefaultMappedFile(req.getFilePath(), req.getFileSize(), runningFlags, messageStore.getTransientStorePool(), writeWithoutMmap); } } else { mappedFile = new DefaultMappedFile(req.getFilePath(), req.getFileSize(), runningFlags, writeWithoutMmap); } long elapsedTime = UtilAll.computeElapsedTimeMilliseconds(beginTime); if (elapsedTime > 10) { int queueSize = this.requestQueue.size(); log.warn("create mappedFile spent time(ms) " + elapsedTime + " queue size " + queueSize + " " + req.getFilePath() + " " + req.getFileSize()); } // pre write mappedFile if (mappedFile.getFileSize() >= this.messageStore.getMessageStoreConfig() .getMappedFileSizeCommitLog() && this.messageStore.getMessageStoreConfig().isWarmMapedFileEnable() && !this.messageStore.getMessageStoreConfig().isWriteWithoutMmap()) { mappedFile.warmMappedFile(this.messageStore.getMessageStoreConfig().getFlushDiskType(), this.messageStore.getMessageStoreConfig().getFlushLeastPagesWhenWarmMapedFile()); } req.setMappedFile(mappedFile); this.hasException = false; isSuccess = true; } } catch (InterruptedException e) { log.warn(this.getServiceName() + " interrupted, possibly by shutdown."); this.hasException = true; return false; } catch (IOException e) { log.warn(this.getServiceName() + " service has exception. ", e); this.hasException = true; if (null != req) { requestQueue.offer(req); try { Thread.sleep(1); } catch (InterruptedException ignored) { } } } finally { if (req != null && isSuccess) req.getCountDownLatch().countDown(); } return true; } /** * Preprocess handler interface for external extension */ @FunctionalInterface public interface PreprocessHandler { /** * Preprocess before allocating mapped file * * @param nextFilePath the next file path * @param nextNextFilePath the next next file path * @param fileSize the file size */ void preprocess(String nextFilePath, String nextNextFilePath, int fileSize); } static class AllocateRequest implements Comparable { // Full file path private String filePath; private int fileSize; private CountDownLatch countDownLatch = new CountDownLatch(1); private volatile MappedFile mappedFile = null; public AllocateRequest(String filePath, int fileSize) { this.filePath = filePath; this.fileSize = fileSize; } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } public int getFileSize() { return fileSize; } public void setFileSize(int fileSize) { this.fileSize = fileSize; } public CountDownLatch getCountDownLatch() { return countDownLatch; } public void setCountDownLatch(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } public MappedFile getMappedFile() { return mappedFile; } public void setMappedFile(MappedFile mappedFile) { this.mappedFile = mappedFile; } public int compareTo(AllocateRequest other) { if (this.fileSize < other.fileSize) return 1; else if (this.fileSize > other.fileSize) { return -1; } else { int mIndex = this.filePath.lastIndexOf(File.separator); long mName = Long.parseLong(this.filePath.substring(mIndex + 1)); int oIndex = other.filePath.lastIndexOf(File.separator); long oName = Long.parseLong(other.filePath.substring(oIndex + 1)); if (mName < oName) { return -1; } else if (mName > oName) { return 1; } else { return 0; } } // return this.fileSize < other.fileSize ? 1 : this.fileSize > // other.fileSize ? -1 : 0; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((filePath == null) ? 0 : filePath.hashCode()); result = prime * result + fileSize; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AllocateRequest other = (AllocateRequest) obj; if (filePath == null) { if (other.filePath != null) return false; } else if (!filePath.equals(other.filePath)) return false; if (fileSize != other.fileSize) return false; return true; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/AppendMessageCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; /** * Write messages callback interface */ public interface AppendMessageCallback { /** * After message serialization, write MappedByteBuffer * * @return How many bytes to write */ AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank, final MessageExtBrokerInner msg, PutMessageContext putMessageContext); /** * After batched message serialization, write MappedByteBuffer * * @param messageExtBatch, backed up by a byte array * @return How many bytes to write */ AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank, final MessageExtBatch messageExtBatch, PutMessageContext putMessageContext); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/AppendMessageResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.function.Supplier; /** * When write a message to the commit log, returns results */ public class AppendMessageResult { // Return code private AppendMessageStatus status; // Where to start writing private long wroteOffset; // Write Bytes private int wroteBytes; // Message ID private String msgId; private Supplier msgIdSupplier; // Message storage timestamp private long storeTimestamp; // Consume queue's offset(step by one) private long logicsOffset; private long pagecacheRT = 0; private int msgNum = 1; public AppendMessageResult(AppendMessageStatus status) { this(status, 0, 0, "", 0, 0, 0); } public AppendMessageResult(AppendMessageStatus status, long wroteOffset, int wroteBytes, String msgId, long storeTimestamp, long logicsOffset, long pagecacheRT) { this.status = status; this.wroteOffset = wroteOffset; this.wroteBytes = wroteBytes; this.msgId = msgId; this.storeTimestamp = storeTimestamp; this.logicsOffset = logicsOffset; this.pagecacheRT = pagecacheRT; } public AppendMessageResult(AppendMessageStatus status, long wroteOffset, int wroteBytes, long storeTimestamp) { this.status = status; this.wroteOffset = wroteOffset; this.wroteBytes = wroteBytes; this.storeTimestamp = storeTimestamp; } public AppendMessageResult(AppendMessageStatus status, long wroteOffset, int wroteBytes, Supplier msgIdSupplier, long storeTimestamp, long logicsOffset, long pagecacheRT) { this.status = status; this.wroteOffset = wroteOffset; this.wroteBytes = wroteBytes; this.msgIdSupplier = msgIdSupplier; this.storeTimestamp = storeTimestamp; this.logicsOffset = logicsOffset; this.pagecacheRT = pagecacheRT; } public AppendMessageResult(AppendMessageStatus status, long wroteOffset, int wroteBytes, Supplier msgIdSupplier, long storeTimestamp, long logicsOffset, long pagecacheRT, int msgNum) { this.status = status; this.wroteOffset = wroteOffset; this.wroteBytes = wroteBytes; this.msgIdSupplier = msgIdSupplier; this.storeTimestamp = storeTimestamp; this.logicsOffset = logicsOffset; this.pagecacheRT = pagecacheRT; this.msgNum = msgNum; } public long getPagecacheRT() { return pagecacheRT; } public void setPagecacheRT(final long pagecacheRT) { this.pagecacheRT = pagecacheRT; } public boolean isOk() { return this.status == AppendMessageStatus.PUT_OK; } public AppendMessageStatus getStatus() { return status; } public void setStatus(AppendMessageStatus status) { this.status = status; } public long getWroteOffset() { return wroteOffset; } public void setWroteOffset(long wroteOffset) { this.wroteOffset = wroteOffset; } public int getWroteBytes() { return wroteBytes; } public void setWroteBytes(int wroteBytes) { this.wroteBytes = wroteBytes; } public String getMsgId() { if (msgId == null && msgIdSupplier != null) { msgId = msgIdSupplier.get(); } return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public long getStoreTimestamp() { return storeTimestamp; } public void setStoreTimestamp(long storeTimestamp) { this.storeTimestamp = storeTimestamp; } public long getLogicsOffset() { return logicsOffset; } public void setLogicsOffset(long logicsOffset) { this.logicsOffset = logicsOffset; } public int getMsgNum() { return msgNum; } public void setMsgNum(int msgNum) { this.msgNum = msgNum; } @Override public String toString() { return "AppendMessageResult{" + "status=" + status + ", wroteOffset=" + wroteOffset + ", wroteBytes=" + wroteBytes + ", msgId='" + msgId + '\'' + ", storeTimestamp=" + storeTimestamp + ", logicsOffset=" + logicsOffset + ", pagecacheRT=" + pagecacheRT + ", msgNum=" + msgNum + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/AppendMessageStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; /** * When write a message to the commit log, returns code */ public enum AppendMessageStatus { PUT_OK, END_OF_FILE, MESSAGE_SIZE_EXCEEDED, PROPERTIES_SIZE_EXCEEDED, UNKNOWN_ERROR, ROCKSDB_ERROR, } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/CommitLog.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import com.google.common.base.Strings; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import java.util.stream.Collectors; import io.netty.util.internal.PlatformDependent; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageVersion; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.MessageExtEncoder.PutMessageThreadLocal; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.exception.StoreException; import org.apache.rocketmq.store.ha.HAService; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; import org.apache.rocketmq.store.lock.AdaptiveBackOffSpinLockImpl; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.util.LibC; import org.rocksdb.RocksDBException; /** * Store all metadata downtime for recovery, data protection reliability */ public class CommitLog implements Swappable { // Message's MAGIC CODE daa320a7 public final static int MESSAGE_MAGIC_CODE = -626843481; protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); // End of file empty MAGIC CODE cbd43194 public final static int BLANK_MAGIC_CODE = -875286124; /** * CRC32 Format: [PROPERTY_CRC32 + NAME_VALUE_SEPARATOR + 10-digit fixed-length string + PROPERTY_SEPARATOR] */ public static final int CRC32_RESERVED_LEN = MessageConst.PROPERTY_CRC32.length() + 1 + 10 + 1; protected final MappedFileQueue mappedFileQueue; protected final DefaultMessageStore defaultMessageStore; private final FlushManager flushManager; private final ColdDataCheckService coldDataCheckService; private final AppendMessageCallback appendMessageCallback; private final ThreadLocal putMessageThreadLocal; protected volatile long confirmOffset = -1L; private volatile long beginTimeInLock = 0; protected final PutMessageLock putMessageLock; protected final TopicQueueLock topicQueueLock; private volatile Set fullStorePaths = Collections.emptySet(); private final FlushDiskWatcher flushDiskWatcher; protected int commitLogSize; private final boolean enabledAppendPropCRC; public CommitLog(final DefaultMessageStore messageStore) { String storePath = messageStore.getMessageStoreConfig().getStorePathCommitLog(); RunningFlags runningFlags = messageStore.getMessageStoreConfig().isEnableRunningFlagsInFlush() ? messageStore.getRunningFlags() : null; if (storePath.contains(MixAll.MULTI_PATH_SPLITTER)) { this.mappedFileQueue = new MultiPathMappedFileQueue(messageStore.getMessageStoreConfig(), messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), messageStore.getAllocateMappedFileService(), this::getFullStorePaths, runningFlags); } else { this.mappedFileQueue = new MappedFileQueue(storePath, messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), messageStore.getAllocateMappedFileService(), runningFlags, messageStore.getMessageStoreConfig().isWriteWithoutMmap()); } this.defaultMessageStore = messageStore; this.flushManager = new DefaultFlushManager(); this.coldDataCheckService = new ColdDataCheckService(); this.appendMessageCallback = new DefaultAppendMessageCallback(defaultMessageStore.getMessageStoreConfig()); putMessageThreadLocal = new ThreadLocal() { @Override protected PutMessageThreadLocal initialValue() { return new PutMessageThreadLocal(defaultMessageStore.getMessageStoreConfig()); } }; PutMessageLock adaptiveBackOffSpinLock = new AdaptiveBackOffSpinLockImpl(); this.putMessageLock = messageStore.getMessageStoreConfig().getUseABSLock() ? adaptiveBackOffSpinLock : messageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock(); this.flushDiskWatcher = new FlushDiskWatcher(); this.topicQueueLock = new TopicQueueLock(messageStore.getMessageStoreConfig().getTopicQueueLockNum()); this.commitLogSize = messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); this.enabledAppendPropCRC = messageStore.getMessageStoreConfig().isEnabledAppendPropCRC(); } public void setFullStorePaths(Set fullStorePaths) { this.fullStorePaths = fullStorePaths; } public Set getFullStorePaths() { return fullStorePaths; } public long getTotalSize() { return this.mappedFileQueue.getTotalFileSize(); } public ThreadLocal getPutMessageThreadLocal() { return putMessageThreadLocal; } public boolean load() { boolean result = this.mappedFileQueue.load(); if (result && !defaultMessageStore.getMessageStoreConfig().isDataReadAheadEnable()) { scanFileAndSetReadMode(LibC.MADV_RANDOM); } this.mappedFileQueue.checkSelf(); log.info("load commit log {}", result ? "OK" : "Failed"); return result; } public void cleanResourceAll() { mappedFileQueue.cleanResourcesAll(); } public void start() { this.flushManager.start(); log.info("start commitLog successfully. storeRoot: {}", this.defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); flushDiskWatcher.setDaemon(true); flushDiskWatcher.start(); if (this.coldDataCheckService != null) { this.coldDataCheckService.start(); } } public void shutdown() { if (this.flushManager != null) { this.flushManager.shutdown(); } if (flushDiskWatcher != null) { flushDiskWatcher.shutdown(true); } if (this.coldDataCheckService != null) { this.coldDataCheckService.shutdown(); } putMessageThreadLocal.remove(); log.info("shutdown commitLog successfully. storeRoot: {}", this.defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); } public long flush() { this.mappedFileQueue.commit(0); this.mappedFileQueue.flush(0); return this.mappedFileQueue.getFlushedWhere(); } public long getFlushedWhere() { return this.mappedFileQueue.getFlushedWhere(); } public long getMaxOffset() { return this.mappedFileQueue.getMaxOffset(); } public long remainHowManyDataToCommit() { return this.mappedFileQueue.remainHowManyDataToCommit(); } public long remainHowManyDataToFlush() { return this.mappedFileQueue.remainHowManyDataToFlush(); } public int deleteExpiredFile( final long expiredTime, final int deleteFilesInterval, final long intervalForcibly, final boolean cleanImmediately ) { return deleteExpiredFile(expiredTime, deleteFilesInterval, intervalForcibly, cleanImmediately, 0); } public int deleteExpiredFile( final long expiredTime, final int deleteFilesInterval, final long intervalForcibly, final boolean cleanImmediately, final int deleteFileBatchMax ) { return this.mappedFileQueue.deleteExpiredFileByTime(expiredTime, deleteFilesInterval, intervalForcibly, cleanImmediately, deleteFileBatchMax); } /** * Read CommitLog data, use data replication */ public SelectMappedBufferResult getData(final long offset) { return this.getData(offset, offset == 0); } public SelectMappedBufferResult getData(final long offset, final boolean returnFirstOnNotFound) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, returnFirstOnNotFound); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); SelectMappedBufferResult result = mappedFile.selectMappedBuffer(pos); return result; } return null; } public boolean getData(final long offset, final int size, final ByteBuffer byteBuffer) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); return mappedFile.getData(pos, size, byteBuffer); } return false; } public List getBulkData(final long offset, final int size) { List bufferResultList = new ArrayList<>(); int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); int remainSize = size; long startOffset = offset; long maxOffset = this.getMaxOffset(); if (offset + size > maxOffset) { remainSize = (int) (maxOffset - offset); log.warn("get bulk data size out of range, correct to max offset. offset: {}, size: {}, max: {}", offset, remainSize, maxOffset); } while (remainSize > 0) { MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(startOffset, startOffset == 0); if (mappedFile != null) { int pos = (int) (startOffset % mappedFileSize); int readableSize = mappedFile.getReadPosition() - pos; int readSize = Math.min(remainSize, readableSize); SelectMappedBufferResult bufferResult = mappedFile.selectMappedBuffer(pos, readSize); if (bufferResult == null) { break; } bufferResultList.add(bufferResult); remainSize -= readSize; startOffset += readSize; } } return bufferResultList; } public SelectMappedFileResult getFile(final long offset) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int size = (int) (mappedFile.getReadPosition() - offset % mappedFileSize); if (size > 0) { return new SelectMappedFileResult(size, mappedFile); } } return null; } //Create new mappedFile if not exits. public boolean getLastMappedFile(final long startOffset) { MappedFile lastMappedFile = this.mappedFileQueue.getLastMappedFile(startOffset); if (null == lastMappedFile) { log.error("getLastMappedFile error. offset:{}", startOffset); return false; } return true; } /** * When the normal exit, data recovery, all memory data have been flush * * @throws RocksDBException only in rocksdb mode */ public void recoverNormally(long dispatchFromPhyOffset) throws RocksDBException { boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover(); boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable(); int maxRecoverNum = this.defaultMessageStore.getMessageStoreConfig().getCommitLogRecoverMaxNum(); if (maxRecoverNum <= 0) { maxRecoverNum = 10; } log.info("recoverNormally maxRecoverNum: {}", maxRecoverNum); final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { int index = mappedFiles.size() - 1; while (index > 0) { MappedFile mappedFile = mappedFiles.get(index); maxRecoverNum--; if (isMappedFileMatchedRecover(mappedFile, true) || maxRecoverNum <= 0) { // It's safe to recover from this mapped file break; } index--; } // TODO: Discuss if we need to load more commit-log mapped files into memory. MappedFile mappedFile = mappedFiles.get(index); ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset = 0; long lastValidMsgPhyOffset = this.getConfirmOffset(); while (true) { DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover, checkDupInfo); int size = dispatchRequest.getMsgSize(); boolean doDispatch = dispatchRequest.getCommitLogOffset() > dispatchFromPhyOffset; // Normal data if (dispatchRequest.isSuccess() && size > 0) { lastValidMsgPhyOffset = processOffset + mappedFileOffset; mappedFileOffset += size; this.getMessageStore().onCommitLogDispatch(dispatchRequest, doDispatch, mappedFile, true, false); } // Come the end of the file, switch to the next file Since the // return 0 representatives met last hole, // this can not be included in truncate offset else if (dispatchRequest.isSuccess() && size == 0) { this.getMessageStore().onCommitLogDispatch(dispatchRequest, doDispatch, mappedFile, true, true); index++; if (index >= mappedFiles.size()) { // Current branch can not happen log.info("recover last 3 physics file over, last mapped file {}", mappedFile.getFileName()); break; } else { mappedFile = mappedFiles.get(index); byteBuffer = mappedFile.sliceByteBuffer(); processOffset = mappedFile.getFileFromOffset(); mappedFileOffset = 0; log.info("recover next physics file, {}", mappedFile.getFileName()); } } // Intermediate file read error else if (!dispatchRequest.isSuccess()) { if (size > 0) { log.warn("found a half message at {}, it will be truncated.", processOffset + mappedFileOffset); } log.info("recover physics file end, {}", mappedFile.getFileName()); break; } } processOffset += mappedFileOffset; if (this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (this.defaultMessageStore.getConfirmOffset() < this.defaultMessageStore.getMinPhyOffset()) { log.error("confirmOffset {} is less than minPhyOffset {}, correct confirmOffset to minPhyOffset", this.defaultMessageStore.getConfirmOffset(), this.defaultMessageStore.getMinPhyOffset()); this.defaultMessageStore.setConfirmOffset(this.defaultMessageStore.getMinPhyOffset()); } else if (this.defaultMessageStore.getConfirmOffset() > processOffset) { log.error("confirmOffset {} is larger than processOffset {}, correct confirmOffset to processOffset", this.defaultMessageStore.getConfirmOffset(), processOffset); this.defaultMessageStore.setConfirmOffset(processOffset); } } else { this.setConfirmOffset(lastValidMsgPhyOffset); } // Clear ConsumeQueue redundant data this.defaultMessageStore.truncateDirtyLogicFiles(processOffset); this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); } else { // Commitlog case files are deleted log.warn("The commitlog files are deleted, and delete the consume queue files"); this.mappedFileQueue.setFlushedWhere(0); this.mappedFileQueue.setCommittedWhere(0); this.defaultMessageStore.destroyConsumeQueueStore(true); } } public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, final boolean checkCRC, final boolean checkDupInfo) { return this.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, true); } private void doNothingForDeadCode(final Object obj) { if (obj != null) { log.debug(String.valueOf(obj.hashCode())); } } /** * check the message and returns the message size * * @return 0 Come the end of the file // >0 Normal messages // -1 Message checksum failure */ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, final boolean checkCRC, final boolean checkDupInfo, final boolean readBody) { try { if (byteBuffer.remaining() <= 4) { return new DispatchRequest(-1, false /* fail */); } // 1 TOTAL SIZE int totalSize = byteBuffer.getInt(); if (byteBuffer.remaining() < totalSize - 4) { return new DispatchRequest(-1, false /* fail */); } // 2 MAGIC CODE int magicCode = byteBuffer.getInt(); switch (magicCode) { case MessageDecoder.MESSAGE_MAGIC_CODE: case MessageDecoder.MESSAGE_MAGIC_CODE_V2: break; case BLANK_MAGIC_CODE: return new DispatchRequest(0, true /* success */); default: log.warn("found a illegal magic code 0x{}", Integer.toHexString(magicCode)); return new DispatchRequest(-1, false /* success */); } MessageVersion messageVersion = MessageVersion.valueOfMagicCode(magicCode); byte[] bytesContent = new byte[totalSize]; int bodyCRC = byteBuffer.getInt(); int queueId = byteBuffer.getInt(); int flag = byteBuffer.getInt(); long queueOffset = byteBuffer.getLong(); long physicOffset = byteBuffer.getLong(); int sysFlag = byteBuffer.getInt(); long bornTimeStamp = byteBuffer.getLong(); ByteBuffer byteBuffer1; if ((sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0) { byteBuffer1 = byteBuffer.get(bytesContent, 0, 4 + 4); } else { byteBuffer1 = byteBuffer.get(bytesContent, 0, 16 + 4); } long storeTimestamp = byteBuffer.getLong(); ByteBuffer byteBuffer2; if ((sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0) { byteBuffer2 = byteBuffer.get(bytesContent, 0, 4 + 4); } else { byteBuffer2 = byteBuffer.get(bytesContent, 0, 16 + 4); } int reconsumeTimes = byteBuffer.getInt(); long preparedTransactionOffset = byteBuffer.getLong(); int bodyLen = byteBuffer.getInt(); if (bodyLen > 0) { if (readBody) { byteBuffer.get(bytesContent, 0, bodyLen); if (checkCRC) { /** * When the forceVerifyPropCRC = false, * use original bodyCrc validation. */ if (!this.defaultMessageStore.getMessageStoreConfig().isForceVerifyPropCRC()) { int crc = UtilAll.crc32(bytesContent, 0, bodyLen); if (crc != bodyCRC) { log.warn("CRC check failed. bodyCRC={}, currentCRC={}", crc, bodyCRC); return new DispatchRequest(-1, false/* success */); } } } } else { byteBuffer.position(byteBuffer.position() + bodyLen); } } int topicLen = messageVersion.getTopicLength(byteBuffer); byteBuffer.get(bytesContent, 0, topicLen); String topic = new String(bytesContent, 0, topicLen, MessageDecoder.CHARSET_UTF8); long tagsCode = 0; String keys = ""; String uniqKey = null; short propertiesLength = byteBuffer.getShort(); Map propertiesMap = null; if (propertiesLength > 0) { byteBuffer.get(bytesContent, 0, propertiesLength); String properties = new String(bytesContent, 0, propertiesLength, MessageDecoder.CHARSET_UTF8); propertiesMap = MessageDecoder.string2messageProperties(properties); keys = propertiesMap.get(MessageConst.PROPERTY_KEYS); uniqKey = propertiesMap.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (checkDupInfo) { String dupInfo = propertiesMap.get(MessageConst.DUP_INFO); if (null == dupInfo || dupInfo.split("_").length != 2) { log.warn("DupInfo in properties check failed. dupInfo={}", dupInfo); return new DispatchRequest(-1, false); } } String tags = propertiesMap.get(MessageConst.PROPERTY_TAGS); if (!Strings.isNullOrEmpty(tags)) { tagsCode = MessageExtBrokerInner.tagsString2tagsCode(MessageExt.parseTopicFilterType(sysFlag), tags); } // Timing message processing { String t = propertiesMap.get(MessageConst.PROPERTY_DELAY_TIME_LEVEL); if (TopicValidator.RMQ_SYS_SCHEDULE_TOPIC.equals(topic) && t != null) { int delayLevel = Integer.parseInt(t); if (delayLevel > this.defaultMessageStore.getMaxDelayLevel()) { delayLevel = this.defaultMessageStore.getMaxDelayLevel(); } if (delayLevel > 0) { tagsCode = this.defaultMessageStore.computeDeliverTimestamp(delayLevel, storeTimestamp); } } } } if (checkCRC) { /** * When the forceVerifyPropCRC = true, * Crc verification needs to be performed on the entire message data (excluding the length reserved at the tail) */ if (this.defaultMessageStore.getMessageStoreConfig().isForceVerifyPropCRC()) { int expectedCRC = -1; if (propertiesMap != null) { String crc32Str = propertiesMap.get(MessageConst.PROPERTY_CRC32); if (crc32Str != null) { expectedCRC = 0; for (int i = crc32Str.length() - 1; i >= 0; i--) { int num = crc32Str.charAt(i) - '0'; expectedCRC *= 10; expectedCRC += num; } } } if (expectedCRC >= 0) { ByteBuffer tmpBuffer = byteBuffer.duplicate(); tmpBuffer.position(tmpBuffer.position() - totalSize); tmpBuffer.limit(tmpBuffer.position() + totalSize - CommitLog.CRC32_RESERVED_LEN); int crc = UtilAll.crc32(tmpBuffer); if (crc != expectedCRC) { log.warn( "CommitLog#checkAndDispatchMessage: failed to check message CRC, expected " + "CRC={}, actual CRC={}", bodyCRC, crc); return new DispatchRequest(-1, false/* success */); } } else { // Read full message for logging when error occurs ByteBuffer fullMessageBuffer = byteBuffer.duplicate(); int messageStartPos = fullMessageBuffer.position() - totalSize; fullMessageBuffer.position(messageStartPos); fullMessageBuffer.limit(messageStartPos + totalSize); byte[] fullMessageBytes = new byte[totalSize]; fullMessageBuffer.get(fullMessageBytes, 0, totalSize); // Print full message and especially properties log.warn( "CommitLog#checkAndDispatchMessage: failed to check message CRC, not found CRC in properties. topic={}, properties={}, propertiesLength={}, fullMessageHex={}", topic, propertiesMap != null ? propertiesMap.toString() : "null", propertiesLength, UtilAll.bytes2string(fullMessageBytes)); return new DispatchRequest(-1, false/* success */); } } } int readLength = MessageExtEncoder.calMsgLength(messageVersion, sysFlag, bodyLen, topicLen, propertiesLength); if (totalSize != readLength) { doNothingForDeadCode(reconsumeTimes); doNothingForDeadCode(flag); doNothingForDeadCode(bornTimeStamp); doNothingForDeadCode(byteBuffer1); doNothingForDeadCode(byteBuffer2); log.error( "[BUG]read total count not equals msg total size. totalSize={}, readTotalCount={}, bodyLen={}, topicLen={}, propertiesLength={}", totalSize, readLength, bodyLen, topicLen, propertiesLength); return new DispatchRequest(totalSize, false/* success */); } DispatchRequest dispatchRequest = new DispatchRequest( topic, queueId, physicOffset, totalSize, tagsCode, storeTimestamp, queueOffset, keys, uniqKey, sysFlag, preparedTransactionOffset, propertiesMap ); setBatchSizeIfNeeded(propertiesMap, dispatchRequest); return dispatchRequest; } catch (Exception e) { log.error("checkMessageAndReturnSize failed, may can not dispatch", e); } return new DispatchRequest(-1, false /* success */); } private void setBatchSizeIfNeeded(Map propertiesMap, DispatchRequest dispatchRequest) { if (null != propertiesMap && propertiesMap.containsKey(MessageConst.PROPERTY_INNER_NUM) && propertiesMap.containsKey(MessageConst.PROPERTY_INNER_BASE)) { dispatchRequest.setMsgBaseOffset(Long.parseLong(propertiesMap.get(MessageConst.PROPERTY_INNER_BASE))); dispatchRequest.setBatchSize(Short.parseShort(propertiesMap.get(MessageConst.PROPERTY_INNER_NUM))); } } // Fetch and compute the newest confirmOffset. // Even if it is just inited. public long getConfirmOffset() { if (this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (this.defaultMessageStore.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE && !this.defaultMessageStore.getRunningFlags().isFenced()) { if (((AutoSwitchHAService) this.defaultMessageStore.getHaService()).getLocalSyncStateSet().size() == 1 || !this.defaultMessageStore.getMessageStoreConfig().isAllAckInSyncStateSet()) { return this.defaultMessageStore.getMaxPhyOffset(); } // First time it will compute the confirmOffset. if (this.confirmOffset < 0) { setConfirmOffset(((AutoSwitchHAService) this.defaultMessageStore.getHaService()).computeConfirmOffset()); log.info("Init the confirmOffset to {}.", this.confirmOffset); } } return this.confirmOffset; } else if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { return this.confirmOffset; } else { return this.defaultMessageStore.isSyncDiskFlush() ? getFlushedWhere() : getMaxOffset(); } } // Fetch the original confirmOffset's value. // Without checking and re-computing. public long getConfirmOffsetDirectly() { if (this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (this.defaultMessageStore.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE && !this.defaultMessageStore.getRunningFlags().isFenced()) { if (((AutoSwitchHAService) this.defaultMessageStore.getHaService()).getLocalSyncStateSet().size() == 1) { return this.defaultMessageStore.getMaxPhyOffset(); } } return this.confirmOffset; } else if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { return this.confirmOffset; } else { return getMaxOffset(); } } public void setConfirmOffset(long phyOffset) { this.confirmOffset = phyOffset; this.defaultMessageStore.getStoreCheckpoint().setConfirmPhyOffset(confirmOffset); } public long getLastFileFromOffset() { MappedFile lastMappedFile = this.mappedFileQueue.getLastMappedFile(); if (lastMappedFile != null) { if (lastMappedFile.isAvailable()) { return lastMappedFile.getFileFromOffset(); } } return -1; } /** * @throws RocksDBException only in rocksdb mode */ public void recoverAbnormally(long dispatchFromPhyOffset) throws RocksDBException { // recover by the minimum time stamp boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover(); boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable(); boolean checkCommitLogOffsetOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCommitLogOffsetOnRecover(); int maxRecoverNum = this.defaultMessageStore.getMessageStoreConfig().getCommitLogRecoverMaxNum(); if (maxRecoverNum <= 0) { maxRecoverNum = 10; } log.info("recoverAbnormally maxRecoverNum: {}", maxRecoverNum); final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { // Looking beginning to recover from which file int index = mappedFiles.size() - 1; MappedFile mappedFile = null; for (; index >= 0; index--) { mappedFile = mappedFiles.get(index); maxRecoverNum--; if (this.isMappedFileMatchedRecover(mappedFile, false) || maxRecoverNum <= 0) { log.info("recover from this mapped file " + mappedFile.getFileName()); break; } } if (index < 0) { index = 0; mappedFile = mappedFiles.get(index); } ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset; long lastValidMsgPhyOffset; long lastConfirmValidMsgPhyOffset; if (defaultMessageStore.getMessageStoreConfig().isEnableAcceleratedRecovery()) { mappedFileOffset = dispatchFromPhyOffset - mappedFile.getFileFromOffset(); // Protective measures, falling back to non-accelerated mode, which is extremely unlikely to occur if (mappedFileOffset < 0) { mappedFileOffset = 0; lastValidMsgPhyOffset = processOffset; lastConfirmValidMsgPhyOffset = processOffset; } else { log.info("recover using acceleration, recovery offset is {}", dispatchFromPhyOffset); lastValidMsgPhyOffset = dispatchFromPhyOffset; lastConfirmValidMsgPhyOffset = dispatchFromPhyOffset; byteBuffer.position((int) mappedFileOffset); } } else { mappedFileOffset = 0; lastValidMsgPhyOffset = processOffset; lastConfirmValidMsgPhyOffset = processOffset; } // abnormal recover require dispatching boolean doDispatch = true; while (true) { DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover, checkDupInfo); int size = dispatchRequest.getMsgSize(); if (dispatchRequest.isSuccess()) { // Check commitlog offset validity if enabled if (size > 0 && checkCommitLogOffsetOnRecover) { if (dispatchRequest.getCommitLogOffset() < mappedFile.getFileFromOffset() || dispatchRequest.getCommitLogOffset() > mappedFile.getFileFromOffset() + mappedFile.getFileSize()) { log.warn("found illegal commitlog offset {} in {}, current pos={}, it will be truncated.", dispatchRequest.getCommitLogOffset(), mappedFile.getFileName(), processOffset + mappedFileOffset); log.info("recover physics file end, {} pos={}", mappedFile.getFileName(), byteBuffer.position()); break; } } // Normal data if (size > 0) { lastValidMsgPhyOffset = processOffset + mappedFileOffset; mappedFileOffset += size; if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable() || this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (dispatchRequest.getCommitLogOffset() + size <= this.defaultMessageStore.getCommitLog().getConfirmOffset()) { this.getMessageStore().onCommitLogDispatch(dispatchRequest, doDispatch, mappedFile, true, false); lastConfirmValidMsgPhyOffset = dispatchRequest.getCommitLogOffset() + size; } } else { this.getMessageStore().onCommitLogDispatch(dispatchRequest, doDispatch, mappedFile, true, false); } } // Come the end of the file, switch to the next file // Since the return 0 representatives met last hole, this can // not be included in truncate offset else if (size == 0) { this.getMessageStore().onCommitLogDispatch(dispatchRequest, doDispatch, mappedFile, true, true); index++; if (index >= mappedFiles.size()) { // The current branch under normal circumstances should // not happen log.info("recover physics file over, last mapped file {}", mappedFile.getFileName()); break; } else { mappedFile = mappedFiles.get(index); byteBuffer = mappedFile.sliceByteBuffer(); processOffset = mappedFile.getFileFromOffset(); mappedFileOffset = 0; log.info("recover next physics file, {}", mappedFile.getFileName()); } } } else { if (size > 0) { log.warn("found a half message at {}, it will be truncated.", processOffset + mappedFileOffset); } log.info("recover physics file end, {} pos={}", mappedFile.getFileName(), byteBuffer.position()); break; } } try { this.getMessageStore().getQueueStore().flush(); } catch (StoreException e) { log.error("Failed to flush ConsumeQueueStore", e); } processOffset += mappedFileOffset; if (this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (this.defaultMessageStore.getConfirmOffset() < this.defaultMessageStore.getMinPhyOffset()) { log.error("confirmOffset {} is less than minPhyOffset {}, correct confirmOffset to minPhyOffset", this.defaultMessageStore.getConfirmOffset(), this.defaultMessageStore.getMinPhyOffset()); this.defaultMessageStore.setConfirmOffset(this.defaultMessageStore.getMinPhyOffset()); } else if (this.defaultMessageStore.getConfirmOffset() > lastConfirmValidMsgPhyOffset) { log.error("confirmOffset {} is larger than lastConfirmValidMsgPhyOffset {}, correct confirmOffset to lastConfirmValidMsgPhyOffset", this.defaultMessageStore.getConfirmOffset(), lastConfirmValidMsgPhyOffset); this.defaultMessageStore.setConfirmOffset(lastConfirmValidMsgPhyOffset); } } else { this.setConfirmOffset(lastValidMsgPhyOffset); } // Clear ConsumeQueue redundant data this.defaultMessageStore.truncateDirtyLogicFiles(processOffset); this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); } // Commitlog case files are deleted else { log.warn("The commitlog files are deleted, and delete the consume queue files"); this.mappedFileQueue.setFlushedWhere(0); this.mappedFileQueue.setCommittedWhere(0); this.defaultMessageStore.destroyConsumeQueueStore(true); } } public void truncateDirtyFiles(long phyOffset) { if (phyOffset <= this.getFlushedWhere()) { this.mappedFileQueue.setFlushedWhere(phyOffset); } if (phyOffset <= this.mappedFileQueue.getCommittedWhere()) { this.mappedFileQueue.setCommittedWhere(phyOffset); } this.mappedFileQueue.truncateDirtyFiles(phyOffset); if (this.confirmOffset > phyOffset) { this.setConfirmOffset(phyOffset); } } protected void onCommitLogAppend(MessageExtBrokerInner msg, AppendMessageResult result, MappedFile commitLogFile) { this.getMessageStore().onCommitLogAppend(msg, result, commitLogFile); } private boolean isMappedFileMatchedRecover(final MappedFile mappedFile, boolean recoverNormally) throws RocksDBException { ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover(); boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable(); DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover, checkDupInfo); if (!dispatchRequest.isSuccess()) { return false; } long storeTimestamp = dispatchRequest.getStoreTimestamp(); long phyOffset = dispatchRequest.getCommitLogOffset(); if (0 == dispatchRequest.getStoreTimestamp()) { return false; } return isMappedFileMatchedRecover(phyOffset, storeTimestamp, recoverNormally); } private boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException { boolean result = this.defaultMessageStore.getQueueStore().isMappedFileMatchedRecover(phyOffset, storeTimestamp, recoverNormally); // Check all registered CommitLogDispatchStore instances for (CommitLogDispatchStore store : defaultMessageStore.getCommitLogDispatchStores()) { result = result && store.isMappedFileMatchedRecover(phyOffset, storeTimestamp, recoverNormally); } return result; } public boolean resetOffset(long offset) { return this.mappedFileQueue.resetOffset(offset); } public long getBeginTimeInLock() { return beginTimeInLock; } public String generateKey(StringBuilder keyBuilder, MessageExt messageExt) { keyBuilder.setLength(0); keyBuilder.append(messageExt.getTopic()); keyBuilder.append('-'); keyBuilder.append(messageExt.getQueueId()); return keyBuilder.toString(); } public void setMappedFileQueueOffset(final long phyOffset) { this.mappedFileQueue.setFlushedWhere(phyOffset); this.mappedFileQueue.setCommittedWhere(phyOffset); } public void updateMaxMessageSize(PutMessageThreadLocal putMessageThreadLocal) { // dynamically adjust maxMessageSize, but not support DLedger mode temporarily int newMaxMessageSize = this.defaultMessageStore.getMessageStoreConfig().getMaxMessageSize(); if (newMaxMessageSize >= 10 && putMessageThreadLocal.getEncoder().getMaxMessageBodySize() != newMaxMessageSize) { putMessageThreadLocal.getEncoder().updateEncoderBufferCapacity(newMaxMessageSize); } } public CompletableFuture asyncPutMessage(final MessageExtBrokerInner msg) { // Set the storage time if (!defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { msg.setStoreTimestamp(System.currentTimeMillis()); } // Set the message body CRC (consider the most appropriate setting on the client) msg.setBodyCRC(UtilAll.crc32(msg.getBody())); if (enabledAppendPropCRC) { // delete crc32 properties if exist msg.deleteProperty(MessageConst.PROPERTY_CRC32); } // Back to Results AppendMessageResult result = null; StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService(); String topic = msg.getTopic(); msg.setVersion(MessageVersion.MESSAGE_VERSION_V1); boolean autoMessageVersionOnTopicLen = this.defaultMessageStore.getMessageStoreConfig().isAutoMessageVersionOnTopicLen(); if (autoMessageVersionOnTopicLen && topic.length() > Byte.MAX_VALUE) { msg.setVersion(MessageVersion.MESSAGE_VERSION_V2); } InetSocketAddress bornSocketAddress = (InetSocketAddress) msg.getBornHost(); if (bornSocketAddress.getAddress() instanceof Inet6Address) { msg.setBornHostV6Flag(); } InetSocketAddress storeSocketAddress = (InetSocketAddress) msg.getStoreHost(); if (storeSocketAddress.getAddress() instanceof Inet6Address) { msg.setStoreHostAddressV6Flag(); } PutMessageThreadLocal putMessageThreadLocal = this.putMessageThreadLocal.get(); updateMaxMessageSize(putMessageThreadLocal); String topicQueueKey = generateKey(putMessageThreadLocal.getKeyBuilder(), msg); long elapsedTimeInLock = 0; MappedFile unlockMappedFile = null; MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); long currOffset; if (mappedFile == null) { currOffset = 0; } else { currOffset = mappedFile.getFileFromOffset() + mappedFile.getWrotePosition(); } int needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas(); boolean needHandleHA = needHandleHA(msg); if (needHandleHA && this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (this.defaultMessageStore.getHaService().inSyncReplicasNums(currOffset) < this.defaultMessageStore.getMessageStoreConfig().getMinInSyncReplicas()) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null)); } if (this.defaultMessageStore.getMessageStoreConfig().isAllAckInSyncStateSet()) { // -1 means all ack in SyncStateSet needAckNums = MixAll.ALL_ACK_IN_SYNC_STATE_SET; } } else if (needHandleHA && this.defaultMessageStore.getBrokerConfig().isEnableSlaveActingMaster()) { int inSyncReplicas = Math.min(this.defaultMessageStore.getAliveReplicaNumInGroup(), this.defaultMessageStore.getHaService().inSyncReplicasNums(currOffset)); needAckNums = calcNeedAckNums(inSyncReplicas); if (needAckNums > inSyncReplicas) { // Tell the producer, don't have enough slaves to handle the send request return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null)); } } topicQueueLock.lock(topicQueueKey); try { boolean needAssignOffset = true; if (defaultMessageStore.getMessageStoreConfig().isDuplicationEnable() && defaultMessageStore.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE) { needAssignOffset = false; } if (needAssignOffset) { defaultMessageStore.assignOffset(msg); } PutMessageResult encodeResult = putMessageThreadLocal.getEncoder().encode(msg); if (encodeResult != null) { return CompletableFuture.completedFuture(encodeResult); } msg.setEncodedBuff(putMessageThreadLocal.getEncoder().getEncoderBuffer()); PutMessageContext putMessageContext = new PutMessageContext(topicQueueKey); putMessageLock.lock(); //spin or ReentrantLock, depending on store config try { long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now(); this.beginTimeInLock = beginLockTimestamp; // Here settings are stored timestamp, in order to ensure an orderly // global if (!defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { msg.setStoreTimestamp(beginLockTimestamp); } if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise if (isCloseReadAhead()) { setFileReadMode(mappedFile, LibC.MADV_RANDOM); } } if (null == mappedFile) { log.error("create mapped file1 error, topic: {} clientAddr: {}", msg.getTopic(), msg.getBornHostString()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, null)); } result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext); switch (result.getStatus()) { case PUT_OK: onCommitLogAppend(msg, result, mappedFile); break; case END_OF_FILE: onCommitLogAppend(msg, result, mappedFile); unlockMappedFile = mappedFile; // Create a new file, re-write the message mappedFile = this.mappedFileQueue.getLastMappedFile(0); if (null == mappedFile) { // XXX: warn and notify me log.error("create mapped file2 error, topic: {} clientAddr: {}", msg.getTopic(), msg.getBornHostString()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, result)); } if (isCloseReadAhead()) { setFileReadMode(mappedFile, LibC.MADV_RANDOM); } result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext); if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) { onCommitLogAppend(msg, result, mappedFile); } break; case MESSAGE_SIZE_EXCEEDED: case PROPERTIES_SIZE_EXCEEDED: return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result)); case UNKNOWN_ERROR: default: return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); } elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp; } finally { beginTimeInLock = 0; putMessageLock.unlock(); } // Increase queue offset when messages are successfully written if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) { this.defaultMessageStore.increaseOffset(msg, getMessageNum(msg)); } } catch (RocksDBException e) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); } finally { topicQueueLock.unlock(topicQueueKey); } if (elapsedTimeInLock > 500) { log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, msg.getBody().length, result); } if (null != unlockMappedFile && this.defaultMessageStore.getMessageStoreConfig().isWarmMapedFileEnable()) { this.defaultMessageStore.unlockMappedFile(unlockMappedFile); } PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result); // Statistics storeStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).add(result.getMsgNum()); storeStatsService.getSinglePutMessageTopicSizeTotal(topic).add(result.getWroteBytes()); return handleDiskFlushAndHA(putMessageResult, msg, needAckNums, needHandleHA); } public CompletableFuture asyncPutMessages(final MessageExtBatch messageExtBatch) { messageExtBatch.setStoreTimestamp(System.currentTimeMillis()); AppendMessageResult result = null; StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService(); final int tranType = MessageSysFlag.getTransactionValue(messageExtBatch.getSysFlag()); if (tranType != MessageSysFlag.TRANSACTION_NOT_TYPE) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } if (messageExtBatch.getDelayTimeLevel() > 0) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } InetSocketAddress bornSocketAddress = (InetSocketAddress) messageExtBatch.getBornHost(); if (bornSocketAddress.getAddress() instanceof Inet6Address) { messageExtBatch.setBornHostV6Flag(); } InetSocketAddress storeSocketAddress = (InetSocketAddress) messageExtBatch.getStoreHost(); if (storeSocketAddress.getAddress() instanceof Inet6Address) { messageExtBatch.setStoreHostAddressV6Flag(); } long elapsedTimeInLock = 0; MappedFile unlockMappedFile = null; MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); long currOffset; if (mappedFile == null) { currOffset = 0; } else { currOffset = mappedFile.getFileFromOffset() + mappedFile.getWrotePosition(); } int needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas(); boolean needHandleHA = needHandleHA(messageExtBatch); if (needHandleHA && this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) { if (this.defaultMessageStore.getHaService().inSyncReplicasNums(currOffset) < this.defaultMessageStore.getMessageStoreConfig().getMinInSyncReplicas()) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null)); } if (this.defaultMessageStore.getMessageStoreConfig().isAllAckInSyncStateSet()) { // -1 means all ack in SyncStateSet needAckNums = MixAll.ALL_ACK_IN_SYNC_STATE_SET; } } else if (needHandleHA && this.defaultMessageStore.getBrokerConfig().isEnableSlaveActingMaster()) { int inSyncReplicas = Math.min(this.defaultMessageStore.getAliveReplicaNumInGroup(), this.defaultMessageStore.getHaService().inSyncReplicasNums(currOffset)); needAckNums = calcNeedAckNums(inSyncReplicas); if (needAckNums > inSyncReplicas) { // Tell the producer, don't have enough slaves to handle the send request return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null)); } } messageExtBatch.setVersion(MessageVersion.MESSAGE_VERSION_V1); boolean autoMessageVersionOnTopicLen = this.defaultMessageStore.getMessageStoreConfig().isAutoMessageVersionOnTopicLen(); if (autoMessageVersionOnTopicLen && messageExtBatch.getTopic().length() > Byte.MAX_VALUE) { messageExtBatch.setVersion(MessageVersion.MESSAGE_VERSION_V2); } //fine-grained lock instead of the coarse-grained PutMessageThreadLocal pmThreadLocal = this.putMessageThreadLocal.get(); updateMaxMessageSize(pmThreadLocal); MessageExtEncoder batchEncoder = pmThreadLocal.getEncoder(); String topicQueueKey = generateKey(pmThreadLocal.getKeyBuilder(), messageExtBatch); PutMessageContext putMessageContext = new PutMessageContext(topicQueueKey); messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch, putMessageContext)); topicQueueLock.lock(topicQueueKey); try { defaultMessageStore.assignOffset(messageExtBatch); putMessageLock.lock(); try { long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now(); this.beginTimeInLock = beginLockTimestamp; // Here settings are stored timestamp, in order to ensure an orderly // global messageExtBatch.setStoreTimestamp(beginLockTimestamp); if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise if (isCloseReadAhead()) { setFileReadMode(mappedFile, LibC.MADV_RANDOM); } } if (null == mappedFile) { log.error("Create mapped file1 error, topic: {} clientAddr: {}", messageExtBatch.getTopic(), messageExtBatch.getBornHostString()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, null)); } result = mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback, putMessageContext); switch (result.getStatus()) { case PUT_OK: break; case END_OF_FILE: unlockMappedFile = mappedFile; // Create a new file, re-write the message mappedFile = this.mappedFileQueue.getLastMappedFile(0); if (null == mappedFile) { // XXX: warn and notify me log.error("Create mapped file2 error, topic: {} clientAddr: {}", messageExtBatch.getTopic(), messageExtBatch.getBornHostString()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, result)); } if (isCloseReadAhead()) { setFileReadMode(mappedFile, LibC.MADV_RANDOM); } result = mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback, putMessageContext); break; case MESSAGE_SIZE_EXCEEDED: case PROPERTIES_SIZE_EXCEEDED: return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result)); case UNKNOWN_ERROR: default: return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); } elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp; } finally { beginTimeInLock = 0; putMessageLock.unlock(); } // Increase queue offset when messages are successfully written if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) { this.defaultMessageStore.increaseOffset(messageExtBatch, (short) putMessageContext.getBatchSize()); } } catch (RocksDBException e) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); } finally { topicQueueLock.unlock(topicQueueKey); } if (elapsedTimeInLock > 500) { log.warn("[NOTIFYME]putMessages in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, messageExtBatch.getBody().length, result); } if (null != unlockMappedFile && this.defaultMessageStore.getMessageStoreConfig().isWarmMapedFileEnable()) { this.defaultMessageStore.unlockMappedFile(unlockMappedFile); } PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result); // Statistics storeStatsService.getSinglePutMessageTopicTimesTotal(messageExtBatch.getTopic()).add(result.getMsgNum()); storeStatsService.getSinglePutMessageTopicSizeTotal(messageExtBatch.getTopic()).add(result.getWroteBytes()); return handleDiskFlushAndHA(putMessageResult, messageExtBatch, needAckNums, needHandleHA); } private int calcNeedAckNums(int inSyncReplicas) { int needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas(); if (this.defaultMessageStore.getMessageStoreConfig().isEnableAutoInSyncReplicas()) { needAckNums = Math.min(needAckNums, inSyncReplicas); needAckNums = Math.max(needAckNums, this.defaultMessageStore.getMessageStoreConfig().getMinInSyncReplicas()); } return needAckNums; } private boolean needHandleHA(MessageExt messageExt) { if (!messageExt.isWaitStoreMsgOK()) { /* No need to sync messages that special config to extra broker slaves. @see MessageConst.PROPERTY_WAIT_STORE_MSG_OK */ return false; } if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { return false; } if (BrokerRole.SYNC_MASTER != this.defaultMessageStore.getMessageStoreConfig().getBrokerRole()) { // No need to check ha in async or slave broker return false; } return true; } private CompletableFuture handleDiskFlushAndHA(PutMessageResult putMessageResult, MessageExt messageExt, int needAckNums, boolean needHandleHA) { CompletableFuture flushResultFuture = handleDiskFlush(putMessageResult.getAppendMessageResult(), messageExt); CompletableFuture replicaResultFuture; if (!needHandleHA) { replicaResultFuture = CompletableFuture.completedFuture(PutMessageStatus.PUT_OK); } else { replicaResultFuture = handleHA(putMessageResult.getAppendMessageResult(), putMessageResult, needAckNums); } return flushResultFuture.thenCombine(replicaResultFuture, (flushStatus, replicaStatus) -> { if (flushStatus != PutMessageStatus.PUT_OK) { putMessageResult.setPutMessageStatus(flushStatus); } if (replicaStatus != PutMessageStatus.PUT_OK) { putMessageResult.setPutMessageStatus(replicaStatus); } return putMessageResult; }); } private CompletableFuture handleDiskFlush(AppendMessageResult result, MessageExt messageExt) { return this.flushManager.handleDiskFlush(result, messageExt); } private CompletableFuture handleHA(AppendMessageResult result, PutMessageResult putMessageResult, int needAckNums) { if (needAckNums >= 0 && needAckNums <= 1) { return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK); } HAService haService = this.defaultMessageStore.getHaService(); long nextOffset = result.getWroteOffset() + result.getWroteBytes(); // Wait enough acks from different slaves GroupCommitRequest request = new GroupCommitRequest(nextOffset, this.defaultMessageStore.getMessageStoreConfig().getSlaveTimeout(), needAckNums); haService.putRequest(request); haService.getWaitNotifyObject().wakeupAll(); return request.future(); } /** * According to receive certain message or offset storage time if an error occurs, it returns -1 */ public long pickupStoreTimestamp(final long offset, final int size) { if (defaultMessageStore.isShutdown()) { throw new RuntimeException("message store has shutdown"); } if (offset >= this.getMinOffset() && offset + size <= this.getMaxOffset()) { SelectMappedBufferResult result = this.getMessage(offset, size); if (null != result) { try { int sysFlag = result.getByteBuffer().getInt(MessageDecoder.SYSFLAG_POSITION); int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; int msgStoreTimePos = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 8 + bornhostLength; return result.getByteBuffer().getLong(msgStoreTimePos); } finally { result.release(); } } } return -1; } public long getMinOffset() { MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile(); if (mappedFile != null) { if (mappedFile.isAvailable()) { return mappedFile.getFileFromOffset(); } else { return this.rollNextFile(mappedFile.getFileFromOffset()); } } return -1; } public SelectMappedBufferResult getMessage(final long offset, final int size) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); SelectMappedBufferResult selectMappedBufferResult = mappedFile.selectMappedBuffer(pos, size); if (null != selectMappedBufferResult) { selectMappedBufferResult.setInCache(coldDataCheckService.isDataInPageCache(offset)); return selectMappedBufferResult; } } return null; } public long rollNextFile(final long offset) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); return offset + mappedFileSize - offset % mappedFileSize; } public void destroy() { this.mappedFileQueue.destroy(); } public boolean appendData(long startOffset, byte[] data, int dataStart, int dataLength) { putMessageLock.lock(); try { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(startOffset); if (null == mappedFile) { log.error("appendData getLastMappedFile error {}", startOffset); return false; } return mappedFile.appendMessage(data, dataStart, dataLength); } finally { putMessageLock.unlock(); } } public boolean retryDeleteFirstFile(final long intervalForcibly) { return this.mappedFileQueue.retryDeleteFirstFile(intervalForcibly); } public void checkSelf() { mappedFileQueue.checkSelf(); } public long lockTimeMills() { long diff = 0; long begin = this.beginTimeInLock; if (begin > 0) { diff = this.defaultMessageStore.now() - begin; } if (diff < 0) { diff = 0; } return diff; } protected short getMessageNum(MessageExtBrokerInner msgInner) { short messageNum = 1; // IF inner batch, build batchQueueOffset and batchNum property. CQType cqType = getCqType(msgInner); if (MessageSysFlag.check(msgInner.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG) || CQType.BatchCQ.equals(cqType)) { if (msgInner.getProperty(MessageConst.PROPERTY_INNER_NUM) != null) { messageNum = Short.parseShort(msgInner.getProperty(MessageConst.PROPERTY_INNER_NUM)); messageNum = messageNum >= 1 ? messageNum : 1; } } return messageNum; } private CQType getCqType(MessageExtBrokerInner msgInner) { Optional topicConfig = this.defaultMessageStore.getTopicConfig(msgInner.getTopic()); return QueueTypeUtils.getCQType(topicConfig); } abstract class FlushCommitLogService extends ServiceThread { protected static final int RETRY_TIMES_OVER = 10; } class CommitRealTimeService extends FlushCommitLogService { private long lastCommitTimestamp = 0; @Override public String getServiceName() { if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return CommitLog.this.defaultMessageStore.getBrokerIdentity().getIdentifier() + CommitRealTimeService.class.getSimpleName(); } return CommitRealTimeService.class.getSimpleName(); } @Override public void run() { CommitLog.log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitIntervalCommitLog(); int commitDataLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogLeastPages(); int commitDataThoroughInterval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogThoroughInterval(); long begin = System.currentTimeMillis(); if (begin >= (this.lastCommitTimestamp + commitDataThoroughInterval)) { this.lastCommitTimestamp = begin; commitDataLeastPages = 0; } try { boolean result = CommitLog.this.mappedFileQueue.commit(commitDataLeastPages); long end = System.currentTimeMillis(); if (!result) { this.lastCommitTimestamp = end; // result = false means some data committed. CommitLog.this.flushManager.wakeUpFlush(); } CommitLog.this.getMessageStore().getPerfCounter().flowOnce("COMMIT_DATA_TIME_MS", (int) (end - begin)); if (end - begin > 500) { log.info("Commit data to file costs {} ms", end - begin); } this.waitForRunning(interval); } catch (Throwable e) { CommitLog.log.error("{} service has exception. ", this.getServiceName(), e); } } boolean result = false; for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) { result = CommitLog.this.mappedFileQueue.commit(0); CommitLog.log.info("{} service shutdown, retry {} times {}", this.getServiceName(), i + 1, result ? "OK" : "Not OK"); } CommitLog.log.info("{} service end", this.getServiceName()); } } class FlushRealTimeService extends FlushCommitLogService { private long lastFlushTimestamp = 0; private long printTimes = 0; @Override public void run() { CommitLog.log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed(); int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog(); int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages(); int flushPhysicQueueThoroughInterval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval(); boolean printFlushProgress = false; // Print flush progress long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) { this.lastFlushTimestamp = currentTimeMillis; flushPhysicQueueLeastPages = 0; printFlushProgress = (printTimes++ % 10) == 0; } try { if (flushCommitLogTimed) { Thread.sleep(interval); } else { this.waitForRunning(interval); } if (printFlushProgress) { this.printFlushProgress(); } long begin = System.currentTimeMillis(); CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages); long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp(); if (storeTimestamp > 0) { CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp); } long past = System.currentTimeMillis() - begin; CommitLog.this.getMessageStore().getPerfCounter().flowOnce("FLUSH_DATA_TIME_MS", (int) past); if (past > 500) { log.info("Flush data to disk costs {} ms", past); } } catch (Throwable e) { CommitLog.log.warn("{} service has exception. ", this.getServiceName(), e); this.printFlushProgress(); } } // Normal shutdown, to ensure that all the flush before exit boolean result = false; for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) { result = CommitLog.this.mappedFileQueue.flush(0); CommitLog.log.info("{} service shutdown, retry {} times {}", this.getServiceName(), i + 1, result ? "OK" : "Not OK"); } this.printFlushProgress(); CommitLog.log.info("{} service end", this.getServiceName()); } @Override public String getServiceName() { if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + FlushRealTimeService.class.getSimpleName(); } return FlushRealTimeService.class.getSimpleName(); } private void printFlushProgress() { // CommitLog.log.info("how much disk fall behind memory, " // + CommitLog.this.mappedFileQueue.howMuchFallBehind()); } @Override public long getJoinTime() { return 1000 * 60 * 5; } } public static class GroupCommitRequest { private final long nextOffset; // Indicate the GroupCommitRequest result: true or false private final CompletableFuture flushOKFuture = new CompletableFuture<>(); private volatile int ackNums = 1; private final long deadLine; public GroupCommitRequest(long nextOffset, long timeoutMillis) { this.nextOffset = nextOffset; this.deadLine = System.nanoTime() + (timeoutMillis * 1_000_000); } public GroupCommitRequest(long nextOffset, long timeoutMillis, int ackNums) { this(nextOffset, timeoutMillis); this.ackNums = ackNums; } public long getNextOffset() { return nextOffset; } public int getAckNums() { return ackNums; } public long getDeadLine() { return deadLine; } public void wakeupCustomer(final PutMessageStatus status) { this.flushOKFuture.complete(status); } public CompletableFuture future() { return flushOKFuture; } } /** * GroupCommit Service */ class GroupCommitService extends FlushCommitLogService { private LinkedList requestsWrite = new LinkedList<>(); private LinkedList requestsRead = new LinkedList<>(); private final PutMessageSpinLock lock = new PutMessageSpinLock(); public void putRequest(final GroupCommitRequest request) { lock.lock(); try { this.requestsWrite.add(request); } finally { lock.unlock(); } this.wakeup(); } private void swapRequests() { lock.lock(); try { LinkedList tmp = this.requestsWrite; this.requestsWrite = this.requestsRead; this.requestsRead = tmp; } finally { lock.unlock(); } } private void doCommit() { if (!this.requestsRead.isEmpty()) { for (GroupCommitRequest req : this.requestsRead) { boolean flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset(); for (int i = 0; i < 1000 && !flushOK; i++) { CommitLog.this.mappedFileQueue.flush(0); flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset(); if (flushOK) { break; } else { // When transientStorePoolEnable is true, the messages in writeBuffer may not be committed // to pageCache very quickly, and flushOk here may almost be false, so we can sleep 1ms to // wait for the messages to be committed to pageCache. try { Thread.sleep(1); } catch (InterruptedException ignored) { } } } req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT); } long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp(); if (storeTimestamp > 0) { CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp); } this.requestsRead = new LinkedList<>(); } else { // Because of individual messages is set to not sync flush, it // will come to this process CommitLog.this.mappedFileQueue.flush(0); } } @Override public void run() { CommitLog.log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { this.waitForRunning(10); this.doCommit(); } catch (Exception e) { CommitLog.log.warn("{} service has exception. ", this.getServiceName(), e); } } // Under normal circumstances shutdown, wait for the arrival of the // request, and then flush try { Thread.sleep(10); } catch (InterruptedException e) { CommitLog.log.warn("GroupCommitService Exception, ", e); } this.swapRequests(); this.doCommit(); CommitLog.log.info("{} service end", this.getServiceName()); } @Override protected void onWaitEnd() { this.swapRequests(); } @Override public String getServiceName() { if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + GroupCommitService.class.getSimpleName(); } return GroupCommitService.class.getSimpleName(); } @Override public long getJoinTime() { return 1000 * 60 * 5; } } class GroupCheckService extends FlushCommitLogService { private volatile List requestsWrite = new ArrayList<>(); private volatile List requestsRead = new ArrayList<>(); public boolean isAsyncRequestsFull() { return requestsWrite.size() > CommitLog.this.defaultMessageStore.getMessageStoreConfig().getMaxAsyncPutMessageRequests() * 2; } public synchronized boolean putRequest(final GroupCommitRequest request) { synchronized (this.requestsWrite) { this.requestsWrite.add(request); } if (hasNotified.compareAndSet(false, true)) { waitPoint.countDown(); // notify } boolean flag = this.requestsWrite.size() > CommitLog.this.defaultMessageStore.getMessageStoreConfig().getMaxAsyncPutMessageRequests(); if (flag) { log.info("Async requests {} exceeded the threshold {}", requestsWrite.size(), CommitLog.this.defaultMessageStore.getMessageStoreConfig().getMaxAsyncPutMessageRequests()); } return flag; } private void swapRequests() { List tmp = this.requestsWrite; this.requestsWrite = this.requestsRead; this.requestsRead = tmp; } private void doCommit() { synchronized (this.requestsRead) { if (!this.requestsRead.isEmpty()) { for (GroupCommitRequest req : this.requestsRead) { // There may be a message in the next file, so a maximum of // two times the flush boolean flushOK = false; for (int i = 0; i < 1000; i++) { flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset(); if (flushOK) { break; } else { try { Thread.sleep(1); } catch (Throwable ignored) { } } } req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT); } long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp(); if (storeTimestamp > 0) { CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp); } this.requestsRead.clear(); } } } public void run() { CommitLog.log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { this.waitForRunning(1); this.doCommit(); } catch (Exception e) { CommitLog.log.warn("{} service has exception. ", this.getServiceName(), e); } } // Under normal circumstances shutdown, wait for the arrival of the // request, and then flush try { Thread.sleep(10); } catch (InterruptedException e) { CommitLog.log.warn("GroupCommitService Exception, ", e); } synchronized (this) { this.swapRequests(); } this.doCommit(); CommitLog.log.info("{} service end", this.getServiceName()); } @Override protected void onWaitEnd() { this.swapRequests(); } @Override public String getServiceName() { if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + GroupCheckService.class.getSimpleName(); } return GroupCheckService.class.getSimpleName(); } @Override public long getJoinTime() { return 1000 * 60 * 5; } } class DefaultAppendMessageCallback implements AppendMessageCallback { // File at the end of the minimum fixed length empty private static final int END_FILE_MIN_BLANK_LENGTH = 4 + 4; // Store the message content private final ByteBuffer msgStoreItemMemory; private final int crc32ReservedLength; private final MessageStoreConfig messageStoreConfig; DefaultAppendMessageCallback(MessageStoreConfig messageStoreConfig) { this.msgStoreItemMemory = ByteBuffer.allocate(END_FILE_MIN_BLANK_LENGTH); this.messageStoreConfig = messageStoreConfig; this.crc32ReservedLength = messageStoreConfig.isEnabledAppendPropCRC() ? CommitLog.CRC32_RESERVED_LEN : 0; } public AppendMessageResult handlePropertiesForLmqMsg(ByteBuffer preEncodeBuffer, final MessageExtBrokerInner msgInner) { if (msgInner.isEncodeCompleted()) { return null; } try { LmqDispatch.wrapLmqDispatch(defaultMessageStore, msgInner); } catch (ConsumeQueueException e) { if (e.getCause() instanceof RocksDBException) { log.error("Failed to wrap multi-dispatch", e); return new AppendMessageResult(AppendMessageStatus.ROCKSDB_ERROR); } log.error("Failed to wrap multi-dispatch", e); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); final byte[] propertiesData = msgInner.getPropertiesString() == null ? null : msgInner.getPropertiesString().getBytes(MessageDecoder.CHARSET_UTF8); boolean needAppendLastPropertySeparator = enabledAppendPropCRC && propertiesData != null && propertiesData.length > 0 && propertiesData[propertiesData.length - 1] != MessageDecoder.PROPERTY_SEPARATOR; final int propertiesLength = (propertiesData == null ? 0 : propertiesData.length) + (needAppendLastPropertySeparator ? 1 : 0) + crc32ReservedLength; if (propertiesLength > Short.MAX_VALUE) { log.warn("putMessage message properties length too long. length={}", propertiesData.length); return new AppendMessageResult(AppendMessageStatus.PROPERTIES_SIZE_EXCEEDED); } int msgLenWithoutProperties = preEncodeBuffer.getInt(0); int msgLen = msgLenWithoutProperties + 2 + propertiesLength; // Exceeds the maximum message if (msgLen > this.messageStoreConfig.getMaxMessageSize()) { log.warn("message size exceeded, msg total size: {}, maxMessageSize: {}", msgLen, this.messageStoreConfig.getMaxMessageSize()); return new AppendMessageResult(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED); } // Back filling total message length preEncodeBuffer.putInt(0, msgLen); // Modify position to msgLenWithoutProperties preEncodeBuffer.position(msgLenWithoutProperties); preEncodeBuffer.putShort((short) propertiesLength); if (propertiesLength > crc32ReservedLength) { preEncodeBuffer.put(propertiesData); } if (needAppendLastPropertySeparator) { preEncodeBuffer.put((byte) MessageDecoder.PROPERTY_SEPARATOR); } // 18 CRC32 preEncodeBuffer.position(preEncodeBuffer.position() + crc32ReservedLength); msgInner.setEncodeCompleted(true); return null; } public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank, final MessageExtBrokerInner msgInner, PutMessageContext putMessageContext) { // STORETIMESTAMP + STOREHOSTADDRESS + OFFSET
    ByteBuffer preEncodeBuffer = msgInner.getEncodedBuff(); boolean isMultiDispatchMsg = messageStoreConfig.isEnableLmq() && msgInner.needDispatchLMQ(); if (isMultiDispatchMsg) { AppendMessageResult appendMessageResult = handlePropertiesForLmqMsg(preEncodeBuffer, msgInner); if (appendMessageResult != null) { return appendMessageResult; } } final int msgLen = preEncodeBuffer.getInt(0); preEncodeBuffer.position(0); preEncodeBuffer.limit(msgLen); // PHY OFFSET long wroteOffset = fileFromOffset + byteBuffer.position(); Supplier msgIdSupplier = () -> { int sysflag = msgInner.getSysFlag(); int msgIdLen = (sysflag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 + 8 : 16 + 4 + 8; ByteBuffer msgIdBuffer = ByteBuffer.allocate(msgIdLen); MessageExt.socketAddress2ByteBuffer(msgInner.getStoreHost(), msgIdBuffer); msgIdBuffer.clear();//because socketAddress2ByteBuffer flip the buffer msgIdBuffer.putLong(msgIdLen - 8, wroteOffset); return UtilAll.bytes2string(msgIdBuffer.array()); }; // Record ConsumeQueue information Long queueOffset = msgInner.getQueueOffset(); // this msg maybe an inner-batch msg. short messageNum = getMessageNum(msgInner); // Transaction messages that require special handling final int tranType = MessageSysFlag.getTransactionValue(msgInner.getSysFlag()); switch (tranType) { // Prepared and Rollback message is not consumed, will not enter the consume queue case MessageSysFlag.TRANSACTION_PREPARED_TYPE: case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: queueOffset = 0L; break; case MessageSysFlag.TRANSACTION_NOT_TYPE: case MessageSysFlag.TRANSACTION_COMMIT_TYPE: default: break; } // Determines whether there is sufficient free space if ((msgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) { this.msgStoreItemMemory.clear(); // 1 TOTALSIZE this.msgStoreItemMemory.putInt(maxBlank); // 2 MAGICCODE this.msgStoreItemMemory.putInt(CommitLog.BLANK_MAGIC_CODE); // 3 The remaining space may be any value // Here the length of the specially set maxBlank final long beginTimeMills = CommitLog.this.defaultMessageStore.now(); byteBuffer.put(this.msgStoreItemMemory.array(), 0, 8); return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, wroteOffset, maxBlank, /* only wrote 8 bytes, but declare wrote maxBlank for compute write position */ msgIdSupplier, msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); } int pos = 4 // 1 TOTALSIZE + 4 // 2 MAGICCODE + 4 // 3 BODYCRC + 4 // 4 QUEUEID + 4; // 5 FLAG // 6 QUEUEOFFSET preEncodeBuffer.putLong(pos, queueOffset); pos += 8; // 7 PHYSICALOFFSET preEncodeBuffer.putLong(pos, fileFromOffset + byteBuffer.position()); pos += 8; int ipLen = (msgInner.getSysFlag() & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; // 8 SYSFLAG, 9 BORNTIMESTAMP, 10 BORNHOST pos += 4 + 8 + ipLen; // 11 STORETIMESTAMP refresh store time stamp in lock preEncodeBuffer.putLong(pos, msgInner.getStoreTimestamp()); if (enabledAppendPropCRC) { // 18 CRC32 int checkSize = msgLen - crc32ReservedLength; ByteBuffer tmpBuffer = preEncodeBuffer.duplicate(); tmpBuffer.limit(tmpBuffer.position() + checkSize); int crc32 = UtilAll.crc32(tmpBuffer); // UtilAll.crc32 function will change the position to limit of the buffer tmpBuffer.limit(tmpBuffer.position() + crc32ReservedLength); MessageDecoder.createCrc32(tmpBuffer, crc32); } final long beginTimeMills = CommitLog.this.defaultMessageStore.now(); CommitLog.this.getMessageStore().getPerfCounter().startTick("WRITE_MEMORY_TIME_MS"); // Write messages to the queue buffer byteBuffer.put(preEncodeBuffer); CommitLog.this.getMessageStore().getPerfCounter().endTick("WRITE_MEMORY_TIME_MS"); msgInner.setEncodedBuff(null); if (isMultiDispatchMsg) { try { LmqDispatch.updateLmqOffsets(defaultMessageStore, msgInner); } catch (ConsumeQueueException e) { // Increase in-memory max offset of the queue should not fail. return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } } return new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgIdSupplier, msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills, messageNum); } public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank, final MessageExtBatch messageExtBatch, PutMessageContext putMessageContext) { byteBuffer.mark(); //physical offset long wroteOffset = fileFromOffset + byteBuffer.position(); // Record ConsumeQueue information Long queueOffset = messageExtBatch.getQueueOffset(); long beginQueueOffset = queueOffset; int totalMsgLen = 0; int msgNum = 0; final long beginTimeMills = CommitLog.this.defaultMessageStore.now(); ByteBuffer messagesByteBuff = messageExtBatch.getEncodedBuff(); int sysFlag = messageExtBatch.getSysFlag(); int bornHostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; int storeHostLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; Supplier msgIdSupplier = () -> { int msgIdLen = storeHostLength + 8; int batchCount = putMessageContext.getBatchSize(); long[] phyPosArray = putMessageContext.getPhyPos(); ByteBuffer msgIdBuffer = ByteBuffer.allocate(msgIdLen); MessageExt.socketAddress2ByteBuffer(messageExtBatch.getStoreHost(), msgIdBuffer); msgIdBuffer.clear();//because socketAddress2ByteBuffer flip the buffer StringBuilder buffer = new StringBuilder(batchCount * msgIdLen * 2 + batchCount - 1); for (int i = 0; i < phyPosArray.length; i++) { msgIdBuffer.putLong(msgIdLen - 8, phyPosArray[i]); String msgId = UtilAll.bytes2string(msgIdBuffer.array()); if (i != 0) { buffer.append(','); } buffer.append(msgId); } return buffer.toString(); }; messagesByteBuff.mark(); int index = 0; while (messagesByteBuff.hasRemaining()) { // 1 TOTALSIZE final int msgPos = messagesByteBuff.position(); final int msgLen = messagesByteBuff.getInt(); totalMsgLen += msgLen; // Determines whether there is sufficient free space if ((totalMsgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) { this.msgStoreItemMemory.clear(); // 1 TOTALSIZE this.msgStoreItemMemory.putInt(maxBlank); // 2 MAGICCODE this.msgStoreItemMemory.putInt(CommitLog.BLANK_MAGIC_CODE); // 3 The remaining space may be any value //ignore previous read messagesByteBuff.reset(); // Here the length of the specially set maxBlank byteBuffer.reset(); //ignore the previous appended messages byteBuffer.put(this.msgStoreItemMemory.array(), 0, 8); return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, wroteOffset, maxBlank, msgIdSupplier, messageExtBatch.getStoreTimestamp(), beginQueueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); } //move to add queue offset and commitlog offset int pos = msgPos + 20; messagesByteBuff.putLong(pos, queueOffset); pos += 8; messagesByteBuff.putLong(pos, wroteOffset + totalMsgLen - msgLen); // 8 SYSFLAG, 9 BORNTIMESTAMP, 10 BORNHOST, 11 STORETIMESTAMP pos += 8 + 4 + 8 + bornHostLength; // refresh store time stamp in lock messagesByteBuff.putLong(pos, messageExtBatch.getStoreTimestamp()); if (enabledAppendPropCRC) { //append crc32 int checkSize = msgLen - crc32ReservedLength; ByteBuffer tmpBuffer = messagesByteBuff.duplicate(); tmpBuffer.position(msgPos).limit(msgPos + checkSize); int crc32 = UtilAll.crc32(tmpBuffer); messagesByteBuff.position(msgPos + checkSize); MessageDecoder.createCrc32(messagesByteBuff, crc32); } putMessageContext.getPhyPos()[index++] = wroteOffset + totalMsgLen - msgLen; queueOffset++; msgNum++; messagesByteBuff.position(msgPos + msgLen); } messagesByteBuff.position(0); messagesByteBuff.limit(totalMsgLen); byteBuffer.put(messagesByteBuff); messageExtBatch.setEncodedBuff(null); AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, totalMsgLen, msgIdSupplier, messageExtBatch.getStoreTimestamp(), beginQueueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); result.setMsgNum(msgNum); return result; } } class DefaultFlushManager implements FlushManager { private final FlushCommitLogService flushCommitLogService; //If TransientStorePool enabled, we must flush message to FileChannel at fixed periods private final FlushCommitLogService commitRealTimeService; public DefaultFlushManager() { if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { this.flushCommitLogService = new CommitLog.GroupCommitService(); } else { this.flushCommitLogService = new CommitLog.FlushRealTimeService(); } this.commitRealTimeService = new CommitLog.CommitRealTimeService(); } @Override public void start() { this.flushCommitLogService.start(); if (defaultMessageStore.isTransientStorePoolEnable()) { this.commitRealTimeService.start(); } } @Override public void handleDiskFlush(AppendMessageResult result, PutMessageResult putMessageResult, MessageExt messageExt) { // Synchronization flush if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { final GroupCommitService service = (GroupCommitService) this.flushCommitLogService; if (messageExt.isWaitStoreMsgOK()) { GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(), CommitLog.this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout()); service.putRequest(request); CompletableFuture flushOkFuture = request.future(); PutMessageStatus flushStatus = null; try { flushStatus = flushOkFuture.get(CommitLog.this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { //flushOK=false; } if (flushStatus != PutMessageStatus.PUT_OK) { log.error("do groupcommit, wait for flush failed, topic: {} tags: {} client address: {}", messageExt.getTopic(), messageExt.getTags(), messageExt.getBornHostString()); putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT); } } else { service.wakeup(); } } // Asynchronous flush else { if (!CommitLog.this.defaultMessageStore.isTransientStorePoolEnable()) { if (defaultMessageStore.getMessageStoreConfig().isWakeFlushWhenPutMessage()) { flushCommitLogService.wakeup(); } } else { if (defaultMessageStore.getMessageStoreConfig().isWakeCommitWhenPutMessage()) { commitRealTimeService.wakeup(); } } } } @Override public CompletableFuture handleDiskFlush(AppendMessageResult result, MessageExt messageExt) { // Synchronization flush if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { final GroupCommitService service = (GroupCommitService) this.flushCommitLogService; if (messageExt.isWaitStoreMsgOK()) { GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(), CommitLog.this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout()); flushDiskWatcher.add(request); service.putRequest(request); return request.future(); } else { service.wakeup(); return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK); } } // Asynchronous flush else { if (!CommitLog.this.defaultMessageStore.isTransientStorePoolEnable()) { if (defaultMessageStore.getMessageStoreConfig().isWakeFlushWhenPutMessage()) { flushCommitLogService.wakeup(); } } else { if (defaultMessageStore.getMessageStoreConfig().isWakeCommitWhenPutMessage()) { commitRealTimeService.wakeup(); } } return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK); } } @Override public void wakeUpFlush() { // now wake up flush thread. flushCommitLogService.wakeup(); } @Override public void wakeUpCommit() { // now wake up commit log thread. commitRealTimeService.wakeup(); } @Override public void shutdown() { if (defaultMessageStore.isTransientStorePoolEnable()) { this.commitRealTimeService.shutdown(); } this.flushCommitLogService.shutdown(); } } public int getCommitLogSize() { return commitLogSize; } public MappedFileQueue getMappedFileQueue() { return mappedFileQueue; } public MessageStore getMessageStore() { return defaultMessageStore; } public MappedFile getEarliestMappedFile() { return mappedFileQueue.getEarliestMappedFile(); } @Override public void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) { this.getMappedFileQueue().swapMap(reserveNum, forceSwapIntervalMs, normalSwapIntervalMs); } public boolean isMappedFilesEmpty() { return this.mappedFileQueue.isMappedFilesEmpty(); } @Override public void cleanSwappedMap(long forceCleanSwapIntervalMs) { this.getMappedFileQueue().cleanSwappedMap(forceCleanSwapIntervalMs); } public FlushManager getFlushManager() { return flushManager; } private boolean isCloseReadAhead() { return !MixAll.isWindows() && !defaultMessageStore.getMessageStoreConfig().isDataReadAheadEnable(); } public class ColdDataCheckService extends ServiceThread { private final SystemClock systemClock = new SystemClock(); private final ConcurrentHashMap pageCacheMap = new ConcurrentHashMap<>(); private int pageSize = -1; private int sampleSteps = 32; public ColdDataCheckService() { sampleSteps = defaultMessageStore.getMessageStoreConfig().getSampleSteps(); if (sampleSteps <= 0) { sampleSteps = 32; } initPageSize(); scanFilesInPageCache(); } @Override public String getServiceName() { return ColdDataCheckService.class.getSimpleName(); } @Override public void run() { log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { if (MixAll.isWindows() || !defaultMessageStore.getMessageStoreConfig().isColdDataFlowControlEnable() || !defaultMessageStore.getMessageStoreConfig().isColdDataScanEnable()) { pageCacheMap.clear(); this.waitForRunning(180 * 1000); continue; } else { this.waitForRunning(defaultMessageStore.getMessageStoreConfig().getTimerColdDataCheckIntervalMs()); } if (pageSize < 0) { initPageSize(); } long beginClockTimestamp = this.systemClock.now(); scanFilesInPageCache(); long costTime = this.systemClock.now() - beginClockTimestamp; log.info("[{}] scanFilesInPageCache-cost {} ms.", costTime > 30 * 1000 ? "NOTIFYME" : "OK", costTime); } catch (Throwable e) { log.warn("{} service has e: ", this.getServiceName(), e); } } log.info("{} service end", this.getServiceName()); } public boolean isDataInPageCache(final long offset) { if (!defaultMessageStore.getMessageStoreConfig().isColdDataFlowControlEnable()) { return true; } if (pageSize <= 0 || sampleSteps <= 0) { return true; } if (!defaultMessageStore.checkInColdAreaByCommitOffset(offset, getMaxOffset())) { return true; } if (!defaultMessageStore.getMessageStoreConfig().isColdDataScanEnable()) { return false; } MappedFile mappedFile = mappedFileQueue.findMappedFileByOffset(offset, offset == 0); if (null == mappedFile) { return true; } byte[] bytes = pageCacheMap.get(mappedFile.getFileName()); if (null == bytes) { return true; } int pos = (int) (offset % defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog()); int realIndex = pos / pageSize / sampleSteps; return bytes.length - 1 >= realIndex && bytes[realIndex] != 0; } private void scanFilesInPageCache() { if (MixAll.isWindows() || !defaultMessageStore.getMessageStoreConfig().isColdDataFlowControlEnable() || !defaultMessageStore.getMessageStoreConfig().isColdDataScanEnable() || pageSize <= 0) { return; } try { log.info("pageCacheMap key size: {}", pageCacheMap.size()); clearExpireMappedFile(); mappedFileQueue.getMappedFiles().forEach(mappedFile -> { byte[] pageCacheTable = checkFileInPageCache(mappedFile); if (sampleSteps > 1) { pageCacheTable = sampling(pageCacheTable, sampleSteps); } pageCacheMap.put(mappedFile.getFileName(), pageCacheTable); }); } catch (Exception e) { log.error("scanFilesInPageCache exception", e); } } private void clearExpireMappedFile() { Set currentFileSet = mappedFileQueue.getMappedFiles().stream().map(MappedFile::getFileName).collect(Collectors.toSet()); pageCacheMap.forEach((key, value) -> { if (!currentFileSet.contains(key)) { pageCacheMap.remove(key); log.info("clearExpireMappedFile fileName: {}, has been clear", key); } }); } private byte[] sampling(byte[] pageCacheTable, int sampleStep) { byte[] sample = new byte[(pageCacheTable.length + sampleStep - 1) / sampleStep]; for (int i = 0, j = 0; i < pageCacheTable.length && j < sample.length; i += sampleStep) { sample[j++] = pageCacheTable[i]; } return sample; } private byte[] checkFileInPageCache(MappedFile mappedFile) { long fileSize = mappedFile.getFileSize(); final long address = PlatformDependent.directBufferAddress(mappedFile.getMappedByteBuffer()); int pageNums = (int) (fileSize + this.pageSize - 1) / this.pageSize; byte[] pageCacheRst = new byte[pageNums]; int mincore = LibC.INSTANCE.mincore(new Pointer(address), new NativeLong(fileSize), pageCacheRst); if (mincore != 0) { log.error("checkFileInPageCache call the LibC.INSTANCE.mincore error, fileName: {}, fileSize: {}", mappedFile.getFileName(), fileSize); for (int i = 0; i < pageNums; i++) { pageCacheRst[i] = 1; } } return pageCacheRst; } private void initPageSize() { if (pageSize < 0 && defaultMessageStore.getMessageStoreConfig().isColdDataFlowControlEnable()) { try { if (!MixAll.isWindows()) { pageSize = LibC.INSTANCE.getpagesize(); } else { defaultMessageStore.getMessageStoreConfig().setColdDataFlowControlEnable(false); log.info("windows os, coldDataCheckEnable force setting to be false"); } log.info("initPageSize pageSize: {}", pageSize); } catch (Exception e) { defaultMessageStore.getMessageStoreConfig().setColdDataFlowControlEnable(false); log.error("initPageSize error, coldDataCheckEnable force setting to be false ", e); } } } /** * this result is not high accurate. */ public boolean isMsgInColdArea(String group, String topic, int queueId, long offset) { if (!defaultMessageStore.getMessageStoreConfig().isColdDataFlowControlEnable()) { return false; } try { ConsumeQueueInterface consumeQueue = defaultMessageStore.findConsumeQueue(topic, queueId); if (null == consumeQueue) { return false; } CqUnit cqUnit = consumeQueue.get(offset); if (null == cqUnit) { return false; } long offsetPy = cqUnit.getPos(); if (offsetPy < 0L) { return false; } return defaultMessageStore.checkInColdAreaByCommitOffset(offsetPy, getMaxOffset()); } catch (Exception e) { log.error("isMsgInColdArea group: {}, topic: {}, queueId: {}, offset: {}", group, topic, queueId, offset, e); } return false; } } public void scanFileAndSetReadMode(int mode) { if (MixAll.isWindows()) { log.info("windows os stop scanFileAndSetReadMode"); return; } try { log.info("scanFileAndSetReadMode mode: {}", mode); mappedFileQueue.getMappedFiles().forEach(mappedFile -> { setFileReadMode(mappedFile, mode); }); } catch (Exception e) { log.error("scanFileAndSetReadMode exception", e); } } private int setFileReadMode(MappedFile mappedFile, int mode) { if (null == mappedFile) { log.error("setFileReadMode mappedFile is null"); return -1; } final long address = PlatformDependent.directBufferAddress(mappedFile.getMappedByteBuffer()); int madvise = LibC.INSTANCE.madvise(new Pointer(address), new NativeLong(mappedFile.getFileSize()), mode); if (madvise != 0) { log.error("setFileReadMode error fileName: {}, madvise: {}, mode:{}", mappedFile.getFileName(), madvise, mode); } return madvise; } public ColdDataCheckService getColdDataCheckService() { return coldDataCheckService; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/CommitLogDispatchStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.rocksdb.RocksDBException; /** * Interface for stores that require commitlog dispatch and recovery. Each store implementing this interface should * register itself in the commitlog when loading. This abstraction allows the commitlog recovery process to * automatically consider all registered stores without needing to modify the recovery logic when adding a new store. */ public interface CommitLogDispatchStore { /** * Get the dispatch offset in the store. Messages whose phyOffset larger than this offset need to be dispatched. The * dispatch offset is only used during recovery. * * @param recoverNormally true if broker exited normally last time (normal recovery), false for abnormal recovery * @return the dispatch phyOffset, or null if the store is not enabled or has no valid offset * @throws RocksDBException if there is an error accessing RocksDB storage */ Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException; /** * Used to determine whether to start doDispatch from this commitLog mappedFile. * * @param phyOffset the offset of the first message in this commitlog mappedFile * @param storeTimestamp the timestamp of the first message in this commitlog mappedFile * @param recoverNormally whether this is a normal recovery * @return whether to start recovering from this MappedFile * @throws RocksDBException if there is an error accessing RocksDB storage */ boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException; } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/CommitLogDispatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.rocksdb.RocksDBException; /** * Dispatcher of commit log. */ public interface CommitLogDispatcher { /** * Dispatch messages from store to build consume queues, indexes, and filter data * @param request dispatch message request * @throws RocksDBException only in rocksdb mode */ void dispatch(final DispatchRequest request) throws RocksDBException; } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/CompactionAppendMsgCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; public interface CompactionAppendMsgCallback { AppendMessageResult doAppend(ByteBuffer bbDest, long fileFromOffset, int maxBlank, ByteBuffer bbSrc); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStore; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.MultiDispatchUtils; import org.apache.rocketmq.store.queue.QueueOffsetOperator; import org.apache.rocketmq.store.queue.ReferredIterator; public class ConsumeQueue implements ConsumeQueueInterface { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); /** * ConsumeQueue's store unit. Format: *

         * ┌───────────────────────────────┬───────────────────┬───────────────────────────────┐
         * │    CommitLog Physical Offset  │      Body Size    │            Tag HashCode       │
         * │          (8 Bytes)            │      (4 Bytes)    │             (8 Bytes)         │
         * ├───────────────────────────────┴───────────────────┴───────────────────────────────┤
         * │                                     Store Unit                                    │
         * │                                                                                   │
         * 
    * ConsumeQueue's store unit. Size: CommitLog Physical Offset(8) + Body Size(4) + Tag HashCode(8) = 20 Bytes */ public static final int CQ_STORE_UNIT_SIZE = 20; public static final int MSG_TAG_OFFSET_INDEX = 12; private static final Logger LOG_ERROR = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private final MessageStore messageStore; private final ConsumeQueueStore consumeQueueStore; private final MappedFileQueue mappedFileQueue; private final String topic; private final int queueId; private final ByteBuffer byteBufferIndex; private final String storePath; private final int mappedFileSize; private long maxPhysicOffset = -1; /** * Minimum offset of the consume file queue that points to valid commit log record. */ private volatile long minLogicOffset = 0; private ConsumeQueueExt consumeQueueExt = null; public ConsumeQueue(final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore messageStore) { this(topic, queueId, storePath, mappedFileSize, messageStore, (ConsumeQueueStore) messageStore.getQueueStore()); } public ConsumeQueue(final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore messageStore, final ConsumeQueueStore consumeQueueStore) { this.storePath = storePath; this.mappedFileSize = mappedFileSize; this.messageStore = messageStore; this.consumeQueueStore = consumeQueueStore; this.topic = topic; this.queueId = queueId; String queueDir = this.storePath + File.separator + topic + File.separator + queueId; boolean writeWithoutMmap = false; if (messageStore.getMessageStoreConfig() != null) { writeWithoutMmap = messageStore.getMessageStoreConfig().isWriteWithoutMmap(); } this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null, writeWithoutMmap); this.byteBufferIndex = ByteBuffer.allocate(CQ_STORE_UNIT_SIZE); if (messageStore.getMessageStoreConfig().isEnableConsumeQueueExt()) { this.consumeQueueExt = new ConsumeQueueExt( topic, queueId, StorePathConfigHelper.getStorePathConsumeQueueExt(messageStore.getMessageStoreConfig().getStorePathRootDir()), messageStore.getMessageStoreConfig().getMappedFileSizeConsumeQueueExt(), messageStore.getMessageStoreConfig().getBitMapLengthConsumeQueueExt(), writeWithoutMmap ); } } @Override public boolean load() { boolean result = this.mappedFileQueue.load(); log.info("load consume queue " + this.topic + "-" + this.queueId + " " + (result ? "OK" : "Failed")); if (isExtReadEnable()) { result &= this.consumeQueueExt.load(); } return result; } @Override public void recover() { final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { int index = mappedFiles.size() - 3; if (index < 0) { index = 0; } int mappedFileSizeLogics = this.mappedFileSize; MappedFile mappedFile = mappedFiles.get(index); ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset = 0; long maxExtAddr = 1; while (true) { for (int i = 0; i < mappedFileSizeLogics; i += CQ_STORE_UNIT_SIZE) { long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); long tagsCode = byteBuffer.getLong(); if (offset >= 0 && size > 0) { mappedFileOffset = i + CQ_STORE_UNIT_SIZE; this.setMaxPhysicOffset(offset + size); if (isExtAddr(tagsCode)) { maxExtAddr = tagsCode; } } else { log.info("recover current consume queue file over, " + mappedFile.getFileName() + " " + offset + " " + size + " " + tagsCode); break; } } if (mappedFileOffset == mappedFileSizeLogics) { index++; if (index >= mappedFiles.size()) { log.info("recover last consume queue file over, last mapped file " + mappedFile.getFileName()); break; } else { mappedFile = mappedFiles.get(index); byteBuffer = mappedFile.sliceByteBuffer(); processOffset = mappedFile.getFileFromOffset(); mappedFileOffset = 0; log.info("recover next consume queue file, " + mappedFile.getFileName()); } } else { log.info("recover current consume queue over " + mappedFile.getFileName() + " " + (processOffset + mappedFileOffset)); break; } } processOffset += mappedFileOffset; this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); if (isExtReadEnable()) { this.consumeQueueExt.recover(); log.info("Truncate consume queue extend file by max {}", maxExtAddr); this.consumeQueueExt.truncateByMaxAddress(maxExtAddr); } } } @Override public long getTotalSize() { long totalSize = this.mappedFileQueue.getTotalFileSize(); if (isExtReadEnable()) { totalSize += this.consumeQueueExt.getTotalSize(); } return totalSize; } @Override public int getUnitSize() { return CQ_STORE_UNIT_SIZE; } @Deprecated @Override public long getOffsetInQueueByTime(final long timestamp) { MappedFile mappedFile = this.mappedFileQueue.getConsumeQueueMappedFileByTime(timestamp, messageStore.getCommitLog(), BoundaryType.LOWER); return binarySearchInQueueByTime(mappedFile, timestamp, BoundaryType.LOWER); } @Override public long getOffsetInQueueByTime(final long timestamp, final BoundaryType boundaryType) { MappedFile mappedFile = this.mappedFileQueue.getConsumeQueueMappedFileByTime(timestamp, messageStore.getCommitLog(), boundaryType); return binarySearchInQueueByTime(mappedFile, timestamp, boundaryType); } private long binarySearchInQueueByTime(final MappedFile mappedFile, final long timestamp, BoundaryType boundaryType) { if (mappedFile != null) { long offset = 0; int low = minLogicOffset > mappedFile.getFileFromOffset() ? (int) (minLogicOffset - mappedFile.getFileFromOffset()) : 0; int high = 0; int midOffset = -1, targetOffset = -1, leftOffset = -1, rightOffset = -1; long minPhysicOffset = this.messageStore.getMinPhyOffset(); int range = mappedFile.getFileSize(); if (mappedFile.getWrotePosition() != 0 && mappedFile.getWrotePosition() != mappedFile.getFileSize()) { // mappedFile is the last one and is currently being written. range = mappedFile.getWrotePosition(); } SelectMappedBufferResult sbr = mappedFile.selectMappedBuffer(0, range); if (null != sbr) { ByteBuffer byteBuffer = sbr.getByteBuffer(); int ceiling = byteBuffer.limit() - CQ_STORE_UNIT_SIZE; int floor = low; high = ceiling; try { // Handle the following corner cases first: // 1. store time of (high) < timestamp // 2. store time of (low) > timestamp long storeTime; long phyOffset; int size; // Handle case 1 byteBuffer.position(ceiling); phyOffset = byteBuffer.getLong(); size = byteBuffer.getInt(); storeTime = messageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size); if (storeTime < timestamp) { switch (boundaryType) { case LOWER: return (mappedFile.getFileFromOffset() + ceiling + CQ_STORE_UNIT_SIZE) / CQ_STORE_UNIT_SIZE; case UPPER: return (mappedFile.getFileFromOffset() + ceiling) / CQ_STORE_UNIT_SIZE; default: log.warn("Unknown boundary type"); break; } } // Handle case 2 byteBuffer.position(floor); phyOffset = byteBuffer.getLong(); size = byteBuffer.getInt(); storeTime = messageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size); if (storeTime > timestamp) { switch (boundaryType) { case LOWER: return mappedFile.getFileFromOffset() / CQ_STORE_UNIT_SIZE; case UPPER: return 0; default: log.warn("Unknown boundary type"); break; } } // Perform binary search while (high >= low) { midOffset = (low + high) / (2 * CQ_STORE_UNIT_SIZE) * CQ_STORE_UNIT_SIZE; byteBuffer.position(midOffset); phyOffset = byteBuffer.getLong(); size = byteBuffer.getInt(); if (phyOffset < minPhysicOffset) { low = midOffset + CQ_STORE_UNIT_SIZE; leftOffset = midOffset; continue; } storeTime = this.messageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size); if (storeTime < 0) { log.warn("Failed to query store timestamp for commit log offset: {}", phyOffset); return 0; } else if (storeTime == timestamp) { targetOffset = midOffset; break; } else if (storeTime > timestamp) { high = midOffset - CQ_STORE_UNIT_SIZE; rightOffset = midOffset; } else { low = midOffset + CQ_STORE_UNIT_SIZE; leftOffset = midOffset; } } if (targetOffset != -1) { // We just found ONE matched record. These next to it might also share the same store-timestamp. offset = targetOffset; switch (boundaryType) { case LOWER: { int previousAttempt = targetOffset; while (true) { int attempt = previousAttempt - CQ_STORE_UNIT_SIZE; if (attempt < floor) { break; } byteBuffer.position(attempt); long physicalOffset = byteBuffer.getLong(); int messageSize = byteBuffer.getInt(); long messageStoreTimestamp = messageStore.getCommitLog() .pickupStoreTimestamp(physicalOffset, messageSize); if (messageStoreTimestamp == timestamp) { previousAttempt = attempt; continue; } break; } offset = previousAttempt; break; } case UPPER: { int previousAttempt = targetOffset; while (true) { int attempt = previousAttempt + CQ_STORE_UNIT_SIZE; if (attempt > ceiling) { break; } byteBuffer.position(attempt); long physicalOffset = byteBuffer.getLong(); int messageSize = byteBuffer.getInt(); long messageStoreTimestamp = messageStore.getCommitLog() .pickupStoreTimestamp(physicalOffset, messageSize); if (messageStoreTimestamp == timestamp) { previousAttempt = attempt; continue; } break; } offset = previousAttempt; break; } default: { log.warn("Unknown boundary type"); break; } } } else { // Given timestamp does not have any message records. But we have a range enclosing the // timestamp. /* * Consider the follow case: t2 has no consume queue entry and we are searching offset of * t2 for lower and upper boundaries. * -------------------------- * timestamp Consume Queue * t1 1 * t1 2 * t1 3 * t3 4 * t3 5 * -------------------------- * Now, we return 3 as upper boundary of t2 and 4 as its lower boundary. It looks * contradictory at first sight, but it does make sense when performing range queries. */ switch (boundaryType) { case LOWER: { offset = rightOffset; break; } case UPPER: { offset = leftOffset; break; } default: { log.warn("Unknown boundary type"); break; } } } return (mappedFile.getFileFromOffset() + offset) / CQ_STORE_UNIT_SIZE; } finally { sbr.release(); } } } return 0; } @Override public void truncateDirtyLogicFiles(long phyOffset) { truncateDirtyLogicFiles(phyOffset, true); } public void truncateDirtyLogicFiles(long phyOffset, boolean deleteFile) { int logicFileSize = this.mappedFileSize; this.setMaxPhysicOffset(phyOffset); long maxExtAddr = 1; boolean shouldDeleteFile = false; while (true) { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile != null) { ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); mappedFile.setWrotePosition(0); mappedFile.setCommittedPosition(0); mappedFile.setFlushedPosition(0); for (int i = 0; i < logicFileSize; i += CQ_STORE_UNIT_SIZE) { long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); long tagsCode = byteBuffer.getLong(); if (0 == i) { if (offset >= phyOffset) { shouldDeleteFile = true; break; } else { int pos = i + CQ_STORE_UNIT_SIZE; mappedFile.setWrotePosition(pos); mappedFile.setCommittedPosition(pos); mappedFile.setFlushedPosition(pos); this.setMaxPhysicOffset(offset + size); // This maybe not take effect, when not every consume queue has extend file. if (isExtAddr(tagsCode)) { maxExtAddr = tagsCode; } } } else { if (offset >= 0 && size > 0) { if (offset >= phyOffset) { return; } int pos = i + CQ_STORE_UNIT_SIZE; mappedFile.setWrotePosition(pos); mappedFile.setCommittedPosition(pos); mappedFile.setFlushedPosition(pos); this.setMaxPhysicOffset(offset + size); if (isExtAddr(tagsCode)) { maxExtAddr = tagsCode; } if (pos == logicFileSize) { return; } } else { return; } } } if (shouldDeleteFile) { if (deleteFile) { this.mappedFileQueue.deleteLastMappedFile(); } else { this.mappedFileQueue.deleteExpiredFile(Collections.singletonList(this.mappedFileQueue.getLastMappedFile())); } } } else { break; } } if (isExtReadEnable()) { this.consumeQueueExt.truncateByMaxAddress(maxExtAddr); } } @Override public long getLastOffset() { long lastOffset = -1; int logicFileSize = this.mappedFileSize; MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile != null) { int position = mappedFile.getWrotePosition() - CQ_STORE_UNIT_SIZE; if (position < 0) position = 0; ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); byteBuffer.position(position); for (int i = 0; i < logicFileSize; i += CQ_STORE_UNIT_SIZE) { long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); byteBuffer.getLong(); if (offset >= 0 && size > 0) { lastOffset = offset + size; } else { break; } } } return lastOffset; } @Override public boolean flush(final int flushLeastPages) { boolean result = this.mappedFileQueue.flush(flushLeastPages); if (isExtReadEnable()) { result = result & this.consumeQueueExt.flush(flushLeastPages); } return result; } @Override public int deleteExpiredFile(long offset) { int cnt = this.mappedFileQueue.deleteExpiredFileByOffset(offset, CQ_STORE_UNIT_SIZE); this.correctMinOffset(offset); return cnt; } /** * Update minLogicOffset such that entries after it would point to valid commit log address. * * @param minCommitLogOffset Minimum commit log offset */ @Override public void correctMinOffset(long minCommitLogOffset) { // Check if the consume queue is the state of deprecation. if (minLogicOffset >= mappedFileQueue.getMaxOffset()) { log.info("ConsumeQueue[Topic={}, queue-id={}] contains no valid entries", topic, queueId); return; } // Check whether the consume queue maps no valid data at all. This check may cost 1 IO operation. // The rationale is that consume queue always preserves the last file. In case there are many deprecated topics, // This check would save a lot of efforts. MappedFile lastMappedFile = this.mappedFileQueue.getLastMappedFile(); if (null == lastMappedFile) { return; } SelectMappedBufferResult lastRecord = null; try { int maxReadablePosition = lastMappedFile.getReadPosition(); if (maxReadablePosition >= ConsumeQueue.CQ_STORE_UNIT_SIZE) { lastRecord = lastMappedFile.selectMappedBuffer(maxReadablePosition - ConsumeQueue.CQ_STORE_UNIT_SIZE, ConsumeQueue.CQ_STORE_UNIT_SIZE); } if (null != lastRecord) { ByteBuffer buffer = lastRecord.getByteBuffer(); long commitLogOffset = buffer.getLong(); if (commitLogOffset < minCommitLogOffset) { // Keep the largest known consume offset, even if this consume-queue contains no valid entries at // all. Let minLogicOffset point to a future slot. this.minLogicOffset = lastMappedFile.getFileFromOffset() + maxReadablePosition; log.info("ConsumeQueue[topic={}, queue-id={}] contains no valid entries. Min-offset is assigned as: {}.", topic, queueId, getMinOffsetInQueue()); return; } } } finally { if (null != lastRecord) { lastRecord.release(); } } MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile(); long minExtAddr = 1; if (mappedFile != null) { // Search from previous min logical offset. Typically, a consume queue file segment contains 300,000 entries // searching from previous position saves significant amount of comparisons and IOs boolean intact = true; // Assume previous value is still valid long start = this.minLogicOffset - mappedFile.getFileFromOffset(); if (start < 0) { intact = false; start = 0; } if (start > mappedFile.getReadPosition()) { log.error("[Bug][InconsistentState] ConsumeQueue file {} should have been deleted", mappedFile.getFileName()); return; } SelectMappedBufferResult result = mappedFile.selectMappedBuffer((int) start); if (result == null) { log.warn("[Bug] Failed to scan consume queue entries from file on correcting min offset: {}", mappedFile.getFileName()); return; } try { // No valid consume entries if (result.getSize() == 0) { log.debug("ConsumeQueue[topic={}, queue-id={}] contains no valid entries", topic, queueId); return; } ByteBuffer buffer = result.getByteBuffer().slice(); // Verify whether the previous value is still valid or not before conducting binary search long commitLogOffset = buffer.getLong(); if (intact && commitLogOffset >= minCommitLogOffset) { log.info("Abort correction as previous min-offset points to {}, which is greater than {}", commitLogOffset, minCommitLogOffset); return; } // Binary search between range [previous_min_logic_offset, first_file_from_offset + file_size) // Note the consume-queue deletion procedure ensures the last entry points to somewhere valid. int low = 0; int high = result.getSize() - ConsumeQueue.CQ_STORE_UNIT_SIZE; while (true) { if (high - low <= ConsumeQueue.CQ_STORE_UNIT_SIZE) { break; } int mid = (low + high) / 2 / ConsumeQueue.CQ_STORE_UNIT_SIZE * ConsumeQueue.CQ_STORE_UNIT_SIZE; buffer.position(mid); commitLogOffset = buffer.getLong(); if (commitLogOffset > minCommitLogOffset) { high = mid; } else if (commitLogOffset == minCommitLogOffset) { low = mid; high = mid; break; } else { low = mid; } } // Examine the last one or two entries for (int i = low; i <= high; i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { buffer.position(i); long offsetPy = buffer.getLong(); buffer.position(i + 12); long tagsCode = buffer.getLong(); if (offsetPy >= minCommitLogOffset) { this.minLogicOffset = mappedFile.getFileFromOffset() + start + i; log.info("Compute logical min offset: {}, topic: {}, queueId: {}", this.getMinOffsetInQueue(), this.topic, this.queueId); // This maybe not take effect, when not every consume queue has an extended file. if (isExtAddr(tagsCode)) { minExtAddr = tagsCode; } break; } } } catch (Exception e) { log.error("Exception thrown when correctMinOffset", e); } finally { result.release(); } } if (isExtReadEnable()) { this.consumeQueueExt.truncateByMinAddress(minExtAddr); } } @Override public long getMinOffsetInQueue() { return this.minLogicOffset / CQ_STORE_UNIT_SIZE; } @Override public void putMessagePositionInfoWrapper(DispatchRequest request) { final int maxRetries = 30; boolean canWrite = this.messageStore.getRunningFlags().isCQWriteable(); for (int i = 0; i < maxRetries && canWrite; i++) { long tagsCode = request.getTagsCode(); if (isExtWriteEnable()) { ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit(); cqExtUnit.setFilterBitMap(request.getBitMap()); cqExtUnit.setMsgStoreTime(request.getStoreTimestamp()); cqExtUnit.setTagsCode(request.getTagsCode()); long extAddr = this.consumeQueueExt.put(cqExtUnit); if (isExtAddr(extAddr)) { tagsCode = extAddr; } else { log.warn("Save consume queue extend fail, So just save tagsCode! {}, topic:{}, queueId:{}, offset:{}", cqExtUnit, topic, queueId, request.getCommitLogOffset()); } } boolean result = this.putMessagePositionInfo(request.getCommitLogOffset(), request.getMsgSize(), tagsCode, request.getConsumeQueueOffset()); if (result) { if (this.messageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE || this.messageStore.getMessageStoreConfig().isEnableDLegerCommitLog()) { this.messageStore.getStoreCheckpoint().setPhysicMsgTimestamp(request.getStoreTimestamp()); } this.messageStore.getStoreCheckpoint().setTmpLogicsMsgTimestamp(request.getStoreTimestamp()); this.messageStore.getStoreCheckpoint().setTmpLogicsPhysicalOffset(request.getCommitLogOffset()); if (MultiDispatchUtils.checkMultiDispatchQueue(this.messageStore.getMessageStoreConfig(), request)) { multiDispatchLmqQueue(request, maxRetries); } return; } else { // XXX: warn and notify me log.warn("[BUG]put commit log position info to " + topic + ":" + queueId + " " + request.getCommitLogOffset() + " failed, retry " + i + " times"); try { Thread.sleep(1000); } catch (InterruptedException e) { log.warn("", e); } } } // XXX: warn and notify me log.error("[BUG]consume queue can not write, {} {}", this.topic, this.queueId); this.messageStore.getRunningFlags().makeLogicsQueueError(); } private void multiDispatchLmqQueue(DispatchRequest request, int maxRetries) { Map prop = request.getPropertiesMap(); String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String multiQueueOffset = prop.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET); String[] queues = multiDispatchQueue.split(MixAll.LMQ_DISPATCH_SEPARATOR); String[] queueOffsets = multiQueueOffset.split(MixAll.LMQ_DISPATCH_SEPARATOR); if (queues.length != queueOffsets.length) { log.error("[bug] queues.length!=queueOffsets.length ", request.getTopic()); return; } for (int i = 0; i < queues.length; i++) { String queueName = queues[i]; if (StringUtils.contains(queueName, File.separator)) { continue; } long queueOffset = Long.parseLong(queueOffsets[i]); int queueId = request.getQueueId(); if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { queueId = 0; } doDispatchLmqQueue(request, maxRetries, queueName, queueOffset, queueId); } } private void doDispatchLmqQueue(DispatchRequest request, int maxRetries, String queueName, long queueOffset, int queueId) { ConsumeQueueInterface cq = this.messageStore.findConsumeQueue(queueName, queueId); boolean canWrite = this.messageStore.getRunningFlags().isCQWriteable(); for (int i = 0; i < maxRetries && canWrite; i++) { boolean result = ((ConsumeQueue) cq).putMessagePositionInfo(request.getCommitLogOffset(), request.getMsgSize(), request.getTagsCode(), queueOffset); if (result) { break; } else { log.warn("[BUG]put commit log position info to " + queueName + ":" + queueId + " " + request.getCommitLogOffset() + " failed, retry " + i + " times"); try { Thread.sleep(1000); } catch (InterruptedException e) { log.warn("", e); } } } } @Override public void assignQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg) { String topicQueueKey = getTopic() + "-" + getQueueId(); long queueOffset = queueOffsetOperator.getQueueOffset(topicQueueKey); msg.setQueueOffset(queueOffset); } @Override public void increaseQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg, short messageNum) { String topicQueueKey = getTopic() + "-" + getQueueId(); queueOffsetOperator.increaseQueueOffset(topicQueueKey, messageNum); } private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode, final long cqOffset) { if (offset + size <= this.getMaxPhysicOffset()) { // During the recovery process after broker crashes, this logs will cause the scrolling of valid logs. if (messageStore.getStateMachine().getCurrentState().isAfter(MessageStoreStateMachine.MessageStoreState.RECOVER_COMMITLOG_OK) || messageStore.getMessageStoreConfig().isEnableLogConsumeQueueRepeatedlyBuildWhenRecover()) { log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", this.getMaxPhysicOffset(), offset); } return true; } this.byteBufferIndex.flip(); this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE); this.byteBufferIndex.putLong(offset); this.byteBufferIndex.putInt(size); this.byteBufferIndex.putLong(tagsCode); final long expectLogicOffset = cqOffset * CQ_STORE_UNIT_SIZE; MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(expectLogicOffset); if (mappedFile != null) { if (mappedFile.isFirstCreateInQueue() && cqOffset != 0 && mappedFile.getWrotePosition() == 0) { this.minLogicOffset = expectLogicOffset; this.mappedFileQueue.setFlushedWhere(expectLogicOffset); this.mappedFileQueue.setCommittedWhere(expectLogicOffset); this.fillPreBlank(mappedFile, expectLogicOffset); log.info("fill pre blank space " + mappedFile.getFileName() + " " + expectLogicOffset + " " + mappedFile.getWrotePosition()); } if (cqOffset != 0) { long currentLogicOffset = mappedFile.getWrotePosition() + mappedFile.getFileFromOffset(); if (expectLogicOffset < currentLogicOffset) { log.warn("Build consume queue repeatedly, expectLogicOffset: {} currentLogicOffset: {} Topic: {} QID: {} Diff: {}", expectLogicOffset, currentLogicOffset, this.topic, this.queueId, expectLogicOffset - currentLogicOffset); return true; } if (expectLogicOffset != currentLogicOffset) { LOG_ERROR.warn( "[BUG]logic queue order maybe wrong, expectLogicOffset: {} currentLogicOffset: {} Topic: {} QID: {} Diff: {}", expectLogicOffset, currentLogicOffset, this.topic, this.queueId, expectLogicOffset - currentLogicOffset ); } } this.setMaxPhysicOffset(offset + size); boolean appendResult; if (messageStore.getMessageStoreConfig().isPutConsumeQueueDataByFileChannel()) { appendResult = mappedFile.appendMessageUsingFileChannel(this.byteBufferIndex.array()); } else { appendResult = mappedFile.appendMessage(this.byteBufferIndex.array()); } return appendResult; } return false; } private void fillPreBlank(final MappedFile mappedFile, final long untilWhere) { ByteBuffer byteBuffer = ByteBuffer.allocate(CQ_STORE_UNIT_SIZE); byteBuffer.putLong(0L); byteBuffer.putInt(Integer.MAX_VALUE); byteBuffer.putLong(0L); int until = (int) (untilWhere % this.mappedFileQueue.getMappedFileSize()); for (int i = 0; i < until; i += CQ_STORE_UNIT_SIZE) { if (messageStore.getMessageStoreConfig().isPutConsumeQueueDataByFileChannel()) { mappedFile.appendMessageUsingFileChannel(byteBuffer.array()); } else { mappedFile.appendMessage(byteBuffer.array()); } } } public SelectMappedBufferResult getIndexBuffer(final long startIndex) { int mappedFileSize = this.mappedFileSize; long offset = startIndex * CQ_STORE_UNIT_SIZE; if (offset >= this.getMinLogicOffset()) { MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset); if (mappedFile != null) { return mappedFile.selectMappedBuffer((int) (offset % mappedFileSize)); } } return null; } @Override public ReferredIterator iterateFrom(long startOffset) { SelectMappedBufferResult sbr = getIndexBuffer(startOffset); if (sbr == null) { return null; } return new ConsumeQueueIterator(sbr); } @Override public ReferredIterator iterateFrom(long startIndex, int count) { return iterateFrom(startIndex); } @Override public CqUnit get(long offset) { ReferredIterator it = iterateFrom(offset); if (it == null) { return null; } return it.nextAndRelease(); } @Override public Pair getCqUnitAndStoreTime(long index) { CqUnit cqUnit = get(index); Long messageStoreTime = this.consumeQueueStore.getStoreTime(cqUnit); return new Pair<>(cqUnit, messageStoreTime); } @Override public Pair getEarliestUnitAndStoreTime() { CqUnit cqUnit = getEarliestUnit(); Long messageStoreTime = this.consumeQueueStore.getStoreTime(cqUnit); return new Pair<>(cqUnit, messageStoreTime); } @Override public CqUnit getEarliestUnit() { /** * here maybe should not return null */ ReferredIterator it = iterateFrom(minLogicOffset / CQ_STORE_UNIT_SIZE); if (it == null) { return null; } return it.nextAndRelease(); } @Override public CqUnit getLatestUnit() { ReferredIterator it = iterateFrom((mappedFileQueue.getMaxOffset() / CQ_STORE_UNIT_SIZE) - 1); if (it == null) { return null; } return it.nextAndRelease(); } @Override public boolean isFirstFileAvailable() { return false; } @Override public boolean isFirstFileExist() { return false; } private class ConsumeQueueIterator implements ReferredIterator { private SelectMappedBufferResult sbr; private int relativePos = 0; public ConsumeQueueIterator(SelectMappedBufferResult sbr) { this.sbr = sbr; if (sbr != null && sbr.getByteBuffer() != null) { relativePos = sbr.getByteBuffer().position(); } } @Override public boolean hasNext() { if (sbr == null || sbr.getByteBuffer() == null) { return false; } return sbr.getByteBuffer().hasRemaining(); } @Override public CqUnit next() { if (!hasNext()) { return null; } long queueOffset = (sbr.getStartOffset() + sbr.getByteBuffer().position() - relativePos) / CQ_STORE_UNIT_SIZE; CqUnit cqUnit = new CqUnit(queueOffset, sbr.getByteBuffer().getLong(), sbr.getByteBuffer().getInt(), sbr.getByteBuffer().getLong()); if (isExtAddr(cqUnit.getTagsCode())) { ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit(); boolean extRet = getExt(cqUnit.getTagsCode(), cqExtUnit); if (extRet) { cqUnit.setTagsCode(cqExtUnit.getTagsCode()); cqUnit.setCqExtUnit(cqExtUnit); } else { // can't find ext content.Client will filter messages by tag also. log.error("[BUG] can't find consume queue extend file content! addr={}, offsetPy={}, sizePy={}, topic={}", cqUnit.getTagsCode(), cqUnit.getPos(), cqUnit.getPos(), getTopic()); } } return cqUnit; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } @Override public void release() { if (sbr != null) { sbr.release(); sbr = null; } } @Override public CqUnit nextAndRelease() { try { return next(); } finally { release(); } } } public ConsumeQueueExt.CqExtUnit getExt(final long offset) { if (isExtReadEnable()) { return this.consumeQueueExt.get(offset); } return null; } public boolean getExt(final long offset, ConsumeQueueExt.CqExtUnit cqExtUnit) { if (isExtReadEnable()) { return this.consumeQueueExt.get(offset, cqExtUnit); } return false; } @Override public long getMinLogicOffset() { return minLogicOffset; } public void setMinLogicOffset(long minLogicOffset) { this.minLogicOffset = minLogicOffset; } @Override public long rollNextFile(final long nextBeginOffset) { int mappedFileSize = this.mappedFileSize; int totalUnitsInFile = mappedFileSize / CQ_STORE_UNIT_SIZE; return nextBeginOffset + totalUnitsInFile - nextBeginOffset % totalUnitsInFile; } @Override public String getTopic() { return topic; } @Override public int getQueueId() { return queueId; } @Override public CQType getCQType() { return CQType.SimpleCQ; } @Override public long getMaxPhysicOffset() { return maxPhysicOffset; } public void setMaxPhysicOffset(long maxPhysicOffset) { this.maxPhysicOffset = maxPhysicOffset; } @Override public void destroy() { this.setMaxPhysicOffset(-1); this.minLogicOffset = 0; this.mappedFileQueue.destroy(); if (isExtReadEnable()) { this.consumeQueueExt.destroy(); } } @Override public long getMessageTotalInQueue() { return this.getMaxOffsetInQueue() - this.getMinOffsetInQueue(); } @Override public long getMaxOffsetInQueue() { return this.mappedFileQueue.getMaxOffset() / CQ_STORE_UNIT_SIZE; } @Override public void checkSelf() { mappedFileQueue.checkSelf(); if (isExtReadEnable()) { this.consumeQueueExt.checkSelf(); } } protected boolean isExtReadEnable() { return this.consumeQueueExt != null; } protected boolean isExtWriteEnable() { return this.consumeQueueExt != null && this.messageStore.getMessageStoreConfig().isEnableConsumeQueueExt(); } /** * Check {@code tagsCode} is address of extend file or tags code. */ public boolean isExtAddr(long tagsCode) { return ConsumeQueueExt.isExtAddr(tagsCode); } @Override public void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) { mappedFileQueue.swapMap(reserveNum, forceSwapIntervalMs, normalSwapIntervalMs); } @Override public void cleanSwappedMap(long forceCleanSwapIntervalMs) { mappedFileQueue.cleanSwappedMap(forceCleanSwapIntervalMs); } @Override public long estimateMessageCount(long from, long to, MessageFilter filter) { long physicalOffsetFrom = from * CQ_STORE_UNIT_SIZE; long physicalOffsetTo = to * CQ_STORE_UNIT_SIZE; List mappedFiles = mappedFileQueue.range(physicalOffsetFrom, physicalOffsetTo); if (mappedFiles.isEmpty()) { return -1; } boolean sample = false; long match = 0; long raw = 0; for (MappedFile mappedFile : mappedFiles) { int start = 0; int len = mappedFile.getFileSize(); // calculate start and len for first segment and last segment to reduce scanning // first file segment if (mappedFile.getFileFromOffset() <= physicalOffsetFrom) { start = (int) (physicalOffsetFrom - mappedFile.getFileFromOffset()); if (mappedFile.getFileFromOffset() + mappedFile.getFileSize() >= physicalOffsetTo) { // current mapped file covers search range completely. len = (int) (physicalOffsetTo - physicalOffsetFrom); } else { len = mappedFile.getFileSize() - start; } } // last file segment if (0 == start && mappedFile.getFileFromOffset() + mappedFile.getFileSize() > physicalOffsetTo) { len = (int) (physicalOffsetTo - mappedFile.getFileFromOffset()); } // select partial data to scan SelectMappedBufferResult slice = mappedFile.selectMappedBuffer(start, len); if (null != slice) { try { ByteBuffer buffer = slice.getByteBuffer(); int current = 0; while (current < len) { // skip physicalOffset and message length fields. buffer.position(current + MSG_TAG_OFFSET_INDEX); long tagCode = buffer.getLong(); ConsumeQueueExt.CqExtUnit ext = null; if (isExtWriteEnable()) { ext = consumeQueueExt.get(tagCode); tagCode = ext.getTagsCode(); } if (filter.isMatchedByConsumeQueue(tagCode, ext)) { match++; } raw++; current += CQ_STORE_UNIT_SIZE; if (raw >= messageStore.getMessageStoreConfig().getMaxConsumeQueueScan()) { sample = true; break; } if (match > messageStore.getMessageStoreConfig().getSampleCountThreshold()) { sample = true; break; } } } finally { slice.release(); } } // we have scanned enough entries, now is the time to return an educated guess. if (sample) { break; } } long result = match; if (sample) { if (0 == raw) { log.error("[BUG]. Raw should NOT be 0"); return 0; } result = (long) (match * (to - from) * 1.0 / raw); } log.debug("Result={}, raw={}, match={}, sample={}", result, raw, match, sample); return result; } @Override public void initializeWithOffset(long offset, long minPhyOffset) { // Because the file version cq requires that files are continuous, // If existing cq not be completely deleted, new cq can not initialize with given offset. destroy(); // correct min offset // TODO: when min commitLog offset is 0 and restart store, min offset of cq may be set to 0 incorrectly setMinLogicOffset(offset * ConsumeQueue.CQ_STORE_UNIT_SIZE); // transientStorePool is null, only need set wrote position here MappedFile mappedFile = mappedFileQueue.getLastMappedFile(offset * ConsumeQueue.CQ_STORE_UNIT_SIZE, true); fillPreBlank(mappedFile, offset * ConsumeQueue.CQ_STORE_UNIT_SIZE); flush(0); } @Override public boolean shutdown() { this.mappedFileQueue.cleanResourcesAll(); return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ConsumeQueueExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.io.File; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.rocketmq.store.logfile.MappedFile; /** * Extend of consume queue, to store something not important, * such as message store time, filter bit map and etc. *

    *

  • 1. This class is used only by {@link ConsumeQueue}
  • *
  • 2. And is weakly reliable.
  • *
  • 3. Be careful, address returned is always less than 0.
  • *
  • 4. Pls keep this file small.
  • */ public class ConsumeQueueExt { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final MappedFileQueue mappedFileQueue; private final String topic; private final int queueId; private final String storePath; private final int mappedFileSize; private ByteBuffer tempContainer; public static final int END_BLANK_DATA_LENGTH = 4; /** * Addr can not exceed this value.For compatible. */ public static final long MAX_ADDR = Integer.MIN_VALUE - 1L; public static final long MAX_REAL_OFFSET = MAX_ADDR - Long.MIN_VALUE; /** * Constructor. * * @param topic topic * @param queueId id of queue * @param storePath root dir of files to store. * @param mappedFileSize file size * @param bitMapLength bit map length. */ public ConsumeQueueExt(final String topic, final int queueId, final String storePath, final int mappedFileSize, final int bitMapLength) { this.storePath = storePath; this.mappedFileSize = mappedFileSize; this.topic = topic; this.queueId = queueId; String queueDir = this.storePath + File.separator + topic + File.separator + queueId; this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null); if (bitMapLength > 0) { this.tempContainer = ByteBuffer.allocate( bitMapLength / Byte.SIZE ); } } /** * Constructor with writeWithoutMmap support. * * @param topic topic * @param queueId id of queue * @param storePath root dir of files to store. * @param mappedFileSize file size * @param bitMapLength bit map length. * @param writeWithoutMmap whether to use RandomAccessFile instead of MappedByteBuffer */ public ConsumeQueueExt(final String topic, final int queueId, final String storePath, final int mappedFileSize, final int bitMapLength, final boolean writeWithoutMmap) { this.storePath = storePath; this.mappedFileSize = mappedFileSize; this.topic = topic; this.queueId = queueId; String queueDir = this.storePath + File.separator + topic + File.separator + queueId; this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null, writeWithoutMmap); if (bitMapLength > 0) { this.tempContainer = ByteBuffer.allocate( bitMapLength / Byte.SIZE ); } } public long getTotalSize() { return this.mappedFileQueue.getTotalFileSize(); } /** * Check whether {@code address} point to extend file. *

    * Just test {@code address} is less than 0. *

    */ public static boolean isExtAddr(final long address) { return address <= MAX_ADDR; } /** * Transform {@code address}(decorated by {@link #decorate}) to offset in mapped file. *

    * if {@code address} is less than 0, return {@code address} - {@link java.lang.Long#MIN_VALUE}; * else, just return {@code address} *

    */ public long unDecorate(final long address) { if (isExtAddr(address)) { return address - Long.MIN_VALUE; } return address; } /** * Decorate {@code offset} from mapped file, in order to distinguish with tagsCode(saved in cq originally). *

    * if {@code offset} is greater than or equal to 0, then return {@code offset} + {@link java.lang.Long#MIN_VALUE}; * else, just return {@code offset} *

    * * @return ext address(value is less than 0) */ public long decorate(final long offset) { if (!isExtAddr(offset)) { return offset + Long.MIN_VALUE; } return offset; } /** * Get data from buffer. * * @param address less than 0 */ public CqExtUnit get(final long address) { CqExtUnit cqExtUnit = new CqExtUnit(); if (get(address, cqExtUnit)) { return cqExtUnit; } return null; } /** * Get data from buffer, and set to {@code cqExtUnit} * * @param address less than 0 */ public boolean get(final long address, final CqExtUnit cqExtUnit) { if (!isExtAddr(address)) { return false; } final int mappedFileSize = this.mappedFileSize; final long realOffset = unDecorate(address); MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(realOffset, realOffset == 0); if (mappedFile == null) { return false; } int pos = (int) (realOffset % mappedFileSize); SelectMappedBufferResult bufferResult = mappedFile.selectMappedBuffer(pos); if (bufferResult == null) { log.warn("[BUG] Consume queue extend unit({}) is not found!", realOffset); return false; } boolean ret = false; try { ret = cqExtUnit.read(bufferResult.getByteBuffer()); } finally { bufferResult.release(); } return ret; } /** * Save to mapped buffer of file and return address. *

    * Be careful, this method is not thread safe. *

    * * @return success: < 0: fail: >=0 */ public long put(final CqExtUnit cqExtUnit) { final int retryTimes = 3; try { int size = cqExtUnit.calcUnitSize(); if (size > CqExtUnit.MAX_EXT_UNIT_SIZE) { log.error("Size of cq ext unit is greater than {}, {}", CqExtUnit.MAX_EXT_UNIT_SIZE, cqExtUnit); return 1; } if (this.mappedFileQueue.getMaxOffset() + size > MAX_REAL_OFFSET) { log.warn("Capacity of ext is maximum!{}, {}", this.mappedFileQueue.getMaxOffset(), size); return 1; } // unit size maybe change.but, the same most of the time. if (this.tempContainer == null || this.tempContainer.capacity() < size) { this.tempContainer = ByteBuffer.allocate(size); } for (int i = 0; i < retryTimes; i++) { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile == null || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); } if (mappedFile == null) { log.error("Create mapped file when save consume queue extend, {}", cqExtUnit); continue; } final int wrotePosition = mappedFile.getWrotePosition(); final int blankSize = this.mappedFileSize - wrotePosition - END_BLANK_DATA_LENGTH; // check whether has enough space. if (size > blankSize) { fullFillToEnd(mappedFile, wrotePosition); log.info("No enough space(need:{}, has:{}) of file {}, so fill to end", size, blankSize, mappedFile.getFileName()); continue; } if (mappedFile.appendMessage(cqExtUnit.write(this.tempContainer), 0, size)) { return decorate(wrotePosition + mappedFile.getFileFromOffset()); } } } catch (Throwable e) { log.error("Save consume queue extend error, " + cqExtUnit, e); } return 1; } protected void fullFillToEnd(final MappedFile mappedFile, final int wrotePosition) { ByteBuffer mappedFileBuffer = mappedFile.sliceByteBuffer(); mappedFileBuffer.position(wrotePosition); // ending. mappedFileBuffer.putShort((short) -1); mappedFile.setWrotePosition(this.mappedFileSize); } /** * Load data from file when startup. */ public boolean load() { boolean result = this.mappedFileQueue.load(); log.info("load consume queue extend" + this.topic + "-" + this.queueId + " " + (result ? "OK" : "Failed")); return result; } /** * Check whether the step size in mapped file queue is correct. */ public void checkSelf() { this.mappedFileQueue.checkSelf(); } /** * Recover. */ public void recover() { final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (mappedFiles == null || mappedFiles.isEmpty()) { return; } // load all files, consume queue will truncate extend files. int index = 0; MappedFile mappedFile = mappedFiles.get(index); ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset = 0; CqExtUnit extUnit = new CqExtUnit(); while (true) { extUnit.readBySkip(byteBuffer); // check whether write sth. if (extUnit.getSize() > 0) { mappedFileOffset += extUnit.getSize(); continue; } index++; if (index < mappedFiles.size()) { mappedFile = mappedFiles.get(index); byteBuffer = mappedFile.sliceByteBuffer(); processOffset = mappedFile.getFileFromOffset(); mappedFileOffset = 0; log.info("Recover next consume queue extend file, " + mappedFile.getFileName()); continue; } log.info("All files of consume queue extend has been recovered over, last mapped file " + mappedFile.getFileName()); break; } processOffset += mappedFileOffset; this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); } /** * Delete files before {@code minAddress}. * * @param minAddress less than 0 */ public void truncateByMinAddress(final long minAddress) { if (!isExtAddr(minAddress)) { return; } log.info("Truncate consume queue ext by min {}.", minAddress); List willRemoveFiles = new ArrayList<>(); List mappedFiles = this.mappedFileQueue.getMappedFiles(); final long realOffset = unDecorate(minAddress); for (MappedFile file : mappedFiles) { long fileTailOffset = file.getFileFromOffset() + this.mappedFileSize; if (fileTailOffset < realOffset) { log.info("Destroy consume queue ext by min: file={}, fileTailOffset={}, minOffset={}", file.getFileName(), fileTailOffset, realOffset); if (file.destroy(1000)) { willRemoveFiles.add(file); } } } this.mappedFileQueue.deleteExpiredFile(willRemoveFiles); } /** * Delete files after {@code maxAddress}, and reset wrote/commit/flush position to last file. * * @param maxAddress less than 0 */ public void truncateByMaxAddress(final long maxAddress) { if (!isExtAddr(maxAddress)) { return; } log.info("Truncate consume queue ext by max {}.", maxAddress); CqExtUnit cqExtUnit = get(maxAddress); if (cqExtUnit == null) { log.error("[BUG] address {} of consume queue extend not found!", maxAddress); return; } final long realOffset = unDecorate(maxAddress); this.mappedFileQueue.truncateDirtyFiles(realOffset + cqExtUnit.getSize()); } /** * flush buffer to file. */ public boolean flush(final int flushLeastPages) { return this.mappedFileQueue.flush(flushLeastPages); } /** * delete files and directory. */ public void destroy() { this.mappedFileQueue.destroy(); } /** * Max address(value is less than 0). *

    *

    * Be careful: it's an address just when invoking this method. *

    */ public long getMaxAddress() { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile == null) { return decorate(0); } return decorate(mappedFile.getFileFromOffset() + mappedFile.getWrotePosition()); } /** * Minus address saved in file. */ public long getMinAddress() { MappedFile firstFile = this.mappedFileQueue.getFirstMappedFile(); if (firstFile == null) { return decorate(0); } return decorate(firstFile.getFileFromOffset()); } /** * Store unit. */ public static class CqExtUnit { public static final short MIN_EXT_UNIT_SIZE = 2 * 1 // size, 32k max + 8 * 2 // msg time + tagCode + 2; // bitMapSize public static final int MAX_EXT_UNIT_SIZE = Short.MAX_VALUE; public CqExtUnit() { } public CqExtUnit(Long tagsCode, long msgStoreTime, byte[] filterBitMap) { this.tagsCode = tagsCode == null ? 0 : tagsCode; this.msgStoreTime = msgStoreTime; this.filterBitMap = filterBitMap; this.bitMapSize = (short) (filterBitMap == null ? 0 : filterBitMap.length); this.size = (short) (MIN_EXT_UNIT_SIZE + this.bitMapSize); } /** * unit size */ private short size; /** * has code of tags */ private long tagsCode; /** * the time to store into commit log of message */ private long msgStoreTime; /** * size of bit map */ private short bitMapSize; /** * filter bit map */ private byte[] filterBitMap; /** * build unit from buffer from current position. */ private boolean read(final ByteBuffer buffer) { if (buffer.position() + 2 > buffer.limit()) { return false; } this.size = buffer.getShort(); if (this.size < 1) { return false; } this.tagsCode = buffer.getLong(); this.msgStoreTime = buffer.getLong(); this.bitMapSize = buffer.getShort(); if (this.bitMapSize < 1) { return true; } if (this.filterBitMap == null || this.filterBitMap.length != this.bitMapSize) { this.filterBitMap = new byte[bitMapSize]; } buffer.get(this.filterBitMap); return true; } /** * Only read first 2 byte to get unit size. *

    * if size > 0, then skip buffer position with size. *

    *

    * if size <= 0, nothing to do. *

    */ private void readBySkip(final ByteBuffer buffer) { ByteBuffer temp = buffer.slice(); short tempSize = temp.getShort(); this.size = tempSize; if (tempSize > 0) { buffer.position(buffer.position() + this.size); } } /** * Transform unit data to byte array. *

    *

  • 1. @{code container} can be null, it will be created if null.
  • *
  • 2. if capacity of @{code container} is less than unit size, it will be created also.
  • *
  • 3. Pls be sure that size of unit is not greater than {@link #MAX_EXT_UNIT_SIZE}
  • */ private byte[] write(final ByteBuffer container) { this.bitMapSize = (short) (filterBitMap == null ? 0 : filterBitMap.length); this.size = (short) (MIN_EXT_UNIT_SIZE + this.bitMapSize); ByteBuffer temp = container; if (temp == null || temp.capacity() < this.size) { temp = ByteBuffer.allocate(this.size); } temp.flip(); temp.limit(this.size); temp.putShort(this.size); temp.putLong(this.tagsCode); temp.putLong(this.msgStoreTime); temp.putShort(this.bitMapSize); if (this.bitMapSize > 0) { temp.put(this.filterBitMap); } return temp.array(); } /** * Calculate unit size by current data. */ private int calcUnitSize() { int sizeTemp = MIN_EXT_UNIT_SIZE + (filterBitMap == null ? 0 : filterBitMap.length); return sizeTemp; } public long getTagsCode() { return tagsCode; } public void setTagsCode(final long tagsCode) { this.tagsCode = tagsCode; } public long getMsgStoreTime() { return msgStoreTime; } public void setMsgStoreTime(final long msgStoreTime) { this.msgStoreTime = msgStoreTime; } public byte[] getFilterBitMap() { if (this.bitMapSize < 1) { return null; } return filterBitMap; } public void setFilterBitMap(final byte[] filterBitMap) { this.filterBitMap = filterBitMap; // not safe transform, but size will be calculate by #calcUnitSize this.bitMapSize = (short) (filterBitMap == null ? 0 : filterBitMap.length); } public short getSize() { return size; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CqExtUnit)) return false; CqExtUnit cqExtUnit = (CqExtUnit) o; if (bitMapSize != cqExtUnit.bitMapSize) return false; if (msgStoreTime != cqExtUnit.msgStoreTime) return false; if (size != cqExtUnit.size) return false; if (tagsCode != cqExtUnit.tagsCode) return false; if (!Arrays.equals(filterBitMap, cqExtUnit.filterBitMap)) return false; return true; } @Override public int hashCode() { int result = (int) size; result = 31 * result + (int) (tagsCode ^ (tagsCode >>> 32)); result = 31 * result + (int) (msgStoreTime ^ (msgStoreTime >>> 32)); result = 31 * result + (int) bitMapSize; result = 31 * result + (filterBitMap != null ? Arrays.hashCode(filterBitMap) : 0); return result; } @Override public String toString() { return "CqExtUnit{" + "size=" + size + ", tagsCode=" + tagsCode + ", msgStoreTime=" + msgStoreTime + ", bitMapSize=" + bitMapSize + ", filterBitMap=" + Arrays.toString(filterBitMap) + '}'; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/DefaultMessageFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import java.util.Map; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class DefaultMessageFilter implements MessageFilter { private SubscriptionData subscriptionData; public DefaultMessageFilter(final SubscriptionData subscriptionData) { this.subscriptionData = subscriptionData; } @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { if (null == tagsCode || null == subscriptionData) { return true; } if (subscriptionData.isClassFilterMode()) { return true; } return subscriptionData.getSubString().equals(SubscriptionData.SUB_ALL) || subscriptionData.getCodeSet().contains(tagsCode.intValue()); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import com.google.common.collect.Sets; import com.google.common.hash.Hashing; import io.openmessaging.storage.dledger.entry.DLedgerEntry; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; import com.alibaba.fastjson2.JSON; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CleanupPolicy; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.running.RunningStats; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.CleanupPolicyUtils; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.common.utils.ServiceProvider; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.dledger.DLedgerCommitLog; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.ha.DefaultHAService; import org.apache.rocketmq.store.ha.HAService; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; import org.apache.rocketmq.store.hook.PutMessageHook; import org.apache.rocketmq.store.hook.SendMessageBackHook; import org.apache.rocketmq.store.index.IndexService; import org.apache.rocketmq.store.index.QueryOffsetResult; import org.apache.rocketmq.store.index.rocksdb.IndexRocksDBStore; import org.apache.rocketmq.store.kv.CommitLogDispatcherCompaction; import org.apache.rocketmq.store.kv.CompactionService; import org.apache.rocketmq.store.kv.CompactionStore; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.logfile.SharedByteBufferManager; import org.apache.rocketmq.store.metrics.DefaultStoreMetricsManager; import org.apache.rocketmq.store.queue.CombineConsumeQueueStore; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStore; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.rocksdb.TimerMessageRocksDBStore; import org.apache.rocketmq.store.transaction.TransMessageRocksDBStore; import org.apache.rocketmq.store.util.PerfCounter; import org.apache.rocketmq.store.metrics.StoreMetricsManager; import org.rocksdb.RocksDBException; public class DefaultMessageStore implements MessageStore { protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); public final PerfCounter.Ticks perfs = new PerfCounter.Ticks(LOGGER); private final MessageStoreConfig messageStoreConfig; // CommitLog protected final CommitLog commitLog; protected final ConsumeQueueStoreInterface consumeQueueStore; protected final CleanCommitLogService cleanCommitLogService; protected final IndexService indexService; protected final IndexRocksDBStore indexRocksDBStore; private final AllocateMappedFileService allocateMappedFileService; private ReputMessageService reputMessageService; private HAService haService; // CompactionLog private CompactionStore compactionStore; private CompactionService compactionService; private final StoreStatsService storeStatsService; private final TransientStorePool transientStorePool; protected final RunningFlags runningFlags = new RunningFlags(); private final SystemClock systemClock = new SystemClock(); private final ScheduledExecutorService scheduledExecutorService; private final BrokerStatsManager brokerStatsManager; private final MessageArrivingListener messageArrivingListener; private final BrokerConfig brokerConfig; private volatile boolean shutdown = true; private boolean notifyMessageArriveInBatch = false; protected StoreCheckpoint storeCheckpoint; private MessageRocksDBStorage messageRocksDBStorage; private TimerMessageStore timerMessageStore; private final DefaultStoreMetricsManager defaultStoreMetricsManager; private TimerMessageRocksDBStore timerMessageRocksDBStore; private TransMessageRocksDBStore transMessageRocksDBStore; private final LinkedList dispatcherList = new LinkedList<>(); /** * List of stores that require commitlog dispatch and recovery. Each store registers itself when loading. */ private final List commitLogDispatchStores = new ArrayList<>(); private final RandomAccessFile lockFile; private FileLock lock; boolean shutDownNormal = false; // Max pull msg size private final static int MAX_PULL_MSG_SIZE = 128 * 1024 * 1024; private volatile int aliveReplicasNum = 1; // Refer the MessageStore of MasterBroker in the same process. // If current broker is master, this reference point to null or itself. // If current broker is slave, this reference point to the store of master broker, and the two stores belong to // different broker groups. private MessageStore masterStoreInProcess = null; private volatile long masterFlushedOffset = -1L; private volatile long brokerInitMaxOffset = -1L; private final List putMessageHookList = new ArrayList<>(); private SendMessageBackHook sendMessageBackHook; private final ConcurrentSkipListMap delayLevelTable = new ConcurrentSkipListMap<>(); private int maxDelayLevel; private final AtomicInteger mappedPageHoldCount = new AtomicInteger(0); private final ConcurrentLinkedQueue batchDispatchRequestQueue = new ConcurrentLinkedQueue<>(); private final int dispatchRequestOrderlyQueueSize = 16; private final DispatchRequestOrderlyQueue dispatchRequestOrderlyQueue = new DispatchRequestOrderlyQueue(dispatchRequestOrderlyQueueSize); private long stateMachineVersion = 0L; // this is a unmodifiableMap private final ConcurrentMap topicConfigTable; private final MessageStoreStateMachine stateMachine; private final ScheduledExecutorService scheduledCleanQueueExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreCleanQueueScheduledThread")); public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager, final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig, final ConcurrentMap topicConfigTable) throws IOException { stateMachine = new MessageStoreStateMachine(LOGGER); this.messageArrivingListener = messageArrivingListener; this.brokerConfig = brokerConfig; this.messageStoreConfig = messageStoreConfig; this.aliveReplicasNum = messageStoreConfig.getTotalReplicas(); this.brokerStatsManager = brokerStatsManager; this.topicConfigTable = topicConfigTable; this.allocateMappedFileService = new AllocateMappedFileService(this); this.commitLog = messageStoreConfig.isEnableDLegerCommitLog() ? new DLedgerCommitLog(this) : new CommitLog(this); this.consumeQueueStore = createConsumeQueueStore(); this.cleanCommitLogService = new CleanCommitLogService(); this.storeStatsService = new StoreStatsService(getBrokerIdentity()); this.messageRocksDBStorage = new MessageRocksDBStorage(getMessageStoreConfig()); this.indexService = new IndexService(this); this.indexRocksDBStore = new IndexRocksDBStore(this); this.dispatcherList.addLast(new CommitLogDispatcherBuildConsumeQueue()); this.dispatcherList.addLast(new CommitLogDispatcherBuildIndex()); this.dispatcherList.addLast(new CommitLogDispatcherBuildTransIndex()); initializeHAService(); this.reputMessageService = messageStoreConfig.isEnableBuildConsumeQueueConcurrently() ? new ConcurrentReputMessageService() : new ReputMessageService(); this.transientStorePool = new TransientStorePool(messageStoreConfig.getTransientStorePoolSize(), messageStoreConfig.getMappedFileSizeCommitLog()); if (messageStoreConfig.isWriteWithoutMmap()) { SharedByteBufferManager.getInstance().init(messageStoreConfig.getMaxMessageSize(), messageStoreConfig.getSharedByteBufferNum()); } this.defaultStoreMetricsManager = new DefaultStoreMetricsManager(); this.scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread", getBrokerIdentity())); if (messageStoreConfig.isEnableCompaction()) { this.compactionStore = new CompactionStore(this); this.compactionService = new CompactionService(commitLog, this, compactionStore); this.dispatcherList.addLast(new CommitLogDispatcherCompaction(compactionService)); } File file = new File(StorePathConfigHelper.getLockFile(messageStoreConfig.getStorePathRootDir())); UtilAll.ensureDirOK(file.getParent()); UtilAll.ensureDirOK(getStorePathPhysic()); UtilAll.ensureDirOK(getStorePathLogic()); lockFile = new RandomAccessFile(file, "rw"); parseDelayLevel(); } public ConsumeQueueStoreInterface createConsumeQueueStore() { if (messageStoreConfig.isRocksdbCQDoubleWriteEnable()) { return new CombineConsumeQueueStore(this); } return new ConsumeQueueStore(this); } public boolean parseDelayLevel() { HashMap timeUnitTable = new HashMap<>(); timeUnitTable.put("s", 1000L); timeUnitTable.put("m", 1000L * 60); timeUnitTable.put("h", 1000L * 60 * 60); timeUnitTable.put("d", 1000L * 60 * 60 * 24); String levelString = messageStoreConfig.getMessageDelayLevel(); try { String[] levelArray = levelString.split(" "); for (int i = 0; i < levelArray.length; i++) { String value = levelArray[i]; String ch = value.substring(value.length() - 1); Long tu = timeUnitTable.get(ch); int level = i + 1; if (level > this.maxDelayLevel) { this.maxDelayLevel = level; } long num = Long.parseLong(value.substring(0, value.length() - 1)); long delayTimeMillis = tu * num; this.delayLevelTable.put(level, delayTimeMillis); } } catch (Exception e) { LOGGER.error("parse message delay level failed. messageDelayLevel = {}", levelString, e); return false; } return true; } /** * @throws IOException */ @Override public boolean load() { boolean result = true; stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.LOAD_BEGIN); try { boolean lastExitOK = !this.isTempFileExist(); LOGGER.info("last shutdown {}, store path root dir: {}", lastExitOK ? "normally" : "abnormally", messageStoreConfig.getStorePathRootDir()); // load Commit Log result = this.commitLog.load(); stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.LOAD_COMMITLOG_OK, result); // load Consume Queue result = result && this.consumeQueueStore.load(); stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.LOAD_CONSUME_QUEUE_OK, result); // Register consume queue store for commitlog dispatch // AbstractConsumeQueueStore implements CommitLogDispatchStore, so we can register it directly if (this.consumeQueueStore != null) { registerCommitLogDispatchStore(this.consumeQueueStore); } if (messageStoreConfig.isEnableCompaction()) { result = result && this.compactionService.load(lastExitOK); stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.LOAD_COMPACTION_OK, result); } if (result) { loadCheckPoint(); result = this.indexService.load(lastExitOK); registerCommitLogDispatchStore(this.indexService); stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.LOAD_INDEX_OK, result); // Register IndexRocksDBStore and TransMessageRocksDBStore for commit-log dispatch if (messageStoreConfig.isIndexRocksDBEnable()) { registerCommitLogDispatchStore(this.indexRocksDBStore); } if (messageStoreConfig.isTransRocksDBEnable() && transMessageRocksDBStore != null) { registerCommitLogDispatchStore(this.transMessageRocksDBStore); } this.recover(lastExitOK); LOGGER.info("message store recover end, and the max phy offset = {}", this.getMaxPhyOffset()); } long maxOffset = this.getMaxPhyOffset(); this.setBrokerInitMaxOffset(maxOffset); LOGGER.info("load over, and the max phy offset = {}", maxOffset); } catch (Exception e) { LOGGER.error("load exception", e); result = false; } if (!result) { this.allocateMappedFileService.shutdown(); } return result; } public void loadCheckPoint() throws IOException { this.storeCheckpoint = new StoreCheckpoint( StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir())); this.masterFlushedOffset = this.storeCheckpoint.getMasterFlushedOffset(); setConfirmOffset(this.storeCheckpoint.getConfirmPhyOffset()); } private void recover(final boolean lastExitOK) throws RocksDBException { this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.RECOVER_BEGIN); // recover consume queue this.consumeQueueStore.recover(this.brokerConfig.isRecoverConcurrently()); this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.RECOVER_CONSUME_QUEUE_OK); // recover commitlog // Calculate the minimum dispatch offset from all registered stores Long dispatchFromPhyOffset = this.consumeQueueStore.getDispatchFromPhyOffset(lastExitOK); for (CommitLogDispatchStore store : commitLogDispatchStores) { Long storeOffset = store.getDispatchFromPhyOffset(lastExitOK); if (storeOffset != null && storeOffset > 0) { dispatchFromPhyOffset = Math.min(dispatchFromPhyOffset, storeOffset); } } if (lastExitOK) { this.commitLog.recoverNormally(dispatchFromPhyOffset); } else { this.commitLog.recoverAbnormally(dispatchFromPhyOffset); } this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.RECOVER_COMMITLOG_OK); // recover consume offset table this.recoverTopicQueueTable(); this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.RECOVER_TOPIC_QUEUE_TABLE_OK); } /** * @throws Exception */ @Override public void start() throws Exception { if (!messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { this.haService.init(this); } if (this.isTransientStorePoolEnable()) { this.transientStorePool.init(); } this.allocateMappedFileService.start(); this.indexService.start(); lock = lockFile.getChannel().tryLock(0, 1, false); if (lock == null || lock.isShared() || !lock.isValid()) { throw new RuntimeException("Lock failed, MQ already started, lock status: " + lock); } lockFile.getChannel().write(ByteBuffer.wrap("lock".getBytes(StandardCharsets.UTF_8))); lockFile.getChannel().force(true); this.reputMessageService.setReputFromOffset(this.commitLog.getConfirmOffset()); this.reputMessageService.start(); // Checking is not necessary, as long as the dLedger's implementation exactly follows the definition of Recover, // which is eliminating the dispatch inconsistency between the commitLog and consumeQueue at the end of recovery. this.doRecheckReputOffsetFromCq(); this.commitLog.start(); this.consumeQueueStore.start(); this.storeStatsService.start(); if (this.haService != null) { this.haService.start(); } this.createTempFile(); this.addScheduleTask(); this.perfs.start(); this.shutdown = false; this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.RUNNING); } private void doRecheckReputOffsetFromCq() throws InterruptedException { if (!messageStoreConfig.isRecheckReputOffsetFromCq()) { return; } /* * 1. Make sure the fast-forward messages to be truncated during the recovering according to the max physical offset of the commitlog; * 2. DLedger committedPos may be missing, so the maxPhysicalPosInLogicQueue maybe bigger that maxOffset returned by DLedgerCommitLog, just let it go; * 3. Calculate the reput offset according to the consume queue; * 4. Make sure the fall-behind messages to be dispatched before starting the commitlog, especially when the broker role are automatically changed. */ long maxPhysicalPosInLogicQueue = commitLog.getMinOffset(); for (ConcurrentMap maps : this.getConsumeQueueTable().values()) { for (ConsumeQueueInterface logic : maps.values()) { if (logic.getMaxPhysicOffset() > maxPhysicalPosInLogicQueue) { maxPhysicalPosInLogicQueue = logic.getMaxPhysicOffset(); } } } // If maxPhyPos(CQs) < minPhyPos(CommitLog), some newly deleted topics may be re-dispatched into cqs mistakenly. if (maxPhysicalPosInLogicQueue < 0) { maxPhysicalPosInLogicQueue = 0; } if (maxPhysicalPosInLogicQueue < this.commitLog.getMinOffset()) { maxPhysicalPosInLogicQueue = this.commitLog.getMinOffset(); /* * This happens in following conditions: * 1. If someone removes all the consumequeue files or the disk get damaged. * 2. Launch a new broker, and copy the commitlog from other brokers. * * All the conditions has the same in common that the maxPhysicalPosInLogicQueue should be 0. * If the maxPhysicalPosInLogicQueue is gt 0, there maybe something wrong. */ LOGGER.warn("[TooSmallCqOffset] maxPhysicalPosInLogicQueue={} clMinOffset={}", maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset()); } LOGGER.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}", maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(), this.commitLog.getConfirmOffset()); this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue); /** * 1. Finish dispatching the messages fall behind, then to start other services. * 2. DLedger committedPos may be missing, so here just require dispatchBehindBytes <= 0 */ while (true) { if (dispatchBehindBytes() <= 0) { break; } Thread.sleep(1000); LOGGER.info("Try to finish doing reput the messages fall behind during the starting, reputOffset={} maxOffset={} behind={}", this.reputMessageService.getReputFromOffset(), this.getMaxPhyOffset(), this.dispatchBehindBytes()); } this.recoverTopicQueueTable(); } @Override public void shutdown() { if (!this.stateMachine.getCurrentState().equals(MessageStoreStateMachine.MessageStoreState.SHUTDOWN_OK)) { this.shutdown = true; this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.SHUTDOWN_BEGIN); if (this.scheduledExecutorService != null) { this.scheduledExecutorService.shutdown(); } this.scheduledCleanQueueExecutorService.shutdown(); try { this.scheduledExecutorService.awaitTermination(3, TimeUnit.SECONDS); this.scheduledCleanQueueExecutorService.awaitTermination(3, TimeUnit.SECONDS); Thread.sleep(1000 * 3); } catch (Exception e) { LOGGER.error("shutdown Exception, ", e); } if (this.haService != null) { this.haService.shutdown(); } if (this.storeStatsService != null) { this.storeStatsService.shutdown(); } if (this.commitLog != null) { this.commitLog.shutdown(); } if (this.reputMessageService != null) { this.reputMessageService.shutdown(); } if (this.consumeQueueStore != null) { this.consumeQueueStore.shutdown(); } // dispatch-related services must be shut down after reputMessageService if (this.indexService != null) { this.indexService.shutdown(); } if (this.indexRocksDBStore != null) { this.indexRocksDBStore.shutdown(); } if (this.compactionService != null) { this.compactionService.shutdown(); } if (this.allocateMappedFileService != null) { this.allocateMappedFileService.shutdown(); } if (this.storeCheckpoint != null) { this.storeCheckpoint.shutdown(); } this.perfs.shutdown(); if (this.runningFlags.isWriteable() && dispatchBehindBytes() == 0) { this.deleteFile(StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir())); shutDownNormal = true; this.stateMachine.transitTo(MessageStoreStateMachine.MessageStoreState.SHUTDOWN_OK); } else { LOGGER.warn("the store may be wrong, so shutdown abnormally, and keep abort file."); } } if (this.transientStorePool != null) { this.transientStorePool.destroy(); } if (this.messageRocksDBStorage != null) { this.messageRocksDBStorage.shutdown(); } if (lock != null) { try { lock.release(); } catch (IOException e) { LOGGER.error("release file lock error", e); } } if (lockFile != null) { try { lockFile.close(); } catch (Throwable e) { LOGGER.error("lock file close error", e); } } } @Override public void destroy() { this.consumeQueueStore.destroy(false); this.commitLog.destroy(); this.indexService.destroy(); this.indexRocksDBStore.destroy(); this.deleteFile(StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir())); this.deleteFile(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir())); } public long getMajorFileSize() { long commitLogSize = 0; if (this.commitLog != null) { commitLogSize = this.commitLog.getTotalSize(); } long consumeQueueSize = 0; if (this.consumeQueueStore != null) { consumeQueueSize = this.consumeQueueStore.getTotalSize(); } long indexFileSize = 0; if (this.indexService != null) { indexFileSize = this.indexService.getTotalSize(); } return commitLogSize + consumeQueueSize + indexFileSize; } @Override public CompletableFuture asyncPutMessage(MessageExtBrokerInner msg) { for (PutMessageHook putMessageHook : putMessageHookList) { PutMessageResult handleResult = putMessageHook.executeBeforePutMessage(msg); if (handleResult != null) { return CompletableFuture.completedFuture(handleResult); } } if (msg.getProperties().containsKey(MessageConst.PROPERTY_INNER_NUM) && !MessageSysFlag.check(msg.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG)) { LOGGER.warn("[BUG]The message had property {} but is not an inner batch", MessageConst.PROPERTY_INNER_NUM); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } if (MessageSysFlag.check(msg.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG)) { Optional topicConfig = this.getTopicConfig(msg.getTopic()); if (!QueueTypeUtils.isBatchCq(topicConfig)) { LOGGER.error("[BUG]The message is an inner batch but cq type is not batch cq"); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } } long beginTime = this.getSystemClock().now(); CompletableFuture putResultFuture = this.commitLog.asyncPutMessage(msg); putResultFuture.thenAccept(result -> { long elapsedTime = this.getSystemClock().now() - beginTime; if (elapsedTime > 500) { LOGGER.warn("DefaultMessageStore#putMessage: CommitLog#putMessage cost {}ms, topic={}, bodyLength={}", elapsedTime, msg.getTopic(), msg.getBody().length); } this.storeStatsService.setPutMessageEntireTimeMax(elapsedTime); if (null == result || !result.isOk()) { this.storeStatsService.getPutMessageFailedTimes().add(1); } }); return putResultFuture; } @Override public CompletableFuture asyncPutMessages(MessageExtBatch messageExtBatch) { for (PutMessageHook putMessageHook : putMessageHookList) { PutMessageResult handleResult = putMessageHook.executeBeforePutMessage(messageExtBatch); if (handleResult != null) { return CompletableFuture.completedFuture(handleResult); } } long beginTime = this.getSystemClock().now(); CompletableFuture putResultFuture = this.commitLog.asyncPutMessages(messageExtBatch); putResultFuture.thenAccept(result -> { long eclipseTime = this.getSystemClock().now() - beginTime; if (eclipseTime > 500) { LOGGER.warn("not in lock eclipse time(ms)={}, bodyLength={}", eclipseTime, messageExtBatch.getBody().length); } this.storeStatsService.setPutMessageEntireTimeMax(eclipseTime); if (null == result || !result.isOk()) { this.storeStatsService.getPutMessageFailedTimes().add(1); } }); return putResultFuture; } @Override public PutMessageResult putMessage(MessageExtBrokerInner msg) { return waitForPutResult(asyncPutMessage(msg)); } @Override public PutMessageResult putMessages(MessageExtBatch messageExtBatch) { return waitForPutResult(asyncPutMessages(messageExtBatch)); } private PutMessageResult waitForPutResult(CompletableFuture putMessageResultFuture) { try { int putMessageTimeout = Math.max(this.messageStoreConfig.getSyncFlushTimeout(), this.messageStoreConfig.getSlaveTimeout()) + 5000; return putMessageResultFuture.get(putMessageTimeout, TimeUnit.MILLISECONDS); } catch (ExecutionException | InterruptedException e) { return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, null); } catch (TimeoutException e) { LOGGER.error("usually it will never timeout, putMessageTimeout is much bigger than slaveTimeout and " + "flushTimeout so the result can be got anyway, but in some situations timeout will happen like full gc " + "process hangs or other unexpected situations."); return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, null); } } @Override public boolean isOSPageCacheBusy() { long begin = this.getCommitLog().getBeginTimeInLock(); long diff = this.systemClock.now() - begin; return diff < 10000000 && diff > this.messageStoreConfig.getOsPageCacheBusyTimeOutMills(); } @Override public long lockTimeMills() { return this.commitLog.lockTimeMills(); } @Override public long getMasterFlushedOffset() { return this.masterFlushedOffset; } @Override public void setMasterFlushedOffset(long masterFlushedOffset) { this.masterFlushedOffset = masterFlushedOffset; this.storeCheckpoint.setMasterFlushedOffset(masterFlushedOffset); } @Override public long getBrokerInitMaxOffset() { return this.brokerInitMaxOffset; } @Override public void setBrokerInitMaxOffset(long brokerInitMaxOffset) { this.brokerInitMaxOffset = brokerInitMaxOffset; } public SystemClock getSystemClock() { return systemClock; } @Override public CommitLog getCommitLog() { return commitLog; } public void truncateDirtyFiles(long offsetToTruncate) throws RocksDBException { LOGGER.info("truncate dirty files to {}", offsetToTruncate); if (offsetToTruncate >= this.getMaxPhyOffset()) { LOGGER.info("no need to truncate files, truncate offset is {}, max physical offset is {}", offsetToTruncate, this.getMaxPhyOffset()); return; } this.reputMessageService.shutdown(); long oldReputFromOffset = this.reputMessageService.getReputFromOffset(); // truncate consume queue this.truncateDirtyLogicFiles(offsetToTruncate); // truncate commitLog this.commitLog.truncateDirtyFiles(offsetToTruncate); this.recoverTopicQueueTable(); if (!messageStoreConfig.isEnableBuildConsumeQueueConcurrently()) { this.reputMessageService = new ReputMessageService(); } else { this.reputMessageService = new ConcurrentReputMessageService(); } long resetReputOffset = Math.min(oldReputFromOffset, offsetToTruncate); LOGGER.info("oldReputFromOffset is {}, reset reput from offset to {}", oldReputFromOffset, resetReputOffset); this.reputMessageService.setReputFromOffset(resetReputOffset); this.reputMessageService.start(); } @Override public void truncateDirtyLogicFiles(long phyOffset) throws RocksDBException { this.consumeQueueStore.truncateDirty(phyOffset); } @Override public boolean truncateFiles(long offsetToTruncate) throws RocksDBException { if (offsetToTruncate >= this.getMaxPhyOffset()) { LOGGER.info("no need to truncate files, truncate offset is {}, max physical offset is {}", offsetToTruncate, this.getMaxPhyOffset()); return true; } if (!isOffsetAligned(offsetToTruncate)) { LOGGER.error("offset {} is not align, truncate failed, need manual fix", offsetToTruncate); return false; } truncateDirtyFiles(offsetToTruncate); return true; } @Override public boolean isOffsetAligned(long offset) { SelectMappedBufferResult mappedBufferResult = this.getCommitLogData(offset); if (mappedBufferResult == null) { return true; } DispatchRequest dispatchRequest = this.commitLog.checkMessageAndReturnSize(mappedBufferResult.getByteBuffer(), true, false); return dispatchRequest.isSuccess(); } @Override public GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final MessageFilter messageFilter) { return getMessage(group, topic, queueId, offset, maxMsgNums, MAX_PULL_MSG_SIZE, messageFilter); } @Override public CompletableFuture getMessageAsync(String group, String topic, int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) { return CompletableFuture.completedFuture(getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter)); } @Override public GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final int maxTotalMsgSize, final MessageFilter messageFilter) { if (this.shutdown) { LOGGER.warn("message store has shutdown, so getMessage is forbidden"); return null; } if (!this.runningFlags.isReadable()) { LOGGER.warn("message store is not readable, so getMessage is forbidden " + this.runningFlags.getFlagBits()); return null; } Optional topicConfig = getTopicConfig(topic); CleanupPolicy policy = CleanupPolicyUtils.getDeletePolicy(topicConfig); //check request topic flag if (Objects.equals(policy, CleanupPolicy.COMPACTION) && messageStoreConfig.isEnableCompaction()) { return compactionStore.getMessage(group, topic, queueId, offset, maxMsgNums, maxTotalMsgSize); } // else skip long beginTime = this.getSystemClock().now(); GetMessageStatus status = GetMessageStatus.NO_MESSAGE_IN_QUEUE; long nextBeginOffset = offset; long minOffset = 0; long maxOffset = 0; GetMessageResult getResult = new GetMessageResult(); int filterMessageCount = 0; final long maxOffsetPy = this.commitLog.getMaxOffset(); ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (consumeQueue != null) { minOffset = consumeQueue.getMinOffsetInQueue(); maxOffset = consumeQueue.getMaxOffsetInQueue(); if (maxOffset == 0) { status = GetMessageStatus.NO_MESSAGE_IN_QUEUE; nextBeginOffset = nextOffsetCorrection(offset, 0); } else if (offset < minOffset) { status = GetMessageStatus.OFFSET_TOO_SMALL; nextBeginOffset = nextOffsetCorrection(offset, minOffset); } else if (offset == maxOffset) { status = GetMessageStatus.OFFSET_OVERFLOW_ONE; nextBeginOffset = nextOffsetCorrection(offset, offset); } else if (offset > maxOffset) { status = GetMessageStatus.OFFSET_OVERFLOW_BADLY; nextBeginOffset = nextOffsetCorrection(offset, maxOffset); } else { final int maxFilterMessageSize = Math.max(this.messageStoreConfig.getMaxFilterMessageSize(), maxMsgNums * consumeQueue.getUnitSize()); final boolean diskFallRecorded = this.messageStoreConfig.isDiskFallRecorded(); long maxPullSize = Math.max(maxTotalMsgSize, 100); if (maxPullSize > MAX_PULL_MSG_SIZE) { LOGGER.warn("The max pull size is too large maxPullSize={} topic={} queueId={}", maxPullSize, topic, queueId); maxPullSize = MAX_PULL_MSG_SIZE; } status = GetMessageStatus.NO_MATCHED_MESSAGE; long maxPhyOffsetPulling = 0; int cqFileNum = 0; while (getResult.getBufferTotalSize() <= 0 && nextBeginOffset < maxOffset && cqFileNum++ < this.messageStoreConfig.getTravelCqFileNumWhenGetMessage()) { ReferredIterator bufferConsumeQueue = null; try { bufferConsumeQueue = consumeQueue.iterateFrom(nextBeginOffset, maxMsgNums); if (bufferConsumeQueue == null) { status = GetMessageStatus.OFFSET_FOUND_NULL; nextBeginOffset = nextOffsetCorrection(nextBeginOffset, consumeQueue.rollNextFile(nextBeginOffset)); LOGGER.warn("consumer request topic: " + topic + ", offset: " + offset + ", minOffset: " + minOffset + ", maxOffset: " + maxOffset + ", but access logic queue failed. Correct nextBeginOffset to " + nextBeginOffset); break; } long nextPhyFileStartOffset = Long.MIN_VALUE; while (bufferConsumeQueue.hasNext() && nextBeginOffset < maxOffset) { CqUnit cqUnit = bufferConsumeQueue.next(); long offsetPy = cqUnit.getPos(); int sizePy = cqUnit.getSize(); boolean isInMem = estimateInMemByCommitOffset(offsetPy, maxOffsetPy); if ((cqUnit.getQueueOffset() - offset) * consumeQueue.getUnitSize() >= maxFilterMessageSize) { break; } if (this.isTheBatchFull(sizePy, cqUnit.getBatchNum(), maxMsgNums, maxPullSize, getResult.getBufferTotalSize(), getResult.getMessageCount(), isInMem)) { break; } if (getResult.getBufferTotalSize() >= maxPullSize) { break; } maxPhyOffsetPulling = offsetPy; //Be careful, here should before the isTheBatchFull nextBeginOffset = cqUnit.getQueueOffset() + cqUnit.getBatchNum(); if (nextPhyFileStartOffset != Long.MIN_VALUE) { if (offsetPy < nextPhyFileStartOffset) { continue; } } if (messageFilter != null && !messageFilter.isMatchedByConsumeQueue(cqUnit.getValidTagsCodeAsLong(), cqUnit.getCqExtUnit())) { if (getResult.getBufferTotalSize() == 0) { status = GetMessageStatus.NO_MATCHED_MESSAGE; } continue; } SelectMappedBufferResult selectResult = this.commitLog.getMessage(offsetPy, sizePy); if (null == selectResult) { if (getResult.getBufferTotalSize() == 0) { status = GetMessageStatus.MESSAGE_WAS_REMOVING; } nextPhyFileStartOffset = this.commitLog.rollNextFile(offsetPy); continue; } if (messageStoreConfig.isColdDataFlowControlEnable() && !MixAll.isSysConsumerGroupPullMessage(group) && !selectResult.isInCache()) { getResult.setColdDataSum(getResult.getColdDataSum() + sizePy); } if (messageFilter != null && !messageFilter.isMatchedByCommitLog(selectResult.getByteBuffer().slice(), null)) { if (getResult.getBufferTotalSize() == 0) { status = GetMessageStatus.NO_MATCHED_MESSAGE; } // release... selectResult.release(); filterMessageCount++; continue; } this.storeStatsService.getGetMessageTransferredMsgCount().add(cqUnit.getBatchNum()); getResult.addMessage(selectResult, cqUnit.getQueueOffset(), cqUnit.getBatchNum()); status = GetMessageStatus.FOUND; nextPhyFileStartOffset = Long.MIN_VALUE; } } catch (RocksDBException e) { ERROR_LOG.error("getMessage Failed. cid: {}, topic: {}, queueId: {}, offset: {}, minOffset: {}, maxOffset: {}, {}", group, topic, queueId, offset, minOffset, maxOffset, e.getMessage()); } finally { if (bufferConsumeQueue != null) { bufferConsumeQueue.release(); } } } if (diskFallRecorded) { long fallBehind = maxOffsetPy - maxPhyOffsetPulling; brokerStatsManager.recordDiskFallBehindSize(group, topic, queueId, fallBehind); } long diff = maxOffsetPy - maxPhyOffsetPulling; long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0)); getResult.setSuggestPullingFromSlave(diff > memory); } } else { status = GetMessageStatus.NO_MATCHED_LOGIC_QUEUE; nextBeginOffset = nextOffsetCorrection(offset, 0); } if (GetMessageStatus.FOUND == status) { this.storeStatsService.getGetMessageTimesTotalFound().add(1); } else { this.storeStatsService.getGetMessageTimesTotalMiss().add(1); } if (this.messageStoreConfig.isDiskFallRecorded() && GetMessageStatus.OFFSET_OVERFLOW_ONE == status) { brokerStatsManager.recordDiskFallBehindSize(group, topic, queueId, 0); brokerStatsManager.recordDiskFallBehindTime(group, topic, queueId, 0); } long elapsedTime = this.getSystemClock().now() - beginTime; this.storeStatsService.setGetMessageEntireTimeMax(elapsedTime); // lazy init no data found. if (getResult == null) { getResult = new GetMessageResult(0); } getResult.setStatus(status); getResult.setNextBeginOffset(nextBeginOffset); getResult.setMaxOffset(maxOffset); getResult.setMinOffset(minOffset); getResult.setFilterMessageCount(filterMessageCount); return getResult; } @Override public CompletableFuture getMessageAsync(String group, String topic, int queueId, long offset, int maxMsgNums, int maxTotalMsgSize, MessageFilter messageFilter) { return CompletableFuture.completedFuture(getMessage(group, topic, queueId, offset, maxMsgNums, maxTotalMsgSize, messageFilter)); } @Override public long getMaxOffsetInQueue(String topic, int queueId) throws ConsumeQueueException { return getMaxOffsetInQueue(topic, queueId, true); } @Override public long getMaxOffsetInQueue(String topic, int queueId, boolean committed) throws ConsumeQueueException { if (committed) { ConsumeQueueInterface logic = this.getConsumeQueue(topic, queueId); if (logic != null) { return logic.getMaxOffsetInQueue(); } } else { Long offset = this.consumeQueueStore.getMaxOffset(topic, queueId); if (offset != null) { return offset; } } return 0; } @Override public long getMinOffsetInQueue(String topic, int queueId) { try { return this.consumeQueueStore.getMinOffsetInQueue(topic, queueId); } catch (RocksDBException e) { ERROR_LOG.error("getMinOffsetInQueue Failed. topic: {}, queueId: {}", topic, queueId, e); return -1; } } @Override public TimerMessageStore getTimerMessageStore() { return this.timerMessageStore; } @Override public TimerMessageRocksDBStore getTimerMessageRocksDBStore() { return this.timerMessageRocksDBStore; } @Override public TransMessageRocksDBStore getTransMessageRocksDBStore() { return this.transMessageRocksDBStore; } @Override public void setTimerMessageStore(TimerMessageStore timerMessageStore) { this.timerMessageStore = timerMessageStore; } @Override public void setTimerMessageRocksDBStore(TimerMessageRocksDBStore timerMessageRocksDBStore) { this.timerMessageRocksDBStore = timerMessageRocksDBStore; } @Override public void setTransMessageRocksDBStore(TransMessageRocksDBStore transMessageRocksDBStore) { this.transMessageRocksDBStore = transMessageRocksDBStore; // Register TransMessageRocksDBStore for commitlog dispatch if enabled if (transMessageRocksDBStore != null && messageStoreConfig.isTransRocksDBEnable()) { registerCommitLogDispatchStore(this.transMessageRocksDBStore); } } /** * Register a store that requires commitlog dispatch and recovery. Each store should register itself when loading. * * @param store the store to register */ public void registerCommitLogDispatchStore(CommitLogDispatchStore store) { if (store != null) { commitLogDispatchStores.add(store); LOGGER.info("Registered CommitLogDispatchStore: {}", store.getClass().getSimpleName()); } } /** * Get all registered CommitLogDispatchStore instances. * * @return list of registered stores */ public List getCommitLogDispatchStores() { return commitLogDispatchStores; } @Override public long getCommitLogOffsetInQueue(String topic, int queueId, long consumeQueueOffset) { ConsumeQueueInterface consumeQueue = getConsumeQueue(topic, queueId); if (consumeQueue != null) { CqUnit cqUnit = consumeQueue.get(consumeQueueOffset); if (cqUnit != null) { return cqUnit.getPos(); } } return 0; } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp) { return this.getOffsetInQueueByTime(topic, queueId, timestamp, BoundaryType.LOWER); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) { try { return this.consumeQueueStore.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType); } catch (RocksDBException e) { ERROR_LOG.error("getOffsetInQueueByTime Failed. topic: {}, queueId: {}, timestamp: {} boundaryType: {}, {}", topic, queueId, timestamp, boundaryType, e.getMessage()); } return 0; } @Override public MessageExt lookMessageByOffset(long commitLogOffset) { SelectMappedBufferResult sbr = this.commitLog.getMessage(commitLogOffset, 4); if (null != sbr) { try { // 1 TOTALSIZE int size = sbr.getByteBuffer().getInt(); return lookMessageByOffset(commitLogOffset, size); } finally { sbr.release(); } } return null; } @Override public SelectMappedBufferResult selectOneMessageByOffset(long commitLogOffset) { SelectMappedBufferResult sbr = this.commitLog.getMessage(commitLogOffset, 4); if (null != sbr) { try { // 1 TOTALSIZE int size = sbr.getByteBuffer().getInt(); return this.commitLog.getMessage(commitLogOffset, size); } finally { sbr.release(); } } return null; } @Override public SelectMappedBufferResult selectOneMessageByOffset(long commitLogOffset, int msgSize) { return this.commitLog.getMessage(commitLogOffset, msgSize); } @Override public String getRunningDataInfo() { return this.storeStatsService.toString(); } public String getStorePathPhysic() { String storePathPhysic; if (DefaultMessageStore.this.getMessageStoreConfig().isEnableDLegerCommitLog()) { storePathPhysic = ((DLedgerCommitLog) DefaultMessageStore.this.getCommitLog()).getdLedgerServer().getdLedgerConfig().getDataStorePath(); } else { storePathPhysic = DefaultMessageStore.this.getMessageStoreConfig().getStorePathCommitLog(); } return storePathPhysic; } public String getStorePathLogic() { return StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()); } public MessageArrivingListener getMessageArrivingListener() { return messageArrivingListener; } @Override public HashMap getRuntimeInfo() { HashMap result = this.storeStatsService.getRuntimeInfo(); { double minPhysicsUsedRatio = Double.MAX_VALUE; String commitLogStorePath = getStorePathPhysic(); String[] paths = commitLogStorePath.trim().split(MixAll.MULTI_PATH_SPLITTER); for (String clPath : paths) { double physicRatio = UtilAll.isPathExists(clPath) ? UtilAll.getDiskPartitionSpaceUsedPercent(clPath) : -1; result.put(RunningStats.commitLogDiskRatio.name() + "_" + clPath, String.valueOf(physicRatio)); minPhysicsUsedRatio = Math.min(minPhysicsUsedRatio, physicRatio); } result.put(RunningStats.commitLogDiskRatio.name(), String.valueOf(minPhysicsUsedRatio)); } { double logicsRatio = UtilAll.getDiskPartitionSpaceUsedPercent(getStorePathLogic()); result.put(RunningStats.consumeQueueDiskRatio.name(), String.valueOf(logicsRatio)); } result.put(RunningStats.commitLogMinOffset.name(), String.valueOf(DefaultMessageStore.this.getMinPhyOffset())); result.put(RunningStats.commitLogMaxOffset.name(), String.valueOf(DefaultMessageStore.this.getMaxPhyOffset())); return result; } @Override public long getMaxPhyOffset() { return this.commitLog.getMaxOffset(); } @Override public long getMinPhyOffset() { return this.commitLog.getMinOffset(); } @Override public long getLastFileFromOffset() { return this.commitLog.getLastFileFromOffset(); } @Override public boolean getLastMappedFile(long startOffset) { return this.commitLog.getLastMappedFile(startOffset); } @Override public long getEarliestMessageTime(String topic, int queueId) { ConsumeQueueInterface logicQueue = this.getConsumeQueue(topic, queueId); if (logicQueue != null) { Pair pair = logicQueue.getEarliestUnitAndStoreTime(); if (pair != null && pair.getObject2() != null) { return pair.getObject2(); } } return -1; } @Override public CompletableFuture getEarliestMessageTimeAsync(String topic, int queueId) { return CompletableFuture.completedFuture(getEarliestMessageTime(topic, queueId)); } @Override public long getEarliestMessageTime() { long minPhyOffset = this.getMinPhyOffset(); if (this.getCommitLog() instanceof DLedgerCommitLog) { minPhyOffset += DLedgerEntry.BODY_OFFSET; } int size = MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION + 8; if (NetworkUtil.validCommonInet6Address(this.brokerConfig.getBrokerIP1())) { size = MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION + 20; } return this.getCommitLog().pickupStoreTimestamp(minPhyOffset, size); } @Override public long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) { ConsumeQueueInterface logicQueue = this.getConsumeQueue(topic, queueId); if (logicQueue != null) { Pair pair = logicQueue.getCqUnitAndStoreTime(consumeQueueOffset); if (pair != null && pair.getObject2() != null) { return pair.getObject2(); } } return -1; } @Override public CompletableFuture getMessageStoreTimeStampAsync(String topic, int queueId, long consumeQueueOffset) { return CompletableFuture.completedFuture(getMessageStoreTimeStamp(topic, queueId, consumeQueueOffset)); } @Override public long getMessageTotalInQueue(String topic, int queueId) { ConsumeQueueInterface logicQueue = this.getConsumeQueue(topic, queueId); if (logicQueue != null) { return logicQueue.getMessageTotalInQueue(); } return 0; } @Override public SelectMappedBufferResult getCommitLogData(final long offset) { if (this.shutdown) { LOGGER.warn("message store has shutdown, so getPhyQueueData is forbidden"); return null; } return this.commitLog.getData(offset); } @Override public List getBulkCommitLogData(final long offset, final int size) { if (this.shutdown) { LOGGER.warn("message store has shutdown, so getBulkCommitLogData is forbidden"); return null; } return this.commitLog.getBulkData(offset, size); } @Override public boolean appendToCommitLog(long startOffset, byte[] data, int dataStart, int dataLength) { if (this.shutdown) { LOGGER.warn("message store has shutdown, so appendToCommitLog is forbidden"); return false; } boolean result = this.commitLog.appendData(startOffset, data, dataStart, dataLength); if (result) { this.reputMessageService.wakeup(); } else { LOGGER.error( "DefaultMessageStore#appendToCommitLog: failed to append data to commitLog, physical offset={}, data " + "length={}", startOffset, data.length); } return result; } @Override public void executeDeleteFilesManually() { this.cleanCommitLogService.executeDeleteFilesManually(); } @Override public QueryMessageResult queryMessage(String topic, String key, int maxNum, long begin, long end) { QueryMessageResult queryMessageResult = new QueryMessageResult(); long lastQueryMsgTime = end; for (int i = 0; i < 3; i++) { QueryOffsetResult queryOffsetResult = null; if (messageStoreConfig.isIndexFileReadEnable()) { queryOffsetResult = this.indexService.queryOffset(topic, key, maxNum, begin, lastQueryMsgTime, null); LOGGER.debug("indexService query Message queryOffsetResult : {}", JSON.toJSONString(queryOffsetResult)); } else if (messageStoreConfig.isIndexRocksDBEnable()) { queryOffsetResult = this.indexRocksDBStore.queryOffset(topic, key, maxNum, begin, lastQueryMsgTime, null, null); LOGGER.debug("indexRocksDBStore query Message queryOffsetResult : {}", JSON.toJSONString(queryOffsetResult)); } if (null == queryOffsetResult || CollectionUtils.isEmpty(queryOffsetResult.getPhyOffsets())) { break; } Collections.sort(queryOffsetResult.getPhyOffsets()); queryMessageResult.setIndexLastUpdatePhyoffset(queryOffsetResult.getIndexLastUpdatePhyoffset()); queryMessageResult.setIndexLastUpdateTimestamp(queryOffsetResult.getIndexLastUpdateTimestamp()); for (int m = 0; m < queryOffsetResult.getPhyOffsets().size(); m++) { long offset = queryOffsetResult.getPhyOffsets().get(m); try { MessageExt msg = this.lookMessageByOffset(offset); if (0 == m) { lastQueryMsgTime = msg.getStoreTimestamp(); } SelectMappedBufferResult result = this.commitLog.getData(offset, false); if (result != null) { int size = result.getByteBuffer().getInt(0); result.getByteBuffer().limit(size); result.setSize(size); queryMessageResult.addMessage(result); } } catch (Exception e) { LOGGER.error("queryMessage exception", e); } } if (queryMessageResult.getBufferTotalSize() > 0) { break; } if (lastQueryMsgTime < begin) { break; } } return queryMessageResult; } @Override public QueryMessageResult queryMessage(String topic, String key, int maxNum, long begin, long end, String indexType, String lastKey) { QueryMessageResult queryMessageResult = new QueryMessageResult(); long lastQueryMsgTime = end; for (int i = 0; i < 3; i++) { QueryOffsetResult queryOffsetResult = null; if (messageStoreConfig.isIndexFileReadEnable()) { queryOffsetResult = this.indexService.queryOffset(topic, key, maxNum, begin, lastQueryMsgTime, indexType); LOGGER.debug("indexService query Message queryOffsetResult : {}", JSON.toJSONString(queryOffsetResult)); } else if (messageStoreConfig.isIndexRocksDBEnable()) { queryOffsetResult = this.indexRocksDBStore.queryOffset(topic, key, maxNum, begin, lastQueryMsgTime, indexType, lastKey); LOGGER.debug("indexRocksDBStore query Message queryOffsetResult : {}", JSON.toJSONString(queryOffsetResult)); } if (null == queryOffsetResult || CollectionUtils.isEmpty(queryOffsetResult.getPhyOffsets())) { break; } Collections.sort(queryOffsetResult.getPhyOffsets()); queryMessageResult.setIndexLastUpdatePhyoffset(queryOffsetResult.getIndexLastUpdatePhyoffset()); queryMessageResult.setIndexLastUpdateTimestamp(queryOffsetResult.getIndexLastUpdateTimestamp()); for (int m = 0; m < queryOffsetResult.getPhyOffsets().size(); m++) { long offset = queryOffsetResult.getPhyOffsets().get(m); try { MessageExt msg = this.lookMessageByOffset(offset); if (0 == m && null != msg) { lastQueryMsgTime = msg.getStoreTimestamp(); } SelectMappedBufferResult result = this.commitLog.getData(offset, false); if (result != null) { int size = result.getByteBuffer().getInt(0); result.getByteBuffer().limit(size); result.setSize(size); queryMessageResult.addMessage(result); } } catch (Exception e) { LOGGER.error("queryMessage exception", e); } } if (queryMessageResult.getBufferTotalSize() > 0) { break; } if (lastQueryMsgTime < begin) { break; } } return queryMessageResult; } @Override public CompletableFuture queryMessageAsync(String topic, String key, int maxNum, long begin, long end) { return CompletableFuture.completedFuture(queryMessage(topic, key, maxNum, begin, end)); } @Override public CompletableFuture queryMessageAsync(String topic, String key, int maxNum, long begin, long end, String indexType, String lastKey) { return CompletableFuture.completedFuture(queryMessage(topic, key, maxNum, begin, end, indexType, lastKey)); } @Override public void updateHaMasterAddress(String newAddr) { if (this.haService != null) { this.haService.updateHaMasterAddress(newAddr); } } @Override public void updateMasterAddress(String newAddr) { if (this.haService != null) { this.haService.updateMasterAddress(newAddr); } if (this.compactionService != null) { this.compactionService.updateMasterAddress(newAddr); } } @Override public void setAliveReplicaNumInGroup(int aliveReplicaNums) { this.aliveReplicasNum = aliveReplicaNums; } @Override public void wakeupHAClient() { if (this.haService != null) { this.haService.getHAClient().wakeup(); } } @Override public int getAliveReplicaNumInGroup() { return this.aliveReplicasNum; } @Override public long slaveFallBehindMuch() { if (this.haService == null || this.messageStoreConfig.isDuplicationEnable() || this.messageStoreConfig.isEnableDLegerCommitLog()) { LOGGER.warn("haServer is null or duplication is enable or enableDLegerCommitLog is true"); return -1; } else { return this.commitLog.getMaxOffset() - this.haService.getPush2SlaveMaxOffset().get(); } } @Override public long now() { return this.systemClock.now(); } /** * Lazy clean queue offset table. If offset table is cleaned, and old messages are dispatching after the old consume * queue is cleaned, consume queue will be created with old offset, then later message with new offset table can not * be dispatched to consume queue. */ @Override public int deleteTopics(final Set deleteTopics) { if (deleteTopics == null || deleteTopics.isEmpty()) { return 0; } int deleteCount = 0; for (String topic : deleteTopics) { if (!consumeQueueStore.deleteTopic(topic)) { continue; } if (this.brokerConfig.isAutoDeleteUnusedStats()) { if (!MixAll.isLmq(topic)) { this.brokerStatsManager.onTopicDeleted(topic); } } // destroy consume queue dir String consumeQueueDir = StorePathConfigHelper.getStorePathConsumeQueue( this.messageStoreConfig.getStorePathRootDir()) + File.separator + topic; String consumeQueueExtDir = StorePathConfigHelper.getStorePathConsumeQueueExt( this.messageStoreConfig.getStorePathRootDir()) + File.separator + topic; String batchConsumeQueueDir = StorePathConfigHelper.getStorePathBatchConsumeQueue( this.messageStoreConfig.getStorePathRootDir()) + File.separator + topic; UtilAll.deleteEmptyDirectory(new File(consumeQueueDir)); UtilAll.deleteEmptyDirectory(new File(consumeQueueExtDir)); UtilAll.deleteEmptyDirectory(new File(batchConsumeQueueDir)); LOGGER.info("DeleteTopic: Topic has been destroyed, topic={}", topic); deleteCount++; } return deleteCount; } @Override public int cleanUnusedTopic(final Set retainTopics) { Set consumeQueueTopicSet = this.getConsumeQueueTable().keySet(); int deleteCount = 0; for (String topicName : Sets.difference(consumeQueueTopicSet, retainTopics)) { if (retainTopics.contains(topicName) || TopicValidator.isSystemTopic(topicName) || MixAll.isLmq(topicName)) { continue; } deleteCount += this.deleteTopics(Sets.newHashSet(topicName)); } return deleteCount; } @Override public void cleanExpiredConsumerQueue() { long minCommitLogOffset = this.commitLog.getMinOffset(); this.consumeQueueStore.cleanExpired(minCommitLogOffset); } public Map getMessageIds(final String topic, final int queueId, long minOffset, long maxOffset, SocketAddress storeHost) { Map messageIds = new HashMap<>(); if (this.shutdown) { return messageIds; } ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (consumeQueue != null) { minOffset = Math.max(minOffset, consumeQueue.getMinOffsetInQueue()); maxOffset = Math.min(maxOffset, consumeQueue.getMaxOffsetInQueue()); if (maxOffset == 0) { return messageIds; } long nextOffset = minOffset; while (nextOffset < maxOffset) { ReferredIterator bufferConsumeQueue = consumeQueue.iterateFrom(nextOffset); try { if (bufferConsumeQueue != null && bufferConsumeQueue.hasNext()) { while (bufferConsumeQueue.hasNext()) { CqUnit cqUnit = bufferConsumeQueue.next(); long offsetPy = cqUnit.getPos(); InetSocketAddress inetSocketAddress = (InetSocketAddress) storeHost; int msgIdLength = (inetSocketAddress.getAddress() instanceof Inet6Address) ? 16 + 4 + 8 : 4 + 4 + 8; final ByteBuffer msgIdMemory = ByteBuffer.allocate(msgIdLength); String msgId = MessageDecoder.createMessageId(msgIdMemory, MessageExt.socketAddress2ByteBuffer(storeHost), offsetPy); messageIds.put(msgId, cqUnit.getQueueOffset()); nextOffset = cqUnit.getQueueOffset() + cqUnit.getBatchNum(); if (nextOffset >= maxOffset) { return messageIds; } } } else { return messageIds; } } finally { if (bufferConsumeQueue != null) { bufferConsumeQueue.release(); } } } } return messageIds; } @Override @Deprecated public boolean checkInDiskByConsumeOffset(final String topic, final int queueId, long consumeOffset) { final long maxOffsetPy = this.commitLog.getMaxOffset(); ConsumeQueueInterface consumeQueue = getConsumeQueue(topic, queueId); if (consumeQueue != null) { CqUnit cqUnit = consumeQueue.get(consumeOffset); if (cqUnit != null) { long offsetPy = cqUnit.getPos(); return !estimateInMemByCommitOffset(offsetPy, maxOffsetPy); } else { return false; } } return false; } @Override public boolean checkInMemByConsumeOffset(final String topic, final int queueId, long consumeOffset, int batchSize) { ConsumeQueueInterface consumeQueue = getConsumeQueue(topic, queueId); if (consumeQueue != null) { CqUnit firstCQItem = consumeQueue.get(consumeOffset); if (firstCQItem == null) { return false; } long startOffsetPy = firstCQItem.getPos(); if (batchSize <= 1) { int size = firstCQItem.getSize(); return checkInMemByCommitOffset(startOffsetPy, size); } CqUnit lastCQItem = consumeQueue.get(consumeOffset + batchSize); if (lastCQItem == null) { int size = firstCQItem.getSize(); return checkInMemByCommitOffset(startOffsetPy, size); } long endOffsetPy = lastCQItem.getPos(); int size = (int) (endOffsetPy - startOffsetPy) + lastCQItem.getSize(); return checkInMemByCommitOffset(startOffsetPy, size); } return false; } @Override public boolean checkInStoreByConsumeOffset(String topic, int queueId, long consumeOffset) { long commitLogOffset = getCommitLogOffsetInQueue(topic, queueId, consumeOffset); return checkInDiskByCommitOffset(commitLogOffset); } @Override public long dispatchBehindBytes() { return this.reputMessageService.behind(); } @Override public long dispatchBehindMilliseconds() { return this.reputMessageService.behindMs(); } @Override public long flushBehindBytes() { if (this.messageStoreConfig.isTransientStorePoolEnable()) { return this.commitLog.remainHowManyDataToCommit() + this.commitLog.remainHowManyDataToFlush(); } else { return this.commitLog.remainHowManyDataToFlush(); } } @Override public long flush() { return this.commitLog.flush(); } @Override public long getFlushedWhere() { return this.commitLog.getFlushedWhere(); } // Fetch and compute the newest confirmOffset. // Even if it is just inited. @Override public long getConfirmOffset() { return this.commitLog.getConfirmOffset(); } // Fetch the original confirmOffset's value. // Without checking and re-computing. public long getConfirmOffsetDirectly() { return this.commitLog.getConfirmOffsetDirectly(); } @Override public void setConfirmOffset(long phyOffset) { this.commitLog.setConfirmOffset(phyOffset); } @Override public byte[] calcDeltaChecksum(long from, long to) { if (from < 0 || to <= from) { return new byte[0]; } int size = (int) (to - from); if (size > this.messageStoreConfig.getMaxChecksumRange()) { LOGGER.error("Checksum range from {}, size {} exceeds threshold {}", from, size, this.messageStoreConfig.getMaxChecksumRange()); return null; } List msgList = new ArrayList<>(); List bufferResultList = this.getBulkCommitLogData(from, size); if (bufferResultList.isEmpty()) { return new byte[0]; } for (SelectMappedBufferResult bufferResult : bufferResultList) { msgList.addAll(MessageDecoder.decodesBatch(bufferResult.getByteBuffer(), true, false, false)); bufferResult.release(); } if (msgList.isEmpty()) { return new byte[0]; } ByteBuffer byteBuffer = ByteBuffer.allocate(size); for (MessageExt msg : msgList) { try { byteBuffer.put(MessageDecoder.encodeUniquely(msg, false)); } catch (IOException ignore) { } } return Hashing.murmur3_128().hashBytes(byteBuffer.array()).asBytes(); } @Override public void setPhysicalOffset(long phyOffset) { this.commitLog.setMappedFileQueueOffset(phyOffset); } @Override public boolean isMappedFilesEmpty() { return this.commitLog.isMappedFilesEmpty(); } @Override public MessageExt lookMessageByOffset(long commitLogOffset, int size) { SelectMappedBufferResult sbr = this.commitLog.getMessage(commitLogOffset, size); if (null != sbr) { try { return MessageDecoder.decode(sbr.getByteBuffer(), true, false); } finally { sbr.release(); } } return null; } @Override public ConsumeQueueInterface findConsumeQueue(String topic, int queueId) { return this.consumeQueueStore.findOrCreateConsumeQueue(topic, queueId); } private long nextOffsetCorrection(long oldOffset, long newOffset) { long nextOffset = oldOffset; if (this.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE || this.getMessageStoreConfig().isOffsetCheckInSlave()) { nextOffset = newOffset; } return nextOffset; } private boolean estimateInMemByCommitOffset(long offsetPy, long maxOffsetPy) { long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0)); return (maxOffsetPy - offsetPy) <= memory; } private boolean checkInMemByCommitOffset(long offsetPy, int size) { SelectMappedBufferResult message = this.commitLog.getMessage(offsetPy, size); if (message != null) { try { return message.isInMem(); } finally { message.release(); } } return false; } public boolean checkInDiskByCommitOffset(long offsetPy) { return offsetPy >= commitLog.getMinOffset(); } /** * The ratio val is estimated by the experiment and experience so that the result is not high accurate for different * business * * @return */ public boolean checkInColdAreaByCommitOffset(long offsetPy, long maxOffsetPy) { long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE * (this.messageStoreConfig.getAccessMessageInMemoryHotRatio() / 100.0)); return (maxOffsetPy - offsetPy) > memory; } private boolean isTheBatchFull(int sizePy, int unitBatchNum, int maxMsgNums, long maxMsgSize, int bufferTotal, int messageTotal, boolean isInMem) { if (0 == bufferTotal || 0 == messageTotal) { return false; } if (messageTotal + unitBatchNum > maxMsgNums) { return true; } if (bufferTotal + sizePy > maxMsgSize) { return true; } if (isInMem) { if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInMemory()) { return true; } return messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInMemory() - 1; } else { if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInDisk()) { return true; } return messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInDisk() - 1; } } private void deleteFile(final String fileName) { File file = new File(fileName); boolean result = file.delete(); LOGGER.info(fileName + (result ? " delete OK" : " delete Failed")); } /** * @throws IOException */ private void createTempFile() throws IOException { String fileName = StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir()); File file = new File(fileName); UtilAll.ensureDirOK(file.getParent()); boolean result = file.createNewFile(); LOGGER.info(fileName + (result ? " create OK" : " already exists")); MixAll.string2File(Long.toString(MixAll.getPID()), file.getAbsolutePath()); } private void addScheduleTask() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { DefaultMessageStore.this.cleanFilesPeriodically(); } }, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { DefaultMessageStore.this.checkSelf(); } }, 1, 10, TimeUnit.MINUTES); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (DefaultMessageStore.this.getMessageStoreConfig().isDebugLockEnable()) { try { if (DefaultMessageStore.this.commitLog.getBeginTimeInLock() != 0) { long lockTime = System.currentTimeMillis() - DefaultMessageStore.this.commitLog.getBeginTimeInLock(); if (lockTime > 1000 && lockTime < 10000000) { String stack = UtilAll.jstack(); final String fileName = System.getProperty("user.home") + File.separator + "debug/lock/stack-" + DefaultMessageStore.this.commitLog.getBeginTimeInLock() + "-" + lockTime; MixAll.string2FileNotSafe(stack, fileName); } } } catch (Exception e) { } } } }, 1, 1, TimeUnit.SECONDS); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { DefaultMessageStore.this.storeCheckpoint.flush(); } }, 1, 1, TimeUnit.SECONDS); } private void initializeHAService() { if (!this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { if (brokerConfig.isEnableControllerMode()) { this.haService = new AutoSwitchHAService(); LOGGER.warn("Load AutoSwitch HA Service: {}", AutoSwitchHAService.class.getSimpleName()); } else { this.haService = ServiceProvider.loadClass(HAService.class); if (null == this.haService) { this.haService = new DefaultHAService(); LOGGER.warn("Load default HA Service: {}", DefaultHAService.class.getSimpleName()); } } } } private void cleanFilesPeriodically() { this.cleanCommitLogService.run(); } private void checkSelf() { this.commitLog.checkSelf(); this.consumeQueueStore.checkSelf(); } private boolean isTempFileExist() { String fileName = StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir()); File file = new File(fileName); return file.exists(); } @Override public long getTimingMessageCount(String topic) { if (null == timerMessageStore) { return 0L; } else { return timerMessageStore.getTimerMetrics().getTimingCount(topic); } } @Override public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } @Override public TransientStorePool getTransientStorePool() { return transientStorePool; } @Override public void recoverTopicQueueTable() { long minPhyOffset = this.commitLog.getMinOffset(); this.consumeQueueStore.recoverOffsetTable(minPhyOffset); } @Override public AllocateMappedFileService getAllocateMappedFileService() { return allocateMappedFileService; } @Override public StoreStatsService getStoreStatsService() { return storeStatsService; } public RunningFlags getAccessRights() { return runningFlags; } public ConcurrentMap> getConsumeQueueTable() { return consumeQueueStore.getConsumeQueueTable(); } @Override public StoreCheckpoint getStoreCheckpoint() { return storeCheckpoint; } @Override public HAService getHaService() { return haService; } @Override public RunningFlags getRunningFlags() { return runningFlags; } public void doDispatch(DispatchRequest req) throws RocksDBException { for (CommitLogDispatcher dispatcher : this.dispatcherList) { dispatcher.dispatch(req); } } /** * @param dispatchRequest * @throws RocksDBException only in rocksdb mode */ protected void putMessagePositionInfo(DispatchRequest dispatchRequest) throws RocksDBException { this.consumeQueueStore.putMessagePositionInfoWrapper(dispatchRequest); } @Override public DispatchRequest checkMessageAndReturnSize(final ByteBuffer byteBuffer, final boolean checkCRC, final boolean checkDupInfo, final boolean readBody) { return this.commitLog.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); } @Override public long getStateMachineVersion() { return stateMachineVersion; } public void setStateMachineVersion(long stateMachineVersion) { this.stateMachineVersion = stateMachineVersion; } public BrokerStatsManager getBrokerStatsManager() { return brokerStatsManager; } public BrokerConfig getBrokerConfig() { return brokerConfig; } public int remainTransientStoreBufferNumbs() { if (this.isTransientStorePoolEnable()) { return this.transientStorePool.availableBufferNums(); } return Integer.MAX_VALUE; } @Override public boolean isTransientStorePoolDeficient() { return remainTransientStoreBufferNumbs() == 0; } @Override public long remainHowManyDataToCommit() { return this.commitLog.remainHowManyDataToCommit(); } @Override public long remainHowManyDataToFlush() { return this.commitLog.remainHowManyDataToFlush(); } @Override public LinkedList getDispatcherList() { return this.dispatcherList; } @Override public void addDispatcher(CommitLogDispatcher dispatcher) { this.dispatcherList.add(dispatcher); } @Override public void setMasterStoreInProcess(MessageStore masterStoreInProcess) { this.masterStoreInProcess = masterStoreInProcess; } @Override public MessageStore getMasterStoreInProcess() { return this.masterStoreInProcess; } @Override public boolean getData(long offset, int size, ByteBuffer byteBuffer) { return this.commitLog.getData(offset, size, byteBuffer); } @Override public ConsumeQueueInterface getConsumeQueue(String topic, int queueId) { return this.consumeQueueStore.getConsumeQueue(topic, queueId); } @Override public void unlockMappedFile(final MappedFile mappedFile) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { mappedFile.munlock(); } }, 6, TimeUnit.SECONDS); } @Override public PerfCounter.Ticks getPerfCounter() { return perfs; } @Override public ConsumeQueueStoreInterface getQueueStore() { return consumeQueueStore; } @Override public void onCommitLogAppend(MessageExtBrokerInner msg, AppendMessageResult result, MappedFile commitLogFile) { // empty } @Override public void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile, boolean isRecover, boolean isFileEnd) throws RocksDBException { if (doDispatch && !isFileEnd) { this.doDispatch(dispatchRequest); } } @Override public boolean isSyncDiskFlush() { return FlushDiskType.SYNC_FLUSH == this.getMessageStoreConfig().getFlushDiskType(); } @Override public boolean isSyncMaster() { return BrokerRole.SYNC_MASTER == this.getMessageStoreConfig().getBrokerRole(); } @Override public void assignOffset(MessageExtBrokerInner msg) throws RocksDBException { final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) { this.consumeQueueStore.assignQueueOffset(msg); } } @Override public void increaseOffset(MessageExtBrokerInner msg, short messageNum) { final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) { this.consumeQueueStore.increaseQueueOffset(msg, messageNum); } } public ConcurrentMap getTopicConfigs() { return this.topicConfigTable; } public Optional getTopicConfig(String topic) { if (this.topicConfigTable == null) { return Optional.empty(); } return Optional.ofNullable(this.topicConfigTable.get(topic)); } public BrokerIdentity getBrokerIdentity() { if (messageStoreConfig.isEnableDLegerCommitLog()) { return new BrokerIdentity( brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), Integer.parseInt(messageStoreConfig.getdLegerSelfId().substring(1)), brokerConfig.isInBrokerContainer()); } else { return new BrokerIdentity( brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerConfig.getBrokerId(), brokerConfig.isInBrokerContainer()); } } class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher { @Override public void dispatch(DispatchRequest request) throws RocksDBException { final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag()); switch (tranType) { case MessageSysFlag.TRANSACTION_NOT_TYPE: case MessageSysFlag.TRANSACTION_COMMIT_TYPE: putMessagePositionInfo(request); break; case MessageSysFlag.TRANSACTION_PREPARED_TYPE: case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: break; } } } class CommitLogDispatcherBuildIndex implements CommitLogDispatcher { @Override public void dispatch(DispatchRequest request) { if (DefaultMessageStore.this.messageStoreConfig.isMessageIndexEnable()) { if (DefaultMessageStore.this.messageStoreConfig.isIndexFileWriteEnable()) { DefaultMessageStore.this.indexService.buildIndex(request); } if (DefaultMessageStore.this.messageStoreConfig.isIndexRocksDBEnable()) { DefaultMessageStore.this.indexRocksDBStore.buildIndex(request); } } } } class CommitLogDispatcherBuildTransIndex implements CommitLogDispatcher { @Override public void dispatch(DispatchRequest request) { if (DefaultMessageStore.this.messageStoreConfig.isTransRocksDBEnable()) { if (null == request || StringUtils.isEmpty(request.getTopic())) { return; } if (!request.getTopic().equals(TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC) && !request.getTopic().equals(TopicValidator.RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC)) { return; } if (null == DefaultMessageStore.this.transMessageRocksDBStore) { if (System.currentTimeMillis() % 1000 == 0) { LOGGER.error("CommitLogDispatcherBuildTransIndex dispatch error, transMessageRocksDBStore is null"); } return; } DefaultMessageStore.this.transMessageRocksDBStore.buildTransIndex(request); } } } public boolean isTimeToDelete() { String when = messageStoreConfig.getDeleteWhen(); if (UtilAll.isItTimeToDo(when)) { LOGGER.info("it's time to reclaim disk space, " + when); return true; } return false; } class CleanCommitLogService { private final static int MAX_MANUAL_DELETE_FILE_TIMES = 20; private final String diskSpaceWarningLevelRatio = System.getProperty("rocketmq.broker.diskSpaceWarningLevelRatio", ""); private final String diskSpaceCleanForciblyRatio = System.getProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", ""); private long lastRedeleteTimestamp = 0; private final AtomicInteger manualDeleteFileSeveralTimes = new AtomicInteger(); private volatile boolean cleanImmediately = false; private int forceCleanFailedTimes = 0; double getDiskSpaceWarningLevelRatio() { double finalDiskSpaceWarningLevelRatio; if ("".equals(diskSpaceWarningLevelRatio)) { finalDiskSpaceWarningLevelRatio = DefaultMessageStore.this.getMessageStoreConfig().getDiskSpaceWarningLevelRatio() / 100.0; } else { finalDiskSpaceWarningLevelRatio = Double.parseDouble(diskSpaceWarningLevelRatio); } if (finalDiskSpaceWarningLevelRatio > 0.90) { finalDiskSpaceWarningLevelRatio = 0.90; } if (finalDiskSpaceWarningLevelRatio < 0.35) { finalDiskSpaceWarningLevelRatio = 0.35; } return finalDiskSpaceWarningLevelRatio; } double getDiskSpaceCleanForciblyRatio() { double finalDiskSpaceCleanForciblyRatio; if ("".equals(diskSpaceCleanForciblyRatio)) { finalDiskSpaceCleanForciblyRatio = DefaultMessageStore.this.getMessageStoreConfig().getDiskSpaceCleanForciblyRatio() / 100.0; } else { finalDiskSpaceCleanForciblyRatio = Double.parseDouble(diskSpaceCleanForciblyRatio); } if (finalDiskSpaceCleanForciblyRatio > 0.85) { finalDiskSpaceCleanForciblyRatio = 0.85; } if (finalDiskSpaceCleanForciblyRatio < 0.30) { finalDiskSpaceCleanForciblyRatio = 0.30; } return finalDiskSpaceCleanForciblyRatio; } public void executeDeleteFilesManually() { this.manualDeleteFileSeveralTimes.set(MAX_MANUAL_DELETE_FILE_TIMES); DefaultMessageStore.LOGGER.info("executeDeleteFilesManually was invoked"); } public void run() { try { this.deleteExpiredFiles(); this.reDeleteHangedFile(); } catch (Throwable e) { DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } private void deleteExpiredFiles() { int deleteCount = 0; long fileReservedTime = DefaultMessageStore.this.getMessageStoreConfig().getFileReservedTime(); int deletePhysicFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteCommitLogFilesInterval(); int destroyMappedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly(); int deleteFileBatchMax = DefaultMessageStore.this.getMessageStoreConfig().getDeleteFileBatchMax(); boolean isTimeUp = DefaultMessageStore.this.isTimeToDelete(); boolean isUsageExceedsThreshold = this.isSpaceToDelete(); boolean isManualDelete = this.manualDeleteFileSeveralTimes.get() > 0; if (isTimeUp || isUsageExceedsThreshold || isManualDelete) { if (isManualDelete) { this.manualDeleteFileSeveralTimes.decrementAndGet(); } boolean cleanAtOnce = DefaultMessageStore.this.getMessageStoreConfig().isCleanFileForciblyEnable() && this.cleanImmediately; LOGGER.info("begin to delete before {} hours file. isTimeUp: {} isUsageExceedsThreshold: {} manualDeleteFileSeveralTimes: {} cleanAtOnce: {} deleteFileBatchMax: {}", fileReservedTime, isTimeUp, isUsageExceedsThreshold, manualDeleteFileSeveralTimes.get(), cleanAtOnce, deleteFileBatchMax); fileReservedTime *= 60 * 60 * 1000; deleteCount = DefaultMessageStore.this.commitLog.deleteExpiredFile(fileReservedTime, deletePhysicFilesInterval, destroyMappedFileIntervalForcibly, cleanAtOnce, deleteFileBatchMax); if (deleteCount > 0) { // If in the controller mode, we should notify the AutoSwitchHaService to truncateEpochFile if (DefaultMessageStore.this.brokerConfig.isEnableControllerMode()) { if (DefaultMessageStore.this.haService instanceof AutoSwitchHAService) { final long minPhyOffset = getMinPhyOffset(); ((AutoSwitchHAService) DefaultMessageStore.this.haService).truncateEpochFilePrefix(minPhyOffset - 1); } } } else if (isUsageExceedsThreshold) { LOGGER.warn("disk space will be full soon, but delete file failed."); } } } private void reDeleteHangedFile() { int interval = DefaultMessageStore.this.getMessageStoreConfig().getRedeleteHangedFileInterval(); long currentTimestamp = System.currentTimeMillis(); if ((currentTimestamp - this.lastRedeleteTimestamp) > interval) { this.lastRedeleteTimestamp = currentTimestamp; int destroyMappedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly(); if (DefaultMessageStore.this.commitLog.retryDeleteFirstFile(destroyMappedFileIntervalForcibly)) { } } } public String getServiceName() { return DefaultMessageStore.this.brokerConfig.getIdentifier() + CleanCommitLogService.class.getSimpleName(); } private boolean isSpaceToDelete() { cleanImmediately = false; String commitLogStorePath = DefaultMessageStore.this.getStorePathPhysic(); String[] storePaths = commitLogStorePath.trim().split(MixAll.MULTI_PATH_SPLITTER); Set fullStorePath = new HashSet<>(); double minPhysicRatio = 100; String minStorePath = null; for (String storePathPhysic : storePaths) { double physicRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic); if (minPhysicRatio > physicRatio) { minPhysicRatio = physicRatio; minStorePath = storePathPhysic; } if (physicRatio > getDiskSpaceCleanForciblyRatio()) { fullStorePath.add(storePathPhysic); } } DefaultMessageStore.this.commitLog.setFullStorePaths(fullStorePath); if (minPhysicRatio > getDiskSpaceWarningLevelRatio()) { boolean diskFull = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); if (diskFull) { DefaultMessageStore.LOGGER.error("physic disk maybe full soon " + minPhysicRatio + ", so mark disk full, storePathPhysic=" + minStorePath); } cleanImmediately = true; return true; } else if (minPhysicRatio > getDiskSpaceCleanForciblyRatio()) { cleanImmediately = true; return true; } else { boolean diskOK = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); if (!diskOK) { DefaultMessageStore.LOGGER.info("physic disk space OK " + minPhysicRatio + ", so mark disk ok, storePathPhysic=" + minStorePath); } } String storePathLogics = StorePathConfigHelper .getStorePathConsumeQueue(DefaultMessageStore.this.getMessageStoreConfig().getStorePathRootDir()); double logicsRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathLogics); if (logicsRatio > getDiskSpaceWarningLevelRatio()) { boolean diskOK = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); if (diskOK) { DefaultMessageStore.LOGGER.error("logics disk maybe full soon " + logicsRatio + ", so mark disk full"); } cleanImmediately = true; return true; } else if (logicsRatio > getDiskSpaceCleanForciblyRatio()) { cleanImmediately = true; return true; } else { boolean diskOK = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); if (!diskOK) { DefaultMessageStore.LOGGER.info("logics disk space OK " + logicsRatio + ", so mark disk ok"); } } double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0; int replicasPerPartition = DefaultMessageStore.this.getMessageStoreConfig().getReplicasPerDiskPartition(); // Only one commitLog in node if (replicasPerPartition <= 1) { if (minPhysicRatio < 0 || minPhysicRatio > ratio) { DefaultMessageStore.LOGGER.info("commitLog disk maybe full soon, so reclaim space, " + minPhysicRatio); return true; } if (logicsRatio < 0 || logicsRatio > ratio) { DefaultMessageStore.LOGGER.info("consumeQueue disk maybe full soon, so reclaim space, " + logicsRatio); return true; } return false; } else { long majorFileSize = DefaultMessageStore.this.getMajorFileSize(); long partitionLogicalSize = UtilAll.getDiskPartitionTotalSpace(minStorePath) / replicasPerPartition; double logicalRatio = 1.0 * majorFileSize / partitionLogicalSize; if (logicalRatio > DefaultMessageStore.this.getMessageStoreConfig().getLogicalDiskSpaceCleanForciblyThreshold()) { // if logical ratio exceeds 0.80, then clean immediately DefaultMessageStore.LOGGER.info("Logical disk usage {} exceeds logical disk space clean forcibly threshold {}, forcibly: {}", logicalRatio, minPhysicRatio, cleanImmediately); cleanImmediately = true; return true; } boolean isUsageExceedsThreshold = logicalRatio > ratio; if (isUsageExceedsThreshold) { DefaultMessageStore.LOGGER.info("Logical disk usage {} exceeds clean threshold {}, forcibly: {}", logicalRatio, ratio, cleanImmediately); } return isUsageExceedsThreshold; } } public int getManualDeleteFileSeveralTimes() { return manualDeleteFileSeveralTimes.get(); } public void setManualDeleteFileSeveralTimes(int manualDeleteFileSeveralTimes) { this.manualDeleteFileSeveralTimes.set(manualDeleteFileSeveralTimes); } public double calcStorePathPhysicRatio() { Set fullStorePath = new HashSet<>(); String storePath = getStorePathPhysic(); String[] paths = storePath.trim().split(MixAll.MULTI_PATH_SPLITTER); double minPhysicRatio = 100; for (String path : paths) { double physicRatio = UtilAll.isPathExists(path) ? UtilAll.getDiskPartitionSpaceUsedPercent(path) : -1; minPhysicRatio = Math.min(minPhysicRatio, physicRatio); if (physicRatio > getDiskSpaceCleanForciblyRatio()) { fullStorePath.add(path); } } DefaultMessageStore.this.commitLog.setFullStorePaths(fullStorePath); return minPhysicRatio; } public boolean isSpaceFull() { double physicRatio = calcStorePathPhysicRatio(); double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0; if (physicRatio > ratio) { DefaultMessageStore.LOGGER.info("physic disk of commitLog used: " + physicRatio); } if (physicRatio > this.getDiskSpaceWarningLevelRatio()) { boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); if (diskok) { DefaultMessageStore.LOGGER.error("physic disk of commitLog maybe full soon, used " + physicRatio + ", so mark disk full"); } return true; } else { boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); if (!diskok) { DefaultMessageStore.LOGGER.info("physic disk space of commitLog OK " + physicRatio + ", so mark disk ok"); } return false; } } } static class BatchDispatchRequest { private final ByteBuffer byteBuffer; private final int position; private final int size; private final long id; public BatchDispatchRequest(ByteBuffer byteBuffer, int position, int size, long id) { this.byteBuffer = byteBuffer; this.position = position; this.size = size; this.id = id; } } static class DispatchRequestOrderlyQueue { DispatchRequest[][] buffer; long ptr = 0; AtomicLong maxPtr = new AtomicLong(); public DispatchRequestOrderlyQueue(int bufferNum) { this.buffer = new DispatchRequest[bufferNum][]; } public void put(long index, DispatchRequest[] dispatchRequests) { while (ptr + this.buffer.length <= index) { synchronized (this) { try { this.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } int mod = (int) (index % this.buffer.length); this.buffer[mod] = dispatchRequests; maxPtr.incrementAndGet(); } public DispatchRequest[] get(List dispatchRequestsList) { synchronized (this) { for (int i = 0; i < this.buffer.length; i++) { int mod = (int) (ptr % this.buffer.length); DispatchRequest[] ret = this.buffer[mod]; if (ret == null) { this.notifyAll(); return null; } dispatchRequestsList.add(ret); this.buffer[mod] = null; ptr++; } } return null; } public synchronized boolean isEmpty() { return maxPtr.get() == ptr; } } @Override public void notifyMessageArriveIfNecessary(DispatchRequest dispatchRequest) { if (DefaultMessageStore.this.brokerConfig.isLongPollingEnable() && DefaultMessageStore.this.messageArrivingListener != null) { DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(), dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1, dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(), dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap()); DefaultMessageStore.this.reputMessageService.notifyMessageArrive4MultiQueue(dispatchRequest); } } class ReputMessageService extends ServiceThread { protected volatile long reputFromOffset = 0; protected volatile long currentReputTimestamp = System.currentTimeMillis(); public long getReputFromOffset() { return reputFromOffset; } public void setReputFromOffset(long reputFromOffset) { this.reputFromOffset = reputFromOffset; } public long getCurrentReputTimestamp() { return currentReputTimestamp; } @Override public void shutdown() { for (int i = 0; i < 50 && this.isCommitLogAvailable(); i++) { try { Thread.sleep(100); } catch (InterruptedException ignored) { } } if (this.isCommitLogAvailable()) { LOGGER.warn("shutdown ReputMessageService, but CommitLog have not finish to be dispatched, CommitLog max" + " offset={}, reputFromOffset={}", DefaultMessageStore.this.commitLog.getMaxOffset(), this.reputFromOffset); } super.shutdown(); } public long behind() { return DefaultMessageStore.this.getConfirmOffset() - this.reputFromOffset; } public long behindMs() { long lastCommitLogFileTimeStamp = System.currentTimeMillis(); MappedFile lastMappedFile = DefaultMessageStore.this.commitLog.getMappedFileQueue().getLastMappedFile(); if (lastMappedFile != null) { lastCommitLogFileTimeStamp = lastMappedFile.getStoreTimestamp(); } return Math.max(0, lastCommitLogFileTimeStamp - this.currentReputTimestamp); } public boolean isCommitLogAvailable() { return this.reputFromOffset < getReputEndOffset(); } protected long getReputEndOffset() { return DefaultMessageStore.this.getMessageStoreConfig().isReadUnCommitted() ? DefaultMessageStore.this.commitLog.getMaxOffset() : DefaultMessageStore.this.commitLog.getConfirmOffset(); } public void doReput() { if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) { LOGGER.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.", this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset()); this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset(); } boolean isCommitLogAvailable = isCommitLogAvailable(); if (!isCommitLogAvailable) { currentReputTimestamp = System.currentTimeMillis(); } for (boolean doNext = true; isCommitLogAvailable() && doNext; ) { SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset); if (result == null) { break; } try { this.reputFromOffset = result.getStartOffset(); for (int readSize = 0; readSize < result.getSize() && reputFromOffset < getReputEndOffset() && doNext; ) { DispatchRequest dispatchRequest = DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false, false); int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize(); if (reputFromOffset + size > getReputEndOffset()) { doNext = false; break; } if (dispatchRequest.isSuccess()) { if (size > 0) { currentReputTimestamp = dispatchRequest.getStoreTimestamp(); DefaultMessageStore.this.doDispatch(dispatchRequest); if (!notifyMessageArriveInBatch) { notifyMessageArriveIfNecessary(dispatchRequest); } this.reputFromOffset += size; readSize += size; if (!DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable() && DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { DefaultMessageStore.this.storeStatsService .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).add(dispatchRequest.getBatchSize()); DefaultMessageStore.this.storeStatsService .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic()) .add(dispatchRequest.getMsgSize()); } } else if (size == 0) { this.reputFromOffset = DefaultMessageStore.this.commitLog.rollNextFile(this.reputFromOffset); readSize = result.getSize(); } } else { if (size > 0) { LOGGER.error("[BUG]read total count not equals msg total size. reputFromOffset={}", reputFromOffset); this.reputFromOffset += size; } else { doNext = false; // If user open the dledger pattern or the broker is master node, // it will not ignore the exception and fix the reputFromOffset variable if (DefaultMessageStore.this.getMessageStoreConfig().isEnableDLegerCommitLog() || DefaultMessageStore.this.brokerConfig.getBrokerId() == MixAll.MASTER_ID) { LOGGER.error("[BUG]dispatch message to consume queue error, COMMITLOG OFFSET: {}", this.reputFromOffset); this.reputFromOffset += result.getSize() - readSize; } } } } } catch (RocksDBException e) { ERROR_LOG.info("dispatch message to cq exception. reputFromOffset: {}", this.reputFromOffset, e); return; } finally { result.release(); } } } private void notifyMessageArrive4MultiQueue(DispatchRequest dispatchRequest) { Map prop = dispatchRequest.getPropertiesMap(); if (prop == null || dispatchRequest.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { return; } String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String multiQueueOffset = prop.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET); if (StringUtils.isBlank(multiDispatchQueue) || StringUtils.isBlank(multiQueueOffset)) { return; } String[] queues = multiDispatchQueue.split(MixAll.LMQ_DISPATCH_SEPARATOR); String[] queueOffsets = multiQueueOffset.split(MixAll.LMQ_DISPATCH_SEPARATOR); if (queues.length != queueOffsets.length) { return; } for (int i = 0; i < queues.length; i++) { String queueName = queues[i]; long queueOffset = Long.parseLong(queueOffsets[i]); int queueId = dispatchRequest.getQueueId(); if (DefaultMessageStore.this.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { queueId = MixAll.LMQ_QUEUE_ID; } DefaultMessageStore.this.messageArrivingListener.arriving( queueName, queueId, queueOffset + 1, dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(), dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap()); } } @Override public void run() { DefaultMessageStore.LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { TimeUnit.MILLISECONDS.sleep(1); this.doReput(); } catch (Throwable e) { DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } DefaultMessageStore.LOGGER.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (DefaultMessageStore.this.getBrokerConfig().isInBrokerContainer()) { return DefaultMessageStore.this.getBrokerIdentity().getIdentifier() + ReputMessageService.class.getSimpleName(); } return ReputMessageService.class.getSimpleName(); } } class MainBatchDispatchRequestService extends ServiceThread { private final ExecutorService batchDispatchRequestExecutor; public MainBatchDispatchRequestService() { batchDispatchRequestExecutor = ThreadUtils.newThreadPoolExecutor( DefaultMessageStore.this.getMessageStoreConfig().getBatchDispatchRequestThreadPoolNums(), DefaultMessageStore.this.getMessageStoreConfig().getBatchDispatchRequestThreadPoolNums(), 1000 * 60, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(4096), new ThreadFactoryImpl("BatchDispatchRequestServiceThread_"), new ThreadPoolExecutor.AbortPolicy()); } private void pollBatchDispatchRequest() { try { if (!batchDispatchRequestQueue.isEmpty()) { BatchDispatchRequest task = batchDispatchRequestQueue.peek(); batchDispatchRequestExecutor.execute(() -> { try { ByteBuffer tmpByteBuffer = task.byteBuffer; tmpByteBuffer.position(task.position); tmpByteBuffer.limit(task.position + task.size); List dispatchRequestList = new ArrayList<>(); while (tmpByteBuffer.hasRemaining()) { DispatchRequest dispatchRequest = DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(tmpByteBuffer, false, false, false); if (dispatchRequest.isSuccess()) { dispatchRequestList.add(dispatchRequest); } else { LOGGER.error("[BUG]read total count not equals msg total size."); } } dispatchRequestOrderlyQueue.put(task.id, dispatchRequestList.toArray(new DispatchRequest[dispatchRequestList.size()])); mappedPageHoldCount.getAndDecrement(); } catch (Exception e) { LOGGER.error("There is an exception in task execution.", e); } }); batchDispatchRequestQueue.poll(); } } catch (Exception e) { DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } @Override public void run() { DefaultMessageStore.LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { TimeUnit.MILLISECONDS.sleep(1); pollBatchDispatchRequest(); } catch (Exception e) { DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } DefaultMessageStore.LOGGER.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (DefaultMessageStore.this.getBrokerConfig().isInBrokerContainer()) { return DefaultMessageStore.this.getBrokerIdentity().getIdentifier() + MainBatchDispatchRequestService.class.getSimpleName(); } return MainBatchDispatchRequestService.class.getSimpleName(); } } class DispatchService extends ServiceThread { private final List dispatchRequestsList = new ArrayList<>(); // dispatchRequestsList:[ // {dispatchRequests:[{dispatchRequest}, {dispatchRequest}]}, // {dispatchRequests:[{dispatchRequest}, {dispatchRequest}]}] private void dispatch() throws Exception { dispatchRequestsList.clear(); dispatchRequestOrderlyQueue.get(dispatchRequestsList); if (!dispatchRequestsList.isEmpty()) { for (DispatchRequest[] dispatchRequests : dispatchRequestsList) { for (DispatchRequest dispatchRequest : dispatchRequests) { DefaultMessageStore.this.doDispatch(dispatchRequest); // wake up long-polling DefaultMessageStore.this.notifyMessageArriveIfNecessary(dispatchRequest); if (!DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable() && DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { DefaultMessageStore.this.storeStatsService .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).add(1); DefaultMessageStore.this.storeStatsService .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic()) .add(dispatchRequest.getMsgSize()); } } } } } @Override public void run() { DefaultMessageStore.LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { TimeUnit.MILLISECONDS.sleep(1); dispatch(); } catch (Exception e) { DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } DefaultMessageStore.LOGGER.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (DefaultMessageStore.this.getBrokerConfig().isInBrokerContainer()) { return DefaultMessageStore.this.getBrokerIdentity().getIdentifier() + DispatchService.class.getSimpleName(); } return DispatchService.class.getSimpleName(); } } class ConcurrentReputMessageService extends ReputMessageService { private static final int BATCH_SIZE = 1024 * 1024 * 4; private long batchId = 0; private MainBatchDispatchRequestService mainBatchDispatchRequestService; private DispatchService dispatchService; public ConcurrentReputMessageService() { super(); this.mainBatchDispatchRequestService = new MainBatchDispatchRequestService(); this.dispatchService = new DispatchService(); } public void createBatchDispatchRequest(ByteBuffer byteBuffer, int position, int size) { if (position < 0) { return; } mappedPageHoldCount.getAndIncrement(); BatchDispatchRequest task = new BatchDispatchRequest(byteBuffer.duplicate(), position, size, batchId++); batchDispatchRequestQueue.offer(task); } @Override public void start() { super.start(); this.mainBatchDispatchRequestService.start(); this.dispatchService.start(); } @Override public void doReput() { if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) { LOGGER.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.", this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset()); this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset(); } for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) { SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset); if (result == null) { break; } int batchDispatchRequestStart = -1; int batchDispatchRequestSize = -1; try { this.reputFromOffset = result.getStartOffset(); for (int readSize = 0; readSize < result.getSize() && reputFromOffset < getReputEndOffset() && doNext; ) { ByteBuffer byteBuffer = result.getByteBuffer(); int totalSize = preCheckMessageAndReturnSize(byteBuffer); if (totalSize > 0) { if (batchDispatchRequestStart == -1) { batchDispatchRequestStart = byteBuffer.position(); batchDispatchRequestSize = 0; } batchDispatchRequestSize += totalSize; if (batchDispatchRequestSize > BATCH_SIZE) { this.createBatchDispatchRequest(byteBuffer, batchDispatchRequestStart, batchDispatchRequestSize); batchDispatchRequestStart = -1; batchDispatchRequestSize = -1; } byteBuffer.position(byteBuffer.position() + totalSize); this.reputFromOffset += totalSize; readSize += totalSize; } else { doNext = false; if (totalSize == 0) { this.reputFromOffset = DefaultMessageStore.this.commitLog.rollNextFile(this.reputFromOffset); } this.createBatchDispatchRequest(byteBuffer, batchDispatchRequestStart, batchDispatchRequestSize); batchDispatchRequestStart = -1; batchDispatchRequestSize = -1; } } } finally { this.createBatchDispatchRequest(result.getByteBuffer(), batchDispatchRequestStart, batchDispatchRequestSize); boolean over = mappedPageHoldCount.get() == 0; while (!over) { try { TimeUnit.MILLISECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } over = mappedPageHoldCount.get() == 0; } result.release(); } } } /** * pre-check the message and returns the message size * * @return 0 Come to the end of file // >0 Normal messages // -1 Message checksum failure */ public int preCheckMessageAndReturnSize(ByteBuffer byteBuffer) { byteBuffer.mark(); int totalSize = byteBuffer.getInt(); if (reputFromOffset + totalSize > DefaultMessageStore.this.getConfirmOffset()) { return -1; } int magicCode = byteBuffer.getInt(); switch (magicCode) { case MessageDecoder.MESSAGE_MAGIC_CODE: case MessageDecoder.MESSAGE_MAGIC_CODE_V2: break; case MessageDecoder.BLANK_MAGIC_CODE: return 0; default: return -1; } byteBuffer.reset(); return totalSize; } @Override public void shutdown() { for (int i = 0; i < 50 && this.isCommitLogAvailable(); i++) { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException ignored) { } } if (this.isCommitLogAvailable()) { LOGGER.warn("shutdown concurrentReputMessageService, but CommitLog have not finish to be dispatched, CommitLog max" + " offset={}, reputFromOffset={}", DefaultMessageStore.this.commitLog.getMaxOffset(), this.reputFromOffset); } this.mainBatchDispatchRequestService.shutdown(); this.dispatchService.shutdown(); super.shutdown(); } @Override public String getServiceName() { if (DefaultMessageStore.this.getBrokerConfig().isInBrokerContainer()) { return DefaultMessageStore.this.getBrokerIdentity().getIdentifier() + ConcurrentReputMessageService.class.getSimpleName(); } return ConcurrentReputMessageService.class.getSimpleName(); } } @Override public HARuntimeInfo getHARuntimeInfo() { if (haService != null) { return this.haService.getRuntimeInfo(this.commitLog.getMaxOffset()); } else { return null; } } public int getMaxDelayLevel() { return maxDelayLevel; } public long computeDeliverTimestamp(final int delayLevel, final long storeTimestamp) { Long time = this.delayLevelTable.get(delayLevel); if (time != null) { return time + storeTimestamp; } return storeTimestamp + 1000; } public List getPutMessageHookList() { return putMessageHookList; } @Override public void setSendMessageBackHook(SendMessageBackHook sendMessageBackHook) { this.sendMessageBackHook = sendMessageBackHook; } @Override public SendMessageBackHook getSendMessageBackHook() { return sendMessageBackHook; } @Override public boolean isShutdown() { return shutdown; } @Override public long estimateMessageCount(String topic, int queueId, long from, long to, MessageFilter filter) { if (from < 0) { from = 0; } if (from >= to) { return 0; } if (null == filter) { return to - from; } ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (null == consumeQueue) { return 0; } // correct the "from" argument to min offset in queue if it is too small long minOffset = consumeQueue.getMinOffsetInQueue(); if (from < minOffset) { long diff = to - from; from = minOffset; to = from + diff; } long msgCount = consumeQueue.estimateMessageCount(from, to, filter); return msgCount == -1 ? to - from : msgCount; } @Override public List> getMetricsView() { return this.defaultStoreMetricsManager.getMetricsView(); } @Override public void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { this.defaultStoreMetricsManager.init(meter, attributesBuilderSupplier, this); } /** * Enable transient commitLog store pool only if transientStorePoolEnable is true and broker role is not SLAVE or * enableControllerMode is true * * @return true or false */ public boolean isTransientStorePoolEnable() { return this.messageStoreConfig.isTransientStorePoolEnable() && (this.brokerConfig.isEnableControllerMode() || this.messageStoreConfig.getBrokerRole() != BrokerRole.SLAVE) && !messageStoreConfig.isWriteWithoutMmap(); } public long getReputFromOffset() { return this.reputMessageService.getReputFromOffset(); } public CompactionStore getCompactionStore() { return compactionStore; } public IndexService getIndexService() { return indexService; } public ScheduledExecutorService getScheduledCleanQueueExecutorService() { return scheduledCleanQueueExecutorService; } public void destroyConsumeQueueStore(boolean loadAfterDestroy) { consumeQueueStore.destroy(loadAfterDestroy); } public MessageStoreStateMachine getStateMachine() { return stateMachine; } @Override public MessageRocksDBStorage getMessageRocksDBStorage() { return this.messageRocksDBStorage; } public boolean isNotifyMessageArriveInBatch() { return notifyMessageArriveInBatch; } public void setNotifyMessageArriveInBatch(boolean notifyMessageArriveInBatch) { this.notifyMessageArriveInBatch = notifyMessageArriveInBatch; } public DefaultStoreMetricsManager getDefaultStoreMetricsManager() { return defaultStoreMetricsManager; } @Override public StoreMetricsManager getStoreMetricsManager() { return defaultStoreMetricsManager; } public IndexRocksDBStore getIndexRocksDBStore() { return indexRocksDBStore; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/DispatchRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; public class DispatchRequest { private final String topic; private final int queueId; private final long commitLogOffset; private int msgSize; private final long tagsCode; private final long storeTimestamp; private final long consumeQueueOffset; private final String keys; private final boolean success; private final String uniqKey; private final int sysFlag; private final long preparedTransactionOffset; private final Map propertiesMap; private byte[] bitMap; private int bufferSize = -1;//the buffer size maybe larger than the msg size if the message is wrapped by something // for batch consume queue private long msgBaseOffset = -1; private short batchSize = 1; private long nextReputFromOffset = -1; private String offsetId; public DispatchRequest( final String topic, final int queueId, final long commitLogOffset, final int msgSize, final long tagsCode, final long storeTimestamp, final long consumeQueueOffset, final String keys, final String uniqKey, final int sysFlag, final long preparedTransactionOffset, final Map propertiesMap ) { this.topic = topic; this.queueId = queueId; this.commitLogOffset = commitLogOffset; this.msgSize = msgSize; this.tagsCode = tagsCode; this.storeTimestamp = storeTimestamp; this.consumeQueueOffset = consumeQueueOffset; this.msgBaseOffset = consumeQueueOffset; this.keys = keys; this.uniqKey = uniqKey; this.sysFlag = sysFlag; this.preparedTransactionOffset = preparedTransactionOffset; this.success = true; this.propertiesMap = propertiesMap; } public DispatchRequest(String topic, int queueId, long consumeQueueOffset, long commitLogOffset, int size, long tagsCode) { this.topic = topic; this.queueId = queueId; this.commitLogOffset = commitLogOffset; this.msgSize = size; this.tagsCode = tagsCode; this.storeTimestamp = 0; this.consumeQueueOffset = consumeQueueOffset; this.keys = ""; this.uniqKey = null; this.sysFlag = 0; this.preparedTransactionOffset = 0; this.success = false; this.propertiesMap = null; } public DispatchRequest(int size) { this.topic = ""; this.queueId = 0; this.commitLogOffset = 0; this.msgSize = size; this.tagsCode = 0; this.storeTimestamp = 0; this.consumeQueueOffset = 0; this.keys = ""; this.uniqKey = null; this.sysFlag = 0; this.preparedTransactionOffset = 0; this.success = false; this.propertiesMap = null; } public DispatchRequest(int size, boolean success) { this.topic = ""; this.queueId = 0; this.commitLogOffset = 0; this.msgSize = size; this.tagsCode = 0; this.storeTimestamp = 0; this.consumeQueueOffset = 0; this.keys = ""; this.uniqKey = null; this.sysFlag = 0; this.preparedTransactionOffset = 0; this.success = success; this.propertiesMap = null; } public String getTopic() { return topic; } public int getQueueId() { return queueId; } public long getCommitLogOffset() { return commitLogOffset; } public int getMsgSize() { return msgSize; } public long getStoreTimestamp() { return storeTimestamp; } public long getConsumeQueueOffset() { return consumeQueueOffset; } public String getKeys() { return keys; } public long getTagsCode() { return tagsCode; } public int getSysFlag() { return sysFlag; } public long getPreparedTransactionOffset() { return preparedTransactionOffset; } public boolean isSuccess() { return success; } public String getUniqKey() { return uniqKey; } public Map getPropertiesMap() { return propertiesMap; } public byte[] getBitMap() { return bitMap; } public void setBitMap(byte[] bitMap) { this.bitMap = bitMap; } public short getBatchSize() { return batchSize; } public void setBatchSize(short batchSize) { this.batchSize = batchSize; } public void setMsgSize(int msgSize) { this.msgSize = msgSize; } public long getMsgBaseOffset() { return msgBaseOffset; } public void setMsgBaseOffset(long msgBaseOffset) { this.msgBaseOffset = msgBaseOffset; } public int getBufferSize() { return bufferSize; } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } public long getNextReputFromOffset() { return nextReputFromOffset; } public void setNextReputFromOffset(long nextReputFromOffset) { this.nextReputFromOffset = nextReputFromOffset; } public String getOffsetId() { return offsetId; } public void setOffsetId(String offsetId) { this.offsetId = offsetId; } public boolean containsLMQ() { if (!MixAll.topicAllowsLMQ(topic)) { return false; } if (null == propertiesMap || propertiesMap.isEmpty()) { return false; } String lmqNames = propertiesMap.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String lmqOffsets = propertiesMap.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET); return !StringUtils.isBlank(lmqNames) && !StringUtils.isBlank(lmqOffsets); } @Override public String toString() { return "DispatchRequest{" + "topic='" + topic + '\'' + ", queueId=" + queueId + ", commitLogOffset=" + commitLogOffset + ", msgSize=" + msgSize + ", success=" + success + ", msgBaseOffset=" + msgBaseOffset + ", batchSize=" + batchSize + ", nextReputFromOffset=" + nextReputFromOffset + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/FileQueueSnapshot.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.store.logfile.MappedFile; public class FileQueueSnapshot { private MappedFile firstFile; private long firstFileIndex; private MappedFile lastFile; private long lastFileIndex; private long currentFile; private long currentFileIndex; private long behindCount; private boolean exist; public FileQueueSnapshot() { } public FileQueueSnapshot(MappedFile firstFile, long firstFileIndex, MappedFile lastFile, long lastFileIndex, long currentFile, long currentFileIndex, long behindCount, boolean exist) { this.firstFile = firstFile; this.firstFileIndex = firstFileIndex; this.lastFile = lastFile; this.lastFileIndex = lastFileIndex; this.currentFile = currentFile; this.currentFileIndex = currentFileIndex; this.behindCount = behindCount; this.exist = exist; } public MappedFile getFirstFile() { return firstFile; } public long getFirstFileIndex() { return firstFileIndex; } public MappedFile getLastFile() { return lastFile; } public long getLastFileIndex() { return lastFileIndex; } public long getCurrentFile() { return currentFile; } public long getCurrentFileIndex() { return currentFileIndex; } public long getBehindCount() { return behindCount; } public boolean isExist() { return exist; } @Override public String toString() { return "FileQueueSnapshot{" + "firstFile=" + firstFile + ", firstFileIndex=" + firstFileIndex + ", lastFile=" + lastFile + ", lastFileIndex=" + lastFileIndex + ", currentFile=" + currentFile + ", currentFileIndex=" + currentFileIndex + ", behindCount=" + behindCount + ", exist=" + exist + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/FlushDiskWatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.concurrent.LinkedBlockingQueue; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.CommitLog.GroupCommitRequest; public class FlushDiskWatcher extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final LinkedBlockingQueue commitRequests = new LinkedBlockingQueue<>(); @Override public String getServiceName() { return FlushDiskWatcher.class.getSimpleName(); } @Override public void run() { while (!isStopped()) { GroupCommitRequest request = null; try { request = commitRequests.take(); } catch (InterruptedException e) { log.warn("take flush disk commit request, but interrupted, this may caused by shutdown"); continue; } while (!request.future().isDone()) { long now = System.nanoTime(); if (now - request.getDeadLine() >= 0) { request.wakeupCustomer(PutMessageStatus.FLUSH_DISK_TIMEOUT); break; } // To avoid frequent thread switching, replace future.get with sleep here, long sleepTime = (request.getDeadLine() - now) / 1_000_000; sleepTime = Math.min(10, sleepTime); if (sleepTime == 0) { request.wakeupCustomer(PutMessageStatus.FLUSH_DISK_TIMEOUT); break; } try { Thread.sleep(sleepTime); } catch (InterruptedException e) { log.warn( "An exception occurred while waiting for flushing disk to complete. this may caused by shutdown"); break; } } } } public void add(GroupCommitRequest request) { commitRequests.add(request); } public int queueSize() { return commitRequests.size(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/FlushManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageExt; public interface FlushManager { void start(); void shutdown(); void wakeUpFlush(); void wakeUpCommit(); void handleDiskFlush(AppendMessageResult result, PutMessageResult putMessageResult, MessageExt messageExt); CompletableFuture handleDiskFlush(AppendMessageResult result, MessageExt messageExt); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class GetMessageResult { private final List messageMapedList; private final List messageBufferList; private final List messageQueueOffset; private GetMessageStatus status; private long nextBeginOffset; private long minOffset; private long maxOffset; private int bufferTotalSize = 0; private int messageCount = 0; private boolean suggestPullingFromSlave = false; private int msgCount4Commercial = 0; private int commercialSizePerMsg = 4 * 1024; private long coldDataSum = 0L; private int filterMessageCount; public static final GetMessageResult NO_MATCH_LOGIC_QUEUE = new GetMessageResult(GetMessageStatus.NO_MATCHED_LOGIC_QUEUE, 0, 0, 0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); public GetMessageResult() { messageMapedList = new ArrayList<>(100); messageBufferList = new ArrayList<>(100); messageQueueOffset = new ArrayList<>(100); } public GetMessageResult(int resultSize) { messageMapedList = new ArrayList<>(resultSize); messageBufferList = new ArrayList<>(resultSize); messageQueueOffset = new ArrayList<>(resultSize); } private GetMessageResult(GetMessageStatus status, long nextBeginOffset, long minOffset, long maxOffset, List messageMapedList, List messageBufferList, List messageQueueOffset) { this.status = status; this.nextBeginOffset = nextBeginOffset; this.minOffset = minOffset; this.maxOffset = maxOffset; this.messageMapedList = messageMapedList; this.messageBufferList = messageBufferList; this.messageQueueOffset = messageQueueOffset; } public GetMessageStatus getStatus() { return status; } public void setStatus(GetMessageStatus status) { this.status = status; } public long getNextBeginOffset() { return nextBeginOffset; } public void setNextBeginOffset(long nextBeginOffset) { this.nextBeginOffset = nextBeginOffset; } public long getMinOffset() { return minOffset; } public void setMinOffset(long minOffset) { this.minOffset = minOffset; } public long getMaxOffset() { return maxOffset; } public void setMaxOffset(long maxOffset) { this.maxOffset = maxOffset; } public List getMessageMapedList() { return messageMapedList; } public List getMessageBufferList() { return messageBufferList; } public void addMessage(final SelectMappedBufferResult mapedBuffer) { this.messageMapedList.add(mapedBuffer); this.messageBufferList.add(mapedBuffer.getByteBuffer()); this.bufferTotalSize += mapedBuffer.getSize(); this.msgCount4Commercial += (int) Math.ceil( mapedBuffer.getSize() / (double)commercialSizePerMsg); this.messageCount++; } public void addMessage(final SelectMappedBufferResult mapedBuffer, final long queueOffset) { this.messageMapedList.add(mapedBuffer); this.messageBufferList.add(mapedBuffer.getByteBuffer()); this.bufferTotalSize += mapedBuffer.getSize(); this.msgCount4Commercial += (int) Math.ceil( mapedBuffer.getSize() / (double)commercialSizePerMsg); this.messageCount++; this.messageQueueOffset.add(queueOffset); } public void addMessage(final SelectMappedBufferResult mapedBuffer, final long queueOffset, final int batchNum) { addMessage(mapedBuffer, queueOffset); messageCount += batchNum - 1; } public void release() { for (SelectMappedBufferResult select : this.messageMapedList) { select.release(); } } public int getBufferTotalSize() { return bufferTotalSize; } public int getMessageCount() { return messageCount; } public boolean isSuggestPullingFromSlave() { return suggestPullingFromSlave; } public void setSuggestPullingFromSlave(boolean suggestPullingFromSlave) { this.suggestPullingFromSlave = suggestPullingFromSlave; } public int getMsgCount4Commercial() { return msgCount4Commercial; } public void setMsgCount4Commercial(int msgCount4Commercial) { this.msgCount4Commercial = msgCount4Commercial; } public List getMessageQueueOffset() { return messageQueueOffset; } public long getColdDataSum() { return coldDataSum; } public void setColdDataSum(long coldDataSum) { this.coldDataSum = coldDataSum; } public int getFilterMessageCount() { return filterMessageCount; } public void setFilterMessageCount(int filterMessageCount) { this.filterMessageCount = filterMessageCount; } @Override public String toString() { return "GetMessageResult [status=" + status + ", nextBeginOffset=" + nextBeginOffset + ", minOffset=" + minOffset + ", maxOffset=" + maxOffset + ", bufferTotalSize=" + bufferTotalSize + ", messageCount=" + messageCount + ", filterMessageCount=" + filterMessageCount + ", suggestPullingFromSlave=" + suggestPullingFromSlave + "]"; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/GetMessageStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; public enum GetMessageStatus { FOUND, NO_MATCHED_MESSAGE, MESSAGE_WAS_REMOVING, OFFSET_FOUND_NULL, OFFSET_OVERFLOW_BADLY, OFFSET_OVERFLOW_ONE, OFFSET_TOO_SMALL, NO_MATCHED_LOGIC_QUEUE, NO_MESSAGE_IN_QUEUE, OFFSET_RESET } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/LmqDispatch.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.exception.ConsumeQueueException; public class LmqDispatch { private static final short VALUE_OF_EACH_INCREMENT = 1; public static void wrapLmqDispatch(MessageStore messageStore, final MessageExtBrokerInner msg) throws ConsumeQueueException { String lmqNames = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String[] queueNames = lmqNames.split(MixAll.LMQ_DISPATCH_SEPARATOR); Long[] queueOffsets = new Long[queueNames.length]; if (messageStore.getMessageStoreConfig().isEnableLmq()) { for (int i = 0; i < queueNames.length; i++) { if (MixAll.isLmq(queueNames[i])) { queueOffsets[i] = messageStore.getQueueStore().getLmqQueueOffset(queueNames[i], MixAll.LMQ_QUEUE_ID); } } } MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, StringUtils.join(queueOffsets, MixAll.LMQ_DISPATCH_SEPARATOR)); msg.removeWaitStorePropertyString(); } public static void updateLmqOffsets(MessageStore messageStore, final MessageExtBrokerInner msgInner) throws ConsumeQueueException { String lmqNames = msgInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String[] queueNames = lmqNames.split(MixAll.LMQ_DISPATCH_SEPARATOR); for (String queueName : queueNames) { if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { messageStore.getQueueStore().increaseLmqOffset(queueName, MixAll.LMQ_QUEUE_ID, VALUE_OF_EACH_INCREMENT); } } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import com.google.common.collect.Lists; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Stream; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; public class MappedFileQueue implements Swappable { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger LOG_ERROR = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); protected final String storePath; protected final int mappedFileSize; protected final CopyOnWriteArrayList mappedFiles = new CopyOnWriteArrayList<>(); protected final AllocateMappedFileService allocateMappedFileService; protected long flushedWhere = 0; protected long committedWhere = 0; protected volatile long storeTimestamp = 0; protected RunningFlags runningFlags; /** * Configuration flag to use RandomAccessFile instead of MappedByteBuffer for writing */ protected boolean writeWithoutMmap = false; public MappedFileQueue(final String storePath, int mappedFileSize, AllocateMappedFileService allocateMappedFileService) { this(storePath, mappedFileSize, allocateMappedFileService, null, false); } public MappedFileQueue(final String storePath, int mappedFileSize, AllocateMappedFileService allocateMappedFileService, RunningFlags runningFlags) { this(storePath, mappedFileSize, allocateMappedFileService, runningFlags, false); } public MappedFileQueue(final String storePath, int mappedFileSize, AllocateMappedFileService allocateMappedFileService, boolean writeWithoutMmap) { this(storePath, mappedFileSize, allocateMappedFileService, null, writeWithoutMmap); } public MappedFileQueue(final String storePath, int mappedFileSize, AllocateMappedFileService allocateMappedFileService, RunningFlags runningFlags, boolean writeWithoutMmap) { this.storePath = storePath; this.mappedFileSize = mappedFileSize; this.allocateMappedFileService = allocateMappedFileService; this.runningFlags = runningFlags; this.writeWithoutMmap = writeWithoutMmap; } public void checkSelf() { List mappedFiles = new ArrayList<>(this.mappedFiles); if (!mappedFiles.isEmpty()) { Iterator iterator = mappedFiles.iterator(); MappedFile pre = null; while (iterator.hasNext()) { MappedFile cur = iterator.next(); if (pre != null) { if (cur.getFileFromOffset() - pre.getFileFromOffset() != this.mappedFileSize) { LOG_ERROR.error("[BUG]The mappedFile queue's data is damaged, the adjacent mappedFile's offset don't match. pre file {}, cur file {}", pre.getFileName(), cur.getFileName()); } } pre = cur; } } } public MappedFile getConsumeQueueMappedFileByTime(final long timestamp, CommitLog commitLog, BoundaryType boundaryType) { Object[] mfs = copyMappedFiles(0); if (null == mfs) { return null; } /* * Make sure each mapped file in consume queue has accurate start and stop time in accordance with commit log * mapped files. Note last modified time from file system is not reliable. */ for (int i = mfs.length - 1; i >= 0; i--) { DefaultMappedFile mappedFile = (DefaultMappedFile) mfs[i]; // Figure out the earliest message store time in the consume queue mapped file. if (mappedFile.getStartTimestamp() < 0) { SelectMappedBufferResult selectMappedBufferResult = mappedFile.selectMappedBuffer(0, ConsumeQueue.CQ_STORE_UNIT_SIZE); if (null != selectMappedBufferResult) { try { ByteBuffer buffer = selectMappedBufferResult.getByteBuffer(); long physicalOffset = buffer.getLong(); int messageSize = buffer.getInt(); long messageStoreTime = commitLog.pickupStoreTimestamp(physicalOffset, messageSize); if (messageStoreTime > 0) { mappedFile.setStartTimestamp(messageStoreTime); } } finally { selectMappedBufferResult.release(); } } } // Figure out the latest message store time in the consume queue mapped file. if (i < mfs.length - 1 && mappedFile.getStopTimestamp() < 0) { SelectMappedBufferResult selectMappedBufferResult = mappedFile.selectMappedBuffer(mappedFileSize - ConsumeQueue.CQ_STORE_UNIT_SIZE, ConsumeQueue.CQ_STORE_UNIT_SIZE); if (null != selectMappedBufferResult) { try { ByteBuffer buffer = selectMappedBufferResult.getByteBuffer(); long physicalOffset = buffer.getLong(); int messageSize = buffer.getInt(); long messageStoreTime = commitLog.pickupStoreTimestamp(physicalOffset, messageSize); if (messageStoreTime > 0) { mappedFile.setStopTimestamp(messageStoreTime); } } finally { selectMappedBufferResult.release(); } } } } switch (boundaryType) { case LOWER: { for (int i = 0; i < mfs.length; i++) { DefaultMappedFile mappedFile = (DefaultMappedFile) mfs[i]; if (i < mfs.length - 1) { long stopTimestamp = mappedFile.getStopTimestamp(); if (stopTimestamp >= timestamp) { return mappedFile; } } // Just return the latest one. if (i == mfs.length - 1) { return mappedFile; } } } case UPPER: { for (int i = mfs.length - 1; i >= 0; i--) { DefaultMappedFile mappedFile = (DefaultMappedFile) mfs[i]; if (mappedFile.getStartTimestamp() <= timestamp) { return mappedFile; } } } default: { log.warn("Unknown boundary type"); break; } } return null; } public MappedFile getMappedFileByTime(final long timestamp) { Object[] mfs = this.copyMappedFiles(0); if (null == mfs) return null; for (int i = 0; i < mfs.length; i++) { MappedFile mappedFile = (MappedFile) mfs[i]; if (mappedFile.getLastModifiedTimestamp() >= timestamp) { return mappedFile; } } return (MappedFile) mfs[mfs.length - 1]; } protected Object[] copyMappedFiles(final int reservedMappedFiles) { Object[] mfs; if (this.mappedFiles.size() <= reservedMappedFiles) { return null; } mfs = this.mappedFiles.toArray(); return mfs; } public void truncateDirtyFiles(long offset) { List willRemoveFiles = new ArrayList<>(); for (MappedFile file : this.mappedFiles) { long fileTailOffset = file.getFileFromOffset() + this.mappedFileSize; if (fileTailOffset > offset) { if (offset >= file.getFileFromOffset()) { file.setWrotePosition((int) (offset % this.mappedFileSize)); file.setCommittedPosition((int) (offset % this.mappedFileSize)); file.setFlushedPosition((int) (offset % this.mappedFileSize)); } else { file.destroy(1000); willRemoveFiles.add(file); } } } this.deleteExpiredFile(willRemoveFiles); } void deleteExpiredFile(List files) { if (!files.isEmpty()) { Iterator iterator = files.iterator(); while (iterator.hasNext()) { MappedFile cur = iterator.next(); if (!this.mappedFiles.contains(cur)) { iterator.remove(); log.info("This mappedFile {} is not contained by mappedFiles, so skip it.", cur.getFileName()); } } try { if (!this.mappedFiles.removeAll(files)) { log.error("deleteExpiredFile remove failed."); } } catch (Exception e) { log.error("deleteExpiredFile has exception.", e); } } } public boolean load() { File dir = new File(this.storePath); File[] ls = dir.listFiles(); if (ls != null) { return doLoad(Arrays.asList(ls)); } return true; } public boolean doLoad(List files) { // ascending order files.sort(Comparator.comparing(File::getName)); for (int i = 0; i < files.size(); i++) { File file = files.get(i); if (file.isDirectory()) { continue; } if (file.length() == 0 && i == files.size() - 1) { boolean ok = file.delete(); log.warn("{} size is 0, auto delete. is_ok: {}", file, ok); continue; } if (file.length() != this.mappedFileSize) { log.warn(file + "\t" + file.length() + " length not matched message store config value, please check it manually"); return false; } try { MappedFile mappedFile = new DefaultMappedFile(file.getPath(), mappedFileSize, runningFlags, writeWithoutMmap); mappedFile.setWrotePosition(this.mappedFileSize); mappedFile.setFlushedPosition(this.mappedFileSize); mappedFile.setCommittedPosition(this.mappedFileSize); this.mappedFiles.add(mappedFile); log.info("load " + file.getPath() + " OK"); } catch (IOException e) { log.error("load file " + file + " error", e); return false; } } return true; } public long howMuchFallBehind() { if (this.mappedFiles.isEmpty()) return 0; long committed = this.getFlushedWhere(); if (committed != 0) { MappedFile mappedFile = this.getLastMappedFile(0, false); if (mappedFile != null) { return (mappedFile.getFileFromOffset() + mappedFile.getWrotePosition()) - committed; } } return 0; } public MappedFile getLastMappedFile(final long startOffset, boolean needCreate) { long createOffset = -1; MappedFile mappedFileLast = getLastMappedFile(); if (mappedFileLast == null) { createOffset = startOffset - (startOffset % this.mappedFileSize); } if (mappedFileLast != null && mappedFileLast.isFull()) { createOffset = mappedFileLast.getFileFromOffset() + this.mappedFileSize; } if (createOffset != -1 && needCreate) { return tryCreateMappedFile(createOffset); } return mappedFileLast; } public boolean isMappedFilesEmpty() { return this.mappedFiles.isEmpty(); } public boolean isEmptyOrCurrentFileFull() { MappedFile mappedFileLast = getLastMappedFile(); if (mappedFileLast == null) { return true; } if (mappedFileLast.isFull()) { return true; } return false; } public boolean shouldRoll(final int msgSize) { if (isEmptyOrCurrentFileFull()) { return true; } MappedFile mappedFileLast = getLastMappedFile(); if (mappedFileLast.getWrotePosition() + msgSize > mappedFileLast.getFileSize()) { return true; } return false; } public MappedFile tryCreateMappedFile(long createOffset) { String nextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset); String nextNextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset + this.mappedFileSize); return doCreateMappedFile(nextFilePath, nextNextFilePath); } protected MappedFile doCreateMappedFile(String nextFilePath, String nextNextFilePath) { MappedFile mappedFile = null; if (this.allocateMappedFileService != null) { mappedFile = this.allocateMappedFileService.putRequestAndReturnMappedFile(nextFilePath, nextNextFilePath, this.mappedFileSize); } else { try { mappedFile = new DefaultMappedFile(nextFilePath, this.mappedFileSize, runningFlags, this.writeWithoutMmap); } catch (IOException e) { log.error("create mappedFile exception", e); } } if (mappedFile != null) { if (this.mappedFiles.isEmpty()) { mappedFile.setFirstCreateInQueue(true); } this.mappedFiles.add(mappedFile); } return mappedFile; } public MappedFile getLastMappedFile(final long startOffset) { return getLastMappedFile(startOffset, true); } public MappedFile getLastMappedFile() { MappedFile mappedFileLast = null; while (!this.mappedFiles.isEmpty()) { try { mappedFileLast = this.mappedFiles.get(this.mappedFiles.size() - 1); break; } catch (IndexOutOfBoundsException e) { //continue; } catch (Exception e) { log.error("getLastMappedFile has exception.", e); break; } } return mappedFileLast; } public boolean resetOffset(long offset) { MappedFile mappedFileLast = getLastMappedFile(); if (mappedFileLast != null) { long lastOffset = mappedFileLast.getFileFromOffset() + mappedFileLast.getWrotePosition(); long diff = lastOffset - offset; final int maxDiff = this.mappedFileSize * 2; if (diff > maxDiff) return false; } ListIterator iterator = this.mappedFiles.listIterator(mappedFiles.size()); List toRemoves = new ArrayList<>(); while (iterator.hasPrevious()) { mappedFileLast = iterator.previous(); if (offset >= mappedFileLast.getFileFromOffset()) { int where = (int) (offset % mappedFileLast.getFileSize()); mappedFileLast.setFlushedPosition(where); mappedFileLast.setWrotePosition(where); mappedFileLast.setCommittedPosition(where); break; } else { toRemoves.add(mappedFileLast); } } if (!toRemoves.isEmpty()) { this.mappedFiles.removeAll(toRemoves); } return true; } public long getMinOffset() { if (!this.mappedFiles.isEmpty()) { try { return this.mappedFiles.get(0).getFileFromOffset(); } catch (IndexOutOfBoundsException e) { //continue; } catch (Exception e) { log.error("getMinOffset has exception.", e); } } return -1; } public long getMaxOffset() { MappedFile mappedFile = getLastMappedFile(); if (mappedFile != null) { return mappedFile.getFileFromOffset() + mappedFile.getReadPosition(); } return 0; } public long getMaxWrotePosition() { MappedFile mappedFile = getLastMappedFile(); if (mappedFile != null) { return mappedFile.getFileFromOffset() + mappedFile.getWrotePosition(); } return 0; } public long remainHowManyDataToCommit() { return getMaxWrotePosition() - getCommittedWhere(); } public long remainHowManyDataToFlush() { return getMaxOffset() - this.getFlushedWhere(); } public void deleteLastMappedFile() { MappedFile lastMappedFile = getLastMappedFile(); if (lastMappedFile != null) { lastMappedFile.destroy(1000); this.mappedFiles.remove(lastMappedFile); log.info("on recover, destroy a logic mapped file " + lastMappedFile.getFileName()); } } public int deleteExpiredFileByTime(final long expiredTime, final int deleteFilesInterval, final long intervalForcibly, final boolean cleanImmediately, final int deleteFileBatchMax) { Object[] mfs = this.copyMappedFiles(0); if (null == mfs) return 0; int mfsLength = mfs.length - 1; int deleteCount = 0; List files = new ArrayList<>(); int skipFileNum = 0; if (null != mfs) { //do check before deleting checkSelf(); for (int i = 0; i < mfsLength; i++) { MappedFile mappedFile = (MappedFile) mfs[i]; long liveMaxTimestamp = mappedFile.getLastModifiedTimestamp() + expiredTime; if (System.currentTimeMillis() >= liveMaxTimestamp || cleanImmediately) { if (skipFileNum > 0) { log.info("Delete CommitLog {} but skip {} files", mappedFile.getFileName(), skipFileNum); } if (mappedFile.destroy(intervalForcibly)) { files.add(mappedFile); deleteCount++; if (files.size() >= deleteFileBatchMax) { break; } if (deleteFilesInterval > 0 && (i + 1) < mfsLength) { try { Thread.sleep(deleteFilesInterval); } catch (InterruptedException e) { } } } else { break; } } else { skipFileNum++; //avoid deleting files in the middle break; } } } deleteExpiredFile(files); return deleteCount; } public int deleteExpiredFileByOffset(long offset, int unitSize) { Object[] mfs = this.copyMappedFiles(0); List files = new ArrayList<>(); int deleteCount = 0; if (null != mfs) { int mfsLength = mfs.length - 1; for (int i = 0; i < mfsLength; i++) { boolean destroy; MappedFile mappedFile = (MappedFile) mfs[i]; SelectMappedBufferResult result = mappedFile.selectMappedBuffer(this.mappedFileSize - unitSize); if (result != null) { long maxOffsetInLogicQueue = result.getByteBuffer().getLong(); result.release(); destroy = maxOffsetInLogicQueue < offset; if (destroy) { log.info("physic min offset " + offset + ", logics in current mappedFile max offset " + maxOffsetInLogicQueue + ", delete it"); } } else if (!mappedFile.isAvailable()) { // Handle hanged file. log.warn("Found a hanged consume queue file, attempting to delete it."); destroy = true; } else { log.warn("this being not executed forever."); break; } if (destroy && mappedFile.destroy(1000 * 60)) { files.add(mappedFile); deleteCount++; } else { break; } } } deleteExpiredFile(files); return deleteCount; } public int deleteExpiredFileByOffsetForTimerLog(long offset, int checkOffset, int unitSize) { Object[] mfs = this.copyMappedFiles(0); List files = new ArrayList<>(); int deleteCount = 0; if (null != mfs) { int mfsLength = mfs.length - 1; for (int i = 0; i < mfsLength; i++) { boolean destroy = false; MappedFile mappedFile = (MappedFile) mfs[i]; SelectMappedBufferResult result = mappedFile.selectMappedBuffer(checkOffset); try { if (result != null) { int position = result.getByteBuffer().position(); int size = result.getByteBuffer().getInt();//size result.getByteBuffer().getLong(); //prev pos int magic = result.getByteBuffer().getInt(); if (size == unitSize && (magic | 0xF) == 0xF) { result.getByteBuffer().position(position + MixAll.UNIT_PRE_SIZE_FOR_MSG); long maxOffsetPy = result.getByteBuffer().getLong(); destroy = maxOffsetPy < offset; if (destroy) { log.info("physic min commitlog offset " + offset + ", current mappedFile's max offset " + maxOffsetPy + ", delete it"); } } else { log.warn("Found error data in [{}] checkOffset:{} unitSize:{}", mappedFile.getFileName(), checkOffset, unitSize); } } else if (!mappedFile.isAvailable()) { // Handle hanged file. log.warn("Found a hanged consume queue file, attempting to delete it."); destroy = true; } else { log.warn("this being not executed forever."); break; } } finally { if (null != result) { result.release(); } } if (destroy && mappedFile.destroy(1000 * 60)) { files.add(mappedFile); deleteCount++; } else { break; } } } deleteExpiredFile(files); return deleteCount; } public boolean flush(final int flushLeastPages) { boolean result = true; MappedFile mappedFile = this.findMappedFileByOffset(this.getFlushedWhere(), this.getFlushedWhere() == 0); if (mappedFile != null) { long tmpTimeStamp = mappedFile.getStoreTimestamp(); int offset = mappedFile.flush(flushLeastPages); long where = mappedFile.getFileFromOffset() + offset; result = where == this.getFlushedWhere(); this.setFlushedWhere(where); if (0 == flushLeastPages) { this.setStoreTimestamp(tmpTimeStamp); } } return result; } public synchronized boolean commit(final int commitLeastPages) { boolean result = true; MappedFile mappedFile = this.findMappedFileByOffset(this.getCommittedWhere(), this.getCommittedWhere() == 0); if (mappedFile != null) { int offset = mappedFile.commit(commitLeastPages); long where = mappedFile.getFileFromOffset() + offset; result = where == this.getCommittedWhere(); this.setCommittedWhere(where); } return result; } /** * Finds a mapped file by offset. * * @param offset Offset. * @param returnFirstOnNotFound If the mapped file is not found, then return the first one. * @return Mapped file or null (when not found and returnFirstOnNotFound is false). */ public MappedFile findMappedFileByOffset(final long offset, final boolean returnFirstOnNotFound) { try { MappedFile firstMappedFile = this.getFirstMappedFile(); MappedFile lastMappedFile = this.getLastMappedFile(); if (firstMappedFile != null && lastMappedFile != null) { if (offset < firstMappedFile.getFileFromOffset() || offset >= lastMappedFile.getFileFromOffset() + this.mappedFileSize) { LOG_ERROR.warn("Offset not matched. Request offset: {}, firstOffset: {}, lastOffset: {}, mappedFileSize: {}, mappedFiles count: {}", offset, firstMappedFile.getFileFromOffset(), lastMappedFile.getFileFromOffset() + this.mappedFileSize, this.mappedFileSize, this.mappedFiles.size()); } else { int index = (int) ((offset / this.mappedFileSize) - (firstMappedFile.getFileFromOffset() / this.mappedFileSize)); MappedFile targetFile = null; try { targetFile = this.mappedFiles.get(index); } catch (Exception ignored) { } if (targetFile != null && offset >= targetFile.getFileFromOffset() && offset < targetFile.getFileFromOffset() + this.mappedFileSize) { return targetFile; } for (MappedFile tmpMappedFile : this.mappedFiles) { if (offset >= tmpMappedFile.getFileFromOffset() && offset < tmpMappedFile.getFileFromOffset() + this.mappedFileSize) { return tmpMappedFile; } } } if (returnFirstOnNotFound) { return firstMappedFile; } } } catch (Exception e) { log.error("findMappedFileByOffset Exception", e); } return null; } public MappedFile getFirstMappedFile() { MappedFile mappedFileFirst = null; if (!this.mappedFiles.isEmpty()) { try { mappedFileFirst = this.mappedFiles.get(0); } catch (IndexOutOfBoundsException e) { //ignore } catch (Exception e) { log.error("getFirstMappedFile has exception.", e); } } return mappedFileFirst; } public MappedFile findMappedFileByOffset(final long offset) { return findMappedFileByOffset(offset, false); } public long getMappedMemorySize() { long size = 0; Object[] mfs = this.copyMappedFiles(0); if (mfs != null) { for (Object mf : mfs) { if (((ReferenceResource) mf).isAvailable()) { size += this.mappedFileSize; } } } return size; } public boolean retryDeleteFirstFile(final long intervalForcibly) { MappedFile mappedFile = this.getFirstMappedFile(); if (mappedFile != null) { if (!mappedFile.isAvailable()) { log.warn("the mappedFile was destroyed once, but still alive, " + mappedFile.getFileName()); boolean result = mappedFile.destroy(intervalForcibly); if (result) { log.info("the mappedFile re delete OK, " + mappedFile.getFileName()); List tmpFiles = new ArrayList<>(); tmpFiles.add(mappedFile); this.deleteExpiredFile(tmpFiles); } else { log.warn("the mappedFile re delete failed, " + mappedFile.getFileName()); } return result; } } return false; } public void shutdown(final long intervalForcibly) { for (MappedFile mf : this.mappedFiles) { mf.shutdown(intervalForcibly); } } public void cleanResourcesAll() { for (MappedFile mf : this.mappedFiles) { mf.cleanResources(); } } public void destroy() { for (MappedFile mf : this.mappedFiles) { mf.destroy(1000 * 3); } this.mappedFiles.clear(); this.setFlushedWhere(0); // delete parent directory File file = new File(storePath); if (file.isDirectory()) { file.delete(); } } @Override public void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) { if (mappedFiles.isEmpty()) { return; } if (reserveNum < 3) { reserveNum = 3; } Object[] mfs = this.copyMappedFiles(0); if (null == mfs) { return; } for (int i = mfs.length - reserveNum - 1; i >= 0; i--) { MappedFile mappedFile = (MappedFile) mfs[i]; if (System.currentTimeMillis() - mappedFile.getRecentSwapMapTime() > forceSwapIntervalMs) { mappedFile.swapMap(); continue; } if (System.currentTimeMillis() - mappedFile.getRecentSwapMapTime() > normalSwapIntervalMs && mappedFile.getMappedByteBufferAccessCountSinceLastSwap() > 0) { mappedFile.swapMap(); continue; } } } @Override public void cleanSwappedMap(long forceCleanSwapIntervalMs) { if (mappedFiles.isEmpty()) { return; } int reserveNum = 3; Object[] mfs = this.copyMappedFiles(0); if (null == mfs) { return; } for (int i = mfs.length - reserveNum - 1; i >= 0; i--) { MappedFile mappedFile = (MappedFile) mfs[i]; if (System.currentTimeMillis() - mappedFile.getRecentSwapMapTime() > forceCleanSwapIntervalMs) { mappedFile.cleanSwapedMap(false); } } } public Object[] snapshot() { // return a safe copy return this.mappedFiles.toArray(); } public Stream stream() { return this.mappedFiles.stream(); } public Stream reversedStream() { return Lists.reverse(this.mappedFiles).stream(); } public long getFlushedWhere() { return flushedWhere; } public void setFlushedWhere(long flushedWhere) { this.flushedWhere = flushedWhere; } public long getStoreTimestamp() { return storeTimestamp; } public void setStoreTimestamp(long storeTimestamp) { this.storeTimestamp = storeTimestamp; } public List getMappedFiles() { return mappedFiles; } public int getMappedFileSize() { return mappedFileSize; } public long getCommittedWhere() { return committedWhere; } public void setCommittedWhere(final long committedWhere) { this.committedWhere = committedWhere; } public long getTotalFileSize() { return (long) mappedFileSize * mappedFiles.size(); } public String getStorePath() { return storePath; } public List range(final long from, final long to) { Object[] mfs = copyMappedFiles(0); if (null == mfs) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Object mf : mfs) { MappedFile mappedFile = (MappedFile) mf; if (mappedFile.getFileFromOffset() + mappedFile.getFileSize() <= from) { continue; } if (to <= mappedFile.getFileFromOffset()) { break; } result.add(mappedFile); } return result; } public MappedFile getEarliestMappedFile() { MappedFile mappedFile = null; while (!this.mappedFiles.isEmpty()) { try { mappedFile = this.mappedFiles.get(0); break; } catch (IndexOutOfBoundsException e) { //continue; } catch (Exception e) { log.error("getEarliestMappedFile error: {}", e.getMessage()); break; } } return mappedFile; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MessageArrivingListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.Map; public interface MessageArrivingListener { /** * Notify that a new message arrives in a consume queue * @param topic topic name * @param queueId consume queue id * @param logicOffset consume queue offset * @param tagsCode message tags hash code * @param msgStoreTime message store time * @param filterBitMap message bloom filter * @param properties message properties */ void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MessageExtEncoder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator; import java.nio.ByteBuffer; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageVersion; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.config.MessageStoreConfig; public class MessageExtEncoder { protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private ByteBuf byteBuf; // The maximum length of the message body. private int maxMessageBodySize; // The maximum length of the full message. private int maxMessageSize; private final int crc32ReservedLength; private MessageStoreConfig messageStoreConfig; public MessageExtEncoder(final int maxMessageBodySize, final MessageStoreConfig messageStoreConfig) { this(messageStoreConfig); } public MessageExtEncoder(final MessageStoreConfig messageStoreConfig) { ByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT; this.messageStoreConfig = messageStoreConfig; this.maxMessageBodySize = messageStoreConfig.getMaxMessageSize(); //Reserve 64kb for encoding buffer outside body int maxMessageSize = Integer.MAX_VALUE - maxMessageBodySize >= 64 * 1024 ? maxMessageBodySize + 64 * 1024 : Integer.MAX_VALUE; byteBuf = alloc.directBuffer(maxMessageSize); this.maxMessageSize = maxMessageSize; this.crc32ReservedLength = messageStoreConfig.isEnabledAppendPropCRC() ? CommitLog.CRC32_RESERVED_LEN : 0; } public static int calMsgLength(MessageVersion messageVersion, int sysFlag, int bodyLength, int topicLength, int propertiesLength) { int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; int storehostAddressLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 8 : 20; return 4 //TOTALSIZE + 4 //MAGICCODE + 4 //BODYCRC + 4 //QUEUEID + 4 //FLAG + 8 //QUEUEOFFSET + 8 //PHYSICALOFFSET + 4 //SYSFLAG + 8 //BORNTIMESTAMP + bornhostLength //BORNHOST + 8 //STORETIMESTAMP + storehostAddressLength //STOREHOSTADDRESS + 4 //RECONSUMETIMES + 8 //Prepared Transaction Offset + 4 + (Math.max(bodyLength, 0)) //BODY + messageVersion.getTopicLengthSize() + topicLength //TOPIC + 2 + (Math.max(propertiesLength, 0)); //propertiesLength } public static int calMsgLengthNoProperties(MessageVersion messageVersion, int sysFlag, int bodyLength, int topicLength) { int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20; int storehostAddressLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 8 : 20; return 4 //TOTALSIZE + 4 //MAGICCODE + 4 //BODYCRC + 4 //QUEUEID + 4 //FLAG + 8 //QUEUEOFFSET + 8 //PHYSICALOFFSET + 4 //SYSFLAG + 8 //BORNTIMESTAMP + bornhostLength //BORNHOST + 8 //STORETIMESTAMP + storehostAddressLength //STOREHOSTADDRESS + 4 //RECONSUMETIMES + 8 //Prepared Transaction Offset + 4 + (Math.max(bodyLength, 0)) //BODY + messageVersion.getTopicLengthSize() + topicLength; //TOPIC } public PutMessageResult encodeWithoutProperties(MessageExtBrokerInner msgInner) { final byte[] topicData = msgInner.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; final int bodyLength = msgInner.getBody() == null ? 0 : msgInner.getBody().length; // Exceeds the maximum message body if (bodyLength > this.maxMessageBodySize) { CommitLog.log.warn("message body size exceeded, msg body size: " + bodyLength + ", maxMessageSize: " + this.maxMessageBodySize); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } final int msgLenNoProperties = calMsgLengthNoProperties(msgInner.getVersion(), msgInner.getSysFlag(), bodyLength, topicLength); // 1 TOTALSIZE this.byteBuf.writeInt(msgLenNoProperties); // 2 MAGICCODE this.byteBuf.writeInt(msgInner.getVersion().getMagicCode()); // 3 BODYCRC this.byteBuf.writeInt(msgInner.getBodyCRC()); // 4 QUEUEID this.byteBuf.writeInt(msgInner.getQueueId()); // 5 FLAG this.byteBuf.writeInt(msgInner.getFlag()); // 6 QUEUEOFFSET, need update later this.byteBuf.writeLong(0); // 7 PHYSICALOFFSET, need update later this.byteBuf.writeLong(0); // 8 SYSFLAG this.byteBuf.writeInt(msgInner.getSysFlag()); // 9 BORNTIMESTAMP this.byteBuf.writeLong(msgInner.getBornTimestamp()); // 10 BORNHOST ByteBuffer bornHostBytes = msgInner.getBornHostBytes(); this.byteBuf.writeBytes(bornHostBytes.array()); // 11 STORETIMESTAMP this.byteBuf.writeLong(msgInner.getStoreTimestamp()); // 12 STOREHOSTADDRESS ByteBuffer storeHostBytes = msgInner.getStoreHostBytes(); this.byteBuf.writeBytes(storeHostBytes.array()); // 13 RECONSUMETIMES this.byteBuf.writeInt(msgInner.getReconsumeTimes()); // 14 Prepared Transaction Offset this.byteBuf.writeLong(msgInner.getPreparedTransactionOffset()); // 15 BODY this.byteBuf.writeInt(bodyLength); if (bodyLength > 0) this.byteBuf.writeBytes(msgInner.getBody()); // 16 TOPIC if (MessageVersion.MESSAGE_VERSION_V2.equals(msgInner.getVersion())) { this.byteBuf.writeShort((short) topicLength); } else { this.byteBuf.writeByte((byte) topicLength); } this.byteBuf.writeBytes(topicData); return null; } public PutMessageResult encode(MessageExtBrokerInner msgInner) { this.byteBuf.clear(); if (messageStoreConfig.isEnableLmq() && msgInner.needDispatchLMQ()) { return encodeWithoutProperties(msgInner); } /* * Serialize message */ final byte[] propertiesData = msgInner.getPropertiesString() == null ? null : msgInner.getPropertiesString().getBytes(MessageDecoder.CHARSET_UTF8); boolean needAppendLastPropertySeparator = crc32ReservedLength > 0 && propertiesData != null && propertiesData.length > 0 && propertiesData[propertiesData.length - 1] != MessageDecoder.PROPERTY_SEPARATOR; final int propertiesLength = (propertiesData == null ? 0 : propertiesData.length) + (needAppendLastPropertySeparator ? 1 : 0) + crc32ReservedLength; if (propertiesLength > Short.MAX_VALUE) { log.warn("putMessage message properties length too long. length={}", propertiesLength); return new PutMessageResult(PutMessageStatus.PROPERTIES_SIZE_EXCEEDED, null); } final byte[] topicData = msgInner.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; final int bodyLength = msgInner.getBody() == null ? 0 : msgInner.getBody().length; final int msgLen = calMsgLength( msgInner.getVersion(), msgInner.getSysFlag(), bodyLength, topicLength, propertiesLength); // Exceeds the maximum message body if (bodyLength > this.maxMessageBodySize) { CommitLog.log.warn("message body size exceeded, msg total size: " + msgLen + ", msg body size: " + bodyLength + ", maxMessageSize: " + this.maxMessageBodySize); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } final long queueOffset = msgInner.getQueueOffset(); // Exceeds the maximum message if (msgLen > this.maxMessageSize) { CommitLog.log.warn("message size exceeded, msg total size: " + msgLen + ", msg body size: " + bodyLength + ", maxMessageSize: " + this.maxMessageSize); return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null); } // 1 TOTALSIZE this.byteBuf.writeInt(msgLen); // 2 MAGICCODE this.byteBuf.writeInt(msgInner.getVersion().getMagicCode()); // 3 BODYCRC this.byteBuf.writeInt(msgInner.getBodyCRC()); // 4 QUEUEID this.byteBuf.writeInt(msgInner.getQueueId()); // 5 FLAG this.byteBuf.writeInt(msgInner.getFlag()); // 6 QUEUEOFFSET this.byteBuf.writeLong(queueOffset); // 7 PHYSICALOFFSET, need update later this.byteBuf.writeLong(0); // 8 SYSFLAG this.byteBuf.writeInt(msgInner.getSysFlag()); // 9 BORNTIMESTAMP this.byteBuf.writeLong(msgInner.getBornTimestamp()); // 10 BORNHOST ByteBuffer bornHostBytes = msgInner.getBornHostBytes(); this.byteBuf.writeBytes(bornHostBytes.array()); // 11 STORETIMESTAMP this.byteBuf.writeLong(msgInner.getStoreTimestamp()); // 12 STOREHOSTADDRESS ByteBuffer storeHostBytes = msgInner.getStoreHostBytes(); this.byteBuf.writeBytes(storeHostBytes.array()); // 13 RECONSUMETIMES this.byteBuf.writeInt(msgInner.getReconsumeTimes()); // 14 Prepared Transaction Offset this.byteBuf.writeLong(msgInner.getPreparedTransactionOffset()); // 15 BODY this.byteBuf.writeInt(bodyLength); if (bodyLength > 0) this.byteBuf.writeBytes(msgInner.getBody()); // 16 TOPIC if (MessageVersion.MESSAGE_VERSION_V2.equals(msgInner.getVersion())) { this.byteBuf.writeShort((short) topicLength); } else { this.byteBuf.writeByte((byte) topicLength); } this.byteBuf.writeBytes(topicData); // 17 PROPERTIES this.byteBuf.writeShort((short) propertiesLength); if (propertiesLength > crc32ReservedLength) { this.byteBuf.writeBytes(propertiesData); } if (needAppendLastPropertySeparator) { this.byteBuf.writeByte((byte) MessageDecoder.PROPERTY_SEPARATOR); } // 18 CRC32 this.byteBuf.writerIndex(this.byteBuf.writerIndex() + crc32ReservedLength); return null; } public ByteBuffer encode(final MessageExtBatch messageExtBatch, PutMessageContext putMessageContext) { this.byteBuf.clear(); ByteBuffer messagesByteBuff = messageExtBatch.wrap(); int totalLength = messagesByteBuff.limit(); if (totalLength > this.maxMessageBodySize) { CommitLog.log.warn("message body size exceeded, msg body size: " + totalLength + ", maxMessageSize: " + this.maxMessageBodySize); throw new RuntimeException("message body size exceeded"); } int batchSize = 0; while (messagesByteBuff.hasRemaining()) { batchSize++; // 1 TOTALSIZE messagesByteBuff.getInt(); // 2 MAGICCODE messagesByteBuff.getInt(); // 3 BODYCRC messagesByteBuff.getInt(); // 4 FLAG int flag = messagesByteBuff.getInt(); // 5 BODY int bodyLen = messagesByteBuff.getInt(); int bodyPos = messagesByteBuff.position(); int bodyCrc = UtilAll.crc32(messagesByteBuff.array(), bodyPos, bodyLen); messagesByteBuff.position(bodyPos + bodyLen); // 6 properties short propertiesLen = messagesByteBuff.getShort(); int propertiesPos = messagesByteBuff.position(); messagesByteBuff.position(propertiesPos + propertiesLen); final byte[] topicData = messageExtBatch.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; int totalPropLen = propertiesLen; // properties need to add crc32 totalPropLen += crc32ReservedLength; final int msgLen = calMsgLength( messageExtBatch.getVersion(), messageExtBatch.getSysFlag(), bodyLen, topicLength, totalPropLen); // 1 TOTALSIZE this.byteBuf.writeInt(msgLen); // 2 MAGICCODE this.byteBuf.writeInt(messageExtBatch.getVersion().getMagicCode()); // 3 BODYCRC this.byteBuf.writeInt(bodyCrc); // 4 QUEUEID this.byteBuf.writeInt(messageExtBatch.getQueueId()); // 5 FLAG this.byteBuf.writeInt(flag); // 6 QUEUEOFFSET this.byteBuf.writeLong(0); // 7 PHYSICALOFFSET this.byteBuf.writeLong(0); // 8 SYSFLAG this.byteBuf.writeInt(messageExtBatch.getSysFlag()); // 9 BORNTIMESTAMP this.byteBuf.writeLong(messageExtBatch.getBornTimestamp()); // 10 BORNHOST ByteBuffer bornHostBytes = messageExtBatch.getBornHostBytes(); this.byteBuf.writeBytes(bornHostBytes.array()); // 11 STORETIMESTAMP this.byteBuf.writeLong(messageExtBatch.getStoreTimestamp()); // 12 STOREHOSTADDRESS ByteBuffer storeHostBytes = messageExtBatch.getStoreHostBytes(); this.byteBuf.writeBytes(storeHostBytes.array()); // 13 RECONSUMETIMES this.byteBuf.writeInt(messageExtBatch.getReconsumeTimes()); // 14 Prepared Transaction Offset, batch does not support transaction this.byteBuf.writeLong(0); // 15 BODY this.byteBuf.writeInt(bodyLen); if (bodyLen > 0) this.byteBuf.writeBytes(messagesByteBuff.array(), bodyPos, bodyLen); // 16 TOPIC if (MessageVersion.MESSAGE_VERSION_V2.equals(messageExtBatch.getVersion())) { this.byteBuf.writeShort((short) topicLength); } else { this.byteBuf.writeByte((byte) topicLength); } this.byteBuf.writeBytes(topicData); // 17 PROPERTIES this.byteBuf.writeShort((short) totalPropLen); if (propertiesLen > 0) { this.byteBuf.writeBytes(messagesByteBuff.array(), propertiesPos, propertiesLen); } this.byteBuf.writerIndex(this.byteBuf.writerIndex() + crc32ReservedLength); } putMessageContext.setBatchSize(batchSize); putMessageContext.setPhyPos(new long[batchSize]); return this.byteBuf.nioBuffer(); } public ByteBuffer getEncoderBuffer() { return this.byteBuf.nioBuffer(0, this.byteBuf.capacity()); } public int getMaxMessageBodySize() { return this.maxMessageBodySize; } public void updateEncoderBufferCapacity(int newMaxMessageBodySize) { this.maxMessageBodySize = newMaxMessageBodySize; //Reserve 64kb for encoding buffer outside body this.maxMessageSize = Integer.MAX_VALUE - newMaxMessageBodySize >= 64 * 1024 ? this.maxMessageBodySize + 64 * 1024 : Integer.MAX_VALUE; this.byteBuf.capacity(this.maxMessageSize); } static class PutMessageThreadLocal { private final MessageExtEncoder encoder; private final StringBuilder keyBuilder; PutMessageThreadLocal(MessageStoreConfig messageStoreConfig) { encoder = new MessageExtEncoder(messageStoreConfig); keyBuilder = new StringBuilder(); } public MessageExtEncoder getEncoder() { return encoder; } public StringBuilder getKeyBuilder() { return keyBuilder; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MessageFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import java.util.Map; public interface MessageFilter { /** * match by tags code or filter bit map which is calculated when message received * and stored in consume queue ext. * * @param tagsCode tagsCode * @param cqExtUnit extend unit of consume queue */ boolean isMatchedByConsumeQueue(final Long tagsCode, final ConsumeQueueExt.CqExtUnit cqExtUnit); /** * match by message content which are stored in commit log. *
    {@code msgBuffer} and {@code properties} are not all null.If invoked in store, * {@code properties} is null;If invoked in {@code PullRequestHoldService}, {@code msgBuffer} is null. * * @param msgBuffer message buffer in commit log, may be null if not invoked in store. * @param properties message properties, should decode from buffer if null by yourself. */ boolean isMatchedByCommitLog(final ByteBuffer msgBuffer, final Map properties); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MessageStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import javax.annotation.Nonnull; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.ha.HAService; import org.apache.rocketmq.store.hook.PutMessageHook; import org.apache.rocketmq.store.hook.SendMessageBackHook; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.rocksdb.TimerMessageRocksDBStore; import org.apache.rocketmq.store.transaction.TransMessageRocksDBStore; import org.apache.rocketmq.store.util.PerfCounter; import org.apache.rocketmq.store.metrics.StoreMetricsManager; import org.rocksdb.RocksDBException; /** * This class defines contracting interfaces to implement, allowing third-party vendor to use customized message store. */ public interface MessageStore { /** * Load previously stored messages. * * @return true if success; false otherwise. */ boolean load(); /** * Launch this message store. * * @throws Exception if there is any error. */ void start() throws Exception; /** * Shutdown this message store. */ void shutdown(); /** * Destroy this message store. Generally, all persistent files should be removed after invocation. */ void destroy(); /** * Store a message into store in async manner, the processor can process the next request rather than wait for * result when result is completed, notify the client in async manner * * @param msg MessageInstance to store * @return a CompletableFuture for the result of store operation */ default CompletableFuture asyncPutMessage(final MessageExtBrokerInner msg) { return CompletableFuture.completedFuture(putMessage(msg)); } /** * Store a batch of messages in async manner * * @param messageExtBatch the message batch * @return a CompletableFuture for the result of store operation */ default CompletableFuture asyncPutMessages(final MessageExtBatch messageExtBatch) { return CompletableFuture.completedFuture(putMessages(messageExtBatch)); } /** * Store a message into store. * * @param msg Message instance to store * @return result of store operation. */ PutMessageResult putMessage(final MessageExtBrokerInner msg); /** * Store a batch of messages. * * @param messageExtBatch Message batch. * @return result of storing batch messages. */ PutMessageResult putMessages(final MessageExtBatch messageExtBatch); /** * Query at most maxMsgNums messages belonging to topic at queueId starting * from given offset. Resulting messages will further be screened using provided message filter. * * @param group Consumer group that launches this query. * @param topic Topic to query. * @param queueId Queue ID to query. * @param offset Logical offset to start from. * @param maxMsgNums Maximum count of messages to query. * @param messageFilter Message filter used to screen desired messages. * @return Matched messages. */ GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final MessageFilter messageFilter); /** * Asynchronous get message * @see #getMessage(String, String, int, long, int, MessageFilter) getMessage * * @param group Consumer group that launches this query. * @param topic Topic to query. * @param queueId Queue ID to query. * @param offset Logical offset to start from. * @param maxMsgNums Maximum count of messages to query. * @param messageFilter Message filter used to screen desired messages. * @return Matched messages. */ CompletableFuture getMessageAsync(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final MessageFilter messageFilter); /** * Query at most maxMsgNums messages belonging to topic at queueId starting * from given offset. Resulting messages will further be screened using provided message filter. * * @param group Consumer group that launches this query. * @param topic Topic to query. * @param queueId Queue ID to query. * @param offset Logical offset to start from. * @param maxMsgNums Maximum count of messages to query. * @param maxTotalMsgSize Maximum total msg size of the messages * @param messageFilter Message filter used to screen desired messages. * @return Matched messages. */ GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final int maxTotalMsgSize, final MessageFilter messageFilter); /** * Asynchronous get message * @see #getMessage(String, String, int, long, int, int, MessageFilter) getMessage * * @param group Consumer group that launches this query. * @param topic Topic to query. * @param queueId Queue ID to query. * @param offset Logical offset to start from. * @param maxMsgNums Maximum count of messages to query. * @param maxTotalMsgSize Maximum total msg size of the messages * @param messageFilter Message filter used to screen desired messages. * @return Matched messages. */ CompletableFuture getMessageAsync(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final int maxTotalMsgSize, final MessageFilter messageFilter); /** * Get maximum offset of the topic queue. * * @param topic Topic name. * @param queueId Queue ID. * @return Maximum offset at present. */ long getMaxOffsetInQueue(final String topic, final int queueId) throws ConsumeQueueException; /** * Get maximum offset of the topic queue. * * @param topic Topic name. * @param queueId Queue ID. * @param committed return the max offset in ConsumeQueue if true, or the max offset in CommitLog if false * @return Maximum offset at present. */ long getMaxOffsetInQueue(final String topic, final int queueId, final boolean committed) throws ConsumeQueueException; /** * Get the minimum offset of the topic queue. * * @param topic Topic name. * @param queueId Queue ID. * @return Minimum offset at present. */ long getMinOffsetInQueue(final String topic, final int queueId); TimerMessageStore getTimerMessageStore(); TimerMessageRocksDBStore getTimerMessageRocksDBStore(); TransMessageRocksDBStore getTransMessageRocksDBStore(); void setTimerMessageStore(TimerMessageStore timerMessageStore); void setTimerMessageRocksDBStore(TimerMessageRocksDBStore timerMessageRocksDBStore); void setTransMessageRocksDBStore(TransMessageRocksDBStore transMessageRocksDBStore); /** * Get the offset of the message in the commit log, which is also known as physical offset. * * @param topic Topic of the message to lookup. * @param queueId Queue ID. * @param consumeQueueOffset offset of consume queue. * @return physical offset. */ long getCommitLogOffsetInQueue(final String topic, final int queueId, final long consumeQueueOffset); /** * Look up the physical offset of the message whose store timestamp is as specified. * * @param topic Topic of the message. * @param queueId Queue ID. * @param timestamp Timestamp to look up. * @return physical offset which matches. */ long getOffsetInQueueByTime(final String topic, final int queueId, final long timestamp); /** * Look up the physical offset of the message whose store timestamp is as specified with specific boundaryType. * * @param topic Topic of the message. * @param queueId Queue ID. * @param timestamp Timestamp to look up. * @param boundaryType Lower or Upper * @return physical offset which matches. */ long getOffsetInQueueByTime(final String topic, final int queueId, final long timestamp, final BoundaryType boundaryType); /** * Look up the message by given commit log offset. * * @param commitLogOffset physical offset. * @return Message whose physical offset is as specified. */ MessageExt lookMessageByOffset(final long commitLogOffset); /** * Look up the message by given commit log offset and size. * * @param commitLogOffset physical offset. * @param size message size * @return Message whose physical offset is as specified. */ MessageExt lookMessageByOffset(long commitLogOffset, int size); /** * Get one message from the specified commit log offset. * * @param commitLogOffset commit log offset. * @return wrapped result of the message. */ SelectMappedBufferResult selectOneMessageByOffset(final long commitLogOffset); /** * Get one message from the specified commit log offset. * * @param commitLogOffset commit log offset. * @param msgSize message size. * @return wrapped result of the message. */ SelectMappedBufferResult selectOneMessageByOffset(final long commitLogOffset, final int msgSize); /** * Get the running information of this store. * * @return message store running info. */ String getRunningDataInfo(); long getTimingMessageCount(String topic); /** * Message store runtime information, which should generally contains various statistical information. * * @return runtime information of the message store in format of key-value pairs. */ HashMap getRuntimeInfo(); /** * HA runtime information * @return runtime information of ha */ HARuntimeInfo getHARuntimeInfo(); /** * Get the maximum commit log offset. * * @return maximum commit log offset. */ long getMaxPhyOffset(); /** * Get the minimum commit log offset. * * @return minimum commit log offset. */ long getMinPhyOffset(); /** * Get the store time of the earliest message in the given queue. * * @param topic Topic of the messages to query. * @param queueId Queue ID to find. * @return store time of the earliest message. */ long getEarliestMessageTime(final String topic, final int queueId); /** * Get the store time of the earliest message in this store. * * @return timestamp of the earliest message in this store. */ long getEarliestMessageTime(); /** * Asynchronous get the store time of the earliest message in this store. * @see #getEarliestMessageTime() getEarliestMessageTime * * @return timestamp of the earliest message in this store. */ CompletableFuture getEarliestMessageTimeAsync(final String topic, final int queueId); /** * Get the store time of the message specified. * * @param topic message topic. * @param queueId queue ID. * @param consumeQueueOffset consume queue offset. * @return store timestamp of the message. */ long getMessageStoreTimeStamp(final String topic, final int queueId, final long consumeQueueOffset); /** * Asynchronous get the store time of the message specified. * @see #getMessageStoreTimeStamp(String, int, long) getMessageStoreTimeStamp * * @param topic message topic. * @param queueId queue ID. * @param consumeQueueOffset consume queue offset. * @return store timestamp of the message. */ CompletableFuture getMessageStoreTimeStampAsync(final String topic, final int queueId, final long consumeQueueOffset); /** * Get the total number of the messages in the specified queue. * * @param topic Topic * @param queueId Queue ID. * @return total number. */ long getMessageTotalInQueue(final String topic, final int queueId); /** * Get the raw commit log data starting from the given offset, which should used for replication purpose. * * @param offset starting offset. * @return commit log data. */ SelectMappedBufferResult getCommitLogData(final long offset); /** * Get the raw commit log data starting from the given offset, across multiple mapped files. * * @param offset starting offset. * @param size size of data to get * @return commit log data. */ List getBulkCommitLogData(final long offset, final int size); /** * Append data to commit log. * * @param startOffset starting offset. * @param data data to append. * @param dataStart the start index of data array * @param dataLength the length of data array * @return true if success; false otherwise. */ boolean appendToCommitLog(final long startOffset, final byte[] data, int dataStart, int dataLength); /** * Execute file deletion manually. */ void executeDeleteFilesManually(); /** * Query messages by given key. * * @param topic topic of the message. * @param key message key. * @param maxNum maximum number of the messages possible. * @param begin begin timestamp. * @param end end timestamp. */ QueryMessageResult queryMessage(final String topic, final String key, final int maxNum, final long begin, final long end); QueryMessageResult queryMessage(final String topic, final String key, final int maxNum, final long begin, final long end, final String indexType, final String lastKey); /** * Asynchronous query messages by given key. * @see #queryMessage(String, String, int, long, long) queryMessage * * @param topic topic of the message. * @param key message key. * @param maxNum maximum number of the messages possible. * @param begin begin timestamp. * @param end end timestamp. */ CompletableFuture queryMessageAsync(final String topic, final String key, final int maxNum, final long begin, final long end); CompletableFuture queryMessageAsync(final String topic, final String key, final int maxNum, final long begin, final long end, final String indexType, final String lastKey); /** * Update HA master address. * * @param newAddr new address. */ void updateHaMasterAddress(final String newAddr); /** * Update master address. * * @param newAddr new address. */ void updateMasterAddress(final String newAddr); /** * Return how much the slave falls behind. * * @return number of bytes that slave falls behind. */ long slaveFallBehindMuch(); /** * Return the current timestamp of the store. * * @return current time in milliseconds since 1970-01-01. */ long now(); /** * Delete topic's consume queue file and unused stats. * This interface allows user delete system topic. * * @param deleteTopics unused topic name set * @return the number of the topics which has been deleted. */ int deleteTopics(final Set deleteTopics); /** * Clean unused topics which not in retain topic name set. * * @param retainTopics all valid topics. * @return number of the topics deleted. */ int cleanUnusedTopic(final Set retainTopics); /** * Clean expired consume queues. */ void cleanExpiredConsumerQueue(); /** * Check if the given message has been swapped out of the memory. * * @param topic topic. * @param queueId queue ID. * @param consumeOffset consume queue offset. * @return true if the message is no longer in memory; false otherwise. * @deprecated As of RIP-57, replaced by {@link #checkInMemByConsumeOffset(String, int, long, int)}, see this issue for more details */ @Deprecated boolean checkInDiskByConsumeOffset(final String topic, final int queueId, long consumeOffset); /** * Check if the given message is in the page cache. * * @param topic topic. * @param queueId queue ID. * @param consumeOffset consume queue offset. * @return true if the message is in page cache; false otherwise. */ boolean checkInMemByConsumeOffset(final String topic, final int queueId, long consumeOffset, int batchSize); /** * Check if the given message is in store. * * @param topic topic. * @param queueId queue ID. * @param consumeOffset consume queue offset. * @return true if the message is in store; false otherwise. */ boolean checkInStoreByConsumeOffset(final String topic, final int queueId, long consumeOffset); /** * Get number of the bytes that have been stored in commit log and not yet dispatched to consume queue. * * @return number of the bytes to dispatch. */ long dispatchBehindBytes(); /** * Get number of the bytes that have been stored in commit log and not yet flushed to disk. * * @return number of the bytes to flush. */ long flushBehindBytes(); /** * Get number of the milliseconds that have been stored in commit log and not yet dispatched to consume queue. * * @return number of the milliseconds to dispatch. */ long dispatchBehindMilliseconds(); /** * Flush the message store to persist all data. * * @return maximum offset flushed to persistent storage device. */ long flush(); /** * Get the current flushed offset. * * @return flushed offset */ long getFlushedWhere(); /** * Get confirm offset. * * @return confirm offset. */ long getConfirmOffset(); /** * Set confirm offset. * * @param phyOffset confirm offset to set. */ void setConfirmOffset(long phyOffset); /** * Check if the operating system page cache is busy or not. * * @return true if the OS page cache is busy; false otherwise. */ boolean isOSPageCacheBusy(); /** * Get lock time in milliseconds of the store by far. * * @return lock time in milliseconds. */ long lockTimeMills(); /** * Check if the transient store pool is deficient. * * @return true if the transient store pool is running out; false otherwise. */ boolean isTransientStorePoolDeficient(); /** * Get the dispatcher list. * * @return list of the dispatcher. */ LinkedList getDispatcherList(); /** * Add dispatcher. * * @param dispatcher commit log dispatcher to add */ void addDispatcher(CommitLogDispatcher dispatcher); /** * Get consume queue of the topic/queue. If consume queue not exist, will return null * * @param topic Topic. * @param queueId Queue ID. * @return Consume queue. */ ConsumeQueueInterface getConsumeQueue(String topic, int queueId); /** * Get consume queue of the topic/queue. If consume queue not exist, will create one then return it. * @param topic Topic. * @param queueId Queue ID. * @return Consume queue. */ ConsumeQueueInterface findConsumeQueue(String topic, int queueId); /** * Get BrokerStatsManager of the messageStore. * * @return BrokerStatsManager. */ BrokerStatsManager getBrokerStatsManager(); /** * Will be triggered when a new message is appended to commit log. * * @param msg the msg that is appended to commit log * @param result append message result * @param commitLogFile commit log file */ void onCommitLogAppend(MessageExtBrokerInner msg, AppendMessageResult result, MappedFile commitLogFile); /** * Will be triggered when a new dispatch request is sent to message store. * * @param dispatchRequest dispatch request * @param doDispatch do dispatch if true * @param commitLogFile commit log file * @param isRecover is from recover process * @param isFileEnd if the dispatch request represents 'file end' * @throws RocksDBException only in rocksdb mode */ void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile, boolean isRecover, boolean isFileEnd) throws RocksDBException; /** * Get the message store config * * @return the message store config */ MessageStoreConfig getMessageStoreConfig(); /** * Get the statistics service * * @return the statistics service */ StoreStatsService getStoreStatsService(); /** * Get the store checkpoint component * * @return the checkpoint component */ StoreCheckpoint getStoreCheckpoint(); /** * Get the system clock * * @return the system clock */ SystemClock getSystemClock(); /** * Get the commit log * * @return the commit log */ CommitLog getCommitLog(); /** * Get running flags * * @return running flags */ RunningFlags getRunningFlags(); /** * Get the transient store pool * * @return the transient store pool */ TransientStorePool getTransientStorePool(); /** * Get the HA service * * @return the HA service */ HAService getHaService(); /** * Get the allocate-mappedFile service * * @return the allocate-mappedFile service */ AllocateMappedFileService getAllocateMappedFileService(); /** * Truncate dirty logic files * * @param phyOffset physical offset * @throws RocksDBException only in rocksdb mode */ void truncateDirtyLogicFiles(long phyOffset) throws RocksDBException; /** * Unlock mappedFile * * @param unlockMappedFile the file that needs to be unlocked */ void unlockMappedFile(MappedFile unlockMappedFile); /** * Get the perf counter component * * @return the perf counter component */ PerfCounter.Ticks getPerfCounter(); /** * Get the queue store * * @return the queue store */ @Nonnull ConsumeQueueStoreInterface getQueueStore(); /** * If 'sync disk flush' is configured in this message store * * @return yes if true, no if false */ boolean isSyncDiskFlush(); /** * If this message store is sync master role * * @return yes if true, no if false */ boolean isSyncMaster(); /** * Assign a message to queue offset. If there is a race condition, you need to lock/unlock this method * yourself. * * @param msg message * @throws RocksDBException */ void assignOffset(MessageExtBrokerInner msg) throws RocksDBException; /** * Increase queue offset in memory table. If there is a race condition, you need to lock/unlock this method * * @param msg message * @param messageNum message num */ void increaseOffset(MessageExtBrokerInner msg, short messageNum); /** * Get master broker message store in process in broker container * * @return */ MessageStore getMasterStoreInProcess(); /** * Set master broker message store in process * * @param masterStoreInProcess */ void setMasterStoreInProcess(MessageStore masterStoreInProcess); /** * Use FileChannel to get data * * @param offset * @param size * @param byteBuffer * @return */ boolean getData(long offset, int size, ByteBuffer byteBuffer); /** * Set the number of alive replicas in group. * * @param aliveReplicaNums number of alive replicas */ void setAliveReplicaNumInGroup(int aliveReplicaNums); /** * Get the number of alive replicas in group. * * @return number of alive replicas */ int getAliveReplicaNumInGroup(); /** * Wake up AutoRecoverHAClient to start HA connection. */ void wakeupHAClient(); /** * Get master flushed offset. * * @return master flushed offset */ long getMasterFlushedOffset(); /** * Get broker init max offset. * * @return broker max offset in startup */ long getBrokerInitMaxOffset(); /** * Set master flushed offset. * * @param masterFlushedOffset master flushed offset */ void setMasterFlushedOffset(long masterFlushedOffset); /** * Set broker init max offset. * * @param brokerInitMaxOffset broker init max offset */ void setBrokerInitMaxOffset(long brokerInitMaxOffset); /** * Calculate the checksum of a certain range of data. * * @param from begin offset * @param to end offset * @return checksum */ byte[] calcDeltaChecksum(long from, long to); /** * Truncate commitLog and consume queue to certain offset. * * @param offsetToTruncate offset to truncate * @return true if truncate succeed, false otherwise * @throws RocksDBException only in rocksdb mode */ boolean truncateFiles(long offsetToTruncate) throws RocksDBException; /** * Check if the offset is aligned with one message. * * @param offset offset to check * @return true if aligned, false otherwise */ boolean isOffsetAligned(long offset); /** * Get put message hook list * * @return List of PutMessageHook */ List getPutMessageHookList(); /** * Set send message back hook * * @param sendMessageBackHook */ void setSendMessageBackHook(SendMessageBackHook sendMessageBackHook); /** * Get send message back hook * * @return SendMessageBackHook */ SendMessageBackHook getSendMessageBackHook(); //The following interfaces are used for duplication mode /** * Get last mapped file and return lase file first Offset * * @return lastMappedFile first Offset */ long getLastFileFromOffset(); /** * Get last mapped file * * @param startOffset * @return true when get the last mapped file, false when get null */ boolean getLastMappedFile(long startOffset); /** * Set physical offset * * @param phyOffset */ void setPhysicalOffset(long phyOffset); /** * Return whether mapped file is empty * * @return whether mapped file is empty */ boolean isMappedFilesEmpty(); /** * Get state machine version * * @return state machine version */ long getStateMachineVersion(); /** * Get store metrics manager * * @return store metrics manager */ StoreMetricsManager getStoreMetricsManager(); /** * Check message and return size * * @param byteBuffer * @param checkCRC * @param checkDupInfo * @param readBody * @return DispatchRequest */ DispatchRequest checkMessageAndReturnSize(final ByteBuffer byteBuffer, final boolean checkCRC, final boolean checkDupInfo, final boolean readBody); /** * Get remain transientStoreBuffer numbers * * @return remain transientStoreBuffer numbers */ int remainTransientStoreBufferNumbs(); /** * Get remain how many data to commit * * @return remain how many data to commit */ long remainHowManyDataToCommit(); /** * Get remain how many data to flush * * @return remain how many data to flush */ long remainHowManyDataToFlush(); /** * Get whether message store is shutdown * * @return whether shutdown */ boolean isShutdown(); /** * Estimate number of messages, within [from, to], which match given filter * * @param topic Topic name * @param queueId Queue ID * @param from Lower boundary of the range, inclusive. * @param to Upper boundary of the range, inclusive. * @param filter The message filter. * @return Estimate number of messages matching given filter. */ long estimateMessageCount(String topic, int queueId, long from, long to, MessageFilter filter); /** * Get metrics view of store * * @return List of metrics selector and view pair */ List> getMetricsView(); /** * Init store metrics * * @param meter opentelemetry meter * @param attributesBuilderSupplier metrics attributes builder */ void initMetrics(Meter meter, Supplier attributesBuilderSupplier); /** * Recover topic queue table */ void recoverTopicQueueTable(); /** * notify message arrive if necessary */ void notifyMessageArriveIfNecessary(DispatchRequest dispatchRequest); MessageStoreStateMachine getStateMachine(); MessageRocksDBStorage getMessageRocksDBStorage(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MessageStoreStateMachine.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MessageStoreStateMachine { protected final Logger log; private MessageStoreState currentState; private long lastStateChangeTimestamp; private final long startTimestamp; public enum MessageStoreState { INIT(0), LOAD_BEGIN(10), LOAD_COMMITLOG_OK(11), LOAD_CONSUME_QUEUE_OK(12), LOAD_COMPACTION_OK(13), LOAD_INDEX_OK(14), RECOVER_BEGIN(20), RECOVER_CONSUME_QUEUE_OK(21), RECOVER_COMMITLOG_OK(22), RECOVER_TOPIC_QUEUE_TABLE_OK(23), RUNNING(30), SHUTDOWN_BEGIN(40), SHUTDOWN_OK(41); final int order; MessageStoreState(int order) { this.order = order; } public int getOrder() { return order; } public boolean isBefore(MessageStoreState storeState) { return this.order < storeState.order; } public boolean isAfter(MessageStoreState storeState) { return this.order > storeState.order; } } public MessageStoreStateMachine(Logger log) { this.log = log == null ? LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME) : log; this.currentState = MessageStoreState.INIT; this.startTimestamp = System.currentTimeMillis(); this.lastStateChangeTimestamp = startTimestamp; logStateChange(null, currentState, true); } public void transitTo(MessageStoreState newState) { transitTo(newState, true); } public void transitTo(MessageStoreState newState, boolean success) { if (!newState.isAfter(currentState)) { throw new IllegalStateException( String.format("Invalid state transition from %s to %s. Can only move forward.", currentState, newState) ); } logStateChange(currentState, newState, success); if (success) { this.currentState = newState; this.lastStateChangeTimestamp = System.currentTimeMillis(); } } private void logStateChange(MessageStoreState fromState, MessageStoreState toState, boolean success) { if (fromState == null && success) { log.info("MessageStoreState initialized, state={}", toState); } else if (success) { log.info("MessageStoreState transition from {} to {}; Time in previous state={}ms, Total time={}ms", fromState, toState, getCurrentStateRunningTimeMs(), getTotalRunningTimeMs()); } else { log.warn("MessageStoreState transition from {} to {} failed; Time in previous state={}ms, Total " + "time={}ms", fromState, toState, getCurrentStateRunningTimeMs(), getTotalRunningTimeMs()); } } public MessageStoreState getCurrentState() { return currentState; } public long getTotalRunningTimeMs() { return System.currentTimeMillis() - startTimestamp; } public long getCurrentStateRunningTimeMs() { return System.currentTimeMillis() - lastStateChangeTimestamp; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.MappedFile; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class MultiPathMappedFileQueue extends MappedFileQueue { private final MessageStoreConfig config; private final Supplier> fullStorePathsSupplier; public MultiPathMappedFileQueue(MessageStoreConfig messageStoreConfig, int mappedFileSize, AllocateMappedFileService allocateMappedFileService, Supplier> fullStorePathsSupplier) { this(messageStoreConfig, mappedFileSize, allocateMappedFileService, fullStorePathsSupplier, null); } public MultiPathMappedFileQueue(MessageStoreConfig messageStoreConfig, int mappedFileSize, AllocateMappedFileService allocateMappedFileService, Supplier> fullStorePathsSupplier, RunningFlags runningFlags) { super(messageStoreConfig.getStorePathCommitLog(), mappedFileSize, allocateMappedFileService, runningFlags, messageStoreConfig.isWriteWithoutMmap()); this.config = messageStoreConfig; this.fullStorePathsSupplier = fullStorePathsSupplier; } private Set getPaths() { String[] paths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); return new HashSet<>(Arrays.asList(paths)); } private Set getReadonlyPaths() { String pathStr = config.getReadOnlyCommitLogStorePaths(); if (StringUtils.isBlank(pathStr)) { return Collections.emptySet(); } String[] paths = pathStr.trim().split(MixAll.MULTI_PATH_SPLITTER); return new HashSet<>(Arrays.asList(paths)); } @Override public boolean load() { Set storePathSet = getPaths(); storePathSet.addAll(getReadonlyPaths()); List files = new ArrayList<>(); for (String path : storePathSet) { File dir = new File(path); File[] ls = dir.listFiles(); if (ls != null) { Collections.addAll(files, ls); } } return doLoad(files); } @Override public MappedFile tryCreateMappedFile(long createOffset) { long fileIdx = createOffset / this.mappedFileSize; Set storePath = getPaths(); Set readonlyPathSet = getReadonlyPaths(); Set fullStorePaths = fullStorePathsSupplier == null ? Collections.emptySet() : fullStorePathsSupplier.get(); HashSet availableStorePath = new HashSet<>(storePath); //do not create file in readonly store path. availableStorePath.removeAll(readonlyPathSet); //do not create file is space is nearly full. availableStorePath.removeAll(fullStorePaths); //if no store path left, fall back to writable store path. if (availableStorePath.isEmpty()) { availableStorePath = new HashSet<>(storePath); availableStorePath.removeAll(readonlyPathSet); } String[] paths = availableStorePath.toArray(new String[]{}); Arrays.sort(paths); String nextFilePath = paths[(int) (fileIdx % paths.length)] + File.separator + UtilAll.offset2FileName(createOffset); String nextNextFilePath = paths[(int) ((fileIdx + 1) % paths.length)] + File.separator + UtilAll.offset2FileName(createOffset + this.mappedFileSize); return doCreateMappedFile(nextFilePath, nextNextFilePath); } @Override public void destroy() { for (MappedFile mf : this.mappedFiles) { mf.destroy(1000 * 3); } this.mappedFiles.clear(); this.setFlushedWhere(0); Set storePathSet = getPaths(); storePathSet.addAll(getReadonlyPaths()); for (String path : storePathSet) { File file = new File(path); if (file.isDirectory()) { file.delete(); } } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/PutMessageContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; public class PutMessageContext { private String topicQueueTableKey; private long[] phyPos; private int batchSize; public PutMessageContext(String topicQueueTableKey) { this.topicQueueTableKey = topicQueueTableKey; } public String getTopicQueueTableKey() { return topicQueueTableKey; } public long[] getPhyPos() { return phyPos; } public void setPhyPos(long[] phyPos) { this.phyPos = phyPos; } public int getBatchSize() { return batchSize; } public void setBatchSize(int batchSize) { this.batchSize = batchSize; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/PutMessageLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; /** * Used when trying to put message */ public interface PutMessageLock { void lock(); void unlock(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/PutMessageReentrantLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.concurrent.locks.ReentrantLock; /** * Exclusive lock implementation to put message */ public class PutMessageReentrantLock implements PutMessageLock { private ReentrantLock putMessageNormalLock = new ReentrantLock(); // NonfairSync @Override public void lock() { putMessageNormalLock.lock(); } @Override public void unlock() { putMessageNormalLock.unlock(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/PutMessageResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; public class PutMessageResult { private PutMessageStatus putMessageStatus; private AppendMessageResult appendMessageResult; private boolean remotePut = false; public PutMessageResult(PutMessageStatus putMessageStatus, AppendMessageResult appendMessageResult) { this.putMessageStatus = putMessageStatus; this.appendMessageResult = appendMessageResult; } public PutMessageResult(PutMessageStatus putMessageStatus, AppendMessageResult appendMessageResult, boolean remotePut) { this.putMessageStatus = putMessageStatus; this.appendMessageResult = appendMessageResult; this.remotePut = remotePut; } public boolean isOk() { if (remotePut) { return putMessageStatus == PutMessageStatus.PUT_OK || putMessageStatus == PutMessageStatus.FLUSH_DISK_TIMEOUT || putMessageStatus == PutMessageStatus.FLUSH_SLAVE_TIMEOUT || putMessageStatus == PutMessageStatus.SLAVE_NOT_AVAILABLE; } else { return this.appendMessageResult != null && this.appendMessageResult.isOk(); } } public AppendMessageResult getAppendMessageResult() { return appendMessageResult; } public void setAppendMessageResult(AppendMessageResult appendMessageResult) { this.appendMessageResult = appendMessageResult; } public PutMessageStatus getPutMessageStatus() { return putMessageStatus; } public void setPutMessageStatus(PutMessageStatus putMessageStatus) { this.putMessageStatus = putMessageStatus; } public boolean isRemotePut() { return remotePut; } public void setRemotePut(boolean remotePut) { this.remotePut = remotePut; } @Override public String toString() { return "PutMessageResult [putMessageStatus=" + putMessageStatus + ", appendMessageResult=" + appendMessageResult + ", remotePut=" + remotePut + "]"; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/PutMessageSpinLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.concurrent.atomic.AtomicBoolean; /** * Spin lock Implementation to put message, suggest using this with low race conditions */ public class PutMessageSpinLock implements PutMessageLock { //true: Can lock, false : in lock. private AtomicBoolean putMessageSpinLock = new AtomicBoolean(true); @Override public void lock() { boolean flag; do { flag = this.putMessageSpinLock.compareAndSet(true, false); } while (!flag); } @Override public void unlock() { this.putMessageSpinLock.compareAndSet(false, true); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/PutMessageStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; public enum PutMessageStatus { PUT_OK, FLUSH_DISK_TIMEOUT, FLUSH_SLAVE_TIMEOUT, SLAVE_NOT_AVAILABLE, SERVICE_NOT_AVAILABLE, CREATE_MAPPED_FILE_FAILED, MESSAGE_ILLEGAL, PROPERTIES_SIZE_EXCEEDED, OS_PAGE_CACHE_BUSY, UNKNOWN_ERROR, IN_SYNC_REPLICAS_NOT_ENOUGH, PUT_TO_REMOTE_BROKER_FAIL, LMQ_CONSUME_QUEUE_NUM_EXCEEDED, WHEEL_TIMER_FLOW_CONTROL, WHEEL_TIMER_MSG_ILLEGAL, WHEEL_TIMER_NOT_ENABLE } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/QueryMessageResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; public class QueryMessageResult { private final List messageMapedList = new ArrayList<>(100); private final List messageBufferList = new ArrayList<>(100); private long indexLastUpdateTimestamp; private long indexLastUpdatePhyoffset; private int bufferTotalSize = 0; public void addMessage(final SelectMappedBufferResult mapedBuffer) { this.messageMapedList.add(mapedBuffer); this.messageBufferList.add(mapedBuffer.getByteBuffer()); this.bufferTotalSize += mapedBuffer.getSize(); } public void release() { for (SelectMappedBufferResult select : this.messageMapedList) { select.release(); } } public long getIndexLastUpdateTimestamp() { return indexLastUpdateTimestamp; } public void setIndexLastUpdateTimestamp(long indexLastUpdateTimestamp) { this.indexLastUpdateTimestamp = indexLastUpdateTimestamp; } public long getIndexLastUpdatePhyoffset() { return indexLastUpdatePhyoffset; } public void setIndexLastUpdatePhyoffset(long indexLastUpdatePhyoffset) { this.indexLastUpdatePhyoffset = indexLastUpdatePhyoffset; } public List getMessageBufferList() { return messageBufferList; } public int getBufferTotalSize() { return bufferTotalSize; } public List getMessageMapedList() { return messageMapedList; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ReferenceResource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.concurrent.atomic.AtomicLong; public abstract class ReferenceResource { protected final AtomicLong refCount = new AtomicLong(1); protected volatile boolean available = true; protected volatile boolean cleanupOver = false; private volatile long firstShutdownTimestamp = 0; public synchronized boolean hold() { if (this.isAvailable()) { if (this.refCount.getAndIncrement() > 0) { return true; } else { this.refCount.getAndDecrement(); } } return false; } public boolean isAvailable() { return this.available; } public void shutdown(final long intervalForcibly) { if (this.available) { this.available = false; this.firstShutdownTimestamp = System.currentTimeMillis(); this.release(); } else if (this.getRefCount() > 0) { if ((System.currentTimeMillis() - this.firstShutdownTimestamp) >= intervalForcibly) { this.refCount.set(-1000 - this.getRefCount()); this.release(); } } } public void release() { long value = this.refCount.decrementAndGet(); if (value > 0) return; synchronized (this) { this.cleanupOver = this.cleanup(value); } } public long getRefCount() { return this.refCount.get(); } public abstract boolean cleanup(final long currentRef); public boolean isCleanupOver() { return this.refCount.get() <= 0 && this.cleanupOver; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/RocksDBMessageStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.IOException; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class RocksDBMessageStore extends DefaultMessageStore { public RocksDBMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager, final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig, final ConcurrentMap topicConfigTable) throws IOException { super(messageStoreConfig, brokerStatsManager, messageArrivingListener, brokerConfig, topicConfigTable); } @Override public ConsumeQueueStoreInterface createConsumeQueueStore() { return new RocksDBConsumeQueueStore(this); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/RunningFlags.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; public class RunningFlags { private static final int NOT_READABLE_BIT = 1; private static final int NOT_WRITEABLE_BIT = 1 << 1; private static final int WRITE_LOGICS_QUEUE_ERROR_BIT = 1 << 2; private static final int WRITE_INDEX_FILE_ERROR_BIT = 1 << 3; private static final int DISK_FULL_BIT = 1 << 4; private static final int FENCED_BIT = 1 << 5; private static final int LOGIC_DISK_FULL_BIT = 1 << 6; private volatile int flagBits = 0; public RunningFlags() { } public int getFlagBits() { return flagBits; } public boolean getAndMakeReadable() { boolean result = this.isReadable(); if (!result) { this.flagBits &= ~NOT_READABLE_BIT; } return result; } public boolean isReadable() { return (this.flagBits & NOT_READABLE_BIT) == 0; } public boolean isFenced() { return (this.flagBits & FENCED_BIT) != 0; } public boolean getAndMakeNotReadable() { boolean result = this.isReadable(); if (result) { this.flagBits |= NOT_READABLE_BIT; } return result; } public void clearLogicsQueueError() { this.flagBits &= ~WRITE_LOGICS_QUEUE_ERROR_BIT; } public boolean getAndMakeWriteable() { boolean result = this.isWriteable(); if (!result) { this.flagBits &= ~NOT_WRITEABLE_BIT; } return result; } public boolean isWriteable() { if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | DISK_FULL_BIT | WRITE_INDEX_FILE_ERROR_BIT | FENCED_BIT | LOGIC_DISK_FULL_BIT)) == 0) { return true; } return false; } public boolean isStoreWriteable() { if ((this.flagBits & NOT_WRITEABLE_BIT) == 0) { return true; } return false; } //for consume queue, just ignore the DISK_FULL_BIT public boolean isCQWriteable() { if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | WRITE_INDEX_FILE_ERROR_BIT | LOGIC_DISK_FULL_BIT)) == 0) { return true; } return false; } public boolean getAndMakeStoreNotWriteable() { boolean result = this.isWriteable(); if (result) { this.flagBits |= NOT_WRITEABLE_BIT; } return result; } public void makeLogicsQueueError() { this.flagBits |= WRITE_LOGICS_QUEUE_ERROR_BIT; } public void makeFenced(boolean fenced) { if (fenced) { this.flagBits |= FENCED_BIT; } else { this.flagBits &= ~FENCED_BIT; } } public boolean isLogicsQueueError() { if ((this.flagBits & WRITE_LOGICS_QUEUE_ERROR_BIT) == WRITE_LOGICS_QUEUE_ERROR_BIT) { return true; } return false; } public void makeIndexFileError() { this.flagBits |= WRITE_INDEX_FILE_ERROR_BIT; } public boolean isIndexFileError() { if ((this.flagBits & WRITE_INDEX_FILE_ERROR_BIT) == WRITE_INDEX_FILE_ERROR_BIT) { return true; } return false; } public boolean getAndMakeDiskFull() { boolean result = !((this.flagBits & DISK_FULL_BIT) == DISK_FULL_BIT); this.flagBits |= DISK_FULL_BIT; return result; } public boolean getAndMakeDiskOK() { boolean result = !((this.flagBits & DISK_FULL_BIT) == DISK_FULL_BIT); this.flagBits &= ~DISK_FULL_BIT; return result; } public boolean getAndMakeLogicDiskFull() { boolean result = !((this.flagBits & LOGIC_DISK_FULL_BIT) == LOGIC_DISK_FULL_BIT); this.flagBits |= LOGIC_DISK_FULL_BIT; return result; } public boolean getAndMakeLogicDiskOK() { boolean result = !((this.flagBits & LOGIC_DISK_FULL_BIT) == LOGIC_DISK_FULL_BIT); this.flagBits &= ~LOGIC_DISK_FULL_BIT; return result; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import org.apache.rocketmq.store.logfile.MappedFile; public class SelectMappedBufferResult { private final long startOffset; private final ByteBuffer byteBuffer; private int size; protected MappedFile mappedFile; private boolean isInCache = true; public SelectMappedBufferResult(long startOffset, ByteBuffer byteBuffer, int size, MappedFile mappedFile) { this.startOffset = startOffset; this.byteBuffer = byteBuffer; this.size = size; this.mappedFile = mappedFile; } public ByteBuffer getByteBuffer() { return byteBuffer; } public int getSize() { return size; } public void setSize(final int s) { this.size = s; this.byteBuffer.limit(this.size); } public MappedFile getMappedFile() { return mappedFile; } public synchronized void release() { if (this.mappedFile != null) { this.mappedFile.release(); this.mappedFile = null; } } public synchronized boolean hasReleased() { return this.mappedFile == null; } public long getStartOffset() { return startOffset; } public boolean isInMem() { if (mappedFile == null) { return true; } long pos = startOffset - mappedFile.getFileFromOffset(); return mappedFile.isLoaded(pos, size); } public boolean isInCache() { return isInCache; } public void setInCache(boolean inCache) { isInCache = inCache; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/SelectMappedFileResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.store.logfile.MappedFile; public class SelectMappedFileResult { protected int size; protected MappedFile mappedFile; public SelectMappedFileResult(int size, MappedFile mappedFile) { this.size = size; this.mappedFile = mappedFile; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public MappedFile getMappedFile() { return mappedFile; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/StoreCheckpoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.logfile.DefaultMappedFile; public class StoreCheckpoint { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final RandomAccessFile randomAccessFile; private final FileChannel fileChannel; private final MappedByteBuffer mappedByteBuffer; private volatile long tmpLogicsMsgTimestamp = 0; private volatile long physicMsgTimestamp = 0; private volatile long logicsMsgTimestamp = 0; private volatile long tmpLogicsPhysicalOffset = 0; private volatile long logicsPhysicalOffset = 0; private volatile long indexMsgTimestamp = 0; private volatile long masterFlushedOffset = 0; private volatile long confirmPhyOffset = 0; public StoreCheckpoint(final String scpPath) throws IOException { File file = new File(scpPath); UtilAll.ensureDirOK(file.getParent()); boolean fileExists = file.exists(); this.randomAccessFile = new RandomAccessFile(file, "rw"); this.fileChannel = this.randomAccessFile.getChannel(); this.mappedByteBuffer = fileChannel.map(MapMode.READ_WRITE, 0, DefaultMappedFile.OS_PAGE_SIZE); if (fileExists) { log.info("store checkpoint file exists, " + scpPath); this.physicMsgTimestamp = this.mappedByteBuffer.getLong(0); this.logicsMsgTimestamp = this.mappedByteBuffer.getLong(8); this.indexMsgTimestamp = this.mappedByteBuffer.getLong(16); this.masterFlushedOffset = this.mappedByteBuffer.getLong(24); this.confirmPhyOffset = this.mappedByteBuffer.getLong(32); this.logicsPhysicalOffset = this.mappedByteBuffer.getLong(40); log.info("store checkpoint file physicMsgTimestamp " + this.physicMsgTimestamp + ", " + UtilAll.timeMillisToHumanString(this.physicMsgTimestamp)); log.info("store checkpoint file logicsMsgTimestamp " + this.logicsMsgTimestamp + ", " + UtilAll.timeMillisToHumanString(this.logicsMsgTimestamp)); log.info("store checkpoint file indexMsgTimestamp " + this.indexMsgTimestamp + ", " + UtilAll.timeMillisToHumanString(this.indexMsgTimestamp)); log.info("store checkpoint file masterFlushedOffset " + this.masterFlushedOffset); log.info("store checkpoint file confirmPhyOffset " + this.confirmPhyOffset); log.info("store checkpoint file logicsPhysicalOffset " + this.logicsPhysicalOffset); } else { log.info("store checkpoint file not exists, " + scpPath); } } public void shutdown() { this.flush(); // unmap mappedByteBuffer UtilAll.cleanBuffer(this.mappedByteBuffer); try { this.fileChannel.close(); } catch (Throwable e) { log.error("Failed to close file channel", e); } } public void flush() { try { this.mappedByteBuffer.putLong(0, this.physicMsgTimestamp); this.mappedByteBuffer.putLong(8, this.logicsMsgTimestamp); this.mappedByteBuffer.putLong(16, this.indexMsgTimestamp); this.mappedByteBuffer.putLong(24, this.masterFlushedOffset); this.mappedByteBuffer.putLong(32, this.confirmPhyOffset); this.mappedByteBuffer.putLong(40, this.logicsPhysicalOffset); this.mappedByteBuffer.force(); } catch (Throwable e) { log.error("Failed to flush", e); } } public long getPhysicMsgTimestamp() { return physicMsgTimestamp; } public void setPhysicMsgTimestamp(long physicMsgTimestamp) { this.physicMsgTimestamp = physicMsgTimestamp; } public long getLogicsMsgTimestamp() { return logicsMsgTimestamp; } public void setLogicsMsgTimestamp(long logicsMsgTimestamp) { this.logicsMsgTimestamp = logicsMsgTimestamp; } public long getTmpLogicsMsgTimestamp() { return tmpLogicsMsgTimestamp; } public void setTmpLogicsMsgTimestamp(long tmpLogicsMsgTimestamp) { this.tmpLogicsMsgTimestamp = tmpLogicsMsgTimestamp; } public long getTmpLogicsPhysicalOffset() { return tmpLogicsPhysicalOffset; } public void setTmpLogicsPhysicalOffset(long tmpLogicsPhysicalOffset) { this.tmpLogicsPhysicalOffset = tmpLogicsPhysicalOffset; } public long getLogicsPhysicalOffset() { return logicsPhysicalOffset; } public void setLogicsPhysicalOffset(long logicsPhysicalOffset) { this.logicsPhysicalOffset = logicsPhysicalOffset; } public long getConfirmPhyOffset() { return confirmPhyOffset; } public void setConfirmPhyOffset(long confirmPhyOffset) { this.confirmPhyOffset = confirmPhyOffset; } public long getMinTimestampIndex() { return Math.min(this.getMinTimestamp(), this.indexMsgTimestamp); } public long getMinTimestamp() { long min = Math.min(this.physicMsgTimestamp, this.logicsMsgTimestamp); min -= 1000 * 3; if (min < 0) { min = 0; } return min; } public long getIndexMsgTimestamp() { return indexMsgTimestamp; } public void setIndexMsgTimestamp(long indexMsgTimestamp) { this.indexMsgTimestamp = indexMsgTimestamp; } public long getMasterFlushedOffset() { return masterFlushedOffset; } public void setMasterFlushedOffset(long masterFlushedOffset) { this.masterFlushedOffset = masterFlushedOffset; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/StoreStatsService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.ReentrantLock; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class StoreStatsService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final int FREQUENCY_OF_SAMPLING = 1000; private static final int MAX_RECORDS_OF_SAMPLING = 60 * 10; private static final String[] PUT_MESSAGE_ENTIRE_TIME_MAX_DESC = new String[] { "[<=0ms]", "[0~10ms]", "[10~50ms]", "[50~100ms]", "[100~200ms]", "[200~500ms]", "[500ms~1s]", "[1~2s]", "[2~3s]", "[3~4s]", "[4~5s]", "[5~10s]", "[10s~]", }; //The rule to define buckets private static final Map PUT_MESSAGE_ENTIRE_TIME_BUCKETS = new TreeMap<>(); //buckets private TreeMap buckets = new TreeMap<>(); private Map lastBuckets = new TreeMap<>(); private static int printTPSInterval = 60 * 1; private final LongAdder putMessageFailedTimes = new LongAdder(); private final ConcurrentMap putMessageTopicTimesTotal = new ConcurrentHashMap<>(128); private final ConcurrentMap putMessageTopicSizeTotal = new ConcurrentHashMap<>(128); private final LongAdder getMessageTimesTotalFound = new LongAdder(); private final LongAdder getMessageTransferredMsgCount = new LongAdder(); private final LongAdder getMessageTimesTotalMiss = new LongAdder(); private final LinkedList putTimesList = new LinkedList<>(); private final LinkedList getTimesFoundList = new LinkedList<>(); private final LinkedList getTimesMissList = new LinkedList<>(); private final LinkedList transferredMsgCountList = new LinkedList<>(); private volatile LongAdder[] putMessageDistributeTime; private volatile LongAdder[] lastPutMessageDistributeTime; private long messageStoreBootTimestamp = System.currentTimeMillis(); private volatile long putMessageEntireTimeMax = 0; private volatile long getMessageEntireTimeMax = 0; // for putMessageEntireTimeMax private ReentrantLock putLock = new ReentrantLock(); // for getMessageEntireTimeMax private ReentrantLock getLock = new ReentrantLock(); private volatile long dispatchMaxBuffer = 0; private ReentrantLock samplingLock = new ReentrantLock(); private long lastPrintTimestamp = System.currentTimeMillis(); private BrokerIdentity brokerIdentity; public StoreStatsService(BrokerIdentity brokerIdentity) { this(); this.brokerIdentity = brokerIdentity; } public StoreStatsService() { PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(1,20); //0-20 PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(2,15); //20-50 PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(5,10); //50-100 PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(10,10); //100-200 PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(50,6); //200-500 PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(100,5); //500-1000 PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(1000,9); //1s-10s this.resetPutMessageTimeBuckets(); this.resetPutMessageDistributeTime(); } private void resetPutMessageTimeBuckets() { TreeMap nextBuckets = new TreeMap<>(); AtomicLong index = new AtomicLong(0); PUT_MESSAGE_ENTIRE_TIME_BUCKETS.forEach((interval, times) -> { for (int i = 0; i < times; i++) { nextBuckets.put(index.addAndGet(interval), new LongAdder()); } }); nextBuckets.put(Long.MAX_VALUE, new LongAdder()); this.lastBuckets = this.buckets; this.buckets = nextBuckets; } public void incPutMessageEntireTime(long value) { Map.Entry targetBucket = buckets.ceilingEntry(value); if (targetBucket != null) { targetBucket.getValue().add(1); } } public double findPutMessageEntireTimePX(double px) { Map lastBuckets = this.lastBuckets; long start = System.currentTimeMillis(); double result = 0.0; long totalRequest = lastBuckets.values().stream().mapToLong(LongAdder::longValue).sum(); long pxIndex = (long) (totalRequest * px); long passCount = 0; List bucketValue = new ArrayList<>(lastBuckets.keySet()); for (int i = 0; i < bucketValue.size(); i++) { long count = lastBuckets.get(bucketValue.get(i)).longValue(); if (pxIndex <= passCount + count) { long relativeIndex = pxIndex - passCount; if (i == 0) { result = count == 0 ? 0 : bucketValue.get(i) * relativeIndex / (double)count; } else { long lastBucket = bucketValue.get(i - 1); result = lastBucket + (count == 0 ? 0 : (bucketValue.get(i) - lastBucket) * relativeIndex / (double)count); } break; } else { passCount += count; } } log.info("findPutMessageEntireTimePX {}={}ms cost {}ms", px, String.format("%.2f", result), System.currentTimeMillis() - start); return result; } private LongAdder[] resetPutMessageDistributeTime() { LongAdder[] next = new LongAdder[13]; for (int i = 0; i < next.length; i++) { next[i] = new LongAdder(); } this.lastPutMessageDistributeTime = this.putMessageDistributeTime; this.putMessageDistributeTime = next; return lastPutMessageDistributeTime; } public long getPutMessageEntireTimeMax() { return putMessageEntireTimeMax; } public void setPutMessageEntireTimeMax(long value) { this.incPutMessageEntireTime(value); final LongAdder[] times = this.putMessageDistributeTime; if (null == times) return; // us if (value <= 0) { times[0].add(1); } else if (value < 10) { times[1].add(1); } else if (value < 50) { times[2].add(1); } else if (value < 100) { times[3].add(1); } else if (value < 200) { times[4].add(1); } else if (value < 500) { times[5].add(1); } else if (value < 1000) { times[6].add(1); } // 2s else if (value < 2000) { times[7].add(1); } // 3s else if (value < 3000) { times[8].add(1); } // 4s else if (value < 4000) { times[9].add(1); } // 5s else if (value < 5000) { times[10].add(1); } // 10s else if (value < 10000) { times[11].add(1); } else { times[12].add(1); } if (value > this.putMessageEntireTimeMax) { this.putLock.lock(); this.putMessageEntireTimeMax = value > this.putMessageEntireTimeMax ? value : this.putMessageEntireTimeMax; this.putLock.unlock(); } } public long getGetMessageEntireTimeMax() { return getMessageEntireTimeMax; } public void setGetMessageEntireTimeMax(long value) { if (value > this.getMessageEntireTimeMax) { this.getLock.lock(); this.getMessageEntireTimeMax = value > this.getMessageEntireTimeMax ? value : this.getMessageEntireTimeMax; this.getLock.unlock(); } } public long getDispatchMaxBuffer() { return dispatchMaxBuffer; } public void setDispatchMaxBuffer(long value) { this.dispatchMaxBuffer = value > this.dispatchMaxBuffer ? value : this.dispatchMaxBuffer; } @Override public String toString() { final StringBuilder sb = new StringBuilder(1024); Long totalTimes = getPutMessageTimesTotal(); if (0 == totalTimes) { totalTimes = 1L; } sb.append("\truntime: " + this.getFormatRuntime() + "\r\n"); sb.append("\tputMessageEntireTimeMax: " + this.putMessageEntireTimeMax + "\r\n"); sb.append("\tputMessageTimesTotal: " + totalTimes + "\r\n"); sb.append("\tgetPutMessageFailedTimes: " + this.getPutMessageFailedTimes() + "\r\n"); sb.append("\tputMessageSizeTotal: " + this.getPutMessageSizeTotal() + "\r\n"); sb.append("\tputMessageDistributeTime: " + this.getPutMessageDistributeTimeStringInfo(totalTimes) + "\r\n"); sb.append("\tputMessageAverageSize: " + (this.getPutMessageSizeTotal() / totalTimes.doubleValue()) + "\r\n"); sb.append("\tdispatchMaxBuffer: " + this.dispatchMaxBuffer + "\r\n"); sb.append("\tgetMessageEntireTimeMax: " + this.getMessageEntireTimeMax + "\r\n"); sb.append("\tputTps: " + this.getPutTps() + "\r\n"); sb.append("\tgetFoundTps: " + this.getGetFoundTps() + "\r\n"); sb.append("\tgetMissTps: " + this.getGetMissTps() + "\r\n"); sb.append("\tgetTotalTps: " + this.getGetTotalTps() + "\r\n"); sb.append("\tgetTransferredTps: " + this.getGetTransferredTps() + "\r\n"); return sb.toString(); } public long getPutMessageTimesTotal() { Map map = putMessageTopicTimesTotal; return map.values() .parallelStream() .mapToLong(LongAdder::longValue) .sum(); } private String getFormatRuntime() { final long millisecond = 1; final long second = 1000 * millisecond; final long minute = 60 * second; final long hour = 60 * minute; final long day = 24 * hour; final MessageFormat messageFormat = new MessageFormat("[ {0} days, {1} hours, {2} minutes, {3} seconds ]"); long time = System.currentTimeMillis() - this.messageStoreBootTimestamp; long days = time / day; long hours = (time % day) / hour; long minutes = (time % hour) / minute; long seconds = (time % minute) / second; return messageFormat.format(new Long[] {days, hours, minutes, seconds}); } public long getPutMessageSizeTotal() { Map map = putMessageTopicSizeTotal; return map.values() .parallelStream() .mapToLong(LongAdder::longValue) .sum(); } private String getPutMessageDistributeTimeStringInfo(Long total) { return this.putMessageDistributeTimeToString(); } private String getPutTps() { StringBuilder sb = new StringBuilder(); sb.append(this.getPutTps(10)); sb.append(" "); sb.append(this.getPutTps(60)); sb.append(" "); sb.append(this.getPutTps(600)); return sb.toString(); } private String getGetFoundTps() { StringBuilder sb = new StringBuilder(); sb.append(this.getGetFoundTps(10)); sb.append(" "); sb.append(this.getGetFoundTps(60)); sb.append(" "); sb.append(this.getGetFoundTps(600)); return sb.toString(); } private String getGetMissTps() { StringBuilder sb = new StringBuilder(); sb.append(this.getGetMissTps(10)); sb.append(" "); sb.append(this.getGetMissTps(60)); sb.append(" "); sb.append(this.getGetMissTps(600)); return sb.toString(); } private String getGetTotalTps() { StringBuilder sb = new StringBuilder(); sb.append(this.getGetTotalTps(10)); sb.append(" "); sb.append(this.getGetTotalTps(60)); sb.append(" "); sb.append(this.getGetTotalTps(600)); return sb.toString(); } private String getGetTransferredTps() { StringBuilder sb = new StringBuilder(); sb.append(this.getGetTransferredTps(10)); sb.append(" "); sb.append(this.getGetTransferredTps(60)); sb.append(" "); sb.append(this.getGetTransferredTps(600)); return sb.toString(); } private String putMessageDistributeTimeToString() { final LongAdder[] times = this.lastPutMessageDistributeTime; if (null == times) return null; final StringBuilder sb = new StringBuilder(); for (int i = 0; i < times.length; i++) { long value = times[i].longValue(); sb.append(String.format("%s:%d", PUT_MESSAGE_ENTIRE_TIME_MAX_DESC[i], value)); sb.append(" "); } return sb.toString(); } private String getPutTps(int time) { String result = ""; this.samplingLock.lock(); try { CallSnapshot last = this.putTimesList.getLast(); if (this.putTimesList.size() > time) { CallSnapshot lastBefore = this.putTimesList.get(this.putTimesList.size() - (time + 1)); result += CallSnapshot.getTPS(lastBefore, last); } } finally { this.samplingLock.unlock(); } return result; } private String getGetFoundTps(int time) { String result = ""; this.samplingLock.lock(); try { CallSnapshot last = this.getTimesFoundList.getLast(); if (this.getTimesFoundList.size() > time) { CallSnapshot lastBefore = this.getTimesFoundList.get(this.getTimesFoundList.size() - (time + 1)); result += CallSnapshot.getTPS(lastBefore, last); } } finally { this.samplingLock.unlock(); } return result; } private String getGetMissTps(int time) { String result = ""; this.samplingLock.lock(); try { CallSnapshot last = this.getTimesMissList.getLast(); if (this.getTimesMissList.size() > time) { CallSnapshot lastBefore = this.getTimesMissList.get(this.getTimesMissList.size() - (time + 1)); result += CallSnapshot.getTPS(lastBefore, last); } } finally { this.samplingLock.unlock(); } return result; } private String getGetTotalTps(int time) { this.samplingLock.lock(); double found = 0; double miss = 0; try { { CallSnapshot last = this.getTimesFoundList.getLast(); if (this.getTimesFoundList.size() > time) { CallSnapshot lastBefore = this.getTimesFoundList.get(this.getTimesFoundList.size() - (time + 1)); found = CallSnapshot.getTPS(lastBefore, last); } } { CallSnapshot last = this.getTimesMissList.getLast(); if (this.getTimesMissList.size() > time) { CallSnapshot lastBefore = this.getTimesMissList.get(this.getTimesMissList.size() - (time + 1)); miss = CallSnapshot.getTPS(lastBefore, last); } } } finally { this.samplingLock.unlock(); } return Double.toString(found + miss); } private String getGetTransferredTps(int time) { String result = ""; this.samplingLock.lock(); try { CallSnapshot last = this.transferredMsgCountList.getLast(); if (this.transferredMsgCountList.size() > time) { CallSnapshot lastBefore = this.transferredMsgCountList.get(this.transferredMsgCountList.size() - (time + 1)); result += CallSnapshot.getTPS(lastBefore, last); } } finally { this.samplingLock.unlock(); } return result; } public HashMap getRuntimeInfo() { HashMap result = new HashMap<>(64); Long totalTimes = getPutMessageTimesTotal(); if (0 == totalTimes) { totalTimes = 1L; } result.put("bootTimestamp", String.valueOf(this.messageStoreBootTimestamp)); result.put("runtime", this.getFormatRuntime()); result.put("putMessageEntireTimeMax", String.valueOf(this.putMessageEntireTimeMax)); result.put("putMessageTimesTotal", String.valueOf(totalTimes)); result.put("putMessageFailedTimes", String.valueOf(this.putMessageFailedTimes)); result.put("putMessageSizeTotal", String.valueOf(this.getPutMessageSizeTotal())); result.put("putMessageDistributeTime", String.valueOf(this.getPutMessageDistributeTimeStringInfo(totalTimes))); result.put("putMessageAverageSize", String.valueOf(this.getPutMessageSizeTotal() / totalTimes.doubleValue())); result.put("dispatchMaxBuffer", String.valueOf(this.dispatchMaxBuffer)); result.put("getMessageEntireTimeMax", String.valueOf(this.getMessageEntireTimeMax)); result.put("putTps", this.getPutTps()); result.put("getFoundTps", this.getGetFoundTps()); result.put("getMissTps", this.getGetMissTps()); result.put("getTotalTps", this.getGetTotalTps()); result.put("getTransferredTps", this.getGetTransferredTps()); result.put("putLatency99", String.format("%.2f", this.findPutMessageEntireTimePX(0.99))); result.put("putLatency999", String.format("%.2f", this.findPutMessageEntireTimePX(0.999))); return result; } public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.waitForRunning(FREQUENCY_OF_SAMPLING); this.sampling(); this.printTps(); } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (this.brokerIdentity != null && this.brokerIdentity.isInBrokerContainer()) { return brokerIdentity.getIdentifier() + StoreStatsService.class.getSimpleName(); } return StoreStatsService.class.getSimpleName(); } private void sampling() { this.samplingLock.lock(); try { this.putTimesList.add(new CallSnapshot(System.currentTimeMillis(), getPutMessageTimesTotal())); if (this.putTimesList.size() > (MAX_RECORDS_OF_SAMPLING + 1)) { this.putTimesList.removeFirst(); } this.getTimesFoundList.add(new CallSnapshot(System.currentTimeMillis(), this.getMessageTimesTotalFound.longValue())); if (this.getTimesFoundList.size() > (MAX_RECORDS_OF_SAMPLING + 1)) { this.getTimesFoundList.removeFirst(); } this.getTimesMissList.add(new CallSnapshot(System.currentTimeMillis(), this.getMessageTimesTotalMiss.longValue())); if (this.getTimesMissList.size() > (MAX_RECORDS_OF_SAMPLING + 1)) { this.getTimesMissList.removeFirst(); } this.transferredMsgCountList.add(new CallSnapshot(System.currentTimeMillis(), this.getMessageTransferredMsgCount.longValue())); if (this.transferredMsgCountList.size() > (MAX_RECORDS_OF_SAMPLING + 1)) { this.transferredMsgCountList.removeFirst(); } } finally { this.samplingLock.unlock(); } } private void printTps() { if (System.currentTimeMillis() > (this.lastPrintTimestamp + printTPSInterval * 1000)) { this.lastPrintTimestamp = System.currentTimeMillis(); log.info("[STORETPS] put_tps {} get_found_tps {} get_miss_tps {} get_transferred_tps {}", this.getPutTps(printTPSInterval), this.getGetFoundTps(printTPSInterval), this.getGetMissTps(printTPSInterval), this.getGetTransferredTps(printTPSInterval) ); final LongAdder[] times = this.resetPutMessageDistributeTime(); if (null == times) return; final StringBuilder sb = new StringBuilder(); long totalPut = 0; for (int i = 0; i < times.length; i++) { long value = times[i].longValue(); totalPut += value; sb.append(String.format("%s:%d", PUT_MESSAGE_ENTIRE_TIME_MAX_DESC[i], value)); sb.append(" "); } this.resetPutMessageTimeBuckets(); this.findPutMessageEntireTimePX(0.99); this.findPutMessageEntireTimePX(0.999); log.info("[PAGECACHERT] TotalPut {}, PutMessageDistributeTime {}", totalPut, sb.toString()); } } public LongAdder getGetMessageTimesTotalFound() { return getMessageTimesTotalFound; } public LongAdder getGetMessageTimesTotalMiss() { return getMessageTimesTotalMiss; } public LongAdder getGetMessageTransferredMsgCount() { return getMessageTransferredMsgCount; } public LongAdder getPutMessageFailedTimes() { return putMessageFailedTimes; } public LongAdder getSinglePutMessageTopicSizeTotal(String topic) { LongAdder rs = putMessageTopicSizeTotal.get(topic); if (null == rs) { rs = new LongAdder(); LongAdder previous = putMessageTopicSizeTotal.putIfAbsent(topic, rs); if (previous != null) { rs = previous; } } return rs; } public LongAdder getSinglePutMessageTopicTimesTotal(String topic) { LongAdder rs = putMessageTopicTimesTotal.get(topic); if (null == rs) { rs = new LongAdder(); LongAdder previous = putMessageTopicTimesTotal.putIfAbsent(topic, rs); if (previous != null) { rs = previous; } } return rs; } public Map getPutMessageTopicTimesTotal() { return putMessageTopicTimesTotal; } public Map getPutMessageTopicSizeTotal() { return putMessageTopicSizeTotal; } static class CallSnapshot { public final long timestamp; public final long callTimesTotal; public CallSnapshot(long timestamp, long callTimesTotal) { this.timestamp = timestamp; this.callTimesTotal = callTimesTotal; } public static double getTPS(final CallSnapshot begin, final CallSnapshot end) { long total = end.callTimesTotal - begin.callTimesTotal; Long time = end.timestamp - begin.timestamp; double tps = total / time.doubleValue(); return tps * 1000; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/StoreType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.Arrays; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; public enum StoreType { DEFAULT("default"), DEFAULT_ROCKSDB("defaultRocksDB"); private String storeType; StoreType(String storeType) { this.storeType = storeType; } public String getStoreType() { return storeType; } /** * convert string to set of StoreType * * @param str example "default;defaultRocksDB" * @return set of StoreType */ public static Set fromString(String str) { if (str == null || str.trim().isEmpty()) { return Collections.emptySet(); } return Arrays.stream(str.split(";")) .map(String::trim) .filter(s -> !s.isEmpty()) .map(s -> Arrays.stream(StoreType.values()) .filter(type -> type.getStoreType().equalsIgnoreCase(s)) .findFirst() .orElse(null)) .filter(Objects::nonNull) .collect(Collectors.toSet()); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/StoreUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import com.google.common.base.Preconditions; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.logfile.MappedFile; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; import java.nio.ByteBuffer; import static java.lang.String.format; public class StoreUtil { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public static final long TOTAL_PHYSICAL_MEMORY_SIZE = getTotalPhysicalMemorySize(); @SuppressWarnings("restriction") public static long getTotalPhysicalMemorySize() { long physicalTotal = 1024 * 1024 * 1024 * 24L; OperatingSystemMXBean osmxb = ManagementFactory.getOperatingSystemMXBean(); if (osmxb instanceof com.sun.management.OperatingSystemMXBean) { physicalTotal = ((com.sun.management.OperatingSystemMXBean) osmxb).getTotalPhysicalMemorySize(); } return physicalTotal; } public static void fileAppend(MappedFile file, ByteBuffer data) { boolean success = file.appendMessage(data); if (!success) { throw new RuntimeException(format("fileAppend failed for file: %s and data remaining: %d", file, data.remaining())); } } public static FileQueueSnapshot getFileQueueSnapshot(MappedFileQueue mappedFileQueue) { return getFileQueueSnapshot(mappedFileQueue, mappedFileQueue.getLastMappedFile().getFileFromOffset()); } public static FileQueueSnapshot getFileQueueSnapshot(MappedFileQueue mappedFileQueue, final long currentFile) { try { Preconditions.checkNotNull(mappedFileQueue, "file queue shouldn't be null"); MappedFile firstFile = mappedFileQueue.getFirstMappedFile(); MappedFile lastFile = mappedFileQueue.getLastMappedFile(); int mappedFileSize = mappedFileQueue.getMappedFileSize(); if (firstFile == null || lastFile == null) { return new FileQueueSnapshot(firstFile, -1, lastFile, -1, currentFile, -1, 0, false); } long firstFileIndex = 0; long lastFileIndex = (lastFile.getFileFromOffset() - firstFile.getFileFromOffset()) / mappedFileSize; long currentFileIndex = (currentFile - firstFile.getFileFromOffset()) / mappedFileSize; long behind = (lastFile.getFileFromOffset() - currentFile) / mappedFileSize; boolean exist = firstFile.getFileFromOffset() <= currentFile && currentFile <= lastFile.getFileFromOffset(); return new FileQueueSnapshot(firstFile, firstFileIndex, lastFile, lastFileIndex, currentFile, currentFileIndex, behind, exist); } catch (Exception e) { log.error("[BUG] get file queue snapshot failed. fileQueue: {}, currentFile: {}", mappedFileQueue, currentFile, e); } return new FileQueueSnapshot(); } public static MessageExt getMessage(long offsetPy, int sizePy, MessageStore messageStore, ByteBuffer byteBuffer) { try { if (offsetPy < 0L || sizePy <= 0 || null == messageStore || null == byteBuffer) { return null; } byteBuffer.position(0); byteBuffer.limit(sizePy); if (!messageStore.getData(offsetPy, sizePy, byteBuffer)) { return null; } byteBuffer.flip(); return MessageDecoder.decode(byteBuffer, true, false, false); } catch (Exception e) { log.error("getMessage error, offsetPy: {}, sizePy: {}, error: {}", offsetPy, sizePy, e.getMessage()); } return null; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/Swappable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; /** * Clean up page-table on super large disk */ public interface Swappable { void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs); void cleanSwappedMap(long forceCleanSwapIntervalMs); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TopicQueueLock { private final int size; private final List lockList; public TopicQueueLock() { this.size = 32; this.lockList = new ArrayList<>(32); for (int i = 0; i < this.size; i++) { this.lockList.add(new ReentrantLock()); } } public TopicQueueLock(int size) { this.size = size; this.lockList = new ArrayList<>(size); for (int i = 0; i < this.size; i++) { this.lockList.add(new ReentrantLock()); } } public void lock(String topicQueueKey) { Lock lock = this.lockList.get((topicQueueKey.hashCode() & 0x7fffffff) % this.size); lock.lock(); } public void unlock(String topicQueueKey) { Lock lock = this.lockList.get((topicQueueKey.hashCode() & 0x7fffffff) % this.size); lock.unlock(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.nio.ByteBuffer; import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import io.netty.util.internal.PlatformDependent; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.util.LibC; public class TransientStorePool { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final int poolSize; private final int fileSize; private final Deque availableBuffers; private volatile boolean isRealCommit = true; public TransientStorePool(final int poolSize, final int fileSize) { this.poolSize = poolSize; this.fileSize = fileSize; this.availableBuffers = new ConcurrentLinkedDeque<>(); } /** * It's a heavy init method. */ public void init() { for (int i = 0; i < poolSize; i++) { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(fileSize); final long address = PlatformDependent.directBufferAddress(byteBuffer); Pointer pointer = new Pointer(address); LibC.INSTANCE.mlock(pointer, new NativeLong(fileSize)); availableBuffers.offer(byteBuffer); } } public void destroy() { for (ByteBuffer byteBuffer : availableBuffers) { final long address = PlatformDependent.directBufferAddress(byteBuffer); Pointer pointer = new Pointer(address); LibC.INSTANCE.munlock(pointer, new NativeLong(fileSize)); } } public void returnBuffer(ByteBuffer byteBuffer) { byteBuffer.position(0); byteBuffer.limit(fileSize); this.availableBuffers.offerFirst(byteBuffer); } public ByteBuffer borrowBuffer() { ByteBuffer buffer = availableBuffers.pollFirst(); if (availableBuffers.size() < poolSize * 0.4) { log.warn("TransientStorePool only remain {} sheets.", availableBuffers.size()); } return buffer; } public int availableBufferNums() { return availableBuffers.size(); } public boolean isRealCommit() { return isRealCommit; } public void setRealCommit(boolean realCommit) { isRealCommit = realCommit; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/config/BrokerRole.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.config; public enum BrokerRole { ASYNC_MASTER, SYNC_MASTER, SLAVE; } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/config/FlushDiskType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.config; public enum FlushDiskType { SYNC_FLUSH, ASYNC_FLUSH } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.config; import java.io.File; import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.StoreType; import org.apache.rocketmq.store.queue.BatchConsumeQueue; import org.rocksdb.CompressionType; import org.rocksdb.util.SizeUnit; public class MessageStoreConfig { public static final String MULTI_PATH_SPLITTER = System.getProperty("rocketmq.broker.multiPathSplitter", ","); //The root directory in which the log data is kept @ImportantField private String storePathRootDir = System.getProperty("user.home") + File.separator + "store"; //The directory in which the commitlog is kept @ImportantField private String storePathCommitLog = null; @ImportantField private String storePathDLedgerCommitLog = null; //The directory in which the epochFile is kept @ImportantField private String storePathEpochFile = null; @ImportantField private String storePathBrokerIdentity = null; private String readOnlyCommitLogStorePaths = null; // CommitLog file size,default is 1G private int mappedFileSizeCommitLog = 1024 * 1024 * 1024; // CompactionLog file size, default is 100M private int compactionMappedFileSize = 100 * 1024 * 1024; // CompactionLog consumeQueue file size, default is 10M private int compactionCqMappedFileSize = 10 * 1024 * 1024; private int compactionScheduleInternal = 15 * 60 * 1000; private int maxOffsetMapSize = 100 * 1024 * 1024; private int compactionThreadNum = 6; private boolean enableCompaction = true; // TimerLog file size, default is 100M private int mappedFileSizeTimerLog = 100 * 1024 * 1024; private int timerPrecisionMs = 1000; private int timerRollWindowSlot = 3600 * 24 * 2; private int timerFlushIntervalMs = 1000; private int timerGetMessageThreadNum = 3; private int timerPutMessageThreadNum = 3; private boolean timerEnableDisruptor = false; private boolean timerEnableCheckMetrics = true; private boolean timerInterceptDelayLevel = false; private int timerMaxDelaySec = 3600 * 24 * 3; private boolean timerWheelSnapshotFlush = false; private boolean timerWheelEnable = true; /** * 1. Register to broker after (startTime + disappearTimeAfterStart) * 2. Internal msg exchange will start after (startTime + disappearTimeAfterStart) * A. PopReviveService * B. TimerDequeueGetService */ @ImportantField private int disappearTimeAfterStart = -1; private boolean timerStopEnqueue = false; private String timerCheckMetricsWhen = "05"; private boolean timerSkipUnknownError = false; private boolean timerWarmEnable = false; private boolean timerStopDequeue = false; private boolean timerEnableRetryUntilSuccess = false; private int timerCongestNumEachSlot = Integer.MAX_VALUE; private int timerMetricSmallThreshold = 1000000; private int timerProgressLogIntervalMs = 10 * 1000; private int timerWheelSnapshotIntervalMs = 10 * 1000; private int commitLogRecoverMaxNum = 10; private boolean timerRocksDBEnable = false; private boolean timerRocksDBStopScan = false; private long timerRocksDBPrecisionMs = 1000L; private double timerRocksDBRollMaxTps = 8000.0; private double timerRocksDBTimeExpiredMaxTps = 200000.0; private int timerRocksDBRollIntervalHours = 1; private int timerRocksDBRollRangeHours = 2; private boolean timerRecallToTimeWheelEnable = true; private boolean timerRecallToTimelineEnable = true; private int timerReputServiceCorePoolSize = 6; private int timerReputServiceMaxPoolSize = 6; private int timerReputServiceQueueCapacity = 10000; private boolean transRocksDBEnable = false; private boolean transWriteOriginTransHalfEnable = true; private boolean indexRocksDBEnable = false; private int maxRocksDBIndexQueryDays = 7; private boolean indexFileWriteEnable = true; private boolean indexFileReadEnable = true; // default, defaultRocksDB @ImportantField private String storeType = StoreType.DEFAULT.getStoreType(); private boolean iteratorWhenUseRocksdbConsumeQueue = true; // ConsumeQueue file size,default is 30W private int mappedFileSizeConsumeQueue = 300000 * ConsumeQueue.CQ_STORE_UNIT_SIZE; // enable consume queue ext private boolean enableConsumeQueueExt = false; // ConsumeQueue extend file size, 48M private int mappedFileSizeConsumeQueueExt = 48 * 1024 * 1024; private int mapperFileSizeBatchConsumeQueue = 300000 * BatchConsumeQueue.CQ_STORE_UNIT_SIZE; // Bit count of filter bit map. // this will be set by pipe of calculate filter bit map. private int bitMapLengthConsumeQueueExt = 64; // CommitLog flush interval // flush data to disk @ImportantField private int flushIntervalCommitLog = 500; // Only used if TransientStorePool enabled // flush data to FileChannel @ImportantField private int commitIntervalCommitLog = 200; private int maxRecoveryCommitlogFiles = 30; private int diskSpaceWarningLevelRatio = 90; private int diskSpaceCleanForciblyRatio = 85; /** * introduced since 4.0.x. Determine whether to use mutex reentrantLock when putting message.
    */ private boolean useReentrantLockWhenPutMessage = true; // Whether schedule flush @ImportantField private boolean flushCommitLogTimed = true; // ConsumeQueue flush interval private int flushIntervalConsumeQueue = 1000; // Resource reclaim interval private int cleanResourceInterval = 10000; // CommitLog removal interval private int deleteCommitLogFilesInterval = 100; // ConsumeQueue removal interval private int deleteConsumeQueueFilesInterval = 100; private int destroyMapedFileIntervalForcibly = 1000 * 120; private int redeleteHangedFileInterval = 1000 * 120; // When to delete,default is at 4 am @ImportantField private String deleteWhen = "04"; private int diskMaxUsedSpaceRatio = 75; // The number of hours to keep a log file before deleting it (in hours) @ImportantField private int fileReservedTime = 72; @ImportantField private int deleteFileBatchMax = 10; // Flow control for ConsumeQueue private int putMsgIndexHightWater = 600000; // The maximum size of message body,default is 4M,4M only for body length,not include others. private int maxMessageSize = 1024 * 1024 * 4; // The maximum size of message body can be set in config;count with maxMsgNums * CQ_STORE_UNIT_SIZE(20 || 46) private int maxFilterMessageSize = 16000; // Whether check the CRC32 of the records consumed. // This ensures no on-the-wire or on-disk corruption to the messages occurred. // This check adds some overhead,so it may be disabled in cases seeking extreme performance. private boolean checkCRCOnRecover = true; // Whether check the commitlog offset validity during abnormal recovery. // This helps detect and truncate old file data that may pass CRC checks but contains invalid offsets. private boolean checkCommitLogOffsetOnRecover = false; // How many pages are to be flushed when flush CommitLog private int flushCommitLogLeastPages = 4; // How many pages are to be committed when commit data to file private int commitCommitLogLeastPages = 4; // Flush page size when the disk in warming state private int flushLeastPagesWhenWarmMapedFile = 1024 / 4 * 16; // How many pages are to be flushed when flush ConsumeQueue private int flushConsumeQueueLeastPages = 2; private int flushCommitLogThoroughInterval = 1000 * 10; private int commitCommitLogThoroughInterval = 200; private int flushConsumeQueueThoroughInterval = 1000 * 60; @ImportantField private int maxTransferBytesOnMessageInMemory = 1024 * 256; @ImportantField private int maxTransferCountOnMessageInMemory = 32; @ImportantField private int maxTransferBytesOnMessageInDisk = 1024 * 64; @ImportantField private int maxTransferCountOnMessageInDisk = 8; @ImportantField private int accessMessageInMemoryMaxRatio = 40; @ImportantField private boolean messageIndexEnable = true; private int maxHashSlotNum = 5000000; private int maxIndexNum = 5000000 * 4; private int maxMsgsNumBatch = 64; @ImportantField private boolean messageIndexSafe = false; private int haListenPort = 10912; private int haSendHeartbeatInterval = 1000 * 5; private int haHousekeepingInterval = 1000 * 20; /** * Maximum size of data to transfer to slave. * NOTE: cannot be larger than HAClient.READ_MAX_BUFFER_SIZE */ private int haTransferBatchSize = 1024 * 32; @ImportantField private String haMasterAddress = null; private int haMaxGapNotInSync = 1024 * 1024 * 256; @ImportantField private volatile BrokerRole brokerRole = BrokerRole.ASYNC_MASTER; @ImportantField private FlushDiskType flushDiskType = FlushDiskType.ASYNC_FLUSH; // Used by GroupTransferService to sync messages from master to slave private int syncFlushTimeout = 1000 * 5; // Used by PutMessage to wait messages be flushed to disk and synchronized in current broker member group. private int putMessageTimeout = 1000 * 8; private int slaveTimeout = 3000; private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; private long flushDelayOffsetInterval = 1000 * 10; @ImportantField private boolean cleanFileForciblyEnable = true; private boolean warmMapedFileEnable = false; private boolean offsetCheckInSlave = false; private boolean debugLockEnable = false; private boolean duplicationEnable = false; private boolean diskFallRecorded = true; private long osPageCacheBusyTimeOutMills = 1000; private int defaultQueryMaxNum = 32; @ImportantField private boolean transientStorePoolEnable = false; private int transientStorePoolSize = 5; private boolean fastFailIfNoBufferInStorePool = false; /** * When true, use RandomAccessFile for writing instead of MappedByteBuffer. This can be useful for certain scenarios * where mmap is not desired. * * The configurations writeWithoutMmap and transientStorePoolEnable are mutually exclusive. When both are set to * true, only writeWithoutMmap will be effective. */ @ImportantField private boolean writeWithoutMmap = false; // DLedger message store config private boolean enableDLegerCommitLog = false; private String dLegerGroup; private String dLegerPeers; private String dLegerSelfId; private String preferredLeaderId; private boolean enableBatchPush = false; private boolean enableScheduleMessageStats = true; private boolean enableLmq = false; private boolean enableMultiDispatch = false; private int maxLmqConsumeQueueNum = 20000; private boolean enableLmqQuota = false; private boolean enableScheduleAsyncDeliver = false; private int scheduleAsyncDeliverMaxPendingLimit = 2000; private int scheduleAsyncDeliverMaxResendNum2Blocked = 3; private int maxBatchDeleteFilesNum = 50; //Polish dispatch private int dispatchCqThreads = 10; private int dispatchCqCacheNum = 1024 * 4; private boolean enableAsyncReput = true; //For recheck the reput private boolean recheckReputOffsetFromCq = false; // Maximum length of topic, it will be removed in the future release @Deprecated private int maxTopicLength = Byte.MAX_VALUE; /** * Use MessageVersion.MESSAGE_VERSION_V2 automatically if topic length larger than Bytes.MAX_VALUE. * Otherwise, store use MESSAGE_VERSION_V1. Note: Client couldn't decode MESSAGE_VERSION_V2 version message. * Enable this config to resolve this issue. https://github.com/apache/rocketmq/issues/5568 */ private boolean autoMessageVersionOnTopicLen = true; /** * Whether to use runningFlags when flushing data to disk. * When disabled, runningFlags will be set to null during MappedFileQueue and MappedFile initialization. */ @ImportantField private boolean enableRunningFlagsInFlush = false; /** * It cannot be changed after the broker is started. * Modifications need to be restarted to take effect. */ private boolean enabledAppendPropCRC = false; private boolean forceVerifyPropCRC = false; private int travelCqFileNumWhenGetMessage = 1; // Sleep interval between to corrections private int correctLogicMinOffsetSleepInterval = 1; // Force correct min offset interval private int correctLogicMinOffsetForceInterval = 5 * 60 * 1000; // swap private boolean mappedFileSwapEnable = true; private long commitLogForceSwapMapInterval = 12L * 60 * 60 * 1000; private long commitLogSwapMapInterval = 1L * 60 * 60 * 1000; private int commitLogSwapMapReserveFileNum = 100; private long logicQueueForceSwapMapInterval = 12L * 60 * 60 * 1000; private long logicQueueSwapMapInterval = 1L * 60 * 60 * 1000; private long cleanSwapedMapInterval = 5L * 60 * 1000; private int logicQueueSwapMapReserveFileNum = 20; private boolean searchBcqByCacheEnable = true; @ImportantField private boolean dispatchFromSenderThread = false; @ImportantField private boolean wakeCommitWhenPutMessage = true; @ImportantField private boolean wakeFlushWhenPutMessage = false; @ImportantField private boolean enableCleanExpiredOffset = false; private int maxAsyncPutMessageRequests = 5000; private int pullBatchMaxMessageCount = 160; @ImportantField private int totalReplicas = 1; /** * Each message must be written successfully to at least in-sync replicas. * The master broker is considered one of the in-sync replicas, and it's included in the count of total. * If a master broker is ASYNC_MASTER, inSyncReplicas will be ignored. * If enableControllerMode is true and ackAckInSyncStateSet is true, inSyncReplicas will be ignored. */ @ImportantField private int inSyncReplicas = 1; /** * Will be worked in auto multiple replicas mode, to provide minimum in-sync replicas. * It is still valid in controller mode. */ @ImportantField private int minInSyncReplicas = 1; /** * Each message must be written successfully to all replicas in SyncStateSet. */ @ImportantField private boolean allAckInSyncStateSet = false; /** * Dynamically adjust in-sync replicas to provide higher availability, the real time in-sync replicas * will smaller than inSyncReplicas config. */ @ImportantField private boolean enableAutoInSyncReplicas = false; /** * Enable or not ha flow control */ @ImportantField private boolean haFlowControlEnable = false; /** * The max speed for one slave when transfer data in ha */ private long maxHaTransferByteInSecond = 100 * 1024 * 1024; /** * The max gap time that slave doesn't catch up to master. */ private long haMaxTimeSlaveNotCatchup = 1000 * 15; /** * Sync flush offset from master when broker startup, used in upgrading from old version broker. */ private boolean syncMasterFlushOffsetWhenStartup = false; /** * Max checksum range. */ private long maxChecksumRange = 1024 * 1024 * 1024; private int replicasPerDiskPartition = 1; private double logicalDiskSpaceCleanForciblyThreshold = 0.8; private long maxSlaveResendLength = 256 * 1024 * 1024; /** * Whether sync from lastFile when a new broker replicas(no data) join the master. */ private boolean syncFromLastFile = false; private boolean asyncLearner = false; /** * Number of records to scan before starting to estimate. */ private int maxConsumeQueueScan = 20_000; /** * Number of matched records before starting to estimate. */ private int sampleCountThreshold = 5000; private boolean coldDataFlowControlEnable = false; private boolean coldDataScanEnable = false; private boolean dataReadAheadEnable = true; private int timerColdDataCheckIntervalMs = 60 * 1000; private int sampleSteps = 32; private int accessMessageInMemoryHotRatio = 26; /** * Build ConsumeQueue concurrently with multi-thread */ private boolean enableBuildConsumeQueueConcurrently = false; private int batchDispatchRequestThreadPoolNums = 16; // rocksdb mode private long cleanRocksDBDirtyCQIntervalMin = 60; private long statRocksDBCQIntervalSec = 10; private long memTableFlushIntervalMs = 60 * 60 * 1000L; private boolean realTimePersistRocksDBConfig = true; private boolean enableRocksDBLog = false; private int topicQueueLockNum = 32; /** * If readUnCommitted is true, the dispatch of the consume queue will exceed the confirmOffset, which may cause the client to read uncommitted messages. * For example, reput offset exceeding the flush offset during synchronous disk flushing. */ private boolean readUnCommitted = false; private boolean putConsumeQueueDataByFileChannel = true; private boolean rocksdbCQDoubleWriteEnable = false; /** * CombineConsumeQueueStore * combineCQLoadingCQTypes is used to configure the loading types of CQ. load / recover / start order: [default -> defaultRocksDB] * combineCQPreferCQType is used to configure the preferred CQ type when reading. Make sure the CQ type is included in combineCQLoadingCQTypes * combineAssignOffsetCQType is used to configure the CQ type when assign offset. Make sure the CQ type is included in combineCQLoadingCQTypes */ private String combineCQLoadingCQTypes = StoreType.DEFAULT.getStoreType() + ";" + StoreType.DEFAULT_ROCKSDB.getStoreType(); private String combineCQPreferCQType = StoreType.DEFAULT.getStoreType(); private String combineAssignOffsetCQType = StoreType.DEFAULT.getStoreType(); private boolean combineCQEnableCheckSelf = false; private int combineCQMaxExtraSearchCommitLogFiles = 3; /** * If ConsumeQueueStore is RocksDB based, this option is to configure bottom-most tier compression type. * The following values are valid: *
      *
    • snappy
    • *
    • z
    • *
    • bzip2
    • *
    • lz4
    • *
    • lz4hc
    • *
    • xpress
    • *
    • zstd
    • *
    * * LZ4 is the recommended one. */ private String bottomMostCompressionTypeForConsumeQueueStore = CompressionType.ZSTD_COMPRESSION.getLibraryName(); private String rocksdbCompressionType = CompressionType.LZ4_COMPRESSION.getLibraryName(); /** * Flush RocksDB WAL frequency, aka, flush WAL every N write ops. */ private int rocksdbFlushWalFrequency = 1024; private long rocksdbWalFileRollingThreshold = SizeUnit.GB; /** * Note: For correctness, this switch should be enabled only if the previous startup was configured with SYNC_FLUSH. * This switch is not recommended for normal use cases (include master-slave or controller mode). */ private boolean enableAcceleratedRecovery = false; // Shared byte buffer manager configuration private int sharedByteBufferNum = 16; public String getRocksdbCompressionType() { return rocksdbCompressionType; } public void setRocksdbCompressionType(String compressionType) { this.rocksdbCompressionType = compressionType; } /** * Spin number in the retreat strategy of spin lock * Default is 1000 */ private int spinLockCollisionRetreatOptimalDegree = 1000; /** * Use AdaptiveBackOffLock **/ private boolean useABSLock = false; private boolean enableLogConsumeQueueRepeatedlyBuildWhenRecover = false; private boolean appendTopicForTimerDeleteKey = false; public boolean isRocksdbCQDoubleWriteEnable() { return rocksdbCQDoubleWriteEnable; } public void setRocksdbCQDoubleWriteEnable(boolean rocksdbWriteEnable) { this.rocksdbCQDoubleWriteEnable = rocksdbWriteEnable; } public boolean isEnabledAppendPropCRC() { return enabledAppendPropCRC; } public void setEnabledAppendPropCRC(boolean enabledAppendPropCRC) { this.enabledAppendPropCRC = enabledAppendPropCRC; } public boolean isDebugLockEnable() { return debugLockEnable; } public void setDebugLockEnable(final boolean debugLockEnable) { this.debugLockEnable = debugLockEnable; } public boolean isDuplicationEnable() { return duplicationEnable; } public void setDuplicationEnable(final boolean duplicationEnable) { this.duplicationEnable = duplicationEnable; } public long getOsPageCacheBusyTimeOutMills() { return osPageCacheBusyTimeOutMills; } public void setOsPageCacheBusyTimeOutMills(final long osPageCacheBusyTimeOutMills) { this.osPageCacheBusyTimeOutMills = osPageCacheBusyTimeOutMills; } public boolean isDiskFallRecorded() { return diskFallRecorded; } public void setDiskFallRecorded(final boolean diskFallRecorded) { this.diskFallRecorded = diskFallRecorded; } public boolean isWarmMapedFileEnable() { return warmMapedFileEnable; } public void setWarmMapedFileEnable(boolean warmMapedFileEnable) { this.warmMapedFileEnable = warmMapedFileEnable; } public int getCompactionMappedFileSize() { return compactionMappedFileSize; } public int getCompactionCqMappedFileSize() { return compactionCqMappedFileSize; } public void setCompactionMappedFileSize(int compactionMappedFileSize) { this.compactionMappedFileSize = compactionMappedFileSize; } public void setCompactionCqMappedFileSize(int compactionCqMappedFileSize) { this.compactionCqMappedFileSize = compactionCqMappedFileSize; } public int getCompactionScheduleInternal() { return compactionScheduleInternal; } public void setCompactionScheduleInternal(int compactionScheduleInternal) { this.compactionScheduleInternal = compactionScheduleInternal; } public int getMaxOffsetMapSize() { return maxOffsetMapSize; } public void setMaxOffsetMapSize(int maxOffsetMapSize) { this.maxOffsetMapSize = maxOffsetMapSize; } public int getCompactionThreadNum() { return compactionThreadNum; } public void setCompactionThreadNum(int compactionThreadNum) { this.compactionThreadNum = compactionThreadNum; } public boolean isEnableCompaction() { return enableCompaction; } public void setEnableCompaction(boolean enableCompaction) { this.enableCompaction = enableCompaction; } public int getMappedFileSizeCommitLog() { return mappedFileSizeCommitLog; } public void setMappedFileSizeCommitLog(int mappedFileSizeCommitLog) { this.mappedFileSizeCommitLog = mappedFileSizeCommitLog; } public boolean isEnableRocksDBStore() { return StoreType.DEFAULT_ROCKSDB.getStoreType().equalsIgnoreCase(this.storeType); } public String getStoreType() { return storeType; } public void setStoreType(String storeType) { this.storeType = storeType; } public boolean isIteratorWhenUseRocksdbConsumeQueue() { return iteratorWhenUseRocksdbConsumeQueue; } public void setIteratorWhenUseRocksdbConsumeQueue(boolean iteratorWhenUseRocksdbConsumeQueue) { this.iteratorWhenUseRocksdbConsumeQueue = iteratorWhenUseRocksdbConsumeQueue; } public int getMappedFileSizeConsumeQueue() { int factor = (int) Math.ceil(this.mappedFileSizeConsumeQueue / (ConsumeQueue.CQ_STORE_UNIT_SIZE * 1.0)); return (int) (factor * ConsumeQueue.CQ_STORE_UNIT_SIZE); } public void setMappedFileSizeConsumeQueue(int mappedFileSizeConsumeQueue) { this.mappedFileSizeConsumeQueue = mappedFileSizeConsumeQueue; } public boolean isEnableConsumeQueueExt() { return enableConsumeQueueExt; } public void setEnableConsumeQueueExt(boolean enableConsumeQueueExt) { this.enableConsumeQueueExt = enableConsumeQueueExt; } public int getMappedFileSizeConsumeQueueExt() { return mappedFileSizeConsumeQueueExt; } public void setMappedFileSizeConsumeQueueExt(int mappedFileSizeConsumeQueueExt) { this.mappedFileSizeConsumeQueueExt = mappedFileSizeConsumeQueueExt; } public int getBitMapLengthConsumeQueueExt() { return bitMapLengthConsumeQueueExt; } public void setBitMapLengthConsumeQueueExt(int bitMapLengthConsumeQueueExt) { this.bitMapLengthConsumeQueueExt = bitMapLengthConsumeQueueExt; } public int getFlushIntervalCommitLog() { return flushIntervalCommitLog; } public void setFlushIntervalCommitLog(int flushIntervalCommitLog) { this.flushIntervalCommitLog = flushIntervalCommitLog; } public int getFlushIntervalConsumeQueue() { return flushIntervalConsumeQueue; } public void setFlushIntervalConsumeQueue(int flushIntervalConsumeQueue) { this.flushIntervalConsumeQueue = flushIntervalConsumeQueue; } public int getPutMsgIndexHightWater() { return putMsgIndexHightWater; } public void setPutMsgIndexHightWater(int putMsgIndexHightWater) { this.putMsgIndexHightWater = putMsgIndexHightWater; } public int getCleanResourceInterval() { return cleanResourceInterval; } public void setCleanResourceInterval(int cleanResourceInterval) { this.cleanResourceInterval = cleanResourceInterval; } public int getMaxMessageSize() { return maxMessageSize; } public void setMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; } public int getMaxFilterMessageSize() { return maxFilterMessageSize; } public void setMaxFilterMessageSize(int maxFilterMessageSize) { this.maxFilterMessageSize = maxFilterMessageSize; } @Deprecated public int getMaxTopicLength() { return maxTopicLength; } @Deprecated public void setMaxTopicLength(int maxTopicLength) { this.maxTopicLength = maxTopicLength; } public boolean isAutoMessageVersionOnTopicLen() { return autoMessageVersionOnTopicLen; } public void setAutoMessageVersionOnTopicLen(boolean autoMessageVersionOnTopicLen) { this.autoMessageVersionOnTopicLen = autoMessageVersionOnTopicLen; } public int getTravelCqFileNumWhenGetMessage() { return travelCqFileNumWhenGetMessage; } public void setTravelCqFileNumWhenGetMessage(int travelCqFileNumWhenGetMessage) { this.travelCqFileNumWhenGetMessage = travelCqFileNumWhenGetMessage; } public int getCorrectLogicMinOffsetSleepInterval() { return correctLogicMinOffsetSleepInterval; } public void setCorrectLogicMinOffsetSleepInterval(int correctLogicMinOffsetSleepInterval) { this.correctLogicMinOffsetSleepInterval = correctLogicMinOffsetSleepInterval; } public int getCorrectLogicMinOffsetForceInterval() { return correctLogicMinOffsetForceInterval; } public void setCorrectLogicMinOffsetForceInterval(int correctLogicMinOffsetForceInterval) { this.correctLogicMinOffsetForceInterval = correctLogicMinOffsetForceInterval; } public boolean isCheckCRCOnRecover() { return checkCRCOnRecover; } public boolean getCheckCRCOnRecover() { return checkCRCOnRecover; } public void setCheckCRCOnRecover(boolean checkCRCOnRecover) { this.checkCRCOnRecover = checkCRCOnRecover; } public boolean isCheckCommitLogOffsetOnRecover() { return checkCommitLogOffsetOnRecover; } public void setCheckCommitLogOffsetOnRecover(boolean checkCommitLogOffsetOnRecover) { this.checkCommitLogOffsetOnRecover = checkCommitLogOffsetOnRecover; } public boolean isForceVerifyPropCRC() { return forceVerifyPropCRC; } public void setForceVerifyPropCRC(boolean forceVerifyPropCRC) { this.forceVerifyPropCRC = forceVerifyPropCRC; } public String getStorePathCommitLog() { if (storePathCommitLog == null) { return storePathRootDir + File.separator + "commitlog"; } return storePathCommitLog; } public void setStorePathCommitLog(String storePathCommitLog) { this.storePathCommitLog = storePathCommitLog; } public String getStorePathDLedgerCommitLog() { return storePathDLedgerCommitLog; } public void setStorePathDLedgerCommitLog(String storePathDLedgerCommitLog) { this.storePathDLedgerCommitLog = storePathDLedgerCommitLog; } public String getStorePathEpochFile() { if (storePathEpochFile == null) { return storePathRootDir + File.separator + "epochFileCheckpoint"; } return storePathEpochFile; } public void setStorePathEpochFile(String storePathEpochFile) { this.storePathEpochFile = storePathEpochFile; } public String getStorePathBrokerIdentity() { if (storePathBrokerIdentity == null) { return storePathRootDir + File.separator + "brokerIdentity"; } return storePathBrokerIdentity; } public void setStorePathBrokerIdentity(String storePathBrokerIdentity) { this.storePathBrokerIdentity = storePathBrokerIdentity; } public String getDeleteWhen() { return deleteWhen; } public void setDeleteWhen(String deleteWhen) { this.deleteWhen = deleteWhen; } public int getDiskMaxUsedSpaceRatio() { if (this.diskMaxUsedSpaceRatio < 10) return 10; if (this.diskMaxUsedSpaceRatio > 95) return 95; return diskMaxUsedSpaceRatio; } public void setDiskMaxUsedSpaceRatio(int diskMaxUsedSpaceRatio) { this.diskMaxUsedSpaceRatio = diskMaxUsedSpaceRatio; } public int getDeleteCommitLogFilesInterval() { return deleteCommitLogFilesInterval; } public void setDeleteCommitLogFilesInterval(int deleteCommitLogFilesInterval) { this.deleteCommitLogFilesInterval = deleteCommitLogFilesInterval; } public int getDeleteConsumeQueueFilesInterval() { return deleteConsumeQueueFilesInterval; } public void setDeleteConsumeQueueFilesInterval(int deleteConsumeQueueFilesInterval) { this.deleteConsumeQueueFilesInterval = deleteConsumeQueueFilesInterval; } public int getMaxTransferBytesOnMessageInMemory() { return maxTransferBytesOnMessageInMemory; } public void setMaxTransferBytesOnMessageInMemory(int maxTransferBytesOnMessageInMemory) { this.maxTransferBytesOnMessageInMemory = maxTransferBytesOnMessageInMemory; } public int getMaxTransferCountOnMessageInMemory() { return maxTransferCountOnMessageInMemory; } public void setMaxTransferCountOnMessageInMemory(int maxTransferCountOnMessageInMemory) { this.maxTransferCountOnMessageInMemory = maxTransferCountOnMessageInMemory; } public int getMaxTransferBytesOnMessageInDisk() { return maxTransferBytesOnMessageInDisk; } public void setMaxTransferBytesOnMessageInDisk(int maxTransferBytesOnMessageInDisk) { this.maxTransferBytesOnMessageInDisk = maxTransferBytesOnMessageInDisk; } public int getMaxTransferCountOnMessageInDisk() { return maxTransferCountOnMessageInDisk; } public void setMaxTransferCountOnMessageInDisk(int maxTransferCountOnMessageInDisk) { this.maxTransferCountOnMessageInDisk = maxTransferCountOnMessageInDisk; } public int getFlushCommitLogLeastPages() { return flushCommitLogLeastPages; } public void setFlushCommitLogLeastPages(int flushCommitLogLeastPages) { this.flushCommitLogLeastPages = flushCommitLogLeastPages; } public int getFlushConsumeQueueLeastPages() { return flushConsumeQueueLeastPages; } public void setFlushConsumeQueueLeastPages(int flushConsumeQueueLeastPages) { this.flushConsumeQueueLeastPages = flushConsumeQueueLeastPages; } public int getFlushCommitLogThoroughInterval() { return flushCommitLogThoroughInterval; } public void setFlushCommitLogThoroughInterval(int flushCommitLogThoroughInterval) { this.flushCommitLogThoroughInterval = flushCommitLogThoroughInterval; } public int getFlushConsumeQueueThoroughInterval() { return flushConsumeQueueThoroughInterval; } public void setFlushConsumeQueueThoroughInterval(int flushConsumeQueueThoroughInterval) { this.flushConsumeQueueThoroughInterval = flushConsumeQueueThoroughInterval; } public int getDestroyMapedFileIntervalForcibly() { return destroyMapedFileIntervalForcibly; } public void setDestroyMapedFileIntervalForcibly(int destroyMapedFileIntervalForcibly) { this.destroyMapedFileIntervalForcibly = destroyMapedFileIntervalForcibly; } public int getFileReservedTime() { return fileReservedTime; } public void setFileReservedTime(int fileReservedTime) { this.fileReservedTime = fileReservedTime; } public int getRedeleteHangedFileInterval() { return redeleteHangedFileInterval; } public void setRedeleteHangedFileInterval(int redeleteHangedFileInterval) { this.redeleteHangedFileInterval = redeleteHangedFileInterval; } public int getAccessMessageInMemoryMaxRatio() { return accessMessageInMemoryMaxRatio; } public void setAccessMessageInMemoryMaxRatio(int accessMessageInMemoryMaxRatio) { this.accessMessageInMemoryMaxRatio = accessMessageInMemoryMaxRatio; } public boolean isMessageIndexEnable() { return messageIndexEnable; } public void setMessageIndexEnable(boolean messageIndexEnable) { this.messageIndexEnable = messageIndexEnable; } public int getMaxHashSlotNum() { return maxHashSlotNum; } public void setMaxHashSlotNum(int maxHashSlotNum) { this.maxHashSlotNum = maxHashSlotNum; } public int getMaxIndexNum() { return maxIndexNum; } public void setMaxIndexNum(int maxIndexNum) { this.maxIndexNum = maxIndexNum; } public int getMaxMsgsNumBatch() { return maxMsgsNumBatch; } public void setMaxMsgsNumBatch(int maxMsgsNumBatch) { this.maxMsgsNumBatch = maxMsgsNumBatch; } public int getHaListenPort() { return haListenPort; } public void setHaListenPort(int haListenPort) { if (haListenPort < 0) { this.haListenPort = 0; return; } this.haListenPort = haListenPort; } public int getHaSendHeartbeatInterval() { return haSendHeartbeatInterval; } public void setHaSendHeartbeatInterval(int haSendHeartbeatInterval) { this.haSendHeartbeatInterval = haSendHeartbeatInterval; } public int getHaHousekeepingInterval() { return haHousekeepingInterval; } public void setHaHousekeepingInterval(int haHousekeepingInterval) { this.haHousekeepingInterval = haHousekeepingInterval; } public BrokerRole getBrokerRole() { return brokerRole; } public void setBrokerRole(BrokerRole brokerRole) { this.brokerRole = brokerRole; } public void setBrokerRole(String brokerRole) { this.brokerRole = BrokerRole.valueOf(brokerRole); } public int getHaTransferBatchSize() { return haTransferBatchSize; } public void setHaTransferBatchSize(int haTransferBatchSize) { this.haTransferBatchSize = haTransferBatchSize; } public int getHaMaxGapNotInSync() { return haMaxGapNotInSync; } public void setHaMaxGapNotInSync(int haMaxGapNotInSync) { this.haMaxGapNotInSync = haMaxGapNotInSync; } public FlushDiskType getFlushDiskType() { return flushDiskType; } public void setFlushDiskType(FlushDiskType flushDiskType) { this.flushDiskType = flushDiskType; } public void setFlushDiskType(String type) { this.flushDiskType = FlushDiskType.valueOf(type); } public int getSyncFlushTimeout() { return syncFlushTimeout; } public void setSyncFlushTimeout(int syncFlushTimeout) { this.syncFlushTimeout = syncFlushTimeout; } public int getPutMessageTimeout() { return putMessageTimeout; } public void setPutMessageTimeout(int putMessageTimeout) { this.putMessageTimeout = putMessageTimeout; } public int getSlaveTimeout() { return slaveTimeout; } public void setSlaveTimeout(int slaveTimeout) { this.slaveTimeout = slaveTimeout; } public String getHaMasterAddress() { return haMasterAddress; } public void setHaMasterAddress(String haMasterAddress) { this.haMasterAddress = haMasterAddress; } public String getMessageDelayLevel() { return messageDelayLevel; } public void setMessageDelayLevel(String messageDelayLevel) { this.messageDelayLevel = messageDelayLevel; } public long getFlushDelayOffsetInterval() { return flushDelayOffsetInterval; } public void setFlushDelayOffsetInterval(long flushDelayOffsetInterval) { this.flushDelayOffsetInterval = flushDelayOffsetInterval; } public boolean isCleanFileForciblyEnable() { return cleanFileForciblyEnable; } public void setCleanFileForciblyEnable(boolean cleanFileForciblyEnable) { this.cleanFileForciblyEnable = cleanFileForciblyEnable; } public boolean isMessageIndexSafe() { return messageIndexSafe; } public void setMessageIndexSafe(boolean messageIndexSafe) { this.messageIndexSafe = messageIndexSafe; } public boolean isFlushCommitLogTimed() { return flushCommitLogTimed; } public void setFlushCommitLogTimed(boolean flushCommitLogTimed) { this.flushCommitLogTimed = flushCommitLogTimed; } public String getStorePathRootDir() { return storePathRootDir; } public void setStorePathRootDir(String storePathRootDir) { this.storePathRootDir = storePathRootDir; } public int getFlushLeastPagesWhenWarmMapedFile() { return flushLeastPagesWhenWarmMapedFile; } public void setFlushLeastPagesWhenWarmMapedFile(int flushLeastPagesWhenWarmMapedFile) { this.flushLeastPagesWhenWarmMapedFile = flushLeastPagesWhenWarmMapedFile; } public boolean isOffsetCheckInSlave() { return offsetCheckInSlave; } public void setOffsetCheckInSlave(boolean offsetCheckInSlave) { this.offsetCheckInSlave = offsetCheckInSlave; } public int getDefaultQueryMaxNum() { return defaultQueryMaxNum; } public void setDefaultQueryMaxNum(int defaultQueryMaxNum) { this.defaultQueryMaxNum = defaultQueryMaxNum; } public boolean isTransientStorePoolEnable() { return transientStorePoolEnable; } public void setTransientStorePoolEnable(final boolean transientStorePoolEnable) { this.transientStorePoolEnable = transientStorePoolEnable; } public boolean isWriteWithoutMmap() { return writeWithoutMmap; } public void setWriteWithoutMmap(final boolean writeWithoutMmap) { this.writeWithoutMmap = writeWithoutMmap; } public int getTransientStorePoolSize() { return transientStorePoolSize; } public void setTransientStorePoolSize(final int transientStorePoolSize) { this.transientStorePoolSize = transientStorePoolSize; } public int getCommitIntervalCommitLog() { return commitIntervalCommitLog; } public void setCommitIntervalCommitLog(final int commitIntervalCommitLog) { this.commitIntervalCommitLog = commitIntervalCommitLog; } public boolean isFastFailIfNoBufferInStorePool() { return fastFailIfNoBufferInStorePool; } public void setFastFailIfNoBufferInStorePool(final boolean fastFailIfNoBufferInStorePool) { this.fastFailIfNoBufferInStorePool = fastFailIfNoBufferInStorePool; } public boolean isUseReentrantLockWhenPutMessage() { return useReentrantLockWhenPutMessage; } public void setUseReentrantLockWhenPutMessage(final boolean useReentrantLockWhenPutMessage) { this.useReentrantLockWhenPutMessage = useReentrantLockWhenPutMessage; } public int getCommitCommitLogLeastPages() { return commitCommitLogLeastPages; } public void setCommitCommitLogLeastPages(final int commitCommitLogLeastPages) { this.commitCommitLogLeastPages = commitCommitLogLeastPages; } public int getCommitCommitLogThoroughInterval() { return commitCommitLogThoroughInterval; } public void setCommitCommitLogThoroughInterval(final int commitCommitLogThoroughInterval) { this.commitCommitLogThoroughInterval = commitCommitLogThoroughInterval; } public boolean isWakeCommitWhenPutMessage() { return wakeCommitWhenPutMessage; } public void setWakeCommitWhenPutMessage(boolean wakeCommitWhenPutMessage) { this.wakeCommitWhenPutMessage = wakeCommitWhenPutMessage; } public boolean isWakeFlushWhenPutMessage() { return wakeFlushWhenPutMessage; } public void setWakeFlushWhenPutMessage(boolean wakeFlushWhenPutMessage) { this.wakeFlushWhenPutMessage = wakeFlushWhenPutMessage; } public int getMapperFileSizeBatchConsumeQueue() { return mapperFileSizeBatchConsumeQueue; } public void setMapperFileSizeBatchConsumeQueue(int mapperFileSizeBatchConsumeQueue) { this.mapperFileSizeBatchConsumeQueue = mapperFileSizeBatchConsumeQueue; } public boolean isEnableCleanExpiredOffset() { return enableCleanExpiredOffset; } public void setEnableCleanExpiredOffset(boolean enableCleanExpiredOffset) { this.enableCleanExpiredOffset = enableCleanExpiredOffset; } public String getReadOnlyCommitLogStorePaths() { return readOnlyCommitLogStorePaths; } public void setReadOnlyCommitLogStorePaths(String readOnlyCommitLogStorePaths) { this.readOnlyCommitLogStorePaths = readOnlyCommitLogStorePaths; } public String getdLegerGroup() { return dLegerGroup; } public void setdLegerGroup(String dLegerGroup) { this.dLegerGroup = dLegerGroup; } public String getdLegerPeers() { return dLegerPeers; } public void setdLegerPeers(String dLegerPeers) { this.dLegerPeers = dLegerPeers; } public String getdLegerSelfId() { return dLegerSelfId; } public void setdLegerSelfId(String dLegerSelfId) { this.dLegerSelfId = dLegerSelfId; } public boolean isEnableDLegerCommitLog() { return enableDLegerCommitLog; } public void setEnableDLegerCommitLog(boolean enableDLegerCommitLog) { this.enableDLegerCommitLog = enableDLegerCommitLog; } public String getPreferredLeaderId() { return preferredLeaderId; } public void setPreferredLeaderId(String preferredLeaderId) { this.preferredLeaderId = preferredLeaderId; } public boolean isEnableBatchPush() { return enableBatchPush; } public void setEnableBatchPush(boolean enableBatchPush) { this.enableBatchPush = enableBatchPush; } public boolean isEnableScheduleMessageStats() { return enableScheduleMessageStats; } public void setEnableScheduleMessageStats(boolean enableScheduleMessageStats) { this.enableScheduleMessageStats = enableScheduleMessageStats; } public int getMaxAsyncPutMessageRequests() { return maxAsyncPutMessageRequests; } public void setMaxAsyncPutMessageRequests(int maxAsyncPutMessageRequests) { this.maxAsyncPutMessageRequests = maxAsyncPutMessageRequests; } public int getMaxRecoveryCommitlogFiles() { return maxRecoveryCommitlogFiles; } public void setMaxRecoveryCommitlogFiles(final int maxRecoveryCommitlogFiles) { this.maxRecoveryCommitlogFiles = maxRecoveryCommitlogFiles; } public boolean isDispatchFromSenderThread() { return dispatchFromSenderThread; } public void setDispatchFromSenderThread(boolean dispatchFromSenderThread) { this.dispatchFromSenderThread = dispatchFromSenderThread; } public int getDispatchCqThreads() { return dispatchCqThreads; } public void setDispatchCqThreads(final int dispatchCqThreads) { this.dispatchCqThreads = dispatchCqThreads; } public int getDispatchCqCacheNum() { return dispatchCqCacheNum; } public void setDispatchCqCacheNum(final int dispatchCqCacheNum) { this.dispatchCqCacheNum = dispatchCqCacheNum; } public boolean isEnableAsyncReput() { return enableAsyncReput; } public void setEnableAsyncReput(final boolean enableAsyncReput) { this.enableAsyncReput = enableAsyncReput; } public boolean isRecheckReputOffsetFromCq() { return recheckReputOffsetFromCq; } public void setRecheckReputOffsetFromCq(final boolean recheckReputOffsetFromCq) { this.recheckReputOffsetFromCq = recheckReputOffsetFromCq; } public long getCommitLogForceSwapMapInterval() { return commitLogForceSwapMapInterval; } public void setCommitLogForceSwapMapInterval(long commitLogForceSwapMapInterval) { this.commitLogForceSwapMapInterval = commitLogForceSwapMapInterval; } public int getCommitLogSwapMapReserveFileNum() { return commitLogSwapMapReserveFileNum; } public void setCommitLogSwapMapReserveFileNum(int commitLogSwapMapReserveFileNum) { this.commitLogSwapMapReserveFileNum = commitLogSwapMapReserveFileNum; } public long getLogicQueueForceSwapMapInterval() { return logicQueueForceSwapMapInterval; } public void setLogicQueueForceSwapMapInterval(long logicQueueForceSwapMapInterval) { this.logicQueueForceSwapMapInterval = logicQueueForceSwapMapInterval; } public int getLogicQueueSwapMapReserveFileNum() { return logicQueueSwapMapReserveFileNum; } public void setLogicQueueSwapMapReserveFileNum(int logicQueueSwapMapReserveFileNum) { this.logicQueueSwapMapReserveFileNum = logicQueueSwapMapReserveFileNum; } public long getCleanSwapedMapInterval() { return cleanSwapedMapInterval; } public void setCleanSwapedMapInterval(long cleanSwapedMapInterval) { this.cleanSwapedMapInterval = cleanSwapedMapInterval; } public long getCommitLogSwapMapInterval() { return commitLogSwapMapInterval; } public void setCommitLogSwapMapInterval(long commitLogSwapMapInterval) { this.commitLogSwapMapInterval = commitLogSwapMapInterval; } public long getLogicQueueSwapMapInterval() { return logicQueueSwapMapInterval; } public void setLogicQueueSwapMapInterval(long logicQueueSwapMapInterval) { this.logicQueueSwapMapInterval = logicQueueSwapMapInterval; } public int getMaxBatchDeleteFilesNum() { return maxBatchDeleteFilesNum; } public void setMaxBatchDeleteFilesNum(int maxBatchDeleteFilesNum) { this.maxBatchDeleteFilesNum = maxBatchDeleteFilesNum; } public boolean isSearchBcqByCacheEnable() { return searchBcqByCacheEnable; } public void setSearchBcqByCacheEnable(boolean searchBcqByCacheEnable) { this.searchBcqByCacheEnable = searchBcqByCacheEnable; } public int getDiskSpaceWarningLevelRatio() { return diskSpaceWarningLevelRatio; } public void setDiskSpaceWarningLevelRatio(int diskSpaceWarningLevelRatio) { this.diskSpaceWarningLevelRatio = diskSpaceWarningLevelRatio; } public int getDiskSpaceCleanForciblyRatio() { return diskSpaceCleanForciblyRatio; } public void setDiskSpaceCleanForciblyRatio(int diskSpaceCleanForciblyRatio) { this.diskSpaceCleanForciblyRatio = diskSpaceCleanForciblyRatio; } public boolean isMappedFileSwapEnable() { return mappedFileSwapEnable; } public void setMappedFileSwapEnable(boolean mappedFileSwapEnable) { this.mappedFileSwapEnable = mappedFileSwapEnable; } public int getPullBatchMaxMessageCount() { return pullBatchMaxMessageCount; } public void setPullBatchMaxMessageCount(int pullBatchMaxMessageCount) { this.pullBatchMaxMessageCount = pullBatchMaxMessageCount; } public int getDeleteFileBatchMax() { return deleteFileBatchMax; } public void setDeleteFileBatchMax(int deleteFileBatchMax) { this.deleteFileBatchMax = deleteFileBatchMax; } public int getTotalReplicas() { return totalReplicas; } public void setTotalReplicas(int totalReplicas) { this.totalReplicas = totalReplicas; } public int getInSyncReplicas() { return inSyncReplicas; } public void setInSyncReplicas(int inSyncReplicas) { this.inSyncReplicas = inSyncReplicas; } public int getMinInSyncReplicas() { return minInSyncReplicas; } public void setMinInSyncReplicas(int minInSyncReplicas) { this.minInSyncReplicas = minInSyncReplicas; } public boolean isAllAckInSyncStateSet() { return allAckInSyncStateSet; } public void setAllAckInSyncStateSet(boolean allAckInSyncStateSet) { this.allAckInSyncStateSet = allAckInSyncStateSet; } public boolean isEnableAutoInSyncReplicas() { return enableAutoInSyncReplicas; } public void setEnableAutoInSyncReplicas(boolean enableAutoInSyncReplicas) { this.enableAutoInSyncReplicas = enableAutoInSyncReplicas; } public boolean isHaFlowControlEnable() { return haFlowControlEnable; } public void setHaFlowControlEnable(boolean haFlowControlEnable) { this.haFlowControlEnable = haFlowControlEnable; } public long getMaxHaTransferByteInSecond() { return maxHaTransferByteInSecond; } public void setMaxHaTransferByteInSecond(long maxHaTransferByteInSecond) { this.maxHaTransferByteInSecond = maxHaTransferByteInSecond; } public long getHaMaxTimeSlaveNotCatchup() { return haMaxTimeSlaveNotCatchup; } public void setHaMaxTimeSlaveNotCatchup(long haMaxTimeSlaveNotCatchup) { this.haMaxTimeSlaveNotCatchup = haMaxTimeSlaveNotCatchup; } public boolean isSyncMasterFlushOffsetWhenStartup() { return syncMasterFlushOffsetWhenStartup; } public void setSyncMasterFlushOffsetWhenStartup(boolean syncMasterFlushOffsetWhenStartup) { this.syncMasterFlushOffsetWhenStartup = syncMasterFlushOffsetWhenStartup; } public long getMaxChecksumRange() { return maxChecksumRange; } public void setMaxChecksumRange(long maxChecksumRange) { this.maxChecksumRange = maxChecksumRange; } public int getReplicasPerDiskPartition() { return replicasPerDiskPartition; } public void setReplicasPerDiskPartition(int replicasPerDiskPartition) { this.replicasPerDiskPartition = replicasPerDiskPartition; } public double getLogicalDiskSpaceCleanForciblyThreshold() { return logicalDiskSpaceCleanForciblyThreshold; } public void setLogicalDiskSpaceCleanForciblyThreshold(double logicalDiskSpaceCleanForciblyThreshold) { this.logicalDiskSpaceCleanForciblyThreshold = logicalDiskSpaceCleanForciblyThreshold; } public int getDisappearTimeAfterStart() { return disappearTimeAfterStart; } public void setDisappearTimeAfterStart(int disappearTimeAfterStart) { this.disappearTimeAfterStart = disappearTimeAfterStart; } public long getMaxSlaveResendLength() { return maxSlaveResendLength; } public void setMaxSlaveResendLength(long maxSlaveResendLength) { this.maxSlaveResendLength = maxSlaveResendLength; } public boolean isSyncFromLastFile() { return syncFromLastFile; } public void setSyncFromLastFile(boolean syncFromLastFile) { this.syncFromLastFile = syncFromLastFile; } public boolean isEnableLmq() { return enableLmq; } public void setEnableLmq(boolean enableLmq) { this.enableLmq = enableLmq; } public boolean isEnableMultiDispatch() { return enableMultiDispatch; } public void setEnableMultiDispatch(boolean enableMultiDispatch) { this.enableMultiDispatch = enableMultiDispatch; } public int getMaxLmqConsumeQueueNum() { return maxLmqConsumeQueueNum; } public void setMaxLmqConsumeQueueNum(int maxLmqConsumeQueueNum) { this.maxLmqConsumeQueueNum = maxLmqConsumeQueueNum; } public boolean isEnableLmqQuota() { return enableLmqQuota; } public void setEnableLmqQuota(boolean enableLmqQuota) { this.enableLmqQuota = enableLmqQuota; } public boolean isEnableScheduleAsyncDeliver() { return enableScheduleAsyncDeliver; } public void setEnableScheduleAsyncDeliver(boolean enableScheduleAsyncDeliver) { this.enableScheduleAsyncDeliver = enableScheduleAsyncDeliver; } public int getScheduleAsyncDeliverMaxPendingLimit() { return scheduleAsyncDeliverMaxPendingLimit; } public void setScheduleAsyncDeliverMaxPendingLimit(int scheduleAsyncDeliverMaxPendingLimit) { this.scheduleAsyncDeliverMaxPendingLimit = scheduleAsyncDeliverMaxPendingLimit; } public int getScheduleAsyncDeliverMaxResendNum2Blocked() { return scheduleAsyncDeliverMaxResendNum2Blocked; } public void setScheduleAsyncDeliverMaxResendNum2Blocked(int scheduleAsyncDeliverMaxResendNum2Blocked) { this.scheduleAsyncDeliverMaxResendNum2Blocked = scheduleAsyncDeliverMaxResendNum2Blocked; } public boolean isAsyncLearner() { return asyncLearner; } public void setAsyncLearner(boolean asyncLearner) { this.asyncLearner = asyncLearner; } public int getMappedFileSizeTimerLog() { return mappedFileSizeTimerLog; } public void setMappedFileSizeTimerLog(final int mappedFileSizeTimerLog) { this.mappedFileSizeTimerLog = mappedFileSizeTimerLog; } public int getTimerPrecisionMs() { return timerPrecisionMs; } public void setTimerPrecisionMs(int timerPrecisionMs) { int[] candidates = {100, 200, 500, 1000}; for (int i = 1; i < candidates.length; i++) { if (timerPrecisionMs < candidates[i]) { this.timerPrecisionMs = candidates[i - 1]; return; } } this.timerPrecisionMs = candidates[candidates.length - 1]; } public int getTimerRollWindowSlot() { return timerRollWindowSlot; } public int getTimerGetMessageThreadNum() { return timerGetMessageThreadNum; } public void setTimerGetMessageThreadNum(int timerGetMessageThreadNum) { this.timerGetMessageThreadNum = timerGetMessageThreadNum; } public int getTimerPutMessageThreadNum() { return timerPutMessageThreadNum; } public void setTimerPutMessageThreadNum(int timerPutMessageThreadNum) { this.timerPutMessageThreadNum = timerPutMessageThreadNum; } public boolean isTimerEnableDisruptor() { return timerEnableDisruptor; } public boolean isTimerEnableCheckMetrics() { return timerEnableCheckMetrics; } public void setTimerEnableCheckMetrics(boolean timerEnableCheckMetrics) { this.timerEnableCheckMetrics = timerEnableCheckMetrics; } public boolean isTimerStopEnqueue() { return timerStopEnqueue; } public void setTimerStopEnqueue(boolean timerStopEnqueue) { this.timerStopEnqueue = timerStopEnqueue; } public String getTimerCheckMetricsWhen() { return timerCheckMetricsWhen; } public boolean isTimerSkipUnknownError() { return timerSkipUnknownError; } public void setTimerSkipUnknownError(boolean timerSkipUnknownError) { this.timerSkipUnknownError = timerSkipUnknownError; } public boolean isTimerEnableRetryUntilSuccess() { return timerEnableRetryUntilSuccess; } public void setTimerEnableRetryUntilSuccess(boolean timerEnableRetryUntilSuccess) { this.timerEnableRetryUntilSuccess = timerEnableRetryUntilSuccess; } public boolean isTimerWarmEnable() { return timerWarmEnable; } public boolean isTimerWheelSnapshotFlush() { return timerWheelSnapshotFlush; } public void setTimerWheelSnapshotFlush(boolean timerWheelSnapshotFlush) { this.timerWheelSnapshotFlush = timerWheelSnapshotFlush; } public boolean isTimerWheelEnable() { return timerWheelEnable; } public void setTimerWheelEnable(boolean timerWheelEnable) { this.timerWheelEnable = timerWheelEnable; } public boolean isTimerStopDequeue() { return timerStopDequeue; } public int getTimerMetricSmallThreshold() { return timerMetricSmallThreshold; } public void setTimerMetricSmallThreshold(int timerMetricSmallThreshold) { this.timerMetricSmallThreshold = timerMetricSmallThreshold; } public int getTimerCongestNumEachSlot() { return timerCongestNumEachSlot; } public void setTimerCongestNumEachSlot(int timerCongestNumEachSlot) { // In order to get this value from messageStoreConfig properties file created before v4.4.1. this.timerCongestNumEachSlot = timerCongestNumEachSlot; } public int getTimerFlushIntervalMs() { return timerFlushIntervalMs; } public void setTimerFlushIntervalMs(final int timerFlushIntervalMs) { this.timerFlushIntervalMs = timerFlushIntervalMs; } public void setTimerRollWindowSlot(final int timerRollWindowSlot) { this.timerRollWindowSlot = timerRollWindowSlot; } public int getTimerProgressLogIntervalMs() { return timerProgressLogIntervalMs; } public int getTimerWheelSnapshotIntervalMs() { return timerWheelSnapshotIntervalMs; } public void setTimerWheelSnapshotIntervalMs(int timerWheelSnapshotIntervalMs) { this.timerWheelSnapshotIntervalMs = timerWheelSnapshotIntervalMs; } public void setTimerProgressLogIntervalMs(final int timerProgressLogIntervalMs) { this.timerProgressLogIntervalMs = timerProgressLogIntervalMs; } public boolean isTimerInterceptDelayLevel() { return timerInterceptDelayLevel; } public void setTimerInterceptDelayLevel(boolean timerInterceptDelayLevel) { this.timerInterceptDelayLevel = timerInterceptDelayLevel; } public int getTimerMaxDelaySec() { return timerMaxDelaySec; } public void setTimerMaxDelaySec(final int timerMaxDelaySec) { this.timerMaxDelaySec = timerMaxDelaySec; } public int getMaxConsumeQueueScan() { return maxConsumeQueueScan; } public void setMaxConsumeQueueScan(int maxConsumeQueueScan) { this.maxConsumeQueueScan = maxConsumeQueueScan; } public int getSampleCountThreshold() { return sampleCountThreshold; } public void setSampleCountThreshold(int sampleCountThreshold) { this.sampleCountThreshold = sampleCountThreshold; } public boolean isColdDataFlowControlEnable() { return coldDataFlowControlEnable; } public void setColdDataFlowControlEnable(boolean coldDataFlowControlEnable) { this.coldDataFlowControlEnable = coldDataFlowControlEnable; } public boolean isColdDataScanEnable() { return coldDataScanEnable; } public void setColdDataScanEnable(boolean coldDataScanEnable) { this.coldDataScanEnable = coldDataScanEnable; } public int getTimerColdDataCheckIntervalMs() { return timerColdDataCheckIntervalMs; } public void setTimerColdDataCheckIntervalMs(int timerColdDataCheckIntervalMs) { this.timerColdDataCheckIntervalMs = timerColdDataCheckIntervalMs; } public int getSampleSteps() { return sampleSteps; } public void setSampleSteps(int sampleSteps) { this.sampleSteps = sampleSteps; } public int getAccessMessageInMemoryHotRatio() { return accessMessageInMemoryHotRatio; } public void setAccessMessageInMemoryHotRatio(int accessMessageInMemoryHotRatio) { this.accessMessageInMemoryHotRatio = accessMessageInMemoryHotRatio; } public boolean isDataReadAheadEnable() { return dataReadAheadEnable; } public void setDataReadAheadEnable(boolean dataReadAheadEnable) { this.dataReadAheadEnable = dataReadAheadEnable; } public boolean isEnableBuildConsumeQueueConcurrently() { return enableBuildConsumeQueueConcurrently; } public void setEnableBuildConsumeQueueConcurrently(boolean enableBuildConsumeQueueConcurrently) { this.enableBuildConsumeQueueConcurrently = enableBuildConsumeQueueConcurrently; } public int getBatchDispatchRequestThreadPoolNums() { return batchDispatchRequestThreadPoolNums; } public void setBatchDispatchRequestThreadPoolNums(int batchDispatchRequestThreadPoolNums) { this.batchDispatchRequestThreadPoolNums = batchDispatchRequestThreadPoolNums; } public boolean isRealTimePersistRocksDBConfig() { return realTimePersistRocksDBConfig; } public void setRealTimePersistRocksDBConfig(boolean realTimePersistRocksDBConfig) { this.realTimePersistRocksDBConfig = realTimePersistRocksDBConfig; } public long getStatRocksDBCQIntervalSec() { return statRocksDBCQIntervalSec; } public void setStatRocksDBCQIntervalSec(long statRocksDBCQIntervalSec) { this.statRocksDBCQIntervalSec = statRocksDBCQIntervalSec; } public long getCleanRocksDBDirtyCQIntervalMin() { return cleanRocksDBDirtyCQIntervalMin; } public void setCleanRocksDBDirtyCQIntervalMin(long cleanRocksDBDirtyCQIntervalMin) { this.cleanRocksDBDirtyCQIntervalMin = cleanRocksDBDirtyCQIntervalMin; } public long getMemTableFlushIntervalMs() { return memTableFlushIntervalMs; } public void setMemTableFlushIntervalMs(long memTableFlushIntervalMs) { this.memTableFlushIntervalMs = memTableFlushIntervalMs; } public boolean isEnableRocksDBLog() { return enableRocksDBLog; } public void setEnableRocksDBLog(boolean enableRocksDBLog) { this.enableRocksDBLog = enableRocksDBLog; } public int getTopicQueueLockNum() { return topicQueueLockNum; } public void setTopicQueueLockNum(int topicQueueLockNum) { this.topicQueueLockNum = topicQueueLockNum; } public boolean isReadUnCommitted() { return readUnCommitted; } public void setReadUnCommitted(boolean readUnCommitted) { this.readUnCommitted = readUnCommitted; } public boolean isPutConsumeQueueDataByFileChannel() { return putConsumeQueueDataByFileChannel; } public void setPutConsumeQueueDataByFileChannel(boolean putConsumeQueueDataByFileChannel) { this.putConsumeQueueDataByFileChannel = putConsumeQueueDataByFileChannel; } public String getBottomMostCompressionTypeForConsumeQueueStore() { return bottomMostCompressionTypeForConsumeQueueStore; } public void setBottomMostCompressionTypeForConsumeQueueStore(String bottomMostCompressionTypeForConsumeQueueStore) { this.bottomMostCompressionTypeForConsumeQueueStore = bottomMostCompressionTypeForConsumeQueueStore; } public int getRocksdbFlushWalFrequency() { return rocksdbFlushWalFrequency; } public void setRocksdbFlushWalFrequency(int rocksdbFlushWalFrequency) { this.rocksdbFlushWalFrequency = rocksdbFlushWalFrequency; } public long getRocksdbWalFileRollingThreshold() { return rocksdbWalFileRollingThreshold; } public void setRocksdbWalFileRollingThreshold(long rocksdbWalFileRollingThreshold) { this.rocksdbWalFileRollingThreshold = rocksdbWalFileRollingThreshold; } public int getSpinLockCollisionRetreatOptimalDegree() { return spinLockCollisionRetreatOptimalDegree; } public void setSpinLockCollisionRetreatOptimalDegree(int spinLockCollisionRetreatOptimalDegree) { this.spinLockCollisionRetreatOptimalDegree = spinLockCollisionRetreatOptimalDegree; } public void setUseABSLock(boolean useABSLock) { this.useABSLock = useABSLock; } public boolean getUseABSLock() { return useABSLock; } public String getCombineCQPreferCQType() { return combineCQPreferCQType; } public void setCombineCQPreferCQType(String combineCQPreferCQType) { this.combineCQPreferCQType = combineCQPreferCQType; } public String getCombineCQLoadingCQTypes() { return combineCQLoadingCQTypes; } public void setCombineCQLoadingCQTypes(String combineCQLoadingCQTypes) { this.combineCQLoadingCQTypes = combineCQLoadingCQTypes; } public String getCombineAssignOffsetCQType() { return combineAssignOffsetCQType; } public void setCombineAssignOffsetCQType(String combineAssignOffsetCQType) { this.combineAssignOffsetCQType = combineAssignOffsetCQType; } public boolean isCombineCQEnableCheckSelf() { return combineCQEnableCheckSelf; } public void setCombineCQEnableCheckSelf(boolean combineCQEnableCheckSelf) { this.combineCQEnableCheckSelf = combineCQEnableCheckSelf; } public int getCombineCQMaxExtraSearchCommitLogFiles() { return combineCQMaxExtraSearchCommitLogFiles; } public void setCombineCQMaxExtraSearchCommitLogFiles(int combineCQMaxExtraSearchCommitLogFiles) { this.combineCQMaxExtraSearchCommitLogFiles = combineCQMaxExtraSearchCommitLogFiles; } public boolean isEnableLogConsumeQueueRepeatedlyBuildWhenRecover() { return enableLogConsumeQueueRepeatedlyBuildWhenRecover; } public void setEnableLogConsumeQueueRepeatedlyBuildWhenRecover( boolean enableLogConsumeQueueRepeatedlyBuildWhenRecover) { this.enableLogConsumeQueueRepeatedlyBuildWhenRecover = enableLogConsumeQueueRepeatedlyBuildWhenRecover; } public boolean isEnableAcceleratedRecovery() { return enableAcceleratedRecovery; } public void setEnableAcceleratedRecovery(boolean enableAcceleratedRecovery) { this.enableAcceleratedRecovery = enableAcceleratedRecovery; } public boolean isEnableRunningFlagsInFlush() { return enableRunningFlagsInFlush; } public void setEnableRunningFlagsInFlush(boolean enableRunningFlagsInFlush) { this.enableRunningFlagsInFlush = enableRunningFlagsInFlush; } public boolean isTimerRocksDBEnable() { return timerRocksDBEnable; } public void setTimerRocksDBEnable(boolean timerRocksDBEnable) { this.timerRocksDBEnable = timerRocksDBEnable; } public double getTimerRocksDBRollMaxTps() { return timerRocksDBRollMaxTps; } public void setTimerRocksDBRollMaxTps(double timerRocksDBRollMaxTps) { this.timerRocksDBRollMaxTps = timerRocksDBRollMaxTps; } public double getTimerRocksDBTimeExpiredMaxTps() { return timerRocksDBTimeExpiredMaxTps; } public void setTimerRocksDBTimeExpiredMaxTps(double timerRocksDBTimeExpiredMaxTps) { this.timerRocksDBTimeExpiredMaxTps = timerRocksDBTimeExpiredMaxTps; } public boolean isTransRocksDBEnable() { return transRocksDBEnable; } public void setTransRocksDBEnable(boolean transRocksDBEnable) { this.transRocksDBEnable = transRocksDBEnable; } public boolean isIndexRocksDBEnable() { return indexRocksDBEnable; } public void setIndexRocksDBEnable(boolean indexRocksDBEnable) { this.indexRocksDBEnable = indexRocksDBEnable; } public int getMaxRocksDBIndexQueryDays() { return maxRocksDBIndexQueryDays; } public void setMaxRocksDBIndexQueryDays(int maxRocksDBIndexQueryDays) { this.maxRocksDBIndexQueryDays = maxRocksDBIndexQueryDays; } public boolean isTimerRocksDBStopScan() { return timerRocksDBStopScan; } public void setTimerRocksDBStopScan(boolean timerRocksDBStopScan) { this.timerRocksDBStopScan = timerRocksDBStopScan; } public long getTimerRocksDBPrecisionMs() { return timerRocksDBPrecisionMs; } public void setTimerRocksDBPrecisionMs(long timerRocksDBPrecisionMs) { this.timerRocksDBPrecisionMs = timerRocksDBPrecisionMs; } public boolean isIndexFileWriteEnable() { return indexFileWriteEnable; } public void setIndexFileWriteEnable(boolean indexFileWriteEnable) { this.indexFileWriteEnable = indexFileWriteEnable; } public boolean isIndexFileReadEnable() { return indexFileReadEnable; } public void setIndexFileReadEnable(boolean indexFileReadEnable) { this.indexFileReadEnable = indexFileReadEnable; } public boolean isTransWriteOriginTransHalfEnable() { return transWriteOriginTransHalfEnable; } public void setTransWriteOriginTransHalfEnable(boolean transWriteOriginTransHalfEnable) { this.transWriteOriginTransHalfEnable = transWriteOriginTransHalfEnable; } public boolean isTimerRecallToTimeWheelEnable() { return timerRecallToTimeWheelEnable; } public void setTimerRecallToTimeWheelEnable(boolean timerRecallToTimeWheelEnable) { this.timerRecallToTimeWheelEnable = timerRecallToTimeWheelEnable; } public boolean isTimerRecallToTimelineEnable() { return timerRecallToTimelineEnable; } public void setTimerRecallToTimelineEnable(boolean timerRecallToTimelineEnable) { this.timerRecallToTimelineEnable = timerRecallToTimelineEnable; } public void setTimerReputServiceCorePoolSize(int timerReputServiceCorePoolSize) { this.timerReputServiceCorePoolSize = timerReputServiceCorePoolSize; } public int getTimerReputServiceCorePoolSize() { return timerReputServiceCorePoolSize; } public void setTimerReputServiceMaxPoolSize(int timerReputServiceMaxPoolSize) { this.timerReputServiceMaxPoolSize = timerReputServiceMaxPoolSize; } public int getTimerReputServiceMaxPoolSize() { return timerReputServiceMaxPoolSize; } public void setTimerReputServiceQueueCapacity(int timerReputServiceQueueCapacity) { this.timerReputServiceQueueCapacity = timerReputServiceQueueCapacity; } public int getTimerReputServiceQueueCapacity() { return timerReputServiceQueueCapacity; } public int getTimerRocksDBRollIntervalHours() { return timerRocksDBRollIntervalHours; } public void setTimerRocksDBRollIntervalHours(int timerRocksDBRollIntervalHours) { this.timerRocksDBRollIntervalHours = timerRocksDBRollIntervalHours; } public int getTimerRocksDBRollRangeHours() { return timerRocksDBRollRangeHours; } public void setTimerRocksDBRollRangeHours(int timerRocksDBRollRangeHours) { this.timerRocksDBRollRangeHours = timerRocksDBRollRangeHours; } public int getCommitLogRecoverMaxNum() { return commitLogRecoverMaxNum; } public void setCommitLogRecoverMaxNum(int commitLogRecoverMaxNum) { this.commitLogRecoverMaxNum = commitLogRecoverMaxNum; } public int getSharedByteBufferNum() { return sharedByteBufferNum; } public void setSharedByteBufferNum(int sharedByteBufferNum) { this.sharedByteBufferNum = sharedByteBufferNum; } public boolean isAppendTopicForTimerDeleteKey() { return appendTopicForTimerDeleteKey; } public void setAppendTopicForTimerDeleteKey(boolean appendTopicForTimerDeleteKey) { this.appendTopicForTimerDeleteKey = appendTopicForTimerDeleteKey; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/config/StorePathConfigHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.config; import java.io.File; public class StorePathConfigHelper { public static String getStorePathConsumeQueue(final String rootDir) { return rootDir + File.separator + "consumequeue"; } public static String getStorePathConsumeQueueExt(final String rootDir) { return rootDir + File.separator + "consumequeue_ext"; } public static String getStorePathBatchConsumeQueue(final String rootDir) { return rootDir + File.separator + "batchconsumequeue"; } public static String getStorePathIndex(final String rootDir) { return rootDir + File.separator + "index"; } public static String getStoreCheckpoint(final String rootDir) { return rootDir + File.separator + "checkpoint"; } public static String getAbortFile(final String rootDir) { return rootDir + File.separator + "abort"; } public static String getLockFile(final String rootDir) { return rootDir + File.separator + "lock"; } public static String getDelayOffsetStorePath(final String rootDir) { return rootDir + File.separator + "config" + File.separator + "delayOffset.json"; } public static String getTranStateTableStorePath(final String rootDir) { return rootDir + File.separator + "transaction" + File.separator + "statetable"; } public static String getTranRedoLogStorePath(final String rootDir) { return rootDir + File.separator + "transaction" + File.separator + "redolog"; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.dledger; import io.openmessaging.storage.dledger.AppendFuture; import io.openmessaging.storage.dledger.BatchAppendFuture; import io.openmessaging.storage.dledger.DLedgerConfig; import io.openmessaging.storage.dledger.DLedgerServer; import io.openmessaging.storage.dledger.entry.DLedgerEntry; import io.openmessaging.storage.dledger.protocol.AppendEntryRequest; import io.openmessaging.storage.dledger.protocol.AppendEntryResponse; import io.openmessaging.storage.dledger.protocol.BatchAppendEntryRequest; import io.openmessaging.storage.dledger.protocol.DLedgerResponseCode; import io.openmessaging.storage.dledger.store.file.DLedgerMmapFileStore; import io.openmessaging.storage.dledger.store.file.MmapFile; import io.openmessaging.storage.dledger.store.file.MmapFileList; import io.openmessaging.storage.dledger.store.file.SelectMmapBufferResult; import io.openmessaging.storage.dledger.utils.DLedgerUtils; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageVersion; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageExtEncoder; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.StoreStatsService; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.MappedFile; import org.rocksdb.RocksDBException; /** * Store all metadata downtime for recovery, data protection reliability */ public class DLedgerCommitLog extends CommitLog { static { System.setProperty("dLedger.multiPath.Splitter", MessageStoreConfig.MULTI_PATH_SPLITTER); } private final DLedgerServer dLedgerServer; private final DLedgerConfig dLedgerConfig; private final DLedgerMmapFileStore dLedgerFileStore; private final MmapFileList dLedgerFileList; //The id identifies the broker role, 0 means master, others means slave private final int id; private final MessageSerializer messageSerializer; private volatile long beginTimeInDledgerLock = 0; //This offset separate the old commitlog from dledger commitlog private long dividedCommitlogOffset = -1; private boolean isInrecoveringOldCommitlog = false; private final StringBuilder msgIdBuilder = new StringBuilder(); public DLedgerCommitLog(final DefaultMessageStore defaultMessageStore) { super(defaultMessageStore); dLedgerConfig = new DLedgerConfig(); dLedgerConfig.setEnableDiskForceClean(defaultMessageStore.getMessageStoreConfig().isCleanFileForciblyEnable()); dLedgerConfig.setStoreType(DLedgerConfig.FILE); dLedgerConfig.setSelfId(defaultMessageStore.getMessageStoreConfig().getdLegerSelfId()); dLedgerConfig.setGroup(defaultMessageStore.getMessageStoreConfig().getdLegerGroup()); dLedgerConfig.setPeers(defaultMessageStore.getMessageStoreConfig().getdLegerPeers()); dLedgerConfig.setStoreBaseDir(defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); dLedgerConfig.setDataStorePath(defaultMessageStore.getMessageStoreConfig().getStorePathDLedgerCommitLog()); dLedgerConfig.setReadOnlyDataStoreDirs(defaultMessageStore.getMessageStoreConfig().getReadOnlyCommitLogStorePaths()); dLedgerConfig.setMappedFileSizeForEntryData(defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog()); dLedgerConfig.setDeleteWhen(defaultMessageStore.getMessageStoreConfig().getDeleteWhen()); dLedgerConfig.setFileReservedHours(defaultMessageStore.getMessageStoreConfig().getFileReservedTime() + 1); dLedgerConfig.setPreferredLeaderId(defaultMessageStore.getMessageStoreConfig().getPreferredLeaderId()); dLedgerConfig.setEnableBatchPush(defaultMessageStore.getMessageStoreConfig().isEnableBatchPush()); dLedgerConfig.setDiskSpaceRatioToCheckExpired(defaultMessageStore.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100f); id = Integer.parseInt(dLedgerConfig.getSelfId().substring(1)) + 1; dLedgerServer = new DLedgerServer(dLedgerConfig); dLedgerFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); DLedgerMmapFileStore.AppendHook appendHook = (entry, buffer, bodyOffset) -> { assert bodyOffset == DLedgerEntry.BODY_OFFSET; buffer.position(buffer.position() + bodyOffset + MessageDecoder.PHY_POS_POSITION); buffer.putLong(entry.getPos() + bodyOffset); }; dLedgerFileStore.addAppendHook(appendHook); dLedgerFileList = dLedgerFileStore.getDataFileList(); this.messageSerializer = new MessageSerializer(defaultMessageStore.getMessageStoreConfig().getMaxMessageSize()); } @Override public boolean load() { return super.load(); } private void refreshConfig() { dLedgerConfig.setEnableDiskForceClean(defaultMessageStore.getMessageStoreConfig().isCleanFileForciblyEnable()); dLedgerConfig.setDeleteWhen(defaultMessageStore.getMessageStoreConfig().getDeleteWhen()); dLedgerConfig.setFileReservedHours(defaultMessageStore.getMessageStoreConfig().getFileReservedTime() + 1); } private void disableDeleteDledger() { dLedgerConfig.setEnableDiskForceClean(false); dLedgerConfig.setFileReservedHours(24 * 365 * 10); } @Override public void start() { dLedgerServer.startup(); } @Override public void shutdown() { dLedgerServer.shutdown(); } @Override public long flush() { dLedgerFileStore.flush(); return dLedgerFileList.getFlushedWhere(); } @Override public long getMaxOffset() { if (dLedgerFileStore.getCommittedPos() > 0) { return dLedgerFileStore.getCommittedPos(); } if (dLedgerFileList.getMinOffset() > 0) { return dLedgerFileList.getMinOffset(); } return 0; } @Override public long getMinOffset() { if (!mappedFileQueue.getMappedFiles().isEmpty()) { return mappedFileQueue.getMinOffset(); } for (MmapFile file : dLedgerFileList.getMappedFiles()) { if (file.isAvailable()) { return file.getFileFromOffset() + file.getStartPosition(); } } return 0; } @Override public long getConfirmOffset() { return this.getMaxOffset(); } @Override public void setConfirmOffset(long phyOffset) { log.warn("Should not set confirm offset {} for dleger commitlog", phyOffset); } @Override public long remainHowManyDataToCommit() { return dLedgerFileList.remainHowManyDataToCommit(); } @Override public long remainHowManyDataToFlush() { return dLedgerFileList.remainHowManyDataToFlush(); } @Override public int deleteExpiredFile( final long expiredTime, final int deleteFilesInterval, final long intervalForcibly, final boolean cleanImmediately ) { if (mappedFileQueue.getMappedFiles().isEmpty()) { refreshConfig(); //To prevent too much log in defaultMessageStore return Integer.MAX_VALUE; } else { disableDeleteDledger(); } int count = super.deleteExpiredFile(expiredTime, deleteFilesInterval, intervalForcibly, cleanImmediately); if (count > 0 || mappedFileQueue.getMappedFiles().size() != 1) { return count; } //the old logic will keep the last file, here to delete it MappedFile mappedFile = mappedFileQueue.getLastMappedFile(); log.info("Try to delete the last old commitlog file {}", mappedFile.getFileName()); long liveMaxTimestamp = mappedFile.getLastModifiedTimestamp() + expiredTime; if (System.currentTimeMillis() >= liveMaxTimestamp || cleanImmediately) { while (!mappedFile.destroy(10 * 1000)) { DLedgerUtils.sleep(1000); } mappedFileQueue.getMappedFiles().remove(mappedFile); } return 1; } public SelectMappedBufferResult convertSbr(SelectMmapBufferResult sbr) { if (sbr == null) { return null; } else { return new DLedgerSelectMappedBufferResult(sbr); } } public SelectMmapBufferResult truncate(SelectMmapBufferResult sbr) { long committedPos = dLedgerFileStore.getCommittedPos(); if (sbr == null || sbr.getStartOffset() == committedPos) { return null; } if (sbr.getStartOffset() + sbr.getSize() <= committedPos) { return sbr; } else { sbr.setSize((int) (committedPos - sbr.getStartOffset())); return sbr; } } @Override public SelectMappedBufferResult getData(final long offset) { if (offset < dividedCommitlogOffset) { return super.getData(offset); } return this.getData(offset, offset == 0); } @Override public SelectMappedBufferResult getData(final long offset, final boolean returnFirstOnNotFound) { if (offset < dividedCommitlogOffset) { return super.getData(offset, returnFirstOnNotFound); } if (offset >= dLedgerFileStore.getCommittedPos()) { return null; } int mappedFileSize = this.dLedgerServer.getdLedgerConfig().getMappedFileSizeForEntryData(); MmapFile mappedFile = this.dLedgerFileList.findMappedFileByOffset(offset, returnFirstOnNotFound); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); SelectMmapBufferResult sbr = mappedFile.selectMappedBuffer(pos); return convertSbr(truncate(sbr)); } return null; } @Override public boolean getData(final long offset, final int size, final ByteBuffer byteBuffer) { if (offset < dividedCommitlogOffset) { return super.getData(offset, size, byteBuffer); } if (offset >= dLedgerFileStore.getCommittedPos()) { return false; } int mappedFileSize = this.dLedgerServer.getdLedgerConfig().getMappedFileSizeForEntryData(); MmapFile mappedFile = this.dLedgerFileList.findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); return mappedFile.getData(pos, size, byteBuffer); } return false; } private void dledgerRecoverNormally(long maxPhyOffsetOfConsumeQueue) throws RocksDBException { dLedgerFileStore.load(); if (!dLedgerFileList.getMappedFiles().isEmpty()) { dLedgerFileStore.recover(); dividedCommitlogOffset = dLedgerFileList.getFirstMappedFile().getFileFromOffset(); MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile != null) { disableDeleteDledger(); } long maxPhyOffset = dLedgerFileList.getMaxWrotePosition(); // Clear ConsumeQueue redundant data if (maxPhyOffsetOfConsumeQueue >= maxPhyOffset) { log.warn("[TruncateCQ]maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, maxPhyOffset); this.defaultMessageStore.truncateDirtyLogicFiles(maxPhyOffset); } return; } //Indicate that, it is the first time to load mixed commitlog, need to recover the old commitlog isInrecoveringOldCommitlog = true; super.recoverNormally(maxPhyOffsetOfConsumeQueue); isInrecoveringOldCommitlog = false; setRecoverPosition(); } private void dledgerRecoverAbnormally(long maxPhyOffsetOfConsumeQueue) throws RocksDBException { boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover(); boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable(); dLedgerFileStore.load(); if (!dLedgerFileList.getMappedFiles().isEmpty()) { dLedgerFileStore.recover(); dividedCommitlogOffset = dLedgerFileList.getFirstMappedFile().getFileFromOffset(); MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile != null) { disableDeleteDledger(); } List mmapFiles = dLedgerFileList.getMappedFiles(); int index = mmapFiles.size() - 1; MmapFile mmapFile = null; for (; index >= 0; index--) { mmapFile = mmapFiles.get(index); if (isMmapFileMatchedRecover(mmapFile, false)) { log.info("dledger recover from this mappFile " + mmapFile.getFileName()); break; } } if (index < 0) { index = 0; mmapFile = mmapFiles.get(index); } ByteBuffer byteBuffer = mmapFile.sliceByteBuffer(); long processOffset = mmapFile.getFileFromOffset(); long mmapFileOffset = 0; while (true) { DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover, checkDupInfo); int size = dispatchRequest.getMsgSize(); if (dispatchRequest.isSuccess()) { if (size > 0) { mmapFileOffset += size; if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) { this.defaultMessageStore.doDispatch(dispatchRequest); } } else { this.defaultMessageStore.doDispatch(dispatchRequest); } } else if (size == 0) { index++; if (index >= mmapFiles.size()) { log.info("dledger recover physics file over, last mapped file " + mmapFile.getFileName()); break; } else { mmapFile = mmapFiles.get(index); byteBuffer = mmapFile.sliceByteBuffer(); processOffset = mmapFile.getFileFromOffset(); mmapFileOffset = 0; log.info("dledger recover next physics file, " + mmapFile.getFileName()); } } } else { log.info("dledger recover physics file end, " + mmapFile.getFileName() + " pos=" + byteBuffer.position()); break; } } processOffset += mmapFileOffset; if (maxPhyOffsetOfConsumeQueue >= processOffset) { log.warn("dledger maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset); this.defaultMessageStore.truncateDirtyLogicFiles(processOffset); } return; } isInrecoveringOldCommitlog = true; super.recoverAbnormally(maxPhyOffsetOfConsumeQueue); isInrecoveringOldCommitlog = false; setRecoverPosition(); } private void setRecoverPosition() { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile == null) { return; } ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); byteBuffer.position(mappedFile.getWrotePosition()); boolean needWriteMagicCode = true; // 1 TOTAL SIZE byteBuffer.getInt(); //size int magicCode = byteBuffer.getInt(); if (magicCode == CommitLog.BLANK_MAGIC_CODE) { needWriteMagicCode = false; } else { log.info("Recover old commitlog found a illegal magic code={}", magicCode); } dLedgerConfig.setEnableDiskForceClean(false); dividedCommitlogOffset = mappedFile.getFileFromOffset() + mappedFile.getFileSize(); log.info("Recover old commitlog needWriteMagicCode={} pos={} file={} dividedCommitlogOffset={}", needWriteMagicCode, mappedFile.getFileFromOffset() + mappedFile.getWrotePosition(), mappedFile.getFileName(), dividedCommitlogOffset); if (needWriteMagicCode) { byteBuffer.position(mappedFile.getWrotePosition()); byteBuffer.putInt(mappedFile.getFileSize() - mappedFile.getWrotePosition()); byteBuffer.putInt(BLANK_MAGIC_CODE); mappedFile.flush(0); } mappedFile.setWrotePosition(mappedFile.getFileSize()); mappedFile.setCommittedPosition(mappedFile.getFileSize()); mappedFile.setFlushedPosition(mappedFile.getFileSize()); dLedgerFileList.getLastMappedFile(dividedCommitlogOffset); log.info("Will set the initial commitlog offset={} for dledger", dividedCommitlogOffset); } private boolean isMmapFileMatchedRecover(final MmapFile mmapFile, boolean recoverNormally) throws RocksDBException { ByteBuffer byteBuffer = mmapFile.sliceByteBuffer(); int magicCode = byteBuffer.getInt(DLedgerEntry.BODY_OFFSET + MessageDecoder.MESSAGE_MAGIC_CODE_POSITION); if (magicCode != MESSAGE_MAGIC_CODE) { return false; } int storeTimestampPosition; int sysFlag = byteBuffer.getInt(DLedgerEntry.BODY_OFFSET + MessageDecoder.SYSFLAG_POSITION); if ((sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0) { storeTimestampPosition = MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION; } else { // v6 address is 12 byte larger than v4 storeTimestampPosition = MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION + 12; } long storeTimestamp = byteBuffer.getLong(DLedgerEntry.BODY_OFFSET + storeTimestampPosition); if (storeTimestamp == 0) { return false; } long phyOffset = byteBuffer.getLong(DLedgerEntry.BODY_OFFSET + MessageDecoder.MESSAGE_PHYSIC_OFFSET_POSITION); if (this.defaultMessageStore.getMessageStoreConfig().isMessageIndexEnable() && this.defaultMessageStore.getMessageStoreConfig().isMessageIndexSafe()) { if (storeTimestamp > this.defaultMessageStore.getStoreCheckpoint().getIndexMsgTimestamp()) { return false; } log.info("DLedgerCommitLog isMmapFileMatchedRecover find satisfied MmapFile for index, " + "MmapFile storeTimestamp={}, MmapFile phyOffset={}, indexMsgTimestamp={}", storeTimestamp, phyOffset, this.defaultMessageStore.getStoreCheckpoint().getIndexMsgTimestamp()); } return this.defaultMessageStore.getQueueStore().isMappedFileMatchedRecover(phyOffset, storeTimestamp, recoverNormally); } @Override public void recoverNormally(long dispatchFromPhyOffset) throws RocksDBException { dledgerRecoverNormally(dispatchFromPhyOffset); } @Override public void recoverAbnormally(long dispatchFromPhyOffset) throws RocksDBException { dledgerRecoverAbnormally(dispatchFromPhyOffset); } @Override public DispatchRequest checkMessageAndReturnSize(ByteBuffer byteBuffer, final boolean checkCRC, final boolean checkDupInfo, final boolean readBody) { if (isInrecoveringOldCommitlog) { return super.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); } try { int bodyOffset = DLedgerEntry.BODY_OFFSET; int pos = byteBuffer.position(); int magic = byteBuffer.getInt(); //In dledger, this field is size, it must be gt 0, so it could prevent collision int magicOld = byteBuffer.getInt(); if (magicOld == CommitLog.BLANK_MAGIC_CODE || magicOld == MessageDecoder.MESSAGE_MAGIC_CODE || magicOld == MessageDecoder.MESSAGE_MAGIC_CODE_V2) { byteBuffer.position(pos); return super.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); } if (magic == MmapFileList.BLANK_MAGIC_CODE) { return new DispatchRequest(0, true); } byteBuffer.position(pos + bodyOffset); DispatchRequest dispatchRequest = super.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); if (dispatchRequest.isSuccess()) { dispatchRequest.setBufferSize(dispatchRequest.getMsgSize() + bodyOffset); } else if (dispatchRequest.getMsgSize() > 0) { dispatchRequest.setBufferSize(dispatchRequest.getMsgSize() + bodyOffset); } return dispatchRequest; } catch (Throwable ignored) { } return new DispatchRequest(-1, false /* success */); } @Override public boolean resetOffset(long offset) { //currently, it seems resetOffset has no use return false; } @Override public long getBeginTimeInLock() { return beginTimeInDledgerLock; } private void setMessageInfo(MessageExtBrokerInner msg, int tranType) { // Set the storage time msg.setStoreTimestamp(System.currentTimeMillis()); // Set the message body BODY CRC (consider the most appropriate setting // on the client) msg.setBodyCRC(UtilAll.crc32(msg.getBody())); InetSocketAddress bornSocketAddress = (InetSocketAddress) msg.getBornHost(); if (bornSocketAddress.getAddress() instanceof Inet6Address) { msg.setBornHostV6Flag(); } InetSocketAddress storeSocketAddress = (InetSocketAddress) msg.getStoreHost(); if (storeSocketAddress.getAddress() instanceof Inet6Address) { msg.setStoreHostAddressV6Flag(); } } @Override public CompletableFuture asyncPutMessage(MessageExtBrokerInner msg) { StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService(); final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); setMessageInfo(msg, tranType); final String finalTopic = msg.getTopic(); msg.setVersion(MessageVersion.MESSAGE_VERSION_V1); boolean autoMessageVersionOnTopicLen = this.defaultMessageStore.getMessageStoreConfig().isAutoMessageVersionOnTopicLen(); if (autoMessageVersionOnTopicLen && msg.getTopic().length() > Byte.MAX_VALUE) { msg.setVersion(MessageVersion.MESSAGE_VERSION_V2); } // Back to Results AppendMessageResult appendResult; AppendFuture dledgerFuture; EncodeResult encodeResult; encodeResult = this.messageSerializer.serialize(msg); if (encodeResult.status != AppendMessageStatus.PUT_OK) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status))); } String topicQueueKey = msg.getTopic() + "-" + msg.getQueueId(); topicQueueLock.lock(topicQueueKey); try { defaultMessageStore.assignOffset(msg); putMessageLock.lock(); //spin or ReentrantLock ,depending on store config long elapsedTimeInLock; long queueOffset; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); queueOffset = getQueueOffsetByKey(msg, tranType); encodeResult.setQueueOffsetKey(queueOffset, false); AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); request.setBody(encodeResult.getData()); dledgerFuture = (AppendFuture) dLedgerServer.handleAppend(request); if (dledgerFuture.getPos() == -1) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.OS_PAGE_CACHE_BUSY, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); } long wroteOffset = dledgerFuture.getPos() + DLedgerEntry.BODY_OFFSET; int msgIdLength = (msg.getSysFlag() & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 + 8 : 16 + 4 + 8; ByteBuffer buffer = ByteBuffer.allocate(msgIdLength); String msgId = MessageDecoder.createMessageId(buffer, msg.getStoreHostBytes(), wroteOffset); elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginTimeInDledgerLock; appendResult = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, encodeResult.getData().length, msgId, System.currentTimeMillis(), queueOffset, elapsedTimeInLock); } finally { beginTimeInDledgerLock = 0; putMessageLock.unlock(); } if (elapsedTimeInLock > 500) { log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, msg.getBody().length, appendResult); } defaultMessageStore.increaseOffset(msg, getMessageNum(msg)); } catch (Exception e) { log.error("Put message error", e); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); } finally { topicQueueLock.unlock(topicQueueKey); } return dledgerFuture.thenApply(appendEntryResponse -> { PutMessageStatus putMessageStatus = PutMessageStatus.UNKNOWN_ERROR; switch (DLedgerResponseCode.valueOf(appendEntryResponse.getCode())) { case SUCCESS: putMessageStatus = PutMessageStatus.PUT_OK; break; case INCONSISTENT_LEADER: case NOT_LEADER: case LEADER_NOT_READY: case DISK_FULL: putMessageStatus = PutMessageStatus.SERVICE_NOT_AVAILABLE; break; case WAIT_QUORUM_ACK_TIMEOUT: //Do not return flush_slave_timeout to the client, for the client will ignore it. putMessageStatus = PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH; break; case LEADER_PENDING_FULL: putMessageStatus = PutMessageStatus.OS_PAGE_CACHE_BUSY; break; } PutMessageResult putMessageResult = new PutMessageResult(putMessageStatus, appendResult); if (putMessageStatus == PutMessageStatus.PUT_OK) { // Statistics storeStatsService.getSinglePutMessageTopicTimesTotal(finalTopic).add(1); storeStatsService.getSinglePutMessageTopicSizeTotal(msg.getTopic()).add(appendResult.getWroteBytes()); } return putMessageResult; }); } @Override public CompletableFuture asyncPutMessages(MessageExtBatch messageExtBatch) { final int tranType = MessageSysFlag.getTransactionValue(messageExtBatch.getSysFlag()); if (tranType != MessageSysFlag.TRANSACTION_NOT_TYPE) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } if (messageExtBatch.getDelayTimeLevel() > 0) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } // Set the storage time messageExtBatch.setStoreTimestamp(System.currentTimeMillis()); StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService(); InetSocketAddress bornSocketAddress = (InetSocketAddress) messageExtBatch.getBornHost(); if (bornSocketAddress.getAddress() instanceof Inet6Address) { messageExtBatch.setBornHostV6Flag(); } InetSocketAddress storeSocketAddress = (InetSocketAddress) messageExtBatch.getStoreHost(); if (storeSocketAddress.getAddress() instanceof Inet6Address) { messageExtBatch.setStoreHostAddressV6Flag(); } messageExtBatch.setVersion(MessageVersion.MESSAGE_VERSION_V1); boolean autoMessageVersionOnTopicLen = this.defaultMessageStore.getMessageStoreConfig().isAutoMessageVersionOnTopicLen(); if (autoMessageVersionOnTopicLen && messageExtBatch.getTopic().length() > Byte.MAX_VALUE) { messageExtBatch.setVersion(MessageVersion.MESSAGE_VERSION_V2); } // Back to Results AppendMessageResult appendResult; BatchAppendFuture dledgerFuture; EncodeResult encodeResult; encodeResult = this.messageSerializer.serialize(messageExtBatch); if (encodeResult.status != AppendMessageStatus.PUT_OK) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult .status))); } int batchNum = encodeResult.batchData.size(); topicQueueLock.lock(encodeResult.queueOffsetKey); try { defaultMessageStore.assignOffset(messageExtBatch); putMessageLock.lock(); //spin or ReentrantLock ,depending on store config msgIdBuilder.setLength(0); long elapsedTimeInLock; long queueOffset; int msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); queueOffset = getQueueOffsetByKey(messageExtBatch, tranType); encodeResult.setQueueOffsetKey(queueOffset, true); BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); request.setBatchMsgs(encodeResult.batchData); AppendFuture appendFuture = (AppendFuture) dLedgerServer.handleAppend(request); if (appendFuture.getPos() == -1) { log.warn("HandleAppend return false due to error code {}", appendFuture.get().getCode()); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.OS_PAGE_CACHE_BUSY, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); } dledgerFuture = (BatchAppendFuture) appendFuture; long wroteOffset = 0; int msgIdLength = (messageExtBatch.getSysFlag() & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 + 8 : 16 + 4 + 8; ByteBuffer buffer = ByteBuffer.allocate(msgIdLength); boolean isFirstOffset = true; long firstWroteOffset = 0; for (long pos : dledgerFuture.getPositions()) { wroteOffset = pos + DLedgerEntry.BODY_OFFSET; if (isFirstOffset) { firstWroteOffset = wroteOffset; isFirstOffset = false; } String msgId = MessageDecoder.createMessageId(buffer, messageExtBatch.getStoreHostBytes(), wroteOffset); if (msgIdBuilder.length() > 0) { msgIdBuilder.append(',').append(msgId); } else { msgIdBuilder.append(msgId); } msgNum++; } elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginTimeInDledgerLock; appendResult = new AppendMessageResult(AppendMessageStatus.PUT_OK, firstWroteOffset, encodeResult.totalMsgLen, msgIdBuilder.toString(), System.currentTimeMillis(), queueOffset, elapsedTimeInLock); appendResult.setMsgNum(msgNum); } finally { beginTimeInDledgerLock = 0; putMessageLock.unlock(); } if (elapsedTimeInLock > 500) { log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, messageExtBatch.getBody().length, appendResult); } defaultMessageStore.increaseOffset(messageExtBatch, (short) batchNum); } catch (Exception e) { log.error("Put message error", e); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); } finally { topicQueueLock.unlock(encodeResult.queueOffsetKey); } return dledgerFuture.thenApply(appendEntryResponse -> { PutMessageStatus putMessageStatus = PutMessageStatus.UNKNOWN_ERROR; switch (DLedgerResponseCode.valueOf(appendEntryResponse.getCode())) { case SUCCESS: putMessageStatus = PutMessageStatus.PUT_OK; break; case INCONSISTENT_LEADER: case NOT_LEADER: case LEADER_NOT_READY: case DISK_FULL: putMessageStatus = PutMessageStatus.SERVICE_NOT_AVAILABLE; break; case WAIT_QUORUM_ACK_TIMEOUT: //Do not return flush_slave_timeout to the client, for the client will ignore it. putMessageStatus = PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH; break; case LEADER_PENDING_FULL: putMessageStatus = PutMessageStatus.OS_PAGE_CACHE_BUSY; break; } PutMessageResult putMessageResult = new PutMessageResult(putMessageStatus, appendResult); if (putMessageStatus == PutMessageStatus.PUT_OK) { // Statistics storeStatsService.getSinglePutMessageTopicTimesTotal(messageExtBatch.getTopic()).add(appendResult.getMsgNum()); storeStatsService.getSinglePutMessageTopicSizeTotal(messageExtBatch.getTopic()).add(appendResult.getWroteBytes()); } return putMessageResult; }); } @Override public SelectMappedBufferResult getMessage(final long offset, final int size) { if (offset < dividedCommitlogOffset) { return super.getMessage(offset, size); } int mappedFileSize = this.dLedgerServer.getdLedgerConfig().getMappedFileSizeForEntryData(); MmapFile mappedFile = this.dLedgerFileList.findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); return convertSbr(mappedFile.selectMappedBuffer(pos, size)); } return null; } @Override public long rollNextFile(final long offset) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); return offset + mappedFileSize - offset % mappedFileSize; } @Override public void destroy() { super.destroy(); dLedgerFileList.destroy(); } @Override public boolean appendData(long startOffset, byte[] data, int dataStart, int dataLength) { //the old ha service will invoke method, here to prevent it return false; } @Override public void checkSelf() { dLedgerFileList.checkSelf(); } @Override public long lockTimeMills() { long diff = 0; long begin = this.beginTimeInDledgerLock; if (begin > 0) { diff = this.defaultMessageStore.now() - begin; } if (diff < 0) { diff = 0; } return diff; } private long getQueueOffsetByKey(MessageExtBrokerInner msg, int tranType) { Long queueOffset = msg.getQueueOffset(); // Transaction messages that require special handling switch (tranType) { // Prepared and Rollback message is not consumed, will not enter the // consumer queuec case MessageSysFlag.TRANSACTION_PREPARED_TYPE: case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: queueOffset = 0L; break; case MessageSysFlag.TRANSACTION_NOT_TYPE: case MessageSysFlag.TRANSACTION_COMMIT_TYPE: default: break; } return queueOffset; } class EncodeResult { private String queueOffsetKey; private ByteBuffer data; private List batchData; private AppendMessageStatus status; private int totalMsgLen; public EncodeResult(AppendMessageStatus status, ByteBuffer data, String queueOffsetKey) { this.data = data; this.status = status; this.queueOffsetKey = queueOffsetKey; } public void setQueueOffsetKey(long offset, boolean isBatch) { if (!isBatch) { this.data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); return; } for (byte[] data : batchData) { ByteBuffer.wrap(data).putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset++); } } public byte[] getData() { return data.array(); } public EncodeResult(AppendMessageStatus status, String queueOffsetKey, List batchData, int totalMsgLen) { this.batchData = batchData; this.status = status; this.queueOffsetKey = queueOffsetKey; this.totalMsgLen = totalMsgLen; } } class MessageSerializer { // The maximum length of the message body private final int maxMessageBodySize; MessageSerializer(final int size) { this.maxMessageBodySize = size; } public EncodeResult serialize(final MessageExtBrokerInner msgInner) { // STORETIMESTAMP + STOREHOSTADDRESS + OFFSET
    // PHY OFFSET long wroteOffset = 0; long queueOffset = 0; int sysflag = msgInner.getSysFlag(); int bornHostLength = (sysflag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; int storeHostLength = (sysflag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; ByteBuffer bornHostHolder = ByteBuffer.allocate(bornHostLength); ByteBuffer storeHostHolder = ByteBuffer.allocate(storeHostLength); String key = msgInner.getTopic() + "-" + msgInner.getQueueId(); /** * Serialize message */ final byte[] propertiesData = msgInner.getPropertiesString() == null ? null : msgInner.getPropertiesString().getBytes(MessageDecoder.CHARSET_UTF8); final int propertiesLength = propertiesData == null ? 0 : propertiesData.length; if (propertiesLength > Short.MAX_VALUE) { log.warn("putMessage message properties length too long. length={}", propertiesData.length); return new EncodeResult(AppendMessageStatus.PROPERTIES_SIZE_EXCEEDED, null, key); } final byte[] topicData = msgInner.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; final int bodyLength = msgInner.getBody() == null ? 0 : msgInner.getBody().length; final int msgLen = MessageExtEncoder.calMsgLength(msgInner.getVersion(), msgInner.getSysFlag(), bodyLength, topicLength, propertiesLength); ByteBuffer msgStoreItemMemory = ByteBuffer.allocate(msgLen); // Exceeds the maximum message if (bodyLength > this.maxMessageBodySize) { DLedgerCommitLog.log.warn("message body size exceeded, msg total size: " + msgLen + ", msg body size: " + bodyLength + ", maxMessageBodySize: " + this.maxMessageBodySize); return new EncodeResult(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED, null, key); } // Initialization of storage space this.resetByteBuffer(msgStoreItemMemory, msgLen); // 1 TOTALSIZE msgStoreItemMemory.putInt(msgLen); // 2 MAGICCODE msgStoreItemMemory.putInt(msgInner.getVersion().getMagicCode()); // 3 BODYCRC msgStoreItemMemory.putInt(msgInner.getBodyCRC()); // 4 QUEUEID msgStoreItemMemory.putInt(msgInner.getQueueId()); // 5 FLAG msgStoreItemMemory.putInt(msgInner.getFlag()); // 6 QUEUEOFFSET msgStoreItemMemory.putLong(queueOffset); // 7 PHYSICALOFFSET msgStoreItemMemory.putLong(wroteOffset); // 8 SYSFLAG msgStoreItemMemory.putInt(msgInner.getSysFlag()); // 9 BORNTIMESTAMP msgStoreItemMemory.putLong(msgInner.getBornTimestamp()); // 10 BORNHOST resetByteBuffer(bornHostHolder, bornHostLength); msgStoreItemMemory.put(msgInner.getBornHostBytes(bornHostHolder)); // 11 STORETIMESTAMP msgStoreItemMemory.putLong(msgInner.getStoreTimestamp()); // 12 STOREHOSTADDRESS resetByteBuffer(storeHostHolder, storeHostLength); msgStoreItemMemory.put(msgInner.getStoreHostBytes(storeHostHolder)); //this.msgBatchMemory.put(msgInner.getStoreHostBytes()); // 13 RECONSUMETIMES msgStoreItemMemory.putInt(msgInner.getReconsumeTimes()); // 14 Prepared Transaction Offset msgStoreItemMemory.putLong(msgInner.getPreparedTransactionOffset()); // 15 BODY msgStoreItemMemory.putInt(bodyLength); if (bodyLength > 0) { msgStoreItemMemory.put(msgInner.getBody()); } // 16 TOPIC msgInner.getVersion().putTopicLength(msgStoreItemMemory, topicLength); msgStoreItemMemory.put(topicData); // 17 PROPERTIES msgStoreItemMemory.putShort((short) propertiesLength); if (propertiesLength > 0) { msgStoreItemMemory.put(propertiesData); } return new EncodeResult(AppendMessageStatus.PUT_OK, msgStoreItemMemory, key); } public EncodeResult serialize(final MessageExtBatch messageExtBatch) { String key = messageExtBatch.getTopic() + "-" + messageExtBatch.getQueueId(); int totalMsgLen = 0; ByteBuffer messagesByteBuff = messageExtBatch.wrap(); int totalLength = messagesByteBuff.limit(); if (totalLength > this.maxMessageBodySize) { CommitLog.log.warn("message body size exceeded, msg body size: " + totalLength + ", maxMessageBodySize: " + this.maxMessageBodySize); throw new RuntimeException("message size exceeded"); } List batchBody = new LinkedList<>(); int sysFlag = messageExtBatch.getSysFlag(); int bornHostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; int storeHostLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 : 16 + 4; ByteBuffer bornHostHolder = ByteBuffer.allocate(bornHostLength); ByteBuffer storeHostHolder = ByteBuffer.allocate(storeHostLength); while (messagesByteBuff.hasRemaining()) { // 1 TOTALSIZE messagesByteBuff.getInt(); // 2 MAGICCODE messagesByteBuff.getInt(); // 3 BODYCRC messagesByteBuff.getInt(); // 4 FLAG int flag = messagesByteBuff.getInt(); // 5 BODY int bodyLen = messagesByteBuff.getInt(); int bodyPos = messagesByteBuff.position(); int bodyCrc = UtilAll.crc32(messagesByteBuff.array(), bodyPos, bodyLen); messagesByteBuff.position(bodyPos + bodyLen); // 6 properties short propertiesLen = messagesByteBuff.getShort(); int propertiesPos = messagesByteBuff.position(); messagesByteBuff.position(propertiesPos + propertiesLen); final byte[] topicData = messageExtBatch.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; final int msgLen = MessageExtEncoder.calMsgLength(messageExtBatch.getVersion(), messageExtBatch.getSysFlag(), bodyLen, topicLength, propertiesLen); ByteBuffer msgStoreItemMemory = ByteBuffer.allocate(msgLen); totalMsgLen += msgLen; // Initialization of storage space this.resetByteBuffer(msgStoreItemMemory, msgLen); // 1 TOTALSIZE msgStoreItemMemory.putInt(msgLen); // 2 MAGICCODE msgStoreItemMemory.putInt(messageExtBatch.getVersion().getMagicCode()); // 3 BODYCRC msgStoreItemMemory.putInt(bodyCrc); // 4 QUEUEID msgStoreItemMemory.putInt(messageExtBatch.getQueueId()); // 5 FLAG msgStoreItemMemory.putInt(flag); // 6 QUEUEOFFSET msgStoreItemMemory.putLong(0L); // 7 PHYSICALOFFSET msgStoreItemMemory.putLong(0); // 8 SYSFLAG msgStoreItemMemory.putInt(messageExtBatch.getSysFlag()); // 9 BORNTIMESTAMP msgStoreItemMemory.putLong(messageExtBatch.getBornTimestamp()); // 10 BORNHOST resetByteBuffer(bornHostHolder, bornHostLength); msgStoreItemMemory.put(messageExtBatch.getBornHostBytes(bornHostHolder)); // 11 STORETIMESTAMP msgStoreItemMemory.putLong(messageExtBatch.getStoreTimestamp()); // 12 STOREHOSTADDRESS resetByteBuffer(storeHostHolder, storeHostLength); msgStoreItemMemory.put(messageExtBatch.getStoreHostBytes(storeHostHolder)); // 13 RECONSUMETIMES msgStoreItemMemory.putInt(messageExtBatch.getReconsumeTimes()); // 14 Prepared Transaction Offset msgStoreItemMemory.putLong(0); // 15 BODY msgStoreItemMemory.putInt(bodyLen); if (bodyLen > 0) { msgStoreItemMemory.put(messagesByteBuff.array(), bodyPos, bodyLen); } // 16 TOPIC messageExtBatch.getVersion().putTopicLength(msgStoreItemMemory, topicLength); msgStoreItemMemory.put(topicData); // 17 PROPERTIES msgStoreItemMemory.putShort(propertiesLen); if (propertiesLen > 0) { msgStoreItemMemory.put(messagesByteBuff.array(), propertiesPos, propertiesLen); } byte[] data = new byte[msgLen]; msgStoreItemMemory.clear(); msgStoreItemMemory.get(data); batchBody.add(data); } return new EncodeResult(AppendMessageStatus.PUT_OK, key, batchBody, totalMsgLen); } private void resetByteBuffer(final ByteBuffer byteBuffer, final int limit) { byteBuffer.flip(); byteBuffer.limit(limit); } } public static class DLedgerSelectMappedBufferResult extends SelectMappedBufferResult { private SelectMmapBufferResult sbr; public DLedgerSelectMappedBufferResult(SelectMmapBufferResult sbr) { super(sbr.getStartOffset(), sbr.getByteBuffer(), sbr.getSize(), null); this.sbr = sbr; } @Override public synchronized void release() { super.release(); if (sbr != null) { sbr.release(); } } } public DLedgerServer getdLedgerServer() { return dLedgerServer; } public int getId() { return id; } public long getDividedCommitlogOffset() { return dividedCommitlogOffset; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/exception/ConsumeQueueException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.exception; public class ConsumeQueueException extends StoreException { public ConsumeQueueException() { } public ConsumeQueueException(String message) { super(message); } public ConsumeQueueException(String message, Throwable cause) { super(message, cause); } public ConsumeQueueException(Throwable cause) { super(cause); } public ConsumeQueueException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/exception/StoreException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.exception; public class StoreException extends Exception { public StoreException() { } public StoreException(String message) { super(message); } public StoreException(String message, Throwable cause) { super(message, cause); } public StoreException(Throwable cause) { super(cause); } public StoreException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.store.DefaultMessageStore; public class DefaultHAClient extends ServiceThread implements HAClient { /** * Report header buffer size. Schema: slaveMaxOffset. Format: * *
         * ┌───────────────────────────────────────────────┐
         * │                  slaveMaxOffset               │
         * │                    (8bytes)                   │
         * ├───────────────────────────────────────────────┤
         * │                                               │
         * │                  Report Header                │
         * 
    *

    */ public static final int REPORT_HEADER_SIZE = 8; private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final int READ_MAX_BUFFER_SIZE = 1024 * 1024 * 4; private final AtomicReference masterHaAddress = new AtomicReference<>(); private final AtomicReference masterAddress = new AtomicReference<>(); private final ByteBuffer reportOffset = ByteBuffer.allocate(REPORT_HEADER_SIZE); private SocketChannel socketChannel; private Selector selector; /** * last time that slave reads date from master. */ private long lastReadTimestamp = System.currentTimeMillis(); /** * last time that slave reports offset to master. */ private long lastWriteTimestamp = System.currentTimeMillis(); private long currentReportedOffset = 0; private int dispatchPosition = 0; private ByteBuffer byteBufferRead = ByteBuffer.allocate(READ_MAX_BUFFER_SIZE); private ByteBuffer byteBufferBackup = ByteBuffer.allocate(READ_MAX_BUFFER_SIZE); private DefaultMessageStore defaultMessageStore; private volatile HAConnectionState currentState = HAConnectionState.READY; private FlowMonitor flowMonitor; public DefaultHAClient(DefaultMessageStore defaultMessageStore) throws IOException { this.selector = NetworkUtil.openSelector(); this.defaultMessageStore = defaultMessageStore; this.flowMonitor = new FlowMonitor(defaultMessageStore.getMessageStoreConfig()); } public void updateHaMasterAddress(final String newAddr) { String currentAddr = this.masterHaAddress.get(); if (masterHaAddress.compareAndSet(currentAddr, newAddr)) { log.info("update master ha address, OLD: " + currentAddr + " NEW: " + newAddr); } } public void updateMasterAddress(final String newAddr) { String currentAddr = this.masterAddress.get(); if (masterAddress.compareAndSet(currentAddr, newAddr)) { log.info("update master address, OLD: " + currentAddr + " NEW: " + newAddr); } } public String getHaMasterAddress() { return this.masterHaAddress.get(); } public String getMasterAddress() { return this.masterAddress.get(); } private boolean isTimeToReportOffset() { long interval = defaultMessageStore.now() - this.lastWriteTimestamp; return interval > defaultMessageStore.getMessageStoreConfig().getHaSendHeartbeatInterval(); } private boolean reportSlaveMaxOffset(final long maxOffset) { this.reportOffset.position(0); this.reportOffset.limit(REPORT_HEADER_SIZE); this.reportOffset.putLong(maxOffset); this.reportOffset.position(0); this.reportOffset.limit(REPORT_HEADER_SIZE); for (int i = 0; i < 3 && this.reportOffset.hasRemaining(); i++) { try { this.socketChannel.write(this.reportOffset); } catch (IOException e) { log.error(this.getServiceName() + "reportSlaveMaxOffset this.socketChannel.write exception", e); return false; } } lastWriteTimestamp = this.defaultMessageStore.getSystemClock().now(); return !this.reportOffset.hasRemaining(); } private void reallocateByteBuffer() { int remain = READ_MAX_BUFFER_SIZE - this.dispatchPosition; if (remain > 0) { this.byteBufferRead.position(this.dispatchPosition); this.byteBufferBackup.position(0); this.byteBufferBackup.limit(READ_MAX_BUFFER_SIZE); this.byteBufferBackup.put(this.byteBufferRead); } this.swapByteBuffer(); this.byteBufferRead.position(remain); this.byteBufferRead.limit(READ_MAX_BUFFER_SIZE); this.dispatchPosition = 0; } private void swapByteBuffer() { ByteBuffer tmp = this.byteBufferRead; this.byteBufferRead = this.byteBufferBackup; this.byteBufferBackup = tmp; } private boolean processReadEvent() { int readSizeZeroTimes = 0; while (this.byteBufferRead.hasRemaining()) { try { int readSize = this.socketChannel.read(this.byteBufferRead); if (readSize > 0) { flowMonitor.addByteCountTransferred(readSize); readSizeZeroTimes = 0; boolean result = this.dispatchReadRequest(); if (!result) { log.error("HAClient, dispatchReadRequest error"); return false; } lastReadTimestamp = System.currentTimeMillis(); } else if (readSize == 0) { if (++readSizeZeroTimes >= 3) { break; } } else { log.info("HAClient, processReadEvent read socket < 0"); return false; } } catch (IOException e) { log.info("HAClient, processReadEvent read socket exception", e); return false; } } return true; } private boolean dispatchReadRequest() { int readSocketPos = this.byteBufferRead.position(); while (true) { int diff = this.byteBufferRead.position() - this.dispatchPosition; if (diff >= DefaultHAConnection.TRANSFER_HEADER_SIZE) { long masterPhyOffset = this.byteBufferRead.getLong(this.dispatchPosition); int bodySize = this.byteBufferRead.getInt(this.dispatchPosition + 8); long slavePhyOffset = this.defaultMessageStore.getMaxPhyOffset(); if (slavePhyOffset != 0) { if (slavePhyOffset != masterPhyOffset) { log.error("master pushed offset not equal the max phy offset in slave, SLAVE: " + slavePhyOffset + " MASTER: " + masterPhyOffset); return false; } } if (diff >= (DefaultHAConnection.TRANSFER_HEADER_SIZE + bodySize)) { byte[] bodyData = byteBufferRead.array(); int dataStart = this.dispatchPosition + DefaultHAConnection.TRANSFER_HEADER_SIZE; this.defaultMessageStore.appendToCommitLog( masterPhyOffset, bodyData, dataStart, bodySize); this.byteBufferRead.position(readSocketPos); this.dispatchPosition += DefaultHAConnection.TRANSFER_HEADER_SIZE + bodySize; if (!reportSlaveMaxOffsetPlus()) { return false; } continue; } } if (!this.byteBufferRead.hasRemaining()) { this.reallocateByteBuffer(); } break; } return true; } private boolean reportSlaveMaxOffsetPlus() { boolean result = true; long currentPhyOffset = this.defaultMessageStore.getMaxPhyOffset(); if (currentPhyOffset > this.currentReportedOffset) { this.currentReportedOffset = currentPhyOffset; result = this.reportSlaveMaxOffset(this.currentReportedOffset); if (!result) { this.closeMaster(); log.error("HAClient, reportSlaveMaxOffset error, " + this.currentReportedOffset); } } return result; } public void changeCurrentState(HAConnectionState currentState) { log.info("change state to {}", currentState); this.currentState = currentState; } public boolean connectMaster() throws ClosedChannelException { if (null == socketChannel) { String addr = this.masterHaAddress.get(); if (addr != null) { SocketAddress socketAddress = NetworkUtil.string2SocketAddress(addr); this.socketChannel = RemotingHelper.connect(socketAddress); if (this.socketChannel != null) { this.socketChannel.register(this.selector, SelectionKey.OP_READ); log.info("HAClient connect to master {}", addr); this.changeCurrentState(HAConnectionState.TRANSFER); } } this.currentReportedOffset = this.defaultMessageStore.getMaxPhyOffset(); this.lastReadTimestamp = System.currentTimeMillis(); } return this.socketChannel != null; } public void closeMaster() { if (null != this.socketChannel) { try { SelectionKey sk = this.socketChannel.keyFor(this.selector); if (sk != null) { sk.cancel(); } this.socketChannel.close(); this.socketChannel = null; log.info("HAClient close connection with master {}", this.masterHaAddress.get()); this.changeCurrentState(HAConnectionState.READY); } catch (IOException e) { log.warn("closeMaster exception. ", e); } this.lastReadTimestamp = 0; this.dispatchPosition = 0; this.byteBufferBackup.position(0); this.byteBufferBackup.limit(READ_MAX_BUFFER_SIZE); this.byteBufferRead.position(0); this.byteBufferRead.limit(READ_MAX_BUFFER_SIZE); } } @Override public void run() { log.info(this.getServiceName() + " service started"); this.flowMonitor.start(); while (!this.isStopped()) { try { switch (this.currentState) { case SHUTDOWN: this.flowMonitor.shutdown(true); return; case READY: if (!this.connectMaster()) { log.warn("HAClient connect to master {} failed", this.masterHaAddress.get()); this.waitForRunning(1000 * 5); } continue; case TRANSFER: if (!transferFromMaster()) { closeMasterAndWait(); continue; } break; default: this.waitForRunning(1000 * 2); continue; } long interval = this.defaultMessageStore.now() - this.lastReadTimestamp; if (interval > this.defaultMessageStore.getMessageStoreConfig().getHaHousekeepingInterval()) { log.warn("AutoRecoverHAClient, housekeeping, found this connection[" + this.masterHaAddress + "] expired, " + interval); this.closeMaster(); log.warn("AutoRecoverHAClient, master not response some time, so close connection"); } } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); this.closeMasterAndWait(); } } this.flowMonitor.shutdown(true); log.info(this.getServiceName() + " service end"); } private boolean transferFromMaster() throws IOException { boolean result; if (this.isTimeToReportOffset()) { log.info("Slave report current offset {}", this.currentReportedOffset); result = this.reportSlaveMaxOffset(this.currentReportedOffset); if (!result) { return false; } } this.selector.select(1000); result = this.processReadEvent(); if (!result) { return false; } return reportSlaveMaxOffsetPlus(); } public void closeMasterAndWait() { this.closeMaster(); this.waitForRunning(1000 * 5); } public long getLastWriteTimestamp() { return this.lastWriteTimestamp; } public long getLastReadTimestamp() { return lastReadTimestamp; } @Override public HAConnectionState getCurrentState() { return currentState; } @Override public long getTransferredByteInSecond() { return flowMonitor.getTransferredByteInSecond(); } @Override public void shutdown() { this.changeCurrentState(HAConnectionState.SHUTDOWN); this.flowMonitor.shutdown(); super.shutdown(); closeMaster(); try { this.selector.close(); } catch (IOException e) { log.warn("Close the selector of AutoRecoverHAClient error, ", e); } } @Override public String getServiceName() { if (this.defaultMessageStore != null && this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return this.defaultMessageStore.getBrokerIdentity().getIdentifier() + DefaultHAClient.class.getSimpleName(); } return DefaultHAClient.class.getSimpleName(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAConnection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.NettySystemConfig; import org.apache.rocketmq.store.SelectMappedBufferResult; public class DefaultHAConnection implements HAConnection { /** * Transfer Header buffer size. Schema: physic offset and body size. Format: * *

         * ┌───────────────────────────────────────────────┬───────────────────────┐
         * │                  physicOffset                 │         bodySize      │
         * │                    (8bytes)                   │         (4bytes)      │
         * ├───────────────────────────────────────────────┴───────────────────────┤
         * │                                                                       │
         * │                           Transfer Header                             │
         * 
    *

    */ public static final int TRANSFER_HEADER_SIZE = 8 + 4; private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final DefaultHAService haService; private final SocketChannel socketChannel; private final String clientAddress; private WriteSocketService writeSocketService; private ReadSocketService readSocketService; private volatile HAConnectionState currentState = HAConnectionState.TRANSFER; private volatile long slaveRequestOffset = -1; private volatile long slaveAckOffset = -1; private FlowMonitor flowMonitor; public DefaultHAConnection(final DefaultHAService haService, final SocketChannel socketChannel) throws IOException { this.haService = haService; this.socketChannel = socketChannel; this.clientAddress = this.socketChannel.socket().getRemoteSocketAddress().toString(); this.socketChannel.configureBlocking(false); this.socketChannel.socket().setSoLinger(false, -1); this.socketChannel.socket().setTcpNoDelay(true); if (NettySystemConfig.socketSndbufSize > 0) { this.socketChannel.socket().setReceiveBufferSize(NettySystemConfig.socketSndbufSize); } if (NettySystemConfig.socketRcvbufSize > 0) { this.socketChannel.socket().setSendBufferSize(NettySystemConfig.socketRcvbufSize); } this.writeSocketService = new WriteSocketService(this.socketChannel); this.readSocketService = new ReadSocketService(this.socketChannel); this.haService.getConnectionCount().incrementAndGet(); this.flowMonitor = new FlowMonitor(haService.getDefaultMessageStore().getMessageStoreConfig()); } public void start() { changeCurrentState(HAConnectionState.TRANSFER); this.flowMonitor.start(); this.readSocketService.start(); this.writeSocketService.start(); } public void shutdown() { changeCurrentState(HAConnectionState.SHUTDOWN); this.writeSocketService.shutdown(true); this.readSocketService.shutdown(true); this.flowMonitor.shutdown(true); this.close(); } public void close() { if (this.socketChannel != null) { try { this.socketChannel.close(); } catch (IOException e) { log.error("", e); } } } public SocketChannel getSocketChannel() { return socketChannel; } public void changeCurrentState(HAConnectionState currentState) { log.info("change state to {}", currentState); this.currentState = currentState; } @Override public HAConnectionState getCurrentState() { return currentState; } @Override public String getClientAddress() { return this.clientAddress; } @Override public long getSlaveAckOffset() { return slaveAckOffset; } public long getTransferredByteInSecond() { return this.flowMonitor.getTransferredByteInSecond(); } public long getTransferFromWhere() { return writeSocketService.getNextTransferFromWhere(); } class ReadSocketService extends ServiceThread { private static final int READ_MAX_BUFFER_SIZE = 1024 * 1024; private final Selector selector; private final SocketChannel socketChannel; private final ByteBuffer byteBufferRead = ByteBuffer.allocate(READ_MAX_BUFFER_SIZE); private int processPosition = 0; private volatile long lastReadTimestamp = System.currentTimeMillis(); public ReadSocketService(final SocketChannel socketChannel) throws IOException { this.selector = NetworkUtil.openSelector(); this.socketChannel = socketChannel; this.socketChannel.register(this.selector, SelectionKey.OP_READ); this.setDaemon(true); } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.selector.select(1000); boolean ok = this.processReadEvent(); if (!ok) { log.error("processReadEvent error"); break; } long interval = DefaultHAConnection.this.haService.getDefaultMessageStore().getSystemClock().now() - this.lastReadTimestamp; if (interval > DefaultHAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaHousekeepingInterval()) { log.warn("ha housekeeping, found this connection[" + DefaultHAConnection.this.clientAddress + "] expired, " + interval); break; } } catch (Exception e) { log.error(this.getServiceName() + " service has exception.", e); break; } } changeCurrentState(HAConnectionState.SHUTDOWN); this.makeStop(); writeSocketService.makeStop(); haService.removeConnection(DefaultHAConnection.this); DefaultHAConnection.this.haService.getConnectionCount().decrementAndGet(); SelectionKey sk = this.socketChannel.keyFor(this.selector); if (sk != null) { sk.cancel(); } try { this.selector.close(); this.socketChannel.close(); } catch (IOException e) { log.error("", e); } flowMonitor.shutdown(true); log.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (haService.getDefaultMessageStore().getBrokerConfig().isInBrokerContainer()) { return haService.getDefaultMessageStore().getBrokerIdentity().getIdentifier() + ReadSocketService.class.getSimpleName(); } return ReadSocketService.class.getSimpleName(); } private boolean processReadEvent() { int readSizeZeroTimes = 0; if (!this.byteBufferRead.hasRemaining()) { this.byteBufferRead.flip(); this.processPosition = 0; } while (this.byteBufferRead.hasRemaining()) { try { int readSize = this.socketChannel.read(this.byteBufferRead); if (readSize > 0) { readSizeZeroTimes = 0; this.lastReadTimestamp = DefaultHAConnection.this.haService.getDefaultMessageStore().getSystemClock().now(); if ((this.byteBufferRead.position() - this.processPosition) >= DefaultHAClient.REPORT_HEADER_SIZE) { int pos = this.byteBufferRead.position() - (this.byteBufferRead.position() % DefaultHAClient.REPORT_HEADER_SIZE); long readOffset = this.byteBufferRead.getLong(pos - 8); this.processPosition = pos; DefaultHAConnection.this.slaveAckOffset = readOffset; if (DefaultHAConnection.this.slaveRequestOffset < 0) { DefaultHAConnection.this.slaveRequestOffset = readOffset; log.info("slave[" + DefaultHAConnection.this.clientAddress + "] request offset " + readOffset); } DefaultHAConnection.this.haService.notifyTransferSome(DefaultHAConnection.this.slaveAckOffset); } } else if (readSize == 0) { if (++readSizeZeroTimes >= 3) { break; } } else { log.error("read socket[" + DefaultHAConnection.this.clientAddress + "] < 0"); return false; } } catch (IOException e) { log.error("processReadEvent exception", e); return false; } } return true; } } class WriteSocketService extends ServiceThread { private final Selector selector; private final SocketChannel socketChannel; private final ByteBuffer byteBufferHeader = ByteBuffer.allocate(TRANSFER_HEADER_SIZE); private long nextTransferFromWhere = -1; private SelectMappedBufferResult selectMappedBufferResult; private boolean lastWriteOver = true; private long lastPrintTimestamp = System.currentTimeMillis(); private long lastWriteTimestamp = System.currentTimeMillis(); public WriteSocketService(final SocketChannel socketChannel) throws IOException { this.selector = NetworkUtil.openSelector(); this.socketChannel = socketChannel; this.socketChannel.register(this.selector, SelectionKey.OP_WRITE); this.setDaemon(true); } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.selector.select(1000); if (-1 == DefaultHAConnection.this.slaveRequestOffset) { Thread.sleep(10); continue; } if (-1 == this.nextTransferFromWhere) { if (0 == DefaultHAConnection.this.slaveRequestOffset) { long masterOffset = DefaultHAConnection.this.haService.getDefaultMessageStore().getCommitLog().getMaxOffset(); masterOffset = masterOffset - (masterOffset % DefaultHAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig() .getMappedFileSizeCommitLog()); if (masterOffset < 0) { masterOffset = 0; } this.nextTransferFromWhere = masterOffset; } else { this.nextTransferFromWhere = DefaultHAConnection.this.slaveRequestOffset; } log.info("master transfer data from " + this.nextTransferFromWhere + " to slave[" + DefaultHAConnection.this.clientAddress + "], and slave request " + DefaultHAConnection.this.slaveRequestOffset); } if (this.lastWriteOver) { long interval = DefaultHAConnection.this.haService.getDefaultMessageStore().getSystemClock().now() - this.lastWriteTimestamp; if (interval > DefaultHAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig() .getHaSendHeartbeatInterval()) { // Build Header this.byteBufferHeader.position(0); this.byteBufferHeader.limit(TRANSFER_HEADER_SIZE); this.byteBufferHeader.putLong(this.nextTransferFromWhere); this.byteBufferHeader.putInt(0); this.byteBufferHeader.flip(); this.lastWriteOver = this.transferData(); if (!this.lastWriteOver) continue; } } else { this.lastWriteOver = this.transferData(); if (!this.lastWriteOver) continue; } SelectMappedBufferResult selectResult = DefaultHAConnection.this.haService.getDefaultMessageStore().getCommitLogData(this.nextTransferFromWhere); if (selectResult != null) { int size = selectResult.getSize(); if (size > DefaultHAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize()) { size = DefaultHAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize(); } int canTransferMaxBytes = flowMonitor.canTransferMaxByteNum(); if (size > canTransferMaxBytes) { if (System.currentTimeMillis() - lastPrintTimestamp > 1000) { log.warn("Trigger HA flow control, max transfer speed {}KB/s, current speed: {}KB/s", String.format("%.2f", flowMonitor.maxTransferByteInSecond() / 1024.0), String.format("%.2f", flowMonitor.getTransferredByteInSecond() / 1024.0)); lastPrintTimestamp = System.currentTimeMillis(); } size = canTransferMaxBytes; } long thisOffset = this.nextTransferFromWhere; this.nextTransferFromWhere += size; selectResult.getByteBuffer().limit(size); this.selectMappedBufferResult = selectResult; // Build Header this.byteBufferHeader.position(0); this.byteBufferHeader.limit(TRANSFER_HEADER_SIZE); this.byteBufferHeader.putLong(thisOffset); this.byteBufferHeader.putInt(size); this.byteBufferHeader.flip(); this.lastWriteOver = this.transferData(); } else { DefaultHAConnection.this.haService.getWaitNotifyObject().allWaitForRunning(100); } } catch (Exception e) { DefaultHAConnection.log.error(this.getServiceName() + " service has exception.", e); break; } } DefaultHAConnection.this.haService.getWaitNotifyObject().removeFromWaitingThreadTable(); if (this.selectMappedBufferResult != null) { this.selectMappedBufferResult.release(); } changeCurrentState(HAConnectionState.SHUTDOWN); this.makeStop(); readSocketService.makeStop(); haService.removeConnection(DefaultHAConnection.this); SelectionKey sk = this.socketChannel.keyFor(this.selector); if (sk != null) { sk.cancel(); } try { this.selector.close(); this.socketChannel.close(); } catch (IOException e) { DefaultHAConnection.log.error("", e); } flowMonitor.shutdown(true); DefaultHAConnection.log.info(this.getServiceName() + " service end"); } private boolean transferData() throws Exception { int writeSizeZeroTimes = 0; // Write Header while (this.byteBufferHeader.hasRemaining()) { int writeSize = this.socketChannel.write(this.byteBufferHeader); if (writeSize > 0) { flowMonitor.addByteCountTransferred(writeSize); writeSizeZeroTimes = 0; this.lastWriteTimestamp = DefaultHAConnection.this.haService.getDefaultMessageStore().getSystemClock().now(); } else if (writeSize == 0) { if (++writeSizeZeroTimes >= 3) { break; } } else { throw new Exception("ha master write header error < 0"); } } if (null == this.selectMappedBufferResult) { return !this.byteBufferHeader.hasRemaining(); } writeSizeZeroTimes = 0; // Write Body if (!this.byteBufferHeader.hasRemaining()) { while (this.selectMappedBufferResult.getByteBuffer().hasRemaining()) { int writeSize = this.socketChannel.write(this.selectMappedBufferResult.getByteBuffer()); if (writeSize > 0) { writeSizeZeroTimes = 0; this.lastWriteTimestamp = DefaultHAConnection.this.haService.getDefaultMessageStore().getSystemClock().now(); } else if (writeSize == 0) { if (++writeSizeZeroTimes >= 3) { break; } } else { throw new Exception("ha master write body error < 0"); } } } boolean result = !this.byteBufferHeader.hasRemaining() && !this.selectMappedBufferResult.getByteBuffer().hasRemaining(); if (!this.selectMappedBufferResult.getByteBuffer().hasRemaining()) { this.selectMappedBufferResult.release(); this.selectMappedBufferResult = null; } return result; } @Override public String getServiceName() { if (haService.getDefaultMessageStore().getBrokerConfig().isInBrokerContainer()) { return haService.getDefaultMessageStore().getBrokerIdentity().getIdentifier() + WriteSocketService.class.getSimpleName(); } return WriteSocketService.class.getSimpleName(); } @Override public void shutdown() { super.shutdown(); } public long getNextTransferFromWhere() { return nextTransferFromWhere; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; public class DefaultHAService implements HAService { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected final AtomicInteger connectionCount = new AtomicInteger(0); protected final List connectionList = new LinkedList<>(); protected AcceptSocketService acceptSocketService; protected DefaultMessageStore defaultMessageStore; protected WaitNotifyObject waitNotifyObject = new WaitNotifyObject(); protected AtomicLong push2SlaveMaxOffset = new AtomicLong(0); protected GroupTransferService groupTransferService; protected HAClient haClient; protected HAConnectionStateNotificationService haConnectionStateNotificationService; public DefaultHAService() { } @Override public void init(final DefaultMessageStore defaultMessageStore) throws IOException { this.defaultMessageStore = defaultMessageStore; this.acceptSocketService = new DefaultAcceptSocketService(defaultMessageStore.getMessageStoreConfig()); this.groupTransferService = new GroupTransferService(this, defaultMessageStore); if (this.defaultMessageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { this.haClient = new DefaultHAClient(this.defaultMessageStore); } this.haConnectionStateNotificationService = new HAConnectionStateNotificationService(this, defaultMessageStore); } @Override public void updateMasterAddress(final String newAddr) { if (this.haClient != null) { this.haClient.updateMasterAddress(newAddr); } } @Override public void updateHaMasterAddress(String newAddr) { if (this.haClient != null) { this.haClient.updateHaMasterAddress(newAddr); } } @Override public void putRequest(final CommitLog.GroupCommitRequest request) { this.groupTransferService.putRequest(request); } @Override public boolean isSlaveOK(final long masterPutWhere) { boolean result = this.connectionCount.get() > 0; result = result && masterPutWhere - this.push2SlaveMaxOffset.get() < this.defaultMessageStore .getMessageStoreConfig().getHaMaxGapNotInSync(); return result; } public void notifyTransferSome(final long offset) { for (long value = this.push2SlaveMaxOffset.get(); offset > value; ) { boolean ok = this.push2SlaveMaxOffset.compareAndSet(value, offset); if (ok) { this.groupTransferService.notifyTransferSome(); break; } else { value = this.push2SlaveMaxOffset.get(); } } } @Override public AtomicInteger getConnectionCount() { return connectionCount; } @Override public void start() throws Exception { this.acceptSocketService.beginAccept(); this.acceptSocketService.start(); this.groupTransferService.start(); this.haConnectionStateNotificationService.start(); if (haClient != null) { this.haClient.start(); } } public void addConnection(final HAConnection conn) { synchronized (this.connectionList) { this.connectionList.add(conn); } } public void removeConnection(final HAConnection conn) { this.haConnectionStateNotificationService.checkConnectionStateAndNotify(conn); synchronized (this.connectionList) { this.connectionList.remove(conn); } } @Override public void shutdown() { if (this.haClient != null) { this.haClient.shutdown(); } if (this.acceptSocketService != null) { this.acceptSocketService.shutdown(true); } this.destroyConnections(); if (this.groupTransferService != null) { groupTransferService.shutdown(); } if (this.haConnectionStateNotificationService != null) { this.haConnectionStateNotificationService.shutdown(); } } public void destroyConnections() { synchronized (this.connectionList) { for (HAConnection c : this.connectionList) { c.shutdown(); } this.connectionList.clear(); } } public DefaultMessageStore getDefaultMessageStore() { return defaultMessageStore; } @Override public WaitNotifyObject getWaitNotifyObject() { return waitNotifyObject; } @Override public AtomicLong getPush2SlaveMaxOffset() { return push2SlaveMaxOffset; } @Override public int inSyncReplicasNums(final long masterPutWhere) { int inSyncNums = 1; for (HAConnection conn : this.connectionList) { if (this.isInSyncSlave(masterPutWhere, conn)) { inSyncNums++; } } return inSyncNums; } protected boolean isInSyncSlave(final long masterPutWhere, HAConnection conn) { if (masterPutWhere - conn.getSlaveAckOffset() < this.defaultMessageStore.getMessageStoreConfig() .getHaMaxGapNotInSync()) { return true; } return false; } @Override public void putGroupConnectionStateRequest(HAConnectionStateNotificationRequest request) { this.haConnectionStateNotificationService.setRequest(request); } @Override public List getConnectionList() { return connectionList; } @Override public HAClient getHAClient() { return this.haClient; } @Override public HARuntimeInfo getRuntimeInfo(long masterPutWhere) { HARuntimeInfo info = new HARuntimeInfo(); if (BrokerRole.SLAVE.equals(this.getDefaultMessageStore().getMessageStoreConfig().getBrokerRole())) { info.setMaster(false); info.getHaClientRuntimeInfo().setMasterAddr(this.haClient.getHaMasterAddress()); info.getHaClientRuntimeInfo().setMaxOffset(this.getDefaultMessageStore().getMaxPhyOffset()); info.getHaClientRuntimeInfo().setLastReadTimestamp(this.haClient.getLastReadTimestamp()); info.getHaClientRuntimeInfo().setLastWriteTimestamp(this.haClient.getLastWriteTimestamp()); info.getHaClientRuntimeInfo().setTransferredByteInSecond(this.haClient.getTransferredByteInSecond()); info.getHaClientRuntimeInfo().setMasterFlushOffset(this.defaultMessageStore.getMasterFlushedOffset()); } else { info.setMaster(true); int inSyncNums = 0; info.setMasterCommitLogMaxOffset(masterPutWhere); for (HAConnection conn : this.connectionList) { HARuntimeInfo.HAConnectionRuntimeInfo cInfo = new HARuntimeInfo.HAConnectionRuntimeInfo(); long slaveAckOffset = conn.getSlaveAckOffset(); cInfo.setSlaveAckOffset(slaveAckOffset); cInfo.setDiff(masterPutWhere - slaveAckOffset); cInfo.setAddr(conn.getClientAddress().substring(1)); cInfo.setTransferredByteInSecond(conn.getTransferredByteInSecond()); cInfo.setTransferFromWhere(conn.getTransferFromWhere()); boolean isInSync = this.isInSyncSlave(masterPutWhere, conn); if (isInSync) { inSyncNums++; } cInfo.setInSync(isInSync); info.getHaConnectionInfo().add(cInfo); } info.setInSyncSlaveNums(inSyncNums); } return info; } class DefaultAcceptSocketService extends AcceptSocketService { public DefaultAcceptSocketService(final MessageStoreConfig messageStoreConfig) { super(messageStoreConfig); } @Override protected HAConnection createConnection(SocketChannel sc) throws IOException { return new DefaultHAConnection(DefaultHAService.this, sc); } @Override public String getServiceName() { if (defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return defaultMessageStore.getBrokerConfig().getIdentifier() + AcceptSocketService.class.getSimpleName(); } return DefaultAcceptSocketService.class.getSimpleName(); } } /** * Listens to slave connections to create {@link HAConnection}. */ protected abstract class AcceptSocketService extends ServiceThread { private final SocketAddress socketAddressListen; private ServerSocketChannel serverSocketChannel; private Selector selector; private final MessageStoreConfig messageStoreConfig; public AcceptSocketService(final MessageStoreConfig messageStoreConfig) { this.messageStoreConfig = messageStoreConfig; this.socketAddressListen = new InetSocketAddress(messageStoreConfig.getHaListenPort()); } /** * Starts listening to slave connections. * * @throws Exception If fails. */ public void beginAccept() throws Exception { this.serverSocketChannel = ServerSocketChannel.open(); this.selector = NetworkUtil.openSelector(); this.serverSocketChannel.socket().setReuseAddress(true); this.serverSocketChannel.socket().bind(this.socketAddressListen); if (0 == messageStoreConfig.getHaListenPort()) { messageStoreConfig.setHaListenPort(this.serverSocketChannel.socket().getLocalPort()); log.info("OS picked up {} to listen for HA", messageStoreConfig.getHaListenPort()); } this.serverSocketChannel.configureBlocking(false); this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT); } /** * {@inheritDoc} */ @Override public void shutdown(final boolean interrupt) { super.shutdown(interrupt); try { if (null != this.serverSocketChannel) { this.serverSocketChannel.close(); } if (null != this.selector) { this.selector.close(); } } catch (IOException e) { log.error("AcceptSocketService shutdown exception", e); } } /** * {@inheritDoc} */ @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.selector.select(1000); Set selected = this.selector.selectedKeys(); if (selected != null) { for (SelectionKey k : selected) { if (k.isAcceptable()) { SocketChannel sc = ((ServerSocketChannel) k.channel()).accept(); if (sc != null) { DefaultHAService.log.info("HAService receive new connection, " + sc.socket().getRemoteSocketAddress()); try { HAConnection conn = createConnection(sc); DefaultHAService.this.addConnection(conn); conn.start(); } catch (Exception e) { log.error("new HAConnection exception", e); sc.close(); } } } else { log.warn("Unexpected ops in select " + k.readyOps()); } } selected.clear(); } } catch (Exception e) { log.error(this.getServiceName() + " service has exception.", e); } } log.info(this.getServiceName() + " service end"); } /** * Create ha connection */ protected abstract HAConnection createConnection(final SocketChannel sc) throws IOException; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/FlowMonitor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.store.config.MessageStoreConfig; public class FlowMonitor extends ServiceThread { private final AtomicLong transferredByte = new AtomicLong(0L); private volatile long transferredByteInSecond; protected MessageStoreConfig messageStoreConfig; public FlowMonitor(MessageStoreConfig messageStoreConfig) { this.messageStoreConfig = messageStoreConfig; } @Override public void run() { while (!this.isStopped()) { this.waitForRunning(1 * 1000); this.calculateSpeed(); } } public void calculateSpeed() { this.transferredByteInSecond = this.transferredByte.get(); this.transferredByte.set(0); } public int canTransferMaxByteNum() { // Flow control is not started at present if (this.isFlowControlEnable()) { long res = Math.max(this.maxTransferByteInSecond() - this.transferredByte.get(), 0); return res > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) res; } return Integer.MAX_VALUE; } public void addByteCountTransferred(long count) { this.transferredByte.addAndGet(count); } public long getTransferredByteInSecond() { return this.transferredByteInSecond; } @Override public String getServiceName() { return FlowMonitor.class.getSimpleName(); } protected boolean isFlowControlEnable() { return this.messageStoreConfig.isHaFlowControlEnable(); } public long maxTransferByteInSecond() { return this.messageStoreConfig.getMaxHaTransferByteInSecond(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/GroupTransferService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.PutMessageSpinLock; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAConnection; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; /** * GroupTransferService Service */ public class GroupTransferService extends ServiceThread { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final WaitNotifyObject notifyTransferObject = new WaitNotifyObject(); private final PutMessageSpinLock lock = new PutMessageSpinLock(); private final DefaultMessageStore defaultMessageStore; private final HAService haService; private volatile List requestsWrite = new LinkedList<>(); private volatile List requestsRead = new LinkedList<>(); public GroupTransferService(final HAService haService, final DefaultMessageStore defaultMessageStore) { this.haService = haService; this.defaultMessageStore = defaultMessageStore; } public void putRequest(final CommitLog.GroupCommitRequest request) { lock.lock(); try { this.requestsWrite.add(request); } finally { lock.unlock(); } wakeup(); } public void notifyTransferSome() { this.notifyTransferObject.wakeup(); } private void swapRequests() { lock.lock(); try { List tmp = this.requestsWrite; this.requestsWrite = this.requestsRead; this.requestsRead = tmp; } finally { lock.unlock(); } } private void doWaitTransfer() { if (!this.requestsRead.isEmpty()) { for (CommitLog.GroupCommitRequest req : this.requestsRead) { boolean transferOK = false; long deadLine = req.getDeadLine(); final boolean allAckInSyncStateSet = req.getAckNums() == MixAll.ALL_ACK_IN_SYNC_STATE_SET; for (int i = 0; !transferOK && deadLine - System.nanoTime() > 0; i++) { if (i > 0) { this.notifyTransferObject.waitForRunning(1); } if (!allAckInSyncStateSet && req.getAckNums() <= 1) { transferOK = haService.getPush2SlaveMaxOffset().get() >= req.getNextOffset(); continue; } if (allAckInSyncStateSet && this.haService instanceof AutoSwitchHAService) { // In this mode, we must wait for all replicas that in SyncStateSet. final AutoSwitchHAService autoSwitchHAService = (AutoSwitchHAService) this.haService; final Set syncStateSet = autoSwitchHAService.getSyncStateSet(); if (syncStateSet.size() <= 1) { // Only master transferOK = true; break; } // Include master int ackNums = 1; for (HAConnection conn : haService.getConnectionList()) { final AutoSwitchHAConnection autoSwitchHAConnection = (AutoSwitchHAConnection) conn; if (syncStateSet.contains(autoSwitchHAConnection.getSlaveId()) && autoSwitchHAConnection.getSlaveAckOffset() >= req.getNextOffset()) { ackNums++; } if (ackNums >= syncStateSet.size()) { transferOK = true; break; } } } else { // Include master int ackNums = 1; for (HAConnection conn : haService.getConnectionList()) { // TODO: We must ensure every HAConnection represents a different slave // Solution: Consider assign a unique and fixed IP:ADDR for each different slave if (conn.getSlaveAckOffset() >= req.getNextOffset()) { ackNums++; } if (ackNums >= req.getAckNums()) { transferOK = true; break; } } } } if (!transferOK) { log.warn("transfer message to slave timeout, offset : {}, request acks: {}", req.getNextOffset(), req.getAckNums()); } req.wakeupCustomer(transferOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_SLAVE_TIMEOUT); } this.requestsRead = new LinkedList<>(); } } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.waitForRunning(10); this.doWaitTransfer(); } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } log.info(this.getServiceName() + " service end"); } @Override protected void onWaitEnd() { this.swapRequests(); } @Override public String getServiceName() { if (defaultMessageStore != null && defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return defaultMessageStore.getBrokerIdentity().getIdentifier() + GroupTransferService.class.getSimpleName(); } return GroupTransferService.class.getSimpleName(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/HAClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; public interface HAClient { /** * Start HAClient */ void start(); /** * Shutdown HAClient */ void shutdown(); /** * Wakeup HAClient */ void wakeup(); /** * Update master address * * @param newAddress */ void updateMasterAddress(String newAddress); /** * Update master ha address * * @param newAddress */ void updateHaMasterAddress(String newAddress); /** * Get master address * * @return master address */ String getMasterAddress(); /** * Get master ha address * * @return master ha address */ String getHaMasterAddress(); /** * Get HAClient last read timestamp * * @return last read timestamp */ long getLastReadTimestamp(); /** * Get HAClient last write timestamp * * @return last write timestamp */ long getLastWriteTimestamp(); /** * Get current state for ha connection * * @return HAConnectionState */ HAConnectionState getCurrentState(); /** * Change the current state for ha connection for testing * * @param haConnectionState */ void changeCurrentState(HAConnectionState haConnectionState); /** * Disconnecting from the master for testing */ void closeMaster(); /** * Get the transfer rate per second * * @return transfer bytes in second */ long getTransferredByteInSecond(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/HAConnection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.nio.channels.SocketChannel; public interface HAConnection { /** * Start HA Connection */ void start(); /** * Shutdown HA Connection */ void shutdown(); /** * Close HA Connection */ void close(); /** * Get socket channel */ SocketChannel getSocketChannel(); /** * Get current state for ha connection * * @return HAConnectionState */ HAConnectionState getCurrentState(); /** * Get client address for ha connection * * @return client ip address */ String getClientAddress(); /** * Get the transfer rate per second * * @return transfer bytes in second */ long getTransferredByteInSecond(); /** * Get the current transfer offset to the slave * * @return the current transfer offset to the slave */ long getTransferFromWhere(); /** * Get slave ack offset * * @return slave ack offset */ long getSlaveAckOffset(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionState.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; public enum HAConnectionState { /** * Ready to start connection. */ READY, /** * CommitLog consistency checking. */ HANDSHAKE, /** * Synchronizing data. */ TRANSFER, /** * Temporarily stop transferring. */ SUSPEND, /** * Connection shutdown. */ SHUTDOWN, } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.util.concurrent.CompletableFuture; public class HAConnectionStateNotificationRequest { private final CompletableFuture requestFuture = new CompletableFuture<>(); private final HAConnectionState expectState; private final String remoteAddr; private final boolean notifyWhenShutdown; public HAConnectionStateNotificationRequest(HAConnectionState expectState, String remoteAddr, boolean notifyWhenShutdown) { this.expectState = expectState; this.remoteAddr = remoteAddr; this.notifyWhenShutdown = notifyWhenShutdown; } public CompletableFuture getRequestFuture() { return requestFuture; } public String getRemoteAddr() { return remoteAddr; } public boolean isNotifyWhenShutdown() { return notifyWhenShutdown; } public HAConnectionState getExpectState() { return expectState; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.net.InetSocketAddress; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.BrokerRole; /** * Service to periodically check and notify for certain connection state. */ public class HAConnectionStateNotificationService extends ServiceThread { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final long CONNECTION_ESTABLISH_TIMEOUT = 10 * 1000; private volatile HAConnectionStateNotificationRequest request; private volatile long lastCheckTimeStamp = -1; private HAService haService; private DefaultMessageStore defaultMessageStore; public HAConnectionStateNotificationService(HAService haService, DefaultMessageStore defaultMessageStore) { this.haService = haService; this.defaultMessageStore = defaultMessageStore; } @Override public String getServiceName() { if (defaultMessageStore != null && defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return defaultMessageStore.getBrokerIdentity().getIdentifier() + HAConnectionStateNotificationService.class.getSimpleName(); } return HAConnectionStateNotificationService.class.getSimpleName(); } public synchronized void setRequest(HAConnectionStateNotificationRequest request) { if (this.request != null) { this.request.getRequestFuture().cancel(true); } this.request = request; lastCheckTimeStamp = System.currentTimeMillis(); } private synchronized void doWaitConnectionState() { if (this.request == null || this.request.getRequestFuture().isDone()) { return; } if (this.defaultMessageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { if (haService.getHAClient().getCurrentState() == this.request.getExpectState()) { this.request.getRequestFuture().complete(true); this.request = null; } else if (haService.getHAClient().getCurrentState() == HAConnectionState.READY) { if ((System.currentTimeMillis() - lastCheckTimeStamp) > CONNECTION_ESTABLISH_TIMEOUT) { LOGGER.error("Wait HA connection establish with {} timeout", this.request.getRemoteAddr()); this.request.getRequestFuture().complete(false); this.request = null; } } else { lastCheckTimeStamp = System.currentTimeMillis(); } } else { boolean connectionFound = false; for (HAConnection connection : haService.getConnectionList()) { if (checkConnectionStateAndNotify(connection)) { connectionFound = true; } } if (connectionFound) { lastCheckTimeStamp = System.currentTimeMillis(); } if (!connectionFound && (System.currentTimeMillis() - lastCheckTimeStamp) > CONNECTION_ESTABLISH_TIMEOUT) { LOGGER.error("Wait HA connection establish with {} timeout", this.request.getRemoteAddr()); this.request.getRequestFuture().complete(false); this.request = null; } } } /** * Check if connection matched and notify request. * * @param connection connection to check. * @return if connection remote address match request. */ public synchronized boolean checkConnectionStateAndNotify(HAConnection connection) { if (this.request == null || connection == null) { return false; } String remoteAddress; try { remoteAddress = ((InetSocketAddress) connection.getSocketChannel().getRemoteAddress()) .getAddress().getHostAddress(); if (remoteAddress.equals(request.getRemoteAddr())) { HAConnectionState connState = connection.getCurrentState(); if (connState == this.request.getExpectState()) { this.request.getRequestFuture().complete(true); this.request = null; } else if (this.request.isNotifyWhenShutdown() && connState == HAConnectionState.SHUTDOWN) { this.request.getRequestFuture().complete(false); this.request = null; } return true; } } catch (Exception e) { LOGGER.error("Check connection address exception: {}", e); } return false; } @Override public void run() { LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.waitForRunning(1000); this.doWaitConnectionState(); } catch (Exception e) { LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } LOGGER.info(this.getServiceName() + " service end"); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/HAService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.rocksdb.RocksDBException; public interface HAService { /** * Init HAService, must be called before other methods. * * @param defaultMessageStore * @throws IOException */ void init(DefaultMessageStore defaultMessageStore) throws IOException; /** * Start HA Service * * @throws Exception */ void start() throws Exception; /** * Shutdown HA Service */ void shutdown(); /** * Change to master state * * @param masterEpoch the new masterEpoch */ default boolean changeToMaster(int masterEpoch) throws RocksDBException { return false; } /** * Change to master state * * @param masterEpoch the new masterEpoch */ default boolean changeToMasterWhenLastRoleIsMaster(int masterEpoch) { return false; } /** * Change to slave state * * @param newMasterAddr new master addr * @param newMasterEpoch new masterEpoch */ default boolean changeToSlave(String newMasterAddr, int newMasterEpoch, Long slaveId) { return false; } /** * Change to slave state * * @param newMasterAddr new master addr * @param newMasterEpoch new masterEpoch */ default boolean changeToSlaveWhenMasterNotChange(String newMasterAddr, int newMasterEpoch) { return false; } /** * Update master address * * @param newAddr */ void updateMasterAddress(String newAddr); /** * Update ha master address * * @param newAddr */ void updateHaMasterAddress(String newAddr); /** * Returns the number of replicas those commit log are not far behind the master. It includes master itself. Returns * syncStateSet size if HAService instanceof AutoSwitchService * * @return the number of slaves * @see MessageStoreConfig#getHaMaxGapNotInSync() */ int inSyncReplicasNums(long masterPutWhere); /** * Get connection count * * @return the number of connection */ AtomicInteger getConnectionCount(); /** * Put request to handle HA * * @param request */ void putRequest(final CommitLog.GroupCommitRequest request); /** * Put GroupConnectionStateRequest for preOnline * * @param request */ void putGroupConnectionStateRequest(HAConnectionStateNotificationRequest request); /** * Get ha connection list * * @return List */ List getConnectionList(); /** * Get HAClient * * @return HAClient */ HAClient getHAClient(); /** * Get the max offset in all slaves */ AtomicLong getPush2SlaveMaxOffset(); /** * Get HA runtime info */ HARuntimeInfo getRuntimeInfo(final long masterPutWhere); /** * Get WaitNotifyObject */ WaitNotifyObject getWaitNotifyObject(); /** * Judge whether the slave keeps up according to the masterPutWhere, If the offset gap exceeds haSlaveFallBehindMax, * then slave is not OK */ boolean isSlaveOK(long masterPutWhere); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; public class WaitNotifyObject { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected final ConcurrentHashMap waitingThreadTable = new ConcurrentHashMap<>(16); protected AtomicBoolean hasNotified = new AtomicBoolean(false); public void wakeup() { boolean needNotify = hasNotified.compareAndSet(false, true); if (needNotify) { synchronized (this) { this.notify(); } } } protected void waitForRunning(long interval) { if (this.hasNotified.compareAndSet(true, false)) { this.onWaitEnd(); return; } synchronized (this) { try { if (this.hasNotified.compareAndSet(true, false)) { this.onWaitEnd(); return; } this.wait(interval); } catch (InterruptedException e) { log.error("Interrupted", e); } finally { this.hasNotified.set(false); this.onWaitEnd(); } } } protected void onWaitEnd() { } public void wakeupAll() { boolean needNotify = false; for (Map.Entry entry : this.waitingThreadTable.entrySet()) { if (entry.getValue().compareAndSet(false, true)) { needNotify = true; } } if (needNotify) { synchronized (this) { this.notifyAll(); } } } public void allWaitForRunning(long interval) { long currentThreadId = Thread.currentThread().getId(); AtomicBoolean notified = ConcurrentHashMapUtils.computeIfAbsent(this.waitingThreadTable, currentThreadId, k -> new AtomicBoolean(false)); if (notified.compareAndSet(true, false)) { this.onWaitEnd(); return; } synchronized (this) { try { if (notified.compareAndSet(true, false)) { this.onWaitEnd(); return; } this.wait(interval); } catch (InterruptedException e) { log.error("Interrupted", e); } finally { notified.set(false); this.onWaitEnd(); } } } public void removeFromWaitingThreadTable() { long currentThreadId = Thread.currentThread().getId(); synchronized (this) { this.waitingThreadTable.remove(currentThreadId); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.EpochEntry; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.ha.FlowMonitor; import org.apache.rocketmq.store.ha.HAClient; import org.apache.rocketmq.store.ha.HAConnectionState; import org.apache.rocketmq.store.ha.io.AbstractHAReader; import org.apache.rocketmq.store.ha.io.HAWriter; public class AutoSwitchHAClient extends ServiceThread implements HAClient { /** * Handshake header buffer size. Schema: state ordinal + Two flags + slaveBrokerId. Format: * *

         *                   ┌──────────────────┬───────────────┐
         *                   │isSyncFromLastFile│ isAsyncLearner│
         *                   │     (2bytes)     │   (2bytes)    │
         *                   └──────────────────┴───────────────┘
         *                     \                              /
         *                      \                            /
         *                       ╲                          /
         *                        ╲                        /
         * ┌───────────────────────┬───────────────────────┬───────────────────────┐
         * │      current state    │          Flags        │      slaveBrokerId    │
         * │         (4bytes)      │         (4bytes)      │         (8bytes)      │
         * ├───────────────────────┴───────────────────────┴───────────────────────┤
         * │                                                                       │
         * │                          HANDSHAKE  Header                            │
         * 
    *

    * Flag: isSyncFromLastFile(short), isAsyncLearner(short)... we can add more flags in the future if needed */ public static final int HANDSHAKE_HEADER_SIZE = 4 + 4 + 8; /** * Header + slaveAddress, Format: *

         *                   ┌──────────────────┬───────────────┐
         *                   │isSyncFromLastFile│ isAsyncLearner│
         *                   │     (2bytes)     │   (2bytes)    │
         *                   └──────────────────┴───────────────┘
         *                     \                              /
         *                      \                            /
         *                       ╲                          /
         *                        ╲                        /
         * ┌───────────────────────┬───────────────────────┬───────────────────────┬───────────────────────────────┐
         * │      current state    │          Flags        │  slaveAddressLength   │          slaveAddress         │
         * │         (4bytes)      │         (4bytes)      │         (4bytes)      │             (50bytes)         │
         * ├───────────────────────┴───────────────────────┴───────────────────────┼───────────────────────────────┤
         * │                                                                       │                               │
         * │                        HANDSHAKE  Header                              │               body            │
         * 
    */ @Deprecated public static final int HANDSHAKE_SIZE = HANDSHAKE_HEADER_SIZE + 50; /** * Transfer header buffer size. Schema: state ordinal + maxOffset. Format: *
         * ┌───────────────────────┬───────────────────────┐
         * │      current state    │        maxOffset      │
         * │         (4bytes)      │         (8bytes)      │
         * ├───────────────────────┴───────────────────────┤
         * │                                               │
         * │                TRANSFER  Header               │
         * 
    */ public static final int TRANSFER_HEADER_SIZE = 4 + 8; public static final int MIN_HEADER_SIZE = Math.min(HANDSHAKE_HEADER_SIZE, TRANSFER_HEADER_SIZE); private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final int READ_MAX_BUFFER_SIZE = 1024 * 1024 * 4; private final AtomicReference masterHaAddress = new AtomicReference<>(); private final AtomicReference masterAddress = new AtomicReference<>(); private final ByteBuffer handshakeHeaderBuffer = ByteBuffer.allocate(HANDSHAKE_HEADER_SIZE); private final ByteBuffer transferHeaderBuffer = ByteBuffer.allocate(TRANSFER_HEADER_SIZE); private final AutoSwitchHAService haService; private final ByteBuffer byteBufferRead = ByteBuffer.allocate(READ_MAX_BUFFER_SIZE); private final DefaultMessageStore messageStore; private final EpochFileCache epochCache; private final Long brokerId; private SocketChannel socketChannel; private Selector selector; private AbstractHAReader haReader; private HAWriter haWriter; private FlowMonitor flowMonitor; /** * last time that slave reads date from master. */ private long lastReadTimestamp; /** * last time that slave reports offset to master. */ private long lastWriteTimestamp; private long currentReportedOffset; private int processPosition; private volatile HAConnectionState currentState; /** * Current epoch */ private volatile int currentReceivedEpoch; public AutoSwitchHAClient(AutoSwitchHAService haService, DefaultMessageStore defaultMessageStore, EpochFileCache epochCache, Long brokerId) throws IOException { this.haService = haService; this.messageStore = defaultMessageStore; this.epochCache = epochCache; this.brokerId = brokerId; init(); } public void init() throws IOException { this.selector = NetworkUtil.openSelector(); this.flowMonitor = new FlowMonitor(this.messageStore.getMessageStoreConfig()); this.haReader = new HAClientReader(); haReader.registerHook(readSize -> { if (readSize > 0) { AutoSwitchHAClient.this.flowMonitor.addByteCountTransferred(readSize); lastReadTimestamp = System.currentTimeMillis(); } }); this.haWriter = new HAWriter(); haWriter.registerHook(writeSize -> { if (writeSize > 0) { lastWriteTimestamp = System.currentTimeMillis(); } }); changeCurrentState(HAConnectionState.READY); this.currentReceivedEpoch = -1; this.currentReportedOffset = 0; this.processPosition = 0; this.lastReadTimestamp = System.currentTimeMillis(); this.lastWriteTimestamp = System.currentTimeMillis(); } public void reOpen() throws IOException { shutdown(); init(); } @Override public String getServiceName() { if (haService.getDefaultMessageStore().getBrokerConfig().isInBrokerContainer()) { return haService.getDefaultMessageStore().getBrokerIdentity().getIdentifier() + AutoSwitchHAClient.class.getSimpleName(); } return AutoSwitchHAClient.class.getSimpleName(); } @Override public void updateMasterAddress(String newAddress) { String currentAddr = this.masterAddress.get(); if (!StringUtils.equals(newAddress, currentAddr) && masterAddress.compareAndSet(currentAddr, newAddress)) { LOGGER.info("update master address, OLD: " + currentAddr + " NEW: " + newAddress); } } @Override public void updateHaMasterAddress(String newAddress) { String currentAddr = this.masterHaAddress.get(); if (!StringUtils.equals(newAddress, currentAddr) && masterHaAddress.compareAndSet(currentAddr, newAddress)) { LOGGER.info("update master ha address, OLD: " + currentAddr + " NEW: " + newAddress); wakeup(); } } @Override public String getMasterAddress() { return this.masterAddress.get(); } @Override public String getHaMasterAddress() { return this.masterHaAddress.get(); } @Override public long getLastReadTimestamp() { return this.lastReadTimestamp; } @Override public long getLastWriteTimestamp() { return this.lastWriteTimestamp; } @Override public HAConnectionState getCurrentState() { return this.currentState; } @Override public void changeCurrentState(HAConnectionState haConnectionState) { LOGGER.info("change state to {}", haConnectionState); this.currentState = haConnectionState; } public void closeMasterAndWait() { this.closeMaster(); this.waitForRunning(1000 * 5); } @Override public void closeMaster() { if (null != this.socketChannel) { try { SelectionKey sk = this.socketChannel.keyFor(this.selector); if (sk != null) { sk.cancel(); } this.socketChannel.close(); this.socketChannel = null; LOGGER.info("AutoSwitchHAClient close connection with master {}", this.masterHaAddress.get()); this.changeCurrentState(HAConnectionState.READY); } catch (IOException e) { LOGGER.warn("CloseMaster exception. ", e); } this.lastReadTimestamp = 0; this.processPosition = 0; this.byteBufferRead.position(0); this.byteBufferRead.limit(READ_MAX_BUFFER_SIZE); } } @Override public long getTransferredByteInSecond() { return this.flowMonitor.getTransferredByteInSecond(); } @Override public void shutdown() { changeCurrentState(HAConnectionState.SHUTDOWN); // Shutdown thread firstly this.flowMonitor.shutdown(); super.shutdown(); closeMaster(); try { this.selector.close(); } catch (IOException e) { LOGGER.warn("Close the selector of AutoSwitchHAClient error, ", e); } } private boolean isTimeToReportOffset() { long interval = this.messageStore.now() - this.lastWriteTimestamp; return interval > this.messageStore.getMessageStoreConfig().getHaSendHeartbeatInterval(); } private boolean sendHandshakeHeader() throws IOException { this.handshakeHeaderBuffer.position(0); this.handshakeHeaderBuffer.limit(HANDSHAKE_HEADER_SIZE); // Original state this.handshakeHeaderBuffer.putInt(HAConnectionState.HANDSHAKE.ordinal()); // IsSyncFromLastFile short isSyncFromLastFile = this.haService.getDefaultMessageStore().getMessageStoreConfig().isSyncFromLastFile() ? (short) 1 : (short) 0; this.handshakeHeaderBuffer.putShort(isSyncFromLastFile); // IsAsyncLearner role short isAsyncLearner = this.haService.getDefaultMessageStore().getMessageStoreConfig().isAsyncLearner() ? (short) 1 : (short) 0; this.handshakeHeaderBuffer.putShort(isAsyncLearner); // Slave brokerId this.handshakeHeaderBuffer.putLong(this.brokerId); this.handshakeHeaderBuffer.flip(); return this.haWriter.write(this.socketChannel, this.handshakeHeaderBuffer); } private void handshakeWithMaster() throws IOException { boolean result = this.sendHandshakeHeader(); if (!result) { closeMasterAndWait(); } this.selector.select(5000); result = this.haReader.read(this.socketChannel, this.byteBufferRead); if (!result) { closeMasterAndWait(); } } private boolean reportSlaveOffset(HAConnectionState currentState, final long offsetToReport) throws IOException { this.transferHeaderBuffer.position(0); this.transferHeaderBuffer.limit(TRANSFER_HEADER_SIZE); this.transferHeaderBuffer.putInt(currentState.ordinal()); this.transferHeaderBuffer.putLong(offsetToReport); this.transferHeaderBuffer.flip(); return this.haWriter.write(this.socketChannel, this.transferHeaderBuffer); } private boolean reportSlaveMaxOffset(HAConnectionState currentState) throws IOException { boolean result = true; final long maxPhyOffset = this.messageStore.getMaxPhyOffset(); if (maxPhyOffset > this.currentReportedOffset) { this.currentReportedOffset = maxPhyOffset; result = reportSlaveOffset(currentState, this.currentReportedOffset); } return result; } public boolean connectMaster() throws IOException { if (null == this.socketChannel) { String addr = this.masterHaAddress.get(); if (StringUtils.isNotEmpty(addr)) { SocketAddress socketAddress = NetworkUtil.string2SocketAddress(addr); this.socketChannel = RemotingHelper.connect(socketAddress); if (this.socketChannel != null) { this.socketChannel.register(this.selector, SelectionKey.OP_READ); LOGGER.info("AutoSwitchHAClient connect to master {}", addr); changeCurrentState(HAConnectionState.HANDSHAKE); } } this.currentReportedOffset = this.messageStore.getMaxPhyOffset(); this.lastReadTimestamp = System.currentTimeMillis(); } return this.socketChannel != null; } private boolean transferFromMaster() throws IOException { boolean result; if (isTimeToReportOffset()) { LOGGER.info("Slave report current offset {}", this.currentReportedOffset); result = reportSlaveOffset(HAConnectionState.TRANSFER, this.currentReportedOffset); if (!result) { return false; } } this.selector.select(1000); result = this.haReader.read(this.socketChannel, this.byteBufferRead); if (!result) { return false; } return this.reportSlaveMaxOffset(HAConnectionState.TRANSFER); } @Override public void run() { LOGGER.info(this.getServiceName() + " service started"); this.flowMonitor.start(); while (!this.isStopped()) { try { switch (this.currentState) { case SHUTDOWN: this.flowMonitor.shutdown(true); return; case READY: // Truncate invalid msg first final long truncateOffset = AutoSwitchHAClient.this.haService.truncateInvalidMsg(); if (truncateOffset >= 0) { AutoSwitchHAClient.this.epochCache.truncateSuffixByOffset(truncateOffset); } if (!connectMaster()) { LOGGER.warn("AutoSwitchHAClient connect to master {} failed", this.masterHaAddress.get()); waitForRunning(1000 * 5); } continue; case HANDSHAKE: handshakeWithMaster(); continue; case TRANSFER: if (!transferFromMaster()) { closeMasterAndWait(); continue; } break; case SUSPEND: default: waitForRunning(1000 * 5); continue; } long interval = this.messageStore.now() - this.lastReadTimestamp; if (interval > this.messageStore.getMessageStoreConfig().getHaHousekeepingInterval()) { LOGGER.warn("AutoSwitchHAClient, housekeeping, found this connection[" + this.masterHaAddress + "] expired, " + interval); closeMaster(); LOGGER.warn("AutoSwitchHAClient, master not response some time, so close connection"); } } catch (Exception e) { LOGGER.warn(this.getServiceName() + " service has exception. ", e); closeMasterAndWait(); } } this.flowMonitor.shutdown(true); LOGGER.info(this.getServiceName() + " service end"); } /** * Compare the master and slave's epoch file, find consistent point, do truncate. */ private boolean doTruncate(List masterEpochEntries, long masterEndOffset) throws Exception { if (this.epochCache.getEntrySize() == 0) { // If epochMap is empty, means the broker is a new replicas LOGGER.info("Slave local epochCache is empty, skip truncate log"); changeCurrentState(HAConnectionState.TRANSFER); this.currentReportedOffset = 0; } else { final EpochFileCache masterEpochCache = new EpochFileCache(); masterEpochCache.initCacheFromEntries(masterEpochEntries); masterEpochCache.setLastEpochEntryEndOffset(masterEndOffset); final List localEpochEntries = this.epochCache.getAllEntries(); final EpochFileCache localEpochCache = new EpochFileCache(); localEpochCache.initCacheFromEntries(localEpochEntries); localEpochCache.setLastEpochEntryEndOffset(this.messageStore.getMaxPhyOffset()); LOGGER.info("master epoch entries is {}", masterEpochCache.getAllEntries()); LOGGER.info("local epoch entries is {}", localEpochEntries); final long truncateOffset = localEpochCache.findConsistentPoint(masterEpochCache); LOGGER.info("truncateOffset is {}", truncateOffset); if (truncateOffset < 0) { // If truncateOffset < 0, means we can't find a consistent point LOGGER.error("Failed to find a consistent point between masterEpoch:{} and slaveEpoch:{}", masterEpochEntries, localEpochEntries); return false; } if (!this.messageStore.truncateFiles(truncateOffset)) { LOGGER.error("Failed to truncate slave log to {}", truncateOffset); return false; } final long maxPhyOffset = this.messageStore.getMaxPhyOffset(); if (truncateOffset < maxPhyOffset) { this.epochCache.truncateSuffixByOffset(truncateOffset); LOGGER.info("Truncate slave log to {} success, change to transfer state", truncateOffset); } changeCurrentState(HAConnectionState.TRANSFER); this.currentReportedOffset = truncateOffset; } if (!reportSlaveMaxOffset(HAConnectionState.TRANSFER)) { LOGGER.error("AutoSwitchHAClient report max offset to master failed"); return false; } return true; } class HAClientReader extends AbstractHAReader { @Override protected boolean processReadResult(ByteBuffer byteBufferRead) { int readSocketPos = byteBufferRead.position(); try { while (true) { int diff = byteBufferRead.position() - AutoSwitchHAClient.this.processPosition; if (diff >= AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE) { final int processPosition = AutoSwitchHAClient.this.processPosition; int masterState = byteBufferRead.getInt(processPosition + AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE - 20); int bodySize = byteBufferRead.getInt(processPosition + AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE - 16); long masterOffset = byteBufferRead.getLong(processPosition + AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE - 12); int masterEpoch = byteBufferRead.getInt(processPosition + AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE - 4); long masterEpochStartOffset = 0; long confirmOffset = 0; // If master send transfer header data, set masterEpochStartOffset and confirmOffset value. if (masterState == HAConnectionState.TRANSFER.ordinal() && diff >= AutoSwitchHAConnection.TRANSFER_HEADER_SIZE) { masterEpochStartOffset = byteBufferRead.getLong(processPosition + AutoSwitchHAConnection.TRANSFER_HEADER_SIZE - 16); confirmOffset = byteBufferRead.getLong(processPosition + AutoSwitchHAConnection.TRANSFER_HEADER_SIZE - 8); } if (masterState != AutoSwitchHAClient.this.currentState.ordinal()) { int headerSize = masterState == HAConnectionState.TRANSFER.ordinal() ? AutoSwitchHAConnection.TRANSFER_HEADER_SIZE : AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE; AutoSwitchHAClient.this.processPosition += headerSize + bodySize; AutoSwitchHAClient.this.waitForRunning(1); LOGGER.error("State not matched, masterState:{}, slaveState:{}, bodySize:{}, offset:{}, masterEpoch:{}, masterEpochStartOffset:{}, confirmOffset:{}", HAConnectionState.values()[masterState], AutoSwitchHAClient.this.currentState, bodySize, masterOffset, masterEpoch, masterEpochStartOffset, confirmOffset); return false; } // Flag whether the received data is complete boolean isComplete = true; switch (AutoSwitchHAClient.this.currentState) { case HANDSHAKE: { if (diff < AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE + bodySize) { // The received HANDSHAKE data is not complete isComplete = false; break; } AutoSwitchHAClient.this.processPosition += AutoSwitchHAConnection.HANDSHAKE_HEADER_SIZE; // Truncate log int entrySize = AutoSwitchHAConnection.EPOCH_ENTRY_SIZE; final int entryNums = bodySize / entrySize; final ArrayList epochEntries = new ArrayList<>(entryNums); for (int i = 0; i < entryNums; i++) { int epoch = byteBufferRead.getInt(AutoSwitchHAClient.this.processPosition + i * entrySize); long startOffset = byteBufferRead.getLong(AutoSwitchHAClient.this.processPosition + i * entrySize + 4); epochEntries.add(new EpochEntry(epoch, startOffset)); } byteBufferRead.position(readSocketPos); AutoSwitchHAClient.this.processPosition += bodySize; LOGGER.info("Receive handshake, masterMaxPosition {}, masterEpochEntries:{}, try truncate log", masterOffset, epochEntries); if (!doTruncate(epochEntries, masterOffset)) { waitForRunning(1000 * 2); LOGGER.error("AutoSwitchHAClient truncate log failed in handshake state"); return false; } } break; case TRANSFER: { if (diff < AutoSwitchHAConnection.TRANSFER_HEADER_SIZE + bodySize) { // The received TRANSFER data is not complete isComplete = false; break; } byte[] bodyData = new byte[bodySize]; byteBufferRead.position(AutoSwitchHAClient.this.processPosition + AutoSwitchHAConnection.TRANSFER_HEADER_SIZE); byteBufferRead.get(bodyData); byteBufferRead.position(readSocketPos); AutoSwitchHAClient.this.processPosition += AutoSwitchHAConnection.TRANSFER_HEADER_SIZE + bodySize; long slavePhyOffset = AutoSwitchHAClient.this.messageStore.getMaxPhyOffset(); if (slavePhyOffset != 0) { if (slavePhyOffset != masterOffset) { LOGGER.error("master pushed offset not equal the max phy offset in slave, SLAVE: " + slavePhyOffset + " MASTER: " + masterOffset); return false; } } // If epoch changed if (masterEpoch != AutoSwitchHAClient.this.currentReceivedEpoch) { AutoSwitchHAClient.this.currentReceivedEpoch = masterEpoch; AutoSwitchHAClient.this.epochCache.appendEntry(new EpochEntry(masterEpoch, masterEpochStartOffset)); } if (bodySize > 0) { AutoSwitchHAClient.this.messageStore.appendToCommitLog(masterOffset, bodyData, 0, bodyData.length); } haService.getDefaultMessageStore().setConfirmOffset(Math.min(confirmOffset, messageStore.getMaxPhyOffset())); if (!reportSlaveMaxOffset(HAConnectionState.TRANSFER)) { LOGGER.error("AutoSwitchHAClient report max offset to master failed"); return false; } break; } default: break; } if (isComplete) { continue; } } if (!byteBufferRead.hasRemaining()) { byteBufferRead.position(AutoSwitchHAClient.this.processPosition); byteBufferRead.compact(); AutoSwitchHAClient.this.processPosition = 0; } break; } } catch (final Exception e) { LOGGER.error("Error when ha client process read request", e); } return true; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAConnection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.List; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.netty.NettySystemConfig; import org.apache.rocketmq.remoting.protocol.EpochEntry; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.FlowMonitor; import org.apache.rocketmq.store.ha.HAConnection; import org.apache.rocketmq.store.ha.HAConnectionState; import org.apache.rocketmq.store.ha.io.AbstractHAReader; import org.apache.rocketmq.store.ha.io.HAWriter; public class AutoSwitchHAConnection implements HAConnection { /** * Handshake data protocol in syncing msg from master. Format: *
         * ┌─────────────────┬───────────────┬───────────┬───────────┬────────────────────────────────────┐
         * │  current state  │   body size   │   offset  │   epoch   │   EpochEntrySize * EpochEntryNums  │
         * │     (4bytes)    │   (4bytes)    │  (8bytes) │  (4bytes) │      (12bytes * EpochEntryNums)    │
         * ├─────────────────┴───────────────┴───────────┴───────────┼────────────────────────────────────┤
         * │                       Header                            │             Body                   │
         * │                                                         │                                    │
         * 
    * Handshake Header protocol Format: * current state + body size + offset + epoch */ public static final int HANDSHAKE_HEADER_SIZE = 4 + 4 + 8 + 4; /** * Transfer data protocol in syncing msg from master. Format: *
         * ┌─────────────────┬───────────────┬───────────┬───────────┬─────────────────────┬──────────────────┬──────────────────┐
         * │  current state  │   body size   │   offset  │   epoch   │   epochStartOffset  │   confirmOffset  │    log data      │
         * │     (4bytes)    │   (4bytes)    │  (8bytes) │  (4bytes) │      (8bytes)       │      (8bytes)    │   (data size)    │
         * ├─────────────────┴───────────────┴───────────┴───────────┴─────────────────────┴──────────────────┼──────────────────┤
         * │                                               Header                                             │       Body       │
         * │                                                                                                  │                  │
         * 
    * Transfer Header protocol Format: * current state + body size + offset + epoch + epochStartOffset + additionalInfo(confirmOffset) */ public static final int TRANSFER_HEADER_SIZE = HANDSHAKE_HEADER_SIZE + 8 + 8; public static final int EPOCH_ENTRY_SIZE = 12; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final AutoSwitchHAService haService; private final SocketChannel socketChannel; private final String clientAddress; private final EpochFileCache epochCache; private final AbstractWriteSocketService writeSocketService; private final ReadSocketService readSocketService; private final FlowMonitor flowMonitor; private volatile HAConnectionState currentState = HAConnectionState.HANDSHAKE; private volatile long slaveRequestOffset = -1; private volatile long slaveAckOffset = -1; /** * Whether the slave have already sent a handshake message */ private volatile boolean isSlaveSendHandshake = false; private volatile int currentTransferEpoch = -1; private volatile long currentTransferEpochEndOffset = 0; private volatile boolean isSyncFromLastFile = false; private volatile boolean isAsyncLearner = false; private volatile long slaveId = -1; /** * Last endOffset when master transfer data to slave */ private volatile long lastMasterMaxOffset = -1; /** * Last time ms when transfer data to slave. */ private volatile long lastTransferTimeMs = 0; public AutoSwitchHAConnection(AutoSwitchHAService haService, SocketChannel socketChannel, EpochFileCache epochCache) throws IOException { this.haService = haService; this.socketChannel = socketChannel; this.epochCache = epochCache; this.clientAddress = this.socketChannel.socket().getRemoteSocketAddress().toString(); this.socketChannel.configureBlocking(false); this.socketChannel.socket().setSoLinger(false, -1); this.socketChannel.socket().setTcpNoDelay(true); if (NettySystemConfig.socketSndbufSize > 0) { this.socketChannel.socket().setReceiveBufferSize(NettySystemConfig.socketSndbufSize); } if (NettySystemConfig.socketRcvbufSize > 0) { this.socketChannel.socket().setSendBufferSize(NettySystemConfig.socketRcvbufSize); } this.writeSocketService = new WriteSocketService(this.socketChannel); this.readSocketService = new ReadSocketService(this.socketChannel); this.haService.getConnectionCount().incrementAndGet(); this.flowMonitor = new FlowMonitor(haService.getDefaultMessageStore().getMessageStoreConfig()); } @Override public void start() { changeCurrentState(HAConnectionState.HANDSHAKE); this.flowMonitor.start(); this.readSocketService.start(); this.writeSocketService.start(); } @Override public void shutdown() { changeCurrentState(HAConnectionState.SHUTDOWN); this.flowMonitor.shutdown(true); this.writeSocketService.shutdown(true); this.readSocketService.shutdown(true); this.close(); } @Override public void close() { if (this.socketChannel != null) { try { this.socketChannel.close(); } catch (final IOException e) { LOGGER.error("", e); } } } public void changeCurrentState(HAConnectionState connectionState) { LOGGER.info("change state to {}", connectionState); this.currentState = connectionState; } public long getSlaveId() { return slaveId; } @Override public HAConnectionState getCurrentState() { return currentState; } @Override public SocketChannel getSocketChannel() { return socketChannel; } @Override public String getClientAddress() { return clientAddress; } @Override public long getSlaveAckOffset() { return slaveAckOffset; } @Override public long getTransferredByteInSecond() { return flowMonitor.getTransferredByteInSecond(); } @Override public long getTransferFromWhere() { return this.writeSocketService.getNextTransferFromWhere(); } private void changeTransferEpochToNext(final EpochEntry entry) { this.currentTransferEpoch = entry.getEpoch(); this.currentTransferEpochEndOffset = entry.getEndOffset(); if (entry.getEpoch() == this.epochCache.lastEpoch()) { // Use -1 to stand for Long.max this.currentTransferEpochEndOffset = -1; } } public boolean isAsyncLearner() { return isAsyncLearner; } public boolean isSyncFromLastFile() { return isSyncFromLastFile; } private synchronized void updateLastTransferInfo() { this.lastMasterMaxOffset = this.haService.getDefaultMessageStore().getMaxPhyOffset(); this.lastTransferTimeMs = System.currentTimeMillis(); } private synchronized void maybeExpandInSyncStateSet(long slaveMaxOffset) { if (!this.isAsyncLearner && slaveMaxOffset >= this.lastMasterMaxOffset) { long caughtUpTimeMs = this.haService.getDefaultMessageStore().getMaxPhyOffset() == slaveMaxOffset ? System.currentTimeMillis() : this.lastTransferTimeMs; this.haService.updateConnectionLastCaughtUpTime(this.slaveId, caughtUpTimeMs); this.haService.maybeExpandInSyncStateSet(this.slaveId, slaveMaxOffset); } } class ReadSocketService extends ServiceThread { private static final int READ_MAX_BUFFER_SIZE = 1024 * 1024; private final Selector selector; private final SocketChannel socketChannel; private final ByteBuffer byteBufferRead = ByteBuffer.allocate(READ_MAX_BUFFER_SIZE); private final AbstractHAReader haReader; private int processPosition = 0; private volatile long lastReadTimestamp = System.currentTimeMillis(); public ReadSocketService(final SocketChannel socketChannel) throws IOException { this.selector = NetworkUtil.openSelector(); this.socketChannel = socketChannel; this.socketChannel.register(this.selector, SelectionKey.OP_READ); this.setDaemon(true); haReader = new HAServerReader(); haReader.registerHook(readSize -> { if (readSize > 0) { ReadSocketService.this.lastReadTimestamp = haService.getDefaultMessageStore().getSystemClock().now(); } }); } @Override public void run() { LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.selector.select(1000); boolean ok = this.haReader.read(this.socketChannel, this.byteBufferRead); if (!ok) { AutoSwitchHAConnection.LOGGER.error("processReadEvent error"); break; } long interval = haService.getDefaultMessageStore().getSystemClock().now() - this.lastReadTimestamp; if (interval > haService.getDefaultMessageStore().getMessageStoreConfig().getHaHousekeepingInterval()) { LOGGER.warn("ha housekeeping, found this connection[" + clientAddress + "] expired, " + interval); break; } } catch (Exception e) { AutoSwitchHAConnection.LOGGER.error(this.getServiceName() + " service has exception.", e); break; } } this.makeStop(); changeCurrentState(HAConnectionState.SHUTDOWN); writeSocketService.makeStop(); haService.removeConnection(AutoSwitchHAConnection.this); haService.getConnectionCount().decrementAndGet(); SelectionKey sk = this.socketChannel.keyFor(this.selector); if (sk != null) { sk.cancel(); } try { this.selector.close(); this.socketChannel.close(); } catch (IOException e) { AutoSwitchHAConnection.LOGGER.error("", e); } flowMonitor.shutdown(true); AutoSwitchHAConnection.LOGGER.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (haService.getDefaultMessageStore().getBrokerConfig().isInBrokerContainer()) { return haService.getDefaultMessageStore().getBrokerIdentity().getIdentifier() + ReadSocketService.class.getSimpleName(); } return ReadSocketService.class.getSimpleName(); } class HAServerReader extends AbstractHAReader { @Override protected boolean processReadResult(ByteBuffer byteBufferRead) { while (true) { boolean processSuccess = true; int readSocketPos = byteBufferRead.position(); int diff = byteBufferRead.position() - ReadSocketService.this.processPosition; if (diff >= AutoSwitchHAClient.MIN_HEADER_SIZE) { int readPosition = ReadSocketService.this.processPosition; HAConnectionState slaveState = HAConnectionState.values()[byteBufferRead.getInt(readPosition)]; switch (slaveState) { case HANDSHAKE: // SlaveBrokerId Long slaveBrokerId = byteBufferRead.getLong(readPosition + AutoSwitchHAClient.HANDSHAKE_HEADER_SIZE - 8); AutoSwitchHAConnection.this.slaveId = slaveBrokerId; // Flag(isSyncFromLastFile) short syncFromLastFileFlag = byteBufferRead.getShort(readPosition + AutoSwitchHAClient.HANDSHAKE_HEADER_SIZE - 12); if (syncFromLastFileFlag == 1) { AutoSwitchHAConnection.this.isSyncFromLastFile = true; } // Flag(isAsyncLearner role) short isAsyncLearner = byteBufferRead.getShort(readPosition + AutoSwitchHAClient.HANDSHAKE_HEADER_SIZE - 10); if (isAsyncLearner == 1) { AutoSwitchHAConnection.this.isAsyncLearner = true; } isSlaveSendHandshake = true; byteBufferRead.position(readSocketPos); ReadSocketService.this.processPosition += AutoSwitchHAClient.HANDSHAKE_HEADER_SIZE; LOGGER.info("Receive slave handshake, slaveBrokerId:{}, isSyncFromLastFile:{}, isAsyncLearner:{}", AutoSwitchHAConnection.this.slaveId, AutoSwitchHAConnection.this.isSyncFromLastFile, AutoSwitchHAConnection.this.isAsyncLearner); break; case TRANSFER: long slaveMaxOffset = byteBufferRead.getLong(readPosition + 4); ReadSocketService.this.processPosition += AutoSwitchHAClient.TRANSFER_HEADER_SIZE; AutoSwitchHAConnection.this.slaveAckOffset = slaveMaxOffset; if (slaveRequestOffset < 0) { slaveRequestOffset = slaveMaxOffset; } byteBufferRead.position(readSocketPos); maybeExpandInSyncStateSet(slaveMaxOffset); AutoSwitchHAConnection.this.haService.updateConfirmOffsetWhenSlaveAck(AutoSwitchHAConnection.this.slaveId); AutoSwitchHAConnection.this.haService.notifyTransferSome(AutoSwitchHAConnection.this.slaveAckOffset); break; default: LOGGER.error("Current state illegal {}", currentState); return false; } if (!slaveState.equals(currentState)) { LOGGER.warn("Master change state from {} to {}", currentState, slaveState); changeCurrentState(slaveState); } if (processSuccess) { continue; } } if (!byteBufferRead.hasRemaining()) { byteBufferRead.position(ReadSocketService.this.processPosition); byteBufferRead.compact(); ReadSocketService.this.processPosition = 0; } break; } return true; } } } class WriteSocketService extends AbstractWriteSocketService { private SelectMappedBufferResult selectMappedBufferResult; public WriteSocketService(final SocketChannel socketChannel) throws IOException { super(socketChannel); } @Override protected int getNextTransferDataSize() { SelectMappedBufferResult selectResult = haService.getDefaultMessageStore().getCommitLogData(this.nextTransferFromWhere); if (selectResult == null || selectResult.getSize() <= 0) { return 0; } this.selectMappedBufferResult = selectResult; return selectResult.getSize(); } @Override protected void releaseData() { this.selectMappedBufferResult.release(); this.selectMappedBufferResult = null; } @Override protected boolean transferData(int maxTransferSize) throws Exception { if (null != this.selectMappedBufferResult && maxTransferSize >= 0) { this.selectMappedBufferResult.getByteBuffer().limit(maxTransferSize); } // Write Header boolean result = haWriter.write(this.socketChannel, this.byteBufferHeader); if (!result) { return false; } if (null == this.selectMappedBufferResult) { return true; } // Write Body result = haWriter.write(this.socketChannel, this.selectMappedBufferResult.getByteBuffer()); if (result) { releaseData(); } return result; } @Override protected void onStop() { if (this.selectMappedBufferResult != null) { this.selectMappedBufferResult.release(); } } @Override public String getServiceName() { if (haService.getDefaultMessageStore().getBrokerConfig().isInBrokerContainer()) { return haService.getDefaultMessageStore().getBrokerIdentity().getIdentifier() + WriteSocketService.class.getSimpleName(); } return WriteSocketService.class.getSimpleName(); } } abstract class AbstractWriteSocketService extends ServiceThread { protected final Selector selector; protected final SocketChannel socketChannel; protected final HAWriter haWriter; protected final ByteBuffer byteBufferHeader = ByteBuffer.allocate(TRANSFER_HEADER_SIZE); // Store master epochFileCache: (Epoch + startOffset) * 1000 private final ByteBuffer handShakeBuffer = ByteBuffer.allocate(EPOCH_ENTRY_SIZE * 1000); protected long nextTransferFromWhere = -1; protected boolean lastWriteOver = true; protected long lastWriteTimestamp = System.currentTimeMillis(); protected long lastPrintTimestamp = System.currentTimeMillis(); protected long transferOffset = 0; public AbstractWriteSocketService(final SocketChannel socketChannel) throws IOException { this.selector = NetworkUtil.openSelector(); this.socketChannel = socketChannel; this.socketChannel.register(this.selector, SelectionKey.OP_WRITE); this.setDaemon(true); haWriter = new HAWriter(); haWriter.registerHook(writeSize -> { flowMonitor.addByteCountTransferred(writeSize); if (writeSize > 0) { AbstractWriteSocketService.this.lastWriteTimestamp = haService.getDefaultMessageStore().getSystemClock().now(); } }); } public long getNextTransferFromWhere() { return this.nextTransferFromWhere; } private boolean buildHandshakeBuffer() { final List epochEntries = AutoSwitchHAConnection.this.epochCache.getAllEntries(); final int lastEpoch = AutoSwitchHAConnection.this.epochCache.lastEpoch(); final long maxPhyOffset = AutoSwitchHAConnection.this.haService.getDefaultMessageStore().getMaxPhyOffset(); this.byteBufferHeader.position(0); this.byteBufferHeader.limit(HANDSHAKE_HEADER_SIZE); // State this.byteBufferHeader.putInt(currentState.ordinal()); // Body size this.byteBufferHeader.putInt(epochEntries.size() * EPOCH_ENTRY_SIZE); // Offset this.byteBufferHeader.putLong(maxPhyOffset); // Epoch this.byteBufferHeader.putInt(lastEpoch); this.byteBufferHeader.flip(); // EpochEntries this.handShakeBuffer.position(0); this.handShakeBuffer.limit(EPOCH_ENTRY_SIZE * epochEntries.size()); for (final EpochEntry entry : epochEntries) { if (entry != null) { this.handShakeBuffer.putInt(entry.getEpoch()); this.handShakeBuffer.putLong(entry.getStartOffset()); } } this.handShakeBuffer.flip(); LOGGER.info("Master build handshake header: maxEpoch:{}, maxOffset:{}, epochEntries:{}", lastEpoch, maxPhyOffset, epochEntries); return true; } private boolean handshakeWithSlave() throws IOException { // Write Header boolean result = this.haWriter.write(this.socketChannel, this.byteBufferHeader); if (!result) { return false; } // Write Body return this.haWriter.write(this.socketChannel, this.handShakeBuffer); } // Normal transfer method private void buildTransferHeaderBuffer(long nextOffset, int bodySize) { EpochEntry entry = AutoSwitchHAConnection.this.epochCache.getEntry(AutoSwitchHAConnection.this.currentTransferEpoch); if (entry == null) { // If broker is started on empty disk and no message entered (nextOffset = -1 and currentTransferEpoch = -1), do not output error log when sending heartbeat if (nextOffset != -1 || currentTransferEpoch != -1 || bodySize > 0) { LOGGER.error("Failed to find epochEntry with epoch {} when build msg header", AutoSwitchHAConnection.this.currentTransferEpoch); } if (bodySize > 0) { return; } // Maybe it's used for heartbeat entry = AutoSwitchHAConnection.this.epochCache.firstEntry(); } // Build Header this.byteBufferHeader.position(0); this.byteBufferHeader.limit(TRANSFER_HEADER_SIZE); // State this.byteBufferHeader.putInt(currentState.ordinal()); // Body size this.byteBufferHeader.putInt(bodySize); // Offset this.byteBufferHeader.putLong(nextOffset); // Epoch this.byteBufferHeader.putInt(entry.getEpoch()); // EpochStartOffset this.byteBufferHeader.putLong(entry.getStartOffset()); // Additional info(confirm offset) final long confirmOffset = AutoSwitchHAConnection.this.haService.getDefaultMessageStore().getConfirmOffset(); this.byteBufferHeader.putLong(confirmOffset); this.byteBufferHeader.flip(); } private boolean sendHeartbeatIfNeeded() throws Exception { long interval = haService.getDefaultMessageStore().getSystemClock().now() - this.lastWriteTimestamp; if (interval > haService.getDefaultMessageStore().getMessageStoreConfig().getHaSendHeartbeatInterval()) { buildTransferHeaderBuffer(this.nextTransferFromWhere, 0); return this.transferData(0); } return true; } private void transferToSlave() throws Exception { if (this.lastWriteOver) { this.lastWriteOver = sendHeartbeatIfNeeded(); } else { // maxTransferSize == -1 means to continue transfer remaining data. this.lastWriteOver = this.transferData(-1); } if (!this.lastWriteOver) { return; } int size = this.getNextTransferDataSize(); if (size > 0) { if (size > haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize()) { size = haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize(); } int canTransferMaxBytes = flowMonitor.canTransferMaxByteNum(); if (size > canTransferMaxBytes) { if (System.currentTimeMillis() - lastPrintTimestamp > 1000) { LOGGER.warn("Trigger HA flow control, max transfer speed {}KB/s, current speed: {}KB/s", String.format("%.2f", flowMonitor.maxTransferByteInSecond() / 1024.0), String.format("%.2f", flowMonitor.getTransferredByteInSecond() / 1024.0)); lastPrintTimestamp = System.currentTimeMillis(); } size = canTransferMaxBytes; } if (size <= 0) { this.releaseData(); this.waitForRunning(100); return; } // Check and update currentTransferEpochEndOffset if (AutoSwitchHAConnection.this.currentTransferEpochEndOffset == -1) { EpochEntry currentEpochEntry = AutoSwitchHAConnection.this.epochCache.getEntry(AutoSwitchHAConnection.this.currentTransferEpoch); if (currentEpochEntry != null) { if (currentEpochEntry.getEndOffset() != EpochEntry.LAST_EPOCH_END_OFFSET) { LOGGER.info("Update currentTransferEpochEndOffset from -1 to {}", currentEpochEntry.getEndOffset()); AutoSwitchHAConnection.this.currentTransferEpochEndOffset = currentEpochEntry.getEndOffset(); } } else { // we should never reach here LOGGER.warn("[BUG]Can't find currentTransferEpoch [{}] from epoch cache", currentTransferEpoch); } } // We must ensure that the transmitted logs are within the same epoch // If currentEpochEndOffset == -1, means that currentTransferEpoch = last epoch, so the endOffset = Long.max final long currentEpochEndOffset = AutoSwitchHAConnection.this.currentTransferEpochEndOffset; if (currentEpochEndOffset != -1 && this.nextTransferFromWhere + size > currentEpochEndOffset) { final EpochEntry epochEntry = AutoSwitchHAConnection.this.epochCache.nextEntry(AutoSwitchHAConnection.this.currentTransferEpoch); if (epochEntry == null) { LOGGER.error("Can't find a bigger epochEntry than epoch {}", AutoSwitchHAConnection.this.currentTransferEpoch); waitForRunning(100); return; } size = (int) (currentEpochEndOffset - this.nextTransferFromWhere); changeTransferEpochToNext(epochEntry); } this.transferOffset = this.nextTransferFromWhere; this.nextTransferFromWhere += size; updateLastTransferInfo(); // Build Header buildTransferHeaderBuffer(this.transferOffset, size); this.lastWriteOver = this.transferData(size); } else { // If size == 0, we should update the lastCatchupTimeMs AutoSwitchHAConnection.this.haService.updateConnectionLastCaughtUpTime(AutoSwitchHAConnection.this.slaveId, System.currentTimeMillis()); haService.getWaitNotifyObject().allWaitForRunning(100); } } @Override public void run() { AutoSwitchHAConnection.LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.selector.select(1000); switch (currentState) { case HANDSHAKE: // Wait until the slave send it handshake msg to master. if (!isSlaveSendHandshake) { this.waitForRunning(10); continue; } if (this.lastWriteOver) { if (!buildHandshakeBuffer()) { LOGGER.error("AutoSwitchHAConnection build handshake buffer failed"); this.waitForRunning(5000); continue; } } this.lastWriteOver = handshakeWithSlave(); if (this.lastWriteOver) { // change flag to {false} to wait for slave notification isSlaveSendHandshake = false; } break; case TRANSFER: if (-1 == slaveRequestOffset) { this.waitForRunning(10); continue; } if (-1 == this.nextTransferFromWhere) { if (0 == slaveRequestOffset) { // We must ensure that the starting point of syncing log // must be the startOffset of a file (maybe the last file, or the minOffset) final MessageStoreConfig config = haService.getDefaultMessageStore().getMessageStoreConfig(); if (AutoSwitchHAConnection.this.isSyncFromLastFile) { long masterOffset = haService.getDefaultMessageStore().getCommitLog().getMaxOffset(); masterOffset = masterOffset - (masterOffset % config.getMappedFileSizeCommitLog()); if (masterOffset < 0) { masterOffset = 0; } this.nextTransferFromWhere = masterOffset; } else { this.nextTransferFromWhere = haService.getDefaultMessageStore().getCommitLog().getMinOffset(); } } else { this.nextTransferFromWhere = slaveRequestOffset; } // nextTransferFromWhere is not found. It may be empty disk and no message is entered if (this.nextTransferFromWhere == -1) { sendHeartbeatIfNeeded(); waitForRunning(500); break; } // Setup initial transferEpoch EpochEntry epochEntry = AutoSwitchHAConnection.this.epochCache.findEpochEntryByOffset(this.nextTransferFromWhere); if (epochEntry == null) { LOGGER.error("Failed to find an epochEntry to match nextTransferFromWhere {}", this.nextTransferFromWhere); sendHeartbeatIfNeeded(); waitForRunning(500); break; } changeTransferEpochToNext(epochEntry); LOGGER.info("Master transfer data to slave {}, from offset:{}, currentEpoch:{}", AutoSwitchHAConnection.this.clientAddress, this.nextTransferFromWhere, epochEntry); } transferToSlave(); break; default: throw new Exception("unexpected state " + currentState); } } catch (Exception e) { AutoSwitchHAConnection.LOGGER.error(this.getServiceName() + " service has exception.", e); break; } } this.onStop(); changeCurrentState(HAConnectionState.SHUTDOWN); this.makeStop(); readSocketService.makeStop(); haService.removeConnection(AutoSwitchHAConnection.this); SelectionKey sk = this.socketChannel.keyFor(this.selector); if (sk != null) { sk.cancel(); } try { this.selector.close(); this.socketChannel.close(); } catch (IOException e) { AutoSwitchHAConnection.LOGGER.error("", e); } flowMonitor.shutdown(true); AutoSwitchHAConnection.LOGGER.info(this.getServiceName() + " service end"); } abstract protected int getNextTransferDataSize(); abstract protected void releaseData(); abstract protected boolean transferData(int maxTransferSize) throws Exception; abstract protected void onStop(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import java.io.IOException; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.EpochEntry; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.DefaultHAService; import org.apache.rocketmq.store.ha.GroupTransferService; import org.apache.rocketmq.store.ha.HAClient; import org.apache.rocketmq.store.ha.HAConnection; import org.apache.rocketmq.store.ha.HAConnectionStateNotificationService; import org.rocksdb.RocksDBException; /** * SwitchAble ha service, support switch role to master or slave. */ public class AutoSwitchHAService extends DefaultHAService { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final ExecutorService executorService = ThreadUtils.newSingleThreadExecutor(new ThreadFactoryImpl("AutoSwitchHAService_Executor_")); private final ConcurrentHashMap connectionCaughtUpTimeTable = new ConcurrentHashMap<>(); private final List>> syncStateSetChangedListeners = new ArrayList<>(); private final Set syncStateSet = new HashSet<>(); private final Set remoteSyncStateSet = new HashSet<>(); private final ReadWriteLock syncStateSetReadWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = syncStateSetReadWriteLock.readLock(); private final Lock writeLock = syncStateSetReadWriteLock.writeLock(); // Indicate whether the syncStateSet is currently in the process of being synchronized to controller. private volatile boolean isSynchronizingSyncStateSet = false; private EpochFileCache epochCache; private AutoSwitchHAClient haClient; private Long localBrokerId = null; public AutoSwitchHAService() { } @Override public void init(final DefaultMessageStore defaultMessageStore) throws IOException { this.epochCache = new EpochFileCache(defaultMessageStore.getMessageStoreConfig().getStorePathEpochFile()); this.epochCache.initCacheFromFile(); this.defaultMessageStore = defaultMessageStore; this.acceptSocketService = new AutoSwitchAcceptSocketService(defaultMessageStore.getMessageStoreConfig()); this.groupTransferService = new GroupTransferService(this, defaultMessageStore); this.haConnectionStateNotificationService = new HAConnectionStateNotificationService(this, defaultMessageStore); } @Override public void shutdown() { super.shutdown(); if (this.haClient != null) { this.haClient.shutdown(); } this.executorService.shutdown(); } @Override public void removeConnection(HAConnection conn) { if (!defaultMessageStore.isShutdown()) { Long slave = ((AutoSwitchHAConnection) conn).getSlaveId(); this.writeLock.lock(); try { final Set newSyncStateSet = new HashSet<>(this.syncStateSet); if (newSyncStateSet.contains(slave)) { newSyncStateSet.remove(slave); markSynchronizingSyncStateSet(newSyncStateSet); notifySyncStateSetChanged(newSyncStateSet); this.syncStateSet.clear(); this.syncStateSet.addAll(newSyncStateSet); } } finally { this.writeLock.unlock(); } } super.removeConnection(conn); } @Override public boolean changeToMaster(int masterEpoch) throws RocksDBException { final int lastEpoch = this.epochCache.lastEpoch(); if (masterEpoch < lastEpoch) { LOGGER.warn("newMasterEpoch {} < lastEpoch {}, fail to change to master", masterEpoch, lastEpoch); return false; } destroyConnections(); // Stop ha client if needed if (this.haClient != null) { this.haClient.shutdown(); } // Truncate dirty file final long truncateOffset = truncateInvalidMsg(); this.defaultMessageStore.setConfirmOffset(computeConfirmOffset()); if (truncateOffset >= 0) { this.epochCache.truncateSuffixByOffset(truncateOffset); } // Append new epoch to epochFile final EpochEntry newEpochEntry = new EpochEntry(masterEpoch, this.defaultMessageStore.getMaxPhyOffset()); if (this.epochCache.lastEpoch() >= masterEpoch) { this.epochCache.truncateSuffixByEpoch(masterEpoch); } this.epochCache.appendEntry(newEpochEntry); // Waiting consume queue dispatch while (defaultMessageStore.dispatchBehindBytes() > 0) { try { Thread.sleep(100); } catch (Exception ignored) { } } if (defaultMessageStore.isTransientStorePoolEnable()) { waitingForAllCommit(); defaultMessageStore.getTransientStorePool().setRealCommit(true); } LOGGER.info("TruncateOffset is {}, confirmOffset is {}, maxPhyOffset is {}", truncateOffset, this.defaultMessageStore.getConfirmOffset(), this.defaultMessageStore.getMaxPhyOffset()); this.defaultMessageStore.recoverTopicQueueTable(); this.defaultMessageStore.setStateMachineVersion(masterEpoch); LOGGER.info("Change ha to master success, newMasterEpoch:{}, startOffset:{}", masterEpoch, newEpochEntry.getStartOffset()); return true; } @Override public boolean changeToSlave(String newMasterAddr, int newMasterEpoch, Long slaveId) { final int lastEpoch = this.epochCache.lastEpoch(); if (newMasterEpoch < lastEpoch) { LOGGER.warn("newMasterEpoch {} < lastEpoch {}, fail to change to slave", newMasterEpoch, lastEpoch); return false; } try { destroyConnections(); if (this.haClient == null) { this.haClient = new AutoSwitchHAClient(this, defaultMessageStore, this.epochCache, slaveId); } else { this.haClient.reOpen(); } this.haClient.updateMasterAddress(newMasterAddr); this.haClient.updateHaMasterAddress(null); this.haClient.start(); if (defaultMessageStore.isTransientStorePoolEnable()) { waitingForAllCommit(); defaultMessageStore.getTransientStorePool().setRealCommit(false); } this.defaultMessageStore.setStateMachineVersion(newMasterEpoch); LOGGER.info("Change ha to slave success, newMasterAddress:{}, newMasterEpoch:{}", newMasterAddr, newMasterEpoch); return true; } catch (final Exception e) { LOGGER.error("Error happen when change ha to slave", e); return false; } } @Override public boolean changeToMasterWhenLastRoleIsMaster(int masterEpoch) { final int lastEpoch = this.epochCache.lastEpoch(); if (masterEpoch < lastEpoch) { LOGGER.warn("newMasterEpoch {} < lastEpoch {}, fail to change to master", masterEpoch, lastEpoch); return false; } // Append new epoch to epochFile final EpochEntry newEpochEntry = new EpochEntry(masterEpoch, this.defaultMessageStore.getMaxPhyOffset()); if (this.epochCache.lastEpoch() >= masterEpoch) { this.epochCache.truncateSuffixByEpoch(masterEpoch); } this.epochCache.appendEntry(newEpochEntry); this.defaultMessageStore.setStateMachineVersion(masterEpoch); LOGGER.info("Change ha to master success, last role is master, newMasterEpoch:{}, startOffset:{}", masterEpoch, newEpochEntry.getStartOffset()); return true; } @Override public boolean changeToSlaveWhenMasterNotChange(String newMasterAddr, int newMasterEpoch) { final int lastEpoch = this.epochCache.lastEpoch(); if (newMasterEpoch < lastEpoch) { LOGGER.warn("newMasterEpoch {} < lastEpoch {}, fail to change to slave", newMasterEpoch, lastEpoch); return false; } this.defaultMessageStore.setStateMachineVersion(newMasterEpoch); LOGGER.info("Change ha to slave success, master doesn't change, newMasterAddress:{}, newMasterEpoch:{}", newMasterAddr, newMasterEpoch); return true; } public void waitingForAllCommit() { while (getDefaultMessageStore().remainHowManyDataToCommit() > 0) { getDefaultMessageStore().getCommitLog().getFlushManager().wakeUpCommit(); try { Thread.sleep(100); } catch (Exception e) { } } } @Override public HAClient getHAClient() { return this.haClient; } @Override public void updateHaMasterAddress(String newAddr) { if (this.haClient != null) { this.haClient.updateHaMasterAddress(newAddr); } } @Override public void updateMasterAddress(String newAddr) { } public void registerSyncStateSetChangedListener(final Consumer> listener) { this.syncStateSetChangedListeners.add(listener); } public void notifySyncStateSetChanged(final Set newSyncStateSet) { this.executorService.submit(() -> { syncStateSetChangedListeners.forEach(listener -> listener.accept(newSyncStateSet)); }); LOGGER.info("Notify the syncStateSet has been changed into {}.", newSyncStateSet); } /** * Check and maybe shrink the SyncStateSet. * A slave will be removed from SyncStateSet if (curTime - HaConnection.lastCaughtUpTime) > option(haMaxTimeSlaveNotCatchup) */ public Set maybeShrinkSyncStateSet() { final Set newSyncStateSet = getLocalSyncStateSet(); boolean isSyncStateSetChanged = false; final long haMaxTimeSlaveNotCatchup = this.defaultMessageStore.getMessageStoreConfig().getHaMaxTimeSlaveNotCatchup(); for (Map.Entry next : this.connectionCaughtUpTimeTable.entrySet()) { final Long slaveBrokerId = next.getKey(); if (newSyncStateSet.contains(slaveBrokerId)) { final Long lastCaughtUpTimeMs = next.getValue(); if ((System.currentTimeMillis() - lastCaughtUpTimeMs) > haMaxTimeSlaveNotCatchup) { newSyncStateSet.remove(slaveBrokerId); isSyncStateSetChanged = true; } } } // If the slaveBrokerId is in syncStateSet but not in connectionCaughtUpTimeTable, // it means that the broker has not connected. Iterator iterator = newSyncStateSet.iterator(); while (iterator.hasNext()) { Long slaveBrokerId = iterator.next(); if (!Objects.equals(slaveBrokerId, this.localBrokerId) && !this.connectionCaughtUpTimeTable.containsKey(slaveBrokerId)) { iterator.remove(); isSyncStateSetChanged = true; } } if (isSyncStateSetChanged) { markSynchronizingSyncStateSet(newSyncStateSet); } return newSyncStateSet; } /** * Check and maybe add the slave to SyncStateSet. A slave will be added to SyncStateSet if its slaveMaxOffset >= * current confirmOffset, and it is caught up to an offset within the current leader epoch. */ public void maybeExpandInSyncStateSet(final Long slaveBrokerId, final long slaveMaxOffset) { final Set currentSyncStateSet = getLocalSyncStateSet(); if (currentSyncStateSet.contains(slaveBrokerId)) { return; } final long confirmOffset = this.defaultMessageStore.getConfirmOffset(); if (slaveMaxOffset >= confirmOffset) { final EpochEntry currentLeaderEpoch = this.epochCache.lastEntry(); if (slaveMaxOffset >= currentLeaderEpoch.getStartOffset()) { LOGGER.info("The slave {} has caught up, slaveMaxOffset: {}, confirmOffset: {}, epoch: {}, leader epoch startOffset: {}.", slaveBrokerId, slaveMaxOffset, confirmOffset, currentLeaderEpoch.getEpoch(), currentLeaderEpoch.getStartOffset()); currentSyncStateSet.add(slaveBrokerId); markSynchronizingSyncStateSet(currentSyncStateSet); // Notify the upper layer that syncStateSet changed. notifySyncStateSetChanged(currentSyncStateSet); } } } private void markSynchronizingSyncStateSet(final Set newSyncStateSet) { this.writeLock.lock(); try { this.isSynchronizingSyncStateSet = true; this.remoteSyncStateSet.clear(); this.remoteSyncStateSet.addAll(newSyncStateSet); } finally { this.writeLock.unlock(); } } private void markSynchronizingSyncStateSetDone() { // No need to lock, because the upper-level calling method has already locked write lock this.isSynchronizingSyncStateSet = false; } public boolean isSynchronizingSyncStateSet() { return isSynchronizingSyncStateSet; } public void updateConnectionLastCaughtUpTime(final Long slaveBrokerId, final long lastCaughtUpTimeMs) { Long prevTime = ConcurrentHashMapUtils.computeIfAbsent(this.connectionCaughtUpTimeTable, slaveBrokerId, k -> 0L); this.connectionCaughtUpTimeTable.put(slaveBrokerId, Math.max(prevTime, lastCaughtUpTimeMs)); } public void updateConfirmOffsetWhenSlaveAck(final Long slaveBrokerId) { this.readLock.lock(); try { if (this.syncStateSet.contains(slaveBrokerId)) { this.defaultMessageStore.setConfirmOffset(computeConfirmOffset()); } } finally { this.readLock.unlock(); } } @Override public int inSyncReplicasNums(final long masterPutWhere) { this.readLock.lock(); try { if (this.isSynchronizingSyncStateSet) { return Math.max(this.syncStateSet.size(), this.remoteSyncStateSet.size()); } else { return this.syncStateSet.size(); } } finally { this.readLock.unlock(); } } @Override public HARuntimeInfo getRuntimeInfo(long masterPutWhere) { HARuntimeInfo info = new HARuntimeInfo(); if (BrokerRole.SLAVE.equals(this.getDefaultMessageStore().getMessageStoreConfig().getBrokerRole())) { info.setMaster(false); info.getHaClientRuntimeInfo().setMasterAddr(this.haClient.getHaMasterAddress()); info.getHaClientRuntimeInfo().setMaxOffset(this.getDefaultMessageStore().getMaxPhyOffset()); info.getHaClientRuntimeInfo().setLastReadTimestamp(this.haClient.getLastReadTimestamp()); info.getHaClientRuntimeInfo().setLastWriteTimestamp(this.haClient.getLastWriteTimestamp()); info.getHaClientRuntimeInfo().setTransferredByteInSecond(this.haClient.getTransferredByteInSecond()); info.getHaClientRuntimeInfo().setMasterFlushOffset(this.defaultMessageStore.getMasterFlushedOffset()); } else { info.setMaster(true); info.setMasterCommitLogMaxOffset(masterPutWhere); Set localSyncStateSet = getLocalSyncStateSet(); for (HAConnection conn : this.connectionList) { HARuntimeInfo.HAConnectionRuntimeInfo cInfo = new HARuntimeInfo.HAConnectionRuntimeInfo(); long slaveAckOffset = conn.getSlaveAckOffset(); cInfo.setSlaveAckOffset(slaveAckOffset); cInfo.setDiff(masterPutWhere - slaveAckOffset); cInfo.setAddr(conn.getClientAddress().substring(1)); cInfo.setTransferredByteInSecond(conn.getTransferredByteInSecond()); cInfo.setTransferFromWhere(conn.getTransferFromWhere()); cInfo.setInSync(localSyncStateSet.contains(((AutoSwitchHAConnection) conn).getSlaveId())); info.getHaConnectionInfo().add(cInfo); } info.setInSyncSlaveNums(localSyncStateSet.size() - 1); } return info; } public long computeConfirmOffset() { final Set currentSyncStateSet = getSyncStateSet(); long newConfirmOffset = this.defaultMessageStore.getMaxPhyOffset(); List idList = this.connectionList.stream().map(connection -> ((AutoSwitchHAConnection)connection).getSlaveId()).collect(Collectors.toList()); // To avoid the syncStateSet is not consistent with connectionList. // Fix issue: https://github.com/apache/rocketmq/issues/6662 for (Long syncId : currentSyncStateSet) { if (!idList.contains(syncId) && this.localBrokerId != null && !Objects.equals(syncId, this.localBrokerId)) { LOGGER.warn("Slave {} is still in syncStateSet, but has lost its connection. So new offset can't be compute.", syncId); // Without check and re-compute, return the confirmOffset's value directly. return this.defaultMessageStore.getConfirmOffsetDirectly(); } } for (HAConnection connection : this.connectionList) { final Long slaveId = ((AutoSwitchHAConnection) connection).getSlaveId(); if (currentSyncStateSet.contains(slaveId) && connection.getSlaveAckOffset() > 0) { newConfirmOffset = Math.min(newConfirmOffset, connection.getSlaveAckOffset()); } } return newConfirmOffset; } public void setSyncStateSet(final Set syncStateSet) { this.writeLock.lock(); try { markSynchronizingSyncStateSetDone(); this.syncStateSet.clear(); this.syncStateSet.addAll(syncStateSet); this.defaultMessageStore.setConfirmOffset(computeConfirmOffset()); } finally { this.writeLock.unlock(); } } /** * Return the union of the local and remote syncStateSets */ public Set getSyncStateSet() { this.readLock.lock(); try { if (this.isSynchronizingSyncStateSet) { Set unionSyncStateSet = new HashSet<>(this.syncStateSet.size() + this.remoteSyncStateSet.size()); unionSyncStateSet.addAll(this.syncStateSet); unionSyncStateSet.addAll(this.remoteSyncStateSet); return unionSyncStateSet; } else { HashSet syncStateSet = new HashSet<>(this.syncStateSet.size()); syncStateSet.addAll(this.syncStateSet); return syncStateSet; } } finally { this.readLock.unlock(); } } public Set getLocalSyncStateSet() { this.readLock.lock(); try { HashSet localSyncStateSet = new HashSet<>(this.syncStateSet.size()); localSyncStateSet.addAll(this.syncStateSet); return localSyncStateSet; } finally { this.readLock.unlock(); } } public void truncateEpochFilePrefix(final long offset) { this.epochCache.truncatePrefixByOffset(offset); } public void truncateEpochFileSuffix(final long offset) { this.epochCache.truncateSuffixByOffset(offset); } /** * Try to truncate incomplete msg transferred from master. */ public long truncateInvalidMsg() throws RocksDBException { long dispatchBehind = this.defaultMessageStore.dispatchBehindBytes(); if (dispatchBehind <= 0) { LOGGER.info("Dispatch complete, skip truncate"); return -1; } boolean doNext = true; // Here we could use reputFromOffset in DefaultMessageStore directly. long reputFromOffset = this.defaultMessageStore.getReputFromOffset(); do { SelectMappedBufferResult result = this.defaultMessageStore.getCommitLog().getData(reputFromOffset); if (result == null) { break; } try { reputFromOffset = result.getStartOffset(); int readSize = 0; while (readSize < result.getSize()) { DispatchRequest dispatchRequest = this.defaultMessageStore.getCommitLog().checkMessageAndReturnSize(result.getByteBuffer(), false, false); if (dispatchRequest.isSuccess()) { int size = dispatchRequest.getMsgSize(); if (size > 0) { reputFromOffset += size; readSize += size; } else { reputFromOffset = this.defaultMessageStore.getCommitLog().rollNextFile(reputFromOffset); break; } } else { doNext = false; break; } } } finally { result.release(); } } while (reputFromOffset < this.defaultMessageStore.getMaxPhyOffset() && doNext); LOGGER.info("Truncate commitLog to {}", reputFromOffset); this.defaultMessageStore.truncateDirtyFiles(reputFromOffset); return reputFromOffset; } public int getLastEpoch() { return this.epochCache.lastEpoch(); } public List getEpochEntries() { return this.epochCache.getAllEntries(); } public Long getLocalBrokerId() { return localBrokerId; } public void setLocalBrokerId(Long localBrokerId) { this.localBrokerId = localBrokerId; } class AutoSwitchAcceptSocketService extends AcceptSocketService { public AutoSwitchAcceptSocketService(final MessageStoreConfig messageStoreConfig) { super(messageStoreConfig); } @Override public String getServiceName() { if (defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { return defaultMessageStore.getBrokerConfig().getIdentifier() + AcceptSocketService.class.getSimpleName(); } return AutoSwitchAcceptSocketService.class.getSimpleName(); } @Override protected HAConnection createConnection(SocketChannel sc) throws IOException { return new AutoSwitchHAConnection(AutoSwitchHAService.this, sc, AutoSwitchHAService.this.epochCache); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/BrokerMetadata.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import org.apache.commons.lang3.StringUtils; import java.util.Objects; public class BrokerMetadata extends MetadataFile { protected String clusterName; protected String brokerName; protected Long brokerId; public BrokerMetadata(String filePath) { this.filePath = filePath; } public void updateAndPersist(String clusterName, String brokerName, Long brokerId) throws Exception { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerId = brokerId; writeToFile(); } @Override public String encodeToStr() { StringBuilder sb = new StringBuilder(); sb.append(clusterName).append("#"); sb.append(brokerName).append("#"); sb.append(brokerId); return sb.toString(); } @Override public void decodeFromStr(String dataStr) { if (dataStr == null) return; String[] dataArr = dataStr.split("#"); this.clusterName = dataArr[0]; this.brokerName = dataArr[1]; this.brokerId = Long.valueOf(dataArr[2]); } @Override public boolean isLoaded() { return StringUtils.isNotEmpty(this.clusterName) && StringUtils.isNotEmpty(this.brokerName) && brokerId != null; } @Override public void clearInMem() { this.clusterName = null; this.brokerName = null; this.brokerId = null; } public String getBrokerName() { return brokerName; } public Long getBrokerId() { return brokerId; } public String getClusterName() { return clusterName; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BrokerMetadata that = (BrokerMetadata) o; return Objects.equals(clusterName, that.clusterName) && Objects.equals(brokerName, that.brokerName) && Objects.equals(brokerId, that.brokerId); } @Override public int hashCode() { return Objects.hash(clusterName, brokerName, brokerId); } @Override public String toString() { return "BrokerMetadata{" + "clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + ", filePath='" + filePath + '\'' + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCache.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Predicate; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.CheckpointFile; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.EpochEntry; /** * Cache for epochFile. Mapping (Epoch -> StartOffset) */ public class EpochFileCache { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = this.readWriteLock.readLock(); private final Lock writeLock = this.readWriteLock.writeLock(); private final TreeMap epochMap; private CheckpointFile checkpoint; public EpochFileCache() { this.epochMap = new TreeMap<>(); } public EpochFileCache(final String path) { this.epochMap = new TreeMap<>(); this.checkpoint = new CheckpointFile<>(path, new EpochEntrySerializer()); } public boolean initCacheFromFile() { this.writeLock.lock(); try { final List entries = this.checkpoint.read(); initEntries(entries); return true; } catch (final IOException e) { log.error("Error happen when init epoch entries from epochFile", e); return false; } finally { this.writeLock.unlock(); } } public void initCacheFromEntries(final List entries) { this.writeLock.lock(); try { initEntries(entries); flush(); } finally { this.writeLock.unlock(); } } private void initEntries(final List entries) { this.epochMap.clear(); EpochEntry preEntry = null; for (final EpochEntry entry : entries) { this.epochMap.put(entry.getEpoch(), entry); if (preEntry != null) { preEntry.setEndOffset(entry.getStartOffset()); } preEntry = entry; } } public int getEntrySize() { this.readLock.lock(); try { return this.epochMap.size(); } finally { this.readLock.unlock(); } } public boolean appendEntry(final EpochEntry entry) { this.writeLock.lock(); try { if (!this.epochMap.isEmpty()) { final EpochEntry lastEntry = this.epochMap.lastEntry().getValue(); if (lastEntry.getEpoch() >= entry.getEpoch() || lastEntry.getStartOffset() >= entry.getStartOffset()) { log.error("The appending entry's lastEpoch or endOffset {} is not bigger than lastEntry {}, append failed", entry, lastEntry); return false; } lastEntry.setEndOffset(entry.getStartOffset()); } this.epochMap.put(entry.getEpoch(), new EpochEntry(entry)); flush(); return true; } finally { this.writeLock.unlock(); } } /** * Set endOffset for lastEpochEntry. */ public void setLastEpochEntryEndOffset(final long endOffset) { this.writeLock.lock(); try { if (!this.epochMap.isEmpty()) { final EpochEntry lastEntry = this.epochMap.lastEntry().getValue(); if (lastEntry.getStartOffset() <= endOffset) { lastEntry.setEndOffset(endOffset); } } } finally { this.writeLock.unlock(); } } public EpochEntry firstEntry() { this.readLock.lock(); try { if (this.epochMap.isEmpty()) { return null; } return new EpochEntry(this.epochMap.firstEntry().getValue()); } finally { this.readLock.unlock(); } } public EpochEntry lastEntry() { this.readLock.lock(); try { if (this.epochMap.isEmpty()) { return null; } return new EpochEntry(this.epochMap.lastEntry().getValue()); } finally { this.readLock.unlock(); } } public int lastEpoch() { final EpochEntry entry = lastEntry(); if (entry != null) { return entry.getEpoch(); } return -1; } public EpochEntry getEntry(final int epoch) { this.readLock.lock(); try { if (this.epochMap.containsKey(epoch)) { final EpochEntry entry = this.epochMap.get(epoch); return new EpochEntry(entry); } return null; } finally { this.readLock.unlock(); } } public EpochEntry findEpochEntryByOffset(final long offset) { this.readLock.lock(); try { if (!this.epochMap.isEmpty()) { for (Map.Entry entry : this.epochMap.entrySet()) { if (entry.getValue().getStartOffset() <= offset && entry.getValue().getEndOffset() > offset) { return new EpochEntry(entry.getValue()); } } } return null; } finally { this.readLock.unlock(); } } public EpochEntry nextEntry(final int epoch) { this.readLock.lock(); try { final Map.Entry entry = this.epochMap.ceilingEntry(epoch + 1); if (entry != null) { return new EpochEntry(entry.getValue()); } return null; } finally { this.readLock.unlock(); } } public List getAllEntries() { this.readLock.lock(); try { final ArrayList result = new ArrayList<>(this.epochMap.size()); this.epochMap.forEach((key, value) -> result.add(new EpochEntry(value))); return result; } finally { this.readLock.unlock(); } } /** * Find the consistentPoint between compareCache and local. * * @return the consistent offset */ public long findConsistentPoint(final EpochFileCache compareCache) { this.readLock.lock(); try { long consistentOffset = -1; final Map descendingMap = new TreeMap<>(this.epochMap).descendingMap(); final Iterator> iter = descendingMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry curLocalEntry = iter.next(); final EpochEntry compareEntry = compareCache.getEntry(curLocalEntry.getKey()); if (compareEntry != null && compareEntry.getStartOffset() == curLocalEntry.getValue().getStartOffset()) { consistentOffset = Math.min(curLocalEntry.getValue().getEndOffset(), compareEntry.getEndOffset()); break; } } return consistentOffset; } finally { this.readLock.unlock(); } } /** * Remove epochEntries with epoch >= truncateEpoch. */ public void truncateSuffixByEpoch(final int truncateEpoch) { Predicate predict = entry -> entry.getEpoch() >= truncateEpoch; doTruncateSuffix(predict); } /** * Remove epochEntries with startOffset >= truncateOffset. */ public void truncateSuffixByOffset(final long truncateOffset) { Predicate predict = entry -> entry.getStartOffset() >= truncateOffset; doTruncateSuffix(predict); } private void doTruncateSuffix(Predicate predict) { this.writeLock.lock(); try { this.epochMap.entrySet().removeIf(entry -> predict.test(entry.getValue())); final EpochEntry entry = lastEntry(); if (entry != null) { entry.setEndOffset(Long.MAX_VALUE); } flush(); } finally { this.writeLock.unlock(); } } /** * Remove epochEntries with endOffset <= truncateOffset. */ public void truncatePrefixByOffset(final long truncateOffset) { Predicate predict = entry -> entry.getEndOffset() <= truncateOffset; this.writeLock.lock(); try { this.epochMap.entrySet().removeIf(entry -> predict.test(entry.getValue())); flush(); } finally { this.writeLock.unlock(); } } private void flush() { this.writeLock.lock(); try { if (this.checkpoint != null) { final ArrayList entries = new ArrayList<>(this.epochMap.values()); this.checkpoint.write(entries); } } catch (final IOException e) { log.error("Error happen when flush epochEntries to epochCheckpointFile", e); } finally { this.writeLock.unlock(); } } static class EpochEntrySerializer implements CheckpointFile.CheckpointSerializer { @Override public String toLine(EpochEntry entry) { if (entry != null) { return String.format("%d-%d", entry.getEpoch(), entry.getStartOffset()); } else { return null; } } @Override public EpochEntry fromLine(String line) { final String[] arr = line.split("-"); if (arr.length == 2) { final int epoch = Integer.parseInt(arr[0]); final long startOffset = Long.parseLong(arr[1]); return new EpochEntry(epoch, startOffset); } return null; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/MetadataFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import java.io.File; public abstract class MetadataFile { protected String filePath; public abstract String encodeToStr(); public abstract void decodeFromStr(String dataStr); public abstract boolean isLoaded(); public abstract void clearInMem(); public void writeToFile() throws Exception { UtilAll.deleteFile(new File(filePath)); MixAll.string2File(encodeToStr(), this.filePath); } public void readFromFile() throws Exception { String dataStr = MixAll.file2String(filePath); decodeFromStr(dataStr); } public boolean fileExists() { File file = new File(filePath); return file.exists(); } public void clear() { clearInMem(); UtilAll.deleteFile(new File(filePath)); } public String getFilePath() { return filePath; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/TempBrokerMetadata.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import org.apache.commons.lang3.StringUtils; public class TempBrokerMetadata extends BrokerMetadata { private String registerCheckCode; public TempBrokerMetadata(String filePath) { this(filePath, null, null, null, null); } public TempBrokerMetadata(String filePath, String clusterName, String brokerName, Long brokerId, String registerCheckCode) { super(filePath); super.clusterName = clusterName; super.brokerId = brokerId; super.brokerName = brokerName; this.registerCheckCode = registerCheckCode; } public void updateAndPersist(String clusterName, String brokerName, Long brokerId, String registerCheckCode) throws Exception { super.clusterName = clusterName; super.brokerName = brokerName; super.brokerId = brokerId; this.registerCheckCode = registerCheckCode; writeToFile(); } @Override public String encodeToStr() { StringBuilder sb = new StringBuilder(); sb.append(clusterName).append("#"); sb.append(brokerName).append("#"); sb.append(brokerId).append("#"); sb.append(registerCheckCode); return sb.toString(); } @Override public void decodeFromStr(String dataStr) { if (dataStr == null) return; String[] dataArr = dataStr.split("#"); this.clusterName = dataArr[0]; this.brokerName = dataArr[1]; this.brokerId = Long.valueOf(dataArr[2]); this.registerCheckCode = dataArr[3]; } @Override public boolean isLoaded() { return super.isLoaded() && StringUtils.isNotEmpty(this.registerCheckCode); } @Override public void clearInMem() { super.clearInMem(); this.registerCheckCode = null; } public Long getBrokerId() { return brokerId; } public String getRegisterCheckCode() { return registerCheckCode; } @Override public String toString() { return "TempBrokerMetadata{" + "registerCheckCode='" + registerCheckCode + '\'' + ", clusterName='" + clusterName + '\'' + ", brokerName='" + brokerName + '\'' + ", brokerId=" + brokerId + ", filePath='" + filePath + '\'' + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/io/AbstractHAReader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.io; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class AbstractHAReader { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected final List readHookList = new ArrayList<>(); public boolean read(SocketChannel socketChannel, ByteBuffer byteBufferRead) { int readSizeZeroTimes = 0; while (byteBufferRead.hasRemaining()) { try { int readSize = socketChannel.read(byteBufferRead); for (HAReadHook readHook : readHookList) { readHook.afterRead(readSize); } if (readSize > 0) { readSizeZeroTimes = 0; boolean result = processReadResult(byteBufferRead); if (!result) { LOGGER.error("Process read result failed"); return false; } } else if (readSize == 0) { if (++readSizeZeroTimes >= 3) { break; } } else { LOGGER.info("Read socket < 0"); return false; } } catch (IOException e) { LOGGER.info("Read socket exception", e); return false; } } return true; } public void registerHook(HAReadHook readHook) { readHookList.add(readHook); } public void clearHook() { readHookList.clear(); } /** * Process read result. * * @param byteBufferRead read result * @return true if process succeed, false otherwise */ protected abstract boolean processReadResult(ByteBuffer byteBufferRead); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/io/HAReadHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.io; public interface HAReadHook { void afterRead(int readSize); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriteHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.io; public interface HAWriteHook { void afterWrite(int writeSize); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.io; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class HAWriter { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected final List writeHookList = new ArrayList<>(); public boolean write(SocketChannel socketChannel, ByteBuffer byteBufferWrite) throws IOException { int writeSizeZeroTimes = 0; while (byteBufferWrite.hasRemaining()) { int writeSize = socketChannel.write(byteBufferWrite); for (HAWriteHook writeHook : writeHookList) { writeHook.afterWrite(writeSize); } if (writeSize > 0) { writeSizeZeroTimes = 0; } else if (writeSize == 0) { if (++writeSizeZeroTimes >= 3) { break; } } else { LOGGER.info("Write socket < 0"); } } return !byteBufferWrite.hasRemaining(); } public void registerHook(HAWriteHook writeHook) { writeHookList.add(writeHook); } public void clearHook() { writeHookList.clear(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/hook/PutMessageHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.hook; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.store.PutMessageResult; public interface PutMessageHook { /** * Name of the hook. * * @return name of the hook */ String hookName(); /** * Execute before put message. For example, Message verification or special message transform * @param msg * @return */ PutMessageResult executeBeforePutMessage(MessageExt msg); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/hook/SendMessageBackHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.hook; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; public interface SendMessageBackHook { /** * Slave send message back to master at certain offset when HA handshake * * @param msgList * @param brokerName * @param brokerAddr * @return */ boolean executeSendMessageBack(List msgList, String brokerName, String brokerAddr); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.util.List; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; public class IndexFile { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static int hashSlotSize = 4; /** * Each index's store unit. Format: *
         * ┌───────────────┬───────────────────────────────┬───────────────┬───────────────┐
         * │ Key HashCode  │        Physical Offset        │   Time Diff   │ Next Index Pos│
         * │   (4 Bytes)   │          (8 Bytes)            │   (4 Bytes)   │   (4 Bytes)   │
         * ├───────────────┴───────────────────────────────┴───────────────┴───────────────┤
         * │                                 Index Store Unit                              │
         * │                                                                               │
         * 
    * Each index's store unit. Size: * Key HashCode(4) + Physical Offset(8) + Time Diff(4) + Next Index Pos(4) = 20 Bytes */ private static int indexSize = 20; private static int invalidIndex = 0; private final int hashSlotNum; private final int indexNum; private final int fileTotalSize; private final MappedFile mappedFile; private final MappedByteBuffer mappedByteBuffer; private final IndexHeader indexHeader; public IndexFile(final String fileName, final int hashSlotNum, final int indexNum, final long endPhyOffset, final long endTimestamp) throws IOException { this.fileTotalSize = IndexHeader.INDEX_HEADER_SIZE + (hashSlotNum * hashSlotSize) + (indexNum * indexSize); this.mappedFile = new DefaultMappedFile(fileName, fileTotalSize); this.mappedByteBuffer = this.mappedFile.getMappedByteBuffer(); this.hashSlotNum = hashSlotNum; this.indexNum = indexNum; ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); this.indexHeader = new IndexHeader(byteBuffer); if (endPhyOffset > 0) { this.indexHeader.setBeginPhyOffset(endPhyOffset); this.indexHeader.setEndPhyOffset(endPhyOffset); } if (endTimestamp > 0) { this.indexHeader.setBeginTimestamp(endTimestamp); this.indexHeader.setEndTimestamp(endTimestamp); } } public String getFileName() { return this.mappedFile.getFileName(); } public int getFileSize() { return this.fileTotalSize; } public void load() { this.indexHeader.load(); } public void shutdown() { try { this.flush(); } catch (Throwable e) { log.error("flush error when shutdown", e); } mappedFile.cleanResources(); } public void flush() { long beginTime = System.currentTimeMillis(); if (this.mappedFile.hold()) { this.indexHeader.updateByteBuffer(); this.mappedByteBuffer.force(); this.mappedFile.release(); log.info("flush index file elapsed time(ms) " + (System.currentTimeMillis() - beginTime)); } } public boolean isWriteFull() { return this.indexHeader.getIndexCount() >= this.indexNum; } public boolean destroy(final long intervalForcibly) { return this.mappedFile.destroy(intervalForcibly); } public boolean putKey(final String key, final long phyOffset, final long storeTimestamp) { if (this.indexHeader.getIndexCount() < this.indexNum) { int keyHash = indexKeyHashMethod(key); int slotPos = keyHash % this.hashSlotNum; int absSlotPos = IndexHeader.INDEX_HEADER_SIZE + slotPos * hashSlotSize; try { int slotValue = this.mappedByteBuffer.getInt(absSlotPos); if (slotValue <= invalidIndex || slotValue > this.indexHeader.getIndexCount()) { slotValue = invalidIndex; } long timeDiff = storeTimestamp - this.indexHeader.getBeginTimestamp(); timeDiff = timeDiff / 1000; if (this.indexHeader.getBeginTimestamp() <= 0) { timeDiff = 0; } else if (timeDiff > Integer.MAX_VALUE) { timeDiff = Integer.MAX_VALUE; } else if (timeDiff < 0) { timeDiff = 0; } int absIndexPos = IndexHeader.INDEX_HEADER_SIZE + this.hashSlotNum * hashSlotSize + this.indexHeader.getIndexCount() * indexSize; this.mappedByteBuffer.putInt(absIndexPos, keyHash); this.mappedByteBuffer.putLong(absIndexPos + 4, phyOffset); this.mappedByteBuffer.putInt(absIndexPos + 4 + 8, (int) timeDiff); this.mappedByteBuffer.putInt(absIndexPos + 4 + 8 + 4, slotValue); this.mappedByteBuffer.putInt(absSlotPos, this.indexHeader.getIndexCount()); if (this.indexHeader.getIndexCount() <= 1) { this.indexHeader.setBeginPhyOffset(phyOffset); this.indexHeader.setBeginTimestamp(storeTimestamp); } if (invalidIndex == slotValue) { this.indexHeader.incHashSlotCount(); } this.indexHeader.incIndexCount(); this.indexHeader.setEndPhyOffset(phyOffset); this.indexHeader.setEndTimestamp(storeTimestamp); return true; } catch (Exception e) { log.error("putKey exception, Key: " + key + " KeyHashCode: " + key.hashCode(), e); } } else { log.warn("Over index file capacity: index count = " + this.indexHeader.getIndexCount() + "; index max num = " + this.indexNum); } return false; } public int indexKeyHashMethod(final String key) { int keyHash = key.hashCode(); int keyHashPositive = Math.abs(keyHash); if (keyHashPositive < 0) { keyHashPositive = 0; } return keyHashPositive; } public long getBeginTimestamp() { return this.indexHeader.getBeginTimestamp(); } public long getEndTimestamp() { return this.indexHeader.getEndTimestamp(); } public long getEndPhyOffset() { return this.indexHeader.getEndPhyOffset(); } public boolean isTimeMatched(final long begin, final long end) { boolean result = begin < this.indexHeader.getBeginTimestamp() && end > this.indexHeader.getEndTimestamp(); result = result || begin >= this.indexHeader.getBeginTimestamp() && begin <= this.indexHeader.getEndTimestamp(); result = result || end >= this.indexHeader.getBeginTimestamp() && end <= this.indexHeader.getEndTimestamp(); return result; } public void selectPhyOffset(final List phyOffsets, final String key, final int maxNum, final long begin, final long end) { if (this.mappedFile.hold()) { int keyHash = indexKeyHashMethod(key); int slotPos = keyHash % this.hashSlotNum; int absSlotPos = IndexHeader.INDEX_HEADER_SIZE + slotPos * hashSlotSize; try { int slotValue = this.mappedByteBuffer.getInt(absSlotPos); if (slotValue <= invalidIndex || slotValue > this.indexHeader.getIndexCount() || this.indexHeader.getIndexCount() <= 1) { } else { for (int nextIndexToRead = slotValue; ; ) { if (phyOffsets.size() >= maxNum) { break; } int absIndexPos = IndexHeader.INDEX_HEADER_SIZE + this.hashSlotNum * hashSlotSize + nextIndexToRead * indexSize; int keyHashRead = this.mappedByteBuffer.getInt(absIndexPos); long phyOffsetRead = this.mappedByteBuffer.getLong(absIndexPos + 4); long timeDiff = this.mappedByteBuffer.getInt(absIndexPos + 4 + 8); int prevIndexRead = this.mappedByteBuffer.getInt(absIndexPos + 4 + 8 + 4); if (timeDiff < 0) { break; } timeDiff *= 1000L; long timeRead = this.indexHeader.getBeginTimestamp() + timeDiff; boolean timeMatched = timeRead >= begin && timeRead <= end; if (keyHash == keyHashRead && timeMatched) { phyOffsets.add(phyOffsetRead); } if (prevIndexRead <= invalidIndex || prevIndexRead > this.indexHeader.getIndexCount() || prevIndexRead == nextIndexToRead || timeRead < begin) { break; } nextIndexToRead = prevIndexRead; } } } catch (Exception e) { log.error("selectPhyOffset exception ", e); } finally { this.mappedFile.release(); } } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/index/IndexHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * Index File Header. Format: *
     * ┌───────────────────────────────┬───────────────────────────────┬───────────────────────────────┬───────────────────────────────┬───────────────────┬───────────────────┐
     * │        Begin Timestamp        │          End Timestamp        │     Begin Physical Offset     │       End Physical Offset     │  Hash Slot Count  │    Index Count    │
     * │           (8 Bytes)           │            (8 Bytes)          │           (8 Bytes)           │           (8 Bytes)           │      (4 Bytes)    │      (4 Bytes)    │
     * ├───────────────────────────────┴───────────────────────────────┴───────────────────────────────┴───────────────────────────────┴───────────────────┴───────────────────┤
     * │                                                                      Index File Header                                                                                │
     * │
     * 
    * Index File Header. Size: * Begin Timestamp(8) + End Timestamp(8) + Begin Physical Offset(8) + End Physical Offset(8) + Hash Slot Count(4) + Index Count(4) = 40 Bytes */ public class IndexHeader { public static final int INDEX_HEADER_SIZE = 40; private static int beginTimestampIndex = 0; private static int endTimestampIndex = 8; private static int beginPhyoffsetIndex = 16; private static int endPhyoffsetIndex = 24; private static int hashSlotcountIndex = 32; private static int indexCountIndex = 36; private final ByteBuffer byteBuffer; private final AtomicLong beginTimestamp = new AtomicLong(0); private final AtomicLong endTimestamp = new AtomicLong(0); private final AtomicLong beginPhyOffset = new AtomicLong(0); private final AtomicLong endPhyOffset = new AtomicLong(0); private final AtomicInteger hashSlotCount = new AtomicInteger(0); private final AtomicInteger indexCount = new AtomicInteger(1); public IndexHeader(final ByteBuffer byteBuffer) { this.byteBuffer = byteBuffer; } public void load() { this.beginTimestamp.set(byteBuffer.getLong(beginTimestampIndex)); this.endTimestamp.set(byteBuffer.getLong(endTimestampIndex)); this.beginPhyOffset.set(byteBuffer.getLong(beginPhyoffsetIndex)); this.endPhyOffset.set(byteBuffer.getLong(endPhyoffsetIndex)); this.hashSlotCount.set(byteBuffer.getInt(hashSlotcountIndex)); this.indexCount.set(byteBuffer.getInt(indexCountIndex)); if (this.indexCount.get() <= 0) { this.indexCount.set(1); } } public void updateByteBuffer() { this.byteBuffer.putLong(beginTimestampIndex, this.beginTimestamp.get()); this.byteBuffer.putLong(endTimestampIndex, this.endTimestamp.get()); this.byteBuffer.putLong(beginPhyoffsetIndex, this.beginPhyOffset.get()); this.byteBuffer.putLong(endPhyoffsetIndex, this.endPhyOffset.get()); this.byteBuffer.putInt(hashSlotcountIndex, this.hashSlotCount.get()); this.byteBuffer.putInt(indexCountIndex, this.indexCount.get()); } public long getBeginTimestamp() { return beginTimestamp.get(); } public void setBeginTimestamp(long beginTimestamp) { this.beginTimestamp.set(beginTimestamp); this.byteBuffer.putLong(beginTimestampIndex, beginTimestamp); } public long getEndTimestamp() { return endTimestamp.get(); } public void setEndTimestamp(long endTimestamp) { this.endTimestamp.set(endTimestamp); this.byteBuffer.putLong(endTimestampIndex, endTimestamp); } public long getBeginPhyOffset() { return beginPhyOffset.get(); } public void setBeginPhyOffset(long beginPhyOffset) { this.beginPhyOffset.set(beginPhyOffset); this.byteBuffer.putLong(beginPhyoffsetIndex, beginPhyOffset); } public long getEndPhyOffset() { return endPhyOffset.get(); } public void setEndPhyOffset(long endPhyOffset) { this.endPhyOffset.set(endPhyOffset); this.byteBuffer.putLong(endPhyoffsetIndex, endPhyOffset); } public AtomicInteger getHashSlotCount() { return hashSlotCount; } public void incHashSlotCount() { int value = this.hashSlotCount.incrementAndGet(); this.byteBuffer.putInt(hashSlotcountIndex, value); } public int getIndexCount() { return indexCount.get(); } public void incIndexCount() { int value = this.indexCount.incrementAndGet(); this.byteBuffer.putInt(indexCountIndex, value); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/index/IndexService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.store.CommitLogDispatchStore; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.rocksdb.RocksDBException; public class IndexService implements CommitLogDispatchStore { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); /** * Maximum times to attempt index file creation. */ private static final int MAX_TRY_IDX_CREATE = 3; private final DefaultMessageStore defaultMessageStore; private final int hashSlotNum; private final int indexNum; private final String storePath; private final ArrayList indexFileList = new ArrayList<>(); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public IndexService(final DefaultMessageStore store) { this.defaultMessageStore = store; this.hashSlotNum = store.getMessageStoreConfig().getMaxHashSlotNum(); this.indexNum = store.getMessageStoreConfig().getMaxIndexNum(); this.storePath = StorePathConfigHelper.getStorePathIndex(defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); } public boolean load(final boolean lastExitOK) { File dir = new File(this.storePath); File[] files = dir.listFiles(); if (files != null) { // ascending order Arrays.sort(files); for (File file : files) { try { IndexFile f = new IndexFile(file.getPath(), this.hashSlotNum, this.indexNum, 0, 0); f.load(); if (!lastExitOK) { if (f.getEndTimestamp() > this.defaultMessageStore.getStoreCheckpoint() .getIndexMsgTimestamp()) { f.destroy(0); continue; } } LOGGER.info("load index file OK, " + f.getFileName()); this.indexFileList.add(f); } catch (IOException e) { LOGGER.error("load file {} error", file, e); return false; } catch (NumberFormatException e) { LOGGER.error("load file {} error", file, e); } } } return true; } public long getTotalSize() { if (indexFileList.isEmpty()) { return 0; } return (long) indexFileList.get(0).getFileSize() * indexFileList.size(); } public void deleteExpiredFile(long offset) { Object[] files = null; try { this.readWriteLock.readLock().lock(); if (this.indexFileList.isEmpty()) { return; } long endPhyOffset = this.indexFileList.get(0).getEndPhyOffset(); if (endPhyOffset < offset) { files = this.indexFileList.toArray(); } } catch (Exception e) { LOGGER.error("destroy exception", e); } finally { this.readWriteLock.readLock().unlock(); } if (files != null) { List fileList = new ArrayList<>(); for (int i = 0; i < (files.length - 1); i++) { IndexFile f = (IndexFile) files[i]; if (f.getEndPhyOffset() < offset) { fileList.add(f); } else { break; } } this.deleteExpiredFile(fileList); } } private void deleteExpiredFile(List files) { if (!files.isEmpty()) { try { this.readWriteLock.writeLock().lock(); for (IndexFile file : files) { boolean destroyed = file.destroy(3000); destroyed = destroyed && this.indexFileList.remove(file); if (!destroyed) { LOGGER.error("deleteExpiredFile remove failed."); break; } } } catch (Exception e) { LOGGER.error("deleteExpiredFile has exception.", e); } finally { this.readWriteLock.writeLock().unlock(); } } } public void destroy() { try { this.readWriteLock.writeLock().lock(); for (IndexFile f : this.indexFileList) { f.destroy(1000 * 3); } this.indexFileList.clear(); } catch (Exception e) { LOGGER.error("destroy exception", e); } finally { this.readWriteLock.writeLock().unlock(); } } public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long begin, long end) { long indexLastUpdateTimestamp = 0; long indexLastUpdatePhyoffset = 0; maxNum = Math.min(maxNum, this.defaultMessageStore.getMessageStoreConfig().getMaxMsgsNumBatch()); List phyOffsets = new ArrayList<>(maxNum); try { this.readWriteLock.readLock().lock(); if (!this.indexFileList.isEmpty()) { for (int i = this.indexFileList.size(); i > 0; i--) { IndexFile f = this.indexFileList.get(i - 1); boolean lastFile = i == this.indexFileList.size(); if (lastFile) { indexLastUpdateTimestamp = f.getEndTimestamp(); indexLastUpdatePhyoffset = f.getEndPhyOffset(); } if (f.isTimeMatched(begin, end)) { f.selectPhyOffset(phyOffsets, buildKey(topic, key), maxNum, begin, end); } if (f.getBeginTimestamp() < begin) { break; } if (phyOffsets.size() >= maxNum) { break; } } } } catch (Exception e) { LOGGER.error("queryMsg exception", e); } finally { this.readWriteLock.readLock().unlock(); } return new QueryOffsetResult(phyOffsets, indexLastUpdateTimestamp, indexLastUpdatePhyoffset); } public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long begin, long end, String indexType) { List phyOffsets = new ArrayList<>(maxNum); long indexLastUpdateTimestamp = 0; long indexLastUpdatePhyoffset = 0; maxNum = Math.min(maxNum, this.defaultMessageStore.getMessageStoreConfig().getMaxMsgsNumBatch()); try { this.readWriteLock.readLock().lock(); if (!this.indexFileList.isEmpty()) { for (int i = this.indexFileList.size(); i > 0; i--) { IndexFile f = this.indexFileList.get(i - 1); boolean lastFile = i == this.indexFileList.size(); if (lastFile) { indexLastUpdateTimestamp = f.getEndTimestamp(); indexLastUpdatePhyoffset = f.getEndPhyOffset(); } if (f.isTimeMatched(begin, end)) { String queryKey; if (!StringUtils.isEmpty(indexType) && MessageConst.INDEX_TAG_TYPE.equals(indexType)) { queryKey = buildKey(topic, key, MessageConst.INDEX_TAG_TYPE); } else { queryKey = buildKey(topic, key); } f.selectPhyOffset(phyOffsets, queryKey, maxNum, begin, end); } if (f.getBeginTimestamp() < begin) { break; } if (phyOffsets.size() >= maxNum) { break; } } } } catch (Exception e) { LOGGER.error("queryMsg queryOffset exception", e); } finally { this.readWriteLock.readLock().unlock(); } return new QueryOffsetResult(phyOffsets, indexLastUpdateTimestamp, indexLastUpdatePhyoffset); } private String buildKey(final String topic, final String key) { return topic + "#" + key; } private String buildKey(final String topic, final String key, final String indexType) { return topic + "#" + indexType + "#" + key; } public void buildIndex(DispatchRequest req) { IndexFile indexFile = retryGetAndCreateIndexFile(); if (indexFile != null) { long endPhyOffset = indexFile.getEndPhyOffset(); DispatchRequest msg = req; String topic = msg.getTopic(); String keys = msg.getKeys(); if (msg.getCommitLogOffset() < endPhyOffset) { return; } final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); switch (tranType) { case MessageSysFlag.TRANSACTION_NOT_TYPE: case MessageSysFlag.TRANSACTION_PREPARED_TYPE: case MessageSysFlag.TRANSACTION_COMMIT_TYPE: break; case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: return; } if (req.getUniqKey() != null) { indexFile = putKey(indexFile, msg, buildKey(topic, req.getUniqKey())); if (indexFile == null) { LOGGER.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); return; } } if (keys != null && keys.length() > 0) { String[] keyset = keys.split(MessageConst.KEY_SEPARATOR); for (int i = 0; i < keyset.length; i++) { String key = keyset[i]; if (key.length() > 0) { indexFile = putKey(indexFile, msg, buildKey(topic, key)); if (indexFile == null) { LOGGER.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); return; } } } } Map propertiesMap = req.getPropertiesMap(); if (null != propertiesMap && propertiesMap.containsKey(MessageConst.PROPERTY_TAGS)) { String tags = req.getPropertiesMap().get(MessageConst.PROPERTY_TAGS); if (!StringUtils.isEmpty(tags)) { indexFile = putKey(indexFile, msg, buildKey(topic, tags, MessageConst.INDEX_TAG_TYPE)); if (indexFile == null) { LOGGER.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); return; } } } } else { LOGGER.error("build index error, stop building index"); } } private IndexFile putKey(IndexFile indexFile, DispatchRequest msg, String idxKey) { for (boolean ok = indexFile.putKey(idxKey, msg.getCommitLogOffset(), msg.getStoreTimestamp()); !ok; ) { LOGGER.warn("Index file [" + indexFile.getFileName() + "] is full, trying to create another one"); indexFile = retryGetAndCreateIndexFile(); if (null == indexFile) { return null; } ok = indexFile.putKey(idxKey, msg.getCommitLogOffset(), msg.getStoreTimestamp()); } return indexFile; } /** * Retries to get or create index file. * * @return {@link IndexFile} or null on failure. */ public IndexFile retryGetAndCreateIndexFile() { IndexFile indexFile = null; for (int times = 0; null == indexFile && times < MAX_TRY_IDX_CREATE; times++) { indexFile = this.getAndCreateLastIndexFile(); if (null != indexFile) { break; } try { LOGGER.info("Tried to create index file " + times + " times"); Thread.sleep(1000); } catch (InterruptedException e) { LOGGER.error("Interrupted", e); } } if (null == indexFile) { this.defaultMessageStore.getRunningFlags().makeIndexFileError(); LOGGER.error("Mark index file cannot build flag"); } return indexFile; } public IndexFile getAndCreateLastIndexFile() { IndexFile indexFile = null; IndexFile prevIndexFile = null; long lastUpdateEndPhyOffset = 0; long lastUpdateIndexTimestamp = 0; { this.readWriteLock.readLock().lock(); if (!this.indexFileList.isEmpty()) { IndexFile tmp = this.indexFileList.get(this.indexFileList.size() - 1); if (!tmp.isWriteFull()) { indexFile = tmp; } else { lastUpdateEndPhyOffset = tmp.getEndPhyOffset(); lastUpdateIndexTimestamp = tmp.getEndTimestamp(); prevIndexFile = tmp; } } this.readWriteLock.readLock().unlock(); } if (indexFile == null) { try { String fileName = this.storePath + File.separator + UtilAll.timeMillisToHumanString(System.currentTimeMillis()); indexFile = new IndexFile(fileName, this.hashSlotNum, this.indexNum, lastUpdateEndPhyOffset, lastUpdateIndexTimestamp); this.readWriteLock.writeLock().lock(); this.indexFileList.add(indexFile); } catch (Exception e) { LOGGER.error("getLastIndexFile exception ", e); } finally { this.readWriteLock.writeLock().unlock(); } if (indexFile != null) { final IndexFile flushThisFile = prevIndexFile; Thread flushThread = new Thread(new Runnable() { @Override public void run() { IndexService.this.flush(flushThisFile); } }, "FlushIndexFileThread"); flushThread.setDaemon(true); flushThread.start(); } } return indexFile; } public void flush(final IndexFile f) { if (null == f) { return; } long indexMsgTimestamp = 0; if (f.isWriteFull()) { indexMsgTimestamp = f.getEndTimestamp(); } f.flush(); if (indexMsgTimestamp > 0) { this.defaultMessageStore.getStoreCheckpoint().setIndexMsgTimestamp(indexMsgTimestamp); this.defaultMessageStore.getStoreCheckpoint().flush(); } } public void start() { } public void shutdown() { try { this.readWriteLock.writeLock().lock(); for (IndexFile f : this.indexFileList) { try { f.shutdown(); } catch (Exception e) { LOGGER.error("shutdown " + f.getFileName() + " exception", e); } } this.indexFileList.clear(); } catch (Exception e) { LOGGER.error("shutdown exception", e); } finally { this.readWriteLock.writeLock().unlock(); } } @Override public Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException { return -1L; } @Override public boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException { if (this.defaultMessageStore.getMessageStoreConfig().isMessageIndexEnable() && this.defaultMessageStore.getMessageStoreConfig().isMessageIndexSafe()) { if (storeTimestamp > this.defaultMessageStore.getStoreCheckpoint().getIndexMsgTimestamp()) { return false; } LOGGER.info("CommitLog isMmapFileMatchedRecover find satisfied MmapFile for index, " + "MmapFile storeTimestamp={}, MmapFile phyOffset={}, indexMsgTimestamp={}, recoverNormally={}", storeTimestamp, phyOffset, this.defaultMessageStore.getStoreCheckpoint().getIndexMsgTimestamp(), recoverNormally); } return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/index/QueryOffsetResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index; import java.util.List; public class QueryOffsetResult { private final List phyOffsets; private final long indexLastUpdateTimestamp; private final long indexLastUpdatePhyoffset; public QueryOffsetResult(List phyOffsets, long indexLastUpdateTimestamp, long indexLastUpdatePhyoffset) { this.phyOffsets = phyOffsets; this.indexLastUpdateTimestamp = indexLastUpdateTimestamp; this.indexLastUpdatePhyoffset = indexLastUpdatePhyoffset; } public List getPhyOffsets() { return phyOffsets; } public long getIndexLastUpdateTimestamp() { return indexLastUpdateTimestamp; } public long getIndexLastUpdatePhyoffset() { return indexLastUpdatePhyoffset; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/index/rocksdb/IndexRocksDBRecord.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index.rocksdb; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; public class IndexRocksDBRecord { public static final String KEY_SPLIT = "@"; public static final byte[] KEY_SPLIT_BYTES = KEY_SPLIT.getBytes(StandardCharsets.UTF_8); private static final int VALUE_LENGTH = Long.BYTES; private long storeTime; private String topic; private String key; private String tag; private String uniqKey; private long offsetPy; public IndexRocksDBRecord(String topic, String key, String tag, long storeTime, String uniqKey, long offsetPy) { this.topic = topic; this.key = key; this.tag = tag; this.storeTime = storeTime; this.uniqKey = uniqKey; this.offsetPy = offsetPy; } public byte[] getKeyBytes() { if (StringUtils.isEmpty(topic) || StringUtils.isEmpty(uniqKey) || offsetPy < 0L || storeTime <= 0L) { return null; } long storeTimeHour = MixAll.dealTimeToHourStamps(storeTime); if (storeTimeHour <= 0L) { return null; } String keyMiddleStr; if (!StringUtils.isEmpty(key)) { keyMiddleStr = KEY_SPLIT + topic + KEY_SPLIT + MessageConst.INDEX_KEY_TYPE + KEY_SPLIT + key + KEY_SPLIT + uniqKey + KEY_SPLIT; } else if (!StringUtils.isEmpty(tag)) { keyMiddleStr = KEY_SPLIT + topic + KEY_SPLIT + MessageConst.INDEX_TAG_TYPE + KEY_SPLIT + tag + KEY_SPLIT + uniqKey + KEY_SPLIT; } else { keyMiddleStr = KEY_SPLIT + topic + KEY_SPLIT + MessageConst.INDEX_UNIQUE_TYPE + KEY_SPLIT + uniqKey + KEY_SPLIT; } if (StringUtils.isEmpty(keyMiddleStr)) { return null; } byte[] keyMiddleBytes = keyMiddleStr.getBytes(StandardCharsets.UTF_8); int keyLength = Long.BYTES + keyMiddleBytes.length + Long.BYTES; return ByteBuffer.allocate(keyLength).putLong(storeTimeHour).put(keyMiddleBytes).putLong(offsetPy).array(); } public byte[] getValueBytes() { if (storeTime <= 0L) { return null; } return ByteBuffer.allocate(VALUE_LENGTH).putLong(storeTime).array(); } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public long getStoreTime() { return storeTime; } public void setStoreTime(long storeTime) { this.storeTime = storeTime; } public String getUniqKey() { return uniqKey; } public void setUniqKey(String uniqKey) { this.uniqKey = uniqKey; } public long getOffsetPy() { return offsetPy; } public void setOffsetPy(long offsetPy) { this.offsetPy = offsetPy; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/index/rocksdb/IndexRocksDBStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index.rocksdb; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.CommitLogDispatchStore; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.index.QueryOffsetResult; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import static org.apache.rocketmq.common.MixAll.dealTimeToHourStamps; public class IndexRocksDBStore implements CommitLogDispatchStore { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final int DEFAULT_CAPACITY = 100000; private static final int BATCH_SIZE = 1000; private static final Set INDEX_TYPE_SET = new HashSet<>(); static { INDEX_TYPE_SET.add(MessageConst.INDEX_KEY_TYPE); INDEX_TYPE_SET.add(MessageConst.INDEX_TAG_TYPE); INDEX_TYPE_SET.add(MessageConst.INDEX_UNIQUE_TYPE); } private static final int INITIAL = 0, RUNNING = 1, SHUTDOWN = 2; private volatile int state = INITIAL; private final MessageStore messageStore; private final MessageStoreConfig storeConfig; private final MessageRocksDBStorage messageRocksDBStorage; private volatile long lastDeleteIndexTime = 0L; private IndexBuildService indexBuildService; private BlockingQueue originIndexMsgQueue; public IndexRocksDBStore(MessageStore messageStore) { this.messageStore = messageStore; this.storeConfig = messageStore.getMessageStoreConfig(); this.messageRocksDBStorage = messageStore.getMessageRocksDBStorage(); if (this.storeConfig.isIndexRocksDBEnable()) { this.initAndStart(); } } private void initAndStart() { if (this.state == RUNNING) { return; } this.indexBuildService = new IndexBuildService(); this.originIndexMsgQueue = new LinkedBlockingDeque<>(DEFAULT_CAPACITY); this.indexBuildService.start(); this.state = RUNNING; Long lastOffsetPy = messageRocksDBStorage.getLastOffsetPy(RocksDB.DEFAULT_COLUMN_FAMILY); log.info("IndexRocksDBStore start success, lastOffsetPy: {}", lastOffsetPy); } public void shutdown() { if (this.state != RUNNING || this.state == SHUTDOWN) { return; } if (null != this.indexBuildService) { this.indexBuildService.shutdown(); } this.state = SHUTDOWN; log.info("IndexRocksDBStore shutdown success"); } public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long beginTime, long endTime, String indexType, String lastKey) { if (StringUtils.isEmpty(topic) || StringUtils.isEmpty(key) || maxNum <= 0 || beginTime < 0L || endTime <= 0L || beginTime > endTime || !StringUtils.isEmpty(indexType) && !INDEX_TYPE_SET.contains(indexType)) { logError.error("IndexRocksDBStore queryOffset param error, topic: {}, key: {}, maxNum: {}, beginTime: {}, endTime: {}, indexType: {}, lastKey: {}", topic, key, maxNum, beginTime, endTime, indexType, lastKey); return null; } if (beginTime == 0L || Long.MAX_VALUE == endTime) { endTime = System.currentTimeMillis(); beginTime = endTime - TimeUnit.DAYS.toMillis(storeConfig.getMaxRocksDBIndexQueryDays()); } if ((endTime - beginTime) > (TimeUnit.DAYS.toMillis(storeConfig.getMaxRocksDBIndexQueryDays()))) { logError.error("IndexRocksDBStore queryOffset index in rocksdb, can not query more than: {} days", storeConfig.getMaxRocksDBIndexQueryDays()); return null; } long lastUpdateTime = 0L; long lastOffsetPy = 0L; maxNum = Math.min(maxNum, this.storeConfig.getMaxMsgsNumBatch()); List phyOffsets = null; try { lastUpdateTime = messageRocksDBStorage.getLastStoreTimeStampForIndex(RocksDB.DEFAULT_COLUMN_FAMILY); lastOffsetPy = messageRocksDBStorage.getLastOffsetPy(RocksDB.DEFAULT_COLUMN_FAMILY); //compact old client if (StringUtils.isEmpty(indexType)) { phyOffsets = messageRocksDBStorage.queryOffsetForIndex(RocksDB.DEFAULT_COLUMN_FAMILY, topic, MessageConst.INDEX_KEY_TYPE, key, beginTime, endTime, maxNum, null); if (CollectionUtils.isEmpty(phyOffsets)) { phyOffsets = messageRocksDBStorage.queryOffsetForIndex(RocksDB.DEFAULT_COLUMN_FAMILY, topic, MessageConst.INDEX_UNIQUE_TYPE, key, beginTime, endTime, maxNum, null); } } else { phyOffsets = messageRocksDBStorage.queryOffsetForIndex(RocksDB.DEFAULT_COLUMN_FAMILY, topic, indexType, key, beginTime, endTime, maxNum, lastKey); } } catch (Exception e) { logError.error("IndexRocksDBStore queryOffset error, topic: {}, key: {}, maxNum: {}, beginTime: {}, endTime: {}, error: {}", topic, key, maxNum, beginTime, endTime, e.getMessage()); } return new QueryOffsetResult(phyOffsets, lastUpdateTime, lastOffsetPy); } public void buildIndex(DispatchRequest dispatchRequest) { if (null == dispatchRequest || dispatchRequest.getCommitLogOffset() < 0L || dispatchRequest.getMsgSize() <= 0 || state != RUNNING || null == this.originIndexMsgQueue) { logError.error("IndexRocksDBStore buildIndex error, dispatchRequest: {}, state: {}, originIndexMsgQueue: {}", dispatchRequest, state, originIndexMsgQueue); return; } try { long reqOffsetPy = dispatchRequest.getCommitLogOffset(); long endOffsetPy = messageRocksDBStorage.getLastOffsetPy(RocksDB.DEFAULT_COLUMN_FAMILY); if (reqOffsetPy < endOffsetPy) { if (System.currentTimeMillis() % 1000 == 0) { log.warn("IndexRocksDBStore recover buildIndex, but ignore, build index offset reqOffsetPy: {}, endOffsetPy: {}", reqOffsetPy, endOffsetPy); } return; } final int tranType = MessageSysFlag.getTransactionValue(dispatchRequest.getSysFlag()); switch (tranType) { case MessageSysFlag.TRANSACTION_NOT_TYPE: case MessageSysFlag.TRANSACTION_PREPARED_TYPE: case MessageSysFlag.TRANSACTION_COMMIT_TYPE: break; case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: return; } String topic = dispatchRequest.getTopic(); String uniqKey = dispatchRequest.getUniqKey(); long storeTime = dispatchRequest.getStoreTimestamp(); if (StringUtils.isEmpty(topic) || StringUtils.isEmpty(uniqKey) || storeTime <= 0L || reqOffsetPy < 0L) { return; } String keys = dispatchRequest.getKeys(); if (!StringUtils.isEmpty(keys)) { String[] keySplit = keys.split(MessageConst.KEY_SEPARATOR); if (keySplit.length > 0) { Set keySet = Arrays.stream(keySplit).filter(i -> !StringUtils.isEmpty(i)).collect(Collectors.toSet()); for (String key : keySet) { try { while (!originIndexMsgQueue.offer(new IndexRocksDBRecord(topic, key, null, storeTime, uniqKey, reqOffsetPy), 3, TimeUnit.SECONDS)) { if (System.currentTimeMillis() % 1000 == 0) { logError.error("IndexRocksDBStore buildIndex keys error, topic: {}, key: {}, storeTime: {}, uniqKey: {}, reqOffsetPy: {}", topic, key, storeTime, uniqKey, reqOffsetPy); } } } catch (Exception e) { logError.error("IndexRocksDBStore buildIndex keys error, key: {}, uniqKey: {}, topic: {}, error: {}", key, uniqKey, topic, e.getMessage()); } } } } Map propertiesMap = dispatchRequest.getPropertiesMap(); if (null != propertiesMap && propertiesMap.containsKey(MessageConst.PROPERTY_TAGS)) { String tags = propertiesMap.get(MessageConst.PROPERTY_TAGS); if (!StringUtils.isEmpty(tags)) { try { while (!originIndexMsgQueue.offer(new IndexRocksDBRecord(topic, null, tags, storeTime, uniqKey, reqOffsetPy), 3, TimeUnit.SECONDS)) { if (System.currentTimeMillis() % 1000 == 0) { logError.error("IndexRocksDBStore buildIndex offer tags error, topic: {}, tags: {}, storeTime: {}, uniqKey: {}, reqOffsetPy: {}", topic, tags, storeTime, uniqKey, reqOffsetPy); } } } catch (Exception e) { logError.error("IndexRocksDBStore buildIndex tags error, tags: {}, uniqKey: {}, topic: {}, error: {}", tags, uniqKey, topic, e.getMessage()); } } } try { while (!originIndexMsgQueue.offer(new IndexRocksDBRecord(topic, null, null, storeTime, uniqKey, reqOffsetPy), 3, TimeUnit.SECONDS)) { if (System.currentTimeMillis() % 1000 == 0) { logError.error("IndexRocksDBStore buildIndex uniqKey error, topic: {}, storeTime: {}, uniqKey: {}, reqOffsetPy: {}", topic, storeTime, uniqKey, reqOffsetPy); } } } catch (Exception e) { logError.error("IndexRocksDBStore buildIndex uniqKey error: {}", e.getMessage()); } } catch (Exception e) { logError.error("IndexRocksDBStore buildIndex error: {}", e.getMessage()); } } public void deleteExpiredIndex() { try { MappedFile mappedFile = messageStore.getCommitLog().getEarliestMappedFile(); if (null == mappedFile) { logError.info("IndexRocksDBStore deleteExpiredIndex mappedFile is null"); return; } File file = mappedFile.getFile(); if (null == file || StringUtils.isEmpty(file.getAbsolutePath())) { logError.info("IndexRocksDBStore deleteExpiredIndex error, file is null"); return; } Path path = Paths.get(file.getAbsolutePath()); BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); long deleteIndexFileTime = attrs.creationTime().toMillis() - TimeUnit.HOURS.toMillis(1); long desDeleteTimeHour = dealTimeToHourStamps(deleteIndexFileTime); if (desDeleteTimeHour != lastDeleteIndexTime) { messageRocksDBStorage.deleteRecordsForIndex(RocksDB.DEFAULT_COLUMN_FAMILY, desDeleteTimeHour, 168); lastDeleteIndexTime = desDeleteTimeHour; } else { log.info("IndexRocksDBStore ignore this delete, lastDeleteIndexTime: {}, desDeleteTimeHour: {}", lastDeleteIndexTime, desDeleteTimeHour); } } catch (Exception e) { logError.error("IndexRocksDBStore deleteExpiredIndex rocksdb error: {}", e.getMessage()); } } public boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException { if (!storeConfig.isIndexRocksDBEnable()) { return true; } Long lastOffsetPy = messageRocksDBStorage.getLastOffsetPy(RocksDB.DEFAULT_COLUMN_FAMILY); log.info("index isMappedFileMatchedRecover lastOffsetPy: {}", lastOffsetPy); if (null != lastOffsetPy && phyOffset <= lastOffsetPy) { log.info("isMappedFileMatchedRecover IndexRocksDBStore recover form this offset, phyOffset: {}, lastOffsetPy: {}", phyOffset, lastOffsetPy); return true; } return false; } public void destroy() { } @Override public Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException { if (!storeConfig.isIndexRocksDBEnable()) { return null; } Long dispatchFromIndexPhyOffset = messageRocksDBStorage.getLastOffsetPy(RocksDB.DEFAULT_COLUMN_FAMILY); if (dispatchFromIndexPhyOffset != null && dispatchFromIndexPhyOffset > 0) { return dispatchFromIndexPhyOffset; } return null; } private String getServiceThreadName() { String brokerIdentifier = ""; if (this.messageStore instanceof DefaultMessageStore) { DefaultMessageStore messageStore = (DefaultMessageStore) IndexRocksDBStore.this.messageStore; if (messageStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = messageStore.getBrokerConfig().getIdentifier(); } } return brokerIdentifier; } private class IndexBuildService extends ServiceThread { private final Logger log = IndexRocksDBStore.log; private List irs; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); irs = new ArrayList<>(BATCH_SIZE); while (!this.isStopped() || !originIndexMsgQueue.isEmpty()) { try { pollAndPutIndexRequest(); } catch (Exception e) { irs.clear(); logError.error("IndexRocksDBStore error occurred in: {}, error: {}", getServiceName(), e.getMessage()); } } log.info(this.getServiceName() + " service end"); } private void pollAndPutIndexRequest() { pollIndexRecord(); if (CollectionUtils.isEmpty(irs)) { return; } try { messageRocksDBStorage.writeRecordsForIndex(RocksDB.DEFAULT_COLUMN_FAMILY, irs); } catch (Exception e) { logError.error("IndexRocksDBStore IndexBuildService pollAndPutIndexRequest error: {}", e.getMessage()); } irs.clear(); } private void pollIndexRecord() { try { IndexRocksDBRecord firstReq = originIndexMsgQueue.poll(100, TimeUnit.MILLISECONDS); if (firstReq == null) { return; } irs.add(firstReq); originIndexMsgQueue.drainTo(irs, BATCH_SIZE - irs.size()); while (irs.size() < BATCH_SIZE) { IndexRocksDBRecord tmpReq = originIndexMsgQueue.poll(100, TimeUnit.MILLISECONDS); if (tmpReq == null) { break; } irs.add(tmpReq); originIndexMsgQueue.drainTo(irs, BATCH_SIZE - irs.size()); } } catch (Exception e) { logError.error("IndexRocksDBStore IndexBuildService error: {}", e.getMessage()); } } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/kv/CommitLogDispatcherCompaction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import org.apache.rocketmq.store.CommitLogDispatcher; import org.apache.rocketmq.store.DispatchRequest; public class CommitLogDispatcherCompaction implements CommitLogDispatcher { private final CompactionService cptService; public CommitLogDispatcherCompaction(CompactionService srv) { this.cptService = srv; } @Override public void dispatch(DispatchRequest request) { if (cptService != null) { cptService.putRequest(request); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/kv/CompactionLog.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import com.google.common.collect.Lists; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.CompactionAppendMsgCallback; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MappedFileQueue; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageLock; import org.apache.rocketmq.store.PutMessageReentrantLock; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageSpinLock; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.StoreUtil; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.BatchConsumeQueue; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.apache.rocketmq.store.queue.SparseConsumeQueue; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static org.apache.rocketmq.common.message.MessageDecoder.BLANK_MAGIC_CODE; public class CompactionLog { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final int END_FILE_MIN_BLANK_LENGTH = 4 + 4; private static final int MAX_PULL_MSG_SIZE = 128 * 1024 * 1024; public static final String COMPACTING_SUB_FOLDER = "compacting"; public static final String REPLICATING_SUB_FOLDER = "replicating"; private final int compactionLogMappedFileSize; private final int compactionCqMappedFileSize; private final String compactionLogFilePath; private final String compactionCqFilePath; private final MessageStore defaultMessageStore; private final CompactionStore compactionStore; private final MessageStoreConfig messageStoreConfig; private final CompactionAppendMsgCallback endMsgCallback; private final String topic; private final int queueId; private final int offsetMapMemorySize; private final PutMessageLock putMessageLock; private final PutMessageLock readMessageLock; private TopicPartitionLog current; private TopicPartitionLog compacting; private TopicPartitionLog replicating; private final CompactionPositionMgr positionMgr; private final AtomicReference state; public CompactionLog(final MessageStore messageStore, final CompactionStore compactionStore, final String topic, final int queueId) throws IOException { this.topic = topic; this.queueId = queueId; this.defaultMessageStore = messageStore; this.compactionStore = compactionStore; this.messageStoreConfig = messageStore.getMessageStoreConfig(); this.offsetMapMemorySize = compactionStore.getOffsetMapSize(); this.compactionCqMappedFileSize = messageStoreConfig.getCompactionCqMappedFileSize() / BatchConsumeQueue.CQ_STORE_UNIT_SIZE * BatchConsumeQueue.CQ_STORE_UNIT_SIZE; this.compactionLogMappedFileSize = getCompactionLogSize(compactionCqMappedFileSize, messageStoreConfig.getCompactionMappedFileSize()); this.compactionLogFilePath = Paths.get(compactionStore.getCompactionLogPath(), topic, String.valueOf(queueId)).toString(); this.compactionCqFilePath = compactionStore.getCompactionCqPath(); // batch consume queue already separated this.positionMgr = compactionStore.getPositionMgr(); this.putMessageLock = messageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock(); this.readMessageLock = messageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock(); this.endMsgCallback = new CompactionAppendEndMsgCallback(); this.state = new AtomicReference<>(State.INITIALIZING); log.info("CompactionLog {}:{} init completed.", topic, queueId); } private int getCompactionLogSize(int cqSize, int origLogSize) { int n = origLogSize / cqSize; if (n < 5) { return cqSize * 5; } int m = origLogSize % cqSize; if (m > 0 && m < (cqSize >> 1)) { return n * cqSize; } else { return (n + 1) * cqSize; } } public void load(boolean exitOk) throws IOException, RuntimeException { initLogAndCq(exitOk); if (defaultMessageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE && getLog().isMappedFilesEmpty()) { log.info("{}:{} load compactionLog from remote master", topic, queueId); loadFromRemoteAsync(); } else { state.compareAndSet(State.INITIALIZING, State.NORMAL); } } private void initLogAndCq(boolean exitOk) throws IOException, RuntimeException { current = new TopicPartitionLog(this); current.init(exitOk); } private boolean putMessageFromRemote(byte[] bytes) { ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); // split bytebuffer to avoid encode message again while (byteBuffer.hasRemaining()) { int mark = byteBuffer.position(); ByteBuffer bb = byteBuffer.slice(); int size = bb.getInt(); if (size < 0 || size > byteBuffer.capacity()) { break; } else { bb.limit(size); bb.rewind(); } MessageExt messageExt = MessageDecoder.decode(bb, false, false); long messageOffset = messageExt.getQueueOffset(); long minOffsetInQueue = getCQ().getMinOffsetInQueue(); if (getLog().isMappedFilesEmpty() || messageOffset < minOffsetInQueue) { asyncPutMessage(bb, messageExt, replicating); } else { log.info("{}:{} message offset {} >= minOffsetInQueue {}, stop pull...", topic, queueId, messageOffset, minOffsetInQueue); return false; } byteBuffer.position(mark + size); } return true; } private void pullMessageFromMaster() throws Exception { if (StringUtils.isBlank(compactionStore.getMasterAddr())) { compactionStore.getCompactionSchedule().schedule(() -> { try { pullMessageFromMaster(); } catch (Exception e) { log.error("pullMessageFromMaster exception: ", e); } }, 5, TimeUnit.SECONDS); return; } replicating = new TopicPartitionLog(this, REPLICATING_SUB_FOLDER); try (MessageFetcher messageFetcher = new MessageFetcher()) { messageFetcher.pullMessageFromMaster(topic, queueId, getCQ().getMinOffsetInQueue(), compactionStore.getMasterAddr(), (currOffset, response) -> { if (currOffset < 0) { log.info("{}:{} current offset {}, stop pull...", topic, queueId, currOffset); return false; } return putMessageFromRemote(response.getBody()); // positionMgr.setOffset(topic, queueId, currOffset); }); } // merge files if (getLog().isMappedFilesEmpty()) { replaceFiles(getLog().getMappedFiles(), current, replicating); } else if (replicating.getLog().isMappedFilesEmpty()) { log.info("replicating message is empty"); //break } else { List newFiles = Lists.newArrayList(); List toCompactFiles = Lists.newArrayList(replicating.getLog().getMappedFiles()); putMessageLock.lock(); try { // combine current and replicating to mappedFileList newFiles = Lists.newArrayList(getLog().getMappedFiles()); toCompactFiles.addAll(newFiles); //all from current current.roll(toCompactFiles.size() * compactionLogMappedFileSize); } catch (Throwable e) { log.error("roll log and cq exception: ", e); } finally { putMessageLock.unlock(); } try { // doCompaction with current and replicating compactAndReplace(new ProcessFileList(toCompactFiles, toCompactFiles)); } catch (Throwable e) { log.error("do merge replicating and current exception: ", e); } } // cleanReplicatingResource, force clean cq replicating.clean(false, true); // positionMgr.setOffset(topic, queueId, currentPullOffset); state.compareAndSet(State.INITIALIZING, State.NORMAL); } private void loadFromRemoteAsync() { compactionStore.getCompactionSchedule().submit(() -> { try { pullMessageFromMaster(); } catch (Exception e) { log.error("fetch message from master exception: ", e); } }); // update (currentStatus) = LOADING // request => get (start, end) // pull message => current message offset > end // done // positionMgr.persist(); // update (currentStatus) = RUNNING } private long nextOffsetCorrection(long oldOffset, long newOffset) { long nextOffset = oldOffset; if (messageStoreConfig.getBrokerRole() != BrokerRole.SLAVE || messageStoreConfig.isOffsetCheckInSlave()) { nextOffset = newOffset; } return nextOffset; } private boolean checkInDiskByCommitOffset(long offsetPy, long maxOffsetPy) { long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0)); return (maxOffsetPy - offsetPy) > memory; } private boolean isTheBatchFull(int sizePy, int unitBatchNum, int maxMsgNums, long maxMsgSize, int bufferTotal, int messageTotal, boolean isInDisk) { if (0 == bufferTotal || 0 == messageTotal) { return false; } if (messageTotal + unitBatchNum > maxMsgNums) { return true; } if (bufferTotal + sizePy > maxMsgSize) { return true; } if (isInDisk) { if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInDisk()) { return true; } if (messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInDisk() - 1) { return true; } } else { if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInMemory()) { return true; } if (messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInMemory() - 1) { return true; } } return false; } public long rollNextFile(final long offset) { return offset + compactionLogMappedFileSize - offset % compactionLogMappedFileSize; } boolean shouldRetainMsg(final MessageExt msgExt, final OffsetMap map) throws DigestException { if (msgExt.getQueueOffset() > map.getLastOffset()) { return true; } String key = msgExt.getKeys(); if (StringUtils.isNotBlank(key)) { boolean keyNotExistOrOffsetBigger = msgExt.getQueueOffset() >= map.get(key); boolean hasBody = ArrayUtils.isNotEmpty(msgExt.getBody()); return keyNotExistOrOffsetBigger && hasBody; } else { log.error("message has no keys"); return false; } } public void checkAndPutMessage(final SelectMappedBufferResult selectMappedBufferResult, final MessageExt msgExt, final OffsetMap offsetMap, final TopicPartitionLog tpLog) throws DigestException { if (shouldRetainMsg(msgExt, offsetMap)) { asyncPutMessage(selectMappedBufferResult.getByteBuffer(), msgExt, tpLog); } } public CompletableFuture asyncPutMessage(final SelectMappedBufferResult selectMappedBufferResult) { return asyncPutMessage(selectMappedBufferResult, current); } public CompletableFuture asyncPutMessage(final SelectMappedBufferResult selectMappedBufferResult, final TopicPartitionLog tpLog) { MessageExt msgExt = MessageDecoder.decode(selectMappedBufferResult.getByteBuffer(), false, false); return asyncPutMessage(selectMappedBufferResult.getByteBuffer(), msgExt, tpLog); } public CompletableFuture asyncPutMessage(final ByteBuffer msgBuffer, final MessageExt msgExt, final TopicPartitionLog tpLog) { return asyncPutMessage(msgBuffer, msgExt.getTopic(), msgExt.getQueueId(), msgExt.getQueueOffset(), msgExt.getMsgId(), msgExt.getKeys(), MessageExtBrokerInner.tagsString2tagsCode(msgExt.getTags()), msgExt.getStoreTimestamp(), tpLog); } public CompletableFuture asyncPutMessage(final ByteBuffer msgBuffer, final DispatchRequest dispatchRequest) { return asyncPutMessage(msgBuffer, dispatchRequest.getTopic(), dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset(), dispatchRequest.getUniqKey(), dispatchRequest.getKeys(), dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(), current); } public CompletableFuture asyncPutMessage(final ByteBuffer msgBuffer, final DispatchRequest dispatchRequest, final TopicPartitionLog tpLog) { return asyncPutMessage(msgBuffer, dispatchRequest.getTopic(), dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset(), dispatchRequest.getUniqKey(), dispatchRequest.getKeys(), dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(), tpLog); } public CompletableFuture asyncPutMessage(final ByteBuffer msgBuffer, String topic, int queueId, long queueOffset, String msgId, String keys, long tagsCode, long storeTimestamp, final TopicPartitionLog tpLog) { // fix duplicate if (tpLog.getCQ().getMaxOffsetInQueue() - 1 >= queueOffset) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } if (StringUtils.isBlank(keys)) { log.warn("message {}-{}:{} have no key, will not put in compaction log", topic, queueId, msgId); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null)); } putMessageLock.lock(); try { long beginTime = System.nanoTime(); if (tpLog.isEmptyOrCurrentFileFull()) { try { tpLog.roll(); } catch (IOException e) { log.error("create mapped file or consumerQueue exception: ", e); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, null)); } } MappedFile mappedFile = tpLog.getLog().getLastMappedFile(); CompactionAppendMsgCallback callback = new CompactionAppendMessageCallback(topic, queueId, tagsCode, storeTimestamp, tpLog.getCQ()); AppendMessageResult result = mappedFile.appendMessage(msgBuffer, callback); switch (result.getStatus()) { case PUT_OK: break; case END_OF_FILE: try { tpLog.roll(); } catch (IOException e) { log.error("create mapped file2 error, topic: {}, msgId: {}", topic, msgId); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, result)); } mappedFile = tpLog.getLog().getLastMappedFile(); result = mappedFile.appendMessage(msgBuffer, callback); break; default: return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); } return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, result)); } finally { putMessageLock.unlock(); } } private SelectMappedBufferResult getMessage(final long offset, final int size) { MappedFile mappedFile = this.getLog().findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int pos = (int) (offset % compactionLogMappedFileSize); return mappedFile.selectMappedBuffer(pos, size); } return null; } private boolean validateCqUnit(CqUnit cqUnit) { return cqUnit.getPos() >= 0 && cqUnit.getSize() > 0 && cqUnit.getQueueOffset() >= 0 && cqUnit.getBatchNum() > 0; } public GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final int maxTotalMsgSize) { readMessageLock.lock(); try { long beginTime = System.nanoTime(); GetMessageStatus status; long nextBeginOffset = offset; long minOffset = 0; long maxOffset = 0; GetMessageResult getResult = new GetMessageResult(); final long maxOffsetPy = getLog().getMaxOffset(); SparseConsumeQueue consumeQueue = getCQ(); if (consumeQueue != null) { minOffset = consumeQueue.getMinOffsetInQueue(); maxOffset = consumeQueue.getMaxOffsetInQueue(); if (maxOffset == 0) { status = GetMessageStatus.NO_MESSAGE_IN_QUEUE; nextBeginOffset = nextOffsetCorrection(offset, 0); } else if (offset == maxOffset) { status = GetMessageStatus.OFFSET_OVERFLOW_ONE; nextBeginOffset = nextOffsetCorrection(offset, offset); } else if (offset > maxOffset) { status = GetMessageStatus.OFFSET_OVERFLOW_BADLY; if (0 == minOffset) { nextBeginOffset = nextOffsetCorrection(offset, minOffset); } else { nextBeginOffset = nextOffsetCorrection(offset, maxOffset); } } else { long maxPullSize = Math.max(maxTotalMsgSize, 100); if (maxPullSize > MAX_PULL_MSG_SIZE) { log.warn("The max pull size is too large maxPullSize={} topic={} queueId={}", maxPullSize, topic, queueId); maxPullSize = MAX_PULL_MSG_SIZE; } status = GetMessageStatus.NO_MATCHED_MESSAGE; long maxPhyOffsetPulling = 0; int cqFileNum = 0; while (getResult.getBufferTotalSize() <= 0 && nextBeginOffset < maxOffset && cqFileNum++ < this.messageStoreConfig.getTravelCqFileNumWhenGetMessage()) { ReferredIterator bufferConsumeQueue = consumeQueue.iterateFromOrNext(nextBeginOffset); if (bufferConsumeQueue == null) { status = GetMessageStatus.OFFSET_FOUND_NULL; nextBeginOffset = nextOffsetCorrection(nextBeginOffset, consumeQueue.rollNextFile(nextBeginOffset)); log.warn("consumer request topic:{}, offset:{}, minOffset:{}, maxOffset:{}, " + "but access logic queue failed. correct nextBeginOffset to {}", topic, offset, minOffset, maxOffset, nextBeginOffset); break; } try { long nextPhyFileStartOffset = Long.MIN_VALUE; while (bufferConsumeQueue.hasNext() && nextBeginOffset < maxOffset) { CqUnit cqUnit = bufferConsumeQueue.next(); if (!validateCqUnit(cqUnit)) { break; } long offsetPy = cqUnit.getPos(); int sizePy = cqUnit.getSize(); boolean isInDisk = checkInDiskByCommitOffset(offsetPy, maxOffsetPy); if (isTheBatchFull(sizePy, cqUnit.getBatchNum(), maxMsgNums, maxPullSize, getResult.getBufferTotalSize(), getResult.getMessageCount(), isInDisk)) { break; } if (getResult.getBufferTotalSize() >= maxPullSize) { break; } maxPhyOffsetPulling = offsetPy; //Be careful, here should before the isTheBatchFull nextBeginOffset = cqUnit.getQueueOffset() + cqUnit.getBatchNum(); if (nextPhyFileStartOffset != Long.MIN_VALUE) { if (offsetPy < nextPhyFileStartOffset) { continue; } } SelectMappedBufferResult selectResult = getMessage(offsetPy, sizePy); if (null == selectResult) { if (getResult.getBufferTotalSize() == 0) { status = GetMessageStatus.MESSAGE_WAS_REMOVING; } // nextPhyFileStartOffset = this.commitLog.rollNextFile(offsetPy); nextPhyFileStartOffset = rollNextFile(offsetPy); continue; } this.defaultMessageStore.getStoreStatsService().getGetMessageTransferredMsgCount().add(cqUnit.getBatchNum()); getResult.addMessage(selectResult, cqUnit.getQueueOffset(), cqUnit.getBatchNum()); status = GetMessageStatus.FOUND; nextPhyFileStartOffset = Long.MIN_VALUE; } } finally { bufferConsumeQueue.release(); } } long diff = maxOffsetPy - maxPhyOffsetPulling; long memory = (long)(StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0)); getResult.setSuggestPullingFromSlave(diff > memory); } } else { status = GetMessageStatus.NO_MATCHED_LOGIC_QUEUE; nextBeginOffset = nextOffsetCorrection(offset, 0); } if (GetMessageStatus.FOUND == status) { this.defaultMessageStore.getStoreStatsService().getGetMessageTimesTotalFound().add(getResult.getMessageCount()); } else { this.defaultMessageStore.getStoreStatsService().getGetMessageTimesTotalMiss().add(getResult.getMessageCount()); } long elapsedTime = this.defaultMessageStore.getSystemClock().now() - beginTime; this.defaultMessageStore.getStoreStatsService().setGetMessageEntireTimeMax(elapsedTime); getResult.setStatus(status); getResult.setNextBeginOffset(nextBeginOffset); getResult.setMaxOffset(maxOffset); getResult.setMinOffset(minOffset); return getResult; } finally { readMessageLock.unlock(); } } ProcessFileList getCompactionFile() { List mappedFileList = Lists.newArrayList(getLog().getMappedFiles()); if (mappedFileList.size() < 2) { return null; } List toCompactFiles = mappedFileList.subList(0, mappedFileList.size() - 1); //exclude the last writing file List newFiles = Lists.newArrayList(); for (int i = 0; i < mappedFileList.size() - 1; i++) { MappedFile mf = mappedFileList.get(i); long maxQueueOffsetInFile = getCQ().getMaxMsgOffsetFromFile(mf.getFile().getName()); if (maxQueueOffsetInFile > positionMgr.getOffset(topic, queueId)) { newFiles.add(mf); } } if (newFiles.isEmpty()) { return null; } return new ProcessFileList(toCompactFiles, newFiles); } void compactAndReplace(ProcessFileList compactFiles) throws Throwable { if (compactFiles == null || compactFiles.isEmpty()) { return; } long startTime = System.nanoTime(); OffsetMap offsetMap = getOffsetMap(compactFiles.newFiles); compaction(compactFiles.toCompactFiles, offsetMap); replaceFiles(compactFiles.toCompactFiles, current, compacting); positionMgr.setOffset(topic, queueId, offsetMap.lastOffset); positionMgr.persist(); compacting.clean(false, false); log.info("this compaction elapsed {} milliseconds", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); } void doCompaction() { if (!state.compareAndSet(State.NORMAL, State.COMPACTING)) { log.warn("compactionLog state is {}, skip this time", state.get()); return; } try { compactAndReplace(getCompactionFile()); } catch (Throwable e) { log.error("do compaction exception: ", e); } state.compareAndSet(State.COMPACTING, State.NORMAL); } protected OffsetMap getOffsetMap(List mappedFileList) throws NoSuchAlgorithmException, DigestException { OffsetMap offsetMap = new OffsetMap(offsetMapMemorySize); for (MappedFile mappedFile : mappedFileList) { Iterator iterator = mappedFile.iterator(0); while (iterator.hasNext()) { SelectMappedBufferResult smb = null; try { smb = iterator.next(); //decode bytebuffer MessageExt msg = MessageDecoder.decode(smb.getByteBuffer(), true, false); if (msg != null) { ////get key & offset and put to offsetMap if (msg.getQueueOffset() > positionMgr.getOffset(topic, queueId)) { offsetMap.put(msg.getKeys(), msg.getQueueOffset()); } } else { // msg is null indicate that file is end break; } } catch (DigestException e) { log.error("offsetMap put exception: ", e); throw e; } finally { if (smb != null) { smb.release(); } } } } return offsetMap; } protected void putEndMessage(MappedFileQueue mappedFileQueue) { MappedFile lastFile = mappedFileQueue.getLastMappedFile(); if (!lastFile.isFull()) { lastFile.appendMessage(ByteBuffer.allocate(0), endMsgCallback); } } protected void compaction(List mappedFileList, OffsetMap offsetMap) throws DigestException { compacting = new TopicPartitionLog(this, COMPACTING_SUB_FOLDER); for (MappedFile mappedFile : mappedFileList) { Iterator iterator = mappedFile.iterator(0); while (iterator.hasNext()) { SelectMappedBufferResult smb = null; try { smb = iterator.next(); MessageExt msgExt = MessageDecoder.decode(smb.getByteBuffer(), true, true); if (msgExt == null) { // file end break; } else { checkAndPutMessage(smb, msgExt, offsetMap, compacting); } } finally { if (smb != null) { smb.release(); } } } } putEndMessage(compacting.getLog()); } protected void replaceFiles(List mappedFileList, TopicPartitionLog current, TopicPartitionLog newLog) { MappedFileQueue dest = current.getLog(); MappedFileQueue src = newLog.getLog(); long beginTime = System.nanoTime(); // List fileNameToReplace = mappedFileList.stream() // .map(m -> m.getFile().getName()) // .collect(Collectors.toList()); List fileNameToReplace = dest.getMappedFiles().stream() .filter(mappedFileList::contains) .map(mf -> mf.getFile().getName()) .collect(Collectors.toList()); mappedFileList.forEach(MappedFile::renameToDelete); src.getMappedFiles().forEach(mappedFile -> { try { mappedFile.flush(0); mappedFile.moveToParent(); } catch (IOException e) { log.error("move file {} to parent directory exception: ", mappedFile.getFileName()); } }); dest.getMappedFiles().stream() .filter(m -> !mappedFileList.contains(m)) .forEach(m -> src.getMappedFiles().add(m)); readMessageLock.lock(); try { mappedFileList.forEach(mappedFile -> mappedFile.destroy(1000)); dest.getMappedFiles().clear(); dest.getMappedFiles().addAll(src.getMappedFiles()); src.getMappedFiles().clear(); replaceCqFiles(getCQ(), newLog.getCQ(), fileNameToReplace); log.info("replace file elapsed {} milliseconds", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginTime)); } finally { readMessageLock.unlock(); } } protected void replaceCqFiles(SparseConsumeQueue currentBcq, SparseConsumeQueue compactionBcq, List fileNameToReplace) { long beginTime = System.nanoTime(); MappedFileQueue currentMq = currentBcq.getMappedFileQueue(); MappedFileQueue compactMq = compactionBcq.getMappedFileQueue(); List fileListToDelete = currentMq.getMappedFiles().stream().filter(m -> fileNameToReplace.contains(m.getFile().getName())).collect(Collectors.toList()); fileListToDelete.forEach(MappedFile::renameToDelete); compactMq.getMappedFiles().forEach(mappedFile -> { try { mappedFile.flush(0); mappedFile.moveToParent(); } catch (IOException e) { log.error("move consume queue file {} to parent directory exception: ", mappedFile.getFileName(), e); } }); currentMq.getMappedFiles().stream() .filter(m -> !fileListToDelete.contains(m)) .forEach(m -> compactMq.getMappedFiles().add(m)); fileListToDelete.forEach(mappedFile -> mappedFile.destroy(1000)); currentMq.getMappedFiles().clear(); currentMq.getMappedFiles().addAll(compactMq.getMappedFiles()); compactMq.getMappedFiles().clear(); currentBcq.refresh(); log.info("replace consume queue file elapsed {} millsecs.", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginTime)); } public MappedFileQueue getLog() { return current.mappedFileQueue; } public SparseConsumeQueue getCQ() { return current.consumeQueue; } // public SparseConsumeQueue getCompactionScq() { // return compactionScq; // } public void flush(int flushLeastPages) { this.flushLog(flushLeastPages); this.flushCQ(flushLeastPages); } public void flushLog(int flushLeastPages) { getLog().flush(flushLeastPages); } public void flushCQ(int flushLeastPages) { getCQ().flush(flushLeastPages); } static class CompactionAppendEndMsgCallback implements CompactionAppendMsgCallback { @Override public AppendMessageResult doAppend(ByteBuffer bbDest, long fileFromOffset, int maxBlank, ByteBuffer bbSrc) { ByteBuffer endInfo = ByteBuffer.allocate(END_FILE_MIN_BLANK_LENGTH); endInfo.putInt(maxBlank); endInfo.putInt(BLANK_MAGIC_CODE); return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, fileFromOffset + bbDest.position(), maxBlank, System.currentTimeMillis()); } } static class CompactionAppendMessageCallback implements CompactionAppendMsgCallback { private final String topic; private final int queueId; private final long tagsCode; private final long storeTimestamp; private final SparseConsumeQueue bcq; public CompactionAppendMessageCallback(MessageExt msgExt, SparseConsumeQueue bcq) { this.topic = msgExt.getTopic(); this.queueId = msgExt.getQueueId(); this.tagsCode = MessageExtBrokerInner.tagsString2tagsCode(msgExt.getTags()); this.storeTimestamp = msgExt.getStoreTimestamp(); this.bcq = bcq; } public CompactionAppendMessageCallback(String topic, int queueId, long tagsCode, long storeTimestamp, SparseConsumeQueue bcq) { this.topic = topic; this.queueId = queueId; this.tagsCode = tagsCode; this.storeTimestamp = storeTimestamp; this.bcq = bcq; } @Override public AppendMessageResult doAppend(ByteBuffer bbDest, long fileFromOffset, int maxBlank, ByteBuffer bbSrc) { final int msgLen = bbSrc.getInt(0); MappedFile bcqMappedFile = bcq.getMappedFileQueue().getLastMappedFile(); if (bcqMappedFile.getWrotePosition() + BatchConsumeQueue.CQ_STORE_UNIT_SIZE >= bcqMappedFile.getFileSize() || (msgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) { //bcq will full or log will full bcq.putEndPositionInfo(bcqMappedFile); bbDest.putInt(maxBlank); bbDest.putInt(BLANK_MAGIC_CODE); return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, fileFromOffset + bbDest.position(), maxBlank, storeTimestamp); } //get logic offset and physical offset int logicOffsetPos = 4 + 4 + 4 + 4 + 4; long logicOffset = bbSrc.getLong(logicOffsetPos); int destPos = bbDest.position(); long physicalOffset = fileFromOffset + bbDest.position(); bbSrc.rewind(); bbSrc.limit(msgLen); bbDest.put(bbSrc); bbDest.putLong(destPos + logicOffsetPos + 8, physicalOffset); //replace physical offset boolean result = bcq.putBatchMessagePositionInfo(physicalOffset, msgLen, tagsCode, storeTimestamp, logicOffset, (short)1); if (!result) { log.error("put message {}-{} position info failed", topic, queueId); } return new AppendMessageResult(AppendMessageStatus.PUT_OK, physicalOffset, msgLen, storeTimestamp); } } static class OffsetMap { private final ByteBuffer dataBytes; private final int capacity; private final int entrySize; private int entryNum; private final MessageDigest digest; private final int hashSize; private long lastOffset; private final byte[] hash1; private final byte[] hash2; public OffsetMap(int memorySize) throws NoSuchAlgorithmException { this(memorySize, MessageDigest.getInstance("MD5")); } public OffsetMap(int memorySize, MessageDigest digest) { this.hashSize = digest.getDigestLength(); this.entrySize = hashSize + (Long.SIZE / Byte.SIZE); this.capacity = Math.max(memorySize / entrySize, 100); this.dataBytes = ByteBuffer.allocate(capacity * entrySize); this.hash1 = new byte[hashSize]; this.hash2 = new byte[hashSize]; this.entryNum = 0; this.digest = digest; } public void put(String key, final long offset) throws DigestException { if (entryNum >= capacity) { throw new IllegalArgumentException("offset map is full"); } hashInto(key, hash1); int tryNum = 0; int index = indexOf(hash1, tryNum); while (!isEmpty(index)) { dataBytes.position(index); dataBytes.get(hash2); if (Arrays.equals(hash1, hash2)) { dataBytes.putLong(offset); lastOffset = offset; return; } tryNum++; index = indexOf(hash1, tryNum); } dataBytes.position(index); dataBytes.put(hash1); dataBytes.putLong(offset); lastOffset = offset; entryNum += 1; } public long get(String key) throws DigestException { hashInto(key, hash1); int tryNum = 0; int maxTryNum = entryNum + hashSize - 4; int index = 0; do { if (tryNum >= maxTryNum) { return -1L; } index = indexOf(hash1, tryNum); dataBytes.position(index); if (isEmpty(index)) { return -1L; } dataBytes.get(hash2); tryNum++; } while (!Arrays.equals(hash1, hash2)); return dataBytes.getLong(); } public long getLastOffset() { return lastOffset; } private boolean isEmpty(int pos) { return dataBytes.getLong(pos) == 0 && dataBytes.getLong(pos + 8) == 0 && dataBytes.getLong(pos + 16) == 0; } private int indexOf(byte[] hash, int tryNum) { int index = readInt(hash, Math.min(tryNum, hashSize - 4)) + Math.max(0, tryNum - hashSize + 4); int entry = Math.abs(index) % capacity; return entry * entrySize; } private void hashInto(String key, byte[] buf) throws DigestException { digest.update(key.getBytes(StandardCharsets.UTF_8)); digest.digest(buf, 0, hashSize); } private int readInt(byte[] buf, int offset) { return ((buf[offset] & 0xFF) << 24) | ((buf[offset + 1] & 0xFF) << 16) | ((buf[offset + 2] & 0xFF) << 8) | ((buf[offset + 3] & 0xFF)); } } static class TopicPartitionLog { MappedFileQueue mappedFileQueue; SparseConsumeQueue consumeQueue; public TopicPartitionLog(CompactionLog compactionLog) { this(compactionLog, null); } public TopicPartitionLog(CompactionLog compactionLog, String subFolder) { if (StringUtils.isBlank(subFolder)) { mappedFileQueue = new MappedFileQueue(compactionLog.compactionLogFilePath, compactionLog.compactionLogMappedFileSize, null); consumeQueue = new SparseConsumeQueue(compactionLog.topic, compactionLog.queueId, compactionLog.compactionCqFilePath, compactionLog.compactionCqMappedFileSize, compactionLog.defaultMessageStore); } else { mappedFileQueue = new MappedFileQueue(compactionLog.compactionLogFilePath + File.separator + subFolder, compactionLog.compactionLogMappedFileSize, null); consumeQueue = new SparseConsumeQueue(compactionLog.topic, compactionLog.queueId, compactionLog.compactionCqFilePath, compactionLog.compactionCqMappedFileSize, compactionLog.defaultMessageStore, subFolder); } } public void shutdown() { mappedFileQueue.shutdown(1000 * 30); consumeQueue.getMappedFileQueue().shutdown(1000 * 30); } public void init(boolean exitOk) throws IOException, RuntimeException { if (!mappedFileQueue.load()) { shutdown(); throw new IOException("load log exception"); } if (!consumeQueue.load()) { shutdown(); throw new IOException("load consume queue exception"); } try { consumeQueue.recover(); recover(); sanityCheck(); } catch (Exception e) { shutdown(); throw e; } } private void recover() { long maxCqPhysicOffset = consumeQueue.getMaxPhyOffsetInLog(); log.info("{}:{} max physical offset in compaction log is {}", consumeQueue.getTopic(), consumeQueue.getQueueId(), maxCqPhysicOffset); if (maxCqPhysicOffset > 0) { this.mappedFileQueue.setFlushedWhere(maxCqPhysicOffset); this.mappedFileQueue.setCommittedWhere(maxCqPhysicOffset); this.mappedFileQueue.truncateDirtyFiles(maxCqPhysicOffset); } } void sanityCheck() throws RuntimeException { List mappedFileList = mappedFileQueue.getMappedFiles(); for (MappedFile file : mappedFileList) { if (!consumeQueue.containsOffsetFile(Long.parseLong(file.getFile().getName()))) { throw new RuntimeException("log file mismatch with consumeQueue file " + file.getFileName()); } } List cqMappedFileList = consumeQueue.getMappedFileQueue().getMappedFiles(); for (MappedFile file: cqMappedFileList) { if (mappedFileList.stream().noneMatch(m -> Objects.equals(m.getFile().getName(), file.getFile().getName()))) { throw new RuntimeException("consumeQueue file mismatch with log file " + file.getFileName()); } } } public synchronized void roll() throws IOException { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); if (mappedFile == null) { throw new IOException("create new file error"); } long baseOffset = mappedFile.getFileFromOffset(); MappedFile cqFile = consumeQueue.createFile(baseOffset); if (cqFile == null) { mappedFile.destroy(1000); mappedFileQueue.getMappedFiles().remove(mappedFile); throw new IOException("create new consumeQueue file error"); } } public synchronized void roll(int baseOffset) throws IOException { MappedFile mappedFile = mappedFileQueue.tryCreateMappedFile(baseOffset); if (mappedFile == null) { throw new IOException("create new file error"); } MappedFile cqFile = consumeQueue.createFile(baseOffset); if (cqFile == null) { mappedFile.destroy(1000); mappedFileQueue.getMappedFiles().remove(mappedFile); throw new IOException("create new consumeQueue file error"); } } public boolean isEmptyOrCurrentFileFull() { return mappedFileQueue.isEmptyOrCurrentFileFull() || consumeQueue.getMappedFileQueue().isEmptyOrCurrentFileFull(); } public void clean(MappedFileQueue mappedFileQueue) throws IOException { for (MappedFile mf : mappedFileQueue.getMappedFiles()) { if (mf.getFile().exists()) { log.error("directory {} with {} not empty.", mappedFileQueue.getStorePath(), mf.getFileName()); throw new IOException("directory " + mappedFileQueue.getStorePath() + " not empty."); } } mappedFileQueue.destroy(); } public void clean(boolean forceCleanLog, boolean forceCleanCq) throws IOException { //clean and delete sub_folder if (forceCleanLog) { mappedFileQueue.destroy(); } else { clean(mappedFileQueue); } if (forceCleanCq) { consumeQueue.getMappedFileQueue().destroy(); } else { clean(consumeQueue.getMappedFileQueue()); } } public MappedFileQueue getLog() { return mappedFileQueue; } public SparseConsumeQueue getCQ() { return consumeQueue; } } enum State { NORMAL, INITIALIZING, COMPACTING, } static class ProcessFileList { List newFiles; List toCompactFiles; public ProcessFileList(List toCompactFiles, List newFiles) { this.toCompactFiles = toCompactFiles; this.newFiles = newFiles; } boolean isEmpty() { return CollectionUtils.isEmpty(newFiles) || CollectionUtils.isEmpty(toCompactFiles); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/kv/CompactionPositionMgr.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.io.File; import java.util.concurrent.ConcurrentHashMap; public class CompactionPositionMgr extends ConfigManager { public static final String CHECKPOINT_FILE = "position-checkpoint"; private transient String compactionPath; private transient String checkpointFileName; private ConcurrentHashMap queueOffsetMap = new ConcurrentHashMap<>(); private CompactionPositionMgr() { } public CompactionPositionMgr(final String compactionPath) { this.compactionPath = compactionPath; this.checkpointFileName = compactionPath + File.separator + CHECKPOINT_FILE; this.load(); } public void setOffset(String topic, int queueId, final long offset) { queueOffsetMap.put(topic + "_" + queueId, offset); } public long getOffset(String topic, int queueId) { return queueOffsetMap.getOrDefault(topic + "_" + queueId, -1L); } public boolean isEmpty() { return queueOffsetMap.isEmpty(); } public boolean isCompaction(String topic, int queueId, long offset) { return getOffset(topic, queueId) > offset; } @Override public String configFilePath() { return checkpointFileName; } @Override public String encode() { return this.encode(false); } @Override public String encode(boolean prettyFormat) { return RemotingSerializable.toJson(this, prettyFormat); } @Override public void decode(String jsonString) { if (jsonString != null) { CompactionPositionMgr obj = RemotingSerializable.fromJson(jsonString, CompactionPositionMgr.class); if (obj != null) { this.queueOffsetMap = obj.queueOffsetMap; } } } public ConcurrentHashMap getQueueOffsetMap() { return queueOffsetMap; } public void setQueueOffsetMap(ConcurrentHashMap queueOffsetMap) { this.queueOffsetMap = queueOffsetMap; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/kv/CompactionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CleanupPolicy; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.CleanupPolicyUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; import java.util.Objects; import java.util.Optional; public class CompactionService { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final CompactionStore compactionStore; private final DefaultMessageStore defaultMessageStore; private final CommitLog commitLog; public CompactionService(CommitLog commitLog, DefaultMessageStore messageStore, CompactionStore compactionStore) { this.commitLog = commitLog; this.defaultMessageStore = messageStore; this.compactionStore = compactionStore; } public void putRequest(DispatchRequest request) { if (request == null) { return; } String topic = request.getTopic(); Optional topicConfig = defaultMessageStore.getTopicConfig(topic); CleanupPolicy policy = CleanupPolicyUtils.getDeletePolicy(topicConfig); //check request topic flag if (Objects.equals(policy, CleanupPolicy.COMPACTION)) { SelectMappedBufferResult smr = null; try { smr = commitLog.getData(request.getCommitLogOffset()); if (smr != null) { compactionStore.doDispatch(request, smr); } } catch (Exception e) { log.error("putMessage into {}:{} compactionLog exception: ", request.getTopic(), request.getQueueId(), e); } finally { if (smr != null) { smr.release(); } } } // else skip if message isn't compaction } public boolean load(boolean exitOK) { try { compactionStore.load(exitOK); return true; } catch (Exception e) { log.error("load compaction store error ", e); return false; } } // @Override // public void start() { // compactionStore.load(); // super.start(); // } public void shutdown() { compactionStore.shutdown(); } public void updateMasterAddress(String addr) { compactionStore.updateMasterAddress(addr); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CleanupPolicy; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.CleanupPolicyUtils; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; public class CompactionStore { public static final String COMPACTION_DIR = "compaction"; public static final String COMPACTION_LOG_DIR = "compactionLog"; public static final String COMPACTION_CQ_DIR = "compactionCq"; private final String compactionPath; private final String compactionLogPath; private final String compactionCqPath; private final DefaultMessageStore defaultMessageStore; private final CompactionPositionMgr positionMgr; private final ConcurrentHashMap compactionLogTable; private final ScheduledExecutorService compactionSchedule; private final int scanInterval = 30000; private final int compactionInterval; private final int compactionThreadNum; private final int offsetMapSize; private String masterAddr; private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public CompactionStore(DefaultMessageStore defaultMessageStore) { this.defaultMessageStore = defaultMessageStore; this.compactionLogTable = new ConcurrentHashMap<>(); MessageStoreConfig config = defaultMessageStore.getMessageStoreConfig(); String storeRootPath = config.getStorePathRootDir(); this.compactionPath = Paths.get(storeRootPath, COMPACTION_DIR).toString(); this.compactionLogPath = Paths.get(compactionPath, COMPACTION_LOG_DIR).toString(); this.compactionCqPath = Paths.get(compactionPath, COMPACTION_CQ_DIR).toString(); this.positionMgr = new CompactionPositionMgr(compactionPath); this.compactionThreadNum = Math.min(Runtime.getRuntime().availableProcessors(), Math.max(1, config.getCompactionThreadNum())); this.compactionSchedule = ThreadUtils.newScheduledThreadPool(this.compactionThreadNum, new ThreadFactoryImpl("compactionSchedule_")); this.offsetMapSize = config.getMaxOffsetMapSize() / compactionThreadNum; this.compactionInterval = defaultMessageStore.getMessageStoreConfig().getCompactionScheduleInternal(); } public void load(boolean exitOk) throws Exception { File logRoot = new File(compactionLogPath); File[] fileTopicList = logRoot.listFiles(); if (fileTopicList != null) { for (File fileTopic : fileTopicList) { if (!fileTopic.isDirectory()) { continue; } File[] fileQueueIdList = fileTopic.listFiles(); if (fileQueueIdList != null) { for (File fileQueueId : fileQueueIdList) { if (!fileQueueId.isDirectory()) { continue; } try { String topic = fileTopic.getName(); int queueId = Integer.parseInt(fileQueueId.getName()); if (Files.isDirectory(Paths.get(compactionCqPath, topic, String.valueOf(queueId)))) { loadAndGetClog(topic, queueId); } else { log.error("{}:{} compactionLog mismatch with compactionCq", topic, queueId); } } catch (Exception e) { log.error("load compactionLog {}:{} exception: ", fileTopic.getName(), fileQueueId.getName(), e); throw new Exception("load compactionLog " + fileTopic.getName() + ":" + fileQueueId.getName() + " exception: " + e.getMessage()); } } } } } log.info("compactionStore {}:{} load completed.", compactionLogPath, compactionCqPath); compactionSchedule.scheduleWithFixedDelay(this::scanAllTopicConfig, scanInterval, scanInterval, TimeUnit.MILLISECONDS); log.info("loop to scan all topicConfig with fixed delay {}ms", scanInterval); } private void scanAllTopicConfig() { log.info("start to scan all topicConfig"); try { Iterator> iterator = defaultMessageStore.getTopicConfigs().entrySet().iterator(); while (iterator.hasNext()) { Map.Entry it = iterator.next(); TopicConfig topicConfig = it.getValue(); CleanupPolicy policy = CleanupPolicyUtils.getDeletePolicy(Optional.ofNullable(topicConfig)); //check topic flag if (Objects.equals(policy, CleanupPolicy.COMPACTION)) { for (int queueId = 0; queueId < topicConfig.getWriteQueueNums(); queueId++) { loadAndGetClog(it.getKey(), queueId); } } } } catch (Throwable ignore) { // ignore } log.info("scan all topicConfig over"); } private CompactionLog loadAndGetClog(String topic, int queueId) { CompactionLog clog = compactionLogTable.compute(topic + "_" + queueId, (k, v) -> { if (v == null) { try { v = new CompactionLog(defaultMessageStore, this, topic, queueId); v.load(true); int randomDelay = 1000 + new Random(System.currentTimeMillis()).nextInt(compactionInterval); compactionSchedule.scheduleWithFixedDelay(v::doCompaction, compactionInterval + randomDelay, compactionInterval + randomDelay, TimeUnit.MILLISECONDS); } catch (IOException e) { log.error("create compactionLog exception: ", e); return null; } } return v; }); return clog; } public void putMessage(String topic, int queueId, SelectMappedBufferResult smr) throws Exception { CompactionLog clog = loadAndGetClog(topic, queueId); if (clog != null) { clog.asyncPutMessage(smr); } } public void doDispatch(DispatchRequest dispatchRequest, SelectMappedBufferResult smr) throws Exception { CompactionLog clog = loadAndGetClog(dispatchRequest.getTopic(), dispatchRequest.getQueueId()); if (clog != null) { clog.asyncPutMessage(smr.getByteBuffer(), dispatchRequest); } } public GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset, final int maxMsgNums, final int maxTotalMsgSize) { CompactionLog log = compactionLogTable.get(topic + "_" + queueId); if (log == null) { return GetMessageResult.NO_MATCH_LOGIC_QUEUE; } else { return log.getMessage(group, topic, queueId, offset, maxMsgNums, maxTotalMsgSize); } } public void flush(int flushLeastPages) { compactionLogTable.values().forEach(log -> log.flush(flushLeastPages)); } public void flushLog(int flushLeastPages) { compactionLogTable.values().forEach(log -> log.flushLog(flushLeastPages)); } public void flushCQ(int flushLeastPages) { compactionLogTable.values().forEach(log -> log.flushCQ(flushLeastPages)); } public void updateMasterAddress(String addr) { this.masterAddr = addr; } public void shutdown() { // close the thread pool first compactionSchedule.shutdown(); try { if (!compactionSchedule.awaitTermination(1000, TimeUnit.MILLISECONDS)) { List droppedTasks = compactionSchedule.shutdownNow(); log.warn("compactionSchedule was abruptly shutdown. {} tasks will not be executed.", droppedTasks.size()); } } catch (InterruptedException e) { log.warn("wait compaction schedule shutdown interrupted. "); } this.flush(0); positionMgr.persist(); } public ScheduledExecutorService getCompactionSchedule() { return compactionSchedule; } public String getCompactionLogPath() { return compactionLogPath; } public String getCompactionCqPath() { return compactionCqPath; } public CompactionPositionMgr getPositionMgr() { return positionMgr; } public int getOffsetMapSize() { return offsetMapSize; } public String getMasterAddr() { return masterAddr; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/kv/MessageFetcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.io.IOException; import java.util.function.BiFunction; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.sysflag.PullSysFlag; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.exception.RemotingCommandException; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PullMessageResponseHeader; import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class MessageFetcher implements AutoCloseable { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final RemotingClient client; public MessageFetcher() { NettyClientConfig nettyClientConfig = new NettyClientConfig(); nettyClientConfig.setUseTLS(false); this.client = new NettyRemotingClient(nettyClientConfig); this.client.start(); } @Override public void close() throws IOException { this.client.shutdown(); } private PullMessageRequestHeader createPullMessageRequest(String topic, int queueId, long queueOffset, long subVersion) { int sysFlag = PullSysFlag.buildSysFlag(false, false, false, false, true); PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup(getConsumerGroup(topic, queueId)); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setQueueOffset(queueOffset); requestHeader.setMaxMsgNums(10); requestHeader.setSysFlag(sysFlag); requestHeader.setCommitOffset(0L); requestHeader.setSuspendTimeoutMillis(20_000L); // requestHeader.setSubscription(subExpression); requestHeader.setSubVersion(subVersion); requestHeader.setMaxMsgBytes(Integer.MAX_VALUE); // requestHeader.setExpressionType(expressionType); return requestHeader; } private String getConsumerGroup(String topic, int queueId) { return String.join("-", topic, String.valueOf(queueId), "pull", "group"); } private String getClientId() { return String.join("@", NetworkUtil.getLocalAddress(), "compactionIns", "compactionUnit"); } private boolean prepare(String masterAddr, String topic, String groupName, long subVersion) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { HeartbeatData heartbeatData = new HeartbeatData(); heartbeatData.setClientID(getClientId()); ConsumerData consumerData = new ConsumerData(); consumerData.setGroupName(groupName); consumerData.setConsumeType(ConsumeType.CONSUME_ACTIVELY); consumerData.setMessageModel(MessageModel.CLUSTERING); consumerData.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); // consumerData.setSubscriptionDataSet(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setSubString(SubscriptionData.SUB_ALL); subscriptionData.setSubVersion(subVersion); consumerData.setSubscriptionDataSet(Sets.newHashSet(subscriptionData)); heartbeatData.getConsumerDataSet().add(consumerData); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, new HeartbeatRequestHeader()); request.setLanguage(LanguageCode.JAVA); request.setBody(heartbeatData.encode()); RemotingCommand response = client.invokeSync(masterAddr, request, 1000 * 30L); if (response != null && response.getCode() == ResponseCode.SUCCESS) { return true; } return false; } private boolean pullDone(String masterAddr, String groupName) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException { UnregisterClientRequestHeader requestHeader = new UnregisterClientRequestHeader(); requestHeader.setClientID(getClientId()); requestHeader.setProducerGroup(""); requestHeader.setConsumerGroup(groupName); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, requestHeader); RemotingCommand response = client.invokeSync(masterAddr, request, 1000 * 30L); if (response != null && response.getCode() == ResponseCode.SUCCESS) { return true; } return false; } private boolean stopPull(long currPullOffset, long endOffset) { return currPullOffset >= endOffset && endOffset != -1; } public void pullMessageFromMaster(String topic, int queueId, long endOffset, String masterAddr, BiFunction responseHandler) throws Exception { long currentPullOffset = 0; try { long subVersion = System.currentTimeMillis(); String groupName = getConsumerGroup(topic, queueId); if (!prepare(masterAddr, topic, groupName, subVersion)) { log.error("{}:{} prepare to {} pull message failed", topic, queueId, masterAddr); throw new RemotingCommandException(topic + ":" + queueId + " prepare to " + masterAddr + " pull message failed"); } boolean noNewMsg = false; boolean keepPull = true; // PullMessageRequestHeader requestHeader = createPullMessageRequest(topic, queueId, subVersion, currentPullOffset); while (!stopPull(currentPullOffset, endOffset)) { // requestHeader.setQueueOffset(currentPullOffset); PullMessageRequestHeader requestHeader = createPullMessageRequest(topic, queueId, currentPullOffset, subVersion); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LITE_PULL_MESSAGE, requestHeader); RemotingCommand response = client.invokeSync(masterAddr, request, 1000 * 30L); PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class); if (responseHeader == null) { log.error("{}:{} pull message responseHeader is null", topic, queueId); throw new RemotingCommandException(topic + ":" + queueId + " pull message responseHeader is null"); } switch (response.getCode()) { case ResponseCode.SUCCESS: long curOffset = responseHeader.getNextBeginOffset() - 1; keepPull = responseHandler.apply(curOffset, response); currentPullOffset = responseHeader.getNextBeginOffset(); break; case ResponseCode.PULL_NOT_FOUND: // NO_NEW_MSG, need break loop log.info("PULL_NOT_FOUND, topic:{}, queueId:{}, pullOffset:{},", topic, queueId, currentPullOffset); noNewMsg = true; break; case ResponseCode.PULL_RETRY_IMMEDIATELY: log.info("PULL_RETRY_IMMEDIATE, topic:{}, queueId:{}, pullOffset:{},", topic, queueId, currentPullOffset); break; case ResponseCode.PULL_OFFSET_MOVED: log.info("PULL_OFFSET_MOVED, topic:{}, queueId:{}, pullOffset:{},", topic, queueId, currentPullOffset); break; default: log.warn("Pull Message error, response code: {}, remark: {}", response.getCode(), response.getRemark()); } if (noNewMsg || !keepPull) { break; } } pullDone(masterAddr, groupName); } finally { if (client != null) { client.closeChannels(Lists.newArrayList(masterAddr)); } } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/lock/AdaptiveBackOffSpinLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.lock; import org.apache.rocketmq.store.PutMessageLock; import org.apache.rocketmq.store.config.MessageStoreConfig; public interface AdaptiveBackOffSpinLock extends PutMessageLock { /** * Configuration update * @param messageStoreConfig */ default void update(MessageStoreConfig messageStoreConfig) { } /** * Locking mechanism switching */ default void swap() { } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/lock/AdaptiveBackOffSpinLockImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.lock; import org.apache.rocketmq.store.config.MessageStoreConfig; import java.time.LocalTime; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class AdaptiveBackOffSpinLockImpl implements AdaptiveBackOffSpinLock { private AdaptiveBackOffSpinLock adaptiveLock; //state private AtomicBoolean state = new AtomicBoolean(true); // Used to determine the switchover between a mutex lock and a spin lock private final static float SWAP_SPIN_LOCK_RATIO = 0.8f; // It is used to adjust the spin number K of the escape spin lock // When (retreat number / TPS) <= (1 / BASE_SWAP_ADAPTIVE_RATIO * SPIN_LOCK_ADAPTIVE_RATIO), K is decreased private final static int SPIN_LOCK_ADAPTIVE_RATIO = 4; // It is used to adjust the spin number K of the escape spin lock // When (retreat number / TPS) >= (1 / BASE_SWAP_ADAPTIVE_RATIO), K is increased private final static int BASE_SWAP_LOCK_RATIO = 320; private final static String BACK_OFF_SPIN_LOCK = "SpinLock"; private final static String REENTRANT_LOCK = "ReentrantLock"; private Map locks; private final List tpsTable; private final List> threadTable; private int swapCriticalPoint; private AtomicInteger currentThreadNum = new AtomicInteger(0); private AtomicBoolean isOpen = new AtomicBoolean(true); public AdaptiveBackOffSpinLockImpl() { this.locks = new HashMap<>(); this.locks.put(REENTRANT_LOCK, new BackOffReentrantLock()); this.locks.put(BACK_OFF_SPIN_LOCK, new BackOffSpinLock()); this.threadTable = new ArrayList<>(2); this.threadTable.add(ConcurrentHashMap.newKeySet()); this.threadTable.add(ConcurrentHashMap.newKeySet()); this.tpsTable = new ArrayList<>(2); this.tpsTable.add(new AtomicInteger(0)); this.tpsTable.add(new AtomicInteger(0)); adaptiveLock = this.locks.get(BACK_OFF_SPIN_LOCK); } @Override public void lock() { int slot = LocalTime.now().getSecond() % 2; this.threadTable.get(slot).add(Thread.currentThread()); this.tpsTable.get(slot).getAndIncrement(); boolean state; do { state = this.state.get(); } while (!state); currentThreadNum.incrementAndGet(); this.adaptiveLock.lock(); } @Override public void unlock() { this.adaptiveLock.unlock(); currentThreadNum.decrementAndGet(); if (isOpen.get()) { swap(); } } @Override public void update(MessageStoreConfig messageStoreConfig) { this.adaptiveLock.update(messageStoreConfig); } @Override public void swap() { if (!this.state.get()) { return; } boolean needSwap = false; int slot = 1 - LocalTime.now().getSecond() % 2; int tps = this.tpsTable.get(slot).get() + 1; int threadNum = this.threadTable.get(slot).size(); this.tpsTable.get(slot).set(-1); this.threadTable.get(slot).clear(); if (tps == 0) { return; } if (this.adaptiveLock instanceof BackOffSpinLock) { BackOffSpinLock lock = (BackOffSpinLock) this.adaptiveLock; // Avoid frequent adjustment of K, and make a reasonable range through experiments // reasonable range : (retreat number / TPS) > (1 / BASE_SWAP_ADAPTIVE_RATIO * SPIN_LOCK_ADAPTIVE_RATIO) && // (retreat number / TPS) < (1 / BASE_SWAP_ADAPTIVE_RATIO) if (lock.getNumberOfRetreat(slot) * BASE_SWAP_LOCK_RATIO >= tps) { if (lock.isAdapt()) { lock.adapt(true); } else { // It is used to switch between mutex lock and spin lock this.swapCriticalPoint = tps * threadNum; needSwap = true; } } else if (lock.getNumberOfRetreat(slot) * BASE_SWAP_LOCK_RATIO * SPIN_LOCK_ADAPTIVE_RATIO <= tps) { lock.adapt(false); } lock.setNumberOfRetreat(slot, 0); } else { if (tps * threadNum <= this.swapCriticalPoint * SWAP_SPIN_LOCK_RATIO) { needSwap = true; } } if (needSwap) { if (this.state.compareAndSet(true, false)) { // Ensures that no threads are in contention locks as well as in critical zones int currentThreadNum; do { currentThreadNum = this.currentThreadNum.get(); } while (currentThreadNum != 0); try { if (this.adaptiveLock instanceof BackOffSpinLock) { this.adaptiveLock = this.locks.get(REENTRANT_LOCK); } else { this.adaptiveLock = this.locks.get(BACK_OFF_SPIN_LOCK); ((BackOffSpinLock) this.adaptiveLock).adapt(false); } } catch (Exception e) { //ignore } finally { this.state.compareAndSet(false, true); } } } } public Collection getLocks() { return this.locks.values(); } public void setLocks(Map locks) { this.locks = locks; } public boolean getState() { return this.state.get(); } public void setState(boolean state) { this.state.set(state); } public AdaptiveBackOffSpinLock getAdaptiveLock() { return adaptiveLock; } public List getTpsTable() { return tpsTable; } public void setSwapCriticalPoint(int swapCriticalPoint) { this.swapCriticalPoint = swapCriticalPoint; } public int getSwapCriticalPoint() { return swapCriticalPoint; } public boolean isOpen() { return this.isOpen.get(); } public void setOpen(boolean open) { this.isOpen.set(open); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/lock/BackOffReentrantLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.lock; import java.util.concurrent.locks.ReentrantLock; public class BackOffReentrantLock implements AdaptiveBackOffSpinLock { private ReentrantLock putMessageNormalLock = new ReentrantLock(); // NonfairSync @Override public void lock() { putMessageNormalLock.lock(); } @Override public void unlock() { putMessageNormalLock.unlock(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/lock/BackOffSpinLock.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.lock; import org.apache.rocketmq.store.config.MessageStoreConfig; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class BackOffSpinLock implements AdaptiveBackOffSpinLock { private AtomicBoolean putMessageSpinLock = new AtomicBoolean(true); private int optimalDegree; private final static int INITIAL_DEGREE = 1000; private final static int MAX_OPTIMAL_DEGREE = 10000; private final List numberOfRetreat; public BackOffSpinLock() { this.optimalDegree = INITIAL_DEGREE; numberOfRetreat = new ArrayList<>(2); numberOfRetreat.add(new AtomicInteger(0)); numberOfRetreat.add(new AtomicInteger(0)); } @Override public void lock() { int spinDegree = this.optimalDegree; while (true) { for (int i = 0; i < spinDegree; i++) { if (this.putMessageSpinLock.compareAndSet(true, false)) { return; } } numberOfRetreat.get(LocalTime.now().getSecond() % 2).getAndIncrement(); try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void unlock() { this.putMessageSpinLock.compareAndSet(false, true); } @Override public void update(MessageStoreConfig messageStoreConfig) { this.optimalDegree = messageStoreConfig.getSpinLockCollisionRetreatOptimalDegree(); } public int getOptimalDegree() { return this.optimalDegree; } public void setOptimalDegree(int optimalDegree) { this.optimalDegree = optimalDegree; } public boolean isAdapt() { return optimalDegree < MAX_OPTIMAL_DEGREE; } public synchronized void adapt(boolean isRise) { if (isRise) { if (optimalDegree * 2 <= MAX_OPTIMAL_DEGREE) { optimalDegree *= 2; } else { if (optimalDegree + INITIAL_DEGREE <= MAX_OPTIMAL_DEGREE) { optimalDegree += INITIAL_DEGREE; } } } else { if (optimalDegree >= 2 * INITIAL_DEGREE) { optimalDegree -= INITIAL_DEGREE; } } } public int getNumberOfRetreat(int pos) { return numberOfRetreat.get(pos).get(); } public void setNumberOfRetreat(int pos, int size) { this.numberOfRetreat.get(pos).set(size); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/logfile/AbstractMappedFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import org.apache.rocketmq.store.ReferenceResource; public abstract class AbstractMappedFile extends ReferenceResource implements MappedFile { } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/logfile/DefaultMappedFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import io.netty.util.internal.PlatformDependent; import org.apache.commons.lang3.SystemUtils; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.AppendMessageCallback; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.CompactionAppendMsgCallback; import org.apache.rocketmq.store.PutMessageContext; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.TransientStorePool; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.util.LibC; import sun.misc.Unsafe; public class DefaultMappedFile extends AbstractMappedFile { public static final int OS_PAGE_SIZE = 1024 * 4; public static final Unsafe UNSAFE = getUnsafe(); private static final Method IS_LOADED_METHOD; public static final int UNSAFE_PAGE_SIZE = UNSAFE == null ? OS_PAGE_SIZE : UNSAFE.pageSize(); protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0); protected static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0); protected static final AtomicIntegerFieldUpdater WROTE_POSITION_UPDATER; protected static final AtomicIntegerFieldUpdater COMMITTED_POSITION_UPDATER; protected static final AtomicIntegerFieldUpdater FLUSHED_POSITION_UPDATER; protected volatile int wrotePosition; protected volatile int committedPosition; protected volatile int flushedPosition; protected int fileSize; protected FileChannel fileChannel; /** * Message will put to here first, and then reput to FileChannel if writeBuffer is not null. */ protected ByteBuffer writeBuffer = null; protected TransientStorePool transientStorePool = null; /** * Configuration flag to use RandomAccessFile instead of MappedByteBuffer for writing */ protected boolean writeWithoutMmap = false; protected String fileName; protected long fileFromOffset; protected File file; protected MappedByteBuffer mappedByteBuffer; protected volatile long storeTimestamp = 0; protected boolean firstCreateInQueue = false; private long lastFlushTime = -1L; protected MappedByteBuffer mappedByteBufferWaitToClean = null; protected long swapMapTime = 0L; protected long mappedByteBufferAccessCountSinceLastSwap = 0L; /** * If this mapped file belongs to consume queue, this field stores store-timestamp of first message referenced by * this logical queue. */ private long startTimestamp = -1; /** * If this mapped file belongs to consume queue, this field stores store-timestamp of last message referenced by * this logical queue. */ private long stopTimestamp = -1; protected RunningFlags runningFlags; static { WROTE_POSITION_UPDATER = AtomicIntegerFieldUpdater.newUpdater(DefaultMappedFile.class, "wrotePosition"); COMMITTED_POSITION_UPDATER = AtomicIntegerFieldUpdater.newUpdater(DefaultMappedFile.class, "committedPosition"); FLUSHED_POSITION_UPDATER = AtomicIntegerFieldUpdater.newUpdater(DefaultMappedFile.class, "flushedPosition"); Method isLoaded0method = null; // On the windows platform and openjdk 11 method isLoaded0 always returns false. // see https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/windows/native/libnio/MappedByteBuffer.c#L34 if (!SystemUtils.IS_OS_WINDOWS) { try { isLoaded0method = MappedByteBuffer.class.getDeclaredMethod("isLoaded0", long.class, long.class, int.class); isLoaded0method.setAccessible(true); } catch (NoSuchMethodException ignore) { } } IS_LOADED_METHOD = isLoaded0method; } public DefaultMappedFile() { } public DefaultMappedFile(final String fileName, final int fileSize) throws IOException { this(fileName, fileSize, null); } public DefaultMappedFile(final String fileName, final int fileSize, boolean writeWithoutMmap) throws IOException { this(fileName, fileSize, null, null, writeWithoutMmap); } public DefaultMappedFile(final String fileName, final int fileSize, RunningFlags runningFlags) throws IOException { this(fileName, fileSize, runningFlags, null, false); } public DefaultMappedFile(final String fileName, final int fileSize, final RunningFlags runningFlags, final TransientStorePool transientStorePool) throws IOException { this(fileName, fileSize, runningFlags, transientStorePool, false); } public DefaultMappedFile(final String fileName, final int fileSize, final RunningFlags runningFlags, final boolean writeWithoutMmap) throws IOException { this(fileName, fileSize, runningFlags, null, writeWithoutMmap); } public DefaultMappedFile(final String fileName, final int fileSize, final TransientStorePool transientStorePool, final boolean writeWithoutMmap) throws IOException { this(fileName, fileSize, null, transientStorePool, writeWithoutMmap); } public DefaultMappedFile(final String fileName, final int fileSize, final RunningFlags runningFlags, final TransientStorePool transientStorePool, final boolean writeWithoutMmap) throws IOException { this.writeWithoutMmap = writeWithoutMmap; init(fileName, fileSize, runningFlags, transientStorePool); } public static int getTotalMappedFiles() { return TOTAL_MAPPED_FILES.get(); } public static long getTotalMappedVirtualMemory() { return TOTAL_MAPPED_VIRTUAL_MEMORY.get(); } @Override public void init(final String fileName, final int fileSize, final RunningFlags runningFlags, final TransientStorePool transientStorePool) throws IOException { init(fileName, fileSize, runningFlags); if (transientStorePool != null) { this.writeBuffer = transientStorePool.borrowBuffer(); this.transientStorePool = transientStorePool; } } private void init(final String fileName, final int fileSize, final RunningFlags runningFlags) throws IOException { this.fileName = fileName; this.fileSize = fileSize; this.file = new File(fileName); this.fileFromOffset = Long.parseLong(this.file.getName()); this.runningFlags = runningFlags; boolean ok = false; UtilAll.ensureDirOK(this.file.getParent()); try { this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel(); if (writeWithoutMmap) { // Still create MappedByteBuffer for reading operations this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_ONLY, 0, fileSize); } else { // Use MappedByteBuffer for both reading and writing (default behavior) this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize); } TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize); TOTAL_MAPPED_FILES.incrementAndGet(); ok = true; } catch (FileNotFoundException e) { log.error("Failed to create file " + this.fileName, e); throw e; } catch (IOException e) { log.error("Failed to map file " + this.fileName, e); throw e; } finally { if (!ok && this.fileChannel != null) { this.fileChannel.close(); } } } @Override public boolean renameTo(String fileName) { File newFile = new File(fileName); boolean rename = file.renameTo(newFile); if (rename) { this.fileName = fileName; this.file = newFile; } return rename; } @Override public long getLastModifiedTimestamp() { return this.file.lastModified(); } public boolean getData(int pos, int size, ByteBuffer byteBuffer) { if (byteBuffer.remaining() < size) { return false; } int readPosition = getReadPosition(); if ((pos + size) <= readPosition) { if (this.hold()) { try { int readNum = fileChannel.read(byteBuffer, pos); return size == readNum; } catch (Throwable t) { log.warn("Get data failed pos:{} size:{} fileFromOffset:{}", pos, size, this.fileFromOffset); return false; } finally { this.release(); } } else { log.debug("matched, but hold failed, request pos: " + pos + ", fileFromOffset: " + this.fileFromOffset); } } else { log.warn("selectMappedBuffer request pos invalid, request pos: " + pos + ", size: " + size + ", fileFromOffset: " + this.fileFromOffset); } return false; } @Override public int getFileSize() { return fileSize; } @Override public FileChannel getFileChannel() { return fileChannel; } public AppendMessageResult appendMessage(final ByteBuffer byteBufferMsg, final CompactionAppendMsgCallback cb) { assert byteBufferMsg != null; assert cb != null; int currentPos = WROTE_POSITION_UPDATER.get(this); long fileFromOffset = this.getFileFromOffset(); if (currentPos < this.fileSize) { SharedByteBufferManager.SharedByteBuffer sharedByteBuffer = null; ByteBuffer byteBuffer; if (writeWithoutMmap) { sharedByteBuffer = SharedByteBufferManager.getInstance().borrowSharedByteBuffer(); byteBuffer = sharedByteBuffer.acquire(); byteBuffer.position(0).limit(byteBuffer.capacity()); fileFromOffset += currentPos; } else { byteBuffer = appendMessageBuffer().slice(); byteBuffer.position(currentPos); } try { AppendMessageResult result = cb.doAppend(byteBuffer, fileFromOffset, this.fileSize - currentPos, byteBufferMsg); if (sharedByteBuffer != null) { try { this.fileChannel.position(currentPos); byteBuffer.position(0).limit(result.getWroteBytes()); this.fileChannel.write(byteBuffer); } catch (Throwable t) { log.error("Failed to write to mappedFile {}", this.fileName, t); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } } WROTE_POSITION_UPDATER.addAndGet(this, result.getWroteBytes()); this.storeTimestamp = result.getStoreTimestamp(); return result; } finally { if (sharedByteBuffer != null) { sharedByteBuffer.release(); } } } log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } @Override public AppendMessageResult appendMessage(final MessageExtBrokerInner msg, final AppendMessageCallback cb, PutMessageContext putMessageContext) { return appendMessagesInner(msg, cb, putMessageContext); } @Override public AppendMessageResult appendMessages(final MessageExtBatch messageExtBatch, final AppendMessageCallback cb, PutMessageContext putMessageContext) { return appendMessagesInner(messageExtBatch, cb, putMessageContext); } public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb, PutMessageContext putMessageContext) { assert messageExt != null; assert cb != null; int currentPos = WROTE_POSITION_UPDATER.get(this); long fileFromOffset = this.getFileFromOffset(); if (currentPos < this.fileSize) { SharedByteBufferManager.SharedByteBuffer sharedByteBuffer = null; ByteBuffer byteBuffer; if (writeWithoutMmap) { sharedByteBuffer = SharedByteBufferManager.getInstance().borrowSharedByteBuffer(); byteBuffer = sharedByteBuffer.acquire(); byteBuffer.position(0).limit(byteBuffer.capacity()); fileFromOffset += currentPos; } else { byteBuffer = appendMessageBuffer().slice(); byteBuffer.position(currentPos); } AppendMessageResult result; try { if (messageExt instanceof MessageExtBatch && !((MessageExtBatch) messageExt).isInnerBatch()) { // traditional batch message result = cb.doAppend(fileFromOffset, byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt, putMessageContext); } else if (messageExt instanceof MessageExtBrokerInner) { // traditional single message or newly introduced inner-batch message result = cb.doAppend(fileFromOffset, byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt, putMessageContext); } else { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } if (sharedByteBuffer != null) { try { int msgLen = result.getWroteBytes(); int endpos = currentPos + msgLen; // alignment end position int extraAppendSize = UNSAFE_PAGE_SIZE - endpos % UNSAFE_PAGE_SIZE; if (extraAppendSize == UNSAFE_PAGE_SIZE) { extraAppendSize = 0; } int actualAppendSize = msgLen + extraAppendSize; this.fileChannel.position(currentPos); // commitlog can contain dirty data at the end. if (byteBuffer.capacity() >= actualAppendSize) { byteBuffer.position(0).limit(actualAppendSize); } else { byteBuffer.position(0).limit(msgLen); } this.fileChannel.write(byteBuffer); } catch (Throwable t) { log.error("Failed to write to mappedFile {}", this.fileName, t); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } } } finally { if (sharedByteBuffer != null) { sharedByteBuffer.release(); } } WROTE_POSITION_UPDATER.addAndGet(this, result.getWroteBytes()); this.storeTimestamp = result.getStoreTimestamp(); return result; } log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } protected ByteBuffer appendMessageBuffer() { this.mappedByteBufferAccessCountSinceLastSwap++; return writeBuffer != null ? writeBuffer : this.mappedByteBuffer; } @Override public long getFileFromOffset() { return this.fileFromOffset; } @Override public boolean appendMessage(final byte[] data) { return appendMessage(data, 0, data.length); } @Override public boolean appendMessage(ByteBuffer data) { int currentPos = WROTE_POSITION_UPDATER.get(this); int remaining = data.remaining(); if ((currentPos + remaining) <= this.fileSize) { try { if (writeWithoutMmap) { // Use FileChannel for writing this.fileChannel.position(currentPos); byte[] buffer = new byte[remaining]; data.get(buffer); ByteBuffer writeBuffer = ByteBuffer.wrap(buffer); this.fileChannel.write(writeBuffer); } else { // Use FileChannel for writing (default behavior) this.fileChannel.position(currentPos); while (data.hasRemaining()) { this.fileChannel.write(data); } } WROTE_POSITION_UPDATER.addAndGet(this, remaining); return true; } catch (Throwable e) { log.error("Error occurred when append message to mappedFile.", e); return false; } } return false; } /** * Content of data from offset to offset + length will be written to file. * * @param offset The offset of the subarray to be used. * @param length The length of the subarray to be used. */ @Override public boolean appendMessage(final byte[] data, final int offset, final int length) { int currentPos = WROTE_POSITION_UPDATER.get(this); if ((currentPos + length) <= this.fileSize) { try { if (writeWithoutMmap) { // Use FileChannel for writing this.fileChannel.position(currentPos); ByteBuffer writeBuffer = ByteBuffer.wrap(data, offset, length); this.fileChannel.write(writeBuffer); } else { // Use MappedByteBuffer for writing (default behavior) ByteBuffer buf = this.mappedByteBuffer.slice(); buf.position(currentPos); buf.put(data, offset, length); } WROTE_POSITION_UPDATER.addAndGet(this, length); return true; } catch (Throwable e) { log.error("Error occurred when append message to mappedFile.", e); return false; } } return false; } @Override public boolean appendMessageUsingFileChannel(byte[] data) { int currentPos = WROTE_POSITION_UPDATER.get(this); if ((currentPos + data.length) <= this.fileSize) { try { this.fileChannel.position(currentPos); this.fileChannel.write(ByteBuffer.wrap(data, 0, data.length)); WROTE_POSITION_UPDATER.addAndGet(this, data.length); return true; } catch (Throwable e) { log.error("Error occurred when append message to mappedFile.", e); return false; } } return false; } /** * @return The current flushed position */ @Override public int flush(final int flushLeastPages) { if (!isWriteable()) { return this.getFlushedPosition(); } if (this.isAbleToFlush(flushLeastPages)) { if (this.hold()) { int value = getReadPosition(); try { this.mappedByteBufferAccessCountSinceLastSwap++; //We only append data to fileChannel or mappedByteBuffer, never both. if (writeWithoutMmap || writeBuffer != null || this.fileChannel.position() != 0) { this.fileChannel.force(false); } else { this.mappedByteBuffer.force(); } this.lastFlushTime = System.currentTimeMillis(); FLUSHED_POSITION_UPDATER.set(this, value); } catch (Throwable e) { if (e instanceof IOException) { getAndMakeNotWriteable(); } log.error("Error occurred when force data to disk.", e); } this.release(); } else { log.warn("in flush, hold failed, flush offset = " + FLUSHED_POSITION_UPDATER.get(this)); FLUSHED_POSITION_UPDATER.set(this, getReadPosition()); } } return this.getFlushedPosition(); } @Override public int commit(final int commitLeastPages) { if (writeBuffer == null) { //no need to commit data to file channel, so just regard wrotePosition as committedPosition. return WROTE_POSITION_UPDATER.get(this); } //no need to commit data to file channel, so just set committedPosition to wrotePosition. if (transientStorePool != null && !transientStorePool.isRealCommit()) { COMMITTED_POSITION_UPDATER.set(this, WROTE_POSITION_UPDATER.get(this)); } else if (this.isAbleToCommit(commitLeastPages)) { if (this.hold()) { commit0(); this.release(); } else { log.warn("in commit, hold failed, commit offset = " + COMMITTED_POSITION_UPDATER.get(this)); } } // All dirty data has been committed to FileChannel. if (writeBuffer != null && this.transientStorePool != null && this.fileSize == COMMITTED_POSITION_UPDATER.get(this)) { this.transientStorePool.returnBuffer(writeBuffer); this.writeBuffer = null; } return COMMITTED_POSITION_UPDATER.get(this); } protected void commit0() { int writePos = WROTE_POSITION_UPDATER.get(this); int lastCommittedPosition = COMMITTED_POSITION_UPDATER.get(this); if (writePos - lastCommittedPosition > 0) { try { ByteBuffer byteBuffer = writeBuffer.slice(); byteBuffer.position(lastCommittedPosition); byteBuffer.limit(writePos); this.fileChannel.position(lastCommittedPosition); this.fileChannel.write(byteBuffer); COMMITTED_POSITION_UPDATER.set(this, writePos); } catch (Throwable e) { log.error("Error occurred when commit data to FileChannel.", e); } } } public boolean getAndMakeNotWriteable() { if (runningFlags == null) { return false; } return runningFlags.getAndMakeStoreNotWriteable(); } public boolean isWriteable() { if (runningFlags == null) { return true; } return runningFlags.isWriteable(); } private boolean isAbleToFlush(final int flushLeastPages) { int flush = FLUSHED_POSITION_UPDATER.get(this); int write = getReadPosition(); if (this.isFull()) { return true; } if (flushLeastPages > 0) { return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages; } return write > flush; } protected boolean isAbleToCommit(final int commitLeastPages) { int commit = COMMITTED_POSITION_UPDATER.get(this); int write = WROTE_POSITION_UPDATER.get(this); if (this.isFull()) { return true; } if (commitLeastPages > 0) { return ((write / OS_PAGE_SIZE) - (commit / OS_PAGE_SIZE)) >= commitLeastPages; } return write > commit; } @Override public int getFlushedPosition() { return FLUSHED_POSITION_UPDATER.get(this); } @Override public void setFlushedPosition(int pos) { FLUSHED_POSITION_UPDATER.set(this, pos); } @Override public boolean isFull() { return this.fileSize == WROTE_POSITION_UPDATER.get(this); } @Override public SelectMappedBufferResult selectMappedBuffer(int pos, int size) { int readPosition = getReadPosition(); if ((pos + size) <= readPosition) { if (this.hold()) { this.mappedByteBufferAccessCountSinceLastSwap++; ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } else { log.warn("matched, but hold failed, request pos: " + pos + ", fileFromOffset: " + this.fileFromOffset); } } else { log.warn("selectMappedBuffer request pos invalid, request pos: " + pos + ", size: " + size + ", fileFromOffset: " + this.fileFromOffset); } return null; } @Override public SelectMappedBufferResult selectMappedBuffer(int pos) { int readPosition = getReadPosition(); if (pos < readPosition && pos >= 0) { if (this.hold()) { this.mappedByteBufferAccessCountSinceLastSwap++; ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); int size = readPosition - pos; ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } } return null; } @Override public boolean cleanup(final long currentRef) { if (this.isAvailable()) { log.error("this file[REF:" + currentRef + "] " + this.fileName + " have not shutdown, stop unmapping."); return false; } if (this.isCleanupOver()) { log.error("this file[REF:" + currentRef + "] " + this.fileName + " have cleanup, do not do it again."); return true; } cleanResources(); log.info("unmap file[REF:" + currentRef + "] " + this.fileName + " OK"); return true; } @Override public void cleanResources() { UtilAll.cleanBuffer(this.mappedByteBuffer); UtilAll.cleanBuffer(this.mappedByteBufferWaitToClean); this.mappedByteBufferWaitToClean = null; TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(this.fileSize * (-1)); TOTAL_MAPPED_FILES.decrementAndGet(); try { fileChannel.close(); } catch (Throwable e) { log.warn("close file channel {" + this.fileName + "} failed when cleanup", e); } } @Override public boolean destroy(final long intervalForcibly) { this.shutdown(intervalForcibly); if (this.isCleanupOver()) { try { long lastModified = getLastModifiedTimestamp(); this.fileChannel.close(); log.info("close file channel " + this.fileName + " OK"); long beginTime = System.currentTimeMillis(); boolean result = this.file.delete(); log.info("delete file[REF:" + this.getRefCount() + "] " + this.fileName + (result ? " OK, " : " Failed, ") + "W:" + this.getWrotePosition() + " M:" + this.getFlushedPosition() + ", " + UtilAll.computeElapsedTimeMilliseconds(beginTime) + "," + (System.currentTimeMillis() - lastModified)); } catch (Exception e) { log.warn("close file channel " + this.fileName + " Failed. ", e); } return true; } else { log.warn("destroy mapped file[REF:" + this.getRefCount() + "] " + this.fileName + " Failed. cleanupOver: " + this.cleanupOver); } return false; } @Override public int getWrotePosition() { return WROTE_POSITION_UPDATER.get(this); } @Override public void setWrotePosition(int pos) { WROTE_POSITION_UPDATER.set(this, pos); } /** * @return The max position which have valid data */ @Override public int getReadPosition() { return transientStorePool == null || !transientStorePool.isRealCommit() ? WROTE_POSITION_UPDATER.get(this) : COMMITTED_POSITION_UPDATER.get(this); } @Override public void setCommittedPosition(int pos) { COMMITTED_POSITION_UPDATER.set(this, pos); } @Override public void warmMappedFile(FlushDiskType type, int pages) { this.mappedByteBufferAccessCountSinceLastSwap++; long beginTime = System.currentTimeMillis(); ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); long flush = 0; // long time = System.currentTimeMillis(); for (long i = 0, j = 0; i < this.fileSize; i += DefaultMappedFile.OS_PAGE_SIZE, j++) { byteBuffer.put((int) i, (byte) 0); // force flush when flush disk type is sync if (type == FlushDiskType.SYNC_FLUSH) { if ((i / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE) >= pages) { flush = i; mappedByteBuffer.force(); } } // prevent gc // if (j % 1000 == 0) { // log.info("j={}, costTime={}", j, System.currentTimeMillis() - time); // time = System.currentTimeMillis(); // try { // Thread.sleep(0); // } catch (InterruptedException e) { // log.error("Interrupted", e); // } // } } // force flush when prepare load finished if (type == FlushDiskType.SYNC_FLUSH) { log.info("mapped file warm-up done, force to disk, mappedFile={}, costTime={}", this.getFileName(), System.currentTimeMillis() - beginTime); mappedByteBuffer.force(); } log.info("mapped file warm-up done. mappedFile={}, costTime={}", this.getFileName(), System.currentTimeMillis() - beginTime); this.mlock(); } @Override public boolean swapMap() { if (getRefCount() == 1 && this.mappedByteBufferWaitToClean == null) { if (!hold()) { log.warn("in swapMap, hold failed, fileName: " + this.fileName); return false; } try { this.mappedByteBufferWaitToClean = this.mappedByteBuffer; this.mappedByteBuffer = this.fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize); this.mappedByteBufferAccessCountSinceLastSwap = 0L; this.swapMapTime = System.currentTimeMillis(); log.info("swap file " + this.fileName + " success."); return true; } catch (Exception e) { log.error("swapMap file " + this.fileName + " Failed. ", e); } finally { this.release(); } } else { log.info("Will not swap file: " + this.fileName + ", ref=" + getRefCount()); } return false; } @Override public void cleanSwapedMap(boolean force) { try { if (this.mappedByteBufferWaitToClean == null) { return; } long minGapTime = 120 * 1000L; long gapTime = System.currentTimeMillis() - this.swapMapTime; if (!force && gapTime < minGapTime) { Thread.sleep(minGapTime - gapTime); } UtilAll.cleanBuffer(this.mappedByteBufferWaitToClean); mappedByteBufferWaitToClean = null; log.info("cleanSwapedMap file " + this.fileName + " success."); } catch (Exception e) { log.error("cleanSwapedMap file " + this.fileName + " Failed. ", e); } } @Override public long getRecentSwapMapTime() { return 0; } @Override public long getMappedByteBufferAccessCountSinceLastSwap() { return this.mappedByteBufferAccessCountSinceLastSwap; } @Override public long getLastFlushTime() { return this.lastFlushTime; } @Override public String getFileName() { return fileName; } @Override public MappedByteBuffer getMappedByteBuffer() { this.mappedByteBufferAccessCountSinceLastSwap++; return mappedByteBuffer; } @Override public ByteBuffer sliceByteBuffer() { this.mappedByteBufferAccessCountSinceLastSwap++; return this.mappedByteBuffer.slice(); } @Override public long getStoreTimestamp() { return storeTimestamp; } @Override public boolean isFirstCreateInQueue() { return firstCreateInQueue; } @Override public void setFirstCreateInQueue(boolean firstCreateInQueue) { this.firstCreateInQueue = firstCreateInQueue; } @Override public void mlock() { final long beginTime = System.currentTimeMillis(); final long address = PlatformDependent.directBufferAddress(this.mappedByteBuffer); Pointer pointer = new Pointer(address); { int ret = LibC.INSTANCE.mlock(pointer, new NativeLong(this.fileSize)); log.info("mlock {} {} {} ret = {} time consuming = {}", address, this.fileName, this.fileSize, ret, System.currentTimeMillis() - beginTime); } { int ret = LibC.INSTANCE.madvise(pointer, new NativeLong(this.fileSize), LibC.MADV_WILLNEED); log.info("madvise {} {} {} ret = {} time consuming = {}", address, this.fileName, this.fileSize, ret, System.currentTimeMillis() - beginTime); } } @Override public void munlock() { final long beginTime = System.currentTimeMillis(); final long address = PlatformDependent.directBufferAddress(this.mappedByteBuffer); Pointer pointer = new Pointer(address); int ret = LibC.INSTANCE.munlock(pointer, new NativeLong(this.fileSize)); log.info("munlock {} {} {} ret = {} time consuming = {}", address, this.fileName, this.fileSize, ret, System.currentTimeMillis() - beginTime); } @Override public File getFile() { return this.file; } @Override public void renameToDelete() { //use Files.move if (!fileName.endsWith(".delete")) { String newFileName = this.fileName + ".delete"; try { Path newFilePath = Paths.get(newFileName); // https://bugs.openjdk.org/browse/JDK-4724038 // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4715154 // Windows can't move the file when mmapped. if (NetworkUtil.isWindowsPlatform() && mappedByteBuffer != null) { long position = this.fileChannel.position(); UtilAll.cleanBuffer(this.mappedByteBuffer); this.fileChannel.close(); Files.move(Paths.get(fileName), newFilePath, StandardCopyOption.ATOMIC_MOVE); try (RandomAccessFile file = new RandomAccessFile(newFileName, "rw")) { this.fileChannel = file.getChannel(); this.fileChannel.position(position); this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize); } } else { Files.move(Paths.get(fileName), newFilePath, StandardCopyOption.ATOMIC_MOVE); } this.fileName = newFileName; this.file = new File(newFileName); } catch (IOException e) { log.error("move file {} failed", fileName, e); } } } @Override public void moveToParent() throws IOException { Path currentPath = Paths.get(fileName); String baseName = currentPath.getFileName().toString(); Path parentPath = currentPath.getParent().getParent().resolve(baseName); // https://bugs.openjdk.org/browse/JDK-4724038 // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4715154 // Windows can't move the file when mmapped. if (NetworkUtil.isWindowsPlatform() && mappedByteBuffer != null) { long position = this.fileChannel.position(); UtilAll.cleanBuffer(this.mappedByteBuffer); this.fileChannel.close(); Files.move(Paths.get(fileName), parentPath, StandardCopyOption.ATOMIC_MOVE); try (RandomAccessFile file = new RandomAccessFile(parentPath.toFile(), "rw")) { this.fileChannel = file.getChannel(); this.fileChannel.position(position); this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize); } } else { Files.move(Paths.get(fileName), parentPath, StandardCopyOption.ATOMIC_MOVE); } this.file = parentPath.toFile(); this.fileName = parentPath.toString(); } @Override public String toString() { return this.fileName; } public long getStartTimestamp() { return startTimestamp; } public void setStartTimestamp(long startTimestamp) { this.startTimestamp = startTimestamp; } public long getStopTimestamp() { return stopTimestamp; } public void setStopTimestamp(long stopTimestamp) { this.stopTimestamp = stopTimestamp; } public Iterator iterator(int startPos) { return new Itr(startPos); } public static Unsafe getUnsafe() { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return (Unsafe) f.get(null); } catch (Exception ignore) { } return null; } public static long mappingAddr(long addr) { long offset = addr % UNSAFE_PAGE_SIZE; offset = (offset >= 0) ? offset : (UNSAFE_PAGE_SIZE + offset); return addr - offset; } public static int pageCount(long size) { return (int) (size + (long) UNSAFE_PAGE_SIZE - 1L) / UNSAFE_PAGE_SIZE; } @Override public boolean isLoaded(long position, int size) { if (IS_LOADED_METHOD == null) { return true; } try { long addr = PlatformDependent.directBufferAddress(mappedByteBuffer) + position; return (boolean) IS_LOADED_METHOD.invoke(mappedByteBuffer, mappingAddr(addr), size, pageCount(size)); } catch (Exception e) { log.info("invoke isLoaded0 of file {} error:", file.getAbsolutePath(), e); } return true; } private class Itr implements Iterator { private int start; private int current; private ByteBuffer buf; public Itr(int pos) { this.start = pos; this.current = pos; this.buf = mappedByteBuffer.slice(); this.buf.position(start); } @Override public boolean hasNext() { return current < getReadPosition(); } @Override public SelectMappedBufferResult next() { int readPosition = getReadPosition(); if (current < readPosition && current >= 0) { if (hold()) { ByteBuffer byteBuffer = buf.slice(); byteBuffer.position(current); int size = byteBuffer.getInt(current); ByteBuffer bufferResult = byteBuffer.slice(); bufferResult.limit(size); current += size; return new SelectMappedBufferResult(fileFromOffset + current, bufferResult, size, DefaultMappedFile.this); } } return null; } @Override public void forEachRemaining(Consumer action) { Iterator.super.forEachRemaining(action); } @Override public void remove() { throw new UnsupportedOperationException(); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/logfile/MappedFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.Iterator; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.AppendMessageCallback; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.CompactionAppendMsgCallback; import org.apache.rocketmq.store.PutMessageContext; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.TransientStorePool; import org.apache.rocketmq.store.config.FlushDiskType; public interface MappedFile { /** * Returns the file name of the {@code MappedFile}. * * @return the file name */ String getFileName(); /** * Change the file name of the {@code MappedFile}. * * @param fileName the new file name */ boolean renameTo(String fileName); /** * Returns the file size of the {@code MappedFile}. * * @return the file size */ int getFileSize(); /** * Returns the {@code FileChannel} behind the {@code MappedFile}. * * @return the file channel */ FileChannel getFileChannel(); /** * Returns true if this {@code MappedFile} is full and no new messages can be added. * * @return true if the file is full */ boolean isFull(); /** * Returns true if this {@code MappedFile} is available. *

    * The mapped file will be not available if it's shutdown or destroyed. * * @return true if the file is available */ boolean isAvailable(); /** * Appends a message object to the current {@code MappedFile} with a specific call back. * * @param message a message to append * @param messageCallback the specific call back to execute the real append action * @param putMessageContext * @return the append result */ AppendMessageResult appendMessage(MessageExtBrokerInner message, AppendMessageCallback messageCallback, PutMessageContext putMessageContext); /** * Appends a batch message object to the current {@code MappedFile} with a specific call back. * * @param message a message to append * @param messageCallback the specific call back to execute the real append action * @param putMessageContext * @return the append result */ AppendMessageResult appendMessages(MessageExtBatch message, AppendMessageCallback messageCallback, PutMessageContext putMessageContext); AppendMessageResult appendMessage(final ByteBuffer byteBufferMsg, final CompactionAppendMsgCallback cb); /** * Appends a raw message data represents by a byte array to the current {@code MappedFile}. * Using mappedByteBuffer * * @param data the byte array to append * @return true if success; false otherwise. */ boolean appendMessage(byte[] data); /** * Appends a raw message data represents by a byte array to the current {@code MappedFile}. * Using fileChannel * * @param data the byte array to append * @return true if success; false otherwise. */ boolean appendMessageUsingFileChannel(byte[] data); /** * Appends a raw message data represents by a byte array to the current {@code MappedFile}. * * @param data the byte buffer to append * @return true if success; false otherwise. */ boolean appendMessage(ByteBuffer data); /** * Appends a raw message data represents by a byte array to the current {@code MappedFile}, * starting at the given offset in the array. * * @param data the byte array to append * @param offset the offset within the array of the first byte to be read * @param length the number of bytes to be read from the given array * @return true if success; false otherwise. */ boolean appendMessage(byte[] data, int offset, int length); /** * Returns the global offset of the current {code MappedFile}, it's a long value of the file name. * * @return the offset of this file */ long getFileFromOffset(); /** * Flushes the data in cache to disk immediately. * * @param flushLeastPages the least pages to flush * @return the flushed position after the method call */ int flush(int flushLeastPages); /** * Flushes the data in the secondary cache to page cache or disk immediately. * * @param commitLeastPages the least pages to commit * @return the committed position after the method call */ int commit(int commitLeastPages); /** * Selects a slice of the mapped byte buffer's sub-region behind the mapped file, * starting at the given position. * * @param pos the given position * @param size the size of the returned sub-region * @return a {@code SelectMappedBufferResult} instance contains the selected slice */ SelectMappedBufferResult selectMappedBuffer(int pos, int size); /** * Selects a slice of the mapped byte buffer's sub-region behind the mapped file, * starting at the given position. * * @param pos the given position * @return a {@code SelectMappedBufferResult} instance contains the selected slice */ SelectMappedBufferResult selectMappedBuffer(int pos); /** * Returns the mapped byte buffer behind the mapped file. * * @return the mapped byte buffer */ MappedByteBuffer getMappedByteBuffer(); /** * Returns a slice of the mapped byte buffer behind the mapped file. * * @return the slice of the mapped byte buffer */ ByteBuffer sliceByteBuffer(); /** * Returns the store timestamp of the last message. * * @return the store timestamp */ long getStoreTimestamp(); /** * Returns the last modified timestamp of the file. * * @return the last modified timestamp */ long getLastModifiedTimestamp(); /** * Get data from a certain pos offset with size byte * * @param pos a certain pos offset to get data * @param size the size of data * @param byteBuffer the data * @return true if with data; false if no data; */ boolean getData(int pos, int size, ByteBuffer byteBuffer); /** * Destroys the file and delete it from the file system. * * @param intervalForcibly The time interval in milliseconds after which any remaining references will be forcibly released during destroy * @return true if success; false otherwise. */ boolean destroy(long intervalForcibly); /** * Shutdowns the file and mark it unavailable. * * @param intervalForcibly The time interval in milliseconds after which any remaining references will be forcibly released during shutdown */ void shutdown(long intervalForcibly); /** * Decreases the reference count by {@code 1} and clean up the mapped file if the reference count reaches at * {@code 0}. */ void release(); /** * Increases the reference count by {@code 1}. * * @return true if success; false otherwise. */ boolean hold(); /** * Returns true if the current file is first mapped file of some consume queue. * * @return true or false */ boolean isFirstCreateInQueue(); /** * Sets the flag whether the current file is first mapped file of some consume queue. * * @param firstCreateInQueue true or false */ void setFirstCreateInQueue(boolean firstCreateInQueue); /** * Returns the flushed position of this mapped file. * * @return the flushed posotion */ int getFlushedPosition(); /** * Sets the flushed position of this mapped file. * * @param flushedPosition the specific flushed position */ void setFlushedPosition(int flushedPosition); /** * Returns the wrote position of this mapped file. * * @return the wrote position */ int getWrotePosition(); /** * Sets the wrote position of this mapped file. * * @param wrotePosition the specific wrote position */ void setWrotePosition(int wrotePosition); /** * Returns the current max readable position of this mapped file. * * @return the max readable position */ int getReadPosition(); /** * Sets the committed position of this mapped file. * * @param committedPosition the specific committed position */ void setCommittedPosition(int committedPosition); /** * Lock the mapped bytebuffer */ void mlock(); /** * Unlock the mapped bytebuffer */ void munlock(); /** * Warm up the mapped bytebuffer * @param type * @param pages */ void warmMappedFile(FlushDiskType type, int pages); /** * Swap map */ boolean swapMap(); /** * Clean pageTable */ void cleanSwapedMap(boolean force); void cleanResources(); /** * Get recent swap map time */ long getRecentSwapMapTime(); /** * Get recent MappedByteBuffer access count since last swap */ long getMappedByteBufferAccessCountSinceLastSwap(); /** * Get the underlying file * @return */ File getFile(); /** * rename file to add ".delete" suffix */ void renameToDelete(); /** * move the file to the parent directory * @throws IOException */ void moveToParent() throws IOException; /** * Get the last flush time * @return */ long getLastFlushTime(); /** * Init mapped file * @param fileName file name * @param fileSize file size * @param transientStorePool transient store pool * @throws IOException */ void init(String fileName, int fileSize, RunningFlags runningFlags, TransientStorePool transientStorePool) throws IOException; Iterator iterator(int pos); /** * Check mapped file is loaded to memory with given position and size * @param position start offset of data * @param size data size * @return data is resided in memory or not */ boolean isLoaded(long position, int size); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/logfile/SharedByteBufferManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import java.nio.ByteBuffer; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.ReentrantLock; /** * Shared byte buffer manager for managing some shared ByteBuffers Buffer size is set based on MessageStoreConfig's * maxMessageSize */ public class SharedByteBufferManager { private static volatile SharedByteBufferManager instance; private static final Object LOCK = new Object(); private SharedByteBuffer[] sharedByteBuffers; private int bufferSize; private int maxSharedNum; private volatile boolean initialized = false; private SharedByteBufferManager() { // Private constructor } /** * Get singleton instance */ public static SharedByteBufferManager getInstance() { if (instance == null) { synchronized (LOCK) { if (instance == null) { instance = new SharedByteBufferManager(); } } } return instance; } /** * Initialize shared buffers with specified messageSize size and shared buffer number * * @param maxMessageSize max messageSize size * @param sharedBufferNum number of shared buffers */ public synchronized void init(int maxMessageSize, int sharedBufferNum) { if (!initialized) { //Reserve 64kb for encoding buffer outside body bufferSize = Integer.MAX_VALUE - maxMessageSize >= 64 * 1024 ? maxMessageSize + 64 * 1024 : Integer.MAX_VALUE; this.maxSharedNum = sharedBufferNum; this.sharedByteBuffers = new SharedByteBuffer[maxSharedNum]; for (int i = 0; i < maxSharedNum; i++) { this.sharedByteBuffers[i] = new SharedByteBuffer(bufferSize); } this.initialized = true; } } /** * Borrow a shared buffer * * @return Shared buffer */ public SharedByteBuffer borrowSharedByteBuffer() { if (!initialized) { throw new IllegalStateException("SharedByteBufferManager not initialized"); } int idx = ThreadLocalRandom.current().nextInt(maxSharedNum); return sharedByteBuffers[idx]; } /** * Get current buffer size * * @return Buffer size */ public int getBufferSize() { return bufferSize; } /** * Check if initialized * * @return Whether initialized */ public boolean isInitialized() { return initialized; } /** * Shared byte buffer class */ public static class SharedByteBuffer { private final ReentrantLock lock; private final ByteBuffer buffer; public SharedByteBuffer(int size) { this.lock = new ReentrantLock(); this.buffer = ByteBuffer.allocateDirect(size); } public void release() { this.lock.unlock(); } public ByteBuffer acquire() { this.lock.lock(); return buffer; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsConstant.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.metrics; public class DefaultStoreMetricsConstant { public static final String GAUGE_STORAGE_SIZE = "rocketmq_storage_size"; public static final String GAUGE_STORAGE_FLUSH_BEHIND = "rocketmq_storage_flush_behind_bytes"; public static final String GAUGE_STORAGE_DISPATCH_BEHIND = "rocketmq_storage_dispatch_behind_bytes"; public static final String GAUGE_STORAGE_MESSAGE_RESERVE_TIME = "rocketmq_storage_message_reserve_time"; public static final String GAUGE_TIMER_ENQUEUE_LAG = "rocketmq_timer_enqueue_lag"; public static final String GAUGE_TIMER_ENQUEUE_LATENCY = "rocketmq_timer_enqueue_latency"; public static final String GAUGE_TIMER_DEQUEUE_LAG = "rocketmq_timer_dequeue_lag"; public static final String GAUGE_TIMER_DEQUEUE_LATENCY = "rocketmq_timer_dequeue_latency"; public static final String GAUGE_TIMING_MESSAGES = "rocketmq_timing_messages"; public static final String COUNTER_TIMER_ENQUEUE_TOTAL = "rocketmq_timer_enqueue_total"; public static final String COUNTER_TIMER_DEQUEUE_TOTAL = "rocketmq_timer_dequeue_total"; public static final String GAUGE_TIMER_MESSAGE_SNAPSHOT = "rocketmq_timer_message_snapshot"; public static final String HISTOGRAM_DELAY_MSG_LATENCY = "rocketmq_delay_message_latency"; public static final String LABEL_STORAGE_TYPE = "storage_type"; public static final String DEFAULT_STORAGE_TYPE = "local"; public static final String LABEL_STORAGE_MEDIUM = "storage_medium"; public static final String DEFAULT_STORAGE_MEDIUM = "disk"; public static final String LABEL_TOPIC = "topic"; public static final String LABEL_TIMING_BOUND = "timer_bound_s"; public static final String GAUGE_BYTES_ROCKSDB_WRITTEN = "rocketmq_rocksdb_bytes_written"; public static final String GAUGE_BYTES_ROCKSDB_READ = "rocketmq_rocksdb_bytes_read"; public static final String GAUGE_TIMES_ROCKSDB_WRITTEN_SELF = "rocketmq_rocksdb_times_written_self"; public static final String GAUGE_TIMES_ROCKSDB_WRITTEN_OTHER = "rocketmq_rocksdb_times_written_other"; public static final String GAUGE_RATE_ROCKSDB_CACHE_HIT = "rocketmq_rocksdb_rate_cache_hit"; public static final String GAUGE_TIMES_ROCKSDB_COMPRESSED = "rocketmq_rocksdb_times_compressed"; public static final String GAUGE_BYTES_READ_AMPLIFICATION = "rocketmq_rocksdb_read_amplification_bytes"; public static final String GAUGE_TIMES_ROCKSDB_READ = "rocketmq_rocksdb_times_read"; } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.metrics; import com.google.common.collect.Lists; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.io.File; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.metrics.NopLongCounter; import org.apache.rocketmq.common.metrics.NopLongHistogram; import org.apache.rocketmq.common.metrics.NopObservableLongGauge; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.timer.Slot; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.TimerMetrics; import org.apache.rocketmq.store.timer.TimerWheel; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.COUNTER_TIMER_DEQUEUE_TOTAL; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.COUNTER_TIMER_ENQUEUE_TOTAL; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.DEFAULT_STORAGE_MEDIUM; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.DEFAULT_STORAGE_TYPE; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_STORAGE_DISPATCH_BEHIND; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_STORAGE_FLUSH_BEHIND; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_STORAGE_MESSAGE_RESERVE_TIME; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_STORAGE_SIZE; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_TIMER_DEQUEUE_LAG; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_TIMER_DEQUEUE_LATENCY; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_TIMER_ENQUEUE_LAG; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_TIMER_ENQUEUE_LATENCY; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_TIMER_MESSAGE_SNAPSHOT; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_TIMING_MESSAGES; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.HISTOGRAM_DELAY_MSG_LATENCY; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.LABEL_STORAGE_MEDIUM; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.LABEL_STORAGE_TYPE; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.LABEL_TIMING_BOUND; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.LABEL_TOPIC; public class DefaultStoreMetricsManager implements StoreMetricsManager { private Supplier attributesBuilderSupplier; private MessageStoreConfig messageStoreConfig; private ObservableLongGauge storageSize = new NopObservableLongGauge(); private ObservableLongGauge flushBehind = new NopObservableLongGauge(); private ObservableLongGauge dispatchBehind = new NopObservableLongGauge(); private ObservableLongGauge messageReserveTime = new NopObservableLongGauge(); private ObservableLongGauge timerEnqueueLag = new NopObservableLongGauge(); private ObservableLongGauge timerEnqueueLatency = new NopObservableLongGauge(); private ObservableLongGauge timerDequeueLag = new NopObservableLongGauge(); private ObservableLongGauge timerDequeueLatency = new NopObservableLongGauge(); private ObservableLongGauge timingMessages = new NopObservableLongGauge(); private LongCounter timerDequeueTotal = new NopLongCounter(); private LongCounter timerEnqueueTotal = new NopLongCounter(); private ObservableLongGauge timerMessageSnapshot = new NopObservableLongGauge(); private LongHistogram timerMessageSetLatency = new NopLongHistogram(); private RocksDBStoreMetricsManager rocksDBStoreMetricsManager; public DefaultStoreMetricsManager() { this.rocksDBStoreMetricsManager = new RocksDBStoreMetricsManager(); } public List> getMetricsView() { List rpcCostTimeBuckets = Arrays.asList( // day * hour * min * second 1d * 1 * 1 * 60, // 60 second 1d * 1 * 10 * 60, // 10 min 1d * 1 * 60 * 60, // 1 hour 1d * 12 * 60 * 60, // 12 hour 1d * 24 * 60 * 60, // 1 day 3d * 24 * 60 * 60 // 3 day ); InstrumentSelector selector = InstrumentSelector.builder() .setType(InstrumentType.HISTOGRAM) .setName(HISTOGRAM_DELAY_MSG_LATENCY) .build(); ViewBuilder viewBuilder = View.builder() .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets)); return Lists.newArrayList(new Pair<>(selector, viewBuilder)); } public void init(Meter meter, Supplier attributesBuilderSupplier, MessageStore messageStore) { // Also add some metrics for rocksdb's monitoring. this.rocksDBStoreMetricsManager.init(meter, attributesBuilderSupplier, messageStore.getQueueStore()); this.attributesBuilderSupplier = attributesBuilderSupplier; this.messageStoreConfig = messageStore.getMessageStoreConfig(); this.storageSize = meter.gaugeBuilder(GAUGE_STORAGE_SIZE) .setDescription("Broker storage size") .setUnit("bytes") .ofLongs() .buildWithCallback(measurement -> { File storeDir = new File(this.messageStoreConfig.getStorePathRootDir()); if (storeDir.exists() && storeDir.isDirectory()) { long totalSpace = storeDir.getTotalSpace(); if (totalSpace > 0) { measurement.record(totalSpace - storeDir.getFreeSpace(), this.newAttributesBuilder().build()); } } }); this.flushBehind = meter.gaugeBuilder(GAUGE_STORAGE_FLUSH_BEHIND) .setDescription("Broker flush behind bytes") .setUnit("bytes") .ofLongs() .buildWithCallback(measurement -> measurement.record(messageStore.flushBehindBytes(), this.newAttributesBuilder().build())); this.dispatchBehind = meter.gaugeBuilder(GAUGE_STORAGE_DISPATCH_BEHIND) .setDescription("Broker dispatch behind bytes") .setUnit("bytes") .ofLongs() .buildWithCallback(measurement -> measurement.record(messageStore.dispatchBehindBytes(), this.newAttributesBuilder().build())); this.messageReserveTime = meter.gaugeBuilder(GAUGE_STORAGE_MESSAGE_RESERVE_TIME) .setDescription("Broker message reserve time") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> { long earliestMessageTime = messageStore.getEarliestMessageTime(); if (earliestMessageTime <= 0) { return; } measurement.record(System.currentTimeMillis() - earliestMessageTime, this.newAttributesBuilder().build()); }); if (messageStore.getMessageStoreConfig().isTimerWheelEnable()) { this.timerEnqueueLag = meter.gaugeBuilder(GAUGE_TIMER_ENQUEUE_LAG) .setDescription("Timer enqueue messages lag") .ofLongs() .buildWithCallback(measurement -> { TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); measurement.record(timerMessageStore.getEnqueueBehindMessages(), this.newAttributesBuilder().build()); }); this.timerEnqueueLatency = meter.gaugeBuilder(GAUGE_TIMER_ENQUEUE_LATENCY) .setDescription("Timer enqueue latency") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> { TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); measurement.record(timerMessageStore.getEnqueueBehindMillis(), this.newAttributesBuilder().build()); }); this.timerDequeueLag = meter.gaugeBuilder(GAUGE_TIMER_DEQUEUE_LAG) .setDescription("Timer dequeue messages lag") .ofLongs() .buildWithCallback(measurement -> { TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); measurement.record(timerMessageStore.getDequeueBehindMessages(), this.newAttributesBuilder().build()); }); this.timerDequeueLatency = meter.gaugeBuilder(GAUGE_TIMER_DEQUEUE_LATENCY) .setDescription("Timer dequeue latency") .setUnit("milliseconds") .ofLongs() .buildWithCallback(measurement -> { TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); measurement.record(timerMessageStore.getDequeueBehindMillis(), this.newAttributesBuilder().build()); }); this.timingMessages = meter.gaugeBuilder(GAUGE_TIMING_MESSAGES) .setDescription("Current message number in timing") .ofLongs() .buildWithCallback(measurement -> { TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); timerMessageStore.getTimerMetrics() .getTimingCount() .forEach((topic, metric) -> { measurement.record( metric.getCount().get(), this.newAttributesBuilder().put(LABEL_TOPIC, topic).build() ); }); }); this.timerDequeueTotal = meter.counterBuilder(COUNTER_TIMER_DEQUEUE_TOTAL) .setDescription("Total number of timer dequeue") .build(); this.timerEnqueueTotal = meter.counterBuilder(COUNTER_TIMER_ENQUEUE_TOTAL) .setDescription("Total number of timer enqueue") .build(); this.timerMessageSnapshot = meter.gaugeBuilder(GAUGE_TIMER_MESSAGE_SNAPSHOT) .setDescription("Timer message distribution snapshot, only count timing messages in 24h.") .ofLongs() .buildWithCallback(measurement -> { TimerMetrics timerMetrics = messageStore.getTimerMessageStore().getTimerMetrics(); TimerWheel timerWheel = messageStore.getTimerMessageStore().getTimerWheel(); int precisionMs = this.messageStoreConfig.getTimerPrecisionMs(); List timerDist = timerMetrics.getTimerDistList(); long currTime = System.currentTimeMillis() / precisionMs * precisionMs; for (int i = 0; i < timerDist.size(); i++) { int slotBeforeNum = i == 0 ? 0 : timerDist.get(i - 1) * 1000 / precisionMs; int slotTotalNum = timerDist.get(i) * 1000 / precisionMs; int periodTotal = 0; for (int j = slotBeforeNum; j < slotTotalNum; j++) { Slot slotEach = timerWheel.getSlot(currTime + (long) j * precisionMs); periodTotal += slotEach.num; } measurement.record(periodTotal, this.newAttributesBuilder().put(LABEL_TIMING_BOUND, timerDist.get(i).toString()).build()); } }); this.timerMessageSetLatency = meter.histogramBuilder(HISTOGRAM_DELAY_MSG_LATENCY) .setDescription("Timer message set latency distribution") .setUnit("seconds") .ofLongs() .build(); } } public void incTimerDequeueCount(String topic) { this.timerDequeueTotal.add(1, this.newAttributesBuilder() .put(LABEL_TOPIC, topic) .build()); } public void incTimerEnqueueCount(String topic) { AttributesBuilder attributesBuilder = this.newAttributesBuilder(); if (topic != null) { attributesBuilder.put(LABEL_TOPIC, topic); } this.timerEnqueueTotal.add(1, attributesBuilder.build()); } public AttributesBuilder newAttributesBuilder() { if (this.attributesBuilderSupplier == null) { return Attributes.builder(); } return this.attributesBuilderSupplier.get() .put(LABEL_STORAGE_TYPE, DEFAULT_STORAGE_TYPE) .put(LABEL_STORAGE_MEDIUM, DEFAULT_STORAGE_MEDIUM); } // Getter methods for external access public Supplier getAttributesBuilderSupplier() { return attributesBuilderSupplier; } public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } public ObservableLongGauge getStorageSize() { return storageSize; } public ObservableLongGauge getFlushBehind() { return flushBehind; } public ObservableLongGauge getDispatchBehind() { return dispatchBehind; } public ObservableLongGauge getMessageReserveTime() { return messageReserveTime; } public ObservableLongGauge getTimerEnqueueLag() { return timerEnqueueLag; } public ObservableLongGauge getTimerEnqueueLatency() { return timerEnqueueLatency; } public ObservableLongGauge getTimerDequeueLag() { return timerDequeueLag; } public ObservableLongGauge getTimerDequeueLatency() { return timerDequeueLatency; } public ObservableLongGauge getTimingMessages() { return timingMessages; } public LongCounter getTimerDequeueTotal() { return timerDequeueTotal; } public LongCounter getTimerEnqueueTotal() { return timerEnqueueTotal; } public ObservableLongGauge getTimerMessageSnapshot() { return timerMessageSnapshot; } public LongHistogram getTimerMessageSetLatency() { return timerMessageSetLatency; } // Setter methods for testing public void setAttributesBuilderSupplier(Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; } public RocksDBStoreMetricsManager getRocksDBStoreMetricsManager() { return rocksDBStoreMetricsManager; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/metrics/RocksDBStoreMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.metrics; import com.google.common.collect.Lists; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableDoubleGauge; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.util.List; import java.util.function.Supplier; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.metrics.NopObservableDoubleGauge; import org.apache.rocketmq.common.metrics.NopObservableLongGauge; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.queue.CombineConsumeQueueStore; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore; import org.rocksdb.TickerType; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.DEFAULT_STORAGE_MEDIUM; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.DEFAULT_STORAGE_TYPE; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_BYTES_ROCKSDB_READ; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.GAUGE_BYTES_ROCKSDB_WRITTEN; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.LABEL_STORAGE_MEDIUM; import static org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant.LABEL_STORAGE_TYPE; public class RocksDBStoreMetricsManager { private Supplier attributesBuilderSupplier; private MessageStoreConfig messageStoreConfig; // The cumulative number of bytes read from the database. private ObservableLongGauge bytesRocksdbRead = new NopObservableLongGauge(); // The cumulative number of bytes written to the database. private ObservableLongGauge bytesRocksdbWritten = new NopObservableLongGauge(); // The cumulative number of read operations performed. private ObservableLongGauge timesRocksdbRead = new NopObservableLongGauge(); // The cumulative number of write operations performed. private ObservableLongGauge timesRocksdbWrittenSelf = new NopObservableLongGauge(); private ObservableLongGauge timesRocksdbWrittenOther = new NopObservableLongGauge(); // The cumulative number of compressions that have occurred. private ObservableLongGauge timesRocksdbCompressed = new NopObservableLongGauge(); // The ratio of the amount of data actually written to the storage medium to the amount of data written by the application. private ObservableDoubleGauge bytesRocksdbAmplificationRead = new NopObservableDoubleGauge(); // The rate at which cache lookups were served from the cache rather than needing to be fetched from disk. private ObservableDoubleGauge rocksdbCacheHitRate = new NopObservableDoubleGauge(); private volatile long blockCacheHitTimes = 0; private volatile long blockCacheMissTimes = 0; public RocksDBStoreMetricsManager() { } public List> getMetricsView() { return Lists.newArrayList(); } public void init(Meter meter, Supplier attributesBuilderSupplier, ConsumeQueueStoreInterface consumeQueueStore) { final RocksDBConsumeQueueStore rocksDBMessageStore; if (consumeQueueStore instanceof RocksDBConsumeQueueStore) { rocksDBMessageStore = (RocksDBConsumeQueueStore) consumeQueueStore; } else if (consumeQueueStore instanceof CombineConsumeQueueStore) { rocksDBMessageStore = ((CombineConsumeQueueStore) consumeQueueStore).getRocksDBConsumeQueueStore(); } else { rocksDBMessageStore = null; } if (rocksDBMessageStore == null) { return; } this.attributesBuilderSupplier = attributesBuilderSupplier; this.bytesRocksdbWritten = meter.gaugeBuilder(GAUGE_BYTES_ROCKSDB_WRITTEN) .setDescription("The cumulative number of bytes written to the database.") .ofLongs() .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.BYTES_WRITTEN), this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.bytesRocksdbRead = meter.gaugeBuilder(GAUGE_BYTES_ROCKSDB_READ) .setDescription("The cumulative number of bytes read from the database.") .ofLongs() .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.BYTES_READ), this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.timesRocksdbWrittenSelf = meter.gaugeBuilder(DefaultStoreMetricsConstant.GAUGE_TIMES_ROCKSDB_WRITTEN_SELF) .setDescription("The cumulative number of write operations performed by self.") .ofLongs() .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.WRITE_DONE_BY_SELF), this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.timesRocksdbWrittenOther = meter.gaugeBuilder(DefaultStoreMetricsConstant.GAUGE_TIMES_ROCKSDB_WRITTEN_OTHER) .setDescription("The cumulative number of write operations performed by other.") .ofLongs() .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.WRITE_DONE_BY_OTHER), this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.timesRocksdbRead = meter.gaugeBuilder(DefaultStoreMetricsConstant.GAUGE_TIMES_ROCKSDB_READ) .setDescription("The cumulative number of write operations performed by other.") .ofLongs() .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.NUMBER_KEYS_READ), this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.rocksdbCacheHitRate = meter.gaugeBuilder(DefaultStoreMetricsConstant.GAUGE_RATE_ROCKSDB_CACHE_HIT) .setDescription("The rate at which cache lookups were served from the cache rather than needing to be fetched from disk.") .buildWithCallback(measurement -> { long newHitTimes = rocksDBMessageStore.getStatistics().getTickerCount(TickerType.BLOCK_CACHE_HIT); long newMissTimes = rocksDBMessageStore.getStatistics().getTickerCount(TickerType.BLOCK_CACHE_MISS); long totalPeriod = newHitTimes - this.blockCacheHitTimes + newMissTimes - this.blockCacheMissTimes; double hitRate = totalPeriod == 0 ? 0 : (double)(newHitTimes - this.blockCacheHitTimes) / totalPeriod; this.blockCacheHitTimes = newHitTimes; this.blockCacheMissTimes = newMissTimes; measurement.record(hitRate, this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.timesRocksdbCompressed = meter.gaugeBuilder(DefaultStoreMetricsConstant.GAUGE_TIMES_ROCKSDB_COMPRESSED) .setDescription("The cumulative number of compressions that have occurred.") .ofLongs() .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.NUMBER_BLOCK_COMPRESSED), this.newAttributesBuilder().put("type", "consume_queue").build()); }); this.bytesRocksdbAmplificationRead = meter.gaugeBuilder(DefaultStoreMetricsConstant.GAUGE_BYTES_READ_AMPLIFICATION) .setDescription("The rate at which cache lookups were served from the cache rather than needing to be fetched from disk.") .buildWithCallback(measurement -> { measurement.record(rocksDBMessageStore .getStatistics().getTickerCount(TickerType.READ_AMP_TOTAL_READ_BYTES), this.newAttributesBuilder().put("type", "consume_queue").build()); }); } public AttributesBuilder newAttributesBuilder() { if (this.attributesBuilderSupplier == null) { return Attributes.builder(); } return this.attributesBuilderSupplier.get() .put(LABEL_STORAGE_TYPE, DEFAULT_STORAGE_TYPE) .put(LABEL_STORAGE_MEDIUM, DEFAULT_STORAGE_MEDIUM); } // Getter methods for external access public Supplier getAttributesBuilderSupplier() { return attributesBuilderSupplier; } public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } public ObservableLongGauge getBytesRocksdbRead() { return bytesRocksdbRead; } public ObservableLongGauge getBytesRocksdbWritten() { return bytesRocksdbWritten; } public ObservableLongGauge getTimesRocksdbRead() { return timesRocksdbRead; } public ObservableLongGauge getTimesRocksdbWrittenSelf() { return timesRocksdbWrittenSelf; } public ObservableLongGauge getTimesRocksdbWrittenOther() { return timesRocksdbWrittenOther; } public ObservableLongGauge getTimesRocksdbCompressed() { return timesRocksdbCompressed; } public ObservableDoubleGauge getBytesRocksdbAmplificationRead() { return bytesRocksdbAmplificationRead; } public ObservableDoubleGauge getRocksdbCacheHitRate() { return rocksdbCacheHitRate; } public long getBlockCacheHitTimes() { return blockCacheHitTimes; } public long getBlockCacheMissTimes() { return blockCacheMissTimes; } // Setter methods for testing public void setAttributesBuilderSupplier(Supplier attributesBuilderSupplier) { this.attributesBuilderSupplier = attributesBuilderSupplier; } public void setMessageStoreConfig(MessageStoreConfig messageStoreConfig) { this.messageStoreConfig = messageStoreConfig; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/metrics/StoreMetricsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.metrics; import io.opentelemetry.api.common.AttributesBuilder; import java.util.List; import java.util.function.Supplier; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.store.MessageStore; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.ViewBuilder; /** * Store metrics manager interface for different message store implementations. * This interface provides a unified way to access metrics functionality * regardless of the underlying message store type. */ public interface StoreMetricsManager { /** * Initialize metrics with the given meter and attributes builder supplier. * * @param meter OpenTelemetry meter * @param attributesBuilderSupplier Metrics attributes builder supplier * @param messageStore The message store instance */ void init(Meter meter, Supplier attributesBuilderSupplier, MessageStore messageStore); /** * Get metrics view configuration. * * @return List of instrument selector and view builder pairs */ List> getMetricsView(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.plugin; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo; import org.apache.rocketmq.store.AllocateMappedFileService; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.CommitLogDispatcher; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.MessageStoreStateMachine; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.QueryMessageResult; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.StoreCheckpoint; import org.apache.rocketmq.store.StoreStatsService; import org.apache.rocketmq.store.TransientStorePool; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.ha.HAService; import org.apache.rocketmq.store.hook.PutMessageHook; import org.apache.rocketmq.store.hook.SendMessageBackHook; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.util.PerfCounter; import org.apache.rocketmq.store.metrics.StoreMetricsManager; import org.rocksdb.RocksDBException; public abstract class AbstractPluginMessageStore implements MessageStore { protected MessageStore next; protected MessageStorePluginContext context; public AbstractPluginMessageStore(MessageStorePluginContext context, MessageStore next) { this.next = next; this.context = context; } @Override public long getEarliestMessageTime() { return next.getEarliestMessageTime(); } @Override public long lockTimeMills() { return next.lockTimeMills(); } @Override public boolean isOSPageCacheBusy() { return next.isOSPageCacheBusy(); } @Override public boolean isTransientStorePoolDeficient() { return next.isTransientStorePoolDeficient(); } @Override public boolean load() { return next.load(); } @Override public void start() throws Exception { next.start(); } @Override public void shutdown() { next.shutdown(); } @Override public void destroy() { next.destroy(); } @Override public PutMessageResult putMessage(MessageExtBrokerInner msg) { return next.putMessage(msg); } @Override public CompletableFuture asyncPutMessage(MessageExtBrokerInner msg) { return next.asyncPutMessage(msg); } @Override public CompletableFuture asyncPutMessages(MessageExtBatch messageExtBatch) { return next.asyncPutMessages(messageExtBatch); } @Override public GetMessageResult getMessage(String group, String topic, int queueId, long offset, int maxMsgNums, final MessageFilter messageFilter) { return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter); } @Override public CompletableFuture getMessageAsync(String group, String topic, int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) { return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter); } @Override public long getMaxOffsetInQueue(String topic, int queueId) throws ConsumeQueueException { return next.getMaxOffsetInQueue(topic, queueId); } @Override public long getMaxOffsetInQueue(String topic, int queueId, boolean committed) throws ConsumeQueueException { return next.getMaxOffsetInQueue(topic, queueId, committed); } @Override public long getMinOffsetInQueue(String topic, int queueId) { return next.getMinOffsetInQueue(topic, queueId); } @Override public long getCommitLogOffsetInQueue(String topic, int queueId, long consumeQueueOffset) { return next.getCommitLogOffsetInQueue(topic, queueId, consumeQueueOffset); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp) { return next.getOffsetInQueueByTime(topic, queueId, timestamp); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) { return next.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType); } @Override public MessageExt lookMessageByOffset(long commitLogOffset) { return next.lookMessageByOffset(commitLogOffset); } @Override public SelectMappedBufferResult selectOneMessageByOffset(long commitLogOffset) { return next.selectOneMessageByOffset(commitLogOffset); } @Override public SelectMappedBufferResult selectOneMessageByOffset(long commitLogOffset, int msgSize) { return next.selectOneMessageByOffset(commitLogOffset, msgSize); } @Override public String getRunningDataInfo() { return next.getRunningDataInfo(); } @Override public HashMap getRuntimeInfo() { return next.getRuntimeInfo(); } @Override public long getMaxPhyOffset() { return next.getMaxPhyOffset(); } @Override public long getMinPhyOffset() { return next.getMinPhyOffset(); } @Override public long getEarliestMessageTime(String topic, int queueId) { return next.getEarliestMessageTime(topic, queueId); } @Override public CompletableFuture getEarliestMessageTimeAsync(String topic, int queueId) { return next.getEarliestMessageTimeAsync(topic, queueId); } @Override public long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) { return next.getMessageStoreTimeStamp(topic, queueId, consumeQueueOffset); } @Override public CompletableFuture getMessageStoreTimeStampAsync(String topic, int queueId, long consumeQueueOffset) { return next.getMessageStoreTimeStampAsync(topic, queueId, consumeQueueOffset); } @Override public long getMessageTotalInQueue(String topic, int queueId) { return next.getMessageTotalInQueue(topic, queueId); } @Override public SelectMappedBufferResult getCommitLogData(long offset) { return next.getCommitLogData(offset); } @Override public boolean appendToCommitLog(long startOffset, byte[] data, int dataStart, int dataLength) { return next.appendToCommitLog(startOffset, data, dataStart, dataLength); } @Override public void executeDeleteFilesManually() { next.executeDeleteFilesManually(); } @Override public QueryMessageResult queryMessage(String topic, String key, int maxNum, long begin, long end) { return next.queryMessage(topic, key, maxNum, begin, end); } @Override public CompletableFuture queryMessageAsync(String topic, String key, int maxNum, long begin, long end) { return next.queryMessageAsync(topic, key, maxNum, begin, end); } @Override public long now() { return next.now(); } @Override public int deleteTopics(final Set deleteTopics) { return next.deleteTopics(deleteTopics); } @Override public int cleanUnusedTopic(final Set retainTopics) { return next.cleanUnusedTopic(retainTopics); } @Override public void cleanExpiredConsumerQueue() { next.cleanExpiredConsumerQueue(); } @Override @Deprecated public boolean checkInDiskByConsumeOffset(String topic, int queueId, long consumeOffset) { return next.checkInDiskByConsumeOffset(topic, queueId, consumeOffset); } @Override public boolean checkInMemByConsumeOffset(String topic, int queueId, long consumeOffset, int batchSize) { return next.checkInMemByConsumeOffset(topic, queueId, consumeOffset, batchSize); } @Override public boolean checkInStoreByConsumeOffset(String topic, int queueId, long consumeOffset) { return next.checkInStoreByConsumeOffset(topic, queueId, consumeOffset); } @Override public long dispatchBehindBytes() { return next.dispatchBehindBytes(); } @Override public long flushBehindBytes() { return next.flushBehindBytes(); } @Override public long dispatchBehindMilliseconds() { return next.dispatchBehindMilliseconds(); } @Override public long flush() { return next.flush(); } @Override public long getConfirmOffset() { return next.getConfirmOffset(); } @Override public void setConfirmOffset(long phyOffset) { next.setConfirmOffset(phyOffset); } @Override public LinkedList getDispatcherList() { return next.getDispatcherList(); } @Override public void addDispatcher(CommitLogDispatcher dispatcher) { next.addDispatcher(dispatcher); } @Override public ConsumeQueueInterface getConsumeQueue(String topic, int queueId) { return next.getConsumeQueue(topic, queueId); } @Override public ConsumeQueueInterface findConsumeQueue(String topic, int queueId) { return next.findConsumeQueue(topic, queueId); } @Override public BrokerStatsManager getBrokerStatsManager() { return next.getBrokerStatsManager(); } @Override public int remainTransientStoreBufferNumbs() { return next.remainTransientStoreBufferNumbs(); } @Override public long remainHowManyDataToCommit() { return next.remainHowManyDataToCommit(); } @Override public long remainHowManyDataToFlush() { return next.remainHowManyDataToFlush(); } @Override public DispatchRequest checkMessageAndReturnSize(final ByteBuffer byteBuffer, final boolean checkCRC, final boolean checkDupInfo, final boolean readBody) { return next.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); } @Override public long getStateMachineVersion() { return next.getStateMachineVersion(); } @Override public PutMessageResult putMessages(MessageExtBatch messageExtBatch) { return next.putMessages(messageExtBatch); } @Override public HARuntimeInfo getHARuntimeInfo() { return next.getHARuntimeInfo(); } @Override public boolean getLastMappedFile(long startOffset) { return next.getLastMappedFile(startOffset); } @Override public void updateHaMasterAddress(String newAddr) { next.updateHaMasterAddress(newAddr); } @Override public void updateMasterAddress(String newAddr) { next.updateMasterAddress(newAddr); } @Override public long slaveFallBehindMuch() { return next.slaveFallBehindMuch(); } @Override public long getFlushedWhere() { return next.getFlushedWhere(); } @Override public MessageStore getMasterStoreInProcess() { return next.getMasterStoreInProcess(); } @Override public void setMasterStoreInProcess(MessageStore masterStoreInProcess) { next.setMasterStoreInProcess(masterStoreInProcess); } @Override public boolean getData(long offset, int size, ByteBuffer byteBuffer) { return next.getData(offset, size, byteBuffer); } @Override public void setAliveReplicaNumInGroup(int aliveReplicaNums) { next.setAliveReplicaNumInGroup(aliveReplicaNums); } @Override public int getAliveReplicaNumInGroup() { return next.getAliveReplicaNumInGroup(); } @Override public void wakeupHAClient() { next.wakeupHAClient(); } @Override public long getMasterFlushedOffset() { return next.getMasterFlushedOffset(); } @Override public long getBrokerInitMaxOffset() { return next.getBrokerInitMaxOffset(); } @Override public void setMasterFlushedOffset(long masterFlushedOffset) { next.setMasterFlushedOffset(masterFlushedOffset); } @Override public void setBrokerInitMaxOffset(long brokerInitMaxOffset) { next.setBrokerInitMaxOffset(brokerInitMaxOffset); } @Override public byte[] calcDeltaChecksum(long from, long to) { return next.calcDeltaChecksum(from, to); } @Override public HAService getHaService() { return next.getHaService(); } @Override public boolean truncateFiles(long offsetToTruncate) throws RocksDBException { return next.truncateFiles(offsetToTruncate); } @Override public boolean isOffsetAligned(long offset) { return next.isOffsetAligned(offset); } @Override public RunningFlags getRunningFlags() { return next.getRunningFlags(); } @Override public void setSendMessageBackHook(SendMessageBackHook sendMessageBackHook) { next.setSendMessageBackHook(sendMessageBackHook); } @Override public SendMessageBackHook getSendMessageBackHook() { return next.getSendMessageBackHook(); } @Override public GetMessageResult getMessage(String group, String topic, int queueId, long offset, int maxMsgNums, int maxTotalMsgSize, MessageFilter messageFilter) { return next.getMessage(group, topic, queueId, offset, maxMsgNums, maxTotalMsgSize, messageFilter); } @Override public CompletableFuture getMessageAsync(String group, String topic, int queueId, long offset, int maxMsgNums, int maxTotalMsgSize, MessageFilter messageFilter) { return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, maxTotalMsgSize, messageFilter); } @Override public MessageExt lookMessageByOffset(long commitLogOffset, int size) { return next.lookMessageByOffset(commitLogOffset, size); } @Override public List getBulkCommitLogData(long offset, int size) { return next.getBulkCommitLogData(offset, size); } @Override public void onCommitLogAppend(MessageExtBrokerInner msg, AppendMessageResult result, MappedFile commitLogFile) { next.onCommitLogAppend(msg, result, commitLogFile); } @Override public void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile, boolean isRecover, boolean isFileEnd) throws RocksDBException { next.onCommitLogDispatch(dispatchRequest, doDispatch, commitLogFile, isRecover, isFileEnd); } @Override public MessageStoreConfig getMessageStoreConfig() { return next.getMessageStoreConfig(); } @Override public StoreStatsService getStoreStatsService() { return next.getStoreStatsService(); } @Override public StoreCheckpoint getStoreCheckpoint() { return next.getStoreCheckpoint(); } @Override public SystemClock getSystemClock() { return next.getSystemClock(); } @Override public CommitLog getCommitLog() { return next.getCommitLog(); } @Override public TransientStorePool getTransientStorePool() { return next.getTransientStorePool(); } @Override public AllocateMappedFileService getAllocateMappedFileService() { return next.getAllocateMappedFileService(); } @Override public void truncateDirtyLogicFiles(long phyOffset) throws RocksDBException { next.truncateDirtyLogicFiles(phyOffset); } @Override public void unlockMappedFile(MappedFile unlockMappedFile) { next.unlockMappedFile(unlockMappedFile); } @Override public PerfCounter.Ticks getPerfCounter() { return next.getPerfCounter(); } @Override public ConsumeQueueStoreInterface getQueueStore() { return next.getQueueStore(); } @Override public boolean isSyncDiskFlush() { return next.isSyncDiskFlush(); } @Override public boolean isSyncMaster() { return next.isSyncMaster(); } @Override public void assignOffset(MessageExtBrokerInner msg) throws RocksDBException { next.assignOffset(msg); } @Override public void increaseOffset(MessageExtBrokerInner msg, short messageNum) { next.increaseOffset(msg, messageNum); } @Override public List getPutMessageHookList() { return next.getPutMessageHookList(); } @Override public long getLastFileFromOffset() { return next.getLastFileFromOffset(); } @Override public void setPhysicalOffset(long phyOffset) { next.setPhysicalOffset(phyOffset); } @Override public boolean isMappedFilesEmpty() { return next.isMappedFilesEmpty(); } @Override public TimerMessageStore getTimerMessageStore() { return next.getTimerMessageStore(); } @Override public void setTimerMessageStore(TimerMessageStore timerMessageStore) { next.setTimerMessageStore(timerMessageStore); } @Override public long getTimingMessageCount(String topic) { return next.getTimingMessageCount(topic); } @Override public boolean isShutdown() { return next.isShutdown(); } @Override public long estimateMessageCount(String topic, int queueId, long from, long to, MessageFilter filter) { return next.estimateMessageCount(topic, queueId, from, to, filter); } @Override public List> getMetricsView() { return next.getMetricsView(); } @Override public void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { next.initMetrics(meter, attributesBuilderSupplier); } @Override public void recoverTopicQueueTable() { next.recoverTopicQueueTable(); } @Override public void notifyMessageArriveIfNecessary(DispatchRequest dispatchRequest) { next.notifyMessageArriveIfNecessary(dispatchRequest); } public MessageStore getNext() { return next; } @Override public MessageStoreStateMachine getStateMachine() { return next.getStateMachine(); } @Override public StoreMetricsManager getStoreMetricsManager() { return next.getStoreMetricsManager(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/plugin/MessageStoreFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.plugin; import java.io.IOException; import java.lang.reflect.Constructor; import org.apache.rocketmq.store.MessageStore; public final class MessageStoreFactory { public static MessageStore build(MessageStorePluginContext context, MessageStore messageStore) throws IOException { String plugin = context.getBrokerConfig().getMessageStorePlugIn(); if (plugin != null && plugin.trim().length() != 0) { String[] pluginClasses = plugin.split(","); for (int i = pluginClasses.length - 1; i >= 0; --i) { String pluginClass = pluginClasses[i]; try { @SuppressWarnings("unchecked") Class clazz = (Class) Class.forName(pluginClass); Constructor construct = clazz.getConstructor(MessageStorePluginContext.class, MessageStore.class); AbstractPluginMessageStore pluginMessageStore = construct.newInstance(context, messageStore); messageStore = pluginMessageStore; } catch (Throwable e) { throw new RuntimeException("Initialize plugin's class: " + pluginClass + " not found!", e); } } } return messageStore; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/plugin/MessageStorePluginContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.plugin; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.Configuration; import org.apache.rocketmq.store.MessageArrivingListener; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class MessageStorePluginContext { private MessageStoreConfig messageStoreConfig; private BrokerStatsManager brokerStatsManager; private MessageArrivingListener messageArrivingListener; private BrokerConfig brokerConfig; private final Configuration configuration; public MessageStorePluginContext(MessageStoreConfig messageStoreConfig, BrokerStatsManager brokerStatsManager, MessageArrivingListener messageArrivingListener, BrokerConfig brokerConfig, Configuration configuration) { super(); this.messageStoreConfig = messageStoreConfig; this.brokerStatsManager = brokerStatsManager; this.messageArrivingListener = messageArrivingListener; this.brokerConfig = brokerConfig; this.configuration = configuration; } public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } public BrokerStatsManager getBrokerStatsManager() { return brokerStatsManager; } public MessageArrivingListener getMessageArrivingListener() { return messageArrivingListener; } public BrokerConfig getBrokerConfig() { return brokerConfig; } public void registerConfiguration(Object config) { MixAll.properties2Object(configuration.getAllConfigs(), config); configuration.registerConfig(config); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.pop; import com.alibaba.fastjson2.annotation.JSONField; public class AckMsg { @JSONField(name = "ao", alternateNames = {"ackOffset"}) private long ackOffset; @JSONField(name = "so", alternateNames = {"startOffset"}) private long startOffset; @JSONField(name = "c", alternateNames = {"consumerGroup"}) private String consumerGroup; @JSONField(name = "t", alternateNames = {"topic"}) private String topic; @JSONField(name = "q", alternateNames = {"queueId"}) private int queueId; @JSONField(name = "pt", alternateNames = {"popTime"}) private long popTime; @JSONField(name = "bn", alternateNames = {"brokerName"}) private String brokerName; public long getPopTime() { return popTime; } public void setPopTime(long popTime) { this.popTime = popTime; } public void setQueueId(int queueId) { this.queueId = queueId; } public int getQueueId() { return queueId; } public void setTopic(String topic) { this.topic = topic; } public String getTopic() { return topic; } public long getAckOffset() { return ackOffset; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public void setAckOffset(long ackOffset) { this.ackOffset = ackOffset; } public long getStartOffset() { return startOffset; } public void setStartOffset(long startOffset) { this.startOffset = startOffset; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } @Override public String toString() { final StringBuilder sb = new StringBuilder("AckMsg{"); sb.append("ackOffset=").append(ackOffset); sb.append(", startOffset=").append(startOffset); sb.append(", consumerGroup='").append(consumerGroup).append('\''); sb.append(", topic='").append(topic).append('\''); sb.append(", queueId=").append(queueId); sb.append(", popTime=").append(popTime); sb.append(", brokerName=").append(brokerName); sb.append('}'); return sb.toString(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/pop/BatchAckMsg.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.pop; import com.alibaba.fastjson2.annotation.JSONField; import java.util.ArrayList; import java.util.List; public class BatchAckMsg extends AckMsg { @JSONField(name = "aol", alternateNames = {"ackOffsetList"}) private List ackOffsetList = new ArrayList(32); public List getAckOffsetList() { return ackOffsetList; } public void setAckOffsetList(List ackOffsetList) { this.ackOffsetList = ackOffsetList; } @Override public String toString() { final StringBuilder sb = new StringBuilder("BatchAckMsg{"); sb.append("ackOffsetList=").append(ackOffsetList); sb.append(", startOffset=").append(getStartOffset()); sb.append(", consumerGroup='").append(getConsumerGroup()).append('\''); sb.append(", topic='").append(getTopic()).append('\''); sb.append(", queueId=").append(getQueueId()); sb.append(", popTime=").append(getPopTime()); sb.append(", brokerName=").append(getBrokerName()); sb.append('}'); return sb.toString(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.pop; import com.alibaba.fastjson2.annotation.JSONField; import java.util.ArrayList; import java.util.List; public class PopCheckPoint implements Comparable { @JSONField(name = "so") private long startOffset; @JSONField(name = "pt") private long popTime; @JSONField(name = "it") private long invisibleTime; @JSONField(name = "bm") private int bitMap; @JSONField(name = "n") private byte num; @JSONField(name = "q") private int queueId; @JSONField(name = "t") private String topic; private String cid; @JSONField(name = "ro") private long reviveOffset; @JSONField(name = "d") private List queueOffsetDiff; @JSONField(name = "bn") String brokerName; @JSONField(name = "rp") String rePutTimes; // ck rePut times @JSONField(name = "sp") private boolean suspend; // nack without inc reconsume times, false default. public long getReviveOffset() { return reviveOffset; } public void setReviveOffset(long reviveOffset) { this.reviveOffset = reviveOffset; } public long getStartOffset() { return startOffset; } public void setStartOffset(long startOffset) { this.startOffset = startOffset; } public void setPopTime(long popTime) { this.popTime = popTime; } public void setInvisibleTime(long invisibleTime) { this.invisibleTime = invisibleTime; } public long getPopTime() { return popTime; } public long getInvisibleTime() { return invisibleTime; } public long getReviveTime() { return popTime + invisibleTime; } public int getBitMap() { return bitMap; } public void setBitMap(int bitMap) { this.bitMap = bitMap; } public byte getNum() { return num; } public void setNum(byte num) { this.num = num; } public int getQueueId() { return queueId; } public void setQueueId(int queueId) { this.queueId = queueId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } @JSONField(name = "c") public String getCId() { return cid; } @JSONField(name = "c") public void setCId(String cid) { this.cid = cid; } public List getQueueOffsetDiff() { return queueOffsetDiff; } public void setQueueOffsetDiff(List queueOffsetDiff) { this.queueOffsetDiff = queueOffsetDiff; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getRePutTimes() { return rePutTimes; } public void setRePutTimes(String rePutTimes) { this.rePutTimes = rePutTimes; } public boolean isSuspend() { return suspend; } public void setSuspend(boolean suspend) { this.suspend = suspend; } public void addDiff(int diff) { if (this.queueOffsetDiff == null) { this.queueOffsetDiff = new ArrayList<>(8); } this.queueOffsetDiff.add(diff); } public int indexOfAck(long ackOffset) { if (ackOffset < startOffset) { return -1; } // old version of checkpoint if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { if (ackOffset - startOffset < num) { return (int) (ackOffset - startOffset); } return -1; } // new version of checkpoint return queueOffsetDiff.indexOf((int) (ackOffset - startOffset)); } public long ackOffsetByIndex(byte index) { // old version of checkpoint if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { return startOffset + index; } return startOffset + queueOffsetDiff.get(index); } public int parseRePutTimes() { if (null == rePutTimes) { return 0; } try { return Integer.parseInt(rePutTimes); } catch (Exception e) { } return Byte.MAX_VALUE; } @Override public String toString() { return "PopCheckPoint [topic=" + topic + ", cid=" + cid + ", queueId=" + queueId + ", startOffset=" + startOffset + ", bitMap=" + bitMap + ", num=" + num + ", reviveTime=" + getReviveTime() + ", reviveOffset=" + reviveOffset + ", diff=" + queueOffsetDiff + ", brokerName=" + brokerName + ", rePutTimes=" + rePutTimes + ", suspend=" + suspend + "]"; } @Override public int compareTo(PopCheckPoint o) { return (int) (this.getStartOffset() - o.getStartOffset()); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/AbstractConsumeQueueStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.rocksdb.RocksDBException; public abstract class AbstractConsumeQueueStore implements ConsumeQueueStoreInterface { protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected final DefaultMessageStore messageStore; protected final MessageStoreConfig messageStoreConfig; protected final QueueOffsetOperator queueOffsetOperator = new QueueOffsetOperator(); protected final ConcurrentMap> consumeQueueTable; public AbstractConsumeQueueStore(DefaultMessageStore messageStore) { this.messageStore = messageStore; this.messageStoreConfig = messageStore.getMessageStoreConfig(); if (messageStoreConfig.isEnableLmq()) { this.consumeQueueTable = new ConcurrentHashMap<>(32_768); } else { this.consumeQueueTable = new ConcurrentHashMap<>(32); } } public void putMessagePositionInfoWrapper(ConsumeQueueInterface consumeQueue, DispatchRequest request) { consumeQueue.putMessagePositionInfoWrapper(request); } @Override public Long getMaxOffset(String topic, int queueId) throws ConsumeQueueException { return this.queueOffsetOperator.currentQueueOffset(topic + "-" + queueId); } public void setTopicQueueTable(ConcurrentMap topicQueueTable) { this.queueOffsetOperator.setTopicQueueTable(topicQueueTable); this.queueOffsetOperator.setLmqTopicQueueTable(topicQueueTable); } @Override public void assignQueueOffset(MessageExtBrokerInner msg) throws RocksDBException { ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId()); consumeQueue.assignQueueOffset(this.queueOffsetOperator, msg); } @Override public void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum) { ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId()); consumeQueue.increaseQueueOffset(this.queueOffsetOperator, msg, messageNum); } @Override public void increaseLmqOffset(String topic, int queueId, short delta) throws ConsumeQueueException { queueOffsetOperator.increaseLmqOffset(topic, queueId, delta); } @Override public long getLmqQueueOffset(String topic, int queueId) throws ConsumeQueueException { return queueOffsetOperator.getLmqOffset(topic, queueId, (t, q) -> 0L); } public void removeTopicQueueTable(String topic, Integer queueId) { this.queueOffsetOperator.remove(topic, queueId); } @Override public ConcurrentMap> getConsumeQueueTable() { return this.consumeQueueTable; } public long getStoreTime(CqUnit cqUnit) { if (cqUnit != null) { try { final long phyOffset = cqUnit.getPos(); final int size = cqUnit.getSize(); return this.messageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size); } catch (Exception e) { log.error("Failed to getStoreTime", e); } } return -1; } /** * get max physic offset in consumeQueue * * @return the max physic offset in consumeQueue * @throws RocksDBException only in rocksdb mode */ public abstract long getMaxPhyOffsetInConsumeQueue() throws RocksDBException; /** * destroy the specific consumeQueue * * @param consumeQueue consumeQueue to be destroyed * @throws RocksDBException only in rocksdb mode */ protected abstract void destroy(ConsumeQueueInterface consumeQueue) throws RocksDBException; @Override public boolean deleteTopic(String topic) { ConcurrentMap queueTable = this.consumeQueueTable.get(topic); if (queueTable == null || queueTable.isEmpty()) { return false; } for (ConsumeQueueInterface cq : queueTable.values()) { try { destroy(cq); } catch (RocksDBException e) { log.error("DeleteTopic: ConsumeQueue cleans error!, topic={}, queueId={}", cq.getTopic(), cq.getQueueId(), e); } log.info("DeleteTopic: ConsumeQueue has been cleaned, topic={}, queueId={}", cq.getTopic(), cq.getQueueId()); removeTopicQueueTable(cq.getTopic(), cq.getQueueId()); } // remove topic from cq table this.consumeQueueTable.remove(topic); return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentSkipListMap; import java.util.function.Function; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MappedFileQueue; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.logfile.MappedFile; public class BatchConsumeQueue implements ConsumeQueueInterface { protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); /** * BatchConsumeQueue's store unit. Format: *

         * ┌─────────────────────────┬───────────┬────────────┬──────────┬─────────────┬─────────┬───────────────┬─────────┐
         * │CommitLog Physical Offset│ Body Size │Tag HashCode│Store time│msgBaseOffset│batchSize│compactedOffset│reserved │
         * │        (8 Bytes)        │ (4 Bytes) │ (8 Bytes)  │(8 Bytes) │(8 Bytes)    │(2 Bytes)│   (4 Bytes)   │(4 Bytes)│
         * ├─────────────────────────┴───────────┴────────────┴──────────┴─────────────┴─────────┴───────────────┴─────────┤
         * │                                                  Store Unit                                                   │
         * │                                                                                                               │
         * 
    * BatchConsumeQueue's store unit. Size: * CommitLog Physical Offset(8) + Body Size(4) + Tag HashCode(8) + Store time(8) + * msgBaseOffset(8) + batchSize(2) + compactedOffset(4) + reserved(4)= 46 Bytes */ public static final int CQ_STORE_UNIT_SIZE = 46; public static final int MSG_TAG_OFFSET_INDEX = 12; public static final int MSG_STORE_TIME_OFFSET_INDEX = 20; public static final int MSG_BASE_OFFSET_INDEX = 28; public static final int MSG_BATCH_SIZE_INDEX = 36; public static final int MSG_COMPACT_OFFSET_INDEX = 38; private static final int MSG_COMPACT_OFFSET_LENGTH = 4; public static final int INVALID_POS = -1; protected final MappedFileQueue mappedFileQueue; protected MessageStore messageStore; protected ConsumeQueueStore consumeQueueStore; protected final String topic; protected final int queueId; protected final ByteBuffer byteBufferItem; protected final String storePath; protected final int mappedFileSize; protected volatile long maxMsgPhyOffsetInCommitLog = -1; protected volatile long minLogicOffset = 0; protected volatile long maxOffsetInQueue = 0; protected volatile long minOffsetInQueue = -1; protected final int commitLogSize; protected ConcurrentSkipListMap offsetCache = new ConcurrentSkipListMap<>(); protected ConcurrentSkipListMap timeCache = new ConcurrentSkipListMap<>(); public BatchConsumeQueue( final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore messageStore, final ConsumeQueueStore consumeQueueStore, final String subfolder) { this.storePath = storePath; this.mappedFileSize = mappedFileSize; this.messageStore = messageStore; this.consumeQueueStore = consumeQueueStore; this.commitLogSize = messageStore.getCommitLog().getCommitLogSize(); this.topic = topic; this.queueId = queueId; boolean writeWithoutMmap = false; if (messageStore.getMessageStoreConfig() != null) { writeWithoutMmap = messageStore.getMessageStoreConfig().isWriteWithoutMmap(); } if (StringUtils.isBlank(subfolder)) { String queueDir = this.storePath + File.separator + topic + File.separator + queueId; this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null, writeWithoutMmap); } else { String queueDir = this.storePath + File.separator + topic + File.separator + queueId + File.separator + subfolder; this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null, writeWithoutMmap); } this.byteBufferItem = ByteBuffer.allocate(CQ_STORE_UNIT_SIZE); } public BatchConsumeQueue( final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore messageStore, final String subfolder) { this(topic, queueId, storePath, mappedFileSize, messageStore, (ConsumeQueueStore) messageStore.getQueueStore(), subfolder); } public BatchConsumeQueue( final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore defaultMessageStore) { this(topic, queueId, storePath, mappedFileSize, defaultMessageStore, StringUtils.EMPTY); } @Override public boolean load() { boolean result = this.mappedFileQueue.load(); log.info("Load batch consume queue {}-{} {} {}", topic, queueId, result ? "OK" : "Failed", mappedFileQueue.getMappedFiles().size()); return result; } protected void doRefreshCache(Function offsetFunction) { if (!this.messageStore.getMessageStoreConfig().isSearchBcqByCacheEnable()) { return; } ConcurrentSkipListMap newOffsetCache = new ConcurrentSkipListMap<>(); ConcurrentSkipListMap newTimeCache = new ConcurrentSkipListMap<>(); List mappedFiles = mappedFileQueue.getMappedFiles(); // iterate all BCQ files for (int i = 0; i < mappedFiles.size(); i++) { MappedFile bcq = mappedFiles.get(i); if (isNewFile(bcq)) { continue; } BatchOffsetIndex offset = offsetFunction.apply(bcq); if (offset == null) { continue; } newOffsetCache.put(offset.getMsgOffset(), offset.getMappedFile()); newTimeCache.put(offset.getStoreTimestamp(), offset.getMappedFile()); } this.offsetCache = newOffsetCache; this.timeCache = newTimeCache; log.info("refreshCache for BCQ [Topic: {}, QueueId: {}]." + "offsetCacheSize: {}, minCachedMsgOffset: {}, maxCachedMsgOffset: {}, " + "timeCacheSize: {}, minCachedTime: {}, maxCachedTime: {}", this.topic, this.queueId, this.offsetCache.size(), this.offsetCache.firstEntry(), this.offsetCache.lastEntry(), this.timeCache.size(), this.timeCache.firstEntry(), this.timeCache.lastEntry()); } protected void refreshCache() { doRefreshCache(m -> getMinMsgOffset(m, false, true)); } private void destroyCache() { this.offsetCache.clear(); this.timeCache.clear(); log.info("BCQ [Topic: {}, QueueId: {}]. Cache destroyed", this.topic, this.queueId); } protected void cacheBcq(MappedFile bcq) { try { BatchOffsetIndex min = getMinMsgOffset(bcq, false, true); this.offsetCache.put(min.getMsgOffset(), min.getMappedFile()); this.timeCache.put(min.getStoreTimestamp(), min.getMappedFile()); } catch (Exception e) { log.error("Failed caching offset and time on BCQ [Topic: {}, QueueId: {}, File: {}]", this.topic, this.queueId, bcq); } } protected boolean isNewFile(MappedFile mappedFile) { return mappedFile.getReadPosition() < CQ_STORE_UNIT_SIZE; } protected MappedFile searchOffsetFromCache(long msgOffset) { Map.Entry floorEntry = this.offsetCache.floorEntry(msgOffset); if (floorEntry == null) { // the offset is too small. return null; } else { return floorEntry.getValue(); } } private MappedFile searchTimeFromCache(long time) { Map.Entry floorEntry = this.timeCache.floorEntry(time); if (floorEntry == null) { // the timestamp is too small. so we decide to result first BCQ file. return this.mappedFileQueue.getFirstMappedFile(); } else { return floorEntry.getValue(); } } @Override public void recover() { final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { int index = mappedFiles.size() - 3; if (index < 0) index = 0; int mappedFileSizeLogics = this.mappedFileSize; MappedFile mappedFile = mappedFiles.get(index); ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset = 0; while (true) { for (int i = 0; i < mappedFileSizeLogics; i += CQ_STORE_UNIT_SIZE) { byteBuffer.position(i); long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); byteBuffer.getLong();//tagscode byteBuffer.getLong();//timestamp long msgBaseOffset = byteBuffer.getLong(); short batchSize = byteBuffer.getShort(); if (offset >= 0 && size > 0 && msgBaseOffset >= 0 && batchSize > 0) { mappedFileOffset = i + CQ_STORE_UNIT_SIZE; this.maxMsgPhyOffsetInCommitLog = offset; } else { log.info("Recover current batch consume queue file over, file:{} offset:{} size:{} msgBaseOffset:{} batchSize:{} mappedFileOffset:{}", mappedFile.getFileName(), offset, size, msgBaseOffset, batchSize, mappedFileOffset); break; } } if (mappedFileOffset == mappedFileSizeLogics) { index++; if (index >= mappedFiles.size()) { log.info("Recover last batch consume queue file over, last mapped file:{} ", mappedFile.getFileName()); break; } else { mappedFile = mappedFiles.get(index); byteBuffer = mappedFile.sliceByteBuffer(); processOffset = mappedFile.getFileFromOffset(); mappedFileOffset = 0; log.info("Recover next batch consume queue file: " + mappedFile.getFileName()); } } else { log.info("Recover current batch consume queue file over:{} processOffset:{}", mappedFile.getFileName(), processOffset + mappedFileOffset); break; } } processOffset += mappedFileOffset; this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); reviseMaxAndMinOffsetInQueue(); } } void reviseMinOffsetInQueue() { MappedFile firstMappedFile = this.mappedFileQueue.getFirstMappedFile(); if (null == firstMappedFile) { maxOffsetInQueue = 0; minOffsetInQueue = -1; minLogicOffset = -1; log.info("reviseMinOffsetInQueue found firstMappedFile null, topic:{} queue:{}", topic, queueId); return; } minLogicOffset = firstMappedFile.getFileFromOffset(); BatchOffsetIndex min = getMinMsgOffset(firstMappedFile, false, false); minOffsetInQueue = null == min ? -1 : min.getMsgOffset(); } void reviseMaxOffsetInQueue() { MappedFile lastMappedFile = this.mappedFileQueue.getLastMappedFile(); BatchOffsetIndex max = getMaxMsgOffset(lastMappedFile, true, false); if (null == max && this.mappedFileQueue.getMappedFiles().size() >= 2) { MappedFile lastTwoMappedFile = this.mappedFileQueue.getMappedFiles().get(this.mappedFileQueue.getMappedFiles().size() - 2); max = getMaxMsgOffset(lastTwoMappedFile, true, false); } maxOffsetInQueue = (null == max) ? 0 : max.getMsgOffset() + max.getBatchSize(); } void reviseMaxAndMinOffsetInQueue() { reviseMinOffsetInQueue(); reviseMaxOffsetInQueue(); } @Override public long getMaxPhysicOffset() { return maxMsgPhyOffsetInCommitLog; } @Override public long getMinLogicOffset() { return minLogicOffset; } @Override public ReferredIterator iterateFrom(long startOffset) { SelectMappedBufferResult sbr = getBatchMsgIndexBuffer(startOffset); if (sbr == null) { return null; } return new BatchConsumeQueueIterator(sbr); } @Override public ReferredIterator iterateFrom(long startIndex, int count) { return iterateFrom(startIndex); } @Override public CqUnit get(long offset) { ReferredIterator it = iterateFrom(offset); if (it == null) { return null; } return it.nextAndRelease(); } @Override public Pair getCqUnitAndStoreTime(long index) { CqUnit cqUnit = get(index); Long messageStoreTime = this.consumeQueueStore.getStoreTime(cqUnit); return new Pair<>(cqUnit, messageStoreTime); } @Override public Pair getEarliestUnitAndStoreTime() { CqUnit cqUnit = getEarliestUnit(); Long messageStoreTime = this.consumeQueueStore.getStoreTime(cqUnit); return new Pair<>(cqUnit, messageStoreTime); } @Override public CqUnit getEarliestUnit() { return get(minOffsetInQueue); } @Override public CqUnit getLatestUnit() { return get(maxOffsetInQueue - 1); } @Override public long getLastOffset() { CqUnit latestUnit = getLatestUnit(); return latestUnit.getPos() + latestUnit.getSize(); } @Override public boolean isFirstFileAvailable() { MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile(); if (mappedFile != null) { return mappedFile.isAvailable(); } return false; } @Override public boolean isFirstFileExist() { MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile(); return mappedFile != null; } @Override public void truncateDirtyLogicFiles(long phyOffset) { long oldMinOffset = minOffsetInQueue; long oldMaxOffset = maxOffsetInQueue; int logicFileSize = this.mappedFileSize; this.maxMsgPhyOffsetInCommitLog = phyOffset - 1; boolean stop = false; while (!stop) { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile != null) { ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); mappedFile.setWrotePosition(0); mappedFile.setCommittedPosition(0); mappedFile.setFlushedPosition(0); for (int i = 0; i < logicFileSize; i += CQ_STORE_UNIT_SIZE) { byteBuffer.position(i); long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); byteBuffer.getLong();//tagscode byteBuffer.getLong();//timestamp long msgBaseOffset = byteBuffer.getLong(); short batchSize = byteBuffer.getShort(); if (0 == i) { if (offset >= phyOffset) { this.mappedFileQueue.deleteLastMappedFile(); break; } else { int pos = i + CQ_STORE_UNIT_SIZE; mappedFile.setWrotePosition(pos); mappedFile.setCommittedPosition(pos); mappedFile.setFlushedPosition(pos); this.maxMsgPhyOffsetInCommitLog = offset; } } else { if (offset >= 0 && size > 0 && msgBaseOffset >= 0 && batchSize > 0) { if (offset >= phyOffset) { stop = true; break; } int pos = i + CQ_STORE_UNIT_SIZE; mappedFile.setWrotePosition(pos); mappedFile.setCommittedPosition(pos); mappedFile.setFlushedPosition(pos); this.maxMsgPhyOffsetInCommitLog = offset; if (pos == logicFileSize) { stop = true; break; } } else { stop = true; break; } } } } else { break; } } reviseMaxAndMinOffsetInQueue(); log.info("Truncate batch logic file topic={} queue={} oldMinOffset={} oldMaxOffset={} minOffset={} maxOffset={} maxPhyOffsetHere={} maxPhyOffsetThere={}", topic, queueId, oldMinOffset, oldMaxOffset, minOffsetInQueue, maxOffsetInQueue, maxMsgPhyOffsetInCommitLog, phyOffset); } @Override public boolean flush(final int flushLeastPages) { boolean result = this.mappedFileQueue.flush(flushLeastPages); return result; } @Override public int deleteExpiredFile(long minCommitLogPos) { int cnt = this.mappedFileQueue.deleteExpiredFileByOffset(minCommitLogPos, CQ_STORE_UNIT_SIZE); this.correctMinOffset(minCommitLogPos); return cnt; } @Override public void correctMinOffset(long phyMinOffset) { reviseMinOffsetInQueue(); refreshCache(); long oldMinOffset = minOffsetInQueue; MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile(); if (mappedFile != null) { SelectMappedBufferResult result = mappedFile.selectMappedBuffer(0); if (result != null) { try { int startPos = result.getByteBuffer().position(); for (int i = 0; i < result.getSize(); i += BatchConsumeQueue.CQ_STORE_UNIT_SIZE) { result.getByteBuffer().position(startPos + i); long offsetPy = result.getByteBuffer().getLong(); result.getByteBuffer().getInt(); //size result.getByteBuffer().getLong();//tagscode result.getByteBuffer().getLong();//timestamp long msgBaseOffset = result.getByteBuffer().getLong(); short batchSize = result.getByteBuffer().getShort(); if (offsetPy < phyMinOffset) { this.minOffsetInQueue = msgBaseOffset + batchSize; } else { break; } } } catch (Exception e) { log.error("Exception thrown when correctMinOffset", e); } finally { result.release(); } } else { /** * It will go to here under two conditions: 1. the files number is 1, and it has no data 2. the pull process hold the cq reference, and release it just the moment */ log.warn("Correct min offset found null cq file topic:{} queue:{} files:{} minOffset:{} maxOffset:{}", topic, queueId, this.mappedFileQueue.getMappedFiles().size(), minOffsetInQueue, maxOffsetInQueue); } } if (oldMinOffset != this.minOffsetInQueue) { log.info("BatchCQ Compute new minOffset:{} oldMinOffset{} topic:{} queue:{}", minOffsetInQueue, oldMinOffset, topic, queueId); } } @Override public void putMessagePositionInfoWrapper(DispatchRequest request) { final int maxRetries = 30; boolean canWrite = this.messageStore.getRunningFlags().isCQWriteable(); if (request.getMsgBaseOffset() < 0 || request.getBatchSize() < 0) { log.warn("[NOTIFYME]unexpected dispatch request in batch consume queue topic:{} queue:{} offset:{}", topic, queueId, request.getCommitLogOffset()); return; } for (int i = 0; i < maxRetries && canWrite; i++) { boolean result = this.putBatchMessagePositionInfo(request.getCommitLogOffset(), request.getMsgSize(), request.getTagsCode(), request.getStoreTimestamp(), request.getMsgBaseOffset(), request.getBatchSize()); if (result) { if (BrokerRole.SLAVE == this.messageStore.getMessageStoreConfig().getBrokerRole()) { this.messageStore.getStoreCheckpoint().setPhysicMsgTimestamp(request.getStoreTimestamp()); } this.messageStore.getStoreCheckpoint().setTmpLogicsMsgTimestamp(request.getStoreTimestamp()); this.messageStore.getStoreCheckpoint().setTmpLogicsPhysicalOffset(request.getCommitLogOffset()); return; } else { // XXX: warn and notify me log.warn("[NOTIFYME]put commit log position info to batch consume queue " + topic + ":" + queueId + " " + request.getCommitLogOffset() + " failed, retry " + i + " times"); try { Thread.sleep(1000); } catch (InterruptedException e) { log.warn("", e); } } } // XXX: warn and notify me log.error("[NOTIFYME]batch consume queue can not write, {} {}", this.topic, this.queueId); this.messageStore.getRunningFlags().makeLogicsQueueError(); } @Override public void assignQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg) { String topicQueueKey = getTopic() + "-" + getQueueId(); long queueOffset = queueOffsetOperator.getBatchQueueOffset(topicQueueKey); if (MessageSysFlag.check(msg.getSysFlag(), MessageSysFlag.INNER_BATCH_FLAG)) { MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_BASE, String.valueOf(queueOffset)); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); } msg.setQueueOffset(queueOffset); } @Override public void increaseQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg, short messageNum) { String topicQueueKey = getTopic() + "-" + getQueueId(); queueOffsetOperator.increaseBatchQueueOffset(topicQueueKey, messageNum); } public boolean putBatchMessagePositionInfo(final long offset, final int size, final long tagsCode, final long storeTime, final long msgBaseOffset, final short batchSize) { if (offset <= this.maxMsgPhyOffsetInCommitLog) { if (System.currentTimeMillis() % 1000 == 0) { log.warn("Build batch consume queue repeatedly, maxMsgPhyOffsetInCommitLog:{} offset:{} Topic: {} QID: {}", maxMsgPhyOffsetInCommitLog, offset, this.topic, this.queueId); } return true; } long behind = System.currentTimeMillis() - storeTime; if (behind > 10000 && System.currentTimeMillis() % 10000 == 0) { String flag = "LEVEL" + (behind / 10000); log.warn("Reput behind {} topic:{} queue:{} offset:{} behind:{}", flag, topic, queueId, offset, behind); } this.byteBufferItem.flip(); this.byteBufferItem.limit(CQ_STORE_UNIT_SIZE); this.byteBufferItem.putLong(offset); this.byteBufferItem.putInt(size); this.byteBufferItem.putLong(tagsCode); this.byteBufferItem.putLong(storeTime); this.byteBufferItem.putLong(msgBaseOffset); this.byteBufferItem.putShort(batchSize); this.byteBufferItem.putInt(INVALID_POS); this.byteBufferItem.putInt(0); // 4 bytes reserved MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(this.mappedFileQueue.getMaxOffset()); if (mappedFile != null) { boolean isNewFile = isNewFile(mappedFile); boolean appendRes; if (messageStore.getMessageStoreConfig().isPutConsumeQueueDataByFileChannel()) { appendRes = mappedFile.appendMessageUsingFileChannel(this.byteBufferItem.array()); } else { appendRes = mappedFile.appendMessage(this.byteBufferItem.array()); } if (appendRes) { maxMsgPhyOffsetInCommitLog = offset; maxOffsetInQueue = msgBaseOffset + batchSize; //only the first time need to correct the minOffsetInQueue //the other correctness is done in correctLogicMinoffsetService if (mappedFile.isFirstCreateInQueue() && minOffsetInQueue == -1) { reviseMinOffsetInQueue(); } if (isNewFile) { // cache new file this.cacheBcq(mappedFile); } } return appendRes; } return false; } protected BatchOffsetIndex getMinMsgOffset(MappedFile mappedFile, boolean getBatchSize, boolean getStoreTime) { if (mappedFile.getReadPosition() < CQ_STORE_UNIT_SIZE) { return null; } return getBatchOffsetIndexByPos(mappedFile, 0, getBatchSize, getStoreTime); } protected BatchOffsetIndex getBatchOffsetIndexByPos(MappedFile mappedFile, int pos, boolean getBatchSize, boolean getStoreTime) { SelectMappedBufferResult sbr = mappedFile.selectMappedBuffer(pos); try { return new BatchOffsetIndex(mappedFile, pos, sbr.getByteBuffer().getLong(MSG_BASE_OFFSET_INDEX), getBatchSize ? sbr.getByteBuffer().getShort(MSG_BATCH_SIZE_INDEX) : 0, getStoreTime ? sbr.getByteBuffer().getLong(MSG_STORE_TIME_OFFSET_INDEX) : 0); } finally { if (sbr != null) { sbr.release(); } } } protected BatchOffsetIndex getMaxMsgOffset(MappedFile mappedFile, boolean getBatchSize, boolean getStoreTime) { if (mappedFile == null || mappedFile.getReadPosition() < CQ_STORE_UNIT_SIZE) { return null; } int pos = mappedFile.getReadPosition() - CQ_STORE_UNIT_SIZE; return getBatchOffsetIndexByPos(mappedFile, pos, getBatchSize, getStoreTime); } private static int ceil(int pos) { return (pos / CQ_STORE_UNIT_SIZE) * CQ_STORE_UNIT_SIZE; } /** * Gets SelectMappedBufferResult by batch-message offset * Node: the caller is responsible for the release of SelectMappedBufferResult * @param msgOffset * @return SelectMappedBufferResult */ public SelectMappedBufferResult getBatchMsgIndexBuffer(final long msgOffset) { if (msgOffset >= maxOffsetInQueue) { return null; } MappedFile targetBcq; BatchOffsetIndex targetMinOffset; // first check the last bcq file MappedFile lastBcq = mappedFileQueue.getLastMappedFile(); BatchOffsetIndex minForLastBcq = getMinMsgOffset(lastBcq, false, false); if (null != minForLastBcq && minForLastBcq.getMsgOffset() <= msgOffset) { // found, it's the last bcq. targetBcq = lastBcq; targetMinOffset = minForLastBcq; } else { boolean searchBcqByCacheEnable = this.messageStore.getMessageStoreConfig().isSearchBcqByCacheEnable(); if (searchBcqByCacheEnable) { // it's not the last BCQ file, so search it through cache. targetBcq = this.searchOffsetFromCache(msgOffset); // not found in cache if (targetBcq == null) { MappedFile firstBcq = mappedFileQueue.getFirstMappedFile(); BatchOffsetIndex minForFirstBcq = getMinMsgOffset(firstBcq, false, false); if (minForFirstBcq != null && minForFirstBcq.getMsgOffset() <= msgOffset && msgOffset < minForLastBcq.getMsgOffset()) { // old search logic targetBcq = this.searchOffsetFromFiles(msgOffset); } log.warn("cache is not working on BCQ [Topic: {}, QueueId: {}] for msgOffset: {}, targetBcq: {}", this.topic, this.queueId, msgOffset, targetBcq); } } else { // old search logic targetBcq = this.searchOffsetFromFiles(msgOffset); } if (targetBcq == null) { return null; } targetMinOffset = getMinMsgOffset(targetBcq, false, false); } BatchOffsetIndex targetMaxOffset = getMaxMsgOffset(targetBcq, false, false); if (null == targetMinOffset || null == targetMaxOffset) { return null; } // then use binary search to find the indexed position SelectMappedBufferResult sbr = targetMinOffset.getMappedFile().selectMappedBuffer(0); try { ByteBuffer byteBuffer = sbr.getByteBuffer(); int left = targetMinOffset.getIndexPos(), right = targetMaxOffset.getIndexPos(); int mid = binarySearch(byteBuffer, left, right, CQ_STORE_UNIT_SIZE, MSG_BASE_OFFSET_INDEX, msgOffset); if (mid != -1) { // return a buffer that needs to be released manually. return targetMinOffset.getMappedFile().selectMappedBuffer(mid); } } finally { sbr.release(); } return null; } public MappedFile searchOffsetFromFiles(long msgOffset) { MappedFile targetBcq = null; // find the mapped file one by one reversely int mappedFileNum = this.mappedFileQueue.getMappedFiles().size(); for (int i = mappedFileNum - 1; i >= 0; i--) { MappedFile mappedFile = mappedFileQueue.getMappedFiles().get(i); BatchOffsetIndex tmpMinMsgOffset = getMinMsgOffset(mappedFile, false, false); if (null != tmpMinMsgOffset && tmpMinMsgOffset.getMsgOffset() <= msgOffset) { targetBcq = mappedFile; break; } } return targetBcq; } /** * Find the message whose timestamp is the smallest, greater than or equal to the given time. * * @param timestamp * @return */ @Deprecated @Override public long getOffsetInQueueByTime(final long timestamp) { return getOffsetInQueueByTime(timestamp, BoundaryType.LOWER); } @Override public long getOffsetInQueueByTime(long timestamp, BoundaryType boundaryType) { MappedFile targetBcq; BatchOffsetIndex targetMinOffset; // first check the last bcq MappedFile lastBcq = mappedFileQueue.getLastMappedFile(); BatchOffsetIndex minForLastBcq = getMinMsgOffset(lastBcq, false, true); if (null != minForLastBcq && minForLastBcq.getStoreTimestamp() <= timestamp) { // found, it's the last bcq. targetBcq = lastBcq; targetMinOffset = minForLastBcq; } else { boolean searchBcqByCacheEnable = this.messageStore.getMessageStoreConfig().isSearchBcqByCacheEnable(); if (searchBcqByCacheEnable) { // it's not the last BCQ file, so search it through cache. targetBcq = this.searchTimeFromCache(timestamp); if (targetBcq == null) { // not found in cache MappedFile firstBcq = mappedFileQueue.getFirstMappedFile(); BatchOffsetIndex minForFirstBcq = getMinMsgOffset(firstBcq, false, true); if (minForFirstBcq != null && minForFirstBcq.getStoreTimestamp() <= timestamp && timestamp < minForLastBcq.getStoreTimestamp()) { // old search logic targetBcq = this.searchTimeFromFiles(timestamp); } log.warn("cache is not working on BCQ [Topic: {}, QueueId: {}] for timestamp: {}, targetBcq: {}", this.topic, this.queueId, timestamp, targetBcq); } } else { // old search logic targetBcq = this.searchTimeFromFiles(timestamp); } if (targetBcq == null) { return -1; } targetMinOffset = getMinMsgOffset(targetBcq, false, true); } BatchOffsetIndex targetMaxOffset = getMaxMsgOffset(targetBcq, false, true); if (null == targetMinOffset || null == targetMaxOffset) { return -1; } //then use binary search to find the indexed position SelectMappedBufferResult sbr = targetMinOffset.getMappedFile().selectMappedBuffer(0); try { ByteBuffer byteBuffer = sbr.getByteBuffer(); int left = targetMinOffset.getIndexPos(), right = targetMaxOffset.getIndexPos(); long maxQueueTimestamp = byteBuffer.getLong(right + MSG_STORE_TIME_OFFSET_INDEX); if (timestamp >= maxQueueTimestamp) { return byteBuffer.getLong(right + MSG_BASE_OFFSET_INDEX); } int mid = binarySearchRight(byteBuffer, left, right, CQ_STORE_UNIT_SIZE, MSG_STORE_TIME_OFFSET_INDEX, timestamp, boundaryType); if (mid != -1) { return byteBuffer.getLong(mid + MSG_BASE_OFFSET_INDEX); } } finally { sbr.release(); } return -1; } private MappedFile searchTimeFromFiles(long timestamp) { MappedFile targetBcq = null; int mappedFileNum = this.mappedFileQueue.getMappedFiles().size(); for (int i = mappedFileNum - 1; i >= 0; i--) { MappedFile mappedFile = mappedFileQueue.getMappedFiles().get(i); BatchOffsetIndex tmpMinMsgOffset = getMinMsgOffset(mappedFile, false, true); if (tmpMinMsgOffset == null) { //Maybe the new file continue; } BatchOffsetIndex tmpMaxMsgOffset = getMaxMsgOffset(mappedFile, false, true); //Here should not be null if (tmpMaxMsgOffset == null) { break; } if (tmpMaxMsgOffset.getStoreTimestamp() >= timestamp) { if (tmpMinMsgOffset.getStoreTimestamp() <= timestamp) { targetBcq = mappedFile; break; } else { if (i - 1 < 0) { //This is the first file targetBcq = mappedFile; break; } else { //The min timestamp of this file is larger than the given timestamp, so check the next file continue; } } } else { //The max timestamp of this file is smaller than the given timestamp, so double check the previous file if (i + 1 <= mappedFileNum - 1) { mappedFile = mappedFileQueue.getMappedFiles().get(i + 1); targetBcq = mappedFile; break; } else { //There is no timestamp larger than the given timestamp break; } } } return targetBcq; } /** * Find the offset of which the value is equal or larger than the given targetValue. * If there are many values equal to the target, then return the lowest offset if boundaryType is LOWER while * return the highest offset if boundaryType is UPPER. */ public static int binarySearchRight(ByteBuffer byteBuffer, int left, int right, final int unitSize, final int unitShift, long targetValue, BoundaryType boundaryType) { int mid = -1; while (left <= right) { mid = ceil((left + right) / 2); long tmpValue = byteBuffer.getLong(mid + unitShift); if (mid == right) { //Means left and the right are the same if (tmpValue >= targetValue) { return mid; } else { return -1; } } else if (mid == left) { //Means the left + unitSize = right if (tmpValue >= targetValue) { return mid; } else { left = mid + unitSize; } } else { //mid is actually in the mid switch (boundaryType) { case LOWER: if (tmpValue < targetValue) { left = mid + unitSize; } else { right = mid; } break; case UPPER: if (tmpValue <= targetValue) { left = mid; } else { right = mid - unitSize; } break; default: log.warn("Unknown boundary type"); return -1; } } } return -1; } /** * Here is vulnerable, the min value of the bytebuffer must be smaller or equal then the given value. * Otherwise, it may get -1 */ protected int binarySearch(ByteBuffer byteBuffer, int left, int right, final int unitSize, final int unitShift, long targetValue) { int maxRight = right; int mid = -1; while (left <= right) { mid = ceil((left + right) / 2); long tmpValue = byteBuffer.getLong(mid + unitShift); if (tmpValue == targetValue) { return mid; } if (tmpValue > targetValue) { right = mid - unitSize; } else { if (mid == left) { //the binary search is converging to the left, so maybe the one on the right of mid is the exactly correct one if (mid + unitSize <= maxRight && byteBuffer.getLong(mid + unitSize + unitShift) <= targetValue) { return mid + unitSize; } else { return mid; } } else { left = mid; } } } return -1; } static class BatchConsumeQueueIterator implements ReferredIterator { private SelectMappedBufferResult sbr; private int relativePos = 0; public BatchConsumeQueueIterator(SelectMappedBufferResult sbr) { this.sbr = sbr; if (sbr != null && sbr.getByteBuffer() != null) { relativePos = sbr.getByteBuffer().position(); } } @Override public boolean hasNext() { if (sbr == null || sbr.getByteBuffer() == null) { return false; } return sbr.getByteBuffer().hasRemaining(); } @Override public CqUnit next() { if (!hasNext()) { return null; } ByteBuffer tmpBuffer = sbr.getByteBuffer().slice(); tmpBuffer.position(MSG_COMPACT_OFFSET_INDEX); ByteBuffer compactOffsetStoreBuffer = tmpBuffer.slice(); compactOffsetStoreBuffer.limit(MSG_COMPACT_OFFSET_LENGTH); int relativePos = sbr.getByteBuffer().position(); long offsetPy = sbr.getByteBuffer().getLong(); int sizePy = sbr.getByteBuffer().getInt(); long tagsCode = sbr.getByteBuffer().getLong(); //tagscode sbr.getByteBuffer().getLong();//timestamp long msgBaseOffset = sbr.getByteBuffer().getLong(); short batchSize = sbr.getByteBuffer().getShort(); int compactedOffset = sbr.getByteBuffer().getInt(); sbr.getByteBuffer().position(relativePos + CQ_STORE_UNIT_SIZE); return new CqUnit(msgBaseOffset, offsetPy, sizePy, tagsCode, batchSize, compactedOffset, compactOffsetStoreBuffer); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } @Override public void release() { if (sbr != null) { sbr.release(); sbr = null; } } @Override public CqUnit nextAndRelease() { try { return next(); } finally { release(); } } } @Override public String getTopic() { return topic; } @Override public int getQueueId() { return queueId; } @Override public CQType getCQType() { return CQType.BatchCQ; } @Override public long getTotalSize() { return this.mappedFileQueue.getTotalFileSize(); } @Override public int getUnitSize() { return CQ_STORE_UNIT_SIZE; } @Override public void destroy() { this.maxMsgPhyOffsetInCommitLog = -1; this.minOffsetInQueue = -1; this.maxOffsetInQueue = 0; this.mappedFileQueue.destroy(); this.destroyCache(); } @Override public long getMessageTotalInQueue() { return this.getMaxOffsetInQueue() - this.getMinOffsetInQueue(); } @Override public long rollNextFile(long nextBeginOffset) { return 0; } /** * Batch msg offset (deep logic offset) * * @return max deep offset */ @Override public long getMaxOffsetInQueue() { return maxOffsetInQueue; } @Override public long getMinOffsetInQueue() { return minOffsetInQueue; } @Override public void checkSelf() { mappedFileQueue.checkSelf(); } @Override public void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) { mappedFileQueue.swapMap(reserveNum, forceSwapIntervalMs, normalSwapIntervalMs); } @Override public void cleanSwappedMap(long forceCleanSwapIntervalMs) { mappedFileQueue.cleanSwappedMap(forceCleanSwapIntervalMs); } public MappedFileQueue getMappedFileQueue() { return mappedFileQueue; } @Override public long estimateMessageCount(long from, long to, MessageFilter filter) { // transfer message offset to physical offset SelectMappedBufferResult firstMappedFileBuffer = getBatchMsgIndexBuffer(from); if (firstMappedFileBuffer == null) { return -1; } long physicalOffsetFrom = firstMappedFileBuffer.getStartOffset(); SelectMappedBufferResult lastMappedFileBuffer = getBatchMsgIndexBuffer(to); if (lastMappedFileBuffer == null) { return -1; } long physicalOffsetTo = lastMappedFileBuffer.getStartOffset(); List mappedFiles = mappedFileQueue.range(physicalOffsetFrom, physicalOffsetTo); if (mappedFiles.isEmpty()) { return -1; } boolean sample = false; long match = 0; long matchCqUnitCount = 0; long raw = 0; long scanCqUnitCount = 0; for (MappedFile mappedFile : mappedFiles) { int start = 0; int len = mappedFile.getFileSize(); // calculate start and len for first segment and last segment to reduce scanning // first file segment if (mappedFile.getFileFromOffset() <= physicalOffsetFrom) { start = (int) (physicalOffsetFrom - mappedFile.getFileFromOffset()); if (mappedFile.getFileFromOffset() + mappedFile.getFileSize() >= physicalOffsetTo) { // current mapped file covers search range completely. len = (int) (physicalOffsetTo - physicalOffsetFrom); } else { len = mappedFile.getFileSize() - start; } } // last file segment if (0 == start && mappedFile.getFileFromOffset() + mappedFile.getFileSize() > physicalOffsetTo) { len = (int) (physicalOffsetTo - mappedFile.getFileFromOffset()); } // select partial data to scan SelectMappedBufferResult slice = mappedFile.selectMappedBuffer(start, len); if (null != slice) { try { ByteBuffer buffer = slice.getByteBuffer(); int current = 0; while (current < len) { // skip physicalOffset and message length fields. buffer.position(current + MSG_TAG_OFFSET_INDEX); long tagCode = buffer.getLong(); buffer.position(current + MSG_BATCH_SIZE_INDEX); long batchSize = buffer.getShort(); if (filter.isMatchedByConsumeQueue(tagCode, null)) { match += batchSize; matchCqUnitCount++; } raw += batchSize; scanCqUnitCount++; current += CQ_STORE_UNIT_SIZE; if (scanCqUnitCount >= messageStore.getMessageStoreConfig().getMaxConsumeQueueScan()) { sample = true; break; } if (matchCqUnitCount > messageStore.getMessageStoreConfig().getSampleCountThreshold()) { sample = true; break; } } } finally { slice.release(); } } // we have scanned enough entries, now is the time to return an educated guess. if (sample) { break; } } long result = match; if (sample) { if (0 == raw) { log.error("[BUG]. Raw should NOT be 0"); return 0; } result = (long) (match * (to - from) * 1.0 / raw); } log.debug("Result={}, raw={}, match={}, sample={}", result, raw, match, sample); return result; } @Override public void initializeWithOffset(long offset, long minPhyOffset) { // not support now } @Override public boolean shutdown() { return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/BatchOffsetIndex.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.store.logfile.MappedFile; public class BatchOffsetIndex { private final MappedFile mappedFile; private final int indexPos; private final long msgOffset; private final short batchSize; private final long storeTimestamp; public BatchOffsetIndex(MappedFile file, int pos, long msgOffset, short size, long storeTimestamp) { mappedFile = file; indexPos = pos; this.msgOffset = msgOffset; batchSize = size; this.storeTimestamp = storeTimestamp; } public MappedFile getMappedFile() { return mappedFile; } public int getIndexPos() { return indexPos; } public long getMsgOffset() { return msgOffset; } public short getBatchSize() { return batchSize; } public long getStoreTimestamp() { return storeTimestamp; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/CombineConsumeQueueStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import com.alibaba.fastjson2.JSON; import com.google.common.annotations.VisibleForTesting; import java.util.Arrays; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.CheckRocksdbCqWriteResult; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.StoreType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.exception.StoreException; import org.rocksdb.RocksDBException; public class CombineConsumeQueueStore implements ConsumeQueueStoreInterface { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger BROKER_LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final DefaultMessageStore messageStore; private final MessageStoreConfig messageStoreConfig; // Inner consume queue store. private final LinkedList innerConsumeQueueStoreList = new LinkedList<>(); private final ConsumeQueueStore consumeQueueStore; private final RocksDBConsumeQueueStore rocksDBConsumeQueueStore; // current read consume queue store. private final AbstractConsumeQueueStore currentReadStore; // consume queue store for assign offset and increase offset. private final AbstractConsumeQueueStore assignOffsetStore; /** * ConsumeQueueStore recovers through commitlog dispatch, so it needs to search which file in the commitLog to * start recovery from. It might not be possible for all inner consumeQueueStores in CombineConsumeQueueStore to * fully recover (for example, a newly consumeQueueStore needs to start dispatch from the first file, which * could be very time-consuming). *

    * However, we need to ensure that assignOffsetStore can be fully recovered to guarantee the correctness of * commitlog. When assignOffsetStore can be fully recovered but other stores cannot, we need to use * extraSearchCommitLogFilesForRecovery to control whether to continue searching forward for positions that might * satisfy the recovery of other stores. */ private final AtomicInteger extraSearchCommitLogFilesForRecovery; public CombineConsumeQueueStore(DefaultMessageStore messageStore) { this.messageStore = messageStore; this.messageStoreConfig = messageStore.getMessageStoreConfig(); extraSearchCommitLogFilesForRecovery = new AtomicInteger(messageStoreConfig.getCombineCQMaxExtraSearchCommitLogFiles()); Set loadingConsumeQueueTypeSet = StoreType.fromString(messageStoreConfig.getCombineCQLoadingCQTypes()); if (loadingConsumeQueueTypeSet.isEmpty()) { throw new IllegalArgumentException("CombineConsumeQueueStore loadingCQTypes is empty"); } if (loadingConsumeQueueTypeSet.contains(StoreType.DEFAULT)) { this.consumeQueueStore = new ConsumeQueueStore(messageStore); this.innerConsumeQueueStoreList.add(consumeQueueStore); } else { this.consumeQueueStore = null; } if (loadingConsumeQueueTypeSet.contains(StoreType.DEFAULT_ROCKSDB)) { this.rocksDBConsumeQueueStore = new RocksDBConsumeQueueStore(messageStore); this.innerConsumeQueueStoreList.add(rocksDBConsumeQueueStore); } else { this.rocksDBConsumeQueueStore = null; } if (innerConsumeQueueStoreList.isEmpty()) { throw new IllegalArgumentException("CombineConsumeQueueStore loadingCQTypes is empty"); } assignOffsetStore = getInnerStoreByString(messageStoreConfig.getCombineAssignOffsetCQType()); if (assignOffsetStore == null) { log.error("CombineConsumeQueueStore chooseAssignOffsetStore fail, config={}", messageStoreConfig.getCombineAssignOffsetCQType()); throw new IllegalArgumentException("CombineConsumeQueue chooseAssignOffsetStore fail"); } currentReadStore = getInnerStoreByString(messageStoreConfig.getCombineCQPreferCQType()); if (currentReadStore == null) { log.error("CombineConsumeQueueStore choosePreferCQ fail, config={}", messageStoreConfig.getCombineCQPreferCQType()); throw new IllegalArgumentException("CombineConsumeQueue choosePreferCQ fail"); } log.info("CombineConsumeQueueStore init, consumeQueueStoreList={}, currentReadStore={}, assignOffsetStore={}", innerConsumeQueueStoreList, currentReadStore.getClass().getSimpleName(), assignOffsetStore.getClass().getSimpleName()); } @Override public boolean load() { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { if (!store.load()) { log.error("CombineConsumeQueueStore load fail, loadType={}", store.getClass().getSimpleName()); return false; } } log.info("CombineConsumeQueueStore load success"); return true; } @Override public void recover(boolean concurrently) throws RocksDBException { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.recover(concurrently); } log.info("CombineConsumeQueueStore recover success, concurrently={}", concurrently); } @Override public boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException { // make sure assignOffsetStore can be fully recovered if (!assignOffsetStore.isMappedFileMatchedRecover(phyOffset, storeTimestamp, recoverNormally)) { return false; } for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { if (store == assignOffsetStore || store.isMappedFileMatchedRecover(phyOffset, storeTimestamp, recoverNormally)) { continue; } // if other store is not matched for fully recovery, extraSearchCommitLogFilesForRecovery will minus 1 if (extraSearchCommitLogFilesForRecovery.getAndDecrement() <= 0) { // extraSearchCommitLogFilesForRecovery <= 0, only can read from assignOffsetStore if (assignOffsetStore != currentReadStore) { log.error("CombineConsumeQueueStore currentReadStore not satisfied readable conditions, assignOffsetStore={}, currentReadStore={}", assignOffsetStore.getClass().getSimpleName(), currentReadStore.getClass().getSimpleName()); throw new IllegalArgumentException(store.getClass().getSimpleName() + " not satisfied readable conditions, only can read from " + assignOffsetStore.getClass().getSimpleName()); } log.warn("CombineConsumeQueueStore can not recover all inner store, maybe some inner store start haven’t started before, store={}", store.getClass().getSimpleName()); return true; } else { return false; } } return true; } @Override public Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException { Long dispatchFromPhyOffset = assignOffsetStore.getDispatchFromPhyOffset(recoverNormally); for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { if (store == assignOffsetStore) { continue; } Long storeOffset = store.getDispatchFromPhyOffset(recoverNormally); if (storeOffset != null && dispatchFromPhyOffset != null && storeOffset < dispatchFromPhyOffset) { dispatchFromPhyOffset = storeOffset; } } return dispatchFromPhyOffset; } @Override public void start() { boolean success = false; try { success = verifyAndInitOffsetForAllStore(true); } catch (RocksDBException e) { log.error("CombineConsumeQueueStore checkAssignOffsetStore fail", e); } if (!success && assignOffsetStore != currentReadStore) { log.error("CombineConsumeQueueStore currentReadStore not satisfied readable conditions, " + "checkAssignOffsetResult={}, assignOffsetStore={}, currentReadStore={}", success, assignOffsetStore.getClass().getSimpleName(), currentReadStore.getClass().getSimpleName()); throw new RuntimeException("CombineConsumeQueueStore currentReadStore not satisfied readable conditions"); } for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.start(); } } public boolean verifyAndInitOffsetForAllStore(boolean initializeOffset) throws RocksDBException { if (innerConsumeQueueStoreList.size() <= 1) { return true; } boolean result = true; long minPhyOffset = this.messageStore.getCommitLog().getMinOffset(); // for each topic and queueId in assignOffsetStore for (Map.Entry> entry : assignOffsetStore.getConsumeQueueTable().entrySet()) { for (Map.Entry entry0 : entry.getValue().entrySet()) { String topic = entry.getKey(); int queueId = entry0.getKey(); long maxOffsetInAssign = entry0.getValue().getMaxOffsetInQueue(); for (AbstractConsumeQueueStore abstractConsumeQueueStore : innerConsumeQueueStoreList) { // skip compare self if (abstractConsumeQueueStore == assignOffsetStore) { continue; } ConsumeQueueInterface queue = abstractConsumeQueueStore.findOrCreateConsumeQueue(topic, queueId); long maxOffset0 = queue.getMaxOffsetInQueue(); if (maxOffsetInAssign == maxOffset0 || maxOffsetInAssign <= 0 && maxOffset0 <= 0) { continue; } if (maxOffset0 > 0) { log.error("CombineConsumeQueueStore checkAssignOffsetStore fail, topic={}, queueId={}, maxOffsetInAssign={}, otherCQ={}, maxOffset0={}", topic, queueId, maxOffsetInAssign, abstractConsumeQueueStore.getClass().getSimpleName(), maxOffset0); result = false; } if (initializeOffset) { queue.initializeWithOffset(maxOffsetInAssign, minPhyOffset); log.info("CombineConsumeQueueStore initialize offset in queue, topic={}, queueId={}, maxOffsetInAssign={}, otherCQ={}, maxOffset0={}, maxOffsetNew={}", topic, queueId, maxOffsetInAssign, abstractConsumeQueueStore.getClass().getSimpleName(), maxOffset0, queue.getMaxOffsetInQueue()); } } } } return result; } @Override public boolean shutdown() { boolean result = true; for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { if (!store.shutdown()) { result = false; } } return result; } @Override public void destroy(boolean loadAfterDestroy) { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.destroy(loadAfterDestroy); } } @Override public boolean deleteTopic(String topic) { boolean result = false; for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { if (store.deleteTopic(topic)) { result = true; } } return result; } @Override public void flush() throws StoreException { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.flush(); } } @Override public void cleanExpired(long minCommitLogOffset) { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.cleanExpired(minCommitLogOffset); } } @Override public void checkSelf() { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.checkSelf(); } if (messageStoreConfig.isCombineCQEnableCheckSelf()) { try { verifyAndInitOffsetForAllStore(false); } catch (RocksDBException e) { log.error("CombineConsumeQueueStore checkAssignOffsetStore fail in checkSelf", e); } CheckRocksdbCqWriteResult checkResult = doCheckCqWriteProgress(null, System.currentTimeMillis() - 10 * 60 * 1000, StoreType.DEFAULT, StoreType.DEFAULT_ROCKSDB); BROKER_LOG.info("checkRocksdbCqWriteProgress result: {}", JSON.toJSONString(checkResult)); } } @Override public void truncateDirty(long offsetToTruncate) throws RocksDBException { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.truncateDirty(offsetToTruncate); } } @Override public void putMessagePositionInfoWrapper(DispatchRequest request) throws RocksDBException { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.putMessagePositionInfoWrapper(request); } } @Override public ConcurrentMap> getConsumeQueueTable() { return currentReadStore.getConsumeQueueTable(); } @Override public void assignQueueOffset(MessageExtBrokerInner msg) throws RocksDBException { assignOffsetStore.assignQueueOffset(msg); } @Override public void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum) { assignOffsetStore.increaseQueueOffset(msg, messageNum); } @Override public void increaseLmqOffset(String topic, int queueId, short delta) throws ConsumeQueueException { assignOffsetStore.increaseLmqOffset(topic, queueId, delta); } @Override public long getLmqQueueOffset(String topic, int queueId) throws ConsumeQueueException { return assignOffsetStore.getLmqQueueOffset(topic, queueId); } @Override public void recoverOffsetTable(long minPhyOffset) { for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { store.recoverOffsetTable(minPhyOffset); } } @Override public Long getMaxOffset(String topic, int queueId) throws ConsumeQueueException { return currentReadStore.getMaxOffset(topic, queueId); } @Override public long getMinOffsetInQueue(String topic, int queueId) throws RocksDBException { return currentReadStore.getMinOffsetInQueue(topic, queueId); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) throws RocksDBException { return currentReadStore.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType); } @Override public ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId) { return currentReadStore.findOrCreateConsumeQueue(topic, queueId); } @Override public ConsumeQueueInterface getConsumeQueue(String topic, int queueId) { return currentReadStore.getConsumeQueue(topic, queueId); } @Override public long getTotalSize() { long result = 0; for (AbstractConsumeQueueStore store : innerConsumeQueueStoreList) { result += store.getTotalSize(); } return result; } @Override public int getLmqNum() { return currentReadStore.getLmqNum(); } @Override public boolean isLmqExist(String lmqTopic) { return currentReadStore.isLmqExist(lmqTopic); } public RocksDBConsumeQueueStore getRocksDBConsumeQueueStore() { return rocksDBConsumeQueueStore; } @VisibleForTesting public ConsumeQueueStore getConsumeQueueStore() { return consumeQueueStore; } @VisibleForTesting public AbstractConsumeQueueStore getCurrentReadStore() { return currentReadStore; } @VisibleForTesting public AbstractConsumeQueueStore getAssignOffsetStore() { return assignOffsetStore; } public CheckRocksdbCqWriteResult doCheckCqWriteProgress(String requestTopic, long checkStoreTime, StoreType baseStoreType, StoreType compareStoreType) { CheckRocksdbCqWriteResult result = new CheckRocksdbCqWriteResult(); AbstractConsumeQueueStore baseStore = getInnerStoreByStoreType(baseStoreType); AbstractConsumeQueueStore compareStore = getInnerStoreByStoreType(compareStoreType); if (baseStore == null || compareStore == null) { result.setCheckResult("baseStore or compareStore is null, no need check"); result.setCheckStatus(CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue()); return result; } ConcurrentMap> cqTable = baseStore.getConsumeQueueTable(); StringBuilder diffResult = new StringBuilder(); try { if (StringUtils.isNotBlank(requestTopic)) { boolean checkResult = processConsumeQueuesForTopic(cqTable.get(requestTopic), requestTopic, compareStore, diffResult, true, checkStoreTime); result.setCheckResult(diffResult.toString()); result.setCheckStatus(checkResult ? CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue() : CheckRocksdbCqWriteResult.CheckStatus.CHECK_NOT_OK.getValue()); return result; } int successNum = 0; int checkSize = 0; for (Map.Entry> topicEntry : cqTable.entrySet()) { boolean checkResult = processConsumeQueuesForTopic(topicEntry.getValue(), topicEntry.getKey(), compareStore, diffResult, false, checkStoreTime); successNum += checkResult ? 1 : 0; checkSize++; } // check all topic finish, all topic is ready, checkSize: 100, currentQueueNum: 110 -> ready (The currentQueueNum means when we do checking, new topics are added.) // check all topic finish, success/all : 89/100, currentQueueNum: 110 -> not ready boolean checkReady = successNum == checkSize; String checkResultString = checkReady ? String.format("all topic is ready, checkSize: %s, currentQueueNum: %s", checkSize, cqTable.size()) : String.format("success/all : %s/%s, currentQueueNum: %s", successNum, checkSize, cqTable.size()); diffResult.append("check all topic finish, ").append(checkResultString); result.setCheckResult(diffResult.toString()); result.setCheckStatus(checkReady ? CheckRocksdbCqWriteResult.CheckStatus.CHECK_OK.getValue() : CheckRocksdbCqWriteResult.CheckStatus.CHECK_NOT_OK.getValue()); } catch (Exception e) { log.error("CheckRocksdbCqWriteProgressCommand error", e); result.setCheckResult(e.getMessage() + Arrays.toString(e.getStackTrace())); result.setCheckStatus(CheckRocksdbCqWriteResult.CheckStatus.CHECK_ERROR.getValue()); } return result; } private boolean processConsumeQueuesForTopic(ConcurrentMap queueMap, String topic, AbstractConsumeQueueStore abstractConsumeQueueStore, StringBuilder diffResult, boolean printDetail, long checkpointByStoreTime) { boolean processResult = true; for (Map.Entry queueEntry : queueMap.entrySet()) { Integer queueId = queueEntry.getKey(); ConsumeQueueInterface baseCQ = queueEntry.getValue(); ConsumeQueueInterface compareCQ = abstractConsumeQueueStore.findOrCreateConsumeQueue(topic, queueId); if (printDetail) { String format = String.format("[topic: %s, queue: %s] \n kvEarliest : %s | kvLatest : %s \n fileEarliest: %s | fileEarliest: %s ", topic, queueId, compareCQ.getEarliestUnit(), compareCQ.getLatestUnit(), baseCQ.getEarliestUnit(), baseCQ.getLatestUnit()); diffResult.append(format).append("\n"); } long minOffsetByTime = 0L; try { minOffsetByTime = abstractConsumeQueueStore.getOffsetInQueueByTime(topic, queueId, checkpointByStoreTime, BoundaryType.UPPER); } catch (Exception e) { // ignore } long minOffsetInQueue = compareCQ.getMinOffsetInQueue(); long checkFrom = Math.max(minOffsetInQueue, minOffsetByTime); long checkTo = baseCQ.getMaxOffsetInQueue() - 1; /* checkTo(maxOffsetInQueue - 1) v baseCQ +------------------------------------------------------+ compareCQ +----------------------------------------------+ ^ ^ minOffsetInQueue minOffsetByTime ^ checkFrom = max(minOffsetInQueue, minOffsetByTime) */ // The latest message is earlier than the check time Pair fileLatestCq = baseCQ.getCqUnitAndStoreTime(checkTo); if (fileLatestCq != null) { if (fileLatestCq.getObject2() < checkpointByStoreTime) { continue; } } for (long i = checkFrom; i <= checkTo; i++) { Pair baseCqUnit = baseCQ.getCqUnitAndStoreTime(i); Pair compareCqUnit = compareCQ.getCqUnitAndStoreTime(i); if (baseCqUnit == null || compareCqUnit == null || !checkCqUnitEqual(compareCqUnit.getObject1(), baseCqUnit.getObject1())) { log.error(String.format("[topic: %s, queue: %s, offset: %s] \n file : %s \n kv : %s \n", topic, queueId, i, compareCqUnit != null ? compareCqUnit.getObject1() : "null", baseCqUnit != null ? baseCqUnit.getObject1() : "null")); processResult = false; break; } } } return processResult; } private boolean checkCqUnitEqual(CqUnit cqUnit1, CqUnit cqUnit2) { if (cqUnit1.getQueueOffset() != cqUnit2.getQueueOffset()) { return false; } if (cqUnit1.getSize() != cqUnit2.getSize()) { return false; } if (cqUnit1.getPos() != cqUnit2.getPos()) { return false; } if (cqUnit1.getBatchNum() != cqUnit2.getBatchNum()) { return false; } return cqUnit1.getTagsCode() == cqUnit2.getTagsCode(); } private AbstractConsumeQueueStore getInnerStoreByString(String storeTypeString) { if (StoreType.DEFAULT.getStoreType().equalsIgnoreCase(storeTypeString)) { return consumeQueueStore; } else if (StoreType.DEFAULT_ROCKSDB.getStoreType().equalsIgnoreCase(storeTypeString)) { return rocksDBConsumeQueueStore; } else { return null; } } private AbstractConsumeQueueStore getInnerStoreByStoreType(StoreType storeType) { switch (storeType) { case DEFAULT: return consumeQueueStore; case DEFAULT_ROCKSDB: return rocksDBConsumeQueueStore; default: return null; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageFilter; import org.rocksdb.RocksDBException; public interface ConsumeQueueInterface extends FileQueueLifeCycle { /** * Get the topic name * @return the topic this cq belongs to. */ String getTopic(); /** * Get queue id * @return the queue id this cq belongs to. */ int getQueueId(); /** * Get the units from the start offset. * * @param startIndex start index * @return the unit iterateFrom */ ReferredIterator iterateFrom(long startIndex); /** * Get the units from the start offset. * * @param startIndex start index * @param count the unit counts will be iterated * @return the unit iterateFrom * @throws RocksDBException only in rocksdb mode */ ReferredIterator iterateFrom(long startIndex, int count) throws RocksDBException; /** * Get cq unit at specified index * @param index index * @return the cq unit at index */ CqUnit get(long index); /** * Get earliest cq unit * @return the cq unit and message storeTime at index */ Pair getCqUnitAndStoreTime(long index); /** * Get earliest cq unit * @return earliest cq unit and message storeTime */ Pair getEarliestUnitAndStoreTime(); /** * Get earliest cq unit * @return earliest cq unit */ CqUnit getEarliestUnit(); /** * Get last cq unit * @return last cq unit */ CqUnit getLatestUnit(); /** * Get last commit log offset * @return last commit log offset */ long getLastOffset(); /** * Get min offset(index) in queue * @return the min offset(index) in queue */ long getMinOffsetInQueue(); /** * Get max offset(index) in queue * @return the max offset(index) in queue */ long getMaxOffsetInQueue(); /** * Get total message count * @return total message count */ long getMessageTotalInQueue(); /** * Get the message whose timestamp is the smallest, greater than or equal to the given time. * @param timestamp timestamp * @return the offset(index) */ long getOffsetInQueueByTime(final long timestamp); /** * Get the message whose timestamp is the smallest, greater than or equal to the given time and when there are more * than one message satisfy the condition, decide which one to return based on boundaryType. * @param timestamp timestamp * @param boundaryType Lower or Upper * @return the offset(index) */ long getOffsetInQueueByTime(final long timestamp, final BoundaryType boundaryType); /** * The max physical offset of commitlog has been dispatched to this queue. * It should be exclusive. * * @return the max physical offset point to commitlog */ long getMaxPhysicOffset(); /** * Usually, the cq files are not exactly consistent with the commitlog, there maybe some redundant data in the first * cq file. * * @return the minimal effective pos of the cq file. */ long getMinLogicOffset(); /** * Get cq type * @return cq type */ CQType getCQType(); /** * Gets the occupied size of CQ file on disk * @return total size */ long getTotalSize(); /** * Get the unit size of this CQ which is different in different CQ impl * @return cq unit size */ int getUnitSize(); /** * Correct min offset by min commit log offset. * @param minCommitLogOffset min commit log offset */ void correctMinOffset(long minCommitLogOffset); /** * Do dispatch. * @param request the request containing dispatch information. */ void putMessagePositionInfoWrapper(DispatchRequest request); /** * Assign queue offset. * @param queueOffsetAssigner the delegated queue offset assigner * @param msg message itself * @throws RocksDBException only in rocksdb mode */ void assignQueueOffset(QueueOffsetOperator queueOffsetAssigner, MessageExtBrokerInner msg) throws RocksDBException; /** * Increase queue offset. * @param queueOffsetAssigner the delegated queue offset assigner * @param msg message itself * @param messageNum message number */ void increaseQueueOffset(QueueOffsetOperator queueOffsetAssigner, MessageExtBrokerInner msg, short messageNum); /** * Estimate number of records matching given filter. * * @param from Lower boundary, inclusive. * @param to Upper boundary, inclusive. * @param filter Specified filter criteria * @return Number of matching records. */ long estimateMessageCount(long from, long to, MessageFilter filter); /** * Initialize cq and set max offset and min offset to given offset * * @param offset set max and min offset to given offset * @param minPhyOffset min physical offset, used to correct min offset */ void initializeWithOffset(long offset, long minPhyOffset); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.StoreException; import org.rocksdb.RocksDBException; import static java.lang.String.format; import static org.apache.rocketmq.store.config.StorePathConfigHelper.getStorePathBatchConsumeQueue; import static org.apache.rocketmq.store.config.StorePathConfigHelper.getStorePathConsumeQueue; public class ConsumeQueueStore extends AbstractConsumeQueueStore { private final FlushConsumeQueueService flushConsumeQueueService; private final CorrectLogicOffsetService correctLogicOffsetService; private final CleanConsumeQueueService cleanConsumeQueueService; private final AtomicInteger lmqCounter = new AtomicInteger(0); public ConsumeQueueStore(DefaultMessageStore messageStore) { super(messageStore); this.flushConsumeQueueService = new FlushConsumeQueueService(); this.correctLogicOffsetService = new CorrectLogicOffsetService(); this.cleanConsumeQueueService = new CleanConsumeQueueService(); } @Override public void start() { this.flushConsumeQueueService.start(); messageStore.getScheduledCleanQueueExecutorService().scheduleWithFixedDelay(this::cleanQueueFilesPeriodically, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS); log.info("Default ConsumeQueueStore start!"); } private void cleanQueueFilesPeriodically() { this.correctLogicOffsetService.run(); this.cleanConsumeQueueService.run(); } @Override public boolean load() { boolean cqLoadResult = loadConsumeQueues(getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.SimpleCQ); boolean bcqLoadResult = loadConsumeQueues(getStorePathBatchConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.BatchCQ); return cqLoadResult && bcqLoadResult; } @Override public void recover(boolean concurrently) { log.info("Start to recover consume queue concurrently={}", concurrently); if (concurrently) { recoverConcurrently(); } else { for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { this.recover(logic); } } } } /** * Implementation of CommitLogDispatchStore.getDispatchFromPhyOffset() (inherited from ConsumeQueueStoreInterface). * When recoverNormally is false, returns checkpoint's logicsPhysicalOffset so commitlog abnormal recovery starts * from it. */ @Override public Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException { if (recoverNormally) { return getMaxPhyOffsetInConsumeQueue(); } else { long fromCheckpoint = this.messageStore.getStoreCheckpoint().getLogicsPhysicalOffset(); long physicMsgTimestamp = this.messageStore.getStoreCheckpoint().getPhysicMsgTimestamp(); if (physicMsgTimestamp > 0 && fromCheckpoint <= 0 && messageStoreConfig.isEnableAcceleratedRecovery()) { throw new RuntimeException("Accelerated recovery is enabled but checkpoint's logicsPhysicalOffset is invalid"); } return fromCheckpoint; } } public boolean recoverConcurrently() { int count = 0; for (ConcurrentMap maps : this.consumeQueueTable.values()) { count += maps.values().size(); } final CountDownLatch countDownLatch = new CountDownLatch(count); BlockingQueue recoverQueue = new LinkedBlockingQueue<>(); final ExecutorService executor = buildExecutorService(recoverQueue, "RecoverConsumeQueueThread_"); List> result = new ArrayList<>(count); try { for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (final ConsumeQueueInterface logic : maps.values()) { FutureTask futureTask = new FutureTask<>(() -> { boolean ret = true; try { logic.recover(); } catch (Throwable e) { ret = false; log.error("Exception occurs while recover consume queue concurrently, " + "topic={}, queueId={}", logic.getTopic(), logic.getQueueId(), e); } finally { countDownLatch.countDown(); } return ret; }); result.add(futureTask); executor.submit(futureTask); } } countDownLatch.await(); for (FutureTask task : result) { if (task != null && task.isDone()) { if (!task.get()) { return false; } } } } catch (Exception e) { log.error("Exception occurs while recover consume queue concurrently", e); return false; } finally { executor.shutdown(); } return true; } @Override public boolean shutdown() { try { flush(); this.flushConsumeQueueService.shutdown(); } catch (StoreException e) { log.error("Failed to flush all consume queues", e); return false; } return true; } public void correctMinOffset(ConsumeQueueInterface consumeQueue, long minCommitLogOffset) { consumeQueue.correctMinOffset(minCommitLogOffset); } public void putMessagePositionInfoWrapper(DispatchRequest dispatchRequest) { ConsumeQueueInterface cq = this.findOrCreateConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId()); this.putMessagePositionInfoWrapper(cq, dispatchRequest); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) { ConsumeQueueInterface logic = findOrCreateConsumeQueue(topic, queueId); if (logic != null) { long resultOffset = logic.getOffsetInQueueByTime(timestamp, boundaryType); // Make sure the result offset is in valid range. resultOffset = Math.max(resultOffset, logic.getMinOffsetInQueue()); resultOffset = Math.min(resultOffset, logic.getMaxOffsetInQueue()); return resultOffset; } return 0; } private FileQueueLifeCycle getLifeCycle(String topic, int queueId) { return findOrCreateConsumeQueue(topic, queueId); } public boolean load(ConsumeQueueInterface consumeQueue) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); return fileQueueLifeCycle.load(); } private boolean loadConsumeQueues(String storePath, CQType cqType) { File dirLogic = new File(storePath); File[] fileTopicList = dirLogic.listFiles(); if (fileTopicList != null) { for (File fileTopic : fileTopicList) { String topic = fileTopic.getName(); File[] fileQueueIdList = fileTopic.listFiles(); if (fileQueueIdList != null) { for (File fileQueueId : fileQueueIdList) { int queueId; try { queueId = Integer.parseInt(fileQueueId.getName()); } catch (NumberFormatException e) { continue; } queueTypeShouldBe(topic, cqType); ConsumeQueueInterface logic = createConsumeQueueByType(cqType, topic, queueId, storePath); this.putConsumeQueue(topic, queueId, logic); if (!this.load(logic)) { return false; } } } } } log.info("load {} all over, OK", cqType); return true; } private ConsumeQueueInterface createConsumeQueueByType(CQType cqType, String topic, int queueId, String storePath) { if (Objects.equals(CQType.SimpleCQ, cqType)) { return new ConsumeQueue( topic, queueId, storePath, this.messageStoreConfig.getMappedFileSizeConsumeQueue(), this.messageStore, this); } else if (Objects.equals(CQType.BatchCQ, cqType)) { return new BatchConsumeQueue( topic, queueId, storePath, this.messageStoreConfig.getMapperFileSizeBatchConsumeQueue(), this.messageStore); } else { throw new RuntimeException(format("queue type %s is not supported.", cqType.toString())); } } private void queueTypeShouldBe(String topic, CQType cqTypeExpected) { Optional topicConfig = this.messageStore.getTopicConfig(topic); CQType cqTypeActual = QueueTypeUtils.getCQType(topicConfig); if (!Objects.equals(cqTypeExpected, cqTypeActual)) { throw new RuntimeException(format("The queue type of topic: %s should be %s, but is %s", topic, cqTypeExpected, cqTypeActual)); } } private ExecutorService buildExecutorService(BlockingQueue blockingQueue, String threadNamePrefix) { return ThreadUtils.newThreadPoolExecutor( this.messageStore.getBrokerConfig().getRecoverThreadPoolNums(), this.messageStore.getBrokerConfig().getRecoverThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, blockingQueue, new ThreadFactoryImpl(threadNamePrefix)); } public void recover(ConsumeQueueInterface consumeQueue) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); fileQueueLifeCycle.recover(); } @Override public long getMaxPhyOffsetInConsumeQueue() { long maxPhysicOffset = -1L; for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { if (logic.getMaxPhysicOffset() > maxPhysicOffset) { maxPhysicOffset = logic.getMaxPhysicOffset(); } } } return maxPhysicOffset; } @Override public long getMinOffsetInQueue(String topic, int queueId) { ConsumeQueueInterface logic = findOrCreateConsumeQueue(topic, queueId); if (logic != null) { return logic.getMinOffsetInQueue(); } return -1; } public void checkSelf(ConsumeQueueInterface consumeQueue) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); fileQueueLifeCycle.checkSelf(); } @Override public void checkSelf() { for (Map.Entry> topicEntry : this.consumeQueueTable.entrySet()) { for (Map.Entry cqEntry : topicEntry.getValue().entrySet()) { this.checkSelf(cqEntry.getValue()); } } } public boolean flush(ConsumeQueueInterface consumeQueue, int flushLeastPages) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); return fileQueueLifeCycle.flush(flushLeastPages); } public void flush() throws StoreException { for (Map.Entry> topicEntry : this.consumeQueueTable.entrySet()) { for (Map.Entry cqEntry : topicEntry.getValue().entrySet()) { flush(cqEntry.getValue(), 0); } } } @Override public void destroy(ConsumeQueueInterface consumeQueue) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); fileQueueLifeCycle.destroy(); if (MixAll.isLmq(consumeQueue.getTopic())) { lmqCounter.decrementAndGet(); } } public int deleteExpiredFile(ConsumeQueueInterface consumeQueue, long minCommitLogPos) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); return fileQueueLifeCycle.deleteExpiredFile(minCommitLogPos); } public void truncateDirtyLogicFiles(ConsumeQueueInterface consumeQueue, long phyOffset) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); fileQueueLifeCycle.truncateDirtyLogicFiles(phyOffset); } public void swapMap(ConsumeQueueInterface consumeQueue, int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); fileQueueLifeCycle.swapMap(reserveNum, forceSwapIntervalMs, normalSwapIntervalMs); } public void cleanSwappedMap(ConsumeQueueInterface consumeQueue, long forceCleanSwapIntervalMs) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); fileQueueLifeCycle.cleanSwappedMap(forceCleanSwapIntervalMs); } public boolean isFirstFileAvailable(ConsumeQueueInterface consumeQueue) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); return fileQueueLifeCycle.isFirstFileAvailable(); } public boolean isFirstFileExist(ConsumeQueueInterface consumeQueue) { FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); return fileQueueLifeCycle.isFirstFileExist(); } @Override public ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId) { ConcurrentMap map = consumeQueueTable.get(topic); if (null == map) { ConcurrentMap newMap = new ConcurrentHashMap<>(128); ConcurrentMap oldMap = consumeQueueTable.putIfAbsent(topic, newMap); if (oldMap != null) { map = oldMap; } else { map = newMap; } } ConsumeQueueInterface logic = map.get(queueId); if (logic != null) { return logic; } ConsumeQueueInterface newLogic; Optional topicConfig = this.messageStore.getTopicConfig(topic); // TODO maybe the topic has been deleted. if (Objects.equals(CQType.BatchCQ, QueueTypeUtils.getCQType(topicConfig))) { newLogic = new BatchConsumeQueue( topic, queueId, getStorePathBatchConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), this.messageStoreConfig.getMapperFileSizeBatchConsumeQueue(), this.messageStore); } else { newLogic = new ConsumeQueue( topic, queueId, getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), this.messageStoreConfig.getMappedFileSizeConsumeQueue(), this.messageStore, this); } ConsumeQueueInterface oldLogic = map.putIfAbsent(queueId, newLogic); if (oldLogic != null) { logic = oldLogic; } else { logic = newLogic; if (MixAll.isLmq(topic)) { lmqCounter.incrementAndGet(); } } return logic; } @Override public ConsumeQueueInterface getConsumeQueue(String topic, int queueId) { ConcurrentMap map = this.getConsumeQueueTable().get(topic); if (map == null) { return null; } return map.get(queueId); } public void setBatchTopicQueueTable(ConcurrentMap batchTopicQueueTable) { this.queueOffsetOperator.setBatchTopicQueueTable(batchTopicQueueTable); } public void updateQueueOffset(String topic, int queueId, long offset) { String topicQueueKey = topic + "-" + queueId; this.queueOffsetOperator.updateQueueOffset(topicQueueKey, offset); } private void putConsumeQueue(final String topic, final int queueId, final ConsumeQueueInterface consumeQueue) { ConcurrentMap map = this.consumeQueueTable.get(topic); if (null == map) { map = new ConcurrentHashMap<>(); map.put(queueId, consumeQueue); this.consumeQueueTable.put(topic, map); if (MixAll.isLmq(topic)) { lmqCounter.incrementAndGet(); } } else { ConsumeQueueInterface prev = map.put(queueId, consumeQueue); if (null == prev && MixAll.isLmq(topic)) { lmqCounter.incrementAndGet(); } } } @Override public void recoverOffsetTable(long minPhyOffset) { ConcurrentMap cqOffsetTable = new ConcurrentHashMap<>(1024); ConcurrentMap bcqOffsetTable = new ConcurrentHashMap<>(1024); for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { String key = logic.getTopic() + "-" + logic.getQueueId(); long maxOffsetInQueue = logic.getMaxOffsetInQueue(); if (Objects.equals(CQType.BatchCQ, logic.getCQType())) { bcqOffsetTable.put(key, maxOffsetInQueue); } else { cqOffsetTable.put(key, maxOffsetInQueue); } this.correctMinOffset(logic, minPhyOffset); } } // Correct unSubmit consumeOffset if (messageStoreConfig.isDuplicationEnable() || messageStore.getBrokerConfig().isEnableControllerMode()) { compensateForHA(cqOffsetTable); } this.setTopicQueueTable(cqOffsetTable); this.setBatchTopicQueueTable(bcqOffsetTable); } private void compensateForHA(ConcurrentMap cqOffsetTable) { SelectMappedBufferResult lastBuffer = null; long startReadOffset = messageStore.getCommitLog().getConfirmOffset() == -1 ? 0 : messageStore.getCommitLog().getConfirmOffset(); log.info("Correct unsubmitted offset...StartReadOffset = {}", startReadOffset); while ((lastBuffer = messageStore.selectOneMessageByOffset(startReadOffset)) != null) { try { if (lastBuffer.getStartOffset() > startReadOffset) { startReadOffset = lastBuffer.getStartOffset(); continue; } ByteBuffer bb = lastBuffer.getByteBuffer(); int magicCode = bb.getInt(bb.position() + 4); if (magicCode == CommitLog.BLANK_MAGIC_CODE) { startReadOffset += bb.getInt(bb.position()); continue; } else if (magicCode != MessageDecoder.MESSAGE_MAGIC_CODE) { throw new RuntimeException("Unknown magicCode: " + magicCode); } lastBuffer.getByteBuffer().mark(); DispatchRequest dispatchRequest = messageStore.getCommitLog().checkMessageAndReturnSize(lastBuffer.getByteBuffer(), true, messageStoreConfig.isDuplicationEnable(), true); if (!dispatchRequest.isSuccess()) break; lastBuffer.getByteBuffer().reset(); MessageExt msg = MessageDecoder.decode(lastBuffer.getByteBuffer(), true, false, false, false, true); if (msg == null) break; String key = msg.getTopic() + "-" + msg.getQueueId(); cqOffsetTable.put(key, msg.getQueueOffset() + 1); startReadOffset += msg.getStoreSize(); log.info("Correcting. Key:{}, start read Offset: {}", key, startReadOffset); } finally { if (lastBuffer != null) lastBuffer.release(); } } } /** * @param loadAfterDestroy file version cq do not need reload, so ignore */ @Override public void destroy(boolean loadAfterDestroy) { for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { this.destroy(logic); } } } @Override public void cleanExpired(long minCommitLogOffset) { Iterator>> it = this.consumeQueueTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry> next = it.next(); String topic = next.getKey(); if (!TopicValidator.isSystemTopic(topic)) { ConcurrentMap queueTable = next.getValue(); Iterator> itQT = queueTable.entrySet().iterator(); while (itQT.hasNext()) { Map.Entry nextQT = itQT.next(); long maxCLOffsetInConsumeQueue = nextQT.getValue().getLastOffset(); if (maxCLOffsetInConsumeQueue == -1) { log.warn("maybe ConsumeQueue was created just now. topic={} queueId={} maxPhysicOffset={} minLogicOffset={}.", nextQT.getValue().getTopic(), nextQT.getValue().getQueueId(), nextQT.getValue().getMaxPhysicOffset(), nextQT.getValue().getMinLogicOffset()); } else if (maxCLOffsetInConsumeQueue < minCommitLogOffset) { log.info( "cleanExpiredConsumerQueue: {} {} consumer queue destroyed, minCommitLogOffset: {} maxCLOffsetInConsumeQueue: {}", topic, nextQT.getKey(), minCommitLogOffset, maxCLOffsetInConsumeQueue); removeTopicQueueTable(nextQT.getValue().getTopic(), nextQT.getValue().getQueueId()); this.destroy(nextQT.getValue()); itQT.remove(); } } if (queueTable.isEmpty()) { log.info("cleanExpiredConsumerQueue: {},topic destroyed", topic); it.remove(); } } } } @Override public void truncateDirty(long offsetToTruncate) { long maxPhyOffsetOfConsumeQueue = getMaxPhyOffsetInConsumeQueue(); if (maxPhyOffsetOfConsumeQueue >= offsetToTruncate) { log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, offsetToTruncate); for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { this.truncateDirtyLogicFiles(logic, offsetToTruncate); } } } } @Override public long getTotalSize() { long totalSize = 0; for (ConcurrentMap maps : this.consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { totalSize += logic.getTotalSize(); } } return totalSize; } @Override public boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException { if (!recoverNormally && this.messageStore.getStoreCheckpoint().getLogicsPhysicalOffset() <= 0) { // for the sake of compatibility return storeTimestamp <= this.messageStore.getStoreCheckpoint().getLogicsMsgTimestamp(); } return phyOffset <= getDispatchFromPhyOffset(recoverNormally); } @Override public int getLmqNum() { return lmqCounter.get(); } @Override public boolean isLmqExist(String lmqTopic) { return getConsumeQueue(lmqTopic, 0) != null; } public class FlushConsumeQueueService extends ServiceThread { private static final int RETRY_TIMES_OVER = 3; private long lastFlushTimestamp = 0; private void doFlush(int retryTimes) { int flushConsumeQueueLeastPages = messageStoreConfig.getFlushConsumeQueueLeastPages(); if (retryTimes == RETRY_TIMES_OVER) { flushConsumeQueueLeastPages = 0; } long logicsMsgTimestamp = 0; long logicsPhysicalOffset = 0; int flushConsumeQueueThoroughInterval = messageStoreConfig.getFlushConsumeQueueThoroughInterval(); long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis >= (this.lastFlushTimestamp + flushConsumeQueueThoroughInterval)) { this.lastFlushTimestamp = currentTimeMillis; flushConsumeQueueLeastPages = 0; logicsMsgTimestamp = messageStore.getStoreCheckpoint().getTmpLogicsMsgTimestamp(); logicsPhysicalOffset = messageStore.getStoreCheckpoint().getTmpLogicsPhysicalOffset(); } for (ConcurrentMap maps : consumeQueueTable.values()) { for (ConsumeQueueInterface cq : maps.values()) { boolean result = false; for (int i = 0; i < retryTimes && !result; i++) { result = flush(cq, flushConsumeQueueLeastPages); } } } if (messageStoreConfig.isEnableCompaction()) { messageStore.getCompactionStore().flush(flushConsumeQueueLeastPages); } if (0 == flushConsumeQueueLeastPages) { if (logicsMsgTimestamp > 0) { messageStore.getStoreCheckpoint().setLogicsMsgTimestamp(logicsMsgTimestamp); } if (logicsPhysicalOffset > 0) { messageStore.getStoreCheckpoint().setLogicsPhysicalOffset(logicsPhysicalOffset); } messageStore.getStoreCheckpoint().flush(); } } @Override public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { int interval = messageStoreConfig.getFlushIntervalConsumeQueue(); this.waitForRunning(interval); this.doFlush(1); } catch (Exception e) { log.warn(this.getServiceName() + " service has exception. ", e); } } this.doFlush(RETRY_TIMES_OVER); log.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { if (messageStore.getBrokerConfig().isInBrokerContainer()) { return messageStore.getBrokerIdentity().getIdentifier() + FlushConsumeQueueService.class.getSimpleName(); } return FlushConsumeQueueService.class.getSimpleName(); } @Override public long getJoinTime() { return 1000 * 60; } } class CorrectLogicOffsetService { private long lastForceCorrectTime = -1L; public void run() { try { this.correctLogicMinOffset(); } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception. ", e); } } private boolean needCorrect(ConsumeQueueInterface logic, long minPhyOffset, long lastForeCorrectTimeCurRun) { if (logic == null) { return false; } // If first exist and not available, it means first file may destroy failed, delete it. if (isFirstFileExist(logic) && !isFirstFileAvailable(logic)) { log.error("CorrectLogicOffsetService.needCorrect. first file not available, trigger correct." + " topic:{}, queue:{}, maxPhyOffset in queue:{}, minPhyOffset " + "in commit log:{}, minOffset in queue:{}, maxOffset in queue:{}, cqType:{}" , logic.getTopic(), logic.getQueueId(), logic.getMaxPhysicOffset() , minPhyOffset, logic.getMinOffsetInQueue(), logic.getMaxOffsetInQueue(), logic.getCQType()); return true; } // logic.getMaxPhysicOffset() or minPhyOffset = -1 // means there is no message in current queue, so no need to correct. if (logic.getMaxPhysicOffset() == -1 || minPhyOffset == -1) { return false; } if (logic.getMaxPhysicOffset() < minPhyOffset) { if (logic.getMinOffsetInQueue() < logic.getMaxOffsetInQueue()) { log.error("CorrectLogicOffsetService.needCorrect. logic max phy offset: {} is less than min phy offset: {}, " + "but min offset: {} is less than max offset: {}. topic:{}, queue:{}, cqType:{}." , logic.getMaxPhysicOffset(), minPhyOffset, logic.getMinOffsetInQueue() , logic.getMaxOffsetInQueue(), logic.getTopic(), logic.getQueueId(), logic.getCQType()); return true; } else if (logic.getMinOffsetInQueue() == logic.getMaxOffsetInQueue()) { return false; } else { log.error("CorrectLogicOffsetService.needCorrect. It should not happen, logic max phy offset: {} is less than min phy offset: {}," + " but min offset: {} is larger than max offset: {}. topic:{}, queue:{}, cqType:{}" , logic.getMaxPhysicOffset(), minPhyOffset, logic.getMinOffsetInQueue() , logic.getMaxOffsetInQueue(), logic.getTopic(), logic.getQueueId(), logic.getCQType()); return false; } } //the logic.getMaxPhysicOffset() >= minPhyOffset int forceCorrectInterval = messageStoreConfig.getCorrectLogicMinOffsetForceInterval(); if ((System.currentTimeMillis() - lastForeCorrectTimeCurRun) > forceCorrectInterval) { lastForceCorrectTime = System.currentTimeMillis(); CqUnit cqUnit = logic.getEarliestUnit(); if (cqUnit == null) { if (logic.getMinOffsetInQueue() == logic.getMaxOffsetInQueue()) { return false; } else { log.error("CorrectLogicOffsetService.needCorrect. cqUnit is null, logic max phy offset: {} is greater than min phy offset: {}, " + "but min offset: {} is not equal to max offset: {}. topic:{}, queue:{}, cqType:{}." , logic.getMaxPhysicOffset(), minPhyOffset, logic.getMinOffsetInQueue() , logic.getMaxOffsetInQueue(), logic.getTopic(), logic.getQueueId(), logic.getCQType()); return true; } } if (cqUnit.getPos() < minPhyOffset) { log.error("CorrectLogicOffsetService.needCorrect. logic max phy offset: {} is greater than min phy offset: {}, " + "but minPhyPos in cq is: {}. min offset in queue: {}, max offset in queue: {}, topic:{}, queue:{}, cqType:{}." , logic.getMaxPhysicOffset(), minPhyOffset, cqUnit.getPos(), logic.getMinOffsetInQueue() , logic.getMaxOffsetInQueue(), logic.getTopic(), logic.getQueueId(), logic.getCQType()); return true; } if (cqUnit.getPos() >= minPhyOffset) { // Normal case, do not need to correct. return false; } } return false; } private void correctLogicMinOffset() { long lastForeCorrectTimeCurRun = lastForceCorrectTime; long minPhyOffset = messageStore.getMinPhyOffset(); for (ConcurrentMap maps : consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { if (Objects.equals(CQType.SimpleCQ, logic.getCQType())) { // cq is not supported for now. continue; } if (needCorrect(logic, minPhyOffset, lastForeCorrectTimeCurRun)) { doCorrect(logic, minPhyOffset); } } } } private void doCorrect(ConsumeQueueInterface logic, long minPhyOffset) { deleteExpiredFile(logic, minPhyOffset); int sleepIntervalWhenCorrectMinOffset = messageStoreConfig.getCorrectLogicMinOffsetSleepInterval(); if (sleepIntervalWhenCorrectMinOffset > 0) { try { Thread.sleep(sleepIntervalWhenCorrectMinOffset); } catch (InterruptedException ignored) { } } } public String getServiceName() { if (messageStore.getBrokerConfig().isInBrokerContainer()) { return messageStore.getBrokerConfig().getIdentifier() + CorrectLogicOffsetService.class.getSimpleName(); } return CorrectLogicOffsetService.class.getSimpleName(); } } public class CleanConsumeQueueService { protected long lastPhysicalMinOffset = 0; public void run() { try { this.deleteExpiredFiles(); } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception. ", e); } } protected void deleteExpiredFiles() { int deleteLogicsFilesInterval = messageStoreConfig.getDeleteConsumeQueueFilesInterval(); long minOffset = messageStore.getCommitLog().getMinOffset(); if (minOffset > this.lastPhysicalMinOffset) { this.lastPhysicalMinOffset = minOffset; for (ConcurrentMap maps : consumeQueueTable.values()) { for (ConsumeQueueInterface logic : maps.values()) { int deleteCount = deleteExpiredFile(logic, minOffset); if (deleteCount > 0 && deleteLogicsFilesInterval > 0) { try { Thread.sleep(deleteLogicsFilesInterval); } catch (InterruptedException ignored) { } } } } messageStore.getIndexService().deleteExpiredFile(minOffset); if (messageStoreConfig.isIndexRocksDBEnable() && null != messageStore.getIndexRocksDBStore()) { messageStore.getIndexRocksDBStore().deleteExpiredIndex(); } } } public String getServiceName() { return messageStore.getBrokerConfig().getIdentifier() + CleanConsumeQueueService.class.getSimpleName(); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreInterface.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.CommitLogDispatchStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.exception.StoreException; import org.rocksdb.RocksDBException; public interface ConsumeQueueStoreInterface extends CommitLogDispatchStore { /** * Load from file. * * @return true if loaded successfully. */ boolean load(); /** * Recover from file. * @param concurrently whether to recover concurrently */ void recover(boolean concurrently) throws RocksDBException; /** * Start the consumeQueueStore */ void start(); /** * Shutdown the consumeQueueStore * @return true if shutdown successfully. */ boolean shutdown(); /** * destroy all consumeQueues * @param loadAfterDestroy reload store after destroy, only used in RocksDB mode */ void destroy(boolean loadAfterDestroy); /** * delete topic */ boolean deleteTopic(String topic); /** * Flush all nested consume queues to disk * * @throws StoreException if there is an error during flush */ void flush() throws StoreException; /** * clean expired data from minCommitLogOffset * @param minCommitLogOffset Minimum commit log offset */ void cleanExpired(long minCommitLogOffset); /** * Check files. */ void checkSelf(); /** * truncate dirty data * @param offsetToTruncate * @throws RocksDBException only in rocksdb mode */ void truncateDirty(long offsetToTruncate) throws RocksDBException; /** * Apply the dispatched request. This function should be idempotent. * * @param request dispatch request * @throws RocksDBException only in rocksdb mode will throw exception */ void putMessagePositionInfoWrapper(DispatchRequest request) throws RocksDBException; /** * get consumeQueue table * @return the consumeQueue table */ ConcurrentMap> getConsumeQueueTable(); /** * Assign queue offset. * @param msg message itself * @throws RocksDBException only in rocksdb mode */ void assignQueueOffset(MessageExtBrokerInner msg) throws RocksDBException; /** * Increase queue offset. * @param msg message itself * @param messageNum message number */ void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum); /** * Increase lmq offset * @param topic Topic/Queue name * @param queueId Queue ID * @param delta amount to increase */ void increaseLmqOffset(String topic, int queueId, short delta) throws ConsumeQueueException; /** * get lmq queue offset * @param topic * @param queueId * @return */ long getLmqQueueOffset(String topic, int queueId) throws ConsumeQueueException; /** * recover topicQueue table by minPhyOffset * @param minPhyOffset */ void recoverOffsetTable(long minPhyOffset); /** * get maxOffset of specific topic-queueId in topicQueue table * * @param topic Topic name * @param queueId Queue identifier * @return the max offset in QueueOffsetOperator * @throws ConsumeQueueException if there is an error while retrieving max consume queue offset */ Long getMaxOffset(String topic, int queueId) throws ConsumeQueueException; /** * get min logic offset of specific topic-queueId in consumeQueue * @param topic * @param queueId * @return the min logic offset of specific topic-queueId in consumeQueue * @throws RocksDBException only in rocksdb mode */ long getMinOffsetInQueue(final String topic, final int queueId) throws RocksDBException; /** * Get the message whose timestamp is the smallest, greater than or equal to the given time and when there are more * than one message satisfy the condition, decide which one to return based on boundaryType. * @param timestamp timestamp * @param boundaryType Lower or Upper * @return the offset(index) * @throws RocksDBException only in rocksdb mode */ long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) throws RocksDBException; /** * find or create the consumeQueue * @param topic * @param queueId * @return the consumeQueue */ ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId); /** * only find consumeQueue * * @param topic * @param queueId * @return the consumeQueue */ ConsumeQueueInterface getConsumeQueue(String topic, int queueId); /** * get the total size of all consumeQueue * @return the total size of all consumeQueue */ long getTotalSize(); /** * get lmq consume queue count * @return the count of lmq */ int getLmqNum(); /** * Check if the LMQ exists, this is different from getConsumeQueue() * @param lmqTopic * @return exist or not */ boolean isLmqExist(String lmqTopic); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/CqUnit.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.store.ConsumeQueueExt; import java.nio.ByteBuffer; public class CqUnit { private final long queueOffset; private final int size; private final long pos; private final short batchNum; /** * Be careful, the tagsCode is reused as an address for extent file. To prevent accident mistake, we follow the * rules: 1. If the cqExtUnit is not null, make tagsCode == cqExtUnit.getTagsCode() 2. If the cqExtUnit is null, and * the tagsCode is smaller than 0, it is an invalid tagsCode, which means failed to get cqExtUnit by address */ private long tagsCode; private ConsumeQueueExt.CqExtUnit cqExtUnit; private final ByteBuffer nativeBuffer; private final int compactedOffset; public CqUnit(long queueOffset, long pos, int size, long tagsCode) { this(queueOffset, pos, size, tagsCode, (short) 1, 0, null); } public CqUnit(long queueOffset, long pos, int size, long tagsCode, short batchNum, int compactedOffset, ByteBuffer buffer) { this.queueOffset = queueOffset; this.pos = pos; this.size = size; this.tagsCode = tagsCode; this.batchNum = batchNum; this.nativeBuffer = buffer; this.compactedOffset = compactedOffset; } public int getSize() { return size; } public long getPos() { return pos; } public long getTagsCode() { return tagsCode; } public Long getValidTagsCodeAsLong() { if (!isTagsCodeValid()) { return null; } return tagsCode; } public boolean isTagsCodeValid() { return !ConsumeQueueExt.isExtAddr(tagsCode); } public ConsumeQueueExt.CqExtUnit getCqExtUnit() { return cqExtUnit; } public void setCqExtUnit(ConsumeQueueExt.CqExtUnit cqExtUnit) { this.cqExtUnit = cqExtUnit; } public void setTagsCode(long tagsCode) { this.tagsCode = tagsCode; } public long getQueueOffset() { return queueOffset; } public short getBatchNum() { return batchNum; } public void correctCompactOffset(int correctedOffset) { this.nativeBuffer.putInt(correctedOffset); } public int getCompactedOffset() { return compactedOffset; } @Override public String toString() { return "CqUnit{" + "queueOffset=" + queueOffset + ", size=" + size + ", pos=" + pos + ", batchNum=" + batchNum + ", tagsCode=" + tagsCode + ", compactedOffset=" + compactedOffset + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/DispatchEntry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.nio.charset.StandardCharsets; import javax.annotation.Nonnull; import org.apache.rocketmq.store.DispatchRequest; /** * Use Record when Java 16 is available */ public class DispatchEntry { public byte[] topic; public int queueId; public long queueOffset; public long commitLogOffset; public int messageSize; public long tagCode; public long storeTimestamp; public static DispatchEntry from(@Nonnull DispatchRequest request) { DispatchEntry entry = new DispatchEntry(); entry.topic = request.getTopic().getBytes(StandardCharsets.UTF_8); entry.queueId = request.getQueueId(); entry.queueOffset = request.getConsumeQueueOffset(); entry.commitLogOffset = request.getCommitLogOffset(); entry.messageSize = request.getMsgSize(); entry.tagCode = request.getTagsCode(); entry.storeTimestamp = request.getStoreTimestamp(); return entry; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/FileQueueLifeCycle.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.store.Swappable; /** * FileQueueLifeCycle contains life cycle methods of ConsumerQueue that is directly implemented by FILE. */ public interface FileQueueLifeCycle extends Swappable { /** * Load from file. * @return true if loaded successfully. */ boolean load(); /** * Recover from file. */ void recover(); /** * Check files. */ void checkSelf(); /** * Flush cache to file. * @param flushLeastPages the minimum number of pages to be flushed * @return true if any data has been flushed. */ boolean flush(int flushLeastPages); /** * Destroy files. */ void destroy(); /** * Truncate dirty logic files starting at max commit log position. * @param maxCommitLogPos max commit log position */ void truncateDirtyLogicFiles(long maxCommitLogPos); /** * Delete expired files ending at min commit log position. * @param minCommitLogPos min commit log position * @return deleted file numbers. */ int deleteExpiredFile(long minCommitLogPos); /** * Roll to next file. * @param nextBeginOffset next begin offset * @return the beginning offset of the next file */ long rollNextFile(final long nextBeginOffset); /** * Is the first file available? * @return true if it's available */ boolean isFirstFileAvailable(); /** * Does the first file exist? * * @return true if it exists */ boolean isFirstFileExist(); boolean shutdown(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/MultiDispatchUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.config.MessageStoreConfig; public class MultiDispatchUtils { public static String lmqQueueKey(String queueName) { StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(queueName); keyBuilder.append('-'); int queueId = 0; keyBuilder.append(queueId); return keyBuilder.toString(); } public static boolean isNeedHandleMultiDispatch(MessageStoreConfig messageStoreConfig, String topic) { return messageStoreConfig.isEnableMultiDispatch() && !topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && !topic.startsWith(TopicValidator.SYSTEM_TOPIC_PREFIX) && !topic.equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC); } public static boolean checkMultiDispatchQueue(MessageStoreConfig messageStoreConfig, DispatchRequest dispatchRequest) { if (!isNeedHandleMultiDispatch(messageStoreConfig, dispatchRequest.getTopic())) { return false; } Map prop = dispatchRequest.getPropertiesMap(); if (prop == null || prop.isEmpty()) { return false; } String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String multiQueueOffset = prop.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET); if (StringUtils.isBlank(multiDispatchQueue) || StringUtils.isBlank(multiQueueOffset)) { return false; } return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/OffsetInitializer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.store.exception.ConsumeQueueException; public interface OffsetInitializer { long maxConsumeQueueOffset(String topic, int queueId) throws ConsumeQueueException; } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/OffsetInitializerRocksDBImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.rocksdb.RocksDBException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class OffsetInitializerRocksDBImpl implements OffsetInitializer { private static final Logger LOGGER = LoggerFactory.getLogger(OffsetInitializerRocksDBImpl.class); private final RocksDBConsumeQueueStore consumeQueueStore; public OffsetInitializerRocksDBImpl(RocksDBConsumeQueueStore consumeQueueStore) { this.consumeQueueStore = consumeQueueStore; } @Override public long maxConsumeQueueOffset(String topic, int queueId) throws ConsumeQueueException { try { long offset = consumeQueueStore.getMaxOffsetInQueue(topic, queueId); LOGGER.info("Look up RocksDB for max-offset of LMQ[{}:{}]: {}", topic, queueId, offset); return offset; } catch (RocksDBException e) { throw new ConsumeQueueException(e); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import com.google.common.base.Preconditions; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.exception.ConsumeQueueException; /** * QueueOffsetOperator is a component for operating offsets for queues. */ public class QueueOffsetOperator { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private ConcurrentMap topicQueueTable = new ConcurrentHashMap<>(1024); private ConcurrentMap batchTopicQueueTable = new ConcurrentHashMap<>(1024); /** * {TOPIC}-{QUEUE_ID} --> NEXT Consume Queue Offset */ private ConcurrentMap lmqTopicQueueTable = new ConcurrentHashMap<>(1024); public long getQueueOffset(String topicQueueKey) { return ConcurrentHashMapUtils.computeIfAbsent(this.topicQueueTable, topicQueueKey, k -> 0L); } public Long getTopicQueueNextOffset(String topicQueueKey) { return this.topicQueueTable.get(topicQueueKey); } public void increaseQueueOffset(String topicQueueKey, short messageNum) { Long queueOffset = ConcurrentHashMapUtils.computeIfAbsent(this.topicQueueTable, topicQueueKey, k -> 0L); topicQueueTable.put(topicQueueKey, queueOffset + messageNum); } public void updateQueueOffset(String topicQueueKey, long offset) { this.topicQueueTable.put(topicQueueKey, offset); } public long getBatchQueueOffset(String topicQueueKey) { return ConcurrentHashMapUtils.computeIfAbsent(this.batchTopicQueueTable, topicQueueKey, k -> 0L); } public void increaseBatchQueueOffset(String topicQueueKey, short messageNum) { Long batchQueueOffset = ConcurrentHashMapUtils.computeIfAbsent(this.batchTopicQueueTable, topicQueueKey, k -> 0L); this.batchTopicQueueTable.put(topicQueueKey, batchQueueOffset + messageNum); } public long getLmqOffset(String topic, int queueId, OffsetInitializer callback) throws ConsumeQueueException { Preconditions.checkNotNull(callback, "ConsumeQueueOffsetCallback cannot be null"); String topicQueue = topic + "-" + queueId; if (!lmqTopicQueueTable.containsKey(topicQueue)) { // Load from RocksDB on cache miss. Long prev = lmqTopicQueueTable.putIfAbsent(topicQueue, callback.maxConsumeQueueOffset(topic, queueId)); if (null != prev) { log.error("[BUG] Data racing, lmqTopicQueueTable should NOT contain key={}", topicQueue); } } return lmqTopicQueueTable.get(topicQueue); } public void increaseLmqOffset(String topic, int queueId, short delta) throws ConsumeQueueException { String topicQueue = topic + "-" + queueId; if (!this.lmqTopicQueueTable.containsKey(topicQueue)) { throw new ConsumeQueueException(String.format("Max offset of Queue[name=%s, id=%d] should have existed", topic, queueId)); } long prev = lmqTopicQueueTable.get(topicQueue); this.lmqTopicQueueTable.compute(topicQueue, (k, offset) -> offset + delta); long current = lmqTopicQueueTable.get(topicQueue); log.debug("Max offset of LMQ[{}:{}] increased: {} --> {}", topic, queueId, prev, current); } public long currentQueueOffset(String topicQueueKey) { Long currentQueueOffset = this.topicQueueTable.get(topicQueueKey); return currentQueueOffset == null ? 0L : currentQueueOffset; } public synchronized void remove(String topic, Integer queueId) { String topicQueueKey = topic + "-" + queueId; // Beware of thread-safety this.topicQueueTable.remove(topicQueueKey); this.batchTopicQueueTable.remove(topicQueueKey); this.lmqTopicQueueTable.remove(topicQueueKey); log.info("removeQueueFromTopicQueueTable OK Topic: {} QueueId: {}", topic, queueId); } public void setTopicQueueTable(ConcurrentMap topicQueueTable) { this.topicQueueTable = topicQueueTable; } public void setLmqTopicQueueTable(ConcurrentMap lmqTopicQueueTable) { ConcurrentMap table = new ConcurrentHashMap(1024); for (Map.Entry entry : lmqTopicQueueTable.entrySet()) { if (MixAll.isLmq(entry.getKey())) { table.put(entry.getKey(), entry.getValue()); } } this.lmqTopicQueueTable = table; } public ConcurrentMap getTopicQueueTable() { return topicQueueTable; } public void setBatchTopicQueueTable(ConcurrentMap batchTopicQueueTable) { this.batchTopicQueueTable = batchTopicQueueTable; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/ReferredIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.Iterator; public interface ReferredIterator extends Iterator { /** * Release the referred resources. */ void release(); T nextAndRelease(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.rocksdb.RocksDBException; public class RocksDBConsumeQueue implements ConsumeQueueInterface { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private final MessageStoreConfig messageStoreConfig; private final RocksDBConsumeQueueStore consumeQueueStore; private final String topic; private final int queueId; public RocksDBConsumeQueue(final MessageStoreConfig messageStoreConfig, final RocksDBConsumeQueueStore consumeQueueStore, final String topic, final int queueId) { this.messageStoreConfig = messageStoreConfig; this.consumeQueueStore = consumeQueueStore; this.topic = topic; this.queueId = queueId; } /** * Only used to pass parameters when calling the destroy method * * @see RocksDBConsumeQueueStore#destroy(ConsumeQueueInterface) */ public RocksDBConsumeQueue(final String topic, final int queueId) { this(null, null, topic, queueId); } @Override public boolean load() { return true; } @Override public void recover() { // ignore } @Override public void checkSelf() { // ignore } @Override public boolean flush(final int flushLeastPages) { return true; } @Override public void destroy() { // ignore } @Override public void truncateDirtyLogicFiles(long maxCommitLogPos) { // ignored } @Override public int deleteExpiredFile(long minCommitLogPos) { return 0; } @Override public long rollNextFile(long nextBeginOffset) { return 0; } @Override public boolean isFirstFileAvailable() { return true; } @Override public boolean isFirstFileExist() { return true; } @Override public void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) { // ignore } @Override public void cleanSwappedMap(long forceCleanSwapIntervalMs) { // ignore } @Override public long getMaxOffsetInQueue() { try { return this.consumeQueueStore.getMaxOffsetInQueue(topic, queueId); } catch (RocksDBException e) { ERROR_LOG.error("getMaxOffsetInQueue Failed. topic: {}, queueId: {}", topic, queueId, e); return 0; } } @Override public long getMessageTotalInQueue() { try { long maxOffsetInQueue = this.consumeQueueStore.getMaxOffsetInQueue(topic, queueId); long minOffsetInQueue = this.consumeQueueStore.getMinOffsetInQueue(topic, queueId); return maxOffsetInQueue - minOffsetInQueue; } catch (RocksDBException e) { ERROR_LOG.error("getMessageTotalInQueue Failed. topic: {}, queueId: {}, {}", topic, queueId, e); } return -1; } /** * We already implement it in RocksDBConsumeQueueStore. * @see RocksDBConsumeQueueStore#getOffsetInQueueByTime * @param timestamp timestamp * @return */ @Override public long getOffsetInQueueByTime(long timestamp) { return 0; } /** * We already implement it in RocksDBConsumeQueueStore. * @see RocksDBConsumeQueueStore#getOffsetInQueueByTime * @param timestamp timestamp * @param boundaryType Lower or Upper * @return */ @Override public long getOffsetInQueueByTime(long timestamp, BoundaryType boundaryType) { return 0; } @Override public long getMaxPhysicOffset() { Long maxPhyOffset = this.consumeQueueStore.getMaxPhyOffsetInConsumeQueue(topic, queueId); return maxPhyOffset == null ? -1 : maxPhyOffset; } @Override public long getMinLogicOffset() { return 0; } @Override public CQType getCQType() { return CQType.RocksDBCQ; } @Override public long getTotalSize() { // ignored return 0; } @Override public int getUnitSize() { // attention: unitSize should equal to 'ConsumeQueue.CQ_STORE_UNIT_SIZE' return ConsumeQueue.CQ_STORE_UNIT_SIZE; } /** * Ignored, we already implement this method * @see org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable#getMinCqOffset(String, int) */ @Override public void correctMinOffset(long minCommitLogOffset) { } /** * Ignored, in rocksdb mode, we build cq in RocksDBConsumeQueueStore */ @Override public void putMessagePositionInfoWrapper(DispatchRequest request) { } @Override public void assignQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg) throws RocksDBException { String topicQueueKey = getTopic() + "-" + getQueueId(); Long queueOffset = queueOffsetOperator.getTopicQueueNextOffset(topicQueueKey); if (queueOffset == null) { // we will recover topic queue table from rocksdb when we use it. queueOffset = this.consumeQueueStore.getMaxOffsetInQueue(topic, queueId); queueOffsetOperator.updateQueueOffset(topicQueueKey, queueOffset); } msg.setQueueOffset(queueOffset); } @Override public void increaseQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg, short messageNum) { String topicQueueKey = getTopic() + "-" + getQueueId(); queueOffsetOperator.increaseQueueOffset(topicQueueKey, messageNum); } /** * It is CPU-intensive with many offline group * Optimize by caching their estimated info */ @Override public long estimateMessageCount(long from, long to, MessageFilter filter) { Pair fromUnit = getCqUnitAndStoreTime(from); if (fromUnit == null || from >= to) { return -1L; } if (to > getMaxOffsetInQueue()) { to = getMaxOffsetInQueue(); } int sampleCount = 0; int sampleTotal = Math.min((int) (to - from), messageStoreConfig.getMaxConsumeQueueScan()); int matchCount = 0; int matchTotal = messageStoreConfig.getSampleCountThreshold(); try { ReferredIterator iterator = this.iterateFrom(from, matchTotal); while (iterator != null && iterator.hasNext() && sampleCount++ < sampleTotal) { CqUnit cqUnit = iterator.next(); if (filter.isMatchedByConsumeQueue( cqUnit.getTagsCode(), cqUnit.getCqExtUnit())) { if (++matchCount > matchTotal) { sampleTotal = sampleCount; break; } } } } catch (Throwable t) { log.error("EstimateLag error, from={}, to={}", from, to, t); } long result = sampleTotal == 0 ? 0 : (long) ((to - from) * (matchCount / (sampleTotal * 1.0))); if (log.isTraceEnabled()) { log.trace("EstimateLag, topic={}, queueId={}, offset={}-{}, total={}, hit rate={}/{}({}%), result={}", topic, queueId, from, to, to - from, matchCount, sampleCount, String.format("%.1f", (double) matchCount * 100.0 / sampleCount), result); } return result; } @Override public long getMinOffsetInQueue() { try { return this.consumeQueueStore.getMinOffsetInQueue(topic, queueId); } catch (RocksDBException e) { ERROR_LOG.error("getMinOffsetInQueue Failed. topic: {}, queueId: {}", topic, queueId, e); return -1; } } private int pullNum(long cqOffset, long maxCqOffset) { long diffLong = maxCqOffset - cqOffset; if (diffLong < Integer.MAX_VALUE) { return (int) diffLong; } return Integer.MAX_VALUE; } @Override public ReferredIterator iterateFrom(final long startIndex) { long maxCqOffset = getMaxOffsetInQueue(); if (startIndex < maxCqOffset && startIndex >= 0) { int num = pullNum(startIndex, maxCqOffset); return new LargeRocksDBConsumeQueueIterator(startIndex, num); } return null; } @Override public ReferredIterator iterateFrom(long startIndex, int count) throws RocksDBException { long maxCqOffset = getMaxOffsetInQueue(); if (startIndex < maxCqOffset) { int num = Math.min((int)(maxCqOffset - startIndex), count); return iterateFrom0(startIndex, num, maxCqOffset); } return null; } @Override public CqUnit get(long index) { Pair pair = getCqUnitAndStoreTime(index); return pair == null ? null : pair.getObject1(); } @Override public Pair getCqUnitAndStoreTime(long index) { ByteBuffer byteBuffer; try { byteBuffer = this.consumeQueueStore.get(topic, queueId, index); } catch (RocksDBException e) { ERROR_LOG.error("getUnitAndStoreTime Failed. topic: {}, queueId: {}", topic, queueId, e); return null; } if (byteBuffer == null || byteBuffer.remaining() < RocksDBConsumeQueueTable.CQ_UNIT_SIZE) { return null; } long phyOffset = byteBuffer.getLong(); int size = byteBuffer.getInt(); long tagCode = byteBuffer.getLong(); long messageStoreTime = byteBuffer.getLong(); return new Pair<>(new CqUnit(index, phyOffset, size, tagCode), messageStoreTime); } @Override public Pair getEarliestUnitAndStoreTime() { try { long minOffset = this.consumeQueueStore.getMinOffsetInQueue(topic, queueId); return getCqUnitAndStoreTime(minOffset); } catch (RocksDBException e) { ERROR_LOG.error("getEarliestUnitAndStoreTime Failed. topic: {}, queueId: {}", topic, queueId, e); } return null; } @Override public CqUnit getEarliestUnit() { Pair pair = getEarliestUnitAndStoreTime(); return pair == null ? null : pair.getObject1(); } @Override public CqUnit getLatestUnit() { try { long maxOffset = this.consumeQueueStore.getMaxOffsetInQueue(topic, queueId); return get(maxOffset > 0 ? maxOffset - 1 : maxOffset); } catch (RocksDBException e) { ERROR_LOG.error("getLatestUnit Failed. topic: {}, queueId: {}, {}", topic, queueId, e.getMessage()); } return null; } @Override public long getLastOffset() { return getMaxPhysicOffset(); } private ReferredIterator iterateFrom0( final long startIndex, final int count, final long maxOffset) throws RocksDBException { if (messageStoreConfig.isIteratorWhenUseRocksdbConsumeQueue()) { return new RocksDBReusableIterator(topic, queueId, startIndex, count, maxOffset); } List byteBufferList = this.consumeQueueStore.rangeQuery(topic, queueId, startIndex, count); if (byteBufferList == null || byteBufferList.isEmpty()) { if (this.messageStoreConfig.isEnableRocksDBLog()) { log.warn("iterateFrom0 - find nothing, startIndex:{}, count:{}", startIndex, count); } return null; } return new RocksDBConsumeQueueIterator(byteBufferList, startIndex); } @Override public String getTopic() { return topic; } @Override public int getQueueId() { return queueId; } private class RocksDBReusableIterator implements ReferredIterator { private final String topic; private final int queueId; private long offset; private final int count; private final long maxOffset; private int bufferIndex; private List buffers; // offset + count <= max offset public RocksDBReusableIterator(String topic, int queueId, long offset, int count, long maxOffset) { this.topic = topic; this.queueId = queueId; this.offset = offset; this.count = count; this.maxOffset = maxOffset; this.bufferIndex = 0; this.buffers = new ArrayList<>(count); } @Override public void release() { } @Override public CqUnit nextAndRelease() { try { return next(); } finally { release(); } } @Override public boolean hasNext() { return offset < maxOffset; } @Override public CqUnit next() { try { if (buffers.isEmpty() || bufferIndex >= buffers.size()) { int batchSize = (int) Math.min(count, maxOffset - offset); if (batchSize == 0) { return null; } else { bufferIndex = 0; buffers = consumeQueueStore.rangeQuery(topic, queueId, offset, batchSize); } } if (bufferIndex < buffers.size()) { ByteBuffer buffer = buffers.get(bufferIndex++); return new CqUnit(offset++, buffer.getLong(), buffer.getInt(), buffer.getLong()); } } catch (Throwable t) { log.error("RocksDB reusable iterator search error, " + "topic={}, queueId={}, offset={}, count={}", topic, queueId, offset, count, maxOffset, t); } return null; } } private class RocksDBConsumeQueueIterator implements ReferredIterator { private final List byteBufferList; private final long startIndex; private final int totalCount; private int currentIndex; public RocksDBConsumeQueueIterator(final List byteBufferList, final long startIndex) { this.byteBufferList = byteBufferList; this.startIndex = startIndex; this.totalCount = byteBufferList.size(); this.currentIndex = 0; } @Override public boolean hasNext() { return this.currentIndex < this.totalCount; } @Override public CqUnit next() { if (!hasNext()) { return null; } final int currentIndex = this.currentIndex; final ByteBuffer byteBuffer = this.byteBufferList.get(currentIndex); CqUnit cqUnit = new CqUnit(this.startIndex + currentIndex, byteBuffer.getLong(), byteBuffer.getInt(), byteBuffer.getLong()); this.currentIndex++; return cqUnit; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } @Override public void release() { } @Override public CqUnit nextAndRelease() { try { return next(); } finally { release(); } } } private class LargeRocksDBConsumeQueueIterator implements ReferredIterator { private final long startIndex; private final int totalCount; private int currentIndex; public LargeRocksDBConsumeQueueIterator(final long startIndex, final int num) { this.startIndex = startIndex; this.totalCount = num; this.currentIndex = 0; } @Override public boolean hasNext() { return this.currentIndex < this.totalCount; } @Override public CqUnit next() { if (!hasNext()) { return null; } final ByteBuffer byteBuffer; try { byteBuffer = consumeQueueStore.get(topic, queueId, startIndex + currentIndex); } catch (RocksDBException e) { ERROR_LOG.error("get cq from rocksdb failed. topic: {}, queueId: {}", topic, queueId, e); return null; } if (byteBuffer == null || byteBuffer.remaining() < RocksDBConsumeQueueTable.CQ_UNIT_SIZE) { return null; } CqUnit cqUnit = new CqUnit(this.startIndex + currentIndex, byteBuffer.getLong(), byteBuffer.getInt(), byteBuffer.getLong()); this.currentIndex++; return cqUnit; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } @Override public void release() { } @Override public CqUnit nextAndRelease() { try { return next(); } finally { release(); } } } public void initializeWithOffset(long offset, long minPhyOffset) { log.info("RocksDBConsumeQueue initializeWithOffset topic={}, queueId={}, offset={}, oldMax={}, oldMin={}", topic, queueId, offset, getMaxOffsetInQueue(), getMinOffsetInQueue()); try { // clean the expired cqUnit and offset consumeQueueStore.cleanExpired(minPhyOffset); // update the max and min offset this.consumeQueueStore.updateCqOffset(topic, queueId, 0L, offset - 1, true); // set phyOffset to 0, min cq offset will be lazy corrected to max cq Offset + 1 this.consumeQueueStore.updateCqOffset(topic, queueId, 0L, offset, false); } catch (RocksDBException e) { ERROR_LOG.error("RocksDBConsumeQueue initializeWithOffset Failed. topic={}, queueId={}, offset={}", topic, queueId, offset, e); } } @Override public boolean shutdown() { return true; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueOffsetTable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import static org.apache.rocketmq.common.config.AbstractRocksDBStorage.CTRL_1; import io.netty.util.internal.PlatformDependent; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.queue.offset.OffsetEntry; import org.apache.rocketmq.store.queue.offset.OffsetEntryType; import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; public class RocksDBConsumeQueueOffsetTable { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final Logger ROCKSDB_LOG = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); private static final byte[] MAX_BYTES = "max".getBytes(StandardCharsets.UTF_8); private static final byte[] MIN_BYTES = "min".getBytes(StandardCharsets.UTF_8); /** * Rocksdb ConsumeQueue's Offset unit. Format: * *

         * ┌─────────────────────────┬───────────┬───────────────────────┬───────────┬───────────┬───────────┬─────────────┐
         * │ Topic Bytes Array Size  │  CTRL_1   │   Topic Bytes Array   │  CTRL_1   │  Max(Min) │  CTRL_1   │   QueueId   │
         * │        (4 Bytes)        │ (1 Bytes) │       (n Bytes)       │ (1 Bytes) │ (3 Bytes) │ (1 Bytes) │  (4 Bytes)  │
         * ├─────────────────────────┴───────────┴───────────────────────┴───────────┴───────────┴───────────┴─────────────┤
         * │                                                    Key Unit                                                   │
         * │                                                                                                               │
         * 
    * *
         * ┌─────────────────────────────┬────────────────────────┐
         * │  CommitLog Physical Offset  │   ConsumeQueue Offset  │
         * │        (8 Bytes)            │    (8 Bytes)           │
         * ├─────────────────────────────┴────────────────────────┤
         * │                     Value Unit                       │
         * │                                                      │
         * 
    * ConsumeQueue's Offset unit. Size: CommitLog Physical Offset(8) + ConsumeQueue Offset(8) = 16 Bytes */ static final int OFFSET_PHY_OFFSET = 0; static final int OFFSET_CQ_OFFSET = 8; /** * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────┬─────────────┐ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ Max(Min) │ CTRL_1 │ QueueId │ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (3 Bytes) │ (1 Bytes) │ (4 Bytes) │ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────┴─────────────┤ */ public static final int OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES = 4 + 1 + 1 + 3 + 1 + 4; private static final int OFFSET_VALUE_LENGTH = 8 + 8; /** * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────┐ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ Max(Min) │ CTRL_1 │ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (3 Bytes) │ (1 Bytes) │ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────┤ */ public static final int OFFSET_KEY_LENGTH_WITHOUT_TOPIC_QUEUE_ID_BYTES = 4 + 1 + 1 + 3 + 1; /** * We use a new system topic='CHECKPOINT_TOPIC' to record the maxPhyOffset built by CQ dispatch thread. * * @see ConsumeQueueStore#getMaxPhyOffsetInConsumeQueue(), we use it to find the maxPhyOffset built by CQ dispatch thread. * If we do not record the maxPhyOffset, it may take us a long time to start traversing from the head of * RocksDBConsumeQueueOffsetTable to find it. */ private static final String MAX_PHYSICAL_OFFSET_CHECKPOINT = TopicValidator.RMQ_SYS_ROCKSDB_OFFSET_TOPIC; private static final byte[] MAX_PHYSICAL_OFFSET_CHECKPOINT_BYTES = MAX_PHYSICAL_OFFSET_CHECKPOINT.getBytes(StandardCharsets.UTF_8); private static final int INNER_CHECKPOINT_TOPIC_LEN = OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + MAX_PHYSICAL_OFFSET_CHECKPOINT_BYTES.length; private static final ByteBuffer INNER_CHECKPOINT_TOPIC = ByteBuffer.allocateDirect(INNER_CHECKPOINT_TOPIC_LEN); private static final byte[] MAX_PHYSICAL_OFFSET_CHECKPOINT_KEY = new byte[INNER_CHECKPOINT_TOPIC_LEN]; private final ByteBuffer maxPhyOffsetBB; static { buildOffsetKeyByteBuffer0(INNER_CHECKPOINT_TOPIC, MAX_PHYSICAL_OFFSET_CHECKPOINT_BYTES, 0, true); INNER_CHECKPOINT_TOPIC.position(0).limit(INNER_CHECKPOINT_TOPIC_LEN); INNER_CHECKPOINT_TOPIC.get(MAX_PHYSICAL_OFFSET_CHECKPOINT_KEY); } private final RocksDBConsumeQueueTable rocksDBConsumeQueueTable; private final ConsumeQueueRocksDBStorage rocksDBStorage; private final DefaultMessageStore messageStore; private ColumnFamilyHandle offsetCFH; /** * Although we have already put max(min) consumeQueueOffset and physicalOffset in rocksdb, we still hope to get them * from heap to avoid accessing rocksdb. * * @see ConsumeQueue#getMaxPhysicOffset(), maxPhysicOffset --> topicQueueMaxCqOffset * @see ConsumeQueue#getMinLogicOffset(), minLogicOffset --> topicQueueMinOffset */ private final ConcurrentMap topicQueueMinOffset; private final ConcurrentMap topicQueueMaxCqOffset; private final AtomicInteger lmqCounter = new AtomicInteger(0); public RocksDBConsumeQueueOffsetTable(RocksDBConsumeQueueTable rocksDBConsumeQueueTable, ConsumeQueueRocksDBStorage rocksDBStorage, DefaultMessageStore messageStore) { this.rocksDBConsumeQueueTable = rocksDBConsumeQueueTable; this.rocksDBStorage = rocksDBStorage; this.messageStore = messageStore; this.topicQueueMinOffset = new ConcurrentHashMap<>(1024); this.topicQueueMaxCqOffset = new ConcurrentHashMap<>(1024); this.maxPhyOffsetBB = ByteBuffer.allocateDirect(8); } public void load() { this.offsetCFH = this.rocksDBStorage.getOffsetCFHandle(); loadMaxConsumeQueueOffsets(); } public Set scanAllQueueIdInTopic(String topic) throws RocksDBException { Set queueIdSet = new HashSet<>(); byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.allocate(OFFSET_KEY_LENGTH_WITHOUT_TOPIC_QUEUE_ID_BYTES + topicBytes.length); byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1).put(MAX_BYTES).put(CTRL_1); byteBuffer.flip(); byte[] prefix = byteBuffer.array(); rocksDBStorage.iterate(offsetCFH, prefix, (keyBytes, unused) -> { ByteBuffer keyBuffer = ByteBuffer.wrap(keyBytes); keyBuffer.position(prefix.length); int queueId = keyBuffer.getInt(); queueIdSet.add(queueId); }); return queueIdSet; } private void loadMaxConsumeQueueOffsets() { lmqCounter.set(0); Function predicate = entry -> entry.type == OffsetEntryType.MAXIMUM; Consumer fn = entry -> { topicQueueMaxCqOffset.putIfAbsent(entry.topic + "-" + entry.queueId, entry.offset); if (MixAll.isLmq(entry.topic)) { lmqCounter.incrementAndGet(); } log.info("LoadMaxConsumeQueueOffsets Max {}:{} --> {}|{}", entry.topic, entry.queueId, entry.offset, entry.commitLogOffset); }; try { forEach(predicate, fn); log.info("lmq count from maxConsumeQueueOffset table. {}", lmqCounter.get()); } catch (RocksDBException e) { log.error("Failed to maximum consume queue offset", e); } } public void forEach(Function predicate, Consumer fn) throws RocksDBException { try (RocksIterator iterator = this.rocksDBStorage.seekOffsetCF()) { if (null == iterator) { return; } int keyBufferCapacity = 256; iterator.seekToFirst(); ByteBuffer keyBuffer = ByteBuffer.allocateDirect(keyBufferCapacity); ByteBuffer valueBuffer = ByteBuffer.allocateDirect(16); while (iterator.isValid()) { // parse key buffer according to key layout keyBuffer.clear(); // clear position and limit before reuse int total = iterator.key(keyBuffer); if (total > keyBufferCapacity) { keyBufferCapacity = total; PlatformDependent.freeDirectBuffer(keyBuffer); keyBuffer = ByteBuffer.allocateDirect(keyBufferCapacity); continue; } if (keyBuffer.remaining() <= OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES) { iterator.next(); ROCKSDB_LOG.warn("Malformed Key/Value pair"); continue; } int topicLength = keyBuffer.getInt(); byte ctrl1 = keyBuffer.get(); assert ctrl1 == CTRL_1; byte[] topicBytes = new byte[topicLength]; keyBuffer.get(topicBytes); ctrl1 = keyBuffer.get(); assert ctrl1 == CTRL_1; String topic = new String(topicBytes, StandardCharsets.UTF_8); byte[] minMax = new byte[3]; keyBuffer.get(minMax); OffsetEntryType entryType; if (Arrays.equals(minMax, MAX_BYTES)) { entryType = OffsetEntryType.MAXIMUM; } else { entryType = OffsetEntryType.MINIMUM; } ctrl1 = keyBuffer.get(); assert ctrl1 == CTRL_1; assert keyBuffer.remaining() == Integer.BYTES; int queueId = keyBuffer.getInt(); // Read and parse value buffer according to value layout valueBuffer.clear(); // clear position and limit before reuse total = iterator.value(valueBuffer); if (total != Long.BYTES + Long.BYTES) { // Skip system checkpoint topic as its value is only 8 bytes iterator.next(); continue; } long commitLogOffset = valueBuffer.getLong(); long consumeOffset = valueBuffer.getLong(); OffsetEntry entry = new OffsetEntry(); entry.topic = topic; entry.queueId = queueId; entry.type = entryType; entry.offset = consumeOffset; entry.commitLogOffset = commitLogOffset; if (predicate.apply(entry)) { fn.accept(entry); } iterator.next(); } // clean up direct buffers PlatformDependent.freeDirectBuffer(keyBuffer); PlatformDependent.freeDirectBuffer(valueBuffer); } } public void putMaxPhyAndCqOffset(final Map> tempTopicQueueMaxOffsetMap, final WriteBatch writeBatch, final long maxPhyOffset) throws RocksDBException { for (Map.Entry> entry : tempTopicQueueMaxOffsetMap.entrySet()) { writeBatch.put(this.offsetCFH, entry.getKey(), entry.getValue().getObject1()); } appendMaxPhyOffset(writeBatch, maxPhyOffset); } public void putHeapMaxCqOffset(final Map> tempTopicQueueMaxOffsetMap) { for (Map.Entry> entry : tempTopicQueueMaxOffsetMap.entrySet()) { DispatchEntry dispatchEntry = entry.getValue().getObject2(); String topic = new String(dispatchEntry.topic, StandardCharsets.UTF_8); putHeapMaxCqOffset(topic, dispatchEntry.queueId, dispatchEntry.queueOffset); } } /** * When topic is deleted, we clean up its offset info in rocksdb. * * @param topic * @param queueId * @throws RocksDBException */ public void destroyOffset(String topic, int queueId, WriteBatch writeBatch) throws RocksDBException { final byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); final ByteBuffer minOffsetKey = buildOffsetKeyByteBuffer(topicBytes, queueId, false); byte[] minOffsetBytes = this.rocksDBStorage.getOffset(minOffsetKey.array()); Long startCQOffset = (minOffsetBytes != null) ? ByteBuffer.wrap(minOffsetBytes).getLong(OFFSET_CQ_OFFSET) : null; final ByteBuffer maxOffsetKey = buildOffsetKeyByteBuffer(topicBytes, queueId, true); byte[] maxOffsetBytes = this.rocksDBStorage.getOffset(maxOffsetKey.array()); Long endCQOffset = (maxOffsetBytes != null) ? ByteBuffer.wrap(maxOffsetBytes).getLong(OFFSET_CQ_OFFSET) : null; writeBatch.delete(this.offsetCFH, minOffsetKey.array()); writeBatch.delete(this.offsetCFH, maxOffsetKey.array()); String topicQueueId = buildTopicQueueId(topic, queueId); removeHeapMinCqOffset(topicQueueId); removeHeapMaxCqOffset(topicQueueId); log.info("RocksDB offset table delete topic: {}, queueId: {}, minOffset: {}, maxOffset: {}", topic, queueId, startCQOffset, endCQOffset); } private void appendMaxPhyOffset(final WriteBatch writeBatch, final long maxPhyOffset) throws RocksDBException { final ByteBuffer maxPhyOffsetBB = this.maxPhyOffsetBB; maxPhyOffsetBB.position(0).limit(8); maxPhyOffsetBB.putLong(maxPhyOffset); maxPhyOffsetBB.flip(); INNER_CHECKPOINT_TOPIC.position(0).limit(INNER_CHECKPOINT_TOPIC_LEN); writeBatch.put(this.offsetCFH, INNER_CHECKPOINT_TOPIC, maxPhyOffsetBB); } public long getMaxPhyOffset() throws RocksDBException { byte[] valueBytes = this.rocksDBStorage.getOffset(MAX_PHYSICAL_OFFSET_CHECKPOINT_KEY); if (valueBytes == null) { return 0; } ByteBuffer valueBB = ByteBuffer.wrap(valueBytes); return valueBB.getLong(0); } /** * Traverse the offset table to find dirty topic * * @param existTopicSet * @return */ public Map> iterateOffsetTable2FindDirty(final Set existTopicSet) { Map> topicQueueIdToBeDeletedMap = new HashMap<>(); try (RocksIterator iterator = rocksDBStorage.seekOffsetCF()) { if (iterator == null) { return topicQueueIdToBeDeletedMap; } for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { byte[] key = iterator.key(); byte[] value = iterator.value(); if (key == null || key.length <= OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES || value == null || value.length != OFFSET_VALUE_LENGTH) { continue; } ByteBuffer keyBB = ByteBuffer.wrap(key); int topicLen = keyBB.getInt(0); byte[] topicBytes = new byte[topicLen]; /* * "Topic Bytes Array Size" + "CTRL_1" = 4 + 1 */ keyBB.position(4 + 1); keyBB.get(topicBytes); String topic = new String(topicBytes, StandardCharsets.UTF_8); if (TopicValidator.isSystemTopic(topic)) { continue; } // LMQ topic offsets should NOT be removed if (MixAll.isLmq(topic)) { continue; } /* * "Topic Bytes Array Size" + "CTRL_1" + "Topic Bytes Array" + "CTRL_1" + "Max(min)" + "CTRL_1" * = 4 + 1 + topicLen + 1 + 3 + 1 */ int queueId = keyBB.getInt(4 + 1 + topicLen + 1 + 3 + 1); if (!existTopicSet.contains(topic)) { ByteBuffer valueBB = ByteBuffer.wrap(value); long cqOffset = valueBB.getLong(OFFSET_CQ_OFFSET); Set topicQueueIdSet = topicQueueIdToBeDeletedMap.get(topic); if (topicQueueIdSet == null) { Set newSet = new HashSet<>(); newSet.add(queueId); topicQueueIdToBeDeletedMap.put(topic, newSet); } else { topicQueueIdSet.add(queueId); } ERROR_LOG.info("RocksDBConsumeQueueOffsetTable has dirty cqOffset. topic: {}, queueId: {}, cqOffset: {}", topic, queueId, cqOffset); } } } catch (Exception e) { ERROR_LOG.error("iterateOffsetTable2MarkDirtyCQ Failed.", e); } return topicQueueIdToBeDeletedMap; } public Long getMaxCqOffset(String topic, int queueId) throws RocksDBException { Long maxCqOffset = getHeapMaxCqOffset(topic, queueId); if (maxCqOffset == null) { final ByteBuffer byteBuffer = getMaxPhyAndCqOffsetInKV(topic, queueId); maxCqOffset = (byteBuffer != null) ? byteBuffer.getLong(OFFSET_CQ_OFFSET) : null; String topicQueueId = buildTopicQueueId(topic, queueId); long offset = maxCqOffset != null ? maxCqOffset : -1L; Long prev = this.topicQueueMaxCqOffset.putIfAbsent(topicQueueId, offset); if (null == prev) { ROCKSDB_LOG.info("Max offset of {} is initialized to {} according to RocksDB", topicQueueId, offset); } if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("updateMaxOffsetInQueue. {}, {}", topicQueueId, offset); } } return maxCqOffset; } /** * truncate dirty offset in rocksdb * * @param offsetToTruncate * @throws RocksDBException */ public void truncateDirty(long offsetToTruncate) throws RocksDBException { correctMaxPyhOffset(offsetToTruncate); Function predicate = entry -> { if (entry.type == OffsetEntryType.MINIMUM) { return false; } // Normal entry offset MUST have the following inequality // entry commit-log offset + message-size-in-bytes <= offsetToTruncate; // otherwise, the consume queue contains dirty records to truncate; // // If the broker node is configured to use async-flush, it's possible consume queues contain // pointers to message records that is not flushed and lost during restart. return entry.commitLogOffset >= offsetToTruncate; }; Consumer fn = entry -> { try { truncateDirtyOffset(entry.topic, entry.queueId); } catch (RocksDBException e) { log.error("Failed to truncate maximum offset of consume queue[topic={}, queue-id={}]", entry.topic, entry.queueId, e); } }; forEach(predicate, fn); } private Pair isMinOffsetOk(final String topic, final int queueId, final long minPhyOffset) throws RocksDBException { PhyAndCQOffset phyAndCQOffset = getHeapMinOffset(topic, queueId); if (phyAndCQOffset != null) { final long phyOffset = phyAndCQOffset.getPhyOffset(); final long cqOffset = phyAndCQOffset.getCqOffset(); return (phyOffset >= minPhyOffset) ? new Pair<>(true, cqOffset) : new Pair<>(false, cqOffset); } ByteBuffer byteBuffer = getMinPhyAndCqOffsetInKV(topic, queueId); if (byteBuffer == null) { return new Pair<>(false, 0L); } final long phyOffset = byteBuffer.getLong(OFFSET_PHY_OFFSET); final long cqOffset = byteBuffer.getLong(OFFSET_CQ_OFFSET); if (phyOffset >= minPhyOffset) { String topicQueueId = buildTopicQueueId(topic, queueId); PhyAndCQOffset newPhyAndCQOffset = new PhyAndCQOffset(phyOffset, cqOffset); this.topicQueueMinOffset.putIfAbsent(topicQueueId, newPhyAndCQOffset); if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("updateMinOffsetInQueue. {}, {}", topicQueueId, newPhyAndCQOffset); } return new Pair<>(true, cqOffset); } return new Pair<>(false, cqOffset); } private void truncateDirtyOffset(String topic, int queueId) throws RocksDBException { final ByteBuffer byteBuffer = getMaxPhyAndCqOffsetInKV(topic, queueId); if (byteBuffer == null) { return; } long maxPhyOffset = byteBuffer.getLong(OFFSET_PHY_OFFSET); long maxCqOffset = byteBuffer.getLong(OFFSET_CQ_OFFSET); long maxPhyOffsetInCQ = getMaxPhyOffset(); if (maxPhyOffset >= maxPhyOffsetInCQ) { correctMaxCqOffset(topic, queueId, maxCqOffset, maxPhyOffsetInCQ); Long newMaxCqOffset = getHeapMaxCqOffset(topic, queueId); ROCKSDB_LOG.warn("truncateDirtyLogicFile topic: {}, queueId: {} from {} to {}", topic, queueId, maxPhyOffset, newMaxCqOffset); } } private void correctMaxPyhOffset(long maxPhyOffset) throws RocksDBException { if (!this.rocksDBStorage.hold()) { return; } try (WriteBatch writeBatch = new WriteBatch()) { long oldMaxPhyOffset = getMaxPhyOffset(); if (oldMaxPhyOffset <= maxPhyOffset) { return; } log.info("correctMaxPyhOffset, oldMaxPhyOffset={}, newMaxPhyOffset={}", oldMaxPhyOffset, maxPhyOffset); appendMaxPhyOffset(writeBatch, maxPhyOffset); this.rocksDBStorage.batchPut(writeBatch); } catch (RocksDBException e) { ERROR_LOG.error("correctMaxPyhOffset Failed.", e); throw e; } finally { this.rocksDBStorage.release(); } } public long getMinCqOffset(String topic, int queueId) throws RocksDBException { final long minPhyOffset = this.messageStore.getMinPhyOffset(); Pair pair = isMinOffsetOk(topic, queueId, minPhyOffset); final long cqOffset = pair.getObject2(); if (!pair.getObject1() && correctMinCqOffset(topic, queueId, cqOffset, minPhyOffset)) { PhyAndCQOffset phyAndCQOffset = getHeapMinOffset(topic, queueId); if (phyAndCQOffset != null) { if (this.messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("getMinOffsetInQueue miss heap. topic: {}, queueId: {}, old: {}, new: {}", topic, queueId, cqOffset, phyAndCQOffset); } return phyAndCQOffset.getCqOffset(); } } return cqOffset; } public Long getMaxPhyOffset(String topic, int queueId) { try { ByteBuffer byteBuffer = getMaxPhyAndCqOffsetInKV(topic, queueId); if (byteBuffer != null) { return byteBuffer.getLong(OFFSET_PHY_OFFSET); } } catch (Exception e) { ERROR_LOG.info("getMaxPhyOffset error. topic: {}, queueId: {}", topic, queueId); } return null; } private ByteBuffer getMinPhyAndCqOffsetInKV(String topic, int queueId) throws RocksDBException { return getPhyAndCqOffsetInKV(topic, queueId, false); } private ByteBuffer getMaxPhyAndCqOffsetInKV(String topic, int queueId) throws RocksDBException { return getPhyAndCqOffsetInKV(topic, queueId, true); } private ByteBuffer getPhyAndCqOffsetInKV(String topic, int queueId, boolean max) throws RocksDBException { final byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); final ByteBuffer keyBB = buildOffsetKeyByteBuffer(topicBytes, queueId, max); byte[] value = this.rocksDBStorage.getOffset(keyBB.array()); return (value != null) ? ByteBuffer.wrap(value) : null; } private String buildTopicQueueId(final String topic, final int queueId) { return topic + "-" + queueId; } private void putHeapMinCqOffset(final String topic, final int queueId, final long minPhyOffset, final long minCQOffset) { String topicQueueId = buildTopicQueueId(topic, queueId); PhyAndCQOffset phyAndCQOffset = new PhyAndCQOffset(minPhyOffset, minCQOffset); this.topicQueueMinOffset.put(topicQueueId, phyAndCQOffset); } private void putHeapMaxCqOffset(final String topic, final int queueId, final long maxOffset) { String topicQueueId = buildTopicQueueId(topic, queueId); Long prev = this.topicQueueMaxCqOffset.put(topicQueueId, maxOffset); if (prev != null && prev > maxOffset) { ERROR_LOG.error("Max offset of consume-queue[topic={}, queue-id={}] regressed. prev-max={}, current-max={}", topic, queueId, prev, maxOffset); } if (prev != null && prev == -1 && MixAll.isLmq(topic)) { lmqCounter.incrementAndGet(); } if (null == prev && MixAll.isLmq(topic)) { // this usually happens when broker exits abnormally, do nothing here and wait for the next scan to delete it. ERROR_LOG.error("probably recover a lmq which was already deleted. lmq:{}, maxOffset:{}", topic, maxOffset); lmqCounter.incrementAndGet(); } } private PhyAndCQOffset getHeapMinOffset(final String topic, final int queueId) { return this.topicQueueMinOffset.get(buildTopicQueueId(topic, queueId)); } private Long getHeapMaxCqOffset(final String topic, final int queueId) { String topicQueueId = buildTopicQueueId(topic, queueId); return this.topicQueueMaxCqOffset.get(topicQueueId); } private PhyAndCQOffset removeHeapMinCqOffset(String topicQueueId) { return this.topicQueueMinOffset.remove(topicQueueId); } private Long removeHeapMaxCqOffset(String topicQueueId) { Long prev = this.topicQueueMaxCqOffset.remove(topicQueueId); if (prev != null && topicQueueId.startsWith(MixAll.LMQ_PREFIX)) { lmqCounter.decrementAndGet(); } return prev; } public void updateCqOffset(final String topic, final int queueId, final long phyOffset, final long cqOffset, boolean max) throws RocksDBException { if (!this.rocksDBStorage.hold()) { return; } try (WriteBatch writeBatch = new WriteBatch()) { final byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); final ByteBuffer offsetKey = buildOffsetKeyByteBuffer(topicBytes, queueId, max); final ByteBuffer offsetValue = buildOffsetValueByteBuffer(phyOffset, cqOffset); writeBatch.put(this.offsetCFH, offsetKey.array(), offsetValue.array()); this.rocksDBStorage.batchPut(writeBatch); if (max) { putHeapMaxCqOffset(topic, queueId, cqOffset); } else { putHeapMinCqOffset(topic, queueId, phyOffset, cqOffset); } } catch (RocksDBException e) { ERROR_LOG.error("updateCqOffset({}) failed.", max ? "max" : "min", e); throw e; } finally { this.rocksDBStorage.release(); if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("updateCqOffset({}). topic: {}, queueId: {}, phyOffset: {}, cqOffset: {}", max ? "max" : "min", topic, queueId, phyOffset, cqOffset); } } } public int getLmqNum() { return lmqCounter.get(); } public boolean isLmqExist(String lmqTopic) { return this.topicQueueMaxCqOffset.containsKey(buildTopicQueueId(lmqTopic, 0)); } private boolean correctMaxCqOffset(final String topic, final int queueId, final long maxCQOffset, final long maxPhyOffsetInCQ) throws RocksDBException { // 'getMinOffsetInQueue' may correct minCqOffset and put it into heap long minCQOffset = getMinCqOffset(topic, queueId); PhyAndCQOffset minPhyAndCQOffset = getHeapMinOffset(topic, queueId); if (minPhyAndCQOffset == null || minPhyAndCQOffset.getCqOffset() != minCQOffset || minPhyAndCQOffset.getPhyOffset() > maxPhyOffsetInCQ) { ROCKSDB_LOG.info("[BUG] correctMaxCqOffset error! topic: {}, queueId: {}, maxPhyOffsetInCQ: {}, " + "minCqOffset: {}, phyAndCQOffset: {}", topic, queueId, maxPhyOffsetInCQ, minCQOffset, minPhyAndCQOffset); throw new RocksDBException("correctMaxCqOffset error"); } PhyAndCQOffset targetPhyAndCQOffset = this.rocksDBConsumeQueueTable.binarySearchInCQ(topic, queueId, maxCQOffset, minCQOffset, maxPhyOffsetInCQ, false); long targetCQOffset = targetPhyAndCQOffset.getCqOffset(); long targetPhyOffset = targetPhyAndCQOffset.getPhyOffset(); if (targetCQOffset == -1) { if (maxCQOffset != minCQOffset) { updateCqOffset(topic, queueId, minPhyAndCQOffset.getPhyOffset(), minCQOffset, true); } if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("correct error. {}, {}, {}, {}, {}", topic, queueId, minCQOffset, maxCQOffset, minPhyAndCQOffset.getPhyOffset()); } return false; } else { updateCqOffset(topic, queueId, targetPhyOffset, targetCQOffset, true); return true; } } private boolean correctMinCqOffset(final String topic, final int queueId, final long minCQOffset, final long minPhyOffset) throws RocksDBException { final ByteBuffer maxBB = getMaxPhyAndCqOffsetInKV(topic, queueId); if (maxBB == null) { updateCqOffset(topic, queueId, minPhyOffset, 0L, false); return true; } final long maxPhyOffset = maxBB.getLong(OFFSET_PHY_OFFSET); final long maxCQOffset = maxBB.getLong(OFFSET_CQ_OFFSET); if (maxPhyOffset < minPhyOffset) { updateCqOffset(topic, queueId, minPhyOffset, maxCQOffset + 1, false); return true; } PhyAndCQOffset phyAndCQOffset = this.rocksDBConsumeQueueTable.binarySearchInCQ(topic, queueId, maxCQOffset, minCQOffset, minPhyOffset, true); long targetCQOffset = phyAndCQOffset.getCqOffset(); long targetPhyOffset = phyAndCQOffset.getPhyOffset(); if (targetCQOffset == -1) { if (maxCQOffset != minCQOffset) { updateCqOffset(topic, queueId, maxPhyOffset, maxCQOffset, false); } if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("correct error. {}, {}, {}, {}, {}", topic, queueId, minCQOffset, maxCQOffset, minPhyOffset); } return false; } else { updateCqOffset(topic, queueId, targetPhyOffset, targetCQOffset, false); return true; } } public static Pair getOffsetByteBufferPair() { ByteBuffer offsetKey = ByteBuffer.allocateDirect(RocksDBConsumeQueueStore.MAX_KEY_LEN); ByteBuffer offsetValue = ByteBuffer.allocateDirect(OFFSET_VALUE_LENGTH); return new Pair<>(offsetKey, offsetValue); } static void buildOffsetKeyAndValueByteBuffer(final Pair offsetBBPair, final DispatchEntry entry) { final ByteBuffer offsetKey = offsetBBPair.getObject1(); buildOffsetKeyByteBuffer(offsetKey, entry.topic, entry.queueId, true); final ByteBuffer offsetValue = offsetBBPair.getObject2(); buildOffsetValueByteBuffer(offsetValue, entry.commitLogOffset, entry.queueOffset); } private static ByteBuffer buildOffsetKeyByteBuffer(final byte[] topicBytes, final int queueId, final boolean max) { ByteBuffer byteBuffer = ByteBuffer.allocate(OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length); buildOffsetKeyByteBuffer0(byteBuffer, topicBytes, queueId, max); return byteBuffer; } public static void buildOffsetKeyByteBuffer(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final boolean max) { byteBuffer.position(0).limit(OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length); buildOffsetKeyByteBuffer0(byteBuffer, topicBytes, queueId, max); } private static void buildOffsetKeyByteBuffer0(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final boolean max) { byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1); if (max) { byteBuffer.put(MAX_BYTES); } else { byteBuffer.put(MIN_BYTES); } byteBuffer.put(CTRL_1).putInt(queueId); byteBuffer.flip(); } private static void buildOffsetValueByteBuffer(final ByteBuffer byteBuffer, final long phyOffset, final long cqOffset) { byteBuffer.position(0).limit(OFFSET_VALUE_LENGTH); buildOffsetValueByteBuffer0(byteBuffer, phyOffset, cqOffset); } private static ByteBuffer buildOffsetValueByteBuffer(final long phyOffset, final long cqOffset) { final ByteBuffer byteBuffer = ByteBuffer.allocate(OFFSET_VALUE_LENGTH); buildOffsetValueByteBuffer0(byteBuffer, phyOffset, cqOffset); return byteBuffer; } private static void buildOffsetValueByteBuffer0(final ByteBuffer byteBuffer, final long phyOffset, final long cqOffset) { byteBuffer.putLong(phyOffset).putLong(cqOffset); byteBuffer.flip(); } static class PhyAndCQOffset { private final long phyOffset; private final long cqOffset; public PhyAndCQOffset(final long phyOffset, final long cqOffset) { this.phyOffset = phyOffset; this.cqOffset = cqOffset; } public long getPhyOffset() { return this.phyOffset; } public long getCqOffset() { return this.cqOffset; } @Override public String toString() { return "[cqOffset=" + cqOffset + ", phyOffset=" + phyOffset + "]"; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.exception.StoreException; import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage; import org.rocksdb.FlushOptions; import org.rocksdb.RocksDBException; import org.rocksdb.Statistics; import org.rocksdb.WriteBatch; public class RocksDBConsumeQueueStore extends AbstractConsumeQueueStore { private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final Logger ROCKSDB_LOG = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); private static final int DEFAULT_BYTE_BUFFER_CAPACITY = 16; public static final int MAX_KEY_LEN = 300; private final ScheduledExecutorService scheduledExecutorService; private final String storePath; /** * we use two tables with different ColumnFamilyHandle, called RocksDBConsumeQueueTable and RocksDBConsumeQueueOffsetTable. * 1.RocksDBConsumeQueueTable uses to store CqUnit[physicalOffset, msgSize, tagHashCode, msgStoreTime] * 2.RocksDBConsumeQueueOffsetTable uses to store physicalOffset and consumeQueueOffset(@see PhyAndCQOffset) of topic-queueId */ private final ConsumeQueueRocksDBStorage rocksDBStorage; private final RocksDBConsumeQueueTable rocksDBConsumeQueueTable; private final RocksDBConsumeQueueOffsetTable rocksDBConsumeQueueOffsetTable; private final List> cqBBPairList; private final List> offsetBBPairList; private final Map> tempTopicQueueMaxOffsetMap; private volatile boolean isCQError = false; private int consumeQueueByteBufferCacheIndex; private int offsetBufferCacheIndex; private final OffsetInitializer offsetInitializer; private final RocksGroupCommitService groupCommitService; private final AtomicReference serviceState = new AtomicReference<>(ServiceState.CREATE_JUST); private final RocksDBCleanConsumeQueueService cleanConsumeQueueService; private long dispatchFromPhyOffset; /** * there are two threads to notify longPolling when build cq successfully * * @see DefaultMessageStore.ReputMessageService#doReput() * @see RocksGroupCommitService#groupCommit() *

    * RocksDB CQ is build by RocksGroupCommitService, so we do not need to notify longPolling in * ReputMessageService */ public RocksDBConsumeQueueStore(DefaultMessageStore messageStore) { super(messageStore); messageStore.setNotifyMessageArriveInBatch(true); this.storePath = StorePathConfigHelper.getStorePathConsumeQueue(messageStoreConfig.getStorePathRootDir()); this.rocksDBStorage = new ConsumeQueueRocksDBStorage(messageStore, storePath); this.rocksDBConsumeQueueTable = new RocksDBConsumeQueueTable(rocksDBStorage, messageStore); this.rocksDBConsumeQueueOffsetTable = new RocksDBConsumeQueueOffsetTable(rocksDBConsumeQueueTable, rocksDBStorage, messageStore); this.offsetInitializer = new OffsetInitializerRocksDBImpl(this); this.groupCommitService = new RocksGroupCommitService(this); this.cqBBPairList = new ArrayList<>(16); this.offsetBBPairList = new ArrayList<>(DEFAULT_BYTE_BUFFER_CAPACITY); for (int i = 0; i < DEFAULT_BYTE_BUFFER_CAPACITY; i++) { this.cqBBPairList.add(RocksDBConsumeQueueTable.getCQByteBufferPair()); this.offsetBBPairList.add(RocksDBConsumeQueueOffsetTable.getOffsetByteBufferPair()); } this.tempTopicQueueMaxOffsetMap = new HashMap<>(); this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryImpl("RocksDBConsumeQueueStoreScheduledThread", messageStore.getBrokerIdentity())); this.cleanConsumeQueueService = new RocksDBCleanConsumeQueueService(); } private Pair getCQByteBufferPair() { int idx = consumeQueueByteBufferCacheIndex++; if (idx >= cqBBPairList.size()) { this.cqBBPairList.add(RocksDBConsumeQueueTable.getCQByteBufferPair()); } return cqBBPairList.get(idx); } private Pair getOffsetByteBufferPair() { int idx = offsetBufferCacheIndex++; if (idx >= offsetBBPairList.size()) { this.offsetBBPairList.add(RocksDBConsumeQueueOffsetTable.getOffsetByteBufferPair()); } return offsetBBPairList.get(idx); } @Override public void start() { if (serviceState.compareAndSet(ServiceState.CREATE_JUST, ServiceState.RUNNING)) { log.info("RocksDB ConsumeQueueStore start!"); this.groupCommitService.start(); this.scheduledExecutorService.scheduleAtFixedRate(() -> { this.rocksDBStorage.statRocksdb(ROCKSDB_LOG); }, 10, this.messageStoreConfig.getStatRocksDBCQIntervalSec(), TimeUnit.SECONDS); this.scheduledExecutorService.scheduleWithFixedDelay(() -> { cleanDirty(messageStore.getTopicConfigs().keySet()); }, 10, this.messageStoreConfig.getCleanRocksDBDirtyCQIntervalMin(), TimeUnit.MINUTES); messageStore.getScheduledCleanQueueExecutorService().scheduleAtFixedRate(this.cleanConsumeQueueService::run, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS); } } private void cleanDirty(final Set existTopicSet) { try { Map> topicQueueIdToBeDeletedMap = this.rocksDBConsumeQueueOffsetTable.iterateOffsetTable2FindDirty(existTopicSet); for (Map.Entry> entry : topicQueueIdToBeDeletedMap.entrySet()) { String topic = entry.getKey(); for (int queueId : entry.getValue()) { destroy(new RocksDBConsumeQueue(topic, queueId)); } } } catch (Exception e) { log.error("cleanUnusedTopic Failed.", e); } } @Override public boolean load() { boolean result = this.rocksDBStorage.start(); this.rocksDBConsumeQueueTable.load(); this.rocksDBConsumeQueueOffsetTable.load(); log.info("load rocksdb consume queue {}.", result ? "OK" : "Failed"); return result; } @Override public void recover(boolean concurrently) throws RocksDBException { start(); this.dispatchFromPhyOffset = getMaxPhyOffsetInConsumeQueue(); } @Override public Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException { return dispatchFromPhyOffset; } @Override public boolean shutdown() { if (serviceState.compareAndSet(ServiceState.RUNNING, ServiceState.SHUTDOWN_ALREADY)) { if (this.groupCommitService != null) { this.groupCommitService.shutdown(); } if (this.scheduledExecutorService != null) { this.scheduledExecutorService.shutdown(); } return shutdownInner(); } return true; } private boolean shutdownInner() { if (this.rocksDBStorage != null) { return this.rocksDBStorage.shutdown(); } return true; } @Override public void putMessagePositionInfoWrapper(DispatchRequest request) throws RocksDBException { if (null == request) { return; } try { groupCommitService.putRequest(request); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } public void putMessagePosition(List requests) throws RocksDBException { final int maxRetries = 30; for (int i = 0; i < maxRetries; i++) { if (putMessagePosition0(requests)) { if (this.isCQError) { this.messageStore.getRunningFlags().clearLogicsQueueError(); this.isCQError = false; } return; } else { ERROR_LOG.warn("Put cq Failed. retryTime: {}", i); try { Thread.sleep(100); } catch (InterruptedException ignored) { } } } if (!this.isCQError) { ERROR_LOG.error("[BUG] put CQ Failed."); this.messageStore.getRunningFlags().makeLogicsQueueError(); this.isCQError = true; } throw new RocksDBException("put CQ Failed"); } private boolean putMessagePosition0(List requests) { if (!this.rocksDBStorage.hold()) { return false; } try (WriteBatch writeBatch = new WriteBatch()) { final int size = requests.size(); if (size == 0) { return true; } long maxPhyOffset = 0; for (int i = size - 1; i >= 0; i--) { final DispatchRequest request = requests.get(i); DispatchEntry entry = DispatchEntry.from(request); dispatch(entry, writeBatch); dispatchLMQ(request, writeBatch); final int msgSize = request.getMsgSize(); final long phyOffset = request.getCommitLogOffset(); if (phyOffset + msgSize >= maxPhyOffset) { maxPhyOffset = phyOffset + msgSize; } } this.rocksDBConsumeQueueOffsetTable.putMaxPhyAndCqOffset(tempTopicQueueMaxOffsetMap, writeBatch, maxPhyOffset); this.rocksDBStorage.batchPut(writeBatch); this.rocksDBConsumeQueueOffsetTable.putHeapMaxCqOffset(tempTopicQueueMaxOffsetMap); notifyMessageArriveAndClear(requests); return true; } catch (Exception e) { ERROR_LOG.error("putMessagePosition0 failed.", e); return false; } finally { tempTopicQueueMaxOffsetMap.clear(); consumeQueueByteBufferCacheIndex = 0; offsetBufferCacheIndex = 0; this.rocksDBStorage.release(); } } private void dispatch(@Nonnull DispatchEntry entry, @Nonnull final WriteBatch writeBatch) throws RocksDBException { this.rocksDBConsumeQueueTable.buildAndPutCQByteBuffer(getCQByteBufferPair(), entry, writeBatch); updateTempTopicQueueMaxOffset(getOffsetByteBufferPair(), entry); } private void updateTempTopicQueueMaxOffset(final Pair offsetBBPair, final DispatchEntry entry) { RocksDBConsumeQueueOffsetTable.buildOffsetKeyAndValueByteBuffer(offsetBBPair, entry); ByteBuffer topicQueueId = offsetBBPair.getObject1(); ByteBuffer maxOffsetBB = offsetBBPair.getObject2(); Pair old = tempTopicQueueMaxOffsetMap.get(topicQueueId); if (old == null) { tempTopicQueueMaxOffsetMap.put(topicQueueId, new Pair<>(maxOffsetBB, entry)); } else { long oldMaxOffset = old.getObject1().getLong(RocksDBConsumeQueueOffsetTable.OFFSET_CQ_OFFSET); long maxOffset = maxOffsetBB.getLong(RocksDBConsumeQueueOffsetTable.OFFSET_CQ_OFFSET); if (maxOffset >= oldMaxOffset) { ERROR_LOG.error("cqOffset invalid1. old: {}, now: {}", oldMaxOffset, maxOffset); } } } private void dispatchLMQ(@Nonnull DispatchRequest request, @Nonnull final WriteBatch writeBatch) throws RocksDBException { if (!messageStoreConfig.isEnableLmq() || !request.containsLMQ()) { return; } Map map = request.getPropertiesMap(); String lmqNames = map.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); String lmqOffsets = map.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET); String[] queues = lmqNames.split(MixAll.LMQ_DISPATCH_SEPARATOR); String[] queueOffsets = lmqOffsets.split(MixAll.LMQ_DISPATCH_SEPARATOR); if (queues.length != queueOffsets.length) { ERROR_LOG.error("[bug] queues.length!=queueOffsets.length ", request.getTopic()); return; } for (int i = 0; i < queues.length; i++) { String queueName = queues[i]; DispatchEntry entry = DispatchEntry.from(request); long queueOffset = Long.parseLong(queueOffsets[i]); int queueId = request.getQueueId(); if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { queueId = MixAll.LMQ_QUEUE_ID; } entry.queueId = queueId; entry.queueOffset = queueOffset; entry.topic = queueName.getBytes(StandardCharsets.UTF_8); log.debug("Dispatch LMQ[{}:{}]:{} --> {}", queueName, queueId, queueOffset, entry.commitLogOffset); dispatch(entry, writeBatch); } } private void notifyMessageArriveAndClear(List requests) { try { for (DispatchRequest dp : requests) { this.messageStore.notifyMessageArriveIfNecessary(dp); } requests.clear(); } catch (Exception e) { ERROR_LOG.error("notifyMessageArriveAndClear Failed.", e); } } public Statistics getStatistics() { return rocksDBStorage.getStatistics(); } public List rangeQuery(final String topic, final int queueId, final long startIndex, final int num) throws RocksDBException { return this.rocksDBConsumeQueueTable.rangeQuery(topic, queueId, startIndex, num); } public ByteBuffer get(final String topic, final int queueId, final long cqOffset) throws RocksDBException { return this.rocksDBConsumeQueueTable.getCQInKV(topic, queueId, cqOffset); } /** * Try to set topicQueueTable = new HashMap<>(), otherwise it will cause bug when broker role changes. * And unlike method in DefaultMessageStore, we don't need to really recover topic queue table advance, * because we can recover topic queue table from rocksdb when we need to use it. * @see RocksDBConsumeQueue#assignQueueOffset * * @see RocksDBConsumeQueue#increaseQueueOffset(QueueOffsetOperator, MessageExtBrokerInner, short) * @see org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable#getMinCqOffset(String, int) */ @Override public void recoverOffsetTable(long minPhyOffset) { this.setTopicQueueTable(new ConcurrentHashMap<>()); } @Override public void destroy(boolean loadAfterDestroy) { try { shutdownInner(); FileUtils.deleteDirectory(new File(this.storePath)); } catch (Exception e) { ERROR_LOG.error("destroy cq Failed. {}", this.storePath, e); } if (loadAfterDestroy) { load(); } } @Override public void destroy(ConsumeQueueInterface consumeQueue) throws RocksDBException { String topic = consumeQueue.getTopic(); int queueId = consumeQueue.getQueueId(); if (StringUtils.isEmpty(topic) || queueId < 0 || !this.rocksDBStorage.hold()) { return; } try (WriteBatch writeBatch = new WriteBatch()) { this.rocksDBConsumeQueueTable.destroyCQ(topic, queueId, writeBatch); this.rocksDBConsumeQueueOffsetTable.destroyOffset(topic, queueId, writeBatch); this.rocksDBStorage.batchPut(writeBatch); } catch (RocksDBException e) { ERROR_LOG.error("kv deleteTopic {} Failed.", topic, e); throw e; } finally { this.rocksDBStorage.release(); } } /** * ConsumerQueueTable, as an in-memory data structure, uses lazy loading mechanism in RocksDBConsumeQueueStore. * This means that when the broker restarts, it may not be able to retrieve all ConsumerQueues from the table. * Therefore, before deleting a topic, we need to attempt to build all ConsumerQueues under that topic to ensure * the completeness of the deletion operation. */ @Override public boolean deleteTopic(String topic) { try { Set queueIds = rocksDBConsumeQueueOffsetTable.scanAllQueueIdInTopic(topic); queueIds.forEach(queueId -> findOrCreateConsumeQueue(topic, queueId)); } catch (RocksDBException e) { ERROR_LOG.error("Failed to scan queueIds for topic. topic={}", topic, e); } return super.deleteTopic(topic); } @Override public void flush() throws StoreException { try (FlushOptions flushOptions = new FlushOptions()) { flushOptions.setWaitForFlush(true); flushOptions.setAllowWriteStall(true); this.rocksDBStorage.flush(flushOptions); } catch (RocksDBException e) { throw new StoreException(e); } } @Override public void checkSelf() { // ignored } /** * We do not need to truncate dirty CQ in RocksDBConsumeQueueTable, Because dirty CQ in RocksDBConsumeQueueTable * will be rewritten by new KV when new messages are appended or will be cleaned up when topics are deleted. * But dirty offset info in RocksDBConsumeQueueOffsetTable must be truncated, because we use offset info in * RocksDBConsumeQueueOffsetTable to rebuild topicQueueTable(@see RocksDBConsumeQueue#increaseQueueOffset). * * @param offsetToTruncate CommitLog offset to truncate to * @throws RocksDBException If there is any error. */ @Override public void truncateDirty(long offsetToTruncate) throws RocksDBException { long maxPhyOffsetInRocksdb = getMaxPhyOffsetInConsumeQueue(); if (offsetToTruncate >= maxPhyOffsetInRocksdb) { return; } this.rocksDBConsumeQueueOffsetTable.truncateDirty(offsetToTruncate); } @Override public void cleanExpired(final long minPhyOffset) { this.rocksDBStorage.manualCompaction(minPhyOffset); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) throws RocksDBException { final long minPhysicOffset = this.messageStore.getMinPhyOffset(); long low = this.rocksDBConsumeQueueOffsetTable.getMinCqOffset(topic, queueId); Long high = this.rocksDBConsumeQueueOffsetTable.getMaxCqOffset(topic, queueId); if (high == null || high == -1) { return 0; } return this.rocksDBConsumeQueueTable.binarySearchInCQByTime(topic, queueId, high, low, timestamp, minPhysicOffset, boundaryType); } /** * This method actually returns NEXT slot index to use, starting from 0. For example, if the queue is empty, * it returns 0, pointing to the first slot of the 0-based queue; * * @param topic Topic name * @param queueId Queue ID * @return Index of the next slot to push into * @throws RocksDBException if RocksDB fails to fulfill the request. */ public long getMaxOffsetInQueue(String topic, int queueId) throws RocksDBException { Long maxOffset = this.rocksDBConsumeQueueOffsetTable.getMaxCqOffset(topic, queueId); return (maxOffset != null) ? maxOffset + 1 : 0; } @Override public long getMinOffsetInQueue(String topic, int queueId) throws RocksDBException { return this.rocksDBConsumeQueueOffsetTable.getMinCqOffset(topic, queueId); } public Long getMaxPhyOffsetInConsumeQueue(String topic, int queueId) { return this.rocksDBConsumeQueueOffsetTable.getMaxPhyOffset(topic, queueId); } @Override public long getMaxPhyOffsetInConsumeQueue() throws RocksDBException { return this.rocksDBConsumeQueueOffsetTable.getMaxPhyOffset(); } @Override public ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId) { ConcurrentMap map = this.consumeQueueTable.get(topic); if (null == map) { ConcurrentMap newMap; if (MixAll.isLmq(topic)) { // For LMQ, no need to over allocate internal hashtable newMap = new ConcurrentHashMap<>(1, 1.0F); } else { newMap = new ConcurrentHashMap<>(8); } ConcurrentMap oldMap = this.consumeQueueTable.putIfAbsent(topic, newMap); if (oldMap != null) { map = oldMap; } else { map = newMap; } } ConsumeQueueInterface logic = map.get(queueId); if (logic != null) { return logic; } ConsumeQueueInterface newLogic = new RocksDBConsumeQueue(this.messageStore.getMessageStoreConfig(), this, topic, queueId); ConsumeQueueInterface oldLogic = map.putIfAbsent(queueId, newLogic); return oldLogic != null ? oldLogic : newLogic; } @Override public ConsumeQueueInterface getConsumeQueue(String topic, int queueId) { return findOrCreateConsumeQueue(topic, queueId); } @Override public long getTotalSize() { return 0; } @Override public boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) { return phyOffset <= dispatchFromPhyOffset; } @Override public long getLmqQueueOffset(String topic, int queueId) throws ConsumeQueueException { return queueOffsetOperator.getLmqOffset(topic, queueId, offsetInitializer); } @Override public Long getMaxOffset(String topic, int queueId) throws ConsumeQueueException { if (MixAll.isLmq(topic)) { return getLmqQueueOffset(topic, queueId); } return super.getMaxOffset(topic, queueId); } @Override public int getLmqNum() { return this.rocksDBConsumeQueueOffsetTable.getLmqNum(); } @Override public boolean isLmqExist(String lmqTopic) { return MixAll.isLmq(lmqTopic) ? this.rocksDBConsumeQueueOffsetTable.isLmqExist(lmqTopic) : false; } public boolean isStopped() { return ServiceState.SHUTDOWN_ALREADY == serviceState.get(); } public void updateCqOffset(final String topic, final int queueId, final long phyOffset, final long cqOffset, boolean max) throws RocksDBException { this.rocksDBConsumeQueueOffsetTable.updateCqOffset(topic, queueId, phyOffset, cqOffset, max); } class RocksDBCleanConsumeQueueService { protected long lastPhysicalMinOffset = 0; private final double diskSpaceWarningLevelRatio = Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceWarningLevelRatio", "0.90")); private final double diskSpaceCleanForciblyRatio = Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", "0.85")); public void run() { try { this.deleteExpiredFiles(); } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception. ", e); } } public String getServiceName() { return messageStore.getBrokerConfig().getIdentifier() + ConsumeQueueStore.CleanConsumeQueueService.class.getSimpleName(); } protected void deleteExpiredFiles() { long minOffset = messageStore.getCommitLog().getMinOffset(); if (minOffset > this.lastPhysicalMinOffset) { this.lastPhysicalMinOffset = minOffset; boolean spaceFull = isSpaceToDelete(); boolean timeUp = messageStore.isTimeToDelete(); if (spaceFull || timeUp) { // To delete the CQ Units whose physical offset is smaller min physical offset in commitLog. cleanExpired(minOffset); } messageStore.getIndexService().deleteExpiredFile(minOffset); if (messageStoreConfig.isIndexRocksDBEnable() && null != messageStore.getIndexRocksDBStore()) { messageStore.getIndexRocksDBStore().deleteExpiredIndex(); } } } private boolean isSpaceToDelete() { double ratio = messageStoreConfig.getDiskMaxUsedSpaceRatio() / 100.0; String storePathLogics = StorePathConfigHelper .getStorePathConsumeQueue(messageStoreConfig.getStorePathRootDir()); double logicsRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathLogics); if (logicsRatio > diskSpaceWarningLevelRatio) { boolean diskMaybeFull = messageStore.getRunningFlags().getAndMakeLogicDiskFull(); if (diskMaybeFull) { log.error("logics disk maybe full soon " + logicsRatio + ", so mark disk full"); } } else if (logicsRatio > diskSpaceCleanForciblyRatio) { } else { boolean diskOk = messageStore.getRunningFlags().getAndMakeLogicDiskOK(); if (!diskOk) { log.info("logics disk space OK " + logicsRatio + ", so mark disk ok"); } } if (logicsRatio < 0 || logicsRatio > ratio) { log.info("logics disk maybe full soon, so reclaim space, " + logicsRatio); return true; } return false; } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable.PhyAndCQOffset; import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.RocksDBException; import org.rocksdb.WriteBatch; import static org.apache.rocketmq.common.config.AbstractRocksDBStorage.CTRL_0; import static org.apache.rocketmq.common.config.AbstractRocksDBStorage.CTRL_1; import static org.apache.rocketmq.common.config.AbstractRocksDBStorage.CTRL_2; /** * We use RocksDBConsumeQueueTable to store cqUnit. */ public class RocksDBConsumeQueueTable { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger ROCKSDB_LOG = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); /** * Rocksdb ConsumeQueue's store unit. Format: * *

         * ┌─────────────────────────┬───────────┬───────────────────────┬───────────┬───────────┬───────────┬───────────────────────┐
         * │ Topic Bytes Array Size  │  CTRL_1   │   Topic Bytes Array   │  CTRL_1   │  QueueId  │  CTRL_1   │  ConsumeQueue Offset  │
         * │        (4 Bytes)        │ (1 Bytes) │       (n Bytes)       │ (1 Bytes) │ (4 Bytes) │ (1 Bytes) │     (8 Bytes)         │
         * ├─────────────────────────┴───────────┴───────────────────────┴───────────┴───────────┴───────────┴───────────────────────┤
         * │                                                    Key Unit                                                             │
         * │                                                                                                                         │
         * 
    * *
         * ┌─────────────────────────────┬───────────────────┬──────────────────┬──────────────────┐
         * │  CommitLog Physical Offset  │      Body Size    │   Tag HashCode   │  Msg Store Time  │
         * │        (8 Bytes)            │      (4 Bytes)    │    (8 Bytes)     │    (8 Bytes)     │
         * ├─────────────────────────────┴───────────────────┴──────────────────┴──────────────────┤
         * │                                                    Value Unit                         │
         * │                                                                                       │
         * 
    * ConsumeQueue's store unit. Size: * CommitLog Physical Offset(8) + Body Size(4) + Tag HashCode(8) + Msg Store Time(8) = 28 Bytes */ private static final int PHY_OFFSET_OFFSET = 0; private static final int PHY_MSG_LEN_OFFSET = 8; private static final int MSG_TAG_HASHCODE_OFFSET = 12; private static final int MSG_STORE_TIME_SIZE_OFFSET = 20; public static final int CQ_UNIT_SIZE = 8 + 4 + 8 + 8; /** * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────┬───────────────────────┐ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ QueueId │ CTRL_1 │ ConsumeQueue Offset │ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (4 Bytes) │ (1 Bytes) │ (8 Bytes) │ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────┴───────────────────────┤ */ private static final int CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES = 4 + 1 + 1 + 4 + 1 + 8; /** * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────────────┐ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ QueueId │ CTRL_0(CTRL_2) │ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (4 Bytes) │ (1 Bytes) │ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────────────┤ */ private static final int DELETE_CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES = 4 + 1 + 1 + 4 + 1; private final ConsumeQueueRocksDBStorage rocksDBStorage; private final DefaultMessageStore messageStore; private ColumnFamilyHandle defaultCFH; public RocksDBConsumeQueueTable(ConsumeQueueRocksDBStorage rocksDBStorage, DefaultMessageStore messageStore) { this.rocksDBStorage = rocksDBStorage; this.messageStore = messageStore; } public void load() { this.defaultCFH = this.rocksDBStorage.getDefaultCFHandle(); } public void buildAndPutCQByteBuffer(final Pair cqBBPair, final DispatchEntry request, final WriteBatch writeBatch) throws RocksDBException { final ByteBuffer cqKey = cqBBPair.getObject1(); buildCQKeyByteBuffer(cqKey, request.topic, request.queueId, request.queueOffset); final ByteBuffer cqValue = cqBBPair.getObject2(); buildCQValueByteBuffer(cqValue, request.commitLogOffset, request.messageSize, request.tagCode, request.storeTimestamp); writeBatch.put(this.defaultCFH, cqKey, cqValue); } public ByteBuffer getCQInKV(final String topic, final int queueId, final long cqOffset) throws RocksDBException { final byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); final ByteBuffer keyBB = buildCQKeyByteBuffer(topicBytes, queueId, cqOffset); byte[] value = this.rocksDBStorage.getCQ(keyBB.array()); return (value != null) ? ByteBuffer.wrap(value) : null; } public List rangeQuery(final String topic, final int queueId, final long startIndex, final int num) throws RocksDBException { final byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); final List defaultCFHList = new ArrayList<>(num); final ByteBuffer[] resultList = new ByteBuffer[num]; final List kvIndexList = new ArrayList<>(num); final List kvKeyList = new ArrayList<>(num); for (int i = 0; i < num; i++) { final ByteBuffer keyBB = buildCQKeyByteBuffer(topicBytes, queueId, startIndex + i); kvIndexList.add(i); kvKeyList.add(keyBB.array()); defaultCFHList.add(this.defaultCFH); } int keyNum = kvIndexList.size(); if (keyNum > 0) { List kvValueList = this.rocksDBStorage.multiGet(defaultCFHList, kvKeyList); final int valueNum = kvValueList.size(); if (keyNum != valueNum) { throw new RocksDBException("rocksdb bug, multiGet"); } for (int i = 0; i < valueNum; i++) { byte[] value = kvValueList.get(i); if (value == null) { continue; } ByteBuffer byteBuffer = ByteBuffer.wrap(value); resultList[kvIndexList.get(i)] = byteBuffer; } } final int resultSize = resultList.length; List bbValueList = new ArrayList<>(resultSize); for (int i = 0; i < resultSize; i++) { ByteBuffer byteBuffer = resultList[i]; if (byteBuffer == null) { break; } bbValueList.add(byteBuffer); } return bbValueList; } /** * When topic is deleted, we clean up its CqUnit in rocksdb. * @param topic * @param queueId * @throws RocksDBException */ public void destroyCQ(final String topic, final int queueId, WriteBatch writeBatch) throws RocksDBException { final byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); final ByteBuffer cqStartKey = buildDeleteCQKey(true, topicBytes, queueId); final ByteBuffer cqEndKey = buildDeleteCQKey(false, topicBytes, queueId); writeBatch.deleteRange(this.defaultCFH, cqStartKey.array(), cqEndKey.array()); log.info("Rocksdb consumeQueue table delete topic. {}, {}", topic, queueId); } public long binarySearchInCQByTime(String topic, int queueId, long high, long low, long timestamp, long minPhysicOffset, BoundaryType boundaryType) throws RocksDBException { long result = -1L; long targetOffset = -1L, leftOffset = -1L, rightOffset = -1L; long ceiling = high, floor = low; // Handle the following corner cases first: // 1. store time of (high) < timestamp ByteBuffer buffer = getCQInKV(topic, queueId, ceiling); if (buffer != null) { long storeTime = buffer.getLong(MSG_STORE_TIME_SIZE_OFFSET); if (storeTime < timestamp) { switch (boundaryType) { case LOWER: return ceiling + 1; case UPPER: return ceiling; default: log.warn("Unknown boundary type"); break; } } } // 2. store time of (low) > timestamp buffer = getCQInKV(topic, queueId, floor); if (buffer != null) { long storeTime = buffer.getLong(MSG_STORE_TIME_SIZE_OFFSET); if (storeTime > timestamp) { switch (boundaryType) { case LOWER: return floor; case UPPER: return 0; default: log.warn("Unknown boundary type"); break; } } } while (high >= low) { long midOffset = low + ((high - low) >>> 1); ByteBuffer byteBuffer = getCQInKV(topic, queueId, midOffset); if (byteBuffer == null) { ERROR_LOG.warn("binarySearchInCQByTimeStamp Failed. topic: {}, queueId: {}, timestamp: {}, result: null", topic, queueId, timestamp); low = midOffset + 1; continue; } long phyOffset = byteBuffer.getLong(PHY_OFFSET_OFFSET); if (phyOffset < minPhysicOffset) { low = midOffset + 1; leftOffset = midOffset; continue; } long storeTime = byteBuffer.getLong(MSG_STORE_TIME_SIZE_OFFSET); if (storeTime < 0) { return 0; } else if (storeTime == timestamp) { targetOffset = midOffset; break; } else if (storeTime > timestamp) { high = midOffset - 1; rightOffset = midOffset; } else { low = midOffset + 1; leftOffset = midOffset; } } if (targetOffset != -1) { // offset next to it might also share the same store-timestamp. switch (boundaryType) { case LOWER: { while (true) { long nextOffset = targetOffset - 1; if (nextOffset < floor) { break; } ByteBuffer byteBuffer = getCQInKV(topic, queueId, nextOffset); long storeTime = byteBuffer.getLong(MSG_STORE_TIME_SIZE_OFFSET); if (storeTime != timestamp) { break; } targetOffset = nextOffset; } break; } case UPPER: { while (true) { long nextOffset = targetOffset + 1; if (nextOffset > ceiling) { break; } ByteBuffer byteBuffer = getCQInKV(topic, queueId, nextOffset); long storeTime = byteBuffer.getLong(MSG_STORE_TIME_SIZE_OFFSET); if (storeTime != timestamp) { break; } targetOffset = nextOffset; } break; } default: { log.warn("Unknown boundary type"); break; } } result = targetOffset; } else { switch (boundaryType) { case LOWER: { result = rightOffset; break; } case UPPER: { result = leftOffset; break; } default: { log.warn("Unknown boundary type"); break; } } } return result; } public PhyAndCQOffset binarySearchInCQ(String topic, int queueId, long high, long low, long targetPhyOffset, boolean min) throws RocksDBException { long resultCQOffset = -1L; long resultPhyOffset = -1L; while (high >= low) { long midCQOffset = low + ((high - low) >>> 1); ByteBuffer byteBuffer = getCQInKV(topic, queueId, midCQOffset); if (this.messageStore.getMessageStoreConfig().isEnableRocksDBLog()) { ROCKSDB_LOG.warn("binarySearchInCQ. {}, {}, {}, {}, {}", topic, queueId, midCQOffset, low, high); } if (byteBuffer == null) { low = midCQOffset + 1; continue; } final long phyOffset = byteBuffer.getLong(PHY_OFFSET_OFFSET); if (phyOffset == targetPhyOffset) { if (min) { resultCQOffset = midCQOffset; resultPhyOffset = phyOffset; } break; } else if (phyOffset > targetPhyOffset) { high = midCQOffset - 1; if (min) { resultCQOffset = midCQOffset; resultPhyOffset = phyOffset; } } else { low = midCQOffset + 1; if (!min) { resultCQOffset = midCQOffset; resultPhyOffset = phyOffset; } } } return new PhyAndCQOffset(resultPhyOffset, resultCQOffset); } public static Pair getCQByteBufferPair() { ByteBuffer cqKey = ByteBuffer.allocateDirect(RocksDBConsumeQueueStore.MAX_KEY_LEN); ByteBuffer cqValue = ByteBuffer.allocateDirect(CQ_UNIT_SIZE); return new Pair<>(cqKey, cqValue); } private ByteBuffer buildCQKeyByteBuffer(final byte[] topicBytes, final int queueId, final long cqOffset) { final ByteBuffer byteBuffer = ByteBuffer.allocate(CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length); buildCQKeyByteBuffer0(byteBuffer, topicBytes, queueId, cqOffset); return byteBuffer; } private void buildCQKeyByteBuffer(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final long cqOffset) { byteBuffer.position(0).limit(CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length); buildCQKeyByteBuffer0(byteBuffer, topicBytes, queueId, cqOffset); } private void buildCQKeyByteBuffer0(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final long cqOffset) { byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1).putInt(queueId).put(CTRL_1).putLong(cqOffset); byteBuffer.flip(); } private void buildCQValueByteBuffer(final ByteBuffer byteBuffer, final long phyOffset, final int msgSize, final long tagsCode, final long storeTimestamp) { byteBuffer.position(0).limit(CQ_UNIT_SIZE); buildCQValueByteBuffer0(byteBuffer, phyOffset, msgSize, tagsCode, storeTimestamp); } private void buildCQValueByteBuffer0(final ByteBuffer byteBuffer, final long phyOffset, final int msgSize, final long tagsCode, final long storeTimestamp) { byteBuffer.putLong(phyOffset).putInt(msgSize).putLong(tagsCode).putLong(storeTimestamp); byteBuffer.flip(); } private ByteBuffer buildDeleteCQKey(final boolean start, final byte[] topicBytes, final int queueId) { final ByteBuffer byteBuffer = ByteBuffer.allocate(DELETE_CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length); byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1).putInt(queueId).put(start ? CTRL_0 : CTRL_2); byteBuffer.flip(); return byteBuffer; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/RocksGroupCommitService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.store.DispatchRequest; import org.rocksdb.RocksDBException; public class RocksGroupCommitService extends ServiceThread { private static final int MAX_BUFFER_SIZE = 100_000; private static final int PREFERRED_DISPATCH_REQUEST_COUNT = 256; private final LinkedBlockingQueue buffer; private final RocksDBConsumeQueueStore store; private final List requests = new ArrayList<>(PREFERRED_DISPATCH_REQUEST_COUNT); public RocksGroupCommitService(RocksDBConsumeQueueStore store) { this.store = store; this.buffer = new LinkedBlockingQueue<>(MAX_BUFFER_SIZE); } @Override public String getServiceName() { return "RocksGroupCommit"; } @Override public void run() { log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { this.waitForRunning(10); this.doCommit(); } catch (Exception e) { log.warn("{} service has exception. ", this.getServiceName(), e); } } log.info("{} service end", this.getServiceName()); } public void putRequest(final DispatchRequest request) throws InterruptedException { while (!buffer.offer(request, 3, TimeUnit.SECONDS)) { log.warn("RocksGroupCommitService#buffer is full, 3s elapsed before space becomes available"); } this.wakeup(); } private void doCommit() { while (!buffer.isEmpty()) { while (true) { DispatchRequest dispatchRequest = buffer.poll(); if (null != dispatchRequest) { requests.add(dispatchRequest); } if (requests.isEmpty()) { // buffer has been drained break; } if (null == dispatchRequest || requests.size() >= PREFERRED_DISPATCH_REQUEST_COUNT) { groupCommit(); } } } } private void groupCommit() { while (!store.isStopped()) { try { // putMessagePosition will clear requests after consume queue building completion store.putMessagePosition(requests); break; } catch (RocksDBException e) { log.error("Failed to build consume queue in RocksDB", e); } } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/SparseConsumeQueue.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.logfile.MappedFile; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; public class SparseConsumeQueue extends BatchConsumeQueue { public SparseConsumeQueue( final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore defaultMessageStore) { super(topic, queueId, storePath, mappedFileSize, defaultMessageStore); } public SparseConsumeQueue( final String topic, final int queueId, final String storePath, final int mappedFileSize, final MessageStore defaultMessageStore, final String subfolder) { super(topic, queueId, storePath, mappedFileSize, defaultMessageStore, subfolder); } @Override public void recover() { final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { int index = mappedFiles.size() - 3; if (index < 0) { index = 0; } MappedFile mappedFile = mappedFiles.get(index); ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); int mappedFileOffset = 0; long processOffset = mappedFile.getFileFromOffset(); while (true) { for (int i = 0; i < mappedFileSize; i += CQ_STORE_UNIT_SIZE) { byteBuffer.position(i); long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); byteBuffer.getLong(); //tagscode byteBuffer.getLong(); //timestamp long msgBaseOffset = byteBuffer.getLong(); short batchSize = byteBuffer.getShort(); if (offset >= 0 && size > 0 && msgBaseOffset >= 0 && batchSize > 0) { mappedFileOffset += CQ_STORE_UNIT_SIZE; this.maxMsgPhyOffsetInCommitLog = offset; } else { log.info("Recover current batch consume queue file over, " + "file:{} offset:{} size:{} msgBaseOffset:{} batchSize:{} mappedFileOffset:{}", mappedFile.getFileName(), offset, size, msgBaseOffset, batchSize, mappedFileOffset); if (mappedFileOffset != mappedFileSize) { mappedFile.setWrotePosition(mappedFileOffset); mappedFile.setFlushedPosition(mappedFileOffset); mappedFile.setCommittedPosition(mappedFileOffset); } break; } } index++; if (index >= mappedFiles.size()) { log.info("Recover last batch consume queue file over, last mapped file:{} ", mappedFile.getFileName()); break; } else { mappedFile = mappedFiles.get(index); byteBuffer = mappedFile.sliceByteBuffer(); processOffset = mappedFile.getFileFromOffset(); mappedFileOffset = 0; log.info("Recover next batch consume queue file: " + mappedFile.getFileName()); } } processOffset += mappedFileOffset; mappedFileQueue.setFlushedWhere(processOffset); mappedFileQueue.setCommittedWhere(processOffset); mappedFileQueue.truncateDirtyFiles(processOffset); reviseMaxAndMinOffsetInQueue(); } } public ReferredIterator iterateFromOrNext(long startOffset) { SelectMappedBufferResult sbr = getBatchMsgIndexOrNextBuffer(startOffset); if (sbr == null) { return null; } return new BatchConsumeQueueIterator(sbr); } /** * Gets SelectMappedBufferResult by batch-message offset, if not found will return the next valid offset buffer * Node: the caller is responsible for the release of SelectMappedBufferResult * @param msgOffset * @return SelectMappedBufferResult */ public SelectMappedBufferResult getBatchMsgIndexOrNextBuffer(final long msgOffset) { MappedFile targetBcq; if (msgOffset <= minOffsetInQueue) { targetBcq = mappedFileQueue.getFirstMappedFile(); } else { targetBcq = searchFileByOffsetOrRight(msgOffset); } if (targetBcq == null) { return null; } BatchOffsetIndex minOffset = getMinMsgOffset(targetBcq, false, false); BatchOffsetIndex maxOffset = getMaxMsgOffset(targetBcq, false, false); if (null == minOffset || null == maxOffset) { return null; } SelectMappedBufferResult sbr = minOffset.getMappedFile().selectMappedBuffer(0); try { ByteBuffer byteBuffer = sbr.getByteBuffer(); int left = minOffset.getIndexPos(); int right = maxOffset.getIndexPos(); int mid = binarySearchRight(byteBuffer, left, right, CQ_STORE_UNIT_SIZE, MSG_BASE_OFFSET_INDEX, msgOffset, BoundaryType.LOWER); if (mid != -1) { return minOffset.getMappedFile().selectMappedBuffer(mid); } } finally { sbr.release(); } return null; } protected MappedFile searchOffsetFromCacheOrRight(long msgOffset) { Map.Entry ceilingEntry = this.offsetCache.ceilingEntry(msgOffset); if (ceilingEntry == null) { return null; } else { return ceilingEntry.getValue(); } } protected MappedFile searchFileByOffsetOrRight(long msgOffset) { MappedFile targetBcq = null; boolean searchBcqByCacheEnable = this.messageStore.getMessageStoreConfig().isSearchBcqByCacheEnable(); if (searchBcqByCacheEnable) { // it's not the last BCQ file, so search it through cache. targetBcq = this.searchOffsetFromCacheOrRight(msgOffset); // not found in cache if (targetBcq == null) { MappedFile firstBcq = mappedFileQueue.getFirstMappedFile(); BatchOffsetIndex minForFirstBcq = getMinMsgOffset(firstBcq, false, false); if (minForFirstBcq != null && minForFirstBcq.getMsgOffset() <= msgOffset && msgOffset < maxOffsetInQueue) { // old search logic targetBcq = this.searchOffsetFromFilesOrRight(msgOffset); } log.warn("cache is not working on BCQ [Topic: {}, QueueId: {}] for msgOffset: {}, targetBcq: {}", this.topic, this.queueId, msgOffset, targetBcq); } } else { // old search logic targetBcq = this.searchOffsetFromFilesOrRight(msgOffset); } return targetBcq; } public MappedFile searchOffsetFromFilesOrRight(long msgOffset) { MappedFile targetBcq = null; // find the mapped file one by one reversely int mappedFileNum = this.mappedFileQueue.getMappedFiles().size(); for (int i = mappedFileNum - 1; i >= 0; i--) { MappedFile mappedFile = mappedFileQueue.getMappedFiles().get(i); BatchOffsetIndex tmpMinMsgOffset = getMinMsgOffset(mappedFile, false, false); BatchOffsetIndex tmpMaxMsgOffset = getMaxMsgOffset(mappedFile, false, false); if (null != tmpMaxMsgOffset && tmpMaxMsgOffset.getMsgOffset() < msgOffset) { if (i != mappedFileNum - 1) { //not the last mapped file max msg offset targetBcq = mappedFileQueue.getMappedFiles().get(i + 1); break; } } if (null != tmpMinMsgOffset && tmpMinMsgOffset.getMsgOffset() <= msgOffset && null != tmpMaxMsgOffset && msgOffset <= tmpMaxMsgOffset.getMsgOffset()) { targetBcq = mappedFile; break; } } return targetBcq; } private MappedFile getPreFile(MappedFile file) { int index = mappedFileQueue.getMappedFiles().indexOf(file); if (index < 1) { // indicate that this is the first file or not found return null; } else { return mappedFileQueue.getMappedFiles().get(index - 1); } } private void cacheOffset(MappedFile file, Function offsetGetFunc) { try { BatchOffsetIndex offset = offsetGetFunc.apply(file); if (offset != null) { this.offsetCache.put(offset.getMsgOffset(), offset.getMappedFile()); this.timeCache.put(offset.getStoreTimestamp(), offset.getMappedFile()); } } catch (Exception e) { log.error("Failed caching offset and time on BCQ [Topic: {}, QueueId: {}, File: {}]", this.topic, this.queueId, file); } } @Override protected void cacheBcq(MappedFile bcq) { MappedFile file = getPreFile(bcq); if (file != null) { cacheOffset(file, m -> getMaxMsgOffset(m, false, true)); } } public void putEndPositionInfo(MappedFile mappedFile) { // cache max offset if (!mappedFile.isFull()) { this.byteBufferItem.flip(); this.byteBufferItem.limit(CQ_STORE_UNIT_SIZE); this.byteBufferItem.putLong(-1); this.byteBufferItem.putInt(0); this.byteBufferItem.putLong(0); this.byteBufferItem.putLong(0); this.byteBufferItem.putLong(0); this.byteBufferItem.putShort((short)0); this.byteBufferItem.putInt(INVALID_POS); this.byteBufferItem.putInt(0); // 4 bytes reserved boolean appendRes; if (messageStore.getMessageStoreConfig().isPutConsumeQueueDataByFileChannel()) { appendRes = mappedFile.appendMessageUsingFileChannel(this.byteBufferItem.array()); } else { appendRes = mappedFile.appendMessage(this.byteBufferItem.array()); } if (!appendRes) { log.error("append end position info into {} failed", mappedFile.getFileName()); } } cacheOffset(mappedFile, m -> getMaxMsgOffset(m, false, true)); } public MappedFile createFile(final long physicalOffset) throws IOException { // cache max offset return mappedFileQueue.tryCreateMappedFile(physicalOffset); } public boolean isLastFileFull() { if (mappedFileQueue.getLastMappedFile() != null) { return mappedFileQueue.getLastMappedFile().isFull(); } else { return true; } } public boolean shouldRoll() { if (mappedFileQueue.getLastMappedFile() == null) { return true; } if (mappedFileQueue.getLastMappedFile().isFull()) { return true; } if (mappedFileQueue.getLastMappedFile().getWrotePosition() + BatchConsumeQueue.CQ_STORE_UNIT_SIZE > mappedFileQueue.getMappedFileSize()) { return true; } return false; } public boolean containsOffsetFile(final long physicalOffset) { String fileName = UtilAll.offset2FileName(physicalOffset); return mappedFileQueue.getMappedFiles().stream() .anyMatch(mf -> Objects.equals(mf.getFile().getName(), fileName)); } public long getMaxPhyOffsetInLog() { MappedFile lastMappedFile = mappedFileQueue.getLastMappedFile(); Long maxOffsetInLog = getMax(lastMappedFile, b -> b.getLong(0) + b.getInt(8)); if (maxOffsetInLog != null) { return maxOffsetInLog; } else { return -1; } } private T getMax(MappedFile mappedFile, Function function) { if (mappedFile == null || mappedFile.getReadPosition() < CQ_STORE_UNIT_SIZE) { return null; } ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); for (int i = mappedFile.getReadPosition() - CQ_STORE_UNIT_SIZE; i >= 0; i -= CQ_STORE_UNIT_SIZE) { byteBuffer.position(i); long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); long tagsCode = byteBuffer.getLong(); //tagscode long timestamp = byteBuffer.getLong(); //timestamp long msgBaseOffset = byteBuffer.getLong(); short batchSize = byteBuffer.getShort(); if (offset >= 0 && size > 0 && msgBaseOffset >= 0 && batchSize > 0) { byteBuffer.position(i); //reset position return function.apply(byteBuffer.slice()); } } return null; } @Override protected BatchOffsetIndex getMaxMsgOffset(MappedFile mappedFile, boolean getBatchSize, boolean getStoreTime) { if (mappedFile == null || mappedFile.getReadPosition() < CQ_STORE_UNIT_SIZE) { return null; } ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); for (int i = mappedFile.getReadPosition() - CQ_STORE_UNIT_SIZE; i >= 0; i -= CQ_STORE_UNIT_SIZE) { byteBuffer.position(i); long offset = byteBuffer.getLong(); int size = byteBuffer.getInt(); byteBuffer.getLong(); //tagscode long timestamp = byteBuffer.getLong();//timestamp long msgBaseOffset = byteBuffer.getLong(); short batchSize = byteBuffer.getShort(); if (offset >= 0 && size > 0 && msgBaseOffset >= 0 && batchSize > 0) { // mappedFile.setWrotePosition(i + CQ_STORE_UNIT_SIZE); // mappedFile.setFlushedPosition(i + CQ_STORE_UNIT_SIZE); // mappedFile.setCommittedPosition(i + CQ_STORE_UNIT_SIZE); return new BatchOffsetIndex(mappedFile, i, msgBaseOffset, batchSize, timestamp); } } return null; } public long getMaxMsgOffsetFromFile(String simpleFileName) { MappedFile mappedFile = mappedFileQueue.getMappedFiles().stream() .filter(m -> Objects.equals(m.getFile().getName(), simpleFileName)) .findFirst() .orElse(null); if (mappedFile == null) { return -1; } BatchOffsetIndex max = getMaxMsgOffset(mappedFile, false, false); if (max == null) { return -1; } return max.getMsgOffset(); } private void refreshMaxCache() { doRefreshCache(m -> getMaxMsgOffset(m, false, true)); } @Override protected void refreshCache() { refreshMaxCache(); } public void refresh() { reviseMaxAndMinOffsetInQueue(); refreshCache(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/offset/OffsetEntry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue.offset; public class OffsetEntry { /** * Topic identifier. For now, it's topic name directly. In the future, we should use fixed length topic identifier. */ public String topic; /** * Queue ID */ public int queueId; /** * Flag if the entry is for maximum or minimum */ public OffsetEntryType type; /** * Maximum or minimum consume-queue offset. */ public long offset; /** * Maximum or minimum commit-log offset. */ public long commitLogOffset; } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/queue/offset/OffsetEntryType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue.offset; public enum OffsetEntryType { MAXIMUM, MINIMUM } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.rocksdb; import java.util.function.LongSupplier; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.rocksdb.AbstractCompactionFilter; import org.rocksdb.AbstractCompactionFilterFactory; import org.rocksdb.RemoveConsumeQueueCompactionFilter; public class ConsumeQueueCompactionFilterFactory extends AbstractCompactionFilterFactory { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); private final LongSupplier minPhyOffsetSupplier; public ConsumeQueueCompactionFilterFactory(final LongSupplier minPhyOffsetSupplier) { this.minPhyOffsetSupplier = minPhyOffsetSupplier; } @Override public String name() { return "ConsumeQueueCompactionFilterFactory"; } @Override public RemoveConsumeQueueCompactionFilter createCompactionFilter(final AbstractCompactionFilter.Context context) { long minPhyOffset = this.minPhyOffsetSupplier.getAsLong(); LOGGER.info("manualCompaction minPhyOffset: {}, isFull: {}, isManual: {}", minPhyOffset, context.isFullCompaction(), context.isManualCompaction()); return new RemoveConsumeQueueCompactionFilter(minPhyOffset); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.rocksdb; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.store.MessageStore; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; public class ConsumeQueueRocksDBStorage extends AbstractRocksDBStorage { public static final byte[] OFFSET_COLUMN_FAMILY = "offset".getBytes(StandardCharsets.UTF_8); private final MessageStore messageStore; private volatile ColumnFamilyHandle offsetCFHandle; private ConsumeQueueCompactionFilterFactory compactionFilterFactory; public ConsumeQueueRocksDBStorage(final MessageStore messageStore, final String dbPath) { super(dbPath); this.messageStore = messageStore; this.readOnly = false; } protected void initOptions() { this.options = RocksDBOptionsFactory.createDBOptions(); super.initOptions(); } @Override protected void initTotalOrderReadOptions() { this.totalOrderReadOptions = new ReadOptions(); this.totalOrderReadOptions.setPrefixSameAsStart(false); this.totalOrderReadOptions.setTotalOrderSeek(false); } @Override protected boolean postLoad() { try { UtilAll.ensureDirOK(this.dbPath); initOptions(); final List cfDescriptors = new ArrayList<>(); this.compactionFilterFactory = new ConsumeQueueCompactionFilterFactory(messageStore::getMinPhyOffset); ColumnFamilyOptions cqCfOptions = RocksDBOptionsFactory.createCQCFOptions(this.messageStore, this.compactionFilterFactory); this.cfOptions.add(cqCfOptions); cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cqCfOptions)); ColumnFamilyOptions offsetCfOptions = RocksDBOptionsFactory.createOffsetCFOptions(); this.cfOptions.add(offsetCfOptions); cfDescriptors.add(new ColumnFamilyDescriptor(OFFSET_COLUMN_FAMILY, offsetCfOptions)); open(cfDescriptors); this.defaultCFHandle = cfHandles.get(0); this.offsetCFHandle = cfHandles.get(1); } catch (final Exception e) { LOGGER.error("postLoad Failed. {}", this.dbPath, e); return false; } return true; } @Override protected void preShutdown() { if (this.offsetCFHandle != null) { this.offsetCFHandle.close(); } if (this.compactionFilterFactory != null) { this.compactionFilterFactory.close(); } } public byte[] getCQ(final byte[] keyBytes) throws RocksDBException { return get(this.defaultCFHandle, this.totalOrderReadOptions, keyBytes); } public byte[] getOffset(final byte[] keyBytes) throws RocksDBException { return get(this.offsetCFHandle, this.totalOrderReadOptions, keyBytes); } public List multiGet(final List cfhList, final List keys) throws RocksDBException { return multiGet(this.totalOrderReadOptions, cfhList, keys); } public void batchPut(final WriteBatch batch) throws RocksDBException { batchPut(this.writeOptions, batch); } public void manualCompaction(final long minPhyOffset) { try { manualCompaction(minPhyOffset, this.compactRangeOptions); } catch (Exception e) { LOGGER.error("manualCompaction Failed. minPhyOffset: {}", minPhyOffset, e); } } public RocksIterator seekOffsetCF() { return this.db.newIterator(this.offsetCFHandle, this.totalOrderReadOptions); } public ColumnFamilyHandle getOffsetCFHandle() { return this.offsetCFHandle; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/rocksdb/MessageRocksDBStorage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.rocksdb; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.index.rocksdb.IndexRocksDBRecord; import org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord; import org.apache.rocketmq.store.transaction.TransRocksDBRecord; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksIterator; import org.rocksdb.Slice; import org.rocksdb.WriteBatch; import static org.apache.rocketmq.common.MixAll.dealTimeToHourStamps; import static org.apache.rocketmq.common.MixAll.getHours; import static org.apache.rocketmq.common.MixAll.isHourTime; import static org.apache.rocketmq.store.index.rocksdb.IndexRocksDBRecord.KEY_SPLIT; import static org.apache.rocketmq.store.index.rocksdb.IndexRocksDBRecord.KEY_SPLIT_BYTES; import static org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord.TIMER_ROCKSDB_DELETE; import static org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord.TIMER_ROCKSDB_PUT; import static org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord.TIMER_ROCKSDB_UPDATE; public class MessageRocksDBStorage extends AbstractRocksDBStorage { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final String ROCKSDB_MESSAGE_DIRECTORY = "rocksdbstore"; public static final byte[] TIMER_COLUMN_FAMILY = "timer".getBytes(StandardCharsets.UTF_8); public static final byte[] TRANS_COLUMN_FAMILY = "trans".getBytes(StandardCharsets.UTF_8); private static final byte[] LAST_OFFSET_PY = "lastOffsetPy".getBytes(StandardCharsets.UTF_8); private static final byte[] LAST_STORE_TIMESTAMP = "lastStoreTimeStamp".getBytes(StandardCharsets.UTF_8); private static final byte[] END_SUFFIX_BYTES = new byte[512]; static { Arrays.fill(END_SUFFIX_BYTES, (byte) 0xFF); } private static final Set COMMON_CHECK_POINT_KEY_SET_FOR_TIMER = new HashSet<>(); public static final byte[] SYS_TOPIC_SCAN_OFFSET_CHECK_POINT = "sys_topic_scan_offset_checkpoint".getBytes(StandardCharsets.UTF_8); public static final byte[] TIMELINE_CHECK_POINT = "timeline_checkpoint".getBytes(StandardCharsets.UTF_8); static { COMMON_CHECK_POINT_KEY_SET_FOR_TIMER.add(SYS_TOPIC_SCAN_OFFSET_CHECK_POINT); COMMON_CHECK_POINT_KEY_SET_FOR_TIMER.add(TIMELINE_CHECK_POINT); } private static final byte[] DELETE_VAL_FLAG = new byte[] {(byte)0xFF}; private static final int LAST_OFFSET_PY_LENGTH = LAST_OFFSET_PY.length; private volatile ColumnFamilyHandle timerCFHandle; private volatile ColumnFamilyHandle transCFHandle; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private static final Cache DELETE_KEY_CACHE_FOR_TIMER = CacheBuilder.newBuilder() .maximumSize(10000) .expireAfterWrite(60, TimeUnit.MINUTES) .build(); public MessageRocksDBStorage(MessageStoreConfig messageStoreConfig) { super(Paths.get(messageStoreConfig.getStorePathRootDir(), ROCKSDB_MESSAGE_DIRECTORY).toString()); this.start(); } @Override protected boolean postLoad() { try { UtilAll.ensureDirOK(this.dbPath); initOptions(); ColumnFamilyOptions indexCFOptions = RocksDBOptionsFactory.createIndexCFOptions(); ColumnFamilyOptions timerCFOptions = RocksDBOptionsFactory.createTimerCFOptions(); ColumnFamilyOptions transCFOptions = RocksDBOptionsFactory.createTransCFOptions(); this.cfOptions.add(indexCFOptions); this.cfOptions.add(timerCFOptions); this.cfOptions.add(transCFOptions); List cfDescriptors = new ArrayList<>(); cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, indexCFOptions)); cfDescriptors.add(new ColumnFamilyDescriptor(TIMER_COLUMN_FAMILY, timerCFOptions)); cfDescriptors.add(new ColumnFamilyDescriptor(TRANS_COLUMN_FAMILY, transCFOptions)); this.open(cfDescriptors); this.defaultCFHandle = cfHandles.get(0); this.timerCFHandle = cfHandles.get(1); this.transCFHandle = cfHandles.get(2); scheduler.scheduleAtFixedRate(() -> { try { db.flush(flushOptions, timerCFHandle); log.info("MessageRocksDBStorage flush timer wal success"); } catch (Exception e) { logError.error("MessageRocksDBStorage flush timer wal failed, error: {}", e.getMessage()); } }, 5, 5, TimeUnit.MINUTES); log.info("MessageRocksDBStorage init success, dbPath: {}", this.dbPath); } catch (final Exception e) { logError.error("MessageRocksDBStorage init error, dbPath: {}, error: {}", this.dbPath, e.getMessage()); return false; } return true; } protected void initOptions() { this.options = RocksDBOptionsFactory.createDBOptions(); super.initOptions(); } public String getFilePath() { return this.dbPath; } @Override protected void preShutdown() { log.info("MessageRocksDBStorage pre shutdown success, dbPath: {}", this.dbPath); } public List queryOffsetForIndex(byte[] columnFamily, String topic, String indexType, String key, long beginTime, long endTime, int maxNum, String lastKey) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || StringUtils.isEmpty(topic) || StringUtils.isEmpty(indexType) || StringUtils.isEmpty(key) || beginTime < 0L || endTime <= 0L || beginTime > endTime || maxNum <= 0) { logError.error("MessageRocksDBStorage queryOffsetForIndex param error, cfHandle: {}, topic: {}, indexType: {}, key: {}, beginTime: {}, endTime: {}, maxNum: {}", cfHandle, topic, indexType, key, beginTime, endTime, maxNum); return null; } Long lastIndexTime = getLastIndexTimeForIndex(lastKey); if (!StringUtils.isEmpty(lastKey) && (null == lastIndexTime || lastIndexTime <= 0L || !isHourTime(lastIndexTime))) { logError.error("MessageRocksDBStorage queryOffsetForIndex parse and check lastIndexTime error, lastIndexTime: {}, lastKey: {}", lastIndexTime, lastKey); return null; } List hours = getHours(beginTime, endTime); if (CollectionUtils.isEmpty(hours)) { logError.error("MessageRocksDBStorage queryOffsetForIndex param error, hours is empty, beginTime: {}, endTime: {}", beginTime, endTime); return null; } List offsetPyList = new ArrayList<>(maxNum); String keyMiddleStr = KEY_SPLIT + topic + KEY_SPLIT + indexType + KEY_SPLIT + key + KEY_SPLIT; byte[] keyMiddleBytes = keyMiddleStr.getBytes(StandardCharsets.UTF_8); for (Long hour : hours) { if (null == hour || null != lastIndexTime && hour < lastIndexTime) { continue; } byte[] seekKeyBytes = null; byte[] lastKeyBytes = null; byte[] keyPrefixBytes = ByteBuffer.allocate(Long.BYTES + keyMiddleBytes.length).putLong(hour).put(keyMiddleBytes).array(); if (!StringUtils.isEmpty(lastKey) && hour.equals(lastIndexTime)) { seekKeyBytes = lastKeyToBytes(lastKey); lastKeyBytes = seekKeyBytes; } else { seekKeyBytes = keyPrefixBytes; } if (null == seekKeyBytes) { logError.error("MessageRocksDBStorage queryOffsetForIndex error, seekKeyBytes is null"); return null; } try (RocksIterator iterator = db.newIterator(cfHandle, readOptions)) { for (iterator.seek(seekKeyBytes); iterator.isValid(); iterator.next()) { try { byte[] currentKeyBytes = iterator.key(); if (null == currentKeyBytes || currentKeyBytes.length == 0) { break; } if (null != lastKeyBytes && currentKeyBytes.length == lastKeyBytes.length && MixAll.isByteArrayEqual(currentKeyBytes, 0, currentKeyBytes.length, lastKeyBytes, 0, lastKeyBytes.length)) { continue; } if (currentKeyBytes.length < keyPrefixBytes.length || !MixAll.isByteArrayEqual(currentKeyBytes, 0, keyPrefixBytes.length, keyPrefixBytes, 0, keyPrefixBytes.length)) { break; } ByteBuffer valueBuffer = ByteBuffer.wrap(iterator.value()); long storeTime = valueBuffer.getLong(); if (storeTime >= beginTime && storeTime <= endTime) { byte[] indexKey = iterator.key(); if (null == indexKey || indexKey.length < Long.BYTES) { continue; } byte[] bytes = Arrays.copyOfRange(indexKey, indexKey.length - Long.BYTES, indexKey.length); long offset = ByteBuffer.wrap(bytes).getLong(); offsetPyList.add(offset); if (offsetPyList.size() >= maxNum) { return offsetPyList; } } } catch (Exception e) { logError.error("MessageRocksDBStorage queryOffsetForIndex iterator error: {}", e.getMessage()); } } } catch (Exception e) { logError.error("MessageRocksDBStorage queryOffsetForIndex error: {}", e.getMessage()); } } return offsetPyList; } private byte[] lastKeyToBytes(String lastKey) { if (StringUtils.isEmpty(lastKey)) { return null; } String[] split = lastKey.split(KEY_SPLIT); if (split.length != 6) { log.error("MessageRocksDBStorage lastKeyToBytes split error, lastKey: {}", lastKey); return null; } try { long storeTimeHour = Long.parseLong(split[0]); long offsetPy = Long.parseLong(split[split.length - 1]); StringBuilder stringBuilder = new StringBuilder(); for (int i = 1; i < split.length - 1; i++) { stringBuilder.append(KEY_SPLIT).append(split[i]); } byte[] middleKeyBytes = stringBuilder.append(KEY_SPLIT).toString().getBytes(StandardCharsets.UTF_8); return ByteBuffer.allocate(Long.BYTES + middleKeyBytes.length + Long.BYTES).putLong(storeTimeHour).put(middleKeyBytes).putLong(offsetPy).array(); } catch (Exception e) { log.error("MessageRocksDBStorage lastKeyToBytes error, lastKey: {}, error: {}", lastKey, e.getMessage()); return null; } } public void deleteRecordsForIndex(byte[] columnFamily, long storeTime, int hours) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || storeTime < 0L || hours <= 0) { logError.error("MessageRocksDBStorage deleteRecordsForIndex param error, storeTime: {}, hours: {}", storeTime, hours); return; } long endTime = dealTimeToHourStamps(storeTime); long startTime = endTime - TimeUnit.HOURS.toMillis(hours); try { byte[] startKey = ByteBuffer.allocate(Long.BYTES + KEY_SPLIT_BYTES.length).putLong(startTime).put(KEY_SPLIT_BYTES).array(); byte[] endKey = ByteBuffer.allocate(Long.BYTES + KEY_SPLIT_BYTES.length + END_SUFFIX_BYTES.length).putLong(endTime).put(KEY_SPLIT_BYTES).put(END_SUFFIX_BYTES).array(); rangeDelete(cfHandle, ableWalWriteOptions, startKey, endKey); log.info("MessageRocksDBStorage deleteRecordsForIndex delete success, storeTime: {}, hours: {}", storeTime, hours); } catch (Exception e) { logError.error("MessageRocksDBStorage deleteRecordsForIndex delete error, storeTime: {}, hours: {}, error: {}", storeTime, hours, e.getMessage()); } } public void writeRecordsForIndex(byte[] columnFamily, List recordList) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || CollectionUtils.isEmpty(recordList)) { return; } try (WriteBatch writeBatch = new WriteBatch()) { for (IndexRocksDBRecord record : recordList) { try { if (null == record) { logError.warn("MessageRocksDBStorage writeRecordsForIndex error, record is null"); continue; } byte[] keyBytes = record.getKeyBytes(); byte[] valueBytes = record.getValueBytes(); if (null == keyBytes || keyBytes.length == 0 || null == valueBytes || valueBytes.length == 0) { logError.error("MessageRocksDBStorage writeRecordsForIndex param error, keyBytes: {}, valueBytes: {}", keyBytes, valueBytes); continue; } writeBatch.put(cfHandle, keyBytes, valueBytes); } catch (Exception e) { logError.error("MessageRocksDBStorage writeRecordsForIndex error: {}", e.getMessage()); } } IndexRocksDBRecord lastRecord = recordList.get(recordList.size() - 1); if (null != lastRecord && StringUtils.isEmpty(lastRecord.getKey()) && StringUtils.isEmpty(lastRecord.getTag())) { long offset = lastRecord.getOffsetPy(); Long lastOffsetPy = getLastOffsetPy(columnFamily); if (null == lastOffsetPy || offset > lastOffsetPy) { writeBatch.put(cfHandle, LAST_OFFSET_PY, ByteBuffer.allocate(Long.BYTES).putLong(offset).array()); } long storeTime = lastRecord.getStoreTime(); Long lastStoreTimeStamp = getLastStoreTimeStampForIndex(columnFamily); if (null == lastStoreTimeStamp || storeTime > lastStoreTimeStamp) { writeBatch.put(cfHandle, LAST_STORE_TIMESTAMP, ByteBuffer.allocate(Long.BYTES).putLong(storeTime).array()); } } batchPut(ableWalWriteOptions, writeBatch); } catch (Exception e) { logError.error("MessageRocksDBStorage writeRecordsForIndex error: {}", e.getMessage()); } } public Long getLastStoreTimeStampForIndex(byte[] columnFamily) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle) { return null; } try { byte[] storeTime = get(cfHandle, readOptions, LAST_STORE_TIMESTAMP); return null == storeTime ? 0L : ByteBuffer.wrap(storeTime).getLong(); } catch (Exception e) { logError.error("MessageRocksDBStorage getLastStoreTimeStampForIndex error: {}", e.getMessage()); return null; } } private static Long getLastIndexTimeForIndex(String lastKey) { if (StringUtils.isEmpty(lastKey)) { return null; } try { String[] split = lastKey.split(KEY_SPLIT); if (split.length > 0) { return Long.valueOf(split[0]); } } catch (Exception e) { logError.error("MessageRocksDBStorage getLastIndexTimeForIndex error lastKey: {}, e: {}", lastKey, e.getMessage()); } return null; } public void writeRecordsForTimer(byte[] columnFamily, List recordList) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || CollectionUtils.isEmpty(recordList)) { return; } try (WriteBatch writeBatch = new WriteBatch()) { for (TimerRocksDBRecord record : recordList) { if (null == record) { logError.error("MessageRocksDBStorage writeRecordsForTimer error, record is null"); continue; } try { byte[] keyBytes = record.getKeyBytes(); byte[] valueBytes = record.getValueBytes(); if (null == keyBytes || keyBytes.length == 0 || null == valueBytes || valueBytes.length == 0) { logError.error("MessageRocksDBStorage writeRecordsForTimer param error, keyBytes: {}, valueBytes: {}", keyBytes, valueBytes); continue; } if (record.getActionFlag() == TIMER_ROCKSDB_PUT) { writeBatch.put(cfHandle, keyBytes, valueBytes); } else if (record.getActionFlag() == TIMER_ROCKSDB_DELETE) { writeBatch.delete(cfHandle, keyBytes); DELETE_KEY_CACHE_FOR_TIMER.put(keyBytes, DELETE_VAL_FLAG); } else if (record.getActionFlag() == TIMER_ROCKSDB_UPDATE) { byte[] deleteByte = DELETE_KEY_CACHE_FOR_TIMER.getIfPresent(keyBytes); if (null == deleteByte) { writeBatch.put(cfHandle, keyBytes, valueBytes); } } else { logError.error("MessageRocksDBStorage writeRecordsForTimer record actionFlag error, actionFlag: {}", record.getActionFlag()); } } catch (Exception e) { logError.error("MessageRocksDBStorage writeRecordsForTimer error: {}", e.getMessage()); } } batchPut(ableWalWriteOptions, writeBatch); } catch (Exception e) { logError.error("MessageRocksDBStorage writeRecordsForTimer error: {}", e.getMessage()); } } public List scanRecordsForTimer(byte[] columnFamily, long lowerTime, long upperTime, int size, byte[] startKey) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || lowerTime <= 0L || upperTime <= 0L || lowerTime > upperTime || size <= 0) { return null; } RocksIterator iterator = null; try (ReadOptions readOptions = new ReadOptions() .setIterateLowerBound(new Slice(ByteBuffer.allocate(Long.BYTES).putLong(lowerTime).array())) .setIterateUpperBound(new Slice(ByteBuffer.allocate(Long.BYTES).putLong(upperTime).array()))) { iterator = db.newIterator(cfHandle, readOptions); if (null == startKey || startKey.length == 0) { iterator.seek(ByteBuffer.allocate(Long.BYTES).putLong(lowerTime).array()); } else { iterator.seek(startKey); iterator.next(); } List records = new ArrayList<>(); for (; iterator.isValid(); iterator.next()) { try { TimerRocksDBRecord timerRocksDBRecord = TimerRocksDBRecord.decode(iterator.key(), iterator.value()); if (null == timerRocksDBRecord) { logError.error("MessageRocksDBStorage scanRecordsForTimer error, decode timerRocksDBRecord is null"); continue; } records.add(timerRocksDBRecord); if (records.size() >= size) { break; } } catch (Exception e) { logError.error("MessageRocksDBStorage scanRecordsForTimer iterator error: {}", e.getMessage()); } } return records; } catch (Exception e) { logError.error("MessageRocksDBStorage scanRecordsForTimer error: {}", e.getMessage()); } finally { if (null != iterator) { iterator.close(); } } return null; } public void deleteRecordsForTimer(byte[] columnFamily, long lowerTime, long upperTime) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || lowerTime <= 0L || upperTime <= 0L || lowerTime > upperTime) { logError.error("MessageRocksDBStorage deleteRecordsForTimer param error, cfHandle: {}, lowerTime: {}, upperTime: {}", cfHandle, lowerTime, upperTime); return; } byte[] startKey = ByteBuffer.allocate(Long.BYTES).putLong(lowerTime).array(); byte[] endKey = ByteBuffer.allocate(Long.BYTES + END_SUFFIX_BYTES.length).putLong(upperTime).put(END_SUFFIX_BYTES).array(); try { rangeDelete(cfHandle, ableWalWriteOptions, startKey, endKey); log.info("MessageRocksDBStorage deleteRecordsForTimer success, lowerTime: {}, upperTime: {}", lowerTime, upperTime); } catch (Exception e) { logError.error("MessageRocksDBStorage deleteRecordsForTimer param error, lowerTime: {}, upperTime: {}, error: {}", lowerTime, upperTime, e.getMessage()); } } public void writeCheckPointForTimer(byte[] columnFamily, byte[] key, long value) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || !COMMON_CHECK_POINT_KEY_SET_FOR_TIMER.contains(key) || value < 0L) { logError.error("MessageRocksDBStorage writeCheckPointForTimer param error, cfHandle: {}, key: {}, value: {}", cfHandle, key, value); return; } try { byte[] valueBytes = ByteBuffer.allocate(Long.BYTES).putLong(value).array(); put(cfHandle, ableWalWriteOptions, key, key.length, valueBytes, valueBytes.length); } catch (Exception e) { logError.error("MessageRocksDBStorage writeCheckPointForTimer error: {}", e.getMessage()); } } public long getCheckpointForTimer(byte[] columnFamily, byte[] key) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || !COMMON_CHECK_POINT_KEY_SET_FOR_TIMER.contains(key)) { logError.error("MessageRocksDBStorage getCheckpointForTimer error, cfHandle: {}, key: {}", cfHandle, key); return 0L; } try { byte[] checkpoint = get(cfHandle, readOptions, key); if (null == checkpoint && Arrays.equals(key, TIMELINE_CHECK_POINT)) { return (System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10)) / TimeUnit.SECONDS.toMillis(1) * TimeUnit.SECONDS.toMillis(1); } return checkpoint == null ? 0L : ByteBuffer.wrap(checkpoint).getLong(); } catch (Exception e) { logError.error("MessageRocksDBStorage getCheckpointForTimer error: {}", e.getMessage()); return 0L; } } public void deleteCheckPointForTimer(byte[] columnFamily, byte[] key) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || !COMMON_CHECK_POINT_KEY_SET_FOR_TIMER.contains(key)) { logError.error("MessageRocksDBStorage deleteCheckPointForTimer error, cfHandle: {}, key: {}", cfHandle, key); return; } try { delete(cfHandle, ableWalWriteOptions, key); } catch (Exception e) { logError.error("MessageRocksDBStorage deleteCheckPointForTimer error: {}", e.getMessage()); throw new RuntimeException("MessageRocksDBStorage deleteCheckPointForTimer error", e); } } public void writeRecordsForTrans(byte[] columnFamily, List recordList) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || CollectionUtils.isEmpty(recordList)) { return; } long lastOffsetPy = 0L; try (WriteBatch writeBatch = new WriteBatch()) { for (TransRocksDBRecord record : recordList) { if (null == record) { logError.error("MessageRocksDBStorage writeRecordsForTrans error, record is null"); continue; } byte[] keyBytes = record.getKeyBytes(); if (null == keyBytes || keyBytes.length == 0) { logError.error("MessageRocksDBStorage writeRecordsForTrans param error, keyBytes: {}", keyBytes); continue; } if (record.isOp()) { writeBatch.delete(cfHandle, record.getKeyBytes()); } else { byte[] valueBytes = record.getValueBytes(); if (null == valueBytes || valueBytes.length == 0) { logError.error("MessageRocksDBStorage writeRecordsForTrans param error, valueBytes: {}", valueBytes); continue; } writeBatch.put(cfHandle, keyBytes, valueBytes); lastOffsetPy = Math.max(lastOffsetPy, record.getOffsetPy()); } } if (lastOffsetPy > 0L) { Long lastOffsetPyStore = getLastOffsetPy(columnFamily); if (null == lastOffsetPyStore || lastOffsetPy > lastOffsetPyStore) { writeBatch.put(cfHandle, LAST_OFFSET_PY, ByteBuffer.allocate(Long.BYTES).putLong(lastOffsetPy).array()); } } batchPut(ableWalWriteOptions, writeBatch); } catch (Exception e) { logError.error("MessageRocksDBStorage writeRecordsForTrans error: {}", e.getMessage()); } } public void updateRecordsForTrans(byte[] columnFamily, List recordList) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || CollectionUtils.isEmpty(recordList)) { return; } try (WriteBatch writeBatch = new WriteBatch()) { for (TransRocksDBRecord record : recordList) { if (null == record) { logError.error("MessageRocksDBStorage updateRecordsForTrans error, record is null"); continue; } byte[] keyBytes = record.getKeyBytes(); byte[] valueBytes = record.getValueBytes(); if (null == keyBytes || keyBytes.length == 0 || null == valueBytes || valueBytes.length == 0) { logError.error("MessageRocksDBStorage updateRecordsForTrans param error, keyBytes: {}, valueBytes: {}", keyBytes, valueBytes); continue; } if (record.isDelete()) { writeBatch.delete(cfHandle, keyBytes); } else { writeBatch.put(cfHandle, keyBytes, valueBytes); } } batchPut(ableWalWriteOptions, writeBatch); } catch (Exception e) { logError.error("MessageRocksDBStorage updateRecordsForTrans error: {}", e.getMessage()); } } public List scanRecordsForTrans(byte[] columnFamily, int size, byte[] startKey) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || size <= 0) { return null; } RocksIterator iterator = null; try { iterator = db.newIterator(cfHandle); if (null == startKey || startKey.length == 0) { iterator.seekToFirst(); } else { iterator.seek(startKey); iterator.next(); } List records = new ArrayList<>(); for (; iterator.isValid(); iterator.next()) { byte[] key = iterator.key(); if (null == key || key.length == 0 || key.length == LAST_OFFSET_PY_LENGTH && Arrays.equals(key, LAST_OFFSET_PY)) { continue; } TransRocksDBRecord transRocksDBRecord = null; try { transRocksDBRecord = TransRocksDBRecord.decode(key, iterator.value()); } catch (Exception e) { logError.error("MessageRocksDBStorage scanRecordsForTrans error: {}", e.getMessage()); } if (null != transRocksDBRecord) { records.add(transRocksDBRecord); } if (records.size() >= size) { break; } } return records; } catch (Exception e) { logError.error("MessageRocksDBStorage scanRecordsForTrans error: {}", e.getMessage()); } finally { if (null != iterator) { iterator.close(); } } return null; } public TransRocksDBRecord getRecordForTrans(byte[] columnFamily, TransRocksDBRecord transRocksDBRecord) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle || null == transRocksDBRecord) { return null; } try { byte[] keyBytes = transRocksDBRecord.getKeyBytes(); if (null == keyBytes) { return null; } byte[] valueBytes = get(cfHandle, readOptions, keyBytes); if (null == valueBytes || valueBytes.length != TransRocksDBRecord.VALUE_LENGTH) { return null; } return TransRocksDBRecord.decode(keyBytes, valueBytes); } catch (Exception e) { logError.error("MessageRocksDBStorage getRecordForTrans error: {}", e.getMessage()); return null; } } public Long getLastOffsetPy(byte[] columnFamily) { ColumnFamilyHandle cfHandle = getColumnFamily(columnFamily); if (null == cfHandle) { return null; } try { byte[] offsetBytes = get(cfHandle, readOptions, LAST_OFFSET_PY); return offsetBytes == null ? 0L : ByteBuffer.wrap(offsetBytes).getLong(); } catch (Exception e) { logError.error("MessageRocksDBStorage getLastOffsetPy error: {}", e.getMessage()); return null; } } @Override public synchronized boolean shutdown() { try { boolean result = super.shutdown(); log.info("shutdown MessageRocksDBStorage result: {}", result); return result; } catch (Exception e) { logError.error("shutdown MessageRocksDBStorage error : {}", e.getMessage()); return false; } } private ColumnFamilyHandle getColumnFamily(byte[] columnFamily) { if (Arrays.equals(columnFamily, RocksDB.DEFAULT_COLUMN_FAMILY)) { return this.defaultCFHandle; } else if (Arrays.equals(columnFamily, TIMER_COLUMN_FAMILY)) { return this.timerCFHandle; } else if (Arrays.equals(columnFamily, TRANS_COLUMN_FAMILY)) { return this.transCFHandle; } throw new RuntimeException("Unknown column family"); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.rocksdb; import org.apache.rocketmq.common.config.ConfigHelper; import org.apache.rocketmq.store.MessageStore; import org.rocksdb.BlockBasedTableConfig; import org.rocksdb.BloomFilter; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.CompactionOptionsUniversal; import org.rocksdb.CompactionPriority; import org.rocksdb.CompactionStopStyle; import org.rocksdb.CompactionStyle; import org.rocksdb.CompressionType; import org.rocksdb.DBOptions; import org.rocksdb.DataBlockIndexType; import org.rocksdb.IndexType; import org.rocksdb.InfoLogLevel; import org.rocksdb.LRUCache; import org.rocksdb.RateLimiter; import org.rocksdb.SkipListMemTableConfig; import org.rocksdb.Statistics; import org.rocksdb.StatsLevel; import org.rocksdb.StringAppendOperator; import org.rocksdb.WALRecoveryMode; import org.rocksdb.util.SizeUnit; public class RocksDBOptionsFactory { public static ColumnFamilyOptions createCQCFOptions(final MessageStore messageStore, ConsumeQueueCompactionFilterFactory consumeQueueCompactionFilterFactory) { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(). setFormatVersion(5). setIndexType(IndexType.kBinarySearch). setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash). setDataBlockHashTableUtilRatio(0.75). setBlockSize(32 * SizeUnit.KB). setMetadataBlockSize(4 * SizeUnit.KB). setFilterPolicy(new BloomFilter(16, false)). setCacheIndexAndFilterBlocks(false). setCacheIndexAndFilterBlocksWithHighPriority(true). setPinL0FilterAndIndexBlocksInCache(false). setPinTopLevelIndexAndFilter(true). setBlockCache(new LRUCache(1024 * SizeUnit.MB, 8, false)). setWholeKeyFiltering(true); ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); CompactionOptionsUniversal compactionOption = new CompactionOptionsUniversal(); compactionOption.setSizeRatio(100). setMaxSizeAmplificationPercent(25). setAllowTrivialMove(true). setMinMergeWidth(2). setMaxMergeWidth(Integer.MAX_VALUE). setStopStyle(CompactionStopStyle.CompactionStopStyleTotalSize). setCompressionSizePercent(-1); String bottomMostCompressionTypeOpt = messageStore.getMessageStoreConfig() .getBottomMostCompressionTypeForConsumeQueueStore(); String compressionTypeOpt = messageStore.getMessageStoreConfig() .getRocksdbCompressionType(); CompressionType bottomMostCompressionType = CompressionType.getCompressionType(bottomMostCompressionTypeOpt); CompressionType compressionType = CompressionType.getCompressionType(compressionTypeOpt); return columnFamilyOptions.setMaxWriteBufferNumber(4). setWriteBufferSize(128 * SizeUnit.MB). setMinWriteBufferNumberToMerge(1). setTableFormatConfig(blockBasedTableConfig). setMemTableConfig(new SkipListMemTableConfig()). setCompressionType(compressionType). setBottommostCompressionType(bottomMostCompressionType). setNumLevels(7). setCompactionPriority(CompactionPriority.MinOverlappingRatio). setCompactionStyle(CompactionStyle.UNIVERSAL). setCompactionOptionsUniversal(compactionOption). setMaxCompactionBytes(100 * SizeUnit.GB). setSoftPendingCompactionBytesLimit(100 * SizeUnit.GB). setHardPendingCompactionBytesLimit(256 * SizeUnit.GB). setLevel0FileNumCompactionTrigger(2). setLevel0SlowdownWritesTrigger(8). setLevel0StopWritesTrigger(10). setTargetFileSizeBase(256 * SizeUnit.MB). setTargetFileSizeMultiplier(2). setMergeOperator(new StringAppendOperator()). setCompactionFilterFactory(consumeQueueCompactionFilterFactory). setReportBgIoStats(true). setOptimizeFiltersForHits(true); } public static ColumnFamilyOptions createOffsetCFOptions() { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(). setFormatVersion(5). setIndexType(IndexType.kBinarySearch). setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch). setBlockSize(32 * SizeUnit.KB). setFilterPolicy(new BloomFilter(16, false)). setCacheIndexAndFilterBlocks(false). setCacheIndexAndFilterBlocksWithHighPriority(true). setPinL0FilterAndIndexBlocksInCache(false). setPinTopLevelIndexAndFilter(true). setBlockCache(new LRUCache(128 * SizeUnit.MB, 8, false)). setWholeKeyFiltering(true); ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); return columnFamilyOptions.setMaxWriteBufferNumber(4). setWriteBufferSize(64 * SizeUnit.MB). setMinWriteBufferNumberToMerge(1). setTableFormatConfig(blockBasedTableConfig). setMemTableConfig(new SkipListMemTableConfig()). setCompressionType(CompressionType.NO_COMPRESSION). setNumLevels(7). setCompactionStyle(CompactionStyle.LEVEL). setLevel0FileNumCompactionTrigger(2). setLevel0SlowdownWritesTrigger(8). setLevel0StopWritesTrigger(10). setTargetFileSizeBase(64 * SizeUnit.MB). setTargetFileSizeMultiplier(2). setMaxBytesForLevelBase(256 * SizeUnit.MB). setMaxBytesForLevelMultiplier(2). setMergeOperator(new StringAppendOperator()). setInplaceUpdateSupport(true); } public static ColumnFamilyOptions createPopCFOptions() { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig() .setFormatVersion(5) .setIndexType(IndexType.kBinarySearch) .setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash) .setDataBlockHashTableUtilRatio(0.75) .setBlockSize(32 * SizeUnit.KB) .setMetadataBlockSize(4 * SizeUnit.KB) .setFilterPolicy(new BloomFilter(16, false)) .setCacheIndexAndFilterBlocks(false) .setCacheIndexAndFilterBlocksWithHighPriority(true) .setPinL0FilterAndIndexBlocksInCache(false) .setPinTopLevelIndexAndFilter(true) .setBlockCache(new LRUCache(1024 * SizeUnit.MB, 8, false)) .setWholeKeyFiltering(true); CompactionOptionsUniversal compactionOption = new CompactionOptionsUniversal() .setSizeRatio(100) .setMaxSizeAmplificationPercent(25) .setAllowTrivialMove(true) .setMinMergeWidth(2) .setMaxMergeWidth(Integer.MAX_VALUE) .setStopStyle(CompactionStopStyle.CompactionStopStyleTotalSize) .setCompressionSizePercent(-1); //noinspection resource return new ColumnFamilyOptions() .setMaxWriteBufferNumber(4) .setWriteBufferSize(128 * SizeUnit.MB) .setMinWriteBufferNumberToMerge(1) .setTableFormatConfig(blockBasedTableConfig) .setMemTableConfig(new SkipListMemTableConfig()) .setCompressionType(CompressionType.NO_COMPRESSION) .setBottommostCompressionType(CompressionType.NO_COMPRESSION) .setNumLevels(7) .setCompactionPriority(CompactionPriority.MinOverlappingRatio) .setCompactionStyle(CompactionStyle.UNIVERSAL) .setCompactionOptionsUniversal(compactionOption) .setMaxCompactionBytes(100 * SizeUnit.GB) .setSoftPendingCompactionBytesLimit(100 * SizeUnit.GB) .setHardPendingCompactionBytesLimit(256 * SizeUnit.GB) .setLevel0FileNumCompactionTrigger(2) .setLevel0SlowdownWritesTrigger(8) .setLevel0StopWritesTrigger(10) .setTargetFileSizeBase(256 * SizeUnit.MB) .setTargetFileSizeMultiplier(2) .setMergeOperator(new StringAppendOperator()) .setReportBgIoStats(true) .setOptimizeFiltersForHits(true); } /** * Create a rocksdb db options, the user must take care to close it after closing db. * @return */ public static DBOptions createDBOptions() { //Turn based on https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide // and http://gitlab.alibaba-inc.com/aloha/aloha/blob/branch_2_5_0/jstorm-core/src/main/java/com/alibaba/jstorm/cache/rocksdb/RocksDbOptionsFactory.java DBOptions options = new DBOptions(); Statistics statistics = new Statistics(); statistics.setStatsLevel(StatsLevel.EXCEPT_DETAILED_TIMERS); return options. setDbLogDir(ConfigHelper.getDBLogDir()). setInfoLogLevel(InfoLogLevel.INFO_LEVEL). setWalRecoveryMode(WALRecoveryMode.PointInTimeRecovery). setManualWalFlush(true). setCreateIfMissing(true). setBytesPerSync(SizeUnit.MB). setCreateMissingColumnFamilies(true). setMaxOpenFiles(-1). setMaxLogFileSize(SizeUnit.GB). setKeepLogFileNum(5). setMaxManifestFileSize(SizeUnit.GB). setAllowConcurrentMemtableWrite(false). setStatistics(statistics). setAtomicFlush(true). setCompactionReadaheadSize(4 * SizeUnit.MB). setMaxBackgroundJobs(32). setMaxSubcompactions(8). setParanoidChecks(true). setDelayedWriteRate(16 * SizeUnit.MB). setRateLimiter(new RateLimiter(100 * SizeUnit.MB)). setUseDirectIoForFlushAndCompaction(false). setUseDirectReads(false); } public static ColumnFamilyOptions createTimerCFOptions() { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig() .setFormatVersion(5) .setIndexType(IndexType.kBinarySearch) .setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash) .setDataBlockHashTableUtilRatio(0.75) .setBlockSize(128 * SizeUnit.KB) .setMetadataBlockSize(4 * SizeUnit.KB) .setFilterPolicy(new BloomFilter(16, false)) .setCacheIndexAndFilterBlocks(false) .setCacheIndexAndFilterBlocksWithHighPriority(true) .setPinL0FilterAndIndexBlocksInCache(false) .setPinTopLevelIndexAndFilter(true) .setBlockCache(new LRUCache(2048 * SizeUnit.MB, 8, false)) .setWholeKeyFiltering(true); //noinspection resource return new ColumnFamilyOptions() .setMaxWriteBufferNumber(6) .setWriteBufferSize(256 * SizeUnit.MB) .setMinWriteBufferNumberToMerge(1) .setTableFormatConfig(blockBasedTableConfig) .setMemTableConfig(new SkipListMemTableConfig()) .setCompressionType(CompressionType.ZSTD_COMPRESSION) .setBottommostCompressionType(CompressionType.NO_COMPRESSION) .setNumLevels(7) .setCompactionPriority(CompactionPriority.MinOverlappingRatio) .setCompactionStyle(CompactionStyle.LEVEL) .setMaxCompactionBytes(256 * SizeUnit.MB) .setSoftPendingCompactionBytesLimit(100 * SizeUnit.GB) .setHardPendingCompactionBytesLimit(256 * SizeUnit.GB) .setLevel0FileNumCompactionTrigger(2) .setLevel0SlowdownWritesTrigger(8) .setLevel0StopWritesTrigger(10) .setTargetFileSizeBase(256 * SizeUnit.MB) .setTargetFileSizeMultiplier(2) .setMergeOperator(new StringAppendOperator()) .setReportBgIoStats(true) .setOptimizeFiltersForHits(true) .setMaxBytesForLevelBase(512 * SizeUnit.MB); } public static ColumnFamilyOptions createTransCFOptions() { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig() .setFormatVersion(5) .setIndexType(IndexType.kBinarySearch) .setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash) .setDataBlockHashTableUtilRatio(0.75) .setBlockSize(128 * SizeUnit.KB) .setMetadataBlockSize(4 * SizeUnit.KB) .setFilterPolicy(new BloomFilter(16, false)) .setCacheIndexAndFilterBlocks(false) .setCacheIndexAndFilterBlocksWithHighPriority(true) .setPinL0FilterAndIndexBlocksInCache(false) .setPinTopLevelIndexAndFilter(true) .setBlockCache(new LRUCache(1024 * SizeUnit.MB, 8, false)) .setWholeKeyFiltering(true); CompactionOptionsUniversal compactionOption = new CompactionOptionsUniversal() .setSizeRatio(100) .setMaxSizeAmplificationPercent(25) .setAllowTrivialMove(true) .setMinMergeWidth(2) .setMaxMergeWidth(Integer.MAX_VALUE) .setStopStyle(CompactionStopStyle.CompactionStopStyleTotalSize) .setCompressionSizePercent(-1); //noinspection resource return new ColumnFamilyOptions() .setMaxWriteBufferNumber(6) .setWriteBufferSize(128 * SizeUnit.MB) .setMinWriteBufferNumberToMerge(1) .setTableFormatConfig(blockBasedTableConfig) .setMemTableConfig(new SkipListMemTableConfig()) .setCompressionType(CompressionType.NO_COMPRESSION) .setBottommostCompressionType(CompressionType.NO_COMPRESSION) .setNumLevels(7) .setCompactionPriority(CompactionPriority.MinOverlappingRatio) .setCompactionStyle(CompactionStyle.UNIVERSAL) .setCompactionOptionsUniversal(compactionOption) .setMaxCompactionBytes(100 * SizeUnit.GB) .setSoftPendingCompactionBytesLimit(100 * SizeUnit.GB) .setHardPendingCompactionBytesLimit(256 * SizeUnit.GB) .setLevel0FileNumCompactionTrigger(2) .setLevel0SlowdownWritesTrigger(8) .setLevel0StopWritesTrigger(10) .setTargetFileSizeBase(256 * SizeUnit.MB) .setTargetFileSizeMultiplier(2) .setMergeOperator(new StringAppendOperator()) .setReportBgIoStats(true) .setOptimizeFiltersForHits(true); } public static ColumnFamilyOptions createIndexCFOptions() { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig() .setFormatVersion(5) .setIndexType(IndexType.kBinarySearch) .setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash) .setDataBlockHashTableUtilRatio(0.75) .setBlockSize(128 * SizeUnit.KB) .setMetadataBlockSize(4 * SizeUnit.KB) .setFilterPolicy(new BloomFilter(16, false)) .setCacheIndexAndFilterBlocks(false) .setCacheIndexAndFilterBlocksWithHighPriority(true) .setPinL0FilterAndIndexBlocksInCache(false) .setPinTopLevelIndexAndFilter(true) .setBlockCache(new LRUCache(1024 * SizeUnit.MB, 8, false)) .setWholeKeyFiltering(true); CompactionOptionsUniversal compactionOption = new CompactionOptionsUniversal() .setSizeRatio(100) .setMaxSizeAmplificationPercent(25) .setAllowTrivialMove(true) .setMinMergeWidth(2) .setMaxMergeWidth(Integer.MAX_VALUE) .setStopStyle(CompactionStopStyle.CompactionStopStyleTotalSize) .setCompressionSizePercent(-1); //noinspection resource return new ColumnFamilyOptions() .setMaxWriteBufferNumber(6) .setWriteBufferSize(128 * SizeUnit.MB) .setMinWriteBufferNumberToMerge(1) .setTableFormatConfig(blockBasedTableConfig) .setMemTableConfig(new SkipListMemTableConfig()) .setCompressionType(CompressionType.NO_COMPRESSION) .setBottommostCompressionType(CompressionType.NO_COMPRESSION) .setNumLevels(7) .setCompactionPriority(CompactionPriority.MinOverlappingRatio) .setCompactionStyle(CompactionStyle.UNIVERSAL) .setCompactionOptionsUniversal(compactionOption) .setMaxCompactionBytes(256 * SizeUnit.MB) .setSoftPendingCompactionBytesLimit(100 * SizeUnit.GB) .setHardPendingCompactionBytesLimit(256 * SizeUnit.GB) .setLevel0FileNumCompactionTrigger(8) .setLevel0SlowdownWritesTrigger(8) .setLevel0StopWritesTrigger(20) .setTargetFileSizeBase(256 * SizeUnit.MB) .setTargetFileSizeMultiplier(2) .setMergeOperator(new StringAppendOperator()) .setReportBgIoStats(true) .setOptimizeFiltersForHits(true); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/stats/BrokerStats.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.stats; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.MessageStore; public class BrokerStats { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final MessageStore defaultMessageStore; private volatile long msgPutTotalYesterdayMorning; private volatile long msgPutTotalTodayMorning; private volatile long msgGetTotalYesterdayMorning; private volatile long msgGetTotalTodayMorning; public BrokerStats(MessageStore defaultMessageStore) { this.defaultMessageStore = defaultMessageStore; } public void record() { this.msgPutTotalYesterdayMorning = this.msgPutTotalTodayMorning; this.msgGetTotalYesterdayMorning = this.msgGetTotalTodayMorning; this.msgPutTotalTodayMorning = this.defaultMessageStore.getBrokerStatsManager().getBrokerPutNumsWithoutSystemTopic(); this.msgGetTotalTodayMorning = this.defaultMessageStore.getBrokerStatsManager().getBrokerGetNumsWithoutSystemTopic(); log.info("yesterday put message total: {}", msgPutTotalTodayMorning - msgPutTotalYesterdayMorning); log.info("yesterday get message total: {}", msgGetTotalTodayMorning - msgGetTotalYesterdayMorning); } public long getMsgPutTotalYesterdayMorning() { return msgPutTotalYesterdayMorning; } public void setMsgPutTotalYesterdayMorning(long msgPutTotalYesterdayMorning) { this.msgPutTotalYesterdayMorning = msgPutTotalYesterdayMorning; } public long getMsgPutTotalTodayMorning() { return msgPutTotalTodayMorning; } public void setMsgPutTotalTodayMorning(long msgPutTotalTodayMorning) { this.msgPutTotalTodayMorning = msgPutTotalTodayMorning; } public long getMsgGetTotalYesterdayMorning() { return msgGetTotalYesterdayMorning; } public void setMsgGetTotalYesterdayMorning(long msgGetTotalYesterdayMorning) { this.msgGetTotalYesterdayMorning = msgGetTotalYesterdayMorning; } public long getMsgGetTotalTodayMorning() { return msgGetTotalTodayMorning; } public void setMsgGetTotalTodayMorning(long msgGetTotalTodayMorning) { this.msgGetTotalTodayMorning = msgGetTotalTodayMorning; } public long getMsgPutTotalTodayNow() { return this.defaultMessageStore.getBrokerStatsManager().getBrokerPutNumsWithoutSystemTopic(); } public long getMsgGetTotalTodayNow() { return this.defaultMessageStore.getBrokerStatsManager().getBrokerGetNumsWithoutSystemTopic(); } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.stats; import java.util.HashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.tuple.Pair; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.statistics.StatisticsItem; import org.apache.rocketmq.common.statistics.StatisticsItemFormatter; import org.apache.rocketmq.common.statistics.StatisticsItemPrinter; import org.apache.rocketmq.common.statistics.StatisticsItemScheduledIncrementPrinter; import org.apache.rocketmq.common.statistics.StatisticsItemScheduledPrinter; import org.apache.rocketmq.common.statistics.StatisticsItemStateGetter; import org.apache.rocketmq.common.statistics.StatisticsKindMeta; import org.apache.rocketmq.common.statistics.StatisticsManager; import org.apache.rocketmq.common.stats.MomentStatsItemSet; import org.apache.rocketmq.common.stats.Stats; import org.apache.rocketmq.common.stats.StatsItem; import org.apache.rocketmq.common.stats.StatsItemSet; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class BrokerStatsManager { @Deprecated public static final String QUEUE_PUT_NUMS = Stats.QUEUE_PUT_NUMS; @Deprecated public static final String QUEUE_PUT_SIZE = Stats.QUEUE_PUT_SIZE; @Deprecated public static final String QUEUE_GET_NUMS = Stats.QUEUE_GET_NUMS; @Deprecated public static final String QUEUE_GET_SIZE = Stats.QUEUE_GET_SIZE; @Deprecated public static final String TOPIC_PUT_NUMS = Stats.TOPIC_PUT_NUMS; @Deprecated public static final String TOPIC_PUT_SIZE = Stats.TOPIC_PUT_SIZE; @Deprecated public static final String GROUP_GET_NUMS = Stats.GROUP_GET_NUMS; @Deprecated public static final String GROUP_GET_SIZE = Stats.GROUP_GET_SIZE; @Deprecated public static final String SNDBCK_PUT_NUMS = Stats.SNDBCK_PUT_NUMS; @Deprecated public static final String BROKER_PUT_NUMS = Stats.BROKER_PUT_NUMS; @Deprecated public static final String BROKER_GET_NUMS = Stats.BROKER_GET_NUMS; @Deprecated public static final String GROUP_GET_FROM_DISK_NUMS = Stats.GROUP_GET_FROM_DISK_NUMS; @Deprecated public static final String GROUP_GET_FROM_DISK_SIZE = Stats.GROUP_GET_FROM_DISK_SIZE; @Deprecated public static final String BROKER_GET_FROM_DISK_NUMS = Stats.BROKER_GET_FROM_DISK_NUMS; @Deprecated public static final String BROKER_GET_FROM_DISK_SIZE = Stats.BROKER_GET_FROM_DISK_SIZE; // For commercial @Deprecated public static final String COMMERCIAL_SEND_TIMES = Stats.COMMERCIAL_SEND_TIMES; @Deprecated public static final String COMMERCIAL_SNDBCK_TIMES = Stats.COMMERCIAL_SNDBCK_TIMES; @Deprecated public static final String COMMERCIAL_RCV_TIMES = Stats.COMMERCIAL_RCV_TIMES; @Deprecated public static final String COMMERCIAL_RCV_EPOLLS = Stats.COMMERCIAL_RCV_EPOLLS; @Deprecated public static final String COMMERCIAL_SEND_SIZE = Stats.COMMERCIAL_SEND_SIZE; @Deprecated public static final String COMMERCIAL_RCV_SIZE = Stats.COMMERCIAL_RCV_SIZE; @Deprecated public static final String COMMERCIAL_PERM_FAILURES = Stats.COMMERCIAL_PERM_FAILURES; // Send message latency @Deprecated public static final String TOPIC_PUT_LATENCY = "TOPIC_PUT_LATENCY"; @Deprecated public static final String GROUP_ACK_NUMS = "GROUP_ACK_NUMS"; @Deprecated public static final String GROUP_CK_NUMS = "GROUP_CK_NUMS"; public static final String DLQ_PUT_NUMS = "DLQ_PUT_NUMS"; public static final String BROKER_ACK_NUMS = "BROKER_ACK_NUMS"; public static final String BROKER_CK_NUMS = "BROKER_CK_NUMS"; public static final String BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC = "BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC"; public static final String BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC = "BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC"; public static final String SNDBCK2DLQ_TIMES = "SNDBCK2DLQ_TIMES"; public static final String COMMERCIAL_OWNER = "Owner"; public static final String ACCOUNT_OWNER_PARENT = "OWNER_PARENT"; public static final String ACCOUNT_OWNER_SELF = "OWNER_SELF"; public static final long ACCOUNT_STAT_INVERTAL = 60 * 1000; public static final String ACCOUNT_AUTH_TYPE = "AUTH_TYPE"; public static final String ACCOUNT_SEND = "SEND"; public static final String ACCOUNT_RCV = "RCV"; public static final String ACCOUNT_SEND_BACK = "SEND_BACK"; public static final String ACCOUNT_SEND_BACK_TO_DLQ = "SEND_BACK_TO_DLQ"; public static final String ACCOUNT_AUTH_FAILED = "AUTH_FAILED"; public static final String ACCOUNT_SEND_REJ = "SEND_REJ"; public static final String ACCOUNT_REV_REJ = "RCV_REJ"; public static final String MSG_NUM = "MSG_NUM"; public static final String MSG_SIZE = "MSG_SIZE"; public static final String SUCCESS_MSG_NUM = "SUCCESS_MSG_NUM"; public static final String FAILURE_MSG_NUM = "FAILURE_MSG_NUM"; public static final String COMMERCIAL_MSG_NUM = "COMMERCIAL_MSG_NUM"; public static final String SUCCESS_REQ_NUM = "SUCCESS_REQ_NUM"; public static final String FAILURE_REQ_NUM = "FAILURE_REQ_NUM"; public static final String SUCCESS_MSG_SIZE = "SUCCESS_MSG_SIZE"; public static final String FAILURE_MSG_SIZE = "FAILURE_MSG_SIZE"; public static final String RT = "RT"; public static final String INNER_RT = "INNER_RT"; @Deprecated public static final String GROUP_GET_FALL_SIZE = Stats.GROUP_GET_FALL_SIZE; @Deprecated public static final String GROUP_GET_FALL_TIME = Stats.GROUP_GET_FALL_TIME; // Pull Message Latency @Deprecated public static final String GROUP_GET_LATENCY = Stats.GROUP_GET_LATENCY; // Consumer Register Time public static final String CONSUMER_REGISTER_TIME = "CONSUMER_REGISTER_TIME"; // Producer Register Time public static final String PRODUCER_REGISTER_TIME = "PRODUCER_REGISTER_TIME"; public static final String CHANNEL_ACTIVITY = "CHANNEL_ACTIVITY"; public static final String CHANNEL_ACTIVITY_CONNECT = "CONNECT"; public static final String CHANNEL_ACTIVITY_IDLE = "IDLE"; public static final String CHANNEL_ACTIVITY_EXCEPTION = "EXCEPTION"; public static final String CHANNEL_ACTIVITY_CLOSE = "CLOSE"; private static final String[] NEED_CLEAN_STATS_SET = new String[] {TOPIC_PUT_NUMS, TOPIC_PUT_SIZE, GROUP_GET_NUMS, GROUP_GET_SIZE, SNDBCK_PUT_NUMS, GROUP_GET_LATENCY}; /** * read disk follow stats */ private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_STATS_LOGGER_NAME); private static final Logger COMMERCIAL_LOG = LoggerFactory.getLogger( LoggerName.COMMERCIAL_LOGGER_NAME); private static final Logger ACCOUNT_LOG = LoggerFactory.getLogger(LoggerName.ACCOUNT_LOGGER_NAME); private static final Logger DLQ_STAT_LOG = LoggerFactory.getLogger( LoggerName.DLQ_STATS_LOGGER_NAME); private ScheduledExecutorService scheduledExecutorService; private ScheduledExecutorService commercialExecutor; private ScheduledExecutorService accountExecutor; private ScheduledExecutorService cleanResourceExecutor; private final HashMap statsTable = new HashMap<>(); private final String clusterName; private final boolean enableQueueStat; private MomentStatsItemSet momentStatsItemSetFallSize; private MomentStatsItemSet momentStatsItemSetFallTime; private final StatisticsManager accountStatManager = new StatisticsManager(); private StateGetter producerStateGetter; private StateGetter consumerStateGetter; private BrokerConfig brokerConfig; public BrokerStatsManager(BrokerConfig brokerConfig) { this.brokerConfig = brokerConfig; this.enableQueueStat = brokerConfig.isEnableDetailStat(); initScheduleService(); this.clusterName = brokerConfig.getBrokerClusterName(); init(); } public BrokerStatsManager(String clusterName, boolean enableQueueStat) { this.clusterName = clusterName; this.enableQueueStat = enableQueueStat; initScheduleService(); init(); } public void init() { momentStatsItemSetFallSize = new MomentStatsItemSet(GROUP_GET_FALL_SIZE, scheduledExecutorService, log); momentStatsItemSetFallTime = new MomentStatsItemSet(GROUP_GET_FALL_TIME, scheduledExecutorService, log); if (enableQueueStat) { this.statsTable.put(Stats.QUEUE_PUT_NUMS, new StatsItemSet(Stats.QUEUE_PUT_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.QUEUE_PUT_SIZE, new StatsItemSet(Stats.QUEUE_PUT_SIZE, this.scheduledExecutorService, log)); this.statsTable.put(Stats.QUEUE_GET_NUMS, new StatsItemSet(Stats.QUEUE_GET_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.QUEUE_GET_SIZE, new StatsItemSet(Stats.QUEUE_GET_SIZE, this.scheduledExecutorService, log)); } this.statsTable.put(Stats.TOPIC_PUT_NUMS, new StatsItemSet(Stats.TOPIC_PUT_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.TOPIC_PUT_SIZE, new StatsItemSet(Stats.TOPIC_PUT_SIZE, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_GET_NUMS, new StatsItemSet(Stats.GROUP_GET_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_GET_SIZE, new StatsItemSet(Stats.GROUP_GET_SIZE, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_ACK_NUMS, new StatsItemSet(Stats.GROUP_ACK_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_CK_NUMS, new StatsItemSet(Stats.GROUP_CK_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_GET_LATENCY, new StatsItemSet(Stats.GROUP_GET_LATENCY, this.scheduledExecutorService, log)); this.statsTable.put(Stats.TOPIC_PUT_LATENCY, new StatsItemSet(Stats.TOPIC_PUT_LATENCY, this.scheduledExecutorService, log)); this.statsTable.put(Stats.SNDBCK_PUT_NUMS, new StatsItemSet(Stats.SNDBCK_PUT_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(DLQ_PUT_NUMS, new StatsItemSet(DLQ_PUT_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.BROKER_PUT_NUMS, new StatsItemSet(Stats.BROKER_PUT_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.BROKER_GET_NUMS, new StatsItemSet(Stats.BROKER_GET_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(BROKER_ACK_NUMS, new StatsItemSet(BROKER_ACK_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(BROKER_CK_NUMS, new StatsItemSet(BROKER_CK_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC, new StatsItemSet(BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC, this.scheduledExecutorService, log)); this.statsTable.put(BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC, new StatsItemSet(BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_GET_FROM_DISK_NUMS, new StatsItemSet(Stats.GROUP_GET_FROM_DISK_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.GROUP_GET_FROM_DISK_SIZE, new StatsItemSet(Stats.GROUP_GET_FROM_DISK_SIZE, this.scheduledExecutorService, log)); this.statsTable.put(Stats.BROKER_GET_FROM_DISK_NUMS, new StatsItemSet(Stats.BROKER_GET_FROM_DISK_NUMS, this.scheduledExecutorService, log)); this.statsTable.put(Stats.BROKER_GET_FROM_DISK_SIZE, new StatsItemSet(Stats.BROKER_GET_FROM_DISK_SIZE, this.scheduledExecutorService, log)); this.statsTable.put(SNDBCK2DLQ_TIMES, new StatsItemSet(SNDBCK2DLQ_TIMES, this.scheduledExecutorService, DLQ_STAT_LOG)); this.statsTable.put(Stats.COMMERCIAL_SEND_TIMES, new StatsItemSet(Stats.COMMERCIAL_SEND_TIMES, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(Stats.COMMERCIAL_RCV_TIMES, new StatsItemSet(Stats.COMMERCIAL_RCV_TIMES, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(Stats.COMMERCIAL_SEND_SIZE, new StatsItemSet(Stats.COMMERCIAL_SEND_SIZE, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(Stats.COMMERCIAL_RCV_SIZE, new StatsItemSet(Stats.COMMERCIAL_RCV_SIZE, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(Stats.COMMERCIAL_RCV_EPOLLS, new StatsItemSet(Stats.COMMERCIAL_RCV_EPOLLS, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(Stats.COMMERCIAL_SNDBCK_TIMES, new StatsItemSet(Stats.COMMERCIAL_SNDBCK_TIMES, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(Stats.COMMERCIAL_PERM_FAILURES, new StatsItemSet(Stats.COMMERCIAL_PERM_FAILURES, this.commercialExecutor, COMMERCIAL_LOG)); this.statsTable.put(CONSUMER_REGISTER_TIME, new StatsItemSet(CONSUMER_REGISTER_TIME, this.scheduledExecutorService, log)); this.statsTable.put(PRODUCER_REGISTER_TIME, new StatsItemSet(PRODUCER_REGISTER_TIME, this.scheduledExecutorService, log)); this.statsTable.put(CHANNEL_ACTIVITY, new StatsItemSet(CHANNEL_ACTIVITY, this.scheduledExecutorService, log)); StatisticsItemFormatter formatter = new StatisticsItemFormatter(); accountStatManager.setBriefMeta(new Pair[] { Pair.of(RT, new long[][] {{50, 50}, {100, 10}, {1000, 10}}), Pair.of(INNER_RT, new long[][] {{10, 10}, {100, 10}, {1000, 10}})}); String[] itemNames = new String[] { MSG_NUM, SUCCESS_MSG_NUM, FAILURE_MSG_NUM, COMMERCIAL_MSG_NUM, SUCCESS_REQ_NUM, FAILURE_REQ_NUM, MSG_SIZE, SUCCESS_MSG_SIZE, FAILURE_MSG_SIZE, RT, INNER_RT}; this.accountStatManager.addStatisticsKindMeta(createStatisticsKindMeta( ACCOUNT_SEND, itemNames, this.accountExecutor, formatter, ACCOUNT_LOG, ACCOUNT_STAT_INVERTAL)); this.accountStatManager.addStatisticsKindMeta(createStatisticsKindMeta( ACCOUNT_RCV, itemNames, this.accountExecutor, formatter, ACCOUNT_LOG, ACCOUNT_STAT_INVERTAL)); this.accountStatManager.addStatisticsKindMeta(createStatisticsKindMeta( ACCOUNT_SEND_BACK, itemNames, this.accountExecutor, formatter, ACCOUNT_LOG, ACCOUNT_STAT_INVERTAL)); this.accountStatManager.addStatisticsKindMeta(createStatisticsKindMeta( ACCOUNT_SEND_BACK_TO_DLQ, itemNames, this.accountExecutor, formatter, ACCOUNT_LOG, ACCOUNT_STAT_INVERTAL)); this.accountStatManager.addStatisticsKindMeta(createStatisticsKindMeta( ACCOUNT_SEND_REJ, itemNames, this.accountExecutor, formatter, ACCOUNT_LOG, ACCOUNT_STAT_INVERTAL)); this.accountStatManager.addStatisticsKindMeta(createStatisticsKindMeta( ACCOUNT_REV_REJ, itemNames, this.accountExecutor, formatter, ACCOUNT_LOG, ACCOUNT_STAT_INVERTAL)); this.accountStatManager.setStatisticsItemStateGetter(new StatisticsItemStateGetter() { @Override public boolean online(StatisticsItem item) { String[] strArr = null; try { strArr = splitAccountStatKey(item.getStatObject()); } catch (Exception e) { log.warn("parse account stat key failed, key: {}", item.getStatObject()); return false; } // TODO ugly if (strArr == null || strArr.length < 4) { return false; } String instanceId = strArr[1]; String topic = strArr[2]; String group = strArr[3]; String kind = item.getStatKind(); if (ACCOUNT_SEND.equals(kind) || ACCOUNT_SEND_REJ.equals(kind)) { return producerStateGetter.online(instanceId, group, topic); } else if (ACCOUNT_RCV.equals(kind) || ACCOUNT_SEND_BACK.equals(kind) || ACCOUNT_SEND_BACK_TO_DLQ.equals(kind) || ACCOUNT_REV_REJ.equals(kind)) { return consumerStateGetter.online(instanceId, group, topic); } return false; } }); cleanResourceExecutor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { cleanAllResource(); } }, 10, 10, TimeUnit.MINUTES); } private void initScheduleService() { this.scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("BrokerStatsThread", true, brokerConfig)); this.commercialExecutor = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CommercialStatsThread", true, brokerConfig)); this.accountExecutor = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("AccountStatsThread", true, brokerConfig)); this.cleanResourceExecutor = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CleanStatsResourceThread", true, brokerConfig)); } public MomentStatsItemSet getMomentStatsItemSetFallSize() { return momentStatsItemSetFallSize; } public MomentStatsItemSet getMomentStatsItemSetFallTime() { return momentStatsItemSetFallTime; } public StateGetter getProducerStateGetter() { return producerStateGetter; } public void setProducerStateGetter(StateGetter producerStateGetter) { this.producerStateGetter = producerStateGetter; } public StateGetter getConsumerStateGetter() { return consumerStateGetter; } public void setConsumerStateGetter(StateGetter consumerStateGetter) { this.consumerStateGetter = consumerStateGetter; } public void start() { } public void shutdown() { this.scheduledExecutorService.shutdown(); this.commercialExecutor.shutdown(); this.accountExecutor.shutdown(); this.cleanResourceExecutor.shutdown(); this.accountStatManager.shutdown(); } public StatsItem getStatsItem(final String statsName, final String statsKey) { try { return this.statsTable.get(statsName).getStatsItem(statsKey); } catch (Exception e) { } return null; } public void onTopicDeleted(final String topic) { this.statsTable.get(Stats.TOPIC_PUT_NUMS).delValue(topic); this.statsTable.get(Stats.TOPIC_PUT_SIZE).delValue(topic); if (enableQueueStat) { this.statsTable.get(Stats.QUEUE_PUT_NUMS).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.QUEUE_PUT_SIZE).delValueByPrefixKey(topic, "@"); } this.statsTable.get(Stats.GROUP_GET_NUMS).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.GROUP_GET_SIZE).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.GROUP_CK_NUMS).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.GROUP_ACK_NUMS).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.QUEUE_GET_NUMS).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.QUEUE_GET_SIZE).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.SNDBCK_PUT_NUMS).delValueByPrefixKey(topic, "@"); this.statsTable.get(Stats.GROUP_GET_LATENCY).delValueByInfixKey(topic, "@"); this.statsTable.get(Stats.TOPIC_PUT_LATENCY).delValueBySuffixKey(topic, "@"); this.momentStatsItemSetFallSize.delValueByInfixKey(topic, "@"); this.momentStatsItemSetFallTime.delValueByInfixKey(topic, "@"); } public void onGroupDeleted(final String group) { this.statsTable.get(Stats.GROUP_GET_NUMS).delValueBySuffixKey(group, "@"); this.statsTable.get(Stats.GROUP_GET_SIZE).delValueBySuffixKey(group, "@"); this.statsTable.get(Stats.GROUP_CK_NUMS).delValueBySuffixKey(group, "@"); this.statsTable.get(Stats.GROUP_ACK_NUMS).delValueBySuffixKey(group, "@"); if (enableQueueStat) { this.statsTable.get(Stats.QUEUE_GET_NUMS).delValueBySuffixKey(group, "@"); this.statsTable.get(Stats.QUEUE_GET_SIZE).delValueBySuffixKey(group, "@"); } this.statsTable.get(Stats.SNDBCK_PUT_NUMS).delValueBySuffixKey(group, "@"); this.statsTable.get(Stats.GROUP_GET_LATENCY).delValueBySuffixKey(group, "@"); this.momentStatsItemSetFallSize.delValueBySuffixKey(group, "@"); this.momentStatsItemSetFallTime.delValueBySuffixKey(group, "@"); } public void incQueuePutNums(final String topic, final Integer queueId) { if (enableQueueStat) { this.statsTable.get(Stats.QUEUE_PUT_NUMS).addValue(buildStatsKey(topic, queueId), 1, 1); } } public void incQueuePutNums(final String topic, final Integer queueId, int num, int times) { if (enableQueueStat) { this.statsTable.get(Stats.QUEUE_PUT_NUMS).addValue(buildStatsKey(topic, queueId), num, times); } } public void incQueuePutSize(final String topic, final Integer queueId, final int size) { if (enableQueueStat) { this.statsTable.get(Stats.QUEUE_PUT_SIZE).addValue(buildStatsKey(topic, queueId), size, 1); } } public void incQueueGetNums(final String group, final String topic, final Integer queueId, final int incValue) { if (enableQueueStat) { final String statsKey = buildStatsKey(topic, queueId, group); this.statsTable.get(Stats.QUEUE_GET_NUMS).addValue(statsKey, incValue, 1); } } public void incQueueGetSize(final String group, final String topic, final Integer queueId, final int incValue) { if (enableQueueStat) { final String statsKey = buildStatsKey(topic, queueId, group); this.statsTable.get(Stats.QUEUE_GET_SIZE).addValue(statsKey, incValue, 1); } } public void incConsumerRegisterTime(final int incValue) { this.statsTable.get(CONSUMER_REGISTER_TIME).addValue(this.clusterName, incValue, 1); } public void incProducerRegisterTime(final int incValue) { this.statsTable.get(PRODUCER_REGISTER_TIME).addValue(this.clusterName, incValue, 1); } public void incChannelConnectNum() { this.statsTable.get(CHANNEL_ACTIVITY).addValue(CHANNEL_ACTIVITY_CONNECT, 1, 1); } public void incChannelCloseNum() { this.statsTable.get(CHANNEL_ACTIVITY).addValue(CHANNEL_ACTIVITY_CLOSE, 1, 1); } public void incChannelExceptionNum() { this.statsTable.get(CHANNEL_ACTIVITY).addValue(CHANNEL_ACTIVITY_EXCEPTION, 1, 1); } public void incChannelIdleNum() { this.statsTable.get(CHANNEL_ACTIVITY).addValue(CHANNEL_ACTIVITY_IDLE, 1, 1); } public void incTopicPutNums(final String topic) { this.statsTable.get(Stats.TOPIC_PUT_NUMS).addValue(topic, 1, 1); } public void incTopicPutNums(final String topic, int num, int times) { this.statsTable.get(Stats.TOPIC_PUT_NUMS).addValue(topic, num, times); } public void incTopicPutSize(final String topic, final int size) { this.statsTable.get(Stats.TOPIC_PUT_SIZE).addValue(topic, size, 1); } public void incGroupGetNums(final String group, final String topic, final int incValue) { final String statsKey = buildStatsKey(topic, group); this.statsTable.get(Stats.GROUP_GET_NUMS).addValue(statsKey, incValue, 1); } public void incGroupCkNums(final String group, final String topic, final int incValue) { final String statsKey = buildStatsKey(topic, group); this.statsTable.get(Stats.GROUP_CK_NUMS).addValue(statsKey, incValue, 1); } public void incGroupAckNums(final String group, final String topic, final int incValue) { final String statsKey = buildStatsKey(topic, group); this.statsTable.get(Stats.GROUP_ACK_NUMS).addValue(statsKey, incValue, 1); } public String buildStatsKey(String topic, String group) { StringBuilder strBuilder; if (topic != null && group != null) { strBuilder = new StringBuilder(topic.length() + group.length() + 1); } else { strBuilder = new StringBuilder(); } strBuilder.append(topic).append("@").append(group); return strBuilder.toString(); } public String buildStatsKey(String topic, int queueId) { StringBuilder strBuilder; if (topic != null) { strBuilder = new StringBuilder(topic.length() + 5); } else { strBuilder = new StringBuilder(); } strBuilder.append(topic).append("@").append(queueId); return strBuilder.toString(); } public String buildStatsKey(String topic, int queueId, String group) { StringBuilder strBuilder; if (topic != null && group != null) { strBuilder = new StringBuilder(topic.length() + group.length() + 6); } else { strBuilder = new StringBuilder(); } strBuilder.append(topic).append("@").append(queueId).append("@").append(group); return strBuilder.toString(); } public String buildStatsKey(int queueId, String topic, String group) { StringBuilder strBuilder; if (topic != null && group != null) { strBuilder = new StringBuilder(topic.length() + group.length() + 6); } else { strBuilder = new StringBuilder(); } strBuilder.append(queueId).append("@").append(topic).append("@").append(group); return strBuilder.toString(); } public void incGroupGetSize(final String group, final String topic, final int incValue) { final String statsKey = buildStatsKey(topic, group); this.statsTable.get(Stats.GROUP_GET_SIZE).addValue(statsKey, incValue, 1); } public void incGroupGetLatency(final String group, final String topic, final int queueId, final int incValue) { String statsKey; if (enableQueueStat) { statsKey = buildStatsKey(queueId, topic, group); } else { statsKey = buildStatsKey(topic, group); } this.statsTable.get(Stats.GROUP_GET_LATENCY).addRTValue(statsKey, incValue, 1); } public void incTopicPutLatency(final String topic, final int queueId, final int incValue) { StringBuilder statsKey; if (topic != null) { statsKey = new StringBuilder(topic.length() + 6); } else { statsKey = new StringBuilder(6); } statsKey.append(queueId).append("@").append(topic); this.statsTable.get(Stats.TOPIC_PUT_LATENCY).addValue(statsKey.toString(), incValue, 1); } public void incBrokerPutNums() { this.statsTable.get(Stats.BROKER_PUT_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(1); } public void incBrokerPutNums(final String topic, final int incValue) { this.statsTable.get(Stats.BROKER_PUT_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); incBrokerPutNumsWithoutSystemTopic(topic, incValue); } public void incBrokerGetNums(final String topic, final int incValue) { this.statsTable.get(Stats.BROKER_GET_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); this.incBrokerGetNumsWithoutSystemTopic(topic, incValue); } public void incBrokerAckNums(final int incValue) { this.statsTable.get(BROKER_ACK_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); } public void incBrokerCkNums(final int incValue) { this.statsTable.get(BROKER_CK_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); } public void incBrokerGetNumsWithoutSystemTopic(final String topic, final int incValue) { if (TopicValidator.isSystemTopic(topic)) { return; } this.statsTable.get(BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); } public void incBrokerPutNumsWithoutSystemTopic(final String topic, final int incValue) { if (TopicValidator.isSystemTopic(topic)) { return; } this.statsTable.get(BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); } public long getBrokerGetNumsWithoutSystemTopic() { final StatsItemSet statsItemSet = this.statsTable.get(BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC); if (statsItemSet == null) { return 0; } final StatsItem statsItem = statsItemSet.getStatsItem(this.clusterName); if (statsItem == null) { return 0; } return statsItem.getValue().longValue(); } public long getBrokerPutNumsWithoutSystemTopic() { final StatsItemSet statsItemSet = this.statsTable.get(BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC); if (statsItemSet == null) { return 0; } final StatsItem statsItem = statsItemSet.getStatsItem(this.clusterName); if (statsItem == null) { return 0; } return statsItem.getValue().longValue(); } public void incSendBackNums(final String group, final String topic) { final String statsKey = buildStatsKey(topic, group); this.statsTable.get(Stats.SNDBCK_PUT_NUMS).addValue(statsKey, 1, 1); } public double tpsTopicPutNums(final String topic) { return this.statsTable.get(TOPIC_PUT_NUMS).getStatsDataInMinute(topic).getTps(); } public double tpsGroupGetNums(final String group, final String topic) { final String statsKey = buildStatsKey(topic, group); return this.statsTable.get(Stats.GROUP_GET_NUMS).getStatsDataInMinute(statsKey).getTps(); } public void recordDiskFallBehindTime(final String group, final String topic, final int queueId, final long fallBehind) { final String statsKey = buildStatsKey(queueId, topic, group); this.momentStatsItemSetFallTime.setValue(statsKey, fallBehind); } public void recordDiskFallBehindSize(final String group, final String topic, final int queueId, final long fallBehind) { final String statsKey = buildStatsKey(queueId, topic, group); this.momentStatsItemSetFallSize.setValue(statsKey, fallBehind); } public void incDLQStatValue(final String key, final String owner, final String group, final String topic, final String type, final int incValue) { final String statsKey = buildCommercialStatsKey(owner, topic, group, type); this.statsTable.get(key).addValue(statsKey, incValue, 1); } public void incCommercialValue(final String key, final String owner, final String group, final String topic, final String type, final int incValue) { final String statsKey = buildCommercialStatsKey(owner, topic, group, type); this.statsTable.get(key).addValue(statsKey, incValue, 1); } public void incAccountValue(final String key, final String accountOwnerParent, final String accountOwnerSelf, final String instanceId, final String group, final String topic, final String msgType, final int incValue) { final String statsKey = buildAccountStatsKey(accountOwnerParent, accountOwnerSelf, instanceId, topic, group, msgType); this.statsTable.get(key).addValue(statsKey, incValue, 1); } public void incAccountValue(final String key, final String accountOwnerParent, final String accountOwnerSelf, final String instanceId, final String group, final String topic, final String msgType, final String flowlimitThreshold, final int incValue) { final String statsKey = buildAccountStatsKey(accountOwnerParent, accountOwnerSelf, instanceId, topic, group, msgType, flowlimitThreshold); this.statsTable.get(key).addValue(statsKey, incValue, 1); } public void incAccountValue(final String statType, final String owner, final String instanceId, final String topic, final String group, final String msgType, final long... incValues) { final String key = buildAccountStatKey(owner, instanceId, topic, group, msgType); this.accountStatManager.inc(statType, key, incValues); } public void incAccountValue(final String statType, final String owner, final String instanceId, final String topic, final String group, final String msgType, final String flowlimitThreshold, final long... incValues) { final String key = buildAccountStatKey(owner, instanceId, topic, group, msgType, flowlimitThreshold); this.accountStatManager.inc(statType, key, incValues); } public String buildCommercialStatsKey(String owner, String topic, String group, String type) { StringBuilder strBuilder = new StringBuilder(); strBuilder.append(owner); strBuilder.append("@"); strBuilder.append(topic); strBuilder.append("@"); strBuilder.append(group); strBuilder.append("@"); strBuilder.append(type); return strBuilder.toString(); } public String buildAccountStatsKey(String accountOwnerParent, String accountOwnerSelf, String instanceId, String topic, String group, String msgType) { StringBuffer strBuilder = new StringBuffer(); strBuilder.append(accountOwnerParent); strBuilder.append("@"); strBuilder.append(accountOwnerSelf); strBuilder.append("@"); strBuilder.append(instanceId); strBuilder.append("@"); strBuilder.append(topic); strBuilder.append("@"); strBuilder.append(group); strBuilder.append("@"); strBuilder.append(msgType); return strBuilder.toString(); } public String buildAccountStatsKey(String accountOwnerParent, String accountOwnerSelf, String instanceId, String topic, String group, String msgType, String flowlimitThreshold) { StringBuffer strBuilder = new StringBuffer(); strBuilder.append(accountOwnerParent); strBuilder.append("@"); strBuilder.append(accountOwnerSelf); strBuilder.append("@"); strBuilder.append(instanceId); strBuilder.append("@"); strBuilder.append(topic); strBuilder.append("@"); strBuilder.append(group); strBuilder.append("@"); strBuilder.append(msgType); strBuilder.append("@"); strBuilder.append(flowlimitThreshold); return strBuilder.toString(); } public String buildAccountStatKey(final String owner, final String instanceId, final String topic, final String group, final String msgType) { final String sep = "|"; StringBuffer strBuilder = new StringBuffer(); strBuilder.append(owner).append(sep); strBuilder.append(instanceId).append(sep); strBuilder.append(topic).append(sep); strBuilder.append(group).append(sep); strBuilder.append(msgType); return strBuilder.toString(); } public String buildAccountStatKey(final String owner, final String instanceId, final String topic, final String group, final String msgType, String flowlimitThreshold) { final String sep = "|"; StringBuffer strBuilder = new StringBuffer(); strBuilder.append(owner).append(sep); strBuilder.append(instanceId).append(sep); strBuilder.append(topic).append(sep); strBuilder.append(group).append(sep); strBuilder.append(msgType).append(sep); strBuilder.append(flowlimitThreshold); return strBuilder.toString(); } public String[] splitAccountStatKey(final String accountStatKey) { final String sep = "\\|"; return accountStatKey.split(sep); } private StatisticsKindMeta createStatisticsKindMeta(String name, String[] itemNames, ScheduledExecutorService executorService, StatisticsItemFormatter formatter, Logger log, long interval) { final BrokerConfig brokerConfig = this.brokerConfig; StatisticsItemPrinter printer = new StatisticsItemPrinter(formatter, log); StatisticsKindMeta kindMeta = new StatisticsKindMeta(); kindMeta.setName(name); kindMeta.setItemNames(itemNames); kindMeta.setScheduledPrinter( new StatisticsItemScheduledIncrementPrinter( "Stat In One Minute: ", printer, executorService, new StatisticsItemScheduledPrinter.InitialDelay() { @Override public long get() { return Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()); } }, interval, new String[] {MSG_NUM}, new StatisticsItemScheduledIncrementPrinter.Valve() { @Override public boolean enabled() { return brokerConfig != null ? brokerConfig.isAccountStatsEnable() : true; } @Override public boolean printZeroLine() { return brokerConfig != null ? brokerConfig.isAccountStatsPrintZeroValues() : true; } } ) ); return kindMeta; } public interface StateGetter { boolean online(String instanceId, String group, String topic); } private void cleanAllResource() { try { int maxStatsIdleTimeInMinutes = brokerConfig != null ? brokerConfig.getMaxStatsIdleTimeInMinutes() : -1; if (maxStatsIdleTimeInMinutes < 0) { COMMERCIAL_LOG.info("[BrokerStatsManager#cleanAllResource] maxStatsIdleTimeInMinutes={}, no need to clean resource", maxStatsIdleTimeInMinutes); return; } if (maxStatsIdleTimeInMinutes <= 10 && maxStatsIdleTimeInMinutes >= 0) { maxStatsIdleTimeInMinutes = 30; } for (String statsKind : NEED_CLEAN_STATS_SET) { StatsItemSet statsItemSet = this.statsTable.get(statsKind); if (null == statsItemSet) { continue; } statsItemSet.cleanResource(maxStatsIdleTimeInMinutes); } momentStatsItemSetFallSize.cleanResource(maxStatsIdleTimeInMinutes); momentStatsItemSetFallTime.cleanResource(maxStatsIdleTimeInMinutes); } catch (Throwable throwable) { COMMERCIAL_LOG.error("[BrokerStatsManager#cleanAllResource] clean resource error", throwable); } } public enum StatsType { SEND_SUCCESS, SEND_FAILURE, RCV_SUCCESS, RCV_EPOLLS, SEND_BACK, SEND_BACK_TO_DLQ, SEND_ORDER, SEND_TIMER, SEND_TRANSACTION, PERM_FAILURE } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/stats/LmqBrokerStatsManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.stats; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; public class LmqBrokerStatsManager extends BrokerStatsManager { private final BrokerConfig brokerConfig; public LmqBrokerStatsManager(BrokerConfig brokerConfig) { super(brokerConfig.getBrokerClusterName(), brokerConfig.isEnableDetailStat()); this.brokerConfig = brokerConfig; } @Override public void incGroupGetNums(final String group, final String topic, final int incValue) { super.incGroupGetNums(getAdjustedGroup(group), getAdjustedTopic(topic), incValue); } @Override public void incGroupGetSize(final String group, final String topic, final int incValue) { super.incGroupGetSize(getAdjustedGroup(group), getAdjustedTopic(topic), incValue); } @Override public void incGroupAckNums(final String group, final String topic, final int incValue) { super.incGroupAckNums(getAdjustedGroup(group), getAdjustedTopic(topic), incValue); } @Override public void incGroupCkNums(final String group, final String topic, final int incValue) { super.incGroupCkNums(getAdjustedGroup(group), getAdjustedTopic(topic), incValue); } @Override public void incGroupGetLatency(final String group, final String topic, final int queueId, final int incValue) { super.incGroupGetLatency(getAdjustedGroup(group), getAdjustedTopic(topic), queueId, incValue); } @Override public void incSendBackNums(final String group, final String topic) { super.incSendBackNums(getAdjustedGroup(group), getAdjustedTopic(topic)); } @Override public double tpsGroupGetNums(final String group, final String topic) { return super.tpsGroupGetNums(getAdjustedGroup(group), getAdjustedTopic(topic)); } @Override public void recordDiskFallBehindTime(final String group, final String topic, final int queueId, final long fallBehind) { super.recordDiskFallBehindTime(getAdjustedGroup(group), getAdjustedTopic(topic), queueId, fallBehind); } @Override public void recordDiskFallBehindSize(final String group, final String topic, final int queueId, final long fallBehind) { super.recordDiskFallBehindSize(getAdjustedGroup(group), getAdjustedTopic(topic), queueId, fallBehind); } private String getAdjustedGroup(String group) { return !brokerConfig.isEnableLmqStats() && MixAll.isLmq(group) ? MixAll.LMQ_PREFIX : group; } private String getAdjustedTopic(String topic) { return !brokerConfig.isEnableLmqStats() && MixAll.isLmq(topic) ? MixAll.LMQ_PREFIX : topic; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/Slot.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; /** * Represents a slot of timing wheel. Format: * ┌────────────┬───────────┬───────────┬───────────┬───────────┐ * │delayed time│ first pos │ last pos │ num │ magic │ * ├────────────┼───────────┼───────────┼───────────┼───────────┤ * │ 8bytes │ 8bytes │ 8bytes │ 4bytes │ 4bytes │ * └────────────┴───────────┴───────────┴───────────┴───────────┘ */ public class Slot { public static final short SIZE = 32; public final long timeMs; //delayed time public final long firstPos; public final long lastPos; public final int num; public final int magic; //no use now, just keep it public Slot(long timeMs, long firstPos, long lastPos) { this.timeMs = timeMs; this.firstPos = firstPos; this.lastPos = lastPos; this.num = 0; this.magic = 0; } public Slot(long timeMs, long firstPos, long lastPos, int num, int magic) { this.timeMs = timeMs; this.firstPos = firstPos; this.lastPos = lastPos; this.num = num; this.magic = magic; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/TimerCheckpoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.store.logfile.DefaultMappedFile; public class TimerCheckpoint { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final RandomAccessFile randomAccessFile; private final FileChannel fileChannel; private final MappedByteBuffer mappedByteBuffer; private volatile long lastReadTimeMs = 0; //if it is slave, need to read from master private volatile long lastTimerLogFlushPos = 0; private volatile long lastTimerQueueOffset = 0; private volatile long masterTimerQueueOffset = 0; // read from master private final DataVersion dataVersion = new DataVersion(); public TimerCheckpoint() { this.randomAccessFile = null; this.fileChannel = null; this.mappedByteBuffer = null; } public TimerCheckpoint(final String scpPath) throws IOException { File file = new File(scpPath); UtilAll.ensureDirOK(file.getParent()); boolean fileExists = file.exists(); this.randomAccessFile = new RandomAccessFile(file, "rw"); this.fileChannel = this.randomAccessFile.getChannel(); this.mappedByteBuffer = fileChannel.map(MapMode.READ_WRITE, 0, DefaultMappedFile.OS_PAGE_SIZE); if (fileExists) { log.info("timer checkpoint file exists, " + scpPath); this.lastReadTimeMs = this.mappedByteBuffer.getLong(0); this.lastTimerLogFlushPos = this.mappedByteBuffer.getLong(8); this.lastTimerQueueOffset = this.mappedByteBuffer.getLong(16); this.masterTimerQueueOffset = this.mappedByteBuffer.getLong(24); // new add to record dataVersion if (this.mappedByteBuffer.hasRemaining()) { dataVersion.setStateVersion(this.mappedByteBuffer.getLong(32)); dataVersion.setTimestamp(this.mappedByteBuffer.getLong(40)); dataVersion.setCounter(new AtomicLong(this.mappedByteBuffer.getLong(48))); } log.info("timer checkpoint file lastReadTimeMs " + this.lastReadTimeMs + ", " + UtilAll.timeMillisToHumanString(this.lastReadTimeMs)); log.info("timer checkpoint file lastTimerLogFlushPos " + this.lastTimerLogFlushPos); log.info("timer checkpoint file lastTimerQueueOffset " + this.lastTimerQueueOffset); log.info("timer checkpoint file masterTimerQueueOffset " + this.masterTimerQueueOffset); log.info("timer checkpoint file data version state version " + this.dataVersion.getStateVersion()); log.info("timer checkpoint file data version timestamp " + this.dataVersion.getTimestamp()); log.info("timer checkpoint file data version counter " + this.dataVersion.getCounter()); } else { log.info("timer checkpoint file not exists, " + scpPath); } } public void shutdown() { try { this.flush(); } catch (Throwable e) { log.error("Shutdown error in timer check point", e); } if (null != this.mappedByteBuffer) { // unmap mappedByteBuffer UtilAll.cleanBuffer(this.mappedByteBuffer); } if (null != this.fileChannel) { try { this.fileChannel.close(); } catch (Throwable e) { log.error("Shutdown error in timer check point", e); } } } public void flush() { if (null == this.mappedByteBuffer) { return; } this.mappedByteBuffer.putLong(0, this.lastReadTimeMs); this.mappedByteBuffer.putLong(8, this.lastTimerLogFlushPos); this.mappedByteBuffer.putLong(16, this.lastTimerQueueOffset); this.mappedByteBuffer.putLong(24, this.masterTimerQueueOffset); // new add to record dataVersion this.mappedByteBuffer.putLong(32, this.dataVersion.getStateVersion()); this.mappedByteBuffer.putLong(40, this.dataVersion.getTimestamp()); this.mappedByteBuffer.putLong(48, this.dataVersion.getCounter().get()); this.mappedByteBuffer.force(); } public long getLastReadTimeMs() { return lastReadTimeMs; } public static ByteBuffer encode(TimerCheckpoint another) { ByteBuffer byteBuffer = ByteBuffer.allocate(56); byteBuffer.putLong(another.getLastReadTimeMs()); byteBuffer.putLong(another.getLastTimerLogFlushPos()); byteBuffer.putLong(another.getLastTimerQueueOffset()); byteBuffer.putLong(another.getMasterTimerQueueOffset()); // new add to record dataVersion byteBuffer.putLong(another.getDataVersion().getStateVersion()); byteBuffer.putLong(another.getDataVersion().getTimestamp()); byteBuffer.putLong(another.getDataVersion().getCounter().get()); byteBuffer.flip(); return byteBuffer; } public static TimerCheckpoint decode(ByteBuffer byteBuffer) { TimerCheckpoint tmp = new TimerCheckpoint(); tmp.setLastReadTimeMs(byteBuffer.getLong()); tmp.setLastTimerLogFlushPos(byteBuffer.getLong()); tmp.setLastTimerQueueOffset(byteBuffer.getLong()); tmp.setMasterTimerQueueOffset(byteBuffer.getLong()); // new add to record dataVersion if (byteBuffer.hasRemaining()) { tmp.getDataVersion().setStateVersion(byteBuffer.getLong()); tmp.getDataVersion().setTimestamp(byteBuffer.getLong()); tmp.getDataVersion().setCounter(new AtomicLong(byteBuffer.getLong())); } return tmp; } public void setLastReadTimeMs(long lastReadTimeMs) { this.lastReadTimeMs = lastReadTimeMs; } public long getLastTimerLogFlushPos() { return lastTimerLogFlushPos; } public void setLastTimerLogFlushPos(long lastTimerLogFlushPos) { this.lastTimerLogFlushPos = lastTimerLogFlushPos; } public long getLastTimerQueueOffset() { return lastTimerQueueOffset; } public void setLastTimerQueueOffset(long lastTimerQueueOffset) { this.lastTimerQueueOffset = lastTimerQueueOffset; } public long getMasterTimerQueueOffset() { return masterTimerQueueOffset; } public void setMasterTimerQueueOffset(final long masterTimerQueueOffset) { this.masterTimerQueueOffset = masterTimerQueueOffset; } public void updateDataVersion(long stateVersion) { dataVersion.nextVersion(stateVersion); } public DataVersion getDataVersion() { return dataVersion; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/TimerLog.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.MappedFileQueue; import org.apache.rocketmq.store.SelectMappedBufferResult; import java.nio.ByteBuffer; public class TimerLog { private static Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public final static int BLANK_MAGIC_CODE = 0xBBCCDDEE ^ 1880681586 + 8; private final static int MIN_BLANK_LEN = 4 + 8 + 4; public final static int UNIT_SIZE = 4 //size + 8 //prev pos + 4 //magic value + 8 //curr write time, for trace + 4 //delayed time, for check + 8 //offsetPy + 4 //sizePy + 4 //hash code of real topic + 8; //reserved value, just in case of public final static int UNIT_PRE_SIZE_FOR_MSG = 28; public final static int UNIT_PRE_SIZE_FOR_METRIC = 40; private final MappedFileQueue mappedFileQueue; private final int fileSize; public TimerLog(final String storePath, final int fileSize) { this(storePath, fileSize, null, false); } public TimerLog(final String storePath, final int fileSize, RunningFlags runningFlags, boolean writeWithoutMmap) { this.fileSize = fileSize; this.mappedFileQueue = new MappedFileQueue(storePath, fileSize, null, runningFlags, writeWithoutMmap); } public boolean load() { return this.mappedFileQueue.load(); } public long append(byte[] data) { return append(data, 0, data.length); } public long append(byte[] data, int pos, int len) { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); } if (null == mappedFile) { log.error("Create mapped file1 error for timer log"); return -1; } if (len + MIN_BLANK_LEN > mappedFile.getFileSize() - mappedFile.getWrotePosition()) { ByteBuffer byteBuffer = ByteBuffer.allocate(MIN_BLANK_LEN); byteBuffer.putInt(mappedFile.getFileSize() - mappedFile.getWrotePosition()); byteBuffer.putLong(0); byteBuffer.putInt(BLANK_MAGIC_CODE); if (mappedFile.appendMessage(byteBuffer.array())) { //need to set the wrote position mappedFile.setWrotePosition(mappedFile.getFileSize()); } else { log.error("Append blank error for timer log"); return -1; } mappedFile = this.mappedFileQueue.getLastMappedFile(0); if (null == mappedFile) { log.error("create mapped file2 error for timer log"); return -1; } } long currPosition = mappedFile.getFileFromOffset() + mappedFile.getWrotePosition(); if (!mappedFile.appendMessage(data, pos, len)) { log.error("Append error for timer log"); return -1; } return currPosition; } public SelectMappedBufferResult getTimerMessage(long offsetPy) { MappedFile mappedFile = mappedFileQueue.findMappedFileByOffset(offsetPy); if (null == mappedFile) return null; return mappedFile.selectMappedBuffer((int) (offsetPy % mappedFile.getFileSize())); } public SelectMappedBufferResult getWholeBuffer(long offsetPy) { MappedFile mappedFile = mappedFileQueue.findMappedFileByOffset(offsetPy); if (null == mappedFile) return null; return mappedFile.selectMappedBuffer(0); } public MappedFileQueue getMappedFileQueue() { return mappedFileQueue; } public void shutdown() { try { this.mappedFileQueue.flush(0); } catch (Throwable e) { log.error("flush error when shutdown", e); } this.mappedFileQueue.cleanResourcesAll(); } // be careful. // if the format of timerlog changed, this offset has to be changed too // so does the batch writing public int getOffsetForLastUnit() { return fileSize - (fileSize - MIN_BLANK_LEN) % UNIT_SIZE - MIN_BLANK_LEN - UNIT_SIZE; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; import io.opentelemetry.api.common.Attributes; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.RunningFlags; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.StoreUtil; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant; import org.apache.rocketmq.store.metrics.DefaultStoreMetricsManager; import org.apache.rocketmq.store.metrics.StoreMetricsManager; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.util.PerfCounter; public class TimerMessageStore { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public static final int INITIAL = 0, RUNNING = 1, HAULT = 2, SHUTDOWN = 3; private volatile int state = INITIAL; public static final String TIMER_TOPIC = TopicValidator.SYSTEM_TOPIC_PREFIX + "wheel_timer"; public static final String TIMER_OUT_MS = MessageConst.PROPERTY_TIMER_OUT_MS; public static final String TIMER_ENQUEUE_MS = MessageConst.PROPERTY_TIMER_ENQUEUE_MS; public static final String TIMER_DEQUEUE_MS = MessageConst.PROPERTY_TIMER_DEQUEUE_MS; public static final String TIMER_ROLL_TIMES = MessageConst.PROPERTY_TIMER_ROLL_TIMES; public static final String TIMER_DELETE_UNIQUE_KEY = MessageConst.PROPERTY_TIMER_DEL_UNIQKEY; public static final Random RANDOM = new Random(); public static final int PUT_OK = 0, PUT_NEED_RETRY = 1, PUT_NO_RETRY = 2; public static final int DAY_SECS = 24 * 3600; public static final int DEFAULT_CAPACITY = 1024; // The total days in the timer wheel when precision is 1000ms. // If the broker shutdown last more than the configured days, will cause message loss public static final int TIMER_WHEEL_TTL_DAY = 7; public static final int TIMER_BLANK_SLOTS = 60; public static final int MAGIC_DEFAULT = 1; public static final int MAGIC_ROLL = 1 << 1; public static final int MAGIC_DELETE = 1 << 2; public boolean debug = false; protected static final String ENQUEUE_PUT = "enqueue_put"; protected static final String DEQUEUE_PUT = "dequeue_put"; protected final PerfCounter.Ticks perfCounterTicks = new PerfCounter.Ticks(LOGGER); protected final BlockingQueue enqueuePutQueue; protected final BlockingQueue> dequeueGetQueue; protected final BlockingQueue dequeuePutQueue; private final ByteBuffer timerLogBuffer = ByteBuffer.allocate(4 * 1024); private final ThreadLocal bufferLocal; private final ScheduledExecutorService scheduler; private final MessageStore messageStore; private final TimerWheel timerWheel; private final TimerLog timerLog; private final TimerCheckpoint timerCheckpoint; private TimerEnqueueGetService enqueueGetService; private TimerEnqueuePutService enqueuePutService; private TimerDequeueWarmService dequeueWarmService; private TimerDequeueGetService dequeueGetService; private TimerDequeuePutMessageService[] dequeuePutMessageServices; private TimerDequeueGetMessageService[] dequeueGetMessageServices; private TimerFlushService timerFlushService; protected volatile long currReadTimeMs; protected volatile long currWriteTimeMs; protected volatile long preReadTimeMs; protected volatile long commitReadTimeMs; protected volatile long currQueueOffset; //only one queue that is 0 protected volatile long commitQueueOffset; protected volatile long lastCommitReadTimeMs; protected volatile long lastCommitQueueOffset; private long lastEnqueueButExpiredTime; private long lastEnqueueButExpiredStoreTime; private final int commitLogFileSize; private final int timerLogFileSize; private final int timerRollWindowSlots; private final int slotsTotal; protected final int precisionMs; protected final MessageStoreConfig storeConfig; protected TimerMetrics timerMetrics; protected long lastTimeOfCheckMetrics = System.currentTimeMillis(); protected AtomicInteger frequency = new AtomicInteger(0); private volatile BrokerRole lastBrokerRole = BrokerRole.SLAVE; //the dequeue is an asynchronous process, use this flag to track if the status has changed private boolean dequeueStatusChangeFlag = false; private long shouldStartTime; // True if current store is master or current brokerId is equal to the minimum brokerId of the replica group in slaveActingMaster mode. protected volatile boolean shouldRunningDequeue; private final BrokerStatsManager brokerStatsManager; private Function escapeBridgeHook; private final Object lockWhenFlush = new Object(); public TimerMessageStore(final MessageStore messageStore, final MessageStoreConfig storeConfig, TimerCheckpoint timerCheckpoint, TimerMetrics timerMetrics, final BrokerStatsManager brokerStatsManager) throws IOException { this.messageStore = messageStore; this.storeConfig = storeConfig; this.commitLogFileSize = storeConfig.getMappedFileSizeCommitLog(); this.timerLogFileSize = storeConfig.getMappedFileSizeTimerLog(); this.precisionMs = storeConfig.getTimerPrecisionMs(); // TimerWheel contains the fixed number of slots regardless of precision. this.slotsTotal = TIMER_WHEEL_TTL_DAY * DAY_SECS; String timerWheelPath = getTimerWheelPath(storeConfig.getStorePathRootDir()); long snapOffset = -1; if (storeConfig.isTimerWheelSnapshotFlush()) { snapOffset = TimerWheel.getMaxSnapshotFlag(timerWheelPath); if (snapOffset > 0) { // correct recover offset timerCheckpoint.setLastTimerLogFlushPos(snapOffset); LOGGER.info("found timerWheel snapshot offset {}", snapOffset); } else { LOGGER.info("not found timerWheel snapshot", snapOffset); } } RunningFlags runningFlags = null; if (storeConfig.isEnableRunningFlagsInFlush() && messageStore != null) { runningFlags = messageStore.getRunningFlags(); } this.timerWheel = new TimerWheel( timerWheelPath, this.slotsTotal, precisionMs, snapOffset); this.timerLog = new TimerLog(getTimerLogPath(storeConfig.getStorePathRootDir()), timerLogFileSize, runningFlags, storeConfig.isWriteWithoutMmap()); this.timerMetrics = timerMetrics; this.timerCheckpoint = timerCheckpoint; this.lastBrokerRole = storeConfig.getBrokerRole(); if (messageStore instanceof DefaultMessageStore) { scheduler = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryImpl("TimerScheduledThread", ((DefaultMessageStore) messageStore).getBrokerIdentity())); } else { scheduler = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryImpl("TimerScheduledThread")); } // timerRollWindow contains the fixed number of slots regardless of precision. if (storeConfig.getTimerRollWindowSlot() > slotsTotal - TIMER_BLANK_SLOTS || storeConfig.getTimerRollWindowSlot() < 2) { this.timerRollWindowSlots = slotsTotal - TIMER_BLANK_SLOTS; } else { this.timerRollWindowSlots = storeConfig.getTimerRollWindowSlot(); } bufferLocal = new ThreadLocal() { @Override protected ByteBuffer initialValue() { return ByteBuffer.allocateDirect(storeConfig.getMaxMessageSize() + 100); } }; if (storeConfig.isTimerEnableDisruptor()) { enqueuePutQueue = new DisruptorBlockingQueue<>(DEFAULT_CAPACITY); dequeueGetQueue = new DisruptorBlockingQueue<>(DEFAULT_CAPACITY); dequeuePutQueue = new DisruptorBlockingQueue<>(DEFAULT_CAPACITY); } else { enqueuePutQueue = new LinkedBlockingDeque<>(DEFAULT_CAPACITY); dequeueGetQueue = new LinkedBlockingDeque<>(DEFAULT_CAPACITY); dequeuePutQueue = new LinkedBlockingDeque<>(DEFAULT_CAPACITY); } this.brokerStatsManager = brokerStatsManager; } public void initService() { enqueueGetService = new TimerEnqueueGetService(); enqueuePutService = new TimerEnqueuePutService(); dequeueWarmService = new TimerDequeueWarmService(); dequeueGetService = new TimerDequeueGetService(); timerFlushService = new TimerFlushService(); int getThreadNum = Math.max(storeConfig.getTimerGetMessageThreadNum(), 1); dequeueGetMessageServices = new TimerDequeueGetMessageService[getThreadNum]; for (int i = 0; i < dequeueGetMessageServices.length; i++) { dequeueGetMessageServices[i] = new TimerDequeueGetMessageService(); } int putThreadNum = Math.max(storeConfig.getTimerPutMessageThreadNum(), 1); dequeuePutMessageServices = new TimerDequeuePutMessageService[putThreadNum]; for (int i = 0; i < dequeuePutMessageServices.length; i++) { dequeuePutMessageServices[i] = new TimerDequeuePutMessageService(); } } public boolean load() { this.initService(); boolean load = timerLog.load(); load = load && this.timerMetrics.load(); recover(); calcTimerDistribution(); return load; } public static String getTimerWheelPath(final String rootDir) { return rootDir + File.separator + "timerwheel"; } public static String getTimerLogPath(final String rootDir) { return rootDir + File.separator + "timerlog"; } private void calcTimerDistribution() { long startTime = System.currentTimeMillis(); List timerDist = this.timerMetrics.getTimerDistList(); long currTime = System.currentTimeMillis() / precisionMs * precisionMs; for (int i = 0; i < timerDist.size(); i++) { int slotBeforeNum = i == 0 ? 0 : timerDist.get(i - 1) * 1000 / precisionMs; int slotTotalNum = timerDist.get(i) * 1000 / precisionMs; int periodTotal = 0; for (int j = slotBeforeNum; j < slotTotalNum; j++) { Slot slotEach = timerWheel.getSlot(currTime + (long) j * precisionMs); periodTotal += slotEach.num; } LOGGER.debug("{} period's total num: {}", timerDist.get(i), periodTotal); this.timerMetrics.updateDistPair(timerDist.get(i), periodTotal); } long endTime = System.currentTimeMillis(); LOGGER.debug("Total cost Time: {}", endTime - startTime); } @SuppressWarnings("NonAtomicOperationOnVolatileField") public void recover() { //recover timerLog long lastFlushPos = timerCheckpoint.getLastTimerLogFlushPos(); MappedFile lastFile = timerLog.getMappedFileQueue().getLastMappedFile(); if (null != lastFile) { lastFlushPos = lastFlushPos - lastFile.getFileSize(); } if (lastFlushPos < 0) { lastFlushPos = 0; } long processOffset = recoverAndRevise(lastFlushPos, true); timerLog.getMappedFileQueue().setFlushedWhere(processOffset); //revise queue offset long queueOffset = reviseQueueOffset(processOffset); if (-1 == queueOffset) { currQueueOffset = timerCheckpoint.getLastTimerQueueOffset(); } else { currQueueOffset = queueOffset + 1; } currQueueOffset = Math.min(currQueueOffset, timerCheckpoint.getMasterTimerQueueOffset()); if (storeConfig.isTimerRocksDBEnable()) { long commitOffsetInRocksDB = messageStore.getTimerMessageRocksDBStore().getCommitOffsetInRocksDB(); LOGGER.info("recover time wheel, currQueueOffset: {}, commitOffsetInRocksDB: {}", currQueueOffset, commitOffsetInRocksDB); currQueueOffset = Math.max(currQueueOffset, commitOffsetInRocksDB); } ConsumeQueueInterface cq = this.messageStore.getConsumeQueue(TIMER_TOPIC, 0); // Correction based consume queue if (cq != null && currQueueOffset < cq.getMinOffsetInQueue()) { LOGGER.warn("Timer currQueueOffset:{} is smaller than minOffsetInQueue:{}", currQueueOffset, cq.getMinOffsetInQueue()); currQueueOffset = cq.getMinOffsetInQueue(); } else if (cq != null && currQueueOffset > cq.getMaxOffsetInQueue()) { LOGGER.warn("Timer currQueueOffset:{} is larger than maxOffsetInQueue:{}", currQueueOffset, cq.getMaxOffsetInQueue()); currQueueOffset = cq.getMaxOffsetInQueue(); } //check timer wheel currReadTimeMs = timerCheckpoint.getLastReadTimeMs(); long nextReadTimeMs = formatTimeMs( System.currentTimeMillis()) - (long) slotsTotal * precisionMs + (long) TIMER_BLANK_SLOTS * precisionMs; if (currReadTimeMs < nextReadTimeMs) { currReadTimeMs = nextReadTimeMs; } //the timer wheel may contain physical offset bigger than timerLog //This will only happen when the timerLog is damaged //hard to test long minFirst = timerWheel.checkPhyPos(currReadTimeMs, processOffset); if (debug) { minFirst = 0; } if (minFirst < processOffset) { LOGGER.warn("Timer recheck because of minFirst:{} processOffset:{}", minFirst, processOffset); recoverAndRevise(minFirst, false); } LOGGER.info("Timer recover ok currReadTimerMs:{} currQueueOffset:{} checkQueueOffset:{} processOffset:{}", currReadTimeMs, currQueueOffset, timerCheckpoint.getLastTimerQueueOffset(), processOffset); commitReadTimeMs = currReadTimeMs; commitQueueOffset = currQueueOffset; prepareTimerCheckPoint(); } public long reviseQueueOffset(long processOffset) { SelectMappedBufferResult selectRes = timerLog.getTimerMessage(processOffset - (TimerLog.UNIT_SIZE - TimerLog.UNIT_PRE_SIZE_FOR_MSG)); if (null == selectRes) { return -1; } try { long offsetPy = selectRes.getByteBuffer().getLong(); int sizePy = selectRes.getByteBuffer().getInt(); MessageExt messageExt = getMessageByCommitOffset(offsetPy, sizePy); if (null == messageExt) { return -1; } // check offset in msg is equal to offset of cq. // if not, use cq offset. long msgQueueOffset = messageExt.getQueueOffset(); int queueId = messageExt.getQueueId(); ConsumeQueueInterface cq = this.messageStore.getConsumeQueue(TIMER_TOPIC, queueId); if (null == cq) { return msgQueueOffset; } long cqOffset = msgQueueOffset; long tmpOffset = msgQueueOffset; int maxCount = 20000; while (maxCount-- > 0) { if (tmpOffset < 0) { LOGGER.warn("reviseQueueOffset check cq offset fail, msg in cq is not found.{}, {}", offsetPy, sizePy); break; } ReferredIterator iterator = null; try { iterator = cq.iterateFrom(tmpOffset); CqUnit cqUnit = null; if (null == iterator || (cqUnit = iterator.next()) == null) { // offset in msg may be greater than offset of cq. tmpOffset -= 1; continue; } long offsetPyTemp = cqUnit.getPos(); int sizePyTemp = cqUnit.getSize(); if (offsetPyTemp == offsetPy && sizePyTemp == sizePy) { LOGGER.info("reviseQueueOffset check cq offset ok. {}, {}, {}", tmpOffset, offsetPyTemp, sizePyTemp); cqOffset = tmpOffset; break; } tmpOffset -= 1; } catch (Throwable e) { LOGGER.error("reviseQueueOffset check cq offset error.", e); } finally { if (iterator != null) { iterator.release(); } } } return cqOffset; } finally { selectRes.release(); } } //recover timerLog and revise timerWheel //return process offset private long recoverAndRevise(long beginOffset, boolean checkTimerLog) { LOGGER.info("Begin to recover timerLog offset:{} check:{}", beginOffset, checkTimerLog); MappedFile lastFile = timerLog.getMappedFileQueue().getLastMappedFile(); if (null == lastFile) { return 0; } List mappedFiles = timerLog.getMappedFileQueue().getMappedFiles(); int index = mappedFiles.size() - 1; for (; index >= 0; index--) { MappedFile mappedFile = mappedFiles.get(index); if (beginOffset >= mappedFile.getFileFromOffset()) { break; } } if (index < 0) { index = 0; } long checkOffset = mappedFiles.get(index).getFileFromOffset(); for (; index < mappedFiles.size(); index++) { MappedFile mappedFile = mappedFiles.get(index); SelectMappedBufferResult sbr = mappedFile.selectMappedBuffer(0, checkTimerLog ? mappedFiles.get(index).getFileSize() : mappedFile.getReadPosition()); ByteBuffer bf = sbr.getByteBuffer(); int position = 0; boolean stopCheck = false; for (; position < sbr.getSize(); position += TimerLog.UNIT_SIZE) { try { bf.position(position); int size = bf.getInt();//size bf.getLong();//prev pos int magic = bf.getInt(); if (magic == TimerLog.BLANK_MAGIC_CODE) { break; } if (checkTimerLog && (!isMagicOK(magic) || TimerLog.UNIT_SIZE != size)) { stopCheck = true; break; } long delayTime = bf.getLong() + bf.getInt(); if (TimerLog.UNIT_SIZE == size && isMagicOK(magic)) { timerWheel.reviseSlot(delayTime, TimerWheel.IGNORE, sbr.getStartOffset() + position, true); } } catch (Exception e) { LOGGER.error("Recover timerLog error", e); stopCheck = true; break; } } sbr.release(); checkOffset = mappedFiles.get(index).getFileFromOffset() + position; if (stopCheck) { break; } } if (checkTimerLog) { timerLog.getMappedFileQueue().truncateDirtyFiles(checkOffset); } return checkOffset; } public static boolean isMagicOK(int magic) { return (magic | 0xF) == 0xF; } public void start() { this.shouldStartTime = storeConfig.getDisappearTimeAfterStart() + System.currentTimeMillis(); maybeMoveWriteTime(); enqueueGetService.start(); enqueuePutService.start(); dequeueWarmService.start(); dequeueGetService.start(); for (int i = 0; i < dequeueGetMessageServices.length; i++) { dequeueGetMessageServices[i].start(); } for (int i = 0; i < dequeuePutMessageServices.length; i++) { dequeuePutMessageServices[i].start(); } timerFlushService.start(); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { long minPy = messageStore.getMinPhyOffset(); int checkOffset = timerLog.getOffsetForLastUnit(); timerLog.getMappedFileQueue() .deleteExpiredFileByOffsetForTimerLog(minPy, checkOffset, TimerLog.UNIT_SIZE); } catch (Exception e) { LOGGER.error("Error in cleaning timerLog", e); } } }, 30, 30, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { if (storeConfig.isTimerEnableCheckMetrics()) { String when = storeConfig.getTimerCheckMetricsWhen(); if (!UtilAll.isItTimeToDo(when)) { return; } long curr = System.currentTimeMillis(); if (curr - lastTimeOfCheckMetrics > 70 * 60 * 1000) { lastTimeOfCheckMetrics = curr; checkAndReviseMetrics(); LOGGER.info("[CheckAndReviseMetrics]Timer do check timer metrics cost {} ms", System.currentTimeMillis() - curr); } } } catch (Exception e) { LOGGER.error("Error in cleaning timerLog", e); } } }, 45, 45, TimeUnit.MINUTES); state = RUNNING; LOGGER.info("Timer start ok currReadTimerMs:[{}] queueOffset:[{}]", new Timestamp(currReadTimeMs), currQueueOffset); } public void start(boolean shouldRunningDequeue) { this.shouldRunningDequeue = shouldRunningDequeue; this.start(); } public void shutdown() { if (SHUTDOWN == state) { return; } state = SHUTDOWN; if (this.scheduler != null) { List remainingTasks = this.scheduler.shutdownNow(); if (!remainingTasks.isEmpty()) { LOGGER.info("Timer scheduler shutdown interrupted {} tasks", remainingTasks.size()); } try { if (!this.scheduler.awaitTermination(30, TimeUnit.SECONDS)) { LOGGER.warn("Timer scheduler did not terminate gracefully"); } } catch (InterruptedException e) { LOGGER.warn("Interrupted while waiting for scheduler termination", e); } } //first save checkpoint if (timerCheckpoint != null) { prepareTimerCheckPoint(); } if (timerFlushService != null) { timerFlushService.shutdown(); } if (timerCheckpoint != null) { timerCheckpoint.shutdown(); } if (enqueuePutQueue != null) { enqueuePutQueue.clear(); //avoid blocking } if (dequeueGetQueue != null) { dequeueGetQueue.clear(); //avoid blocking } if (dequeuePutQueue != null) { dequeuePutQueue.clear(); //avoid blocking } if (enqueueGetService != null) { enqueueGetService.shutdown(); } if (enqueuePutService != null) { enqueuePutService.shutdown(); } if (dequeueWarmService != null) { dequeueWarmService.shutdown(); } if (dequeueGetService != null) { dequeueGetService.shutdown(); } if (dequeueGetMessageServices != null) { for (TimerDequeueGetMessageService dequeueGetMessageServices : dequeueGetMessageServices) { if (dequeueGetMessageServices != null) { dequeueGetMessageServices.shutdown(); } } } if (dequeuePutMessageServices != null) { for (TimerDequeuePutMessageService dequeuePutMessageServices : dequeuePutMessageServices) { if (dequeuePutMessageServices != null) { dequeuePutMessageServices.shutdown(); } } } if (timerWheel != null) { timerWheel.shutdown(false); } if (timerLog != null) { timerLog.shutdown(); } if (this.bufferLocal != null) { UtilAll.cleanBuffer(this.bufferLocal.get()); this.bufferLocal.remove(); } } protected void maybeMoveWriteTime() { if (currWriteTimeMs < formatTimeMs(System.currentTimeMillis())) { currWriteTimeMs = formatTimeMs(System.currentTimeMillis()); } } private void moveReadTime() { currReadTimeMs = currReadTimeMs + precisionMs; commitReadTimeMs = currReadTimeMs; } private boolean isRunning() { return RUNNING == state; } private void checkBrokerRole() { BrokerRole currRole = storeConfig.getBrokerRole(); if (lastBrokerRole != currRole) { synchronized (lastBrokerRole) { LOGGER.info("Broker role change from {} to {}", lastBrokerRole, currRole); //if change to master, do something if (BrokerRole.SLAVE != currRole) { currQueueOffset = Math.min(currQueueOffset, timerCheckpoint.getMasterTimerQueueOffset()); commitQueueOffset = currQueueOffset; prepareTimerCheckPoint(); try { timerCheckpoint.flush(); } catch (Throwable e) { LOGGER.error("Error in flush timerCheckpoint", e); } currReadTimeMs = timerCheckpoint.getLastReadTimeMs(); commitReadTimeMs = currReadTimeMs; } //if change to slave, just let it go lastBrokerRole = currRole; } } } private boolean isRunningEnqueue() { checkBrokerRole(); if (!shouldRunningDequeue && !isMaster() && currQueueOffset >= timerCheckpoint.getMasterTimerQueueOffset()) { return false; } return isRunning(); } private boolean isRunningDequeue() { if (!this.shouldRunningDequeue) { syncLastReadTimeMs(); return false; } return isRunning(); } public void syncLastReadTimeMs() { currReadTimeMs = timerCheckpoint.getLastReadTimeMs(); commitReadTimeMs = currReadTimeMs; } public void setShouldRunningDequeue(final boolean shouldRunningDequeue) { this.shouldRunningDequeue = shouldRunningDequeue; } public boolean isShouldRunningDequeue() { return shouldRunningDequeue; } public void addMetric(MessageExt msg, int value) { try { if (null == msg || null == msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC)) { return; } if (msg.getProperty(TIMER_ENQUEUE_MS) != null && NumberUtils.toLong(msg.getProperty(TIMER_ENQUEUE_MS)) == Long.MAX_VALUE) { return; } // pass msg into addAndGet, for further more judgement extension. timerMetrics.addAndGet(msg, value); } catch (Throwable t) { if (frequency.incrementAndGet() % 1000 == 0) { LOGGER.error("error in adding metric", t); } } } public void holdMomentForUnknownError(long ms) { try { Thread.sleep(ms); } catch (Exception ignored) { } } public void holdMomentForUnknownError() { holdMomentForUnknownError(50); } public boolean enqueue(int queueId) { if (storeConfig.isTimerStopEnqueue()) { return false; } if (!isRunningEnqueue()) { return false; } ConsumeQueueInterface cq = this.messageStore.getConsumeQueue(TIMER_TOPIC, queueId); if (null == cq) { return false; } if (currQueueOffset < cq.getMinOffsetInQueue()) { LOGGER.warn("Timer currQueueOffset:{} is smaller than minOffsetInQueue:{}", currQueueOffset, cq.getMinOffsetInQueue()); currQueueOffset = cq.getMinOffsetInQueue(); } long offset = currQueueOffset; ReferredIterator iterator = null; try { iterator = cq.iterateFrom(offset); if (null == iterator) { return false; } int i = 0; while (iterator.hasNext()) { i++; perfCounterTicks.startTick("enqueue_get"); try { CqUnit cqUnit = iterator.next(); long offsetPy = cqUnit.getPos(); int sizePy = cqUnit.getSize(); cqUnit.getTagsCode(); //tags code MessageExt msgExt = getMessageByCommitOffset(offsetPy, sizePy); if (null == msgExt) { perfCounterTicks.getCounter("enqueue_get_miss"); } else { lastEnqueueButExpiredTime = System.currentTimeMillis(); lastEnqueueButExpiredStoreTime = msgExt.getStoreTimestamp(); long delayedTime = Long.parseLong(msgExt.getProperty(TIMER_OUT_MS)); // use CQ offset, not offset in Message msgExt.setQueueOffset(offset + i); TimerRequest timerRequest = new TimerRequest(offsetPy, sizePy, delayedTime, System.currentTimeMillis(), MAGIC_DEFAULT, msgExt); // System.out.printf("build enqueue request, %s%n", timerRequest); while (!enqueuePutQueue.offer(timerRequest, 3, TimeUnit.SECONDS)) { if (!isRunningEnqueue()) { return false; } } // Record timer message set latency StoreMetricsManager metricsManager = messageStore.getStoreMetricsManager(); if (metricsManager instanceof DefaultStoreMetricsManager) { DefaultStoreMetricsManager defaultMetricsManager = (DefaultStoreMetricsManager) metricsManager; Attributes attributes = defaultMetricsManager.newAttributesBuilder() .put(DefaultStoreMetricsConstant.LABEL_TOPIC, msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC)).build(); defaultMetricsManager.getTimerMessageSetLatency().record((delayedTime - msgExt.getBornTimestamp()) / 1000, attributes); } } } catch (Exception e) { // here may cause the message loss if (storeConfig.isTimerSkipUnknownError()) { LOGGER.warn("Unknown error in skipped in enqueuing", e); } else { holdMomentForUnknownError(); throw e; } } finally { perfCounterTicks.endTick("enqueue_get"); } // if broker role changes, ignore last enqueue if (!isRunningEnqueue()) { return false; } currQueueOffset = offset + i; } currQueueOffset = offset + i; return i > 0; } catch (Exception e) { LOGGER.error("Unknown exception in enqueuing", e); } finally { if (iterator != null) { iterator.release(); } } return false; } public boolean doEnqueue(long offsetPy, int sizePy, long delayedTime, MessageExt messageExt, boolean isFromTimeline) { LOGGER.debug("Do enqueue [{}] [{}]", new Timestamp(delayedTime), messageExt); //copy the value first, avoid concurrent problem long tmpWriteTimeMs = currWriteTimeMs; boolean needRoll = delayedTime - tmpWriteTimeMs >= (long) timerRollWindowSlots * precisionMs; int magic = MAGIC_DEFAULT; if (needRoll) { magic = magic | MAGIC_ROLL; if (delayedTime - tmpWriteTimeMs - (long) timerRollWindowSlots * precisionMs < (long) timerRollWindowSlots / 3 * precisionMs) { //give enough time to next roll delayedTime = tmpWriteTimeMs + (long) (timerRollWindowSlots / 2) * precisionMs; } else { delayedTime = tmpWriteTimeMs + (long) timerRollWindowSlots * precisionMs; } } boolean isDelete = messageExt.getProperty(TIMER_DELETE_UNIQUE_KEY) != null; if (isDelete) { magic = magic | MAGIC_DELETE; if (!isFromTimeline) { recallToTimeline(delayedTime, offsetPy, sizePy, messageExt); } } String realTopic = messageExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC); Slot slot = timerWheel.getSlot(delayedTime); ByteBuffer tmpBuffer = timerLogBuffer; tmpBuffer.clear(); tmpBuffer.putInt(TimerLog.UNIT_SIZE); //size tmpBuffer.putLong(slot.lastPos); //prev pos tmpBuffer.putInt(magic); //magic tmpBuffer.putLong(tmpWriteTimeMs); //currWriteTime tmpBuffer.putInt((int) (delayedTime - tmpWriteTimeMs)); //delayTime tmpBuffer.putLong(offsetPy); //offset tmpBuffer.putInt(sizePy); //size tmpBuffer.putInt(hashTopicForMetrics(realTopic)); //hashcode of real topic tmpBuffer.putLong(0); //reserved value, just set to 0 now long ret = timerLog.append(tmpBuffer.array(), 0, TimerLog.UNIT_SIZE); if (-1 != ret) { // If it's a delete message, then slot's total num -1 // TODO: check if the delete msg is in the same slot with "the msg to be deleted". timerWheel.putSlot(delayedTime, slot.firstPos == -1 ? ret : slot.firstPos, ret, isDelete ? slot.num - 1 : slot.num + 1, slot.magic); addMetric(messageExt, isDelete ? -1 : 1); } return -1 != ret; } @SuppressWarnings("NonAtomicOperationOnVolatileField") public int warmDequeue() { if (!isRunningDequeue()) { return -1; } if (!storeConfig.isTimerWarmEnable()) { return -1; } if (preReadTimeMs <= currReadTimeMs) { preReadTimeMs = currReadTimeMs + precisionMs; } if (preReadTimeMs >= currWriteTimeMs) { return -1; } if (preReadTimeMs >= currReadTimeMs + 3L * precisionMs) { return -1; } Slot slot = timerWheel.getSlot(preReadTimeMs); if (-1 == slot.timeMs) { preReadTimeMs = preReadTimeMs + precisionMs; return 0; } long currOffsetPy = slot.lastPos; LinkedList sbrs = new LinkedList<>(); SelectMappedBufferResult timeSbr = null; SelectMappedBufferResult msgSbr = null; try { //read the msg one by one while (currOffsetPy != -1) { if (!isRunning()) { break; } perfCounterTicks.startTick("warm_dequeue"); if (null == timeSbr || timeSbr.getStartOffset() > currOffsetPy) { timeSbr = timerLog.getWholeBuffer(currOffsetPy); if (null != timeSbr) { sbrs.add(timeSbr); } } if (null == timeSbr) { break; } long prevPos = -1; try { int position = (int) (currOffsetPy % timerLogFileSize); timeSbr.getByteBuffer().position(position); timeSbr.getByteBuffer().getInt(); //size prevPos = timeSbr.getByteBuffer().getLong(); timeSbr.getByteBuffer().position(position + TimerLog.UNIT_PRE_SIZE_FOR_MSG); long offsetPy = timeSbr.getByteBuffer().getLong(); int sizePy = timeSbr.getByteBuffer().getInt(); if (null == msgSbr || msgSbr.getStartOffset() > offsetPy) { msgSbr = messageStore.getCommitLogData(offsetPy - offsetPy % commitLogFileSize); if (null != msgSbr) { sbrs.add(msgSbr); } } if (null != msgSbr) { ByteBuffer bf = msgSbr.getByteBuffer(); int firstPos = (int) (offsetPy % commitLogFileSize); for (int pos = firstPos; pos < firstPos + sizePy; pos += 4096) { bf.position(pos); bf.get(); } } } catch (Exception e) { LOGGER.error("Unexpected error in warm", e); } finally { currOffsetPy = prevPos; perfCounterTicks.endTick("warm_dequeue"); } } for (SelectMappedBufferResult sbr : sbrs) { if (null != sbr) { sbr.release(); } } } finally { preReadTimeMs = preReadTimeMs + precisionMs; } return 1; } public boolean checkStateForPutMessages(int state) { for (AbstractStateService service : dequeuePutMessageServices) { if (!service.isState(state)) { return false; } } return true; } public boolean checkStateForGetMessages(int state) { for (AbstractStateService service : dequeueGetMessageServices) { if (!service.isState(state)) { return false; } } return true; } public void checkDequeueLatch(CountDownLatch latch, long delayedTime) throws Exception { if (latch.await(1, TimeUnit.SECONDS)) { return; } int checkNum = 0; while (true) { if (!isRunningDequeue()) { LOGGER.info("Not Running dequeue, skip checkDequeueLatch for delayedTime:{}", delayedTime); break; } if (dequeuePutQueue.size() > 0 || !checkStateForGetMessages(AbstractStateService.WAITING) || !checkStateForPutMessages(AbstractStateService.WAITING)) { //let it go } else { checkNum++; if (checkNum >= 2) { break; } } if (latch.await(1, TimeUnit.SECONDS)) { break; } } if (!latch.await(1, TimeUnit.SECONDS)) { LOGGER.warn("Check latch failed delayedTime:{}", delayedTime); } } public int dequeue() throws Exception { if (storeConfig.isTimerStopDequeue()) { return -1; } if (!isRunningDequeue()) { return -1; } if (currReadTimeMs >= currWriteTimeMs) { return -1; } Slot slot = timerWheel.getSlot(currReadTimeMs); if (-1 == slot.timeMs) { moveReadTime(); return 0; } try { //clear the flag dequeueStatusChangeFlag = false; long currOffsetPy = slot.lastPos; Set deleteUniqKeys = new ConcurrentSkipListSet<>(); LinkedList normalMsgStack = new LinkedList<>(); LinkedList deleteMsgStack = new LinkedList<>(); LinkedList sbrs = new LinkedList<>(); SelectMappedBufferResult timeSbr = null; //read the timer log one by one while (currOffsetPy != -1) { perfCounterTicks.startTick("dequeue_read_timerlog"); if (null == timeSbr || timeSbr.getStartOffset() > currOffsetPy) { timeSbr = timerLog.getWholeBuffer(currOffsetPy); if (null != timeSbr) { sbrs.add(timeSbr); } } if (null == timeSbr) { break; } long prevPos = -1; try { int position = (int) (currOffsetPy % timerLogFileSize); timeSbr.getByteBuffer().position(position); timeSbr.getByteBuffer().getInt(); //size prevPos = timeSbr.getByteBuffer().getLong(); int magic = timeSbr.getByteBuffer().getInt(); long enqueueTime = timeSbr.getByteBuffer().getLong(); long delayedTime = timeSbr.getByteBuffer().getInt() + enqueueTime; long offsetPy = timeSbr.getByteBuffer().getLong(); int sizePy = timeSbr.getByteBuffer().getInt(); TimerRequest timerRequest = new TimerRequest(offsetPy, sizePy, delayedTime, enqueueTime, magic); timerRequest.setDeleteList(deleteUniqKeys); if (needDelete(magic) && !needRoll(magic)) { deleteMsgStack.add(timerRequest); } else { normalMsgStack.addFirst(timerRequest); } } catch (Exception e) { LOGGER.error("Error in dequeue_read_timerlog", e); } finally { currOffsetPy = prevPos; perfCounterTicks.endTick("dequeue_read_timerlog"); } } if (deleteMsgStack.size() == 0 && normalMsgStack.size() == 0) { LOGGER.warn("dequeue time:{} but read nothing from timerLog", currReadTimeMs); } for (SelectMappedBufferResult sbr : sbrs) { if (null != sbr) { sbr.release(); } } if (!isRunningDequeue()) { return -1; } CountDownLatch deleteLatch = new CountDownLatch(deleteMsgStack.size()); //read the delete msg: the msg used to mark another msg is deleted for (List deleteList : splitIntoLists(deleteMsgStack)) { for (TimerRequest tr : deleteList) { tr.setLatch(deleteLatch); } dequeueGetQueue.put(deleteList); } //do we need to use loop with tryAcquire checkDequeueLatch(deleteLatch, currReadTimeMs); CountDownLatch normalLatch = new CountDownLatch(normalMsgStack.size()); //read the normal msg for (List normalList : splitIntoLists(normalMsgStack)) { for (TimerRequest tr : normalList) { tr.setLatch(normalLatch); } dequeueGetQueue.put(normalList); } checkDequeueLatch(normalLatch, currReadTimeMs); // if master -> slave -> master, then the read time move forward, and messages will be lossed if (dequeueStatusChangeFlag) { return -1; } if (!isRunningDequeue()) { return -1; } moveReadTime(); } catch (Throwable t) { LOGGER.error("Unknown error in dequeue process", t); if (storeConfig.isTimerSkipUnknownError()) { moveReadTime(); } } return 1; } private List> splitIntoLists(List origin) { //this method assume that the origin is not null; List> lists = new LinkedList<>(); if (origin.size() < 100) { lists.add(origin); return lists; } List currList = null; int fileIndexPy = -1; int msgIndex = 0; for (TimerRequest tr : origin) { if (fileIndexPy != tr.getOffsetPy() / commitLogFileSize) { msgIndex = 0; if (null != currList && currList.size() > 0) { lists.add(currList); } currList = new LinkedList<>(); currList.add(tr); fileIndexPy = (int) (tr.getOffsetPy() / commitLogFileSize); } else { currList.add(tr); if (++msgIndex % 2000 == 0) { lists.add(currList); currList = new ArrayList<>(); } } } if (null != currList && currList.size() > 0) { lists.add(currList); } return lists; } private MessageExt getMessageByCommitOffset(long offsetPy, int sizePy) { for (int i = 0; i < 3; i++) { MessageExt msgExt = StoreUtil.getMessage(offsetPy, sizePy, messageStore, bufferLocal.get()); if (null == msgExt) { LOGGER.warn("Fail to read msg from commitLog offsetPy:{} sizePy:{}", offsetPy, sizePy); } else { return msgExt; } } return null; } public MessageExtBrokerInner convert(MessageExt messageExt, long enqueueTime, boolean needRoll) { if (enqueueTime != -1) { MessageAccessor.putProperty(messageExt, TIMER_ENQUEUE_MS, enqueueTime + ""); } if (needRoll) { if (messageExt.getProperty(TIMER_ROLL_TIMES) != null) { MessageAccessor.putProperty(messageExt, TIMER_ROLL_TIMES, Integer.parseInt(messageExt.getProperty(TIMER_ROLL_TIMES)) + 1 + ""); } else { MessageAccessor.putProperty(messageExt, TIMER_ROLL_TIMES, 1 + ""); } } MessageAccessor.putProperty(messageExt, TIMER_DEQUEUE_MS, System.currentTimeMillis() + ""); MessageExtBrokerInner message = convertMessage(messageExt, needRoll); return message; } //0 succ; 1 fail, need retry; 2 fail, do not retry; public int doPut(MessageExtBrokerInner message, boolean roll) throws Exception { if (!roll && null != message.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)) { LOGGER.warn("Trying do put delete timer msg:[{}] roll:[{}]", message, roll); return PUT_NO_RETRY; } PutMessageResult putMessageResult = null; if (escapeBridgeHook != null) { putMessageResult = escapeBridgeHook.apply(message); } else { putMessageResult = messageStore.putMessage(message); } if (putMessageResult != null && putMessageResult.getPutMessageStatus() != null) { switch (putMessageResult.getPutMessageStatus()) { case PUT_OK: if (brokerStatsManager != null) { brokerStatsManager.incTopicPutNums(message.getTopic(), 1, 1); if (putMessageResult.getAppendMessageResult() != null) { brokerStatsManager.incTopicPutSize(message.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); } brokerStatsManager.incBrokerPutNums(message.getTopic(), 1); } return PUT_OK; case MESSAGE_ILLEGAL: case PROPERTIES_SIZE_EXCEEDED: case WHEEL_TIMER_NOT_ENABLE: case WHEEL_TIMER_MSG_ILLEGAL: return PUT_NO_RETRY; case SERVICE_NOT_AVAILABLE: case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case OS_PAGE_CACHE_BUSY: case CREATE_MAPPED_FILE_FAILED: case SLAVE_NOT_AVAILABLE: return PUT_NEED_RETRY; case UNKNOWN_ERROR: default: if (storeConfig.isTimerSkipUnknownError()) { LOGGER.warn("Skipping message due to unknown error, msg: {}", message); return PUT_NO_RETRY; } else { holdMomentForUnknownError(); return PUT_NEED_RETRY; } } } return PUT_NEED_RETRY; } public MessageExtBrokerInner convertMessage(MessageExt msgExt, boolean needRoll) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setBody(msgExt.getBody()); msgInner.setFlag(msgExt.getFlag()); MessageAccessor.setProperties(msgInner, MessageAccessor.deepCopyProperties(msgExt.getProperties())); TopicFilterType topicFilterType = MessageExt.parseTopicFilterType(msgInner.getSysFlag()); long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags()); msgInner.setTagsCode(tagsCodeValue); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); msgInner.setSysFlag(msgExt.getSysFlag()); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setStoreHost(msgExt.getStoreHost()); msgInner.setReconsumeTimes(msgExt.getReconsumeTimes()); msgInner.setWaitStoreMsgOK(false); if (needRoll) { msgInner.setTopic(msgExt.getTopic()); msgInner.setQueueId(msgExt.getQueueId()); } else { msgInner.setTopic(msgInner.getProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgInner.setQueueId(Integer.parseInt(msgInner.getProperty(MessageConst.PROPERTY_REAL_QUEUE_ID))); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID); } return msgInner; } protected String getRealTopic(MessageExt msgExt) { if (msgExt == null) { return null; } return msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC); } private long formatTimeMs(long timeMs) { return timeMs / precisionMs * precisionMs; } public int hashTopicForMetrics(String topic) { return null == topic ? 0 : topic.hashCode(); } public void checkAndReviseMetrics() { Map smallOnes = new HashMap<>(); Map bigOnes = new HashMap<>(); Map smallHashs = new HashMap<>(); Set smallHashCollisions = new HashSet<>(); for (Map.Entry entry : timerMetrics.getTimingCount().entrySet()) { if (entry.getValue().getCount().get() < storeConfig.getTimerMetricSmallThreshold()) { smallOnes.put(entry.getKey(), entry.getValue()); int hash = hashTopicForMetrics(entry.getKey()); if (smallHashs.containsKey(hash)) { LOGGER.warn("[CheckAndReviseMetrics]Metric hash collision between small-small code:{} small topic:{}{} small topic:{}{}", hash, entry.getKey(), entry.getValue(), smallHashs.get(hash), smallOnes.get(smallHashs.get(hash))); smallHashCollisions.add(hash); } smallHashs.put(hash, entry.getKey()); } else { bigOnes.put(entry.getKey(), entry.getValue()); } } //check the hash collision between small ons and big ons for (Map.Entry bjgEntry : bigOnes.entrySet()) { if (smallHashs.containsKey(hashTopicForMetrics(bjgEntry.getKey()))) { Iterator> smallIt = smallOnes.entrySet().iterator(); while (smallIt.hasNext()) { Map.Entry smallEntry = smallIt.next(); if (hashTopicForMetrics(smallEntry.getKey()) == hashTopicForMetrics(bjgEntry.getKey())) { LOGGER.warn("[CheckAndReviseMetrics]Metric hash collision between small-big code:{} small topic:{}{} big topic:{}{}", hashTopicForMetrics(smallEntry.getKey()), smallEntry.getKey(), smallEntry.getValue(), bjgEntry.getKey(), bjgEntry.getValue()); smallIt.remove(); } } } } //refresh smallHashs.clear(); Map newSmallOnes = new HashMap<>(); for (String topic : smallOnes.keySet()) { newSmallOnes.put(topic, new TimerMetrics.Metric()); smallHashs.put(hashTopicForMetrics(topic), topic); } //travel the timer log long readTimeMs = currReadTimeMs; long currOffsetPy = timerWheel.checkPhyPos(readTimeMs, 0); LinkedList sbrs = new LinkedList<>(); boolean hasError = false; try { while (true) { SelectMappedBufferResult timeSbr = timerLog.getWholeBuffer(currOffsetPy); if (timeSbr == null) { break; } else { sbrs.add(timeSbr); } ByteBuffer bf = timeSbr.getByteBuffer(); for (int position = 0; position < timeSbr.getSize(); position += TimerLog.UNIT_SIZE) { bf.position(position); bf.getInt();//size bf.getLong();//prev pos int magic = bf.getInt(); //magic if (magic == TimerLog.BLANK_MAGIC_CODE) { break; } long enqueueTime = bf.getLong(); long delayedTime = bf.getInt() + enqueueTime; long offsetPy = bf.getLong(); int sizePy = bf.getInt(); int hashCode = bf.getInt(); if (delayedTime < readTimeMs) { continue; } if (!smallHashs.containsKey(hashCode)) { continue; } String topic = null; if (smallHashCollisions.contains(hashCode)) { MessageExt messageExt = getMessageByCommitOffset(offsetPy, sizePy); if (null != messageExt) { topic = messageExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC); } } else { topic = smallHashs.get(hashCode); } if (null != topic && newSmallOnes.containsKey(topic)) { newSmallOnes.get(topic).getCount().addAndGet(needDelete(magic) ? -1 : 1); } else { LOGGER.warn("[CheckAndReviseMetrics]Unexpected topic in checking timer metrics topic:{} code:{} offsetPy:{} size:{}", topic, hashCode, offsetPy, sizePy); } } if (timeSbr.getSize() < timerLogFileSize) { break; } else { currOffsetPy = currOffsetPy + timerLogFileSize; } } } catch (Exception e) { hasError = true; LOGGER.error("[CheckAndReviseMetrics]Unknown error in checkAndReviseMetrics and abort", e); } finally { for (SelectMappedBufferResult sbr : sbrs) { if (null != sbr) { sbr.release(); } } } if (!hasError) { //update for (String topic : newSmallOnes.keySet()) { LOGGER.info("[CheckAndReviseMetrics]Revise metric for topic {} from {} to {}", topic, smallOnes.get(topic), newSmallOnes.get(topic)); } timerMetrics.getTimingCount().putAll(newSmallOnes); } } public class TimerEnqueueGetService extends ServiceThread { @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { if (storeConfig.isTimerRocksDBEnable() && !storeConfig.isTimerRocksDBStopScan()) { LOGGER.info("now timer use rocksdb to driver, so will not enqueue in timer wheel"); waitForRunning(10 * 1000L); } else if (!TimerMessageStore.this.enqueue(0)) { waitForRunning(100L * precisionMs / 1000); } } catch (Throwable e) { TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); } } public String getServiceThreadName() { String brokerIdentifier = ""; if (TimerMessageStore.this.messageStore instanceof DefaultMessageStore) { DefaultMessageStore messageStore = (DefaultMessageStore) TimerMessageStore.this.messageStore; if (messageStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = messageStore.getBrokerConfig().getIdentifier(); } } return brokerIdentifier; } public class TimerEnqueuePutService extends ServiceThread { @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } /** * collect the requests */ protected List fetchTimerRequests() throws InterruptedException { List trs = null; TimerRequest firstReq = enqueuePutQueue.poll(10, TimeUnit.MILLISECONDS); if (null != firstReq) { trs = new ArrayList<>(16); trs.add(firstReq); while (true) { TimerRequest tmpReq = enqueuePutQueue.poll(3, TimeUnit.MILLISECONDS); if (null == tmpReq) { break; } trs.add(tmpReq); if (trs.size() > 10) { break; } } } return trs; } protected void putMessageToTimerWheel(TimerRequest req) { try { perfCounterTicks.startTick(ENQUEUE_PUT); StoreMetricsManager metricsManager = messageStore.getStoreMetricsManager(); if (metricsManager instanceof DefaultStoreMetricsManager) { ((DefaultStoreMetricsManager) metricsManager).incTimerEnqueueCount(getRealTopic(req.getMsg())); } if (shouldRunningDequeue && req.getDelayTime() < currWriteTimeMs) { req.setEnqueueTime(Long.MAX_VALUE); dequeuePutQueue.put(req); } else { boolean doEnqueueRes = doEnqueue( req.getOffsetPy(), req.getSizePy(), req.getDelayTime(), req.getMsg(), false); req.idempotentRelease(doEnqueueRes || storeConfig.isTimerSkipUnknownError()); } perfCounterTicks.endTick(ENQUEUE_PUT); } catch (Throwable t) { LOGGER.error("Unknown error", t); if (storeConfig.isTimerSkipUnknownError()) { req.idempotentRelease(true); } else { holdMomentForUnknownError(); } } } protected void fetchAndPutTimerRequest() throws Exception { long tmpCommitQueueOffset = currQueueOffset; List trs = this.fetchTimerRequests(); if (CollectionUtils.isEmpty(trs)) { commitQueueOffset = tmpCommitQueueOffset; maybeMoveWriteTime(); return; } while (!isStopped()) { CountDownLatch latch = new CountDownLatch(trs.size()); for (TimerRequest req : trs) { req.setLatch(latch); if (storeConfig.isTimerWheelSnapshotFlush()) { synchronized (lockWhenFlush) { this.putMessageToTimerWheel(req); } } else { this.putMessageToTimerWheel(req); } } checkDequeueLatch(latch, -1); boolean allSuccess = trs.stream().allMatch(TimerRequest::isSucc); if (allSuccess) { break; } else { holdMomentForUnknownError(); } } commitQueueOffset = trs.get(trs.size() - 1).getMsg().getQueueOffset(); maybeMoveWriteTime(); } @Override public void run() { TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped() || enqueuePutQueue.size() != 0) { try { fetchAndPutTimerRequest(); } catch (Throwable e) { TimerMessageStore.LOGGER.error("Unknown error", e); } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); } } public class TimerDequeueGetService extends ServiceThread { @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { if (System.currentTimeMillis() < shouldStartTime) { TimerMessageStore.LOGGER.info("TimerDequeueGetService ready to run after {}.", shouldStartTime); waitForRunning(1000); continue; } if (-1 == TimerMessageStore.this.dequeue()) { waitForRunning(100L * precisionMs / 1000); } } catch (Throwable e) { TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); } } abstract class AbstractStateService extends ServiceThread { public static final int INITIAL = -1, START = 0, WAITING = 1, RUNNING = 2, END = 3; protected int state = INITIAL; protected void setState(int state) { this.state = state; } protected boolean isState(int state) { return this.state == state; } } public class TimerDequeuePutMessageService extends AbstractStateService { @Override public String getServiceName() { String brokerIdentifier = ""; if (TimerMessageStore.this.messageStore instanceof DefaultMessageStore) { try { DefaultMessageStore defaultStore = (DefaultMessageStore) TimerMessageStore.this.messageStore; if (defaultStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = defaultStore.getBrokerConfig().getIdentifier(); } } catch (Exception e) { LOGGER.warn("Failed to get broker identifier", e); } } return brokerIdentifier + this.getClass().getSimpleName(); } @Override public void run() { setState(AbstractStateService.START); TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped() || dequeuePutQueue.size() != 0) { try { setState(AbstractStateService.WAITING); TimerRequest tr = dequeuePutQueue.poll(10, TimeUnit.MILLISECONDS); if (null == tr) { continue; } setState(AbstractStateService.RUNNING); boolean tmpDequeueChangeFlag = false; try { while (!isStopped()) { if (!isRunningDequeue()) { dequeueStatusChangeFlag = true; tmpDequeueChangeFlag = true; break; } try { perfCounterTicks.startTick(DEQUEUE_PUT); MessageExt msgExt = tr.getMsg(); StoreMetricsManager metricsManager = messageStore.getStoreMetricsManager(); if (metricsManager instanceof DefaultStoreMetricsManager) { ((DefaultStoreMetricsManager) metricsManager).incTimerDequeueCount(getRealTopic(msgExt)); } if (tr.getEnqueueTime() == Long.MAX_VALUE) { // Never enqueue, mark it. MessageAccessor.putProperty(msgExt, TIMER_ENQUEUE_MS, String.valueOf(Long.MAX_VALUE)); } addMetric(msgExt, -1); MessageExtBrokerInner msg = convert(msgExt, tr.getEnqueueTime(), needRoll(tr.getMagic())); boolean processed = false; int retryCount = 0; while (!processed && !isStopped()) { int result = doPut(msg, needRoll(tr.getMagic())); if (result == PUT_OK) { processed = true; } else if (result == PUT_NO_RETRY) { TimerMessageStore.LOGGER.warn("Skipping message due to unrecoverable error. Msg: {}", msg); processed = true; } else { retryCount++; // Without enabling TimerEnableRetryUntilSuccess, messages will retry up to 3 times before being discarded if (!storeConfig.isTimerEnableRetryUntilSuccess() && retryCount >= 3) { TimerMessageStore.LOGGER.error("Message processing failed after {} retries. Msg: {}", retryCount, msg); processed = true; } else { Thread.sleep(500L * precisionMs / 1000); TimerMessageStore.LOGGER.warn("Retrying to process message. Retry count: {}, Msg: {}", retryCount, msg); } } } perfCounterTicks.endTick(DEQUEUE_PUT); break; } catch (Throwable t) { TimerMessageStore.LOGGER.info("Unknown error", t); if (storeConfig.isTimerSkipUnknownError()) { break; } else { holdMomentForUnknownError(); } } } } finally { tr.idempotentRelease(!tmpDequeueChangeFlag); } } catch (Throwable e) { TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); setState(AbstractStateService.END); } } public class TimerDequeueGetMessageService extends AbstractStateService { @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { setState(AbstractStateService.START); TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { setState(AbstractStateService.WAITING); List trs = dequeueGetQueue.poll(100L * precisionMs / 1000, TimeUnit.MILLISECONDS); if (null == trs || trs.size() == 0) { continue; } setState(AbstractStateService.RUNNING); for (int i = 0; i < trs.size(); ) { TimerRequest tr = trs.get(i); boolean doRes = false; try { long start = System.currentTimeMillis(); MessageExt msgExt = getMessageByCommitOffset(tr.getOffsetPy(), tr.getSizePy()); if (null != msgExt) { if (needDelete(tr.getMagic()) && !needRoll(tr.getMagic())) { if (msgExt.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY) != null && tr.getDeleteList() != null) { //Execute metric plus one for messages that fail to be deleted addMetric(msgExt, 1); tr.getDeleteList().add(msgExt.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)); } tr.idempotentRelease(); doRes = true; } else { String uniqueKey = MessageClientIDSetter.getUniqID(msgExt); if (null == uniqueKey) { LOGGER.warn("No uniqueKey for msg:{}", msgExt); } if (null != uniqueKey && tr.getDeleteList() != null && tr.getDeleteList().size() > 0 && tr.getDeleteList().contains(buildDeleteKey(getRealTopic(msgExt), uniqueKey, storeConfig.isAppendTopicForTimerDeleteKey()))) { //Normally, it cancels out with the +1 above addMetric(msgExt, -1); doRes = true; tr.idempotentRelease(); perfCounterTicks.getCounter("dequeue_delete").flow(1); } else { tr.setMsg(msgExt); while (!isStopped() && !doRes) { doRes = dequeuePutQueue.offer(tr, 3, TimeUnit.SECONDS); } } } perfCounterTicks.getCounter("dequeue_get_msg").flow(System.currentTimeMillis() - start); } else { //the tr will never be processed afterwards, so idempotentRelease it tr.idempotentRelease(); doRes = true; perfCounterTicks.getCounter("dequeue_get_msg_miss").flow(System.currentTimeMillis() - start); } } catch (Throwable e) { LOGGER.error("Unknown exception", e); if (storeConfig.isTimerSkipUnknownError()) { tr.idempotentRelease(); doRes = true; } else { holdMomentForUnknownError(); } } finally { if (doRes) { i++; } } } trs.clear(); } catch (Throwable e) { TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); setState(AbstractStateService.END); } } public class TimerDequeueWarmService extends ServiceThread { @Override public String getServiceName() { String brokerIdentifier = ""; if (TimerMessageStore.this.messageStore instanceof DefaultMessageStore && ((DefaultMessageStore) TimerMessageStore.this.messageStore).getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = ((DefaultMessageStore) TimerMessageStore.this.messageStore).getBrokerConfig().getIdentifier(); } return brokerIdentifier + this.getClass().getSimpleName(); } @Override public void run() { TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { //if (!storeConfig.isTimerWarmEnable() || -1 == TimerMessageStore.this.warmDequeue()) { waitForRunning(50); //} } catch (Throwable e) { TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); } } public boolean needRoll(int magic) { return (magic & MAGIC_ROLL) != 0; } public boolean needDelete(int magic) { return (magic & MAGIC_DELETE) != 0; } public class TimerFlushService extends ServiceThread { private final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss"); @Override public String getServiceName() { String brokerIdentifier = ""; if (TimerMessageStore.this.messageStore instanceof DefaultMessageStore && ((DefaultMessageStore) TimerMessageStore.this.messageStore).getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = ((DefaultMessageStore) TimerMessageStore.this.messageStore).getBrokerConfig().getIdentifier(); } return brokerIdentifier + this.getClass().getSimpleName(); } private String format(long time) { return sdf.format(new Date(time)); } @Override public void run() { TimerMessageStore.LOGGER.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { this.flush(); } catch (Throwable e) { TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); } try { waitForRunning(storeConfig.getTimerFlushIntervalMs()); } catch (Throwable e) { // ignore interrupt } } TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); } long start = System.currentTimeMillis(); long lastSnapshotTime = System.currentTimeMillis(); public void flush() throws IOException { if (storeConfig.isTimerWheelSnapshotFlush()) { synchronized (lockWhenFlush) { prepareTimerCheckPoint(); timerLog.getMappedFileQueue().flush(0); if (System.currentTimeMillis() - lastSnapshotTime > storeConfig.getTimerWheelSnapshotIntervalMs()) { lastSnapshotTime = System.currentTimeMillis(); timerWheel.backup(timerLog.getMappedFileQueue().getFlushedWhere()); } timerCheckpoint.flush(); } } else { prepareTimerCheckPoint(); timerLog.getMappedFileQueue().flush(0); timerWheel.flush(); timerCheckpoint.flush(); } if (System.currentTimeMillis() - start > storeConfig.getTimerProgressLogIntervalMs()) { start = System.currentTimeMillis(); long tmpQueueOffset = currQueueOffset; ConsumeQueueInterface cq = messageStore.getConsumeQueue(TIMER_TOPIC, 0); long maxOffsetInQueue = cq == null ? 0 : cq.getMaxOffsetInQueue(); TimerMessageStore.LOGGER.info("[{}]Timer progress-check commitRead:[{}] currRead:[{}] currWrite:[{}] readBehind:{} currReadOffset:{} offsetBehind:{} behindMaster:{} " + "enqPutQueue:{} deqGetQueue:{} deqPutQueue:{} allCongestNum:{} enqExpiredStoreTime:{}", storeConfig.getBrokerRole(), format(commitReadTimeMs), format(currReadTimeMs), format(currWriteTimeMs), getDequeueBehind(), tmpQueueOffset, maxOffsetInQueue - tmpQueueOffset, timerCheckpoint.getMasterTimerQueueOffset() - tmpQueueOffset, enqueuePutQueue.size(), dequeueGetQueue.size(), dequeuePutQueue.size(), getAllCongestNum(), format(lastEnqueueButExpiredStoreTime)); } timerMetrics.persist(); } } public long getAllCongestNum() { return timerWheel.getAllNum(currReadTimeMs); } public long getCongestNum(long deliverTimeMs) { return timerWheel.getNum(deliverTimeMs); } public boolean isReject(long deliverTimeMs) { long congestNum = timerWheel.getNum(deliverTimeMs); if (congestNum <= storeConfig.getTimerCongestNumEachSlot()) { return false; } if (congestNum >= storeConfig.getTimerCongestNumEachSlot() * 2L) { return true; } if (RANDOM.nextInt(1000) > 1000 * (congestNum - storeConfig.getTimerCongestNumEachSlot()) / (storeConfig.getTimerCongestNumEachSlot() + 0.1)) { return true; } return false; } public long getEnqueueBehindMessages() { long tmpQueueOffset = currQueueOffset; ConsumeQueueInterface cq = messageStore.getConsumeQueue(TIMER_TOPIC, 0); long maxOffsetInQueue = cq == null ? 0 : cq.getMaxOffsetInQueue(); return maxOffsetInQueue - tmpQueueOffset; } public long getEnqueueBehindMillis() { if (System.currentTimeMillis() - lastEnqueueButExpiredTime < 2000) { return System.currentTimeMillis() - lastEnqueueButExpiredStoreTime; } return 0; } public long getEnqueueBehind() { return getEnqueueBehindMillis() / 1000; } public long getDequeueBehindMessages() { return timerWheel.getAllNum(currReadTimeMs); } public long getDequeueBehindMillis() { return System.currentTimeMillis() - currReadTimeMs; } public long getDequeueBehind() { return getDequeueBehindMillis() / 1000; } public float getEnqueueTps() { return perfCounterTicks.getCounter(ENQUEUE_PUT).getLastTps(); } public float getDequeueTps() { return perfCounterTicks.getCounter("dequeue_put").getLastTps(); } public void prepareTimerCheckPoint() { timerCheckpoint.setLastTimerLogFlushPos(timerLog.getMappedFileQueue().getFlushedWhere()); timerCheckpoint.setLastReadTimeMs(commitReadTimeMs); if (shouldRunningDequeue) { timerCheckpoint.setMasterTimerQueueOffset(commitQueueOffset); if (commitReadTimeMs != lastCommitReadTimeMs || commitQueueOffset != lastCommitQueueOffset) { timerCheckpoint.updateDataVersion(messageStore.getStateMachineVersion()); lastCommitReadTimeMs = commitReadTimeMs; lastCommitQueueOffset = commitQueueOffset; } } timerCheckpoint.setLastTimerQueueOffset(Math.min(commitQueueOffset, timerCheckpoint.getMasterTimerQueueOffset())); } public void registerEscapeBridgeHook(Function escapeBridgeHook) { this.escapeBridgeHook = escapeBridgeHook; } public boolean isMaster() { return BrokerRole.SLAVE != lastBrokerRole; } public long getCurrReadTimeMs() { return this.currReadTimeMs; } public long getQueueOffset() { return currQueueOffset; } public long getCommitQueueOffset() { return this.commitQueueOffset; } public long getCommitReadTimeMs() { return this.commitReadTimeMs; } public MessageStore getMessageStore() { return messageStore; } public TimerWheel getTimerWheel() { return timerWheel; } public TimerLog getTimerLog() { return timerLog; } public TimerMetrics getTimerMetrics() { return this.timerMetrics; } public int getPrecisionMs() { return precisionMs; } public TimerEnqueueGetService getEnqueueGetService() { return enqueueGetService; } public void setEnqueueGetService(TimerEnqueueGetService enqueueGetService) { this.enqueueGetService = enqueueGetService; } public TimerEnqueuePutService getEnqueuePutService() { return enqueuePutService; } public void setEnqueuePutService(TimerEnqueuePutService enqueuePutService) { this.enqueuePutService = enqueuePutService; } public TimerDequeueWarmService getDequeueWarmService() { return dequeueWarmService; } public void setDequeueWarmService( TimerDequeueWarmService dequeueWarmService) { this.dequeueWarmService = dequeueWarmService; } public TimerDequeueGetService getDequeueGetService() { return dequeueGetService; } public void setDequeueGetService(TimerDequeueGetService dequeueGetService) { this.dequeueGetService = dequeueGetService; } public TimerDequeuePutMessageService[] getDequeuePutMessageServices() { return dequeuePutMessageServices; } public void setDequeuePutMessageServices( TimerDequeuePutMessageService[] dequeuePutMessageServices) { this.dequeuePutMessageServices = dequeuePutMessageServices; } public TimerDequeueGetMessageService[] getDequeueGetMessageServices() { return dequeueGetMessageServices; } public void setDequeueGetMessageServices( TimerDequeueGetMessageService[] dequeueGetMessageServices) { this.dequeueGetMessageServices = dequeueGetMessageServices; } public void setTimerMetrics(TimerMetrics timerMetrics) { this.timerMetrics = timerMetrics; } public AtomicInteger getFrequency() { return frequency; } public void setFrequency(AtomicInteger frequency) { this.frequency = frequency; } public TimerCheckpoint getTimerCheckpoint() { return timerCheckpoint; } // identify a message by topic or topic + uk(like query operation) public static String buildDeleteKey(String realTopic, String uniqueKey, Boolean appendTopicForTimerDeleteKey) { return appendTopicForTimerDeleteKey ? (realTopic + "+" + uniqueKey) : uniqueKey; } private void recallToTimeline(long delayTime, long offsetPy, int sizePy, MessageExt messageExt) { if (!storeConfig.isTimerRecallToTimelineEnable() || !storeConfig.isTimerRocksDBEnable()) { return; } if (delayTime < 0L || offsetPy < 0L || sizePy <= 0 || null == messageExt) { LOGGER.error("recallToTimeline param error, delayTime: {}, offsetPy: {}, sizePy: {}, messageExt: {}", delayTime, offsetPy, sizePy, messageExt); return; } if (null == messageStore.getTimerMessageRocksDBStore() || null == messageStore.getTimerMessageRocksDBStore().getTimeline()) { LOGGER.error("recallToTimeline error, timerRocksDBStore is null or timeline is null"); return; } try { messageStore.getTimerMessageRocksDBStore().getTimeline().putDeleteRecord(delayTime, messageExt.getMsgId(), offsetPy, sizePy, messageExt.getQueueOffset(), messageExt); } catch (Exception e) { LOGGER.error("recallToTimeline error: {}", e.getMessage()); } } public boolean restart() { try { if (this.state != RUNNING) { LOGGER.info("TimerMessageStore restart operation just support for running state"); return false; } this.storeConfig.setTimerRocksDBStopScan(true); if (this.state == RUNNING && !this.storeConfig.isTimerStopEnqueue()) { LOGGER.info("restart TimerMessageStore has been running"); return true; } long commitOffsetRocksDB = this.messageStore.getTimerMessageRocksDBStore().getCommitOffsetInRocksDB(); long commitOffsetFile = this.messageStore.getTimerMessageStore().getCommitQueueOffset(); long maxCommitOffset = Math.max(commitOffsetFile, commitOffsetRocksDB); currQueueOffset = maxCommitOffset; this.storeConfig.setTimerStopEnqueue(false); LOGGER.info("TimerMessageStore restart commitOffsetRocksDB: {}, commitOffsetFile: {}, currQueueOffset: {}", commitOffsetRocksDB, commitOffsetFile, currQueueOffset); return true; } catch (Exception e) { LOGGER.error("TimerMessageStore restart error: {}", e.getMessage()); return false; } } public TimerFlushService getTimerFlushService() { return timerFlushService; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TimerMetrics extends ConfigManager { private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private static final long LOCK_TIMEOUT_MILLIS = 3000; private transient final Lock lock = new ReentrantLock(); private final ConcurrentMap timingCount = new ConcurrentHashMap<>(1024); private final ConcurrentMap timingDistribution = new ConcurrentHashMap<>(1024); @SuppressWarnings("DoubleBraceInitialization") public List timerDist = new ArrayList() {{ add(5); add(60); add(300); // 5s, 1min, 5min add(900); add(3600); add(14400); // 15min, 1h, 4h add(28800); add(86400); // 8h, 24h }}; private final DataVersion dataVersion = new DataVersion(); private final String configPath; public TimerMetrics(String configPath) { this.configPath = configPath; } public long updateDistPair(int period, int value) { Metric distPair = getDistPair(period); return distPair.getCount().addAndGet(value); } public long addAndGet(MessageExt msg, int value) { String topic = msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC); Metric pair = getTopicPair(topic); getDataVersion().nextVersion(); pair.setTimeStamp(System.currentTimeMillis()); return pair.getCount().addAndGet(value); } public Metric getDistPair(Integer period) { Metric pair = timingDistribution.get(period); if (null != pair) { return pair; } pair = new Metric(); final Metric previous = timingDistribution.putIfAbsent(period, pair); if (null != previous) { return previous; } return pair; } public Metric getTopicPair(String topic) { Metric pair = timingCount.get(topic); if (null != pair) { return pair; } pair = new Metric(); final Metric previous = timingCount.putIfAbsent(topic, pair); if (null != previous) { return previous; } return pair; } public List getTimerDistList() { return this.timerDist; } public void setTimerDistList(List timerDist) { this.timerDist = timerDist; } public long getTimingCount(String topic) { Metric pair = timingCount.get(topic); if (null == pair) { return 0; } else { return pair.getCount().get(); } } public Map getTimingCount() { return timingCount; } protected void write0(Writer writer) throws IOException { TimerMetricsSerializeWrapper wrapper = new TimerMetricsSerializeWrapper(); wrapper.setTimingCount(timingCount); wrapper.setDataVersion(dataVersion); writer.write(JSON.toJSONString(wrapper, JSONWriter.Feature.BrowserCompatible)); } @Override public String encode() { return encode(false); } @Override public String configFilePath() { return configPath; } @Override public void decode(String jsonString) { if (jsonString != null) { TimerMetricsSerializeWrapper timerMetricsSerializeWrapper = TimerMetricsSerializeWrapper.fromJson(jsonString, TimerMetricsSerializeWrapper.class); if (timerMetricsSerializeWrapper != null) { this.timingCount.putAll(timerMetricsSerializeWrapper.getTimingCount()); this.dataVersion.assignNewOne(timerMetricsSerializeWrapper.getDataVersion()); } } } @Override public String encode(boolean prettyFormat) { TimerMetricsSerializeWrapper metricsSerializeWrapper = new TimerMetricsSerializeWrapper(); metricsSerializeWrapper.setDataVersion(this.dataVersion); metricsSerializeWrapper.setTimingCount(this.timingCount); return metricsSerializeWrapper.toJson(prettyFormat); } public DataVersion getDataVersion() { return dataVersion; } public void cleanMetrics(Set topics) { if (topics == null || topics.isEmpty()) { return; } Iterator> iterator = timingCount.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); final String topic = entry.getKey(); if (topic.startsWith(TopicValidator.SYSTEM_TOPIC_PREFIX) || topic.startsWith(MixAll.LMQ_PREFIX)) { continue; } if (topics.contains(topic)) { continue; } iterator.remove(); log.info("clean timer metrics, because not in topic config, {}", topic); } } public boolean removeTimingCount(String topic) { try { timingCount.remove(topic); } catch (Exception e) { log.error("removeTimingCount error", e); return false; } return true; } public static class TimerMetricsSerializeWrapper extends RemotingSerializable { private ConcurrentMap timingCount = new ConcurrentHashMap<>(1024); private DataVersion dataVersion = new DataVersion(); public ConcurrentMap getTimingCount() { return timingCount; } public void setTimingCount(ConcurrentMap timingCount) { this.timingCount = timingCount; } public DataVersion getDataVersion() { return dataVersion; } public void setDataVersion(DataVersion dataVersion) { this.dataVersion = dataVersion; } } @Override public synchronized void persist() { try { // bak metrics file String config = configFilePath(); String backup = config + ".bak"; File configFile = new File(config); File bakFile = new File(backup); if (configFile.exists()) { // atomic move Files.move(configFile.toPath(), bakFile.toPath(), StandardCopyOption.ATOMIC_MOVE); // sync the directory, ensure that the bak file is visible MixAll.fsyncDirectory(Paths.get(bakFile.getParent())); } File dir = new File(configFile.getParent()); if (!dir.exists()) { Files.createDirectories(dir.toPath()); } // persist metrics file StringWriter stringWriter = new StringWriter(); write0(stringWriter); try (RandomAccessFile randomAccessFile = new RandomAccessFile(config, "rw")) { randomAccessFile.write(stringWriter.toString().getBytes(StandardCharsets.UTF_8)); randomAccessFile.getChannel().force(true); // sync the directory, ensure that the config file is visible MixAll.fsyncDirectory(Paths.get(configFile.getParent())); } } catch (Throwable t) { log.error("Failed to persist", t); } } public static class Metric { private AtomicLong count; private long timeStamp; public Metric() { count = new AtomicLong(0); timeStamp = System.currentTimeMillis(); } public AtomicLong getCount() { return count; } public void setCount(AtomicLong count) { this.count = count; } public long getTimeStamp() { return timeStamp; } public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } @Override public String toString() { return String.format("[%d,%d]", count.get(), timeStamp); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import org.apache.rocketmq.common.message.MessageExt; import java.util.Set; import java.util.concurrent.CountDownLatch; public class TimerRequest { private final long offsetPy; private final int sizePy; private final long delayTime; private final int magic; private long enqueueTime; private MessageExt msg; //optional would be a good choice, but it relies on JDK 8 private CountDownLatch latch; private boolean released; //whether the operation is successful private boolean succ; private Set deleteList; public TimerRequest(long offsetPy, int sizePy, long delayTime, long enqueueTime, int magic) { this(offsetPy, sizePy, delayTime, enqueueTime, magic, null); } public TimerRequest(long offsetPy, int sizePy, long delayTime, long enqueueTime, int magic, MessageExt msg) { this.offsetPy = offsetPy; this.sizePy = sizePy; this.delayTime = delayTime; this.enqueueTime = enqueueTime; this.magic = magic; this.msg = msg; } public long getOffsetPy() { return offsetPy; } public int getSizePy() { return sizePy; } public long getDelayTime() { return delayTime; } public long getEnqueueTime() { return enqueueTime; } public MessageExt getMsg() { return msg; } public void setMsg(MessageExt msg) { this.msg = msg; } public int getMagic() { return magic; } public Set getDeleteList() { return deleteList; } public void setDeleteList(Set deleteList) { this.deleteList = deleteList; } public void setLatch(CountDownLatch latch) { this.latch = latch; } public void setEnqueueTime(long enqueueTime) { this.enqueueTime = enqueueTime; } public void idempotentRelease() { idempotentRelease(true); } public void idempotentRelease(boolean succ) { this.succ = succ; if (!released && latch != null) { released = true; latch.countDown(); } } public boolean isSucc() { return succ; } @Override public String toString() { return "TimerRequest{" + "offsetPy=" + offsetPy + ", sizePy=" + sizePy + ", delayTime=" + delayTime + ", enqueueTime=" + enqueueTime + ", magic=" + magic + ", msg=" + msg + ", latch=" + latch + ", released=" + released + ", succ=" + succ + ", deleteList=" + deleteList + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/TimerWheel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class TimerWheel { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public static final String TIMER_WHEEL_FILE_NAME = "timerwheel"; public static final int BLANK = -1, IGNORE = -2; public final int slotsTotal; public final int precisionMs; private final String fileName; private final MappedByteBuffer mappedByteBuffer; private final RandomAccessFile randomAccessFile; private final FileChannel fileChannel; private final ByteBuffer byteBuffer; private final ThreadLocal localBuffer = new ThreadLocal() { @Override protected ByteBuffer initialValue() { return byteBuffer.duplicate(); } }; private final int wheelLength; private long snapOffset; public TimerWheel(String fileName, int slotsTotal, int precisionMs) throws IOException { this(fileName, slotsTotal, precisionMs, -1); } public TimerWheel(String fileName, int slotsTotal, int precisionMs, long snapOffset) throws IOException { this.slotsTotal = slotsTotal; this.precisionMs = precisionMs; this.fileName = fileName; this.wheelLength = this.slotsTotal * 2 * Slot.SIZE; this.snapOffset = snapOffset; String finalFileName = selectSnapshotByFlag(snapOffset); File file = new File(finalFileName); UtilAll.ensureDirOK(file.getParent()); try { randomAccessFile = new RandomAccessFile(finalFileName, "rw"); if (file.exists() && randomAccessFile.length() != 0 && randomAccessFile.length() != wheelLength) { throw new RuntimeException(String.format("Timer wheel length:%d != expected:%s", randomAccessFile.length(), wheelLength)); } randomAccessFile.setLength(wheelLength); if (snapOffset < 0) { fileChannel = randomAccessFile.getChannel(); mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, wheelLength); assert wheelLength == mappedByteBuffer.remaining(); } else { fileChannel = null; mappedByteBuffer = null; randomAccessFile.close(); } this.byteBuffer = ByteBuffer.allocateDirect(wheelLength); this.byteBuffer.put(Files.readAllBytes(file.toPath())); } catch (FileNotFoundException e) { log.error("create file channel " + finalFileName + " Failed. ", e); throw e; } catch (IOException e) { log.error("map file " + finalFileName + " Failed. ", e); throw e; } } public void shutdown() { shutdown(true); } public void shutdown(boolean flush) { if (flush) { try { this.flush(); } catch (Throwable e) { log.error("flush error when shutdown", e); } } // unmap mappedByteBuffer UtilAll.cleanBuffer(this.mappedByteBuffer); UtilAll.cleanBuffer(this.byteBuffer); localBuffer.remove(); try { this.fileChannel.close(); } catch (Throwable t) { log.error("Shutdown error in timer wheel", t); } } public void flush() { if (mappedByteBuffer == null) { return; } ByteBuffer bf = localBuffer.get(); bf.position(0); bf.limit(wheelLength); mappedByteBuffer.position(0); mappedByteBuffer.limit(wheelLength); for (int i = 0; i < wheelLength; i++) { if (bf.get(i) != mappedByteBuffer.get(i)) { mappedByteBuffer.put(i, bf.get(i)); } } this.mappedByteBuffer.force(); } /** * Perform backup operation. *

    * Select snapshot file based on the provided flag, write current buffer content to a temporary file, * then rename the temporary file to the formal snapshot file. If rename fails, delete the temporary file. * Finally clean up expired snapshot files. * * @param flushWhere Flag used to select snapshot file. * @throws IOException If I/O error occurs during backup process. */ public void backup(long flushWhere) throws IOException { // Get current local buffer and position it to the beginning ByteBuffer bf = localBuffer.get(); bf.position(0); bf.limit(wheelLength); // Select snapshot file name based on flag String fileName = selectSnapshotByFlag(flushWhere); File bakFile = new File(fileName); // Create or open temporary file for snapshot, ready for writing File tmpFile = new File(fileName + ".tmp"); // Delete if exists first Files.deleteIfExists(tmpFile.toPath()); try (RandomAccessFile randomAccessFile = new RandomAccessFile(tmpFile, "rw")) { try (FileChannel fileChannel = randomAccessFile.getChannel()) { fileChannel.write(bf); fileChannel.force(true); } } if (tmpFile.exists()) { // atomic move Files.move(tmpFile.toPath(), bakFile.toPath(), StandardCopyOption.ATOMIC_MOVE); // sync the directory, ensure that the bak file is visible MixAll.fsyncDirectory(Paths.get(bakFile.getParent())); } cleanExpiredSnapshot(); // Clean up expired snapshot files } /** * Select snapshot file name based on flag. * * @param flag Flag used to select or identify snapshot file. * @return Name of the snapshot file. */ private String selectSnapshotByFlag(long flag) { if (flag < 0) { return this.fileName; // If flag is less than 0, return default file name } return this.fileName + "." + flag; // Otherwise, return file name with flag suffix } /** * Clean up expired snapshot files. *

    * This method will find and delete all snapshot files with flags smaller than the specified value * under the current file name, keeping the two snapshot files with the largest flags. */ public void cleanExpiredSnapshot() { File dir = new File(this.fileName).getParentFile(); File[] files = dir.listFiles(); if (files == null) { return; } // Collect all snapshot files and their flags List snapshotFiles = new ArrayList<>(); for (File file : files) { String fileName = file.getName(); if (fileName.startsWith(TIMER_WHEEL_FILE_NAME + ".")) { long flag = UtilAll.asLong(fileName.substring(TIMER_WHEEL_FILE_NAME.length() + 1), -1); if (flag >= 0) { snapshotFiles.add(new FileWithFlag(file, flag)); } } } // Sort by flag in descending order snapshotFiles.sort((a, b) -> Long.compare(b.flag, a.flag)); // Delete all files except the first two for (int i = 2; i < snapshotFiles.size(); i++) { UtilAll.deleteFile(snapshotFiles.get(i).file); } } /** * Get the maximum flag from existing snapshot files. * * @return The maximum flag value, or -1 if no snapshot files exist */ public static long getMaxSnapshotFlag(String timerWheelPath) { File dir = new File(timerWheelPath).getParentFile(); File[] files = dir.listFiles(); if (files == null) { return -1; } long maxFlag = -1; for (File file : files) { String fileName = file.getName(); if (fileName.startsWith(TIMER_WHEEL_FILE_NAME + ".")) { long flag = UtilAll.asLong(fileName.substring(TIMER_WHEEL_FILE_NAME.length() + 1), -1); if (flag > maxFlag) { maxFlag = flag; } } } return maxFlag; } /** * Wrapper class for file and flag */ private static class FileWithFlag { final File file; final long flag; FileWithFlag(File file, long flag) { this.file = file; this.flag = flag; } } public Slot getSlot(long timeMs) { Slot slot = getRawSlot(timeMs); if (slot.timeMs != timeMs / precisionMs * precisionMs) { return new Slot(-1, -1, -1); } return slot; } //testable public Slot getRawSlot(long timeMs) { localBuffer.get().position(getSlotIndex(timeMs) * Slot.SIZE); return new Slot(localBuffer.get().getLong() * precisionMs, localBuffer.get().getLong(), localBuffer.get().getLong(), localBuffer.get().getInt(), localBuffer.get().getInt()); } public int getSlotIndex(long timeMs) { return (int) (timeMs / precisionMs % (slotsTotal * 2)); } public void putSlot(long timeMs, long firstPos, long lastPos) { localBuffer.get().position(getSlotIndex(timeMs) * Slot.SIZE); // To be compatible with previous version. // The previous version's precision is fixed at 1000ms and it store timeMs / 1000 in slot. localBuffer.get().putLong(timeMs / precisionMs); localBuffer.get().putLong(firstPos); localBuffer.get().putLong(lastPos); } public void putSlot(long timeMs, long firstPos, long lastPos, int num, int magic) { localBuffer.get().position(getSlotIndex(timeMs) * Slot.SIZE); localBuffer.get().putLong(timeMs / precisionMs); localBuffer.get().putLong(firstPos); localBuffer.get().putLong(lastPos); localBuffer.get().putInt(num); localBuffer.get().putInt(magic); } public void reviseSlot(long timeMs, long firstPos, long lastPos, boolean force) { localBuffer.get().position(getSlotIndex(timeMs) * Slot.SIZE); if (timeMs / precisionMs != localBuffer.get().getLong()) { if (force) { putSlot(timeMs, firstPos != IGNORE ? firstPos : lastPos, lastPos); } } else { if (IGNORE != firstPos) { localBuffer.get().putLong(firstPos); } else { localBuffer.get().getLong(); } if (IGNORE != lastPos) { localBuffer.get().putLong(lastPos); } } } //check the timerwheel to see if its stored offset > maxOffset in timerlog public long checkPhyPos(long timeStartMs, long maxOffset) { long minFirst = Long.MAX_VALUE; int firstSlotIndex = getSlotIndex(timeStartMs); for (int i = 0; i < slotsTotal * 2; i++) { int slotIndex = (firstSlotIndex + i) % (slotsTotal * 2); localBuffer.get().position(slotIndex * Slot.SIZE); if ((timeStartMs + i * precisionMs) / precisionMs != localBuffer.get().getLong()) { continue; } long first = localBuffer.get().getLong(); long last = localBuffer.get().getLong(); if (last > maxOffset) { if (first < minFirst) { minFirst = first; } } } return minFirst; } public long getNum(long timeMs) { return getSlot(timeMs).num; } public long getAllNum(long timeStartMs) { int allNum = 0; int firstSlotIndex = getSlotIndex(timeStartMs); for (int i = 0; i < slotsTotal * 2; i++) { int slotIndex = (firstSlotIndex + i) % (slotsTotal * 2); localBuffer.get().position(slotIndex * Slot.SIZE); if ((timeStartMs + i * precisionMs) / precisionMs == localBuffer.get().getLong()) { localBuffer.get().getLong(); //first pos localBuffer.get().getLong(); //last pos allNum = allNum + localBuffer.get().getInt(); } } return allNum; } public String getFileName() { return fileName; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/rocksdb/Timeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer.rocksdb; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.timer.TimerMessageStore; import org.apache.rocketmq.store.timer.TimerMetrics; import static org.apache.rocketmq.common.message.MessageConst.PROPERTY_TIMER_ROLL_LABEL; import static org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage.TIMER_COLUMN_FAMILY; import static org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord.TIMER_ROCKSDB_DELETE; import static org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord.TIMER_ROCKSDB_PUT; import static org.apache.rocketmq.store.timer.rocksdb.TimerRocksDBRecord.TIMER_ROCKSDB_UPDATE; public class Timeline { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final String DELETE_KEY_SPLIT = "+"; private static final int ORIGIN_CAPACITY = 100000; private static final int BATCH_SIZE = 1000, MAX_BATCH_SIZE_FROM_ROCKSDB = 8000; private static final int INITIAL = 0, RUNNING = 1, SHUTDOWN = 2; private volatile int state = INITIAL; private final AtomicLong commitOffset = new AtomicLong(0); private final MessageStore messageStore; private final MessageStoreConfig storeConfig; private final TimerMessageStore timerMessageStore; private final MessageRocksDBStorage messageRocksDBStorage; private final TimerMessageRocksDBStore timerMessageRocksDBStore; private final long precisionMs; private final TimerMetrics timerMetrics; private TimelineIndexBuildService timelineIndexBuildService; private TimelineForwardService timelineForwardService; private TimelineRollService timelineRollService; private TimelineDeleteService timelineDeleteService; private BlockingQueue originTimerMsgQueue; public Timeline(final MessageStore messageStore, final MessageRocksDBStorage messageRocksDBStorage, final TimerMessageRocksDBStore timerMessageRocksDBStore, final TimerMetrics timerMetrics) { this.messageStore = messageStore; this.storeConfig = messageStore.getMessageStoreConfig(); this.timerMessageStore = messageStore.getTimerMessageStore(); this.messageRocksDBStorage = messageRocksDBStorage; this.timerMessageRocksDBStore = timerMessageRocksDBStore; this.precisionMs = timerMessageRocksDBStore.precisionMs; this.timerMetrics = timerMetrics; initService(); } private void initService() { this.timelineIndexBuildService = new TimelineIndexBuildService(); this.timelineForwardService = new TimelineForwardService(); if (storeConfig.isTimerEnableDisruptor()) { this.originTimerMsgQueue = new DisruptorBlockingQueue<>(ORIGIN_CAPACITY); } else { this.originTimerMsgQueue = new LinkedBlockingDeque<>(ORIGIN_CAPACITY); } this.timelineRollService = new TimelineRollService(); this.timelineDeleteService = new TimelineDeleteService(); } public void start() { if (this.state == RUNNING) { return; } this.commitOffset.set(this.timerMessageRocksDBStore.getReadOffset().get()); this.timelineIndexBuildService.start(); this.timelineForwardService.start(); this.timelineRollService.start(); this.timelineDeleteService.start(); this.state = RUNNING; log.info("Timeline start success, start commitOffset: {}", this.commitOffset.get()); } public void shutDown() { if (this.state != RUNNING || this.state == SHUTDOWN) { return; } if (null != this.timelineIndexBuildService) { this.timelineIndexBuildService.shutdown(); } if (null != this.timelineForwardService) { this.timelineForwardService.shutdown(); } if (null != this.timelineRollService) { this.timelineRollService.shutdown(); } if (null != this.timelineDeleteService) { this.timelineDeleteService.shutdown(); } this.state = SHUTDOWN; log.info("Timeline shutdown success"); } public void putRecord(TimerRocksDBRecord timerRecord) throws InterruptedException { if (null == timerRecord) { return; } while (!originTimerMsgQueue.offer(timerRecord, 3, TimeUnit.SECONDS)) { if (null != timerRecord.getMessageExt()) { logError.error("Timeline originTimerMsgQueue put record failed, topic: {}, uniqKey: {}", timerRecord.getMessageExt().getTopic(), timerRecord.getUniqKey()); } else { logError.error("Timeline originTimerMsgQueue put record failed, uniqKey: {}", timerRecord.getUniqKey()); } } } public void putDeleteRecord(long delayTime, String uniqKey, long offsetPy, int sizePy, long queueOffset, MessageExt messageExt) throws InterruptedException { if (delayTime <= 0L || StringUtils.isEmpty(uniqKey) || offsetPy < 0L || sizePy < 0 || queueOffset < 0L || null == messageExt) { log.info("Timeline putDeleteRecord param error, delayTime: {}, uniqKey: {}, offsetPy: {}, sizePy: {}, queueOffset: {}, messageExt: {}", delayTime, uniqKey, offsetPy, sizePy, queueOffset, messageExt); return; } while (!originTimerMsgQueue.offer(new TimerRocksDBRecord(delayTime, uniqKey, offsetPy, sizePy, queueOffset, messageExt), 3, TimeUnit.SECONDS)) { log.error("Timeline putDeleteRecord originTimerMsgQueue put delete record failed, uniqKey: {}", uniqKey); } } public void addMetric(MessageExt msg, int value) { if (null == msg || null == msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC)) { return; } if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_ENQUEUE_MS) && NumberUtils.toLong(msg.getProperty(MessageConst.PROPERTY_TIMER_ENQUEUE_MS)) == Long.MAX_VALUE) { return; } timerMetrics.addAndGet(msg, value); } private String getServiceThreadName() { String brokerIdentifier = ""; if (Timeline.this.messageStore instanceof DefaultMessageStore) { DefaultMessageStore messageStore = (DefaultMessageStore) Timeline.this.messageStore; if (messageStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = messageStore.getBrokerConfig().getIdentifier(); } } return brokerIdentifier; } private void recallToTimeWheel(TimerRocksDBRecord tr) { if (!messageStore.getMessageStoreConfig().isTimerRecallToTimeWheelEnable()) { return; } if (null == tr || null == tr.getMessageExt()) { return; } try { timerMessageStore.doEnqueue(tr.getOffsetPy(), tr.getSizePy(), tr.getDelayTime(), tr.getMessageExt(), true); } catch (Exception e) { log.error("Timeline recallToTimeWheel error: {}", e.getMessage()); } } private boolean scanRecordsToQueue(long checkpoint, long checkRange, BlockingQueue> queue) { if (checkpoint <= 0L || checkRange <= 0L || null == queue) { logError.error("Timeline scanRecordsToQueue param error, checkpoint: {}, checkRange: {}, queue: {}", checkpoint, checkRange, queue); return false; } if (storeConfig.isTimerStopDequeue()) { logError.info("Timeline scanRecordsToQueue storeConfig isTimerStopDequeue is true, stop to scan records to queue"); return false; } long count = 0; byte[] lastKey = null; while (true) { try { List trs = messageRocksDBStorage.scanRecordsForTimer(TIMER_COLUMN_FAMILY, checkpoint, checkpoint + checkRange, MAX_BATCH_SIZE_FROM_ROCKSDB, lastKey); if (null == trs || CollectionUtils.isEmpty(trs)) { break; } count += trs.size(); boolean hasMoreData = trs.size() >= MAX_BATCH_SIZE_FROM_ROCKSDB; lastKey = hasMoreData ? trs.get(trs.size() - 1).getKeyBytes() : null; if (null == lastKey) { trs.get(trs.size() - 1).setCheckPoint(checkpoint + checkRange); } while (!queue.offer(trs, 3, TimeUnit.SECONDS)) { log.debug("Timeline scanRecordsToQueue offer to queue error, queue size: {}, records size: {}", queue.size(), trs.size()); } if (!hasMoreData) { break; } } catch (Exception e) { logError.error("Timeline scanRecordsToQueue error: {}", e.getMessage()); return false; } } log.info("Timeline scan records from rocksdb, checkpoint: {}, records size: {}", checkpoint, count); return true; } public class TimelineIndexBuildService extends ServiceThread { private final Logger log = Timeline.log; private List trs; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); trs = new ArrayList<>(BATCH_SIZE); while (!this.isStopped() || !originTimerMsgQueue.isEmpty()) { try { buildTimelineIndex(); } catch (Exception e) { logError.error("Timeline error occurred in: {}, error: {}", getServiceName(), e.getMessage()); } } log.info(this.getServiceName() + " service end"); } private void buildTimelineIndex() throws InterruptedException { pollTimerMessageRecords(); if (CollectionUtils.isEmpty(trs)) { return; } List cudlist = new ArrayList<>(); for (TimerRocksDBRecord tr : trs) { try { MessageExt messageExt = tr.getMessageExt(); if (null == messageExt) { logError.error("Timeline TimelineIndexBuildService buildTimelineIndex error, messageExt is null"); continue; } String timerDelUniqKey = messageExt.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY); if (!StringUtils.isEmpty(timerDelUniqKey)) { tr.setUniqKey(extractUniqKey(timerDelUniqKey)); tr.setActionFlag(TIMER_ROCKSDB_DELETE); cudlist.add(tr); addMetric(messageExt, -1); recallToTimeWheel(tr); } else if (TimerMessageRocksDBStore.isExpired(tr.getDelayTime())) { timerMessageRocksDBStore.putRealTopicMessage(tr.getMessageExt()); } else if (!StringUtils.isEmpty(messageExt.getProperty(PROPERTY_TIMER_ROLL_LABEL))) { tr.setActionFlag(TIMER_ROCKSDB_UPDATE); cudlist.add(tr); } else { tr.setActionFlag(TIMER_ROCKSDB_PUT); cudlist.add(tr); addMetric(messageExt, 1); } } catch (Exception e) { logError.error("Timeline TimelineIndexBuildService buildTimelineIndex deal trs error", e); } } if (!CollectionUtils.isEmpty(cudlist)) { messageRocksDBStorage.writeRecordsForTimer(TIMER_COLUMN_FAMILY, cudlist); } synCommitOffset(trs); trs.clear(); } private String extractUniqKey(String deleteKey) throws IllegalArgumentException { if (StringUtils.isEmpty(deleteKey)) { throw new IllegalArgumentException("deleteKey is empty"); } int separatorIndex = deleteKey.indexOf(DELETE_KEY_SPLIT); if (separatorIndex == -1) { return deleteKey; } return deleteKey.substring(separatorIndex + 1); } private void pollTimerMessageRecords() throws InterruptedException { TimerRocksDBRecord firstReq = originTimerMsgQueue.poll(100, TimeUnit.MILLISECONDS); if (null != firstReq) { trs.add(firstReq); while (true) { TimerRocksDBRecord tmpReq = originTimerMsgQueue.poll(100, TimeUnit.MILLISECONDS); if (null == tmpReq) { break; } trs.add(tmpReq); if (trs.size() >= BATCH_SIZE) { break; } } } } private void synCommitOffset(List trs) { if (CollectionUtils.isEmpty(trs)) { return; } long lastQueueOffset = messageRocksDBStorage.getCheckpointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.SYS_TOPIC_SCAN_OFFSET_CHECK_POINT); long queueOffset = trs.get(trs.size() - 1).getQueueOffset() + 1L; if (queueOffset > lastQueueOffset) { commitOffset.set(queueOffset); messageRocksDBStorage.writeCheckPointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.SYS_TOPIC_SCAN_OFFSET_CHECK_POINT, commitOffset.get()); } } } private class TimelineForwardService extends ServiceThread { private final Logger log = Timeline.log; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { long checkpoint = messageRocksDBStorage.getCheckpointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.TIMELINE_CHECK_POINT); log.info(this.getServiceName() + " service start, checkpoint: {}", checkpoint); while (!this.isStopped()) { try { if (!timelineForward(checkpoint, precisionMs)) { waitForRunning(100L); } else { checkpoint += precisionMs; } } catch (Exception e) { logError.error("Timeline error occurred in " + getServiceName(), e); } } log.info(this.getServiceName() + " service end"); } private boolean timelineForward(long checkpoint, long checkRange) { if (checkpoint > System.currentTimeMillis()) { return false; } try { long begin = System.currentTimeMillis(); boolean result = scanRecordsToQueue(checkpoint, checkRange, timerMessageRocksDBStore.getExpiredMessageQueue()); log.info("Timeline TimelineForwardService timelineForward scanRecordsToQueue end, result: {}, checkpoint: {}, checkRange: {}, checkDelay: {}, cost: {}", result, checkpoint, checkRange, System.currentTimeMillis() - checkpoint, System.currentTimeMillis() - begin); return result; } catch (Exception e) { logError.error("Timeline TimelineForwardService timelineForward error: {}", e.getMessage()); return false; } } } private class TimelineRollService extends ServiceThread { private final Logger log = Timeline.log; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); while (!this.isStopped()) { int rollIntervalHour = 1; int rollRangeHour = 2; try { if (storeConfig.getTimerRocksDBRollIntervalHours() > 0) { rollIntervalHour = storeConfig.getTimerRocksDBRollIntervalHours(); } if (storeConfig.getTimerRocksDBRollRangeHours() > 0) { rollRangeHour = storeConfig.getTimerRocksDBRollRangeHours(); } this.waitForRunning(TimeUnit.HOURS.toMillis(rollIntervalHour)); if (stopped) { log.info(this.getServiceName() + " service end"); return; } } catch (Exception e) { logError.error("Timeline TimelineRollService wait error: {}", e.getMessage()); } long rollCheckpoint = System.currentTimeMillis(); try { log.info("Timeline TimelineRollService start roll rollCheckpoint: {}", rollCheckpoint); while (!scanRecordsToQueue(rollCheckpoint + TimeUnit.HOURS.toMillis(rollRangeHour), TimeUnit.SECONDS.toMillis(storeConfig.getTimerMaxDelaySec()), timerMessageRocksDBStore.getRollMessageQueue())) { logError.error("Timeline TimelineRollService scanRecordsToQueue error."); Thread.sleep(200); } log.info("Timeline TimelineRollService roll records success, lastRollTime: {}, rollCheckpoint: {}, cost: {}", rollCheckpoint, rollCheckpoint, System.currentTimeMillis() - rollCheckpoint); } catch (Exception e) { logError.error("Timeline TimelineRollService failed error: {}", e.getMessage()); } } log.info(this.getServiceName() + " service end"); } } private class TimelineDeleteService extends ServiceThread { private final Logger log = Timeline.log; private long lastDeleteCheckPoint = 0L; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); while (!this.isStopped()) { try { this.waitForRunning(TimeUnit.MINUTES.toMillis(30)); if (stopped) { log.info(this.getServiceName() + " service end"); return; } } catch (Exception e) { logError.error("Timeline TimelineDeleteService wait error: {}", e.getMessage()); } try { long checkpoint = messageRocksDBStorage.getCheckpointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.TIMELINE_CHECK_POINT); if (lastDeleteCheckPoint == checkpoint) { continue; } messageRocksDBStorage.deleteRecordsForTimer(TIMER_COLUMN_FAMILY, checkpoint - TimeUnit.HOURS.toMillis(168), checkpoint - TimeUnit.MINUTES.toMillis(30)); lastDeleteCheckPoint = checkpoint; } catch (Exception e) { logError.error("Timeline TimelineDeleteService delete failed, lastDeleteCheckPoint: {} error: {}", lastDeleteCheckPoint, e.getMessage()); } } log.info(this.getServiceName() + " service end"); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/rocksdb/TimerMessageRocksDBStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer.rocksdb; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; import com.google.common.util.concurrent.RateLimiter; import io.opentelemetry.api.common.Attributes; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.StoreUtil; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.metrics.DefaultStoreMetricsConstant; import org.apache.rocketmq.store.metrics.DefaultStoreMetricsManager; import org.apache.rocketmq.store.metrics.StoreMetricsManager; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.timer.TimerMetrics; import org.apache.rocketmq.store.util.PerfCounter; import static org.apache.rocketmq.common.message.MessageConst.PROPERTY_TIMER_ROLL_LABEL; import static org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage.TIMER_COLUMN_FAMILY; import static org.apache.rocketmq.store.timer.TimerMessageStore.TIMER_TOPIC; public class TimerMessageRocksDBStore { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final String SCAN_SYS_TOPIC = "scanSysTopic"; private static final String SCAN_SYS_TOPIC_MISS = "scanSysTopicMiss"; private static final String OUT_BIZ_MESSAGE = "outBizMsg"; private static final String ROLL_LABEL = "R"; private static final int PUT_OK = 0, PUT_NEED_RETRY = 1, PUT_NO_RETRY = 2; private static final int MAX_GET_MSG_TIMES = 3, MAX_PUT_MSG_TIMES = 3; private static final int TIME_UP_CAPACITY = 100, ROLL_CAPACITY = 50; private static final int INITIAL = 0, RUNNING = 1, SHUTDOWN = 2; private volatile int state = INITIAL; private static long expirationThresholdMillis = 999L; private final AtomicLong readOffset = new AtomicLong(0);//timerSysTopic read offset private final MessageStore messageStore; private final TimerMetrics timerMetrics; private final MessageStoreConfig storeConfig; private final BrokerStatsManager brokerStatsManager; private final MessageRocksDBStorage messageRocksDBStorage; private Timeline timeline; private TimerSysTopicScanService timerSysTopicScanService; private TimerMessageReputService expiredMessageReputService; private TimerMessageReputService rollMessageReputService; protected long precisionMs; private BlockingQueue> expiredMessageQueue; private BlockingQueue> rollMessageQueue; protected final PerfCounter.Ticks perfCounterTicks = new PerfCounter.Ticks(log); private Function escapeBridgeHook; private ThreadLocal bufferLocal = null; public TimerMessageRocksDBStore(final MessageStore messageStore, final TimerMetrics timerMetrics, final BrokerStatsManager brokerStatsManager) { this.messageStore = messageStore; this.storeConfig = messageStore.getMessageStoreConfig(); this.precisionMs = storeConfig.getTimerRocksDBPrecisionMs() < 100L ? 1000L : storeConfig.getTimerRocksDBPrecisionMs(); expirationThresholdMillis = precisionMs - 1L; this.messageRocksDBStorage = messageStore.getMessageRocksDBStorage(); this.timerMetrics = timerMetrics; this.brokerStatsManager = brokerStatsManager; bufferLocal = ThreadLocal.withInitial(() -> ByteBuffer.allocate(storeConfig.getMaxMessageSize())); } public synchronized boolean load() { initService(); boolean result = this.timerMetrics.load(); log.info("TimerMessageRocksDBStore load result: {}", result); return result; } public synchronized void start() { if (this.state == RUNNING) { return; } long commitOffsetFile = null != this.messageStore.getTimerMessageStore() ? this.messageStore.getTimerMessageStore().getCommitQueueOffset() : 0L; long commitOffsetRocksDB = messageRocksDBStorage.getCheckpointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.SYS_TOPIC_SCAN_OFFSET_CHECK_POINT); long maxCommitOffset = Math.max(commitOffsetFile, commitOffsetRocksDB); this.readOffset.set(maxCommitOffset); this.expiredMessageReputService.start(); this.rollMessageReputService.start(); this.timeline.start(); this.timerSysTopicScanService.start(); this.state = RUNNING; log.info("TimerMessageRocksDBStore start success, start commitOffsetFile: {}, commitOffsetRocksDB: {}, readOffset: {}", commitOffsetFile, commitOffsetRocksDB, this.readOffset.get()); } public synchronized boolean restart() { try { this.storeConfig.setTimerStopEnqueue(true); if (this.state == RUNNING && !this.storeConfig.isTimerRocksDBStopScan()) { log.info("restart TimerMessageRocksDBStore has been running"); return true; } if (this.state == RUNNING && this.storeConfig.isTimerRocksDBStopScan()) { long commitOffsetFile = null != this.messageStore.getTimerMessageStore() ? this.messageStore.getTimerMessageStore().getCommitQueueOffset() : 0L; long commitOffsetRocksDB = messageRocksDBStorage.getCheckpointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.SYS_TOPIC_SCAN_OFFSET_CHECK_POINT); long maxCommitOffset = Math.max(commitOffsetFile, commitOffsetRocksDB); this.readOffset.set(maxCommitOffset); log.info("restart TimerMessageRocksDBStore has been recover running, commitOffsetFile: {}, commitOffsetRocksDB: {}, readOffset: {}", commitOffsetFile, commitOffsetRocksDB, readOffset.get()); } else { this.load(); this.start(); } this.storeConfig.setTimerRocksDBEnable(true); this.storeConfig.setTimerRocksDBStopScan(false); return true; } catch (Exception e) { logError.error("TimerMessageRocksDBStore restart error: {}", e.getMessage()); return false; } } public void shutdown() { if (this.state != RUNNING || this.state == SHUTDOWN) { return; } if (null != this.timerSysTopicScanService) { this.timerSysTopicScanService.shutdown(); } if (null != this.timeline) { this.timeline.shutDown(); } if (null != this.expiredMessageReputService) { this.expiredMessageReputService.shutdown(); } if (null != this.rollMessageReputService) { this.rollMessageReputService.shutdown(); } this.state = SHUTDOWN; log.info("TimerMessageRocksDBStore shutdown success"); } public void putRealTopicMessage(MessageExt msg) { if (null == msg) { logError.error("putRealTopicMessage msg is null"); return; } MessageExtBrokerInner messageExtBrokerInner = convertMessage(msg); if (null == messageExtBrokerInner) { logError.error("putRealTopicMessage error, messageExtBrokerInner is null"); return; } doPut(messageExtBrokerInner); } public MessageStore getMessageStore() { return messageStore; } public TimerMetrics getTimerMetrics() { return timerMetrics; } public BrokerStatsManager getBrokerStatsManager() { return brokerStatsManager; } public AtomicLong getReadOffset() { return readOffset; } public BlockingQueue> getExpiredMessageQueue() { return expiredMessageQueue; } public BlockingQueue> getRollMessageQueue() { return rollMessageQueue; } public long getCommitOffsetInRocksDB() { if (null == messageRocksDBStorage || !storeConfig.isTimerRocksDBEnable()) { return 0L; } return messageRocksDBStorage.getCheckpointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.SYS_TOPIC_SCAN_OFFSET_CHECK_POINT); } public Timeline getTimeline() { return timeline; } private void initService() { if (storeConfig.isTimerEnableDisruptor()) { this.expiredMessageQueue = new DisruptorBlockingQueue<>(TIME_UP_CAPACITY); this.rollMessageQueue = new DisruptorBlockingQueue<>(ROLL_CAPACITY); } else { this.expiredMessageQueue = new LinkedBlockingDeque<>(TIME_UP_CAPACITY); this.rollMessageQueue = new LinkedBlockingDeque<>(ROLL_CAPACITY); } this.expiredMessageReputService = new TimerMessageReputService(expiredMessageQueue, storeConfig.getTimerRocksDBTimeExpiredMaxTps(), true); this.rollMessageReputService = new TimerMessageReputService(rollMessageQueue, storeConfig.getTimerRocksDBRollMaxTps(), false); this.timeline = new Timeline(messageStore, messageRocksDBStorage, this, timerMetrics); this.timerSysTopicScanService = new TimerSysTopicScanService(); } private MessageExtBrokerInner convertMessage(MessageExt msgExt) { if (null == msgExt) { logError.error("convertMessage msgExt is null"); return null; } MessageExtBrokerInner msgInner = null; try { msgInner = new MessageExtBrokerInner(); msgInner.setBody(msgExt.getBody()); msgInner.setFlag(msgExt.getFlag()); msgInner.setTags(msgExt.getTags()); msgInner.setSysFlag(msgExt.getSysFlag()); TopicFilterType topicFilterType = MessageExt.parseTopicFilterType(msgInner.getSysFlag()); long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags()); msgInner.setTagsCode(tagsCodeValue); msgInner.setBornTimestamp(msgExt.getBornTimestamp()); msgInner.setBornHost(msgExt.getBornHost()); msgInner.setStoreHost(msgExt.getStoreHost()); msgInner.setReconsumeTimes(msgExt.getReconsumeTimes()); msgInner.setWaitStoreMsgOK(false); MessageAccessor.setProperties(msgInner, MessageAccessor.deepCopyProperties(msgExt.getProperties())); if (isNeedRoll(msgInner)) { msgInner.setTopic(msgExt.getTopic()); msgInner.setQueueId(msgExt.getQueueId()); msgInner.getProperties().put(PROPERTY_TIMER_ROLL_LABEL, ROLL_LABEL); } else { msgInner.setTopic(msgInner.getProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgInner.setQueueId(Integer.parseInt(msgInner.getProperty(MessageConst.PROPERTY_REAL_QUEUE_ID))); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID); MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TIMER_ROLL_LABEL); } msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); } catch (Exception e) { logError.error("convertMessage error: {}", e.getMessage()); return null; } return msgInner; } private MessageExt getMessageByCommitOffset(long offsetPy, int sizePy) { if (offsetPy < 0L || sizePy <= 0 || sizePy > storeConfig.getMaxMessageSize()) { logError.error("getMessageByCommitOffset param error, offsetPy: {}, sizePy: {}, maxMsgSize: {}", offsetPy, sizePy, storeConfig.getMaxMessageSize()); return null; } if (sizePy > bufferLocal.get().limit()) { bufferLocal.remove(); bufferLocal.set(ByteBuffer.allocate(sizePy)); } for (int i = 0; i < MAX_GET_MSG_TIMES; i++) { MessageExt msgExt = StoreUtil.getMessage(offsetPy, sizePy, messageStore, bufferLocal.get()); if (null == msgExt) { log.warn("Fail to read msg from commitLog offsetPy: {} sizePy: {}", offsetPy, sizePy); } else { return msgExt; } } return null; } private boolean isNeedRoll(MessageExt messageExt) { try { String property = messageExt.getProperty(MessageConst.PROPERTY_TIMER_OUT_MS); if (StringUtils.isEmpty(property)) { return false; } if (!isExpired(Long.parseLong(property))) { return true; } } catch (Exception e) { logError.error("isNeedRoll error : {}", e.getMessage()); } return false; } private Long getDelayTime(MessageExt msgExt) { if (null == msgExt) { logError.error("getDelayTime msgExt is null"); return null; } String delayTimeStr = msgExt.getProperty(MessageConst.PROPERTY_TIMER_OUT_MS); if (StringUtils.isEmpty(delayTimeStr)) { logError.error("getDelayTime is empty, queueOffset: {}, delayTimeStr: {}", msgExt.getQueueOffset(), delayTimeStr); return null; } try { return Long.parseLong(delayTimeStr); } catch (Exception e) { logError.error("getDelayTime parse to long error : {}", e.getMessage()); } return null; } private int doPut(MessageExtBrokerInner message) { if (null == message) { logError.error("doPut message is null"); return PUT_NO_RETRY; } if (null != message.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)) { logError.warn("Trying do put delete timer msg:[{}]", message); return PUT_NO_RETRY; } PutMessageResult putMessageResult = null; if (escapeBridgeHook != null) { putMessageResult = escapeBridgeHook.apply(message); } else { putMessageResult = messageStore.putMessage(message); } if (null != putMessageResult && null != putMessageResult.getPutMessageStatus()) { switch (putMessageResult.getPutMessageStatus()) { case PUT_OK: if (null != brokerStatsManager) { brokerStatsManager.incTopicPutNums(message.getTopic(), 1, 1); if (null != putMessageResult.getAppendMessageResult()) { brokerStatsManager.incTopicPutSize(message.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); } brokerStatsManager.incBrokerPutNums(message.getTopic(), 1); } return PUT_OK; case MESSAGE_ILLEGAL: case PROPERTIES_SIZE_EXCEEDED: case WHEEL_TIMER_NOT_ENABLE: case WHEEL_TIMER_MSG_ILLEGAL: return PUT_NO_RETRY; case SERVICE_NOT_AVAILABLE: case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case OS_PAGE_CACHE_BUSY: case CREATE_MAPPED_FILE_FAILED: case SLAVE_NOT_AVAILABLE: return PUT_NEED_RETRY; case UNKNOWN_ERROR: default: if (storeConfig.isTimerSkipUnknownError()) { logError.warn("Skipping message due to unknown error, msg: {}", message); return PUT_NO_RETRY; } else { return PUT_NEED_RETRY; } } } return PUT_NEED_RETRY; } public static boolean isExpired(long delayedTime) { return delayedTime <= System.currentTimeMillis() + expirationThresholdMillis; } public void registerEscapeBridgeHook(Function escapeBridgeHook) { this.escapeBridgeHook = escapeBridgeHook; } private String getServiceThreadName() { String brokerIdentifier = ""; if (TimerMessageRocksDBStore.this.messageStore instanceof DefaultMessageStore) { DefaultMessageStore messageStore = (DefaultMessageStore)TimerMessageRocksDBStore.this.messageStore; if (messageStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = messageStore.getBrokerConfig().getIdentifier(); } } return brokerIdentifier; } private class TimerSysTopicScanService extends ServiceThread { private final Logger log = TimerMessageRocksDBStore.log; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); long waitTime; while (!this.isStopped()) { try { if (!storeConfig.isTimerRocksDBEnable() || storeConfig.isTimerRocksDBStopScan()) { waitTime = TimeUnit.SECONDS.toMillis(10); } else { scanSysTimerTopic(); waitTime = 100L; } waitForRunning(waitTime); } catch (Exception e) { logError.error("TimerMessageRocksDBStore error occurred in: {}, error: {}", getServiceName(), e.getMessage()); } } log.info(this.getServiceName() + " service end"); } private void scanSysTimerTopic() { ConsumeQueueInterface cq = messageStore.getConsumeQueue(TIMER_TOPIC, 0); if (null == cq) { return; } if (readOffset.get() < cq.getMinOffsetInQueue()) { logError.warn("scanSysTimerTopic readOffset: {} is smaller than minOffsetInQueue: {}, use minOffsetInQueue to scan timer sysTimerTopic", readOffset.get(), cq.getMinOffsetInQueue()); readOffset.set(cq.getMinOffsetInQueue()); } else if (readOffset.get() > cq.getMaxOffsetInQueue()) { logError.warn("scanSysTimerTopic readOffset: {} is bigger than maxOffsetInQueue: {}, use maxOffsetInQueue to scan timer sysTimerTopic", readOffset.get(), cq.getMaxOffsetInQueue()); readOffset.set(cq.getMaxOffsetInQueue()); } ReferredIterator iterator = null; try { iterator = cq.iterateFrom(readOffset.get()); if (null == iterator) { return; } while (iterator.hasNext()) { perfCounterTicks.startTick(SCAN_SYS_TOPIC); try { CqUnit cqUnit = iterator.next(); if (null == cqUnit) { logError.error("scanSysTimerTopic cqUnit is null, readOffset: {}", readOffset.get()); break; } long offsetPy = cqUnit.getPos(); int sizePy = cqUnit.getSize(); long queueOffset = cqUnit.getQueueOffset(); MessageExt msgExt = getMessageByCommitOffset(offsetPy, sizePy); if (null == msgExt) { perfCounterTicks.getCounter(SCAN_SYS_TOPIC_MISS); break; } Long delayedTime = getDelayTime(msgExt); if (null == delayedTime) { readOffset.incrementAndGet(); continue; } if (isExpired(delayedTime)) { putRealTopicMessage(msgExt); readOffset.incrementAndGet(); continue; } TimerRocksDBRecord timerRecord = new TimerRocksDBRecord(delayedTime, MessageClientIDSetter.getUniqID(msgExt), offsetPy, sizePy, queueOffset, msgExt); timeline.putRecord(timerRecord); readOffset.incrementAndGet(); StoreMetricsManager metricsManager = messageStore.getStoreMetricsManager(); if (metricsManager instanceof DefaultStoreMetricsManager) { DefaultStoreMetricsManager defaultMetricsManager = (DefaultStoreMetricsManager)metricsManager; Attributes attributes = defaultMetricsManager.newAttributesBuilder().put(DefaultStoreMetricsConstant.LABEL_TOPIC, msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC)).build(); defaultMetricsManager.getTimerMessageSetLatency().record((delayedTime - msgExt.getBornTimestamp()) / 1000, attributes); } } catch (Exception e) { logError.error("Unknown error in scan the system topic error: {}", e.getMessage()); } finally { perfCounterTicks.endTick(SCAN_SYS_TOPIC); } } } catch (Exception e) { logError.error("scanSysTimerTopic throw Unknown exception. {}", e.getMessage()); } finally { if (iterator != null) { iterator.release(); } } } } private class TimerMessageReputService extends ServiceThread { private final Logger log = TimerMessageRocksDBStore.log; private final BlockingQueue> queue; private final RateLimiter rateLimiter; private final boolean writeCheckPoint; private final ExecutorService executor = ThreadUtils.newThreadPoolExecutor( storeConfig.getTimerReputServiceCorePoolSize(), storeConfig.getTimerReputServiceMaxPoolSize(), 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(storeConfig.getTimerReputServiceQueueCapacity()), ThreadUtils.newGenericThreadFactory("TimerMessageReputService", false), new ThreadPoolExecutor.CallerRunsPolicy() ); public TimerMessageReputService(BlockingQueue> queue, double maxTps, boolean writeCheckPoint) { this.queue = queue; this.rateLimiter = RateLimiter.create(maxTps); this.writeCheckPoint = writeCheckPoint; } @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + " service start"); while (!this.isStopped() || !queue.isEmpty()) { try { List trs = queue.poll(100L, TimeUnit.MILLISECONDS); if (CollectionUtils.isEmpty(trs)) { continue; } long start = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(trs.size()); for (TimerRocksDBRecord record : trs) { executor.submit(new Task(countDownLatch, record)); } countDownLatch.await(); log.info("TimerMessageReputService reput messages to commitlog, cost: {}, trs size: {}, checkPoint: {}", System.currentTimeMillis() - start, trs.size(), trs.get(trs.size() - 1).getCheckPoint()); if (this.writeCheckPoint && !CollectionUtils.isEmpty(trs) && trs.get(trs.size() - 1).getCheckPoint() > 0L) { log.info("TimerMessageReputService reput messages to commitlog, checkPoint: {}", trs.get(trs.size() - 1).getCheckPoint()); messageRocksDBStorage.writeCheckPointForTimer(TIMER_COLUMN_FAMILY, MessageRocksDBStorage.TIMELINE_CHECK_POINT, trs.get(trs.size() - 1).getCheckPoint()); } } catch (Exception e) { logError.error("TimerMessageReputService error: {}", e.getMessage()); } } log.info(this.getServiceName() + " service end"); } private void putMsgWithRetry(MessageExtBrokerInner msg) throws InterruptedException { if (null == msg) { return; } for (int retryCount = 0; !isStopped() && retryCount <= MAX_PUT_MSG_TIMES; retryCount++) { int result = doPut(msg); switch (result) { case PUT_OK: return; case PUT_NO_RETRY: logError.warn("Skipping message due to unrecoverable error. Msg: {}", msg); return; default: if (retryCount == MAX_PUT_MSG_TIMES) { logError.error("Message processing failed after {} retries. Msg: {}", retryCount, msg); return; } else { Thread.sleep(100L); logError.warn("Retrying to process message. Retry count: {}, Msg: {}", retryCount, msg); } } } } class Task implements Callable { private CountDownLatch countDownLatch; private TimerRocksDBRecord record; public Task(CountDownLatch countDownLatch, TimerRocksDBRecord record) { this.countDownLatch = countDownLatch; this.record = record; } @Override public Void call() throws Exception { try { perfCounterTicks.startTick(OUT_BIZ_MESSAGE); MessageExt messageExt = record.getMessageExt(); if (null == messageExt) { messageExt = getMessageByCommitOffset(record.getOffsetPy(), record.getSizePy()); if (null == messageExt) { return null; } } MessageExtBrokerInner msg = convertMessage(messageExt); if (null == msg) { return null; } record.setUniqKey(MessageClientIDSetter.getUniqID(msg)); putMsgWithRetry(msg); timeline.addMetric(msg, -1); perfCounterTicks.endTick(OUT_BIZ_MESSAGE); rateLimiter.acquire(); } catch (Exception e) { logError.error("TimerMessageReputService running error: {}", e.getMessage()); } finally { countDownLatch.countDown(); } return null; } } @Override public void shutdown() { super.shutdown(); ThreadUtils.shutdownGracefully(executor, 5, TimeUnit.SECONDS); } } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/timer/rocksdb/TimerRocksDBRecord.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer.rocksdb; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class TimerRocksDBRecord { private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); public static final byte TIMER_ROCKSDB_PUT = (byte)0; public static final byte TIMER_ROCKSDB_DELETE = (byte)1; public static final byte TIMER_ROCKSDB_UPDATE = (byte)2; private static final int VALUE_LENGTH = Integer.BYTES + Long.BYTES; private long delayTime; private String uniqKey; private int sizePy; private long offsetPy; private long queueOffset; private long checkPoint; private byte actionFlag; private MessageExt messageExt; public TimerRocksDBRecord() {} public TimerRocksDBRecord(long delayTime, String uniqKey, long offsetPy, int sizePy, long queueOffset, MessageExt messageExt) { this.delayTime = delayTime; this.uniqKey = uniqKey; this.offsetPy = offsetPy; this.sizePy = sizePy; this.messageExt = messageExt; this.queueOffset = queueOffset; } public byte[] getKeyBytes() { if (StringUtils.isEmpty(uniqKey) || delayTime <= 0L) { return null; } try { byte[] uniqKeyBytes = uniqKey.getBytes(StandardCharsets.UTF_8); int keyLength = Long.BYTES + uniqKeyBytes.length; return ByteBuffer.allocate(keyLength).putLong(delayTime).put(uniqKeyBytes).array(); } catch (Exception e) { logError.error("TimerRocksDBRecord getKeyBytes error: {}", e.getMessage()); return null; } } public byte[] getValueBytes() { if (sizePy <= 0 || offsetPy < 0L) { return null; } try { return ByteBuffer.allocate(VALUE_LENGTH).putInt(sizePy).putLong(offsetPy).array(); } catch (Exception e) { logError.error("TimerRocksDBRecord getValueBytes error: {}", e.getMessage()); return null; } } public static TimerRocksDBRecord decode(byte[] key, byte[] value) { if (null == key || key.length < Long.BYTES || null == value || value.length != VALUE_LENGTH) { return null; } try { TimerRocksDBRecord rocksDBRecord = new TimerRocksDBRecord(); ByteBuffer keyBuffer = ByteBuffer.wrap(key); rocksDBRecord.setDelayTime(keyBuffer.getLong()); byte[] uniqKey = new byte[key.length - Long.BYTES]; keyBuffer.get(uniqKey); rocksDBRecord.setUniqKey(new String(uniqKey, StandardCharsets.UTF_8)); ByteBuffer valueByteBuffer = ByteBuffer.wrap(value); rocksDBRecord.setSizePy(valueByteBuffer.getInt()); rocksDBRecord.setOffsetPy(valueByteBuffer.getLong()); return rocksDBRecord; } catch (Exception e) { logError.error("TimerRocksDBRecord decode error: {}", e.getMessage()); return null; } } public void setDelayTime(long delayTime) { this.delayTime = delayTime; } public void setOffsetPy(long offsetPy) { this.offsetPy = offsetPy; } public void setSizePy(int sizePy) { this.sizePy = sizePy; } public int getSizePy() { return sizePy; } public long getDelayTime() { return delayTime; } public long getOffsetPy() { return offsetPy; } public MessageExt getMessageExt() { return messageExt; } public void setMessageExt(MessageExt messageExt) { this.messageExt = messageExt; } public String getUniqKey() { return uniqKey; } public void setUniqKey(String uniqKey) { this.uniqKey = uniqKey; } public void setCheckPoint(long checkPoint) { this.checkPoint = checkPoint; } public long getCheckPoint() { return checkPoint; } public long getQueueOffset() { return queueOffset; } public void setQueueOffset(long queueOffset) { this.queueOffset = queueOffset; } public byte getActionFlag() { return actionFlag; } public void setActionFlag(byte actionFlag) { this.actionFlag = actionFlag; } @Override public String toString() { return "TimerRocksDBRecord{" + "delayTime=" + delayTime + ", uniqKey=" + uniqKey + ", sizePy=" + sizePy + ", offsetPy=" + offsetPy + ", queueOffset=" + queueOffset + ", checkPoint=" + checkPoint + '}'; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/transaction/TransMessageRocksDBStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.transaction; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.CommitLogDispatchStore; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.StoreUtil; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.rocksdb.RocksDBException; import static org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage.TRANS_COLUMN_FAMILY; public class TransMessageRocksDBStore implements CommitLogDispatchStore { private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); private static final String REMOVE_TAG = "d"; private static final byte[] FILL_BYTE = new byte[] {(byte) 0}; private static final int DEFAULT_CAPACITY = 100000; private static final int BATCH_SIZE = 1000; private static final int MAX_GET_MSG_TIMES = 3; private static final int INITIAL = 0, RUNNING = 1, SHUTDOWN = 2; private volatile int state = INITIAL; private final MessageStore messageStore; private final MessageStoreConfig storeConfig; private final MessageRocksDBStorage messageRocksDBStorage; private final BrokerStatsManager brokerStatsManager; private final SocketAddress storeHost; private ThreadLocal bufferLocal = null; private TransIndexBuildService transIndexBuildService; protected BlockingQueue originTransMsgQueue; public TransMessageRocksDBStore(final MessageStore messageStore, final BrokerStatsManager brokerStatsManager, final SocketAddress storeHost) { this.messageStore = messageStore; this.storeConfig = messageStore.getMessageStoreConfig(); this.messageRocksDBStorage = messageStore.getMessageRocksDBStorage(); this.brokerStatsManager = brokerStatsManager; this.storeHost = storeHost; bufferLocal = ThreadLocal.withInitial(() -> ByteBuffer.allocate(storeConfig.getMaxMessageSize())); if (storeConfig.isTransRocksDBEnable()) { init(); } } private void init() { if (this.state == RUNNING) { return; } this.transIndexBuildService = new TransIndexBuildService(); this.originTransMsgQueue = new LinkedBlockingDeque<>(DEFAULT_CAPACITY); this.transIndexBuildService.start(); this.state = RUNNING; Long lastOffsetPy = messageRocksDBStorage.getLastOffsetPy(TRANS_COLUMN_FAMILY); log.info("TransMessageRocksDBStore start success, lastOffsetPy: {}", lastOffsetPy); } public void shutdown() { if (this.state != RUNNING || this.state == SHUTDOWN) { return; } if (null != this.transIndexBuildService) { this.transIndexBuildService.shutdown(); } this.state = SHUTDOWN; log.info("TransMessageRocksDBStore shutdown success"); } public void buildTransIndex(DispatchRequest dispatchRequest) { if (null == dispatchRequest || dispatchRequest.getCommitLogOffset() < 0L || dispatchRequest.getMsgSize() <= 0 || state != RUNNING || null == this.originTransMsgQueue) { logError.error("TransMessageRocksDBStore buildTransIndex error, dispatchRequest: {}, state: {}, originTransMsgQueue: {}", dispatchRequest, state, originTransMsgQueue); return; } long reqOffsetPy = dispatchRequest.getCommitLogOffset(); long endOffsetPy = messageRocksDBStorage.getLastOffsetPy(TRANS_COLUMN_FAMILY); if (reqOffsetPy < endOffsetPy) { if (System.currentTimeMillis() % 1000 == 0) { log.warn("TransMessageRocksDBStore buildTransIndex recover, but ignore, reqOffsetPy: {}, endOffsetPy: {}", reqOffsetPy, endOffsetPy); } return; } int reqMsgSize = dispatchRequest.getMsgSize(); try { MessageExt msgInner = getMessage(reqOffsetPy, reqMsgSize); if (null == msgInner) { logError.error("TransMessageRocksDBStore buildTransIndex error, msgInner is not found, reqOffsetPy: {}, reqMsgSize: {}", reqOffsetPy, reqMsgSize); return; } String topic = msgInner.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC); String uniqKey = msgInner.getUserProperty(MessageConst.PROPERTY_TRANSACTION_ID); if (StringUtils.isEmpty(topic) || StringUtils.isEmpty(uniqKey)) { logError.error("TransMessageRocksDBStore buildTransIndex error, uniqKey: {}, topic: {}", uniqKey, topic); return; } TransRocksDBRecord transRocksDBRecord = null; String reqTopic = dispatchRequest.getTopic(); if (TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC.equals(reqTopic)) { transRocksDBRecord = new TransRocksDBRecord(reqOffsetPy, topic, uniqKey, reqMsgSize, 0); } else if (TopicValidator.RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC.equals(reqTopic)) { long offsetPy = -1L; String transOffsetPy = null; try { transOffsetPy = msgInner.getUserProperty(MessageConst.PROPERTY_TRANS_OFFSET); if (!StringUtils.isEmpty(transOffsetPy)) { offsetPy = Long.parseLong(transOffsetPy); } if (offsetPy >= 0L) { transRocksDBRecord = new TransRocksDBRecord(offsetPy, topic, uniqKey, true); } } catch (Exception e) { logError.error("TransMessageRocksDBStore buildTransIndex error, transOffsetPy: {}, error: {}", transOffsetPy, e.getMessage()); } } if (null != transRocksDBRecord) { while (!originTransMsgQueue.offer(transRocksDBRecord, 3, TimeUnit.SECONDS)) { if (System.currentTimeMillis() % 1000 == 0) { logError.error("TransMessageRocksDBStore buildTransStatus offer transRocksDBRecord error, topic: {}, uniqKey: {}, reqOffsetPy: {}", topic, uniqKey, reqOffsetPy); } } } } catch (Exception e) { logError.error("TransMessageRocksDBStore buildTransStatus error: {}", e.getMessage()); } } public void deletePrepareMessage(MessageExt messageExt) { if (null == messageExt) { logError.error("TransMessageRocksDBStore deletePrepareMessage error, messageExt is null"); return; } try { MessageExtBrokerInner msgInner = makeOpMessageInner(messageExt); if (null == msgInner) { logError.error("TransMessageRocksDBStore deletePrepareMessage msgInner is null"); return; } PutMessageResult result = messageStore.putMessage(msgInner); if (result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { this.brokerStatsManager.incTopicPutNums(msgInner.getTopic()); this.brokerStatsManager.incTopicPutSize(msgInner.getTopic(), result.getAppendMessageResult().getWroteBytes()); this.brokerStatsManager.incBrokerPutNums(); return; } logError.error("TransMessageRocksDBStore deletePrepareMessage put op msg failed, result: {}", result); } catch (Exception e) { logError.error("TransMessageRocksDBStore deletePrepareMessage error: {}", e.getMessage()); } } public MessageExt getMessage(long offsetPy, int sizePy) { if (offsetPy < 0L || sizePy <= 0 || sizePy > storeConfig.getMaxMessageSize()) { logError.error("TransMessageRocksDBStore getMessage param error, offsetPy: {}, sizePy: {}, maxMsgSizeConfig: {}", offsetPy, sizePy, storeConfig.getMaxMessageSize()); return null; } ByteBuffer byteBuffer = bufferLocal.get(); if (sizePy > byteBuffer.limit()) { bufferLocal.remove(); byteBuffer = ByteBuffer.allocate(sizePy); bufferLocal.set(byteBuffer); } for (int i = 0; i < MAX_GET_MSG_TIMES; i++) { try { MessageExt msgExt = StoreUtil.getMessage(offsetPy, sizePy, messageStore, bufferLocal.get()); if (null == msgExt) { log.warn("Fail to read msg from commitLog offsetPy:{} sizePy:{}", offsetPy, sizePy); } else { return msgExt; } } catch (Exception e) { logError.error("TransMessageRocksDBStore getMessage error, offsetPy: {}, sizePy: {}, error: {}", offsetPy, sizePy, e.getMessage()); } } return null; } public MessageRocksDBStorage getMessageRocksDBStorage() { return messageRocksDBStorage; } private MessageExtBrokerInner makeOpMessageInner(MessageExt messageExt) { if (null == messageExt) { logError.error("TransMessageRocksDBStore makeOpMessageInner messageExt is null"); return null; } try { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(TopicValidator.RMQ_SYS_ROCKSDB_TRANS_OP_HALF_TOPIC); msgInner.setBody(FILL_BYTE); msgInner.setQueueId(0); msgInner.setTags(REMOVE_TAG); msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgInner.getTags())); msgInner.setSysFlag(0); msgInner.setBornTimestamp(System.currentTimeMillis()); msgInner.setBornHost(this.storeHost); msgInner.setStoreHost(this.storeHost); msgInner.setWaitStoreMsgOK(false); MessageClientIDSetter.setUniqID(msgInner); String uniqKey = MessageClientIDSetter.getUniqID(messageExt); if (!StringUtils.isEmpty(uniqKey)) { MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_ID, uniqKey); } MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANS_OFFSET, String.valueOf(messageExt.getCommitLogOffset())); MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, messageExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); return msgInner; } catch (Exception e) { logError.error("TransMessageRocksDBStore makeOpMessageInner error: {}", e.getMessage()); return null; } } public Integer getCheckTimes(String topic, String uniqKey, Long offsetPy) { if (StringUtils.isEmpty(topic) || StringUtils.isEmpty(uniqKey) || null == offsetPy || offsetPy < 0L) { return null; } try { TransRocksDBRecord record = messageRocksDBStorage.getRecordForTrans(TRANS_COLUMN_FAMILY, new TransRocksDBRecord(offsetPy, topic, uniqKey, false)); if (null == record) { return null; } return record.getCheckTimes(); } catch (Exception e) { logError.error("TransMessageRocksDBStore getCheckTimes error, topic: {}, uniqKey: {}, offsetPy: {}, error: {}", topic, uniqKey, offsetPy, e.getMessage()); return null; } } public boolean isMappedFileMatchedRecover(long phyOffset, long storeTimestamp, boolean recoverNormally) throws RocksDBException { if (!storeConfig.isTransRocksDBEnable()) { return true; } Long lastOffsetPy = messageRocksDBStorage.getLastOffsetPy(TRANS_COLUMN_FAMILY); log.info("trans isMappedFileMatchedRecover lastOffsetPy: {}", lastOffsetPy); if (null != lastOffsetPy && phyOffset <= lastOffsetPy) { log.info("isMappedFileMatchedRecover TransMessageRocksDBStore recover form this offset, phyOffset: {}, lastOffsetPy: {}", phyOffset, lastOffsetPy); return true; } return false; } private String getServiceThreadName() { String brokerIdentifier = ""; if (TransMessageRocksDBStore.this.messageStore instanceof DefaultMessageStore) { DefaultMessageStore messageStore = (DefaultMessageStore) TransMessageRocksDBStore.this.messageStore; if (messageStore.getBrokerConfig().isInBrokerContainer()) { brokerIdentifier = messageStore.getBrokerConfig().getIdentifier(); } } return brokerIdentifier; } public class TransIndexBuildService extends ServiceThread { private final Logger log = TransMessageRocksDBStore.log; private List trs; @Override public String getServiceName() { return getServiceThreadName() + this.getClass().getSimpleName(); } @Override public void run() { log.info(this.getServiceName() + "service start"); trs = new ArrayList<>(BATCH_SIZE); while (!this.isStopped() || !originTransMsgQueue.isEmpty()) { try { buildTransIndex(); } catch (Exception e) { trs.clear(); logError.error("TransMessageRocksDBStore error occurred in: {}, error: {}", getServiceName(), e.getMessage()); } } log.info(this.getServiceName() + " service end"); } protected void buildTransIndex() { pollTransMessageRecords(); if (CollectionUtils.isEmpty(trs)) { return; } try { messageRocksDBStorage.writeRecordsForTrans(TRANS_COLUMN_FAMILY, trs); } catch (Exception e) { logError.error("TransMessageRocksDBStore pollAndPutTransRequest writeRecords error: {}", e.getMessage()); } trs.clear(); } protected void pollTransMessageRecords() { try { TransRocksDBRecord firstReq = originTransMsgQueue.poll(100, TimeUnit.MILLISECONDS); if (null != firstReq) { trs.add(firstReq); while (true) { TransRocksDBRecord tmpReq = originTransMsgQueue.poll(100, TimeUnit.MILLISECONDS); if (null == tmpReq) { break; } trs.add(tmpReq); if (trs.size() >= BATCH_SIZE) { break; } } } } catch (Exception e) { logError.error("TransMessageRocksDBStore fetchTransMessageRecord error: {}", e.getMessage()); } } } @Override public Long getDispatchFromPhyOffset(boolean recoverNormally) throws RocksDBException { if (!storeConfig.isTransRocksDBEnable()) { return null; } Long dispatchFromTransPhyOffset = messageRocksDBStorage.getLastOffsetPy(TRANS_COLUMN_FAMILY); if (dispatchFromTransPhyOffset != null && dispatchFromTransPhyOffset > 0) { return dispatchFromTransPhyOffset; } return null; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/transaction/TransRocksDBRecord.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.transaction; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class TransRocksDBRecord { private static final Logger logError = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); public static final int VALUE_LENGTH = Integer.BYTES + Integer.BYTES; private static final String KEY_SPLIT = "@"; protected long offsetPy; private String topic; private String uniqKey; private int checkTimes = 0; private int sizePy; private boolean isOp; private boolean delete; private MessageExt messageExt; public TransRocksDBRecord(long offsetPy, String topic, String uniqKey, int sizePy, int checkTimes) { this.offsetPy = offsetPy; this.topic = topic; this.uniqKey = uniqKey; this.sizePy = sizePy; this.checkTimes = checkTimes; } public TransRocksDBRecord(long offsetPy, String topic, String uniqKey, boolean isOp) { this.offsetPy = offsetPy; this.topic = topic; this.uniqKey = uniqKey; this.isOp = isOp; } public TransRocksDBRecord() {} public byte[] getKeyBytes() { if (offsetPy < 0L || StringUtils.isEmpty(topic) || StringUtils.isEmpty(uniqKey)) { return null; } byte[] keySuffixBytes = (KEY_SPLIT + topic + KEY_SPLIT + uniqKey).getBytes(StandardCharsets.UTF_8); int keyLength = Long.BYTES + keySuffixBytes.length; return ByteBuffer.allocate(keyLength).putLong(offsetPy).put(keySuffixBytes).array(); } public byte[] getValueBytes() { if (checkTimes < 0 || sizePy <= 0) { logError.error("TransRocksDBRecord getValueBytes error, checkTimes: {}, sizePy: {}", checkTimes, sizePy); return null; } return ByteBuffer.allocate(VALUE_LENGTH).putInt(checkTimes).putInt(sizePy).array(); } public static TransRocksDBRecord decode(byte[] key, byte[] value) { if (null == key || key.length <= Long.BYTES || null == value || value.length != VALUE_LENGTH) { logError.error("TransRocksDBRecord decode param error, key: {}, value: {}", key, value); return null; } TransRocksDBRecord transRocksDBRecord = null; try { transRocksDBRecord = new TransRocksDBRecord(); ByteBuffer keyByteBuffer = ByteBuffer.wrap(key); transRocksDBRecord.setOffsetPy(keyByteBuffer.getLong()); byte[] keySuffix = new byte[key.length - Long.BYTES]; keyByteBuffer.get(keySuffix); String[] keySuffixSplit = new String(keySuffix, StandardCharsets.UTF_8).split(KEY_SPLIT); if (keySuffixSplit.length != 3) { logError.error("TransRocksDBRecord decode keySuffixSplit parse error"); return null; } transRocksDBRecord.setTopic(keySuffixSplit[1]); transRocksDBRecord.setUniqKey(keySuffixSplit[2]); ByteBuffer valueByteBuffer = ByteBuffer.wrap(value); transRocksDBRecord.setCheckTimes(valueByteBuffer.getInt()); transRocksDBRecord.setSizePy(valueByteBuffer.getInt()); } catch (Exception e) { logError.error("TransRocksDBRecord decode error, valueLength: {}, error: {}", value.length, e.getMessage()); return null; } return transRocksDBRecord; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getUniqKey() { return uniqKey; } public void setUniqKey(String uniqKey) { this.uniqKey = uniqKey; } public int getCheckTimes() { return checkTimes; } public void setCheckTimes(int checkTimes) { this.checkTimes = checkTimes; } public int getSizePy() { return sizePy; } public void setSizePy(int sizePy) { this.sizePy = sizePy; } public long getOffsetPy() { return offsetPy; } public void setOffsetPy(long offsetPy) { this.offsetPy = offsetPy; } public MessageExt getMessageExt() { return messageExt; } public void setMessageExt(MessageExt messageExt) { this.messageExt = messageExt; } public boolean isOp() { return isOp; } public void setOp(boolean op) { isOp = op; } public boolean isDelete() { return delete; } public void setDelete(boolean delete) { this.delete = delete; } } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/util/LibC.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.util; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.Platform; import com.sun.jna.Pointer; public interface LibC extends Library { LibC INSTANCE = (LibC) Native.loadLibrary(Platform.isWindows() ? "msvcrt" : "c", LibC.class); int MADV_NORMAL = 0; int MADV_RANDOM = 1; int MADV_WILLNEED = 3; int MADV_DONTNEED = 4; int MCL_CURRENT = 1; int MCL_FUTURE = 2; int MCL_ONFAULT = 4; /* sync memory asynchronously */ int MS_ASYNC = 0x0001; /* invalidate mappings & caches */ int MS_INVALIDATE = 0x0002; /* synchronous memory sync */ int MS_SYNC = 0x0004; int mlock(Pointer var1, NativeLong var2); int munlock(Pointer var1, NativeLong var2); int madvise(Pointer var1, NativeLong var2, int var3); Pointer memset(Pointer p, int v, long len); int mlockall(int flags); int msync(Pointer p, NativeLong length, int flags); int mincore(Pointer p, NativeLong length, byte[] vec); int getpagesize(); } ================================================ FILE: store/src/main/java/org/apache/rocketmq/store/util/PerfCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.util; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.logging.org.slf4j.Logger; import java.sql.Timestamp; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class PerfCounter { private long last = System.currentTimeMillis(); private float lastTps = 0.0f; private final ThreadLocal lastTickMs = new ThreadLocal() { @Override protected AtomicLong initialValue() { return new AtomicLong(System.currentTimeMillis()); } }; private final Logger logger; private String prefix = "DEFAULT"; public float getLastTps() { if (System.currentTimeMillis() - last <= maxTimeMsPerCount + 3000) { return lastTps; } return 0.0f; } //1000 * ms, 1000 * 10 ms, then 100ms every slots private final AtomicInteger[] count; private final AtomicLong allCount; private final int maxNumPerCount; private final int maxTimeMsPerCount; public PerfCounter() { this(5001, null, null, 1000 * 1000, 10 * 1000); } public PerfCounter(int slots, Logger logger, String prefix, int maxNumPerCount, int maxTimeMsPerCount) { if (slots < 3000) { throw new RuntimeException("slots must bigger than 3000, but:%s" + slots); } count = new AtomicInteger[slots]; allCount = new AtomicLong(0); this.logger = logger; if (prefix != null) { this.prefix = prefix; } this.maxNumPerCount = maxNumPerCount; this.maxTimeMsPerCount = maxTimeMsPerCount; reset(); } public void flow(long cost) { flow(cost, 1); } public void flow(long cost, int num) { if (cost < 0) return; allCount.addAndGet(num); count[getIndex(cost)].addAndGet(num); if (allCount.get() >= maxNumPerCount || System.currentTimeMillis() - last >= maxTimeMsPerCount) { synchronized (allCount) { if (allCount.get() < maxNumPerCount && System.currentTimeMillis() - last < maxTimeMsPerCount) { return; } print(); this.reset(); } } } public void print() { int min = this.getMin(); int max = this.getMax(); int tp50 = this.getTPValue(0.5f); int tp80 = this.getTPValue(0.8f); int tp90 = this.getTPValue(0.9f); int tp99 = this.getTPValue(0.99f); int tp999 = this.getTPValue(0.999f); long count0t1 = this.getCount(0, 1); long count2t5 = this.getCount(2, 5); long count6t10 = this.getCount(6, 10); long count11t50 = this.getCount(11, 50); long count51t100 = this.getCount(51, 100); long count101t500 = this.getCount(101, 500); long count501t999 = this.getCount(501, 999); long count1000t = this.getCount(1000, 100000000); long elapsed = System.currentTimeMillis() - last; lastTps = (allCount.get() + 0.1f) * 1000 / elapsed; String str = String.format("PERF_COUNTER_%s[%s] num:%d cost:%d tps:%.4f min:%d max:%d tp50:%d tp80:%d tp90:%d tp99:%d tp999:%d " + "0_1:%d 2_5:%d 6_10:%d 11_50:%d 51_100:%d 101_500:%d 501_999:%d 1000_:%d", prefix, new Timestamp(System.currentTimeMillis()), allCount.get(), elapsed, lastTps, min, max, tp50, tp80, tp90, tp99, tp999, count0t1, count2t5, count6t10, count11t50, count51t100, count101t500, count501t999, count1000t); if (logger != null) { logger.info(str); } } private int getIndex(long cost) { if (cost < 1000) { return (int) cost; } if (cost >= 1000 && cost < 1000 + 1000 * 10) { int units = (int) ((cost - 1000) / 10); return 1000 + units; } int units = (int) ((cost - 1000 - 1000 * 10) / 100); units = 2000 + units; if (units >= count.length) { units = count.length - 1; } return units; } private int convert(int index) { if (index < 1000) { return index; } else if (index >= 1000 && index < 2000) { return (index - 1000) * 10 + 1000; } else { return (index - 2000) * 100 + 1000 * 10 + 1000; } } public float getRate(int from, int to) { long tmp = getCount(from, to); return ((tmp + 0.0f) * 100) / (allCount.get() + 1); } public long getCount(int from, int to) { from = getIndex(from); to = getIndex(to); long tmp = 0; for (int i = from; i <= to && i < count.length; i++) { tmp = tmp + count[i].get(); } return tmp; } public int getTPValue(float ratio) { if (ratio <= 0 || ratio >= 1) { ratio = 0.99f; } long num = (long) (allCount.get() * (1 - ratio)); int tmp = 0; for (int i = count.length - 1; i > 0; i--) { tmp += count[i].get(); if (tmp > num) { return convert(i); } } return 0; } public int getMin() { for (int i = 0; i < count.length; i++) { if (count[i].get() > 0) { return convert(i); } } return 0; } public int getMax() { for (int i = count.length - 1; i > 0; i--) { if (count[i].get() > 0) { return convert(i); } } return 99999999; } public void reset() { for (int i = 0; i < count.length; i++) { if (count[i] == null) { count[i] = new AtomicInteger(0); } else { count[i].set(0); } } allCount.set(0); last = System.currentTimeMillis(); } public void startTick() { lastTickMs.get().set(System.currentTimeMillis()); } public void endTick() { flow(System.currentTimeMillis() - lastTickMs.get().get()); } public static class Ticks extends ServiceThread { private final Logger logger; private final Map perfs = new ConcurrentHashMap<>(); private final Map keyFreqs = new ConcurrentHashMap<>(); private final PerfCounter defaultPerf; private final AtomicLong defaultTime = new AtomicLong(System.currentTimeMillis()); private final int maxKeyNumPerf; private final int maxKeyNumDebug; private final int maxNumPerCount; private final int maxTimeMsPerCount; public Ticks() { this(null, 1000 * 1000, 10 * 1000, 20 * 1000, 100 * 1000); } public Ticks(Logger logger) { this(logger, 1000 * 1000, 10 * 1000, 20 * 1000, 100 * 1000); } @Override public String getServiceName() { return this.getClass().getName(); } public Ticks(Logger logger, int maxNumPerCount, int maxTimeMsPerCount, int maxKeyNumPerf, int maxKeyNumDebug) { this.logger = logger; this.maxNumPerCount = maxNumPerCount; this.maxTimeMsPerCount = maxTimeMsPerCount; this.maxKeyNumPerf = maxKeyNumPerf; this.maxKeyNumDebug = maxKeyNumDebug; this.defaultPerf = new PerfCounter(3001, logger, null, maxNumPerCount, maxTimeMsPerCount); } private PerfCounter makeSureExists(String key) { if (perfs.get(key) == null) { if (perfs.size() >= maxKeyNumPerf + 100) { return defaultPerf; } perfs.put(key, new PerfCounter(3001, logger, key, maxNumPerCount, maxTimeMsPerCount)); } return perfs.getOrDefault(key, defaultPerf); } public void startTick(String key) { try { makeSureExists(key).startTick(); } catch (Throwable ignored) { } } public void endTick(String key) { try { makeSureExists(key).endTick(); } catch (Throwable ignored) { } } public void flowOnce(String key, int cost) { try { makeSureExists(key).flow(cost); } catch (Throwable ignored) { } } public PerfCounter getCounter(String key) { try { return makeSureExists(key); } catch (Throwable ignored) { return defaultPerf; } } private AtomicLong makeSureDebugKeyExists(String key) { AtomicLong lastTimeMs = keyFreqs.get(key); if (null == lastTimeMs) { if (keyFreqs.size() >= maxKeyNumDebug + 100) { return defaultTime; } lastTimeMs = new AtomicLong(0); keyFreqs.put(key, lastTimeMs); } return keyFreqs.getOrDefault(key, defaultTime); } public boolean shouldDebugKeyAndTimeMs(String key, int intervalMs) { try { AtomicLong lastTimeMs = makeSureDebugKeyExists(key); if (System.currentTimeMillis() - lastTimeMs.get() > intervalMs) { lastTimeMs.set(System.currentTimeMillis()); return true; } return false; } catch (Throwable ignored) { } return false; } @Override public void run() { logger.info("{} get started", getServiceName()); while (!this.isStopped()) { try { long maxLiveTimeMs = maxTimeMsPerCount * 2 + 1000; this.waitForRunning(maxLiveTimeMs); if (perfs.size() >= maxKeyNumPerf || keyFreqs.size() >= maxKeyNumDebug) { logger.warn("The key is full {}-{} {}-{}", perfs.size(), maxKeyNumPerf, keyFreqs.size(), maxKeyNumDebug); } { Iterator> it = perfs.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); PerfCounter value = entry.getValue(); // May have concurrency problem, but it has no effect, we can ignore it. if (System.currentTimeMillis() - value.last > maxLiveTimeMs) { it.remove(); } } } { Iterator> it = keyFreqs.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); AtomicLong value = entry.getValue(); // May have concurrency problem, but it has no effect, we can ignore it. if (System.currentTimeMillis() - value.get() > maxLiveTimeMs) { it.remove(); } } } } catch (Exception e) { logger.error("{} get unknown error", getServiceName(), e); try { Thread.sleep(1000); } catch (Throwable ignored) { } } } logger.info("{} get stopped", getServiceName()); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; public class AppendCallbackTest { AppendMessageCallback callback; MessageExtEncoder batchEncoder; @Before public void init() throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setMaxMessageSize(10 * 1024 * 1024); messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore"); messageStoreConfig.setStorePathCommitLog(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore" + File.separator + "commitlog"); //too much reference DefaultMessageStore messageStore = new DefaultMessageStore(messageStoreConfig, null, null, new BrokerConfig(), new ConcurrentHashMap<>()); CommitLog commitLog = new CommitLog(messageStore); callback = commitLog.new DefaultAppendMessageCallback(messageStoreConfig); batchEncoder = new MessageExtEncoder(messageStoreConfig); } @After public void destroy() { UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore")); } @Test public void testAppendMessageBatchEndOfFile() throws Exception { List messages = new ArrayList<>(); String topic = "test-topic"; int queue = 0; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody("body".getBytes()); msg.setTopic(topic); msg.setTags("abc"); messages.add(msg); } MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setBornHost(new InetSocketAddress("127.0.0.1", 123)); messageExtBatch.setStoreHost(new InetSocketAddress("127.0.0.1", 124)); messageExtBatch.setBody(MessageDecoder.encodeMessages(messages)); PutMessageContext putMessageContext = new PutMessageContext(topic + "-" + queue); messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch, putMessageContext)); ByteBuffer buff = ByteBuffer.allocate(1024 * 10); //encounter end of file when append half of the data AppendMessageResult result = callback.doAppend(0, buff, 1000, messageExtBatch, putMessageContext); assertEquals(AppendMessageStatus.END_OF_FILE, result.getStatus()); assertEquals(0, result.getWroteOffset()); assertEquals(0, result.getLogicsOffset()); assertEquals(1000, result.getWroteBytes()); assertEquals(8, buff.position()); //write blank size and magic value assertTrue(result.getMsgId().length() > 0); //should have already constructed some message ids } @Test public void testAppendIPv6HostMessageBatchEndOfFile() throws Exception { List messages = new ArrayList<>(); String topic = "test-topic"; int queue = 0; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody("body".getBytes()); msg.setTopic(topic); msg.setTags("abc"); messages.add(msg); } MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExtBatch.setSysFlag(0); messageExtBatch.setBornHostV6Flag(); messageExtBatch.setStoreHostAddressV6Flag(); messageExtBatch.setBornHost(new InetSocketAddress("1050:0000:0000:0000:0005:0600:300c:326b", 123)); messageExtBatch.setStoreHost(new InetSocketAddress("::1", 124)); messageExtBatch.setBody(MessageDecoder.encodeMessages(messages)); PutMessageContext putMessageContext = new PutMessageContext(topic + "-" + queue); messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch, putMessageContext)); ByteBuffer buff = ByteBuffer.allocate(1024 * 10); //encounter end of file when append half of the data AppendMessageResult result = callback.doAppend(0, buff, 1000, messageExtBatch, putMessageContext); assertEquals(AppendMessageStatus.END_OF_FILE, result.getStatus()); assertEquals(0, result.getWroteOffset()); assertEquals(0, result.getLogicsOffset()); assertEquals(1000, result.getWroteBytes()); assertEquals(8, buff.position()); //write blank size and magic value assertTrue(result.getMsgId().length() > 0); //should have already constructed some message ids } @Test public void testAppendMessageBatchSucc() throws Exception { List messages = new ArrayList<>(); String topic = "test-topic"; int queue = 0; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody("body".getBytes()); msg.setTopic(topic); msg.setTags("abc"); messages.add(msg); } MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setBornHost(new InetSocketAddress("127.0.0.1", 123)); messageExtBatch.setStoreHost(new InetSocketAddress("127.0.0.1", 124)); messageExtBatch.setBody(MessageDecoder.encodeMessages(messages)); PutMessageContext putMessageContext = new PutMessageContext(topic + "-" + queue); messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch, putMessageContext)); ByteBuffer buff = ByteBuffer.allocate(1024 * 10); AppendMessageResult allresult = callback.doAppend(0, buff, 1024 * 10, messageExtBatch, putMessageContext); assertEquals(AppendMessageStatus.PUT_OK, allresult.getStatus()); assertEquals(0, allresult.getWroteOffset()); assertEquals(0, allresult.getLogicsOffset()); assertEquals(buff.position(), allresult.getWroteBytes()); assertEquals(messages.size(), allresult.getMsgNum()); Set msgIds = new HashSet<>(); for (String msgId : allresult.getMsgId().split(",")) { assertEquals(32, msgId.length()); msgIds.add(msgId); } assertEquals(messages.size(), msgIds.size()); List decodeMsgs = MessageDecoder.decodes((ByteBuffer) buff.flip()); assertEquals(decodeMsgs.size(), decodeMsgs.size()); long queueOffset = decodeMsgs.get(0).getQueueOffset(); long storeTimeStamp = decodeMsgs.get(0).getStoreTimestamp(); for (int i = 0; i < messages.size(); i++) { assertEquals(messages.get(i).getTopic(), decodeMsgs.get(i).getTopic()); assertEquals(new String(messages.get(i).getBody()), new String(decodeMsgs.get(i).getBody())); assertEquals(messages.get(i).getTags(), decodeMsgs.get(i).getTags()); assertEquals(messageExtBatch.getBornHostNameString(), decodeMsgs.get(i).getBornHostNameString()); assertEquals(messageExtBatch.getBornTimestamp(), decodeMsgs.get(i).getBornTimestamp()); assertEquals(storeTimeStamp, decodeMsgs.get(i).getStoreTimestamp()); assertEquals(queueOffset++, decodeMsgs.get(i).getQueueOffset()); } } @Test public void testAppendIPv6HostMessageBatchSucc() throws Exception { List messages = new ArrayList<>(); String topic = "test-topic"; int queue = 0; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody("body".getBytes()); msg.setTopic(topic); msg.setTags("abc"); messages.add(msg); } MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExtBatch.setSysFlag(0); messageExtBatch.setBornHostV6Flag(); messageExtBatch.setStoreHostAddressV6Flag(); messageExtBatch.setBornHost(new InetSocketAddress("1050:0000:0000:0000:0005:0600:300c:326b", 123)); messageExtBatch.setStoreHost(new InetSocketAddress("::1", 124)); messageExtBatch.setBody(MessageDecoder.encodeMessages(messages)); PutMessageContext putMessageContext = new PutMessageContext(topic + "-" + queue); messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch, putMessageContext)); ByteBuffer buff = ByteBuffer.allocate(1024 * 10); AppendMessageResult allresult = callback.doAppend(0, buff, 1024 * 10, messageExtBatch, putMessageContext); assertEquals(AppendMessageStatus.PUT_OK, allresult.getStatus()); assertEquals(0, allresult.getWroteOffset()); assertEquals(0, allresult.getLogicsOffset()); assertEquals(buff.position(), allresult.getWroteBytes()); assertEquals(messages.size(), allresult.getMsgNum()); Set msgIds = new HashSet<>(); for (String msgId : allresult.getMsgId().split(",")) { assertEquals(56, msgId.length()); msgIds.add(msgId); } assertEquals(messages.size(), msgIds.size()); List decodeMsgs = MessageDecoder.decodes((ByteBuffer) buff.flip()); assertEquals(decodeMsgs.size(), decodeMsgs.size()); long queueOffset = decodeMsgs.get(0).getQueueOffset(); long storeTimeStamp = decodeMsgs.get(0).getStoreTimestamp(); for (int i = 0; i < messages.size(); i++) { assertEquals(messages.get(i).getTopic(), decodeMsgs.get(i).getTopic()); assertEquals(new String(messages.get(i).getBody()), new String(decodeMsgs.get(i).getBody())); assertEquals(messages.get(i).getTags(), decodeMsgs.get(i).getTags()); assertEquals(messageExtBatch.getBornHostNameString(), decodeMsgs.get(i).getBornHostNameString()); assertEquals(messageExtBatch.getBornTimestamp(), decodeMsgs.get(i).getBornTimestamp()); assertEquals(storeTimeStamp, decodeMsgs.get(i).getStoreTimestamp()); assertEquals(queueOffset++, decodeMsgs.get(i).getQueueOffset()); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/AppendPropCRCTest.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class AppendPropCRCTest { AppendMessageCallback callback; MessageExtEncoder encoder; CommitLog commitLog; @Before public void init() throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setMaxMessageSize(10 * 1024 * 1024); messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore"); messageStoreConfig.setStorePathCommitLog(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore" + File.separator + "commitlog"); messageStoreConfig.setForceVerifyPropCRC(true); messageStoreConfig.setEnabledAppendPropCRC(true); //too much reference DefaultMessageStore messageStore = new DefaultMessageStore(messageStoreConfig, null, null, new BrokerConfig(), new ConcurrentHashMap<>()); commitLog = new CommitLog(messageStore); encoder = new MessageExtEncoder(messageStoreConfig); callback = commitLog.new DefaultAppendMessageCallback(messageStoreConfig); } @After public void destroy() { UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "unitteststore")); } @Test public void testAppendMessageSucc() throws Exception { String topic = "test-topic"; int queue = 0; int msgNum = 10; int propertiesLen = 0; Message msg = new Message(); msg.setBody("body".getBytes()); msg.setTopic(topic); msg.setTags("abc"); msg.putUserProperty("a", "aaaaaaaa"); msg.putUserProperty("b", "bbbbbbbb"); msg.putUserProperty("c", "cccccccc"); msg.putUserProperty("d", "dddddddd"); msg.putUserProperty("e", "eeeeeeee"); msg.putUserProperty("f", "ffffffff"); MessageExtBrokerInner messageExtBrokerInner = new MessageExtBrokerInner(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(queue); messageExtBrokerInner.setBornTimestamp(System.currentTimeMillis()); messageExtBrokerInner.setBornHost(new InetSocketAddress("127.0.0.1", 123)); messageExtBrokerInner.setStoreHost(new InetSocketAddress("127.0.0.1", 124)); messageExtBrokerInner.setBody(msg.getBody()); messageExtBrokerInner.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); propertiesLen = messageExtBrokerInner.getPropertiesString().length(); ByteBuffer buff = ByteBuffer.allocate(1024 * 10); for (int i = 0; i < msgNum; i++) { encoder.encode(messageExtBrokerInner); messageExtBrokerInner.setEncodedBuff(encoder.getEncoderBuffer()); AppendMessageResult allresult = callback.doAppend(0, buff, 1024 * 10, messageExtBrokerInner, null); assertEquals(AppendMessageStatus.PUT_OK, allresult.getStatus()); } // Expected to pass when message is not modified buff.flip(); for (int i = 0; i < msgNum - 1; i++) { DispatchRequest request = commitLog.checkMessageAndReturnSize(buff, true, false); assertTrue(request.isSuccess()); } // Modify the properties of the last message and expect the verification to fail. int idx = buff.limit() - (propertiesLen / 2); buff.put(idx, (byte) (buff.get(idx) + 1)); DispatchRequest request = commitLog.checkMessageAndReturnSize(buff, true, false); assertFalse(request.isSuccess()); } @Test public void testAppendMessageBatchSucc() throws Exception { List messages = new ArrayList<>(); String topic = "test-topic"; int queue = 0; int propertiesLen = 0; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody("body".getBytes()); msg.setTopic(topic); msg.setTags("abc"); msg.putUserProperty("a", "aaaaaaaa"); msg.putUserProperty("b", "bbbbbbbb"); msg.putUserProperty("c", "cccccccc"); msg.putUserProperty("d", "dddddddd"); msg.putUserProperty("e", "eeeeeeee"); msg.putUserProperty("f", "ffffffff"); String propertiesString = MessageDecoder.messageProperties2String(msg.getProperties()); propertiesLen = propertiesString.length(); messages.add(msg); } MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setBornHost(new InetSocketAddress("127.0.0.1", 123)); messageExtBatch.setStoreHost(new InetSocketAddress("127.0.0.1", 124)); messageExtBatch.setBody(MessageDecoder.encodeMessages(messages)); PutMessageContext putMessageContext = new PutMessageContext(topic + "-" + queue); messageExtBatch.setEncodedBuff(encoder.encode(messageExtBatch, putMessageContext)); ByteBuffer buff = ByteBuffer.allocate(1024 * 10); //encounter end of file when append half of the data AppendMessageResult allresult = callback.doAppend(0, buff, 1024 * 10, messageExtBatch, putMessageContext); assertEquals(AppendMessageStatus.PUT_OK, allresult.getStatus()); assertEquals(0, allresult.getWroteOffset()); assertEquals(0, allresult.getLogicsOffset()); assertEquals(buff.position(), allresult.getWroteBytes()); assertEquals(messages.size(), allresult.getMsgNum()); Set msgIds = new HashSet<>(); for (String msgId : allresult.getMsgId().split(",")) { assertEquals(32, msgId.length()); msgIds.add(msgId); } assertEquals(messages.size(), msgIds.size()); List decodeMsgs = MessageDecoder.decodes((ByteBuffer) buff.flip()); assertEquals(decodeMsgs.size(), decodeMsgs.size()); long queueOffset = decodeMsgs.get(0).getQueueOffset(); long storeTimeStamp = decodeMsgs.get(0).getStoreTimestamp(); for (int i = 0; i < messages.size(); i++) { assertEquals(messages.get(i).getTopic(), decodeMsgs.get(i).getTopic()); assertEquals(new String(messages.get(i).getBody()), new String(decodeMsgs.get(i).getBody())); assertEquals(messages.get(i).getTags(), decodeMsgs.get(i).getTags()); assertEquals(messageExtBatch.getBornHostNameString(), decodeMsgs.get(i).getBornHostNameString()); assertEquals(messageExtBatch.getBornTimestamp(), decodeMsgs.get(i).getBornTimestamp()); assertEquals(storeTimeStamp, decodeMsgs.get(i).getStoreTimestamp()); assertEquals(queueOffset++, decodeMsgs.get(i).getQueueOffset()); } // Expected to pass when message is not modified buff.flip(); for (int i = 0; i < messages.size() - 1; i++) { DispatchRequest request = commitLog.checkMessageAndReturnSize(buff, true, false); assertTrue(request.isSuccess()); } // Modify the properties of the last message and expect the verification to fail. int idx = buff.limit() - (propertiesLen / 2); buff.put(idx, (byte) (buff.get(idx) + 1)); DispatchRequest request = commitLog.checkMessageAndReturnSize(buff, true, false); assertFalse(request.isSuccess()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.rocketmq.common.message.MessageDecoder.messageProperties2String; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertTrue; import java.io.File; import java.net.InetSocketAddress; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Before; import org.junit.Test; public class BatchPutMessageTest { private MessageStore messageStore; public final static Charset CHARSET_UTF8 = StandardCharsets.UTF_8; @Before public void init() throws Exception { messageStore = buildMessageStore(); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); } @After public void destroy() { messageStore.shutdown(); messageStore.destroy(); UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "putmessagesteststore")); } private MessageStore buildMessageStore() throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "putmessagesteststore"); messageStoreConfig.setStorePathCommitLog(System.getProperty("java.io.tmpdir") + File.separator + "putmessagesteststore" + File.separator + "commitlog"); messageStoreConfig.setHaListenPort(0); return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); } @Test public void testPutMessages() throws Exception { String batchPropK = "extraKey"; String batchPropV = "extraValue"; Map batchProp = new HashMap<>(1); batchProp.put(batchPropK, batchPropV); short batchPropLen = (short) messageProperties2String(batchProp).getBytes(MessageDecoder.CHARSET_UTF8).length; List messages = new ArrayList<>(); String topic = "batch-write-topic"; int queue = 0; int[] msgLengthArr = new int[11]; msgLengthArr[0] = 0; int j = 1; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody(("body" + i).getBytes()); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys(String.valueOf(System.currentTimeMillis())); messages.add(msg); String properties = messageProperties2String(msg.getProperties()); byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8); short propertiesLength = (short) propertiesBytes.length; final byte[] topicData = msg.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; msgLengthArr[j] = calMsgLength(msg.getBody().length, topicLength, propertiesLength) + msgLengthArr[j - 1]; j++; } byte[] batchMessageBody = MessageDecoder.encodeMessages(messages); MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBody(batchMessageBody); messageExtBatch.putUserProperty(batchPropK, batchPropV); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setStoreHost(new InetSocketAddress("127.0.0.1", 125)); messageExtBatch.setBornHost(new InetSocketAddress("127.0.0.1", 126)); PutMessageResult putMessageResult = messageStore.putMessages(messageExtBatch); assertThat(putMessageResult.isOk()).isTrue(); for (long i = 0; i < 10; i++) { final long index = i; Boolean exist = await().atMost(3, SECONDS).until(() -> { MessageExt messageExt = messageStore.lookMessageByOffset(msgLengthArr[(int) index]); if (messageExt == null) { return false; } GetMessageResult result = messageStore.getMessage("batch_write_group", topic, queue, index, 1024 * 1024, null); if (result == null) { return false; } boolean equals = GetMessageStatus.FOUND.equals(result.getStatus()); result.release(); return equals; }, item -> item); assertTrue(exist); } } @Test public void testPutIPv6HostMessages() throws Exception { List messages = new ArrayList<>(); String topic = "batch-write-topic"; int queue = 0; int[] msgLengthArr = new int[11]; msgLengthArr[0] = 0; int j = 1; for (int i = 0; i < 10; i++) { Message msg = new Message(); msg.setBody(("body" + i).getBytes()); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys(String.valueOf(System.currentTimeMillis())); messages.add(msg); String properties = messageProperties2String(msg.getProperties()); byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8); short propertiesLength = (short) propertiesBytes.length; final byte[] topicData = msg.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); final int topicLength = topicData.length; msgLengthArr[j] = calIPv6HostMsgLength(msg.getBody().length, topicLength, propertiesLength) + msgLengthArr[j - 1]; j++; } byte[] batchMessageBody = MessageDecoder.encodeMessages(messages); MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBody(batchMessageBody); messageExtBatch.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setSysFlag(0); messageExtBatch.setBornHostV6Flag(); messageExtBatch.setStoreHostAddressV6Flag(); messageExtBatch.setStoreHost(new InetSocketAddress("1050:0000:0000:0000:0005:0600:300c:326b", 125)); messageExtBatch.setBornHost(new InetSocketAddress("::1", 126)); PutMessageResult putMessageResult = messageStore.putMessages(messageExtBatch); assertThat(putMessageResult.isOk()).isTrue(); for (long i = 0; i < 10; i++) { final long index = i; Boolean exist = await().atMost(3, SECONDS).until(() -> { MessageExt messageExt = messageStore.lookMessageByOffset(msgLengthArr[(int) index]); if (messageExt == null) { return false; } GetMessageResult result = messageStore.getMessage("batch_write_group", topic, queue, index, 1024 * 1024, null); if (result == null) { return false; } boolean equals = GetMessageStatus.FOUND.equals(result.getStatus()); result.release(); return equals; }, item -> item); assertTrue(exist); } } private String generateKey(StringBuilder keyBuilder, MessageExt messageExt) { keyBuilder.setLength(0); keyBuilder.append(messageExt.getTopic()); keyBuilder.append('-'); keyBuilder.append(messageExt.getQueueId()); return keyBuilder.toString(); } private int calMsgLength(int bodyLength, int topicLength, int propertiesLength) { final int msgLen = 4 //TOTALSIZE + 4 //MAGICCODE + 4 //BODYCRC + 4 //QUEUEID + 4 //FLAG + 8 //QUEUEOFFSET + 8 //PHYSICALOFFSET + 4 //SYSFLAG + 8 //BORNTIMESTAMP + 8 //BORNHOST + 8 //STORETIMESTAMP + 8 //STOREHOSTADDRESS + 4 //RECONSUMETIMES + 8 //Prepared Transaction Offset + 4 + (bodyLength > 0 ? bodyLength : 0) //BODY + 1 + topicLength //TOPIC + 2 + (propertiesLength > 0 ? propertiesLength : 0) //propertiesLength + 0; return msgLen; } private int calIPv6HostMsgLength(int bodyLength, int topicLength, int propertiesLength) { final int msgLen = 4 //TOTALSIZE + 4 //MAGICCODE + 4 //BODYCRC + 4 //QUEUEID + 4 //FLAG + 8 //QUEUEOFFSET + 8 //PHYSICALOFFSET + 4 //SYSFLAG + 8 //BORNTIMESTAMP + 20 //BORNHOST + 8 //STORETIMESTAMP + 20 //STOREHOSTADDRESS + 4 //RECONSUMETIMES + 8 //Prepared Transaction Offset + 4 + (bodyLength > 0 ? bodyLength : 0) //BODY + 1 + topicLength //TOPIC + 2 + (propertiesLength > 0 ? propertiesLength : 0) //propertiesLength + 0; return msgLen; } private class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ConsumeQueueExtTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.util.Random; import org.apache.rocketmq.common.UtilAll; import org.junit.After; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class ConsumeQueueExtTest { private static final String TOPIC = "abc"; private static final int QUEUE_ID = 0; private static final String STORE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "unit_test_store"; private static final int BIT_MAP_LENGTH = 64; private static final int UNIT_SIZE_WITH_BIT_MAP = ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + BIT_MAP_LENGTH / Byte.SIZE; private static final int CQ_EXT_FILE_SIZE = 10 * UNIT_SIZE_WITH_BIT_MAP; private static final int UNIT_COUNT = 20; protected ConsumeQueueExt genExt() { return new ConsumeQueueExt( TOPIC, QUEUE_ID, STORE_PATH, CQ_EXT_FILE_SIZE, BIT_MAP_LENGTH ); } protected byte[] genBitMap(int bitMapLength) { byte[] bytes = new byte[bitMapLength / Byte.SIZE]; Random random = new Random(System.currentTimeMillis()); random.nextBytes(bytes); return bytes; } protected ConsumeQueueExt.CqExtUnit genUnit(boolean hasBitMap) { ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit(); cqExtUnit.setTagsCode(Math.abs((new Random(System.currentTimeMillis())).nextInt())); cqExtUnit.setMsgStoreTime(System.currentTimeMillis()); if (hasBitMap) { cqExtUnit.setFilterBitMap(genBitMap(BIT_MAP_LENGTH)); } return cqExtUnit; } protected void putSth(ConsumeQueueExt consumeQueueExt, boolean getAfterPut, boolean unitSameSize, int unitCount) { for (int i = 0; i < unitCount; i++) { ConsumeQueueExt.CqExtUnit putUnit = unitSameSize ? genUnit(true) : genUnit(i % 2 == 0); long addr = consumeQueueExt.put(putUnit); assertThat(addr).isLessThan(0); if (getAfterPut) { ConsumeQueueExt.CqExtUnit getUnit = consumeQueueExt.get(addr); assertThat(getUnit).isNotNull(); assertThat(putUnit).isEqualTo(getUnit); } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); assertThat(false).isTrue(); } } } @Test public void testPut() { ConsumeQueueExt consumeQueueExt = genExt(); try { putSth(consumeQueueExt, true, false, UNIT_COUNT); } finally { consumeQueueExt.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testGet() { ConsumeQueueExt consumeQueueExt = genExt(); putSth(consumeQueueExt, false, false, UNIT_COUNT); try { // from start. long addr = consumeQueueExt.decorate(0); ConsumeQueueExt.CqExtUnit unit = new ConsumeQueueExt.CqExtUnit(); while (true) { boolean ret = consumeQueueExt.get(addr, unit); if (!ret) { break; } assertThat(unit.getSize()).isGreaterThanOrEqualTo(ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE); addr += unit.getSize(); } } finally { consumeQueueExt.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testGet_invalidAddress() { ConsumeQueueExt consumeQueueExt = genExt(); putSth(consumeQueueExt, false, true, UNIT_COUNT); try { ConsumeQueueExt.CqExtUnit unit = consumeQueueExt.get(0); assertThat(unit).isNull(); long addr = (CQ_EXT_FILE_SIZE / UNIT_SIZE_WITH_BIT_MAP) * UNIT_SIZE_WITH_BIT_MAP; addr += UNIT_SIZE_WITH_BIT_MAP; unit = consumeQueueExt.get(addr); assertThat(unit).isNull(); } finally { consumeQueueExt.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testRecovery() { ConsumeQueueExt putCqExt = genExt(); putSth(putCqExt, false, true, UNIT_COUNT); ConsumeQueueExt loadCqExt = genExt(); loadCqExt.load(); loadCqExt.recover(); try { assertThat(loadCqExt.getMinAddress()).isEqualTo(Long.MIN_VALUE); // same unit size. int countPerFile = (CQ_EXT_FILE_SIZE - ConsumeQueueExt.END_BLANK_DATA_LENGTH) / UNIT_SIZE_WITH_BIT_MAP; int lastFileUnitCount = UNIT_COUNT % countPerFile; int fileCount = UNIT_COUNT / countPerFile + 1; if (lastFileUnitCount == 0) { fileCount -= 1; } if (lastFileUnitCount == 0) { assertThat(loadCqExt.unDecorate(loadCqExt.getMaxAddress()) % CQ_EXT_FILE_SIZE).isEqualTo(0); } else { assertThat(loadCqExt.unDecorate(loadCqExt.getMaxAddress())) .isEqualTo(lastFileUnitCount * UNIT_SIZE_WITH_BIT_MAP + (fileCount - 1) * CQ_EXT_FILE_SIZE); } } finally { putCqExt.destroy(); loadCqExt.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testTruncateByMinOffset() { ConsumeQueueExt consumeQueueExt = genExt(); putSth(consumeQueueExt, false, true, UNIT_COUNT * 2); try { // truncate first one file. long address = consumeQueueExt.decorate((long) (CQ_EXT_FILE_SIZE * 1.5)); long expectMinAddress = consumeQueueExt.decorate(CQ_EXT_FILE_SIZE); consumeQueueExt.truncateByMinAddress(address); long minAddress = consumeQueueExt.getMinAddress(); assertThat(expectMinAddress).isEqualTo(minAddress); } finally { consumeQueueExt.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testTruncateByMaxOffset() { ConsumeQueueExt consumeQueueExt = genExt(); putSth(consumeQueueExt, false, true, UNIT_COUNT * 2); try { // truncate, only first 3 files exist. long address = consumeQueueExt.decorate(CQ_EXT_FILE_SIZE * 2 + UNIT_SIZE_WITH_BIT_MAP); long expectMaxAddress = address + UNIT_SIZE_WITH_BIT_MAP; consumeQueueExt.truncateByMaxAddress(address); long maxAddress = consumeQueueExt.getMaxAddress(); assertThat(expectMaxAddress).isEqualTo(maxAddress); } finally { consumeQueueExt.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @After public void destroy() { UtilAll.deleteFile(new File(STORE_PATH)); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.ReferredIterator; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.awaitility.Awaitility; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; import org.mockito.Mockito; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; public class ConsumeQueueTest { private static final String MSG = "Once, there was a chance for me!"; private static final byte[] MSG_BODY = MSG.getBytes(); private static final String TOPIC = "abc"; private static final int QUEUE_ID = 0; private static final String STORE_PATH = System.getProperty("java.io.tmpdir") + File.separator + "unit_test_store"; private static final int COMMIT_LOG_FILE_SIZE = 1024 * 8; private static final int CQ_FILE_SIZE = 10 * 20; private static final int CQ_EXT_FILE_SIZE = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); private static SocketAddress bornHost; private static SocketAddress storeHost; static { try { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); } catch (UnknownHostException e) { e.printStackTrace(); } try { bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (UnknownHostException e) { e.printStackTrace(); } } public MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(TOPIC); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(MSG_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); for (int i = 0; i < 1; i++) { msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); } msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } public MessageExtBrokerInner buildIPv6HostMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(TOPIC); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(MSG_BODY); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); msg.setBornTimestamp(System.currentTimeMillis()); msg.setBornHost(new InetSocketAddress("1050:0000:0000:0000:0005:0600:300c:326b", 123)); msg.setStoreHost(new InetSocketAddress("::1", 124)); for (int i = 0; i < 1; i++) { msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); } msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } public MessageStoreConfig buildStoreConfig(int commitLogFileSize, int cqFileSize, boolean enableCqExt, int cqExtFileSize) { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(commitLogFileSize); messageStoreConfig.setMappedFileSizeConsumeQueue(cqFileSize); messageStoreConfig.setMappedFileSizeConsumeQueueExt(cqExtFileSize); messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(enableCqExt); messageStoreConfig.setHaListenPort(0); messageStoreConfig.setStorePathRootDir(STORE_PATH); messageStoreConfig.setStorePathCommitLog(STORE_PATH + File.separator + "commitlog"); return messageStoreConfig; } protected DefaultMessageStore gen() throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); BrokerConfig brokerConfig = new BrokerConfig(); DefaultMessageStore master = new DefaultMessageStore( messageStoreConfig, new BrokerStatsManager(brokerConfig.getBrokerClusterName(), brokerConfig.isEnableDetailStat()), new MessageArrivingListener() { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } , brokerConfig, new ConcurrentHashMap<>()); assertThat(master.load()).isTrue(); master.start(); return master; } protected DefaultMessageStore genForMultiQueue() throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); messageStoreConfig.setEnableLmq(true); messageStoreConfig.setEnableMultiDispatch(true); BrokerConfig brokerConfig = new BrokerConfig(); DefaultMessageStore master = new DefaultMessageStore( messageStoreConfig, new BrokerStatsManager(brokerConfig.getBrokerClusterName(), brokerConfig.isEnableDetailStat()), new MessageArrivingListener() { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } , brokerConfig, new ConcurrentHashMap<>()); assertThat(master.load()).isTrue(); master.start(); return master; } protected void putMsg(DefaultMessageStore master) { long totalMsgs = 200; for (long i = 0; i < totalMsgs; i++) { if (i < totalMsgs / 2) { master.putMessage(buildMessage()); } else { master.putMessage(buildIPv6HostMessage()); } } } protected void putMsgMultiQueue(DefaultMessageStore master) { for (long i = 0; i < 1; i++) { master.putMessage(buildMessageMultiQueue()); } } private MessageExtBrokerInner buildMessageMultiQueue() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(TOPIC); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(MSG_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); for (int i = 0; i < 1; i++) { MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, "%LMQ%123,%LMQ%456"); msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); } msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } protected void deleteDirectory(String rootPath) { File file = new File(rootPath); deleteFile(file); } protected void deleteFile(File file) { File[] subFiles = file.listFiles(); if (subFiles != null) { for (File sub : subFiles) { deleteFile(sub); } } file.delete(); } @Test public void testPutMessagePositionInfo_buildCQRepeatedly() throws Exception { DefaultMessageStore messageStore = null; try { messageStore = gen(); int totalMessages = 10; for (int i = 0; i < totalMessages; i++) { putMsg(messageStore); } // Wait consume queue build finish. final MessageStore store = messageStore; Awaitility.with().pollInterval(100, TimeUnit.MILLISECONDS).await().timeout(1, TimeUnit.MINUTES).until(() -> { return store.dispatchBehindBytes() == 0; }); ConsumeQueueInterface cq = messageStore.getConsumeQueueTable().get(TOPIC).get(QUEUE_ID); Method method = cq.getClass().getDeclaredMethod("putMessagePositionInfo", long.class, int.class, long.class, long.class); assertThat(method).isNotNull(); method.setAccessible(true); SelectMappedBufferResult result = messageStore.getCommitLog().getData(0); assertThat(result != null).isTrue(); DispatchRequest dispatchRequest = messageStore.getCommitLog().checkMessageAndReturnSize(result.getByteBuffer(), false, false); assertThat(cq).isNotNull(); Object dispatchResult = method.invoke(cq, dispatchRequest.getCommitLogOffset(), dispatchRequest.getMsgSize(), dispatchRequest.getTagsCode(), dispatchRequest.getConsumeQueueOffset()); assertThat(Boolean.parseBoolean(dispatchResult.toString())).isTrue(); } finally { if (messageStore != null) { messageStore.shutdown(); messageStore.destroy(); } deleteDirectory(STORE_PATH); } } @Test public void testPutMessagePositionInfoWrapper_MultiQueue() throws Exception { Assume.assumeFalse(MixAll.isWindows()); DefaultMessageStore messageStore = null; try { messageStore = genForMultiQueue(); int totalMessages = 10; for (int i = 0; i < totalMessages; i++) { putMsgMultiQueue(messageStore); } // Wait consume queue build finish. final MessageStore store = messageStore; Awaitility.with().pollInterval(100, TimeUnit.MILLISECONDS).await().timeout(1, TimeUnit.MINUTES).until(() -> { return store.dispatchBehindBytes() == 0; }); ConsumeQueueInterface cq = messageStore.getConsumeQueueTable().get(TOPIC).get(QUEUE_ID); Method method = ((ConsumeQueue) cq).getClass().getDeclaredMethod("putMessagePositionInfoWrapper", DispatchRequest.class); assertThat(method).isNotNull(); method.setAccessible(true); SelectMappedBufferResult result = messageStore.getCommitLog().getData(0); assertThat(result != null).isTrue(); DispatchRequest dispatchRequest = messageStore.getCommitLog().checkMessageAndReturnSize(result.getByteBuffer(), false, false); assertThat(cq).isNotNull(); Object dispatchResult = method.invoke(cq, dispatchRequest); ConsumeQueueInterface lmqCq1 = messageStore.getConsumeQueueTable().get("%LMQ%123").get(0); ConsumeQueueInterface lmqCq2 = messageStore.getConsumeQueueTable().get("%LMQ%456").get(0); assertThat(lmqCq1).isNotNull(); assertThat(lmqCq2).isNotNull(); } finally { if (messageStore != null) { messageStore.shutdown(); messageStore.destroy(); } deleteDirectory(STORE_PATH); } } @Test public void testPutMessagePositionInfoMultiQueue() throws Exception { DefaultMessageStore messageStore = null; try { messageStore = genForMultiQueue(); int totalMessages = 10; for (int i = 0; i < totalMessages; i++) { putMsgMultiQueue(messageStore); } // Wait consume queue build finish. final MessageStore store = messageStore; Awaitility.with().pollInterval(100, TimeUnit.MILLISECONDS).await().timeout(1, TimeUnit.MINUTES).until(() -> { return store.dispatchBehindBytes() == 0; }); ConsumeQueueInterface cq = messageStore.getConsumeQueueTable().get(TOPIC).get(QUEUE_ID); ConsumeQueueInterface lmqCq1 = messageStore.getConsumeQueueTable().get("%LMQ%123").get(0); ConsumeQueueInterface lmqCq2 = messageStore.getConsumeQueueTable().get("%LMQ%456").get(0); assertThat(cq).isNotNull(); assertThat(lmqCq1).isNotNull(); assertThat(lmqCq2).isNotNull(); } finally { if (messageStore != null) { messageStore.shutdown(); messageStore.destroy(); } deleteDirectory(STORE_PATH); } } @Test public void testConsumeQueueWithExtendData() { DefaultMessageStore master = null; try { master = gen(); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } master.getDispatcherList().addFirst(new CommitLogDispatcher() { @Override public void dispatch(DispatchRequest request) { runCount++; } private int runCount = 0; }); try { putMsg(master); final DefaultMessageStore master1 = master; ConsumeQueueInterface cq = await().atMost(3, SECONDS).until(() -> { ConcurrentMap map = master1.getConsumeQueueTable().get(TOPIC); if (map == null) { return null; } ConsumeQueueInterface anInterface = map.get(QUEUE_ID); return anInterface; }, item -> null != item); assertThat(cq).isNotNull(); ReferredIterator bufferResult = cq.iterateFrom(0); assertThat(bufferResult).isNotNull(); Assert.assertTrue(bufferResult.hasNext()); try { while (bufferResult.hasNext()) { CqUnit cqUnit = bufferResult.next(); Assert.assertNotNull(cqUnit); long phyOffset = cqUnit.getPos(); int size = cqUnit.getSize(); long tagsCode = cqUnit.getTagsCode(); assertThat(phyOffset).isGreaterThanOrEqualTo(0); assertThat(size).isGreaterThan(0); assertThat(tagsCode).isGreaterThan(0); ConsumeQueueExt.CqExtUnit cqExtUnit = cqUnit.getCqExtUnit(); assertThat(cqExtUnit).isNotNull(); assertThat(tagsCode).isEqualTo(cqExtUnit.getTagsCode()); assertThat(cqExtUnit.getSize()).isGreaterThan((short) 0); assertThat(cqExtUnit.getMsgStoreTime()).isGreaterThan(0); assertThat(cqExtUnit.getTagsCode()).isGreaterThan(0); } } finally { bufferResult.release(); } } finally { master.shutdown(); master.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testCorrectMinOffset() { String topic = "T1"; int queueId = 0; MessageStoreConfig storeConfig = new MessageStoreConfig(); File tmpDir = new File(System.getProperty("java.io.tmpdir"), "test_correct_min_offset"); tmpDir.deleteOnExit(); storeConfig.setStorePathRootDir(tmpDir.getAbsolutePath()); storeConfig.setEnableConsumeQueueExt(false); DefaultMessageStore messageStore = Mockito.mock(DefaultMessageStore.class); Mockito.when(messageStore.getMessageStoreConfig()).thenReturn(storeConfig); RunningFlags runningFlags = new RunningFlags(); Mockito.when(messageStore.getRunningFlags()).thenReturn(runningFlags); StoreCheckpoint storeCheckpoint = Mockito.mock(StoreCheckpoint.class); Mockito.when(messageStore.getStoreCheckpoint()).thenReturn(storeCheckpoint); ConsumeQueue consumeQueue = new ConsumeQueue(topic, queueId, storeConfig.getStorePathRootDir(), storeConfig.getMappedFileSizeConsumeQueue(), messageStore); int max = 10000; int messageSize = 100; for (int i = 0; i < max; ++i) { DispatchRequest dispatchRequest = new DispatchRequest(topic, queueId, messageSize * i, messageSize, 0, 0, i, null, null, 0, 0, null); consumeQueue.putMessagePositionInfoWrapper(dispatchRequest); } consumeQueue.setMinLogicOffset(0L); consumeQueue.correctMinOffset(0L); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); consumeQueue.setMinLogicOffset(100); consumeQueue.correctMinOffset(2000); Assert.assertEquals(20, consumeQueue.getMinOffsetInQueue()); consumeQueue.setMinLogicOffset((max - 1) * ConsumeQueue.CQ_STORE_UNIT_SIZE); consumeQueue.correctMinOffset(max * messageSize); Assert.assertEquals(max * ConsumeQueue.CQ_STORE_UNIT_SIZE, consumeQueue.getMinLogicOffset()); consumeQueue.setMinLogicOffset(max * ConsumeQueue.CQ_STORE_UNIT_SIZE); consumeQueue.correctMinOffset(max * messageSize); Assert.assertEquals(max * ConsumeQueue.CQ_STORE_UNIT_SIZE, consumeQueue.getMinLogicOffset()); consumeQueue.destroy(); } @Test public void testFillBankThenCorrectMinOffset() throws IOException { String topic = "T1"; int queueId = 0; MessageStoreConfig storeConfig = new MessageStoreConfig(); File tmpDir = new File(System.getProperty("java.io.tmpdir"), "testFillBankThenCorrectMinOffset"); FileUtils.deleteDirectory(tmpDir); storeConfig.setStorePathRootDir(tmpDir.getAbsolutePath()); storeConfig.setEnableConsumeQueueExt(false); DefaultMessageStore messageStore = Mockito.mock(DefaultMessageStore.class); Mockito.when(messageStore.getMessageStoreConfig()).thenReturn(storeConfig); RunningFlags runningFlags = new RunningFlags(); Mockito.when(messageStore.getRunningFlags()).thenReturn(runningFlags); StoreCheckpoint storeCheckpoint = Mockito.mock(StoreCheckpoint.class); Mockito.when(messageStore.getStoreCheckpoint()).thenReturn(storeCheckpoint); { ConsumeQueue consumeQueue = new ConsumeQueue(topic, queueId, storeConfig.getStorePathRootDir(), storeConfig.getMappedFileSizeConsumeQueue(), messageStore); Assert.assertTrue(consumeQueue.load()); consumeQueue.recover(); consumeQueue.initializeWithOffset(100, 100); Assert.assertEquals(100, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(100, consumeQueue.getMaxOffsetInQueue()); } { ConsumeQueue consumeQueue = new ConsumeQueue(topic, queueId, storeConfig.getStorePathRootDir(), storeConfig.getMappedFileSizeConsumeQueue(), messageStore); Assert.assertTrue(consumeQueue.load()); consumeQueue.recover(); consumeQueue.correctMinOffset(1L); Assert.assertEquals(100, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(100, consumeQueue.getMaxOffsetInQueue()); } // { // ConsumeQueue consumeQueue = new ConsumeQueue(topic, queueId, storeConfig.getStorePathRootDir(), // storeConfig.getMappedFileSizeConsumeQueue(), messageStore); // Assert.assertTrue(consumeQueue.load()); // consumeQueue.recover(); // consumeQueue.correctMinOffset(0L); // Assert.assertEquals(100, consumeQueue.getMinOffsetInQueue()); // Assert.assertEquals(100, consumeQueue.getMaxOffsetInQueue()); // } ConsumeQueue consumeQueue0 = new ConsumeQueue(topic, queueId, storeConfig.getStorePathRootDir(), storeConfig.getMappedFileSizeConsumeQueue(), messageStore); consumeQueue0.destroy(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.index.IndexFile; import org.apache.rocketmq.store.index.IndexService; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.ConsumeQueueStore; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.apache.rocketmq.common.message.MessageDecoder.CHARSET_UTF8; import static org.apache.rocketmq.store.ConsumeQueue.CQ_STORE_UNIT_SIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** * Test case for DefaultMessageStore.CleanCommitLogService and DefaultMessageStore.CleanConsumeQueueService */ public class DefaultMessageStoreCleanFilesTest { private DefaultMessageStore messageStore; private DefaultMessageStore.CleanCommitLogService cleanCommitLogService; private ConsumeQueueStore.CleanConsumeQueueService cleanConsumeQueueService; private SocketAddress bornHost; private SocketAddress storeHost; private String topic = "test"; private String keys = "hello"; private int queueId = 0; private int fileCountCommitLog = 55; // exactly one message per CommitLog file. private int msgCount = fileCountCommitLog; private int mappedFileSize = 128; private int fileReservedTime = 1; @Before public void init() throws Exception { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } @Test public void testIsSpaceFullFunctionEmpty2Full() throws Exception { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; // used to set disk-full flag double diskSpaceCleanForciblyRatio = 0.01D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); // build and put 55 messages, exactly one message per CommitLog file. buildAndPutMessagesToMessageStore(msgCount); MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); int fileCountConsumeQueue = getFileCountConsumeQueue(); MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); cleanCommitLogService.isSpaceFull(); assertEquals(1 << 4, messageStore.getRunningFlags().getFlagBits() & (1 << 4)); } @Test public void testIsSpaceFullMultiCommitLogStorePath() throws Exception { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; // used to set disk-full flag double diskSpaceCleanForciblyRatio = 0.01D; MessageStoreConfig config = genMessageStoreConfig(deleteWhen, diskMaxUsedSpaceRatio); String storePath = config.getStorePathCommitLog(); StringBuilder storePathBuilder = new StringBuilder(); for (int i = 0; i < 3; i++) { storePathBuilder.append(storePath).append(i).append(MixAll.MULTI_PATH_SPLITTER); } config.setStorePathCommitLog(storePathBuilder.toString()); String[] paths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); assertEquals(3, paths.length); initMessageStore(config, diskSpaceCleanForciblyRatio); // build and put 55 messages, exactly one message per CommitLog file. buildAndPutMessagesToMessageStore(msgCount); MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); int fileCountConsumeQueue = getFileCountConsumeQueue(); MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); cleanCommitLogService.isSpaceFull(); assertEquals(1 << 4, messageStore.getRunningFlags().getFlagBits() & (1 << 4)); } @Test public void testIsSpaceFullFunctionFull2Empty() throws Exception { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; //use to reset disk-full flag double diskSpaceCleanForciblyRatio = 0.999D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); //set disk full messageStore.getRunningFlags().getAndMakeDiskFull(); cleanCommitLogService.isSpaceFull(); assertEquals(0, messageStore.getRunningFlags().getFlagBits() & (1 << 4)); } @Test public void testDeleteExpiredFilesByTimeUp() throws Exception { String deleteWhen = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ""; // the max value of diskMaxUsedSpaceRatio int diskMaxUsedSpaceRatio = 99; // used to ensure that automatic file deletion is not triggered double diskSpaceCleanForciblyRatio = 0.999D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); // build and put 55 messages, exactly one message per CommitLog file. buildAndPutMessagesToMessageStore(msgCount); // undo comment out the code below, if want to debug this case rather than just run it. // Thread.sleep(1000 * 60 + 100); MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); int fileCountConsumeQueue = getFileCountConsumeQueue(); MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); int fileCountIndexFile = getFileCountIndexFile(); assertEquals(fileCountIndexFile, getIndexFileList().size()); int expireFileCount = 15; expireFiles(commitLogQueue, expireFileCount); // magic code 10 reference to MappedFileQueue#DELETE_FILES_BATCH_MAX for (int a = 1, fileCount = expireFileCount; a <= (int) Math.ceil((double) expireFileCount / 10); a++, fileCount -= 10) { cleanCommitLogService.run(); cleanConsumeQueueService.run(); int expectDeletedCount = fileCount >= 10 ? a * 10 : ((a - 1) * 10 + fileCount); assertEquals(fileCountCommitLog - expectDeletedCount, commitLogQueue.getMappedFiles().size()); int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) expectDeletedCount / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); int msgCountPerIndexFile = getMsgCountPerIndexFile(); int expectDeleteCountIndexFile = (int) Math.floor((double) expectDeletedCount / msgCountPerIndexFile); assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @Test public void testDeleteExpiredFilesBySpaceFull() throws Exception { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; // used to ensure that automatic file deletion is not triggered double diskSpaceCleanForciblyRatio = 0.999D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); // build and put 55 messages, exactly one message per CommitLog file. buildAndPutMessagesToMessageStore(msgCount); // undo comment out the code below, if want to debug this case rather than just run it. // Thread.sleep(1000 * 60 + 100); MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); int fileCountConsumeQueue = getFileCountConsumeQueue(); MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); int fileCountIndexFile = getFileCountIndexFile(); assertEquals(fileCountIndexFile, getIndexFileList().size()); int expireFileCount = 15; expireFiles(commitLogQueue, expireFileCount); // magic code 10 reference to MappedFileQueue#DELETE_FILES_BATCH_MAX for (int a = 1, fileCount = expireFileCount; a <= (int) Math.ceil((double) expireFileCount / 10); a++, fileCount -= 10) { cleanCommitLogService.run(); cleanConsumeQueueService.run(); int expectDeletedCount = fileCount >= 10 ? a * 10 : ((a - 1) * 10 + fileCount); assertEquals(fileCountCommitLog - expectDeletedCount, commitLogQueue.getMappedFiles().size()); int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) expectDeletedCount / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); int msgCountPerIndexFile = getMsgCountPerIndexFile(); int expectDeleteCountIndexFile = (int) Math.floor((double) expectDeletedCount / msgCountPerIndexFile); assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @Test public void testDeleteFilesImmediatelyBySpaceFull() throws Exception { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; // make sure to trigger the automatic file deletion feature double diskSpaceCleanForciblyRatio = 0.01D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); // build and put 55 messages, exactly one message per CommitLog file. buildAndPutMessagesToMessageStore(msgCount); // undo comment out the code below, if want to debug this case rather than just run it. // Thread.sleep(1000 * 60 + 100); MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); int fileCountConsumeQueue = getFileCountConsumeQueue(); MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); int fileCountIndexFile = getFileCountIndexFile(); assertEquals(fileCountIndexFile, getIndexFileList().size()); // In this case, there is no need to expire the files. // int expireFileCount = 15; // expireFiles(commitLogQueue, expireFileCount); // magic code 10 reference to MappedFileQueue#DELETE_FILES_BATCH_MAX for (int a = 1, fileCount = fileCountCommitLog; a <= (int) Math.ceil((double) fileCountCommitLog / 10) && fileCount >= 10; a++, fileCount -= 10) { cleanCommitLogService.run(); cleanConsumeQueueService.run(); assertEquals(fileCountCommitLog - 10 * a, commitLogQueue.getMappedFiles().size()); int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) (a * 10) / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); int msgCountPerIndexFile = getMsgCountPerIndexFile(); int expectDeleteCountIndexFile = (int) Math.floor((double) (a * 10) / msgCountPerIndexFile); assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @Test public void testDeleteExpiredFilesManually() throws Exception { String deleteWhen = "04"; // the max value of diskMaxUsedSpaceRatio int diskMaxUsedSpaceRatio = 99; // used to ensure that automatic file deletion is not triggered double diskSpaceCleanForciblyRatio = 0.999D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); messageStore.executeDeleteFilesManually(); // build and put 55 messages, exactly one message per CommitLog file. buildAndPutMessagesToMessageStore(msgCount); // undo comment out the code below, if want to debug this case rather than just run it. // Thread.sleep(1000 * 60 + 100); MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); int fileCountConsumeQueue = getFileCountConsumeQueue(); MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); int fileCountIndexFile = getFileCountIndexFile(); assertEquals(fileCountIndexFile, getIndexFileList().size()); int expireFileCount = 15; expireFiles(commitLogQueue, expireFileCount); // magic code 10 reference to MappedFileQueue#DELETE_FILES_BATCH_MAX for (int a = 1, fileCount = expireFileCount; a <= (int) Math.ceil((double) expireFileCount / 10); a++, fileCount -= 10) { cleanCommitLogService.run(); cleanConsumeQueueService.run(); int expectDeletedCount = fileCount >= 10 ? a * 10 : ((a - 1) * 10 + fileCount); assertEquals(fileCountCommitLog - expectDeletedCount, commitLogQueue.getMappedFiles().size()); int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) expectDeletedCount / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); int msgCountPerIndexFile = getMsgCountPerIndexFile(); int expectDeleteCountIndexFile = (int) Math.floor((double) (a * 10) / msgCountPerIndexFile); assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } private DefaultMessageStore.CleanCommitLogService getCleanCommitLogService() throws Exception { Field serviceField = messageStore.getClass().getDeclaredField("cleanCommitLogService"); serviceField.setAccessible(true); DefaultMessageStore.CleanCommitLogService cleanCommitLogService = (DefaultMessageStore.CleanCommitLogService) serviceField.get(messageStore); serviceField.setAccessible(false); return cleanCommitLogService; } private ConsumeQueueStore.CleanConsumeQueueService getCleanConsumeQueueService() throws Exception { Field serviceField = messageStore.getQueueStore().getClass().getDeclaredField("cleanConsumeQueueService"); serviceField.setAccessible(true); ConsumeQueueStore.CleanConsumeQueueService cleanConsumeQueueService = (ConsumeQueueStore.CleanConsumeQueueService) serviceField.get(messageStore.getQueueStore()); serviceField.setAccessible(false); return cleanConsumeQueueService; } private MappedFileQueue getMappedFileQueueConsumeQueue() throws Exception { ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueueTable().get(topic).get(queueId); Field queueField = consumeQueue.getClass().getDeclaredField("mappedFileQueue"); queueField.setAccessible(true); MappedFileQueue fileQueue = (MappedFileQueue) queueField.get(consumeQueue); queueField.setAccessible(false); return fileQueue; } private MappedFileQueue getMappedFileQueueCommitLog() throws Exception { CommitLog commitLog = messageStore.getCommitLog(); Field queueField = commitLog.getClass().getDeclaredField("mappedFileQueue"); queueField.setAccessible(true); MappedFileQueue fileQueue = (MappedFileQueue) queueField.get(commitLog); queueField.setAccessible(false); return fileQueue; } private ArrayList getIndexFileList() throws Exception { Field indexServiceField = messageStore.getClass().getDeclaredField("indexService"); indexServiceField.setAccessible(true); IndexService indexService = (IndexService) indexServiceField.get(messageStore); Field indexFileListField = indexService.getClass().getDeclaredField("indexFileList"); indexFileListField.setAccessible(true); ArrayList indexFileList = (ArrayList) indexFileListField.get(indexService); return indexFileList; } private int getFileCountConsumeQueue() { int countPerFile = getMsgCountPerConsumeQueueMappedFile(); double fileCount = (double) msgCount / countPerFile; return (int) Math.ceil(fileCount); } private int getFileCountIndexFile() { int countPerFile = getMsgCountPerIndexFile(); double fileCount = (double) msgCount / countPerFile; return (int) Math.ceil(fileCount); } private int getMsgCountPerConsumeQueueMappedFile() { int size = messageStore.getMessageStoreConfig().getMappedFileSizeConsumeQueue(); return size / CQ_STORE_UNIT_SIZE;// 7 in this case } private int getMsgCountPerIndexFile() { // 7 in this case return messageStore.getMessageStoreConfig().getMaxIndexNum() - 1; } private void buildAndPutMessagesToMessageStore(int msgCount) throws Exception { int msgLen = topic.getBytes(CHARSET_UTF8).length + 91; Map properties = new HashMap<>(4); properties.put(MessageConst.PROPERTY_KEYS, keys); String s = MessageDecoder.messageProperties2String(properties); int propertiesLen = s.getBytes(CHARSET_UTF8).length; int commitLogEndFileMinBlankLength = 4 + 4; int singleMsgBodyLen = mappedFileSize - msgLen - propertiesLen - commitLogEndFileMinBlankLength; for (int i = 0; i < msgCount; i++) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setBody(new byte[singleMsgBodyLen]); msg.setKeys(keys); msg.setQueueId(queueId); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); PutMessageResult result = messageStore.putMessage(msg); assertTrue(result != null && result.isOk()); } StoreTestUtil.waitCommitLogReput(messageStore); StoreTestUtil.flushConsumeQueue(messageStore); StoreTestUtil.flushConsumeIndex(messageStore); } private void expireFiles(MappedFileQueue commitLogQueue, int expireCount) { for (int i = 0; i < commitLogQueue.getMappedFiles().size(); i++) { MappedFile mappedFile = commitLogQueue.getMappedFiles().get(i); int reservedTime = fileReservedTime * 60 * 60 * 1000; if (i < expireCount) { boolean modified = mappedFile.getFile().setLastModified(System.currentTimeMillis() - reservedTime * 2); assertTrue(modified); } } } private void initMessageStore(String deleteWhen, int diskMaxUsedSpaceRatio, double diskSpaceCleanForciblyRatio) throws Exception { initMessageStore(genMessageStoreConfig(deleteWhen,diskMaxUsedSpaceRatio), diskSpaceCleanForciblyRatio); } private MessageStoreConfig genMessageStoreConfig(String deleteWhen, int diskMaxUsedSpaceRatio) { MessageStoreConfig messageStoreConfig = new MessageStoreConfigForTest(); messageStoreConfig.setMappedFileSizeCommitLog(mappedFileSize); messageStoreConfig.setMappedFileSizeConsumeQueue(mappedFileSize); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(8); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); // Invalidate DefaultMessageStore`s scheduled task of cleaning expired files. // work with the code 'Thread.sleep(1000 * 60 + 100)' behind. messageStoreConfig.setCleanResourceInterval(Integer.MAX_VALUE); messageStoreConfig.setFileReservedTime(fileReservedTime); messageStoreConfig.setDeleteWhen(deleteWhen); messageStoreConfig.setDiskMaxUsedSpaceRatio(diskMaxUsedSpaceRatio); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "DefaultMessageStoreCleanFilesTest-" + UUID.randomUUID(); String storePathCommitLog = storePathRootDir + File.separator + "commitlog"; messageStoreConfig.setStorePathRootDir(storePathRootDir); messageStoreConfig.setStorePathCommitLog(storePathCommitLog); return messageStoreConfig; } private void initMessageStore(MessageStoreConfig messageStoreConfig, double diskSpaceCleanForciblyRatio) throws Exception { messageStore = new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("test", true), new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); cleanCommitLogService = getCleanCommitLogService(); cleanConsumeQueueService = getCleanConsumeQueueService(); assertTrue(messageStore.load()); messageStore.start(); // partially mock a real obj cleanCommitLogService = spy(cleanCommitLogService); when(cleanCommitLogService.getDiskSpaceWarningLevelRatio()).thenReturn(diskSpaceCleanForciblyRatio); when(cleanCommitLogService.getDiskSpaceCleanForciblyRatio()).thenReturn(diskSpaceCleanForciblyRatio); putFiledBackToMessageStore(cleanCommitLogService); } private void putFiledBackToMessageStore(DefaultMessageStore.CleanCommitLogService cleanCommitLogService) throws Exception { Field cleanCommitLogServiceField = DefaultMessageStore.class.getDeclaredField("cleanCommitLogService"); cleanCommitLogServiceField.setAccessible(true); cleanCommitLogServiceField.set(messageStore, cleanCommitLogService); cleanCommitLogServiceField.setAccessible(false); } private class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } @After public void destroy() { messageStore.shutdown(); messageStore.destroy(); if (messageStore != null) { MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); File file = new File(messageStoreConfig.getStorePathRootDir()); UtilAll.deleteFile(file); } } private class MessageStoreConfigForTest extends MessageStoreConfig { @Override public int getDiskMaxUsedSpaceRatio() { try { Field diskMaxUsedSpaceRatioField = this.getClass().getSuperclass().getDeclaredField("diskMaxUsedSpaceRatio"); diskMaxUsedSpaceRatioField.setAccessible(true); int ratio = (int) diskMaxUsedSpaceRatioField.get(this); diskMaxUsedSpaceRatioField.setAccessible(false); return ratio; } catch (Exception ignored) { } return super.getDiskMaxUsedSpaceRatio(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultMessageStoreShutDownTest { private DefaultMessageStore messageStore; @Before public void init() throws Exception { DefaultMessageStore store = buildMessageStore(); boolean load = store.load(); assertTrue(load); store.start(); messageStore = spy(store); when(messageStore.dispatchBehindBytes()).thenReturn(100L); } @Test public void testDispatchBehindWhenShutdown() { messageStore.shutdown(); assertTrue(!messageStore.shutDownNormal); File file = new File(StorePathConfigHelper.getAbortFile(messageStore.getMessageStoreConfig().getStorePathRootDir())); assertTrue(file.exists()); } @After public void destroy() { messageStore.destroy(); File file = new File(messageStore.getMessageStoreConfig().getStorePathRootDir()); UtilAll.deleteFile(file); } public DefaultMessageStore buildMessageStore() throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setHaListenPort(0); String storeRootPath = System.getProperty("java.io.tmpdir") + File.separator + "store"; messageStoreConfig.setStorePathRootDir(storeRootPath); messageStoreConfig.setHaListenPort(0); return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), null, new BrokerConfig(), new ConcurrentHashMap<>()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import org.mockito.ArgumentCaptor; import com.google.common.collect.Sets; import java.io.File; import java.io.RandomAccessFile; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.OverlappingFileLockException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.assertj.core.util.Strings; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class DefaultMessageStoreTest { private final String storeMessage = "Once, there was a chance for me!"; private final String messageTopic = "FooBar"; private int queueTotal = 100; private AtomicInteger queueId = new AtomicInteger(0); private SocketAddress bornHost; private SocketAddress storeHost; private byte[] messageBody; private MessageStore messageStore; @Before public void init() throws Exception { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); messageStore = buildMessageStore(); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); } @Test(expected = OverlappingFileLockException.class) public void test_repeat_restart() throws Exception { queueTotal = 1; messageBody = storeMessage.getBytes(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "store"); messageStoreConfig.setHaListenPort(0); MessageStore master = new DefaultMessageStore(messageStoreConfig, null, new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); boolean load = master.load(); assertTrue(load); try { master.start(); master.start(); } finally { master.shutdown(); master.destroy(); } } @After public void destroy() { messageStore.shutdown(); messageStore.destroy(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); File file = new File(messageStoreConfig.getStorePathRootDir()); UtilAll.deleteFile(file); } private MessageStore buildMessageStore() throws Exception { return buildMessageStore(null); } private MessageStore buildMessageStore(String storePathRootDir) throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); messageStoreConfig.setHaListenPort(0); if (Strings.isNullOrEmpty(storePathRootDir)) { UUID uuid = UUID.randomUUID(); storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + uuid.toString(); } messageStoreConfig.setStorePathRootDir(storePathRootDir); return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); } @Test public void testWriteAndRead() { long ipv4HostMsgs = 10; long ipv6HostMsgs = 10; long totalMsgs = ipv4HostMsgs + ipv6HostMsgs; queueTotal = 1; messageBody = storeMessage.getBytes(); for (long i = 0; i < ipv4HostMsgs; i++) { messageStore.putMessage(buildMessage()); } for (long i = 0; i < ipv6HostMsgs; i++) { messageStore.putMessage(buildIPv6HostMessage()); } StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); for (long i = 0; i < totalMsgs; i++) { GetMessageResult result = messageStore.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); result.release(); } verifyThatMasterIsFunctional(totalMsgs, messageStore); } @Test public void testLookMessageByOffset_OffsetIsFirst() { final int totalCount = 10; int queueId = new Random().nextInt(10); String topic = "FooBar"; int firstOffset = 0; AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId); AppendMessageResult firstResult = appendMessageResultArray[0]; MessageExt messageExt = messageStore.lookMessageByOffset(firstResult.getWroteOffset()); MessageExt messageExt1 = getDefaultMessageStore().lookMessageByOffset(firstResult.getWroteOffset(), firstResult.getWroteBytes()); assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset)); assertThat(new String(messageExt1.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset)); } @Test public void testLookMessageByOffset_OffsetIsLast() { final int totalCount = 10; int queueId = new Random().nextInt(10); String topic = "FooBar"; AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId); int lastIndex = totalCount - 1; AppendMessageResult lastResult = appendMessageResultArray[lastIndex]; MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastResult.getWroteOffset(), lastResult.getWroteBytes()); assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, lastIndex)); } @Test public void testLookMessageByOffset_OffsetIsOutOfBound() { final int totalCount = 10; int queueId = new Random().nextInt(10); String topic = "FooBar"; AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId); long lastOffset = getMaxOffset(appendMessageResultArray); MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastOffset); assertThat(messageExt).isNull(); } @Test public void testGetOffsetInQueueByTime() { final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp()); CqUnit cqUnit = consumeQueue.get(offset); assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset()); assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes()); } } @Test public void testGetOffsetInQueueByTime_TimestampIsSkewing() { final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); int skewing = 2; ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing); CqUnit cqUnit = consumeQueue.get(offset); assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset()); assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes()); } } @Test public void testGetOffsetInQueueByTime_TimestampSkewingIsLarge() { final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); int skewing = 20000; ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing); CqUnit cqUnit = consumeQueue.get(offset); assertThat(cqUnit.getPos()).isEqualTo(appendMessageResults[0].getWroteOffset()); assertThat(cqUnit.getSize()).isEqualTo(appendMessageResults[0].getWroteBytes()); } } @Test public void testGetOffsetInQueueByTime_ConsumeQueueNotFound1() { final int totalCount = 10; int queueId = 0; int wrongQueueId = 1; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); long offset = messageStore.getOffsetInQueueByTime(topic, wrongQueueId, appendMessageResults[0].getStoreTimestamp()); assertThat(offset).isEqualTo(0); } @Test public void testGetOffsetInQueueByTime_ConsumeQueueNotFound2() { final int totalCount = 10; int queueId = 0; int wrongQueueId = 1; String topic = "FooBar"; putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, wrongQueueId, 0); assertThat(messageStoreTimeStamp).isEqualTo(-1); } @Test public void testGetOffsetInQueueByTime_ConsumeQueueOffsetNotExist() { final int totalCount = 10; int queueId = 0; int wrongQueueId = 1; String topic = "FooBar"; putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, wrongQueueId, -1); assertThat(messageStoreTimeStamp).isEqualTo(-1); } @Test public void testGetMessageStoreTimeStamp() { final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); int minOffsetInQueue = (int) consumeQueue.getMinOffsetInQueue(); for (int i = minOffsetInQueue; i < consumeQueue.getMaxOffsetInQueue(); i++) { long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, queueId, i); assertThat(messageStoreTimeStamp).isEqualTo(appendMessageResults[i].getStoreTimestamp()); } } @Test public void testGetStoreTime_ParamIsNull() { long storeTime = getStoreTime(null); assertThat(storeTime).isEqualTo(-1); } @Test public void testGetStoreTime_EverythingIsOk() { final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, queueId); for (int i = 0; i < totalCount; i++) { CqUnit cqUnit = consumeQueue.get(i); long storeTime = getStoreTime(cqUnit); assertThat(storeTime).isEqualTo(appendMessageResults[i].getStoreTimestamp()); } } @Test public void testGetStoreTime_PhyOffsetIsLessThanCommitLogMinOffset() { long phyOffset = -10; int size = 138; CqUnit cqUnit = new CqUnit(0, phyOffset, size, 0); long storeTime = getStoreTime(cqUnit); assertThat(storeTime).isEqualTo(-1); } @Test public void testPutMessage_whenMessagePropertyIsTooLong() throws ConsumeQueueException { String topicName = "messagePropertyIsTooLongTest"; MessageExtBrokerInner illegalMessage = buildSpecifyLengthPropertyMessage("123".getBytes(StandardCharsets.UTF_8), topicName, Short.MAX_VALUE + 1); assertEquals(messageStore.putMessage(illegalMessage).getPutMessageStatus(), PutMessageStatus.PROPERTIES_SIZE_EXCEEDED); assertEquals(0L, messageStore.getQueueStore().getMaxOffset(topicName, 0).longValue()); MessageExtBrokerInner normalMessage = buildSpecifyLengthPropertyMessage("123".getBytes(StandardCharsets.UTF_8), topicName, 100); assertEquals(messageStore.putMessage(normalMessage).getPutMessageStatus(), PutMessageStatus.PUT_OK); assertEquals(1L, messageStore.getQueueStore().getMaxOffset(topicName, 0).longValue()); } private DefaultMessageStore getDefaultMessageStore() { return (DefaultMessageStore) this.messageStore; } private AppendMessageResult[] putMessages(int totalCount, String topic, int queueId) { return putMessages(totalCount, topic, queueId, false); } private AppendMessageResult[] putMessages(int totalCount, String topic, int queueId, boolean interval) { AppendMessageResult[] appendMessageResultArray = new AppendMessageResult[totalCount]; for (int i = 0; i < totalCount; i++) { String messageBody = buildMessageBodyByOffset(storeMessage, i); MessageExtBrokerInner msgInner = i < totalCount / 2 ? buildMessage(messageBody.getBytes(), topic) : buildIPv6HostMessage(messageBody.getBytes(), topic); msgInner.setQueueId(queueId); PutMessageResult result = messageStore.putMessage(msgInner); appendMessageResultArray[i] = result.getAppendMessageResult(); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); if (interval) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException("Thread sleep ERROR"); } } } return appendMessageResultArray; } private long getMaxOffset(AppendMessageResult[] appendMessageResultArray) { if (appendMessageResultArray == null) { return 0; } AppendMessageResult last = appendMessageResultArray[appendMessageResultArray.length - 1]; return last.getWroteOffset() + last.getWroteBytes(); } private String buildMessageBodyByOffset(String message, long i) { return String.format("%s offset %d", message, i); } private long getStoreTime(CqUnit cqUnit) { try { Class abstractConsumeQueueStore = getDefaultMessageStore().getQueueStore().getClass().getSuperclass(); Method getStoreTime = abstractConsumeQueueStore.getDeclaredMethod("getStoreTime", CqUnit.class); getStoreTime.setAccessible(true); return (long) getStoreTime.invoke(getDefaultMessageStore().getQueueStore(), cqUnit); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } private MessageExtBrokerInner buildMessage(byte[] messageBody, String topic) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } private MessageExtBrokerInner buildSpecifyLengthPropertyMessage(byte[] messageBody, String topic, int length) { StringBuilder stringBuilder = new StringBuilder(); Random random = new Random(); for (int i = 0; i < length; i++) { stringBuilder.append(random.nextInt(10)); } MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.putUserProperty("test", stringBuilder.toString()); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setQueueId(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } private MessageExtBrokerInner buildIPv6HostMessage(byte[] messageBody, String topic) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); msg.setBornTimestamp(System.currentTimeMillis()); try { msg.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } try { msg.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } return msg; } private MessageExtBrokerInner buildMessage() { return buildMessage(messageBody, messageTopic); } public MessageExtBatch buildMessageBatch(MessageBatch msgBatch) { MessageExtBatch msgExtBatch = new MessageExtBatch(); msgExtBatch.setTopic(messageTopic); msgExtBatch.setTags("TAG1"); msgExtBatch.setKeys("Hello"); msgExtBatch.setBody(msgBatch.getBody()); msgExtBatch.setKeys(String.valueOf(System.currentTimeMillis())); msgExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msgExtBatch.setSysFlag(0); msgExtBatch.setBornTimestamp(System.currentTimeMillis()); msgExtBatch.setStoreHost(storeHost); msgExtBatch.setBornHost(bornHost); return msgExtBatch; } @Test public void testGroupCommit() throws Exception { long totalMsgs = 10; queueTotal = 1; messageBody = storeMessage.getBytes(); for (long i = 0; i < totalMsgs; i++) { messageStore.putMessage(buildMessage()); } for (long i = 0; i < totalMsgs; i++) { GetMessageResult result = messageStore.getMessage("GROUP_A", "TOPIC_A", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); result.release(); } verifyThatMasterIsFunctional(totalMsgs, messageStore); } @Test public void testMaxOffset() throws InterruptedException, ConsumeQueueException { int firstBatchMessages = 3; int queueId = 0; messageBody = storeMessage.getBytes(); assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(0); for (int i = 0; i < firstBatchMessages; i++) { final MessageExtBrokerInner msg = buildMessage(); msg.setQueueId(queueId); messageStore.putMessage(msg); } while (messageStore.dispatchBehindBytes() != 0) { TimeUnit.MILLISECONDS.sleep(1); } assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(firstBatchMessages); // Disable the dispatcher messageStore.getDispatcherList().clear(); int secondBatchMessages = 2; for (int i = 0; i < secondBatchMessages; i++) { final MessageExtBrokerInner msg = buildMessage(); msg.setQueueId(queueId); messageStore.putMessage(msg); } assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(firstBatchMessages); assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId, true)).isEqualTo(firstBatchMessages); assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId, false)).isEqualTo(firstBatchMessages + secondBatchMessages); } private MessageExtBrokerInner buildIPv6HostMessage() { return buildIPv6HostMessage(messageBody, "FooBar"); } private void verifyThatMasterIsFunctional(long totalMsgs, MessageStore master) { for (long i = 0; i < totalMsgs; i++) { master.putMessage(buildMessage()); } StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); for (long i = 0; i < totalMsgs; i++) { GetMessageResult result = master.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); result.release(); } } @Test public void testPullSize() throws Exception { String topic = "pullSizeTopic"; for (int i = 0; i < 32; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } // wait for consume queue build // the sleep time should be great than consume queue flush interval //Thread.sleep(100); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); String group = "simple"; GetMessageResult getMessageResult32 = messageStore.getMessage(group, topic, 0, 0, 32, null); assertThat(getMessageResult32.getMessageBufferList().size()).isEqualTo(32); getMessageResult32.release(); GetMessageResult getMessageResult20 = messageStore.getMessage(group, topic, 0, 0, 20, null); assertThat(getMessageResult20.getMessageBufferList().size()).isEqualTo(20); getMessageResult20.release(); GetMessageResult getMessageResult45 = messageStore.getMessage(group, topic, 0, 0, 10, null); assertThat(getMessageResult45.getMessageBufferList().size()).isEqualTo(10); getMessageResult45.release(); } @Test public void testRecover() throws Exception { String topic = "recoverTopic"; messageBody = storeMessage.getBytes(); for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } // Thread.sleep(100);//wait for build consumer queue StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); long maxPhyOffset = messageStore.getMaxPhyOffset(); long maxCqOffset = messageStore.getMaxOffsetInQueue(topic, 0); //1.just reboot messageStore.shutdown(); String storeRootDir = ((DefaultMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir(); messageStore = buildMessageStore(storeRootDir); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); assertTrue(maxPhyOffset == messageStore.getMaxPhyOffset()); assertTrue(maxCqOffset == messageStore.getMaxOffsetInQueue(topic, 0)); //2.damage commit-log and reboot normal for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } //Thread.sleep(100); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); long secondLastPhyOffset = messageStore.getMaxPhyOffset(); long secondLastCqOffset = messageStore.getMaxOffsetInQueue(topic, 0); MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); messageStore.shutdown(); //damage last message damageCommitLog((DefaultMessageStore) messageStore, secondLastPhyOffset); //reboot messageStore = buildMessageStore(storeRootDir); load = messageStore.load(); assertTrue(load); messageStore.start(); assertTrue(secondLastPhyOffset == messageStore.getMaxPhyOffset()); assertTrue(secondLastCqOffset == messageStore.getMaxOffsetInQueue(topic, 0)); //3.damage commitlog and reboot abnormal for (int i = 0; i < 100; i++) { messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } //Thread.sleep(100); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); secondLastPhyOffset = messageStore.getMaxPhyOffset(); secondLastCqOffset = messageStore.getMaxOffsetInQueue(topic, 0); messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); messageStore.shutdown(); //damage last message damageCommitLog((DefaultMessageStore) messageStore, secondLastPhyOffset); //add abort file String fileName = StorePathConfigHelper.getAbortFile(((DefaultMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir()); File file = new File(fileName); UtilAll.ensureDirOK(file.getParent()); file.createNewFile(); messageStore = buildMessageStore(storeRootDir); load = messageStore.load(); assertTrue(load); messageStore.start(); assertTrue(secondLastPhyOffset == messageStore.getMaxPhyOffset()); assertTrue(secondLastCqOffset == messageStore.getMaxOffsetInQueue(topic, 0)); //message write again for (int i = 0; i < 100; i++) { messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } } @Test public void testStorePathOK() { if (messageStore instanceof DefaultMessageStore) { assertTrue(fileExists(((DefaultMessageStore) messageStore).getStorePathPhysic())); assertTrue(fileExists(((DefaultMessageStore) messageStore).getStorePathLogic())); } } private boolean fileExists(String path) { if (path != null) { File f = new File(path); return f.exists(); } return false; } private void damageCommitLog(DefaultMessageStore store, long offset) throws Exception { assertThat(store).isNotNull(); MessageStoreConfig messageStoreConfig = store.getMessageStoreConfig(); File file = new File(messageStoreConfig.getStorePathCommitLog() + File.separator + "00000000000000000000"); try (RandomAccessFile raf = new RandomAccessFile(file, "rw"); FileChannel fileChannel = raf.getChannel()) { MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 10); int bodyLen = mappedByteBuffer.getInt((int) offset + 84); int topicLenIndex = (int) offset + 84 + bodyLen + 4; mappedByteBuffer.position(topicLenIndex); mappedByteBuffer.putInt(0); mappedByteBuffer.putInt(0); mappedByteBuffer.putInt(0); mappedByteBuffer.putInt(0); mappedByteBuffer.force(); fileChannel.force(true); } } @Test public void testPutMsgExceedsMaxLength() { messageBody = new byte[4 * 1024 * 1024 + 1]; MessageExtBrokerInner msg = buildMessage(); PutMessageResult result = messageStore.putMessage(msg); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.MESSAGE_ILLEGAL); } @Test public void testPutMsgBatchExceedsMaxLength() { messageBody = new byte[4 * 1024 * 1024 + 1]; MessageExtBrokerInner msg1 = buildMessage(); MessageExtBrokerInner msg2 = buildMessage(); MessageExtBrokerInner msg3 = buildMessage(); MessageBatch msgBatch = MessageBatch.generateFromList(Arrays.asList(msg1, msg2, msg3)); msgBatch.setBody(msgBatch.encode()); MessageExtBatch msgExtBatch = buildMessageBatch(msgBatch); try { PutMessageResult result = this.messageStore.putMessages(msgExtBatch); } catch (Exception e) { assertThat(e.getMessage()).contains("message body size exceeded"); } } @Test public void testPutMsgWhenReplicasNotEnough() { MessageStoreConfig messageStoreConfig = ((DefaultMessageStore) this.messageStore).getMessageStoreConfig(); messageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER); messageStoreConfig.setTotalReplicas(2); messageStoreConfig.setInSyncReplicas(2); messageStoreConfig.setEnableAutoInSyncReplicas(false); ((DefaultMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(true); this.messageStore.setAliveReplicaNumInGroup(1); MessageExtBrokerInner msg = buildMessage(); PutMessageResult result = this.messageStore.putMessage(msg); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH); ((DefaultMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(false); } @Test public void testPutMsgWhenAdaptiveDegradation() { MessageStoreConfig messageStoreConfig = ((DefaultMessageStore) this.messageStore).getMessageStoreConfig(); messageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER); messageStoreConfig.setTotalReplicas(2); messageStoreConfig.setInSyncReplicas(2); messageStoreConfig.setEnableAutoInSyncReplicas(true); ((DefaultMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(true); this.messageStore.setAliveReplicaNumInGroup(1); MessageExtBrokerInner msg = buildMessage(); PutMessageResult result = this.messageStore.putMessage(msg); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); ((DefaultMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(false); messageStoreConfig.setEnableAutoInSyncReplicas(false); } @Test public void testGetBulkCommitLogData() { DefaultMessageStore defaultMessageStore = (DefaultMessageStore) messageStore; messageBody = new byte[2 * 1024 * 1024]; for (int i = 0; i < 10; i++) { MessageExtBrokerInner msg1 = buildMessage(); messageStore.putMessage(msg1); } System.out.printf("%d%n", defaultMessageStore.getMaxPhyOffset()); List bufferResultList = defaultMessageStore.getBulkCommitLogData(0, (int) defaultMessageStore.getMaxPhyOffset()); List msgList = new ArrayList<>(); for (SelectMappedBufferResult bufferResult : bufferResultList) { msgList.addAll(MessageDecoder.decodesBatch(bufferResult.getByteBuffer(), true, false, false)); bufferResult.release(); } assertThat(msgList.size()).isEqualTo(10); } @Test public void testPutLongMessage() throws Exception { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); CommitLog commitLog = ((DefaultMessageStore) messageStore).getCommitLog(); MessageStoreConfig messageStoreConfig = ((DefaultMessageStore) messageStore).getMessageStoreConfig(); MessageExtEncoder.PutMessageThreadLocal putMessageThreadLocal = commitLog.getPutMessageThreadLocal().get(); //body size, topic size, properties size exactly equal to max size messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]); messageExtBrokerInner.setTopic(new String(new byte[127])); messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE])); PutMessageResult encodeResult1 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertTrue(encodeResult1 == null); //body size exactly more than max message body size messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize() + 1]); PutMessageResult encodeResult2 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertTrue(encodeResult2.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL); //body size exactly equal to max message size messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize() + 64 * 1024]); PutMessageResult encodeResult3 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertTrue(encodeResult3.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL); //message properties length more than properties maxSize messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]); messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE + 1])); PutMessageResult encodeResult4 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertTrue(encodeResult4.getPutMessageStatus() == PutMessageStatus.PROPERTIES_SIZE_EXCEEDED); //message length more than buffer length capacity messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]); messageExtBrokerInner.setTopic(new String(new byte[Short.MAX_VALUE])); messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE])); PutMessageResult encodeResult5 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertTrue(encodeResult5.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL); } @Test public void testDynamicMaxMessageSize() { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); MessageStoreConfig messageStoreConfig = ((DefaultMessageStore) messageStore).getMessageStoreConfig(); int originMaxMessageSize = messageStoreConfig.getMaxMessageSize(); messageExtBrokerInner.setBody(new byte[originMaxMessageSize + 10]); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); assertTrue(putMessageResult.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL); int newMaxMessageSize = originMaxMessageSize + 10; messageStoreConfig.setMaxMessageSize(newMaxMessageSize); putMessageResult = messageStore.putMessage(messageExtBrokerInner); assertTrue(putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK); messageStoreConfig.setMaxMessageSize(10); putMessageResult = messageStore.putMessage(messageExtBrokerInner); assertTrue(putMessageResult.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL); messageStoreConfig.setMaxMessageSize(originMaxMessageSize); } @Test public void testDeleteTopics() { MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); ConcurrentMap> consumeQueueTable = ((DefaultMessageStore) messageStore).getConsumeQueueTable(); for (int i = 0; i < 10; i++) { ConcurrentMap cqTable = new ConcurrentHashMap<>(); String topicName = "topic-" + i; for (int j = 0; j < 4; j++) { ConsumeQueue consumeQueue = new ConsumeQueue(topicName, j, messageStoreConfig.getStorePathRootDir(), messageStoreConfig.getMappedFileSizeConsumeQueue(), (DefaultMessageStore) messageStore); cqTable.put(j, consumeQueue); } consumeQueueTable.put(topicName, cqTable); } Assert.assertEquals(consumeQueueTable.size(), 10); HashSet resultSet = Sets.newHashSet("topic-3", "topic-5"); messageStore.deleteTopics(Sets.difference(consumeQueueTable.keySet(), resultSet)); Assert.assertEquals(consumeQueueTable.size(), 2); Assert.assertEquals(resultSet, consumeQueueTable.keySet()); } @Test public void testCleanUnusedTopic() { MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); ConcurrentMap> consumeQueueTable = ((DefaultMessageStore) messageStore).getConsumeQueueTable(); for (int i = 0; i < 10; i++) { ConcurrentMap cqTable = new ConcurrentHashMap<>(); String topicName = "topic-" + i; for (int j = 0; j < 4; j++) { ConsumeQueue consumeQueue = new ConsumeQueue(topicName, j, messageStoreConfig.getStorePathRootDir(), messageStoreConfig.getMappedFileSizeConsumeQueue(), (DefaultMessageStore) messageStore); cqTable.put(j, consumeQueue); } consumeQueueTable.put(topicName, cqTable); } Assert.assertEquals(consumeQueueTable.size(), 10); HashSet resultSet = Sets.newHashSet("topic-3", "topic-5"); messageStore.cleanUnusedTopic(resultSet); Assert.assertEquals(consumeQueueTable.size(), 2); Assert.assertEquals(resultSet, consumeQueueTable.keySet()); } @Test public void testChangeStoreConfig() { Properties properties = new Properties(); properties.setProperty("enableBatchPush", "true"); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); MixAll.properties2Object(properties, messageStoreConfig); assertThat(messageStoreConfig.isEnableBatchPush()).isTrue(); } @Test public void testRecoverWithRocksDBOffsets() throws Exception { // Test that recovery process considers RocksDB offsets when IndexRocksDBEnable or TransRocksDBEnable is enabled UUID uuid = UUID.randomUUID(); String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-recover-test-" + uuid.toString(); try { // Test case 1: IndexRocksDBEnable enabled with valid offset // index offset: 500L, expected: min(consumeQueueOffset, 500L) testRecoverWithRocksDBOffset(storePathRootDir + "-1", true, false, 500L, null); // Test case 2: TransRocksDBEnable enabled with valid offset // trans offset: 600L, expected: min(consumeQueueOffset, 600L) testRecoverWithRocksDBOffset(storePathRootDir + "-2", false, true, null, 600L); // Test case 3: Both enabled, take minimum value // index offset: 500L, trans offset: 300L, expected: min(consumeQueueOffset, 500L, 300L) testRecoverWithRocksDBOffset(storePathRootDir + "-3", true, true, 500L, 300L); } finally { // Clean up all test directories for (int i = 1; i <= 3; i++) { UtilAll.deleteFile(new File(storePathRootDir + "-" + i)); } } } private void testRecoverWithRocksDBOffset(String storePathRootDir, boolean indexEnable, boolean transEnable, Long indexOffset, Long transOffset) throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setHaListenPort(0); messageStoreConfig.setStorePathRootDir(storePathRootDir); messageStoreConfig.setIndexRocksDBEnable(indexEnable); messageStoreConfig.setTransRocksDBEnable(transEnable); DefaultMessageStore store = new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("test", true), new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); // Get the actual consumeQueueStore dispatchFromPhyOffset before loading (normal recovery) long consumeQueueOffset = store.getQueueStore().getDispatchFromPhyOffset(true); // Calculate expected value: min of consumeQueueOffset and RocksDB offsets long calculatedExpected = consumeQueueOffset; if (indexEnable && indexOffset != null && indexOffset > 0) { calculatedExpected = Math.min(calculatedExpected, indexOffset); } if (transEnable && transOffset != null && transOffset > 0) { calculatedExpected = Math.min(calculatedExpected, transOffset); } // Mock messageRocksDBStorage java.lang.reflect.Field field = DefaultMessageStore.class.getDeclaredField("messageRocksDBStorage"); field.setAccessible(true); org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage mockStorage = mock(org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage.class); field.set(store, mockStorage); // Spy commitLog to verify invocation and capture the dispatchFromPhyOffset value java.lang.reflect.Field commitLogField = DefaultMessageStore.class.getDeclaredField("commitLog"); commitLogField.setAccessible(true); CommitLog commitLog = (CommitLog) commitLogField.get(store); CommitLog spyCommitLog = spy(commitLog); commitLogField.set(store, spyCommitLog); // Use ArgumentCaptor to capture the dispatchFromPhyOffset value ArgumentCaptor offsetCaptor = ArgumentCaptor.forClass(Long.class); // Load store, which will call recover method boolean loadResult = store.load(); assertTrue(loadResult); // Verify recoverNormally or recoverAbnormally is called and capture the argument // Since it's a new store (no abort file), it should call recoverNormally verify(spyCommitLog, atLeastOnce()).recoverNormally(offsetCaptor.capture()); // Verify the dispatchFromPhyOffset value is correct (should be the minimum) Long actualDispatchFromPhyOffset = offsetCaptor.getValue(); assertThat(actualDispatchFromPhyOffset).isEqualTo(calculatedExpected); // Clean up resources store.shutdown(); store.destroy(); } private class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/FlushDiskWatcherTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.store.CommitLog.GroupCommitRequest; import org.junit.Assert; import org.junit.Test; import java.util.LinkedList; import java.util.List; public class FlushDiskWatcherTest { private final long timeoutMill = 5000; @Test public void testTimeout() throws Exception { FlushDiskWatcher flushDiskWatcher = new FlushDiskWatcher(); flushDiskWatcher.setDaemon(true); flushDiskWatcher.start(); int count = 100; List requestList = new LinkedList<>(); for (int i = 0; i < count; i++) { GroupCommitRequest groupCommitRequest = new GroupCommitRequest(0, timeoutMill); requestList.add(groupCommitRequest); flushDiskWatcher.add(groupCommitRequest); } Thread.sleep(2 * timeoutMill); for (GroupCommitRequest request : requestList) { request.wakeupCustomer(PutMessageStatus.PUT_OK); } for (GroupCommitRequest request : requestList) { Assert.assertTrue(request.future().isDone()); Assert.assertEquals(request.future().get(), PutMessageStatus.FLUSH_DISK_TIMEOUT); } Assert.assertEquals(flushDiskWatcher.queueSize(), 0); flushDiskWatcher.shutdown(); } @Test public void testWatcher() throws Exception { FlushDiskWatcher flushDiskWatcher = new FlushDiskWatcher(); flushDiskWatcher.setDaemon(true); flushDiskWatcher.start(); int count = 100; List requestList = new LinkedList<>(); for (int i = 0; i < count; i++) { GroupCommitRequest groupCommitRequest = new GroupCommitRequest(0, timeoutMill); requestList.add(groupCommitRequest); flushDiskWatcher.add(groupCommitRequest); groupCommitRequest.wakeupCustomer(PutMessageStatus.PUT_OK); } Thread.sleep((timeoutMill << 20) / 1000000); for (GroupCommitRequest request : requestList) { Assert.assertTrue(request.future().isDone()); Assert.assertEquals(request.future().get(), PutMessageStatus.PUT_OK); } Assert.assertEquals(flushDiskWatcher.queueSize(), 0); flushDiskWatcher.shutdown(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/GetMessageResultTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.junit.Assert; import org.junit.Test; public class GetMessageResultTest { @Test public void testAddMessage() { GetMessageResult getMessageResult = new GetMessageResult(); SelectMappedBufferResult mappedBufferResult1 = new SelectMappedBufferResult(0, null, 4 * 1024, null); getMessageResult.addMessage(mappedBufferResult1); SelectMappedBufferResult mappedBufferResult2 = new SelectMappedBufferResult(0, null, 2 * 4 * 1024, null); getMessageResult.addMessage(mappedBufferResult2, 0); SelectMappedBufferResult mappedBufferResult3 = new SelectMappedBufferResult(0, null, 4 * 4 * 1024, null); getMessageResult.addMessage(mappedBufferResult3, 0, 2); Assert.assertEquals(getMessageResult.getMessageQueueOffset().size(), 2); Assert.assertEquals(getMessageResult.getMessageBufferList().size(), 3); Assert.assertEquals(getMessageResult.getMessageMapedList().size(), 3); Assert.assertEquals(getMessageResult.getMessageCount(), 4); Assert.assertEquals(getMessageResult.getMsgCount4Commercial(), 1 + 2 + 4); Assert.assertEquals(getMessageResult.getBufferTotalSize(), (1 + 2 + 4) * 4 * 1024); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/HATest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.time.Duration; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.HAConnectionState; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class HATest { private final String storeMessage = "Once, there was a chance for me!"; private int queueTotal = 100; private AtomicInteger queueId = new AtomicInteger(0); private SocketAddress bornHost; private SocketAddress storeHost; private byte[] messageBody; private MessageStore messageStore; private MessageStore slaveMessageStore; private MessageStoreConfig masterMessageStoreConfig; private MessageStoreConfig slaveStoreConfig; private BrokerStatsManager brokerStatsManager = new BrokerStatsManager("simpleTest", true); private String storePathRootParentDir = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID(); private String storePathRootDir = storePathRootParentDir + File.separator + "store"; @Before public void init() throws Exception { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); masterMessageStoreConfig = new MessageStoreConfig(); masterMessageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER); masterMessageStoreConfig.setStorePathRootDir(storePathRootDir + File.separator + "master"); masterMessageStoreConfig.setStorePathCommitLog(storePathRootDir + File.separator + "master" + File.separator + "commitlog"); masterMessageStoreConfig.setHaListenPort(0); masterMessageStoreConfig.setTotalReplicas(2); masterMessageStoreConfig.setInSyncReplicas(2); masterMessageStoreConfig.setHaHousekeepingInterval(2 * 1000); masterMessageStoreConfig.setHaSendHeartbeatInterval(1000); buildMessageStoreConfig(masterMessageStoreConfig); slaveStoreConfig = new MessageStoreConfig(); slaveStoreConfig.setBrokerRole(BrokerRole.SLAVE); slaveStoreConfig.setStorePathRootDir(storePathRootDir + File.separator + "slave"); slaveStoreConfig.setStorePathCommitLog(storePathRootDir + File.separator + "slave" + File.separator + "commitlog"); slaveStoreConfig.setHaListenPort(0); slaveStoreConfig.setTotalReplicas(2); slaveStoreConfig.setInSyncReplicas(2); slaveStoreConfig.setHaHousekeepingInterval(2 * 1000); slaveStoreConfig.setHaSendHeartbeatInterval(1000); buildMessageStoreConfig(slaveStoreConfig); messageStore = buildMessageStore(masterMessageStoreConfig, 0L); slaveMessageStore = buildMessageStore(slaveStoreConfig, 1L); boolean load = messageStore.load(); boolean slaveLoad = slaveMessageStore.load(); assertTrue(load); assertTrue(slaveLoad); messageStore.start(); slaveMessageStore.updateHaMasterAddress("127.0.0.1:" + masterMessageStoreConfig.getHaListenPort()); slaveMessageStore.start(); slaveMessageStore.updateHaMasterAddress("127.0.0.1:" + masterMessageStoreConfig.getHaListenPort()); await().atMost(6, SECONDS).until(() -> slaveMessageStore.getHaService().getHAClient().getCurrentState() == HAConnectionState.TRANSFER); } @Test public void testHandleHA() { long totalMsgs = 10; queueTotal = 1; messageBody = storeMessage.getBytes(); for (long i = 0; i < totalMsgs; i++) { messageStore.putMessage(buildMessage()); } for (long i = 0; i < totalMsgs; i++) { final long index = i; Boolean exist = await().atMost(Duration.ofSeconds(5)).until(() -> { GetMessageResult result = slaveMessageStore.getMessage("GROUP_A", "FooBar", 0, index, 1024 * 1024, null); if (result == null) { return false; } boolean flag = GetMessageStatus.FOUND == result.getStatus(); result.release(); return flag; }, item -> item); assertTrue(exist); } } @Test public void testSemiSyncReplica() throws Exception { long totalMsgs = 5; queueTotal = 1; messageBody = storeMessage.getBytes(); for (long i = 0; i < totalMsgs; i++) { MessageExtBrokerInner msg = buildMessage(); CompletableFuture putResultFuture = messageStore.asyncPutMessage(msg); PutMessageResult result = putResultFuture.get(); assertEquals(PutMessageStatus.PUT_OK, result.getPutMessageStatus()); //message has been replicated to slave's commitLog, but maybe not dispatch to ConsumeQueue yet //so direct read from commitLog by physical offset MessageExt slaveMsg = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); assertNotNull(slaveMsg); assertArrayEquals(msg.getBody(), slaveMsg.getBody()); assertEquals(msg.getTopic(), slaveMsg.getTopic()); assertEquals(msg.getTags(), slaveMsg.getTags()); assertEquals(msg.getKeys(), slaveMsg.getKeys()); } //shutdown slave, putMessage should return FLUSH_SLAVE_TIMEOUT slaveMessageStore.shutdown(); //wait to let master clean the slave's connection await().atMost(Duration.ofSeconds(3)).until(() -> messageStore.getHaService().getConnectionCount().get() == 0); for (long i = 0; i < totalMsgs; i++) { CompletableFuture putResultFuture = messageStore.asyncPutMessage(buildMessage()); PutMessageResult result = putResultFuture.get(); assertEquals(PutMessageStatus.FLUSH_SLAVE_TIMEOUT, result.getPutMessageStatus()); } } @Test public void testSemiSyncReplicaWhenSlaveActingMaster() throws Exception { // SKip MacOS Assume.assumeFalse(MixAll.isMac()); long totalMsgs = 5; queueTotal = 1; messageBody = storeMessage.getBytes(); ((DefaultMessageStore) messageStore).getBrokerConfig().setEnableSlaveActingMaster(true); for (long i = 0; i < totalMsgs; i++) { MessageExtBrokerInner msg = buildMessage(); CompletableFuture putResultFuture = messageStore.asyncPutMessage(msg); PutMessageResult result = putResultFuture.get(); assertEquals(PutMessageStatus.PUT_OK, result.getPutMessageStatus()); //message has been replicated to slave's commitLog, but maybe not dispatch to ConsumeQueue yet //so direct read from commitLog by physical offset MessageExt slaveMsg = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); assertNotNull(slaveMsg); assertArrayEquals(msg.getBody(), slaveMsg.getBody()); assertEquals(msg.getTopic(), slaveMsg.getTopic()); assertEquals(msg.getTags(), slaveMsg.getTags()); assertEquals(msg.getKeys(), slaveMsg.getKeys()); } //shutdown slave, putMessage should return IN_SYNC_REPLICAS_NOT_ENOUGH slaveMessageStore.shutdown(); messageStore.setAliveReplicaNumInGroup(1); //wait to let master clean the slave's connection Thread.sleep(masterMessageStoreConfig.getHaHousekeepingInterval() + 500); for (long i = 0; i < totalMsgs; i++) { CompletableFuture putResultFuture = messageStore.asyncPutMessage(buildMessage()); PutMessageResult result = putResultFuture.get(); assertEquals(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, result.getPutMessageStatus()); } ((DefaultMessageStore) messageStore).getBrokerConfig().setEnableSlaveActingMaster(false); } @Test public void testSemiSyncReplicaWhenAdaptiveDegradation() throws Exception { long totalMsgs = 5; queueTotal = 1; messageBody = storeMessage.getBytes(); ((DefaultMessageStore) messageStore).getBrokerConfig().setEnableSlaveActingMaster(true); messageStore.getMessageStoreConfig().setEnableAutoInSyncReplicas(true); for (long i = 0; i < totalMsgs; i++) { MessageExtBrokerInner msg = buildMessage(); CompletableFuture putResultFuture = messageStore.asyncPutMessage(msg); PutMessageResult result = putResultFuture.get(); assertEquals(PutMessageStatus.PUT_OK, result.getPutMessageStatus()); //message has been replicated to slave's commitLog, but maybe not dispatch to ConsumeQueue yet //so direct read from commitLog by physical offset final MessageExt[] slaveMsg = {null}; await().atMost(Duration.ofSeconds(3)).until(() -> { slaveMsg[0] = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); return slaveMsg[0] != null; }); assertArrayEquals(msg.getBody(), slaveMsg[0].getBody()); assertEquals(msg.getTopic(), slaveMsg[0].getTopic()); assertEquals(msg.getTags(), slaveMsg[0].getTags()); assertEquals(msg.getKeys(), slaveMsg[0].getKeys()); } //shutdown slave, putMessage should return IN_SYNC_REPLICAS_NOT_ENOUGH slaveMessageStore.shutdown(); messageStore.setAliveReplicaNumInGroup(1); //wait to let master clean the slave's connection await().atMost(Duration.ofSeconds(3)).until(() -> messageStore.getHaService().getConnectionCount().get() == 0); for (long i = 0; i < totalMsgs; i++) { CompletableFuture putResultFuture = messageStore.asyncPutMessage(buildMessage()); PutMessageResult result = putResultFuture.get(); assertEquals(PutMessageStatus.PUT_OK, result.getPutMessageStatus()); } ((DefaultMessageStore) messageStore).getBrokerConfig().setEnableSlaveActingMaster(false); messageStore.getMessageStoreConfig().setEnableAutoInSyncReplicas(false); } @After public void destroy() throws Exception { slaveMessageStore.shutdown(); slaveMessageStore.destroy(); messageStore.shutdown(); messageStore.destroy(); File file = new File(storePathRootParentDir); UtilAll.deleteFile(file); } private MessageStore buildMessageStore(MessageStoreConfig messageStoreConfig, long brokerId) throws Exception { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerId(brokerId); return new DefaultMessageStore(messageStoreConfig, brokerStatsManager, null, brokerConfig, new ConcurrentHashMap<>()); } private void buildMessageStoreConfig(MessageStoreConfig messageStoreConfig) { messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); } private MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic("FooBar"); msg.setTags("TAG1"); msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } private boolean isCommitLogAvailable(DefaultMessageStore store) { try { Field serviceField = store.getClass().getDeclaredField("reputMessageService"); serviceField.setAccessible(true); DefaultMessageStore.ReputMessageService reputService = (DefaultMessageStore.ReputMessageService) serviceField.get(store); Method method = DefaultMessageStore.ReputMessageService.class.getDeclaredMethod("isCommitLogAvailable"); method.setAccessible(true); return (boolean) method.invoke(reputService); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { throw new RuntimeException(e); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/MappedFileQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.util.concurrent.CountDownLatch; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; import org.assertj.core.util.Lists; import org.junit.After; import org.junit.Assume; import org.junit.Test; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.assertj.core.api.Assertions.assertThat; public class MappedFileQueueTest { private String storePath = System.getProperty("java.io.tmpdir") + File.separator + "unit_test_store"; @Test public void testGetLastMappedFile() { final String fixedMsg = "0123456789abcdef"; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "a/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testFindMappedFileByOffset() { // four-byte string. final String fixedMsg = "abcd"; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "b/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } assertThat(mappedFileQueue.getMappedMemorySize()).isEqualTo(fixedMsg.getBytes().length * 1024); MappedFile mappedFile = mappedFileQueue.findMappedFileByOffset(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.getFileFromOffset()).isEqualTo(0); mappedFile = mappedFileQueue.findMappedFileByOffset(100); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.getFileFromOffset()).isEqualTo(0); mappedFile = mappedFileQueue.findMappedFileByOffset(1024); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.getFileFromOffset()).isEqualTo(1024); mappedFile = mappedFileQueue.findMappedFileByOffset(1024 + 100); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.getFileFromOffset()).isEqualTo(1024); mappedFile = mappedFileQueue.findMappedFileByOffset(1024 * 2); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.getFileFromOffset()).isEqualTo(1024 * 2); mappedFile = mappedFileQueue.findMappedFileByOffset(1024 * 2 + 100); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.getFileFromOffset()).isEqualTo(1024 * 2); // over mapped memory size. mappedFile = mappedFileQueue.findMappedFileByOffset(1024 * 4); assertThat(mappedFile).isNull(); mappedFile = mappedFileQueue.findMappedFileByOffset(1024 * 4 + 100); assertThat(mappedFile).isNull(); mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testFindMappedFileByOffset_StartOffsetIsNonZero() { MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "b/", 1024, null); //Start from a non-zero offset MappedFile mappedFile = mappedFileQueue.getLastMappedFile(1024); assertThat(mappedFile).isNotNull(); assertThat(mappedFileQueue.findMappedFileByOffset(1025)).isEqualTo(mappedFile); assertThat(mappedFileQueue.findMappedFileByOffset(0)).isNull(); assertThat(mappedFileQueue.findMappedFileByOffset(123, false)).isNull(); assertThat(mappedFileQueue.findMappedFileByOffset(123, true)).isEqualTo(mappedFile); assertThat(mappedFileQueue.findMappedFileByOffset(0, false)).isNull(); assertThat(mappedFileQueue.findMappedFileByOffset(0, true)).isEqualTo(mappedFile); mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testAppendMessage() { final String fixedMsg = "0123456789abcdef"; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "c/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } assertThat(mappedFileQueue.flush(0)).isFalse(); assertThat(mappedFileQueue.getFlushedWhere()).isEqualTo(1024); assertThat(mappedFileQueue.flush(0)).isFalse(); assertThat(mappedFileQueue.getFlushedWhere()).isEqualTo(1024 * 2); assertThat(mappedFileQueue.flush(0)).isFalse(); assertThat(mappedFileQueue.getFlushedWhere()).isEqualTo(1024 * 3); assertThat(mappedFileQueue.flush(0)).isFalse(); assertThat(mappedFileQueue.getFlushedWhere()).isEqualTo(1024 * 4); assertThat(mappedFileQueue.flush(0)).isFalse(); assertThat(mappedFileQueue.getFlushedWhere()).isEqualTo(1024 * 5); assertThat(mappedFileQueue.flush(0)).isFalse(); assertThat(mappedFileQueue.getFlushedWhere()).isEqualTo(1024 * 6); mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testGetMappedMemorySize() { final String fixedMsg = "abcd"; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "d/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } assertThat(mappedFileQueue.getMappedMemorySize()).isEqualTo(fixedMsg.length() * 1024); mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testDeleteExpiredFileByOffset() { MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "e/", 5120, null); for (int i = 0; i < 2048; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); ByteBuffer byteBuffer = ByteBuffer.allocate(ConsumeQueue.CQ_STORE_UNIT_SIZE); byteBuffer.putLong(i); byte[] padding = new byte[12]; Arrays.fill(padding, (byte) '0'); byteBuffer.put(padding); byteBuffer.flip(); assertThat(mappedFile.appendMessage(byteBuffer.array())).isTrue(); } MappedFile first = mappedFileQueue.getFirstMappedFile(); first.hold(); assertThat(mappedFileQueue.deleteExpiredFileByOffset(20480, ConsumeQueue.CQ_STORE_UNIT_SIZE)).isEqualTo(0); first.release(); assertThat(mappedFileQueue.deleteExpiredFileByOffset(20480, ConsumeQueue.CQ_STORE_UNIT_SIZE)).isGreaterThan(0); first = mappedFileQueue.getFirstMappedFile(); assertThat(first.getFileFromOffset()).isGreaterThan(0); mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testDeleteExpiredFileByTime() throws Exception { MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "f/", 1024, null); for (int i = 0; i < 100; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); byte[] bytes = new byte[512]; assertThat(mappedFile.appendMessage(bytes)).isTrue(); } assertThat(mappedFileQueue.getMappedFiles().size()).isEqualTo(50); long expiredTime = 100 * 1000; for (int i = 0; i < mappedFileQueue.getMappedFiles().size(); i++) { DefaultMappedFile mappedFile = (DefaultMappedFile) mappedFileQueue.getMappedFiles().get(i); if (i < 5) { mappedFile.getFile().setLastModified(System.currentTimeMillis() - expiredTime * 2); } if (i > 20) { mappedFile.getFile().setLastModified(System.currentTimeMillis() - expiredTime * 2); } } int maxBatchDeleteFilesNum = 50; mappedFileQueue.deleteExpiredFileByTime(expiredTime, 0, 0, false, maxBatchDeleteFilesNum); assertThat(mappedFileQueue.getMappedFiles().size()).isEqualTo(45); } @Test public void testFindMappedFile_ByIteration() { MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "g/", 1024, null); for (int i = 0; i < 3; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(1024 * i); mappedFile.setWrotePosition(1024); } assertThat(mappedFileQueue.findMappedFileByOffset(1028).getFileFromOffset()).isEqualTo(1024); // Switch two MappedFiles and verify findMappedFileByOffset method MappedFile tmpFile = mappedFileQueue.getMappedFiles().get(1); mappedFileQueue.getMappedFiles().set(1, mappedFileQueue.getMappedFiles().get(2)); mappedFileQueue.getMappedFiles().set(2, tmpFile); assertThat(mappedFileQueue.findMappedFileByOffset(1028).getFileFromOffset()).isEqualTo(1024); } @Test public void testMappedFile_SwapMap() { // four-byte string. final String fixedMsg = "abcdefgh"; final int mappedFileSize = 102400; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "b/", mappedFileSize, null); ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 1000 * 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryImpl("testThreadPool")); for (int i = 0; i < mappedFileSize; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } assertThat(mappedFileQueue.getMappedMemorySize()).isEqualTo(fixedMsg.getBytes().length * mappedFileSize); AtomicBoolean readOver = new AtomicBoolean(false); AtomicBoolean hasException = new AtomicBoolean(false); executor.submit(() -> { try { while (!readOver.get()) { for (MappedFile mappedFile : mappedFileQueue.getMappedFiles()) { mappedFile.swapMap(); Thread.sleep(10); mappedFile.cleanSwapedMap(true); } } } catch (Throwable t) { hasException.set(true); } } ); long start = System.currentTimeMillis(); long maxReadTimeMs = 60 * 1000; try { while (System.currentTimeMillis() - start <= maxReadTimeMs) { for (int i = 0; i < mappedFileSize && !readOver.get(); i++) { MappedFile mappedFile = null; int retryTime = 0; while (mappedFile == null && retryTime < 10000) { mappedFile = mappedFileQueue.findMappedFileByOffset(i * fixedMsg.getBytes().length); retryTime++; if (mappedFile == null) { Thread.sleep(1); } } assertThat(mappedFile != null).isTrue(); retryTime = 0; int pos = (i * fixedMsg.getBytes().length) % mappedFileSize; while ((pos + fixedMsg.getBytes().length) > mappedFile.getReadPosition() && retryTime < 10000) { retryTime++; if ((pos + fixedMsg.getBytes().length) > mappedFile.getReadPosition()) { Thread.sleep(1); } } assertThat((pos + fixedMsg.getBytes().length) <= mappedFile.getReadPosition()).isTrue(); SelectMappedBufferResult ret = mappedFile.selectMappedBuffer(pos, fixedMsg.getBytes().length); byte[] readRes = new byte[fixedMsg.getBytes().length]; ret.getByteBuffer().get(readRes); String readStr = new String(readRes, StandardCharsets.UTF_8); assertThat(readStr.equals(fixedMsg)).isTrue(); } } readOver.set(true); } catch (Throwable e) { hasException.set(true); readOver.set(true); } assertThat(readOver.get()).isTrue(); assertThat(hasException.get()).isFalse(); } @Test public void testMappedFile_CleanSwapedMap() throws InterruptedException { // four-byte string. final String fixedMsg = "abcd"; final int mappedFileSize = 1024000; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "b/", mappedFileSize, null); ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 1000 * 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryImpl("testThreadPool")); for (int i = 0; i < mappedFileSize; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } for (MappedFile mappedFile : mappedFileQueue.getMappedFiles()) { mappedFile.swapMap(); } AtomicBoolean hasException = new AtomicBoolean(false); CountDownLatch downLatch = new CountDownLatch(5); for (int i = 0; i < 5; i++) { executor.submit(() -> { try { for (MappedFile mappedFile : mappedFileQueue.getMappedFiles()) { mappedFile.cleanSwapedMap(true); mappedFile.cleanSwapedMap(true); } } catch (Exception e) { hasException.set(true); } finally { downLatch.countDown(); } }); } downLatch.await(10, TimeUnit.SECONDS); assertThat(hasException.get()).isFalse(); } @Test public void testMappedFile_Rename() throws IOException, InterruptedException { Assume.assumeFalse(MixAll.isWindows()); final String fixedMsg = RandomStringUtils.randomAlphanumeric(128); final byte[] msgByteArr = fixedMsg.getBytes(StandardCharsets.UTF_8); final int mappedFileSize = 5 * 1024 * 1024; MappedFileQueue mappedFileQueue = new MappedFileQueue("target/unit_test_store", mappedFileSize, null); int currentSize = 0; while (currentSize <= 2 * mappedFileSize) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); mappedFile.appendMessage(msgByteArr); currentSize += fixedMsg.length(); } assertThat(mappedFileQueue.getMappedFiles().size()).isEqualTo(3); ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(); ses.scheduleWithFixedDelay(() -> { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); mappedFile.appendMessage(msgByteArr); }, 1,1, TimeUnit.MILLISECONDS); List mappedFileList = Lists.newArrayList(mappedFileQueue.getMappedFiles()); mappedFileList.remove(mappedFileList.size() - 1); MappedFileQueue compactingMappedFileQueue = new MappedFileQueue("target/unit_test_store/compacting", mappedFileSize, null); currentSize = 0; while (currentSize < (2 * mappedFileSize - mappedFileSize / 2)) { MappedFile mappedFile = compactingMappedFileQueue.getLastMappedFile(0); mappedFile.appendMessage(msgByteArr); currentSize += fixedMsg.length(); } mappedFileList.forEach(MappedFile::renameToDelete); assertThat(mappedFileQueue.getFirstMappedFile().getFileName()).endsWith(".delete"); assertThat(mappedFileQueue.findMappedFileByOffset(mappedFileSize + fixedMsg.length()).getFileName()).endsWith(".delete"); SelectMappedBufferResult sbr = mappedFileList.get(mappedFileList.size() - 1).selectMappedBuffer(0, msgByteArr.length); assertThat(sbr).isNotNull(); try { assertThat(sbr.getMappedFile().getFileName().endsWith(".delete")).isTrue(); if (sbr.getByteBuffer().hasArray()) { assertThat(sbr.getByteBuffer().array()).isEqualTo(msgByteArr); } else { for (int i = 0; i < msgByteArr.length; i++) { assertThat(sbr.getByteBuffer().get(i)).isEqualTo(msgByteArr[i]); } } } finally { sbr.release(); } compactingMappedFileQueue.getMappedFiles().forEach(mappedFile -> { try { mappedFile.moveToParent(); } catch (IOException e) { e.printStackTrace(); } }); mappedFileQueue.getMappedFiles().stream() .filter(m -> !mappedFileList.contains(m)) .forEach(m -> compactingMappedFileQueue.getMappedFiles().add(m)); int wrotePosition = mappedFileQueue.getLastMappedFile().getWrotePosition(); mappedFileList.forEach(mappedFile -> { mappedFile.destroy(1000); }); TimeUnit.SECONDS.sleep(3); ses.shutdown(); mappedFileQueue.getMappedFiles().clear(); mappedFileQueue.getMappedFiles().addAll(compactingMappedFileQueue.getMappedFiles()); TimeUnit.SECONDS.sleep(3); } @Test public void testReset() { final String fixedMsg = "0123456789abcdef"; MappedFileQueue mappedFileQueue = new MappedFileQueue(storePath + File.separator + "a/", 64, null); for (int i = 0; i < 8; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg.getBytes())).isTrue(); } assertThat(mappedFileQueue.getMappedFiles().size()).isEqualTo(2); assertThat(mappedFileQueue.resetOffset(0)).isTrue(); assertThat(mappedFileQueue.getMappedFiles().size()).isEqualTo(1); } @After public void destroy() { File file = new File(storePath); UtilAll.deleteFile(file); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/MappedFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: MappedFileTest.java 1831 2013-05-16 01:39:51Z vintagewang@apache.org $ */ package org.apache.rocketmq.store; import java.io.File; import java.io.IOException; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.junit.After; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MappedFileTest { private final String storeMessage = "Once, there was a chance for me!"; @Test public void testSelectMappedBuffer() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile("target/unit_test_store/MappedFileTest/000", 1024 * 64); boolean result = mappedFile.appendMessage(storeMessage.getBytes()); assertThat(result).isTrue(); SelectMappedBufferResult selectMappedBufferResult = mappedFile.selectMappedBuffer(0); byte[] data = new byte[storeMessage.length()]; selectMappedBufferResult.getByteBuffer().get(data); String readString = new String(data); assertThat(readString).isEqualTo(storeMessage); mappedFile.shutdown(1000); assertThat(mappedFile.isAvailable()).isFalse(); selectMappedBufferResult.release(); assertThat(mappedFile.isCleanupOver()).isTrue(); assertThat(mappedFile.destroy(1000)).isTrue(); } @After public void destroy() { File file = new File("target/unit_test_store"); UtilAll.deleteFile(file); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/MessageExtBrokerInnerTest.java ================================================ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class MessageExtBrokerInnerTest { @Test public void testDeleteProperty() { MessageExtBrokerInner messageExtBrokerInner = new MessageExtBrokerInner(); String propertiesString = ""; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA\u0002KeyA\u0001ValueA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyA\u0001ValueA\u0002KeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(""); propertiesString = "KeyB\u0001ValueB\u0002KeyA\u0001ValueA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002"); propertiesString = "KeyB\u0001ValueB\u0002KeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002"); propertiesString = "KeyB\u0001ValueB\u0002KeyA\u0001ValueA\u0002KeyB\u0001ValueB\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002KeyB\u0001ValueB\u0002"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueB\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB\u0002"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueB"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueB"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueBKeyA\u0001ValueA\u0002"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueBKeyA\u0001ValueA\u0002"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueBKeyA\u0001"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueBKeyA\u0001"); propertiesString = "KeyA\u0001ValueA\u0002KeyB\u0001ValueBKeyA"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("KeyA"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo("KeyB\u0001ValueBKeyA"); propertiesString = "__CRC32#\u0001"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("__CRC32#"); assertThat(messageExtBrokerInner.getPropertiesString()).isEmpty(); propertiesString = "__CRC32#"; messageExtBrokerInner.setPropertiesString(propertiesString); messageExtBrokerInner.deleteProperty("__CRC32#"); assertThat(messageExtBrokerInner.getPropertiesString()).isEqualTo(propertiesString); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/MessageStoreStateMachineTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.verify; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.store.MessageStoreStateMachine.MessageStoreState; import org.junit.Test; import org.junit.Before; import org.mockito.Mockito; public class MessageStoreStateMachineTest { private Logger mockLogger; private MessageStoreStateMachine stateMachine; @Before public void setUp() { // Mock Logger mockLogger = Mockito.mock(Logger.class); // Initialize StateMachine stateMachine = new MessageStoreStateMachine(mockLogger); } /** * Test the constructor of MessageStoreStateMachine. */ @Test public void testConstructor() { // Verify initial state assertEquals(MessageStoreState.INIT, stateMachine.getCurrentState()); // Verify logger was called for initialization verify(mockLogger).info(anyString(), eq(MessageStoreState.INIT)); } /** * Test valid state transition in transitTo method. */ @Test public void testValidStateTransition() { // Perform a valid state transition stateMachine.transitTo(MessageStoreState.LOAD_COMMITLOG_OK); // Verify the current state is updated assertEquals(MessageStoreState.LOAD_COMMITLOG_OK, stateMachine.getCurrentState()); // Verify logger was called for state transition verify(mockLogger).info(anyString(), eq(MessageStoreState.INIT), eq(MessageStoreState.LOAD_COMMITLOG_OK), anyLong(), anyLong()); } /** * Test fail state transition in transitTo method. */ @Test public void testValidFailStateTransition() { stateMachine.transitTo(MessageStoreState.LOAD_COMMITLOG_OK, false); assertEquals(MessageStoreState.INIT, stateMachine.getCurrentState()); verify(mockLogger).warn(anyString(), eq(MessageStoreState.INIT), eq(MessageStoreState.LOAD_COMMITLOG_OK), anyLong(), anyLong()); } /** * Test invalid state transition in transitTo method. */ @Test public void testInvalidStateTransition() { // Perform an invalid state transition Exception exception = assertThrows(IllegalStateException.class, () -> { stateMachine.transitTo(MessageStoreState.INIT); }); // Verify the exception message String expectedMessage = "Invalid state transition from INIT to INIT. Can only move forward."; assertEquals(expectedMessage, exception.getMessage()); } /** * Test getCurrentState method. */ @Test public void testGetCurrentState() { // Verify the current state assertEquals(MessageStoreState.INIT, stateMachine.getCurrentState()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/MultiPathMappedFileQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import static org.assertj.core.api.Assertions.assertThat; import java.util.HashSet; import java.util.Set; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.MappedFile; import org.junit.Test; public class MultiPathMappedFileQueueTest { @Test public void testGetLastMappedFile() { final byte[] fixedMsg = new byte[1024]; MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathCommitLog("target/unit_test_store/a/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/b/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/c/"); MappedFileQueue mappedFileQueue = new MultiPathMappedFileQueue(config, 1024, null, null); String[] storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length * i); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); int idx = i % storePaths.length; assertThat(mappedFile.getFileName().startsWith(storePaths[idx])).isTrue(); } mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testLoadReadOnlyMappedFiles() { { //create old mapped files final byte[] fixedMsg = new byte[1024]; MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathCommitLog("target/unit_test_store/a/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/b/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/c/"); MappedFileQueue mappedFileQueue = new MultiPathMappedFileQueue(config, 1024, null, null); String[] storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length * i); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); int idx = i % storePaths.length; assertThat(mappedFile.getFileName().startsWith(storePaths[idx])).isTrue(); } mappedFileQueue.shutdown(1000); } // test load and readonly MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathCommitLog("target/unit_test_store/b/"); config.setReadOnlyCommitLogStorePaths("target/unit_test_store/a" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/c"); MultiPathMappedFileQueue mappedFileQueue = new MultiPathMappedFileQueue(config, 1024, null, null); mappedFileQueue.load(); assertThat(mappedFileQueue.mappedFiles.size()).isEqualTo(1024); for (int i = 0; i < 1024; i++) { assertThat(mappedFileQueue.mappedFiles.get(i).getFile().getName()) .isEqualTo(UtilAll.offset2FileName(1024 * i)); } mappedFileQueue.destroy(); } @Test public void testUpdatePathsOnline() { final byte[] fixedMsg = new byte[1024]; MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathCommitLog("target/unit_test_store/a/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/b/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/c/"); MappedFileQueue mappedFileQueue = new MultiPathMappedFileQueue(config, 1024, null, null); String[] storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length * i); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); int idx = i % storePaths.length; assertThat(mappedFile.getFileName().startsWith(storePaths[idx])).isTrue(); if (i == 500) { config.setStorePathCommitLog("target/unit_test_store/a/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/b/"); storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); } } mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } @Test public void testFullStorePath() { final byte[] fixedMsg = new byte[1024]; Set fullStorePath = new HashSet<>(); MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathCommitLog("target/unit_test_store/a/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/b/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/c/"); MappedFileQueue mappedFileQueue = new MultiPathMappedFileQueue(config, 1024, null, () -> fullStorePath); String[] storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); assertThat(storePaths.length).isEqualTo(3); MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); assertThat(mappedFile).isNotNull(); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); assertThat(mappedFile.getFileName().startsWith(storePaths[0])).isTrue(); mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length); assertThat(mappedFile.getFileName().startsWith(storePaths[1])).isTrue(); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length * 2); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); assertThat(mappedFile.getFileName().startsWith(storePaths[2])).isTrue(); fullStorePath.add("target/unit_test_store/b/"); mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length * 3); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); assertThat(mappedFile.getFileName().startsWith(storePaths[2])).isTrue(); mappedFile = mappedFileQueue.getLastMappedFile(fixedMsg.length * 4); assertThat(mappedFile.appendMessage(fixedMsg)).isTrue(); assertThat(mappedFile.getFileName().startsWith(storePaths[0])).isTrue(); mappedFileQueue.shutdown(1000); mappedFileQueue.destroy(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ReputMessageServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.UUID; import java.io.File; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.any; public class ReputMessageServiceTest { private DefaultMessageStore syncFlushMessageStore; private DefaultMessageStore asyncFlushMessageStore; private final String topic = "FooBar"; private final String tmpdir = System.getProperty("java.io.tmpdir"); private final String storePathRootParentDir = (StringUtils.endsWith(tmpdir, File.separator) ? tmpdir : tmpdir + File.separator) + UUID.randomUUID(); private SocketAddress bornHost; private SocketAddress storeHost; @Before public void init() throws Exception { File file = new File(storePathRootParentDir); UtilAll.deleteFile(file); syncFlushMessageStore = buildMessageStore(FlushDiskType.SYNC_FLUSH); asyncFlushMessageStore = buildMessageStore(FlushDiskType.ASYNC_FLUSH); assertTrue(syncFlushMessageStore.load()); assertTrue(asyncFlushMessageStore.load()); syncFlushMessageStore.start(); asyncFlushMessageStore.start(); storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } private DefaultMessageStore buildMessageStore(FlushDiskType flushDiskType) throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setHaListenPort(0); messageStoreConfig.setFlushDiskType(flushDiskType); messageStoreConfig.setStorePathRootDir(storePathRootParentDir + File.separator + flushDiskType); messageStoreConfig.setStorePathCommitLog(storePathRootParentDir + File.separator + flushDiskType + File.separator + "commitlog"); BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setLongPollingEnable(false); DefaultMessageStore messageStore = new DefaultMessageStore(messageStoreConfig, mock(BrokerStatsManager.class), null, brokerConfig, null); // Mock flush disk service Field field = CommitLog.class.getDeclaredField("flushManager"); field.setAccessible(true); FlushManager flushManager = mock(FlushManager.class); CompletableFuture completableFuture = new CompletableFuture<>(); completableFuture.complete(PutMessageStatus.PUT_OK); when(flushManager.handleDiskFlush(any(AppendMessageResult.class), any(MessageExt.class))).thenReturn(completableFuture); field.set(messageStore.getCommitLog(), flushManager); return messageStore; } @Test public void testReputEndOffset_whenSyncFlush() throws Exception { for (int i = 0; i < 10; i++) { assertEquals(PutMessageStatus.PUT_OK, syncFlushMessageStore.putMessage(buildMessage()).getPutMessageStatus()); } assertEquals(1580, syncFlushMessageStore.getMaxPhyOffset()); assertEquals(0, syncFlushMessageStore.getCommitLog().getFlushedWhere()); // wait for cq dispatch Thread.sleep(3000); assertEquals(0, syncFlushMessageStore.getCommitLog().getFlushedWhere()); assertEquals(0, syncFlushMessageStore.getMaxOffsetInQueue(topic, 0)); GetMessageResult getMessageResult = syncFlushMessageStore.getMessage("testGroup", topic, 0, 0, 32, null); assertEquals(GetMessageStatus.NO_MESSAGE_IN_QUEUE, getMessageResult.getStatus()); } @Test public void testReputEndOffset_whenAsyncFlush() throws Exception { for (int i = 0; i < 10; i++) { assertEquals(PutMessageStatus.PUT_OK, asyncFlushMessageStore.putMessage(buildMessage()).getPutMessageStatus()); } assertEquals(1580, asyncFlushMessageStore.getMaxPhyOffset()); assertEquals(0, asyncFlushMessageStore.getCommitLog().getFlushedWhere()); // wait for cq dispatch Thread.sleep(3000); assertEquals(0, asyncFlushMessageStore.getCommitLog().getFlushedWhere()); assertEquals(10, asyncFlushMessageStore.getMaxOffsetInQueue(topic, 0)); GetMessageResult getMessageResult = asyncFlushMessageStore.getMessage("testGroup", topic, 0, 0, 32, null); assertEquals(10, getMessageResult.getMessageCount()); } private MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setBody("Once, there was a chance for me!".getBytes()); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(0); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } @After public void destroy() throws Exception { if (this.syncFlushMessageStore != null) { syncFlushMessageStore.shutdown(); syncFlushMessageStore.destroy(); } if (this.asyncFlushMessageStore != null) { asyncFlushMessageStore.shutdown(); asyncFlushMessageStore.destroy(); } File file = new File(storePathRootParentDir); UtilAll.deleteFile(file); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/RocksDBMessageStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import com.google.common.collect.Sets; import java.io.File; import java.io.RandomAccessFile; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.OverlappingFileLockException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.queue.RocksDBConsumeQueue; import org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.assertj.core.util.Strings; import org.awaitility.Awaitility; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class RocksDBMessageStoreTest { private final String storeMessage = "Once, there was a chance for me!"; private final String messageTopic = "FooBar"; private final String storeType = StoreType.DEFAULT_ROCKSDB.getStoreType(); private int queueTotal = 100; private final AtomicInteger queueId = new AtomicInteger(0); private SocketAddress bornHost; private SocketAddress storeHost; private byte[] messageBody; private MessageStore messageStore; @Before public void init() throws Exception { if (notExecuted()) { return; } storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); messageStore = buildMessageStore(); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); } @Test(expected = OverlappingFileLockException.class) public void test_repeat_restart() throws Exception { if (notExecuted()) { throw new OverlappingFileLockException(); } queueTotal = 1; messageBody = storeMessage.getBytes(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "store"); messageStoreConfig.setHaListenPort(0); MessageStore master = new RocksDBMessageStore(messageStoreConfig, null, new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); boolean load = master.load(); assertTrue(load); try { master.start(); master.start(); } finally { master.shutdown(); master.destroy(); } } @After public void destroy() { if (notExecuted()) { return; } messageStore.shutdown(); messageStore.destroy(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); File file = new File(messageStoreConfig.getStorePathRootDir()); UtilAll.deleteFile(file); } private MessageStore buildMessageStore() throws Exception { return buildMessageStore(null, ""); } private MessageStore buildMessageStore(String storePathRootDir, String topic) throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); messageStoreConfig.setStoreType(storeType); messageStoreConfig.setHaListenPort(0); if (Strings.isNullOrEmpty(storePathRootDir)) { UUID uuid = UUID.randomUUID(); storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + uuid.toString(); } messageStoreConfig.setStorePathRootDir(storePathRootDir); ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); topicConfigTable.put(topic, new TopicConfig(topic, 1, 1)); return new RocksDBMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig(), topicConfigTable); } @Test public void testWriteAndRead() { if (notExecuted()) { return; } long ipv4HostMessages = 10; long ipv6HostMessages = 10; long totalMessages = ipv4HostMessages + ipv6HostMessages; queueTotal = 1; messageBody = storeMessage.getBytes(); for (long i = 0; i < ipv4HostMessages; i++) { messageStore.putMessage(buildMessage()); } for (long i = 0; i < ipv6HostMessages; i++) { messageStore.putMessage(buildIPv6HostMessage()); } StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); for (long i = 0; i < totalMessages; i++) { GetMessageResult result = messageStore.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); result.release(); } verifyThatMasterIsFunctional(totalMessages, messageStore); } @Test public void testLookMessageByOffset_OffsetIsFirst() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = new Random().nextInt(10); String topic = "FooBar"; int firstOffset = 0; AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId); AppendMessageResult firstResult = appendMessageResultArray[0]; MessageExt messageExt = messageStore.lookMessageByOffset(firstResult.getWroteOffset()); MessageExt messageExt1 = getDefaultMessageStore().lookMessageByOffset(firstResult.getWroteOffset(), firstResult.getWroteBytes()); assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset)); assertThat(new String(messageExt1.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset)); } @Test public void testLookMessageByOffset_OffsetIsLast() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = new Random().nextInt(10); String topic = "FooBar"; AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId); int lastIndex = totalCount - 1; AppendMessageResult lastResult = appendMessageResultArray[lastIndex]; MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastResult.getWroteOffset(), lastResult.getWroteBytes()); assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, lastIndex)); } @Test public void testLookMessageByOffset_OffsetIsOutOfBound() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = new Random().nextInt(10); String topic = "FooBar"; AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId); long lastOffset = getMaxOffset(appendMessageResultArray); MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastOffset); assertThat(messageExt).isNull(); } @Test public void testGetOffsetInQueueByTime() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp()); CqUnit cqUnit = consumeQueue.get(offset); assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset()); assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes()); } } @Test public void testGetOffsetInQueueByTime_TimestampIsSkewing() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); int skewing = 2; ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing); CqUnit cqUnit = consumeQueue.get(offset); assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset()); assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes()); } } @Test public void testGetOffsetInQueueByTime_TimestampSkewingIsLarge() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); int skewing = 20000; ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing); CqUnit cqUnit = consumeQueue.get(offset); assertThat(cqUnit.getPos()).isEqualTo(appendMessageResults[0].getWroteOffset()); assertThat(cqUnit.getSize()).isEqualTo(appendMessageResults[0].getWroteBytes()); } } @Test public void testGetOffsetInQueueByTime_ConsumeQueueNotFound1() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; int wrongQueueId = 1; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); long offset = messageStore.getOffsetInQueueByTime(topic, wrongQueueId, appendMessageResults[0].getStoreTimestamp()); assertThat(offset).isEqualTo(0); } @Test public void testGetOffsetInQueueByTime_ConsumeQueueNotFound2() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; int wrongQueueId = 1; String topic = "FooBar"; putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, wrongQueueId, 0); assertThat(messageStoreTimeStamp).isEqualTo(-1); } @Test public void testGetOffsetInQueueByTime_ConsumeQueueOffsetNotExist() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; int wrongQueueId = 1; String topic = "FooBar"; putMessages(totalCount, topic, queueId, true); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, wrongQueueId, -1); assertThat(messageStoreTimeStamp).isEqualTo(-1); } @Test public void testGetMessageStoreTimeStamp() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); int minOffsetInQueue = (int) consumeQueue.getMinOffsetInQueue(); for (int i = minOffsetInQueue; i < consumeQueue.getMaxOffsetInQueue(); i++) { long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, queueId, i); assertThat(messageStoreTimeStamp).isEqualTo(appendMessageResults[i].getStoreTimestamp()); } } @Test public void testGetStoreTime_ParamIsNull() { if (notExecuted()) { return; } long storeTime = getStoreTime(null); assertThat(storeTime).isEqualTo(-1); } @Test public void testGetStoreTime_EverythingIsOk() { if (notExecuted()) { return; } final int totalCount = 10; int queueId = 0; String topic = "FooBar"; AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, queueId); for (int i = 0; i < totalCount; i++) { CqUnit cqUnit = consumeQueue.get(i); long storeTime = getStoreTime(cqUnit); assertThat(storeTime).isEqualTo(appendMessageResults[i].getStoreTimestamp()); } } @Test public void testGetStoreTime_PhyOffsetIsLessThanCommitLogMinOffset() { if (notExecuted()) { return; } long phyOffset = -10; int size = 138; CqUnit cqUnit = new CqUnit(0, phyOffset, size, 0); long storeTime = getStoreTime(cqUnit); assertThat(storeTime).isEqualTo(-1); } @Test public void testPutMessage_whenMessagePropertyIsTooLong() throws ConsumeQueueException { if (notExecuted()) { return; } String topicName = "messagePropertyIsTooLongTest"; MessageExtBrokerInner illegalMessage = buildSpecifyLengthPropertyMessage("123".getBytes(StandardCharsets.UTF_8), topicName, Short.MAX_VALUE + 1); assertEquals(messageStore.putMessage(illegalMessage).getPutMessageStatus(), PutMessageStatus.PROPERTIES_SIZE_EXCEEDED); assertEquals(0L, messageStore.getQueueStore().getMaxOffset(topicName, 0).longValue()); MessageExtBrokerInner normalMessage = buildSpecifyLengthPropertyMessage("123".getBytes(StandardCharsets.UTF_8), topicName, 100); assertEquals(messageStore.putMessage(normalMessage).getPutMessageStatus(), PutMessageStatus.PUT_OK); assertEquals(1L, messageStore.getQueueStore().getMaxOffset(topicName, 0).longValue()); } private RocksDBMessageStore getDefaultMessageStore() { return (RocksDBMessageStore) this.messageStore; } private AppendMessageResult[] putMessages(int totalCount, String topic, int queueId) { return putMessages(totalCount, topic, queueId, false); } private AppendMessageResult[] putMessages(int totalCount, String topic, int queueId, boolean interval) { AppendMessageResult[] appendMessageResultArray = new AppendMessageResult[totalCount]; for (int i = 0; i < totalCount; i++) { String messageBody = buildMessageBodyByOffset(storeMessage, i); MessageExtBrokerInner msgInner = i < totalCount / 2 ? buildMessage(messageBody.getBytes(), topic) : buildIPv6HostMessage(messageBody.getBytes(), topic); msgInner.setQueueId(queueId); PutMessageResult result = messageStore.putMessage(msgInner); appendMessageResultArray[i] = result.getAppendMessageResult(); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); if (interval) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException("Thread sleep ERROR"); } } } return appendMessageResultArray; } private long getMaxOffset(AppendMessageResult[] appendMessageResultArray) { if (appendMessageResultArray == null) { return 0; } AppendMessageResult last = appendMessageResultArray[appendMessageResultArray.length - 1]; return last.getWroteOffset() + last.getWroteBytes(); } private String buildMessageBodyByOffset(String message, long i) { return String.format("%s offset %d", message, i); } private long getStoreTime(CqUnit cqUnit) { try { Class abstractConsumeQueueStore = getDefaultMessageStore().getQueueStore().getClass().getSuperclass(); Method getStoreTime = abstractConsumeQueueStore.getDeclaredMethod("getStoreTime", CqUnit.class); getStoreTime.setAccessible(true); return (long) getStoreTime.invoke(getDefaultMessageStore().getQueueStore(), cqUnit); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } private MessageExtBrokerInner buildMessage(byte[] messageBody, String topic) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } private MessageExtBrokerInner buildSpecifyLengthPropertyMessage(byte[] messageBody, String topic, int length) { StringBuilder stringBuilder = new StringBuilder(); Random random = new Random(); for (int i = 0; i < length; i++) { stringBuilder.append(random.nextInt(10)); } MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.putUserProperty("test", stringBuilder.toString()); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setQueueId(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } private MessageExtBrokerInner buildIPv6HostMessage(byte[] messageBody, String topic) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); msg.setBornTimestamp(System.currentTimeMillis()); try { msg.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 0)); } catch (UnknownHostException e) { fail("build IPv6 host message error", e); } try { msg.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 0)); } catch (UnknownHostException e) { fail("build IPv6 host message error", e); } return msg; } private MessageExtBrokerInner buildMessage() { return buildMessage(messageBody, messageTopic); } public MessageExtBatch buildMessageBatch(MessageBatch msgBatch) { MessageExtBatch msgExtBatch = new MessageExtBatch(); msgExtBatch.setTopic(messageTopic); msgExtBatch.setTags("TAG1"); msgExtBatch.setKeys("Hello"); msgExtBatch.setBody(msgBatch.getBody()); msgExtBatch.setKeys(String.valueOf(System.currentTimeMillis())); msgExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msgExtBatch.setSysFlag(0); msgExtBatch.setBornTimestamp(System.currentTimeMillis()); msgExtBatch.setStoreHost(storeHost); msgExtBatch.setBornHost(bornHost); return msgExtBatch; } @Test public void testGroupCommit() { if (notExecuted()) { return; } long totalMessages = 10; queueTotal = 1; messageBody = storeMessage.getBytes(); for (long i = 0; i < totalMessages; i++) { messageStore.putMessage(buildMessage()); } for (long i = 0; i < totalMessages; i++) { GetMessageResult result = messageStore.getMessage("GROUP_A", "TOPIC_A", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); result.release(); } verifyThatMasterIsFunctional(totalMessages, messageStore); } @Test public void testMaxOffset() throws ConsumeQueueException { if (notExecuted()) { return; } int firstBatchMessages = 3; int queueId = 0; messageBody = storeMessage.getBytes(); assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(0); for (int i = 0; i < firstBatchMessages; i++) { final MessageExtBrokerInner msg = buildMessage(); msg.setQueueId(queueId); messageStore.putMessage(msg); } Awaitility.await() .with() .atMost(3, TimeUnit.SECONDS) .pollInterval(1, TimeUnit.MILLISECONDS) .until(() -> messageStore.getMaxOffsetInQueue(messageTopic, queueId) == firstBatchMessages); // Disable the dispatcher messageStore.getDispatcherList().clear(); int secondBatchMessages = 2; for (int i = 0; i < secondBatchMessages; i++) { final MessageExtBrokerInner msg = buildMessage(); msg.setQueueId(queueId); messageStore.putMessage(msg); } assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(firstBatchMessages); assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId, true)).isEqualTo(firstBatchMessages); assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId, false)).isEqualTo(firstBatchMessages + secondBatchMessages); } private MessageExtBrokerInner buildIPv6HostMessage() { return buildIPv6HostMessage(messageBody, "FooBar"); } private void verifyThatMasterIsFunctional(long totalMessages, MessageStore master) { for (long i = 0; i < totalMessages; i++) { master.putMessage(buildMessage()); } StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore); for (long i = 0; i < totalMessages; i++) { GetMessageResult result = master.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); result.release(); } } @Test public void testPullSize() { if (notExecuted()) { return; } String topic = "pullSizeTopic"; for (int i = 0; i < 32; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } // wait for consume queue build Awaitility.await().atMost(10, TimeUnit.SECONDS) .with() .pollInterval(10, TimeUnit.MILLISECONDS) .until(() -> messageStore.getMaxOffsetInQueue(topic, 0) >= 32); String group = "simple"; GetMessageResult getMessageResult32 = messageStore.getMessage(group, topic, 0, 0, 32, null); assertThat(getMessageResult32.getMessageBufferList().size()).isEqualTo(32); getMessageResult32.release(); GetMessageResult getMessageResult20 = messageStore.getMessage(group, topic, 0, 0, 20, null); assertThat(getMessageResult20.getMessageBufferList().size()).isEqualTo(20); getMessageResult20.release(); GetMessageResult getMessageResult45 = messageStore.getMessage(group, topic, 0, 0, 10, null); assertThat(getMessageResult45.getMessageBufferList().size()).isEqualTo(10); getMessageResult45.release(); } @Test public void testRecover() throws Exception { if (notExecuted()) { return; } String topic = "recoverTopic"; messageBody = storeMessage.getBytes(); for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } // wait for build consumer queue Awaitility.await() .with() .pollInterval(100, TimeUnit.MILLISECONDS) .atMost(10, TimeUnit.SECONDS) .until(() -> messageStore.getMaxOffsetInQueue(topic, 0) >= 100); long maxPhyOffset = messageStore.getMaxPhyOffset(); long maxCqOffset = messageStore.getMaxOffsetInQueue(topic, 0); //1.just reboot messageStore.shutdown(); String storeRootDir = messageStore.getMessageStoreConfig().getStorePathRootDir(); messageStore = buildMessageStore(storeRootDir, topic); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); assertEquals(maxPhyOffset, messageStore.getMaxPhyOffset()); assertEquals(maxCqOffset, messageStore.getMaxOffsetInQueue(topic, 0)); //2.damage commit-log and reboot normal for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } Awaitility.await() .with() .pollInterval(100, TimeUnit.MILLISECONDS) .atMost(10, TimeUnit.SECONDS) .until(() -> messageStore.getMaxOffsetInQueue(topic, 0) >= 200); long secondLastPhyOffset = messageStore.getMaxPhyOffset(); long secondLastCqOffset = messageStore.getMaxOffsetInQueue(topic, 0); // Append a message to corrupt MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); messageStore.shutdown(); // Corrupt the last message damageCommitLog((RocksDBMessageStore) messageStore, secondLastPhyOffset); //reboot messageStore = buildMessageStore(storeRootDir, topic); load = messageStore.load(); assertTrue(load); messageStore.start(); assertEquals(secondLastPhyOffset, messageStore.getMaxPhyOffset()); assertEquals(secondLastCqOffset, messageStore.getMaxOffsetInQueue(topic, 0)); //3.Corrupt commit-log and reboot abnormal for (int i = 0; i < 100; i++) { messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } Awaitility.await() .with() .pollInterval(100, TimeUnit.MILLISECONDS) .atMost(10, TimeUnit.SECONDS) .until(() -> messageStore.getMaxOffsetInQueue(topic, 0) >= 300); secondLastPhyOffset = messageStore.getMaxPhyOffset(); secondLastCqOffset = messageStore.getMaxOffsetInQueue(topic, 0); messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); messageStore.shutdown(); //Corrupt the last message damageCommitLog((RocksDBMessageStore) messageStore, secondLastPhyOffset); //add abort file String fileName = StorePathConfigHelper.getAbortFile(messageStore.getMessageStoreConfig().getStorePathRootDir()); File file = new File(fileName); UtilAll.ensureDirOK(file.getParent()); assertTrue(file.createNewFile()); messageStore = buildMessageStore(storeRootDir, topic); load = messageStore.load(); assertTrue(load); messageStore.start(); assertEquals(secondLastPhyOffset, messageStore.getMaxPhyOffset()); assertEquals(secondLastCqOffset, messageStore.getMaxOffsetInQueue(topic, 0)); //message write again for (int i = 0; i < 100; i++) { messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); messageExtBrokerInner.setQueueId(0); messageStore.putMessage(messageExtBrokerInner); } } @Test public void testStorePathOK() { if (notExecuted()) { return; } if (messageStore instanceof RocksDBMessageStore) { assertTrue(fileExists(((RocksDBMessageStore) messageStore).getStorePathPhysic())); assertTrue(fileExists(((RocksDBMessageStore) messageStore).getStorePathLogic())); } } private boolean fileExists(String path) { if (path != null) { File f = new File(path); return f.exists(); } return false; } private void damageCommitLog(RocksDBMessageStore store, long offset) throws Exception { assertThat(store).isNotNull(); MessageStoreConfig messageStoreConfig = store.getMessageStoreConfig(); File file = new File(messageStoreConfig.getStorePathCommitLog() + File.separator + "00000000000000000000"); try (RandomAccessFile raf = new RandomAccessFile(file, "rw"); FileChannel fileChannel = raf.getChannel()) { MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 10); int bodyLen = mappedByteBuffer.getInt((int) offset + 84); int topicLenIndex = (int) offset + 84 + bodyLen + 4; mappedByteBuffer.position(topicLenIndex); mappedByteBuffer.putInt(0); mappedByteBuffer.putInt(0); mappedByteBuffer.putInt(0); mappedByteBuffer.putInt(0); mappedByteBuffer.force(); fileChannel.force(true); } } @Test public void testPutMsgExceedsMaxLength() { if (notExecuted()) { return; } messageBody = new byte[4 * 1024 * 1024 + 1]; MessageExtBrokerInner msg = buildMessage(); PutMessageResult result = messageStore.putMessage(msg); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.MESSAGE_ILLEGAL); } @Test public void testPutMsgBatchExceedsMaxLength() { if (notExecuted()) { return; } messageBody = new byte[4 * 1024 * 1024 + 1]; MessageExtBrokerInner msg1 = buildMessage(); MessageExtBrokerInner msg2 = buildMessage(); MessageExtBrokerInner msg3 = buildMessage(); MessageBatch msgBatch = MessageBatch.generateFromList(Arrays.asList(msg1, msg2, msg3)); msgBatch.setBody(msgBatch.encode()); MessageExtBatch msgExtBatch = buildMessageBatch(msgBatch); try { this.messageStore.putMessages(msgExtBatch); fail("Should have raised an exception"); } catch (Exception e) { assertThat(e.getMessage()).contains("message body size exceeded"); } } @Test public void testPutMsgWhenReplicasNotEnough() { if (notExecuted()) { return; } MessageStoreConfig messageStoreConfig = this.messageStore.getMessageStoreConfig(); messageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER); messageStoreConfig.setTotalReplicas(2); messageStoreConfig.setInSyncReplicas(2); messageStoreConfig.setEnableAutoInSyncReplicas(false); ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(true); this.messageStore.setAliveReplicaNumInGroup(1); MessageExtBrokerInner msg = buildMessage(); PutMessageResult result = this.messageStore.putMessage(msg); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH); ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(false); } @Test public void testPutMsgWhenAdaptiveDegradation() { if (notExecuted()) { return; } MessageStoreConfig messageStoreConfig = this.messageStore.getMessageStoreConfig(); messageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER); messageStoreConfig.setTotalReplicas(2); messageStoreConfig.setInSyncReplicas(2); messageStoreConfig.setEnableAutoInSyncReplicas(true); ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(true); this.messageStore.setAliveReplicaNumInGroup(1); MessageExtBrokerInner msg = buildMessage(); PutMessageResult result = this.messageStore.putMessage(msg); assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK); ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(false); messageStoreConfig.setEnableAutoInSyncReplicas(false); } @Test public void testGetBulkCommitLogData() { if (notExecuted()) { return; } RocksDBMessageStore defaultMessageStore = (RocksDBMessageStore) messageStore; messageBody = new byte[2 * 1024 * 1024]; for (int i = 0; i < 10; i++) { MessageExtBrokerInner msg1 = buildMessage(); messageStore.putMessage(msg1); } List bufferResultList = defaultMessageStore.getBulkCommitLogData(0, (int) defaultMessageStore.getMaxPhyOffset()); List msgList = new ArrayList<>(); for (SelectMappedBufferResult bufferResult : bufferResultList) { msgList.addAll(MessageDecoder.decodesBatch(bufferResult.getByteBuffer(), true, false, false)); bufferResult.release(); } assertThat(msgList.size()).isEqualTo(10); } @Test public void testPutLongMessage() { if (notExecuted()) { return; } MessageExtBrokerInner messageExtBrokerInner = buildMessage(); CommitLog commitLog = messageStore.getCommitLog(); MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); MessageExtEncoder.PutMessageThreadLocal putMessageThreadLocal = commitLog.getPutMessageThreadLocal().get(); //body size, topic size, properties size exactly equal to max size messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]); messageExtBrokerInner.setTopic(new String(new byte[127])); messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE])); PutMessageResult encodeResult1 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertNull(encodeResult1); //body size exactly more than max message body size messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize() + 1]); PutMessageResult encodeResult2 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertSame(encodeResult2.getPutMessageStatus(), PutMessageStatus.MESSAGE_ILLEGAL); //body size exactly equal to max message size messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize() + 64 * 1024]); PutMessageResult encodeResult3 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertSame(encodeResult3.getPutMessageStatus(), PutMessageStatus.MESSAGE_ILLEGAL); //message properties length more than properties maxSize messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]); messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE + 1])); PutMessageResult encodeResult4 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertSame(encodeResult4.getPutMessageStatus(), PutMessageStatus.PROPERTIES_SIZE_EXCEEDED); //message length more than buffer length capacity messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]); messageExtBrokerInner.setTopic(new String(new byte[Short.MAX_VALUE])); messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE])); PutMessageResult encodeResult5 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner); assertSame(encodeResult5.getPutMessageStatus(), PutMessageStatus.MESSAGE_ILLEGAL); } @Test public void testDynamicMaxMessageSize() { if (notExecuted()) { return; } MessageExtBrokerInner messageExtBrokerInner = buildMessage(); MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); int originMaxMessageSize = messageStoreConfig.getMaxMessageSize(); messageExtBrokerInner.setBody(new byte[originMaxMessageSize + 10]); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); assertSame(putMessageResult.getPutMessageStatus(), PutMessageStatus.MESSAGE_ILLEGAL); int newMaxMessageSize = originMaxMessageSize + 10; messageStoreConfig.setMaxMessageSize(newMaxMessageSize); putMessageResult = messageStore.putMessage(messageExtBrokerInner); assertSame(putMessageResult.getPutMessageStatus(), PutMessageStatus.PUT_OK); messageStoreConfig.setMaxMessageSize(10); putMessageResult = messageStore.putMessage(messageExtBrokerInner); assertSame(putMessageResult.getPutMessageStatus(), PutMessageStatus.MESSAGE_ILLEGAL); messageStoreConfig.setMaxMessageSize(originMaxMessageSize); } @Test public void testDeleteTopics() { if (notExecuted()) { return; } MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); ConcurrentMap> consumeQueueTable = ((RocksDBMessageStore) messageStore).getConsumeQueueTable(); for (int i = 0; i < 10; i++) { ConcurrentMap cqTable = new ConcurrentHashMap<>(); String topicName = "topic-" + i; for (int j = 0; j < 4; j++) { RocksDBConsumeQueue consumeQueue = new RocksDBConsumeQueue(messageStoreConfig, (RocksDBConsumeQueueStore) messageStore.getQueueStore(), topicName, i); cqTable.put(j, consumeQueue); } consumeQueueTable.put(topicName, cqTable); } assertEquals(consumeQueueTable.size(), 10); HashSet resultSet = Sets.newHashSet("topic-3", "topic-5"); messageStore.deleteTopics(Sets.difference(consumeQueueTable.keySet(), resultSet)); assertEquals(consumeQueueTable.size(), 2); assertEquals(resultSet, consumeQueueTable.keySet()); } @Test public void testCleanUnusedTopic() { if (notExecuted()) { return; } MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig(); ConcurrentMap> consumeQueueTable = ((RocksDBMessageStore) messageStore).getConsumeQueueTable(); for (int i = 0; i < 10; i++) { ConcurrentMap cqTable = new ConcurrentHashMap<>(); String topicName = "topic-" + i; for (int j = 0; j < 4; j++) { RocksDBConsumeQueue consumeQueue = new RocksDBConsumeQueue(messageStoreConfig, (RocksDBConsumeQueueStore) messageStore.getQueueStore(), topicName, i); cqTable.put(j, consumeQueue); } consumeQueueTable.put(topicName, cqTable); } assertEquals(consumeQueueTable.size(), 10); HashSet resultSet = Sets.newHashSet("topic-3", "topic-5"); messageStore.cleanUnusedTopic(resultSet); assertEquals(consumeQueueTable.size(), 2); assertEquals(resultSet, consumeQueueTable.keySet()); } private static class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } private boolean notExecuted() { return MixAll.isMac(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/StoreCheckpointTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: StoreCheckpointTest.java 1831 2013-05-16 01:39:51Z vintagewang@apache.org $ */ package org.apache.rocketmq.store; import java.io.File; import java.io.IOException; import org.apache.rocketmq.common.UtilAll; import org.junit.After; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class StoreCheckpointTest { @Test public void testWriteAndRead() throws IOException { StoreCheckpoint storeCheckpoint = new StoreCheckpoint("target/checkpoint_test/0000"); long physicMsgTimestamp = 0xAABB; long logicsMsgTimestamp = 0xCCDD; long logicsPhysicalOffset = 0x1000L; storeCheckpoint.setPhysicMsgTimestamp(physicMsgTimestamp); storeCheckpoint.setLogicsMsgTimestamp(logicsMsgTimestamp); storeCheckpoint.setLogicsPhysicalOffset(logicsPhysicalOffset); storeCheckpoint.flush(); long diff = physicMsgTimestamp - storeCheckpoint.getMinTimestamp(); assertThat(diff).isEqualTo(3000); storeCheckpoint.shutdown(); storeCheckpoint = new StoreCheckpoint("target/checkpoint_test/0000"); assertThat(storeCheckpoint.getPhysicMsgTimestamp()).isEqualTo(physicMsgTimestamp); assertThat(storeCheckpoint.getLogicsMsgTimestamp()).isEqualTo(logicsMsgTimestamp); assertThat(storeCheckpoint.getLogicsPhysicalOffset()).isEqualTo(logicsPhysicalOffset); } @After public void destroy() { File file = new File("target/checkpoint_test"); UtilAll.deleteFile(file); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/StoreStatsServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; import org.junit.Test; public class StoreStatsServiceTest { @Test public void getSinglePutMessageTopicSizeTotal() throws Exception { final StoreStatsService storeStatsService = new StoreStatsService(); int num = Runtime.getRuntime().availableProcessors() * 2; for (int j = 0; j < 100; j++) { final AtomicReference reference = new AtomicReference<>(null); final CountDownLatch latch = new CountDownLatch(num); final CyclicBarrier barrier = new CyclicBarrier(num); for (int i = 0; i < num; i++) { new Thread(new Runnable() { @Override public void run() { try { barrier.await(); LongAdder longAdder = storeStatsService.getSinglePutMessageTopicSizeTotal("test"); if (reference.compareAndSet(null, longAdder)) { } else if (reference.get() != longAdder) { throw new RuntimeException("Reference should be same!"); } } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } finally { latch.countDown(); } } }).start(); } latch.await(); } } @Test public void getSinglePutMessageTopicTimesTotal() throws Exception { final StoreStatsService storeStatsService = new StoreStatsService(); int num = Runtime.getRuntime().availableProcessors() * 2; for (int j = 0; j < 100; j++) { final AtomicReference reference = new AtomicReference<>(null); final CountDownLatch latch = new CountDownLatch(num); final CyclicBarrier barrier = new CyclicBarrier(num); for (int i = 0; i < num; i++) { new Thread(new Runnable() { @Override public void run() { try { barrier.await(); LongAdder longAdder = storeStatsService.getSinglePutMessageTopicTimesTotal("test"); if (reference.compareAndSet(null, longAdder)) { } else if (reference.get() != longAdder) { throw new RuntimeException("Reference should be same!"); } } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } finally { latch.countDown(); } } }).start(); } latch.await(); } } @Test public void findPutMessageEntireTimePXTest() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { final StoreStatsService storeStatsService = new StoreStatsService(); for (int i = 1; i <= 1000; i++) { for (int j = 0; j < i; j++) { storeStatsService.incPutMessageEntireTime(i); } } Method method = StoreStatsService.class.getDeclaredMethod("resetPutMessageTimeBuckets"); method.setAccessible(true); method.invoke(storeStatsService); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.junit.After; import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; public class StoreTestBase { private static final int QUEUE_TOTAL = 100; private AtomicInteger queueId = new AtomicInteger(0); protected SocketAddress bornHost = new InetSocketAddress("127.0.0.1", 8123); protected SocketAddress storeHost = bornHost; private byte[] messageBody = new byte[1024]; protected Set baseDirs = new HashSet<>(); private static AtomicInteger port = new AtomicInteger(30000); public static synchronized int nextPort() { return port.addAndGet(5); } protected MessageExtBatch buildBatchMessage(int size) { MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic("StoreTest"); messageExtBatch.setTags("TAG1"); messageExtBatch.setKeys("Hello"); messageExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); messageExtBatch.setSysFlag(0); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); messageExtBatch.setBornHost(bornHost); messageExtBatch.setStoreHost(storeHost); List messageList = new ArrayList<>(size); for (int i = 0; i < size; i++) { messageList.add(buildMessage()); } messageExtBatch.setBody(MessageDecoder.encodeMessages(messageList)); return messageExtBatch; } protected MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic("StoreTest"); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); return msg; } protected MessageExtBatch buildIPv6HostBatchMessage(int size) { MessageExtBatch messageExtBatch = new MessageExtBatch(); messageExtBatch.setTopic("StoreTest"); messageExtBatch.setTags("TAG1"); messageExtBatch.setKeys("Hello"); messageExtBatch.setBody(messageBody); messageExtBatch.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExtBatch.setKeys(String.valueOf(System.currentTimeMillis())); messageExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); messageExtBatch.setSysFlag(0); messageExtBatch.setBornHostV6Flag(); messageExtBatch.setStoreHostAddressV6Flag(); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); try { messageExtBatch.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 8123)); } catch (UnknownHostException e) { e.printStackTrace(); } try { messageExtBatch.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 8123)); } catch (UnknownHostException e) { e.printStackTrace(); } List messageList = new ArrayList<>(size); for (int i = 0; i < size; i++) { messageList.add(buildIPv6HostMessage()); } messageExtBatch.setBody(MessageDecoder.encodeMessages(messageList)); return messageExtBatch; } protected MessageExtBrokerInner buildIPv6HostMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic("StoreTest"); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(messageBody); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); msg.setBornTimestamp(System.currentTimeMillis()); try { msg.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 8123)); } catch (UnknownHostException e) { e.printStackTrace(); } try { msg.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 8123)); } catch (UnknownHostException e) { e.printStackTrace(); } return msg; } public static String createBaseDir() { String baseDir = System.getProperty("java.io.tmpdir") + File.separator + "unitteststore" + File.separator + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { System.exit(1); } return baseDir; } public static boolean makeSureFileExists(String fileName) throws Exception { File file = new File(fileName); UtilAll.ensureDirOK(file.getParent()); return file.createNewFile(); } public static void deleteFile(String fileName) { deleteFile(new File(fileName)); } public static void deleteFile(File file) { UtilAll.deleteFile(file); } @After public void clear() { for (String baseDir : baseDirs) { deleteFile(baseDir); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store; import io.openmessaging.storage.dledger.store.file.DefaultMmapFile; import io.openmessaging.storage.dledger.store.file.MmapFile; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.SystemUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.index.IndexFile; import org.apache.rocketmq.store.index.IndexService; import org.apache.rocketmq.store.queue.ConsumeQueueStore; public class StoreTestUtil { private static final Logger log = LoggerFactory.getLogger(StoreTestUtil.class); public static boolean isCommitLogAvailable(DefaultMessageStore store) { try { Field serviceField = null; if (store instanceof RocksDBMessageStore) { serviceField = store.getClass().getSuperclass().getDeclaredField("reputMessageService"); } else { serviceField = store.getClass().getDeclaredField("reputMessageService"); } serviceField.setAccessible(true); DefaultMessageStore.ReputMessageService reputService = (DefaultMessageStore.ReputMessageService) serviceField.get(store); Method method = DefaultMessageStore.ReputMessageService.class.getDeclaredMethod("isCommitLogAvailable"); method.setAccessible(true); return (boolean) method.invoke(reputService); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { throw new RuntimeException(e); } } public static void flushConsumeQueue(DefaultMessageStore store) throws Exception { Field field = store.getQueueStore().getClass().getDeclaredField("flushConsumeQueueService"); field.setAccessible(true); ConsumeQueueStore.FlushConsumeQueueService flushService = (ConsumeQueueStore.FlushConsumeQueueService) field.get(store.getQueueStore()); final int retryTimesOver = 3; Method method = ConsumeQueueStore.FlushConsumeQueueService.class.getDeclaredMethod("doFlush", int.class); method.setAccessible(true); method.invoke(flushService, retryTimesOver); } public static void waitCommitLogReput(DefaultMessageStore store) { for (int i = 0; i < 500 && isCommitLogAvailable(store); i++) { try { Thread.sleep(100); } catch (InterruptedException ignored) { } } if (isCommitLogAvailable(store)) { log.warn("isCommitLogAvailable expected false ,but true"); } } public static void flushConsumeIndex(DefaultMessageStore store) throws NoSuchFieldException, Exception { Field field = store.getClass().getDeclaredField("indexService"); field.setAccessible(true); IndexService indexService = (IndexService) field.get(store); Field field2 = indexService.getClass().getDeclaredField("indexFileList"); field2.setAccessible(true); ArrayList indexFileList = (ArrayList) field2.get(indexService); for (IndexFile f : indexFileList) { indexService.flush(f); } } public static void releaseMmapFilesOnWindows(List mappedFiles) throws IOException { if (!SystemUtils.IS_OS_WINDOWS) { return; } for (final MmapFile mappedFile : mappedFiles) { DefaultMmapFile.clean(mappedFile.getMappedByteBuffer()); mappedFile.getFileChannel().close(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.dledger; import io.openmessaging.storage.dledger.DLedgerServer; import io.openmessaging.storage.dledger.store.file.DLedgerMmapFileStore; import io.openmessaging.storage.dledger.store.file.MmapFileList; import java.io.File; import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.StoreCheckpoint; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.Assume; import org.apache.rocketmq.common.MixAll; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.rocketmq.store.StoreTestUtil.releaseMmapFilesOnWindows; import static org.awaitility.Awaitility.await; public class DLedgerCommitlogTest extends MessageStoreTestBase { @BeforeClass public static void beforeClass() { // Temporarily skip those tests on the macOS as they are flaky Assume.assumeFalse(MixAll.isMac()); } @Ignore @Test public void testTruncateCQ() throws Exception { String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); { DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); DLedgerServer dLedgerServer = dLedgerCommitLog.getdLedgerServer(); DLedgerMmapFileStore dLedgerMmapFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); MmapFileList mmapFileList = dLedgerMmapFileStore.getDataFileList(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doPutMessages(messageStore, topic, 0, 2000, 0); await().atMost(Duration.ofSeconds(10)).until(() -> 2000 == messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(24, mmapFileList.getMappedFiles().size()); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(2000, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); doGetMessages(messageStore, topic, 0, 2000, 0); messageStore.shutdown(); releaseMmapFilesOnWindows(dLedgerMmapFileStore.getDataFileList().getMappedFiles()); } { //Abnormal recover, left some commitlogs DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 4); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); DLedgerServer dLedgerServer = dLedgerCommitLog.getdLedgerServer(); DLedgerMmapFileStore dLedgerMmapFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); MmapFileList mmapFileList = dLedgerMmapFileStore.getDataFileList(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); Assert.assertEquals(20, mmapFileList.getMappedFiles().size()); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1700, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); doGetMessages(messageStore, topic, 0, 1700, 0); messageStore.shutdown(); releaseMmapFilesOnWindows(dLedgerMmapFileStore.getDataFileList().getMappedFiles()); } { //Abnormal recover, left none commitlogs DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 20); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); DLedgerServer dLedgerServer = dLedgerCommitLog.getdLedgerServer(); DLedgerMmapFileStore dLedgerMmapFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); MmapFileList mmapFileList = dLedgerMmapFileStore.getDataFileList(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); Assert.assertEquals(0, mmapFileList.getMappedFiles().size()); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); messageStore.shutdown(); } } @Test public void testRecover() throws Exception { String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); { DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doPutMessages(messageStore, topic, 0, 1000, 0); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); doGetMessages(messageStore, topic, 0, 1000, 0); messageStore.shutdown(); } { //normal recover DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); doGetMessages(messageStore, topic, 0, 1000, 0); messageStore.shutdown(); } { //abnormal recover DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 0); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); doGetMessages(messageStore, topic, 0, 1000, 0); messageStore.shutdown(); } } @Test public void testDLedgerAbnormallyRecover() throws Exception { String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); String topic = UUID.randomUUID().toString(); int messageNumPerQueue = 100; DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); Thread.sleep(1000); doPutMessages(messageStore, topic, 0, messageNumPerQueue, 0); doPutMessages(messageStore, topic, 1, messageNumPerQueue, 0); Thread.sleep(1000); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(messageNumPerQueue, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); doGetMessages(messageStore, topic, 0, messageNumPerQueue, 0); StoreCheckpoint storeCheckpoint = messageStore.getStoreCheckpoint(); storeCheckpoint.setPhysicMsgTimestamp(0); storeCheckpoint.setLogicsMsgTimestamp(0); messageStore.shutdown(); String fileName = StorePathConfigHelper.getAbortFile(base); makeSureFileExists(fileName); File file = new File(base + File.separator + "consumequeue" + File.separator + topic + File.separator + "0" + File.separator + "00000000000000001040"); file.delete(); // truncateAllConsumeQueue(base + File.separator + "consumequeue" + File.separator + topic + File.separator); messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); Thread.sleep(1000); doGetMessages(messageStore, topic, 0, messageNumPerQueue, 0); doGetMessages(messageStore, topic, 1, messageNumPerQueue, 0); messageStore.shutdown(); } @Test public void testPutAndGetMessage() throws Exception { String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); String topic = UUID.randomUUID().toString(); List results = new ArrayList<>(); for (int i = 0; i < 10; i++) { MessageExtBrokerInner msgInner = i < 5 ? buildMessage() : buildIPv6HostMessage(); msgInner.setTopic(topic); msgInner.setQueueId(0); PutMessageResult putMessageResult = messageStore.putMessage(msgInner); results.add(putMessageResult); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i, putMessageResult.getAppendMessageResult().getLogicsOffset()); } await().atMost(Duration.ofSeconds(10)).until(() -> 10 == messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(10, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 0, 32, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(10, getMessageResult.getMessageBufferList().size()); Assert.assertEquals(10, getMessageResult.getMessageMapedList().size()); for (int i = 0; i < results.size(); i++) { ByteBuffer buffer = getMessageResult.getMessageBufferList().get(i); MessageExt messageExt = MessageDecoder.decode(buffer); Assert.assertEquals(i, messageExt.getQueueOffset()); Assert.assertEquals(results.get(i).getAppendMessageResult().getMsgId(), messageExt.getMsgId()); Assert.assertEquals(results.get(i).getAppendMessageResult().getWroteOffset(), messageExt.getCommitLogOffset()); } messageStore.destroy(); messageStore.shutdown(); } @Test public void testBatchPutAndGetMessage() throws Exception { String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); String topic = UUID.randomUUID().toString(); // should be less than 4 int batchMessageSize = 2; int repeat = 10; List results = new ArrayList<>(); for (int i = 0; i < repeat; i++) { MessageExtBatch messageExtBatch = i < repeat / 10 ? buildBatchMessage(batchMessageSize) : buildIPv6HostBatchMessage(batchMessageSize); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(0); PutMessageResult putMessageResult = messageStore.putMessages(messageExtBatch); results.add(putMessageResult); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i * batchMessageSize, putMessageResult.getAppendMessageResult().getLogicsOffset()); } await().atMost(Duration.ofSeconds(10)).until(() -> repeat * batchMessageSize == messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(repeat * batchMessageSize, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 0, 100, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(repeat * batchMessageSize > 32 ? 32 : repeat * batchMessageSize, getMessageResult.getMessageBufferList().size()); Assert.assertEquals(repeat * batchMessageSize > 32 ? 32 : repeat * batchMessageSize, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(repeat * batchMessageSize, getMessageResult.getMaxOffset()); for (int i = 0; i < results.size(); i++) { ByteBuffer buffer = getMessageResult.getMessageBufferList().get(i * batchMessageSize); MessageExt messageExt = MessageDecoder.decode(buffer); Assert.assertEquals(i * batchMessageSize, messageExt.getQueueOffset()); Assert.assertEquals(results.get(i).getAppendMessageResult().getMsgId().split(",").length, batchMessageSize); Assert.assertEquals(results.get(i).getAppendMessageResult().getWroteOffset(), messageExt.getCommitLogOffset()); } messageStore.destroy(); messageStore.shutdown(); } @Test public void testAsyncPutAndGetMessage() throws Exception { Assume.assumeFalse(MixAll.isWindows()); String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); String topic = UUID.randomUUID().toString(); List results = new ArrayList<>(); for (int i = 0; i < 10; i++) { MessageExtBrokerInner msgInner = i < 5 ? buildMessage() : buildIPv6HostMessage(); msgInner.setTopic(topic); msgInner.setQueueId(0); CompletableFuture futureResult = messageStore.asyncPutMessage(msgInner); PutMessageResult putMessageResult = futureResult.get(3000, TimeUnit.MILLISECONDS); results.add(putMessageResult); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i, putMessageResult.getAppendMessageResult().getLogicsOffset()); } await().atMost(Duration.ofSeconds(10)).until(() -> 10 == messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(10, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 0, 32, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(10, getMessageResult.getMessageBufferList().size()); Assert.assertEquals(10, getMessageResult.getMessageMapedList().size()); for (int i = 0; i < results.size(); i++) { ByteBuffer buffer = getMessageResult.getMessageBufferList().get(i); MessageExt messageExt = MessageDecoder.decode(buffer); Assert.assertEquals(i, messageExt.getQueueOffset()); Assert.assertEquals(results.get(i).getAppendMessageResult().getMsgId(), messageExt.getMsgId()); Assert.assertEquals(results.get(i).getAppendMessageResult().getWroteOffset(), messageExt.getCommitLogOffset()); } messageStore.destroy(); messageStore.shutdown(); } @Test public void testAsyncBatchPutAndGetMessage() throws Exception { String base = createBaseDir(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); String topic = UUID.randomUUID().toString(); // should be less than 4 int batchMessageSize = 2; int repeat = 10; List results = new ArrayList<>(); for (int i = 0; i < repeat; i++) { MessageExtBatch messageExtBatch = i < 5 ? buildBatchMessage(batchMessageSize) : buildIPv6HostBatchMessage(batchMessageSize); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(0); CompletableFuture futureResult = messageStore.asyncPutMessages(messageExtBatch); PutMessageResult putMessageResult = futureResult.get(3000, TimeUnit.MILLISECONDS); results.add(putMessageResult); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i * batchMessageSize, putMessageResult.getAppendMessageResult().getLogicsOffset()); } await().atMost(Duration.ofSeconds(10)).until(() -> repeat * batchMessageSize == messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(repeat * batchMessageSize, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.dispatchBehindBytes()); GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 0, 32, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(repeat * batchMessageSize > 32 ? 32 : repeat * batchMessageSize, getMessageResult.getMessageBufferList().size()); Assert.assertEquals(repeat * batchMessageSize > 32 ? 32 : repeat * batchMessageSize, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(repeat * batchMessageSize, getMessageResult.getMaxOffset()); for (int i = 0; i < results.size(); i++) { ByteBuffer buffer = getMessageResult.getMessageBufferList().get(i * batchMessageSize); MessageExt messageExt = MessageDecoder.decode(buffer); Assert.assertEquals(i * batchMessageSize, messageExt.getQueueOffset()); Assert.assertEquals(results.get(i).getAppendMessageResult().getMsgId().split(",").length, batchMessageSize); Assert.assertEquals(results.get(i).getAppendMessageResult().getWroteOffset(), messageExt.getCommitLogOffset()); } messageStore.destroy(); messageStore.shutdown(); } @Test public void testCommittedPos() throws Exception { String peers = String.format("n0-localhost:%d;n1-localhost:%d", nextPort(), nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore leaderStore = createDledgerMessageStore(createBaseDir(), group, "n0", peers, "n0", false, 0); String topic = UUID.randomUUID().toString(); MessageExtBrokerInner msgInner = buildMessage(); msgInner.setTopic(topic); msgInner.setQueueId(0); PutMessageResult putMessageResult = leaderStore.putMessage(msgInner); Assert.assertEquals(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, putMessageResult.getPutMessageStatus()); Assert.assertEquals(0, leaderStore.getCommitLog().getMaxOffset()); Assert.assertEquals(0, leaderStore.getMaxOffsetInQueue(topic, 0)); DefaultMessageStore followerStore = createDledgerMessageStore(createBaseDir(), group, "n1", peers, "n0", false, 0); await().atMost(10, SECONDS).until(followerCatchesUp(followerStore, topic)); Assert.assertEquals(1, leaderStore.getMaxOffsetInQueue(topic, 0)); Assert.assertTrue(leaderStore.getCommitLog().getMaxOffset() > 0); leaderStore.destroy(); followerStore.destroy(); leaderStore.shutdown(); followerStore.shutdown(); } @Test public void testIPv6HostMsgCommittedPos() throws Exception { String peers = String.format("n0-localhost:%d;n1-localhost:%d", nextPort(), nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore leaderStore = createDledgerMessageStore(createBaseDir(), group, "n0", peers, "n0", false, 0); String topic = UUID.randomUUID().toString(); MessageExtBrokerInner msgInner = buildIPv6HostMessage(); msgInner.setTopic(topic); msgInner.setQueueId(0); PutMessageResult putMessageResult = leaderStore.putMessage(msgInner); Assert.assertEquals(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, putMessageResult.getPutMessageStatus()); //Thread.sleep(1000); Assert.assertEquals(0, leaderStore.getCommitLog().getMaxOffset()); Assert.assertEquals(0, leaderStore.getMaxOffsetInQueue(topic, 0)); DefaultMessageStore followerStore = createDledgerMessageStore(createBaseDir(), group, "n1", peers, "n0", false, 0); await().atMost(10, SECONDS).until(followerCatchesUp(followerStore, topic)); Assert.assertEquals(1, leaderStore.getMaxOffsetInQueue(topic, 0)); Assert.assertTrue(leaderStore.getCommitLog().getMaxOffset() > 0); leaderStore.destroy(); followerStore.destroy(); leaderStore.shutdown(); followerStore.shutdown(); } private Callable followerCatchesUp(DefaultMessageStore followerStore, String topic) { return () -> followerStore.getMaxOffsetInQueue(topic, 0) == 1; } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerMultiPathTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.dledger; import java.io.File; import java.time.Duration; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Assert; import org.junit.Test; import org.junit.Assume; import static org.awaitility.Awaitility.await; public class DLedgerMultiPathTest extends MessageStoreTestBase { @Test public void multiDirsStorageTest() throws Exception { Assume.assumeFalse(MixAll.isMac()); Assume.assumeFalse(MixAll.isWindows()); String base = createBaseDir(); String topic = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); String multiStorePath = base + "/multi/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER + base + "/multi/b/" + MessageStoreConfig.MULTI_PATH_SPLITTER + base + "/multi/c/" + MessageStoreConfig.MULTI_PATH_SPLITTER; { DefaultMessageStore dLedgerStore = createDLedgerMessageStore(base, group, "n0", peers, multiStorePath, null); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) dLedgerStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doPutMessages(dLedgerStore, topic, 0, 1000, 0); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == dLedgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(11, dLedgerStore.getMaxPhyOffset() / dLedgerStore.getMessageStoreConfig().getMappedFileSizeCommitLog()); Assert.assertEquals(0, dLedgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, dLedgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dLedgerStore.dispatchBehindBytes()); doGetMessages(dLedgerStore, topic, 0, 1000, 0); dLedgerStore.shutdown(); } { String readOnlyPath = base + "/multi/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER + base + "/multi/b/" + MessageStoreConfig.MULTI_PATH_SPLITTER; multiStorePath = base + "/multi/c/" + MessageStoreConfig.MULTI_PATH_SPLITTER + base + "/multi/d/" + MessageStoreConfig.MULTI_PATH_SPLITTER; DefaultMessageStore dLedgerStore = createDLedgerMessageStore(base, group, "n0", peers, multiStorePath, readOnlyPath); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) dLedgerStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doGetMessages(dLedgerStore, topic, 0, 1000, 0); long beforeSize = Objects.requireNonNull(new File(base + "/multi/a/").listFiles()).length; doPutMessages(dLedgerStore, topic, 0, 1000, 1000); await().atMost(Duration.ofSeconds(10)).until(() -> 2000 == dLedgerStore.getMaxOffsetInQueue(topic, 0)); long afterSize = Objects.requireNonNull(new File(base + "/multi/a/").listFiles()).length; Assert.assertEquals(beforeSize, afterSize); Assert.assertEquals(0, dLedgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(2000, dLedgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dLedgerStore.dispatchBehindBytes()); dLedgerStore.shutdown(); } } protected DefaultMessageStore createDLedgerMessageStore(String base, String group, String selfId, String peers, String dLedgerCommitLogPath, String readOnlyPath) throws Exception { MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setMappedFileSizeCommitLog(1024 * 100); storeConfig.setMappedFileSizeConsumeQueue(1024); storeConfig.setMaxHashSlotNum(100); storeConfig.setMaxIndexNum(100 * 10); storeConfig.setStorePathRootDir(base); storeConfig.setStorePathDLedgerCommitLog(dLedgerCommitLogPath); storeConfig.setReadOnlyCommitLogStorePaths(readOnlyPath); storeConfig.setFlushDiskType(FlushDiskType.ASYNC_FLUSH); storeConfig.setEnableDLegerCommitLog(true); storeConfig.setdLegerGroup(group); storeConfig.setdLegerPeers(peers); storeConfig.setdLegerSelfId(selfId); DefaultMessageStore defaultMessageStore = new DefaultMessageStore(storeConfig, new BrokerStatsManager("DLedgerCommitLogTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, new BrokerConfig(), new ConcurrentHashMap<>()); Assert.assertTrue(defaultMessageStore.load()); defaultMessageStore.start(); return defaultMessageStore; } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/dledger/MessageStoreTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.dledger; import com.google.common.util.concurrent.RateLimiter; import io.openmessaging.storage.dledger.DLedgerConfig; import io.openmessaging.storage.dledger.DLedgerServer; import java.io.File; import java.net.UnknownHostException; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.StoreTestBase; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Assert; public class MessageStoreTestBase extends StoreTestBase { protected DefaultMessageStore createDledgerMessageStore(String base, String group, String selfId, String peers, String leaderId, boolean createAbort, int deleteFileNum) throws Exception { System.setProperty("dledger.disk.ratio.check", "0.95"); System.setProperty("dledger.disk.ratio.clean", "0.95"); baseDirs.add(base); MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setMappedFileSizeCommitLog(1024 * 100); storeConfig.setMappedFileSizeConsumeQueue(1024); storeConfig.setMaxHashSlotNum(100); storeConfig.setMaxIndexNum(100 * 10); storeConfig.setStorePathRootDir(base); storeConfig.setStorePathCommitLog(base + File.separator + "commitlog"); storeConfig.setFlushDiskType(FlushDiskType.ASYNC_FLUSH); storeConfig.setEnableDLegerCommitLog(true); storeConfig.setdLegerGroup(group); storeConfig.setdLegerPeers(peers); storeConfig.setdLegerSelfId(selfId); storeConfig.setRecheckReputOffsetFromCq(true); DefaultMessageStore defaultMessageStore = new DefaultMessageStore(storeConfig, new BrokerStatsManager("DLedgerCommitlogTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, new BrokerConfig(), new ConcurrentHashMap<>()); DLedgerServer dLegerServer = ((DLedgerCommitLog) defaultMessageStore.getCommitLog()).getdLedgerServer(); if (leaderId != null) { dLegerServer.getdLedgerConfig().setEnableLeaderElector(false); if (selfId.equals(leaderId)) { dLegerServer.getMemberState().changeToLeader(0); } else { dLegerServer.getMemberState().changeToFollower(0, leaderId); } } if (createAbort) { String fileName = StorePathConfigHelper.getAbortFile(storeConfig.getStorePathRootDir()); makeSureFileExists(fileName); } if (deleteFileNum > 0) { DLedgerConfig config = dLegerServer.getdLedgerConfig(); if (deleteFileNum > 0) { File dir = new File(config.getDataStorePath()); File[] files = dir.listFiles(); if (files != null) { Arrays.sort(files); for (int i = files.length - 1; i >= 0; i--) { File file = files[i]; file.delete(); if (files.length - i >= deleteFileNum) { break; } } } } } Assert.assertTrue(defaultMessageStore.load()); defaultMessageStore.start(); return defaultMessageStore; } protected DefaultMessageStore createMessageStore(String base, boolean createAbort) throws Exception { baseDirs.add(base); MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setMappedFileSizeCommitLog(1024 * 100); storeConfig.setMappedFileSizeConsumeQueue(1024); storeConfig.setMaxHashSlotNum(100); storeConfig.setMaxIndexNum(100 * 10); storeConfig.setStorePathRootDir(base); storeConfig.setStorePathCommitLog(base + File.separator + "commitlog"); storeConfig.setFlushDiskType(FlushDiskType.ASYNC_FLUSH); DefaultMessageStore defaultMessageStore = new DefaultMessageStore(storeConfig, new BrokerStatsManager("CommitlogTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, new BrokerConfig(), new ConcurrentHashMap<>()); if (createAbort) { String fileName = StorePathConfigHelper.getAbortFile(storeConfig.getStorePathRootDir()); makeSureFileExists(fileName); } Assert.assertTrue(defaultMessageStore.load()); defaultMessageStore.start(); return defaultMessageStore; } protected void doPutMessages(MessageStore messageStore, String topic, int queueId, int num, long beginLogicsOffset) throws UnknownHostException { RateLimiter rateLimiter = RateLimiter.create(100); MessageStoreConfig storeConfig = messageStore.getMessageStoreConfig(); boolean limitAppendRate = storeConfig.isEnableDLegerCommitLog(); for (int i = 0; i < num; i++) { if (limitAppendRate) { rateLimiter.acquire(); } MessageExtBrokerInner msgInner = buildMessage(); msgInner.setTopic(topic); msgInner.setQueueId(queueId); PutMessageResult putMessageResult = messageStore.putMessage(msgInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(beginLogicsOffset + i, putMessageResult.getAppendMessageResult().getLogicsOffset()); } } protected void doGetMessages(MessageStore messageStore, String topic, int queueId, int num, long beginLogicsOffset) { for (int i = 0; i < num; i++) { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, queueId, beginLogicsOffset + i, 3, null); Assert.assertNotNull(getMessageResult); Assert.assertTrue(!getMessageResult.getMessageBufferList().isEmpty()); MessageExt messageExt = MessageDecoder.decode(getMessageResult.getMessageBufferList().get(0)); Assert.assertEquals(beginLogicsOffset + i, messageExt.getQueueOffset()); getMessageResult.release(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/dledger/MixCommitlogTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.dledger; import java.time.Duration; import java.util.UUID; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.StoreTestBase; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.junit.Assert; import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; import static org.awaitility.Awaitility.await; public class MixCommitlogTest extends MessageStoreTestBase { @Ignore @Test public void testFallBehindCQ() throws Exception { Assume.assumeFalse(MixAll.isWindows()); Assume.assumeFalse(MixAll.isMac()); String base = createBaseDir(); String topic = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); { DefaultMessageStore originalStore = createMessageStore(base, false); doPutMessages(originalStore, topic, 0, 1000, 0); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == originalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(11, originalStore.getMaxPhyOffset() / originalStore.getMessageStoreConfig().getMappedFileSizeCommitLog()); Assert.assertEquals(0, originalStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, originalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, originalStore.dispatchBehindBytes()); doGetMessages(originalStore, topic, 0, 1000, 0); originalStore.shutdown(); } //delete the cq files { StoreTestBase.deleteFile(StorePathConfigHelper.getStorePathConsumeQueue(base)); } { DefaultMessageStore dledgerStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) dledgerStore.getCommitLog(); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.dispatchBehindBytes()); doGetMessages(dledgerStore, topic, 0, 1000, 0); doPutMessages(dledgerStore, topic, 0, 1000, 1000); await().atMost(Duration.ofSeconds(10)).until(() -> 2000 == dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(2000, dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.dispatchBehindBytes()); doGetMessages(dledgerStore, topic, 0, 2000, 0); dledgerStore.shutdown(); } } @Test public void testPutAndGet() throws Exception { Assume.assumeFalse(MixAll.isMac()); String base = createBaseDir(); String topic = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); long dividedOffset; { DefaultMessageStore originalStore = createMessageStore(base, false); doPutMessages(originalStore, topic, 0, 1000, 0); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == originalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, originalStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, originalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, originalStore.dispatchBehindBytes()); dividedOffset = originalStore.getCommitLog().getMaxOffset(); dividedOffset = dividedOffset - dividedOffset % originalStore.getMessageStoreConfig().getMappedFileSizeCommitLog() + originalStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); doGetMessages(originalStore, topic, 0, 1000, 0); originalStore.shutdown(); } { DefaultMessageStore recoverOriginalStore = createMessageStore(base, true); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == recoverOriginalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, recoverOriginalStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, recoverOriginalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, recoverOriginalStore.dispatchBehindBytes()); doGetMessages(recoverOriginalStore, topic, 0, 1000, 0); recoverOriginalStore.shutdown(); } { DefaultMessageStore dledgerStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) dledgerStore.getCommitLog(); Assert.assertFalse(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getDividedCommitlogOffset()); Assert.assertEquals(0, dledgerStore.dispatchBehindBytes()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getMaxOffset()); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doPutMessages(dledgerStore, topic, 0, 1000, 1000); await().atMost(Duration.ofSeconds(10)).until(() -> 2000 == dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(2000, dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.dispatchBehindBytes()); doGetMessages(dledgerStore, topic, 0, 2000, 0); dledgerStore.shutdown(); } { DefaultMessageStore recoverDledgerStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) recoverDledgerStore.getCommitLog(); Assert.assertFalse(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getDividedCommitlogOffset()); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doPutMessages(recoverDledgerStore, topic, 0, 1000, 2000); await().atMost(Duration.ofSeconds(10)).until(() -> 3000 == recoverDledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, recoverDledgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(3000, recoverDledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, recoverDledgerStore.dispatchBehindBytes()); doGetMessages(recoverDledgerStore, topic, 0, 3000, 0); recoverDledgerStore.shutdown(); } } @Test public void testDeleteExpiredFiles() throws Exception { Assume.assumeFalse(MixAll.isMac()); String base = createBaseDir(); String topic = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); long dividedOffset; { DefaultMessageStore originalStore = createMessageStore(base, false); doPutMessages(originalStore, topic, 0, 1000, 0); await().atMost(Duration.ofSeconds(10)).until(() -> 1000 == originalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, originalStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(1000, originalStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, originalStore.dispatchBehindBytes()); dividedOffset = originalStore.getCommitLog().getMaxOffset(); dividedOffset = dividedOffset - dividedOffset % originalStore.getMessageStoreConfig().getMappedFileSizeCommitLog() + originalStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); originalStore.shutdown(); } long maxPhysicalOffset; { DefaultMessageStore dledgerStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 0); DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) dledgerStore.getCommitLog(); Assert.assertTrue(dledgerStore.getMessageStoreConfig().isCleanFileForciblyEnable()); Assert.assertFalse(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getDividedCommitlogOffset()); Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); Assert.assertTrue(success); doPutMessages(dledgerStore, topic, 0, 1000, 1000); await().atMost(Duration.ofSeconds(10)).until(() -> 2000 == dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(2000, dledgerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, dledgerStore.dispatchBehindBytes()); Assert.assertEquals(0, dledgerStore.getMinPhyOffset()); maxPhysicalOffset = dledgerStore.getMaxPhyOffset(); Assert.assertTrue(maxPhysicalOffset > 0); doGetMessages(dledgerStore, topic, 0, 2000, 0); for (int i = 0; i < 100; i++) { dledgerStore.getCommitLog().deleteExpiredFile(System.currentTimeMillis(), 0, 0, true); } Assert.assertEquals(dividedOffset, dledgerStore.getMinPhyOffset()); Assert.assertEquals(maxPhysicalOffset, dledgerStore.getMaxPhyOffset()); for (int i = 0; i < 100; i++) { Assert.assertEquals(Integer.MAX_VALUE, dledgerStore.getCommitLog().deleteExpiredFile(System.currentTimeMillis(), 0, 0, true)); } Assert.assertEquals(dividedOffset, dledgerStore.getMinPhyOffset()); Assert.assertEquals(maxPhysicalOffset, dledgerStore.getMaxPhyOffset()); Assert.assertTrue(dledgerStore.getMessageStoreConfig().isCleanFileForciblyEnable()); Assert.assertTrue(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); //Test fresh dledgerStore.getMessageStoreConfig().setCleanFileForciblyEnable(false); for (int i = 0; i < 100; i++) { Assert.assertEquals(Integer.MAX_VALUE, dledgerStore.getCommitLog().deleteExpiredFile(System.currentTimeMillis(), 0, 0, true)); } Assert.assertFalse(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); doGetMessages(dledgerStore, topic, 0, 1000, 1000); dledgerStore.shutdown(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ha/FlowMonitorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.time.Duration; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Test; import static org.awaitility.Awaitility.await; public class FlowMonitorTest { @Test public void testLimit() throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setHaFlowControlEnable(true); messageStoreConfig.setMaxHaTransferByteInSecond(10); FlowMonitor flowMonitor = new FlowMonitor(messageStoreConfig); flowMonitor.start(); flowMonitor.addByteCountTransferred(3); Boolean flag = await().atMost(Duration.ofSeconds(2)).until(() -> 7 == flowMonitor.canTransferMaxByteNum(), item -> item); flag &= await().atMost(Duration.ofSeconds(2)).until(() -> 10 == flowMonitor.canTransferMaxByteNum(), item -> item); Assert.assertTrue(flag); flowMonitor.shutdown(); } @Test public void testSpeed() throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setHaFlowControlEnable(true); messageStoreConfig.setMaxHaTransferByteInSecond(10); FlowMonitor flowMonitor = new FlowMonitor(messageStoreConfig); flowMonitor.addByteCountTransferred(3); flowMonitor.calculateSpeed(); Assert.assertEquals(3, flowMonitor.getTransferredByteInSecond()); flowMonitor.addByteCountTransferred(5); flowMonitor.calculateSpeed(); Assert.assertEquals(5, flowMonitor.getTransferredByteInSecond()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ha/HAClientTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.store.DefaultMessageStore; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class HAClientTest { private HAClient haClient; @Mock private DefaultMessageStore messageStore; @Before public void setUp() throws Exception { // when(messageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); when(messageStore.getBrokerConfig()).thenReturn(new BrokerConfig()); this.haClient = new DefaultHAClient(this.messageStore); } @After public void tearDown() throws Exception { this.haClient.shutdown(); } @Test public void updateMasterAddress() { assertThat(this.haClient.getMasterAddress()).isNull(); this.haClient.updateMasterAddress("127.0.0.1:10911"); assertThat(this.haClient.getMasterAddress()).isEqualTo("127.0.0.1:10911"); this.haClient.updateMasterAddress("127.0.0.1:10912"); assertThat(this.haClient.getMasterAddress()).isEqualTo("127.0.0.1:10912"); } @Test public void updateHaMasterAddress() { assertThat(this.haClient.getHaMasterAddress()).isNull(); this.haClient.updateHaMasterAddress("127.0.0.1:10911"); assertThat(this.haClient.getHaMasterAddress()).isEqualTo("127.0.0.1:10911"); this.haClient.updateHaMasterAddress("127.0.0.1:10912"); assertThat(this.haClient.getHaMasterAddress()).isEqualTo("127.0.0.1:10912"); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.rocksdb.RocksDBException; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @RunWith(MockitoJUnitRunner.Silent.class) public class HAServerTest { private DefaultMessageStore defaultMessageStore; private MessageStoreConfig storeConfig; private HAService haService; private Random random = new Random(); private List haClientList = new ArrayList<>(); @Before public void setUp() throws Exception { this.storeConfig = new MessageStoreConfig(); this.storeConfig.setHaListenPort(9000 + random.nextInt(1000)); this.storeConfig.setHaSendHeartbeatInterval(10); this.defaultMessageStore = mockMessageStore(); this.haService = new DefaultHAService(); this.haService.init(defaultMessageStore); this.haService.start(); } @After public void tearDown() { tearDownAllHAClient(); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() throws Exception { return HAServerTest.this.haService.getConnectionCount().get() == 0; } }); this.haService.shutdown(); } @Test public void testConnectionList_OneHAClient() throws IOException { setUpOneHAClient(); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() { return HAServerTest.this.haService.getConnectionCount().get() == 1; } }); } @Test public void testConnectionList_MultipleHAClient() throws IOException { setUpOneHAClient(); setUpOneHAClient(); setUpOneHAClient(); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() { return HAServerTest.this.haService.getConnectionCount().get() == 3; } }); tearDownOneHAClient(); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() { return HAServerTest.this.haService.getConnectionCount().get() == 2; } }); } @Test public void inSyncReplicasNums() throws IOException, RocksDBException { DefaultMessageStore messageStore = mockMessageStore(); doReturn(123L).when(messageStore).getMaxPhyOffset(); doReturn(123L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); messageStore = mockMessageStore(); doReturn(124L).when(messageStore).getMaxPhyOffset(); doReturn(124L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); messageStore = mockMessageStore(); doReturn(123L).when(messageStore).getMaxPhyOffset(); doReturn(123L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); messageStore = mockMessageStore(); doReturn(125L).when(messageStore).getMaxPhyOffset(); doReturn(125L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); final int haSlaveFallbehindMax = this.defaultMessageStore.getMessageStoreConfig().getHaMaxGapNotInSync(); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() throws Exception { return HAServerTest.this.haService.inSyncReplicasNums(haSlaveFallbehindMax) == 5; } }); assertThat(HAServerTest.this.haService.inSyncReplicasNums(123L + haSlaveFallbehindMax)).isEqualTo(3); assertThat(HAServerTest.this.haService.inSyncReplicasNums(124L + haSlaveFallbehindMax)).isEqualTo(2); assertThat(HAServerTest.this.haService.inSyncReplicasNums(125L + haSlaveFallbehindMax)).isEqualTo(1); } @Test public void isSlaveOK() throws IOException, RocksDBException { DefaultMessageStore messageStore = mockMessageStore(); doReturn(123L).when(messageStore).getMaxPhyOffset(); doReturn(123L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); messageStore = mockMessageStore(); doReturn(124L).when(messageStore).getMaxPhyOffset(); doReturn(124L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); final int haSlaveFallbehindMax = this.defaultMessageStore.getMessageStoreConfig().getHaMaxGapNotInSync(); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() throws Exception { return HAServerTest.this.haService.isSlaveOK(haSlaveFallbehindMax + 123); } }); assertThat(HAServerTest.this.haService.isSlaveOK(122L + haSlaveFallbehindMax)).isTrue(); assertThat(HAServerTest.this.haService.isSlaveOK(124L + haSlaveFallbehindMax)).isFalse(); } @Test public void putRequest_SingleAck() throws IOException, ExecutionException, InterruptedException, TimeoutException, RocksDBException { CommitLog.GroupCommitRequest request = new CommitLog.GroupCommitRequest(124, 4000, 1); this.haService.putRequest(request); assertThat(request.future().get()).isEqualTo(PutMessageStatus.FLUSH_SLAVE_TIMEOUT); DefaultMessageStore messageStore = mockMessageStore(); doReturn(124L).when(messageStore).getMaxPhyOffset(); doReturn(124L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); request = new CommitLog.GroupCommitRequest(124, 4000, 1); this.haService.putRequest(request); assertThat(request.future().get()).isEqualTo(PutMessageStatus.PUT_OK); } @Test public void putRequest_MultipleAckAndRequests() throws IOException, ExecutionException, InterruptedException, RocksDBException { CommitLog.GroupCommitRequest oneAck = new CommitLog.GroupCommitRequest(124, 4000, 2); this.haService.putRequest(oneAck); CommitLog.GroupCommitRequest twoAck = new CommitLog.GroupCommitRequest(124, 4000, 3); this.haService.putRequest(twoAck); DefaultMessageStore messageStore = mockMessageStore(); doReturn(125L).when(messageStore).getMaxPhyOffset(); doReturn(125L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); assertThat(oneAck.future().get()).isEqualTo(PutMessageStatus.PUT_OK); assertThat(twoAck.future().get()).isEqualTo(PutMessageStatus.FLUSH_SLAVE_TIMEOUT); messageStore = mockMessageStore(); doReturn(128L).when(messageStore).getMaxPhyOffset(); doReturn(128L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); twoAck = new CommitLog.GroupCommitRequest(124, 4000, 3); this.haService.putRequest(twoAck); assertThat(twoAck.future().get()).isEqualTo(PutMessageStatus.PUT_OK); } @Test public void getPush2SlaveMaxOffset() throws IOException, RocksDBException { DefaultMessageStore messageStore = mockMessageStore(); doReturn(123L).when(messageStore).getMaxPhyOffset(); doReturn(123L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); messageStore = mockMessageStore(); doReturn(124L).when(messageStore).getMaxPhyOffset(); doReturn(124L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); messageStore = mockMessageStore(); doReturn(125L).when(messageStore).getMaxPhyOffset(); doReturn(125L).when(messageStore).getMasterFlushedOffset(); setUpOneHAClient(messageStore); await().atMost(Duration.ofMinutes(1)).until(new Callable() { @Override public Boolean call() throws Exception { return HAServerTest.this.haService.getPush2SlaveMaxOffset().get() == 125L; } }); } private void setUpOneHAClient(DefaultMessageStore defaultMessageStore) throws IOException { HAClient haClient = new DefaultHAClient(defaultMessageStore); haClient.updateHaMasterAddress("127.0.0.1:" + this.storeConfig.getHaListenPort()); haClient.start(); this.haClientList.add(haClient); } private void setUpOneHAClient() throws IOException { HAClient haClient = new DefaultHAClient(this.defaultMessageStore); haClient.updateHaMasterAddress("127.0.0.1:" + this.storeConfig.getHaListenPort()); haClient.start(); this.haClientList.add(haClient); } private DefaultMessageStore mockMessageStore() throws IOException, RocksDBException { DefaultMessageStore messageStore = mock(DefaultMessageStore.class); BrokerConfig brokerConfig = mock(BrokerConfig.class); doReturn(true).when(brokerConfig).isInBrokerContainer(); doReturn("mock").when(brokerConfig).getIdentifier(); doReturn(brokerConfig).when(messageStore).getBrokerConfig(); doReturn(new SystemClock()).when(messageStore).getSystemClock(); doAnswer(invocation -> System.currentTimeMillis()).when(messageStore).now(); doReturn(this.storeConfig).when(messageStore).getMessageStoreConfig(); doReturn(new BrokerConfig()).when(messageStore).getBrokerConfig(); doReturn(true).when(messageStore).isOffsetAligned(anyLong()); // doReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))).when(messageStore).sendMsgBack(anyLong()); doReturn(true).when(messageStore).truncateFiles(anyLong()); DefaultMessageStore masterStore = mock(DefaultMessageStore.class); doReturn(Long.MAX_VALUE).when(masterStore).getFlushedWhere(); doReturn(masterStore).when(messageStore).getMasterStoreInProcess(); CommitLog commitLog = new CommitLog(messageStore); doReturn(commitLog).when(messageStore).getCommitLog(); return messageStore; } private void tearDownOneHAClient() { final HAClient haClient = this.haClientList.remove(0); haClient.shutdown(); } private void tearDownAllHAClient() { for (final HAClient client : this.haClientList) { client.shutdown(); } this.haClientList.clear(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ha/WaitNotifyObjectTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha; import org.junit.Assert; import org.junit.Test; public class WaitNotifyObjectTest { @Test public void removeFromWaitingThreadTable() throws Exception { final WaitNotifyObject waitNotifyObject = new WaitNotifyObject(); for (int i = 0; i < 5; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { waitNotifyObject.allWaitForRunning(100); waitNotifyObject.removeFromWaitingThreadTable(); } }); t.start(); t.join(); } Assert.assertEquals(0, waitNotifyObject.waitingThreadTable.size()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MappedFileQueue; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.StoreCheckpoint; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; import org.rocksdb.RocksDBException; import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class AutoSwitchHATest { private final String storeMessage = "Once, there was a chance for me!"; private final int defaultMappedFileSize = 1024 * 1024; private int queueTotal = 100; private AtomicInteger queueId = new AtomicInteger(0); private SocketAddress bornHost; private SocketAddress storeHost; private byte[] messageBody; private DefaultMessageStore messageStore1; private DefaultMessageStore messageStore2; private DefaultMessageStore messageStore3; private MessageStoreConfig storeConfig1; private MessageStoreConfig storeConfig2; private MessageStoreConfig storeConfig3; private String store1HaAddress; private String store2HaAddress; private BrokerStatsManager brokerStatsManager = new BrokerStatsManager("simpleTest", true); private String tmpdir = System.getProperty("java.io.tmpdir"); private String storePathRootParentDir = (StringUtils.endsWith(tmpdir, File.separator) ? tmpdir : tmpdir + File.separator) + UUID.randomUUID(); private String storePathRootDir = storePathRootParentDir + File.separator + "store"; private Random random = new Random(); public void init(int mappedFileSize) throws Exception { String brokerName = "AutoSwitchHATest_" + random.nextInt(65535); queueTotal = 1; messageBody = storeMessage.getBytes(); storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); storeConfig1 = new MessageStoreConfig(); storeConfig1.setBrokerRole(BrokerRole.SYNC_MASTER); storeConfig1.setHaSendHeartbeatInterval(1000); storeConfig1.setStorePathRootDir(storePathRootDir + File.separator + brokerName + "#1"); storeConfig1.setStorePathCommitLog(storePathRootDir + File.separator + brokerName + "#1" + File.separator + "commitlog"); storeConfig1.setStorePathEpochFile(storePathRootDir + File.separator + brokerName + "#1" + File.separator + "EpochFileCache"); storeConfig1.setTotalReplicas(3); storeConfig1.setInSyncReplicas(2); buildMessageStoreConfig(storeConfig1, mappedFileSize); this.store1HaAddress = "127.0.0.1:10912"; storeConfig2 = new MessageStoreConfig(); storeConfig2.setBrokerRole(BrokerRole.SLAVE); storeConfig2.setHaSendHeartbeatInterval(1000); storeConfig2.setStorePathRootDir(storePathRootDir + File.separator + brokerName + "#2"); storeConfig2.setStorePathCommitLog(storePathRootDir + File.separator + brokerName + "#2" + File.separator + "commitlog"); storeConfig2.setStorePathEpochFile(storePathRootDir + File.separator + brokerName + "#2" + File.separator + "EpochFileCache"); storeConfig2.setHaListenPort(10943); storeConfig2.setTotalReplicas(3); storeConfig2.setInSyncReplicas(2); buildMessageStoreConfig(storeConfig2, mappedFileSize); this.store2HaAddress = "127.0.0.1:10943"; messageStore1 = buildMessageStore(storeConfig1, 1L); messageStore2 = buildMessageStore(storeConfig2, 2L); storeConfig3 = new MessageStoreConfig(); storeConfig3.setBrokerRole(BrokerRole.SLAVE); storeConfig3.setHaSendHeartbeatInterval(1000); storeConfig3.setStorePathRootDir(storePathRootDir + File.separator + brokerName + "#3"); storeConfig3.setStorePathCommitLog(storePathRootDir + File.separator + brokerName + "#3" + File.separator + "commitlog"); storeConfig3.setStorePathEpochFile(storePathRootDir + File.separator + brokerName + "#3" + File.separator + "EpochFileCache"); storeConfig3.setHaListenPort(10980); storeConfig3.setTotalReplicas(3); storeConfig3.setInSyncReplicas(2); buildMessageStoreConfig(storeConfig3, mappedFileSize); messageStore3 = buildMessageStore(storeConfig3, 3L); assertTrue(messageStore1.load()); assertTrue(messageStore2.load()); assertTrue(messageStore3.load()); messageStore1.start(); messageStore2.start(); messageStore3.start(); // ((AutoSwitchHAService) this.messageStore1.getHaService()).("127.0.0.1:8000"); // ((AutoSwitchHAService) this.messageStore2.getHaService()).setLocalAddress("127.0.0.1:8001"); // ((AutoSwitchHAService) this.messageStore3.getHaService()).setLocalAddress("127.0.0.1:8002"); } public void init(int mappedFileSize, boolean allAckInSyncStateSet) throws Exception { String brokerName = "AutoSwitchHATest_" + random.nextInt(65535); queueTotal = 1; messageBody = storeMessage.getBytes(); storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); storeConfig1 = new MessageStoreConfig(); storeConfig1.setBrokerRole(BrokerRole.SYNC_MASTER); storeConfig1.setStorePathRootDir(storePathRootDir + File.separator + brokerName + "#1"); storeConfig1.setStorePathCommitLog(storePathRootDir + File.separator + brokerName + "#1" + File.separator + "commitlog"); storeConfig1.setStorePathEpochFile(storePathRootDir + File.separator + brokerName + "#1" + File.separator + "EpochFileCache"); storeConfig1.setAllAckInSyncStateSet(allAckInSyncStateSet); buildMessageStoreConfig(storeConfig1, mappedFileSize); this.store1HaAddress = "127.0.0.1:10912"; storeConfig2 = new MessageStoreConfig(); storeConfig2.setBrokerRole(BrokerRole.SLAVE); storeConfig2.setStorePathRootDir(storePathRootDir + File.separator + brokerName + "#2"); storeConfig2.setStorePathCommitLog(storePathRootDir + File.separator + brokerName + "#2" + File.separator + "commitlog"); storeConfig2.setStorePathEpochFile(storePathRootDir + File.separator + brokerName + "#2" + File.separator + "EpochFileCache"); storeConfig2.setHaListenPort(10943); storeConfig2.setAllAckInSyncStateSet(allAckInSyncStateSet); buildMessageStoreConfig(storeConfig2, mappedFileSize); this.store2HaAddress = "127.0.0.1:10943"; messageStore1 = buildMessageStore(storeConfig1, 1L); messageStore2 = buildMessageStore(storeConfig2, 2L); assertTrue(messageStore1.load()); assertTrue(messageStore2.load()); messageStore1.start(); messageStore2.start(); // ((AutoSwitchHAService) this.messageStore1.getHaService()).setLocalAddress("127.0.0.1:8000"); // ((AutoSwitchHAService) this.messageStore2.getHaService()).setLocalAddress("127.0.0.1:8001"); } private boolean changeMasterAndPutMessage(DefaultMessageStore master, MessageStoreConfig masterConfig, DefaultMessageStore slave, long slaveId, MessageStoreConfig slaveConfig, int epoch, String masterHaAddress, int totalPutMessageNums) throws RocksDBException { boolean flag = true; // Change role slaveConfig.setBrokerRole(BrokerRole.SLAVE); masterConfig.setBrokerRole(BrokerRole.SYNC_MASTER); flag &= slave.getHaService().changeToSlave("", epoch, slaveId); slave.getHaService().updateHaMasterAddress(masterHaAddress); flag &= master.getHaService().changeToMaster(epoch); // Put message on master for (int i = 0; i < totalPutMessageNums; i++) { PutMessageResult result = master.putMessage(buildMessage()); flag &= result.isOk(); } return flag; } private void checkMessage(final DefaultMessageStore messageStore, int totalNums, int startOffset) { await().atMost(30, TimeUnit.SECONDS) .until(() -> { GetMessageResult result = messageStore.getMessage("GROUP_A", "FooBar", 0, startOffset, 1024, null); // System.out.printf(result + "%n"); return result != null && result.getStatus() == GetMessageStatus.FOUND && result.getMessageCount() >= totalNums; }); } @Test public void testConfirmOffset() throws Exception { init(defaultMappedFileSize, true); // Step1, set syncStateSet, if both broker1 and broker2 are in syncStateSet, the confirmOffset will be computed as the min slaveAckOffset(broker2's ack) ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Arrays.asList(1L, 2L))); boolean masterAndPutMessage = changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); assertTrue(masterAndPutMessage); checkMessage(this.messageStore2, 10, 0); final long confirmOffset = this.messageStore1.getConfirmOffset(); // Step2, shutdown store2 this.messageStore2.shutdown(); // Put message, which should succeed because slave is removed from syncStateSet, only master remains final PutMessageResult putMessageResult = this.messageStore1.putMessage(buildMessage()); assertEquals(PutMessageStatus.PUT_OK,putMessageResult.getPutMessageStatus()); // The confirmOffset should update because syncStateSet only contains master after slave shutdown assertTrue(this.messageStore1.getConfirmOffset() >= confirmOffset); // Step3, shutdown store1, start store2, change store2 to master, epoch = 2 this.messageStore1.shutdown(); storeConfig2.setBrokerRole(BrokerRole.SYNC_MASTER); messageStore2 = buildMessageStore(storeConfig2, 2L); messageStore2.getRunningFlags().makeFenced(true); assertTrue(messageStore2.load()); messageStore2.start(); messageStore2.getHaService().changeToMaster(2); messageStore2.getRunningFlags().makeFenced(false); ((AutoSwitchHAService) messageStore2.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(2L))); // Put message on master for (int i = 0; i < 10; i++) { messageStore2.putMessage(buildMessage()); } // Step4, start store1, it should truncate dirty logs and syncLog from store2 storeConfig1.setBrokerRole(BrokerRole.SLAVE); messageStore1 = buildMessageStore(storeConfig1, 1L); assertTrue(messageStore1.load()); messageStore1.start(); messageStore1.getHaService().changeToSlave("", 2, 1L); messageStore1.getHaService().updateHaMasterAddress(this.store2HaAddress); checkMessage(this.messageStore1, 20, 0); } @Test public void testAsyncLearnerBrokerRole() throws Exception { init(defaultMappedFileSize); ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); storeConfig1.setBrokerRole(BrokerRole.SYNC_MASTER); storeConfig2.setBrokerRole(BrokerRole.SLAVE); storeConfig2.setAsyncLearner(true); messageStore1.getHaService().changeToMaster(1); messageStore2.getHaService().changeToSlave("", 1, 2L); messageStore2.getHaService().updateHaMasterAddress(store1HaAddress); // Put message on master for (int i = 0; i < 10; i++) { messageStore1.putMessage(buildMessage()); } checkMessage(messageStore2, 10, 0); final Set syncStateSet = ((AutoSwitchHAService) this.messageStore1.getHaService()).getSyncStateSet(); assertFalse(syncStateSet.contains(2L)); } @Test public void testOptionAllAckInSyncStateSet() throws Exception { init(defaultMappedFileSize, true); AtomicReference> syncStateSet = new AtomicReference<>(); ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); ((AutoSwitchHAService) this.messageStore1.getHaService()).registerSyncStateSetChangedListener(newSyncStateSet -> { syncStateSet.set(newSyncStateSet); }); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); // Check syncStateSet final Set result = syncStateSet.get(); assertTrue(result.contains(1L)); assertTrue(result.contains(2L)); // Now, shutdown store2 this.messageStore2.shutdown(); this.messageStore2.destroy(); // Wait for connection to be removed and syncStateSet to be updated by removeConnection await().atMost(10, TimeUnit.SECONDS).until(() -> { AutoSwitchHAService haService = (AutoSwitchHAService) this.messageStore1.getHaService(); return haService.getConnectionCount().get() == 0 && haService.getLocalSyncStateSet().size() == 1; }); // Now manually set syncStateSet back to {1, 2} to test the scenario where // syncStateSet contains a disconnected slave ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(result); final PutMessageResult putMessageResult = this.messageStore1.putMessage(buildMessage()); assertEquals(PutMessageStatus.FLUSH_SLAVE_TIMEOUT,putMessageResult.getPutMessageStatus()); } @Ignore @Test public void testChangeRoleManyTimes() throws Exception { // Skip MacOSX platform for now as this test case is not stable on it. Assume.assumeFalse(MixAll.isMac()); // Step1, change store1 to master, store2 to follower init(defaultMappedFileSize); ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); // Step2, change store1 to follower, store2 to master, epoch = 2 ((AutoSwitchHAService) this.messageStore2.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(2L))); changeMasterAndPutMessage(this.messageStore2, this.storeConfig2, this.messageStore1, 1, this.storeConfig1, 2, store2HaAddress, 10); checkMessage(this.messageStore1, 20, 0); // Step3, change store2 to follower, store1 to master, epoch = 3 changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 3, store1HaAddress, 10); checkMessage(this.messageStore2, 30, 0); } @Test public void testAddBroker() throws Exception { // Step1: broker1 as leader, broker2 as follower init(defaultMappedFileSize); ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); // Step2: add new broker3, link to broker1 messageStore3.getHaService().changeToSlave("", 1, 3L); messageStore3.getHaService().updateHaMasterAddress(store1HaAddress); checkMessage(messageStore3, 10, 0); } @Test public void testTruncateEpochLogAndAddBroker() throws Exception { // Noted that 10 msg 's total size = 1570, and if init the mappedFileSize = 1700, one file only be used to store 10 msg. init(1700); // Step1: broker1 as leader, broker2 as follower, append 2 epoch, each epoch will be stored on one file(Because fileSize = 1700, which only can hold 10 msgs); // Master: ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 2, store1HaAddress, 10); checkMessage(this.messageStore2, 20, 0); // Step2: Check file position, each epoch will be stored on one file(Because fileSize = 1700, which equal to 10 msg size); // So epoch1 was stored in firstFile, epoch2 was stored in second file, the lastFile was empty. final MappedFileQueue fileQueue = this.messageStore1.getCommitLog().getMappedFileQueue(); assertEquals(2, fileQueue.getTotalFileSize() / 1700); // Step3: truncate epoch1's log (truncateEndOffset = 1570), which means we should delete the first file directly. final MappedFile firstFile = this.messageStore1.getCommitLog().getMappedFileQueue().getFirstMappedFile(); firstFile.shutdown(1000); fileQueue.retryDeleteFirstFile(1000); assertEquals(this.messageStore1.getCommitLog().getMinOffset(), 1700); checkMessage(this.messageStore1, 10, 10); final AutoSwitchHAService haService = (AutoSwitchHAService) this.messageStore1.getHaService(); haService.truncateEpochFilePrefix(1570); // Step4: add broker3 as slave, only have 10 msg from offset 10; messageStore3.getHaService().changeToSlave("", 2, 3L); messageStore3.getHaService().updateHaMasterAddress(store1HaAddress); checkMessage(messageStore3, 10, 10); } @Test public void testTruncateEpochLogAndChangeMaster() throws Exception { // Noted that 10 msg 's total size = 1570, and if init the mappedFileSize = 1700, one file only be used to store 10 msg. init(1700); // Step1: broker1 as leader, broker2 as follower, append 2 epoch, each epoch will be stored on one file(Because fileSize = 1700, which only can hold 10 msgs); // Master: ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 2, store1HaAddress, 10); checkMessage(this.messageStore2, 20, 0); // Step2: Check file position, each epoch will be stored on one file(Because fileSize = 1700, which equal to 10 msg size); // So epoch1 was stored in firstFile, epoch2 was stored in second file, the lastFile was empty. final MappedFileQueue fileQueue = this.messageStore1.getCommitLog().getMappedFileQueue(); assertEquals(2, fileQueue.getTotalFileSize() / 1700); // Step3: truncate epoch1's log (truncateEndOffset = 1570), which means we should delete the first file directly. final MappedFile firstFile = this.messageStore1.getCommitLog().getMappedFileQueue().getFirstMappedFile(); firstFile.shutdown(1000); fileQueue.retryDeleteFirstFile(1000); assertEquals(this.messageStore1.getCommitLog().getMinOffset(), 1700); final AutoSwitchHAService haService = (AutoSwitchHAService) this.messageStore1.getHaService(); haService.truncateEpochFilePrefix(1570); checkMessage(this.messageStore1, 10, 10); // Step4: add broker3 as slave messageStore3.getHaService().changeToSlave("", 2, 3L); messageStore3.getHaService().updateHaMasterAddress(store1HaAddress); checkMessage(messageStore3, 10, 10); // Step5: change broker2 as leader, broker3 as follower ((AutoSwitchHAService) this.messageStore2.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(2L))); changeMasterAndPutMessage(this.messageStore2, this.storeConfig2, this.messageStore3, 3, this.storeConfig3, 3, this.store2HaAddress, 10); checkMessage(messageStore3, 20, 10); // Step6, let broker1 link to broker2, it should sync log from epoch3. this.storeConfig1.setBrokerRole(BrokerRole.SLAVE); this.messageStore1.getHaService().changeToSlave("", 3, 1L); this.messageStore1.getHaService().updateHaMasterAddress(this.store2HaAddress); checkMessage(messageStore1, 20, 0); } @Test public void testAddBrokerAndSyncFromLastFile() throws Exception { init(1700); // Step1: broker1 as leader, broker2 as follower, append 2 epoch, each epoch will be stored on one file(Because fileSize = 1700, which only can hold 10 msgs); // Master: ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 2, store1HaAddress, 10); checkMessage(this.messageStore2, 20, 0); // Step2: restart broker3 messageStore3.shutdown(); messageStore3.destroy(); storeConfig3.setSyncFromLastFile(true); messageStore3 = buildMessageStore(storeConfig3, 3L); assertTrue(messageStore3.load()); messageStore3.start(); // Step2: add new broker3, link to broker1. because broker3 request sync from lastFile, so it only synced 10 msg from offset 10; messageStore3.getHaService().changeToSlave("", 2, 3L); messageStore3.getHaService().updateHaMasterAddress("127.0.0.1:10912"); checkMessage(messageStore3, 10, 10); } @Test @SuppressWarnings("DoubleBraceInitialization") public void testCheckSynchronizingSyncStateSetFlag() throws Exception { // Step1: broker1 as leader, broker2 as follower init(defaultMappedFileSize); ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); AutoSwitchHAService masterHAService = (AutoSwitchHAService) this.messageStore1.getHaService(); // Step2: check flag SynchronizingSyncStateSet Assert.assertTrue(masterHAService.isSynchronizingSyncStateSet()); Assert.assertEquals(this.messageStore1.getConfirmOffset(), 1580); Set syncStateSet = masterHAService.getSyncStateSet(); Assert.assertEquals(syncStateSet.size(), 2); Assert.assertTrue(syncStateSet.contains(1L)); // Step3: set new syncStateSet HashSet newSyncStateSet = new HashSet() {{ add(1L); add(2L); }}; masterHAService.setSyncStateSet(newSyncStateSet); Assert.assertFalse(masterHAService.isSynchronizingSyncStateSet()); } @Test public void testBuildConsumeQueueNotExceedConfirmOffset() throws Exception { init(defaultMappedFileSize); ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(new HashSet<>(Collections.singletonList(1L))); changeMasterAndPutMessage(this.messageStore1, this.storeConfig1, this.messageStore2, 2, this.storeConfig2, 1, store1HaAddress, 10); checkMessage(this.messageStore2, 10, 0); long tmpConfirmOffset = this.messageStore2.getConfirmOffset(); long setConfirmOffset = this.messageStore2.getConfirmOffset() - this.messageStore2.getConfirmOffset() / 2; messageStore2.shutdown(); StoreCheckpoint storeCheckpoint = new StoreCheckpoint(storeConfig2.getStorePathRootDir() + File.separator + "checkpoint"); assertEquals(tmpConfirmOffset, storeCheckpoint.getConfirmPhyOffset()); storeCheckpoint.setConfirmPhyOffset(setConfirmOffset); storeCheckpoint.shutdown(); messageStore2 = buildMessageStore(storeConfig2, 2L); messageStore2.getRunningFlags().makeFenced(true); assertTrue(messageStore2.load()); messageStore2.start(); messageStore2.getRunningFlags().makeFenced(false); assertEquals(setConfirmOffset, messageStore2.getConfirmOffset()); checkMessage(this.messageStore2, 5, 0); } @After public void destroy() throws Exception { if (this.messageStore2 != null) { messageStore2.shutdown(); messageStore2.destroy(); } if (this.messageStore1 != null) { messageStore1.shutdown(); messageStore1.destroy(); } if (this.messageStore3 != null) { messageStore3.shutdown(); messageStore3.destroy(); } File file = new File(storePathRootParentDir); UtilAll.deleteFile(file); } private DefaultMessageStore buildMessageStore(MessageStoreConfig messageStoreConfig, long brokerId) throws Exception { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerId(brokerId); brokerConfig.setEnableControllerMode(true); return new DefaultMessageStore(messageStoreConfig, brokerStatsManager, null, brokerConfig, new ConcurrentHashMap<>()); } private void buildMessageStoreConfig(MessageStoreConfig messageStoreConfig, int mappedFileSize) { messageStoreConfig.setMappedFileSizeCommitLog(mappedFileSize); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024); messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); } private MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic("FooBar"); msg.setTags("TAG1"); msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCacheTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.ha.autoswitch; import java.io.File; import java.nio.file.Paths; import org.apache.rocketmq.remoting.protocol.EpochEntry; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class EpochFileCacheTest { private EpochFileCache epochCache; private EpochFileCache epochCache2; private String path; private String path2; @Before public void setup() { this.path = Paths.get(File.separator + "tmp", "EpochCheckpoint").toString(); this.epochCache = new EpochFileCache(path); assertTrue(this.epochCache.appendEntry(new EpochEntry(1, 100))); assertTrue(this.epochCache.appendEntry(new EpochEntry(2, 300))); assertTrue(this.epochCache.appendEntry(new EpochEntry(3, 500))); final EpochEntry entry = this.epochCache.getEntry(2); assertEquals(entry.getEpoch(), 2); assertEquals(entry.getStartOffset(), 300); assertEquals(entry.getEndOffset(), 500); } @After public void shutdown() { new File(this.path).delete(); if (this.path2 != null) { new File(this.path2).delete(); } } @Test public void testInitFromFile() { // Remove entries, init from file assertTrue(this.epochCache.initCacheFromFile()); final EpochEntry entry = this.epochCache.getEntry(2); assertEquals(entry.getEpoch(), 2); assertEquals(entry.getStartOffset(), 300); assertEquals(entry.getEndOffset(), 500); } @Test public void testTruncate() { this.epochCache.truncateSuffixByOffset(150); assertNotNull(this.epochCache.getEntry(1)); assertNull(this.epochCache.getEntry(2)); } @Test public void testFindEpochEntryByOffset() { final EpochEntry entry = this.epochCache.findEpochEntryByOffset(350); assertEquals(entry.getEpoch(), 2); assertEquals(entry.getStartOffset(), 300); assertEquals(entry.getEndOffset(), 500); } @Test public void testFindConsistentPointSample1() { this.path2 = Paths.get(File.separator + "tmp", "EpochCheckpoint2").toString(); this.epochCache2 = new EpochFileCache(path2); assertTrue(this.epochCache2.appendEntry(new EpochEntry(1, 100))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(2, 300))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(3, 450))); /** * cache1: , , * cache2: , , * The consistent point should be 450 */ final long consistentPoint = this.epochCache.findConsistentPoint(this.epochCache2); assertEquals(consistentPoint, 450); } @Test public void testFindConsistentPointSample2() { this.path2 = Paths.get(File.separator + "tmp", "EpochCheckpoint2").toString(); this.epochCache2 = new EpochFileCache(path2); assertTrue(this.epochCache2.appendEntry(new EpochEntry(1, 100))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(2, 300))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(3, 500))); /** * cache1: , , * cache2: , , * The consistent point should be 600 */ this.epochCache.setLastEpochEntryEndOffset(700); this.epochCache2.setLastEpochEntryEndOffset(600); final long consistentPoint = this.epochCache.findConsistentPoint(this.epochCache2); assertEquals(consistentPoint, 600); } @Test public void testFindConsistentPointSample3() { this.path2 = Paths.get(File.separator + "tmp", "EpochCheckpoint2").toString(); this.epochCache2 = new EpochFileCache(path2); assertTrue(this.epochCache2.appendEntry(new EpochEntry(1, 200))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(2, 500))); /** * cache1: , , * cache2: , * The consistent point should be -1 */ final long consistentPoint = this.epochCache.findConsistentPoint(this.epochCache2); assertEquals(consistentPoint, -1); } @Test public void testFindConsistentPointSample4() { this.path2 = Paths.get(File.separator + "tmp", "EpochCheckpoint2").toString(); this.epochCache2 = new EpochFileCache(path2); assertTrue(this.epochCache2.appendEntry(new EpochEntry(1, 100))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(2, 300))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(3, 500))); assertTrue(this.epochCache2.appendEntry(new EpochEntry(4, 800))); /** * cache1: , , * cache2: , , , * The consistent point should be 700 */ this.epochCache.setLastEpochEntryEndOffset(700); final long consistentPoint = this.epochCache2.findConsistentPoint(this.epochCache); assertEquals(consistentPoint, 700); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/index/IndexFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * $Id: IndexFileTest.java 1831 2013-05-16 01:39:51Z vintagewang@apache.org $ */ package org.apache.rocketmq.store.index; import java.io.File; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class IndexFileTest { private static final int HASH_SLOT_NUM = 100; private static final int INDEX_NUM = 400; @Test public void testPutKey() throws Exception { IndexFile indexFile = new IndexFile("100", HASH_SLOT_NUM, INDEX_NUM, 0, 0); for (long i = 0; i < (INDEX_NUM - 1); i++) { boolean putResult = indexFile.putKey(Long.toString(i), i, System.currentTimeMillis()); assertThat(putResult).isTrue(); } // put over index file capacity. boolean putResult = indexFile.putKey(Long.toString(400), 400, System.currentTimeMillis()); assertThat(putResult).isFalse(); indexFile.destroy(0); File file = new File("100"); UtilAll.deleteFile(file); } @Test public void testSelectPhyOffset() throws Exception { IndexFile indexFile = new IndexFile("200", HASH_SLOT_NUM, INDEX_NUM, 0, 0); for (long i = 0; i < (INDEX_NUM - 1); i++) { boolean putResult = indexFile.putKey(Long.toString(i), i, System.currentTimeMillis()); assertThat(putResult).isTrue(); } // put over index file capacity. boolean putResult = indexFile.putKey(Long.toString(400), 400, System.currentTimeMillis()); assertThat(putResult).isFalse(); final List phyOffsets = new ArrayList<>(); indexFile.selectPhyOffset(phyOffsets, "60", 10, 0, Long.MAX_VALUE); assertThat(phyOffsets).isNotEmpty(); assertThat(phyOffsets.size()).isEqualTo(1); indexFile.destroy(0); File file = new File("200"); UtilAll.deleteFile(file); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/index/IndexServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.index; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Test; import java.util.concurrent.ConcurrentHashMap; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class IndexServiceTest { @Test public void testQueryOffsetThrow() throws Exception { assertDoesNotThrow(() -> { DefaultMessageStore store = new DefaultMessageStore( new MessageStoreConfig(), new BrokerStatsManager(new BrokerConfig()), null, new BrokerConfig(), new ConcurrentHashMap<>() ); IndexService indexService = new IndexService(store); indexService.queryOffset("test", "", Integer.MAX_VALUE, 10, 100); }); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/kv/CompactionLogTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import com.google.common.collect.Lists; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MappedFileQueue; import org.apache.rocketmq.store.MessageExtEncoder; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageSpinLock; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.store.queue.SparseConsumeQueue; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.stubbing.Answer; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.security.DigestException; import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; import static org.apache.rocketmq.store.kv.CompactionLog.COMPACTING_SUB_FOLDER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class CompactionLogTest { CompactionLog clog; MessageStoreConfig storeConfig; MessageStore defaultMessageStore; CompactionPositionMgr positionMgr; String topic = "ctopic"; int queueId = 0; int offsetMemorySize = 1024; int compactionFileSize = 10240; int compactionCqFileSize = 1024; private static MessageExtEncoder encoder = new MessageExtEncoder(1024, new MessageStoreConfig()); private static SocketAddress storeHost; private static SocketAddress bornHost; @Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); String logPath; String cqPath; static { try { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); } catch (UnknownHostException e) { } try { bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (UnknownHostException e) { } } @Before public void setUp() throws IOException { File file = tmpFolder.newFolder("compaction"); logPath = Paths.get(file.getAbsolutePath(), "compactionLog").toString(); cqPath = Paths.get(file.getAbsolutePath(), "compactionCq").toString(); storeConfig = mock(MessageStoreConfig.class); doReturn(compactionFileSize).when(storeConfig).getCompactionMappedFileSize(); doReturn(compactionCqFileSize).when(storeConfig).getCompactionCqMappedFileSize(); defaultMessageStore = mock(DefaultMessageStore.class); doReturn(storeConfig).when(defaultMessageStore).getMessageStoreConfig(); positionMgr = mock(CompactionPositionMgr.class); doReturn(-1L).when(positionMgr).getOffset(topic, queueId); } static int queueOffset = 0; static int keyCount = 10; public static ByteBuffer buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic("ctopic"); msg.setTags(System.currentTimeMillis() + "TAG"); msg.setKeys(String.valueOf(queueOffset % keyCount)); msg.setBody(RandomStringUtils.randomAlphabetic(100).getBytes(StandardCharsets.UTF_8)); msg.setQueueId(0); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setQueueOffset(queueOffset); queueOffset++; for (int i = 1; i < 3; i++) { msg.putUserProperty(String.valueOf(i), "xxx" + i); } msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); encoder.encode(msg); return encoder.getEncoderBuffer(); } @Test public void testCheck() throws IllegalAccessException { MappedFileQueue mfq = mock(MappedFileQueue.class); MappedFileQueue smfq = mock(MappedFileQueue.class); SparseConsumeQueue scq = mock(SparseConsumeQueue.class); doReturn(smfq).when(scq).getMappedFileQueue(); CompactionLog.TopicPartitionLog tpLog = mock(CompactionLog.TopicPartitionLog.class); FieldUtils.writeField(tpLog, "mappedFileQueue", mfq, true); FieldUtils.writeField(tpLog, "consumeQueue", scq, true); doReturn(Lists.newArrayList()).when(mfq).getMappedFiles(); doReturn(Lists.newArrayList()).when(smfq).getMappedFiles(); doCallRealMethod().when(tpLog).sanityCheck(); tpLog.sanityCheck(); } @Test(expected = RuntimeException.class) public void testCheckWithException() throws IllegalAccessException, IOException { MappedFileQueue mfq = mock(MappedFileQueue.class); MappedFileQueue smfq = mock(MappedFileQueue.class); SparseConsumeQueue scq = mock(SparseConsumeQueue.class); doReturn(smfq).when(scq).getMappedFileQueue(); CompactionLog.TopicPartitionLog tpLog = mock(CompactionLog.TopicPartitionLog.class); FieldUtils.writeField(tpLog, "mappedFileQueue", mfq, true); FieldUtils.writeField(tpLog, "consumeQueue", scq, true); Files.createDirectories(Paths.get(logPath, topic, String.valueOf(queueId))); Files.write(Paths.get(logPath, topic, String.valueOf(queueId), "102400"), RandomStringUtils.randomAlphanumeric(compactionFileSize).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); MappedFile mappedFile = new DefaultMappedFile( Paths.get(logPath, topic, String.valueOf(queueId), "102400").toFile().getAbsolutePath(), compactionFileSize); doReturn(Lists.newArrayList(mappedFile)).when(mfq).getMappedFiles(); doReturn(Lists.newArrayList()).when(smfq).getMappedFiles(); doCallRealMethod().when(tpLog).sanityCheck(); tpLog.sanityCheck(); } @Test public void testCompaction() throws DigestException, NoSuchAlgorithmException, IllegalAccessException { Iterator iterator = mock(Iterator.class); SelectMappedBufferResult smb = mock(SelectMappedBufferResult.class); when(iterator.hasNext()).thenAnswer((Answer)invocationOnMock -> queueOffset < 1024); when(iterator.next()).thenAnswer((Answer)invocation -> new SelectMappedBufferResult(0, buildMessage(), 0, null)); MappedFile mf = mock(MappedFile.class); List mappedFileList = Lists.newArrayList(mf); doReturn(iterator).when(mf).iterator(0); MessageStore messageStore = mock(DefaultMessageStore.class); CommitLog commitLog = mock(CommitLog.class); when(messageStore.getCommitLog()).thenReturn(commitLog); when(commitLog.getCommitLogSize()).thenReturn(1024 * 1024); CompactionLog clog = mock(CompactionLog.class); FieldUtils.writeField(clog, "defaultMessageStore", messageStore, true); doCallRealMethod().when(clog).getOffsetMap(any()); FieldUtils.writeField(clog, "positionMgr", positionMgr, true); queueOffset = 0; CompactionLog.OffsetMap offsetMap = clog.getOffsetMap(mappedFileList); assertEquals(1023, offsetMap.getLastOffset()); doCallRealMethod().when(clog).compaction(any(List.class), any(CompactionLog.OffsetMap.class)); doNothing().when(clog).putEndMessage(any(MappedFileQueue.class)); doCallRealMethod().when(clog).checkAndPutMessage(any(SelectMappedBufferResult.class), any(MessageExt.class), any(CompactionLog.OffsetMap.class), any(CompactionLog.TopicPartitionLog.class)); doCallRealMethod().when(clog).shouldRetainMsg(any(MessageExt.class), any(CompactionLog.OffsetMap.class)); List compactResult = Lists.newArrayList(); when(clog.asyncPutMessage(any(ByteBuffer.class), any(MessageExt.class), any(CompactionLog.TopicPartitionLog.class))) .thenAnswer((Answer>)invocation -> { compactResult.add(invocation.getArgument(1)); return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); }); queueOffset = 0; clog.compaction(mappedFileList, offsetMap); assertEquals(keyCount, compactResult.size()); assertEquals(1014, compactResult.stream().mapToLong(MessageExt::getQueueOffset).min().orElse(1024)); assertEquals(1023, compactResult.stream().mapToLong(MessageExt::getQueueOffset).max().orElse(0)); } @Test public void testReplaceFiles() throws IOException, IllegalAccessException { Assume.assumeFalse(MixAll.isWindows()); CompactionLog clog = mock(CompactionLog.class); doCallRealMethod().when(clog).replaceFiles(anyList(), any(CompactionLog.TopicPartitionLog.class), any(CompactionLog.TopicPartitionLog.class)); doCallRealMethod().when(clog).replaceCqFiles(any(SparseConsumeQueue.class), any(SparseConsumeQueue.class), anyList()); CompactionLog.TopicPartitionLog dest = mock(CompactionLog.TopicPartitionLog.class); MappedFileQueue destMFQ = mock(MappedFileQueue.class); when(dest.getLog()).thenReturn(destMFQ); List destFiles = Lists.newArrayList(); when(destMFQ.getMappedFiles()).thenReturn(destFiles); List srcFiles = Lists.newArrayList(); String fileName = logPath + File.separator + COMPACTING_SUB_FOLDER + File.separator + String.format("%010d", 0); MappedFile mf = new DefaultMappedFile(fileName, 1024); srcFiles.add(mf); MappedFileQueue srcMFQ = mock(MappedFileQueue.class); when(srcMFQ.getMappedFiles()).thenReturn(srcFiles); CompactionLog.TopicPartitionLog src = mock(CompactionLog.TopicPartitionLog.class); when(src.getLog()).thenReturn(srcMFQ); FieldUtils.writeField(clog, "readMessageLock", new PutMessageSpinLock(), true); clog.replaceFiles(Lists.newArrayList(), dest, src); assertEquals(destFiles.size(), 1); destFiles.forEach(f -> { assertFalse(f.getFileName().contains(COMPACTING_SUB_FOLDER)); }); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/kv/CompactionPositionMgrTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import static org.junit.Assert.assertEquals; public class CompactionPositionMgrTest { @Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); File file; @Before public void setUp() throws IOException { file = tmpFolder.newFolder("compaction"); } @Test public void testGetAndSet() { CompactionPositionMgr mgr = new CompactionPositionMgr(file.getAbsolutePath()); mgr.setOffset("topic1", 1, 1); assertEquals(1, mgr.getOffset("topic1", 1)); mgr.setOffset("topic1", 1, 2); assertEquals(2, mgr.getOffset("topic1", 1)); mgr.setOffset("topic1", 2, 1); assertEquals(1, mgr.getOffset("topic1", 2)); } @Test public void testLoadAndPersist() throws IOException { CompactionPositionMgr mgr = new CompactionPositionMgr(file.getAbsolutePath()); mgr.setOffset("topic1", 1, 2); mgr.setOffset("topic1", 2, 1); mgr.persist(); mgr = null; CompactionPositionMgr mgr2 = new CompactionPositionMgr(file.getAbsolutePath()); mgr2.load(); assertEquals(2, mgr2.getOffset("topic1", 1)); assertEquals(1, mgr2.getOffset("topic1", 2)); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/kv/OffsetMapTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.kv; import org.apache.rocketmq.store.kv.CompactionLog.OffsetMap; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; public class OffsetMapTest { @Test public void testPutAndGet() throws Exception { OffsetMap offsetMap = new OffsetMap(0); //min 100 entry offsetMap.put("abcde", 1); offsetMap.put("abc", 3); offsetMap.put("cde", 4); offsetMap.put("abcde", 9); assertEquals(offsetMap.get("abcde"), 9); assertEquals(offsetMap.get("cde"), 4); assertEquals(offsetMap.get("not_exist"), -1); assertEquals(offsetMap.getLastOffset(), 9); } @Test public void testFull() throws Exception { OffsetMap offsetMap = new OffsetMap(0); //min 100 entry for (int i = 0; i < 100; i++) { offsetMap.put(String.valueOf(i), i); } assertEquals(offsetMap.get("66"), 66); assertNotEquals(offsetMap.get("55"), 56); assertEquals(offsetMap.getLastOffset(), 99); assertThrows(IllegalArgumentException.class, () -> offsetMap.put(String.valueOf(100), 100)); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/lock/AdaptiveBackOffSpinLockImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.lock; import org.junit.Test; import java.util.Collection; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class AdaptiveBackOffSpinLockImplTest { @Test public void testGetLocks() { AdaptiveBackOffSpinLockImpl lockImpl = new AdaptiveBackOffSpinLockImpl(); Collection locks = lockImpl.getLocks(); assertEquals(2, locks.size()); for (AdaptiveBackOffSpinLock lock : locks) { assertNotNull(lock); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/lock/AdaptiveLockTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.lock; import org.junit.Before; import org.junit.Test; import java.util.concurrent.CountDownLatch; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class AdaptiveLockTest { AdaptiveBackOffSpinLockImpl adaptiveLock; @Before public void init() { adaptiveLock = new AdaptiveBackOffSpinLockImpl(); } @Test public void testAdaptiveLock() throws InterruptedException { assertTrue(adaptiveLock.getAdaptiveLock() instanceof BackOffSpinLock); CountDownLatch countDownLatch = new CountDownLatch(1); adaptiveLock.lock(); new Thread(new Runnable() { @Override public void run() { adaptiveLock.lock(); try { Thread.sleep(1); } catch (InterruptedException e) { //ignore } adaptiveLock.unlock(); countDownLatch.countDown(); } }).start(); Thread.sleep(1000); adaptiveLock.unlock(); assertEquals(2000, ((BackOffSpinLock) adaptiveLock.getAdaptiveLock()).getOptimalDegree()); countDownLatch.await(); for (int i = 0; i <= 5; i++) { CountDownLatch countDownLatch1 = new CountDownLatch(1); adaptiveLock.lock(); new Thread(new Runnable() { @Override public void run() { adaptiveLock.lock(); try { Thread.sleep(1); } catch (InterruptedException e) { //ignore } adaptiveLock.unlock(); countDownLatch1.countDown(); } }).start(); Thread.sleep(1000); adaptiveLock.unlock(); countDownLatch1.await(); } assertTrue(adaptiveLock.getAdaptiveLock() instanceof BackOffReentrantLock); for (int i = 0; i <= 2; i++) { adaptiveLock.lock(); adaptiveLock.unlock(); Thread.sleep(1000); } assertTrue(adaptiveLock.getAdaptiveLock() instanceof BackOffSpinLock); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileConcurrencyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import java.io.File; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.UtilAll; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class DefaultMappedFileConcurrencyTest { private String storePath; private String fileName; private int fileSize = 1024 * 1024; // 1MB private static final int THREAD_COUNT = 10; private static final int OPERATIONS_PER_THREAD = 100; @Before public void setUp() throws Exception { storePath = System.getProperty("user.home") + File.separator + "unitteststore" + System.currentTimeMillis(); fileName = storePath + File.separator + "00000000000000000000"; UtilAll.ensureDirOK(storePath); // Initialize SharedByteBufferManager for tests SharedByteBufferManager.getInstance().init(4 * 1024 * 1024, 16); // 4MB default, 16 shared buffers } @After public void tearDown() throws Exception { UtilAll.deleteFile(new File(storePath)); } @Test public void testConcurrentWriteWithoutMmap() throws Exception { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT); CountDownLatch latch = new CountDownLatch(THREAD_COUNT); AtomicInteger successCount = new AtomicInteger(0); AtomicInteger errorCount = new AtomicInteger(0); for (int i = 0; i < THREAD_COUNT; i++) { final int threadId = i; executor.submit(() -> { try { for (int j = 0; j < OPERATIONS_PER_THREAD; j++) { String data = String.format("Thread-%d-Operation-%d", threadId, j); byte[] bytes = data.getBytes(); boolean result = mappedFile.appendMessage(bytes); if (result) { successCount.incrementAndGet(); } else { errorCount.incrementAndGet(); } } } catch (Exception e) { errorCount.incrementAndGet(); e.printStackTrace(); } finally { latch.countDown(); } }); } latch.await(); executor.shutdown(); // Success count: successCount.get() // Error count: errorCount.get() // Final wrote position: mappedFile.getWrotePosition() // All operations should succeed Assert.assertEquals("All write operations should succeed", THREAD_COUNT * OPERATIONS_PER_THREAD, successCount.get()); Assert.assertEquals("No errors should occur", 0, errorCount.get()); } finally { mappedFile.destroy(0); } } @Test public void testConcurrentWriteWithMmap() throws Exception { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, false); try { ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT); CountDownLatch latch = new CountDownLatch(THREAD_COUNT); AtomicInteger successCount = new AtomicInteger(0); AtomicInteger errorCount = new AtomicInteger(0); for (int i = 0; i < THREAD_COUNT; i++) { final int threadId = i; executor.submit(() -> { try { for (int j = 0; j < OPERATIONS_PER_THREAD; j++) { String data = String.format("Thread-%d-Operation-%d", threadId, j); byte[] bytes = data.getBytes(); boolean result = mappedFile.appendMessage(bytes); if (result) { successCount.incrementAndGet(); } else { errorCount.incrementAndGet(); } } } catch (Exception e) { errorCount.incrementAndGet(); e.printStackTrace(); } finally { latch.countDown(); } }); } latch.await(); executor.shutdown(); // Success count: successCount.get() // Error count: errorCount.get() // Final wrote position: mappedFile.getWrotePosition() // All operations should succeed Assert.assertEquals("All write operations should succeed", THREAD_COUNT * OPERATIONS_PER_THREAD, successCount.get()); Assert.assertEquals("No errors should occur", 0, errorCount.get()); } finally { mappedFile.destroy(0); } } @Test public void testConcurrentFlush() throws Exception { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Write some data first for (int i = 0; i < 100; i++) { String data = "Test data " + i; mappedFile.appendMessage(data.getBytes()); } ExecutorService executor = Executors.newFixedThreadPool(5); CountDownLatch latch = new CountDownLatch(5); AtomicInteger flushCount = new AtomicInteger(0); for (int i = 0; i < 5; i++) { executor.submit(() -> { try { int flushed = mappedFile.flush(0); if (flushed > 0) { flushCount.incrementAndGet(); } } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } }); } latch.await(); executor.shutdown(); Assert.assertTrue("At least one flush should succeed", flushCount.get() > 0); } finally { mappedFile.destroy(0); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileErrorHandlingTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.store.AppendMessageCallback; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.CompactionAppendMsgCallback; import org.apache.rocketmq.store.PutMessageContext; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class DefaultMappedFileErrorHandlingTest { private String storePath; private String fileName; private int fileSize = 1024 * 1024; // 1MB @Before public void setUp() throws Exception { storePath = System.getProperty("user.home") + File.separator + "unitteststore" + System.currentTimeMillis(); fileName = storePath + File.separator + "00000000000000000000"; UtilAll.ensureDirOK(storePath); // Initialize SharedByteBufferManager for tests SharedByteBufferManager.getInstance().init(4 * 1024 * 1024, 16); // 4MB default, 16 shared buffers } @After public void tearDown() throws Exception { UtilAll.deleteFile(new File(storePath)); } @Test public void testAppendMessageCallbackErrorHandling() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Test with a callback that returns an error AppendMessageCallback errorCallback = new AppendMessageCallback() { @Override public AppendMessageResult doAppend(long fileFromOffset, ByteBuffer byteBuffer, int maxBlank, MessageExtBrokerInner msg, PutMessageContext putMessageContext) { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } @Override public AppendMessageResult doAppend(long fileFromOffset, ByteBuffer byteBuffer, int maxBlank, MessageExtBatch messageExtBatch, PutMessageContext putMessageContext) { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } }; // Create a mock message MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setBody("test message".getBytes()); AppendMessageResult result = mappedFile.appendMessage(msg, errorCallback, new PutMessageContext("test-topic")); Assert.assertEquals("Should return error status", AppendMessageStatus.UNKNOWN_ERROR, result.getStatus()); } finally { mappedFile.destroy(0); } } @Test public void testCompactionAppendMsgCallbackErrorHandling() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Test with a callback that returns an error CompactionAppendMsgCallback errorCallback = new CompactionAppendMsgCallback() { @Override public AppendMessageResult doAppend(ByteBuffer bbDest, long fileFromOffset, int maxBlank, ByteBuffer bbSrc) { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } }; ByteBuffer testBuffer = ByteBuffer.wrap("test data".getBytes()); AppendMessageResult result = mappedFile.appendMessage(testBuffer, errorCallback); Assert.assertEquals("Should return error status", AppendMessageStatus.UNKNOWN_ERROR, result.getStatus()); } finally { mappedFile.destroy(0); } } @Test public void testWriteWithoutMmapWithNullRandomAccessFile() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Simulate the case where randomAccessFile is null // This should fall back to normal behavior byte[] testData = "test data".getBytes(); boolean result = mappedFile.appendMessage(testData); // Should still work, but using MappedByteBuffer Assert.assertTrue("Should still work with null RandomAccessFile", result); } finally { mappedFile.destroy(0); } } @Test public void testLargeDataWrite() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Test writing data that's close to the file size limit byte[] largeData = new byte[fileSize - 100]; // Leave some space for (int i = 0; i < largeData.length; i++) { largeData[i] = (byte) (i % 256); } boolean result = mappedFile.appendMessage(largeData); Assert.assertTrue("Should successfully write large data", result); Assert.assertEquals("Wrote position should match data size", largeData.length, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } @Test public void testWriteBeyondFileSize() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Fill the file almost completely byte[] data = new byte[fileSize - 10]; boolean result = mappedFile.appendMessage(data); Assert.assertTrue("Should successfully write data", result); // Try to write more data than remaining space byte[] overflowData = new byte[20]; // More than remaining 10 bytes result = mappedFile.appendMessage(overflowData); Assert.assertFalse("Should fail to write beyond file size", result); } finally { mappedFile.destroy(0); } } @Test public void testFlushErrorHandling() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Write some data byte[] testData = "test data for flush".getBytes(); mappedFile.appendMessage(testData); // Flush should succeed int flushedPosition = mappedFile.flush(0); Assert.assertTrue("Flush should succeed", flushedPosition > 0); } finally { mappedFile.destroy(0); } } @Test public void testAppendMessageWithOffset() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { byte[] testData = "Hello, RocketMQ!".getBytes(); // Test with valid offset boolean result = mappedFile.appendMessage(testData, 0, testData.length); Assert.assertTrue("Should successfully append with valid offset", result); // Test with invalid offset (beyond array length) result = mappedFile.appendMessage(testData, testData.length + 1, 1); Assert.assertFalse("Should fail with invalid offset", result); } finally { mappedFile.destroy(0); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFilePerformanceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.rocketmq.common.UtilAll; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class DefaultMappedFilePerformanceTest { private String storePath; private String fileName; private int fileSize = 10 * 1024 * 1024; // 10MB private static final int WRITE_COUNT = 10000; private static final int DATA_SIZE = 1024; // 1KB per write @Before public void setUp() throws Exception { storePath = System.getProperty("user.home") + File.separator + "unitteststore" + System.currentTimeMillis(); fileName = storePath + File.separator + "00000000000000000000"; UtilAll.ensureDirOK(storePath); // Initialize SharedByteBufferManager for tests SharedByteBufferManager.getInstance().init(4 * 1024 * 1024, 16); // 4MB default, 16 shared buffers } @After public void tearDown() throws Exception { UtilAll.deleteFile(new File(storePath)); } @Test public void testWriteWithoutMmapPerformance() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { byte[] testData = new byte[DATA_SIZE]; for (int i = 0; i < testData.length; i++) { testData[i] = (byte) (i % 256); } long startTime = System.currentTimeMillis(); for (int i = 0; i < WRITE_COUNT; i++) { boolean result = mappedFile.appendMessage(testData); Assert.assertTrue("Write should succeed", result); } long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // WriteWithoutMmap Performance: // Writes: WRITE_COUNT // Data size per write: DATA_SIZE bytes // Total data: (WRITE_COUNT * DATA_SIZE / 1024) KB // Duration: duration ms // Throughput: (WRITE_COUNT * DATA_SIZE / 1024.0 / duration * 1000) KB/s Assert.assertEquals("Wrote position should match expected", WRITE_COUNT * DATA_SIZE, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } @Test public void testWriteWithMmapPerformance() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, false); try { byte[] testData = new byte[DATA_SIZE]; for (int i = 0; i < testData.length; i++) { testData[i] = (byte) (i % 256); } long startTime = System.currentTimeMillis(); for (int i = 0; i < WRITE_COUNT; i++) { boolean result = mappedFile.appendMessage(testData); Assert.assertTrue("Write should succeed", result); } long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // WriteWithMmap Performance: // Writes: WRITE_COUNT // Data size per write: DATA_SIZE bytes // Total data: (WRITE_COUNT * DATA_SIZE / 1024) KB // Duration: duration ms // Throughput: (WRITE_COUNT * DATA_SIZE / 1024.0 / duration * 1000) KB/s Assert.assertEquals("Wrote position should match expected", WRITE_COUNT * DATA_SIZE, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } @Test public void testFlushPerformance() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Write some data first byte[] testData = new byte[DATA_SIZE]; for (int i = 0; i < testData.length; i++) { testData[i] = (byte) (i % 256); } for (int i = 0; i < 1000; i++) { mappedFile.appendMessage(testData); } long startTime = System.currentTimeMillis(); int flushedPosition = mappedFile.flush(0); long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // Flush Performance: // Flushed position: flushedPosition // Duration: duration ms Assert.assertTrue("Flush should succeed", flushedPosition > 0); } finally { mappedFile.destroy(0); } } @Test public void testByteBufferWritePerformance() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { ByteBuffer testBuffer = ByteBuffer.allocate(DATA_SIZE); for (int i = 0; i < DATA_SIZE; i++) { testBuffer.put((byte) (i % 256)); } long startTime = System.currentTimeMillis(); for (int i = 0; i < WRITE_COUNT; i++) { testBuffer.rewind(); boolean result = mappedFile.appendMessage(testBuffer); Assert.assertTrue("Write should succeed", result); } long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // ByteBuffer Write Performance: // Writes: WRITE_COUNT // Data size per write: DATA_SIZE bytes // Total data: (WRITE_COUNT * DATA_SIZE / 1024) KB // Duration: duration ms // Throughput: (WRITE_COUNT * DATA_SIZE / 1024.0 / duration * 1000) KB/s Assert.assertEquals("Wrote position should match expected", WRITE_COUNT * DATA_SIZE, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } @Test public void testMixedWriteOperations() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { byte[] testData = new byte[DATA_SIZE]; for (int i = 0; i < testData.length; i++) { testData[i] = (byte) (i % 256); } long startTime = System.currentTimeMillis(); // Mix of different write operations for (int i = 0; i < WRITE_COUNT / 4; i++) { // appendMessage(byte[]) boolean result1 = mappedFile.appendMessage(testData); Assert.assertTrue("Write should succeed", result1); // appendMessage(byte[], offset, length) boolean result2 = mappedFile.appendMessage(testData, 0, testData.length); Assert.assertTrue("Write should succeed", result2); // appendMessage(ByteBuffer) ByteBuffer buffer = ByteBuffer.wrap(testData); boolean result3 = mappedFile.appendMessage(buffer); Assert.assertTrue("Write should succeed", result3); // appendMessageUsingFileChannel(byte[]) boolean result4 = mappedFile.appendMessageUsingFileChannel(testData); Assert.assertTrue("Write should succeed", result4); } long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // Mixed Write Operations Performance: // Total operations: WRITE_COUNT // Data size per operation: DATA_SIZE bytes // Total data: (WRITE_COUNT * DATA_SIZE / 1024) KB // Duration: duration ms // Throughput: (WRITE_COUNT * DATA_SIZE / 1024.0 / duration * 1000) KB/s Assert.assertEquals("Wrote position should match expected", WRITE_COUNT * DATA_SIZE, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.List; import static org.junit.Assert.assertEquals; public class DefaultMappedFileTest { @Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); String path; @Before public void setUp() throws IOException { path = tmpFolder.newFolder("compaction").getAbsolutePath(); } @Test public void testWriteFile() throws IOException { Files.write(Paths.get(path,"test.file"), "111".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); Files.write(Paths.get(path,"test.file"), "111".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); List positions = Files.readAllLines(Paths.get(path, "test.file"), StandardCharsets.UTF_8); int p = Integer.parseInt(positions.stream().findFirst().orElse("0")); assertEquals(111, p); Files.write(Paths.get(path,"test.file"), "222".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); positions = Files.readAllLines(Paths.get(path,"test.file"), StandardCharsets.UTF_8); p = Integer.parseInt(positions.stream().findFirst().orElse("0")); assertEquals(222, p); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileWriteWithoutMmapTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.logfile; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.TransientStorePool; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class DefaultMappedFileWriteWithoutMmapTest { private String storePath; private String fileName; private int fileSize = 1024 * 1024; // 1MB @Before public void setUp() throws Exception { storePath = System.getProperty("user.home") + File.separator + "unitteststore" + System.currentTimeMillis(); fileName = storePath + File.separator + "00000000000000000000"; UtilAll.ensureDirOK(storePath); // Initialize SharedByteBufferManager for tests SharedByteBufferManager.getInstance().init(4 * 1024 * 1024, 16); // 4MB default, 16 shared buffers } @After public void tearDown() throws Exception { UtilAll.deleteFile(new File(storePath)); } @Test public void testWriteWithoutMmapEnabled() throws IOException { // Test with writeWithoutMmap = true DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { // Test appendMessage with byte array byte[] testData = "Hello, RocketMQ!".getBytes(); boolean result = mappedFile.appendMessage(testData); Assert.assertTrue("Should successfully append message", result); Assert.assertEquals("Wrote position should be updated", testData.length, mappedFile.getWrotePosition()); // Test appendMessage with ByteBuffer ByteBuffer buffer = ByteBuffer.wrap("Test ByteBuffer".getBytes()); result = mappedFile.appendMessage(buffer); Assert.assertTrue("Should successfully append ByteBuffer", result); Assert.assertEquals("Wrote position should be updated", testData.length + "Test ByteBuffer".length(), mappedFile.getWrotePosition()); // Test flush int flushedPosition = mappedFile.flush(0); Assert.assertTrue("Flush should succeed", flushedPosition > 0); } finally { mappedFile.destroy(0); } } @Test public void testWriteWithoutMmapDisabled() throws IOException { // Test with writeWithoutMmap = false (default behavior) DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, false); try { // Test appendMessage with byte array byte[] testData = "Hello, RocketMQ!".getBytes(); boolean result = mappedFile.appendMessage(testData); Assert.assertTrue("Should successfully append message", result); Assert.assertEquals("Wrote position should be updated", testData.length, mappedFile.getWrotePosition()); // Test appendMessage with ByteBuffer ByteBuffer buffer = ByteBuffer.wrap("Test ByteBuffer".getBytes()); result = mappedFile.appendMessage(buffer); Assert.assertTrue("Should successfully append ByteBuffer", result); Assert.assertEquals("Wrote position should be updated", testData.length + "Test ByteBuffer".length(), mappedFile.getWrotePosition()); // Test flush int flushedPosition = mappedFile.flush(0); Assert.assertTrue("Flush should succeed", flushedPosition > 0); } finally { mappedFile.destroy(0); } } @Test public void testWriteWithoutMmapWithTransientStorePool() throws IOException { // Test with writeWithoutMmap = true and TransientStorePool TransientStorePool transientStorePool = new TransientStorePool(5, fileSize); DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, transientStorePool, true); try { // Test appendMessage with byte array byte[] testData = "Hello, RocketMQ with TransientStorePool!".getBytes(); boolean result = mappedFile.appendMessage(testData); Assert.assertTrue("Should successfully append message", result); Assert.assertEquals("Wrote position should be updated", testData.length, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } @Test public void testAppendMessageWithOffset() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { byte[] testData = "Hello, RocketMQ with offset!".getBytes(); boolean result = mappedFile.appendMessage(testData, 0, testData.length); Assert.assertTrue("Should successfully append message with offset", result); Assert.assertEquals("Wrote position should be updated", testData.length, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } @Test public void testAppendMessageUsingFileChannel() throws IOException { DefaultMappedFile mappedFile = new DefaultMappedFile(fileName, fileSize, true); try { byte[] testData = "Hello, RocketMQ using FileChannel!".getBytes(); boolean result = mappedFile.appendMessageUsingFileChannel(testData); Assert.assertTrue("Should successfully append message using FileChannel", result); Assert.assertEquals("Wrote position should be updated", testData.length, mappedFile.getWrotePosition()); } finally { mappedFile.destroy(0); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/pop/AckMsgTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.pop; import com.alibaba.fastjson2.JSON; import org.junit.Assert; import org.junit.Test; public class AckMsgTest { @Test public void testSerializeAndDeSerialize() { String longString = "{\"ackOffset\":100,\"brokerName\":\"brokerName\",\"consumerGroup\":\"group\"," + "\"popTime\":1670212915531,\"queueId\":3,\"startOffset\":200,\"topic\":\"topic\"}"; AckMsg ackMsg = new AckMsg(); ackMsg.setBrokerName("brokerName"); ackMsg.setTopic("topic"); ackMsg.setConsumerGroup("group"); ackMsg.setQueueId(3); ackMsg.setStartOffset(200L); ackMsg.setAckOffset(100L); ackMsg.setPopTime(1670212915531L); String jsonString = JSON.toJSONString(ackMsg); AckMsg ackMsg1 = JSON.parseObject(jsonString, AckMsg.class); AckMsg ackMsg2 = JSON.parseObject(longString, AckMsg.class); Assert.assertEquals(ackMsg1.getBrokerName(), ackMsg2.getBrokerName()); Assert.assertEquals(ackMsg1.getTopic(), ackMsg2.getTopic()); Assert.assertEquals(ackMsg1.getConsumerGroup(), ackMsg2.getConsumerGroup()); Assert.assertEquals(ackMsg1.getQueueId(), ackMsg2.getQueueId()); Assert.assertEquals(ackMsg1.getStartOffset(), ackMsg2.getStartOffset()); Assert.assertEquals(ackMsg1.getAckOffset(), ackMsg2.getAckOffset()); Assert.assertEquals(ackMsg1.getPopTime(), ackMsg2.getPopTime()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/pop/BatchAckMsgTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.pop; import com.alibaba.fastjson2.JSON; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.List; public class BatchAckMsgTest { @Test public void testSerializeAndDeSerialize() { String longString = "{\"ackOffsetList\":[100, 101],\"consumerGroup\":\"group\"," + "\"popTime\":1679454922000,\"queueId\":3,\"startOffset\":200,\"topic\":\"topic\"}"; BatchAckMsg batchAckMsg = new BatchAckMsg(); List aol = new ArrayList<>(32); aol.add(100L); aol.add(101L); batchAckMsg.setAckOffsetList(aol); batchAckMsg.setStartOffset(200L); batchAckMsg.setConsumerGroup("group"); batchAckMsg.setTopic("topic"); batchAckMsg.setQueueId(3); batchAckMsg.setPopTime(1679454922000L); String jsonString = JSON.toJSONString(batchAckMsg); BatchAckMsg batchAckMsg1 = JSON.parseObject(jsonString, BatchAckMsg.class); BatchAckMsg batchAckMsg2 = JSON.parseObject(longString, BatchAckMsg.class); Assert.assertEquals(batchAckMsg1.getAckOffsetList(), batchAckMsg2.getAckOffsetList()); Assert.assertEquals(batchAckMsg1.getTopic(), batchAckMsg2.getTopic()); Assert.assertEquals(batchAckMsg1.getConsumerGroup(), batchAckMsg2.getConsumerGroup()); Assert.assertEquals(batchAckMsg1.getQueueId(), batchAckMsg2.getQueueId()); Assert.assertEquals(batchAckMsg1.getStartOffset(), batchAckMsg2.getStartOffset()); Assert.assertEquals(batchAckMsg1.getPopTime(), batchAckMsg2.getPopTime()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeMessageTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.QueueTypeUtils; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; public class BatchConsumeMessageTest extends QueueTestBase { private static final int BATCH_NUM = 10; private static final int TOTAL_MSGS = 200; private DefaultMessageStore messageStore; private ConcurrentMap topicConfigTableMap; @Before public void init() throws Exception { this.topicConfigTableMap = new ConcurrentHashMap<>(); messageStore = (DefaultMessageStore) createMessageStore(null, true, this.topicConfigTableMap); messageStore.load(); messageStore.start(); } @After public void destroy() { messageStore.shutdown(); messageStore.destroy(); File file = new File(messageStore.getMessageStoreConfig().getStorePathRootDir()); UtilAll.deleteFile(file); } @Test public void testSendMessagesToCqTopic() { String topic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.SimpleCQ); this.topicConfigTableMap.putAll(topicConfigTable); // int batchNum = 10; // case 1 has PROPERTY_INNER_NUM but has no INNER_BATCH_FLAG // MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, batchNum); // messageExtBrokerInner.setSysFlag(0); // PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); // Assert.assertEquals(PutMessageStatus.MESSAGE_ILLEGAL, putMessageResult.getPutMessageStatus()); // case 2 has PROPERTY_INNER_NUM and has INNER_BATCH_FLAG, but is not a batchCq // MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, 1); // PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); // Assert.assertEquals(PutMessageStatus.MESSAGE_ILLEGAL, putMessageResult.getPutMessageStatus()); // case 3 has neither PROPERTY_INNER_NUM nor INNER_BATCH_FLAG. MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, -1); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } @Test public void testSendMessagesToBcqTopic() { String topic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); // case 1 has PROPERTY_INNER_NUM but has no INNER_BATCH_FLAG // MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, 1); // PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); // Assert.assertEquals(PutMessageStatus.MESSAGE_ILLEGAL, putMessageResult.getPutMessageStatus()); // case 2 has neither PROPERTY_INNER_NUM nor INNER_BATCH_FLAG. MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, -1); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); // case 3 has INNER_BATCH_FLAG but has no PROPERTY_INNER_NUM. messageExtBrokerInner = buildMessage(topic, 1); MessageAccessor.clearProperty(messageExtBrokerInner, MessageConst.PROPERTY_INNER_NUM); messageExtBrokerInner.setSysFlag(MessageSysFlag.INNER_BATCH_FLAG); putMessageResult = messageStore.putMessage(messageExtBrokerInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } @Test public void testConsumeBatchMessage() { String topic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); int batchNum = 10; MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, batchNum); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); List results = new ArrayList<>(); for (int i = 0; i < batchNum; i++) { GetMessageResult result = messageStore.getMessage("whatever", topic, 0, i, Integer.MAX_VALUE, Integer.MAX_VALUE, null); try { Assert.assertEquals(GetMessageStatus.FOUND, result.getStatus()); results.add(result); } finally { result.release(); } } for (GetMessageResult result : results) { Assert.assertEquals(0, result.getMinOffset()); Assert.assertEquals(batchNum, result.getMaxOffset()); } } @Test public void testNextBeginOffsetConsumeBatchMessage() { String topic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); Random random = new Random(); int putMessageCount = 1000; Queue queue = new ArrayDeque<>(); for (int i = 0; i < putMessageCount; i++) { int batchNum = random.nextInt(1000) + 2; MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, batchNum); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); queue.add(batchNum); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); long pullOffset = 0L; int getMessageCount = 0; int atMostMsgNum = 1; while (true) { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, pullOffset, atMostMsgNum, null); if (Objects.equals(getMessageResult.getStatus(), GetMessageStatus.OFFSET_OVERFLOW_ONE)) { break; } Assert.assertEquals(1, getMessageResult.getMessageQueueOffset().size()); Long baseOffset = getMessageResult.getMessageQueueOffset().get(0); Integer batchNum = queue.poll(); Assert.assertNotNull(batchNum); Assert.assertEquals(baseOffset + batchNum, getMessageResult.getNextBeginOffset()); pullOffset = getMessageResult.getNextBeginOffset(); getMessageCount++; } Assert.assertEquals(putMessageCount, getMessageCount); } @Test public void testGetOffsetInQueueByTime() throws Exception { String topic = "testGetOffsetInQueueByTime"; ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); Assert.assertTrue(QueueTypeUtils.isBatchCq(messageStore.getTopicConfig(topic))); // The initial min max offset, before and after the creation of consume queue Assert.assertEquals(0, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(-1, messageStore.getMinOffsetInQueue(topic, 0)); int batchNum = 10; long timeMid = -1; for (int i = 0; i < 19; i++) { PutMessageResult putMessageResult = messageStore.putMessage(buildMessage(topic, batchNum)); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Thread.sleep(2); if (i == 7) timeMid = System.currentTimeMillis(); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); Assert.assertEquals(80, messageStore.getOffsetInQueueByTime(topic, 0, timeMid)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(190, messageStore.getMaxOffsetInQueue(topic, 0)); int maxBatchDeleteFilesNum = messageStore.getMessageStoreConfig().getMaxBatchDeleteFilesNum(); messageStore.getCommitLog().deleteExpiredFile(1L, 100, 12000, true, maxBatchDeleteFilesNum); Assert.assertEquals(80, messageStore.getOffsetInQueueByTime(topic, 0, timeMid)); // can set periodic interval for executing DefaultMessageStore.this.cleanFilesPeriodically() method, we can execute following code. // default periodic interval is 60s, This code snippet will take 60 seconds. /*final long a = timeMid; await().atMost(Duration.ofMinutes(2)).until(()->{ long time = messageStore.getOffsetInQueueByTime(topic, 0, a); return 180 ==time; }); Assert.assertEquals(180, messageStore.getOffsetInQueueByTime(topic, 0, timeMid));*/ } @Test public void testDispatchNormalConsumeQueue() throws Exception { String topic = "TestDispatchBuildConsumeQueue"; ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.SimpleCQ); this.topicConfigTableMap.putAll(topicConfigTable); long timeStart = -1; long timeMid = -1; long commitLogMid = -1; for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(topic, -1); PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Thread.sleep(2); if (i == 0) { timeStart = putMessageResult.getAppendMessageResult().getStoreTimestamp(); } if (i == 50) { timeMid = putMessageResult.getAppendMessageResult().getStoreTimestamp(); commitLogMid = putMessageResult.getAppendMessageResult().getWroteOffset(); } } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, 0); Assert.assertEquals(CQType.SimpleCQ, consumeQueue.getCQType()); //check the consume queue Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(0, consumeQueue.getOffsetInQueueByTime(0)); Assert.assertEquals(50, consumeQueue.getOffsetInQueueByTime(timeMid)); Assert.assertEquals(100, consumeQueue.getOffsetInQueueByTime(timeMid + Integer.MAX_VALUE)); Assert.assertEquals(100, consumeQueue.getMaxOffsetInQueue()); //check the messagestore Assert.assertEquals(100, messageStore.getMessageTotalInQueue(topic, 0)); Assert.assertEquals(consumeQueue.getMinOffsetInQueue(), messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(consumeQueue.getMaxOffsetInQueue(), messageStore.getMaxOffsetInQueue(topic, 0)); for (int i = -100; i < 100; i += 20) { Assert.assertEquals(consumeQueue.getOffsetInQueueByTime(timeMid + i), messageStore.getOffsetInQueueByTime(topic, 0, timeMid + i)); } //check the message time long earliestMessageTime = messageStore.getEarliestMessageTime(topic, 0); Assert.assertEquals(timeStart, earliestMessageTime); long messageStoreTime = messageStore.getMessageStoreTimeStamp(topic, 0, 50); Assert.assertEquals(timeMid, messageStoreTime); long commitLogOffset = messageStore.getCommitLogOffsetInQueue(topic, 0, 50); Assert.assertTrue(commitLogOffset >= messageStore.getMinPhyOffset()); Assert.assertTrue(commitLogOffset <= messageStore.getMaxPhyOffset()); Assert.assertEquals(commitLogMid, commitLogOffset); Assert.assertTrue(messageStore.checkInMemByConsumeOffset(topic, 0, 50, 1)); } @Test public void testDispatchBuildBatchConsumeQueue() throws Exception { String topic = "testDispatchBuildBatchConsumeQueue"; int batchNum = 10; long timeStart = -1; long timeMid = -1; ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); for (int i = 0; i < 100; i++) { PutMessageResult putMessageResult = messageStore.putMessage(buildMessage(topic, batchNum)); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Thread.sleep(2); if (i == 0) { timeStart = putMessageResult.getAppendMessageResult().getStoreTimestamp(); } if (i == 30) { timeMid = putMessageResult.getAppendMessageResult().getStoreTimestamp(); } } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, 0); Assert.assertEquals(CQType.BatchCQ, consumeQueue.getCQType()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(1000, consumeQueue.getMaxOffsetInQueue()); //check the message store Assert.assertEquals(1000, messageStore.getMessageTotalInQueue(topic, 0)); Assert.assertEquals(consumeQueue.getMinOffsetInQueue(), messageStore.getMinOffsetInQueue(topic, 0)); Assert.assertEquals(consumeQueue.getMaxOffsetInQueue(), messageStore.getMaxOffsetInQueue(topic, 0)); for (int i = -100; i < 100; i += 20) { Assert.assertEquals(consumeQueue.getOffsetInQueueByTime(timeMid + i), messageStore.getOffsetInQueueByTime(topic, 0, timeMid + i)); } //check the message time long earliestMessageTime = messageStore.getEarliestMessageTime(topic, 0); Assert.assertEquals(earliestMessageTime, timeStart); long messageStoreTime = messageStore.getMessageStoreTimeStamp(topic, 0, 300); Assert.assertEquals(messageStoreTime, timeMid); long commitLogOffset = messageStore.getCommitLogOffsetInQueue(topic, 0, 300); Assert.assertTrue(commitLogOffset >= messageStore.getMinPhyOffset()); Assert.assertTrue(commitLogOffset <= messageStore.getMaxPhyOffset()); Assert.assertTrue(messageStore.checkInMemByConsumeOffset(topic, 0, 300, 1)); //get the message Normally GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 0, 10 * batchNum, null); Assert.assertEquals(10, getMessageResult.getMessageMapedList().size()); for (int i = 0; i < 10; i++) { SelectMappedBufferResult sbr = getMessageResult.getMessageMapedList().get(i); MessageExt messageExt = MessageDecoder.decode(sbr.getByteBuffer()); short tmpBatchNum = Short.parseShort(messageExt.getProperty(MessageConst.PROPERTY_INNER_NUM)); Assert.assertEquals(i * batchNum, Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_INNER_BASE))); Assert.assertEquals(batchNum, tmpBatchNum); } } @Test public void testGetBatchMessageWithinNumber() { String topic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); int batchNum = 20; for (int i = 0; i < 200; i++) { PutMessageResult putMessageResult = messageStore.putMessage(buildMessage(topic, batchNum)); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i * batchNum, putMessageResult.getAppendMessageResult().getLogicsOffset()); Assert.assertEquals(batchNum, putMessageResult.getAppendMessageResult().getMsgNum()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, 0); Assert.assertEquals(CQType.BatchCQ, consumeQueue.getCQType()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(200 * batchNum, consumeQueue.getMaxOffsetInQueue()); { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 5, 1, Integer.MAX_VALUE, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(batchNum, getMessageResult.getNextBeginOffset()); Assert.assertEquals(1, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(batchNum, getMessageResult.getMessageCount()); SelectMappedBufferResult sbr = getMessageResult.getMessageMapedList().get(0); MessageExt messageExt = MessageDecoder.decode(sbr.getByteBuffer()); short tmpBatchNum = Short.parseShort(messageExt.getProperty(MessageConst.PROPERTY_INNER_NUM)); Assert.assertEquals(0, messageExt.getQueueOffset()); Assert.assertEquals(batchNum, tmpBatchNum); } { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 5, 39, Integer.MAX_VALUE, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(1, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(batchNum, getMessageResult.getNextBeginOffset()); Assert.assertEquals(batchNum, getMessageResult.getMessageCount()); } { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 5, 60, Integer.MAX_VALUE, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(3, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(3 * batchNum, getMessageResult.getNextBeginOffset()); Assert.assertEquals(3 * batchNum, getMessageResult.getMessageCount()); for (int i = 0; i < getMessageResult.getMessageBufferList().size(); i++) { Assert.assertFalse(getMessageResult.getMessageMapedList().get(i).hasReleased()); SelectMappedBufferResult sbr = getMessageResult.getMessageMapedList().get(i); MessageExt messageExt = MessageDecoder.decode(sbr.getByteBuffer()); Assert.assertNotNull(messageExt); short innerBatchNum = Short.parseShort(messageExt.getProperty(MessageConst.PROPERTY_INNER_NUM)); Assert.assertEquals(i * batchNum, Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_INNER_BASE))); Assert.assertEquals(batchNum, innerBatchNum); } } } @Test public void testGetBatchMessageWithinSize() { String topic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); int batchNum = 10; for (int i = 0; i < 100; i++) { PutMessageResult putMessageResult = messageStore.putMessage(buildMessage(topic, batchNum)); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i * 10, putMessageResult.getAppendMessageResult().getLogicsOffset()); Assert.assertEquals(batchNum, putMessageResult.getAppendMessageResult().getMsgNum()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, 0); Assert.assertEquals(CQType.BatchCQ, consumeQueue.getCQType()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(1000, consumeQueue.getMaxOffsetInQueue()); { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 5, Integer.MAX_VALUE, 100, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(10, getMessageResult.getNextBeginOffset()); Assert.assertEquals(1, getMessageResult.getMessageMapedList().size()); SelectMappedBufferResult sbr = getMessageResult.getMessageMapedList().get(0); MessageExt messageExt = MessageDecoder.decode(sbr.getByteBuffer()); short tmpBatchNum = Short.parseShort(messageExt.getProperty(MessageConst.PROPERTY_INNER_NUM)); Assert.assertEquals(0, messageExt.getQueueOffset()); Assert.assertEquals(batchNum, tmpBatchNum); } { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 5, Integer.MAX_VALUE, 2048, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(1, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(10, getMessageResult.getNextBeginOffset()); } { GetMessageResult getMessageResult = messageStore.getMessage("group", topic, 0, 5, Integer.MAX_VALUE, 4096, null); Assert.assertEquals(GetMessageStatus.FOUND, getMessageResult.getStatus()); Assert.assertEquals(3, getMessageResult.getMessageMapedList().size()); Assert.assertEquals(30, getMessageResult.getNextBeginOffset()); for (int i = 0; i < getMessageResult.getMessageBufferList().size(); i++) { Assert.assertFalse(getMessageResult.getMessageMapedList().get(i).hasReleased()); SelectMappedBufferResult sbr = getMessageResult.getMessageMapedList().get(i); MessageExt messageExt = MessageDecoder.decode(sbr.getByteBuffer()); short tmpBatchNum = Short.parseShort(messageExt.getProperty(MessageConst.PROPERTY_INNER_NUM)); Assert.assertEquals(i * batchNum, Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_INNER_BASE))); Assert.assertEquals(batchNum, tmpBatchNum); } } } protected void putMsg(String topic) { ConcurrentMap topicConfigTable = createTopicConfigTable(topic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); for (int i = 0; i < TOTAL_MSGS; i++) { MessageExtBrokerInner message = buildMessage(topic, BATCH_NUM * (i % 2 + 1)); switch (i % 3) { case 0: message.setTags("TagA"); break; case 1: message.setTags("TagB"); break; } message.setTagsCode(message.getTags().hashCode()); message.setPropertiesString(MessageDecoder.messageProperties2String(message.getProperties())); PutMessageResult putMessageResult = messageStore.putMessage(message); Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); } @Test public void testEstimateMessageCountInEmptyConsumeQueue() { String topic = UUID.randomUUID().toString(); ConsumeQueueInterface consumeQueue = messageStore.findConsumeQueue(topic, 0); MessageFilter filter = new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { return tagsCode == "TagA".hashCode(); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return false; } }; long estimation = consumeQueue.estimateMessageCount(0, 0, filter); Assert.assertEquals(-1, estimation); // test for illegal offset estimation = consumeQueue.estimateMessageCount(0, 100, filter); Assert.assertEquals(-1, estimation); estimation = consumeQueue.estimateMessageCount(100, 1000, filter); Assert.assertEquals(-1, estimation); } @Test public void testEstimateMessageCount() { String topic = UUID.randomUUID().toString(); putMsg(topic); ConsumeQueueInterface cq = messageStore.findConsumeQueue(topic, 0); MessageFilter filter = new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { return tagsCode == "TagA".hashCode(); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return false; } }; long estimation = cq.estimateMessageCount(0, 2999, filter); Assert.assertEquals(1000, estimation); // test for illegal offset estimation = cq.estimateMessageCount(0, Long.MAX_VALUE, filter); Assert.assertEquals(-1, estimation); estimation = cq.estimateMessageCount(100000, 1000000, filter); Assert.assertEquals(-1, estimation); estimation = cq.estimateMessageCount(100, 0, filter); Assert.assertEquals(-1, estimation); } @Test public void testEstimateMessageCountSample() { String topic = UUID.randomUUID().toString(); putMsg(topic); messageStore.getMessageStoreConfig().setSampleCountThreshold(10); messageStore.getMessageStoreConfig().setMaxConsumeQueueScan(20); ConsumeQueueInterface cq = messageStore.findConsumeQueue(topic, 0); MessageFilter filter = new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { return tagsCode == "TagA".hashCode(); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return false; } }; long estimation = cq.estimateMessageCount(1000, 2000, filter); Assert.assertEquals(300, estimation); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.StoreTestBase; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Assert; import org.junit.Test; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Random; import static java.lang.String.format; public class BatchConsumeQueueTest extends StoreTestBase { List batchConsumeQueues = new ArrayList<>(); private BatchConsumeQueue createBatchConsume(String path) { if (path == null) { path = createBaseDir(); } baseDirs.add(path); MessageStore messageStore = null; try { messageStore = createMessageStore(null); } catch (Exception e) { Assert.fail(); } BatchConsumeQueue batchConsumeQueue = new BatchConsumeQueue("topic", 0, path, fileSize, messageStore); batchConsumeQueues.add(batchConsumeQueue); return batchConsumeQueue; } private int fileSize = BatchConsumeQueue.CQ_STORE_UNIT_SIZE * 20; @Test(timeout = 20000) public void testBuildAndIterateBatchConsumeQueue() { BatchConsumeQueue batchConsumeQueue = createBatchConsume(null); batchConsumeQueue.load(); short batchNum = 10; int unitNum = 10000; int initialMsgOffset = 1000; for (int i = 0; i < unitNum; i++) { batchConsumeQueue.putBatchMessagePositionInfo(i, 1024, 111, i * batchNum, i * batchNum + initialMsgOffset, batchNum); } Assert.assertEquals(500, getBcqFileSize(batchConsumeQueue)); Assert.assertEquals(initialMsgOffset + batchNum * unitNum, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(initialMsgOffset, batchConsumeQueue.getMinOffsetInQueue()); { CqUnit first = batchConsumeQueue.getEarliestUnit(); Assert.assertNotNull(first); Assert.assertEquals(initialMsgOffset, first.getQueueOffset()); Assert.assertEquals(batchNum, first.getBatchNum()); } { CqUnit last = batchConsumeQueue.getLatestUnit(); Assert.assertNotNull(last); Assert.assertEquals(initialMsgOffset + batchNum * unitNum - batchNum, last.getQueueOffset()); Assert.assertEquals(batchNum, last.getBatchNum()); } for (int i = 0; i < initialMsgOffset + batchNum * unitNum + 10; i++) { ReferredIterator it = batchConsumeQueue.iterateFrom(i); if (i < initialMsgOffset || i >= initialMsgOffset + batchNum * unitNum) { Assert.assertNull(it); continue; } Assert.assertNotNull(it); CqUnit cqUnit = it.nextAndRelease(); Assert.assertNotNull(cqUnit); long baseOffset = (i / batchNum) * batchNum; Assert.assertEquals(baseOffset, cqUnit.getQueueOffset()); Assert.assertEquals(batchNum, cqUnit.getBatchNum()); Assert.assertEquals((i - initialMsgOffset) / batchNum, cqUnit.getPos()); Assert.assertEquals(1024, cqUnit.getSize()); Assert.assertEquals(111, cqUnit.getTagsCode()); Assert.assertNull(cqUnit.getCqExtUnit()); } batchConsumeQueue.destroy(); } @Test(timeout = 20000) public void testBuildAndSearchBatchConsumeQueue() { // Preparing the data may take some time BatchConsumeQueue batchConsumeQueue = createBatchConsume(null); batchConsumeQueue.load(); short batchSize = 10; int unitNum = 20000; for (int i = 0; i < unitNum; i++) { batchConsumeQueue.putBatchMessagePositionInfo(i, 100, 0, i * batchSize, i * batchSize + 1, batchSize); } Assert.assertEquals(1000, getBcqFileSize(batchConsumeQueue)); Assert.assertEquals(unitNum * batchSize + 1, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(1, batchConsumeQueue.getMinOffsetInQueue()); batchConsumeQueue.reviseMaxAndMinOffsetInQueue(); Assert.assertEquals(unitNum * batchSize + 1, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(1, batchConsumeQueue.getMinOffsetInQueue()); // test search the offset // lower bounds Assert.assertFalse(ableToFindResult(batchConsumeQueue, 0)); Assert.assertTrue(ableToFindResult(batchConsumeQueue, 1)); // upper bounds Assert.assertFalse(ableToFindResult(batchConsumeQueue, unitNum * batchSize + 1)); Assert.assertTrue(ableToFindResult(batchConsumeQueue, unitNum * batchSize)); // iterate every possible batch-msg offset for (int i = 1; i <= unitNum * batchSize; i++) { int expectedValue = ((i - 1) / batchSize) * batchSize + 1; SelectMappedBufferResult batchMsgIndexBuffer = batchConsumeQueue.getBatchMsgIndexBuffer(i); Assert.assertEquals(expectedValue, batchMsgIndexBuffer.getByteBuffer().getLong(BatchConsumeQueue.MSG_BASE_OFFSET_INDEX)); batchMsgIndexBuffer.release(); } SelectMappedBufferResult sbr = batchConsumeQueue.getBatchMsgIndexBuffer(501); Assert.assertEquals(501, sbr.getByteBuffer().getLong(BatchConsumeQueue.MSG_BASE_OFFSET_INDEX)); Assert.assertEquals(10, sbr.getByteBuffer().getShort(BatchConsumeQueue.MSG_BASE_OFFSET_INDEX + 8)); sbr.release(); // test search the storeTime Assert.assertEquals(1, batchConsumeQueue.getOffsetInQueueByTime(-100)); Assert.assertEquals(1, batchConsumeQueue.getOffsetInQueueByTime(0)); Assert.assertEquals(11, batchConsumeQueue.getOffsetInQueueByTime(1)); for (int i = 0; i < unitNum; i++) { int storeTime = i * batchSize; int expectedOffset = storeTime + 1; long offset = batchConsumeQueue.getOffsetInQueueByTime(storeTime); Assert.assertEquals(expectedOffset, offset); } Assert.assertEquals(199991, batchConsumeQueue.getOffsetInQueueByTime(System.currentTimeMillis())); batchConsumeQueue.destroy(); } @Test(timeout = 20000) public void testBuildAndRecoverBatchConsumeQueue() { String tmpPath = createBaseDir(); short batchSize = 10; { BatchConsumeQueue batchConsumeQueue = createBatchConsume(tmpPath); batchConsumeQueue.load(); for (int i = 0; i < 100; i++) { batchConsumeQueue.putBatchMessagePositionInfo(i, 100, 0, i * batchSize, i * batchSize + 1, batchSize); } Assert.assertEquals(5, getBcqFileSize(batchConsumeQueue)); Assert.assertEquals(1001, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(1, batchConsumeQueue.getMinOffsetInQueue()); for (int i = 0; i < 10; i++) { batchConsumeQueue.flush(0); } } { BatchConsumeQueue recover = createBatchConsume(tmpPath); recover.load(); recover.recover(); Assert.assertEquals(5, getBcqFileSize(recover)); Assert.assertEquals(1001, recover.getMaxOffsetInQueue()); Assert.assertEquals(1, recover.getMinOffsetInQueue()); for (int i = 1; i <= 1000; i++) { int expectedValue = ((i - 1) / batchSize) * batchSize + 1; SelectMappedBufferResult batchMsgIndexBuffer = recover.getBatchMsgIndexBuffer(i); Assert.assertEquals(expectedValue, batchMsgIndexBuffer.getByteBuffer().getLong(BatchConsumeQueue.MSG_BASE_OFFSET_INDEX)); batchMsgIndexBuffer.release(); } } } @Test(timeout = 20000) public void testTruncateBatchConsumeQueue() { String tmpPath = createBaseDir(); BatchConsumeQueue batchConsumeQueue = createBatchConsume(tmpPath); batchConsumeQueue.load(); short batchSize = 10; int unitNum = 20000; for (int i = 0; i < unitNum; i++) { batchConsumeQueue.putBatchMessagePositionInfo(i, 100, 0, i * batchSize, i * batchSize + 1, batchSize); } Assert.assertEquals(1000, getBcqFileSize(batchConsumeQueue)); Assert.assertEquals(unitNum * batchSize + 1, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(1, batchConsumeQueue.getMinOffsetInQueue()); int truncatePhyOffset = new Random().nextInt(unitNum); batchConsumeQueue.truncateDirtyLogicFiles(truncatePhyOffset); for (int i = 1; i < unitNum; i++) { long msgOffset = i * batchSize + 1; if (i < truncatePhyOffset) { int expectedValue = ((i - 1) / batchSize) * batchSize + 1; SelectMappedBufferResult batchMsgIndexBuffer = batchConsumeQueue.getBatchMsgIndexBuffer(i); Assert.assertEquals(expectedValue, batchMsgIndexBuffer.getByteBuffer().getLong(BatchConsumeQueue.MSG_BASE_OFFSET_INDEX)); batchMsgIndexBuffer.release(); } else { Assert.assertNull(format("i: %d, truncatePhyOffset: %d", i, truncatePhyOffset), batchConsumeQueue.getBatchMsgIndexBuffer(msgOffset)); } } } @Test public void testTruncateAndDeleteBatchConsumeQueue() { String tmpPath = createBaseDir(); BatchConsumeQueue batchConsumeQueue = createBatchConsume(tmpPath); batchConsumeQueue.load(); short batchSize = 10; for (int i = 0; i < 100; i++) { batchConsumeQueue.putBatchMessagePositionInfo(i, 100, 0, i * batchSize, i * batchSize + 1, batchSize); } Assert.assertEquals(5, batchConsumeQueue.mappedFileQueue.getMappedFiles().size()); Assert.assertEquals(1001, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(1, batchConsumeQueue.getMinOffsetInQueue()); batchConsumeQueue.truncateDirtyLogicFiles(80); Assert.assertEquals(4, batchConsumeQueue.mappedFileQueue.getMappedFiles().size()); Assert.assertEquals(801, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(1, batchConsumeQueue.getMinOffsetInQueue()); //test batchConsumeQueue.deleteExpiredFile(30); Assert.assertEquals(3, batchConsumeQueue.mappedFileQueue.getMappedFiles().size()); Assert.assertEquals(801, batchConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(301, batchConsumeQueue.getMinOffsetInQueue()); } @After @Override public void clear() { super.clear(); for (BatchConsumeQueue batchConsumeQueue : batchConsumeQueues) { batchConsumeQueue.destroy(); } } private int getBcqFileSize(BatchConsumeQueue batchConsumeQueue) { return batchConsumeQueue.mappedFileQueue.getMappedFiles().size(); } private boolean ableToFindResult(BatchConsumeQueue batchConsumeQueue, long msgOffset) { SelectMappedBufferResult batchMsgIndexBuffer = batchConsumeQueue.getBatchMsgIndexBuffer(msgOffset); try { return batchMsgIndexBuffer != null; } finally { if (batchMsgIndexBuffer != null) { batchMsgIndexBuffer.release(); } } } protected MessageStore createMessageStore(String baseDir) throws Exception { if (baseDir == null) { baseDir = createBaseDir(); } baseDirs.add(baseDir); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(100 * ConsumeQueue.CQ_STORE_UNIT_SIZE); messageStoreConfig.setMapperFileSizeBatchConsumeQueue(20 * BatchConsumeQueue.CQ_STORE_UNIT_SIZE); messageStoreConfig.setMappedFileSizeConsumeQueueExt(1024); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setEnableConsumeQueueExt(false); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); messageStoreConfig.setHaListenPort(nextPort()); messageStoreConfig.setMaxTransferBytesOnMessageInDisk(1024 * 1024); messageStoreConfig.setMaxTransferBytesOnMessageInMemory(1024 * 1024); messageStoreConfig.setMaxTransferCountOnMessageInDisk(1024); messageStoreConfig.setMaxTransferCountOnMessageInMemory(1024); messageStoreConfig.setSearchBcqByCacheEnable(true); return new DefaultMessageStore( messageStoreConfig, new BrokerStatsManager("simpleTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, new BrokerConfig(), new ConcurrentHashMap<>()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/CombineConsumeQueueStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.net.InetSocketAddress; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.apache.rocketmq.common.TopicFilterType.SINGLE_TAG; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class CombineConsumeQueueStoreTest extends QueueTestBase { private DefaultMessageStore messageStore; private MessageStoreConfig messageStoreConfig; private ConcurrentMap topicConfigTableMap; String topic = UUID.randomUUID().toString(); final int queueId = 0; final int msgNum = 100; final int msgSize = 1000; @Before public void init() throws Exception { this.topicConfigTableMap = new ConcurrentHashMap<>(); messageStoreConfig = new MessageStoreConfig(); } @After public void destroy() { if (!messageStore.isShutdown()) { messageStore.shutdown(); } messageStore.destroy(); File file = new File(messageStore.getMessageStoreConfig().getStorePathRootDir()); UtilAll.deleteFile(file); } @Test(expected = IllegalArgumentException.class) public void CombineConsumeQueueStore_EmptyLoadingCQTypes_ThrowsException() throws Exception { messageStore = (DefaultMessageStore) createMessageStore(null, false, topicConfigTableMap, messageStoreConfig); messageStoreConfig.setCombineCQLoadingCQTypes(""); new CombineConsumeQueueStore(messageStore); } @Test public void CombineConsumeQueueStore_InitializesConsumeQueueStore() throws Exception { messageStore = (DefaultMessageStore) createMessageStore(null, false, topicConfigTableMap, messageStoreConfig); { messageStoreConfig.setCombineCQLoadingCQTypes("default"); messageStoreConfig.setCombineCQPreferCQType("default"); CombineConsumeQueueStore store = new CombineConsumeQueueStore(messageStore); assertNotNull(store.getConsumeQueueStore()); assertNull(store.getRocksDBConsumeQueueStore()); } { messageStoreConfig.setCombineCQLoadingCQTypes("defaultRocksDB"); messageStoreConfig.setCombineCQPreferCQType("defaultRocksDB"); messageStoreConfig.setCombineAssignOffsetCQType("defaultRocksDB"); CombineConsumeQueueStore store = new CombineConsumeQueueStore(messageStore); assertNull(store.getConsumeQueueStore()); assertNotNull(store.getRocksDBConsumeQueueStore()); assertTrue(store.getCurrentReadStore() instanceof RocksDBConsumeQueueStore); } { messageStoreConfig.setCombineCQLoadingCQTypes(";;default;defaultRocksDB;"); messageStoreConfig.setCombineCQPreferCQType("defaultRocksDB"); CombineConsumeQueueStore store = new CombineConsumeQueueStore(messageStore); assertNotNull(store.getConsumeQueueStore()); assertNotNull(store.getRocksDBConsumeQueueStore()); assertTrue(store.getCurrentReadStore() instanceof RocksDBConsumeQueueStore); } { messageStoreConfig.setCombineCQLoadingCQTypes("default;defaultRocksDB"); messageStoreConfig.setCombineCQPreferCQType("defaultRocksDB"); CombineConsumeQueueStore store = new CombineConsumeQueueStore(messageStore); assertTrue(store.getCurrentReadStore() instanceof RocksDBConsumeQueueStore); } } @Test public void testIterator() throws Exception { messageStoreConfig.setRocksdbCQDoubleWriteEnable(true); messageStore = (DefaultMessageStore) createMessageStore(null, false, topicConfigTableMap, messageStoreConfig); messageStore.load(); messageStore.start(); //The initial min max offset, before and after the creation of consume queue Assert.assertEquals(0, messageStore.getMaxOffsetInQueue(topic, queueId)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, queueId)); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, queueId); Assert.assertEquals(CQType.SimpleCQ, consumeQueue.getCQType()); Assert.assertEquals(0, consumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(0, messageStore.getMaxOffsetInQueue(topic, queueId)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, queueId)); for (int i = 0; i < msgNum; i++) { DispatchRequest request = new DispatchRequest(topic, queueId, i * msgSize, msgSize, i, System.currentTimeMillis(), i, null, null, 0, 0, null); messageStore.getQueueStore().putMessagePositionInfoWrapper(request); } await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { checkCQ(consumeQueue, msgNum, msgSize); CombineConsumeQueueStore combineConsumeQueueStore = (CombineConsumeQueueStore) messageStore.getQueueStore(); ConsumeQueueInterface rocksDBConsumeQueue = combineConsumeQueueStore.getRocksDBConsumeQueueStore().getConsumeQueue(topic, queueId); Assert.assertEquals(CQType.RocksDBCQ, rocksDBConsumeQueue.getCQType()); Assert.assertEquals(msgNum, rocksDBConsumeQueue.getMaxOffsetInQueue()); checkCQ(rocksDBConsumeQueue, msgNum, msgSize); }); } private void checkCQ(ConsumeQueueInterface consumeQueue, int msgNum, int msgSize) { Assert.assertEquals(0, consumeQueue.getMinLogicOffset()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(msgNum, consumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(msgNum, consumeQueue.getMessageTotalInQueue()); assertNull(consumeQueue.iterateFrom(-1)); assertNull(consumeQueue.iterateFrom(msgNum)); { CqUnit first = consumeQueue.getEarliestUnit(); assertNotNull(first); Assert.assertEquals(0, first.getQueueOffset()); Assert.assertEquals(msgSize, first.getSize()); assertTrue(first.isTagsCodeValid()); } { CqUnit last = consumeQueue.getLatestUnit(); assertNotNull(last); Assert.assertEquals(msgNum - 1, last.getQueueOffset()); Assert.assertEquals(msgSize, last.getSize()); assertTrue(last.isTagsCodeValid()); } for (int i = 0; i < msgNum; i++) { ReferredIterator iterator = consumeQueue.iterateFrom(i); assertNotNull(iterator); long queueOffset = i; while (iterator.hasNext()) { CqUnit cqUnit = iterator.next(); Assert.assertEquals(queueOffset, cqUnit.getQueueOffset()); Assert.assertEquals(queueOffset * msgSize, cqUnit.getPos()); Assert.assertEquals(msgSize, cqUnit.getSize()); assertTrue(cqUnit.isTagsCodeValid()); Assert.assertEquals(queueOffset, cqUnit.getTagsCode()); Assert.assertEquals(queueOffset, cqUnit.getValidTagsCodeAsLong().longValue()); Assert.assertEquals(1, cqUnit.getBatchNum()); assertNull(cqUnit.getCqExtUnit()); queueOffset++; } Assert.assertEquals(msgNum, queueOffset); } } @Test public void testInitializeWithOffset() throws Exception { final String path = createBaseDir(); FileUtils.deleteDirectory(new File(path)); topicConfigTableMap.put(topic, new TopicConfig(topic, 1, 1, PermName.PERM_WRITE | PermName.PERM_READ)); { messageStoreConfig.setRocksdbCQDoubleWriteEnable(true); messageStore = (DefaultMessageStore) createMessageStore(path, false, topicConfigTableMap, messageStoreConfig); messageStore.load(); ConsumeQueueStoreInterface consumeQueueStoreInterface = messageStore.getQueueStore(); assertTrue(consumeQueueStoreInterface instanceof CombineConsumeQueueStore); CombineConsumeQueueStore combineConsumeQueueStore = (CombineConsumeQueueStore) consumeQueueStoreInterface; ConsumeQueueStore consumeQueueStore = combineConsumeQueueStore.getConsumeQueueStore(); RocksDBConsumeQueueStore rocksDBConsumeQueueStore = combineConsumeQueueStore.getRocksDBConsumeQueueStore(); assertNotNull(consumeQueueStore); assertNotNull(rocksDBConsumeQueueStore); ConsumeQueueInterface rocksDBConsumeQueue = rocksDBConsumeQueueStore.findOrCreateConsumeQueue(topic, queueId); consumeQueueStore.findOrCreateConsumeQueue(topic, queueId); rocksDBConsumeQueue.initializeWithOffset(100, 0); Assert.assertEquals(100, rocksDBConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(100, rocksDBConsumeQueue.getMinOffsetInQueue()); rocksDBConsumeQueue.initializeWithOffset(200, 0); Assert.assertEquals(200, rocksDBConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(200, rocksDBConsumeQueue.getMinOffsetInQueue()); messageStore.start(); Assert.assertEquals(0, rocksDBConsumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(0, rocksDBConsumeQueue.getMinOffsetInQueue()); ConsumeQueue consumeQueue = (ConsumeQueue) consumeQueueStore.findOrCreateConsumeQueue(topic, queueId); for (int i = 0; i < msgNum; i++) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setQueueId(queueId); msg.setBody(new byte[msgSize]); msg.setTopic(topic); msg.setTags("TAG1"); msg.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(SINGLE_TAG, msg.getTags())); msg.setBornTimestamp(System.currentTimeMillis()); msg.setReconsumeTimes(0); msg.setBornHost(new InetSocketAddress(9999)); msg.setStoreHost(new InetSocketAddress(8888)); messageStore.putMessage(msg); } await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { Assert.assertEquals(msgNum, consumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); }); messageStore.shutdown(); } { messageStoreConfig.setRocksdbCQDoubleWriteEnable(true); messageStore = (DefaultMessageStore) createMessageStore(path, false, topicConfigTableMap, messageStoreConfig); messageStore.load(); ConsumeQueueStoreInterface consumeQueueStoreInterface = messageStore.getQueueStore(); assertTrue(consumeQueueStoreInterface instanceof CombineConsumeQueueStore); CombineConsumeQueueStore combineConsumeQueueStore = (CombineConsumeQueueStore) consumeQueueStoreInterface; ConsumeQueueStore consumeQueueStore = combineConsumeQueueStore.getConsumeQueueStore(); RocksDBConsumeQueueStore rocksDBConsumeQueueStore = combineConsumeQueueStore.getRocksDBConsumeQueueStore(); assertNotNull(consumeQueueStore); assertNotNull(rocksDBConsumeQueueStore); consumeQueueStore.findOrCreateConsumeQueue(topic, queueId).initializeWithOffset(200, 0); ConsumeQueueInterface cq = rocksDBConsumeQueueStore.findOrCreateConsumeQueue(topic, queueId); Assert.assertEquals(msgNum, cq.getMaxOffsetInQueue()); Assert.assertEquals(0, cq.getMinOffsetInQueue()); combineConsumeQueueStore.verifyAndInitOffsetForAllStore(true); Assert.assertEquals(200, cq.getMaxOffsetInQueue()); Assert.assertEquals(200, cq.getMinOffsetInQueue()); messageStore.shutdown(); } } @Test public void testVerifyAndInitOffsetForAllStore() throws Exception { final String path = createBaseDir(); topicConfigTableMap.put(topic, new TopicConfig(topic, 1, 1, PermName.PERM_WRITE | PermName.PERM_READ)); { messageStoreConfig.setRocksdbCQDoubleWriteEnable(false); messageStore = (DefaultMessageStore) createMessageStore(path, false, topicConfigTableMap, messageStoreConfig); messageStore.load(); messageStore.start(); assertTrue(messageStore.getQueueStore() instanceof ConsumeQueueStore); for (int i = 0; i < msgNum; i++) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setQueueId(queueId); msg.setBody(new byte[msgSize]); msg.setTopic(topic); msg.setTags("TAG1"); msg.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(SINGLE_TAG, msg.getTags())); msg.setBornTimestamp(System.currentTimeMillis()); msg.setReconsumeTimes(0); msg.setBornHost(new InetSocketAddress(9999)); msg.setStoreHost(new InetSocketAddress(8888)); messageStore.putMessage(msg); } await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { File cq = new File(path + File.separator + "consumequeue" + File.separator + topic + File.separator + queueId + File.separator + "00000000000000000000"); assertTrue(cq.exists()); Assert.assertEquals(msgNum, (long) messageStore.getQueueStore().getMaxOffset(topic, queueId)); Assert.assertEquals(0, messageStore.getQueueStore().getMinOffsetInQueue(topic, queueId)); }); messageStore.shutdown(); } { messageStoreConfig.setRocksdbCQDoubleWriteEnable(true); messageStore = (DefaultMessageStore) createMessageStore(path, false, topicConfigTableMap, messageStoreConfig); messageStore.load(); await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { assertTrue(((CombineConsumeQueueStore) messageStore.getQueueStore()).verifyAndInitOffsetForAllStore(false)); }); messageStore.start(); messageStore.shutdown(); } { messageStoreConfig.setRocksdbCQDoubleWriteEnable(true); messageStore = (DefaultMessageStore) createMessageStore(path, false, topicConfigTableMap, messageStoreConfig); messageStore.load(); await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { assertTrue(((CombineConsumeQueueStore) messageStore.getQueueStore()).verifyAndInitOffsetForAllStore(false)); }); messageStore.start(); messageStore.shutdown(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.google.common.collect.Sets; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.LmqDispatch; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.File; import java.util.UUID; import java.util.stream.IntStream; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class ConsumeQueueStoreTest extends QueueTestBase { private MessageStore messageStore; private ConcurrentMap topicConfigTableMap; @Before public void init() throws Exception { this.topicConfigTableMap = new ConcurrentHashMap<>(); messageStore = createMessageStore(null, true, topicConfigTableMap); messageStore.load(); messageStore.start(); } @After public void destroy() { messageStore.shutdown(); messageStore.destroy(); File file = new File(messageStore.getMessageStoreConfig().getStorePathRootDir()); UtilAll.deleteFile(file); } @Test public void testLoadConsumeQueuesWithWrongAttribute() { String normalTopic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(normalTopic, CQType.SimpleCQ); this.topicConfigTableMap.putAll(topicConfigTable); for (int i = 0; i < 10; i++) { PutMessageResult putMessageResult = messageStore.putMessage(buildMessage(normalTopic, -1)); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); // simulate delete topic but with files left. this.topicConfigTableMap.clear(); topicConfigTable = createTopicConfigTable(normalTopic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> messageStore.getQueueStore().load()); Assert.assertTrue(runtimeException.getMessage().endsWith("should be SimpleCQ, but is BatchCQ")); } @Test public void testLoadBatchConsumeQueuesWithWrongAttribute() { String batchTopic = UUID.randomUUID().toString(); ConcurrentMap topicConfigTable = createTopicConfigTable(batchTopic, CQType.BatchCQ); this.topicConfigTableMap.putAll(topicConfigTable); for (int i = 0; i < 10; i++) { PutMessageResult putMessageResult = messageStore.putMessage(buildMessage(batchTopic, 10)); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); // simulate delete topic but with files left. this.topicConfigTableMap.clear(); topicConfigTable = createTopicConfigTable(batchTopic, CQType.SimpleCQ); this.topicConfigTableMap.putAll(topicConfigTable); messageStore.shutdown(); RuntimeException runtimeException = Assert.assertThrows(RuntimeException.class, () -> messageStore.getQueueStore().load()); Assert.assertTrue(runtimeException.getMessage().endsWith("should be BatchCQ, but is SimpleCQ")); } @Test public void testLmqCounter_running() throws ConsumeQueueException { messageStore.getMessageStoreConfig().setEnableMultiDispatch(true); messageStore.getMessageStoreConfig().setEnableLmq(true); messageStore.getMessageStoreConfig().setEnableCompaction(false); int num = 5; String topic = "topic"; List lmqNameList = IntStream.range(0, num) .mapToObj(i -> MixAll.LMQ_PREFIX + UUID.randomUUID()) .collect(java.util.stream.Collectors.toList()); assertEquals(0, messageStore.getQueueStore().getLmqNum()); lmqNameList.forEach(lmqName -> assertNull(messageStore.getConsumeQueue(lmqName, 0))); assertEquals(0, messageStore.getQueueStore().getLmqNum()); for (String lmqName : lmqNameList) { MessageExtBrokerInner message = buildMessage(topic, -1); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqName); LmqDispatch.wrapLmqDispatch(messageStore, message); PutMessageResult putMessageResult = messageStore.putMessage(message); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); lmqNameList.forEach(lmqName -> assertNotNull(messageStore.getConsumeQueue(lmqName, 0))); assertEquals(num, messageStore.getQueueStore().getLmqNum()); lmqNameList.forEach(lmqName -> messageStore.deleteTopics(Sets.newHashSet(lmqName))); assertEquals(0, messageStore.getQueueStore().getLmqNum()); } @Test public void testLmqCounter_reload() throws Exception { messageStore.getMessageStoreConfig().setEnableMultiDispatch(true); messageStore.getMessageStoreConfig().setEnableLmq(true); int num = 5; String topic = "topic"; List lmqNameList = IntStream.range(0, num) .mapToObj(i -> MixAll.LMQ_PREFIX + UUID.randomUUID()) .collect(java.util.stream.Collectors.toList()); assertEquals(0, messageStore.getQueueStore().getLmqNum()); for (String lmqName : lmqNameList) { MessageExtBrokerInner message = buildMessage(topic, -1); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqName); LmqDispatch.wrapLmqDispatch(messageStore, message); PutMessageResult putMessageResult = messageStore.putMessage(message); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); assertEquals(num, messageStore.getQueueStore().getLmqNum()); messageStore.shutdown(); // create new one based on current store MessageStore newStore = createMessageStore(messageStore.getMessageStoreConfig().getStorePathRootDir(), true, topicConfigTableMap, messageStore.getMessageStoreConfig()); newStore.load(); newStore.start(); assertEquals(num, newStore.getQueueStore().getLmqNum()); lmqNameList.forEach(lmqName -> assertNotNull(newStore.getConsumeQueue(lmqName, 0))); newStore.shutdown(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.nio.ByteBuffer; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.Assert; import org.junit.Test; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.awaitility.Awaitility.await; public class ConsumeQueueTest extends QueueTestBase { private static final String TOPIC = "StoreTest"; private static final int QUEUE_ID = 0; private static final String STORE_PATH = "." + File.separator + "unit_test_store"; private static final int COMMIT_LOG_FILE_SIZE = 1024 * 8; private static final int CQ_FILE_SIZE = 10 * 20; private static final int CQ_EXT_FILE_SIZE = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); public MessageStoreConfig buildStoreConfig(int commitLogFileSize, int cqFileSize, boolean enableCqExt, int cqExtFileSize) { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(commitLogFileSize); messageStoreConfig.setMappedFileSizeConsumeQueue(cqFileSize); messageStoreConfig.setMappedFileSizeConsumeQueueExt(cqExtFileSize); messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(enableCqExt); messageStoreConfig.setStorePathRootDir(STORE_PATH); messageStoreConfig.setStorePathCommitLog(STORE_PATH + File.separator + "commitlog"); return messageStoreConfig; } protected DefaultMessageStore gen() throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); BrokerConfig brokerConfig = new BrokerConfig(); DefaultMessageStore master = new DefaultMessageStore( messageStoreConfig, new BrokerStatsManager(brokerConfig), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, brokerConfig, new ConcurrentHashMap<>()); assertThat(master.load()).isTrue(); master.start(); return master; } protected RocksDBMessageStore genRocksdbMessageStore() throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); BrokerConfig brokerConfig = new BrokerConfig(); RocksDBMessageStore master = new RocksDBMessageStore( messageStoreConfig, new BrokerStatsManager(brokerConfig), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, brokerConfig, new ConcurrentHashMap<>()); assertThat(master.load()).isTrue(); master.start(); return master; } protected void putMsg(MessageStore messageStore) { int totalMsgs = 200; for (int i = 0; i < totalMsgs; i++) { MessageExtBrokerInner message = buildMessage(); message.setQueueId(0); switch (i % 3) { case 0: message.setTags("TagA"); break; case 1: message.setTags("TagB"); break; case 2: message.setTags("TagC"); break; } message.setTagsCode(message.getTags().hashCode()); message.setPropertiesString(MessageDecoder.messageProperties2String(message.getProperties())); messageStore.putMessage(message); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); } @Test public void testIterator() throws Exception { final int msgNum = 100; final int msgSize = 1000; MessageStore messageStore = createMessageStore(null, true, null); messageStore.load(); String topic = UUID.randomUUID().toString(); //The initial min max offset, before and after the creation of consume queue Assert.assertEquals(0, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, 0); Assert.assertEquals(CQType.SimpleCQ, consumeQueue.getCQType()); Assert.assertEquals(0, consumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(0, messageStore.getMaxOffsetInQueue(topic, 0)); Assert.assertEquals(0, messageStore.getMinOffsetInQueue(topic, 0)); for (int i = 0; i < msgNum; i++) { DispatchRequest request = new DispatchRequest(consumeQueue.getTopic(), consumeQueue.getQueueId(), i * msgSize, msgSize, i, System.currentTimeMillis(), i, null, null, 0, 0, null); request.setBitMap(new byte[10]); ((AbstractConsumeQueueStore) messageStore.getQueueStore()).putMessagePositionInfoWrapper(consumeQueue, request); } Assert.assertEquals(0, consumeQueue.getMinLogicOffset()); Assert.assertEquals(0, consumeQueue.getMinOffsetInQueue()); Assert.assertEquals(msgNum, consumeQueue.getMaxOffsetInQueue()); Assert.assertEquals(msgNum, consumeQueue.getMessageTotalInQueue()); //TO DO Should test it //Assert.assertEquals(100 * 100, consumeQueue.getMaxPhysicOffset()); Assert.assertNull(consumeQueue.iterateFrom(-1)); Assert.assertNull(consumeQueue.iterateFrom(msgNum)); { CqUnit first = consumeQueue.getEarliestUnit(); Assert.assertNotNull(first); Assert.assertEquals(0, first.getQueueOffset()); Assert.assertEquals(msgSize, first.getSize()); Assert.assertTrue(first.isTagsCodeValid()); } { CqUnit last = consumeQueue.getLatestUnit(); Assert.assertNotNull(last); Assert.assertEquals(msgNum - 1, last.getQueueOffset()); Assert.assertEquals(msgSize, last.getSize()); Assert.assertTrue(last.isTagsCodeValid()); } for (int i = 0; i < msgNum; i++) { ReferredIterator iterator = consumeQueue.iterateFrom(i); Assert.assertNotNull(iterator); long queueOffset = i; while (iterator.hasNext()) { CqUnit cqUnit = iterator.next(); Assert.assertEquals(queueOffset, cqUnit.getQueueOffset()); Assert.assertEquals(queueOffset * msgSize, cqUnit.getPos()); Assert.assertEquals(msgSize, cqUnit.getSize()); Assert.assertTrue(cqUnit.isTagsCodeValid()); Assert.assertEquals(queueOffset, cqUnit.getTagsCode()); Assert.assertEquals(queueOffset, cqUnit.getValidTagsCodeAsLong().longValue()); Assert.assertEquals(1, cqUnit.getBatchNum()); Assert.assertNotNull(cqUnit.getCqExtUnit()); ConsumeQueueExt.CqExtUnit cqExtUnit = cqUnit.getCqExtUnit(); Assert.assertEquals(queueOffset, cqExtUnit.getTagsCode()); Assert.assertArrayEquals(new byte[10], cqExtUnit.getFilterBitMap()); queueOffset++; } Assert.assertEquals(msgNum, queueOffset); } ((AbstractConsumeQueueStore) messageStore.getQueueStore()).destroy(consumeQueue); } @Test public void testEstimateMessageCountInEmptyConsumeQueue() { DefaultMessageStore messageStore = null; try { messageStore = gen(); doTestEstimateMessageCountInEmptyConsumeQueue(messageStore); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } @Test public void testEstimateRocksdbMessageCountInEmptyConsumeQueue() { if (notExecuted()) { return; } DefaultMessageStore messageStore = null; try { messageStore = genRocksdbMessageStore(); doTestEstimateMessageCountInEmptyConsumeQueue(messageStore); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } public void doTestEstimateMessageCountInEmptyConsumeQueue(MessageStore master) { try { ConsumeQueueInterface consumeQueue = master.findConsumeQueue(TOPIC, QUEUE_ID); MessageFilter filter = new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { return tagsCode == "TagA".hashCode(); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return false; } }; long estimation = consumeQueue.estimateMessageCount(0, 0, filter); Assert.assertEquals(-1, estimation); // test for illegal offset estimation = consumeQueue.estimateMessageCount(0, 100, filter); Assert.assertEquals(-1, estimation); estimation = consumeQueue.estimateMessageCount(100, 1000, filter); Assert.assertEquals(-1, estimation); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } finally { if (master != null) { master.shutdown(); master.destroy(); } UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testEstimateRocksdbMessageCount() { if (notExecuted()) { return; } DefaultMessageStore messageStore = null; try { messageStore = genRocksdbMessageStore(); doTestEstimateMessageCount(messageStore); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } @Test public void testEstimateMessageCount() { DefaultMessageStore messageStore = null; try { messageStore = gen(); doTestEstimateMessageCount(messageStore); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } public void doTestEstimateMessageCount(MessageStore messageStore) { try { try { putMsg(messageStore); } catch (Exception e) { fail("Failed to put message", e); } ConsumeQueueInterface cq = messageStore.findConsumeQueue(TOPIC, QUEUE_ID); MessageFilter filter = new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { return tagsCode == "TagA".hashCode(); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return false; } }; long estimation = cq.estimateMessageCount(0, 199, filter); Assert.assertEquals(67, estimation); // test for illegal offset estimation = cq.estimateMessageCount(0, 1000, filter); Assert.assertEquals(67, estimation); estimation = cq.estimateMessageCount(1000, 10000, filter); Assert.assertEquals(-1, estimation); estimation = cq.estimateMessageCount(100, 0, filter); Assert.assertEquals(-1, estimation); } finally { messageStore.shutdown(); messageStore.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } @Test public void testEstimateRocksdbMessageCountSample() { if (notExecuted()) { return; } DefaultMessageStore messageStore = null; try { messageStore = genRocksdbMessageStore(); doTestEstimateMessageCountSample(messageStore); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } @Test public void testEstimateMessageCountSample() { DefaultMessageStore messageStore = null; try { messageStore = gen(); doTestEstimateMessageCountSample(messageStore); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); } } public void doTestEstimateMessageCountSample(MessageStore messageStore) { try { try { putMsg(messageStore); } catch (Exception e) { fail("Failed to put message", e); } messageStore.getMessageStoreConfig().setSampleCountThreshold(10); messageStore.getMessageStoreConfig().setMaxConsumeQueueScan(20); ConsumeQueueInterface cq = messageStore.findConsumeQueue(TOPIC, QUEUE_ID); MessageFilter filter = new MessageFilter() { @Override public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { return tagsCode == "TagA".hashCode(); } @Override public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, Map properties) { return false; } }; long estimation = cq.estimateMessageCount(100, 150, filter); Assert.assertEquals(15, estimation); } finally { messageStore.shutdown(); messageStore.destroy(); UtilAll.deleteFile(new File(STORE_PATH)); } } private boolean notExecuted() { return MixAll.isMac(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/QueueTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.StoreTestBase; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; public class QueueTestBase extends StoreTestBase { protected ConcurrentMap createTopicConfigTable(String topic, CQType cqType) { ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); TopicConfig topicConfigToBeAdded = new TopicConfig(); Map attributes = new HashMap<>(); attributes.put(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName(), cqType.toString()); topicConfigToBeAdded.setTopicName(topic); topicConfigToBeAdded.setAttributes(attributes); topicConfigTable.put(topic, topicConfigToBeAdded); return topicConfigTable; } protected Callable fullyDispatched(MessageStore messageStore) { return () -> messageStore.dispatchBehindBytes() == 0; } protected MessageStore createMessageStore(String baseDir, boolean extent, ConcurrentMap topicConfigTable) throws Exception { return createMessageStore(baseDir, extent, topicConfigTable, new MessageStoreConfig()); } protected MessageStore createMessageStore(String baseDir, boolean extent, ConcurrentMap topicConfigTable, MessageStoreConfig messageStoreConfig) throws Exception { if (baseDir == null) { baseDir = createBaseDir(); } baseDirs.add(baseDir); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(100 * ConsumeQueue.CQ_STORE_UNIT_SIZE); messageStoreConfig.setMapperFileSizeBatchConsumeQueue(20 * BatchConsumeQueue.CQ_STORE_UNIT_SIZE); messageStoreConfig.setMappedFileSizeConsumeQueueExt(1024); messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setEnableConsumeQueueExt(extent); messageStoreConfig.setStorePathRootDir(baseDir); messageStoreConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); messageStoreConfig.setHaListenPort(nextPort()); messageStoreConfig.setMaxTransferBytesOnMessageInDisk(1024 * 1024); messageStoreConfig.setMaxTransferBytesOnMessageInMemory(1024 * 1024); messageStoreConfig.setMaxTransferCountOnMessageInDisk(1024); messageStoreConfig.setMaxTransferCountOnMessageInMemory(1024); messageStoreConfig.setFlushIntervalCommitLog(1); messageStoreConfig.setFlushCommitLogThoroughInterval(2); MessageStore messageStore; if (messageStoreConfig.isEnableRocksDBStore()) { messageStore = new RocksDBMessageStore( messageStoreConfig, new BrokerStatsManager("simpleTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, new BrokerConfig(), topicConfigTable); } else { messageStore = new DefaultMessageStore( messageStoreConfig, new BrokerStatsManager("simpleTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { }, new BrokerConfig(), topicConfigTable); } return messageStore; } public MessageExtBrokerInner buildMessage(String topic, int batchNum) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setKeys("Hello"); msg.setBody(new byte[1024]); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(0); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(storeHost); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_NUM, String.valueOf(batchNum)); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); if (batchNum > 1) { msg.setSysFlag(MessageSysFlag.INNER_BATCH_FLAG); } if (batchNum == -1) { MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_INNER_NUM); } return msg; } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueOffsetTableTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.queue.offset.OffsetEntryType; import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.rocksdb.Options; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.WriteBatch; import org.rocksdb.WriteOptions; @RunWith(MockitoJUnitRunner.class) public class RocksDBConsumeQueueOffsetTableTest { private RocksDBConsumeQueueOffsetTable offsetTable; @Mock private ConsumeQueueRocksDBStorage rocksDBStorage; @Mock private RocksDBConsumeQueueTable consumeQueueTable; @Mock private DefaultMessageStore messageStore; private static RocksDB db; private static File dbPath; private static String topicName; @BeforeClass public static void initDB() throws IOException, RocksDBException { TemporaryFolder tempFolder = new TemporaryFolder(); tempFolder.create(); dbPath = tempFolder.newFolder(); db = RocksDB.open(dbPath.getAbsolutePath()); StringBuilder topicBuilder = new StringBuilder(); for (int i = 0; i < 100; i++) { topicBuilder.append("topic"); } topicName = topicBuilder.toString(); writeOffset(topicName, 1, 100, 2, true); } @AfterClass public static void tearDownDB() throws RocksDBException { db.closeE(); RocksDB.destroyDB(dbPath.getAbsolutePath(), new Options()); } @Before public void setUp() { RocksIterator iterator = db.newIterator(); Mockito.doReturn(iterator).when(rocksDBStorage).seekOffsetCF(); offsetTable = new RocksDBConsumeQueueOffsetTable(consumeQueueTable, rocksDBStorage, messageStore); } /** * Verify forEach can expand key-buffer properly and works well for long topic names. * * @throws RocksDBException If there is an RocksDB error. */ @Test public void testForEach() throws RocksDBException { AtomicBoolean called = new AtomicBoolean(false); offsetTable.forEach(entry -> true, entry -> { called.set(true); Assert.assertEquals(topicName, entry.topic); Assert.assertTrue(topicName.length() > 256); Assert.assertEquals(1, entry.queueId); Assert.assertEquals(100, entry.commitLogOffset); Assert.assertEquals(2, entry.offset); Assert.assertEquals(OffsetEntryType.MAXIMUM, entry.type); }); Assert.assertTrue(called.get()); } @Test public void testLmqCounter() throws RocksDBException { Assert.assertEquals(0, offsetTable.getLmqNum()); offsetTable.load(); int initCount = offsetTable.getLmqNum(); int lmqCount = 2; int repeatCount = 3; for (int i = 0; i < lmqCount; i++) { String lmqName = MixAll.LMQ_PREFIX + UUID.randomUUID(); String normalTopic = UUID.randomUUID().toString(); for (int j = 0; j < repeatCount; j++) { writeOffset(lmqName, 0, 100, j, true); writeOffset(lmqName, 0, 100, j, false); writeOffset(normalTopic, 0, 100, j, true); writeOffset(normalTopic, 0, 100, j, false); } } Mockito.doReturn(db.newIterator()).when(rocksDBStorage).seekOffsetCF(); offsetTable.load(); Assert.assertEquals(initCount + lmqCount, offsetTable.getLmqNum()); } private static void writeOffset(String topic, int queueId, long phyOffset, long cqOffset, boolean max) throws RocksDBException { byte[] topicInBytes = topic.getBytes(StandardCharsets.UTF_8); ByteBuffer keyBuffer = ByteBuffer.allocateDirect( RocksDBConsumeQueueOffsetTable.OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicInBytes.length); RocksDBConsumeQueueOffsetTable.buildOffsetKeyByteBuffer(keyBuffer, topicInBytes, 1, max); Assert.assertEquals(0, keyBuffer.position()); Assert.assertEquals(RocksDBConsumeQueueOffsetTable.OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicInBytes.length, keyBuffer.limit()); ByteBuffer valueBuffer = ByteBuffer.allocateDirect(Long.BYTES + Long.BYTES); valueBuffer.putLong(phyOffset); valueBuffer.putLong(cqOffset); valueBuffer.flip(); try (WriteBatch writeBatch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { writeOptions.setDisableWAL(false); writeOptions.setSync(true); writeBatch.put(keyBuffer, valueBuffer); db.write(writeOptions, writeBatch); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTableTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage; import org.junit.Test; import org.mockito.stubbing.Answer; import org.rocksdb.RocksDBException; import java.nio.ByteBuffer; import static org.apache.rocketmq.store.queue.RocksDBConsumeQueueTable.CQ_UNIT_SIZE; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; public class RocksDBConsumeQueueTableTest { @Test public void testBinarySearchInCQByTime() throws RocksDBException { if (MixAll.isMac()) { return; } ConsumeQueueRocksDBStorage rocksDBStorage = mock(ConsumeQueueRocksDBStorage.class); DefaultMessageStore store = mock(DefaultMessageStore.class); RocksDBConsumeQueueTable table = new RocksDBConsumeQueueTable(rocksDBStorage, store); doAnswer((Answer) mock -> { /* * queueOffset timestamp * 100 1000 * 200 2000 * 201 2010 * 1000 10000 */ byte[] keyBytes = mock.getArgument(0); ByteBuffer keyBuffer = ByteBuffer.wrap(keyBytes); int len = keyBuffer.getInt(0); long offset = keyBuffer.getLong(4 + 1 + len + 1 + 4 + 1); long phyOffset = offset; long timestamp = offset * 10; final ByteBuffer byteBuffer = ByteBuffer.allocate(CQ_UNIT_SIZE); byteBuffer.putLong(phyOffset); byteBuffer.putInt(1); byteBuffer.putLong(0); byteBuffer.putLong(timestamp); return byteBuffer.array(); }).when(rocksDBStorage).getCQ(any()); assertEquals(1001, table.binarySearchInCQByTime("topic", 0, 1000, 100, 20000, 0, BoundaryType.LOWER)); assertEquals(1000, table.binarySearchInCQByTime("topic", 0, 1000, 100, 20000, 0, BoundaryType.UPPER)); assertEquals(100, table.binarySearchInCQByTime("topic", 0, 1000, 100, 1, 0, BoundaryType.LOWER)); assertEquals(0, table.binarySearchInCQByTime("topic", 0, 1000, 100, 1, 0, BoundaryType.UPPER)); assertEquals(201, table.binarySearchInCQByTime("topic", 0, 1000, 100, 2001, 0, BoundaryType.LOWER)); assertEquals(200, table.binarySearchInCQByTime("topic", 0, 1000, 100, 2001, 0, BoundaryType.UPPER)); assertEquals(200, table.binarySearchInCQByTime("topic", 0, 1000, 100, 2000, 0, BoundaryType.LOWER)); assertEquals(200, table.binarySearchInCQByTime("topic", 0, 1000, 100, 2000, 0, BoundaryType.UPPER)); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import java.io.File; import java.nio.ByteBuffer; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.IntStream; import com.google.common.collect.Sets; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.LmqDispatch; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.StoreType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.rocketmq.store.queue.RocksDBConsumeQueueTable.CQ_UNIT_SIZE; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class RocksDBConsumeQueueTest extends QueueTestBase { private MessageStore messageStore; private ConcurrentMap topicConfigTableMap; @Before public void init() throws Exception { MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setStoreType(StoreType.DEFAULT_ROCKSDB.getStoreType()); storeConfig.setEnableCompaction(false); this.topicConfigTableMap = new ConcurrentHashMap<>(); messageStore = createMessageStore(null, true, topicConfigTableMap, storeConfig); messageStore.load(); messageStore.start(); } @After public void destroy() { messageStore.shutdown(); messageStore.destroy(); File file = new File(messageStore.getMessageStoreConfig().getStorePathRootDir()); UtilAll.deleteFile(file); } @Test public void testIterator() throws Exception { if (MixAll.isMac()) { return; } DefaultMessageStore messageStore = mock(DefaultMessageStore.class); RocksDBConsumeQueueStore rocksDBConsumeQueueStore = mock(RocksDBConsumeQueueStore.class); when(messageStore.getQueueStore()).thenReturn(rocksDBConsumeQueueStore); when(rocksDBConsumeQueueStore.getMaxOffsetInQueue(anyString(), anyInt())).thenReturn(10000L); when(rocksDBConsumeQueueStore.get(anyString(), anyInt(), anyLong())).then(new Answer() { @Override public ByteBuffer answer(InvocationOnMock mock) throws Throwable { long startIndex = mock.getArgument(2); final ByteBuffer byteBuffer = ByteBuffer.allocate(CQ_UNIT_SIZE); long phyOffset = startIndex * 10; byteBuffer.putLong(phyOffset); byteBuffer.putInt(1); byteBuffer.putLong(0); byteBuffer.putLong(0); byteBuffer.flip(); return byteBuffer; } }); RocksDBConsumeQueue consumeQueue = new RocksDBConsumeQueue(messageStore.getMessageStoreConfig(), rocksDBConsumeQueueStore, "topic", 0); ReferredIterator it = consumeQueue.iterateFrom(9000); for (int i = 0; i < 1000; i++) { assertTrue(it.hasNext()); CqUnit next = it.next(); assertEquals(9000 + i, next.getQueueOffset()); assertEquals(10 * (9000 + i), next.getPos()); } assertFalse(it.hasNext()); } @Test public void testLmqCounter_running() throws ConsumeQueueException { messageStore.getMessageStoreConfig().setEnableMultiDispatch(true); messageStore.getMessageStoreConfig().setEnableLmq(true); int num = 5; String topic = "topic"; List lmqNameList = IntStream.range(0, num) .mapToObj(i -> MixAll.LMQ_PREFIX + UUID.randomUUID()) .collect(java.util.stream.Collectors.toList()); assertEquals(0, messageStore.getQueueStore().getLmqNum()); lmqNameList.forEach(lmqName -> assertNotNull(messageStore.getConsumeQueue(lmqName, 0))); // create if not exist assertEquals(0, messageStore.getQueueStore().getLmqNum()); for (String lmqName : lmqNameList) { MessageExtBrokerInner message = buildMessage(topic, -1); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqName); LmqDispatch.wrapLmqDispatch(messageStore, message); PutMessageResult putMessageResult = messageStore.putMessage(message); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); lmqNameList.forEach(lmqName -> assertNotNull(messageStore.getConsumeQueue(lmqName, 0))); assertEquals(num, messageStore.getQueueStore().getLmqNum()); lmqNameList.forEach(lmqName -> messageStore.deleteTopics(Sets.newHashSet(lmqName))); assertEquals(0, messageStore.getQueueStore().getLmqNum()); } @Test public void testLmqCounter_reload() throws Exception { messageStore.getMessageStoreConfig().setEnableMultiDispatch(true); messageStore.getMessageStoreConfig().setEnableLmq(true); int num = 5; String topic = "topic"; List lmqNameList = IntStream.range(0, num) .mapToObj(i -> MixAll.LMQ_PREFIX + UUID.randomUUID()) .collect(java.util.stream.Collectors.toList()); assertEquals(0, messageStore.getQueueStore().getLmqNum()); for (String lmqName : lmqNameList) { MessageExtBrokerInner message = buildMessage(topic, -1); MessageAccessor.putProperty(message, MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqName); LmqDispatch.wrapLmqDispatch(messageStore, message); PutMessageResult putMessageResult = messageStore.putMessage(message); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } await().atMost(5, SECONDS).until(fullyDispatched(messageStore)); assertEquals(num, messageStore.getQueueStore().getLmqNum()); messageStore.shutdown(); // create new one based on current store MessageStore newStore = createMessageStore(messageStore.getMessageStoreConfig().getStorePathRootDir(), true, topicConfigTableMap, messageStore.getMessageStoreConfig()); newStore.load(); newStore.start(); assertEquals(num, newStore.getQueueStore().getLmqNum()); lmqNameList.forEach(lmqName -> assertNull(newStore.getQueueStore().getConsumeQueueTable().get(lmqName))); // not in consumeQueueTable newStore.shutdown(); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/queue/SparseConsumeQueueTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.queue; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.concurrent.ThreadLocalRandom; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SparseConsumeQueueTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); String path; MessageStore defaultMessageStore; SparseConsumeQueue scq; String topic = "topic1"; int queueId = 1; @Before public void setUp() throws IOException { path = tempFolder.newFolder("scq").getAbsolutePath(); defaultMessageStore = mock(DefaultMessageStore.class); CommitLog commitLog = mock(CommitLog.class); when(defaultMessageStore.getCommitLog()).thenReturn(commitLog); when(commitLog.getCommitLogSize()).thenReturn(10 * 1024 * 1024); MessageStoreConfig config = mock(MessageStoreConfig.class); doReturn(config).when(defaultMessageStore).getMessageStoreConfig(); doReturn(true).when(config).isSearchBcqByCacheEnable(); } private void fillByteBuf(ByteBuffer bb, long phyOffset, long queueOffset) { bb.putLong(phyOffset); bb.putInt("size".length()); bb.putLong("tagsCode".length()); bb.putLong(System.currentTimeMillis()); bb.putLong(queueOffset); bb.putShort((short)1); bb.putInt(0); bb.putInt(0); // 4 bytes reserved } @Test public void testLoad() throws IOException { scq = new SparseConsumeQueue(topic, queueId, path, BatchConsumeQueue.CQ_STORE_UNIT_SIZE, defaultMessageStore); String file1 = UtilAll.offset2FileName(111111); String file2 = UtilAll.offset2FileName(222222); long phyOffset = 10; long queueOffset = 1; ByteBuffer bb = ByteBuffer.allocate(BatchConsumeQueue.CQ_STORE_UNIT_SIZE); fillByteBuf(bb, phyOffset, queueOffset); Files.createDirectories(Paths.get(path, topic, String.valueOf(queueId))); Files.write(Paths.get(path, topic, String.valueOf(queueId), file1), bb.array(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); bb.clear(); fillByteBuf(bb, phyOffset + 1, queueOffset + 1); Files.write(Paths.get(path, topic, String.valueOf(queueId), file2), bb.array(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); scq.load(); scq.recover(); assertEquals(scq.get(queueOffset + 1).getPos(), phyOffset + 1); } private void fillByteBufSeq(ByteBuffer bb, int circle, long basePhyOffset, long baseQueueOffset) { long phyOffset = basePhyOffset; long queueOffset = baseQueueOffset; for (int i = 0; i < circle; i++) { fillByteBuf(bb, phyOffset, queueOffset); phyOffset++; queueOffset++; } } @Test public void testSearch() throws IOException { int fileSize = 10 * BatchConsumeQueue.CQ_STORE_UNIT_SIZE; scq = new SparseConsumeQueue(topic, queueId, path, fileSize, defaultMessageStore); ByteBuffer bb = ByteBuffer.allocate(fileSize); long basePhyOffset = 101; long baseQueueOffset = 101; /* 101 -> 101 ... 110 -> 110 201 -> 201 ... 210 -> 210 301 -> 301 ... 310 -> 310 ... */ for (int i = 0; i < 5; i++) { String fileName = UtilAll.offset2FileName(i * fileSize); fillByteBufSeq(bb, 10, basePhyOffset, baseQueueOffset); Files.createDirectories(Paths.get(path, topic, String.valueOf(queueId))); Files.write(Paths.get(path, topic, String.valueOf(queueId), fileName), bb.array(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); basePhyOffset = i * 100 + 1; baseQueueOffset = i * 100 + 1; bb.clear(); } scq.load(); scq.recover(); ReferredIterator bufferConsumeQueue = scq.iterateFromOrNext(105); //in the file assertNotNull(bufferConsumeQueue); assertTrue(bufferConsumeQueue.hasNext()); assertEquals(bufferConsumeQueue.next().getQueueOffset(), 105); bufferConsumeQueue.release(); bufferConsumeQueue = scq.iterateFromOrNext(120); // in the next file assertNotNull(bufferConsumeQueue); assertTrue(bufferConsumeQueue.hasNext()); assertEquals(bufferConsumeQueue.next().getQueueOffset(), 201); bufferConsumeQueue.release(); bufferConsumeQueue = scq.iterateFromOrNext(600); // not in the file assertNull(bufferConsumeQueue); } @Test public void testCreateFile() throws IOException { scq = new SparseConsumeQueue(topic, queueId, path, BatchConsumeQueue.CQ_STORE_UNIT_SIZE, defaultMessageStore); long physicalOffset = Math.abs(ThreadLocalRandom.current().nextLong()); String formatName = UtilAll.offset2FileName(physicalOffset); scq.createFile(physicalOffset); assertTrue(Files.exists(Paths.get(path, topic, String.valueOf(queueId), formatName))); scq.putBatchMessagePositionInfo(5,4,3,2,1,(short)1); assertEquals(4, scq.get(1).getSize()); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactoryTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.rocksdb; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.Assert; import org.junit.Test; import org.rocksdb.CompressionType; public class RocksDBOptionsFactoryTest { @Test public void testBottomMostCompressionType() { MessageStoreConfig config = new MessageStoreConfig(); Assert.assertEquals(CompressionType.ZSTD_COMPRESSION, CompressionType.getCompressionType(config.getBottomMostCompressionTypeForConsumeQueueStore())); Assert.assertEquals(CompressionType.LZ4_COMPRESSION, CompressionType.getCompressionType("lz4")); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/stats/BrokerStatsManagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.stats; import org.apache.rocketmq.common.topic.TopicValidator; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.apache.rocketmq.common.stats.Stats.BROKER_PUT_NUMS; import static org.apache.rocketmq.common.stats.Stats.GROUP_ACK_NUMS; import static org.apache.rocketmq.common.stats.Stats.GROUP_CK_NUMS; import static org.apache.rocketmq.common.stats.Stats.GROUP_GET_FALL_SIZE; import static org.apache.rocketmq.common.stats.Stats.GROUP_GET_FALL_TIME; import static org.apache.rocketmq.common.stats.Stats.GROUP_GET_LATENCY; import static org.apache.rocketmq.common.stats.Stats.GROUP_GET_NUMS; import static org.apache.rocketmq.common.stats.Stats.GROUP_GET_SIZE; import static org.apache.rocketmq.common.stats.Stats.QUEUE_GET_NUMS; import static org.apache.rocketmq.common.stats.Stats.QUEUE_GET_SIZE; import static org.apache.rocketmq.common.stats.Stats.QUEUE_PUT_NUMS; import static org.apache.rocketmq.common.stats.Stats.QUEUE_PUT_SIZE; import static org.apache.rocketmq.common.stats.Stats.SNDBCK_PUT_NUMS; import static org.apache.rocketmq.common.stats.Stats.TOPIC_PUT_LATENCY; import static org.apache.rocketmq.common.stats.Stats.TOPIC_PUT_NUMS; import static org.apache.rocketmq.common.stats.Stats.TOPIC_PUT_SIZE; import static org.assertj.core.api.Assertions.assertThat; public class BrokerStatsManagerTest { private BrokerStatsManager brokerStatsManager; private static final String TOPIC = "TOPIC_TEST"; private static final Integer QUEUE_ID = 0; private static final String GROUP_NAME = "GROUP_TEST"; private static final String CLUSTER_NAME = "DefaultCluster"; @Before public void init() { brokerStatsManager = new BrokerStatsManager(CLUSTER_NAME, true); brokerStatsManager.start(); } @After public void destroy() { brokerStatsManager.shutdown(); } @Test public void testGetStatsItem() { assertThat(brokerStatsManager.getStatsItem("TEST", "TEST")).isNull(); } @Test public void testIncQueuePutNums() { brokerStatsManager.incQueuePutNums(TOPIC, QUEUE_ID); String statsKey = brokerStatsManager.buildStatsKey(TOPIC, String.valueOf(QUEUE_ID)); assertThat(brokerStatsManager.getStatsItem(QUEUE_PUT_NUMS, statsKey).getTimes().doubleValue()).isEqualTo(1L); brokerStatsManager.incQueuePutNums(TOPIC, QUEUE_ID, 2, 2); assertThat(brokerStatsManager.getStatsItem(QUEUE_PUT_NUMS, statsKey).getValue().doubleValue()).isEqualTo(3L); } @Test public void testIncQueuePutSize() { brokerStatsManager.incQueuePutSize(TOPIC, QUEUE_ID, 2); String statsKey = brokerStatsManager.buildStatsKey(TOPIC, String.valueOf(QUEUE_ID)); assertThat(brokerStatsManager.getStatsItem(QUEUE_PUT_SIZE, statsKey).getValue().doubleValue()).isEqualTo(2L); } @Test public void testIncQueueGetNums() { brokerStatsManager.incQueueGetNums(GROUP_NAME, TOPIC, QUEUE_ID, 1); final String statsKey = brokerStatsManager.buildStatsKey(brokerStatsManager.buildStatsKey(TOPIC, String.valueOf(QUEUE_ID)), GROUP_NAME); assertThat(brokerStatsManager.getStatsItem(QUEUE_GET_NUMS, statsKey).getValue().doubleValue()).isEqualTo(1L); } @Test public void testIncQueueGetSize() { brokerStatsManager.incQueueGetSize(GROUP_NAME, TOPIC, QUEUE_ID, 1); final String statsKey = brokerStatsManager.buildStatsKey(brokerStatsManager.buildStatsKey(TOPIC, String.valueOf(QUEUE_ID)), GROUP_NAME); assertThat(brokerStatsManager.getStatsItem(QUEUE_GET_SIZE, statsKey).getValue().doubleValue()).isEqualTo(1L); } @Test public void testIncTopicPutNums() { brokerStatsManager.incTopicPutNums(TOPIC); assertThat(brokerStatsManager.getStatsItem(TOPIC_PUT_NUMS, TOPIC).getTimes().doubleValue()).isEqualTo(1L); brokerStatsManager.incTopicPutNums(TOPIC, 2, 2); assertThat(brokerStatsManager.getStatsItem(TOPIC_PUT_NUMS, TOPIC).getValue().doubleValue()).isEqualTo(3L); } @Test public void testIncTopicPutSize() { brokerStatsManager.incTopicPutSize(TOPIC, 2); assertThat(brokerStatsManager.getStatsItem(TOPIC_PUT_SIZE, TOPIC).getValue().doubleValue()).isEqualTo(2L); } @Test public void testIncGroupGetNums() { brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); String statsKey = brokerStatsManager.buildStatsKey(TOPIC, GROUP_NAME); assertThat(brokerStatsManager.getStatsItem(GROUP_GET_NUMS, statsKey).getValue().doubleValue()).isEqualTo(1L); } @Test public void testIncGroupGetSize() { brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 1); String statsKey = brokerStatsManager.buildStatsKey(TOPIC, GROUP_NAME); assertThat(brokerStatsManager.getStatsItem(GROUP_GET_SIZE, statsKey).getValue().doubleValue()).isEqualTo(1L); } @Test public void testIncGroupGetLatency() { brokerStatsManager.incGroupGetLatency(GROUP_NAME, TOPIC, 1, 1); String statsKey = String.format("%d@%s@%s", 1, TOPIC, GROUP_NAME); assertThat(brokerStatsManager.getStatsItem(GROUP_GET_LATENCY, statsKey).getValue().doubleValue()).isEqualTo(1L); } @Test public void testIncBrokerPutNums() { brokerStatsManager.incBrokerPutNums(); assertThat(brokerStatsManager.getStatsItem(BROKER_PUT_NUMS, CLUSTER_NAME).getValue().doubleValue()).isEqualTo(1L); } @Test public void testOnTopicDeleted() { brokerStatsManager.incTopicPutNums(TOPIC); brokerStatsManager.incTopicPutSize(TOPIC, 100); brokerStatsManager.incQueuePutNums(TOPIC, QUEUE_ID); brokerStatsManager.incQueuePutSize(TOPIC, QUEUE_ID, 100); brokerStatsManager.incTopicPutLatency(TOPIC, QUEUE_ID, 10); brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 100); brokerStatsManager.incGroupCkNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.incGroupAckNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.incQueueGetNums(GROUP_NAME, TOPIC, QUEUE_ID, 1); brokerStatsManager.incQueueGetSize(GROUP_NAME, TOPIC, QUEUE_ID, 100); brokerStatsManager.incSendBackNums(GROUP_NAME, TOPIC); brokerStatsManager.incGroupGetLatency(GROUP_NAME, TOPIC, 1, 1); brokerStatsManager.recordDiskFallBehindTime(GROUP_NAME, TOPIC, 1, 11L); brokerStatsManager.recordDiskFallBehindSize(GROUP_NAME, TOPIC, 1, 11L); brokerStatsManager.onTopicDeleted(TOPIC); Assert.assertNull(brokerStatsManager.getStatsItem(TOPIC_PUT_NUMS, TOPIC)); Assert.assertNull(brokerStatsManager.getStatsItem(TOPIC_PUT_SIZE, TOPIC)); Assert.assertNull(brokerStatsManager.getStatsItem(QUEUE_PUT_NUMS, TOPIC + "@" + QUEUE_ID)); Assert.assertNull(brokerStatsManager.getStatsItem(QUEUE_PUT_SIZE, TOPIC + "@" + QUEUE_ID)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_SIZE, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(QUEUE_GET_SIZE, TOPIC + "@" + QUEUE_ID + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(QUEUE_GET_NUMS, TOPIC + "@" + QUEUE_ID + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(SNDBCK_PUT_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_LATENCY, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_SIZE, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_TIME, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_CK_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_ACK_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(TOPIC_PUT_LATENCY, QUEUE_ID + "@" + TOPIC)); } @Test public void testOnGroupDeleted() { brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 100); brokerStatsManager.incQueueGetNums(GROUP_NAME, TOPIC, QUEUE_ID, 1); brokerStatsManager.incQueueGetSize(GROUP_NAME, TOPIC, QUEUE_ID, 100); brokerStatsManager.incSendBackNums(GROUP_NAME, TOPIC); brokerStatsManager.incGroupGetLatency(GROUP_NAME, TOPIC, 1, 1); brokerStatsManager.recordDiskFallBehindTime(GROUP_NAME, TOPIC, 1, 11L); brokerStatsManager.recordDiskFallBehindSize(GROUP_NAME, TOPIC, 1, 11L); brokerStatsManager.incGroupCkNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.incGroupAckNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.onGroupDeleted(GROUP_NAME); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_SIZE, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(QUEUE_GET_SIZE, TOPIC + "@" + QUEUE_ID + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(QUEUE_GET_NUMS, TOPIC + "@" + QUEUE_ID + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(SNDBCK_PUT_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_LATENCY, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_SIZE, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_TIME, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_CK_NUMS, TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_ACK_NUMS, TOPIC + "@" + GROUP_NAME)); } @Test public void testIncBrokerGetNumsWithoutSystemTopic() { brokerStatsManager.incBrokerGetNumsWithoutSystemTopic(TOPIC, 1); assertThat(brokerStatsManager.getStatsItem(BrokerStatsManager.BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC, CLUSTER_NAME) .getValue().doubleValue()).isEqualTo(1L); assertThat(brokerStatsManager.getBrokerGetNumsWithoutSystemTopic()).isEqualTo(1L); brokerStatsManager.incBrokerGetNumsWithoutSystemTopic(TopicValidator.RMQ_SYS_TRACE_TOPIC, 1); assertThat(brokerStatsManager.getStatsItem(BrokerStatsManager.BROKER_GET_NUMS_WITHOUT_SYSTEM_TOPIC, CLUSTER_NAME) .getValue().doubleValue()).isEqualTo(1L); assertThat(brokerStatsManager.getBrokerGetNumsWithoutSystemTopic()).isEqualTo(1L); } @Test public void testIncBrokerPutNumsWithoutSystemTopic() { brokerStatsManager.incBrokerPutNumsWithoutSystemTopic(TOPIC, 1); assertThat(brokerStatsManager.getStatsItem(BrokerStatsManager.BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC, CLUSTER_NAME) .getValue().doubleValue()).isEqualTo(1L); assertThat(brokerStatsManager.getBrokerPutNumsWithoutSystemTopic()).isEqualTo(1L); brokerStatsManager.incBrokerPutNumsWithoutSystemTopic(TopicValidator.RMQ_SYS_TRACE_TOPIC, 1); assertThat(brokerStatsManager.getStatsItem(BrokerStatsManager.BROKER_PUT_NUMS_WITHOUT_SYSTEM_TOPIC, CLUSTER_NAME) .getValue().doubleValue()).isEqualTo(1L); assertThat(brokerStatsManager.getBrokerPutNumsWithoutSystemTopic()).isEqualTo(1L); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import java.io.File; import java.util.UUID; public class StoreTestUtils { public static String createBaseDir() { String baseDir = System.getProperty("java.io.tmpdir") + File.separator + "unitteststore-" + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { System.exit(1); } return baseDir; } public static void deleteFile(String fileName) { deleteFile(new File(fileName)); } public static void deleteFile(File file) { if (!file.exists()) { return; } if (file.isFile()) { file.delete(); } else if (file.isDirectory()) { File[] files = file.listFiles(); for (File file1 : files) { deleteFile(file1); } file.delete(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/timer/TimerCheckPointTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import java.io.File; import java.io.IOException; import java.util.concurrent.atomic.AtomicLong; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class TimerCheckPointTest { private String baseDir; @Before public void init() throws IOException { baseDir = StoreTestUtils.createBaseDir(); } @Test public void testCheckPoint() throws IOException { String baseSrc = baseDir + File.separator + "timercheck"; TimerCheckpoint first = new TimerCheckpoint(baseSrc); assertEquals(0, first.getLastReadTimeMs()); assertEquals(0, first.getLastTimerLogFlushPos()); assertEquals(0, first.getLastTimerQueueOffset()); assertEquals(0, first.getMasterTimerQueueOffset()); first.setLastReadTimeMs(1000); first.setLastTimerLogFlushPos(1100); first.setLastTimerQueueOffset(1200); first.setMasterTimerQueueOffset(1300); first.shutdown(); TimerCheckpoint second = new TimerCheckpoint(baseSrc); assertEquals(1000, second.getLastReadTimeMs()); assertEquals(1100, second.getLastTimerLogFlushPos()); assertEquals(1200, second.getLastTimerQueueOffset()); assertEquals(1300, second.getMasterTimerQueueOffset()); } @Test public void testNewCheckPoint() throws IOException { String baseSrc = baseDir + File.separator + "timercheck2"; TimerCheckpoint first = new TimerCheckpoint(baseSrc); assertEquals(0, first.getLastReadTimeMs()); assertEquals(0, first.getLastTimerLogFlushPos()); assertEquals(0, first.getLastTimerQueueOffset()); assertEquals(0, first.getMasterTimerQueueOffset()); assertEquals(0, first.getDataVersion().getStateVersion()); assertEquals(0, first.getDataVersion().getCounter().get()); first.setLastReadTimeMs(1000); first.setLastTimerLogFlushPos(1100); first.setLastTimerQueueOffset(1200); first.setMasterTimerQueueOffset(1300); first.getDataVersion().setStateVersion(1400); first.getDataVersion().setTimestamp(1500); first.getDataVersion().setCounter(new AtomicLong(1600)); first.shutdown(); TimerCheckpoint second = new TimerCheckpoint(baseSrc); assertEquals(1000, second.getLastReadTimeMs()); assertEquals(1100, second.getLastTimerLogFlushPos()); assertEquals(1200, second.getLastTimerQueueOffset()); assertEquals(1300, second.getMasterTimerQueueOffset()); assertEquals(1400, second.getDataVersion().getStateVersion()); assertEquals(1500, second.getDataVersion().getTimestamp()); assertEquals(1600, second.getDataVersion().getCounter().get()); } @Test public void testEncodeDecode() throws IOException { TimerCheckpoint first = new TimerCheckpoint(); first.setLastReadTimeMs(1000); first.setLastTimerLogFlushPos(1100); first.setLastTimerQueueOffset(1200); first.setMasterTimerQueueOffset(1300); TimerCheckpoint second = TimerCheckpoint.decode(TimerCheckpoint.encode(first)); assertEquals(first.getLastReadTimeMs(), second.getLastReadTimeMs()); assertEquals(first.getLastTimerLogFlushPos(), second.getLastTimerLogFlushPos()); assertEquals(first.getLastTimerQueueOffset(), second.getLastTimerQueueOffset()); assertEquals(first.getMasterTimerQueueOffset(), second.getMasterTimerQueueOffset()); } @Test public void testNewEncodeDecode() throws IOException { TimerCheckpoint first = new TimerCheckpoint(); first.setLastReadTimeMs(1000); first.setLastTimerLogFlushPos(1100); first.setLastTimerQueueOffset(1200); first.setMasterTimerQueueOffset(1300); first.getDataVersion().setStateVersion(1400); first.getDataVersion().setTimestamp(1500); first.getDataVersion().setCounter(new AtomicLong(1600)); TimerCheckpoint second = TimerCheckpoint.decode(TimerCheckpoint.encode(first)); assertEquals(first.getLastReadTimeMs(), second.getLastReadTimeMs()); assertEquals(first.getLastTimerLogFlushPos(), second.getLastTimerLogFlushPos()); assertEquals(first.getLastTimerQueueOffset(), second.getLastTimerQueueOffset()); assertEquals(first.getMasterTimerQueueOffset(), second.getMasterTimerQueueOffset()); assertEquals(first.getDataVersion().getStateVersion(), 1400); assertEquals(first.getDataVersion().getTimestamp(), 1500); assertEquals(first.getDataVersion().getCounter().get(), 1600); } @After public void shutdown() { if (null != baseDir) { StoreTestUtils.deleteFile(baseDir); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/timer/TimerLogTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.junit.After; import org.junit.Test; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertArrayEquals; public class TimerLogTest { private final Set baseDirs = new HashSet<>(); private final List timerLogs = new ArrayList<>(); public TimerLog createTimerLog(String baseDir) { if (null == baseDir) { baseDir = StoreTestUtils.createBaseDir(); } TimerLog timerLog = new TimerLog(baseDir, 1024); timerLogs.add(timerLog); baseDirs.add(baseDir); timerLog.load(); return timerLog; } @Test public void testAppendRollSelectDelete() throws Exception { TimerLog timerLog = createTimerLog(null); ByteBuffer byteBuffer = ByteBuffer.allocate(TimerLog.UNIT_SIZE); byteBuffer.putInt(TimerLog.UNIT_SIZE); byteBuffer.putLong(Long.MAX_VALUE); byteBuffer.putInt(0); byteBuffer.putLong(Long.MAX_VALUE); byteBuffer.putInt(0); byteBuffer.putLong(1000); byteBuffer.putInt(10); byteBuffer.putInt(123); byteBuffer.putInt(0); long ret = -1; for (int i = 0; i < 10; i++) { ret = timerLog.append(byteBuffer.array(), 0, TimerLog.UNIT_SIZE); assertEquals(i * TimerLog.UNIT_SIZE, ret); } for (int i = 0; i < 100; i++) { timerLog.append(byteBuffer.array()); } assertEquals(6, timerLog.getMappedFileQueue().getMappedFiles().size()); SelectMappedBufferResult sbr = timerLog.getTimerMessage(ret); assertNotNull(sbr); assertEquals(TimerLog.UNIT_SIZE, sbr.getByteBuffer().getInt()); sbr.release(); SelectMappedBufferResult wholeSbr = timerLog.getWholeBuffer(ret); assertEquals(0, wholeSbr.getStartOffset()); wholeSbr.release(); timerLog.getMappedFileQueue().deleteExpiredFileByOffsetForTimerLog(1024, timerLog.getOffsetForLastUnit(), TimerLog.UNIT_SIZE); assertEquals(1, timerLog.getMappedFileQueue().getMappedFiles().size()); } @Test public void testRecovery() throws Exception { String basedir = StoreTestUtils.createBaseDir(); TimerLog first = createTimerLog(basedir); first.append(new byte[512]); first.append(new byte[510]); byte[] data = "Hello Recovery".getBytes(); first.append(data); first.shutdown(); TimerLog second = createTimerLog(basedir); assertEquals(2, second.getMappedFileQueue().getMappedFiles().size()); second.getMappedFileQueue().truncateDirtyFiles(1204 + 1000); SelectMappedBufferResult sbr = second.getTimerMessage(1024 + 510); byte[] expect = new byte[data.length]; sbr.getByteBuffer().get(expect); assertArrayEquals(expect, data); } @Test public void testAppendBlankByteBuffer() throws Exception { TimerLog timerLog = createTimerLog(null); ByteBuffer byteBuffer = ByteBuffer.allocate(TimerLog.UNIT_SIZE); byteBuffer.putInt(TimerLog.UNIT_SIZE); byteBuffer.putLong(Long.MAX_VALUE); byteBuffer.putInt(0); byteBuffer.putLong(Long.MAX_VALUE); byteBuffer.putInt(0); byteBuffer.putLong(1000); byteBuffer.putInt(10); byteBuffer.putInt(123); byteBuffer.putInt(0); int maxAppend = 1024 / TimerLog.UNIT_SIZE + 1; for (int i = 0; i < maxAppend; i++) { timerLog.append(byteBuffer.array(), 0, TimerLog.UNIT_SIZE); } SelectMappedBufferResult sbr = timerLog.getWholeBuffer(0); ByteBuffer bf = sbr.getByteBuffer(); for (int position = 0; position < sbr.getSize(); position += TimerLog.UNIT_SIZE) { bf.position(position); bf.getInt(); bf.getLong(); int magic = bf.getInt(); if (position / TimerLog.UNIT_SIZE == maxAppend - 1) { assertEquals(TimerLog.BLANK_MAGIC_CODE, magic); continue; } bf.getLong(); bf.getInt(); bf.getLong(); bf.getInt(); bf.getInt(); } } @After public void shutdown() { for (TimerLog timerLog : timerLogs) { timerLog.shutdown(); timerLog.getMappedFileQueue().destroy(); } for (String baseDir : baseDirs) { StoreTestUtils.deleteFile(baseDir); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.AppendMessageResult; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageArrivingListener; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.when; import static org.mockito.Mockito.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; public class TimerMessageStoreTest { private final byte[] msgBody = new byte[1024]; private static MessageStore messageStore; private MessageStore mockMessageStore; private SocketAddress bornHost; private SocketAddress storeHost; private final int precisionMs = 500; private final Set baseDirs = new HashSet<>(); private final List timerStores = new ArrayList<>(); private final AtomicInteger counter = new AtomicInteger(0); public static MessageStoreConfig storeConfig; @Before public void init() throws Exception { String baseDir = StoreTestUtils.createBaseDir(); baseDirs.add(baseDir); storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); storeConfig = new MessageStoreConfig(); storeConfig.setMappedFileSizeCommitLog(1024 * 1024 * 1024); storeConfig.setMappedFileSizeTimerLog(1024 * 1024 * 1024); storeConfig.setMappedFileSizeConsumeQueue(10240); storeConfig.setMaxHashSlotNum(10000); storeConfig.setMaxIndexNum(100 * 1000); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); storeConfig.setFlushDiskType(FlushDiskType.ASYNC_FLUSH); storeConfig.setTimerInterceptDelayLevel(true); storeConfig.setTimerPrecisionMs(precisionMs); storeConfig.setAppendTopicForTimerDeleteKey(false); // reset default value mockMessageStore = Mockito.mock(MessageStore.class); messageStore = new DefaultMessageStore(storeConfig, new BrokerStatsManager("TimerTest",false), new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); } public TimerMessageStore createTimerMessageStore(String rootDir , boolean needMock) throws IOException { if (null == rootDir) { rootDir = StoreTestUtils.createBaseDir(); } TimerCheckpoint timerCheckpoint = new TimerCheckpoint(rootDir + File.separator + "config" + File.separator + "timercheck"); TimerMetrics timerMetrics = new TimerMetrics(rootDir + File.separator + "config" + File.separator + "timermetrics"); MessageStore ms = needMock ? mockMessageStore : messageStore; TimerMessageStore timerMessageStore = new TimerMessageStore(ms, storeConfig, timerCheckpoint, timerMetrics, null); ms.setTimerMessageStore(timerMessageStore); baseDirs.add(rootDir); timerStores.add(timerMessageStore); return timerMessageStore; } private static PutMessageResult transformTimerMessage(TimerMessageStore timerMessageStore, MessageExtBrokerInner msg) { //do transform int delayLevel = msg.getDelayTimeLevel(); long deliverMs; try { if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) { deliverMs = System.currentTimeMillis() + Integer.parseInt(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)) * 1000; } else if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null) { deliverMs = System.currentTimeMillis() + Integer.parseInt(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS)); } else { deliverMs = Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS)); } } catch (Exception e) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null); } if (deliverMs > System.currentTimeMillis()) { if (delayLevel <= 0 && deliverMs - System.currentTimeMillis() > storeConfig.getTimerMaxDelaySec() * 1000L) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null); } int timerPrecisionMs = storeConfig.getTimerPrecisionMs(); if (deliverMs % timerPrecisionMs == 0) { deliverMs -= timerPrecisionMs; } else { deliverMs = deliverMs / timerPrecisionMs * timerPrecisionMs; } if (timerMessageStore.isReject(deliverMs)) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_FLOW_CONTROL, null); } MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TIMER_OUT_MS, deliverMs + ""); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic()); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId())); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); msg.setTopic(TimerMessageStore.TIMER_TOPIC); msg.setQueueId(0); } else if (null != msg.getProperty(MessageConst.PROPERTY_TIMER_DEL_UNIQKEY)) { return new PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null); } return null; } @Test public void testPutTimerMessage() throws Exception { Assume.assumeFalse(MixAll.isWindows()); String topic = "TimerTest_testPutTimerMessage"; final TimerMessageStore timerMessageStore = createTimerMessageStore(null , false); timerMessageStore.load(); timerMessageStore.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; long delayMs = curr + 3000; for (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { MessageExtBrokerInner inner = buildMessage((i % 2 == 0) ? 3000 : delayMs, topic + i, i % 2 == 0); transformTimerMessage(timerMessageStore,inner); PutMessageResult putMessageResult = messageStore.putMessage(inner); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } } // Wait until messages have been wrote to TimerLog but the slot (delayMs) hasn't expired. await().atMost(2000, TimeUnit.MILLISECONDS).until(new Callable() { @Override public Boolean call() { return timerMessageStore.getCommitQueueOffset() == 10 * 5; } }); for (int i = 0; i < 10; i++) { Assert.assertEquals(5, timerMessageStore.getTimerMetrics().getTimingCount(topic + i)); } for (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { ByteBuffer msgBuff = getOneMessage(topic + i, 0, j, 4000); assertNotNull(msgBuff); MessageExt msgExt = MessageDecoder.decode(msgBuff); assertNotNull(msgExt); assertEquals(topic + i, msgExt.getTopic()); // assertThat(System.currentTimeMillis()).isLessThan(delayMs + precisionMs * 2); } } for (int i = 0; i < 10; i++) { Assert.assertEquals(0, timerMessageStore.getTimerMetrics().getTimingCount(topic + i)); } } @Test public void testRetryUntilSuccess() throws Exception { storeConfig.setTimerEnableRetryUntilSuccess(true); TimerMessageStore timerMessageStore = createTimerMessageStore(null , true); timerMessageStore.load(); timerMessageStore.setShouldRunningDequeue(true); Field stateField = TimerMessageStore.class.getDeclaredField("state"); stateField.setAccessible(true); stateField.set(timerMessageStore, TimerMessageStore.RUNNING); MessageExtBrokerInner msg = buildMessage(3000L, "TestRetry", true); transformTimerMessage(timerMessageStore, msg); TimerRequest timerRequest = new TimerRequest(100, 200, 3000, System.currentTimeMillis(), 0, msg); boolean offered = timerMessageStore.dequeuePutQueue.offer(timerRequest); assertTrue(offered); assertFalse(timerMessageStore.dequeuePutQueue.isEmpty()); // If enableRetryUntilSuccess is set and putMessage return NEED_RETRY type, the message should be retried until success. when(mockMessageStore.putMessage(any(MessageExtBrokerInner.class))) .thenReturn(new PutMessageResult(PutMessageStatus.FLUSH_DISK_TIMEOUT, null)) .thenReturn(new PutMessageResult(PutMessageStatus.FLUSH_SLAVE_TIMEOUT, null)) .thenReturn(new PutMessageResult(PutMessageStatus.OS_PAGE_CACHE_BUSY, null)) .thenReturn(new PutMessageResult(PutMessageStatus.OS_PAGE_CACHE_BUSY, null)) .thenReturn(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, null)) .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); final CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { try { timerMessageStore.getDequeuePutMessageServices()[0].run(); } finally { latch.countDown(); } }).start(); latch.await(5, TimeUnit.SECONDS); assertTrue(timerMessageStore.dequeuePutQueue.isEmpty()); verify(mockMessageStore, times(6)).putMessage(any(MessageExtBrokerInner.class)); } @Test public void testTimerFlowControl() throws Exception { String topic = "TimerTest_testTimerFlowControl"; storeConfig.setTimerCongestNumEachSlot(100); TimerMessageStore timerMessageStore = createTimerMessageStore(null , false); timerMessageStore.load(); timerMessageStore.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; // Make sure delayMs won't be over. long delayMs = curr + 100000; int passFlowControlNum = 0; for (int i = 0; i < 500; i++) { MessageExtBrokerInner inner = buildMessage(delayMs, topic, false); PutMessageResult putMessageResult = transformTimerMessage(timerMessageStore,inner); if (putMessageResult == null || !putMessageResult.getPutMessageStatus().equals(PutMessageStatus.WHEEL_TIMER_FLOW_CONTROL)) { putMessageResult = messageStore.putMessage(inner); } else { putMessageResult = new PutMessageResult(PutMessageStatus.WHEEL_TIMER_FLOW_CONTROL,null); } // Message with delayMs in getSlotIndex(delayMs - precisionMs). long congestNum = timerMessageStore.getCongestNum(delayMs - precisionMs); assertTrue(congestNum <= 220); if (congestNum < 100) { assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } else { Assert.assertTrue(PutMessageStatus.PUT_OK == putMessageResult.getPutMessageStatus() || PutMessageStatus.WHEEL_TIMER_FLOW_CONTROL == putMessageResult.getPutMessageStatus()); if (PutMessageStatus.PUT_OK == putMessageResult.getPutMessageStatus()) { passFlowControlNum++; } } //wait reput Thread.sleep(5); } assertThat(passFlowControlNum).isGreaterThan(0).isLessThan(120); } @Test public void testPutExpiredTimerMessage() throws Exception { // Skip on Mac to make CI pass Assume.assumeFalse(MixAll.isMac()); Assume.assumeFalse(MixAll.isWindows()); String topic = "TimerTest_testPutExpiredTimerMessage"; TimerMessageStore timerMessageStore = createTimerMessageStore(null ,false); timerMessageStore.load(); timerMessageStore.start(true); long delayMs = System.currentTimeMillis() - 2 * precisionMs; for (int i = 0; i < 10; i++) { MessageExtBrokerInner inner = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore,inner); PutMessageResult putMessageResult = messageStore.putMessage(inner); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } long curr = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { ByteBuffer msgBuff = getOneMessage(topic, 0, i, 1000); assertNotNull(msgBuff); assertTrue(System.currentTimeMillis() - curr < 200); } } @Test public void testDeleteTimerMessage() throws Exception { String topic = "TimerTest_testDeleteTimerMessage"; TimerMessageStore timerMessageStore = createTimerMessageStore(null ,false); timerMessageStore.load(); timerMessageStore.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; long delayMs = curr + 1000; String uniqKey = null; for (int i = 0; i < 5; i++) { MessageExtBrokerInner inner = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore,inner); if (null == uniqKey) { uniqKey = MessageClientIDSetter.getUniqID(inner); } assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(inner).getPutMessageStatus()); } MessageExtBrokerInner delMsg = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore,delMsg); MessageAccessor.putProperty(delMsg, TimerMessageStore.TIMER_DELETE_UNIQUE_KEY, TimerMessageStore.buildDeleteKey(topic, uniqKey, false)); delMsg.setPropertiesString(MessageDecoder.messageProperties2String(delMsg.getProperties())); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(delMsg).getPutMessageStatus()); // The first one should have been deleted. ByteBuffer msgBuff = getOneMessage(topic, 0, 0, 3000); assertNotNull(msgBuff); MessageExt msgExt = MessageDecoder.decode(msgBuff); assertNotNull(msgExt); assertNotEquals(uniqKey, MessageClientIDSetter.getUniqID(msgExt)); // The last one should be null. assertNull(getOneMessage(topic, 0, 4, 500)); } @Test public void testDeleteTimerMessage_ukCollision() throws Exception { storeConfig.setAppendTopicForTimerDeleteKey(true); // append topic as namespace String topic = "TimerTest_testDeleteTimerMessage"; String collisionTopic = "TimerTest_testDeleteTimerMessage_collision"; TimerMessageStore timerMessageStore = createTimerMessageStore(null , false); timerMessageStore.load(); timerMessageStore.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; long delayMs = curr + 1000; MessageExtBrokerInner inner = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore, inner); String firstUniqKey = MessageClientIDSetter.getUniqID(inner); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(inner).getPutMessageStatus()); inner = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore, inner); String secondUniqKey = MessageClientIDSetter.getUniqID(inner); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(inner).getPutMessageStatus()); MessageExtBrokerInner delMsg = buildMessage(delayMs, "whatever", false); transformTimerMessage(timerMessageStore, delMsg); MessageAccessor.putProperty(delMsg, TimerMessageStore.TIMER_DELETE_UNIQUE_KEY, TimerMessageStore.buildDeleteKey(topic, firstUniqKey, true)); delMsg.setPropertiesString(MessageDecoder.messageProperties2String(delMsg.getProperties())); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(delMsg).getPutMessageStatus()); delMsg = buildMessage(delayMs, "whatever", false); transformTimerMessage(timerMessageStore, delMsg); MessageAccessor.putProperty(delMsg, TimerMessageStore.TIMER_DELETE_UNIQUE_KEY, TimerMessageStore.buildDeleteKey(collisionTopic, secondUniqKey, true)); delMsg.setPropertiesString(MessageDecoder.messageProperties2String(delMsg.getProperties())); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(delMsg).getPutMessageStatus()); // The first one should have been deleted, the second one should not be deleted. ByteBuffer msgBuff = getOneMessage(topic, 0, 0, 3000); assertNotNull(msgBuff); MessageExt msgExt = MessageDecoder.decode(msgBuff); assertNotNull(msgExt); assertNotEquals(firstUniqKey, MessageClientIDSetter.getUniqID(msgExt)); assertEquals(secondUniqKey, MessageClientIDSetter.getUniqID(msgExt)); } @Test public void testPutDeleteTimerMessage() throws Exception { String topic = "TimerTest_testPutDeleteTimerMessage"; final TimerMessageStore timerMessageStore = createTimerMessageStore(null , false); timerMessageStore.load(); timerMessageStore.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; final long delayMs = curr + 1000; for (int i = 0; i < 5; i++) { MessageExtBrokerInner inner = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore,inner); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(inner).getPutMessageStatus()); } MessageExtBrokerInner delMsg = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore,delMsg); MessageAccessor.putProperty(delMsg, TimerMessageStore.TIMER_DELETE_UNIQUE_KEY, "XXX"); delMsg.setPropertiesString(MessageDecoder.messageProperties2String(delMsg.getProperties())); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(delMsg).getPutMessageStatus()); // Wait until currReadTimeMs catches up current time and delayMs is over. await().atMost(5000, TimeUnit.MILLISECONDS).until(new Callable() { @Override public Boolean call() { long curr = System.currentTimeMillis() / precisionMs * precisionMs; return curr >= delayMs && (timerMessageStore.getCurrReadTimeMs() == curr || timerMessageStore.getCurrReadTimeMs() == curr + precisionMs); } }); for (int i = 0; i < 5; i++) { ByteBuffer msgBuff = getOneMessage(topic, 0, i, 1000); assertNotNull(msgBuff); // assertThat(System.currentTimeMillis()).isLessThan(delayMs + precisionMs); } assertNull(getOneMessage(topic, 0, 5, 1000)); // Test put expired delete msg. MessageExtBrokerInner expiredInner = buildMessage(System.currentTimeMillis() - 100, topic, false); MessageAccessor.putProperty(expiredInner, TimerMessageStore.TIMER_DELETE_UNIQUE_KEY, "XXX"); PutMessageResult putMessageResult = transformTimerMessage(timerMessageStore,expiredInner); assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, putMessageResult.getPutMessageStatus()); } @Test public void testStateAndRecover() throws Exception { final String topic = "TimerTest_testStateAndRecover"; String base = StoreTestUtils.createBaseDir(); final TimerMessageStore first = createTimerMessageStore(base , false); first.load(); first.start(true); final int msgNum = 250; long curr = System.currentTimeMillis() / precisionMs * precisionMs; final long delayMs = curr + 5000; for (int i = 0; i < msgNum; i++) { MessageExtBrokerInner inner = buildMessage((i % 2 == 0) ? 5000 : delayMs, topic, i % 2 == 0); transformTimerMessage(first,inner); PutMessageResult putMessageResult = messageStore.putMessage(inner); long cqOffset = first.getCommitQueueOffset(); assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); } // Wait until messages have written to TimerLog and currReadTimeMs catches up current time. await().atMost(5000, TimeUnit.MILLISECONDS).until(new Callable() { @Override public Boolean call() { long curr = System.currentTimeMillis() / precisionMs * precisionMs; long cqOffset = first.getCommitQueueOffset(); return first.getCommitQueueOffset() == msgNum && (first.getCurrReadTimeMs() == curr || first.getCurrReadTimeMs() == curr + precisionMs); } }); assertThat(first.getTimerLog().getMappedFileQueue().getMappedFiles().size()) .isGreaterThanOrEqualTo(msgNum / (storeConfig.getMappedFileSizeTimerLog() / TimerLog.UNIT_SIZE)); assertThat(first.getQueueOffset()).isEqualTo(msgNum); assertThat(first.getCommitQueueOffset()).isEqualTo(first.getQueueOffset()); assertThat(first.getCommitReadTimeMs()).isEqualTo(first.getCurrReadTimeMs()); curr = System.currentTimeMillis() / precisionMs * precisionMs; assertThat(first.getCurrReadTimeMs()).isLessThanOrEqualTo(curr + precisionMs); for (int i = 0; i <= first.getTimerLog().getMappedFileQueue().getMappedFiles().size() + 10; i++) { first.getTimerLog().getMappedFileQueue().flush(0); Thread.sleep(10); } // Damage the timer wheel, trigger the check physical pos. Slot slot = first.getTimerWheel().getSlot(delayMs - precisionMs); assertNotEquals(-1, slot.timeMs); first.getTimerWheel().putSlot(slot.timeMs, -1, Long.MAX_VALUE, slot.num, slot.magic); first.getTimerWheel().flush(); first.shutdown(); final TimerMessageStore second = createTimerMessageStore(base , false); second.debug = true; assertTrue(second.load()); assertEquals(msgNum, second.getQueueOffset()); assertEquals(second.getCommitQueueOffset(), second.getQueueOffset()); assertEquals(second.getCurrReadTimeMs(), second.getCommitReadTimeMs()); assertEquals(first.getCommitReadTimeMs(), second.getCommitReadTimeMs()); second.start(true); // Wait until all messages have been written back to commitLog and consumeQueue. await().atMost(30000, TimeUnit.MILLISECONDS).until(new Callable() { @Override public Boolean call() { ConsumeQueue cq = (ConsumeQueue) messageStore.getConsumeQueue(topic, 0); return cq != null && cq.getMaxOffsetInQueue() >= msgNum - 1; } }); for (int i = 0; i < msgNum; i++) { ByteBuffer msgBuff = getOneMessage(topic, 0, i, 2000); assertThat(msgBuff).isNotNull(); } second.shutdown(); } @Test public void testMaxDelaySec() throws Exception { String topic = "TimerTest_testMaxDelaySec"; TimerMessageStore first = createTimerMessageStore(null , false); first.load(); first.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; long delaySec = storeConfig.getTimerMaxDelaySec() + 20; MessageExtBrokerInner absolute = buildMessage(curr + delaySec * 1000, topic, false); PutMessageResult putMessageResult = transformTimerMessage(first,absolute); assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, putMessageResult.getPutMessageStatus()); MessageExtBrokerInner relative = buildMessage(delaySec * 1000, topic, true); putMessageResult = transformTimerMessage(first,relative); assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, putMessageResult.getPutMessageStatus()); } @Test public void testRollMessage() throws Exception { storeConfig.setTimerRollWindowSlot(2); String topic = "TimerTest_testRollMessage"; TimerMessageStore timerMessageStore = createTimerMessageStore(null , false); timerMessageStore.load(); timerMessageStore.start(true); long curr = System.currentTimeMillis() / precisionMs * precisionMs; long delayMs = curr + 4 * precisionMs; MessageExtBrokerInner inner = buildMessage(delayMs, topic, false); transformTimerMessage(timerMessageStore,inner); assertEquals(PutMessageStatus.PUT_OK, messageStore.putMessage(inner).getPutMessageStatus()); ByteBuffer msgBuff = getOneMessage(topic, 0, 0, 5000); assertNotNull(msgBuff); MessageExt msgExt = MessageDecoder.decode(msgBuff); assertNotNull(msgExt); assertEquals(1, Integer.valueOf(msgExt.getProperty(MessageConst.PROPERTY_TIMER_ROLL_TIMES)).intValue()); storeConfig.setTimerRollWindowSlot(Integer.MAX_VALUE); } public ByteBuffer getOneMessage(String topic, int queue, long offset, int timeout) throws Exception { int retry = timeout / 100; while (retry-- > 0) { GetMessageResult getMessageResult = messageStore.getMessage("TimerGroup", topic, queue, offset, 1, null); if (null != getMessageResult && GetMessageStatus.FOUND == getMessageResult.getStatus()) { return getMessageResult.getMessageBufferList().get(0); } Thread.sleep(100); } return null; } public MessageExtBrokerInner buildMessage(long delayedMs, String topic, boolean relative) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setQueueId(0); msg.setTags(counter.incrementAndGet() + ""); msg.setKeys("timer"); if (relative) { MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TIMER_DELAY_SEC, delayedMs / 1000 + ""); } else { MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TIMER_DELIVER_MS, delayedMs + ""); } msg.setBody(msgBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setBornHost(bornHost); msg.setStoreHost(storeHost); MessageClientIDSetter.setUniqID(msg); TopicFilterType topicFilterType = MessageExt.parseTopicFilterType(msg.getSysFlag()); long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msg.getTags()); msg.setTagsCode(tagsCodeValue); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } private class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } @After public void clear() { for (TimerMessageStore store : timerStores) { store.shutdown(); } for (String baseDir : baseDirs) { StoreTestUtils.deleteFile(baseDir); } if (null != messageStore) { messageStore.shutdown(); messageStore.destroy(); } } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import org.apache.rocketmq.common.message.MessageAccessor; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.List; public class TimerMetricsTest { @Test public void testTimingCount() { String baseDir = StoreTestUtils.createBaseDir(); TimerMetrics first = new TimerMetrics(baseDir); Assert.assertTrue(first.load()); MessageExt msg = new MessageExt(); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, "AAA"); first.addAndGet(msg, 1000); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, "BBB"); first.addAndGet(msg, 2000); Assert.assertEquals(1000, first.getTimingCount("AAA")); Assert.assertEquals(2000, first.getTimingCount("BBB")); long curr = System.currentTimeMillis(); Assert.assertTrue(first.getTopicPair("AAA").getTimeStamp() > curr - 10); Assert.assertTrue(first.getTopicPair("AAA").getTimeStamp() <= curr); first.persist(); TimerMetrics second = new TimerMetrics(baseDir); Assert.assertTrue(second.load()); Assert.assertEquals(1000, second.getTimingCount("AAA")); Assert.assertEquals(2000, second.getTimingCount("BBB")); Assert.assertTrue(second.getTopicPair("BBB").getTimeStamp() > curr - 100); Assert.assertTrue(second.getTopicPair("BBB").getTimeStamp() <= curr); second.persist(); StoreTestUtils.deleteFile(baseDir); } @Test @SuppressWarnings("DoubleBraceInitialization") public void testTimingDistribution() { String baseDir = StoreTestUtils.createBaseDir(); TimerMetrics first = new TimerMetrics(baseDir); List timerDist = new ArrayList() {{ add(5); add(60); add(300); // 5s, 1min, 5min add(900); add(3600); add(14400); // 15min, 1h, 4h add(28800); add(86400); // 8h, 24h }}; for (int period : timerDist) { first.updateDistPair(period, period); } int temp = 0; for (int j = 0; j < 50; j++) { for (int period : timerDist) { Assert.assertEquals(first.getDistPair(period).getCount().get(),period + temp); first.updateDistPair(period, j); } temp += j; } StoreTestUtils.deleteFile(baseDir); } } ================================================ FILE: store/src/test/java/org/apache/rocketmq/store/timer/TimerWheelTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.store.timer; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import static org.junit.Assert.assertEquals; public class TimerWheelTest { private String baseDir; private final int slotsTotal = 30; private final int precisionMs = 500; private TimerWheel timerWheel; private final long defaultDelay = System.currentTimeMillis() / precisionMs * precisionMs; @Before public void init() throws IOException { baseDir = StoreTestUtils.createBaseDir(); timerWheel = new TimerWheel(baseDir, slotsTotal, precisionMs); } @Test public void testPutGet() { long delayedTime = defaultDelay + precisionMs; Slot first = timerWheel.getSlot(delayedTime); assertEquals(-1, first.timeMs); assertEquals(-1, first.firstPos); assertEquals(-1, first.lastPos); timerWheel.putSlot(delayedTime, 1, 2, 3, 4); Slot second = timerWheel.getSlot(delayedTime); assertEquals(delayedTime, second.timeMs); assertEquals(1, second.firstPos); assertEquals(2, second.lastPos); assertEquals(3, second.num); assertEquals(4, second.magic); } @Test public void testGetNum() { long delayedTime = defaultDelay + precisionMs; timerWheel.putSlot(delayedTime, 1, 2, 3, 4); assertEquals(3, timerWheel.getNum(delayedTime)); assertEquals(3, timerWheel.getAllNum(delayedTime)); timerWheel.putSlot(delayedTime + 5 * precisionMs, 5, 6, 7, 8); assertEquals(7, timerWheel.getNum(delayedTime + 5 * precisionMs)); assertEquals(10, timerWheel.getAllNum(delayedTime)); } @Test public void testCheckPhyPos() { long delayedTime = defaultDelay + precisionMs; timerWheel.putSlot(delayedTime, 1, 100, 1, 0); timerWheel.putSlot(delayedTime + 5 * precisionMs, 2, 200, 2, 0); timerWheel.putSlot(delayedTime + 10 * precisionMs, 3, 300, 3, 0); assertEquals(1, timerWheel.checkPhyPos(delayedTime, 50)); assertEquals(2, timerWheel.checkPhyPos(delayedTime, 100)); assertEquals(3, timerWheel.checkPhyPos(delayedTime, 200)); assertEquals(Long.MAX_VALUE, timerWheel.checkPhyPos(delayedTime, 300)); assertEquals(Long.MAX_VALUE, timerWheel.checkPhyPos(delayedTime, 400)); assertEquals(2, timerWheel.checkPhyPos(delayedTime + 5 * precisionMs, 50)); assertEquals(2, timerWheel.checkPhyPos(delayedTime + 5 * precisionMs, 100)); assertEquals(3, timerWheel.checkPhyPos(delayedTime + 5 * precisionMs, 200)); assertEquals(Long.MAX_VALUE, timerWheel.checkPhyPos(delayedTime + 5 * precisionMs, 300)); assertEquals(Long.MAX_VALUE, timerWheel.checkPhyPos(delayedTime + 5 * precisionMs, 400)); } @Test public void testPutRevise() { long delayedTime = System.currentTimeMillis() / precisionMs * precisionMs + 3 * precisionMs; timerWheel.putSlot(delayedTime, 1, 2); timerWheel.reviseSlot(delayedTime + 5 * precisionMs, 3, 4, false); Slot second = timerWheel.getSlot(delayedTime); assertEquals(delayedTime, second.timeMs); assertEquals(1, second.firstPos); assertEquals(2, second.lastPos); timerWheel.reviseSlot(delayedTime, TimerWheel.IGNORE, 4, false); Slot three = timerWheel.getSlot(delayedTime); assertEquals(1, three.firstPos); assertEquals(4, three.lastPos); timerWheel.reviseSlot(delayedTime, 3, TimerWheel.IGNORE, false); Slot four = timerWheel.getSlot(delayedTime); assertEquals(3, four.firstPos); assertEquals(4, four.lastPos); timerWheel.reviseSlot(delayedTime + 2 * slotsTotal * precisionMs, TimerWheel.IGNORE, 5, true); Slot five = timerWheel.getRawSlot(delayedTime); assertEquals(delayedTime + 2 * slotsTotal * precisionMs, five.timeMs); assertEquals(5, five.firstPos); assertEquals(5, five.lastPos); } @Test public void testRecoveryData() throws Exception { long delayedTime = System.currentTimeMillis() / precisionMs * precisionMs + 5 * precisionMs; timerWheel.putSlot(delayedTime, 1, 2, 3, 4); timerWheel.flush(); TimerWheel tmpWheel = new TimerWheel(baseDir, slotsTotal, precisionMs); Slot slot = tmpWheel.getSlot(delayedTime); assertEquals(delayedTime, slot.timeMs); assertEquals(1, slot.firstPos); assertEquals(2, slot.lastPos); assertEquals(3, slot.num); assertEquals(4, slot.magic); tmpWheel.shutdown(); } @Test(expected = RuntimeException.class) public void testRecoveryFixedTTL() throws Exception { timerWheel.flush(); TimerWheel tmpWheel = new TimerWheel(baseDir, slotsTotal + 1, precisionMs); } @After public void shutdown() { if (null != timerWheel) { timerWheel.shutdown(); } if (null != baseDir) { StoreTestUtils.deleteFile(baseDir); } } } ================================================ FILE: store/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: style/copyright/Apache.xml ================================================ ================================================ FILE: style/copyright/profiles_settings.xml ================================================ ================================================ FILE: style/rmq_checkstyle.xml ================================================ ================================================ FILE: style/rmq_codeStyle.xml ================================================ ================================================ FILE: style/spotbugs-suppressions.xml ================================================ ================================================ FILE: test/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "test", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//client", "//common", "//remoting", "//srvutil", "//tools", "@maven//:ch_qos_logback_logback_classic", "@maven//:ch_qos_logback_logback_core", "@maven//:com_github_luben_zstd_jni", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_truth_truth", "@maven//:commons_cli_commons_cli", "@maven//:commons_validator_commons_validator", "@maven//:io_netty_netty_all", "@maven//:org_apache_tomcat_annotations_api", "@maven//:org_apache_commons_commons_lang3", "@maven//:org_awaitility_awaitility", "@maven//:org_lz4_lz4_java", "@maven//:org_reflections_reflections", "@maven//:org_slf4j_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), resources = [ "src/test/resources/rmq-proxy-home/conf/broker.conf", "src/test/resources/rmq-proxy-home/conf/logback_proxy.xml", "src/test/resources/rmq-proxy-home/conf/rmq-proxy.json", "src/test/resources/rmq.logback-test.xml", ] + glob(["src/test/resources/schema/**/*.schema"]), visibility = ["//visibility:public"], deps = [ ":test", "//:test_deps", "//broker", "//client", "//common", "//container", "//controller", "//namesrv", "//proxy", "//remoting", "//store", "//tools", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven//:com_google_protobuf_protobuf_java_util", "@maven//:com_google_truth_truth", "@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_context", "@maven//:io_grpc_grpc_netty_shaded", "@maven//:io_grpc_grpc_stub", "@maven//:io_grpc_grpc_testing", "@maven//:io_netty_netty_all", "@maven//:org_apache_commons_commons_lang3", "@maven//:org_apache_rocketmq_rocketmq_proto", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", ], ) GenTestRules( name = "GeneratedTestRules", default_test_size = "medium", exclude_tests = [ "src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT", # Following tests are found flaky "src/test/java/org/apache/rocketmq/test/statictopic/StaticTopicIT", "src/test/java/org/apache/rocketmq/test/client/consumer/topic/MulConsumerMulTopicIT", "src/test/java/org/apache/rocketmq/test/client/consumer/tag/MulTagSubIT", "src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWith1ConsumerIT", "src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithMulConsumerIT", "src/test/java/org/apache/rocketmq/test/client/consumer/topic/OneConsumerMulTopicIT", "src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgDynamicRebalanceIT", "src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgIT", "src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgWithTagIT", "src/test/java/org/apache/rocketmq/test/client/consumer/cluster/DynamicAddAndCrashIT", "src/test/java/org/apache/rocketmq/test/client/consumer/cluster/DynamicAddConsumerIT", "src/test/java/org/apache/rocketmq/test/client/consumer/cluster/DynamicCrashConsumerIT", "src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithSameGroupConsumerIT", "src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendWithMessageQueueIT", "src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendWithMessageQueueSelectorIT", "src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendWithOnlySendCallBackIT", "src/test/java/org/apache/rocketmq/test/client/producer/exception/msg/MessageUserPropIT", "src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendIT", "src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendWithMQIT", "src/test/java/org/apache/rocketmq/test/offset/OffsetNotFoundIT", "src/test/java/org/apache/rocketmq/test/recall/RecallWithTraceIT", "src/test/java/org/apache/rocketmq/test/recall/SendAndRecallDelayMessageIT", "src/test/java/org/apache/rocketmq/test/client/producer/querymsg/QueryMsgByIdIT", "src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendWithSelectorIT", "src/test/java/org/apache/rocketmq/test/smoke/NormalMessageSendAndRecvIT", "src/test/java/org/apache/rocketmq/test/base/dledger/DLedgerProduceAndConsumeIT", "src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopOrderlyIT", "src/test/java/org/apache/rocketmq/test/delay/NormalMsgDelayIT", "src/test/java/org/apache/rocketmq/test/client/producer/querymsg/QueryMsgByIdExceptionIT", "src/test/java/org/apache/rocketmq/test/offset/LagCalculationIT", "src/test/java/org/apache/rocketmq/test/grpc/v2/ClusterGrpcIT", ], flaky_tests = [ "src/test/java/org/apache/rocketmq/test/client/producer/querymsg/QueryMsgByKeyIT", ], test_files = glob(["src/test/java/**/*IT.java"]), deps = [ ":tests", ], ) ================================================ FILE: test/pom.xml ================================================ rocketmq-all org.apache.rocketmq ${revision} 4.0.0 rocketmq-test rocketmq-test ${project.version} ${basedir}/.. ${project.groupId} rocketmq-proto com.google.protobuf protobuf-java-util ${project.groupId} rocketmq-proxy ${project.groupId} rocketmq-broker ${project.groupId} rocketmq-namesrv ${project.groupId} rocketmq-container org.apache.tomcat annotations-api com.google.truth truth junit junit ${project.groupId} rocketmq-client ${project.groupId} rocketmq-tools io.grpc grpc-testing test org.awaitility awaitility org.reflections reflections io.github.aliyunmq rocketmq-slf4j-api io.github.aliyunmq rocketmq-logback-classic org.slf4j slf4j-api test org.apache.maven.plugins maven-jar-plugin test-jar ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/mq/MQAsyncProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.mq; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.clientinterface.AbstractMQProducer; import org.apache.rocketmq.test.util.TestUtil; public class MQAsyncProducer { private static Logger logger = LoggerFactory.getLogger(MQAsyncProducer.class); private AbstractMQProducer producer = null; private long msgNum; private int intervalMills; private Thread sendT; private AtomicBoolean bPause = new AtomicBoolean(false); public MQAsyncProducer(final AbstractMQProducer producer, final long msgNum, final int intervalMills) { this.producer = producer; this.msgNum = msgNum; this.intervalMills = intervalMills; sendT = new Thread(new Runnable() { public void run() { for (int i = 0; i < msgNum; i++) { if (!bPause.get()) { producer.send(); TestUtil.waitForMonment(intervalMills); } else { while (true) { if (bPause.get()) { TestUtil.waitForMonment(10); } else break; } } } } }); } public void start() { sendT.start(); } public void waitSendAll(int waitMills) { long startTime = System.currentTimeMillis(); while ((producer.getAllMsgBody().size() + producer.getSendErrorMsg().size()) < msgNum) { if (System.currentTimeMillis() - startTime < waitMills) { TestUtil.waitForMonment(200); } else { logger.error(String.format("time elapse:%s, but the message sending has not finished", System.currentTimeMillis() - startTime)); break; } } } public void pauseProducer() { bPause.set(true); } public void notifyProducer() { bPause.set(false); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQAsyncSendProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.clientinterface.AbstractMQProducer; import org.apache.rocketmq.test.sendresult.ResultWrapper; import org.apache.rocketmq.test.util.RandomUtil; import org.apache.rocketmq.test.util.TestUtil; public class RMQAsyncSendProducer extends AbstractMQProducer { private static Logger logger = LoggerFactory .getLogger(RMQAsyncSendProducer.class); private String nsAddr = null; private DefaultMQProducer producer = null; private SendCallback sendCallback = null; private List successSendResult = Collections.synchronizedList(new ArrayList()); private AtomicInteger exceptionMsgCount = new AtomicInteger(0); private int msgSize = 0; public RMQAsyncSendProducer(String nsAddr, String topic) { super(topic); this.nsAddr = nsAddr; sendCallback = new SendCallback() { @Override public void onSuccess(SendResult sendResult) { successSendResult.add(sendResult); } @Override public void onException(Throwable throwable) { exceptionMsgCount.getAndIncrement(); } }; create(); start(); } public int getSuccessMsgCount() { return successSendResult.size(); } public List getSuccessSendResult() { return successSendResult; } public int getExceptionMsgCount() { return exceptionMsgCount.get(); } private void create() { producer = new DefaultMQProducer(); producer.setProducerGroup(RandomUtil.getStringByUUID()); producer.setInstanceName(RandomUtil.getStringByUUID()); if (nsAddr != null) { producer.setNamesrvAddr(nsAddr); } } private void start() { try { producer.start(); } catch (MQClientException e) { logger.error("producer start failed!"); e.printStackTrace(); } } @Override public ResultWrapper send(Object msg, Object arg) { return null; } @Override public void shutdown() { producer.shutdown(); } public void asyncSend(Object msg) { Message metaqMsg = (Message) msg; try { producer.send(metaqMsg, sendCallback); msgBodys.addData(new String(metaqMsg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } catch (Exception e) { e.printStackTrace(); } } public void asyncSend(int msgSize) { this.msgSize = msgSize; for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); this.asyncSend(msg); } } public void asyncSend(Object msg, MessageQueueSelector selector, Object arg) { Message metaqMsg = (Message) msg; try { producer.send(metaqMsg, selector, arg, sendCallback); msgBodys.addData(new String(metaqMsg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } catch (Exception e) { e.printStackTrace(); } } public void asyncSend(int msgSize, MessageQueueSelector selector) { this.msgSize = msgSize; for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); this.asyncSend(msg, selector, i); } } public void asyncSend(Object msg, MessageQueue mq) { Message metaqMsg = (Message) msg; try { producer.send(metaqMsg, mq, sendCallback); msgBodys.addData(new String(metaqMsg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } catch (Exception e) { e.printStackTrace(); } } public void asyncSend(int msgSize, MessageQueue mq) { this.msgSize = msgSize; for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); this.asyncSend(msg, mq); } } public void waitForResponse(int timeoutMills) { long startTime = System.currentTimeMillis(); while (this.successSendResult.size() != this.msgSize) { if (System.currentTimeMillis() - startTime < timeoutMills) { TestUtil.waitForMonment(100); } else { logger.info("timeout but still not recv all response!"); break; } } } public void sendOneWay(Object msg) { Message metaqMsg = (Message) msg; try { producer.sendOneway(metaqMsg); msgBodys.addData(new String(metaqMsg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } catch (Exception e) { e.printStackTrace(); } } public void sendOneWay(int msgSize) { for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); this.sendOneWay(msg); } } public void sendOneWay(Object msg, MessageQueue mq) { Message metaqMsg = (Message) msg; try { producer.sendOneway(metaqMsg, mq); msgBodys.addData(new String(metaqMsg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } catch (Exception e) { e.printStackTrace(); } } public void sendOneWay(int msgSize, MessageQueue mq) { for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); this.sendOneWay(msg, mq); } } public void sendOneWay(Object msg, MessageQueueSelector selector, Object arg) { Message metaqMsg = (Message) msg; try { producer.sendOneway(metaqMsg, selector, arg); msgBodys.addData(new String(metaqMsg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } catch (Exception e) { e.printStackTrace(); } } public void sendOneWay(int msgSize, MessageQueueSelector selector) { for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); this.sendOneWay(msg, selector, i); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQBroadCastConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.listener.AbstractListener; public class RMQBroadCastConsumer extends RMQNormalConsumer { private static Logger logger = LoggerFactory.getLogger(RMQBroadCastConsumer.class); public RMQBroadCastConsumer(String nsAddr, String topic, String subExpression, String consumerGroup, AbstractListener listener) { super(nsAddr, topic, subExpression, consumerGroup, listener); } @Override public void create() { super.create(); consumer.setMessageModel(MessageModel.BROADCASTING); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQNormalConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.clientinterface.AbstractMQConsumer; import org.apache.rocketmq.test.listener.AbstractListener; import org.apache.rocketmq.test.util.RandomUtil; public class RMQNormalConsumer extends AbstractMQConsumer { private static final Logger LOGGER = LoggerFactory.getLogger(RMQNormalConsumer.class); protected DefaultMQPushConsumer consumer = null; public RMQNormalConsumer(String nsAddr, String topic, String subExpression, String consumerGroup, AbstractListener listener) { super(nsAddr, topic, subExpression, consumerGroup, listener); } @Override public AbstractListener getListener() { return listener; } @Override public void setListener(AbstractListener listener) { this.listener = listener; } @Override public void create() { create(false); } @Override public void create(boolean useTLS) { consumer = new DefaultMQPushConsumer(consumerGroup); consumer.setInstanceName(RandomUtil.getStringByUUID()); consumer.setNamesrvAddr(nsAddr); consumer.setPollNameServerInterval(100); try { consumer.subscribe(topic, subExpression); } catch (MQClientException e) { LOGGER.error("consumer subscribe failed!"); e.printStackTrace(); } consumer.setMessageListener(listener); consumer.setUseTLS(useTLS); } @Override public void start() { try { consumer.start(); LOGGER.info(String.format("consumer[%s] started!", consumer.getConsumerGroup())); } catch (MQClientException e) { LOGGER.error("consumer start failed!"); e.printStackTrace(); } } public void subscribe(String topic, String subExpression) { try { consumer.subscribe(topic, subExpression); } catch (MQClientException e) { LOGGER.error("consumer subscribe failed!"); e.printStackTrace(); } } @Override public void shutdown() { consumer.shutdown(); } @Override public void clearMsg() { this.listener.clearMsg(); } public void restart() { consumer.shutdown(); create(); start(); } public DefaultMQPushConsumer getConsumer() { return consumer; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQNormalProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.clientinterface.AbstractMQProducer; import org.apache.rocketmq.test.sendresult.ResultWrapper; public class RMQNormalProducer extends AbstractMQProducer { private static Logger logger = LoggerFactory.getLogger(RMQNormalProducer.class); private DefaultMQProducer producer = null; private String nsAddr = null; public RMQNormalProducer(String nsAddr, String topic) { this(nsAddr, topic, false); } public RMQNormalProducer(String nsAddr, String topic, boolean useTLS) { super(topic); this.nsAddr = nsAddr; create(useTLS); start(); } public RMQNormalProducer(String nsAddr, String topic, String producerGroupName, String producerInstanceName) { this(nsAddr, topic, producerGroupName, producerInstanceName, false); } public RMQNormalProducer(String nsAddr, String topic, String producerGroupName, String producerInstanceName, boolean useTLS) { super(topic); this.producerGroupName = producerGroupName; this.producerInstanceName = producerInstanceName; this.nsAddr = nsAddr; create(useTLS); start(); } public DefaultMQProducer getProducer() { return producer; } public void setProducer(DefaultMQProducer producer) { this.producer = producer; } protected void create(boolean useTLS) { producer = new DefaultMQProducer(); producer.setProducerGroup(getProducerGroupName()); producer.setInstanceName(getProducerInstanceName()); producer.setUseTLS(useTLS); producer.setPollNameServerInterval(100); if (nsAddr != null) { producer.setNamesrvAddr(nsAddr); } } public void start() { try { producer.start(); super.setStartSuccess(true); } catch (MQClientException e) { super.setStartSuccess(false); logger.error("producer start failed!"); e.printStackTrace(); } } public ResultWrapper send(Object msg, Object orderKey) { org.apache.rocketmq.client.producer.SendResult internalSendResult = null; Message message = (Message) msg; try { long start = System.currentTimeMillis(); internalSendResult = producer.send(message); this.msgRTs.addData(System.currentTimeMillis() - start); if (isDebug) { logger.info("SendResult: {}", internalSendResult); } sendResult.setMsgId(internalSendResult.getMsgId()); sendResult.setSendResult(internalSendResult.getSendStatus().equals(SendStatus.SEND_OK)); sendResult.setBrokerIp(internalSendResult.getMessageQueue().getBrokerName()); msgBodys.addData(new String(message.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); originMsgIndex.put(new String(message.getBody(), StandardCharsets.UTF_8), internalSendResult); sendResult.setSendResultObj(internalSendResult); } catch (Exception e) { if (isDebug) { e.printStackTrace(); } sendResult.setSendResult(false); sendResult.setSendException(e); errorMsgs.addData(msg); } return sendResult; } public void send(Map> msgs) { for (MessageQueue mq : msgs.keySet()) { send(msgs.get(mq), mq); } } public void send(List msgs, MessageQueue mq) { for (Object msg : msgs) { sendMQ((Message) msg, mq); } } public void send(int num, MessageQueue mq) { for (int i = 0; i < num; i++) { sendMQ((Message) getMessageByTag(null), mq); } } public ResultWrapper sendMQ(Message msg, MessageQueue mq) { org.apache.rocketmq.client.producer.SendResult internalSendResult = null; try { long start = System.currentTimeMillis(); internalSendResult = producer.send(msg, mq); this.msgRTs.addData(System.currentTimeMillis() - start); if (isDebug) { logger.info("SendResult: {}", internalSendResult); } sendResult.setMsgId(internalSendResult.getMsgId()); sendResult.setSendResult(internalSendResult.getSendStatus().equals(SendStatus.SEND_OK)); sendResult.setBrokerIp(internalSendResult.getMessageQueue().getBrokerName()); msgBodys.addData(new String(msg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); originMsgIndex.put(new String(msg.getBody(), StandardCharsets.UTF_8), internalSendResult); } catch (Exception e) { if (isDebug) { e.printStackTrace(); } sendResult.setSendResult(false); sendResult.setSendException(e); errorMsgs.addData(msg); } return sendResult; } public void shutdown() { producer.shutdown(); } @Override public List getMessageQueue() { List mqs = null; try { mqs = producer.fetchPublishMessageQueues(topic); } catch (MQClientException e) { e.printStackTrace(); } return mqs; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.AckCallback; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopCallback; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.impl.ClientRemotingProcessor; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.remoting.protocol.header.NotificationRequestHeader; import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader; import org.apache.rocketmq.test.clientinterface.MQConsumer; import org.apache.rocketmq.test.util.RandomUtil; public class RMQPopClient implements MQConsumer { private static final long DEFAULT_TIMEOUT = 3000; private MQClientAPIExt mqClientAPI; @Override public void create() { create(false); } @Override public void create(boolean useTLS) { ClientConfig clientConfig = new ClientConfig(); clientConfig.setInstanceName(RandomUtil.getStringByUUID()); NettyClientConfig nettyClientConfig = new NettyClientConfig(); nettyClientConfig.setUseTLS(useTLS); this.mqClientAPI = new MQClientAPIExt( clientConfig, nettyClientConfig, new ClientRemotingProcessor(null), null); } @Override public void start() { this.mqClientAPI.start(); } @Override public void shutdown() { this.mqClientAPI.shutdown(); } public CompletableFuture popMessageAsync(String brokerAddr, MessageQueue mq, long invisibleTime, int maxNums, String consumerGroup, long timeout, boolean poll, int initMode, boolean order, String expressionType, String expression) { return popMessageAsync(brokerAddr, mq, invisibleTime, maxNums, consumerGroup, timeout, poll, initMode, order, expressionType, expression, null); } public CompletableFuture popMessageAsync(String brokerAddr, MessageQueue mq, long invisibleTime, int maxNums, String consumerGroup, long timeout, boolean poll, int initMode, boolean order, String expressionType, String expression, String attemptId) { PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(mq.getTopic()); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setMaxMsgNums(maxNums); requestHeader.setInvisibleTime(invisibleTime); requestHeader.setInitMode(initMode); requestHeader.setExpType(expressionType); requestHeader.setExp(expression); requestHeader.setOrder(order); requestHeader.setAttemptId(attemptId); if (poll) { requestHeader.setPollTime(timeout); requestHeader.setBornTime(System.currentTimeMillis()); timeout += 10 * 1000; } CompletableFuture future = new CompletableFuture<>(); try { this.mqClientAPI.popMessageAsync(mq.getBrokerName(), brokerAddr, requestHeader, timeout, new PopCallback() { @Override public void onSuccess(PopResult popResult) { future.complete(popResult); } @Override public void onException(Throwable e) { future.completeExceptionally(e); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture ackMessageAsync( String brokerAddr, String topic, String consumerGroup, String extraInfo) { String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo); AckMessageRequestHeader requestHeader = new AckMessageRequestHeader(); requestHeader.setTopic(ExtraInfoUtil.getRealTopic(extraInfoStrs, topic, consumerGroup)); requestHeader.setQueueId(ExtraInfoUtil.getQueueId(extraInfoStrs)); requestHeader.setOffset(ExtraInfoUtil.getQueueOffset(extraInfoStrs)); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setExtraInfo(extraInfo); CompletableFuture future = new CompletableFuture<>(); try { this.mqClientAPI.ackMessageAsync(brokerAddr, DEFAULT_TIMEOUT, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { future.complete(ackResult); } @Override public void onException(Throwable e) { future.completeExceptionally(e); } }, requestHeader); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture batchAckMessageAsync(String brokerAddr, String topic, String consumerGroup, List extraInfoList) { CompletableFuture future = new CompletableFuture<>(); try { this.mqClientAPI.batchAckMessageAsync(brokerAddr, DEFAULT_TIMEOUT, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { future.complete(ackResult); } @Override public void onException(Throwable e) { future.completeExceptionally(e); } }, topic, consumerGroup, extraInfoList); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture changeInvisibleTimeAsync(String brokerAddr, String brokerName, String topic, String consumerGroup, String extraInfo, long invisibleTime) { String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo); ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); requestHeader.setTopic(ExtraInfoUtil.getRealTopic(extraInfoStrs, topic, consumerGroup)); requestHeader.setQueueId(ExtraInfoUtil.getQueueId(extraInfoStrs)); requestHeader.setOffset(ExtraInfoUtil.getQueueOffset(extraInfoStrs)); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setExtraInfo(extraInfo); requestHeader.setInvisibleTime(invisibleTime); CompletableFuture future = new CompletableFuture<>(); try { this.mqClientAPI.changeInvisibleTimeAsync(brokerName, brokerAddr, requestHeader, DEFAULT_TIMEOUT, new AckCallback() { @Override public void onSuccess(AckResult ackResult) { future.complete(ackResult); } @Override public void onException(Throwable e) { future.completeExceptionally(e); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } public CompletableFuture notification(String brokerAddr, String topic, String consumerGroup, int queueId, long pollTime, long bornTime, long timeoutMillis) { return notification(brokerAddr, topic, consumerGroup, queueId, null, null, pollTime, bornTime, timeoutMillis); } public CompletableFuture notification(String brokerAddr, String topic, String consumerGroup, int queueId, Boolean order, String attemptId, long pollTime, long bornTime, long timeoutMillis) { return notification(brokerAddr, topic, consumerGroup, queueId, order, attemptId, pollTime, bornTime, timeoutMillis, null, null); } public CompletableFuture notification(String brokerAddr, String topic, String consumerGroup, int queueId, Boolean order, String attemptId, long pollTime, long bornTime, long timeoutMillis, String expType, String exp) { NotificationRequestHeader requestHeader = new NotificationRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(topic); requestHeader.setQueueId(queueId); requestHeader.setPollTime(pollTime); requestHeader.setBornTime(bornTime); requestHeader.setOrder(order); requestHeader.setAttemptId(attemptId); requestHeader.setExpType(expType); requestHeader.setExp(exp); return this.mqClientAPI.notification(brokerAddr, requestHeader, timeoutMillis); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.AbstractListener; public class RMQPopConsumer extends RMQNormalConsumer { private static final Logger log = LoggerFactory.getLogger(RMQPopConsumer.class); public static final long POP_TIMEOUT = 3000; public static final long DEFAULT_INVISIBLE_TIME = 30000; private RMQPopClient client; private int maxNum = 16; public RMQPopConsumer(String nsAddr, String topic, String subExpression, String consumerGroup, AbstractListener listener) { super(nsAddr, topic, subExpression, consumerGroup, listener); } public RMQPopConsumer(String nsAddr, String topic, String subExpression, String consumerGroup, AbstractListener listener, int maxNum) { super(nsAddr, topic, subExpression, consumerGroup, listener); this.maxNum = maxNum; } @Override public void start() { client = ConsumerFactory.getRMQPopClient(); log.info("consumer[{}] started!", consumerGroup); } @Override public void shutdown() { client.shutdown(); } public PopResult pop(String brokerAddr, MessageQueue mq) throws Exception { return this.pop(brokerAddr, mq, DEFAULT_INVISIBLE_TIME, 5000); } public PopResult pop(String brokerAddr, MessageQueue mq, long invisibleTime, long timeout) throws InterruptedException, RemotingException, MQClientException, MQBrokerException, ExecutionException, TimeoutException { CompletableFuture future = this.client.popMessageAsync( brokerAddr, mq, invisibleTime, maxNum, consumerGroup, timeout, true, ConsumeInitMode.MIN, false, ExpressionType.TAG, "*"); return future.get(); } public PopResult popOrderly(String brokerAddr, MessageQueue mq) throws Exception { return this.popOrderly(brokerAddr, mq, DEFAULT_INVISIBLE_TIME, 5000); } public PopResult popOrderly(String brokerAddr, MessageQueue mq, long invisibleTime, long timeout) throws InterruptedException, ExecutionException { CompletableFuture future = this.client.popMessageAsync( brokerAddr, mq, invisibleTime, maxNum, consumerGroup, timeout, true, ConsumeInitMode.MIN, true, ExpressionType.TAG, "*"); return future.get(); } public CompletableFuture ackAsync(String brokerAddr, String extraInfo) { return this.client.ackMessageAsync(brokerAddr, topic, consumerGroup, extraInfo); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQSqlConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.listener.AbstractListener; public class RMQSqlConsumer extends RMQNormalConsumer { private static Logger logger = LoggerFactory.getLogger(RMQSqlConsumer.class); private MessageSelector selector; public RMQSqlConsumer(String nsAddr, String topic, MessageSelector selector, String consumerGroup, AbstractListener listener) { super(nsAddr, topic, "*", consumerGroup, listener); this.selector = selector; } @Override public void create() { super.create(); try { consumer.subscribe(topic, selector); } catch (Exception e) { logger.error("Subscribe Sql Errored", e); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQTransactionalProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.rmq; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.clientinterface.AbstractMQProducer; import org.apache.rocketmq.test.sendresult.ResultWrapper; public class RMQTransactionalProducer extends AbstractMQProducer { private static Logger logger = LoggerFactory.getLogger(RMQTransactionalProducer.class); private TransactionMQProducer producer = null; private String nsAddr = null; public RMQTransactionalProducer(String nsAddr, String topic, TransactionListener transactionListener) { this(nsAddr, topic, false, transactionListener); } public RMQTransactionalProducer(String nsAddr, String topic, boolean useTLS, TransactionListener transactionListener) { super(topic); this.nsAddr = nsAddr; create(useTLS, transactionListener); start(); } protected void create(boolean useTLS, TransactionListener transactionListener) { producer = new TransactionMQProducer(); producer.setProducerGroup(getProducerGroupName()); producer.setInstanceName(getProducerInstanceName()); producer.setTransactionListener(transactionListener); producer.setUseTLS(useTLS); if (nsAddr != null) { producer.setNamesrvAddr(nsAddr); } } public void start() { try { producer.start(); super.setStartSuccess(true); } catch (MQClientException e) { super.setStartSuccess(false); logger.error("", e); e.printStackTrace(); } } @Override public ResultWrapper send(Object msg, Object arg) { boolean commitMsg = ((Pair) arg).getObject2() == LocalTransactionState.COMMIT_MESSAGE; org.apache.rocketmq.client.producer.SendResult metaqResult = null; Message message = (Message) msg; try { long start = System.currentTimeMillis(); metaqResult = producer.sendMessageInTransaction(message, arg); this.msgRTs.addData(System.currentTimeMillis() - start); if (isDebug) { logger.info("SendResult: {}", metaqResult); } sendResult.setMsgId(metaqResult.getMsgId()); sendResult.setSendResult(true); sendResult.setBrokerIp(metaqResult.getMessageQueue().getBrokerName()); if (commitMsg) { msgBodys.addData(new String(message.getBody(), StandardCharsets.UTF_8)); } originMsgs.addData(msg); originMsgIndex.put(new String(message.getBody(), StandardCharsets.UTF_8), metaqResult); } catch (MQClientException e) { if (isDebug) { e.printStackTrace(); } sendResult.setSendResult(false); sendResult.setSendException(e); errorMsgs.addData(msg); } return sendResult; } @Override public void shutdown() { producer.shutdown(); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/clientinterface/AbstractMQConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.clientinterface; import org.apache.rocketmq.test.listener.AbstractListener; public abstract class AbstractMQConsumer implements MQConsumer { protected AbstractListener listener = null; protected String nsAddr = null; protected String topic = null; protected String subExpression = null; protected String consumerGroup = null; protected boolean isDebug = false; public AbstractMQConsumer() { } public AbstractMQConsumer(String nsAddr, String topic, String subExpression, String consumerGroup, AbstractListener listener) { this.topic = topic; this.subExpression = subExpression; this.consumerGroup = consumerGroup; this.listener = listener; this.nsAddr = nsAddr; } public AbstractMQConsumer(String topic, String subExpression) { this.topic = topic; this.subExpression = subExpression; } public void setDebug() { if (listener != null) { listener.setDebug(true); } isDebug = true; } public void setDebug(boolean isDebug) { if (listener != null) { listener.setDebug(isDebug); } this.isDebug = isDebug; } public void setSubscription(String topic, String subExpression) { this.topic = topic; this.subExpression = subExpression; } public AbstractListener getListener() { return listener; } public void setListener(AbstractListener listener) { this.listener = listener; } public String getNsAddr() { return nsAddr; } public void setNsAddr(String nsAddr) { this.nsAddr = nsAddr; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getSubExpression() { return subExpression; } public void setSubExpression(String subExpression) { this.subExpression = subExpression; } public String getConsumerGroup() { return consumerGroup; } public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } public void clearMsg() { listener.clearMsg(); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/clientinterface/AbstractMQProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.clientinterface; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.sendresult.ResultWrapper; import org.apache.rocketmq.test.util.RandomUtil; import org.apache.rocketmq.test.util.TestUtil; public abstract class AbstractMQProducer extends MQCollector implements MQProducer { protected String topic = null; protected ResultWrapper sendResult = new ResultWrapper(); protected boolean startSuccess = false; protected String producerGroupName = null; protected String producerInstanceName = null; protected boolean isDebug = false; public AbstractMQProducer(String topic) { super(); producerGroupName = RandomUtil.getStringByUUID(); producerInstanceName = RandomUtil.getStringByUUID(); this.topic = topic; } public AbstractMQProducer(String topic, String originMsgCollector, String msgBodyCollector) { super(originMsgCollector, msgBodyCollector); producerGroupName = RandomUtil.getStringByUUID(); producerInstanceName = RandomUtil.getStringByUUID(); this.topic = topic; } public boolean isStartSuccess() { return startSuccess; } public void setStartSuccess(boolean startSuccess) { this.startSuccess = startSuccess; } public String getProducerInstanceName() { return producerInstanceName; } public void setProducerInstanceName(String producerInstanceName) { this.producerInstanceName = producerInstanceName; } public String getProducerGroupName() { return producerGroupName; } public void setProducerGroupName(String producerGroupName) { this.producerGroupName = producerGroupName; } public void setDebug() { isDebug = true; } public void setDebug(boolean isDebug) { this.isDebug = isDebug; } public void setRun() { isDebug = false; } public List getMessageQueue() { return null; } private Object getMessage() { return this.getMessageByTag(null); } public Object getMessageByTag(String tag) { Object objMsg = null; if (this instanceof RMQNormalProducer) { org.apache.rocketmq.common.message.Message msg = new org.apache.rocketmq.common.message.Message( topic, (RandomUtil.getStringByUUID() + "." + new Date()).getBytes(StandardCharsets.UTF_8)); objMsg = msg; if (tag != null) { msg.setTags(tag); } } return objMsg; } public void send() { Object msg = getMessage(); send(msg, null); } public void send(Object msg) { send(msg, null); } public void send(long msgNum) { for (int i = 0; i < msgNum; i++) { this.send(); } } public void send(long msgNum, int intervalMills) { for (int i = 0; i < msgNum; i++) { this.send(); TestUtil.waitForMonment(intervalMills); } } public void send(String tag, int msgSize) { for (int i = 0; i < msgSize; i++) { Object msg = getMessageByTag(tag); send(msg, null); } } public void send(String tag, int msgSize, int intervalMills) { for (int i = 0; i < msgSize; i++) { Object msg = getMessageByTag(tag); send(msg, null); TestUtil.waitForMonment(intervalMills); } } public void send(List msgs) { for (Object msg : msgs) { this.send(msg, null); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/clientinterface/MQCollector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.clientinterface; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.test.util.RandomUtil; import org.apache.rocketmq.test.util.data.collect.DataCollector; import org.apache.rocketmq.test.util.data.collect.DataCollectorManager; public abstract class MQCollector { protected DataCollector msgBodys = null; protected DataCollector originMsgs = null; protected DataCollector errorMsgs = null; protected Map originMsgIndex = null; protected Collection msgBodysCopy = null; protected DataCollector msgRTs = null; public MQCollector() { msgBodys = DataCollectorManager.getInstance() .fetchListDataCollector(RandomUtil.getStringByUUID()); originMsgs = DataCollectorManager.getInstance() .fetchListDataCollector(RandomUtil.getStringByUUID()); errorMsgs = DataCollectorManager.getInstance() .fetchListDataCollector(RandomUtil.getStringByUUID()); originMsgIndex = new ConcurrentHashMap(); msgRTs = DataCollectorManager.getInstance() .fetchListDataCollector(RandomUtil.getStringByUUID()); } public MQCollector(String originMsgCollector, String msgBodyCollector) { originMsgs = DataCollectorManager.getInstance().fetchDataCollector(originMsgCollector); msgBodys = DataCollectorManager.getInstance().fetchDataCollector(msgBodyCollector); } public Collection getAllMsgBody() { return msgBodys.getAllData(); } public Collection getAllOriginMsg() { return originMsgs.getAllData(); } public Object getFirstMsg() { return ((List) originMsgs.getAllData()).get(0); } public Collection getAllUndupMsgBody() { return msgBodys.getAllDataWithoutDuplicate(); } public Collection getAllUndupOriginMsg() { return originMsgs.getAllData(); } public Collection getSendErrorMsg() { return errorMsgs.getAllData(); } public Collection getMsgRTs() { return msgRTs.getAllData(); } public Map getOriginMsgIndex() { return originMsgIndex; } public Collection getMsgBodysCopy() { msgBodysCopy = new ArrayList(); msgBodysCopy.addAll(msgBodys.getAllData()); return msgBodysCopy; } public void clearMsg() { if (msgBodys != null) { msgBodys.resetData(); } if (originMsgs != null) { originMsgs.resetData(); } if (originMsgs != null) { errorMsgs.resetData(); } if (originMsgIndex != null) { originMsgIndex.clear(); } if (msgRTs != null) { msgRTs.resetData(); } } public void lockCollectors() { msgBodys.lockIncrement(); originMsgs.lockIncrement(); errorMsgs.lockIncrement(); msgRTs.lockIncrement(); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/clientinterface/MQConsumer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.clientinterface; public interface MQConsumer { void create(); void create(boolean useTLS); void start(); void shutdown(); } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/clientinterface/MQProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.clientinterface; import org.apache.rocketmq.test.sendresult.ResultWrapper; public interface MQProducer { ResultWrapper send(Object msg, Object arg); void setDebug(); void setRun(); void shutdown(); } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/factory/ConsumerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.factory; import java.util.UUID; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; import org.apache.rocketmq.test.client.rmq.RMQSqlConsumer; import org.apache.rocketmq.test.listener.AbstractListener; public class ConsumerFactory { public static RMQNormalConsumer getRMQNormalConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listener) { return getRMQNormalConsumer(nsAddr, consumerGroup, topic, subExpression, listener, false); } public static RMQNormalConsumer getRMQNormalConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listener, boolean useTLS) { RMQNormalConsumer consumer = new RMQNormalConsumer(nsAddr, topic, subExpression, consumerGroup, listener); consumer.create(useTLS); consumer.start(); return consumer; } public static RMQBroadCastConsumer getRMQBroadCastConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listner) { RMQBroadCastConsumer consumer = new RMQBroadCastConsumer(nsAddr, topic, subExpression, consumerGroup, listner); consumer.create(); consumer.start(); return consumer; } public static RMQSqlConsumer getRMQSqlConsumer(String nsAddr, String consumerGroup, String topic, MessageSelector selector, AbstractListener listner) { RMQSqlConsumer consumer = new RMQSqlConsumer(nsAddr, topic, selector, consumerGroup, listner); consumer.create(); consumer.start(); return consumer; } public static RMQPopConsumer getRMQPopConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listener) { RMQPopConsumer consumer = new RMQPopConsumer(nsAddr, topic, subExpression, consumerGroup, listener); consumer.create(); consumer.start(); return consumer; } public static RMQPopClient getRMQPopClient() { RMQPopClient client = new RMQPopClient(); client.create(); client.start(); return client; } public static DefaultMQPullConsumer getRMQPullConsumer(String nsAddr, String consumerGroup) throws Exception { DefaultMQPullConsumer defaultMQPullConsumer = new DefaultMQPullConsumer(consumerGroup); defaultMQPullConsumer.setInstanceName(UUID.randomUUID().toString()); defaultMQPullConsumer.setNamesrvAddr(nsAddr); defaultMQPullConsumer.start(); return defaultMQPullConsumer; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/factory/MQMessageFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.factory; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.util.RandomUtil; public class MQMessageFactory { private static Integer index = 0; public static List getRMQMessage(String tag, String topic, int msgSize) { List msgs = new ArrayList(); for (int i = 0; i < msgSize; i++) { msgs.add(new Message(topic, tag, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8))); } return msgs; } public static List getRMQMessage(List tags, String topic, int msgSize) { List msgs = new ArrayList(); for (int i = 0; i < msgSize; i++) { for (String tag : tags) { msgs.add(new Message(topic, tag, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8))); } } return msgs; } public static List getMessageBody(List msgs) { List msgBodys = new ArrayList(); for (Object msg : msgs) { msgBodys.add(new String(((Message) msg).getBody(), StandardCharsets.UTF_8)); } return msgBodys; } public static Collection getMessage(Collection... msgs) { Collection allMsgs = new ArrayList(); for (Collection msg : msgs) { allMsgs.addAll(msg); } return allMsgs; } public static List getDelayMsg(String topic, int delayLevel, int msgSize) { List msgs = new ArrayList(); for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); msg.setDelayTimeLevel(delayLevel); msgs.add(msg); } return msgs; } public static List getKeyMsg(String topic, String key, int msgSize) { List msgs = new ArrayList(); for (int i = 0; i < msgSize; i++) { Message msg = new Message(topic, null, key, RandomUtil.getStringByUUID().getBytes(StandardCharsets.UTF_8)); msgs.add(msg); } return msgs; } public static Map> getMsgByMQ(MessageQueue mq, int msgSize) { List mqs = new ArrayList(); mqs.add(mq); return getMsgByMQ(mqs, msgSize); } public static Map> getMsgByMQ(List mqs, int msgSize) { return getMsgByMQ(mqs, msgSize, null); } public static Map> getMsgByMQ(List mqs, int msgSize, String tag) { Map> msgs = new HashMap>(); for (MessageQueue mq : mqs) { msgs.put(mq, getMsg(mq.getTopic(), msgSize, tag)); } return msgs; } public static List getMsg(String topic, int msgSize) { return getMsg(topic, msgSize, null); } public static List getMsg(String topic, int msgSize, String tag) { List msgs = new ArrayList(); while (msgSize > 0) { Message msg = new Message(topic, (index++).toString().getBytes(StandardCharsets.UTF_8)); if (tag != null) { msg.setTags(tag); } msgs.add(msg); msgSize--; } return msgs; } public static List getMessageQueues(MessageQueue... mq) { return Arrays.asList(mq); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/factory/MessageFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.factory; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.test.util.RandomUtils; public class MessageFactory { public static Message getRandomMessage(String topic) { return getStringMessage(topic, RandomUtils.getStringByUUID()); } public static Message getStringMessage(String topic, String body) { return new Message(topic, body.getBytes(StandardCharsets.UTF_8)); } public static Message getStringMessageByTag(String topic, String tags, String body) { return new Message(topic, tags, body.getBytes(StandardCharsets.UTF_8)); } public static Message getRandomMessageByTag(String topic, String tags) { return getStringMessageByTag(topic, tags, RandomUtils.getStringByUUID()); } public static Collection getRandomMessageList(String topic, int size) { List msgList = new ArrayList(); for (int i = 0; i < size; i++) { msgList.add(getRandomMessage(topic)); } return msgList; } public static Collection getRandomMessageListByTag(String topic, String tags, int size) { List msgList = new ArrayList(); for (int i = 0; i < size; i++) { msgList.add(getRandomMessageByTag(topic, tags)); } return msgList; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/factory/ProducerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.factory; import java.util.UUID; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.test.util.RandomUtil; public class ProducerFactory { public static DefaultMQProducer getRMQProducer(String ns) { DefaultMQProducer producer = new DefaultMQProducer(RandomUtil.getStringByUUID()); producer.setInstanceName(UUID.randomUUID().toString()); producer.setNamesrvAddr(ns); try { producer.start(); } catch (MQClientException e) { e.printStackTrace(); } return producer; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/factory/SendCallBackFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.factory; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; public class SendCallBackFactory { public static SendCallback getSendCallBack() { return new SendCallback() { @Override public void onSuccess(SendResult sendResult) { } @Override public void onException(Throwable throwable) { } }; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/factory/TagMessage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.factory; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class TagMessage { private List tags = null; private String topic = null; private int msgSize = 0; private Map> rmqMsgs = new HashMap>(); public TagMessage(String tag, String topic, int msgSize) { String[] tags = {tag}; this.tags = Arrays.asList(tags); this.topic = topic; this.msgSize = msgSize; init(); } public TagMessage(String[] tags, String topic, int msgSize) { this(Arrays.asList(tags), topic, msgSize); } public TagMessage(List tags, String topic, int msgSize) { this.tags = tags; this.topic = topic; this.msgSize = msgSize; init(); } private void init() { for (String tag : tags) { List tagMsgs = MQMessageFactory.getRMQMessage(tag, topic, msgSize); rmqMsgs.put(tag, tagMsgs); } } public List getMessageByTag(String tag) { if (tags.contains(tag)) { return rmqMsgs.get(tag); } else { return new ArrayList(); } } public List getMixedTagMessages() { List mixedMsgs = new ArrayList(); for (int i = 0; i < msgSize; i++) { for (String tag : tags) { mixedMsgs.add(rmqMsgs.get(tag).get(i)); } } return mixedMsgs; } public List getMessageBodyByTag(String tag) { if (tags.contains(tag)) { return MQMessageFactory.getMessageBody(rmqMsgs.get(tag)); } else { return new ArrayList(); } } public List getMessageBodyByTag(String... tag) { return this.getMessageBodyByTag(Arrays.asList(tag)); } public List getMessageBodyByTag(List tags) { List msgBodys = new ArrayList(); for (String tag : tags) { msgBodys.addAll(MQMessageFactory.getMessageBody(rmqMsgs.get(tag))); } return msgBodys; } public List getAllTagMessageBody() { List msgs = new ArrayList(); for (String tag : tags) { msgs.addAll(MQMessageFactory.getMessageBody(rmqMsgs.get(tag))); } return msgs; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/listener/AbstractListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.listener; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.rocketmq.client.consumer.listener.MessageListener; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.clientinterface.MQCollector; import org.apache.rocketmq.test.util.TestUtil; public class AbstractListener extends MQCollector implements MessageListener { public static final Logger LOGGER = LoggerFactory.getLogger(AbstractListener.class); protected boolean isDebug = true; protected String listenerName = null; protected Collection allSendMsgs = null; public AbstractListener() { super(); } public AbstractListener(String listenerName) { super(); this.listenerName = listenerName; } public AbstractListener(String originMsgCollector, String msgBodyCollector) { super(originMsgCollector, msgBodyCollector); } public boolean isDebug() { return isDebug; } public void setDebug(boolean debug) { isDebug = debug; } public void waitForMessageConsume(int timeoutMills) { TestUtil.waitForMonment(timeoutMills); } public void stopRecv() { super.lockCollectors(); } public Collection waitForMessageConsume(Collection allSendMessages, int timeoutMills) { this.allSendMsgs = allSendMessages; List sendMessages = new ArrayList<>(allSendMessages); long curTime = System.currentTimeMillis(); while (!sendMessages.isEmpty()) { sendMessages.removeIf(msg -> msgBodys.getAllData().contains(msg)); if (sendMessages.isEmpty()) { break; } else { if (System.currentTimeMillis() - curTime >= timeoutMills) { LOGGER.error(String.format("timeout but [%s] not recv all send messages!", listenerName)); break; } else { LOGGER.info(String.format("[%s] still [%s] msg not recv!", listenerName, sendMessages.size())); TestUtil.waitForMonment(500); } } } return sendMessages; } public long waitForMessageConsume(int size, int timeoutMills) { long curTime = System.currentTimeMillis(); while (true) { if (msgBodys.getDataSize() >= size) { break; } if (System.currentTimeMillis() - curTime >= timeoutMills) { LOGGER.error(String.format("timeout but [%s] not recv all send messages!", listenerName)); break; } else { LOGGER.info(String.format("[%s] still [%s] msg not recv!", listenerName, size - msgBodys.getDataSize())); TestUtil.waitForMonment(500); } } return msgBodys.getDataSize(); } public void waitForMessageConsume(Map sendMsgIndex, int timeoutMills) { Collection notRecvMsgs = waitForMessageConsume(sendMsgIndex.keySet(), timeoutMills); for (Object object : notRecvMsgs) { LOGGER.info("{}", sendMsgIndex.get(object)); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/listener/rmq/concurrent/RMQBlockListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.listener.rmq.concurrent; import java.util.List; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.common.message.MessageExt; public class RMQBlockListener extends RMQNormalListener { private volatile boolean block = true; private volatile boolean inBlock = true; public RMQBlockListener() { super(); } public RMQBlockListener(boolean block) { super(); this.block = block; } public boolean isBlocked() { return inBlock; } public void setBlock(boolean block) { this.block = block; } @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { ConsumeConcurrentlyStatus status = super.consumeMessage(msgs, context); try { while (block) { inBlock = true; Thread.sleep(100); } } catch (InterruptedException ignore) { } return status; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/listener/rmq/concurrent/RMQDelayListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.listener.rmq.concurrent; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.test.listener.AbstractListener; import org.apache.rocketmq.test.util.RandomUtil; import org.apache.rocketmq.test.util.data.collect.DataCollector; import org.apache.rocketmq.test.util.data.collect.DataCollectorManager; import java.util.Collection; import java.util.List; public class RMQDelayListener extends AbstractListener implements MessageListenerConcurrently { private DataCollector msgDelayTimes = null; public RMQDelayListener() { msgDelayTimes = DataCollectorManager.getInstance() .fetchDataCollector(RandomUtil.getStringByUUID()); } public Collection getMsgDelayTimes() { return msgDelayTimes.getAllData(); } public void resetMsgDelayTimes() { msgDelayTimes.resetData(); } public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) { long recvTime = System.currentTimeMillis(); for (MessageExt msg : msgs) { if (isDebug) { LOGGER.info(listenerName + ":" + msg); } msgBodys.addData(new String(msg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); msgDelayTimes.addData(Math.abs(recvTime - msg.getBornTimestamp())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/listener/rmq/concurrent/RMQNormalListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.listener.rmq.concurrent; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.test.listener.AbstractListener; public class RMQNormalListener extends AbstractListener implements MessageListenerConcurrently { private ConsumeConcurrentlyStatus consumeStatus = ConsumeConcurrentlyStatus.CONSUME_SUCCESS; private final AtomicInteger msgIndex = new AtomicInteger(0); public RMQNormalListener() { super(); } public RMQNormalListener(String listenerName) { super(listenerName); } public RMQNormalListener(ConsumeConcurrentlyStatus consumeStatus) { super(); this.consumeStatus = consumeStatus; } public RMQNormalListener(String originMsgCollector, String msgBodyCollector) { super(originMsgCollector, msgBodyCollector); } public AtomicInteger getMsgIndex() { return msgIndex; } @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) { for (MessageExt msg : msgs) { msgIndex.getAndIncrement(); if (isDebug) { if (listenerName != null && !listenerName.isEmpty()) { LOGGER.info(listenerName + ":" + msgIndex.get() + ":" + String.format("msgid:%s broker:%s queueId:%s offset:%s", msg.getMsgId(), msg.getStoreHost(), msg.getQueueId(), msg.getQueueOffset())); } else { LOGGER.info("{}", msg); } } msgBodys.addData(new String(msg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); if (originMsgIndex != null) { originMsgIndex.put(new String(msg.getBody(), StandardCharsets.UTF_8), msg); } } return consumeStatus; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/listener/rmq/order/RMQOrderListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.listener.rmq.order; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.test.listener.AbstractListener; public class RMQOrderListener extends AbstractListener implements MessageListenerOrderly { private Map> msgs = new ConcurrentHashMap>(); public RMQOrderListener() { super(); } public RMQOrderListener(String listnerName) { super(listnerName); } public RMQOrderListener(String originMsgCollector, String msgBodyCollector) { super(originMsgCollector, msgBodyCollector); } public Collection> getMsgs() { return msgs.values(); } private void putMsg(MessageExt msg) { Collection msgQueue = null; String key = getKey(msg.getQueueId(), msg.getStoreHost().toString()); if (!msgs.containsKey(key)) { msgQueue = new ArrayList(); } else { msgQueue = msgs.get(key); } msgQueue.add(new String(msg.getBody(), StandardCharsets.UTF_8)); msgs.put(key, msgQueue); } private String getKey(int queueId, String brokerIp) { return String.format("%s_%s", queueId, brokerIp); } @Override public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { for (MessageExt msg : msgs) { if (isDebug) { if (listenerName != null && listenerName != "") { LOGGER.info(listenerName + ": " + msg); } else { LOGGER.info("{}", msg); } } putMsg(msg); msgBodys.addData(new String(msg.getBody(), StandardCharsets.UTF_8)); originMsgs.addData(msg); } return ConsumeOrderlyStatus.SUCCESS; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/lmq/benchmark/BenchLmqStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.lmq.benchmark; import com.google.common.math.IntMath; import com.google.common.math.LongMath; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.util.StatUtil; public class BenchLmqStore { private static Logger logger = LoggerFactory.getLogger(BenchLmqStore.class); private static String namesrv = System.getProperty("namesrv", "127.0.0.1:9876"); private static String lmqTopic = System.getProperty("lmqTopic", "lmqTestTopic"); private static boolean enableSub = Boolean.parseBoolean(System.getProperty("enableSub", "true")); private static String queuePrefix = System.getProperty("queuePrefix", "lmqTest"); private static int tps = Integer.parseInt(System.getProperty("tps", "1")); private static int lmqNum = Integer.parseInt(System.getProperty("lmqNum", "1")); private static int sendThreadNum = Integer.parseInt(System.getProperty("sendThreadNum", "64")); private static int consumerThreadNum = Integer.parseInt(System.getProperty("consumerThreadNum", "64")); private static String brokerName = System.getProperty("brokerName", "broker-a"); private static int size = Integer.parseInt(System.getProperty("size", "128")); private static int suspendTime = Integer.parseInt(System.getProperty("suspendTime", "2000")); private static final boolean RETRY_NO_MATCHED_MSG = Boolean.parseBoolean(System.getProperty("retry_no_matched_msg", "false")); private static boolean benchOffset = Boolean.parseBoolean(System.getProperty("benchOffset", "false")); private static int benchOffsetNum = Integer.parseInt(System.getProperty("benchOffsetNum", "1")); private static Map offsetMap = new ConcurrentHashMap<>(256); private static Map pullStatus = new ConcurrentHashMap<>(256); private static Map> pullEvent = new ConcurrentHashMap<>(256); public static DefaultMQProducer defaultMQProducer; private static int pullConsumerNum = Integer.parseInt(System.getProperty("pullConsumerNum", "8")); public static DefaultMQPullConsumer[] defaultMQPullConsumers = new DefaultMQPullConsumer[pullConsumerNum]; private static AtomicLong rid = new AtomicLong(); private static final String LMQ_PREFIX = "%LMQ%"; public static void main(String[] args) throws InterruptedException, MQClientException, MQBrokerException, RemotingException { defaultMQProducer = new DefaultMQProducer(); defaultMQProducer.setProducerGroup("PID_LMQ_TEST"); defaultMQProducer.setVipChannelEnabled(false); defaultMQProducer.setNamesrvAddr(namesrv); defaultMQProducer.start(); //defaultMQProducer.createTopic(lmqTopic, lmqTopic, 8); for (int i = 0; i < pullConsumerNum; i++) { DefaultMQPullConsumer defaultMQPullConsumer = new DefaultMQPullConsumer(); defaultMQPullConsumers[i] = defaultMQPullConsumer; defaultMQPullConsumer.setNamesrvAddr(namesrv); defaultMQPullConsumer.setVipChannelEnabled(false); defaultMQPullConsumer.setConsumerGroup("CID_RMQ_SYS_LMQ_TEST_" + i); defaultMQPullConsumer.setInstanceName("CID_RMQ_SYS_LMQ_TEST_" + i); defaultMQPullConsumer.setRegisterTopics(new HashSet<>(Collections.singletonList(lmqTopic))); defaultMQPullConsumer.setBrokerSuspendMaxTimeMillis(suspendTime); defaultMQPullConsumer.setConsumerTimeoutMillisWhenSuspend(suspendTime + 1000); defaultMQPullConsumer.start(); } Thread.sleep(3000L); if (benchOffset) { doBenchOffset(); return; } ScheduledThreadPoolExecutor consumerPool = new ScheduledThreadPoolExecutor(consumerThreadNum, new ThreadFactoryImpl("test")); for (int i = 0; i < consumerThreadNum; i++) { final int idx = i; consumerPool.scheduleWithFixedDelay(() -> { try { Map map = pullEvent.get(idx); if (map == null) { return; } for (Map.Entry entry : map.entrySet()) { try { Boolean status = pullStatus.get(entry.getKey()); if (Boolean.TRUE.equals(status)) { continue; } doPull(map, entry.getKey(), entry.getValue()); } catch (Exception e) { logger.error("pull broker msg error", e); } } } catch (Exception e) { logger.error("exec doPull task error", e); } }, 1, 1, TimeUnit.MILLISECONDS); } // init queue sub if (enableSub && lmqNum > 0 && StringUtils.isNotBlank(brokerName)) { for (int i = 0; i < lmqNum; i++) { long idx = rid.incrementAndGet(); String queue = LMQ_PREFIX + queuePrefix + LongMath.mod(idx, lmqNum); MessageQueue mq = new MessageQueue(queue, brokerName, 0); int queueHash = IntMath.mod(queue.hashCode(), consumerThreadNum); pullEvent.putIfAbsent(queueHash, new ConcurrentHashMap<>()); pullEvent.get(queueHash).put(mq, idx); } } Thread.sleep(5000L); doSend(); } public static void doSend() { StringBuilder sb = new StringBuilder(); for (int j = 0; j < size; j += 10) { sb.append("hello baby"); } byte[] body = sb.toString().getBytes(StandardCharsets.UTF_8); String pubKey = "pub"; ExecutorService sendPool = Executors.newFixedThreadPool(sendThreadNum); for (int i = 0; i < sendThreadNum; i++) { sendPool.execute(() -> { while (true) { if (StatUtil.isOverFlow(pubKey, tps)) { try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } } long start = System.currentTimeMillis(); try { long idx = rid.incrementAndGet(); Message message = new Message(lmqTopic, body); String queue = lmqTopic; if (lmqNum > 0) { queue = LMQ_PREFIX + queuePrefix + idx % lmqNum; message.putUserProperty("INNER_MULTI_DISPATCH", queue); } SendResult sendResult = defaultMQProducer.send(message); StatUtil.addInvoke(pubKey, System.currentTimeMillis() - start); if (StatUtil.nowTps(pubKey) < 10) { logger.warn("pub: {} ", sendResult.getMsgId()); } if (enableSub && null != sendResult.getMessageQueue()) { MessageQueue mq = new MessageQueue(queue, sendResult.getMessageQueue().getBrokerName(), lmqNum > 0 ? 0 : sendResult.getMessageQueue().getQueueId()); int queueHash = IntMath.mod(queue.hashCode(), consumerThreadNum); pullEvent.putIfAbsent(queueHash, new ConcurrentHashMap<>()); pullEvent.get(queueHash).put(mq, idx); } } catch (Exception e) { logger.error("", e); StatUtil.addInvoke(pubKey, System.currentTimeMillis() - start, false); } } }); } } public static void doPull(Map eventMap, MessageQueue mq, Long eventId) throws RemotingException, InterruptedException, MQClientException { if (!enableSub) { eventMap.remove(mq, eventId); pullStatus.remove(mq); return; } DefaultMQPullConsumer defaultMQPullConsumer = defaultMQPullConsumers[(int) (eventId % pullConsumerNum)]; Long offset = offsetMap.get(mq); if (offset == null) { long start = System.currentTimeMillis(); offset = defaultMQPullConsumer.maxOffset(mq); StatUtil.addInvoke("maxOffset", System.currentTimeMillis() - start); offsetMap.put(mq, offset); } long start = System.currentTimeMillis(); if (null != pullStatus.putIfAbsent(mq, true)) { return; } defaultMQPullConsumer.pullBlockIfNotFound( mq, "*", offset, 32, new PullCallback() { @Override public void onSuccess(PullResult pullResult) { StatUtil.addInvoke(pullResult.getPullStatus().name(), System.currentTimeMillis() - start); eventMap.remove(mq, eventId); pullStatus.remove(mq); offsetMap.put(mq, pullResult.getNextBeginOffset()); StatUtil.addInvoke("doPull", System.currentTimeMillis() - start); if (PullStatus.NO_MATCHED_MSG.equals(pullResult.getPullStatus()) && RETRY_NO_MATCHED_MSG) { long idx = rid.incrementAndGet(); eventMap.put(mq, idx); } List list = pullResult.getMsgFoundList(); if (list == null || list.isEmpty()) { StatUtil.addInvoke("NoMsg", System.currentTimeMillis() - start); return; } for (MessageExt messageExt : list) { StatUtil.addInvoke("sub", System.currentTimeMillis() - messageExt.getBornTimestamp()); if (StatUtil.nowTps("sub") < 10) { logger.warn("sub: {}", messageExt.getMsgId()); } } } @Override public void onException(Throwable e) { eventMap.remove(mq, eventId); pullStatus.remove(mq); logger.error("", e); StatUtil.addInvoke("doPull", System.currentTimeMillis() - start, false); } }); } public static void doBenchOffset() throws RemotingException, InterruptedException, MQClientException { ExecutorService sendPool = Executors.newFixedThreadPool(sendThreadNum); Map offsetMap = new ConcurrentHashMap<>(); String statKey = "benchOffset"; TopicRouteData topicRouteData = defaultMQPullConsumers[0].getDefaultMQPullConsumerImpl(). getRebalanceImpl().getmQClientFactory().getMQClientAPIImpl(). getTopicRouteInfoFromNameServer(lmqTopic, 3000); HashMap brokerMap = topicRouteData.getBrokerDatas().get(0).getBrokerAddrs(); if (brokerMap == null || brokerMap.isEmpty()) { return; } String brokerAddress = brokerMap.get(MixAll.MASTER_ID); for (int i = 0; i < sendThreadNum; i++) { final int flag = i; sendPool.execute(new Runnable() { @Override public void run() { while (true) { try { if (StatUtil.isOverFlow(statKey, tps)) { Thread.sleep(100L); } long start = System.currentTimeMillis(); long id = rid.incrementAndGet(); int index = (Integer.MAX_VALUE & (int) id) % defaultMQPullConsumers.length; DefaultMQPullConsumer defaultMQPullConsumer = defaultMQPullConsumers[index]; String lmq = LMQ_PREFIX + queuePrefix + id % benchOffsetNum; String lmqCid = LMQ_PREFIX + "GID_LMQ@@c" + flag + "-" + id % benchOffsetNum; offsetMap.putIfAbsent(lmq, 0L); long newOffset1 = offsetMap.get(lmq) + 1; UpdateConsumerOffsetRequestHeader updateHeader = new UpdateConsumerOffsetRequestHeader(); updateHeader.setTopic(lmq); updateHeader.setConsumerGroup(lmqCid); updateHeader.setQueueId(0); updateHeader.setCommitOffset(newOffset1); defaultMQPullConsumer .getDefaultMQPullConsumerImpl() .getRebalanceImpl() .getmQClientFactory() .getMQClientAPIImpl().updateConsumerOffset(brokerAddress, updateHeader, 1000); QueryConsumerOffsetRequestHeader queryHeader = new QueryConsumerOffsetRequestHeader(); queryHeader.setTopic(lmq); queryHeader.setConsumerGroup(lmqCid); queryHeader.setQueueId(0); long newOffset2 = defaultMQPullConsumer .getDefaultMQPullConsumerImpl() .getRebalanceImpl() .getmQClientFactory() .getMQClientAPIImpl() .queryConsumerOffset(brokerAddress, queryHeader, 1000); offsetMap.put(lmq, newOffset2); if (newOffset1 != newOffset2) { StatUtil.addInvoke("ErrorOffset", 1); } StatUtil.addInvoke(statKey, System.currentTimeMillis() - start); } catch (Exception e) { logger.error("", e); } } } }); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/message/MessageQueueMsg.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.message; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.factory.MQMessageFactory; public class MessageQueueMsg { private Map> msgsWithMQ = null; private Map> msgsWithMQId = null; private Collection msgBodys = null; public MessageQueueMsg(List mqs, int msgSize) { this(mqs, msgSize, null); } public MessageQueueMsg(List mqs, int msgSize, String tag) { msgsWithMQ = MQMessageFactory.getMsgByMQ(mqs, msgSize, tag); msgsWithMQId = new HashMap>(); msgBodys = new ArrayList(); init(); } public Map> getMsgsWithMQ() { return msgsWithMQ; } public Map> getMsgWithMQId() { return msgsWithMQId; } public Collection getMsgBodys() { return msgBodys; } private void init() { for (Entry> mqEntry : msgsWithMQ.entrySet()) { msgsWithMQId.put(mqEntry.getKey().getQueueId(), mqEntry.getValue()); msgBodys.addAll(MQMessageFactory.getMessageBody(mqEntry.getValue())); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/schema/SchemaDefiner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.schema; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListener; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.reflections.Reflections; public class SchemaDefiner { public static final Map, Set> IGNORED_FIELDS = new HashMap<>(); //Use name as the key instead of X.class directly. X.class is not equal to field.getType(). public static final Set FIELD_CLASS_NAMES = new HashSet<>(); public static final List> API_CLASS_LIST = new ArrayList<>(); public static final List> PROTOCOL_CLASS_LIST = new ArrayList<>(); public static void doLoad() { { IGNORED_FIELDS.put(ClientConfig.class, Sets.newHashSet("namesrvAddr", "clientIP", "clientCallbackExecutorThreads")); IGNORED_FIELDS.put(DefaultLitePullConsumer.class, Sets.newHashSet("consumeTimestamp")); IGNORED_FIELDS.put(DefaultMQPushConsumer.class, Sets.newHashSet("consumeTimestamp")); FIELD_CLASS_NAMES.add(String.class.getName()); FIELD_CLASS_NAMES.add(Long.class.getName()); FIELD_CLASS_NAMES.add(Integer.class.getName()); FIELD_CLASS_NAMES.add(Short.class.getName()); FIELD_CLASS_NAMES.add(Byte.class.getName()); FIELD_CLASS_NAMES.add(Double.class.getName()); FIELD_CLASS_NAMES.add(Float.class.getName()); FIELD_CLASS_NAMES.add(Boolean.class.getName()); } { //basic API_CLASS_LIST.add(DefaultMQPushConsumer.class); API_CLASS_LIST.add(DefaultMQProducer.class); API_CLASS_LIST.add(DefaultMQPullConsumer.class); API_CLASS_LIST.add(DefaultLitePullConsumer.class); API_CLASS_LIST.add(DefaultMQAdminExt.class); //argument API_CLASS_LIST.add(Message.class); API_CLASS_LIST.add(MessageQueue.class); API_CLASS_LIST.add(SendCallback.class); API_CLASS_LIST.add(PullCallback.class); API_CLASS_LIST.add(MessageQueueSelector.class); API_CLASS_LIST.add(AllocateMessageQueueStrategy.class); //result API_CLASS_LIST.add(MessageExt.class); API_CLASS_LIST.add(SendResult.class); API_CLASS_LIST.add(SendStatus.class); API_CLASS_LIST.add(PullResult.class); API_CLASS_LIST.add(PullStatus.class); //listener and context API_CLASS_LIST.add(MessageListener.class); API_CLASS_LIST.add(MessageListenerConcurrently.class); API_CLASS_LIST.add(ConsumeConcurrentlyContext.class); API_CLASS_LIST.add(ConsumeConcurrentlyStatus.class); API_CLASS_LIST.add(MessageListenerOrderly.class); API_CLASS_LIST.add(ConsumeOrderlyContext.class); API_CLASS_LIST.add(ConsumeOrderlyStatus.class); //hook and context API_CLASS_LIST.add(RPCHook.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.FilterMessageHook.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.SendMessageHook.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.CheckForbiddenHook.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.ConsumeMessageHook.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.EndTransactionHook.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.FilterMessageContext.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.SendMessageContext.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.ConsumeMessageContext.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.ConsumeMessageContext.class); API_CLASS_LIST.add(org.apache.rocketmq.client.hook.EndTransactionContext.class); } { PROTOCOL_CLASS_LIST.add(RequestCode.class); Reflections reflections = new Reflections("org.apache.rocketmq"); for (Class protocolClass: reflections.getSubTypesOf(CommandCustomHeader.class)) { PROTOCOL_CLASS_LIST.add(protocolClass); } } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/schema/SchemaTools.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.schema; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; import static org.apache.rocketmq.test.schema.SchemaDefiner.FIELD_CLASS_NAMES; import static org.apache.rocketmq.test.schema.SchemaDefiner.IGNORED_FIELDS; public class SchemaTools { public static final String PATH_API = "api"; public static final String PATH_PROTOCOL = "protocol"; public static String isPublicOrPrivate(int modifiers) { if (Modifier.isPublic(modifiers)) { return "public"; } else if (Modifier.isProtected(modifiers)) { return "protected"; } else { return "private"; } } public static TreeMap buildSchemaOfFields(Class apiClass) throws Exception { List fields = new ArrayList<>(); Class current = apiClass; do { fields.addAll(Arrays.asList(current.getDeclaredFields())); } while ((current = current.getSuperclass()) != null && current != Object.class); Object obj = null; if (!apiClass.isInterface() && !Modifier.isAbstract(apiClass.getModifiers()) && !apiClass.isEnum()) { Constructor constructor = null; for (Constructor tmp : apiClass.getConstructors()) { if (constructor == null) { constructor = tmp; } if (tmp.getParameterCount() < constructor.getParameterCount()) { constructor = tmp; } } assert constructor != null; constructor.setAccessible(true); final String msg = constructor.getName(); try { obj = constructor.newInstance(Arrays.stream(constructor.getParameterTypes()).map(x -> { try { if (x.isEnum()) { return x.getEnumConstants()[0]; } if (x == boolean.class) { return false; } else if (x == char.class) { return ""; } else if (x.isPrimitive()) { return 0; } else { return x.newInstance(); } } catch (InstantiationException instantiationException) { return x.cast(null); } catch (Exception e) { throw new RuntimeException(msg + " " + x.getName(), e); } }).toArray()); } catch (Exception e) { throw new RuntimeException(msg, e); } } TreeMap map = new TreeMap<>(); if (apiClass.isEnum()) { for (Object enumObject: apiClass.getEnumConstants()) { String name = ((Enum)enumObject).name(); int ordinal = ((Enum)enumObject).ordinal(); String key = String.format("Field %s", name); String value = String.format("%s %s %s", "public", "int", "" + ordinal); map.put(key, value); } return map; } for (Field field: fields) { if (field.getName().startsWith("$")) { //inner fields continue; } String key = String.format("Field %s", field.getName()); boolean ignore = false; for (Class tmpClass: IGNORED_FIELDS.keySet()) { if (tmpClass.isAssignableFrom(apiClass) && IGNORED_FIELDS.get(tmpClass).contains(field.getName())) { ignore = true; //System.out.printf("Ignore AAA:%s %s %s\n", apiClass.getName(), field.getName(), field.getType().getName()); break; } } if (!field.getType().isEnum() && !field.getType().isPrimitive() && !FIELD_CLASS_NAMES.contains(field.getType().getName())) { //System.out.printf("Ignore BBB:%s %s %s\n", apiClass.getName(), field.getName(), field.getType().getName()); ignore = true; } field.setAccessible(true); Object fieldValue = "null"; try { fieldValue = field.get(obj); } catch (Exception e) { throw new RuntimeException(apiClass.getName() + " " + field.getName(), e); } if (ignore) { //System.out.printf("Ignore:%s %s %s\n", apiClass.getName(), field.getName(), field.getType().getName()); fieldValue = "null"; } String value = String.format("%s %s %s", isPublicOrPrivate(field.getModifiers()), field.getType().getName(), fieldValue); map.put(key, value); } return map; } public static TreeMap buildSchemaOfMethods(Class apiClass) throws Exception { List methods = new ArrayList<>(); Class current = apiClass; do { methods.addAll(Arrays.asList(current.getDeclaredMethods())); } while ((current = current.getSuperclass()) != null && current != Object.class); TreeMap map = new TreeMap<>(); if (apiClass.isEnum()) { return map; } for (Method method: methods) { if (!Modifier.isPublic(method.getModifiers())) { //only care for the public methods continue; } Class[] parameterTypes = method.getParameterTypes(); Arrays.sort(parameterTypes, Comparator.comparing(Class::getName)); Class[] exceptionTypes = method.getExceptionTypes(); Arrays.sort(exceptionTypes, Comparator.comparing(Class::getName)); String key = String.format("Method %s(%s)", method.getName(), Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(","))); String value = String.format("%s throws (%s): %s", isPublicOrPrivate(method.getModifiers()), method.getReturnType().getName(), Arrays.stream(exceptionTypes).map(Class::getName).collect(Collectors.joining(","))); map.put(key, value); } return map; } public static Map> generate(List> classList) throws Exception { Map> schemaMap = new HashMap<>(); for (Class apiClass : classList) { TreeMap map = new TreeMap<>(); map.putAll(buildSchemaOfFields(apiClass)); map.putAll(buildSchemaOfMethods(apiClass)); schemaMap.put(apiClass.getName().replace("org.apache.rocketmq.", ""), map); } return schemaMap; } public static void write(Map> schemaMap, String base, String label) throws Exception { for (Map.Entry> entry : schemaMap.entrySet()) { TreeMap map = entry.getValue(); final String fileName = String.format("%s/%s/%s.schema", base, label, entry.getKey()); File file = new File(fileName); FileOutputStream fileStream = new FileOutputStream(file); Writer writer = new OutputStreamWriter(fileStream, StandardCharsets.UTF_8); writer.write("/*\n" + " * Licensed to the Apache Software Foundation (ASF) under one or more\n" + " * contributor license agreements. See the NOTICE file distributed with\n" + " * this work for additional information regarding copyright ownership.\n" + " * The ASF licenses this file to You under the Apache License, Version 2.0\n" + " * (the \"License\"); you may not use this file except in compliance with\n" + " * the License. You may obtain a copy of the License at\n" + " *\n" + " * http://www.apache.org/licenses/LICENSE-2.0\n" + " *\n" + " * Unless required by applicable law or agreed to in writing, software\n" + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + " * See the License for the specific language governing permissions and\n" + " * limitations under the License.\n" + " */\n\n\n"); for (Map.Entry kv: map.entrySet()) { writer.append(String.format("%s : %s\n", kv.getKey(), kv.getValue())); } writer.close(); } } public static Map> load(String base, String label) throws Exception { File dir = new File(String.format("%s/%s", base, label)); Map> schemaMap = new TreeMap<>(); for (File file: dir.listFiles()) { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); String line = null; TreeMap kvs = new TreeMap<>(); while ((line = br.readLine()) != null) { if (line.contains("*")) { continue; } if (line.trim().isEmpty()) { continue; } String[] items = line.split(":"); kvs.put(items[0].trim(), items[1].trim()); } br.close(); schemaMap.put(file.getName().replace(".schema", ""), kvs); } return schemaMap; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/sendresult/ResultWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.sendresult; import org.apache.rocketmq.client.producer.SendResult; public class ResultWrapper { private boolean sendResult = false; private String msgId = null; private Exception sendException = null; private String brokerIp = null; private SendResult sendResultObj = null; public String getBrokerIp() { return brokerIp; } public void setBrokerIp(String brokerIp) { this.brokerIp = brokerIp; } public boolean isSendResult() { return sendResult; } public void setSendResult(boolean sendResult) { this.sendResult = sendResult; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public Exception getSendException() { return sendException; } public void setSendException(Exception sendException) { this.sendException = sendException; } public SendResult getSendResultObj() { return sendResultObj; } public void setSendResultObj(SendResult sendResultObj) { this.sendResultObj = sendResultObj; } @Override public String toString() { return String.format("sendstatus:%s msgId:%s", sendResult, msgId); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/Condition.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; public interface Condition { boolean meetCondition(); } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/DuplicateMessageInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; public class DuplicateMessageInfo { public void checkDuplicatedMessageInfo(boolean bPrintLog, List> lQueueList) throws IOException { int msgListSize = lQueueList.size(); int maxmsgList = 0; Map msgIdMap = new HashMap(); Map dupMsgMap = new HashMap(); for (int i = 0; i < msgListSize; i++) { if (maxmsgList < lQueueList.get(i).size()) maxmsgList = lQueueList.get(i).size(); } List strBQueue = new LinkedList(); for (int i = 0; i < msgListSize; i++) strBQueue.add(new StringBuilder()); for (int msgListIndex = 0; msgListIndex < maxmsgList; msgListIndex++) { for (int msgQueueListIndex = 0; msgQueueListIndex < msgListSize; msgQueueListIndex++) { if (msgListIndex < lQueueList.get(msgQueueListIndex).size()) { if (msgIdMap.containsKey(lQueueList.get(msgQueueListIndex).get(msgListIndex))) { if (dupMsgMap.containsKey(msgQueueListIndex)) { int dupMsgCount = dupMsgMap.get(msgQueueListIndex); dupMsgCount++; dupMsgMap.remove(msgQueueListIndex); dupMsgMap.put(msgQueueListIndex, dupMsgCount); } else { dupMsgMap.put(msgQueueListIndex, 1); } strBQueue.get(msgQueueListIndex).append("" + msgQueueListIndex + "\t" + msgIdMap.get(lQueueList.get(msgQueueListIndex).get(msgListIndex)) + "\t" + lQueueList.get(msgQueueListIndex).get(msgListIndex) + "\r\n"); } else { msgIdMap.put(lQueueList.get(msgQueueListIndex).get(msgListIndex), msgQueueListIndex); } } } } int msgTotalNum = getMsgTotalNumber(lQueueList); int msgTotalDupNum = getDuplicateMsgNum(dupMsgMap); int msgNoDupNum = msgTotalNum - msgTotalDupNum; float msgDupRate = ((float) msgTotalDupNum / (float) msgTotalNum) * 100.0f; StringBuilder strBuilder = new StringBuilder(); strBuilder.append("msgTotalNum:" + msgTotalNum + "\r\n"); strBuilder.append("msgTotalDupNum:" + msgTotalDupNum + "\r\n"); strBuilder.append("msgNoDupNum:" + msgNoDupNum + "\r\n"); strBuilder.append("msgDupRate" + getFloatNumString(msgDupRate) + "%\r\n"); strBuilder.append("queue\tmsg(dupNum/dupRate)\tdupRate\r\n"); for (int i = 0; i < dupMsgMap.size(); i++) { int msgDupNum = dupMsgMap.get(i); int msgNum = lQueueList.get(i).size(); float msgQueueDupRate = ((float) msgDupNum / (float) msgTotalDupNum) * 100.0f; float msgQueueInnerDupRate = ((float) msgDupNum / (float) msgNum) * 100.0f; strBuilder.append(i + "\t" + msgDupNum + "/" + getFloatNumString(msgQueueDupRate) + "%" + "\t\t" + getFloatNumString(msgQueueInnerDupRate) + "%\r\n"); } System.out.print(strBuilder); String titleString = "queue\tdupQueue\tdupMsg\r\n"; System.out.print(titleString); for (int i = 0; i < msgListSize; i++) System.out.print(strBQueue.get(i).toString()); if (bPrintLog) { String logFileNameStr = "D:" + File.separator + "checkDuplicatedMessageInfo.txt"; File logFileNameFile = new File(logFileNameStr); try (OutputStream out = new FileOutputStream(logFileNameFile, true)) { String strToWrite; byte[] byteToWrite; strToWrite = strBuilder + titleString; for (int i = 0; i < msgListSize; i++) strToWrite += strBQueue.get(i).toString() + "\r\n"; byteToWrite = strToWrite.getBytes(StandardCharsets.UTF_8); out.write(byteToWrite); } } } private int getMsgTotalNumber(List> lQueueList) { int msgTotalNum = 0; for (int i = 0; i < lQueueList.size(); i++) { msgTotalNum += lQueueList.get(i).size(); } return msgTotalNum; } private int getDuplicateMsgNum(Map msgDupMap) { int msgDupNum = 0; for (int i = 0; i < msgDupMap.size(); i++) { msgDupNum += msgDupMap.get(i); } return msgDupNum; } private String getFloatNumString(float fNum) { DecimalFormat dcmFmt = new DecimalFormat("0.00"); return dcmFmt.format(fNum); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/FileUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Map.Entry; import java.util.Properties; public class FileUtil { private static String lineSeparator = System.getProperty("line.separator"); private String filePath = ""; private String fileName = ""; public FileUtil(String filePath, String fileName) { this.filePath = filePath; this.fileName = fileName; } public static void main(String[] args) { String filePath = FileUtil.class.getResource("/").getPath(); String fileName = "test.txt"; FileUtil fileUtil = new FileUtil(filePath, fileName); Properties properties = new Properties(); properties.put("xx", "yy"); properties.put("yy", "xx"); fileUtil.writeProperties(properties); } public void deleteFile() { File file = new File(filePath + File.separator + fileName); if (file.exists()) { file.delete(); } } public void appendFile(String content) { File file = openFile(); String newContent = lineSeparator + content; writeFile(file, newContent, true); } public void coverFile(String content) { File file = openFile(); writeFile(file, content, false); } public void writeProperties(Properties properties) { String content = getPropertiesAsString(properties); this.coverFile(content); } private String getPropertiesAsString(Properties properties) { StringBuilder sb = new StringBuilder(); for (Entry keyEnty : properties.entrySet()) { sb.append(keyEnty.getKey()).append("=").append((String) keyEnty.getValue()) .append(lineSeparator); } return sb.toString(); } private void writeFile(File file, String content, boolean append) { Writer writer = null; try { FileOutputStream fileStream = new FileOutputStream(file, append); writer = new OutputStreamWriter(fileStream, StandardCharsets.UTF_8); writer.write(content); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } private File openFile() { File file = new File(filePath + File.separator + fileName); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } return file; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/MQAdminTestUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingOne; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.protocol.statictopic.TopicRemappingDetailWrapper; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.rpc.ClientMetadata; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.admin.MQAdminUtils; import org.apache.rocketmq.tools.command.CommandUtil; import org.apache.rocketmq.tools.command.topic.RemappingStaticTopicSubCommand; import org.apache.rocketmq.tools.command.topic.UpdateStaticTopicSubCommand; import static org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils.getMappingDetailFromConfig; import static org.awaitility.Awaitility.await; public class MQAdminTestUtils { private static Logger log = LoggerFactory.getLogger(MQAdminTestUtils.class); private static DefaultMQAdminExt mqAdminExt; public static void startAdmin(String nameSrvAddr) throws MQClientException { mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setNamesrvAddr(nameSrvAddr); mqAdminExt.start(); } public static void shutdownAdmin() { mqAdminExt.shutdown(); } public static boolean createTopic(String nameSrvAddr, String clusterName, String topic, int queueNum, Map attributes) { int defaultWaitTime = 30; return createTopic(nameSrvAddr, clusterName, topic, queueNum, attributes, defaultWaitTime); } public static boolean createTopic(String nameSrvAddr, String clusterName, String topic, int queueNum, Map attributes, int waitTimeSec) { DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setInstanceName(UUID.randomUUID().toString()); mqAdminExt.setNamesrvAddr(nameSrvAddr); try { mqAdminExt.start(); mqAdminExt.createTopic(clusterName, topic, queueNum, attributes); } catch (Exception e) { } await().atMost(waitTimeSec, TimeUnit.SECONDS).until(() -> checkTopicExist(mqAdminExt, topic)); ForkJoinPool.commonPool().execute(mqAdminExt::shutdown); return true; } public static boolean checkTopicExist(DefaultMQAdminExt mqAdminExt, String topic) { boolean createResult = false; try { TopicStatsTable topicInfo = mqAdminExt.examineTopicStats(topic); createResult = !topicInfo.getOffsetTable().isEmpty(); } catch (Exception e) { } return createResult; } public static boolean createSub(String nameSrvAddr, String clusterName, SubscriptionGroupConfig config) { boolean createResult = true; DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setNamesrvAddr(nameSrvAddr); try { mqAdminExt.start(); Set masterSet = CommandUtil.fetchMasterAddrByClusterName(mqAdminExt, clusterName); for (String addr : masterSet) { try { mqAdminExt.createAndUpdateSubscriptionGroupConfig(addr, config); log.info("create subscription group {} to {} success.", config.getGroupName(), addr); } catch (Exception e) { e.printStackTrace(); Thread.sleep(1000 * 1); } } } catch (Exception e) { createResult = false; e.printStackTrace(); } ForkJoinPool.commonPool().execute(mqAdminExt::shutdown); return createResult; } public static ClusterInfo getCluster(String nameSrvAddr) { DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setNamesrvAddr(nameSrvAddr); ClusterInfo clusterInfo = null; try { mqAdminExt.start(); clusterInfo = mqAdminExt.examineBrokerClusterInfo(); } catch (Exception e) { e.printStackTrace(); } ForkJoinPool.commonPool().execute(mqAdminExt::shutdown); return clusterInfo; } public static boolean isBrokerExist(String ns, String ip) { ClusterInfo clusterInfo = getCluster(ns); if (clusterInfo == null) { return false; } else { Map brokers = clusterInfo.getBrokerAddrTable(); for (String brokerName : brokers.keySet()) { HashMap brokerIps = brokers.get(brokerName).getBrokerAddrs(); for (long brokerId : brokerIps.keySet()) { if (brokerIps.get(brokerId).contains(ip)) return true; } } } return false; } public static boolean awaitStaticTopicMs(long timeMs, String topic, DefaultMQAdminExt defaultMQAdminExt, MQClientInstance clientInstance) throws Exception { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start <= timeMs) { if (checkStaticTopic(topic, defaultMQAdminExt, clientInstance)) { return true; } Thread.sleep(100); } return false; } //Check if the client metadata is consistent with server metadata public static boolean checkStaticTopic(String topic, DefaultMQAdminExt defaultMQAdminExt, MQClientInstance clientInstance) throws Exception { Map brokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); assert !brokerConfigMap.isEmpty(); TopicQueueMappingUtils.checkPhysicalQueueConsistence(brokerConfigMap); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, brokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(getMappingDetailFromConfig(brokerConfigMap.values()), false, true); for (int i = 0; i < globalIdMap.size(); i++) { TopicQueueMappingOne mappingOne = globalIdMap.get(i); String mockBrokerName = TopicQueueMappingUtils.getMockBrokerName(mappingOne.getMappingDetail().getScope()); String bnameFromRoute = clientInstance.getBrokerNameFromMessageQueue(new MessageQueue(topic, mockBrokerName, mappingOne.getGlobalId())); if (!mappingOne.getBname().equals(bnameFromRoute)) { return false; } } return true; } //should only be test, if some middle operation failed, it does not backup the brokerConfigMap public static Map createStaticTopic(String topic, int queueNum, Set targetBrokers, DefaultMQAdminExt defaultMQAdminExt) throws Exception { Map brokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); assert brokerConfigMap.isEmpty(); TopicQueueMappingUtils.createTopicConfigMapping(topic, queueNum, targetBrokers, brokerConfigMap); MQAdminUtils.completeNoTargetBrokers(brokerConfigMap, defaultMQAdminExt); MQAdminUtils.updateTopicConfigMappingAll(brokerConfigMap, defaultMQAdminExt, false); return brokerConfigMap; } //should only be test, if some middle operation failed, it does not backup the brokerConfigMap public static void remappingStaticTopic(String topic, Set targetBrokers, DefaultMQAdminExt defaultMQAdminExt) throws Exception { Map brokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); assert !brokerConfigMap.isEmpty(); TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.remappingStaticTopic(topic, brokerConfigMap, targetBrokers); MQAdminUtils.completeNoTargetBrokers(brokerConfigMap, defaultMQAdminExt); MQAdminUtils.remappingStaticTopic(topic, wrapper.getBrokerToMapIn(), wrapper.getBrokerToMapOut(), brokerConfigMap, TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE, false, defaultMQAdminExt); } //for test only public static void remappingStaticTopicWithNegativeLogicOffset(String topic, Set targetBrokers, DefaultMQAdminExt defaultMQAdminExt) throws Exception { Map brokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); assert !brokerConfigMap.isEmpty(); TopicRemappingDetailWrapper wrapper = TopicQueueMappingUtils.remappingStaticTopic(topic, brokerConfigMap, targetBrokers); MQAdminUtils.completeNoTargetBrokers(brokerConfigMap, defaultMQAdminExt); remappingStaticTopicWithNegativeLogicOffset(topic, wrapper.getBrokerToMapIn(), wrapper.getBrokerToMapOut(), brokerConfigMap, TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE, false, defaultMQAdminExt); } //for test only public static void remappingStaticTopicWithNegativeLogicOffset(String topic, Set brokersToMapIn, Set brokersToMapOut, Map brokerConfigMap, int blockSeqSize, boolean force, DefaultMQAdminExt defaultMQAdminExt) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { ClientMetadata clientMetadata = MQAdminUtils.getBrokerMetadata(defaultMQAdminExt); MQAdminUtils.checkIfMasterAlive(brokerConfigMap.keySet(), defaultMQAdminExt, clientMetadata); // now do the remapping //Step1: let the new leader can be write without the logicOffset for (String broker : brokersToMapIn) { String addr = clientMetadata.findMasterBrokerAddr(broker); TopicConfigAndQueueMapping configMapping = brokerConfigMap.get(broker); defaultMQAdminExt.createStaticTopic(addr, defaultMQAdminExt.getCreateTopicKey(), configMapping, configMapping.getMappingDetail(), force); } //Step2: forbid the write of old leader for (String broker : brokersToMapOut) { String addr = clientMetadata.findMasterBrokerAddr(broker); TopicConfigAndQueueMapping configMapping = brokerConfigMap.get(broker); defaultMQAdminExt.createStaticTopic(addr, defaultMQAdminExt.getCreateTopicKey(), configMapping, configMapping.getMappingDetail(), force); } //Step5: write the non-target brokers for (String broker : brokerConfigMap.keySet()) { if (brokersToMapIn.contains(broker) || brokersToMapOut.contains(broker)) { continue; } String addr = clientMetadata.findMasterBrokerAddr(broker); TopicConfigAndQueueMapping configMapping = brokerConfigMap.get(broker); defaultMQAdminExt.createStaticTopic(addr, defaultMQAdminExt.getCreateTopicKey(), configMapping, configMapping.getMappingDetail(), force); } } public static void createStaticTopicWithCommand(String topic, int queueNum, Set brokers, String cluster, String nameservers) throws Exception { UpdateStaticTopicSubCommand cmd = new UpdateStaticTopicSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); String[] args; if (cluster != null) { args = new String[]{ "-c", cluster, "-t", topic, "-qn", String.valueOf(queueNum), "-n", nameservers }; } else { String brokerStr = String.join(",", brokers); args = new String[]{ "-b", brokerStr, "-t", topic, "-qn", String.valueOf(queueNum), "-n", nameservers }; } final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), args, cmd.buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { return; } if (commandLine.hasOption('n')) { String namesrvAddr = commandLine.getOptionValue('n'); System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, namesrvAddr); } cmd.execute(commandLine, options, null); } public static void remappingStaticTopicWithCommand(String topic, Set brokers, String cluster, String nameservers) throws Exception { RemappingStaticTopicSubCommand cmd = new RemappingStaticTopicSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); String[] args; if (cluster != null) { args = new String[]{ "-c", cluster, "-t", topic, "-n", nameservers }; } else { String brokerStr = String.join(",", brokers); args = new String[]{ "-b", brokerStr, "-t", topic, "-n", nameservers }; } final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), args, cmd.buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { return; } if (commandLine.hasOption('n')) { String namesrvAddr = commandLine.getOptionValue('n'); System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, namesrvAddr); } cmd.execute(commandLine, options, null); } public static ConsumeStats examineConsumeStats(String brokerAddr, String topic, String group) { ConsumeStats consumeStats = null; try { consumeStats = mqAdminExt.examineConsumeStats(brokerAddr, group, topic, 3000); } catch (Exception ignored) { } return consumeStats; } /** * Delete topic from broker only without cleaning route info from name server forwardly * * @param nameSrvAddr the namesrv addr to connect * @param brokerName the specific broker * @param topic the specific topic to delete */ public static void deleteTopicFromBrokerOnly(String nameSrvAddr, String brokerName, String topic) { DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setNamesrvAddr(nameSrvAddr); try { mqAdminExt.start(); String brokerAddr = CommandUtil.fetchMasterAddrByBrokerName(mqAdminExt, brokerName); mqAdminExt.deleteTopicInBroker(Collections.singleton(brokerAddr), topic); } catch (Exception ignored) { } finally { mqAdminExt.shutdown(); } } public static TopicRouteData examineTopicRouteInfo(String nameSrvAddr, String topicName) { DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); mqAdminExt.setNamesrvAddr(nameSrvAddr); TopicRouteData route = null; try { mqAdminExt.start(); route = mqAdminExt.examineTopicRouteInfo(topicName); } catch (Exception ignored) { } finally { mqAdminExt.shutdown(); } return route; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/MQRandomUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; public class MQRandomUtils { public static String getRandomTopic() { return RandomUtils.getStringByUUID(); } public static String getRandomConsumerGroup() { return RandomUtils.getStringByUUID(); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/MQWait.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.listener.AbstractListener; import static com.google.common.truth.Truth.assertThat; public class MQWait { private static Logger logger = LoggerFactory.getLogger(MQWait.class); public static boolean waitConsumeAll(int timeoutMills, Collection allSendMsgs, AbstractListener... listeners) { boolean recvAll = false; long startTime = System.currentTimeMillis(); Collection noDupMsgs = new ArrayList(); while (!recvAll) { if ((System.currentTimeMillis() - startTime) < timeoutMills) { noDupMsgs.clear(); try { for (AbstractListener listener : listeners) { Collection recvMsgs = Collections .synchronizedCollection(listener.getAllUndupMsgBody()); noDupMsgs.addAll(VerifyUtils.getFilterdMessage(allSendMsgs, recvMsgs)); } } catch (Exception e) { e.printStackTrace(); } try { assertThat(noDupMsgs).containsAllIn(allSendMsgs); recvAll = true; break; } catch (Throwable e) { } TestUtil.waitForMonment(500); } else { logger.error(String.format( "timeout but still not receive all messages,expectSize[%s],realSize[%s]", allSendMsgs.size(), noDupMsgs.size())); break; } } return recvAll; } public static void setCondition(Condition condition, int waitTimeMills, int intervalMills) { long startTime = System.currentTimeMillis(); while (!condition.meetCondition()) { if (System.currentTimeMillis() - startTime > waitTimeMills) { logger.error("time out,but contidion still not meet!"); break; } else { TestUtil.waitForMonment(intervalMills); } } } public static void main(String[] args) { long start = System.currentTimeMillis(); MQWait.setCondition(new Condition() { int i = 0; public boolean meetCondition() { i++; return i == 100; } }, 10 * 1000, 200); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.util.Collection; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.UUID; public final class RandomUtil { private static final int UNICODE_START = '\u4E00'; private static final int UNICODE_END = '\u9FA0'; private static Random rd = new Random(); private RandomUtil() { } public static long getLong() { return rd.nextLong(); } public static long getLongMoreThanZero() { long res = rd.nextLong(); while (res <= 0) { res = rd.nextLong(); } return res; } public static long getLongLessThan(long n) { long res = rd.nextLong(); return res % n; } public static long getLongMoreThanZeroLessThan(long n) { long res = getLongLessThan(n); while (res <= 0) { res = getLongLessThan(n); } return res; } public static long getLongBetween(long n, long m) { if (m <= n) { return n; } long res = getLongMoreThanZero(); return n + res % (m - n); } public static int getInteger() { return rd.nextInt(); } public static int getIntegerMoreThanZero() { int res = rd.nextInt(); while (res <= 0) { res = rd.nextInt(); } return res; } public static int getIntegerLessThan(int n) { int res = rd.nextInt(); return res % n; } public static int getIntegerMoreThanZeroLessThan(int n) { int res = rd.nextInt(n); while (res == 0) { res = rd.nextInt(n); } return res; } public static int getIntegerBetween(int n, int m)// m��ֵ����Ϊ���أ� { if (m == n) { return n; } int res = getIntegerMoreThanZero(); return n + res % (m - n); } private static char getChar(int[] arg) { int size = arg.length; int c = rd.nextInt(size / 2); c = c * 2; return (char) (getIntegerBetween(arg[c], arg[c + 1])); } private static String getString(int n, int[] arg) { StringBuilder res = new StringBuilder(); for (int i = 0; i < n; i++) { res.append(getChar(arg)); } return res.toString(); } public static String getStringWithCharacter(int n) { int[] arg = new int[] {'a', 'z' + 1, 'A', 'Z' + 1}; return getString(n, arg); } public static String getStringWithNumber(int n) { int[] arg = new int[] {'0', '9' + 1}; return getString(n, arg); } public static String getStringWithNumAndCha(int n) { int[] arg = new int[] {'a', 'z' + 1, 'A', 'Z' + 1, '0', '9' + 1}; return getString(n, arg); } public static String getStringShortenThan(int n) { int len = getIntegerMoreThanZeroLessThan(n); return getStringWithCharacter(len); } public static String getStringWithNumAndChaShortenThan(int n) { int len = getIntegerMoreThanZeroLessThan(n); return getStringWithNumAndCha(len); } public static String getStringBetween(int n, int m) { int len = getIntegerBetween(n, m); return getStringWithCharacter(len); } public static String getStringWithNumAndChaBetween(int n, int m) { int len = getIntegerBetween(n, m); return getStringWithNumAndCha(len); } public static String getStringWithPrefix(int n, String prefix) { int len = prefix.length(); if (n <= len) return prefix; else { len = n - len; StringBuilder res = new StringBuilder(prefix); res.append(getStringWithCharacter(len)); return res.toString(); } } public static String getStringWithSuffix(int n, String suffix) { int len = suffix.length(); if (n <= len) return suffix; else { len = n - len; StringBuilder res = new StringBuilder(); res.append(getStringWithCharacter(len)); res.append(suffix); return res.toString(); } } public static String getStringWithBoth(int n, String prefix, String suffix) { int len = prefix.length() + suffix.length(); StringBuilder res = new StringBuilder(prefix); if (n <= len) return res.append(suffix).toString(); else { len = n - len; res.append(getStringWithCharacter(len)); res.append(suffix); return res.toString(); } } public static String getCheseWordWithPrifix(int n, String prefix) { int len = prefix.length(); if (n <= len) return prefix; else { len = n - len; StringBuilder res = new StringBuilder(prefix); res.append(getCheseWord(len)); return res.toString(); } } public static String getCheseWordWithSuffix(int n, String suffix) { int len = suffix.length(); if (n <= len) return suffix; else { len = n - len; StringBuilder res = new StringBuilder(); res.append(getCheseWord(len)); res.append(suffix); return res.toString(); } } public static String getCheseWordWithBoth(int n, String prefix, String suffix) { int len = prefix.length() + suffix.length(); StringBuilder res = new StringBuilder(prefix); if (n <= len) return res.append(suffix).toString(); else { len = n - len; res.append(getCheseWord(len)); res.append(suffix); return res.toString(); } } public static String getCheseWord(int len) { StringBuilder res = new StringBuilder(); for (int i = 0; i < len; i++) { char str = getCheseChar(); res.append(str); } return res.toString(); } private static char getCheseChar() { return (char) (UNICODE_START + rd.nextInt(UNICODE_END - UNICODE_START)); } public static boolean getBoolean() { return getIntegerMoreThanZeroLessThan(3) == 1; } public static String getStringByUUID() { return UUID.randomUUID().toString(); } public static int[] getRandomArray(int min, int max, int n) { int len = max - min + 1; if (max < min || n > len) { return null; } int[] source = new int[len]; for (int i = min; i < min + len; i++) { source[i - min] = i; } int[] result = new int[n]; Random rd = new Random(); int index = 0; for (int i = 0; i < result.length; i++) { index = rd.nextInt(len--); result[i] = source[index]; source[index] = source[len]; } return result; } public static Collection getRandomCollection(int min, int max, int n) { Set res = new HashSet(); int mx = max; int mn = min; if (n == (max + 1 - min)) { for (int i = 1; i <= n; i++) { res.add(i); } return res; } for (int i = 0; i < n; i++) { int v = getIntegerBetween(mn, mx); if (v == mx) { mx--; } if (v == mn) { mn++; } while (res.contains(v)) { v = getIntegerBetween(mn, mx); if (v == mx) { mx = v; } if (v == mn) { mn = v; } } res.add(v); } return res; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/RandomUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.util.Random; import java.util.UUID; public class RandomUtils { private static final int UNICODE_START = '\u4E00'; private static final int UNICODE_END = '\u9FA0'; private static Random rd = new Random(); private RandomUtils() { } public static String getStringByUUID() { return UUID.randomUUID().toString(); } public static String getCheseWord(int len) { StringBuilder res = new StringBuilder(); for (int i = 0; i < len; ++i) { char str = getCheseChar(); res.append(str); } return res.toString(); } public static String getStringWithNumber(int n) { int[] arg = new int[] {'0', '9' + 1}; return getString(n, arg); } public static String getStringWithCharacter(int n) { int[] arg = new int[] {'a', 'z' + 1, 'A', 'Z' + 1}; return getString(n, arg); } private static String getString(int n, int[] arg) { StringBuilder res = new StringBuilder(); for (int i = 0; i < n; i++) { res.append(getChar(arg)); } return res.toString(); } private static char getChar(int[] arg) { int size = arg.length; int c = rd.nextInt(size / 2); c = c * 2; return (char) (getIntegerBetween(arg[c], arg[c + 1])); } public static int getIntegerBetween(int n, int m) { if (m == n) { return n; } int res = getIntegerMoreThanZero(); return n + res % (m - n); } public static int getIntegerMoreThanZero() { int res = rd.nextInt(); while (res <= 0) { res = rd.nextInt(); } return res; } private static char getCheseChar() { return (char) (UNICODE_START + rd.nextInt(UNICODE_END - UNICODE_START)); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/StatUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Generated; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import static java.math.BigDecimal.ROUND_HALF_UP; @Generated("StatUtil") public class StatUtil { private static Logger sysLogger = LoggerFactory.getLogger(StatUtil.class); private static Logger logger = LoggerFactory.getLogger("StatLogger"); private static final int MAX_KEY_NUM = Integer.parseInt(System.getProperty("stat.util.key.max.num", "10000")); private static volatile ConcurrentMap invokeCache = new ConcurrentHashMap<>(64); private static volatile ConcurrentMap> secondInvokeCache = new ConcurrentHashMap<>( 64); private static final int STAT_WINDOW_SECONDS = Integer.parseInt(System.getProperty("stat.win.seconds", "60")); private static final String SPLITTER = "|"; private static ScheduledExecutorService daemon = Executors.newSingleThreadScheduledExecutor(); static class Invoke { AtomicLong totalPv = new AtomicLong(); AtomicLong failPv = new AtomicLong(); AtomicLong sumRt = new AtomicLong(); AtomicLong maxRt = new AtomicLong(); AtomicLong minRt = new AtomicLong(); AtomicInteger topSecondPv = new AtomicInteger(); AtomicInteger secondPv = new AtomicInteger(); AtomicLong second = new AtomicLong(System.currentTimeMillis() / 1000L); } static class SecondInvoke implements Comparable { AtomicLong total = new AtomicLong(); AtomicLong fail = new AtomicLong(); AtomicLong sumRt = new AtomicLong(); AtomicLong maxRt = new AtomicLong(); AtomicLong minRt = new AtomicLong(); Long second = nowSecond(); @Override public int compareTo(SecondInvoke o) { return o.second.compareTo(second); } } static { daemon.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { printInvokeStat(); printSecondInvokeStat(); } catch (Exception e) { logger.error("", e); } } }, STAT_WINDOW_SECONDS, STAT_WINDOW_SECONDS, TimeUnit.SECONDS); } private static void printInvokeStat() { Map tmp = invokeCache; invokeCache = new ConcurrentHashMap<>(64); sysLogger.warn("printInvokeStat key count:{}", tmp.size()); for (Map.Entry entry : tmp.entrySet()) { String key = entry.getKey(); Invoke invoke = entry.getValue(); logger.warn("{}", buildLog(key, invoke.topSecondPv.get(), invoke.totalPv.get(), invoke.failPv.get(), invoke.minRt.get(), invoke.maxRt.get(), invoke.sumRt.get())); } } private static void printSecondInvokeStat() { sysLogger.warn("printSecondInvokeStat key count:{}", secondInvokeCache.size()); for (Map.Entry> entry : secondInvokeCache.entrySet()) { String key = entry.getKey(); Map secondInvokeMap = entry.getValue(); long totalPv = 0L; long failPv = 0L; long topSecondPv = 0L; long sumRt = 0L; long maxRt = 0L; long minRt = 0L; for (Map.Entry invokeEntry : secondInvokeMap.entrySet()) { long second = invokeEntry.getKey(); SecondInvoke secondInvoke = invokeEntry.getValue(); if (nowSecond() - second >= STAT_WINDOW_SECONDS) { secondInvokeMap.remove(second); continue; } long secondPv = secondInvoke.total.get(); totalPv += secondPv; failPv += secondInvoke.fail.get(); sumRt += secondInvoke.sumRt.get(); if (maxRt < secondInvoke.maxRt.get()) { maxRt = secondInvoke.maxRt.get(); } if (minRt > secondInvoke.minRt.get()) { minRt = secondInvoke.minRt.get(); } if (topSecondPv < secondPv) { topSecondPv = secondPv; } } if (secondInvokeMap.isEmpty()) { secondInvokeCache.remove(key); continue; } logger.warn("{}", buildLog(key, topSecondPv, totalPv, failPv, minRt, maxRt, sumRt)); } } private static String buildLog(String key, long topSecondPv, long totalPv, long failPv, long minRt, long maxRt, long sumRt) { StringBuilder sb = new StringBuilder(); sb.append(SPLITTER); sb.append(key); sb.append(SPLITTER); sb.append(topSecondPv); sb.append(SPLITTER); int tps = new BigDecimal(totalPv).divide(new BigDecimal(STAT_WINDOW_SECONDS), ROUND_HALF_UP).intValue(); sb.append(tps); sb.append(SPLITTER); sb.append(totalPv); sb.append(SPLITTER); sb.append(failPv); sb.append(SPLITTER); sb.append(minRt); sb.append(SPLITTER); long avg = new BigDecimal(sumRt).divide(new BigDecimal(totalPv), ROUND_HALF_UP).longValue(); sb.append(avg); sb.append(SPLITTER); sb.append(maxRt); return sb.toString(); } public static String buildKey(String... keys) { if (keys == null || keys.length <= 0) { return null; } StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append(key); sb.append(","); } sb.deleteCharAt(sb.length() - 1); return sb.toString(); } public static void addInvoke(String key, long rt) { addInvoke(key, rt, true); } private static Invoke getAndSetInvoke(String key) { Invoke invoke = invokeCache.get(key); if (invoke == null) { invokeCache.putIfAbsent(key, new Invoke()); } return invokeCache.get(key); } public static void addInvoke(String key, int num, long rt, boolean success) { if (invokeCache.size() > MAX_KEY_NUM || secondInvokeCache.size() > MAX_KEY_NUM) { return; } Invoke invoke = getAndSetInvoke(key); if (invoke == null) { return; } invoke.totalPv.getAndAdd(num); if (!success) { invoke.failPv.getAndAdd(num); } long now = nowSecond(); AtomicLong oldSecond = invoke.second; if (oldSecond.get() == now) { invoke.secondPv.getAndAdd(num); } else { if (oldSecond.compareAndSet(oldSecond.get(), now)) { if (invoke.secondPv.get() > invoke.topSecondPv.get()) { invoke.topSecondPv.set(invoke.secondPv.get()); } invoke.secondPv.set(num); } else { invoke.secondPv.getAndAdd(num); } } invoke.sumRt.addAndGet(rt); if (invoke.maxRt.get() < rt) { invoke.maxRt.set(rt); } if (invoke.minRt.get() > rt) { invoke.minRt.set(rt); } } public static void addInvoke(String key, long rt, boolean success) { if (invokeCache.size() > MAX_KEY_NUM || secondInvokeCache.size() > MAX_KEY_NUM) { return; } Invoke invoke = getAndSetInvoke(key); if (invoke == null) { return; } invoke.totalPv.getAndIncrement(); if (!success) { invoke.failPv.getAndIncrement(); } long now = nowSecond(); AtomicLong oldSecond = invoke.second; if (oldSecond.get() == now) { invoke.secondPv.getAndIncrement(); } else { if (oldSecond.compareAndSet(oldSecond.get(), now)) { if (invoke.secondPv.get() > invoke.topSecondPv.get()) { invoke.topSecondPv.set(invoke.secondPv.get()); } invoke.secondPv.set(1); } else { invoke.secondPv.getAndIncrement(); } } invoke.sumRt.addAndGet(rt); if (invoke.maxRt.get() < rt) { invoke.maxRt.set(rt); } if (invoke.minRt.get() > rt) { invoke.minRt.set(rt); } } public static SecondInvoke getAndSetSecondInvoke(String key) { if (!secondInvokeCache.containsKey(key)) { secondInvokeCache.putIfAbsent(key, new ConcurrentHashMap<>(STAT_WINDOW_SECONDS)); } Map secondInvokeMap = secondInvokeCache.get(key); if (secondInvokeMap == null) { return null; } long second = nowSecond(); if (!secondInvokeMap.containsKey(second)) { secondInvokeMap.putIfAbsent(second, new SecondInvoke()); } return secondInvokeMap.get(second); } public static void addSecondInvoke(String key, long rt) { addSecondInvoke(key, rt, true); } public static void addSecondInvoke(String key, long rt, boolean success) { if (invokeCache.size() > MAX_KEY_NUM || secondInvokeCache.size() > MAX_KEY_NUM) { return; } SecondInvoke secondInvoke = getAndSetSecondInvoke(key); if (secondInvoke == null) { return; } secondInvoke.total.addAndGet(1); if (!success) { secondInvoke.fail.addAndGet(1); } secondInvoke.sumRt.addAndGet(rt); if (secondInvoke.maxRt.get() < rt) { secondInvoke.maxRt.set(rt); } if (secondInvoke.minRt.get() > rt) { secondInvoke.minRt.set(rt); } } public static void addPv(String key, long totalPv) { addPv(key, totalPv, true); } public static void addPv(String key, long totalPv, boolean success) { if (invokeCache.size() > MAX_KEY_NUM || secondInvokeCache.size() > MAX_KEY_NUM) { return; } if (totalPv <= 0) { return; } Invoke invoke = getAndSetInvoke(key); if (invoke == null) { return; } invoke.totalPv.addAndGet(totalPv); if (!success) { invoke.failPv.addAndGet(totalPv); } long now = nowSecond(); AtomicLong oldSecond = invoke.second; if (oldSecond.get() == now) { invoke.secondPv.addAndGet((int)totalPv); } else { if (oldSecond.compareAndSet(oldSecond.get(), now)) { if (invoke.secondPv.get() > invoke.topSecondPv.get()) { invoke.topSecondPv.set(invoke.secondPv.get()); } invoke.secondPv.set((int)totalPv); } else { invoke.secondPv.addAndGet((int)totalPv); } } } public static void addSecondPv(String key, long totalPv) { addSecondPv(key, totalPv, true); } public static void addSecondPv(String key, long totalPv, boolean success) { if (invokeCache.size() > MAX_KEY_NUM || secondInvokeCache.size() > MAX_KEY_NUM) { return; } if (totalPv <= 0) { return; } SecondInvoke secondInvoke = getAndSetSecondInvoke(key); if (secondInvoke == null) { return; } secondInvoke.total.addAndGet(totalPv); if (!success) { secondInvoke.fail.addAndGet(totalPv); } } public static boolean isOverFlow(String key, int tps) { return nowTps(key) >= tps; } public static int nowTps(String key) { Map secondInvokeMap = secondInvokeCache.get(key); if (secondInvokeMap != null) { SecondInvoke secondInvoke = secondInvokeMap.get(nowSecond()); if (secondInvoke != null) { return (int)secondInvoke.total.get(); } } Invoke invoke = invokeCache.get(key); if (invoke == null) { return 0; } AtomicLong oldSecond = invoke.second; if (oldSecond.get() == nowSecond()) { return invoke.secondPv.get(); } return 0; } public static int totalPvInWindow(String key, int windowSeconds) { List list = secondInvokeList(key, windowSeconds); long totalPv = 0; for (int i = 0; i < windowSeconds && i < list.size(); i++) { totalPv += list.get(i).total.get(); } return (int)totalPv; } public static int failPvInWindow(String key, int windowSeconds) { List list = secondInvokeList(key, windowSeconds); long failPv = 0; for (int i = 0; i < windowSeconds && i < list.size(); i++) { failPv += list.get(i).fail.get(); } return (int)failPv; } public static int topTpsInWindow(String key, int windowSeconds) { List list = secondInvokeList(key, windowSeconds); long topTps = 0; for (int i = 0; i < windowSeconds && i < list.size(); i++) { long secondPv = list.get(i).total.get(); if (topTps < secondPv) { topTps = secondPv; } } return (int)topTps; } public static int avgRtInWindow(String key, int windowSeconds) { List list = secondInvokeList(key, windowSeconds); long sumRt = 0; long totalPv = 0; for (int i = 0; i < windowSeconds && i < list.size(); i++) { sumRt += list.get(i).sumRt.get(); totalPv += list.get(i).total.get(); } if (totalPv <= 0) { return 0; } long avg = new BigDecimal(sumRt).divide(new BigDecimal(totalPv), ROUND_HALF_UP).longValue(); return (int)avg; } public static int maxRtInWindow(String key, int windowSeconds) { List list = secondInvokeList(key, windowSeconds); long maxRt = 0; long totalPv = 0; for (int i = 0; i < windowSeconds && i < list.size(); i++) { if (maxRt < list.get(i).maxRt.get()) { maxRt = list.get(i).maxRt.get(); } } return (int)maxRt; } public static int minRtInWindow(String key, int windowSeconds) { List list = secondInvokeList(key, windowSeconds); long minRt = 0; long totalPv = 0; for (int i = 0; i < windowSeconds && i < list.size(); i++) { if (minRt < list.get(i).minRt.get()) { minRt = list.get(i).minRt.get(); } } return (int)minRt; } private static List secondInvokeList(String key, int windowSeconds) { if (windowSeconds > STAT_WINDOW_SECONDS || windowSeconds <= 0) { throw new IllegalArgumentException("windowSeconds Must Not be great than " + STAT_WINDOW_SECONDS); } Map secondInvokeMap = secondInvokeCache.get(key); if (secondInvokeMap == null || secondInvokeMap.isEmpty()) { return new ArrayList<>(); } List list = new ArrayList<>(); list.addAll(secondInvokeMap.values()); Collections.sort(list); return list; } private static long nowSecond() { return System.currentTimeMillis() / 1000L; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/TestUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; public final class TestUtil { private TestUtil() { } public static Long parseStringToLong(String s, Long defval) { Long val = defval; try { val = Long.parseLong(s); } catch (NumberFormatException e) { val = defval; } return val; } public static Integer parseStringToInteger(String s, Integer defval) { Integer val = defval; try { val = Integer.parseInt(s); } catch (NumberFormatException e) { val = defval; } return val; } public static String addQuoteToParamater(String param) { StringBuilder sb = new StringBuilder("'"); sb.append(param).append("'"); return sb.toString(); } public static void waitForMonment(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } public static void waitForSeconds(long time) { try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } public static void waitForMinutes(long time) { try { TimeUnit.MINUTES.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } public static void waitForInputQuit() { waitForInput("quit"); } public static void waitForInput(String keyWord) { waitForInput(keyWord, String.format("The thread will wait until you input stop command[%s]:", keyWord)); } public static void waitForInput(String keyWord, String info) { try { byte[] b = new byte[1024]; int n = System.in.read(b); String s = new String(b, 0, n - 1, StandardCharsets.UTF_8).replace("\r", "").replace("\n", ""); while (!s.equals(keyWord)) { n = System.in.read(b); s = new String(b, 0, n - 1, StandardCharsets.UTF_8); } } catch (IOException e) { e.printStackTrace(); } } public static > Map sortByValue(Map map) { List> list = new LinkedList>(map.entrySet()); list.sort(Map.Entry.comparingByValue()); Map result = new LinkedHashMap(); for (Map.Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/TestUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.util.concurrent.TimeUnit; public class TestUtils { public static void waitForMoment(long time) { try { Thread.sleep(time); } catch (InterruptedException var3) { var3.printStackTrace(); } } public static void waitForSeconds(long time) { try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException var3) { var3.printStackTrace(); } } public static void waitForMinutes(long time) { try { TimeUnit.MINUTES.sleep(time); } catch (InterruptedException var3) { var3.printStackTrace(); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/VerifyUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class VerifyUtils { private static Logger logger = LoggerFactory.getLogger(VerifyUtils.class); public static int verify(Collection sendMsgs, Collection recvMsgs) { int miss = 0; for (Object msg : sendMsgs) { if (!recvMsgs.contains(msg)) { miss++; } } return miss; } public static Collection getFilterdMessage(Collection sendMsgs, Collection recvMsgs) { Collection recvMsgsSync = Collections.synchronizedCollection(recvMsgs); Collection filteredMsgs = new ArrayList(); int filterNum = 0; for (Object msg : recvMsgsSync) { if (sendMsgs.contains(msg)) { filteredMsgs.add(msg); } else { filterNum++; } } logger.info(String.format("[%s] messages is filtered!", filterNum)); return filteredMsgs; } public static int verifyUserProperty(Collection sendMsgs, Collection recvMsgs) { return 0; } public static void verifyMessageQueueId(int expectId, Collection msgs) { for (Object msg : msgs) { MessageExt msgEx = (MessageExt) msg; assert expectId == msgEx.getQueueId(); } } public static boolean verifyBalance(int msgSize, float error, int... recvSize) { boolean balance = true; int evenSize = msgSize / recvSize.length; for (int size : recvSize) { if (Math.abs(size - evenSize) > error * evenSize) { balance = false; break; } } return balance; } public static boolean verifyBalance(int msgSize, int... recvSize) { return verifyBalance(msgSize, 0.1f, recvSize); } public static boolean verifyDelay(long delayTimeMills, long nextLevelDelayTimeMills, Collection recvMsgTimes) { boolean delay = true; for (Object timeObj : recvMsgTimes) { long time = (Long) timeObj; if (time < delayTimeMills || time > nextLevelDelayTimeMills) { delay = false; logger.info(String.format("delay error:%s", Math.abs(time - delayTimeMills))); break; } } return delay; } public static boolean verifyOrder(Collection> queueMsgs) { for (Collection msgs : queueMsgs) { if (!verifyOrderMsg(msgs)) { return false; } } return true; } public static boolean verifyOrderMsg(Collection msgs) { int min = Integer.MIN_VALUE; int curr; if (msgs.size() == 0 || msgs.size() == 1) { return true; } else { for (Object msg : msgs) { curr = Integer.parseInt((String) msg); if (curr < min) { return false; } else { min = curr; } } } return true; } public static boolean verifyRT(Collection rts, long maxRTMills) { boolean rtExpect = true; for (Object obj : rts) { long rt = (Long) obj; if (rt > maxRTMills) { rtExpect = false; logger.info(String.format("%s greater thran maxtRT:%s!", rt, maxRTMills)); } } return rtExpect; } public static void main(String[] args) { verifyBalance(400, 0.1f, 230, 190); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/data/collect/DataCollector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.data.collect; import java.util.Collection; public interface DataCollector { void resetData(); Collection getAllData(); Collection getAllDataWithoutDuplicate(); void addData(Object data); long getDataSizeWithoutDuplicate(); long getDataSize(); boolean isRepeatedData(Object data); int getRepeatedTimeForData(Object data); void removeData(Object data); void lockIncrement(); void unlockIncrement(); } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/data/collect/DataCollectorManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.data.collect; import java.util.HashMap; import java.util.Map; import org.apache.rocketmq.test.util.data.collect.impl.ListDataCollectorImpl; import org.apache.rocketmq.test.util.data.collect.impl.MapDataCollectorImpl; public final class DataCollectorManager { private static DataCollectorManager instance = new DataCollectorManager(); private Map collectMap = new HashMap(); private Object lock = new Object(); private DataCollectorManager() { } public static DataCollectorManager getInstance() { return instance; } public DataCollector fetchDataCollector(String key) { String realKey = key; if (!collectMap.containsKey(realKey)) { synchronized (lock) { if (!collectMap.containsKey(realKey)) { DataCollector collect = (DataCollector) new MapDataCollectorImpl(); collectMap.put(realKey, collect); } } } return collectMap.get(realKey); } public DataCollector fetchMapDataCollector(String key) { String realKey = key; if (!collectMap.containsKey(realKey) || collectMap.get(realKey) instanceof ListDataCollectorImpl) { synchronized (lock) { if (!collectMap.containsKey(realKey) || collectMap.get(realKey) instanceof ListDataCollectorImpl) { DataCollector collect = null; if (collectMap.containsKey(realKey)) { DataCollector src = collectMap.get(realKey); collect = new MapDataCollectorImpl(src.getAllData()); } else { collect = new MapDataCollectorImpl(); } collectMap.put(realKey, collect); } } } return collectMap.get(realKey); } public DataCollector fetchListDataCollector(String key) { String realKey = key; if (!collectMap.containsKey(realKey) || collectMap.get(realKey) instanceof MapDataCollectorImpl) { synchronized (lock) { if (!collectMap.containsKey(realKey) || collectMap.get(realKey) instanceof MapDataCollectorImpl) { DataCollector collect = null; if (collectMap.containsKey(realKey)) { DataCollector src = collectMap.get(realKey); collect = new ListDataCollectorImpl(src.getAllData()); } else { collect = new ListDataCollectorImpl(); } collectMap.put(realKey, collect); } } } return collectMap.get(realKey); } public void resetDataCollect(String key) { if (collectMap.containsKey(key)) { collectMap.get(key).resetData(); } } public void resetAll() { for (Map.Entry entry : collectMap.entrySet()) { entry.getValue().resetData(); } } public void removeDataCollect(String key) { collectMap.remove(key); } public void removeAll() { collectMap.clear(); } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/data/collect/DataFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.data.collect; public interface DataFilter { } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/data/collect/impl/ListDataCollectorImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.data.collect.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import org.apache.rocketmq.test.util.data.collect.DataCollector; public class ListDataCollectorImpl implements DataCollector { private List datas = new ArrayList(); private boolean lock = false; public ListDataCollectorImpl() { } public ListDataCollectorImpl(Collection datas) { for (Object data : datas) { addData(data); } } @Override public Collection getAllData() { return datas; } @Override public synchronized void resetData() { datas.clear(); unlockIncrement(); } @Override public long getDataSizeWithoutDuplicate() { return getAllDataWithoutDuplicate().size(); } @Override public synchronized void addData(Object data) { if (lock) { return; } datas.add(data); } @Override public long getDataSize() { return datas.size(); } @Override public boolean isRepeatedData(Object data) { return Collections.frequency(datas, data) == 1; } @Override public synchronized Collection getAllDataWithoutDuplicate() { return new HashSet(datas); } @Override public int getRepeatedTimeForData(Object data) { int res = 0; for (Object obj : datas) { if (obj.equals(data)) { res++; } } return res; } @Override public synchronized void removeData(Object data) { datas.remove(data); } @Override public void lockIncrement() { lock = true; } @Override public void unlockIncrement() { lock = false; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/data/collect/impl/MapDataCollectorImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.data.collect.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.test.util.data.collect.DataCollector; public class MapDataCollectorImpl implements DataCollector { private Map datas = new ConcurrentHashMap(); private boolean lock = false; public MapDataCollectorImpl() { } public MapDataCollectorImpl(Collection datas) { for (Object data : datas) { addData(data); } } @Override public synchronized void addData(Object data) { if (lock) { return; } if (datas.containsKey(data)) { datas.get(data).addAndGet(1); } else { datas.put(data, new AtomicInteger(1)); } } @Override public Collection getAllData() { List lst = new ArrayList(); for (Entry entry : datas.entrySet()) { for (int i = 0; i < entry.getValue().get(); i++) { lst.add(entry.getKey()); } } return lst; } @Override public long getDataSizeWithoutDuplicate() { return datas.keySet().size(); } @Override public void resetData() { datas.clear(); unlockIncrement(); } @Override public long getDataSize() { long sum = 0; for (AtomicInteger count : datas.values()) { sum = sum + count.get(); } return sum; } @Override public boolean isRepeatedData(Object data) { if (datas.containsKey(data)) { return datas.get(data).get() == 1; } return false; } @Override public Collection getAllDataWithoutDuplicate() { return datas.keySet(); } @Override public int getRepeatedTimeForData(Object data) { if (datas.containsKey(data)) { return datas.get(data).intValue(); } return 0; } @Override public void removeData(Object data) { datas.remove(data); } @Override public void lockIncrement() { lock = true; } @Override public void unlockIncrement() { lock = false; } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/parallel/ParallelTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.parallel; import java.util.concurrent.CountDownLatch; public abstract class ParallelTask extends Thread { private CountDownLatch latch = null; public CountDownLatch getLatch() { return latch; } public void setLatch(CountDownLatch latch) { this.latch = latch; } public abstract void execute(); @Override public void run() { this.execute(); if (latch != null) { latch.countDown(); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/parallel/ParallelTaskExecutor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.parallel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ParallelTaskExecutor { public List tasks = new ArrayList(); public ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); public CountDownLatch latch = null; public ParallelTaskExecutor() { } public void pushTask(ParallelTask task) { tasks.add(task); } public void startBlock() { init(); startTask(); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public void startNoBlock() { for (ParallelTask task : tasks) { cachedThreadPool.execute(task); } } private void init() { latch = new CountDownLatch(tasks.size()); for (ParallelTask task : tasks) { task.setLatch(latch); } } private void startTask() { for (ParallelTask task : tasks) { task.start(); } } } ================================================ FILE: test/src/main/java/org/apache/rocketmq/test/util/parallel/Task4Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.util.parallel; public class Task4Test extends ParallelTask { private String name = ""; public Task4Test(String name) { this.name = name; } @Override public void execute() { } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/autoswitchrole/AutoSwitchRoleBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.autoswitchrole; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; public class AutoSwitchRoleBase { protected static final String STORE_PATH_ROOT_PARENT_DIR = System.getProperty("user.home") + File.separator + UUID.randomUUID().toString().replace("-", ""); private static final String STORE_PATH_ROOT_DIR = STORE_PATH_ROOT_PARENT_DIR + File.separator + "store"; private static final String STORE_MESSAGE = "Once, there was a chance for me!"; private static final byte[] MESSAGE_BODY = STORE_MESSAGE.getBytes(); protected static List brokerList; private static SocketAddress bornHost; private static SocketAddress storeHost; private static int number = 0; protected static void initialize() { brokerList = new ArrayList<>(); try { storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (Exception ignored) { } } public static int nextPort() throws IOException { return nextPort(1001, 9999); } public static int nextPort(int minPort, int maxPort) throws IOException { Random random = new Random(); int tempPort; int port; while (true) { try { tempPort = random.nextInt(maxPort) % (maxPort - minPort + 1) + minPort; ServerSocket serverSocket = new ServerSocket(tempPort); port = serverSocket.getLocalPort(); serverSocket.close(); break; } catch (IOException ignored) { if (number > 200) { throw new IOException("This server's open ports are temporarily full!"); } ++number; } } number = 0; return port; } public BrokerController startBroker(String namesrvAddress, String controllerAddress, String brokerName, int brokerId, int haPort, int brokerListenPort, int nettyListenPort, BrokerRole expectedRole, int mappedFileSize) throws Exception { final MessageStoreConfig storeConfig = buildMessageStoreConfig(brokerName + "#" + brokerId, haPort, mappedFileSize); storeConfig.setHaMaxTimeSlaveNotCatchup(3 * 1000); final BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setListenPort(brokerListenPort); brokerConfig.setNamesrvAddr(namesrvAddress); brokerConfig.setControllerAddr(controllerAddress); brokerConfig.setSyncBrokerMetadataPeriod(2 * 1000); brokerConfig.setCheckSyncStateSetPeriod(2 * 1000); brokerConfig.setBrokerName(brokerName); brokerConfig.setEnableControllerMode(true); final NettyServerConfig nettyServerConfig = new NettyServerConfig(); nettyServerConfig.setListenPort(nettyListenPort); final BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), storeConfig); assertTrue(brokerController.initialize()); brokerController.start(); brokerList.add(brokerController); await().atMost(20, TimeUnit.SECONDS).until(() -> (expectedRole == BrokerRole.SYNC_MASTER) == brokerController.getReplicasManager().isMasterState()); return brokerController; } protected MessageStoreConfig buildMessageStoreConfig(final String brokerDir, final int haPort, final int mappedFileSize) { MessageStoreConfig storeConfig = new MessageStoreConfig(); storeConfig.setHaSendHeartbeatInterval(1000); storeConfig.setBrokerRole(BrokerRole.SLAVE); storeConfig.setHaListenPort(haPort); storeConfig.setStorePathRootDir(STORE_PATH_ROOT_DIR + File.separator + brokerDir); storeConfig.setStorePathCommitLog(STORE_PATH_ROOT_DIR + File.separator + brokerDir + File.separator + "commitlog"); storeConfig.setStorePathEpochFile(STORE_PATH_ROOT_DIR + File.separator + brokerDir + File.separator + "EpochFileCache"); storeConfig.setStorePathBrokerIdentity(STORE_PATH_ROOT_DIR + File.separator + brokerDir + File.separator + "brokerIdentity"); storeConfig.setTotalReplicas(3); storeConfig.setInSyncReplicas(2); storeConfig.setMappedFileSizeCommitLog(mappedFileSize); storeConfig.setMappedFileSizeConsumeQueue(1024 * 1024); storeConfig.setMaxHashSlotNum(10000); storeConfig.setMaxIndexNum(100 * 100); storeConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); storeConfig.setFlushIntervalConsumeQueue(1); return storeConfig; } protected static ControllerConfig buildControllerConfig(final String id, final String peers) { final ControllerConfig config = new ControllerConfig(); config.setControllerDLegerGroup("group1"); config.setControllerDLegerPeers(peers); config.setControllerDLegerSelfId(id); config.setMappedFileSize(1024 * 1024); config.setControllerStorePath(STORE_PATH_ROOT_DIR + File.separator + "namesrv" + id + File.separator + "DLedgerController"); return config; } protected MessageExtBrokerInner buildMessage(String topic) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setTags("TAG1"); msg.setBody(MESSAGE_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); msg.setQueueId(0); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } protected void putMessage(MessageStore messageStore, String topic) { // Put message on master for (int i = 0; i < 10; i++) { assertSame(messageStore.putMessage(buildMessage(topic)).getPutMessageStatus(), PutMessageStatus.PUT_OK); } } protected void checkMessage(final MessageStore messageStore, String topic, int totalNums, int startOffset) { await().atMost(30, TimeUnit.SECONDS) .until(() -> { GetMessageResult result = messageStore.getMessage("GROUP_A", topic, 0, startOffset, 1024, null); // System.out.printf(result + "%n"); // System.out.printf("maxPhyOffset=" + messageStore.getMaxPhyOffset() + "%n"); // System.out.printf("confirmOffset=" + messageStore.getConfirmOffset() + "%n"); return result != null && result.getStatus() == GetMessageStatus.FOUND && result.getMessageCount() >= totalNums; }); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/autoswitchrole/AutoSwitchRoleIntegrationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.autoswitchrole; import java.io.File; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.controller.ReplicasManager; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.controller.ControllerManager; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; import org.apache.rocketmq.store.MappedFileQueue; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.ha.HAClient; import org.apache.rocketmq.store.ha.HAConnectionState; import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; import org.apache.rocketmq.store.logfile.MappedFile; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @Ignore public class AutoSwitchRoleIntegrationTest extends AutoSwitchRoleBase { private static final int DEFAULT_FILE_SIZE = 1024 * 1024; private static NamesrvController namesrvController; private static ControllerManager controllerManager; private static String nameserverAddress; private static String controllerAddress; private static ControllerConfig controllerConfig; private BrokerController brokerController1; private BrokerController brokerController2; private Random random = new Random(); @BeforeClass public static void init() throws Exception { initialize(); int controllerPort = nextPort(); final String peers = String.format("n0-localhost:%d", controllerPort); final NettyServerConfig serverConfig = new NettyServerConfig(); int namesrvPort = nextPort(); serverConfig.setListenPort(namesrvPort); controllerConfig = buildControllerConfig("n0", peers); namesrvController = new NamesrvController(new NamesrvConfig(), serverConfig, new NettyClientConfig()); assertTrue(namesrvController.initialize()); namesrvController.start(); initAndStartControllerManager(); nameserverAddress = "127.0.0.1:" + namesrvPort + ";"; controllerAddress = "127.0.0.1:" + controllerPort + ";"; } private static void initAndStartControllerManager() { controllerManager = new ControllerManager(controllerConfig, new NettyServerConfig(), new NettyClientConfig()); assertTrue(controllerManager.initialize()); controllerManager.start(); } public void initBroker(int mappedFileSize, String brokerName) throws Exception { this.brokerController1 = startBroker(nameserverAddress, controllerAddress, brokerName, 1, nextPort(), nextPort(), nextPort(), BrokerRole.SYNC_MASTER, mappedFileSize); this.brokerController2 = startBroker(nameserverAddress, controllerAddress, brokerName, 2, nextPort(), nextPort(), nextPort(), BrokerRole.SLAVE, mappedFileSize); // Wait slave connecting to master assertTrue(waitSlaveReady(this.brokerController2.getMessageStore())); Thread.sleep(1000); } public void mockData(String topic) throws Exception { final MessageStore messageStore = brokerController1.getMessageStore(); putMessage(messageStore, topic); // Check slave message checkMessage(brokerController2.getMessageStore(), topic, 10, 0); } public boolean waitSlaveReady(MessageStore messageStore) throws InterruptedException { int tryTimes = 0; while (tryTimes < 100) { final HAClient haClient = messageStore.getHaService().getHAClient(); if (haClient != null && haClient.getCurrentState().equals(HAConnectionState.TRANSFER)) { return true; } else { Thread.sleep(2000); tryTimes++; } } return false; } @Test public void testCheckSyncStateSet() throws Exception { String topic = "Topic-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); String brokerName = "Broker-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); initBroker(DEFAULT_FILE_SIZE, brokerName); mockData(topic); // Check SyncStateSet final ReplicasManager replicasManager = brokerController1.getReplicasManager(); SyncStateSet syncStateSet = replicasManager.getSyncStateSet(); assertEquals(2, syncStateSet.getSyncStateSet().size()); // Shutdown controller2 ScheduledExecutorService singleThread = Executors.newSingleThreadScheduledExecutor(); while (!singleThread.awaitTermination(6 * 1000, TimeUnit.MILLISECONDS)) { this.brokerController2.shutdown(); singleThread.shutdown(); } syncStateSet = replicasManager.getSyncStateSet(); shutdownAndClearBroker(); assertEquals(1, syncStateSet.getSyncStateSet().size()); } @Test public void testChangeMaster() throws Exception { String topic = "Topic-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); String brokerName = "Broker-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); initBroker(DEFAULT_FILE_SIZE, brokerName); int listenPort = brokerController1.getBrokerConfig().getListenPort(); int nettyPort = brokerController1.getNettyServerConfig().getListenPort(); mockData(topic); // Let master shutdown brokerController1.shutdown(); brokerList.remove(this.brokerController1); Thread.sleep(6000); // The slave should change to master assertTrue(brokerController2.getReplicasManager().isMasterState()); assertEquals(brokerController2.getReplicasManager().getMasterEpoch(), 2); // Restart old master, it should be slave brokerController1 = startBroker(nameserverAddress, controllerAddress, brokerName, 1, nextPort(), listenPort, nettyPort, BrokerRole.SLAVE, DEFAULT_FILE_SIZE); waitSlaveReady(brokerController1.getMessageStore()); assertFalse(brokerController1.getReplicasManager().isMasterState()); assertEquals(brokerController1.getReplicasManager().getMasterAddress(), brokerController2.getReplicasManager().getBrokerAddress()); // Put another batch messages final MessageStore messageStore = brokerController2.getMessageStore(); putMessage(messageStore, topic); // Check slave message checkMessage(brokerController1.getMessageStore(), topic, 20, 0); shutdownAndClearBroker(); } @Test public void testRestartWithChangedAddress() throws Exception { String topic = "Topic-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); String brokerName = "Broker-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); int oldPort = nextPort(); this.brokerController1 = startBroker(nameserverAddress, controllerAddress, brokerName, 1, nextPort(), oldPort, oldPort, BrokerRole.SYNC_MASTER, DEFAULT_FILE_SIZE); Thread.sleep(1000); assertTrue(brokerController1.getReplicasManager().isMasterState()); assertEquals(brokerController1.getReplicasManager().getMasterEpoch(), 1); // Let master shutdown brokerController1.shutdown(); brokerList.remove(this.brokerController1); Thread.sleep(6000); // Restart with changed address int newPort = nextPort(); this.brokerController1 = startBroker(nameserverAddress, controllerAddress, brokerName, 1, nextPort(), newPort, newPort, BrokerRole.SYNC_MASTER, DEFAULT_FILE_SIZE); Thread.sleep(1000); // Check broker id assertEquals(1, brokerController1.getReplicasManager().getBrokerControllerId().longValue()); // Check role assertTrue(brokerController1.getReplicasManager().isMasterState()); // check ip address RemotingCommand remotingCommand = controllerManager.getController().getReplicaInfo(new GetReplicaInfoRequestHeader(brokerName)).get(500, TimeUnit.MILLISECONDS); GetReplicaInfoResponseHeader resp = (GetReplicaInfoResponseHeader) remotingCommand.readCustomHeader(); assertEquals(1, resp.getMasterBrokerId().longValue()); assertTrue(resp.getMasterAddress().contains(String.valueOf(newPort))); shutdownAndClearBroker(); } @Test public void testBasicWorkWhenControllerShutdown() throws Exception { String topic = "Foobar"; String brokerName = "Broker-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(); initBroker(DEFAULT_FILE_SIZE, brokerName); // Put message from 0 to 9 putMessage(this.brokerController1.getMessageStore(), topic); checkMessage(this.brokerController2.getMessageStore(), topic, 10, 0); // Shutdown Controller controllerManager.shutdown(); // Put message from 10 to 19 putMessage(this.brokerController1.getMessageStore(), topic); checkMessage(this.brokerController2.getMessageStore(), topic, 20, 0); initAndStartControllerManager(); } @Test public void testAddBroker() throws Exception { String topic = "Topic-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); String brokerName = "Broker-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); initBroker(DEFAULT_FILE_SIZE, brokerName); mockData(topic); BrokerController broker3 = startBroker(nameserverAddress, controllerAddress, brokerName, 3, nextPort(), nextPort(), nextPort(), BrokerRole.SLAVE, DEFAULT_FILE_SIZE); waitSlaveReady(broker3.getMessageStore()); checkMessage(broker3.getMessageStore(), topic, 10, 0); putMessage(this.brokerController1.getMessageStore(), topic); checkMessage(broker3.getMessageStore(), topic, 20, 0); shutdownAndClearBroker(); } @Test public void testTruncateEpochLogAndChangeMaster() throws Exception { shutdownAndClearBroker(); String topic = "FooBar"; String brokerName = "Broker-" + AutoSwitchRoleIntegrationTest.class.getSimpleName() + random.nextInt(65535); // Noted that 10 msg 's total size = 1570, and if init the mappedFileSize = 1700, one file only be used to store 10 msg. initBroker(1700, brokerName); // Step1: Put message putMessage(this.brokerController1.getMessageStore(), topic); checkMessage(this.brokerController2.getMessageStore(), topic, 10, 0); // Step2: shutdown broker1, broker2 as master brokerController1.shutdown(); brokerList.remove(brokerController1); Thread.sleep(5000); assertTrue(brokerController2.getReplicasManager().isMasterState()); assertEquals(brokerController2.getReplicasManager().getMasterEpoch(), 2); // Step3: add broker3 BrokerController broker3 = startBroker(nameserverAddress, controllerAddress, brokerName, 3, nextPort(), nextPort(), nextPort(), BrokerRole.SLAVE, 1700); waitSlaveReady(broker3.getMessageStore()); checkMessage(broker3.getMessageStore(), topic, 10, 0); // Step4: put another batch message // Master: putMessage(this.brokerController2.getMessageStore(), topic); checkMessage(broker3.getMessageStore(), topic, 20, 0); // Step5: Check file position, each epoch will be stored on one file(Because fileSize = 1700, which equal to 10 msg size); // So epoch1 was stored in firstFile, epoch2 was stored in second file, the lastFile was empty. final MessageStore broker2MessageStore = this.brokerController2.getMessageStore(); final MappedFileQueue fileQueue = broker2MessageStore.getCommitLog().getMappedFileQueue(); assertEquals(2, fileQueue.getTotalFileSize() / 1700); // Truncate epoch1's log (truncateEndOffset = 1570), which means we should delete the first file directly. final MappedFile firstFile = broker2MessageStore.getCommitLog().getMappedFileQueue().getFirstMappedFile(); firstFile.shutdown(1000); fileQueue.retryDeleteFirstFile(1000); assertEquals(broker2MessageStore.getCommitLog().getMinOffset(), 1700); final AutoSwitchHAService haService = (AutoSwitchHAService) this.brokerController2.getMessageStore().getHaService(); haService.truncateEpochFilePrefix(1570); checkMessage(broker2MessageStore, topic, 10, 10); // Step6, start broker4, link to broker2, it should sync msg from epoch2(offset = 1700). BrokerController broker4 = startBroker(nameserverAddress, controllerAddress, brokerName, 4, nextPort(), nextPort(), nextPort(), BrokerRole.SLAVE, 1700); waitSlaveReady(broker4.getMessageStore()); checkMessage(broker4.getMessageStore(), topic, 10, 10); shutdownAndClearBroker(); } public void shutdownAndClearBroker() throws InterruptedException { for (BrokerController controller : brokerList) { controller.shutdown(); UtilAll.deleteFile(new File(controller.getMessageStoreConfig().getStorePathRootDir())); } brokerList.clear(); } @AfterClass public static void destroy() { if (namesrvController != null) { namesrvController.shutdown(); } if (controllerManager != null) { controllerManager.shutdown(); } File file = new File(STORE_PATH_ROOT_PARENT_DIR); UtilAll.deleteFile(file); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/base/BaseConf.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.base; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.MQPullConsumer; import org.apache.rocketmq.client.consumer.MQPushConsumer; import org.apache.rocketmq.client.producer.MQProducer; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQTransactionalProducer; import org.apache.rocketmq.test.clientinterface.AbstractMQConsumer; import org.apache.rocketmq.test.clientinterface.AbstractMQProducer; import org.apache.rocketmq.test.clientinterface.MQConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.AbstractListener; import org.apache.rocketmq.test.util.MQAdminTestUtils; import org.apache.rocketmq.test.util.MQRandomUtils; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.admin.MQAdminExt; import org.junit.Assert; import static org.apache.rocketmq.test.base.IntegrationTestBase.initMQAdmin; import static org.awaitility.Awaitility.await; public class BaseConf { private final static Logger log = LoggerFactory.getLogger(BaseConf.class); public final static String NAMESRV_ADDR; //the logic queue test need at least three brokers protected final static String CLUSTER_NAME; protected final static String BROKER1_NAME; protected final static String BROKER2_NAME; protected final static String BROKER3_NAME; protected final static int BROKER_NUM = 3; protected final static int WAIT_TIME = 5; protected final static int CONSUME_TIME = 2 * 60 * 1000; protected final static int QUEUE_NUMBERS = 8; protected static NamesrvController namesrvController; protected static BrokerController brokerController1; protected static BrokerController brokerController2; protected static BrokerController brokerController3; protected static List brokerControllerList; protected static Map brokerControllerMap; protected static List mqClients = new ArrayList(); protected static boolean debug = false; static { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); namesrvController = IntegrationTestBase.createAndStartNamesrv(); NAMESRV_ADDR = "127.0.0.1:" + namesrvController.getNettyServerConfig().getListenPort(); log.debug("Name server started, listening: {}", NAMESRV_ADDR); brokerController1 = IntegrationTestBase.createAndStartBroker(NAMESRV_ADDR); log.debug("Broker {} started, listening: {}", brokerController1.getBrokerConfig().getBrokerName(), brokerController1.getBrokerConfig().getListenPort()); brokerController2 = IntegrationTestBase.createAndStartBroker(NAMESRV_ADDR); log.debug("Broker {} started, listening: {}", brokerController2.getBrokerConfig().getBrokerName(), brokerController2.getBrokerConfig().getListenPort()); brokerController3 = IntegrationTestBase.createAndStartBroker(NAMESRV_ADDR); log.debug("Broker {} started, listening: {}", brokerController3.getBrokerConfig().getBrokerName(), brokerController3.getBrokerConfig().getListenPort()); CLUSTER_NAME = brokerController1.getBrokerConfig().getBrokerClusterName(); BROKER1_NAME = brokerController1.getBrokerConfig().getBrokerName(); BROKER2_NAME = brokerController2.getBrokerConfig().getBrokerName(); BROKER3_NAME = brokerController3.getBrokerConfig().getBrokerName(); brokerControllerList = ImmutableList.of(brokerController1, brokerController2, brokerController3); brokerControllerMap = brokerControllerList.stream().collect( Collectors.toMap(input -> input.getBrokerConfig().getBrokerName(), Function.identity())); initMQAdmin(NAMESRV_ADDR); } public BaseConf() { // Add waitBrokerRegistered to BaseConf constructor to make it default for all subclasses. waitBrokerRegistered(NAMESRV_ADDR, CLUSTER_NAME, BROKER_NUM); } // This method can't be placed in the static block of BaseConf, which seems to lead to a strange dead lock. public static void waitBrokerRegistered(final String nsAddr, final String clusterName, final int expectedBrokerNum) { final DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(500); mqAdminExt.setNamesrvAddr(nsAddr); try { mqAdminExt.start(); await().atMost(30, TimeUnit.SECONDS).until(() -> { List brokerDatas; try { brokerDatas = mqAdminExt.examineTopicRouteInfo(clusterName).getBrokerDatas(); } catch (Exception e) { return false; } return brokerDatas.size() == expectedBrokerNum; }); for (BrokerController brokerController: brokerControllerList) { brokerController.getBrokerOuterAPI().refreshMetadata(); } } catch (Exception e) { log.error("init failed, please check BaseConf", e); Assert.fail(e.getMessage()); } ForkJoinPool.commonPool().execute(mqAdminExt::shutdown); } public boolean awaitDispatchMs(long timeMs) throws Exception { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start <= timeMs) { boolean allOk = true; for (BrokerController brokerController: brokerControllerList) { if (brokerController.getMessageStore().dispatchBehindBytes() != 0) { allOk = false; break; } } if (allOk) { return true; } Thread.sleep(100); } return false; } public static String initTopic() { String topic = MQRandomUtils.getRandomTopic(); return initTopicWithName(topic); } public static String initTopic(TopicMessageType topicMessageType) { String topic = MQRandomUtils.getRandomTopic(); return initTopicWithName(topic, topicMessageType); } public static String initTopicOnSampleTopicBroker(String sampleTopic) { String topic = MQRandomUtils.getRandomTopic(); return initTopicOnSampleTopicBroker(topic, sampleTopic); } public static String initTopicOnSampleTopicBroker(String sampleTopic, TopicMessageType topicMessageType) { String topic = MQRandomUtils.getRandomTopic(); return initTopicOnSampleTopicBroker(topic, sampleTopic, topicMessageType); } public static String initTopicWithName(String topicName) { IntegrationTestBase.initTopic(topicName, NAMESRV_ADDR, CLUSTER_NAME, CQType.SimpleCQ); return topicName; } public static String initTopicWithName(String topicName, TopicMessageType topicMessageType) { IntegrationTestBase.initTopic(topicName, NAMESRV_ADDR, CLUSTER_NAME, topicMessageType); return topicName; } public static String initTopicOnSampleTopicBroker(String topicName, String sampleTopic) { IntegrationTestBase.initTopic(topicName, NAMESRV_ADDR, sampleTopic, CQType.SimpleCQ); return topicName; } public static String initTopicOnSampleTopicBroker(String topicName, String sampleTopic, TopicMessageType topicMessageType) { IntegrationTestBase.initTopic(topicName, NAMESRV_ADDR, sampleTopic, topicMessageType); return topicName; } public static String initConsumerGroup() { return initConsumerGroup(MQRandomUtils.getRandomConsumerGroup()); } public static String initConsumerGroup(String group) { SubscriptionGroupConfig config = new SubscriptionGroupConfig(); config.setGroupName(group); MQAdminTestUtils.createSub(NAMESRV_ADDR, CLUSTER_NAME, config); return group; } public static String initConsumerGroup(SubscriptionGroupConfig config) { MQAdminTestUtils.createSub(NAMESRV_ADDR, CLUSTER_NAME, config); return config.getGroupName(); } public static DefaultMQAdminExt getAdmin(String nsAddr) { final DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(3 * 1000); mqAdminExt.setNamesrvAddr(nsAddr); mqAdminExt.setPollNameServerInterval(100); mqClients.add(mqAdminExt); return mqAdminExt; } public static RMQNormalProducer getProducer(String nsAddr, String topic) { return getProducer(nsAddr, topic, false); } public static RMQNormalProducer getProducer(String nsAddr, String topic, boolean useTLS) { RMQNormalProducer producer = new RMQNormalProducer(nsAddr, topic, useTLS); if (debug) { producer.setDebug(); } mqClients.add(producer); return producer; } public static RMQTransactionalProducer getTransactionalProducer(String nsAddr, String topic, TransactionListener transactionListener) { RMQTransactionalProducer producer = new RMQTransactionalProducer(nsAddr, topic, false, transactionListener); if (debug) { producer.setDebug(); } mqClients.add(producer); return producer; } public static RMQNormalProducer getProducer(String nsAddr, String topic, String producerGoup, String instanceName) { RMQNormalProducer producer = new RMQNormalProducer(nsAddr, topic, producerGoup, instanceName); if (debug) { producer.setDebug(); } mqClients.add(producer); return producer; } public static RMQAsyncSendProducer getAsyncProducer(String nsAddr, String topic) { RMQAsyncSendProducer producer = new RMQAsyncSendProducer(nsAddr, topic); if (debug) { producer.setDebug(); } mqClients.add(producer); return producer; } public static RMQNormalConsumer getConsumer(String nsAddr, String topic, String subExpression, AbstractListener listener) { return getConsumer(nsAddr, topic, subExpression, listener, false); } public static RMQNormalConsumer getConsumer(String nsAddr, String topic, String subExpression, AbstractListener listener, boolean useTLS) { String consumerGroup = initConsumerGroup(); return getConsumer(nsAddr, consumerGroup, topic, subExpression, listener, useTLS); } public static RMQNormalConsumer getConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listener) { return getConsumer(nsAddr, consumerGroup, topic, subExpression, listener, false); } public static RMQNormalConsumer getConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listener, boolean useTLS) { RMQNormalConsumer consumer = ConsumerFactory.getRMQNormalConsumer(nsAddr, consumerGroup, topic, subExpression, listener, useTLS); if (debug) { consumer.setDebug(); } mqClients.add(consumer); log.info("consumer[{}] start,topic[{}],subExpression[{}]", consumerGroup, topic, subExpression); return consumer; } public static void shutdown() { ImmutableList mqClients = ImmutableList.copyOf(BaseConf.mqClients); BaseConf.mqClients.clear(); shutdown(mqClients); } public static Set getBrokers() { Set brokers = new HashSet<>(); brokers.add(BROKER1_NAME); brokers.add(BROKER2_NAME); brokers.add(BROKER3_NAME); return brokers; } public static void shutdown(List mqClients) { mqClients.forEach(mqClient -> ForkJoinPool.commonPool().execute(() -> { if (mqClient instanceof AbstractMQProducer) { ((AbstractMQProducer) mqClient).shutdown(); } else if (mqClient instanceof AbstractMQConsumer) { ((AbstractMQConsumer) mqClient).shutdown(); } else if (mqClient instanceof MQAdminExt) { ((MQAdminExt) mqClient).shutdown(); } else if (mqClient instanceof MQProducer) { ((MQProducer) mqClient).shutdown(); } else if (mqClient instanceof MQPullConsumer) { ((MQPullConsumer) mqClient).shutdown(); } else if (mqClient instanceof MQPushConsumer) { ((MQPushConsumer) mqClient).shutdown(); } else if (mqClient instanceof MQConsumer) { ((MQConsumer) mqClient).shutdown(); } })); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.base; import com.google.common.truth.Truth; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import io.grpc.protobuf.services.ChannelzService; import io.grpc.protobuf.services.ProtoReflectionService; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; import org.apache.rocketmq.common.utils.StartAndShutdown; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.proxy.ProxyMode; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.proxy.grpc.GrpcServer; import org.apache.rocketmq.proxy.grpc.GrpcServerBuilder; import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingApplication; import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.proxy.service.cert.TlsCertificateManager; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.test.util.MQAdminTestUtils; import static org.apache.rocketmq.test.base.BaseConf.brokerController1; public class IntegrationTestBase { public static Logger logger = LoggerFactory.getLogger(IntegrationTestBase.class); protected static final String SEP = File.separator; protected static final String BROKER_NAME_PREFIX = "TestBrokerName_"; protected static final AtomicInteger BROKER_INDEX = new AtomicInteger(0); protected static final List TMPE_FILES = new ArrayList<>(); protected static final List BROKER_CONTROLLERS = new ArrayList<>(); protected static final List NAMESRV_CONTROLLERS = new ArrayList<>(); protected static int topicCreateTime = (int) TimeUnit.SECONDS.toSeconds(30); public static volatile int commitLogSize = 1024 * 1024 * 100; protected static final int INDEX_NUM = 1000; static { System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { for (BrokerController brokerController : BROKER_CONTROLLERS) { if (brokerController != null) { brokerController.shutdown(); } } // should destroy message store, otherwise could not delete the temp files. for (BrokerController brokerController : BROKER_CONTROLLERS) { if (brokerController != null) { brokerController.getMessageStore().destroy(); } } for (NamesrvController namesrvController : NAMESRV_CONTROLLERS) { if (namesrvController != null) { namesrvController.shutdown(); } } for (File file : TMPE_FILES) { UtilAll.deleteFile(file); } MQAdminTestUtils.shutdownAdmin(); } catch (Exception e) { logger.error("Shutdown error", e); } } }); } public static String createBaseDir() { String baseDir = System.getProperty("java.io.tmpdir") + SEP + "unitteststore-" + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { logger.info(String.format("[%s] has already existed, please back up and remove it for integration tests", baseDir)); System.exit(1); } TMPE_FILES.add(file); return baseDir; } public static NamesrvController createAndStartNamesrv() { String baseDir = createBaseDir(); NamesrvConfig namesrvConfig = new NamesrvConfig(); NettyServerConfig nameServerNettyServerConfig = new NettyServerConfig(); namesrvConfig.setKvConfigPath(baseDir + SEP + "namesrv" + SEP + "kvConfig.json"); namesrvConfig.setConfigStorePath(baseDir + SEP + "namesrv" + SEP + "namesrv.properties"); nameServerNettyServerConfig.setListenPort(0); NamesrvController namesrvController = new NamesrvController(namesrvConfig, nameServerNettyServerConfig); try { Truth.assertThat(namesrvController.initialize()).isTrue(); logger.info("Name Server Start:{}", nameServerNettyServerConfig.getListenPort()); namesrvController.start(); } catch (Exception e) { logger.info("Name Server start failed", e); System.exit(1); } NAMESRV_CONTROLLERS.add(namesrvController); return namesrvController; } public static BrokerController createAndStartBroker(String nsAddr) { String baseDir = createBaseDir(); BrokerConfig brokerConfig = new BrokerConfig(); MessageStoreConfig storeConfig = new MessageStoreConfig(); brokerConfig.setBrokerName(BROKER_NAME_PREFIX + BROKER_INDEX.incrementAndGet()); brokerConfig.setBrokerIP1("127.0.0.1"); brokerConfig.setNamesrvAddr(nsAddr); brokerConfig.setEnablePropertyFilter(true); brokerConfig.setEnableCalcFilterBitMap(true); brokerConfig.setAppendAckAsync(true); brokerConfig.setAppendCkAsync(true); brokerConfig.setRecallMessageEnable(true); storeConfig.setEnableConsumeQueueExt(true); brokerConfig.setLoadBalancePollNameServerInterval(500); brokerConfig.setPopConsumerKVServiceInit(true); brokerConfig.setConfigManagerVersion(System.getProperty("configManagerVersion", "v1")); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + SEP + "commitlog"); storeConfig.setMappedFileSizeCommitLog(commitLogSize); storeConfig.setMaxIndexNum(INDEX_NUM); storeConfig.setMaxHashSlotNum(INDEX_NUM * 4); storeConfig.setDeleteWhen("01;02;03;04;05;06;07;08;09;10;11;12;13;14;15;16;17;18;19;20;21;22;23;00"); storeConfig.setMaxTransferCountOnMessageInMemory(1024); storeConfig.setMaxTransferCountOnMessageInDisk(1024); storeConfig.setEnableLmq(Boolean.valueOf(System.getProperty("enableLmq", "false"))); storeConfig.setEnableMultiDispatch(Boolean.valueOf(System.getProperty("enableMultiDispatch", "false"))); storeConfig.setStoreType(System.getProperty("storeType", "default")); return createAndStartBroker(storeConfig, brokerConfig); } public static void createAndStartProxy(String nsAddr) { try { ProxyStartAndShutdown startAndShutdown = new ProxyStartAndShutdown(); ConfigurationManager.initConfig(); ProxyConfig config = ConfigurationManager.getProxyConfig(); config.setNamesrvAddr(nsAddr); config.setEnableTopicMessageTypeCheck(false); ThreadPoolExecutor executor = ThreadPoolMonitor.createAndMonitor( config.getGrpcThreadPoolNums(), config.getGrpcThreadPoolNums(), 1, TimeUnit.MINUTES, "GrpcRequestExecutorThread", config.getGrpcThreadPoolQueueCapacity() ); startAndShutdown.appendShutdown(executor::shutdown); String proxyModeStr = ConfigurationManager.getProxyConfig().getProxyMode(); MessagingProcessor messagingProcessor; if (ProxyMode.isClusterMode(proxyModeStr)) { messagingProcessor = DefaultMessagingProcessor.createForClusterMode(); } else { messagingProcessor = DefaultMessagingProcessor.createForLocalMode(brokerController1); } startAndShutdown.appendStartAndShutdown(messagingProcessor); TlsCertificateManager tlsCertificateManager = new TlsCertificateManager(); startAndShutdown.appendStartAndShutdown(tlsCertificateManager); GrpcMessagingApplication application = GrpcMessagingApplication.create(messagingProcessor); startAndShutdown.appendStartAndShutdown(application); GrpcServer grpcServer = GrpcServerBuilder.newBuilder(executor, ConfigurationManager.getProxyConfig().getGrpcServerPort(), tlsCertificateManager) .addService(application) .addService(ChannelzService.newInstance(100)) .addService(ProtoReflectionService.newInstance()) .configInterceptor() .shutdownTime(ConfigurationManager.getProxyConfig().getGrpcShutdownTimeSeconds(), TimeUnit.SECONDS) .build(); startAndShutdown.appendStartAndShutdown(grpcServer); startAndShutdown.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { startAndShutdown.preShutdown(); startAndShutdown.shutdown(); } catch (Exception e) { } })); } catch (Throwable e) { logger.error("proxy start failed, will exit", e); System.exit(1); } } public static BrokerController createAndStartBroker(MessageStoreConfig storeConfig, BrokerConfig brokerConfig) { NettyServerConfig nettyServerConfig = new NettyServerConfig(); NettyClientConfig nettyClientConfig = new NettyClientConfig(); nettyServerConfig.setListenPort(0); storeConfig.setHaListenPort(0); BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, nettyClientConfig, storeConfig); try { Truth.assertThat(brokerController.initialize()).isTrue(); logger.info("Broker Start name:{} addr:{}", brokerConfig.getBrokerName(), brokerController.getBrokerAddr()); brokerController.start(); } catch (Throwable t) { logger.error("Broker start failed, will exit", t); System.exit(1); } BROKER_CONTROLLERS.add(brokerController); return brokerController; } public static boolean initTopic(String topic, String nsAddr, String clusterName, int queueNumbers, CQType cqType) { return initTopic(topic, nsAddr, clusterName, queueNumbers, cqType, TopicMessageType.NORMAL, null); } public static boolean initTopic(String topic, String nsAddr, String clusterName, int queueNumbers, CQType cqType, TopicMessageType topicMessageType) { return initTopic(topic, nsAddr, clusterName, queueNumbers, cqType, topicMessageType, null); } public static boolean initTopic(String topic, String nsAddr, String clusterName, int queueNumbers, CQType cqType, TopicMessageType topicMessageType, Long liteTtl) { boolean createResult; Map attributes = new HashMap<>(); if (!Objects.equals(CQType.SimpleCQ, cqType)) { attributes.put("+" + TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName(), cqType.toString()); } if (!Objects.equals(TopicMessageType.NORMAL, topicMessageType)) { attributes.put("+" + TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), topicMessageType.toString()); } if (Objects.equals(TopicMessageType.LITE, topicMessageType)) { attributes.put("+" + TopicAttributes.LITE_EXPIRATION_ATTRIBUTE.getName(), liteTtl.toString()); } createResult = MQAdminTestUtils.createTopic(nsAddr, clusterName, topic, queueNumbers, attributes, topicCreateTime); return createResult; } public static boolean initTopic(String topic, String nsAddr, String clusterName, CQType cqType) { return initTopic(topic, nsAddr, clusterName, BaseConf.QUEUE_NUMBERS, cqType, TopicMessageType.NORMAL, null); } public static boolean initTopic(String topic, String nsAddr, String clusterName, TopicMessageType topicMessageType) { return initTopic(topic, nsAddr, clusterName, BaseConf.QUEUE_NUMBERS, CQType.SimpleCQ, topicMessageType, null); } public static void deleteFile(File file) { if (!file.exists()) { return; } UtilAll.deleteFile(file); } public static void initMQAdmin(String nsAddr) { try { MQAdminTestUtils.startAdmin(nsAddr); } catch (MQClientException e) { logger.info("MQAdmin start failed"); System.exit(1); } } private static class ProxyStartAndShutdown extends AbstractStartAndShutdown { @Override public void appendStartAndShutdown(StartAndShutdown startAndShutdown) { super.appendStartAndShutdown(startAndShutdown); } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/balance/NormalMsgDynamicBalanceIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.balance; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class NormalMsgDynamicBalanceIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerAndCrashOne() { int msgSize = 400; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); consumer2.shutdown(); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize * 2, producer.getAllUndupMsgBody().size()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); boolean balance = VerifyUtils.verifyBalance(msgSize, VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllUndupMsgBody()).size() - msgSize, VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllUndupMsgBody()).size()); assertThat(balance).isEqualTo(true); } @Test public void test3ConsumerAndCrashOne() { int msgSize = 400; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); consumer3.shutdown(); TestUtils.waitForSeconds(WAIT_TIME); producer.clearMsg(); consumer1.clearMsg(); consumer2.clearMsg(); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); boolean balance = VerifyUtils.verifyBalance(msgSize, VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllUndupMsgBody()).size()); assertThat(balance).isEqualTo(true); } @Test public void testMessageQueueListener() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); // Register message queue listener consumer1.getConsumer().setMessageQueueListener((topic, mqAll, mqAssigned) -> latch.countDown()); // Without message queue listener RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); Assert.assertTrue(latch.await(30, TimeUnit.SECONDS)); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/balance/NormalMsgStaticBalanceIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.balance; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class NormalMsgStaticBalanceIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumersBalance() { int msgSize = 400; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); boolean balance = VerifyUtils.verifyBalance(msgSize, VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllUndupMsgBody()).size()); assertThat(balance).isEqualTo(true); } @Test public void testFourConsumersBalance() { int msgSize = 600; String consumerGroup = initConsumerGroup(); logger.info("use group: {}", consumerGroup); RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, consumerGroup, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumerGroup, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumerGroup, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer4 = getConsumer(NAMESRV_ADDR, consumerGroup, topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener(), consumer4.getListener()); assertThat(recvAll).isEqualTo(true); boolean balance = VerifyUtils .verifyBalance(msgSize, VerifyUtils .getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllUndupMsgBody()) .size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer3.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer4.getListener().getAllUndupMsgBody()).size()); assertThat(balance).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadcast.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.AbstractListener; public class BaseBroadcast extends BaseConf { private static Logger logger = LoggerFactory.getLogger(BaseBroadcast.class); public static RMQBroadCastConsumer getBroadCastConsumer(String nsAddr, String topic, String subExpression, AbstractListener listener) { String consumerGroup = initConsumerGroup(); return getBroadCastConsumer(nsAddr, consumerGroup, topic, subExpression, listener); } public static RMQBroadCastConsumer getBroadCastConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, AbstractListener listener) { RMQBroadCastConsumer consumer = ConsumerFactory.getRMQBroadCastConsumer(nsAddr, consumerGroup, topic, subExpression, listener); consumer.setDebug(); mqClients.add(consumer); logger.info(String.format("consumer[%s] start,topic[%s],subExpression[%s]", consumerGroup, topic, subExpression)); return consumer; } public void printSeparator() { for (int i = 0; i < 3; i++) { logger.info( "<<<<<<<<================================================================================>>>>>>>>"); } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgNotReceiveIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastNormalMsgNotReceiveIT extends BaseBroadcast { private static Logger logger = LoggerFactory .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { printSeparator(); topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testNotConsumeAfterConsume() throws Exception { int msgSize = 16; String group = initConsumerGroup(); RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener(group + "_1")); Thread.sleep(3000); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener(group + "_2")); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), WAIT_TIME); assertThat(consumer2.getListener().getAllMsgBody().size()).isEqualTo(0); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvCrashIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastNormalMsgRecvCrashIT extends BaseBroadcast { private static Logger logger = LoggerFactory .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { printSeparator(); topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testStartTwoAndCrashOneLater() { int msgSize = 16; String group = initConsumerGroup(); RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener(group + "_1")); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener(group + "_2")); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); consumer2.shutdown(); producer.clearMsg(); consumer1.clearMsg(); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvFailIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastNormalMsgRecvFailIT extends BaseBroadcast { private static Logger logger = LoggerFactory .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { printSeparator(); topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Ignore @Test public void testStartTwoConsumerAndOneConsumerFail() { int msgSize = 16; RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener(ConsumeConcurrentlyStatus.RECONSUME_LATER)); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvStartLaterIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastNormalMsgRecvStartLaterIT extends BaseBroadcast { private static Logger logger = LoggerFactory .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { printSeparator(); topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testStartOneAndStartAnotherLater() { int msgSize = 16; String group = initConsumerGroup(); RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener(group + "_1")); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); producer.clearMsg(); consumer1.clearMsg(); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener(group + "_2")); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgTwoDiffGroupRecvIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastNormalMsgTwoDiffGroupRecvIT extends BaseBroadcast { private static Logger logger = LoggerFactory .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { printSeparator(); topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testStartDiffSameGroupConsumer() { int msgSize = 16; String group1 = initConsumerGroup(); String group2 = initConsumerGroup(); RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, group1, topic, "*", new RMQNormalListener(group1 + "_1")); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, group2, topic, "*", new RMQNormalListener(group2 + "_2")); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/NormalMsgTwoSameGroupConsumerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class NormalMsgTwoSameGroupConsumerIT extends BaseBroadcast { private static Logger logger = LoggerFactory .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { printSeparator(); topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testStartTwoSameGroupConsumer() { int msgSize = 16; String group = initConsumerGroup(); RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener(group + "_1")); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener(group + "_2")); TestUtils.waitForSeconds(WAIT_TIME); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadcastIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.order; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.order.RMQOrderListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; /** * Currently, does not support the ordered broadcast message */ @Ignore public class OrderMsgBroadcastIT extends BaseBroadcast { private static Logger logger = LoggerFactory.getLogger(OrderMsgBroadcastIT.class); private RMQNormalProducer producer = null; private String topic = null; private int broadcastConsumeTime = 1 * 60 * 1000; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerSubTag() { int msgSize = 10; RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener()); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener()); TestUtils.waitForSeconds(WAIT_TIME); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), broadcastConsumeTime); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), broadcastConsumeTime); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer1.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer2.getListener()).getMsgs())) .isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerFilterIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.tag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastTwoConsumerFilterIT extends BaseBroadcast { private static Logger logger = LoggerFactory.getLogger(BroadcastTwoConsumerSubTagIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerFilter() { int msgSize = 40; String tag1 = "jueyin_tag_1"; String tag2 = "jueyin_tag_2"; RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, topic, tag1, new RMQNormalListener()); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, tag1, new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(tag2, msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); producer.clearMsg(); producer.send(tag1, msgSize); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubDiffTagIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.tag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastTwoConsumerSubDiffTagIT extends BaseBroadcast { private static Logger logger = LoggerFactory.getLogger(BroadcastTwoConsumerSubTagIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerSubDiffTag() { int msgSize = 40; String tag = "jueyin_tag"; RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, tag, new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(tag, msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubTagIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.broadcast.tag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class BroadcastTwoConsumerSubTagIT extends BaseBroadcast { private static Logger logger = LoggerFactory.getLogger(BroadcastTwoConsumerSubTagIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerSubTag() { int msgSize = 20; String tag = "jueyin_tag"; RMQBroadCastConsumer consumer1 = getBroadCastConsumer(NAMESRV_ADDR, topic, tag, new RMQNormalListener()); RMQBroadCastConsumer consumer2 = getBroadCastConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, tag, new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); producer.send(tag, msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer2.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/cluster/DynamicAddAndCrashIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.cluster; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.balance.NormalMsgStaticBalanceIT; import org.apache.rocketmq.test.client.mq.MQAsyncProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class DynamicAddAndCrashIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testAddOneConsumerAndCrashAfterWhile() { int msgSize = 150; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); MQAsyncProducer asyncDefaultMQProducer = new MQAsyncProducer(producer, msgSize, 100); asyncDefaultMQProducer.start(); TestUtils.waitForSeconds(WAIT_TIME); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); consumer2.shutdown(); asyncDefaultMQProducer.waitSendAll(WAIT_TIME * 6); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); } @Test public void testAddTwoConsumerAndCrashAfterWhile() { int msgSize = 150; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); MQAsyncProducer asyncDefaultMQProducer = new MQAsyncProducer(producer, msgSize, 100); asyncDefaultMQProducer.start(); TestUtils.waitForSeconds(WAIT_TIME); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(WAIT_TIME); consumer2.shutdown(); consumer3.shutdown(); asyncDefaultMQProducer.waitSendAll(WAIT_TIME * 6); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); assertThat(recvAll).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/cluster/DynamicAddConsumerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.cluster; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.balance.NormalMsgStaticBalanceIT; import org.apache.rocketmq.test.client.mq.MQAsyncProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class DynamicAddConsumerIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testAddOneConsumer() { int msgSize = 100; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); MQAsyncProducer asyncDefaultMQProducer = new MQAsyncProducer(producer, msgSize, 100); asyncDefaultMQProducer.start(); TestUtils.waitForSeconds(WAIT_TIME); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); asyncDefaultMQProducer.waitSendAll(WAIT_TIME * 6); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); } @Test public void testAddTwoConsumer() { int msgSize = 100; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); MQAsyncProducer asyncDefaultMQProducer = new MQAsyncProducer(producer, msgSize, 100); asyncDefaultMQProducer.start(); TestUtils.waitForSeconds(WAIT_TIME); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); asyncDefaultMQProducer.waitSendAll(WAIT_TIME * 6); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); assertThat(recvAll).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/cluster/DynamicCrashConsumerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.cluster; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.balance.NormalMsgStaticBalanceIT; import org.apache.rocketmq.test.client.mq.MQAsyncProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class DynamicCrashConsumerIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testAddOneConsumer() { int msgSize = 100; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); MQAsyncProducer asyncDefaultMQProducer = new MQAsyncProducer(producer, msgSize, 100); asyncDefaultMQProducer.start(); TestUtils.waitForSeconds(WAIT_TIME); consumer2.shutdown(); asyncDefaultMQProducer.waitSendAll(WAIT_TIME * 6); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); } @Test public void testAddTwoConsumer() { int msgSize = 100; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQNormalListener()); MQAsyncProducer asyncDefaultMQProducer = new MQAsyncProducer(producer, msgSize, 100); asyncDefaultMQProducer.start(); TestUtils.waitForSeconds(WAIT_TIME); consumer2.shutdown(); consumer3.shutdown(); asyncDefaultMQProducer.waitSendAll(WAIT_TIME * 6); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); assertThat(recvAll).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/filter/SqlFilterIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.filter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQSqlConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class SqlFilterIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(SqlFilterIT.class); private RMQNormalProducer producer = null; private String topic = null; private static final Map OFFSE_TABLE = new HashMap(); @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); OFFSE_TABLE.clear(); } @After public void tearDown() { super.shutdown(); } @Test public void testFilterConsumer() throws Exception { int msgSize = 16; String group = initConsumerGroup(); MessageSelector selector = MessageSelector.bySql("(TAGS is not null and TAGS in ('TagA', 'TagB'))"); RMQSqlConsumer consumer = ConsumerFactory.getRMQSqlConsumer(NAMESRV_ADDR, group, topic, selector, new RMQNormalListener(group + "_1")); Thread.sleep(3000); producer.send("TagA", msgSize); producer.send("TagB", msgSize); producer.send("TagC", msgSize); Assert.assertEquals("Not all sent succeeded", msgSize * 3, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(msgSize * 2, CONSUME_TIME); assertThat(producer.getAllMsgBody()) .containsAllIn(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())); assertThat(consumer.getListener().getAllMsgBody().size()).isEqualTo(msgSize * 2); } @Test public void testFilterPullConsumer() throws Exception { int msgSize = 16; String group = initConsumerGroup(); MessageSelector selector = MessageSelector.bySql("(TAGS is not null and TAGS in ('TagA', 'TagB'))"); DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(group); consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.start(); Thread.sleep(3000); producer.send("TagA", msgSize); producer.send("TagB", msgSize); producer.send("TagC", msgSize); Assert.assertEquals("Not all sent succeeded", msgSize * 3, producer.getAllUndupMsgBody().size()); List receivedMessage = new ArrayList<>(2); Set mqs = consumer.fetchSubscribeMessageQueues(topic); for (MessageQueue mq : mqs) { SINGLE_MQ: while (true) { try { PullResult pullResult = consumer.pull(mq, selector, getMessageQueueOffset(mq), 32); putMessageQueueOffset(mq, pullResult.getNextBeginOffset()); switch (pullResult.getPullStatus()) { case FOUND: List msgs = pullResult.getMsgFoundList(); for (MessageExt msg : msgs) { receivedMessage.add(new String(msg.getBody())); } break; case NO_MATCHED_MSG: break; case NO_NEW_MSG: break SINGLE_MQ; case OFFSET_ILLEGAL: break; default: break; } } catch (Exception e) { e.printStackTrace(); } } } assertThat(receivedMessage.size()).isEqualTo(msgSize * 2); } private static long getMessageQueueOffset(MessageQueue mq) { Long offset = OFFSE_TABLE.get(mq); if (offset != null) return offset; return 0; } private static void putMessageQueueOffset(MessageQueue mq, long offset) { OFFSE_TABLE.put(mq, offset); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePop.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.factory.ConsumerFactory; public class BasePop extends BaseConf { public RMQPopClient getRMQPopClient() { RMQPopClient client = ConsumerFactory.getRMQPopClient(); mqClients.add(client); return client; } protected static class MsgRcv { public final long rcvTime; public final MessageExt messageExt; public MsgRcv(long rcvTime, MessageExt messageExt) { this.rcvTime = rcvTime; this.messageExt = messageExt; } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePopNormally.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.util.MQRandomUtils; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @Ignore public class BasePopNormally extends BasePop { protected String topic; protected String group; protected RMQNormalProducer producer = null; protected RMQPopClient client = null; protected String brokerAddr; protected MessageQueue messageQueue; @Before public void setUp() { brokerAddr = brokerController1.getBrokerAddr(); topic = MQRandomUtils.getRandomTopic(); group = initConsumerGroup(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 8, CQType.SimpleCQ, TopicMessageType.NORMAL); producer = getProducer(NAMESRV_ADDR, topic); client = getRMQPopClient(); messageQueue = new MessageQueue(topic, BROKER1_NAME, -1); } @After public void tearDown() { shutdown(); } protected CompletableFuture popMessageAsync(long invisibleTime, int maxNums, long timeout) { return client.popMessageAsync( brokerAddr, messageQueue, invisibleTime, maxNums, group, timeout, true, ConsumeInitMode.MIN, false, ExpressionType.TAG, "*"); } protected CompletableFuture popMessageAsync(long invisibleTime, int maxNums) { return client.popMessageAsync( brokerAddr, messageQueue, invisibleTime, maxNums, group, 3000, false, ConsumeInitMode.MIN, false, ExpressionType.TAG, "*"); } protected CompletableFuture ackMessageAsync(MessageExt messageExt) { return client.ackMessageAsync(brokerAddr, topic, group, messageExt.getProperty(MessageConst.PROPERTY_POP_CK)); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePopOrderly.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.MQRandomUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.assertj.core.util.Lists; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import static org.junit.Assert.assertEquals; @Ignore public class BasePopOrderly extends BasePop { protected String topic; protected String group; protected RMQNormalProducer producer = null; protected RMQPopClient client = null; protected String brokerAddr; protected MessageQueue messageQueue; protected final Map> msgRecv = new ConcurrentHashMap<>(); protected final List msgRecvSequence = new CopyOnWriteArrayList<>(); protected final List msgDataRecv = new CopyOnWriteArrayList<>(); @Before public void setUp() { brokerController1.getBrokerConfig().setEnableNotifyAfterPopOrderLockRelease(true); brokerAddr = brokerController1.getBrokerAddr(); topic = MQRandomUtils.getRandomTopic(); group = initConsumerGroup(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 1, CQType.SimpleCQ, TopicMessageType.FIFO); producer = getProducer(NAMESRV_ADDR, topic); client = getRMQPopClient(); messageQueue = new MessageQueue(topic, BROKER1_NAME, -1); } @After public void tearDown() { shutdown(); } protected void sendMessage(int num) { MessageQueueMsg mqMsgs = new MessageQueueMsg(Lists.newArrayList(messageQueue), num); producer.send(mqMsgs.getMsgsWithMQ()); } protected void assertMessageRecvOrder() { VerifyUtils.verifyOrderMsg(msgDataRecv); } protected void assertMsgRecv(int seqId, int expectNum) { String msgId = msgRecvSequence.get(seqId); List msgRcvList = msgRecv.get(msgId); assertEquals(expectNum, msgRcvList.size()); assertConsumeTimes(msgRcvList); } protected void assertConsumeTimes(List msgRcvList) { for (int i = 0; i < msgRcvList.size(); i++) { assertEquals(i, msgRcvList.get(i).messageExt.getReconsumeTimes()); } } protected void assertMsgRecv(int seqId, int expectNum, List expectReconsumeTimes) { String msgId = msgRecvSequence.get(seqId); List msgRcvList = msgRecv.get(msgId); assertEquals(expectNum, msgRcvList.size()); assertConsumeTimes(msgRcvList, expectReconsumeTimes); } protected void assertConsumeTimes(List msgRcvList, List expectReconsumeTimes) { for (int i = 0; i < msgRcvList.size(); i++) { assertEquals(expectReconsumeTimes.get(i).intValue(), msgRcvList.get(i).messageExt.getReconsumeTimes()); } } protected void onRecvNewMessage(MessageExt messageExt) { msgDataRecv.add(new String(messageExt.getBody())); msgRecvSequence.add(messageExt.getMsgId()); msgRecv.compute(messageExt.getMsgId(), (k, msgRcvList) -> { if (msgRcvList == null) { msgRcvList = new CopyOnWriteArrayList<>(); } msgRcvList.add(new MsgRcv(System.currentTimeMillis(), messageExt)); return msgRcvList; }); } protected CompletableFuture popMessageOrderlyAsync(long invisibleTime, int maxNums, long timeout) { return popMessageOrderlyAsync(invisibleTime, maxNums, timeout, null); } protected CompletableFuture popMessageOrderlyAsync(long invisibleTime, int maxNums, long timeout, String attemptId) { return client.popMessageAsync( brokerAddr, messageQueue, invisibleTime, maxNums, group, timeout, true, ConsumeInitMode.MIN, true, ExpressionType.TAG, "*", attemptId); } protected CompletableFuture ackMessageAsync(MessageExt messageExt) { return client.ackMessageAsync(brokerAddr, topic, group, messageExt.getProperty(MessageConst.PROPERTY_POP_CK)); } protected CompletableFuture changeInvisibleTimeAsync(MessageExt messageExt, long invisibleTime) { return client.changeInvisibleTimeAsync( brokerAddr, BROKER1_NAME, topic, group, messageExt.getProperty(MessageConst.PROPERTY_POP_CK), invisibleTime); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BatchAckIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import org.apache.rocketmq.client.consumer.AckResult; import org.apache.rocketmq.client.consumer.AckStatus; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.util.MQRandomUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; public class BatchAckIT extends BasePop { protected String topic; protected String group; protected RMQNormalProducer producer = null; protected RMQPopClient client = null; protected String brokerAddr; protected MessageQueue messageQueue; @Before public void setUp() { brokerAddr = brokerController1.getBrokerAddr(); topic = MQRandomUtils.getRandomTopic(); group = initConsumerGroup(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 8, CQType.SimpleCQ, TopicMessageType.NORMAL); producer = getProducer(NAMESRV_ADDR, topic); client = getRMQPopClient(); messageQueue = new MessageQueue(topic, BROKER1_NAME, -1); } @After public void tearDown() { shutdown(); } @Test public void testBatchAckNormallyWithPopBuffer() throws Throwable { brokerController1.getBrokerConfig().setEnablePopBufferMerge(true); brokerController2.getBrokerConfig().setEnablePopBufferMerge(true); testBatchAck(() -> { try { return popMessageAsync().get(); } catch (Exception e) { throw new RuntimeException(e); } }); } @Test public void testBatchAckNormallyWithOutPopBuffer() throws Throwable { brokerController1.getBrokerConfig().setEnablePopBufferMerge(false); brokerController2.getBrokerConfig().setEnablePopBufferMerge(false); testBatchAck(() -> { try { return popMessageAsync().get(); } catch (Exception e) { throw new RuntimeException(e); } }); } @Test public void testBatchAckOrderly() throws Throwable { testBatchAck(() -> { try { return popMessageOrderlyAsync().get(); } catch (Exception e) { throw new RuntimeException(e); } }); } public void testBatchAck(Supplier popResultSupplier) throws Throwable { // Send 10 messages but do not ack, let them enter the retry topic producer.send(10); AtomicInteger firstMsgRcvNum = new AtomicInteger(); await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> { PopResult popResult = popResultSupplier.get(); if (popResult.getPopStatus().equals(PopStatus.FOUND)) { firstMsgRcvNum.addAndGet(popResult.getMsgFoundList().size()); } assertEquals(10, firstMsgRcvNum.get()); }); // sleep 6s, expect messages to enter the retry topic TimeUnit.SECONDS.sleep(6); producer.send(20); List extraInfoList = new ArrayList<>(); await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> { PopResult popResult = popResultSupplier.get(); if (popResult.getPopStatus().equals(PopStatus.FOUND)) { for (MessageExt messageExt : popResult.getMsgFoundList()) { extraInfoList.add(messageExt.getProperty(MessageConst.PROPERTY_POP_CK)); } } assertEquals(30, extraInfoList.size()); }); AckResult ackResult = client.batchAckMessageAsync(brokerAddr, topic, group, extraInfoList).get(); assertEquals(AckStatus.OK, ackResult.getStatus()); // sleep 6s, expected that messages that have been acked will not be re-consumed TimeUnit.SECONDS.sleep(6); PopResult popResult = popResultSupplier.get(); assertEquals(PopStatus.POLLING_NOT_FOUND, popResult.getPopStatus()); } private CompletableFuture popMessageAsync() { return client.popMessageAsync( brokerAddr, messageQueue, Duration.ofSeconds(3).toMillis(), 30, group, 3000, false, ConsumeInitMode.MIN, false, ExpressionType.TAG, "*"); } private CompletableFuture popMessageOrderlyAsync() { return client.popMessageAsync( brokerAddr, messageQueue, Duration.ofSeconds(3).toMillis(), 30, group, 3000, false, ConsumeInitMode.MIN, true, ExpressionType.TAG, "*", null); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/NotificationIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.MQRandomUtils; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Test; import org.junit.Ignore; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class NotificationIT extends BasePop { protected String topic; protected String group; protected RMQNormalProducer producer = null; protected RMQPopClient client = null; protected String brokerAddr; protected MessageQueue messageQueue; @Before public void setUp() { brokerAddr = brokerController1.getBrokerAddr(); topic = MQRandomUtils.getRandomTopic(); group = initConsumerGroup(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 8, CQType.SimpleCQ, TopicMessageType.NORMAL); producer = getProducer(NAMESRV_ADDR, topic); client = getRMQPopClient(); messageQueue = new MessageQueue(topic, BROKER1_NAME, -1); } @Test @Ignore public void testNotification() throws Exception { long pollTime = 500; CompletableFuture future1 = client.notification(brokerAddr, topic, group, messageQueue.getQueueId(), pollTime, System.currentTimeMillis(), 5000); CompletableFuture future2 = client.notification(brokerAddr, topic, group, messageQueue.getQueueId(), pollTime, System.currentTimeMillis(), 5000); sendMessage(1); Boolean result2 = future2.get(); assertThat(result2).isTrue(); client.popMessageAsync(brokerAddr, messageQueue, 10000, 1, group, 1000, false, ConsumeInitMode.MIN, false, null, null).get(); Boolean result1 = future1.get(); assertThat(result1).isFalse(); } @Test public void testNotificationOrderly() throws Exception { long pollTime = 500; String attemptId = "attemptId"; CompletableFuture future1 = client.notification(brokerAddr, topic, group, messageQueue.getQueueId(), true, attemptId, pollTime, System.currentTimeMillis(), 5000); CompletableFuture future2 = client.notification(brokerAddr, topic, group, messageQueue.getQueueId(), true, attemptId, pollTime, System.currentTimeMillis(), 5000); sendMessage(1); Boolean result1 = future1.get(); assertThat(result1).isTrue(); client.popMessageAsync(brokerAddr, messageQueue, 10000, 1, group, 1000, false, ConsumeInitMode.MIN, true, null, null, attemptId).get(); Boolean result2 = future2.get(); assertThat(result2).isTrue(); String attemptId2 = "attemptId2"; CompletableFuture future3 = client.notification(brokerAddr, topic, group, messageQueue.getQueueId(), true, attemptId2, pollTime, System.currentTimeMillis(), 5000); assertThat(future3.get()).isFalse(); } protected void sendMessage(int num) { MessageQueueMsg mqMsgs = new MessageQueueMsg(Lists.newArrayList(messageQueue), num); producer.send(mqMsgs.getMsgsWithMQ()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopBigMessageIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.io.IOException; import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.compression.Compressor; import org.apache.rocketmq.common.compression.CompressorFactory; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; public class PopBigMessageIT extends BasePopNormally { private static final int BODY_LEN = 3 * 1024 * 1024; static { System.setProperty(ClientConfig.DECODE_DECOMPRESS_BODY, "false"); } private Message createBigMessage() { byte[] bytes = new byte[BODY_LEN]; return new Message(topic, bytes); } @Test public void testSendAndRecvBigMsgWhenDisablePopBufferMerge() throws Throwable { brokerController1.getBrokerConfig().setEnablePopBufferMerge(false); brokerController2.getBrokerConfig().setEnablePopBufferMerge(false); this.testSendAndRecvBigMsg(); } @Test public void testSendAndRecvBigMsgWhenEnablePopBufferMerge() throws Throwable { brokerController1.getBrokerConfig().setEnablePopBufferMerge(true); brokerController2.getBrokerConfig().setEnablePopBufferMerge(true); this.testSendAndRecvBigMsg(); } /** * set DECODE_DECOMPRESS_BODY to false, then pop message from broker and not ack *

    * expect when re-consume this message, the message is not decompressed */ private void testSendAndRecvBigMsg() { Message message = createBigMessage(); producer.send(message); AtomicReference firstMessageExtRef = new AtomicReference<>(); await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> { PopResult popResult = popMessageAsync(Duration.ofSeconds(3).toMillis(), 1, 5000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); firstMessageExtRef.set(popResult.getMsgFoundList().get(0)); MessageExt messageExt = firstMessageExtRef.get(); assertMessageRecv(messageExt); }); // no ack, msg will put into pop retry topic await().atMost(Duration.ofSeconds(60)).untilAsserted(() -> { PopResult retryPopResult = popMessageAsync(Duration.ofSeconds(3).toMillis(), 1, 5000).get(); assertEquals(PopStatus.FOUND, retryPopResult.getPopStatus()); MessageExt retryMessageExt = retryPopResult.getMsgFoundList().get(0); assertMessageRecv(retryMessageExt); assertEquals(firstMessageExtRef.get().getBody().length, retryMessageExt.getBody().length); }); } private void assertMessageRecv(MessageExt messageExt) throws IOException { assertEquals(MessageSysFlag.COMPRESSED_FLAG, messageExt.getSysFlag() & MessageSysFlag.COMPRESSED_FLAG); Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(messageExt.getSysFlag())); assertEquals(BODY_LEN, compressor.decompress(messageExt.getBody()).length); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopMessageAndForwardingIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopClient; import org.apache.rocketmq.test.util.MQRandomUtils; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; public class PopMessageAndForwardingIT extends BasePop { protected String topic; protected String group; protected RMQNormalProducer producer = null; protected RMQPopClient client = null; protected String broker1Addr; protected MessageQueue broker1MessageQueue; protected String broker2Addr; protected MessageQueue broker2MessageQueue; @Before public void setUp() { broker1Addr = brokerController1.getBrokerAddr(); broker2Addr = brokerController2.getBrokerAddr(); topic = MQRandomUtils.getRandomTopic(); group = initConsumerGroup(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 8, CQType.SimpleCQ, TopicMessageType.NORMAL); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER2_NAME, 8, CQType.SimpleCQ, TopicMessageType.NORMAL); producer = getProducer(NAMESRV_ADDR, topic); client = getRMQPopClient(); broker1MessageQueue = new MessageQueue(topic, BROKER1_NAME, -1); broker2MessageQueue = new MessageQueue(topic, BROKER2_NAME, -1); } @Test public void test() { producer.send(1, broker1MessageQueue); AtomicReference firstMessageExtRef = new AtomicReference<>(); await().atMost(Duration.ofSeconds(3)).until(() -> { PopResult popResult = client.popMessageAsync(broker1Addr, broker1MessageQueue, 3000, 32, group, 1000, true, ConsumeInitMode.MIN, false, ExpressionType.TAG, "*").get(); if (!popResult.getPopStatus().equals(PopStatus.FOUND)) { return false; } firstMessageExtRef.set(popResult.getMsgFoundList().get(0)); return true; }); producer.sendMQ(firstMessageExtRef.get(), broker2MessageQueue); AtomicReference secondMessageExtRef = new AtomicReference<>(); await().atMost(Duration.ofSeconds(3)).until(() -> { PopResult popResult = client.popMessageAsync(broker2Addr, broker2MessageQueue, 3000, 32, group, 1000, true, ConsumeInitMode.MIN, false, ExpressionType.TAG, "*").get(); if (!popResult.getPopStatus().equals(PopStatus.FOUND)) { return false; } secondMessageExtRef.set(popResult.getMsgFoundList().get(0)); return true; }); assertEquals(firstMessageExtRef.get().getMsgId(), secondMessageExtRef.get().getMsgId()); String firstPopCk = firstMessageExtRef.get().getProperty(MessageConst.PROPERTY_POP_CK); String secondPopCk = secondMessageExtRef.get().getProperty(MessageConst.PROPERTY_POP_CK); assertNotEquals(firstPopCk, secondPopCk); assertEquals(BROKER1_NAME, ExtraInfoUtil.getBrokerName(ExtraInfoUtil.split(firstPopCk))); assertEquals(BROKER2_NAME, ExtraInfoUtil.getBrokerName(ExtraInfoUtil.split(secondPopCk))); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopOrderlyIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.common.message.MessageExt; import org.assertj.core.util.Lists; import org.junit.Test; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; public class PopOrderlyIT extends BasePopOrderly { /** * send 10 messages, pop one message orderly at a time *

    * expect receive this 10 messages in order */ @Test public void testPopOrderly() { sendMessage(10); await().atMost(Duration.ofSeconds(10)).until(() -> { popMessageOrderly().get(); return msgRecv.size() == 10; }); assertMessageRecvOrder(); } private CompletableFuture popMessageOrderly() { CompletableFuture future = popMessageOrderlyAsync(TimeUnit.SECONDS.toMillis(3), 1, TimeUnit.SECONDS.toMillis(30)); CompletableFuture resultFuture = new CompletableFuture<>(); future.whenComplete((popResult, throwable) -> { if (throwable != null) { resultFuture.completeExceptionally(throwable); return; } if (popResult.getMsgFoundList() == null || popResult.getMsgFoundList().isEmpty()) { resultFuture.complete(null); return; } try { for (MessageExt messageExt : popResult.getMsgFoundList()) { onRecvNewMessage(messageExt); // ack later // expect when the lock is free, pop message request can receive messages immediately after ack new Thread(() -> { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException ignored) { } ackMessageAsync(messageExt); }).start(); } resultFuture.complete(null); } catch (Throwable t) { resultFuture.completeExceptionally(t); } }); return resultFuture; } /** * send 10 messages, pop five messages orderly at a time *

    * expect only receive the first five messages */ @Test public void testPopOrderlyThenNoAck() { sendMessage(10); await().atMost(Duration.ofSeconds(5)).until(() -> { popOrderlyThenNoAck().get(); return msgRecvSequence.size() == 10; }); assertEquals(5, msgRecv.size()); for (Map.Entry> entry : msgRecv.entrySet()) { assertEquals(2, entry.getValue().size()); for (int i = 0; i < entry.getValue().size(); i++) { assertEquals(i, entry.getValue().get(i).messageExt.getReconsumeTimes()); } } assertMessageRecvOrder(); } private CompletableFuture popOrderlyThenNoAck() { CompletableFuture future = popMessageOrderlyAsync(TimeUnit.SECONDS.toMillis(3), 5, TimeUnit.SECONDS.toMillis(30)); CompletableFuture resultFuture = new CompletableFuture<>(); future.whenComplete((popResult, throwable) -> { if (throwable != null) { resultFuture.completeExceptionally(throwable); return; } if (popResult.getMsgFoundList() == null || popResult.getMsgFoundList().isEmpty()) { resultFuture.complete(null); return; } try { for (MessageExt messageExt : popResult.getMsgFoundList()) { onRecvNewMessage(messageExt); } resultFuture.complete(null); } catch (Throwable t) { resultFuture.completeExceptionally(t); } }); return resultFuture; } /** * send one message, changeInvisibleTime to 5s later at the first time *

    * expect receive two times */ @Test public void testPopMessageOrderlyThenChangeInvisibleTime() { sendMessage(1); await().atMost(Duration.ofSeconds(15)).until(() -> { popMessageOrderlyThenChangeInvisibleTime().get(); return msgRecvSequence.size() == 2; }); assertMsgRecv(1, 2); assertMessageRecvOrder(); } private CompletableFuture popMessageOrderlyThenChangeInvisibleTime() { CompletableFuture future = popMessageOrderlyAsync(TimeUnit.SECONDS.toMillis(3), 1, TimeUnit.SECONDS.toMillis(30)); CompletableFuture resultFuture = new CompletableFuture<>(); future.whenComplete((popResult, throwable) -> { if (throwable != null) { resultFuture.completeExceptionally(throwable); return; } if (popResult.getMsgFoundList() == null || popResult.getMsgFoundList().isEmpty()) { resultFuture.complete(null); return; } try { for (MessageExt messageExt : popResult.getMsgFoundList()) { onRecvNewMessage(messageExt); if (msgRecvSequence.size() == 1) { try { TimeUnit.MILLISECONDS.sleep(1); changeInvisibleTimeAsync(messageExt, 5000).get(); } catch (Exception e) { resultFuture.completeExceptionally(e); return; } } else { try { ackMessageAsync(messageExt).get(); } catch (Exception e) { resultFuture.completeExceptionally(e); return; } } } resultFuture.complete(null); } catch (Throwable t) { resultFuture.completeExceptionally(t); } }); return resultFuture; } /** * send three messages (msg1, msg2, msg3, msg4) and the max message num of pop request is three *

    * ack msg1 and msg3, changeInvisibleTime msg2 *

    * expect the sequence of messages received is: msg1, msg2, msg3, msg2, msg3, msg4 */ @Test public void testPopMessageOrderlyThenChangeInvisibleTimeMidMessage() { producer.send(4); await().atMost(Duration.ofSeconds(5)).until(() -> { popMessageOrderlyThenChangeInvisibleTimeMidMessage().get(); return msgRecvSequence.size() == 6; }); assertMsgRecv(0, 1); assertMsgRecv(1, 2); assertMsgRecv(2, 2); assertMsgRecv(5, 1); assertEquals(msgRecvSequence.get(1), msgRecvSequence.get(3)); assertEquals(msgRecvSequence.get(2), msgRecvSequence.get(4)); } private CompletableFuture popMessageOrderlyThenChangeInvisibleTimeMidMessage() { CompletableFuture future = popMessageOrderlyAsync(5000, 3, TimeUnit.SECONDS.toMillis(30)); CompletableFuture resultFuture = new CompletableFuture<>(); future.whenComplete((popResult, throwable) -> { if (throwable != null) { resultFuture.completeExceptionally(throwable); return; } if (popResult.getMsgFoundList() == null || popResult.getMsgFoundList().isEmpty()) { resultFuture.complete(null); return; } try { for (MessageExt messageExt : popResult.getMsgFoundList()) { onRecvNewMessage(messageExt); if (msgRecv.size() != 2) { try { ackMessageAsync(messageExt).get(); } catch (Exception e) { resultFuture.completeExceptionally(e); return; } } else { try { TimeUnit.MILLISECONDS.sleep(1); changeInvisibleTimeAsync(messageExt, 3000).get(); } catch (Exception e) { resultFuture.completeExceptionally(e); return; } } } resultFuture.complete(null); } catch (Throwable t) { resultFuture.completeExceptionally(t); } }); return resultFuture; } @Test public void testReentrant() { producer.send(1); popMessageForReentrant(null).join(); assertMsgRecv(0, 1, Lists.newArrayList(0)); String attemptId01 = "attemptId-01"; popMessageForReentrant(attemptId01).join(); assertMsgRecv(0, 2, Lists.newArrayList(0, 1)); popMessageForReentrant(attemptId01).join(); assertMsgRecv(0, 3, Lists.newArrayList(0, 1, 1)); String attemptId02 = "attemptId-02"; await().atLeast(Duration.ofSeconds(5)).atMost(Duration.ofSeconds(15)).until(() -> { popMessageForReentrant(attemptId02).join(); return msgRecvSequence.size() == 4; }); popMessageForReentrant(attemptId02).join(); assertMsgRecv(0, 5, Lists.newArrayList(0, 1, 1, 2, 2)); await().atLeast(Duration.ofSeconds(5)).atMost(Duration.ofSeconds(15)).until(() -> { popMessageForReentrant(null).join(); return msgRecvSequence.size() == 6; }); assertMsgRecv(0, 6, Lists.newArrayList(0, 1, 1, 2, 2, 3)); } private CompletableFuture popMessageForReentrant(String attemptId) { return popMessageOrderlyAsync(TimeUnit.SECONDS.toMillis(10), 3, TimeUnit.SECONDS.toMillis(30), attemptId) .thenAccept(popResult -> { for (MessageExt messageExt : popResult.getMsgFoundList()) { onRecvNewMessage(messageExt); } }); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopPriorityIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.attribute.AttributeParser; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.util.TestUtil; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.apache.rocketmq.common.SubscriptionGroupAttributes.PRIORITY_FACTOR_ATTRIBUTE; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(Parameterized.class) public class PopPriorityIT extends BasePopNormally { private final boolean popConsumerKVServiceEnable; private final boolean priorityOrderAsc; private int writeQueueNum = 8; public PopPriorityIT(boolean popConsumerKVServiceEnable, boolean priorityOrderAsc) { this.popConsumerKVServiceEnable = popConsumerKVServiceEnable; this.priorityOrderAsc = priorityOrderAsc; } @Parameterized.Parameters public static List params() { List result = new ArrayList<>(); result.add(new Object[] {false, true}); result.add(new Object[] {false, false}); result.add(new Object[] {true, true}); result.add(new Object[] {true, false}); return result; } @Before public void setUp() { super.setUp(); // reset default config if changed writeQueueNum = 8; brokerController1.getBrokerConfig().setPopFromRetryProbabilityForPriority(0); brokerController1.getBrokerConfig().setUseSeparateRetryQueue(false); brokerController1.getBrokerConfig().setPopConsumerKVServiceEnable(popConsumerKVServiceEnable); brokerController1.getBrokerConfig().setPriorityOrderAsc(priorityOrderAsc); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, writeQueueNum, CQType.SimpleCQ, TopicMessageType.PRIORITY); } @After public void tearDown() { super.tearDown(); } @Test public void test_normal_send() { int priority = -1; // normal message Set queueIdSet = new HashSet<>(); for (int i = 0; i < 32; i++) { Message message = mockMessage(topic, priority, ""); SendResult sendResult = producer.send(message, null).getSendResultObj(); queueIdSet.add(sendResult.getMessageQueue().getQueueId()); } assertTrue(queueIdSet.size() > 1); } @Test public void test_priority_send() { final int priority = 0; // priority message for (int i = 0; i < 32; i++) { Message message = mockMessage(topic, priority, ""); SendResult sendResult = producer.send(message, null).getSendResultObj(); assertEquals(priority, sendResult.getMessageQueue().getQueueId()); } } @Test public void test_priority_consume_always_high_priority() throws Exception { int msgNumPerQueue = 20; final int maxPriority = priorityOrderAsc ? writeQueueNum - 1 : 0; for (int i = 0; i < writeQueueNum; i++) { Message message = mockMessage(topic, i, String.valueOf(i)); for (int j = 0; j < msgNumPerQueue; j++) { producer.send(message); } } Assert.assertTrue(awaitDispatchMs(2000)); for (int i = 0; i < msgNumPerQueue; i++) { PopResult popResult = popMessageAsync(Duration.ofSeconds(600).toMillis(), 1, 30000).get(); TestUtil.waitForMonment(20); // wait lock release assertEquals(PopStatus.FOUND, popResult.getPopStatus()); MessageExt message = popResult.getMsgFoundList().get(0); assertEquals(maxPriority, message.getPriority()); // not a coincidence } } @Test public void test_priority_consume_from_high_to_low() throws Exception { for (int i = 0; i < writeQueueNum; i++) { Message message = mockMessage(topic, i, String.valueOf(i)); producer.send(message); } Assert.assertTrue(awaitDispatchMs(2000)); for (int i = 0; i < writeQueueNum; i++) { PopResult popResult = popMessageAsync(Duration.ofSeconds(30).toMillis(), 1, 30000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); MessageExt message = popResult.getMsgFoundList().get(0); int expectPriority = priorityOrderAsc ? writeQueueNum - 1 - i : i; assertEquals(0, message.getQueueOffset()); assertEquals(expectPriority, message.getQueueId()); assertEquals(expectPriority, message.getPriority()); } } @Test public void test_priority_consume_disable() throws Exception { SubscriptionGroupConfig config = new SubscriptionGroupConfig(); config.setGroupName(group); config.setAttributes(AttributeParser.parseToMap("+" + PRIORITY_FACTOR_ATTRIBUTE.getName() + "=0")); initConsumerGroup(config); int msgNumPerQueue = 200; for (int i = 0; i < writeQueueNum; i++) { Message message = mockMessage(topic, i, String.valueOf(i)); for (int j = 0; j < msgNumPerQueue; j++) { producer.send(message); } } Assert.assertTrue(awaitDispatchMs(2000)); int sampleCount = 800; int[] queueIdCount = new int[writeQueueNum]; for (int i = 0; i < sampleCount; i++) { PopResult popResult = popMessageAsync(Duration.ofSeconds(600).toMillis(), 1, 30000).get(); TestUtil.waitForMonment(10); // wait lock release assertEquals(PopStatus.FOUND, popResult.getPopStatus()); MessageExt message = popResult.getMsgFoundList().get(0); queueIdCount[message.getQueueId()] = queueIdCount[message.getQueueId()] + 1; } double expectAverage = (double) sampleCount / writeQueueNum; for (int count : queueIdCount) { assertTrue(Math.abs(count - expectAverage) < expectAverage * 0.4); } } @Test public void test_priority_consume_retry_as_lowest() throws Exception { // retry as lowest by default int count = 100; for (int i = 0; i < count; i++) { Message message = mockMessage(topic, new Random().nextInt(writeQueueNum), String.valueOf(i)); producer.send(message); } int invisibleTime = 3; PopResult popResult = popMessageAsync(Duration.ofSeconds(invisibleTime).toMillis(), 1, 30000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); String retryId = popResult.getMsgFoundList().get(0).getMsgId(); TestUtil.waitForSeconds(invisibleTime + 3); Assert.assertTrue(awaitDispatchMs(2000)); List collect = new ArrayList<>(); await() .pollInterval(1, TimeUnit.SECONDS) .atMost(35, TimeUnit.SECONDS) .until(() -> { PopResult result = popMessageAsync(Duration.ofSeconds(600).toMillis(), 32, 5000).get(); if (PopStatus.FOUND.equals(result.getPopStatus())) { collect.addAll(result.getMsgFoundList()); return false; } return true; }); assertEquals(count, collect.size()); assertEquals(1, collect.get(collect.size() - 1).getReconsumeTimes()); assertEquals(retryId, collect.get(collect.size() - 1).getMsgId()); } @Test public void test_priority_consume_retry_as_highest() throws Exception { brokerController1.getBrokerConfig().setPopFromRetryProbabilityForPriority(100); int count = 100; for (int i = 0; i < count; i++) { Message message = mockMessage(topic, new Random().nextInt(writeQueueNum), String.valueOf(i)); producer.send(message); } int invisibleTime = 3; PopResult popResult = popMessageAsync(Duration.ofSeconds(invisibleTime).toMillis(), 1, 30000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); String retryId = popResult.getMsgFoundList().get(0).getMsgId(); TestUtil.waitForSeconds(invisibleTime + 3); Assert.assertTrue(awaitDispatchMs(2000)); List collect = new ArrayList<>(); await() .pollInterval(1, TimeUnit.SECONDS) .atMost(35, TimeUnit.SECONDS) .until(() -> { PopResult result = popMessageAsync(Duration.ofSeconds(600).toMillis(), 32, 5000).get(); if (PopStatus.FOUND.equals(result.getPopStatus())) { collect.addAll(result.getMsgFoundList()); return false; } return true; }); assertEquals(count, collect.size()); assertEquals(1, collect.get(0).getReconsumeTimes()); assertEquals(retryId, collect.get(0).getMsgId()); } @Test public void test_priority_consume_use_separate_retry_queue() throws Exception { brokerController1.getBrokerConfig().setUseSeparateRetryQueue(true); brokerController1.getBrokerConfig().setPopFromRetryProbabilityForPriority(100); for (int i = 0; i < writeQueueNum; i++) { Message message = mockMessage(topic, i, String.valueOf(i)); producer.send(message); } Assert.assertTrue(awaitDispatchMs(2000)); int invisibleTime = 3; PopResult popResult = popMessageAsync(Duration.ofSeconds(invisibleTime).toMillis(), writeQueueNum, 30000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); assertEquals(writeQueueNum, popResult.getMsgFoundList().size()); TestUtil.waitForSeconds(invisibleTime + 3); popResult = popMessageAsync(Duration.ofSeconds(600).toMillis(), 32, 10000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); assertEquals(writeQueueNum, popResult.getMsgFoundList().size()); for (int i = 0; i < writeQueueNum; i++) { MessageExt message = popResult.getMsgFoundList().get(i); assertEquals(0, message.getQueueOffset()); // means a separate retry queue assertEquals(1, message.getReconsumeTimes()); int expectPriority = priorityOrderAsc ? writeQueueNum - 1 - i : i; assertEquals(expectPriority, message.getQueueId()); assertEquals(expectPriority, message.getPriority()); } } @Test public void test_priority_consume_use_separate_retry_queue_with_queue_expansion() throws Exception { // retry as lowest by default brokerController1.getBrokerConfig().setUseSeparateRetryQueue(true); for (int i = 0; i < writeQueueNum; i++) { Message message = mockMessage(topic, i, String.valueOf(i)); producer.send(message); } Assert.assertTrue(awaitDispatchMs(2000)); int invisibleTime = 3; PopResult popResult = popMessageAsync(Duration.ofSeconds(invisibleTime).toMillis(), writeQueueNum, 30000).get(); assertEquals(PopStatus.FOUND, popResult.getPopStatus()); assertEquals(writeQueueNum, popResult.getMsgFoundList().size()); TestUtil.waitForSeconds(invisibleTime + 3); // wait retry created writeQueueNum = writeQueueNum * 2; IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, writeQueueNum, CQType.SimpleCQ, TopicMessageType.PRIORITY); for (int i = writeQueueNum / 2; i < writeQueueNum; i++) { Message message = mockMessage(topic, i, String.valueOf(i)); producer.send(message); } Assert.assertTrue(awaitDispatchMs(2000)); popResult = popMessageAsync(Duration.ofSeconds(invisibleTime).toMillis(), 32, 5000).get(); List msgList = popResult.getMsgFoundList(); // asc == true, collect: [15 -> 8, 7 -> 0] // asc == false, collect: [8 -> 15, 0 -> 7] assertEquals(writeQueueNum, msgList.size()); assertEquals(priorityOrderAsc ? writeQueueNum - 1 : writeQueueNum / 2, msgList.get(0).getQueueId()); assertEquals(priorityOrderAsc ? writeQueueNum - 1 : writeQueueNum / 2, msgList.get(0).getPriority()); assertEquals(priorityOrderAsc ? 0 : writeQueueNum / 2 - 1, msgList.get(msgList.size() - 1).getQueueId()); assertEquals(priorityOrderAsc ? 0 : writeQueueNum / 2 - 1, msgList.get(msgList.size() - 1).getPriority()); assertEquals(1, msgList.get(msgList.size() - 1).getReconsumeTimes()); assertEquals(0, msgList.get(msgList.size() - 1).getQueueOffset()); // means a separate retry queue } private static Message mockMessage(String topic, int priority, String key) { Message msg = new Message(topic, "HW".getBytes()); if (priority >= 0) { msg.setPriority(priority); } msg.setKeys(key); return msg; } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopSubCheckIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.pop; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.RandomUtil; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class PopSubCheckIT extends BaseConf { private static final Logger log = LoggerFactory.getLogger(PopSubCheckIT.class); private String group; private DefaultMQAdminExt defaultMQAdminExt; @Before public void setUp() throws Exception { group = initConsumerGroup(); defaultMQAdminExt = new DefaultMQAdminExt(); defaultMQAdminExt.setInstanceName(RandomUtil.getStringByUUID()); defaultMQAdminExt.start(); } @After public void tearDown() { defaultMQAdminExt.shutdown(); super.shutdown(); } @Ignore @Test public void testNormalPopAck() throws Exception { String topic = initTopic(); log.info("use topic: {}; group: {} !", topic, group); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); producer.getProducer().setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); for (String brokerAddr : new String[]{brokerController1.getBrokerAddr(), brokerController2.getBrokerAddr()}) { defaultMQAdminExt.setMessageRequestMode(brokerAddr, topic, group, MessageRequestMode.POP, 8, 60_000); } RMQPopConsumer consumer = ConsumerFactory.getRMQPopConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener()); mqClients.add(consumer); int msgNum = 1; producer.send(msgNum); Assert.assertEquals("Not all sent succeeded", msgNum, producer.getAllUndupMsgBody().size()); log.info(producer.getFirstMsg().toString()); TestUtils.waitForSeconds(10); consumer.getListener().waitForMessageConsume(msgNum, 30_000); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); for (Object o : consumer.getListener().getAllOriginMsg()) { MessageClientExt msg = (MessageClientExt) o; assertThat(msg.getProperty(MessageConst.PROPERTY_POP_CK)).named("check pop meta").isNotEmpty(); } consumer.getListener().waitForMessageConsume(msgNum, 3_000 * 9); assertThat(consumer.getListener().getAllOriginMsg().size()).isEqualTo(msgNum); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/MulTagSubIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.tag; import java.util.List; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.factory.TagMessage; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class MulTagSubIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); String consumerId = initConsumerGroup(); logger.info(String.format("use topic: %s; consumerId: %s !", topic, consumerId)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testSubTwoTabMessageOnsTag() { String tag = "jueyin1"; String subExpress = String.format("%s||jueyin2", tag); int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tag, msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubTwoTabAndMatchOne() { String tag1 = "jueyin1"; String tag2 = "jueyin2"; String subExpress = String.format("%s||noExistTag", tag2); int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tag1, msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); List tag2Msgs = MQMessageFactory.getRMQMessage(tag2, topic, msgSize); producer.send(tag2Msgs); Assert.assertEquals("Not all sent succeeded", msgSize * 2, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(MQMessageFactory.getMessageBody(tag2Msgs), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(MQMessageFactory.getMessageBody(tag2Msgs)); } @Test public void testSubTwoTabAndMatchTwo() { String[] tags = {"jueyin1", "jueyin2"}; String subExpress = String.format("%s||%s", tags[0], tags[1]); int msgSize = 10; TagMessage tagMessage = new TagMessage(tags, topic, msgSize); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tagMessage.getMixedTagMessages()); Assert.assertEquals("Not all sent succeeded", msgSize * tags.length, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getAllTagMessageBody()); } @Test public void testSubThreeTabAndMatchTwo() { String[] tags = {"jueyin1", "jueyin2", "jueyin3"}; String subExpress = String.format("%s||%s", tags[0], tags[1]); int msgSize = 10; TagMessage tagMessage = new TagMessage(tags, topic, msgSize); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tagMessage.getMixedTagMessages()); Assert.assertEquals("Not all sent succeeded", msgSize * tags.length, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume( tagMessage.getMessageBodyByTag(tags[0], tags[1]), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())).containsExactlyElementsIn( tagMessage.getMessageBodyByTag(tags[0], tags[1])); } @Test public void testNoMatch() { String[] tags = {"jueyin1", "jueyin2", "jueyin3"}; String subExpress = "no_match"; int msgSize = 10; TagMessage tagMessage = new TagMessage(tags, topic, msgSize); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tagMessage.getMixedTagMessages()); Assert.assertEquals("Not all sent succeeded", msgSize * tags.length, producer.getAllUndupMsgBody().size()); TestUtils.waitForSeconds(5); assertThat(VerifyUtils .getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody()) .size()).isEqualTo(0); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWith1ConsumerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.tag; import java.util.List; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class TagMessageWith1ConsumerIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); String consumerId = initConsumerGroup(); logger.info(String.format("use topic: %s; consumerId: %s !", topic, consumerId)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTagSmoke() { String tag = "jueyin"; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, tag, new RMQNormalListener()); producer.send(tag, msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubAllMessageNoTag() { String subExprress = "*"; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExprress, new RMQNormalListener()); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubAllMessageWithTag() { String tag = "jueyin"; String subExpress = "*"; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tag, msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubAllMessageWithNullTag() { String tag = null; String subExpress = "*"; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tag, msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubNullWithTagNull() { String tag = null; String subExpress = null; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); producer.send(tag, msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubAllWithKindsOfMessage() { String tag1 = null; String tag2 = "jueyin"; String subExpress = "*"; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); List tag1Msgs = MQMessageFactory.getRMQMessage(tag1, topic, msgSize); List tag2Msgs = MQMessageFactory.getRMQMessage(tag2, topic, msgSize); producer.send(tag1Msgs); producer.send(tag2Msgs); producer.send(10); Assert.assertEquals("Not all are sent", msgSize * 3, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubNullWithKindsOfMessage() { String tag1 = null; String tag2 = "jueyin"; String subExpress = null; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); List tag1Msgs = MQMessageFactory.getRMQMessage(tag1, topic, msgSize); List tag2Msgs = MQMessageFactory.getRMQMessage(tag2, topic, msgSize); producer.send(tag1Msgs); producer.send(tag2Msgs); Assert.assertEquals("Not all are sent", msgSize * 2, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testSubTagWithKindsOfMessage() { String tag1 = null; String tag2 = "jueyin"; String subExpress = tag2; int msgSize = 10; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, subExpress, new RMQNormalListener()); List tag1Msgs = MQMessageFactory.getRMQMessage(tag1, topic, msgSize); List tag2Msgs = MQMessageFactory.getRMQMessage(tag2, topic, msgSize); producer.send(tag1Msgs); producer.send(tag2Msgs); producer.send(10); Assert.assertEquals("Not all are sent", msgSize * 3, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(MQMessageFactory.getMessageBody(tag2Msgs), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(MQMessageFactory.getMessageBody(tag2Msgs)); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithMulConsumerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.tag; import java.util.Collection; import java.util.List; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.factory.TagMessage; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class TagMessageWithMulConsumerIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); String consumerId = initConsumerGroup(); logger.info(String.format("use topic: %s; consumerId: %s !", topic, consumerId)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testSendTwoTag() { String tag1 = "jueyin1"; String tag2 = "jueyin2"; int msgSize = 10; RMQNormalConsumer consumerTag1 = getConsumer(NAMESRV_ADDR, topic, tag1, new RMQNormalListener()); RMQNormalConsumer consumerTag2 = getConsumer(NAMESRV_ADDR, topic, tag2, new RMQNormalListener()); List tag1Msgs = MQMessageFactory.getRMQMessage(tag1, topic, msgSize); producer.send(tag1Msgs); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); List tag2Msgs = MQMessageFactory.getRMQMessage(tag2, topic, msgSize); producer.send(tag2Msgs); Assert.assertEquals("Not all are sent", msgSize * 2, producer.getAllUndupMsgBody().size()); consumerTag1.getListener().waitForMessageConsume(MQMessageFactory.getMessageBody(tag1Msgs), CONSUME_TIME); consumerTag2.getListener().waitForMessageConsume(MQMessageFactory.getMessageBody(tag2Msgs), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerTag1.getListener().getAllMsgBody())) .containsExactlyElementsIn(MQMessageFactory.getMessageBody(tag1Msgs)); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerTag2.getListener().getAllMsgBody())) .containsExactlyElementsIn(MQMessageFactory.getMessageBody(tag2Msgs)); } @Test public void testSendMessagesWithTwoTag() { String[] tags = {"jueyin1", "jueyin2"}; int msgSize = 10; TagMessage tagMessage = new TagMessage(tags, topic, msgSize); RMQNormalConsumer consumerTag1 = getConsumer(NAMESRV_ADDR, topic, tags[0], new RMQNormalListener()); RMQNormalConsumer consumerTag2 = getConsumer(NAMESRV_ADDR, topic, tags[1], new RMQNormalListener()); List tagMsgs = tagMessage.getMixedTagMessages(); producer.send(tagMsgs); Assert.assertEquals("Not all are sent", msgSize * tags.length, producer.getAllUndupMsgBody().size()); consumerTag1.getListener().waitForMessageConsume(tagMessage.getMessageBodyByTag(tags[0]), CONSUME_TIME); consumerTag2.getListener().waitForMessageConsume(tagMessage.getMessageBodyByTag(tags[1]), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerTag1.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getMessageBodyByTag(tags[0])); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerTag2.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getMessageBodyByTag(tags[1])); } @Test public void testTwoConsumerOneMatchOneOtherMatchAll() { String[] tags = {"jueyin1", "jueyin2"}; String sub1 = String.format("%s||%s", tags[0], tags[1]); String sub2 = String.format("%s|| noExist", tags[0]); int msgSize = 10; TagMessage tagMessage = new TagMessage(tags, topic, msgSize); RMQNormalConsumer consumerTag1 = getConsumer(NAMESRV_ADDR, topic, sub1, new RMQNormalListener()); RMQNormalConsumer consumerTag2 = getConsumer(NAMESRV_ADDR, topic, sub2, new RMQNormalListener()); List tagMsgs = tagMessage.getMixedTagMessages(); producer.send(tagMsgs); Assert.assertEquals("Not all are sent", msgSize * tags.length, producer.getAllUndupMsgBody().size()); consumerTag1.getListener().waitForMessageConsume(tagMessage.getMessageBodyByTag(tags), CONSUME_TIME); consumerTag2.getListener().waitForMessageConsume(tagMessage.getMessageBodyByTag(tags[0]), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerTag1.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getAllTagMessageBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerTag2.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getMessageBodyByTag(tags[0])); } @Test public void testSubKindsOf() { String[] tags = {"jueyin1", "jueyin2"}; String sub1 = String.format("%s||%s", tags[0], tags[1]); String sub2 = String.format("%s|| noExist", tags[0]); String sub3 = tags[0]; String sub4 = "*"; int msgSize = 10; RMQNormalConsumer consumerSubTwoMatchAll = getConsumer(NAMESRV_ADDR, topic, sub1, new RMQNormalListener()); RMQNormalConsumer consumerSubTwoMachieOne = getConsumer(NAMESRV_ADDR, topic, sub2, new RMQNormalListener()); RMQNormalConsumer consumerSubTag1 = getConsumer(NAMESRV_ADDR, topic, sub3, new RMQNormalListener()); RMQNormalConsumer consumerSubAll = getConsumer(NAMESRV_ADDR, topic, sub4, new RMQNormalListener()); producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); Collection msgsWithNoTag = producer.getMsgBodysCopy(); TagMessage tagMessage = new TagMessage(tags, topic, msgSize); List tagMsgs = tagMessage.getMixedTagMessages(); producer.send(tagMsgs); Assert.assertEquals("Not all are sent", msgSize * 3, producer.getAllUndupMsgBody().size()); consumerSubTwoMatchAll.getListener() .waitForMessageConsume(tagMessage.getMessageBodyByTag(tags), CONSUME_TIME); consumerSubTwoMachieOne.getListener() .waitForMessageConsume(tagMessage.getMessageBodyByTag(tags[0]), CONSUME_TIME); consumerSubTag1.getListener().waitForMessageConsume(tagMessage.getMessageBodyByTag(tags[0]), CONSUME_TIME); consumerSubAll.getListener().waitForMessageConsume( MQMessageFactory.getMessage(msgsWithNoTag, tagMessage.getAllTagMessageBody()), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerSubTwoMatchAll.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getAllTagMessageBody()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerSubTwoMachieOne.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getMessageBodyByTag(tags[0])); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerSubTag1.getListener().getAllMsgBody())) .containsExactlyElementsIn(tagMessage.getMessageBodyByTag(tags[0])); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumerSubAll.getListener().getAllMsgBody())) .containsExactlyElementsIn(MQMessageFactory.getMessage(msgsWithNoTag, tagMessage.getAllTagMessageBody())); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithSameGroupConsumerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.tag; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.RandomUtils; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class TagMessageWithSameGroupConsumerIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQNormalProducer producer = null; private String topic = null; private String tag = "tag"; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerWithSameGroup() { int msgSize = 20; String originMsgDCName = RandomUtils.getStringByUUID(); String msgBodyDCName = RandomUtils.getStringByUUID(); RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, tag, new RMQNormalListener(originMsgDCName, msgBodyDCName)); getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), tag, new RMQNormalListener(originMsgDCName, msgBodyDCName)); producer.send(tag, msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testConsumerStartWithInterval() { int msgSize = 100; String originMsgDCName = RandomUtils.getStringByUUID(); String msgBodyDCName = RandomUtils.getStringByUUID(); RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, tag, new RMQNormalListener(originMsgDCName, msgBodyDCName)); producer.send(tag, msgSize, 100); TestUtils.waitForMoment(5); getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), tag, new RMQNormalListener(originMsgDCName, msgBodyDCName)); TestUtils.waitForMoment(5); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testConsumerStartTwoAndCrashOneAfterWhile() { int msgSize = 100; String originMsgDCName = RandomUtils.getStringByUUID(); String msgBodyDCName = RandomUtils.getStringByUUID(); RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, tag, new RMQNormalListener(originMsgDCName, msgBodyDCName)); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), tag, new RMQNormalListener(originMsgDCName, msgBodyDCName)); producer.send(tag, msgSize, 100); TestUtils.waitForMoment(5); consumer2.shutdown(); mqClients.remove(1); TestUtils.waitForMoment(5); consumer1.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/topic/MulConsumerMulTopicIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.topic; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class MulConsumerMulTopicIT extends BaseConf { private RMQNormalProducer producer = null; @Before public void setUp() { producer = getProducer(NAMESRV_ADDR, null); } @After public void tearDown() { super.shutdown(); } @Test public void testSynSendMessage() { int msgSize = 10; String topic1 = initTopic(); String topic2 = initTopic(); RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer1.subscribe(topic2, "*"); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic1, "*", new RMQNormalListener()); consumer2.subscribe(topic2, "*"); producer.send(MQMessageFactory.getMsg(topic1, msgSize)); producer.send(MQMessageFactory.getMsg(topic2, msgSize)); Assert.assertEquals("Not all sent succeeded", msgSize * 2, producer.getAllUndupMsgBody().size()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); } @Test public void testConsumeWithDiffTag() { int msgSize = 10; String topic1 = initTopic(); String topic2 = initTopic(); String tag = "jueyin_tag"; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer1.subscribe(topic2, tag); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic1, "*", new RMQNormalListener()); consumer2.subscribe(topic2, tag); producer.send(MQMessageFactory.getMsg(topic1, msgSize)); producer.send(MQMessageFactory.getMsg(topic2, msgSize, tag)); Assert.assertEquals("Not all sent succeeded", msgSize * 2, producer.getAllUndupMsgBody().size()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); } @Test public void testConsumeWithDiffTagAndFilter() { int msgSize = 10; String topic1 = initTopic(); String topic2 = initTopic(); String tag1 = "jueyin_tag_1"; String tag2 = "jueyin_tag_2"; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer1.subscribe(topic2, tag1); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer2.subscribe(topic2, tag1); producer.send(MQMessageFactory.getMsg(topic2, msgSize, tag2)); producer.clearMsg(); producer.send(MQMessageFactory.getMsg(topic1, msgSize)); producer.send(MQMessageFactory.getMsg(topic2, msgSize, tag1)); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/consumer/topic/OneConsumerMulTopicIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.consumer.topic; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OneConsumerMulTopicIT extends BaseConf { private RMQNormalProducer producer = null; @Before public void setUp() { producer = getProducer(NAMESRV_ADDR, null); } @After public void tearDown() { super.shutdown(); } @Test public void testSynSendMessage() { int msgSize = 10; String topic1 = initTopic(); String topic2 = initTopic(); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer.subscribe(topic2, "*"); producer.send(MQMessageFactory.getMsg(topic1, msgSize)); producer.send(MQMessageFactory.getMsg(topic2, msgSize)); Assert.assertEquals("Not all are sent", msgSize * 2, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testConsumeWithDiffTag() { int msgSize = 10; String topic1 = initTopic(); String topic2 = initTopic(); String tag = "jueyin_tag"; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer.subscribe(topic2, tag); producer.send(MQMessageFactory.getMsg(topic1, msgSize)); producer.send(MQMessageFactory.getMsg(topic2, msgSize, tag)); Assert.assertEquals("Not all are sent", msgSize * 2, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } @Test public void testConsumeWithDiffTagAndFilter() { int msgSize = 10; String topic1 = initTopic(); String topic2 = initTopic(); String tag1 = "jueyin_tag_1"; String tag2 = "jueyin_tag_2"; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic1, "*", new RMQNormalListener()); consumer.subscribe(topic2, tag1); producer.send(MQMessageFactory.getMsg(topic2, msgSize, tag2)); producer.clearMsg(); producer.send(MQMessageFactory.getMsg(topic1, msgSize)); producer.send(MQMessageFactory.getMsg(topic2, msgSize, tag1)); Assert.assertEquals("Not all are sent", msgSize * 2, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendExceptionIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.async; import java.util.List; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.factory.ProducerFactory; import org.apache.rocketmq.test.factory.SendCallBackFactory; import org.apache.rocketmq.test.util.RandomUtils; import org.apache.rocketmq.test.util.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class AsyncSendExceptionIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private static boolean sendFail = false; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); } @After public void tearDown() { super.shutdown(); } @Test public void testSendCallBackNull() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); SendCallback sendCallback = null; producer.send(msg, sendCallback); } @Test public void testSendMQNull() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); MessageQueue messageQueue = null; producer.send(msg, messageQueue, SendCallBackFactory.getSendCallBack()); } @Test public void testSendSelectorNull() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); MessageQueueSelector selector = null; producer.send(msg, selector, 100, SendCallBackFactory.getSendCallBack()); } @Test public void testSelectorThrowsException() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); producer.send(msg, new MessageQueueSelector() { @Override public MessageQueue select(List list, Message message, Object o) { String str = null; return list.get(str.length()); } }, null, SendCallBackFactory.getSendCallBack()); } @Test public void testQueueIdBigThanQueueNum() throws Exception { int queueId = 100; sendFail = false; MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, queueId); Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); producer.send(msg, mq, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { } @Override public void onException(Throwable throwable) { sendFail = true; } }); int checkNum = 50; while (!sendFail && checkNum > 0) { checkNum--; TestUtils.waitForMoment(100); } producer.shutdown(); assertThat(sendFail).isEqualTo(true); } @Test public void testQueueIdSmallZero() throws Exception { int queueId = -100; sendFail = true; MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, queueId); Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); producer.send(msg, mq, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { sendFail = false; } @Override public void onException(Throwable throwable) { sendFail = true; } }); int checkNum = 50; while (sendFail && checkNum > 0) { checkNum--; TestUtils.waitForMoment(100); } producer.shutdown(); assertThat(sendFail).isEqualTo(false); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendWithMessageQueueIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.async; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class AsyncSendWithMessageQueueIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQAsyncSendProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); producer = getAsyncProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testAsyncSendWithMQ() { int msgSize = 20; int queueId = 0; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, queueId); producer.asyncSend(msgSize, mq); producer.waitForResponse(10 * 1000); assertThat(producer.getSuccessMsgCount()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); VerifyUtils.verifyMessageQueueId(queueId, consumer.getListener().getAllOriginMsg()); producer.clearMsg(); consumer.clearMsg(); producer.getSuccessSendResult().clear(); mq = new MessageQueue(topic, BROKER2_NAME, queueId); producer.asyncSend(msgSize, mq); producer.waitForResponse(10 * 1000); assertThat(producer.getSuccessMsgCount()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); VerifyUtils.verifyMessageQueueId(queueId, consumer.getListener().getAllOriginMsg()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendWithMessageQueueSelectorIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.async; import java.util.List; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class AsyncSendWithMessageQueueSelectorIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQAsyncSendProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); producer = getAsyncProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testSendWithSelector() { int msgSize = 20; final int queueId = 0; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); producer.asyncSend(msgSize, new MessageQueueSelector() { @Override public MessageQueue select(List list, Message message, Object o) { for (MessageQueue mq : list) { if (mq.getQueueId() == queueId && mq.getBrokerName().equals(BROKER1_NAME)) { return mq; } } return list.get(0); } }); producer.waitForResponse(5 * 1000); assertThat(producer.getSuccessMsgCount()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); VerifyUtils.verifyMessageQueueId(queueId, consumer.getListener().getAllOriginMsg()); producer.clearMsg(); consumer.clearMsg(); producer.getSuccessSendResult().clear(); producer.asyncSend(msgSize, new MessageQueueSelector() { @Override public MessageQueue select(List list, Message message, Object o) { for (MessageQueue mq : list) { if (mq.getQueueId() == queueId && mq.getBrokerName().equals(BROKER2_NAME)) { return mq; } } return list.get(8); } }); producer.waitForResponse(5 * 1000); assertThat(producer.getSuccessMsgCount()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); VerifyUtils.verifyMessageQueueId(queueId, consumer.getListener().getAllOriginMsg()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/async/AsyncSendWithOnlySendCallBackIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.async; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class AsyncSendWithOnlySendCallBackIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQAsyncSendProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); producer = getAsyncProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testSendWithOnlyCallBack() { int msgSize = 20; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); producer.asyncSend(msgSize); producer.waitForResponse(10 * 1000); assertThat(producer.getSuccessMsgCount()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/batch/BatchSendIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.batch; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.TopicAttributes; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageBatch; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.factory.ProducerFactory; import org.apache.rocketmq.test.util.RandomUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class BatchSendIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private String topic = null; private Random random = new Random(); @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); } @After public void tearDown() { super.shutdown(); } @Test public void testBatchSend_ViewMessage() throws Exception { Assert.assertTrue(brokerController1.getMessageStore() instanceof DefaultMessageStore); Assert.assertTrue(brokerController2.getMessageStore() instanceof DefaultMessageStore); List messageList = new ArrayList<>(); int batchNum = 100; for (int i = 0; i < batchNum; i++) { messageList.add(new Message(topic, RandomUtils.getStringByUUID().getBytes())); } DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); removeBatchUniqueId(producer); SendResult sendResult = producer.send(messageList); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); String[] offsetIds = sendResult.getOffsetMsgId().split(","); String[] msgIds = sendResult.getMsgId().split(","); Assert.assertEquals(messageList.size(), offsetIds.length); Assert.assertEquals(messageList.size(), msgIds.length); Thread.sleep(2000); for (int i = 0; i < 3; i++) { producer.viewMessage(topic, offsetIds[random.nextInt(batchNum)]); } for (int i = 0; i < 3; i++) { producer.viewMessage(topic, msgIds[random.nextInt(batchNum)]); } } @Test public void testBatchSend_SysInnerBatch() throws Exception { waitBrokerRegistered(NAMESRV_ADDR, CLUSTER_NAME, BROKER_NUM); String batchTopic = UUID.randomUUID().toString(); IntegrationTestBase.initTopic(batchTopic, NAMESRV_ADDR, CLUSTER_NAME, CQType.BatchCQ); Assert.assertEquals(CQType.BatchCQ.toString(), brokerController1.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getAttributes().get(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName())); Assert.assertEquals(CQType.BatchCQ.toString(), brokerController2.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getAttributes().get(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName())); Assert.assertEquals(CQType.BatchCQ.toString(), brokerController3.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getAttributes().get(TopicAttributes.QUEUE_TYPE_ATTRIBUTE.getName())); Assert.assertEquals(8, brokerController1.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getReadQueueNums()); Assert.assertEquals(8, brokerController2.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getReadQueueNums()); Assert.assertEquals(8, brokerController3.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getReadQueueNums()); Assert.assertEquals(-1, brokerController1.getMessageStore().getMinOffsetInQueue(batchTopic, 0)); Assert.assertEquals(-1, brokerController2.getMessageStore().getMinOffsetInQueue(batchTopic, 0)); Assert.assertEquals(-1, brokerController3.getMessageStore().getMinOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController1.getMessageStore().getMaxOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController2.getMessageStore().getMaxOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController3.getMessageStore().getMaxOffsetInQueue(batchTopic, 0)); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); MessageQueue messageQueue = producer.fetchPublishMessageQueues(batchTopic).iterator().next(); int batchCount = 10; int batchNum = 10; for (int i = 0; i < batchCount; i++) { List messageList = new ArrayList<>(); for (int j = 0; j < batchNum; j++) { messageList.add(new Message(batchTopic, RandomUtils.getStringByUUID().getBytes())); } SendResult sendResult = producer.send(messageList, messageQueue); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); Assert.assertEquals(messageQueue.getQueueId(), sendResult.getMessageQueue().getQueueId()); Assert.assertEquals(i * batchNum, sendResult.getQueueOffset()); Assert.assertEquals(1, sendResult.getMsgId().split(",").length); } Thread.sleep(300); { DefaultMQPullConsumer defaultMQPullConsumer = ConsumerFactory.getRMQPullConsumer(NAMESRV_ADDR, "group"); PullResult pullResult = defaultMQPullConsumer.pullBlockIfNotFound(messageQueue, "*", 5, batchCount * batchNum); Assert.assertEquals(PullStatus.FOUND, pullResult.getPullStatus()); Assert.assertEquals(0, pullResult.getMinOffset()); Assert.assertEquals(batchCount * batchNum, pullResult.getMaxOffset()); Assert.assertEquals(batchCount * batchNum, pullResult.getMsgFoundList().size()); MessageExt first = pullResult.getMsgFoundList().get(0); for (int i = 0; i < pullResult.getMsgFoundList().size(); i++) { MessageExt messageExt = pullResult.getMsgFoundList().get(i); if (i % batchNum == 0) { first = messageExt; } Assert.assertEquals(i, messageExt.getQueueOffset()); Assert.assertEquals(batchTopic, messageExt.getTopic()); Assert.assertEquals(messageQueue.getQueueId(), messageExt.getQueueId()); Assert.assertEquals(first.getBornHostString(), messageExt.getBornHostString()); Assert.assertEquals(first.getBornHostNameString(), messageExt.getBornHostNameString()); Assert.assertEquals(first.getBornTimestamp(), messageExt.getBornTimestamp()); Assert.assertEquals(first.getStoreTimestamp(), messageExt.getStoreTimestamp()); } } } @Test public void testBatchSend_SysOuterBatch() throws Exception { Assert.assertTrue(brokerController1.getMessageStore() instanceof DefaultMessageStore); Assert.assertTrue(brokerController2.getMessageStore() instanceof DefaultMessageStore); Assert.assertTrue(brokerController3.getMessageStore() instanceof DefaultMessageStore); String batchTopic = UUID.randomUUID().toString(); IntegrationTestBase.initTopic(batchTopic, NAMESRV_ADDR, CLUSTER_NAME, CQType.SimpleCQ); Assert.assertEquals(8, brokerController1.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getReadQueueNums()); Assert.assertEquals(8, brokerController2.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getReadQueueNums()); Assert.assertEquals(8, brokerController3.getTopicConfigManager().getTopicConfigTable().get(batchTopic).getReadQueueNums()); Assert.assertEquals(0, brokerController1.getMessageStore().getMinOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController2.getMessageStore().getMinOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController3.getMessageStore().getMinOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController1.getMessageStore().getMaxOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController2.getMessageStore().getMaxOffsetInQueue(batchTopic, 0)); Assert.assertEquals(0, brokerController3.getMessageStore().getMaxOffsetInQueue(batchTopic, 0)); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); MessageQueue messageQueue = producer.fetchPublishMessageQueues(batchTopic).iterator().next(); int batchCount = 10; int batchNum = 10; for (int i = 0; i < batchCount; i++) { List messageList = new ArrayList<>(); for (int j = 0; j < batchNum; j++) { messageList.add(new Message(batchTopic, RandomUtils.getStringByUUID().getBytes())); } SendResult sendResult = producer.send(messageList, messageQueue); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); Assert.assertEquals(messageQueue.getQueueId(), sendResult.getMessageQueue().getQueueId()); Assert.assertEquals(i * batchNum, sendResult.getQueueOffset()); Assert.assertEquals(10, sendResult.getMsgId().split(",").length); } Thread.sleep(300); { DefaultMQPullConsumer defaultMQPullConsumer = ConsumerFactory.getRMQPullConsumer(NAMESRV_ADDR, "group"); long startOffset = 5; PullResult pullResult = defaultMQPullConsumer.pullBlockIfNotFound(messageQueue, "*", startOffset, batchCount * batchNum); Assert.assertEquals(PullStatus.FOUND, pullResult.getPullStatus()); Assert.assertEquals(0, pullResult.getMinOffset()); Assert.assertEquals(batchCount * batchNum, pullResult.getMaxOffset()); Assert.assertEquals(batchCount * batchNum - startOffset, pullResult.getMsgFoundList().size()); for (int i = 0; i < pullResult.getMsgFoundList().size(); i++) { MessageExt messageExt = pullResult.getMsgFoundList().get(i); Assert.assertEquals(i + startOffset, messageExt.getQueueOffset()); Assert.assertEquals(batchTopic, messageExt.getTopic()); Assert.assertEquals(messageQueue.getQueueId(), messageExt.getQueueId()); } } } @Test public void testBatchSend_CheckProperties() throws Exception { List messageList = new ArrayList<>(); Message message = new Message(); message.setTopic(topic); message.setKeys("keys123"); message.setTags("tags123"); message.setWaitStoreMsgOK(false); message.setBuyerId("buyerid123"); message.setFlag(123); message.setBody("body".getBytes()); messageList.add(message); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); removeBatchUniqueId(producer); SendResult sendResult = producer.send(messageList); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); String[] offsetIds = sendResult.getOffsetMsgId().split(","); String[] msgIds = sendResult.getMsgId().split(","); Assert.assertEquals(messageList.size(), offsetIds.length); Assert.assertEquals(messageList.size(), msgIds.length); Thread.sleep(2000); Message messageByOffset = producer.viewMessage(topic, offsetIds[0]); Message messageByMsgId = producer.viewMessage(topic, msgIds[0]); Assert.assertEquals(message.getTopic(), messageByMsgId.getTopic()); Assert.assertEquals(message.getTopic(), messageByOffset.getTopic()); Assert.assertEquals(message.getKeys(), messageByOffset.getKeys()); Assert.assertEquals(message.getKeys(), messageByMsgId.getKeys()); Assert.assertEquals(message.getTags(), messageByOffset.getTags()); Assert.assertEquals(message.getTags(), messageByMsgId.getTags()); Assert.assertEquals(message.isWaitStoreMsgOK(), messageByOffset.isWaitStoreMsgOK()); Assert.assertEquals(message.isWaitStoreMsgOK(), messageByMsgId.isWaitStoreMsgOK()); Assert.assertEquals(message.getBuyerId(), messageByOffset.getBuyerId()); Assert.assertEquals(message.getBuyerId(), messageByMsgId.getBuyerId()); Assert.assertEquals(message.getFlag(), messageByOffset.getFlag()); Assert.assertEquals(message.getFlag(), messageByMsgId.getFlag()); } // simulate legacy batch message send. private void removeBatchUniqueId(DefaultMQProducer producer) { producer.getDefaultMQProducerImpl().registerSendMessageHook(new SendMessageHook() { @Override public String hookName() { return null; } @Override public void sendMessageBefore(SendMessageContext context) { MessageBatch messageBatch = (MessageBatch) context.getMessage(); if (messageBatch.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX) != null) { messageBatch.getProperties().remove(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); } } @Override public void sendMessageAfter(SendMessageContext context) { } }); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/exception/msg/ChinaPropIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.exception.msg; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.factory.MessageFactory; import org.apache.rocketmq.test.factory.ProducerFactory; import org.apache.rocketmq.test.util.RandomUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class ChinaPropIT extends BaseConf { private static DefaultMQProducer producer = null; private static String topic = null; @Before public void setUp() { producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); topic = initTopic(); } @After public void tearDown() { producer.shutdown(); } /** * @since version3.4.6 */ @Test(expected = org.apache.rocketmq.client.exception.MQBrokerException.class) public void testSend20kChinaPropMsg() throws Exception { Message msg = MessageFactory.getRandomMessage(topic); msg.putUserProperty("key", RandomUtils.getCheseWord(32 * 1024 + 1)); producer.send(msg); } /** * @since version3.4.6 */ @Test public void testSend10kChinaPropMsg() { Message msg = MessageFactory.getRandomMessage(topic); msg.putUserProperty("key", RandomUtils.getCheseWord(10 * 1024)); SendResult sendResult = null; try { sendResult = producer.send(msg); } catch (Exception e) { } assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/exception/msg/MessageExceptionIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.exception.msg; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.factory.MessageFactory; import org.apache.rocketmq.test.factory.ProducerFactory; import org.apache.rocketmq.test.util.RandomUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class MessageExceptionIT extends BaseConf { private static DefaultMQProducer producer = null; private static String topic = null; @Before public void setUp() { producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); topic = initTopic(); } @After public void tearDown() { producer.shutdown(); } @Test public void testProducerSmoke() { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); SendResult sendResult = null; try { sendResult = producer.send(msg); } catch (Exception e) { } assertThat(sendResult).isNotEqualTo(null); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } @Test(expected = java.lang.NullPointerException.class) public void testSynSendNullMessage() throws Exception { producer.send((Message) null); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSynSendNullBodyMessage() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); msg.setBody(null); producer.send(msg); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSynSendZeroSizeBodyMessage() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); msg.setBody(new byte[0]); producer.send(msg); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSynSendOutOfSizeBodyMessage() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); msg.setBody(new byte[1024 * 1024 * 4 + 1]); producer.send(msg); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSynSendNullTopicMessage() throws Exception { Message msg = new Message(null, RandomUtils.getStringByUUID().getBytes()); producer.send(msg); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSynSendBlankTopicMessage() throws Exception { Message msg = new Message("", RandomUtils.getStringByUUID().getBytes()); producer.send(msg); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSend128kMsg() throws Exception { Message msg = new Message(topic, RandomUtils.getStringWithNumber(1024 * 1024 * 4 + 1).getBytes()); producer.send(msg); } @Test public void testSendLess128kMsg() { Message msg = new Message(topic, RandomUtils.getStringWithNumber(128 * 1024).getBytes()); SendResult sendResult = null; try { sendResult = producer.send(msg); } catch (Exception e) { } assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } @Test public void testSendMsgWithUserProperty() { Message msg = MessageFactory.getRandomMessage(topic); msg.putUserProperty("key", RandomUtils.getCheseWord(10 * 1024)); SendResult sendResult = null; try { sendResult = producer.send(msg); } catch (Exception e) { } assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/exception/msg/MessageUserPropIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.exception.msg; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.balance.NormalMsgStaticBalanceIT; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MessageFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class MessageUserPropIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } /** * @since version3.4.6 */ @Test public void testSendEnglishUserProp() { Message msg = MessageFactory.getRandomMessage(topic); String msgKey = "jueyinKey"; String msgValue = "jueyinValue"; msg.putUserProperty(msgKey, msgValue); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); producer.send(msg, null); assertThat(producer.getAllMsgBody().size()).isEqualTo(1); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); Message sendMsg = (Message) producer.getFirstMsg(); Message recvMsg = (Message) consumer.getListener().getFirstMsg(); assertThat(recvMsg.getUserProperty(msgKey)).isEqualTo(sendMsg.getUserProperty(msgKey)); } /** * @since version3.4.6 */ @Test public void testSendChinaUserProp() { Message msg = MessageFactory.getRandomMessage(topic); String msgKey = "jueyinKey"; String msgValue = "jueyinzhi"; msg.putUserProperty(msgKey, msgValue); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); producer.send(msg, null); assertThat(producer.getAllMsgBody().size()).isEqualTo(1); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); Message sendMsg = (Message) producer.getFirstMsg(); Message recvMsg = (Message) consumer.getListener().getFirstMsg(); assertThat(recvMsg.getUserProperty(msgKey)).isEqualTo(sendMsg.getUserProperty(msgKey)); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/exception/producer/ProducerGroupAndInstanceNameValidityIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.exception.producer; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.util.RandomUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class ProducerGroupAndInstanceNameValidityIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(ProducerGroupAndInstanceNameValidityIT.class); private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); } @After public void tearDown() { super.shutdown(); } /** * @since version3.4.6 */ @Test public void testTwoProducerSameGroupAndInstanceName() { RMQNormalProducer producer1 = getProducer(NAMESRV_ADDR, topic); assertThat(producer1.isStartSuccess()).isEqualTo(true); RMQNormalProducer producer2 = getProducer(NAMESRV_ADDR, topic, producer1.getProducerGroupName(), producer1.getProducerInstanceName()); assertThat(producer2.isStartSuccess()).isEqualTo(false); } /** * @since version3.4.6 */ @Test public void testTwoProducerSameGroup() { RMQNormalProducer producer1 = getProducer(NAMESRV_ADDR, topic); assertThat(producer1.isStartSuccess()).isEqualTo(true); RMQNormalProducer producer2 = getProducer(NAMESRV_ADDR, topic, producer1.getProducerGroupName(), RandomUtils.getStringByUUID()); assertThat(producer2.isStartSuccess()).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendExceptionIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.oneway; import java.util.List; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.factory.ProducerFactory; import org.apache.rocketmq.test.util.RandomUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; public class OneWaySendExceptionIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private static boolean sendFail = false; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); } @After public void tearDown() { super.shutdown(); } @Test(expected = java.lang.NullPointerException.class) public void testSendMQNull() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); MessageQueue messageQueue = null; producer.sendOneway(msg, messageQueue); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSendSelectorNull() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); MessageQueueSelector selector = null; producer.sendOneway(msg, selector, 100); } @Test(expected = org.apache.rocketmq.client.exception.MQClientException.class) public void testSelectorThrowsException() throws Exception { Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes()); DefaultMQProducer producer = ProducerFactory.getRMQProducer(NAMESRV_ADDR); producer.sendOneway(msg, new MessageQueueSelector() { @Override public MessageQueue select(List list, Message message, Object o) { String str = null; return list.get(str.length()); } }, null); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.oneway; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OneWaySendIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private RMQAsyncSendProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); producer = getAsyncProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testOneWaySendWithOnlyMsgAsParam() { int msgSize = 20; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); producer.sendOneWay(msgSize); producer.waitForResponse(5 * 1000); assertThat(producer.getAllMsgBody().size()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendWithMQIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.oneway; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OneWaySendWithMQIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private static boolean sendFail = false; private RMQAsyncSendProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); producer = getAsyncProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testAsyncSendWithMQ() { int msgSize = 20; int queueId = 0; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, queueId); producer.sendOneWay(msgSize, mq); producer.waitForResponse(5 * 1000); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/oneway/OneWaySendWithSelectorIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.oneway; import java.util.List; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.tag.TagMessageWith1ConsumerIT; import org.apache.rocketmq.test.client.rmq.RMQAsyncSendProducer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OneWaySendWithSelectorIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TagMessageWith1ConsumerIT.class); private static boolean sendFail = false; private RMQAsyncSendProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("user topic[%s]!", topic)); producer = getAsyncProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testSendWithSelector() { int msgSize = 20; final int queueId = 0; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); producer.sendOneWay(msgSize, new MessageQueueSelector() { @Override public MessageQueue select(List list, Message message, Object o) { for (MessageQueue mq : list) { if (mq.getQueueId() == queueId && mq.getBrokerName().equals(BROKER1_NAME)) { return mq; } } return list.get(0); } }); assertThat(producer.getAllMsgBody().size()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); VerifyUtils.verifyMessageQueueId(queueId, consumer.getListener().getAllOriginMsg()); producer.clearMsg(); consumer.clearMsg(); producer.sendOneWay(msgSize, new MessageQueueSelector() { @Override public MessageQueue select(List list, Message message, Object o) { for (MessageQueue mq : list) { if (mq.getQueueId() == queueId && mq.getBrokerName().equals(BROKER2_NAME)) { return mq; } } return list.get(8); } }); assertThat(producer.getAllMsgBody().size()).isEqualTo(msgSize); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); VerifyUtils.verifyMessageQueueId(queueId, consumer.getListener().getAllOriginMsg()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgDynamicRebalanceIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.order; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.consumer.balance.NormalMsgStaticBalanceIT; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.order.RMQOrderListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OrderMsgDynamicRebalanceIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumerAndCrashOne() { int msgSize = 10; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener("1")); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener("2")); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); MQWait.waitConsumeAll(30 * 1000, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); consumer2.shutdown(); mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer1.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer2.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testThreeConsumerAndCrashOne() { int msgSize = 10; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener("1")); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener("2")); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener("3")); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); consumer3.shutdown(); mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); boolean recvAll = MQWait.waitConsumeAll(30 * 1000, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener()); assertThat(recvAll).isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer1.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer2.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer3.getListener()).getMsgs())) .isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.order; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.listener.rmq.order.RMQOrderListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OrderMsgIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(OrderMsgIT.class); private RMQNormalProducer producer = null; private RMQNormalConsumer consumer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener()); } @After public void tearDown() { shutdown(); } @Test public void testOrderMsg() { int msgSize = 10; List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(mqMsgs.getMsgBodys()); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testSendOneQueue() { int msgSize = 20; List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(MQMessageFactory.getMessageQueues(mqs.get(0)), msgSize); producer.send(mqMsgs.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(mqMsgs.getMsgBodys()); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testSendRandomQueues() { int msgSize = 10; List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg( MQMessageFactory.getMessageQueues(mqs.get(0), mqs.get(1), mqs.get(mqs.size() - 1)), msgSize); producer.send(mqMsgs.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(mqMsgs.getMsgBodys()); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgRebalanceIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.order; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.order.RMQOrderListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OrderMsgRebalanceIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(OrderMsgRebalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s !", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { super.shutdown(); } @Test public void testTwoConsumersBalance() { int msgSize = 10; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener()); TestUtils.waitForSeconds(WAIT_TIME); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); boolean balance = VerifyUtils.verifyBalance(producer.getAllMsgBody().size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllUndupMsgBody()).size()); assertThat(balance).isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer1.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer2.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testFourConsumerBalance() { int msgSize = 20; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener()); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener()); RMQNormalConsumer consumer3 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener()); RMQNormalConsumer consumer4 = getConsumer(NAMESRV_ADDR, consumer1.getConsumerGroup(), topic, "*", new RMQOrderListener()); TestUtils.waitForSeconds(WAIT_TIME); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener(), consumer3.getListener(), consumer4.getListener()); assertThat(recvAll).isEqualTo(true); boolean balance = VerifyUtils .verifyBalance(producer.getAllMsgBody().size(), VerifyUtils .getFilterdMessage(producer.getAllMsgBody(), consumer1.getListener().getAllUndupMsgBody()) .size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer2.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer3.getListener().getAllUndupMsgBody()).size(), VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer4.getListener().getAllUndupMsgBody()).size()); logger.info(String.format("consumer1:%s;consumer2:%s;consumer3:%s,consumer4:%s", consumer1.getListener().getAllMsgBody().size(), consumer2.getListener().getAllMsgBody().size(), consumer3.getListener().getAllMsgBody().size(), consumer4.getListener().getAllMsgBody().size())); assertThat(balance).isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer1.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer2.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer3.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer4.getListener()).getMsgs())) .isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/order/OrderMsgWithTagIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.order; import java.util.List; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.order.RMQOrderListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.MQWait; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OrderMsgWithTagIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(OrderMsgIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { shutdown(); } @Test public void testOrderMsgWithTagSubAll() { int msgSize = 10; String tag = "jueyin_tag"; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQOrderListener()); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize, tag); producer.send(mqMsgs.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(mqMsgs.getMsgBodys()); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testOrderMsgWithTagSubTag() { int msgSize = 5; String tag = "jueyin_tag"; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, tag, new RMQOrderListener()); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize, tag); producer.send(mqMsgs.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(mqMsgs.getMsgBodys()); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testOrderMsgWithTag1AndTag2SubTag1() { int msgSize = 5; String tag1 = "jueyin_tag_1"; String tag2 = "jueyin_tag_2"; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, tag1, new RMQOrderListener()); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize, tag2); producer.send(mqMsgs.getMsgsWithMQ()); producer.clearMsg(); mqMsgs = new MessageQueueMsg(mqs, msgSize, tag1); producer.send(mqMsgs.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(mqMsgs.getMsgBodys()); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testTwoConsumerSubTag() { int msgSize = 10; String tag1 = "jueyin_tag_1"; String tag2 = "jueyin_tag_2"; RMQNormalConsumer consumer1 = getConsumer(NAMESRV_ADDR, topic, tag1, new RMQOrderListener("consumer1")); RMQNormalConsumer consumer2 = getConsumer(NAMESRV_ADDR, topic, tag2, new RMQOrderListener("consumer2")); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize, tag1); producer.send(mqMsgs.getMsgsWithMQ()); mqMsgs = new MessageQueueMsg(mqs, msgSize, tag2); producer.send(mqMsgs.getMsgsWithMQ()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer1.getListener(), consumer2.getListener()); assertThat(recvAll).isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer1.getListener()).getMsgs())) .isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer2.getListener()).getMsgs())) .isEqualTo(true); } @Test public void testConsumeTwoTag() { int msgSize = 10; String tag1 = "jueyin_tag_1"; String tag2 = "jueyin_tag_2"; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, String.format("%s||%s", tag1, tag2), new RMQOrderListener()); List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize, tag1); producer.send(mqMsgs.getMsgsWithMQ()); mqMsgs = new MessageQueueMsg(mqs, msgSize, tag2); producer.send(mqMsgs.getMsgsWithMQ()); boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer.getListener()); assertThat(recvAll).isEqualTo(true); assertThat(VerifyUtils.verifyOrder(((RMQOrderListener) consumer.getListener()).getMsgs())) .isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/querymsg/QueryMsgByIdExceptionIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.querymsg; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class QueryMsgByIdExceptionIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(QueryMsgByKeyIT.class); private static RMQNormalProducer producer = null; private static String topic = null; @BeforeClass public static void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @AfterClass public static void tearDown() { shutdown(); } @Test public void testQueryMsgByErrorMsgId() { producer.clearMsg(); int msgSize = 20; String errorMsgId = "errorMsgId"; producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); MessageExt queryMsg = null; try { queryMsg = producer.getProducer().viewMessage("topic", errorMsgId); } catch (Exception e) { } assertThat(queryMsg).isNull(); } @Test public void testQueryMsgByNullMsgId() { producer.clearMsg(); int msgSize = 20; String errorMsgId = null; producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); MessageExt queryMsg = null; try { queryMsg = producer.getProducer().viewMessage("topic", errorMsgId); } catch (Exception e) { } assertThat(queryMsg).isNull(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/querymsg/QueryMsgByIdIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.querymsg; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class QueryMsgByIdIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(QueryMsgByIdIT.class); private RMQNormalProducer producer = null; private RMQNormalConsumer consumer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); } @After public void tearDown() { shutdown(); } @Test public void testQueryMsg() { int msgSize = 20; producer.send(msgSize); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); Assert.assertEquals("Not all are consumed", 0, VerifyUtils.verify(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())); MessageExt recvMsg = (MessageExt) consumer.getListener().getFirstMsg(); MessageExt queryMsg = null; try { TestUtils.waitForMoment(3000); queryMsg = producer.getProducer().viewMessage(recvMsg.getTopic(), ((MessageClientExt) recvMsg).getOffsetMsgId()); } catch (Exception e) { } assertThat(queryMsg).isNotNull(); assertThat(new String(queryMsg.getBody())).isEqualTo(new String(recvMsg.getBody())); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/querymsg/QueryMsgByKeyIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.querymsg; import java.util.List; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.util.TestUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class QueryMsgByKeyIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(QueryMsgByKeyIT.class); private RMQNormalProducer producer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { shutdown(); } @Test public void testQueryMsg() { int msgSize = 20; String key = "jueyin"; long begin = System.currentTimeMillis(); List msgs = MQMessageFactory.getKeyMsg(topic, key, msgSize); producer.send(msgs); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); List queryMsgs = null; try { TestUtils.waitForMoment(500 * 3); queryMsgs = producer.getProducer().queryMessage(topic, key, msgSize, begin - 5000, System.currentTimeMillis() + 5000).getMessageList(); } catch (Exception e) { } assertThat(queryMsgs).isNotNull(); assertThat(queryMsgs.size()).isEqualTo(msgSize); } @Test public void testQueryMax() { int msgSize = 500; int max = 64 * BROKER_NUM; String key = "jueyin"; long begin = System.currentTimeMillis(); List msgs = MQMessageFactory.getKeyMsg(topic, key, msgSize); producer.send(msgs); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); List queryMsgs = null; try { queryMsgs = producer.getProducer().queryMessage(topic, key, msgSize, begin - 15000, System.currentTimeMillis() + 15000).getMessageList(); int i = 3; while (queryMsgs == null || queryMsgs.size() != BROKER_NUM) { i--; queryMsgs = producer.getProducer().queryMessage(topic, key, msgSize, begin - 15000, System.currentTimeMillis() + 15000).getMessageList(); TestUtils.waitForMoment(1000); if (i == 0 || queryMsgs != null && queryMsgs.size() == max) { break; } } } catch (Exception e) { } assertThat(queryMsgs).isNotNull(); assertThat(queryMsgs.size()).isEqualTo(max); } @Test(expected = MQClientException.class) public void testQueryMsgWithSameHash1() throws Exception { int msgSize = 1; String topicA = "AaTopic"; String keyA = "Aa"; String topicB = "BBTopic"; String keyB = "BB"; initTopicWithName(topicA); initTopicWithName(topicB); RMQNormalProducer producerA = getProducer(NAMESRV_ADDR, topicA); RMQNormalProducer producerB = getProducer(NAMESRV_ADDR, topicB); List msgA = MQMessageFactory.getKeyMsg(topicA, keyA, msgSize); List msgB = MQMessageFactory.getKeyMsg(topicB, keyB, msgSize); producerA.send(msgA); producerB.send(msgB); long begin = System.currentTimeMillis() - 500000; long end = System.currentTimeMillis() + 500000; producerA.getProducer().queryMessage(topicA, keyB, msgSize * 10, begin, end).getMessageList(); } @Test public void testQueryMsgWithSameHash2() throws Exception { int msgSize = 1; String topicA = "AaAaTopic"; String keyA = "Aa"; String topicB = "BBBBTopic"; String keyB = "Aa"; initTopicWithName(topicA); initTopicWithName(topicB); RMQNormalProducer producerA = getProducer(NAMESRV_ADDR, topicA); RMQNormalProducer producerB = getProducer(NAMESRV_ADDR, topicB); List msgA = MQMessageFactory.getKeyMsg(topicA, keyA, msgSize); List msgB = MQMessageFactory.getKeyMsg(topicB, keyB, msgSize); producerA.send(msgA); producerB.send(msgB); long begin = System.currentTimeMillis() - 500000; long end = System.currentTimeMillis() + 500000; List list = producerA.getProducer().queryMessage(topicA, keyA, msgSize * 10, begin, end).getMessageList(); assertThat(list).isNotNull(); assertThat(list.size()).isEqualTo(1); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/client/producer/transaction/TransactionalMsgIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.client.producer.transaction; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQTransactionalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import java.util.List; import java.util.concurrent.ConcurrentHashMap; public class TransactionalMsgIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(TransactionalMsgIT.class); private RMQTransactionalProducer producer = null; private RMQNormalConsumer consumer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getTransactionalProducer(NAMESRV_ADDR, topic, new TransactionListenerImpl()); consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); } @After public void tearDown() { super.shutdown(); } @Test public void testMessageVisibility() throws Exception { Thread.sleep(3000); int msgSize = 120; List msgs = MQMessageFactory.getMsg(topic, msgSize); for (int i = 0; i < msgSize; i++) { producer.send(msgs.get(i), getTransactionHandle(i)); } boolean recvAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer.getListener()); assertThat(recvAll).isEqualTo(true); } static Pair getTransactionHandle(int msgIndex) { switch (msgIndex % 5) { case 0: //commit immediately return new Pair<>(true, LocalTransactionState.COMMIT_MESSAGE); case 1: //rollback immediately return new Pair<>(true, LocalTransactionState.ROLLBACK_MESSAGE); case 2: //commit in check return new Pair<>(false, LocalTransactionState.COMMIT_MESSAGE); case 3: //rollback in check return new Pair<>(false, LocalTransactionState.ROLLBACK_MESSAGE); case 4: default: return new Pair<>(false, LocalTransactionState.UNKNOW); } } static private class TransactionListenerImpl implements TransactionListener { ConcurrentHashMap checkStatus = new ConcurrentHashMap<>(); @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { Pair transactionHandle = (Pair) arg; if (transactionHandle.getObject1()) { return transactionHandle.getObject2(); } else { checkStatus.put(msg.getTransactionId(), transactionHandle.getObject2()); return LocalTransactionState.UNKNOW; } } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { LocalTransactionState state = checkStatus.get(msg.getTransactionId()); if (state == null) { return LocalTransactionState.UNKNOW; } else { return state; } } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/AddAndRemoveBrokerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.container.BrokerContainer; import org.apache.rocketmq.remoting.exception.RemotingConnectException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @Ignore public class AddAndRemoveBrokerIT extends ContainerIntegrationTestBase { private static BrokerContainer brokerContainer4; @BeforeClass public static void beforeClass() { brokerContainer4 = createAndStartBrokerContainer(nsAddr); } @AfterClass public static void afterClass() { brokerContainer4.shutdown(); } @Test public void addBrokerTest() throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { String remark = null; int code = 0; try { defaultMQAdminExt.addBrokerToContainer(brokerContainer4.getBrokerContainerAddr(), ""); } catch (MQBrokerException e) { code = e.getResponseCode(); remark = e.getErrorMessage(); } assertThat(code).isEqualTo(ResponseCode.SYSTEM_ERROR); assertThat(remark).isEqualTo("addBroker properties empty"); } @Test public void removeBrokerTest() throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { boolean exceptionCaught = false; try { defaultMQAdminExt.removeBrokerFromContainer(brokerContainer1.getBrokerContainerAddr(), master3With3Replicas.getBrokerConfig().getBrokerClusterName(), master3With3Replicas.getBrokerConfig().getBrokerName(), 1); } catch (MQBrokerException e) { exceptionCaught = true; } assertThat(exceptionCaught).isFalse(); assertThat(brokerContainer1.getSlaveBrokers().size()).isEqualTo(1); createAndAddSlave(1, brokerContainer1, master3With3Replicas); awaitUntilSlaveOK(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/BrokerFailoverIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.time.Duration; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class BrokerFailoverIT extends ContainerIntegrationTestBase { @Test public void testBrokerFailoverWithoutCompatible() { changeCompatibleMode(false); awaitUntilSlaveOK(); testBrokerFailover(false); } @Test public void testBrokerFailoverWithCompatible() { changeCompatibleMode(true); awaitUntilSlaveOK(); testBrokerFailover(true); } private void testBrokerFailover(boolean compatibleMode) { await().atMost(Duration.ofSeconds(10)).until(() -> master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3 && master2With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3 && master3With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3); InnerSalveBrokerController targetSlave = getSlaveFromContainerByName(brokerContainer2, master1With3Replicas.getBrokerConfig().getBrokerName()); assertThat(targetSlave).isNotNull(); brokerContainer1.registerClientRPCHook(new RPCHook() { @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { if (request.getCode() == (compatibleMode ? RequestCode.QUERY_DATA_VERSION : RequestCode.BROKER_HEARTBEAT)) { request.setCode(-1); } } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } }); InnerSalveBrokerController finalTargetSlave = targetSlave; await().atMost(Duration.ofSeconds(60)).until(() -> finalTargetSlave.getMessageStore().getAliveReplicaNumInGroup() == 2 && master2With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 2 && master3With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 2); brokerContainer1.clearClientRPCHook(); await().atMost(Duration.ofSeconds(60)).until(() -> master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3 && master2With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3 && master3With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/BrokerMemberGroupIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.time.Duration; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup; import org.junit.Ignore; import org.junit.Test; import static org.awaitility.Awaitility.await; @Ignore public class BrokerMemberGroupIT extends ContainerIntegrationTestBase { @Test public void testSyncBrokerMemberGroup() throws Exception { await().atMost(Duration.ofSeconds(5)).until(() -> { final BrokerConfig brokerConfig = master1With3Replicas.getBrokerConfig(); final BrokerMemberGroup memberGroup = master1With3Replicas.getBrokerOuterAPI() .syncBrokerMemberGroup(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName()); return memberGroup.getBrokerAddrs().size() == 3; }); await().atMost(Duration.ofSeconds(5)).until(() -> { final BrokerConfig brokerConfig = master3With3Replicas.getBrokerConfig(); final BrokerMemberGroup memberGroup = master3With3Replicas.getBrokerOuterAPI() .syncBrokerMemberGroup(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName()); return memberGroup.getBrokerAddrs().size() == 3; }); removeSlaveBroker(1, brokerContainer1, master3With3Replicas); removeSlaveBroker(1, brokerContainer2, master1With3Replicas); await().atMost(Duration.ofSeconds(5)).until(() -> { final BrokerConfig brokerConfig = master1With3Replicas.getBrokerConfig(); final BrokerMemberGroup memberGroup = master1With3Replicas.getBrokerOuterAPI() .syncBrokerMemberGroup(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName()); return memberGroup.getBrokerAddrs().size() == 2 && memberGroup.getBrokerAddrs().get(1L) == null; }); await().atMost(Duration.ofSeconds(5)).until(() -> { final BrokerConfig brokerConfig = master3With3Replicas.getBrokerConfig(); final BrokerMemberGroup memberGroup = master3With3Replicas.getBrokerOuterAPI() .syncBrokerMemberGroup(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName()); return memberGroup.getBrokerAddrs().size() == 2 && memberGroup.getBrokerAddrs().get(1L) == null; }); createAndAddSlave(1, brokerContainer2, master1With3Replicas); createAndAddSlave(1, brokerContainer1, master3With3Replicas); awaitUntilSlaveOK(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/ContainerIntegrationTestBase.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import io.netty.channel.ChannelHandlerContext; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.ConfigContext; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.TransactionCheckListener; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.container.BrokerContainer; import org.apache.rocketmq.container.BrokerContainerConfig; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerRequestHeader; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.HAConnection; import org.apache.rocketmq.store.ha.HAConnectionState; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.junit.Assert; import org.junit.BeforeClass; import static org.awaitility.Awaitility.await; /** * ContainerIntegrationTestBase will setup a rocketmq ha cluster contains two broker group: *
  • BrokerA contains two replicas
  • *
  • BrokerB contains three replicas
  • */ public class ContainerIntegrationTestBase { private static final AtomicBoolean CLUSTER_SET_UP = new AtomicBoolean(false); private static final List TMP_FILE_LIST = new ArrayList<>(); private static final Random RANDOM = new Random(); protected static String nsAddr; protected static final String THREE_REPLICAS_TOPIC = "SEND_MESSAGE_TEST_TOPIC_THREE_REPLICAS"; protected static List brokerContainerList = new ArrayList<>(); protected static List namesrvControllers = new ArrayList<>(); protected static final String BROKER_NAME_PREFIX = "TestBrokerName_"; protected static final int COMMIT_LOG_SIZE = 128 * 1024; protected static final int INDEX_NUM = 1000; protected static final AtomicInteger BROKER_INDEX = new AtomicInteger(0); protected static BrokerContainer brokerContainer1; protected static BrokerContainer brokerContainer2; protected static BrokerContainer brokerContainer3; protected static BrokerController master1With3Replicas; protected static BrokerController master2With3Replicas; protected static BrokerController master3With3Replicas; protected static NamesrvController namesrvController; protected static DefaultMQAdminExt defaultMQAdminExt; private final static Logger LOG = LoggerFactory.getLogger(ContainerIntegrationTestBase.class); private static ConcurrentMap slaveStoreConfigCache = new ConcurrentHashMap<>(); protected static ConcurrentMap isolatedBrokers = new ConcurrentHashMap<>(); private static final Set PORTS_IN_USE = new HashSet<>(); @BeforeClass public static void setUp() throws Exception { if (CLUSTER_SET_UP.compareAndSet(false, true)) { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); System.setProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", "0.99"); System.setProperty("rocketmq.broker.diskSpaceWarningLevelRatio", "0.99"); setUpCluster(); setUpTopic(); registerCleaner(); System.out.printf("cluster setup complete%n"); } } private static void setUpTopic() { createTopic(THREE_REPLICAS_TOPIC); } private static void createTopic(String topic) { createTopicTo(master1With3Replicas, topic); createTopicTo(master2With3Replicas, topic); createTopicTo(master3With3Replicas, topic); } private static void setUpCluster() throws Exception { namesrvController = createAndStartNamesrv(); nsAddr = "127.0.0.1:" + namesrvController.getNettyServerConfig().getListenPort(); System.out.printf("namesrv addr: %s%n", nsAddr); /* * BrokerContainer1 | BrokerContainer2 | BrokerContainer3 * * master1With3Replicas(m) master2With3Replicas(m) master3With3Replicas(m) * master3With3Replicas(s0) master1With3Replicas(s0) master2With3Replicas(s0) * master2With3Replicas(s1) master3With3Replicas(s1) master1With3Replicas(s1) */ brokerContainer1 = createAndStartBrokerContainer(nsAddr); brokerContainer2 = createAndStartBrokerContainer(nsAddr); brokerContainer3 = createAndStartBrokerContainer(nsAddr); // Create three broker groups, two contains two replicas, another contains three replicas master1With3Replicas = createAndAddMaster(brokerContainer1, new BrokerGroupConfig(), BROKER_INDEX.getAndIncrement()); master2With3Replicas = createAndAddMaster(brokerContainer2, new BrokerGroupConfig(), BROKER_INDEX.getAndIncrement()); master3With3Replicas = createAndAddMaster(brokerContainer3, new BrokerGroupConfig(), BROKER_INDEX.getAndIncrement()); createAndAddSlave(1, brokerContainer1, master3With3Replicas); createAndAddSlave(1, brokerContainer2, master1With3Replicas); createAndAddSlave(1, brokerContainer3, master2With3Replicas); createAndAddSlave(2, brokerContainer1, master2With3Replicas); createAndAddSlave(2, brokerContainer2, master3With3Replicas); createAndAddSlave(2, brokerContainer3, master1With3Replicas); awaitUntilSlaveOK(); defaultMQAdminExt = new DefaultMQAdminExt("HATest_Admin_Group"); defaultMQAdminExt.setNamesrvAddr(nsAddr); defaultMQAdminExt.start(); } protected static void createTopicTo(BrokerController masterBroker, String topicName, int rqn, int wqn) { try { TopicConfig topicConfig = new TopicConfig(topicName, rqn, wqn, 6, 0); defaultMQAdminExt.createAndUpdateTopicConfig(masterBroker.getBrokerAddr(), topicConfig); triggerSlaveSync(masterBroker.getBrokerConfig().getBrokerName(), brokerContainer1); triggerSlaveSync(masterBroker.getBrokerConfig().getBrokerName(), brokerContainer2); triggerSlaveSync(masterBroker.getBrokerConfig().getBrokerName(), brokerContainer3); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Create topic to broker failed", e); } } protected static void createGroup(BrokerController masterBroker, String groupName) { try { SubscriptionGroupConfig config = new SubscriptionGroupConfig(); config.setGroupName(groupName); masterBroker.getSubscriptionGroupManager().updateSubscriptionGroupConfig(config); triggerSlaveSync(masterBroker.getBrokerConfig().getBrokerName(), brokerContainer1); triggerSlaveSync(masterBroker.getBrokerConfig().getBrokerName(), brokerContainer2); triggerSlaveSync(masterBroker.getBrokerConfig().getBrokerName(), brokerContainer3); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Create group to broker failed", e); } } private static void triggerSlaveSync(String brokerName, BrokerContainer brokerContainer) { for (InnerSalveBrokerController slaveBroker : brokerContainer.getSlaveBrokers()) { if (slaveBroker.getBrokerConfig().getBrokerName().equals(brokerName)) { slaveBroker.getSlaveSynchronize().syncAll(); slaveBroker.registerBrokerAll(true, false, true); } } } protected static void createTopicTo(BrokerController brokerController, String topicName) { createTopicTo(brokerController, topicName, 8, 8); } private static void registerCleaner() { Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (CLUSTER_SET_UP.compareAndSet(true, false)) { System.out.printf("clean up%n"); defaultMQAdminExt.shutdown(); for (final BrokerContainer brokerContainer : brokerContainerList) { brokerContainer.shutdown(); for (BrokerController brokerController : brokerContainer.getBrokerControllers()) { brokerController.getMessageStore().destroy(); } } for (final NamesrvController namesrvController : namesrvControllers) { namesrvController.shutdown(); } for (final File file : TMP_FILE_LIST) { UtilAll.deleteFile(file); } } })); } private static File createBaseDir(String prefix) { final File file; try { file = Files.createTempDirectory(prefix).toFile(); TMP_FILE_LIST.add(file); System.out.printf("create file at %s%n", file.getAbsolutePath()); return file; } catch (IOException e) { throw new RuntimeException("Couldn't create tmp folder", e); } } public static NamesrvController createAndStartNamesrv() { String baseDir = createBaseDir("test-cluster-namesrv").getAbsolutePath(); NamesrvConfig namesrvConfig = new NamesrvConfig(); NettyServerConfig nameServerNettyServerConfig = new NettyServerConfig(); namesrvConfig.setKvConfigPath(baseDir + File.separator + "namesrv" + File.separator + "kvConfig.json"); namesrvConfig.setConfigStorePath(baseDir + File.separator + "namesrv" + File.separator + "namesrv.properties"); namesrvConfig.setSupportActingMaster(true); namesrvConfig.setScanNotActiveBrokerInterval(1000); nameServerNettyServerConfig.setListenPort(generatePort(10000, 10000)); NamesrvController namesrvController = new NamesrvController(namesrvConfig, nameServerNettyServerConfig); try { Assert.assertTrue(namesrvController.initialize()); LOG.info("Name Server Start:{}", nameServerNettyServerConfig.getListenPort()); namesrvController.start(); } catch (Exception e) { LOG.info("Name Server start failed"); e.printStackTrace(); System.exit(1); } namesrvController.getRemotingServer().registerProcessor(RequestCode.REGISTER_BROKER, new NettyRequestProcessor() { @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, final RemotingCommand request) throws Exception { final RegisterBrokerRequestHeader requestHeader = (RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class); final BrokerConfigLite liteConfig = new BrokerConfigLite(requestHeader.getClusterName(), requestHeader.getBrokerName(), requestHeader.getBrokerAddr(), requestHeader.getBrokerId()); if (isolatedBrokers.containsKey(liteConfig)) { // return response with SYSTEM_ERROR return RemotingCommand.createResponseCommand(null); } return namesrvController.getRemotingServer().getDefaultProcessorPair().getObject1().processRequest(ctx, request); } @Override public boolean rejectRequest() { return false; } }, null); namesrvControllers.add(namesrvController); return namesrvController; } public static BrokerContainer createAndStartBrokerContainer(String nsAddr) { BrokerContainerConfig brokerContainerConfig = new BrokerContainerConfig(); NettyServerConfig nettyServerConfig = new NettyServerConfig(); NettyClientConfig nettyClientConfig = new NettyClientConfig(); brokerContainerConfig.setNamesrvAddr(nsAddr); nettyServerConfig.setListenPort(generatePort(20000, 10000)); BrokerContainer brokerContainer = new BrokerContainer(brokerContainerConfig, nettyServerConfig, nettyClientConfig); try { Assert.assertTrue(brokerContainer.initialize()); LOG.info("Broker container Start, listen on {}.", nettyServerConfig.getListenPort()); brokerContainer.start(); } catch (Exception e) { LOG.info("Broker container start failed", e); e.printStackTrace(); System.exit(1); } brokerContainerList.add(brokerContainer); return brokerContainer; } private static int generatePort(int base, int range) { int result = base + RANDOM.nextInt(range); while (PORTS_IN_USE.contains(result) || PORTS_IN_USE.contains(result - 2)) { result = base + RANDOM.nextInt(range); } PORTS_IN_USE.add(result); PORTS_IN_USE.add(result - 2); return result; } public static BrokerController createAndAddMaster(BrokerContainer brokerContainer, BrokerGroupConfig brokerGroupConfig, int brokerIndex) throws Exception { BrokerConfig brokerConfig = new BrokerConfig(); MessageStoreConfig storeConfig = new MessageStoreConfig(); brokerConfig.setBrokerName(BROKER_NAME_PREFIX + brokerIndex); brokerConfig.setBrokerIP1("127.0.0.1"); brokerConfig.setBrokerIP2("127.0.0.1"); brokerConfig.setBrokerId(0); brokerConfig.setEnablePropertyFilter(true); brokerConfig.setEnableSlaveActingMaster(brokerGroupConfig.enableSlaveActingMaster); brokerConfig.setEnableRemoteEscape(brokerGroupConfig.enableRemoteEscape); brokerConfig.setSlaveReadEnable(brokerGroupConfig.slaveReadEnable); brokerConfig.setLockInStrictMode(true); brokerConfig.setConsumerOffsetUpdateVersionStep(10); brokerConfig.setDelayOffsetUpdateVersionStep(10); brokerConfig.setCompatibleWithOldNameSrv(false); brokerConfig.setListenPort(generatePort(brokerContainer.getRemotingServer().localListenPort(), 10000)); String baseDir = createBaseDir(brokerConfig.getBrokerName() + "_" + brokerConfig.getBrokerId()).getAbsolutePath(); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); storeConfig.setHaListenPort(generatePort(30000, 10000)); storeConfig.setMappedFileSizeCommitLog(COMMIT_LOG_SIZE); storeConfig.setMaxIndexNum(INDEX_NUM); storeConfig.setMaxHashSlotNum(INDEX_NUM * 4); storeConfig.setTotalReplicas(brokerGroupConfig.totalReplicas); storeConfig.setInSyncReplicas(brokerGroupConfig.inSyncReplicas); storeConfig.setMinInSyncReplicas(brokerGroupConfig.minReplicas); storeConfig.setEnableAutoInSyncReplicas(brokerGroupConfig.autoReplicas); storeConfig.setBrokerRole(BrokerRole.SYNC_MASTER); storeConfig.setSyncFlushTimeout(10 * 1000); System.out.printf("start master %s with port %d-%d%n", brokerConfig.getCanonicalName(), brokerConfig.getListenPort(), storeConfig.getHaListenPort()); BrokerController brokerController = null; try { brokerController = brokerContainer.addBroker(buildConfigContext(brokerConfig, storeConfig)); Assert.assertNotNull(brokerController); brokerController.start(); TMP_FILE_LIST.add(new File(brokerController.getTopicConfigManager().configFilePath())); TMP_FILE_LIST.add(new File(brokerController.getSubscriptionGroupManager().configFilePath())); LOG.info("Broker Start name:{} addr:{}", brokerConfig.getBrokerName(), brokerController.getBrokerAddr()); } catch (Exception e) { LOG.info("Broker start failed", e); e.printStackTrace(); System.exit(1); } return brokerController; } protected static DefaultMQProducer createProducer(String producerGroup) { DefaultMQProducer producer = new DefaultMQProducer(producerGroup); producer.setInstanceName(UUID.randomUUID().toString()); producer.setNamesrvAddr(nsAddr); return producer; } protected static TransactionMQProducer createTransactionProducer(String producerGroup, TransactionCheckListener transactionCheckListener) { TransactionMQProducer producer = new TransactionMQProducer(producerGroup); producer.setInstanceName(UUID.randomUUID().toString()); producer.setNamesrvAddr(nsAddr); producer.setTransactionCheckListener(transactionCheckListener); return producer; } protected static TransactionMQProducer createTransactionProducer(String producerGroup, TransactionListener transactionListener) { TransactionMQProducer producer = new TransactionMQProducer(producerGroup); producer.setInstanceName(UUID.randomUUID().toString()); producer.setNamesrvAddr(nsAddr); producer.setTransactionListener(transactionListener); return producer; } protected static DefaultMQPullConsumer createPullConsumer(String consumerGroup) { DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(consumerGroup); consumer.setInstanceName(UUID.randomUUID().toString()); consumer.setNamesrvAddr(nsAddr); return consumer; } protected static DefaultMQPushConsumer createPushConsumer(String consumerGroup) { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup); consumer.setInstanceName(UUID.randomUUID().toString()); consumer.setNamesrvAddr(nsAddr); return consumer; } protected static void createAndAddSlave(int slaveBrokerId, BrokerContainer brokerContainer, BrokerController master) { BrokerConfig slaveBrokerConfig = new BrokerConfig(); slaveBrokerConfig.setBrokerName(master.getBrokerConfig().getBrokerName()); slaveBrokerConfig.setBrokerId(slaveBrokerId); slaveBrokerConfig.setBrokerClusterName(master.getBrokerConfig().getBrokerClusterName()); slaveBrokerConfig.setCompatibleWithOldNameSrv(false); slaveBrokerConfig.setBrokerIP1("127.0.0.1"); slaveBrokerConfig.setBrokerIP2("127.0.0.1"); slaveBrokerConfig.setEnablePropertyFilter(true); slaveBrokerConfig.setSlaveReadEnable(true); slaveBrokerConfig.setEnableSlaveActingMaster(true); slaveBrokerConfig.setEnableRemoteEscape(true); slaveBrokerConfig.setLockInStrictMode(true); slaveBrokerConfig.setListenPort(generatePort(brokerContainer.getRemotingServer().localListenPort(), 10000)); slaveBrokerConfig.setConsumerOffsetUpdateVersionStep(10); slaveBrokerConfig.setDelayOffsetUpdateVersionStep(10); MessageStoreConfig storeConfig = slaveStoreConfigCache.get(slaveBrokerConfig); if (storeConfig == null) { storeConfig = new MessageStoreConfig(); String baseDir = createBaseDir(slaveBrokerConfig.getBrokerName() + "_" + slaveBrokerConfig.getBrokerId()).getAbsolutePath(); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + File.separator + "commitlog"); storeConfig.setHaListenPort(generatePort(master.getMessageStoreConfig().getHaListenPort(), 10000)); storeConfig.setMappedFileSizeCommitLog(COMMIT_LOG_SIZE); storeConfig.setMaxIndexNum(INDEX_NUM); storeConfig.setMaxHashSlotNum(INDEX_NUM * 4); storeConfig.setTotalReplicas(master.getMessageStoreConfig().getTotalReplicas()); storeConfig.setInSyncReplicas(master.getMessageStoreConfig().getInSyncReplicas()); storeConfig.setMinInSyncReplicas(master.getMessageStoreConfig().getMinInSyncReplicas()); storeConfig.setBrokerRole(BrokerRole.SLAVE); slaveStoreConfigCache.put(slaveBrokerConfig, storeConfig); } System.out.printf("start slave %s with port %d-%d%n", slaveBrokerConfig.getCanonicalName(), slaveBrokerConfig.getListenPort(), storeConfig.getHaListenPort()); try { BrokerController brokerController = brokerContainer.addBroker(buildConfigContext(slaveBrokerConfig, storeConfig)); Assert.assertNotNull(brokerContainer); brokerController.start(); TMP_FILE_LIST.add(new File(brokerController.getTopicConfigManager().configFilePath())); TMP_FILE_LIST.add(new File(brokerController.getSubscriptionGroupManager().configFilePath())); LOG.info("Add slave name:{} addr:{}", slaveBrokerConfig.getBrokerName(), brokerController.getBrokerAddr()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Couldn't add slave broker", e); } } protected static void removeSlaveBroker(int slaveBrokerId, BrokerContainer brokerContainer, BrokerController master) throws Exception { BrokerIdentity brokerIdentity = new BrokerIdentity(master.getBrokerConfig().getBrokerClusterName(), master.getBrokerConfig().getBrokerName(), slaveBrokerId); brokerContainer.removeBroker(brokerIdentity); } protected static void awaitUntilSlaveOK() { await().atMost(100, TimeUnit.SECONDS) .until(() -> { boolean isOk = master1With3Replicas.getMessageStore().getHaService().getConnectionCount().get() == 2 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3; for (HAConnection haConnection : master1With3Replicas.getMessageStore().getHaService().getConnectionList()) { isOk &= haConnection.getCurrentState().equals(HAConnectionState.TRANSFER); } return isOk; }); await().atMost(100, TimeUnit.SECONDS) .until(() -> { boolean isOk = master2With3Replicas.getMessageStore().getHaService().getConnectionCount().get() == 2 && master2With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3; for (HAConnection haConnection : master2With3Replicas.getMessageStore().getHaService().getConnectionList()) { isOk &= haConnection.getCurrentState().equals(HAConnectionState.TRANSFER); } return isOk; }); await().atMost(100, TimeUnit.SECONDS) .until(() -> { boolean isOk = master3With3Replicas.getMessageStore().getHaService().getConnectionCount().get() == 2 && master3With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3; for (HAConnection haConnection : master3With3Replicas.getMessageStore().getHaService().getConnectionList()) { isOk &= haConnection.getCurrentState().equals(HAConnectionState.TRANSFER); } return isOk; }); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } protected static void isolateBroker(BrokerController brokerController) { final BrokerConfig config = brokerController.getBrokerConfig(); BrokerConfigLite liteConfig = new BrokerConfigLite(config.getBrokerClusterName(), config.getBrokerName(), brokerController.getBrokerAddr(), config.getBrokerId()); // Reject register requests from the specific broker isolatedBrokers.putIfAbsent(liteConfig, brokerController); // UnRegister the specific broker immediately namesrvController.getRouteInfoManager().unregisterBroker(liteConfig.getClusterName(), liteConfig.getBrokerAddr(), liteConfig.getBrokerName(), liteConfig.getBrokerId()); } protected static void cancelIsolatedBroker(BrokerController brokerController) { final BrokerConfig config = brokerController.getBrokerConfig(); BrokerConfigLite liteConfig = new BrokerConfigLite(config.getBrokerClusterName(), config.getBrokerName(), brokerController.getBrokerAddr(), config.getBrokerId()); isolatedBrokers.remove(liteConfig); brokerController.registerBrokerAll(true, false, true); await().atMost(Duration.ofMinutes(1)).until(() -> namesrvController.getRouteInfoManager() .getBrokerMemberGroup(liteConfig.getClusterName(), liteConfig.brokerName).getBrokerAddrs() .containsKey(liteConfig.getBrokerId())); } protected static InnerSalveBrokerController getSlaveFromContainerByName(BrokerContainer brokerContainer, String brokerName) { InnerSalveBrokerController targetSlave = null; for (InnerSalveBrokerController slave : brokerContainer.getSlaveBrokers()) { if (slave.getBrokerConfig().getBrokerName().equals(brokerName)) { targetSlave = slave; } } return targetSlave; } protected static void changeCompatibleMode(boolean compatibleMode) { brokerContainer1.getBrokerControllers().forEach(brokerController -> brokerController.getBrokerConfig().setCompatibleWithOldNameSrv(compatibleMode)); brokerContainer2.getBrokerControllers().forEach(brokerController -> brokerController.getBrokerConfig().setCompatibleWithOldNameSrv(compatibleMode)); brokerContainer3.getBrokerControllers().forEach(brokerController -> brokerController.getBrokerConfig().setCompatibleWithOldNameSrv(compatibleMode)); } protected static Set filterMessageQueue(Set mqSet, String topic) { Set targetMqSet = new HashSet<>(); if (topic != null) { for (MessageQueue mq : mqSet) { if (mq.getTopic().equals(topic)) { targetMqSet.add(mq); } } } return targetMqSet; } public static class BrokerGroupConfig { int totalReplicas = 3; int minReplicas = 1; int inSyncReplicas = 2; boolean autoReplicas = true; boolean enableSlaveActingMaster = true; boolean enableRemoteEscape = true; boolean slaveReadEnable = true; public BrokerGroupConfig() { } public BrokerGroupConfig(final int totalReplicas, final int minReplicas, final int inSyncReplicas, final boolean autoReplicas, boolean enableSlaveActingMaster, boolean slaveReadEnable) { this.totalReplicas = totalReplicas; this.minReplicas = minReplicas; this.inSyncReplicas = inSyncReplicas; this.autoReplicas = autoReplicas; this.enableSlaveActingMaster = enableSlaveActingMaster; this.slaveReadEnable = slaveReadEnable; } } static class BrokerConfigLite { private String clusterName; private String brokerName; private String brokerAddr; private long brokerId; public BrokerConfigLite(final String clusterName, final String brokerName, final String brokerAddr, final long brokerId) { this.clusterName = clusterName; this.brokerName = brokerName; this.brokerAddr = brokerAddr; this.brokerId = brokerId; } public String getClusterName() { return clusterName; } public String getBrokerName() { return brokerName; } public String getBrokerAddr() { return brokerAddr; } public long getBrokerId() { return brokerId; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final BrokerConfigLite lite = (BrokerConfigLite) o; return new EqualsBuilder() .append(clusterName, lite.clusterName) .append(brokerName, lite.brokerName) .append(brokerAddr, lite.brokerAddr) .append(brokerId, lite.brokerId) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(clusterName) .append(brokerName) .append(brokerAddr) .append(brokerId) .toHashCode(); } } public static ConfigContext buildConfigContext(BrokerConfig brokerConfig, MessageStoreConfig messageStoreConfig) { return new ConfigContext.Builder() .brokerConfig(brokerConfig) .messageStoreConfig(messageStoreConfig) .build(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/GetMaxOffsetFromSlaveIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.store.DefaultMessageStore; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class GetMaxOffsetFromSlaveIT extends ContainerIntegrationTestBase { private static DefaultMQProducer mqProducer; private static final String MSG = "Hello RocketMQ "; private static final byte[] MESSAGE_BODY = MSG.getBytes(StandardCharsets.UTF_8); public GetMaxOffsetFromSlaveIT() { } @BeforeClass public static void beforeClass() throws MQClientException { mqProducer = createProducer(GetMaxOffsetFromSlaveIT.class.getSimpleName() + "_Producer"); mqProducer.start(); } @AfterClass public static void afterClass() { if (mqProducer != null) { mqProducer.shutdown(); } } @Test public void testGetMaxOffsetFromSlave() throws InterruptedException, RemotingException, MQClientException, MQBrokerException { awaitUntilSlaveOK(); mqProducer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(THREE_REPLICAS_TOPIC); for (int i = 0; i < 100; i++) { Message msg = new Message(THREE_REPLICAS_TOPIC, MESSAGE_BODY); SendResult sendResult = mqProducer.send(msg, 10000); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } Map maxOffsetMap = new HashMap<>(); TopicPublishInfo publishInfo = mqProducer.getDefaultMQProducerImpl().getTopicPublishInfoTable().get(THREE_REPLICAS_TOPIC); assertThat(publishInfo).isNotNull(); for (MessageQueue mq : publishInfo.getMessageQueueList()) { maxOffsetMap.put(mq.getQueueId(), mqProducer.getDefaultMQProducerImpl(). maxOffset(new MessageQueue(THREE_REPLICAS_TOPIC, master3With3Replicas.getBrokerConfig().getBrokerName(), mq.getQueueId()))); } isolateBroker(master3With3Replicas); mqProducer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(THREE_REPLICAS_TOPIC); assertThat(mqProducer.getDefaultMQProducerImpl().getmQClientFactory().findBrokerAddressInPublish( master3With3Replicas.getBrokerConfig().getBrokerName())).isNotNull(); for (MessageQueue mq : publishInfo.getMessageQueueList()) { assertThat(mqProducer.getDefaultMQProducerImpl().maxOffset( new MessageQueue(THREE_REPLICAS_TOPIC, master3With3Replicas.getBrokerConfig().getBrokerName(), mq.getQueueId()))) .isEqualTo(maxOffsetMap.get(mq.getQueueId())); } cancelIsolatedBroker(master3With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master3With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/GetMetadataReverseIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.time.Duration; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.awaitility.Awaitility.await; @Ignore public class GetMetadataReverseIT extends ContainerIntegrationTestBase { private static DefaultMQProducer producer; private static final String CONSUMER_GROUP = GetMetadataReverseIT.class.getSimpleName() + "_Consumer"; private static final int MESSAGE_COUNT = 32; private final Random random = new Random(); public GetMetadataReverseIT() { } @BeforeClass public static void beforeClass() throws Throwable { producer = createProducer(PushMultipleReplicasIT.class.getSimpleName() + "_PRODUCER"); producer.setSendMsgTimeout(15 * 1000); producer.start(); } @AfterClass public static void afterClass() throws Exception { producer.shutdown(); } @Test public void testGetMetadataReverse_consumerOffset() throws Exception { String topic = GetMetadataReverseIT.class.getSimpleName() + "_consumerOffset" + random.nextInt(65535); createTopicTo(master1With3Replicas, topic, 1, 1); // Wait topic synchronization await().atMost(Duration.ofMinutes(1)).until(() -> { InnerSalveBrokerController slaveBroker = brokerContainer2.getSlaveBrokers().iterator().next(); return slaveBroker.getTopicConfigManager().selectTopicConfig(topic) != null; }); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, Integer.toString(i).getBytes()); SendResult sendResult = producer.send(msg); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId())); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUMER_GROUP); pushConsumer.subscribe(topic, "*"); pushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); await().atMost(Duration.ofMinutes(3)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); await().atMost(Duration.ofMinutes(1)).until(() -> { pushConsumer.getDefaultMQPushConsumerImpl().persistConsumerOffset(); Map slaveOffsetTable = null; for (InnerSalveBrokerController slave : brokerContainer2.getSlaveBrokers()) { if (slave.getBrokerConfig().getBrokerName().equals(master1With3Replicas.getBrokerConfig().getBrokerName())) { slaveOffsetTable = slave.getConsumerOffsetManager().queryOffset(CONSUMER_GROUP, topic); } } if (slaveOffsetTable != null) { long totalOffset = 0; for (final Long offset : slaveOffsetTable.values()) { totalOffset += offset; } return totalOffset >= MESSAGE_COUNT; } return false; }); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); await().atMost(Duration.ofMinutes(1)).until(() -> { Map offsetTable = master1With3Replicas.getConsumerOffsetManager().queryOffset(CONSUMER_GROUP, topic); long totalOffset = 0; if (offsetTable != null) { for (final Long offset : offsetTable.values()) { totalOffset += offset; } } return totalOffset >= MESSAGE_COUNT; }); pushConsumer.shutdown(); } @Test public void testGetMetadataReverse_delayOffset() throws Exception { String topic = GetMetadataReverseIT.class.getSimpleName() + "_delayOffset" + random.nextInt(65535); createTopicTo(master1With3Replicas, topic, 1, 1); createTopicTo(master2With3Replicas, topic, 1, 1); createTopicTo(master3With3Replicas, topic, 1, 1); // Wait topic synchronization await().atMost(Duration.ofMinutes(1)).until(() -> { InnerSalveBrokerController slaveBroker = brokerContainer2.getSlaveBrokers().iterator().next(); return slaveBroker.getTopicConfigManager().selectTopicConfig(topic) != null; }); int delayLevel = 4; DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUMER_GROUP); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, Integer.toString(i).getBytes()); msg.setDelayTimeLevel(delayLevel); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId())); await().atMost(Duration.ofMinutes(1)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); await().atMost(Duration.ofMinutes(1)).until(() -> { pushConsumer.getDefaultMQPushConsumerImpl().persistConsumerOffset(); Map offsetTable = master2With3Replicas.getConsumerOffsetManager().queryOffset(CONSUMER_GROUP, topic); if (offsetTable != null) { long totalOffset = 0; for (final Long offset : offsetTable.values()) { totalOffset += offset; } return totalOffset >= MESSAGE_COUNT; } return false; }); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); await().atMost(Duration.ofMinutes(1)).until(() -> { Map offsetTable = master1With3Replicas.getScheduleMessageService().getOffsetTable(); return offsetTable.get(delayLevel) >= MESSAGE_COUNT; }); pushConsumer.shutdown(); } @Test public void testGetMetadataReverse_timerCheckPoint() throws Exception { String topic = GetMetadataReverseIT.class.getSimpleName() + "_timerCheckPoint" + random.nextInt(65535); createTopicTo(master1With3Replicas, topic, 1, 1); createTopicTo(master2With3Replicas, topic, 1, 1); createTopicTo(master3With3Replicas, topic, 1, 1); // Wait topic synchronization await().atMost(Duration.ofMinutes(1)).until(() -> { InnerSalveBrokerController slaveBroker = brokerContainer2.getSlaveBrokers().iterator().next(); return slaveBroker.getTopicConfigManager().selectTopicConfig(topic) != null; }); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUMER_GROUP); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, Integer.toString(i).getBytes()); msg.setDelayTimeSec(30); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId())); await().atMost(Duration.ofMinutes(1)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); await().atMost(Duration.ofMinutes(1)).until(() -> { pushConsumer.getDefaultMQPushConsumerImpl().persistConsumerOffset(); Map offsetTable = master2With3Replicas.getConsumerOffsetManager().queryOffset(CONSUMER_GROUP, topic); if (offsetTable != null) { long totalOffset = 0; for (final Long offset : offsetTable.values()) { totalOffset += offset; } return totalOffset >= MESSAGE_COUNT; } return false; }); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); await().atMost(Duration.ofMinutes(1)).until(() -> master1With3Replicas.getTimerCheckpoint().getMasterTimerQueueOffset() >= MESSAGE_COUNT); pushConsumer.shutdown(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/PopSlaveActingMasterIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.container.BrokerContainer; import org.apache.rocketmq.container.InnerBrokerController; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class PopSlaveActingMasterIT extends ContainerIntegrationTestBase { private static final String CONSUME_GROUP = PopSlaveActingMasterIT.class.getSimpleName() + "_Consumer"; private final static int MESSAGE_COUNT = 16; private final Random random = new Random(); private static DefaultMQProducer producer; private final static String MESSAGE_STRING = RandomStringUtils.random(1024); private static final byte[] MESSAGE_BODY = MESSAGE_STRING.getBytes(StandardCharsets.UTF_8); private final BrokerConfig brokerConfig = new BrokerConfig(); public PopSlaveActingMasterIT() { } void createTopic(String topic) { createTopicTo(master1With3Replicas, topic, 1, 1); createTopicTo(master2With3Replicas, topic, 1, 1); createTopicTo(master3With3Replicas, topic, 1, 1); } @BeforeClass public static void beforeClass() throws Throwable { producer = createProducer(PopSlaveActingMasterIT.class.getSimpleName() + "_PRODUCER"); producer.setSendMsgTimeout(5000); producer.start(); } @AfterClass public static void afterClass() throws Exception { producer.shutdown(); } @Test public void testLocalActing_ackSlave() throws Exception { String topic = PopSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); String retryTopic = KeyBuilder.buildPopRetryTopic(topic, CONSUME_GROUP, brokerConfig.isEnableRetryTopicV2()); createTopic(retryTopic); this.switchPop(topic); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); DefaultMQPushConsumer consumer = createPushConsumer(CONSUME_GROUP); consumer.subscribe(topic, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); List consumedMessages = new CopyOnWriteArrayList<>(); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { msgs.forEach(msg -> { consumedMessages.add(msg.getMsgId()); }); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.setClientRebalance(false); consumer.start(); await().atMost(Duration.ofMinutes(1)).until(() -> consumedMessages.size() >= MESSAGE_COUNT); consumer.shutdown(); List retryMsgList = new CopyOnWriteArrayList<>(); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(retryTopic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { retryMsgList.add(new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); Thread.sleep(10000L); assertThat(retryMsgList.size()).isEqualTo(0); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); pushConsumer.shutdown(); } @Test public void testLocalActing_notAckSlave() throws Exception { String topic = PopSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); String retryTopic = KeyBuilder.buildPopRetryTopic(topic, CONSUME_GROUP, brokerConfig.isEnableRetryTopicV2()); createTopic(retryTopic); this.switchPop(topic); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); Set sendToIsolateMsgSet = new HashSet<>(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendToIsolateMsgSet.add(new String(msg.getBody())); sendSuccess++; } } System.out.printf("send success %d%n", sendSuccess); final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); System.out.printf("isolate master1%n"); DefaultMQPushConsumer consumer = createPushConsumer(CONSUME_GROUP); consumer.subscribe(topic, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.setPopInvisibleTime(5000L); List consumedMessages = new CopyOnWriteArrayList<>(); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { msgs.forEach(msg -> { msg.setReconsumeTimes(0); consumedMessages.add(msg.getMsgId()); }); return ConsumeConcurrentlyStatus.RECONSUME_LATER; }); consumer.setClientRebalance(false); consumer.start(); await().atMost(Duration.ofMinutes(1)).until(() -> consumedMessages.size() >= MESSAGE_COUNT); consumer.shutdown(); List retryMsgList = new CopyOnWriteArrayList<>(); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(retryTopic, "*"); pushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { retryMsgList.add(new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); AtomicInteger failCnt = new AtomicInteger(0); await().atMost(Duration.ofMinutes(3)).pollInterval(Duration.ofSeconds(10)).until(() -> { if (retryMsgList.size() < MESSAGE_COUNT) { return false; } for (String msgBodyString : retryMsgList) { if (!sendToIsolateMsgSet.contains(msgBodyString)) { return false; } } return true; }); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); pushConsumer.shutdown(); } @Test public void testRemoteActing_ackSlave() throws Exception { String topic = PopSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); String retryTopic = KeyBuilder.buildPopRetryTopic(topic, CONSUME_GROUP, brokerConfig.isEnableRetryTopicV2()); createTopic(retryTopic); switchPop(topic); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); isolateBroker(master2With3Replicas); brokerContainer2.removeBroker(new BrokerIdentity( master2With3Replicas.getBrokerConfig().getBrokerClusterName(), master2With3Replicas.getBrokerConfig().getBrokerName(), master2With3Replicas.getBrokerConfig().getBrokerId())); DefaultMQPushConsumer consumer = createPushConsumer(CONSUME_GROUP); consumer.subscribe(topic, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); List consumedMessages = new CopyOnWriteArrayList<>(); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { msgs.forEach(msg -> { consumedMessages.add(msg.getMsgId()); }); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.setClientRebalance(false); consumer.start(); await().atMost(Duration.ofMinutes(2)).until(() -> consumedMessages.size() >= MESSAGE_COUNT); consumer.shutdown(); List retryMsgList = new CopyOnWriteArrayList<>(); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(retryTopic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { retryMsgList.add(new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); Thread.sleep(10000); assertThat(retryMsgList.size()).isEqualTo(0); cancelIsolatedBroker(master1With3Replicas); //Add back master master2With3Replicas = brokerContainer2.addBroker(buildConfigContext(master2With3Replicas.getBrokerConfig(), master2With3Replicas.getMessageStoreConfig())); master2With3Replicas.start(); cancelIsolatedBroker(master2With3Replicas); awaitUntilSlaveOK(); Thread.sleep(10000); assertThat(retryMsgList.size()).isEqualTo(0); pushConsumer.shutdown(); } @Test public void testRemoteActing_notAckSlave_getFromLocal() throws Exception { String topic = PopSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); this.switchPop(topic); String retryTopic = KeyBuilder.buildPopRetryTopic(topic, CONSUME_GROUP, brokerConfig.isEnableRetryTopicV2()); createTopic(retryTopic); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); Set sendToIsolateMsgSet = new HashSet<>(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendToIsolateMsgSet.add(new String(msg.getBody())); sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); isolateBroker(master2With3Replicas); brokerContainer2.removeBroker(new BrokerIdentity( master2With3Replicas.getBrokerConfig().getBrokerClusterName(), master2With3Replicas.getBrokerConfig().getBrokerName(), master2With3Replicas.getBrokerConfig().getBrokerId())); DefaultMQPushConsumer consumer = createPushConsumer(CONSUME_GROUP); consumer.subscribe(topic, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); List consumedMessages = new CopyOnWriteArrayList<>(); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { msgs.forEach(msg -> { consumedMessages.add(msg.getMsgId()); }); return ConsumeConcurrentlyStatus.RECONSUME_LATER; }); consumer.setClientRebalance(false); consumer.start(); await().atMost(Duration.ofMinutes(3)).until(() -> consumedMessages.size() >= MESSAGE_COUNT); consumer.shutdown(); List retryMsgList = new CopyOnWriteArrayList<>(); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(retryTopic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { retryMsgList.add(new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); await().atMost(Duration.ofMinutes(1)).until(() -> { if (retryMsgList.size() < MESSAGE_COUNT) { return false; } for (String msgBodyString : retryMsgList) { if (!sendToIsolateMsgSet.contains(msgBodyString)) { return false; } } return true; }); cancelIsolatedBroker(master1With3Replicas); //Add back master master2With3Replicas = brokerContainer2.addBroker(buildConfigContext(master2With3Replicas.getBrokerConfig(), master2With3Replicas.getMessageStoreConfig())); master2With3Replicas.start(); cancelIsolatedBroker(master2With3Replicas); awaitUntilSlaveOK(); pushConsumer.shutdown(); } @Test public void testRemoteActing_notAckSlave_getFromRemote() throws Exception { String topic = PopSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); this.switchPop(topic); String retryTopic = KeyBuilder.buildPopRetryTopic(topic, CONSUME_GROUP, brokerConfig.isEnableRetryTopicV2()); createTopic(retryTopic); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); Set sendToIsolateMsgSet = new HashSet<>(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendToIsolateMsgSet.add(new String(msg.getBody())); sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); isolateBroker(master1With3Replicas); isolateBroker(master2With3Replicas); brokerContainer2.removeBroker(new BrokerIdentity( master2With3Replicas.getBrokerConfig().getBrokerClusterName(), master2With3Replicas.getBrokerConfig().getBrokerName(), master2With3Replicas.getBrokerConfig().getBrokerId())); BrokerController slave1InBrokerContainer3 = getSlaveFromContainerByName(brokerContainer3, master1With3Replicas.getBrokerConfig().getBrokerName()); isolateBroker(slave1InBrokerContainer3); brokerContainer3.removeBroker(new BrokerIdentity( slave1InBrokerContainer3.getBrokerConfig().getBrokerClusterName(), slave1InBrokerContainer3.getBrokerConfig().getBrokerName(), slave1InBrokerContainer3.getBrokerConfig().getBrokerId())); DefaultMQPushConsumer consumer = createPushConsumer(CONSUME_GROUP); consumer.subscribe(topic, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); List consumedMessages = new CopyOnWriteArrayList<>(); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { msgs.forEach(msg -> { consumedMessages.add(msg.getMsgId()); }); return ConsumeConcurrentlyStatus.RECONSUME_LATER; }); consumer.setClientRebalance(false); consumer.start(); await().atMost(Duration.ofMinutes(1)).until(() -> consumedMessages.size() >= MESSAGE_COUNT); consumer.shutdown(); List retryMsgList = new CopyOnWriteArrayList<>(); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(retryTopic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { retryMsgList.add(new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); Thread.sleep(10000); await().atMost(Duration.ofMinutes(1)).until(() -> { if (retryMsgList.size() < MESSAGE_COUNT) { return false; } for (String msgBodyString : retryMsgList) { if (!sendToIsolateMsgSet.contains(msgBodyString)) { return false; } } return true; }); cancelIsolatedBroker(master1With3Replicas); //Add back master master2With3Replicas = brokerContainer2.addBroker(buildConfigContext(master2With3Replicas.getBrokerConfig(), master2With3Replicas.getMessageStoreConfig())); master2With3Replicas.start(); cancelIsolatedBroker(master2With3Replicas); //Add back slave1 to container3 slave1InBrokerContainer3 = brokerContainer3.addBroker(buildConfigContext(slave1InBrokerContainer3.getBrokerConfig(), slave1InBrokerContainer3.getMessageStoreConfig())); slave1InBrokerContainer3.start(); cancelIsolatedBroker(slave1InBrokerContainer3); awaitUntilSlaveOK(); pushConsumer.shutdown(); } private void switchPop(String topic) throws Exception { for (BrokerContainer brokerContainer : brokerContainerList) { for (InnerBrokerController master : brokerContainer.getMasterBrokers()) { String brokerAddr = master.getBrokerAddr(); defaultMQAdminExt.setMessageRequestMode(brokerAddr, topic, CONSUME_GROUP, MessageRequestMode.POP, 8, 60_000); } for (InnerSalveBrokerController slave : brokerContainer.getSlaveBrokers()) { defaultMQAdminExt.setMessageRequestMode(slave.getBrokerAddr(), topic, CONSUME_GROUP, MessageRequestMode.POP, 8, 60_000); } } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/PullMultipleReplicasIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.client.impl.consumer.DefaultMQPullConsumerImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.store.DefaultMessageStore; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class PullMultipleReplicasIT extends ContainerIntegrationTestBase { private static DefaultMQPullConsumer pullConsumer; private static DefaultMQProducer producer; private static MQClientInstance mqClientInstance; private static final String MESSAGE_STRING = RandomStringUtils.random(1024); private static final byte[] MESSAGE_BODY = MESSAGE_STRING.getBytes(StandardCharsets.UTF_8); public PullMultipleReplicasIT() { } @BeforeClass public static void beforeClass() throws Exception { pullConsumer = createPullConsumer(PullMultipleReplicasIT.class.getSimpleName() + "_Consumer"); pullConsumer.start(); Field field = DefaultMQPullConsumerImpl.class.getDeclaredField("mQClientFactory"); field.setAccessible(true); mqClientInstance = (MQClientInstance) field.get(pullConsumer.getDefaultMQPullConsumerImpl()); producer = createProducer(PullMultipleReplicasIT.class.getSimpleName() + "_Producer"); producer.setSendMsgTimeout(15 * 1000); producer.start(); } @AfterClass public static void afterClass() { producer.shutdown(); pullConsumer.shutdown(); } @Test public void testPullMessageFromSlave() throws InterruptedException, RemotingException, MQClientException, MQBrokerException, UnsupportedEncodingException { awaitUntilSlaveOK(); Message msg = new Message(THREE_REPLICAS_TOPIC, MESSAGE_BODY); SendResult sendResult = producer.send(msg); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); final MessageQueue messageQueue = sendResult.getMessageQueue(); final long queueOffset = sendResult.getQueueOffset(); final PullResult[] pullResult = {null}; await().atMost(Duration.ofSeconds(5)).until(() -> { pullResult[0] = pullConsumer.pull(messageQueue, "*", queueOffset, 1); return pullResult[0].getPullStatus() == PullStatus.FOUND; }); List msgFoundList = pullResult[0].getMsgFoundList(); assertThat(msgFoundList.size()).isEqualTo(1); assertThat(new String(msgFoundList.get(0).getBody(), RemotingHelper.DEFAULT_CHARSET)).isEqualTo(MESSAGE_STRING); // Pull the same message from the slave broker pullConsumer.getDefaultMQPullConsumerImpl().getPullAPIWrapper().updatePullFromWhichNode(messageQueue, 1); await().atMost(Duration.ofSeconds(5)).until(() -> { pullResult[0] = pullConsumer.pull(messageQueue, "*", queueOffset, 1); return pullResult[0].getPullStatus() == PullStatus.FOUND; }); msgFoundList = pullResult[0].getMsgFoundList(); assertThat(msgFoundList.size()).isEqualTo(1); assertThat(new String(msgFoundList.get(0).getBody(), RemotingHelper.DEFAULT_CHARSET)).isEqualTo(MESSAGE_STRING); // Pull the same message from the slave broker pullConsumer.getDefaultMQPullConsumerImpl().getPullAPIWrapper().updatePullFromWhichNode(messageQueue, 2); await().atMost(Duration.ofSeconds(5)).until(() -> { pullResult[0] = pullConsumer.pull(messageQueue, "*", queueOffset, 1); return pullResult[0].getPullStatus() == PullStatus.FOUND; }); msgFoundList = pullResult[0].getMsgFoundList(); assertThat(msgFoundList.size()).isEqualTo(1); assertThat(new String(msgFoundList.get(0).getBody(), RemotingHelper.DEFAULT_CHARSET)).isEqualTo(MESSAGE_STRING); pullConsumer.getDefaultMQPullConsumerImpl().getPullAPIWrapper().updatePullFromWhichNode(messageQueue, 0); } @Test public void testSendMessageBackToSlave() throws InterruptedException, RemotingException, MQClientException, MQBrokerException, UnsupportedEncodingException { awaitUntilSlaveOK(); String clusterTopic = "TOPIC_ON_BROKER2_AND_BROKER3_FOR_MESSAGE_BACK"; createTopicTo(master1With3Replicas, clusterTopic); createTopicTo(master3With3Replicas, clusterTopic); Message msg = new Message(clusterTopic, MESSAGE_BODY); producer.setSendMsgTimeout(10 * 1000); final MessageQueue[] selectedQueue = new MessageQueue[1]; await().atMost(Duration.ofSeconds(5)).until(() -> { for (final MessageQueue queue : producer.fetchPublishMessageQueues(clusterTopic)) { if (queue.getBrokerName().equals(master3With3Replicas.getBrokerConfig().getBrokerName())) { selectedQueue[0] = queue; } } return selectedQueue[0] != null; }); SendResult sendResult = producer.send(msg, selectedQueue[0]); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); final MessageQueue messageQueue = sendResult.getMessageQueue(); final long queueOffset = sendResult.getQueueOffset(); final PullResult[] pullResult = {null}; await().atMost(Duration.ofSeconds(60)).until(() -> { pullResult[0] = pullConsumer.pull(messageQueue, "*", queueOffset, 1); return pullResult[0].getPullStatus() == PullStatus.FOUND; }); await().atMost(Duration.ofSeconds(60)).until(() -> { DefaultMessageStore messageStore = (DefaultMessageStore) master3With3Replicas.getMessageStore(); return messageStore.getHaService().inSyncReplicasNums(messageStore.getMaxPhyOffset()) == 3; }); InnerSalveBrokerController slaveBroker = null; for (InnerSalveBrokerController slave : brokerContainer1.getSlaveBrokers()) { if (slave.getBrokerConfig().getBrokerName().equals(master3With3Replicas.getBrokerConfig().getBrokerName())) { slaveBroker = slave; } } assertThat(slaveBroker).isNotNull(); MessageExt backMessage = pullResult[0].getMsgFoundList().get(0); // Message will be sent to the master broker(master1With3Replicas) beside a slave broker of master3With3Replicas backMessage.setStoreHost(new InetSocketAddress(slaveBroker.getBrokerConfig().getBrokerIP1(), slaveBroker.getBrokerConfig().getListenPort())); pullConsumer.sendMessageBack(backMessage, 0); String retryTopic = MixAll.getRetryTopic(pullConsumer.getConsumerGroup()); // Retry topic only has one queue by default MessageQueue newMsgQueue = new MessageQueue(retryTopic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); await().atMost(Duration.ofSeconds(60)).until(() -> { pullResult[0] = pullConsumer.pull(newMsgQueue, "*", 0, 1); return pullResult[0].getPullStatus() == PullStatus.FOUND; }); List msgFoundList = pullResult[0].getMsgFoundList(); assertThat(msgFoundList.size()).isEqualTo(1); assertThat(new String(msgFoundList.get(0).getBody(), RemotingHelper.DEFAULT_CHARSET)).isEqualTo(MESSAGE_STRING); awaitUntilSlaveOK(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/PushMultipleReplicasIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.io.UnsupportedEncodingException; import java.time.Duration; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.awaitility.Awaitility.await; @Ignore public class PushMultipleReplicasIT extends ContainerIntegrationTestBase { private static DefaultMQProducer producer; private static final String TOPIC = PushMultipleReplicasIT.class.getSimpleName() + "_TOPIC"; private static final String REDIRECT_TOPIC = PushMultipleReplicasIT.class.getSimpleName() + "_REDIRECT_TOPIC"; private static final String CONSUMER_GROUP = PushMultipleReplicasIT.class.getSimpleName() + "_Consumer"; private static final int MESSAGE_COUNT = 32; public PushMultipleReplicasIT() throws UnsupportedEncodingException { } @BeforeClass public static void beforeClass() throws Throwable { createTopicTo(master1With3Replicas, TOPIC,1, 1); producer = createProducer(PushMultipleReplicasIT.class.getSimpleName() + "_PRODUCER"); producer.setSendMsgTimeout(15 * 1000); producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { producer.send(new Message(TOPIC, Integer.toString(i).getBytes())); } createTopicTo(master3With3Replicas, REDIRECT_TOPIC, 1, 1); } @AfterClass public static void afterClass() throws Exception { producer.shutdown(); } @Test public void consumeMessageFromSlave_PushConsumer() throws MQClientException { // Wait topic synchronization await().atMost(Duration.ofMinutes(1)).until(() -> { InnerSalveBrokerController slaveBroker = brokerContainer2.getSlaveBrokers().iterator().next(); return slaveBroker.getTopicConfigManager().selectTopicConfig(TOPIC) != null; }); isolateBroker(master1With3Replicas); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUMER_GROUP); pushConsumer.subscribe(TOPIC, "*"); pushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); await().atMost(Duration.ofMinutes(5)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); await().atMost(Duration.ofMinutes(1)).until(() -> { pushConsumer.getDefaultMQPushConsumerImpl().persistConsumerOffset(); Map slaveOffsetTable = null; for (InnerSalveBrokerController slave : brokerContainer2.getSlaveBrokers()) { if (slave.getBrokerConfig().getBrokerName().equals(master1With3Replicas.getBrokerConfig().getBrokerName())) { slaveOffsetTable = slave.getConsumerOffsetManager().queryOffset(CONSUMER_GROUP, TOPIC); } } if (slaveOffsetTable != null) { long totalOffset = 0; for (final Long offset : slaveOffsetTable.values()) { totalOffset += offset; } return totalOffset >= MESSAGE_COUNT; } return false; }); pushConsumer.shutdown(); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/RebalanceLockOnSlaveIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; /** * Test lock on slave when acting master enabled */ @Ignore public class RebalanceLockOnSlaveIT extends ContainerIntegrationTestBase { private static final String THREE_REPLICA_CONSUMER_GROUP = "SyncConsumerOffsetIT_ConsumerThreeReplica"; private static DefaultMQProducer mqProducer; private static DefaultMQPushConsumer mqConsumerThreeReplica1; private static DefaultMQPushConsumer mqConsumerThreeReplica2; private static DefaultMQPushConsumer mqConsumerThreeReplica3; public RebalanceLockOnSlaveIT() { } @BeforeClass public static void beforeClass() throws Exception { mqProducer = createProducer("SyncConsumerOffsetIT_Producer"); mqProducer.start(); mqConsumerThreeReplica1 = createPushConsumer(THREE_REPLICA_CONSUMER_GROUP); mqConsumerThreeReplica1.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); mqConsumerThreeReplica1.subscribe(THREE_REPLICAS_TOPIC, "*"); mqConsumerThreeReplica2 = createPushConsumer(THREE_REPLICA_CONSUMER_GROUP); mqConsumerThreeReplica2.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); mqConsumerThreeReplica2.subscribe(THREE_REPLICAS_TOPIC, "*"); mqConsumerThreeReplica3 = createPushConsumer(THREE_REPLICA_CONSUMER_GROUP); mqConsumerThreeReplica3.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); mqConsumerThreeReplica3.subscribe(THREE_REPLICAS_TOPIC, "*"); } @AfterClass public static void afterClass() { if (mqProducer != null) { mqProducer.shutdown(); } } @Test public void lockFromSlave() throws Exception { awaitUntilSlaveOK(); mqConsumerThreeReplica3.registerMessageListener((MessageListenerOrderly) (msgs, context) -> ConsumeOrderlyStatus.SUCCESS); mqConsumerThreeReplica3.start(); final Set mqSet = mqConsumerThreeReplica3.fetchSubscribeMessageQueues(THREE_REPLICAS_TOPIC); assertThat(targetTopicMqCount(mqSet, THREE_REPLICAS_TOPIC)).isEqualTo(24); for (MessageQueue mq : mqSet) { await().atMost(Duration.ofSeconds(60)).until(() -> mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getRebalanceImpl().lock(mq)); } isolateBroker(master3With3Replicas); mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(THREE_REPLICAS_TOPIC); FindBrokerResult result = mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getmQClientFactory().findBrokerAddressInSubscribe( master3With3Replicas.getBrokerConfig().getBrokerName(), MixAll.MASTER_ID, true); assertThat(result).isNotNull(); for (MessageQueue mq : mqSet) { if (mq.getBrokerName().equals(master3With3Replicas.getBrokerConfig().getBrokerName())) { await().atMost(Duration.ofSeconds(60)).until(() -> mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getRebalanceImpl().lock(mq)); } } removeSlaveBroker(1, brokerContainer1, master3With3Replicas); assertThat(brokerContainer1.getSlaveBrokers().size()).isEqualTo(1); mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(THREE_REPLICAS_TOPIC); for (MessageQueue mq : mqSet) { if (mq.getBrokerName().equals(master3With3Replicas.getBrokerConfig().getBrokerName())) { await().atMost(Duration.ofSeconds(60)).until(() -> !mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getRebalanceImpl().lock(mq)); } } cancelIsolatedBroker(master3With3Replicas); createAndAddSlave(1, brokerContainer1, master3With3Replicas); awaitUntilSlaveOK(); mqConsumerThreeReplica3.shutdown(); await().atMost(100, TimeUnit.SECONDS).until(() -> mqConsumerThreeReplica3.getDefaultMQPushConsumerImpl().getServiceState() == ServiceState.SHUTDOWN_ALREADY); } @Ignore @Test public void multiConsumerLockFromSlave() throws MQClientException, InterruptedException { awaitUntilSlaveOK(); mqConsumerThreeReplica1.registerMessageListener((MessageListenerOrderly) (msgs, context) -> ConsumeOrderlyStatus.SUCCESS); mqConsumerThreeReplica1.start(); mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().doRebalance(); Set mqSet1 = filterMessageQueue(mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().getRebalanceImpl().getProcessQueueTable().keySet(), THREE_REPLICAS_TOPIC); assertThat(mqSet1.size()).isEqualTo(24); isolateBroker(master3With3Replicas); System.out.printf("%s isolated%n", master3With3Replicas.getBrokerConfig().getCanonicalName()); Thread.sleep(5000); mqConsumerThreeReplica2.registerMessageListener((MessageListenerOrderly) (msgs, context) -> ConsumeOrderlyStatus.SUCCESS); mqConsumerThreeReplica2.start(); Thread.sleep(5000); mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(THREE_REPLICAS_TOPIC); mqConsumerThreeReplica2.getDefaultMQPushConsumerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(THREE_REPLICAS_TOPIC); assertThat(mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().getmQClientFactory().findBrokerAddressInSubscribe( master3With3Replicas.getBrokerConfig().getBrokerName(), MixAll.MASTER_ID, true)).isNotNull(); mqConsumerThreeReplica2.getDefaultMQPushConsumerImpl().getmQClientFactory().findBrokerAddressInSubscribe( master3With3Replicas.getBrokerConfig().getBrokerName(), MixAll.MASTER_ID, true); assertThat(mqConsumerThreeReplica2.getDefaultMQPushConsumerImpl().getmQClientFactory().findBrokerAddressInSubscribe( master3With3Replicas.getBrokerConfig().getBrokerName(), MixAll.MASTER_ID, true)).isNotNull(); mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().doRebalance(); mqConsumerThreeReplica2.getDefaultMQPushConsumerImpl().doRebalance(); Set mqSet2 = filterMessageQueue(mqConsumerThreeReplica2.getDefaultMQPushConsumerImpl().getRebalanceImpl().getProcessQueueTable().keySet(), THREE_REPLICAS_TOPIC); mqSet1 = filterMessageQueue(mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().getRebalanceImpl().getProcessQueueTable().keySet(), THREE_REPLICAS_TOPIC); List mqList = new ArrayList<>(); for (MessageQueue mq : mqSet2) { if (mq.getTopic().equals(THREE_REPLICAS_TOPIC)) { mqList.add(mq); } } for (MessageQueue mq : mqSet1) { if (mq.getTopic().equals(THREE_REPLICAS_TOPIC)) { mqList.add(mq); } } await().atMost(Duration.ofSeconds(30)).until(() -> mqList.size() == 24); cancelIsolatedBroker(master3With3Replicas); awaitUntilSlaveOK(); mqConsumerThreeReplica1.shutdown(); mqConsumerThreeReplica2.shutdown(); await().atMost(100, TimeUnit.SECONDS).until(() -> mqConsumerThreeReplica1.getDefaultMQPushConsumerImpl().getServiceState() == ServiceState.SHUTDOWN_ALREADY && mqConsumerThreeReplica2.getDefaultMQPushConsumerImpl().getServiceState() == ServiceState.SHUTDOWN_ALREADY ); } private static int targetTopicMqCount(Set mqSet, String topic) { int count = 0; for (MessageQueue mq : mqSet) { if (mq.getTopic().equals(topic)) { count++; } } return count; } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/ScheduleSlaveActingMasterIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.awaitility.Awaitility.await; //The test is correct, but it takes too much time and not core functions, so it is ignored for the time being @Ignore public class ScheduleSlaveActingMasterIT extends ContainerIntegrationTestBase { private static final String CONSUME_GROUP = ScheduleSlaveActingMasterIT.class.getSimpleName() + "_Consumer"; private static final int MESSAGE_COUNT = 32; private final Random random = new Random(); private static DefaultMQProducer producer; private static final String MESSAGE_STRING = RandomStringUtils.random(1024); private static final byte[] MESSAGE_BODY = MESSAGE_STRING.getBytes(StandardCharsets.UTF_8); void createTopic(String topic) { createTopicTo(master1With3Replicas, topic, 1, 1); createTopicTo(master2With3Replicas, topic, 1, 1); createTopicTo(master3With3Replicas, topic, 1, 1); } @BeforeClass public static void beforeClass() throws Throwable { producer = createProducer(ScheduleSlaveActingMasterIT.class.getSimpleName() + "_PRODUCER"); producer.setSendMsgTimeout(5000); producer.start(); } @AfterClass public static void afterClass() throws Exception { producer.shutdown(); } @Test public void testLocalActing_delayMsg() throws Exception { awaitUntilSlaveOK(); String topic = ScheduleSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); AtomicInteger inTimeMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { long period = System.currentTimeMillis() - msgs.get(0).getBornTimestamp(); if (Math.abs(period - 30000) <= 4000) { inTimeMsgCount.addAndGet(msgs.size()); } receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); msg.setDelayTimeLevel(4); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); System.out.printf("send success%n"); isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId())); System.out.printf("Remove master1%n"); await().atMost(Duration.ofMinutes(1)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT && inTimeMsgCount.get() >= MESSAGE_COUNT * 0.95); System.out.printf("consumer received %d msg, %d in time%n", receivedMsgCount.get(), inTimeMsgCount.get()); pushConsumer.shutdown(); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); System.out.printf("Add back master1%n"); awaitUntilSlaveOK(); // sleep a while to recover Thread.sleep(30000); } @Test public void testLocalActing_timerMsg() throws Exception { awaitUntilSlaveOK(); String topic = ScheduleSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); AtomicInteger inTimeMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { long period = System.currentTimeMillis() - msgs.get(0).getBornTimestamp(); if (Math.abs(period - 30000) <= 1000) { inTimeMsgCount.addAndGet(msgs.size()); } receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); msg.setDelayTimeSec(30); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(2)).until(() -> finalSendSuccess >= MESSAGE_COUNT); System.out.printf("send success%n"); isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId())); System.out.printf("Remove master1%n"); await().atMost(Duration.ofMinutes(1)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT && inTimeMsgCount.get() >= MESSAGE_COUNT * 0.95); System.out.printf("consumer received %d msg, %d in time%n", receivedMsgCount.get(), inTimeMsgCount.get()); pushConsumer.shutdown(); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); System.out.printf("Add back master1%n"); awaitUntilSlaveOK(); // sleep a while to recover Thread.sleep(20000); } @Test public void testRemoteActing_delayMsg() throws Exception { awaitUntilSlaveOK(); String topic = ScheduleSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); AtomicInteger receivedMsgCount = new AtomicInteger(0); AtomicInteger inTimeMsgCount = new AtomicInteger(0); AtomicInteger master3MsgCount = new AtomicInteger(0); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); msg.setDelayTimeLevel(4); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); long sendCompleteTimeStamp = System.currentTimeMillis(); System.out.printf("send success%n"); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(topic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { long period = System.currentTimeMillis() - sendCompleteTimeStamp; // Remote Acting lead to born timestamp, msgId changed, it need to polish. if (Math.abs(period - 30000) <= 4000) { inTimeMsgCount.addAndGet(msgs.size()); } if (msgs.get(0).getBrokerName().equals(master3With3Replicas.getBrokerConfig().getBrokerName())) { master3MsgCount.addAndGet(msgs.size()); } receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf("cost " + period + " " + x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); isolateBroker(master1With3Replicas); BrokerIdentity master1BrokerIdentity = new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId()); brokerContainer1.removeBroker(master1BrokerIdentity); System.out.printf("Remove master1%n"); isolateBroker(master2With3Replicas); BrokerIdentity master2BrokerIdentity = new BrokerIdentity( master2With3Replicas.getBrokerConfig().getBrokerClusterName(), master2With3Replicas.getBrokerConfig().getBrokerName(), master2With3Replicas.getBrokerConfig().getBrokerId()); brokerContainer2.removeBroker(master2BrokerIdentity); System.out.printf("Remove master2%n"); await().atMost(Duration.ofMinutes(2)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT && master3MsgCount.get() >= MESSAGE_COUNT && inTimeMsgCount.get() >= MESSAGE_COUNT * 0.95); System.out.printf("consumer received %d msg, %d in time%n", receivedMsgCount.get(), inTimeMsgCount.get()); pushConsumer.shutdown(); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); System.out.printf("Add back master1%n"); //Add back master master2With3Replicas = brokerContainer2.addBroker(buildConfigContext(master2With3Replicas.getBrokerConfig(), master2With3Replicas.getMessageStoreConfig())); master2With3Replicas.start(); cancelIsolatedBroker(master2With3Replicas); System.out.printf("Add back master2%n"); awaitUntilSlaveOK(); // sleep a while to recover Thread.sleep(30000); } @Test public void testRemoteActing_timerMsg() throws Exception { awaitUntilSlaveOK(); String topic = ScheduleSlaveActingMasterIT.class.getSimpleName() + random.nextInt(65535); createTopic(topic); AtomicInteger receivedMsgCount = new AtomicInteger(0); AtomicInteger inTimeMsgCount = new AtomicInteger(0); AtomicInteger master3MsgCount = new AtomicInteger(0); MessageQueue messageQueue = new MessageQueue(topic, master1With3Replicas.getBrokerConfig().getBrokerName(), 0); int sendSuccess = 0; for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); msg.setDelayTimeSec(30); SendResult sendResult = producer.send(msg, messageQueue); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { sendSuccess++; } } final int finalSendSuccess = sendSuccess; await().atMost(Duration.ofMinutes(1)).until(() -> finalSendSuccess >= MESSAGE_COUNT); long sendCompleteTimeStamp = System.currentTimeMillis(); System.out.printf("send success%n"); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(topic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { long period = System.currentTimeMillis() - sendCompleteTimeStamp; // Remote Acting lead to born timestamp, msgId changed, it need to polish. if (Math.abs(period - 30000) <= 3000) { inTimeMsgCount.addAndGet(msgs.size()); } if (msgs.get(0).getBrokerName().equals(master3With3Replicas.getBrokerConfig().getBrokerName())) { master3MsgCount.addAndGet(msgs.size()); } receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf("cost " + period + " " + x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity( master1With3Replicas.getBrokerConfig().getBrokerClusterName(), master1With3Replicas.getBrokerConfig().getBrokerName(), master1With3Replicas.getBrokerConfig().getBrokerId())); System.out.printf("Remove master1%n"); isolateBroker(master2With3Replicas); brokerContainer2.removeBroker(new BrokerIdentity( master2With3Replicas.getBrokerConfig().getBrokerClusterName(), master2With3Replicas.getBrokerConfig().getBrokerName(), master2With3Replicas.getBrokerConfig().getBrokerId())); System.out.printf("Remove master2%n"); await().atMost(Duration.ofMinutes(1)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT && master3MsgCount.get() >= MESSAGE_COUNT && inTimeMsgCount.get() >= MESSAGE_COUNT * 0.95); System.out.printf("consumer received %d msg, %d in time%n", receivedMsgCount.get(), inTimeMsgCount.get()); pushConsumer.shutdown(); //Add back master master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); System.out.printf("Add back master1%n"); //Add back master master2With3Replicas = brokerContainer2.addBroker(buildConfigContext(master2With3Replicas.getBrokerConfig(), master2With3Replicas.getMessageStoreConfig())); master2With3Replicas.start(); cancelIsolatedBroker(master2With3Replicas); System.out.printf("Add back master2%n"); awaitUntilSlaveOK(); // sleep a while to recover Thread.sleep(20000); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/ScheduledMessageIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.store.DefaultMessageStore; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class ScheduledMessageIT extends ContainerIntegrationTestBase { private static DefaultMQProducer producer; private static final String CONSUME_GROUP = ScheduledMessageIT.class.getSimpleName() + "_Consumer"; private static final String MESSAGE_STRING = RandomStringUtils.random(1024); private static final byte[] MESSAGE_BODY = MESSAGE_STRING.getBytes(StandardCharsets.UTF_8); private static final String TOPIC_PREFIX = ScheduledMessageIT.class.getSimpleName() + "_TOPIC"; private final Random random = new Random(); private static final int MESSAGE_COUNT = 128; public ScheduledMessageIT() throws UnsupportedEncodingException { } void createTopic(String topic) { createTopicTo(master1With3Replicas, topic, 1, 1); createTopicTo(master2With3Replicas, topic, 1, 1); createTopicTo(master3With3Replicas, topic, 1, 1); } @BeforeClass public static void beforeClass() throws Throwable { producer = createProducer(ScheduledMessageIT.class.getSimpleName() + "_PRODUCER"); producer.setSendMsgTimeout(5000); producer.start(); } @AfterClass public static void afterClass() throws Exception { producer.shutdown(); } @Ignore @Test public void consumeScheduledMsg() throws MQClientException, RemotingException, InterruptedException, MQBrokerException { String topic = TOPIC_PREFIX + random.nextInt(65535); createTopic(topic); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP + random.nextInt(65535)); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); AtomicInteger inTimeMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { long period = System.currentTimeMillis() - msgs.get(0).getBornTimestamp(); if (Math.abs(period - 5000) <= 1000) { inTimeMsgCount.addAndGet(msgs.size()); } receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(receivedMsgCount.get() + " cost " + period + " " + x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, MESSAGE_BODY); msg.setDelayTimeLevel(2); producer.send(msg); } await().atMost(Duration.ofSeconds(MESSAGE_COUNT * 2)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT && inTimeMsgCount.get() >= MESSAGE_COUNT * 0.95); System.out.printf("consumer received %d msg, %d in time%n", receivedMsgCount.get(), inTimeMsgCount.get()); pushConsumer.shutdown(); } @Test public void consumeScheduledMsgFromSlave() throws MQClientException, RemotingException, InterruptedException, MQBrokerException { String topic = TOPIC_PREFIX + random.nextInt(65535); createTopic(topic); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP + random.nextInt(65535)); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); msgs.forEach(x -> System.out.printf(x + "%n")); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, String.valueOf(i).getBytes()); msg.setDelayTimeLevel(2); producer.send(msg); } isolateBroker(master1With3Replicas); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); assertThat(producer.getDefaultMQProducerImpl().getmQClientFactory().findBrokerAddressInPublish(topic)).isNull(); pushConsumer.start(); await().atMost(Duration.ofSeconds(MESSAGE_COUNT * 2)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); pushConsumer.shutdown(); cancelIsolatedBroker(master1With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2); } @Test public void consumeTimerMsgFromSlave() throws MQClientException, RemotingException, InterruptedException, MQBrokerException { String topic = TOPIC_PREFIX + random.nextInt(65535); createTopic(topic); DefaultMQPushConsumer pushConsumer = createPushConsumer(CONSUME_GROUP); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, String.valueOf(i).getBytes()); msg.setDelayTimeSec(3); producer.send(msg); } isolateBroker(master1With3Replicas); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); assertThat(producer.getDefaultMQProducerImpl().getmQClientFactory().findBrokerAddressInPublish(topic)).isNull(); pushConsumer.start(); await().atMost(Duration.ofSeconds(MESSAGE_COUNT * 2)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); pushConsumer.shutdown(); cancelIsolatedBroker(master1With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/SendMultipleReplicasIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.store.DefaultMessageStore; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class SendMultipleReplicasIT extends ContainerIntegrationTestBase { private static DefaultMQProducer mqProducer; private static final String MSG = "Hello RocketMQ "; private static final byte[] MESSAGE_BODY = MSG.getBytes(StandardCharsets.UTF_8); public SendMultipleReplicasIT() { } @BeforeClass public static void beforeClass() throws Exception { mqProducer = createProducer("SendMultipleReplicasMessageIT_Producer"); mqProducer.setSendMsgTimeout(15 * 1000); mqProducer.start(); } @AfterClass public static void afterClass() { if (mqProducer != null) { mqProducer.shutdown(); } } @Test public void sendMessageToBrokerGroup() throws MQClientException, RemotingException, InterruptedException, MQBrokerException { awaitUntilSlaveOK(); // Send message to broker group with three replicas Message msg = new Message(THREE_REPLICAS_TOPIC, MESSAGE_BODY); SendResult sendResult = mqProducer.send(msg); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } @Test public void sendMessage_Auto_Replicas_Success() throws Exception { await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3); // Broker with 3 replicas configured as 3-2-1 auto replicas mode Message msg = new Message(THREE_REPLICAS_TOPIC, MESSAGE_BODY); SendResult sendResult = mqProducer.send(msg); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); // Remove two slave broker removeSlaveBroker(1, brokerContainer2, master1With3Replicas); removeSlaveBroker(2, brokerContainer3, master1With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 0 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 1); master1With3Replicas.getMessageStoreConfig().setEnableAutoInSyncReplicas(true); List mqList = mqProducer.getDefaultMQProducerImpl().fetchPublishMessageQueues(THREE_REPLICAS_TOPIC); MessageQueue targetMq = null; for (MessageQueue mq : mqList) { if (mq.getBrokerName().equals(master1With3Replicas.getBrokerConfig().getBrokerName())) { targetMq = mq; } } assertThat(targetMq).isNotNull(); // Although this broker group only has one slave broker, send will be success in auto mode. msg = new Message(THREE_REPLICAS_TOPIC, MESSAGE_BODY); sendResult = mqProducer.send(msg, targetMq); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); // Recover the cluster state createAndAddSlave(1, brokerContainer2, master1With3Replicas); createAndAddSlave(2, brokerContainer3, master1With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3); } @Test public void sendMessage_Auto_Replicas_Failed() throws Exception { await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3); // Broker with 3 replicas configured as 3-2-1 auto replicas mode // Remove two slave broker removeSlaveBroker(1, brokerContainer2, master1With3Replicas); removeSlaveBroker(2, brokerContainer3, master1With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 0 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 1); // Disable the auto mode master1With3Replicas.getMessageStoreConfig().setEnableAutoInSyncReplicas(false); List mqList = mqProducer.getDefaultMQProducerImpl().fetchPublishMessageQueues(THREE_REPLICAS_TOPIC); MessageQueue targetMq = null; for (MessageQueue mq : mqList) { if (mq.getBrokerName().equals(master1With3Replicas.getBrokerConfig().getBrokerName())) { targetMq = mq; } } assertThat(targetMq).isNotNull(); Message msg = new Message(THREE_REPLICAS_TOPIC, MESSAGE_BODY); boolean exceptionCaught = false; try { mqProducer.send(msg, targetMq); } catch (MQBrokerException e) { exceptionCaught = true; } assertThat(exceptionCaught).isTrue(); // Recover the cluster state createAndAddSlave(1, brokerContainer2, master1With3Replicas); createAndAddSlave(2, brokerContainer3, master1With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master1With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2 && master1With3Replicas.getMessageStore().getAliveReplicaNumInGroup() == 3); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/SlaveBrokerIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.store.DefaultMessageStore; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class SlaveBrokerIT extends ContainerIntegrationTestBase { @Test public void reAddSlaveBroker() throws Exception { await().atMost(Duration.ofMinutes(1)).until(() -> { ClusterInfo clusterInfo = defaultMQAdminExt.examineBrokerClusterInfo(); if (clusterInfo.getClusterAddrTable().get(master1With3Replicas.getBrokerConfig().getBrokerClusterName()).size() != 3) { return false; } if (clusterInfo.getBrokerAddrTable().get(master1With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size() != 3) { return false; } if (clusterInfo.getBrokerAddrTable().get(master2With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size() != 3) { return false; } if (clusterInfo.getBrokerAddrTable().get(master3With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size() != 3) { return false; } return true; }); // Remove one replicas from each broker group removeSlaveBroker(1, brokerContainer1, master3With3Replicas); removeSlaveBroker(1, brokerContainer2, master1With3Replicas); removeSlaveBroker(1, brokerContainer3, master2With3Replicas); await().atMost(Duration.ofMinutes(1)).until(() -> { // Test cluster info again ClusterInfo clusterInfo = defaultMQAdminExt.examineBrokerClusterInfo(); assertThat(clusterInfo.getBrokerAddrTable().get(master1With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size()) .isEqualTo(2); assertThat(clusterInfo.getBrokerAddrTable().get(master2With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size()) .isEqualTo(2); assertThat(clusterInfo.getBrokerAddrTable().get(master3With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size()) .isEqualTo(2); return true; }); // ReAdd the slave broker createAndAddSlave(1, brokerContainer1, master3With3Replicas); createAndAddSlave(1, brokerContainer2, master1With3Replicas); createAndAddSlave(1, brokerContainer3, master2With3Replicas); // Trigger a register action //for (final SlaveBrokerController slaveBrokerController : brokerContainer1.getSlaveBrokers()) { // slaveBrokerController.registerBrokerAll(false, false, true); //} // //for (final SlaveBrokerController slaveBrokerController : brokerContainer2.getSlaveBrokers()) { // slaveBrokerController.registerBrokerAll(false, false, true); //} await().atMost(Duration.ofMinutes(1)).until(() -> { ClusterInfo clusterInfo = defaultMQAdminExt.examineBrokerClusterInfo(); return clusterInfo.getBrokerAddrTable() .get(master1With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size() == 3 && clusterInfo.getBrokerAddrTable() .get(master2With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size() == 3 && clusterInfo.getBrokerAddrTable() .get(master2With3Replicas.getBrokerConfig().getBrokerName()).getBrokerAddrs().size() == 3; }); } @Test public void reAddSlaveBroker_ConnectionCheck() throws Exception { await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master3With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2); removeSlaveBroker(1, brokerContainer1, master3With3Replicas); createAndAddSlave(1, brokerContainer1, master3With3Replicas); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master3With3Replicas.getMessageStore()).getHaService().getConnectionCount().get() == 2); await().atMost(100, TimeUnit.SECONDS) .until(() -> ((DefaultMessageStore) master3With3Replicas.getMessageStore()).getHaService().inSyncReplicasNums(0) == 3); Thread.sleep(1000 * 101); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/SyncConsumerOffsetIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.container.BrokerContainer; import org.apache.rocketmq.container.InnerSalveBrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class SyncConsumerOffsetIT extends ContainerIntegrationTestBase { private static final String THREE_REPLICA_CONSUMER_GROUP = "SyncConsumerOffsetIT_ConsumerThreeReplica"; private static final String TEST_SYNC_TOPIC = SyncConsumerOffsetIT.class.getSimpleName() + "_topic"; private static DefaultMQProducer mqProducer; private static DefaultMQPushConsumer mqConsumerThreeReplica; private static final String MSG = "Hello RocketMQ "; private static final byte[] MESSAGE_BODY = MSG.getBytes(StandardCharsets.UTF_8); public SyncConsumerOffsetIT() { } @BeforeClass public static void beforeClass() throws Exception { createTopicTo(master3With3Replicas, TEST_SYNC_TOPIC); mqProducer = createProducer("SyncConsumerOffsetIT_Producer"); mqProducer.setSendMsgTimeout(15 * 1000); mqProducer.start(); mqConsumerThreeReplica = createPushConsumer(THREE_REPLICA_CONSUMER_GROUP); mqConsumerThreeReplica.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); mqConsumerThreeReplica.subscribe(TEST_SYNC_TOPIC, "*"); } @AfterClass public static void afterClass() { mqProducer.shutdown(); mqConsumerThreeReplica.shutdown(); } @Test public void syncConsumerOffsetWith3Replicas() throws InterruptedException, RemotingException, MQClientException, MQBrokerException { syncConsumeOffsetInner(TEST_SYNC_TOPIC, mqConsumerThreeReplica, master3With3Replicas, Arrays.asList(brokerContainer1, brokerContainer2)); } private void syncConsumeOffsetInner(String topic, DefaultMQPushConsumer consumer, BrokerController master, List slaveContainers) throws InterruptedException, RemotingException, MQClientException, MQBrokerException { awaitUntilSlaveOK(); String group = THREE_REPLICA_CONSUMER_GROUP; int msgCount = 100; for (int i = 0; i < msgCount; i++) { Message msg = new Message(topic, MESSAGE_BODY); SendResult sendResult = mqProducer.send(msg); assertThat(sendResult.getSendStatus()).isEqualTo(SendStatus.SEND_OK); } CountDownLatch countDownLatch = new CountDownLatch(msgCount); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { countDownLatch.countDown(); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); boolean ok = countDownLatch.await(100, TimeUnit.SECONDS); assertThat(ok).isEqualTo(true); System.out.printf("consume complete%n"); final Set mqSet = filterMessageQueue(consumer.fetchSubscribeMessageQueues(topic), topic); await().atMost(120, TimeUnit.SECONDS).until(() -> { Map consumerOffsetMap = new HashMap<>(); long offsetTotal = 0L; for (MessageQueue mq : mqSet) { long queueOffset = master.getConsumerOffsetManager().queryOffset(group, topic, mq.getQueueId()); if (queueOffset < 0) { continue; } offsetTotal += queueOffset; consumerOffsetMap.put(mq.getQueueId(), queueOffset); } if (offsetTotal < 100) { return false; } boolean syncOk = true; for (BrokerContainer brokerContainer : slaveContainers) { for (InnerSalveBrokerController slave : brokerContainer.getSlaveBrokers()) { if (!slave.getBrokerConfig().getBrokerName().equals(master.getBrokerConfig().getBrokerName())) { continue; } for (MessageQueue mq : mqSet) { long slaveOffset = slave.getConsumerOffsetManager().queryOffset(group, topic, mq.getQueueId()); boolean check = slaveOffset == consumerOffsetMap.get(mq.getQueueId()); syncOk &= check; } } } return syncOk; }); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/TransactionListenerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; public class TransactionListenerImpl implements TransactionListener { private boolean shouldReturnUnknownState = false; public TransactionListenerImpl(boolean shouldReturnUnknownState) { this.shouldReturnUnknownState = shouldReturnUnknownState; } public void setShouldReturnUnknownState(boolean shouldReturnUnknownState) { this.shouldReturnUnknownState = shouldReturnUnknownState; } @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { if (shouldReturnUnknownState) { return LocalTransactionState.UNKNOW; } else { return LocalTransactionState.COMMIT_MESSAGE; } } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { if (shouldReturnUnknownState) { return LocalTransactionState.UNKNOW; } else { return LocalTransactionState.COMMIT_MESSAGE; } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/container/TransactionMessageIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.container; import java.io.UnsupportedEncodingException; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.client.producer.TransactionSendResult; import org.apache.rocketmq.common.BrokerIdentity; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @Ignore public class TransactionMessageIT extends ContainerIntegrationTestBase { private static final String MESSAGE_STRING = RandomStringUtils.random(1024); private static byte[] messageBody; static { try { messageBody = MESSAGE_STRING.getBytes(RemotingHelper.DEFAULT_CHARSET); } catch (UnsupportedEncodingException ignored) { } } private static final int MESSAGE_COUNT = 16; public TransactionMessageIT() { } private static String generateGroup() { return "GID-" + TransactionMessageIT.class.getSimpleName() + RandomStringUtils.randomNumeric(5); } @Test public void consumeTransactionMsg() throws MQClientException { final String topic = generateTopic(); createTopicTo(master1With3Replicas, topic, 1, 1); final String group = generateGroup(); DefaultMQPushConsumer pushConsumer = createPushConsumer(group); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { receivedMsgCount.addAndGet(msgs.size()); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); TransactionMQProducer producer = createTransactionProducer(group, new TransactionListenerImpl(false)); producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, messageBody); TransactionSendResult result = producer.sendMessageInTransaction(msg, null); assertThat(result.getLocalTransactionState()).isEqualTo(LocalTransactionState.COMMIT_MESSAGE); } System.out.printf("send message complete%n"); await().atMost(Duration.ofSeconds(MESSAGE_COUNT * 2)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); System.out.printf("consumer received %d msg%n", receivedMsgCount.get()); pushConsumer.shutdown(); producer.shutdown(); } private static String generateTopic() { return TransactionMessageIT.class.getSimpleName() + RandomStringUtils.randomNumeric(5); } @Test public void consumeTransactionMsgLocalEscape() throws Exception { final String topic = generateTopic(); createTopicTo(master1With3Replicas, topic, 1, 1); final String group = generateGroup(); DefaultMQPushConsumer pushConsumer = createPushConsumer(group); pushConsumer.subscribe(topic, "*"); AtomicInteger receivedMsgCount = new AtomicInteger(0); Map msgSentMap = new HashMap<>(); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { if (msgSentMap.containsKey(msg.getMsgId())) { receivedMsgCount.incrementAndGet(); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); TransactionListenerImpl transactionCheckListener = new TransactionListenerImpl(true); TransactionMQProducer producer = createTransactionProducer(group, transactionCheckListener); producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, messageBody); msg.setKeys(UUID.randomUUID().toString()); SendResult result = producer.sendMessageInTransaction(msg, null); String msgId = result.getMsgId(); msgSentMap.put(msgId, msg); } isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity(master1With3Replicas.getBrokerIdentity().getBrokerClusterName(), master1With3Replicas.getBrokerIdentity().getBrokerName(), master1With3Replicas.getBrokerIdentity().getBrokerId())); System.out.printf("=========" + master1With3Replicas.getBrokerIdentity().getBrokerName() + "-" + master1With3Replicas.getBrokerIdentity().getBrokerId() + " removed%n"); createTopicTo(master2With3Replicas, topic, 1, 1); transactionCheckListener.setShouldReturnUnknownState(false); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); System.out.printf("Wait for consuming%n"); await().atMost(Duration.ofSeconds(300)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); System.out.printf("consumer received %d msg%n", receivedMsgCount.get()); pushConsumer.shutdown(); producer.shutdown(); master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); awaitUntilSlaveOK(); receivedMsgCount.set(0); DefaultMQPushConsumer pushConsumer2 = createPushConsumer(group); pushConsumer2.subscribe(topic, "*"); pushConsumer2.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { if (msgSentMap.containsKey(msg.getMsgId())) { receivedMsgCount.incrementAndGet(); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer2.start(); System.out.printf("Wait for checking...%n"); Thread.sleep(10000L); } @Test public void consumeTransactionMsgRemoteEscape() throws Exception { final String topic = generateTopic(); createTopicTo(master1With3Replicas, topic, 1, 1); final String group = generateGroup(); AtomicInteger receivedMsgCount = new AtomicInteger(0); Map msgSentMap = new HashMap<>(); DefaultMQPushConsumer pushConsumer = createPushConsumer(group); pushConsumer.subscribe(topic, "*"); pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { if (msgSentMap.containsKey(msg.getMsgId())) { receivedMsgCount.incrementAndGet(); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer.start(); TransactionListenerImpl transactionCheckListener = new TransactionListenerImpl(true); TransactionMQProducer producer = createTransactionProducer(group, transactionCheckListener); producer.start(); for (int i = 0; i < MESSAGE_COUNT; i++) { Message msg = new Message(topic, messageBody); msg.setKeys(UUID.randomUUID().toString()); SendResult result = producer.sendMessageInTransaction(msg, null); String msgId = result.getMsgId(); msgSentMap.put(msgId, msg); } isolateBroker(master1With3Replicas); brokerContainer1.removeBroker(new BrokerIdentity(master1With3Replicas.getBrokerIdentity().getBrokerClusterName(), master1With3Replicas.getBrokerIdentity().getBrokerName(), master1With3Replicas.getBrokerIdentity().getBrokerId())); System.out.printf("=========" + master1With3Replicas.getBrokerIdentity().getBrokerName() + "-" + master1With3Replicas.getBrokerIdentity().getBrokerId() + " removed%n"); createTopicTo(master2With3Replicas, topic, 1, 1); createTopicTo(master3With3Replicas, topic, 1, 1); //isolateBroker(master2With3Replicas); brokerContainer2.removeBroker(new BrokerIdentity(master2With3Replicas.getBrokerIdentity().getBrokerClusterName(), master2With3Replicas.getBrokerIdentity().getBrokerName(), master2With3Replicas.getBrokerIdentity().getBrokerId())); System.out.printf("=========" + master2With3Replicas.getBrokerIdentity().getBrokerClusterName() + "-" + master2With3Replicas.getBrokerIdentity().getBrokerName() + "-" + master2With3Replicas.getBrokerIdentity().getBrokerId() + " removed%n"); pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl().doRebalance(false); transactionCheckListener.setShouldReturnUnknownState(false); producer.getDefaultMQProducerImpl().getmQClientFactory().updateTopicRouteInfoFromNameServer(topic); System.out.printf("Wait for consuming%n"); await().atMost(Duration.ofSeconds(180)).until(() -> receivedMsgCount.get() >= MESSAGE_COUNT); System.out.printf("consumer received %d msg%n", receivedMsgCount.get()); pushConsumer.shutdown(); producer.shutdown(); master1With3Replicas = brokerContainer1.addBroker(buildConfigContext(master1With3Replicas.getBrokerConfig(), master1With3Replicas.getMessageStoreConfig())); master1With3Replicas.start(); cancelIsolatedBroker(master1With3Replicas); master2With3Replicas = brokerContainer2.addBroker(buildConfigContext(master2With3Replicas.getBrokerConfig(), master2With3Replicas.getMessageStoreConfig())); master2With3Replicas.start(); cancelIsolatedBroker(master2With3Replicas); awaitUntilSlaveOK(); receivedMsgCount.set(0); DefaultMQPushConsumer pushConsumer2 = createPushConsumer(group); pushConsumer2.subscribe(topic, "*"); pushConsumer2.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { if (msgSentMap.containsKey(msg.getMsgId())) { receivedMsgCount.incrementAndGet(); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); pushConsumer2.start(); System.out.printf("Wait for checking...%n"); Thread.sleep(10000L); assertThat(receivedMsgCount.get()).isEqualTo(0); pushConsumer2.shutdown(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/delay/DelayConf.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.delay; import org.apache.rocketmq.test.base.BaseConf; public class DelayConf extends BaseConf { protected static final int[] DELAY_LEVEL = { 1, 5, 10, 30, 1 * 60, 5 * 60, 10 * 60, 30 * 60, 1 * 3600, 2 * 3600, 6 * 3600, 12 * 3600, 1 * 24 * 3600}; } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/delay/NormalMsgDelayIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.delay; import java.util.List; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.factory.MQMessageFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQDelayListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class NormalMsgDelayIT extends DelayConf { private static Logger logger = LoggerFactory.getLogger(NormalMsgDelayIT.class); protected int msgSize = 100; private RMQNormalProducer producer = null; private RMQNormalConsumer consumer = null; private String topic = null; @Before public void setUp() { topic = initTopic(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQDelayListener()); } @After public void tearDown() { super.shutdown(); } @Test public void testDelayLevel1() throws Exception { Thread.sleep(3000); int delayLevel = 1; List delayMsgs = MQMessageFactory.getDelayMsg(topic, delayLevel, msgSize); producer.send(delayMsgs); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); Assert.assertEquals("Not all are consumed", 0, VerifyUtils.verify(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())); Assert.assertEquals("Timer is not correct", true, VerifyUtils.verifyDelay(DELAY_LEVEL[delayLevel - 1] * 1000, DELAY_LEVEL[delayLevel] * 1000, ((RMQDelayListener) consumer.getListener()).getMsgDelayTimes())); } @Test public void testDelayLevel2() { int delayLevel = 2; List delayMsgs = MQMessageFactory.getDelayMsg(topic, delayLevel, msgSize); producer.send(delayMsgs); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), DELAY_LEVEL[delayLevel - 1] * 1000 * 2); Assert.assertEquals("Not all are consumed", 0, VerifyUtils.verify(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())); Assert.assertEquals("Timer is not correct", true, VerifyUtils.verifyDelay(DELAY_LEVEL[delayLevel - 1] * 1000, DELAY_LEVEL[delayLevel] * 1000, ((RMQDelayListener) consumer.getListener()).getMsgDelayTimes())); } @Test public void testDelayLevel3() { int delayLevel = 3; List delayMsgs = MQMessageFactory.getDelayMsg(topic, delayLevel, msgSize); producer.send(delayMsgs); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), DELAY_LEVEL[delayLevel - 1] * 1000 * 2); Assert.assertEquals("Not all are consumed", 0, VerifyUtils.verify(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())); Assert.assertEquals("Timer is not correct", true, VerifyUtils.verifyDelay(DELAY_LEVEL[delayLevel - 1] * 1000, DELAY_LEVEL[delayLevel] * 1000, ((RMQDelayListener) consumer.getListener()).getMsgDelayTimes())); } @Test public void testDelayLevel4() { int delayLevel = 4; List delayMsgs = MQMessageFactory.getDelayMsg(topic, delayLevel, msgSize); producer.send(delayMsgs); Assert.assertEquals("Not all are sent", msgSize, producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), DELAY_LEVEL[delayLevel - 1] * 1000 * 2); Assert.assertEquals("Not all are consumed", 0, VerifyUtils.verify(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())); Assert.assertEquals("Timer is not correct", true, VerifyUtils.verifyDelay(DELAY_LEVEL[delayLevel - 1] * 1000, DELAY_LEVEL[delayLevel] * 1000, ((RMQDelayListener) consumer.getListener()).getMsgDelayTimes())); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/dledger/DLedgerProduceAndConsumeIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.dledger; import java.util.UUID; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.factory.ProducerFactory; import org.junit.Assert; import org.junit.Test; public class DLedgerProduceAndConsumeIT { public BrokerConfig buildBrokerConfig(String cluster, String brokerName) { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerClusterName(cluster); brokerConfig.setBrokerName(brokerName); brokerConfig.setBrokerIP1("127.0.0.1"); brokerConfig.setNamesrvAddr(BaseConf.NAMESRV_ADDR); return brokerConfig; } public MessageStoreConfig buildStoreConfig(String brokerName, String peers, String selfId) { MessageStoreConfig storeConfig = new MessageStoreConfig(); String baseDir = IntegrationTestBase.createBaseDir(); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + "_" + "commitlog"); storeConfig.setHaListenPort(0); storeConfig.setMappedFileSizeCommitLog(10 * 1024 * 1024); storeConfig.setEnableDLegerCommitLog(true); storeConfig.setdLegerGroup(brokerName); storeConfig.setdLegerSelfId(selfId); storeConfig.setdLegerPeers(peers); return storeConfig; } @Test public void testProduceAndConsume() throws Exception { String cluster = UUID.randomUUID().toString(); String brokerName = UUID.randomUUID().toString(); String selfId = "n0"; // TODO: We need to acquire the actual listening port after the peer has started. String peers = String.format("n0-localhost:%d", 0); BrokerConfig brokerConfig = buildBrokerConfig(cluster, brokerName); MessageStoreConfig storeConfig = buildStoreConfig(brokerName, peers, selfId); BrokerController brokerController = IntegrationTestBase.createAndStartBroker(storeConfig, brokerConfig); BaseConf.waitBrokerRegistered(BaseConf.NAMESRV_ADDR, brokerConfig.getBrokerName(), 1); Assert.assertEquals(BrokerRole.SYNC_MASTER, storeConfig.getBrokerRole()); String topic = UUID.randomUUID().toString(); String consumerGroup = UUID.randomUUID().toString(); IntegrationTestBase.initTopic(topic, BaseConf.NAMESRV_ADDR, cluster, 1, CQType.SimpleCQ); DefaultMQProducer producer = ProducerFactory.getRMQProducer(BaseConf.NAMESRV_ADDR); DefaultMQPullConsumer consumer = ConsumerFactory.getRMQPullConsumer(BaseConf.NAMESRV_ADDR, consumerGroup); for (int i = 0; i < 10; i++) { Message message = new Message(); message.setTopic(topic); message.setBody(("Hello" + i).getBytes()); SendResult sendResult = producer.send(message); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); Assert.assertEquals(0, sendResult.getMessageQueue().getQueueId()); Assert.assertEquals(brokerName, sendResult.getMessageQueue().getBrokerName()); Assert.assertEquals(i, sendResult.getQueueOffset()); Assert.assertNotNull(sendResult.getMsgId()); Assert.assertNotNull(sendResult.getOffsetMsgId()); } Thread.sleep(500); Assert.assertEquals(0, brokerController.getMessageStore().getMinOffsetInQueue(topic, 0)); Assert.assertEquals(10, brokerController.getMessageStore().getMaxOffsetInQueue(topic, 0)); MessageQueue messageQueue = new MessageQueue(topic, brokerName, 0); PullResult pullResult = consumer.pull(messageQueue, "*", 0, 32); Assert.assertEquals(PullStatus.FOUND, pullResult.getPullStatus()); Assert.assertEquals(10, pullResult.getMsgFoundList().size()); for (int i = 0; i < 10; i++) { MessageExt messageExt = pullResult.getMsgFoundList().get(i); Assert.assertEquals(i, messageExt.getQueueOffset()); Assert.assertArrayEquals(("Hello" + i).getBytes(), messageExt.getBody()); } producer.shutdown(); consumer.shutdown(); brokerController.shutdown(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/grpc/v2/ClusterGrpcIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.grpc.v2; import apache.rocketmq.v2.QueryRouteResponse; import java.time.Duration; import java.util.Map; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingApplication; import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.test.util.MQAdminTestUtils; import org.junit.After; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.Ignore; import org.junit.runners.MethodSorters; import static org.awaitility.Awaitility.await; @FixMethodOrder(value = MethodSorters.NAME_ASCENDING) public class ClusterGrpcIT extends GrpcBaseIT { private MessagingProcessor messagingProcessor; private GrpcMessagingApplication grpcMessagingApplication; @Before public void setUp() throws Exception { super.setUp(); ConfigurationManager.getProxyConfig().setTransactionHeartbeatPeriodSecond(3); messagingProcessor = DefaultMessagingProcessor.createForClusterMode(); messagingProcessor.start(); grpcMessagingApplication = GrpcMessagingApplication.create(messagingProcessor); grpcMessagingApplication.start(); setUpServer(grpcMessagingApplication, 0, true); await().atMost(Duration.ofSeconds(40)).until(() -> { Map brokerDataMap = MQAdminTestUtils.getCluster(NAMESRV_ADDR).getBrokerAddrTable(); return brokerDataMap.size() == BROKER_NUM; }); } @After public void tearDown() throws Exception { messagingProcessor.shutdown(); grpcMessagingApplication.shutdown(); shutdown(); } @Test public void testQueryRoute() throws Exception { String topic = initTopic(); QueryRouteResponse response = blockingStub.queryRoute(buildQueryRouteRequest(topic)); assertQueryRoute(response, BROKER_NUM * DEFAULT_QUEUE_NUMS); } @Test public void testQueryAssignment() throws Exception { super.testQueryAssignment(); } @Test public void testQueryFifoAssignment() throws Exception { super.testQueryFifoAssignment(); } @Test @Ignore public void testTransactionCheckThenCommit() { super.testTransactionCheckThenCommit(); } @Test @Ignore public void testSimpleConsumerSendAndRecvDelayMessage() throws Exception { super.testSimpleConsumerSendAndRecvDelayMessage(); } @Test public void testSimpleConsumerSendAndRecallDelayMessage() throws Exception { super.testSimpleConsumerSendAndRecallDelayMessage(); } @Test public void testSimpleConsumerSendAndRecvBigMessage() throws Exception { super.testSimpleConsumerSendAndRecvBigMessage(); } @Test public void testSimpleConsumerSendAndRecv() throws Exception { super.testSimpleConsumerSendAndRecv(); } @Test public void testSimpleConsumerToDLQ() throws Exception { super.testSimpleConsumerToDLQ(); } @Test public void testConsumeOrderly() throws Exception { super.testConsumeOrderly(); } @Test public void testSimpleConsumerSendAndRecvPriorityMessage() throws Exception { super.testSimpleConsumerSendAndRecvPriorityMessage(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.grpc.v2; import apache.rocketmq.v2.AckMessageEntry; import apache.rocketmq.v2.AckMessageRequest; import apache.rocketmq.v2.AckMessageResponse; import apache.rocketmq.v2.AckMessageResultEntry; import apache.rocketmq.v2.Address; import apache.rocketmq.v2.AddressScheme; import apache.rocketmq.v2.ChangeInvisibleDurationRequest; import apache.rocketmq.v2.ChangeInvisibleDurationResponse; import apache.rocketmq.v2.ClientType; import apache.rocketmq.v2.Code; import apache.rocketmq.v2.Encoding; import apache.rocketmq.v2.EndTransactionRequest; import apache.rocketmq.v2.EndTransactionResponse; import apache.rocketmq.v2.Endpoints; import apache.rocketmq.v2.HeartbeatRequest; import apache.rocketmq.v2.Message; import apache.rocketmq.v2.MessageQueue; import apache.rocketmq.v2.MessageType; import apache.rocketmq.v2.MessagingServiceGrpc; import apache.rocketmq.v2.Publishing; import apache.rocketmq.v2.QueryAssignmentRequest; import apache.rocketmq.v2.QueryAssignmentResponse; import apache.rocketmq.v2.QueryRouteRequest; import apache.rocketmq.v2.QueryRouteResponse; import apache.rocketmq.v2.RecallMessageRequest; import apache.rocketmq.v2.RecallMessageResponse; import apache.rocketmq.v2.ReceiveMessageRequest; import apache.rocketmq.v2.ReceiveMessageResponse; import apache.rocketmq.v2.RecoverOrphanedTransactionCommand; import apache.rocketmq.v2.Resource; import apache.rocketmq.v2.RetryPolicy; import apache.rocketmq.v2.SendMessageRequest; import apache.rocketmq.v2.SendMessageResponse; import apache.rocketmq.v2.Settings; import apache.rocketmq.v2.Subscription; import apache.rocketmq.v2.SystemProperties; import apache.rocketmq.v2.TelemetryCommand; import apache.rocketmq.v2.TransactionResolution; import apache.rocketmq.v2.TransactionSource; import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; import io.grpc.Channel; import io.grpc.Metadata; import io.grpc.Server; import io.grpc.ServerInterceptors; import io.grpc.ServerServiceDefinition; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolConfig; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; import io.grpc.stub.MetadataUtils; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import java.io.IOException; import java.net.URL; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import javax.net.ssl.SSLException; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.interceptor.ContextInterceptor; import org.apache.rocketmq.proxy.grpc.interceptor.HeaderInterceptor; import org.apache.rocketmq.common.constant.GrpcConstants; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.util.MQRandomUtils; import org.apache.rocketmq.test.util.RandomUtils; import org.junit.Rule; import static org.apache.rocketmq.common.message.MessageClientIDSetter.createUniqID; import static org.apache.rocketmq.proxy.config.ConfigurationManager.RMQ_PROXY_HOME; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; public class GrpcBaseIT extends BaseConf { /** * Let OS pick up an available port. */ private int port = 0; /** * This rule manages automatic graceful shutdown for the registered servers and channels at the end of test. */ @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); protected MessagingServiceGrpc.MessagingServiceBlockingStub blockingStub; protected MessagingServiceGrpc.MessagingServiceStub stub; protected final Metadata header = new Metadata(); protected static final int DEFAULT_QUEUE_NUMS = 8; public void setUp() throws Exception { brokerController1.getBrokerConfig().setTransactionCheckInterval(1 * 1000); brokerController2.getBrokerConfig().setTransactionCheckInterval(1 * 1000); brokerController3.getBrokerConfig().setTransactionCheckInterval(1 * 1000); header.put(GrpcConstants.CLIENT_ID, "client-id" + UUID.randomUUID()); header.put(GrpcConstants.LANGUAGE, "JAVA"); String mockProxyHome = "/mock/rmq/proxy/home"; URL mockProxyHomeURL = getClass().getClassLoader().getResource("rmq-proxy-home"); if (mockProxyHomeURL != null) { mockProxyHome = mockProxyHomeURL.toURI().getPath(); } if (null != mockProxyHome) { System.setProperty(RMQ_PROXY_HOME, mockProxyHome); } ConfigurationManager.initEnv(); ConfigurationManager.initConfig(); ConfigurationManager.getProxyConfig().setNamesrvAddr(NAMESRV_ADDR); // Set LongPollingReserveTimeInMillis to 500ms to reserve more time for IT ConfigurationManager.getProxyConfig().setLongPollingReserveTimeInMillis(500); ConfigurationManager.getProxyConfig().setRocketMQClusterName(brokerController1.getBrokerConfig().getBrokerClusterName()); ConfigurationManager.getProxyConfig().setHeartbeatSyncerTopicClusterName(brokerController1.getBrokerConfig().getBrokerClusterName()); ConfigurationManager.getProxyConfig().setMinInvisibleTimeMillsForRecv(3); ConfigurationManager.getProxyConfig().setGrpcClientConsumerMinLongPollingTimeoutMillis(0); } protected MessagingServiceGrpc.MessagingServiceStub createStub(Channel channel) { MessagingServiceGrpc.MessagingServiceStub stub = MessagingServiceGrpc.newStub(channel); return stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(header)); } protected MessagingServiceGrpc.MessagingServiceBlockingStub createBlockingStub(Channel channel) { MessagingServiceGrpc.MessagingServiceBlockingStub stub = MessagingServiceGrpc.newBlockingStub(channel); return stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(header)); } protected CompletableFuture sendClientSettings(MessagingServiceGrpc.MessagingServiceStub stub, Settings clientSettings) { CompletableFuture future = new CompletableFuture<>(); StreamObserver requestStreamObserver = stub.telemetry(new DefaultTelemetryCommandStreamObserver() { @Override public void onNext(TelemetryCommand value) { TelemetryCommand.CommandCase commandCase = value.getCommandCase(); if (TelemetryCommand.CommandCase.SETTINGS.equals(commandCase)) { future.complete(value.getSettings()); } } }); requestStreamObserver.onNext(TelemetryCommand.newBuilder() .setSettings(clientSettings) .build()); future.whenComplete((settings, throwable) -> requestStreamObserver.onCompleted()); return future; } protected void setUpServer(MessagingServiceGrpc.MessagingServiceImplBase serverImpl, int port, boolean enableInterceptor) throws IOException, CertificateException { SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); ServerServiceDefinition serviceDefinition = ServerInterceptors.intercept(serverImpl); if (enableInterceptor) { serviceDefinition = ServerInterceptors.intercept(serverImpl, new ContextInterceptor(), new HeaderInterceptor()); } Server server = NettyServerBuilder.forPort(port) .directExecutor() .addService(serviceDefinition) .useTransportSecurity(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) .build() .start(); this.port = server.getPort(); // Create a server, add service, start, and register for automatic graceful shutdown. grpcCleanup.register(server); ConfigurationManager.getProxyConfig().setGrpcServerPort(this.port); blockingStub = createBlockingStub(createChannel(ConfigurationManager.getProxyConfig().getGrpcServerPort())); stub = createStub(createChannel(ConfigurationManager.getProxyConfig().getGrpcServerPort())); } protected Channel createChannel(int port) throws SSLException { return grpcCleanup.register(NettyChannelBuilder.forAddress("127.0.0.1", port) .directExecutor() .sslContext(SslContextBuilder .forClient() .sslProvider(SslProvider.OPENSSL) .trustManager(InsecureTrustManagerFactory.INSTANCE) .applicationProtocolConfig(new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2)) .build() ) .build()); } public void testQueryAssignment() throws Exception { String topic = initTopic(); String group = "group"; QueryAssignmentResponse response = blockingStub.queryAssignment(buildQueryAssignmentRequest(topic, group)); assertQueryAssignment(response, BROKER_NUM); } public void testQueryFifoAssignment() throws Exception { String topic = initTopic(TopicMessageType.FIFO); String group = MQRandomUtils.getRandomConsumerGroup(); SubscriptionGroupConfig groupConfig = brokerController1.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); groupConfig.setConsumeMessageOrderly(true); brokerController1.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); brokerController2.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); brokerController3.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); QueryAssignmentResponse response = blockingStub.queryAssignment(buildQueryAssignmentRequest(topic, group)); assertQueryAssignment(response, BROKER_NUM * QUEUE_NUMBERS); } public void testTransactionCheckThenCommit() { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME, TopicMessageType.TRANSACTION); String group = MQRandomUtils.getRandomConsumerGroup(); initConsumerGroup(group); AtomicReference telemetryCommandRef = new AtomicReference<>(null); StreamObserver requestStreamObserver = stub.telemetry(new DefaultTelemetryCommandStreamObserver() { @Override public void onNext(TelemetryCommand value) { telemetryCommandRef.set(value); } }); try { requestStreamObserver.onNext(TelemetryCommand.newBuilder() .setSettings(buildPushConsumerClientSettings(group)) .build()); await().atMost(java.time.Duration.ofSeconds(3)).until(() -> { if (telemetryCommandRef.get() == null) { return false; } if (telemetryCommandRef.get().getCommandCase() != TelemetryCommand.CommandCase.SETTINGS) { return false; } return telemetryCommandRef.get() != null; }); telemetryCommandRef.set(null); // init consumer offset receiveMessage(blockingStub, topic, group, 1); requestStreamObserver.onNext(TelemetryCommand.newBuilder() .setSettings(buildProducerClientSettings(topic)) .build()); blockingStub.heartbeat(buildHeartbeatRequest(group)); await().atMost(java.time.Duration.ofSeconds(3)).until(() -> { if (telemetryCommandRef.get() == null) { blockingStub.heartbeat(buildHeartbeatRequest(group)); return false; } if (telemetryCommandRef.get().getCommandCase() != TelemetryCommand.CommandCase.SETTINGS) { blockingStub.heartbeat(buildHeartbeatRequest(group)); return false; } return telemetryCommandRef.get() != null; }); telemetryCommandRef.set(null); String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(buildTransactionSendMessageRequest(topic, messageId)); assertSendMessage(sendResponse, messageId); await().atMost(java.time.Duration.ofMinutes(2)).until(() -> { if (telemetryCommandRef.get() == null) { blockingStub.heartbeat(buildHeartbeatRequest(group)); return false; } if (telemetryCommandRef.get().getCommandCase() != TelemetryCommand.CommandCase.RECOVER_ORPHANED_TRANSACTION_COMMAND) { blockingStub.heartbeat(buildHeartbeatRequest(group)); return false; } return telemetryCommandRef.get() != null; }); RecoverOrphanedTransactionCommand recoverOrphanedTransactionCommand = telemetryCommandRef.get().getRecoverOrphanedTransactionCommand(); assertRecoverOrphanedTransactionCommand(recoverOrphanedTransactionCommand, messageId); EndTransactionResponse endTransactionResponse = blockingStub.endTransaction( buildEndTransactionRequest(topic, messageId, recoverOrphanedTransactionCommand.getTransactionId(), TransactionResolution.COMMIT)); assertEndTransactionResponse(endTransactionResponse); requestStreamObserver.onNext(TelemetryCommand.newBuilder() .setSettings(buildPushConsumerClientSettings(group)) .build()); await().atMost(java.time.Duration.ofSeconds(30)).until(() -> { List retryMessageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group)); if (retryMessageList.isEmpty()) { return false; } return retryMessageList.get(0).getSystemProperties() .getMessageId().equals(messageId); }); } finally { requestStreamObserver.onCompleted(); } } public HeartbeatRequest buildHeartbeatRequest(String group) { return HeartbeatRequest.newBuilder() .setGroup(Resource.newBuilder() .setName(group) .build()) .build(); } public void testSimpleConsumerSendAndRecvDelayMessage() throws Exception { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME, TopicMessageType.DELAY); String group = MQRandomUtils.getRandomConsumerGroup(); long delayTime = TimeUnit.SECONDS.toMillis(5); initConsumerGroup(group); // init consumer offset this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBodyEncoding(Encoding.GZIP) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .setDeliveryTimestamp(Timestamps.fromMillis(System.currentTimeMillis() + delayTime)) .build()) .setBody(ByteString.copyFromUtf8("hello")) .build()) .build()); long sendTime = System.currentTimeMillis(); assertSendMessage(sendResponse, messageId); this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); AtomicLong recvTime = new AtomicLong(); AtomicReference recvMessage = new AtomicReference<>(); await().atMost(java.time.Duration.ofSeconds(10)).until(() -> { List messageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group)); if (messageList.isEmpty()) { return false; } recvTime.set(System.currentTimeMillis()); recvMessage.set(messageList.get(0)); return messageList.get(0).getSystemProperties().getMessageId().equals(messageId); }); assertThat(Math.abs(recvTime.get() - sendTime - delayTime) < 2 * 1000).isTrue(); } public void testSimpleConsumerSendAndRecallDelayMessage() throws Exception { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME, TopicMessageType.DELAY); String group = MQRandomUtils.getRandomConsumerGroup(); initConsumerGroup(group); long delayTime = TimeUnit.SECONDS.toMillis(5); // init consumer offset this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.DELAY) .setBodyEncoding(Encoding.GZIP) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .setDeliveryTimestamp(Timestamps.fromMillis(System.currentTimeMillis() + delayTime)) .build()) .setBody(ByteString.copyFromUtf8("hello")) .build()) .build()); long sendTime = System.currentTimeMillis(); assertSendMessage(sendResponse, messageId); String recallHandle = sendResponse.getEntries(0).getRecallHandle(); assertThat(recallHandle).isNotEmpty(); RecallMessageRequest recallRequest = RecallMessageRequest.newBuilder() .setRecallHandle(recallHandle) .setTopic(Resource.newBuilder().setResourceNamespace("").setName(topic).build()) .build(); RecallMessageResponse recallResponse = blockingStub.withDeadlineAfter(2, TimeUnit.SECONDS).recallMessage(recallRequest); assertThat(recallResponse.getStatus()).isEqualTo( ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())); assertThat(recallResponse.getMessageId()).isEqualTo(messageId); this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); AtomicLong recvTime = new AtomicLong(); AtomicReference recvMessage = new AtomicReference<>(); try { await().atMost(java.time.Duration.ofSeconds(10)).until(() -> { List messageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group)); if (messageList.isEmpty()) { return false; } recvTime.set(System.currentTimeMillis()); recvMessage.set(messageList.get(0)); return messageList.get(0).getSystemProperties().getMessageId().equals(messageId); }); } catch (Exception e) { } assertThat(recvTime.get()).isEqualTo(0L); assertThat(recvMessage.get()).isNull(); } public void testSimpleConsumerSendAndRecvBigMessage() throws Exception { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME); String group = MQRandomUtils.getRandomConsumerGroup(); initConsumerGroup(group); int bodySize = 4 * 1024; // init consumer offset this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(buildSendBigMessageRequest(topic, messageId, bodySize)); assertSendMessage(sendResponse, messageId); this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); Message message = assertAndGetReceiveMessage(receiveMessage(blockingStub, topic, group), messageId); assertThat(message.getSystemProperties().getBodyEncoding()).isEqualTo(Encoding.GZIP); assertThat(message.getBody().size()).isEqualTo(bodySize); } public void testSimpleConsumerSendAndRecv() throws Exception { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME); String group = MQRandomUtils.getRandomConsumerGroup(); initConsumerGroup(group); // init consumer offset this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(buildSendMessageRequest(topic, messageId)); assertSendMessage(sendResponse, messageId); assertThat(sendResponse.getEntries(0).getRecallHandle()).isNullOrEmpty(); this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); Message message = assertAndGetReceiveMessage(receiveMessage(blockingStub, topic, group), messageId); String receiptHandle = message.getSystemProperties().getReceiptHandle(); ChangeInvisibleDurationResponse changeResponse = blockingStub.changeInvisibleDuration(buildChangeInvisibleDurationRequest(topic, group, receiptHandle, 5)); assertChangeInvisibleDurationResponse(changeResponse, receiptHandle); List ackHandles = new ArrayList<>(); ackHandles.add(changeResponse.getReceiptHandle()); await().atMost(java.time.Duration.ofSeconds(20)).until(() -> { List retryMessageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group)); if (retryMessageList.isEmpty()) { return false; } if (retryMessageList.get(0).getSystemProperties() .getMessageId().equals(messageId)) { ackHandles.add(retryMessageList.get(0).getSystemProperties().getReceiptHandle()); return true; } return false; }); assertThat(ackHandles.size()).isEqualTo(2); AckMessageResponse ackMessageResponse = blockingStub.ackMessage(buildAckMessageRequest(topic, group, AckMessageEntry.newBuilder().setMessageId(messageId).setReceiptHandle(ackHandles.get(0)).build(), AckMessageEntry.newBuilder().setMessageId(messageId).setReceiptHandle(ackHandles.get(1)).build())); assertThat(ackMessageResponse.getStatus().getCode()).isEqualTo(Code.MULTIPLE_RESULTS); int okNum = 0; int expireNum = 0; for (AckMessageResultEntry entry : ackMessageResponse.getEntriesList()) { if (entry.getStatus().getCode().equals(Code.OK)) { okNum++; } else if (entry.getStatus().getCode().equals(Code.INVALID_RECEIPT_HANDLE)) { expireNum++; } } assertThat(okNum).isEqualTo(1); assertThat(expireNum).isEqualTo(1); } public void testSimpleConsumerToDLQ() throws Exception { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME); String group = MQRandomUtils.getRandomConsumerGroup(); initConsumerGroup(group); int maxDeliveryAttempts = 2; SubscriptionGroupConfig groupConfig = brokerController1.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); groupConfig.setRetryMaxTimes(maxDeliveryAttempts - 1); brokerController1.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); brokerController2.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); brokerController3.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); // init consumer offset this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(buildSendMessageRequest(topic, messageId)); assertSendMessage(sendResponse, messageId); this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); AtomicInteger receiveMessageCount = new AtomicInteger(0); assertAndGetReceiveMessage(receiveMessage(blockingStub, topic, group), messageId); receiveMessageCount.incrementAndGet(); DefaultMQPullConsumer defaultMQPullConsumer = new DefaultMQPullConsumer(group); defaultMQPullConsumer.start(); org.apache.rocketmq.common.message.MessageQueue dlqMQ = new org.apache.rocketmq.common.message.MessageQueue(MixAll.getDLQTopic(group), BROKER1_NAME, 0); await().atMost(java.time.Duration.ofSeconds(30)).until(() -> { try { List messageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group, 1)); receiveMessageCount.addAndGet(messageList.size()); PullResult pullResult = defaultMQPullConsumer.pull(dlqMQ, "*", 0L, 1); if (!PullStatus.FOUND.equals(pullResult.getPullStatus())) { return false; } MessageExt messageExt = pullResult.getMsgFoundList().get(0); return messageId.equals(messageExt.getMsgId()); } catch (Throwable ignore) { return false; } }); assertThat(receiveMessageCount.get()).isEqualTo(maxDeliveryAttempts); } public void testConsumeOrderly() throws Exception { String topic = initTopicOnSampleTopicBroker(BROKER1_NAME, TopicMessageType.FIFO); String group = MQRandomUtils.getRandomConsumerGroup(); SubscriptionGroupConfig groupConfig = brokerController1.getSubscriptionGroupManager().findSubscriptionGroupConfig(group); groupConfig.setConsumeMessageOrderly(true); brokerController1.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); brokerController2.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); brokerController3.getSubscriptionGroupManager().updateSubscriptionGroupConfig(groupConfig); this.sendClientSettings(stub, buildPushConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); String messageGroup = "group"; this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); List messageIdList = new ArrayList<>(); for (int i = 0; i < 3; i++) { String messageId = createUniqID(); messageIdList.add(messageId); SendMessageResponse sendResponse = blockingStub.sendMessage(buildSendOrderMessageRequest(topic, messageId, messageGroup)); assertSendMessage(sendResponse, messageId); } List messageRecvList = new ArrayList<>(); this.sendClientSettings(stub, buildPushConsumerClientSettings(group)).get(); await().atMost(java.time.Duration.ofSeconds(20)).until(() -> { List retryMessageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group)); if (retryMessageList.isEmpty()) { return false; } for (Message message : retryMessageList) { String messageId = message.getSystemProperties().getMessageId(); messageRecvList.add(messageId); blockingStub.ackMessage(buildAckMessageRequest(topic, group, AckMessageEntry.newBuilder().setMessageId(messageId).setReceiptHandle(message.getSystemProperties().getReceiptHandle()).build())); } return messageRecvList.size() == messageIdList.size(); }); for (int i = 0; i < messageIdList.size(); i++) { assertThat(messageRecvList.get(i)).isEqualTo(messageIdList.get(i)); } } public void testSimpleConsumerSendAndRecvPriorityMessage() throws Exception { brokerController1.getBrokerConfig().setPriorityOrderAsc(true); String topic = initTopicOnSampleTopicBroker(BROKER1_NAME, TopicMessageType.PRIORITY); String group = MQRandomUtils.getRandomConsumerGroup(); initConsumerGroup(group); // init consumer offset this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); receiveMessage(blockingStub, topic, group, 1); this.sendClientSettings(stub, buildProducerClientSettings(topic)).get(); for (int i = 0; i < BaseConf.QUEUE_NUMBERS; i++) { String messageId = createUniqID(); SendMessageResponse sendResponse = blockingStub.sendMessage(SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.PRIORITY) .setBodyEncoding(Encoding.GZIP) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .setPriority(i) .build()) .setBody(ByteString.copyFromUtf8("hello")) .build()) .build()); assertSendMessage(sendResponse, messageId); } this.sendClientSettings(stub, buildSimpleConsumerClientSettings(group)).get(); List recvList = new ArrayList<>(); try { await().atMost(java.time.Duration.ofSeconds(10)).until(() -> { List messageList = getMessageFromReceiveMessageResponse(receiveMessage(blockingStub, topic, group)); if (messageList.isEmpty()) { return false; } recvList.addAll(messageList); return recvList.size() == BaseConf.QUEUE_NUMBERS; }); } catch (Exception e) { } for (int i = 0; i < BaseConf.QUEUE_NUMBERS; i++) { // default priority order: 0 as lowest priority assertThat(recvList.get(i).getSystemProperties().getPriority()).isEqualTo(BaseConf.QUEUE_NUMBERS - i - 1); } } public List receiveMessage(MessagingServiceGrpc.MessagingServiceBlockingStub stub, String topic, String group) { return receiveMessage(stub, topic, group, 15); } public List receiveMessage(MessagingServiceGrpc.MessagingServiceBlockingStub stub, String topic, String group, int timeSeconds) { List responseList = new ArrayList<>(); Iterator responseIterator = stub.withDeadlineAfter(timeSeconds, TimeUnit.SECONDS) .receiveMessage(buildReceiveMessageRequest(topic, group)); while (responseIterator.hasNext()) { responseList.add(responseIterator.next()); } return responseList; } public List getMessageFromReceiveMessageResponse(List responseList) { List messageList = new ArrayList<>(); for (ReceiveMessageResponse response : responseList) { if (response.hasMessage()) { messageList.add(response.getMessage()); } } return messageList; } public QueryRouteRequest buildQueryRouteRequest(String topic) { return QueryRouteRequest.newBuilder() .setEndpoints(buildEndpoints(port)) .setTopic(Resource.newBuilder() .setName(topic) .build()) .build(); } public QueryAssignmentRequest buildQueryAssignmentRequest(String topic, String group) { return QueryAssignmentRequest.newBuilder() .setEndpoints(buildEndpoints(port)) .setTopic(Resource.newBuilder().setName(topic).build()) .setGroup(Resource.newBuilder().setName(group).build()) .build(); } public SendMessageRequest buildSendMessageRequest(String topic, String messageId) { return SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build()) .build(); } public SendMessageRequest buildSendOrderMessageRequest(String topic, String messageId, String messageGroup) { return SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.FIFO) .setMessageGroup(messageGroup) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build()) .build(); } public SendMessageRequest buildSendBigMessageRequest(String topic, String messageId, int messageSize) { return SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.NORMAL) .setBodyEncoding(Encoding.GZIP) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8(RandomUtils.getStringWithCharacter(messageSize))) .build()) .build(); } public SendMessageRequest buildTransactionSendMessageRequest(String topic, String messageId) { return SendMessageRequest.newBuilder() .addMessages(Message.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(messageId) .setQueueId(0) .setMessageType(MessageType.TRANSACTION) .setOrphanedTransactionRecoveryDuration(Duration.newBuilder().setSeconds(10)) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(NetworkUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build()) .build(); } public ReceiveMessageRequest buildReceiveMessageRequest(String topic, String group) { return ReceiveMessageRequest.newBuilder() .setGroup(Resource.newBuilder() .setName(group) .build()) .setMessageQueue(MessageQueue.newBuilder() .setTopic(Resource.newBuilder() .setName(topic) .build()) .setId(-1) .build()) .setBatchSize(1) .setAutoRenew(false) .setInvisibleDuration(Duration.newBuilder() .setSeconds(3) .build()) .build(); } public AckMessageRequest buildAckMessageRequest(String topic, String group, AckMessageEntry... entry) { return AckMessageRequest.newBuilder() .setGroup(Resource.newBuilder() .setName(group) .build()) .setTopic(Resource.newBuilder() .setName(topic) .build()) .addAllEntries(Arrays.stream(entry).collect(Collectors.toList())) .build(); } public EndTransactionRequest buildEndTransactionRequest(String topic, String messageId, String transactionId, TransactionResolution resolution) { return EndTransactionRequest.newBuilder() .setMessageId(messageId) .setTopic(Resource.newBuilder() .setName(topic) .build()) .setTransactionId(transactionId) .setResolution(resolution) .setSource(TransactionSource.SOURCE_SERVER_CHECK) .build(); } public ChangeInvisibleDurationRequest buildChangeInvisibleDurationRequest(String topic, String group, String receiptHandle, int second) { return ChangeInvisibleDurationRequest.newBuilder() .setTopic(Resource.newBuilder().setName(topic).build()) .setGroup(Resource.newBuilder().setName(group).build()) .setInvisibleDuration(Durations.fromSeconds(second)) .setReceiptHandle(receiptHandle) .build(); } public void assertQueryRoute(QueryRouteResponse response, int messageQueueSize) { assertThat(response.getStatus()).isEqualTo(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())); assertThat(response.getMessageQueuesList().size()).isEqualTo(messageQueueSize); assertThat(response.getMessageQueues(0).getBroker().getEndpoints().getAddresses(0).getPort()).isEqualTo(ConfigurationManager.getProxyConfig().getGrpcServerPort()); } public void assertQueryAssignment(QueryAssignmentResponse response, int assignmentCount) { assertThat(response.getStatus()).isEqualTo(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())); assertThat(response.getAssignmentsCount()).isEqualTo(assignmentCount); assertThat(response.getAssignments(0).getMessageQueue().getBroker().getEndpoints().getAddresses(0).getPort()).isEqualTo(ConfigurationManager.getProxyConfig().getGrpcServerPort()); } public void assertSendMessage(SendMessageResponse response, String messageId) { assertThat(response.getStatus()).isEqualTo(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())); assertThat(response.getEntries(0).getMessageId()).isEqualTo(messageId); } public Message assertAndGetReceiveMessage(List response, String messageId) { assertThat(response.get(0).hasStatus()).isTrue(); assertThat(response.get(0).getStatus() .getCode()).isEqualTo(Code.OK); assertThat(response.get(1).getMessage() .getSystemProperties() .getMessageId()).isEqualTo(messageId); return response.get(1).getMessage(); } public void assertRecoverOrphanedTransactionCommand(RecoverOrphanedTransactionCommand command, String messageId) { assertThat(command.getTransactionId()).isNotBlank(); } public void assertEndTransactionResponse(EndTransactionResponse response) { assertThat(response.getStatus().getCode()).isEqualTo(Code.OK); } public void assertChangeInvisibleDurationResponse(ChangeInvisibleDurationResponse response, String prevHandle) { assertThat(response.getStatus().getCode()).isEqualTo(Code.OK); assertThat(response.getReceiptHandle()).isNotEqualTo(prevHandle); } public Endpoints buildEndpoints(int port) { return Endpoints.newBuilder() .setScheme(AddressScheme.IPv4) .addAddresses(Address.newBuilder() .setHost("127.0.0.1") .setPort(port) .build()) .build(); } public Settings buildSimpleConsumerClientSettings(String group) { return Settings.newBuilder() .setClientType(ClientType.SIMPLE_CONSUMER) .setRequestTimeout(Durations.fromSeconds(3)) .setSubscription(Subscription.newBuilder() .setGroup(Resource.newBuilder().setName(group).build()) .build()) .build(); } public Settings buildPushConsumerClientSettings(String group) { return buildPushConsumerClientSettings(2, group); } public Settings buildPushConsumerClientSettings(int maxDeliveryAttempts, String group) { return Settings.newBuilder() .setClientType(ClientType.PUSH_CONSUMER) .setRequestTimeout(Durations.fromSeconds(3)) .setBackoffPolicy(RetryPolicy.newBuilder() .setMaxAttempts(maxDeliveryAttempts) .build()) .setSubscription(Subscription.newBuilder() .setGroup(Resource.newBuilder().setName(group).build()) .build()) .build(); } public Settings buildProducerClientSettings(String... topics) { List topicResources = Arrays.stream(topics).map(topic -> Resource.newBuilder().setName(topic).build()) .collect(Collectors.toList()); return Settings.newBuilder() .setClientType(ClientType.PRODUCER) .setPublishing(Publishing.newBuilder() .addAllTopics(topicResources) .build()) .build(); } protected static class DefaultTelemetryCommandStreamObserver implements StreamObserver { @Override public void onNext(TelemetryCommand value) { } @Override public void onError(Throwable t) { } @Override public void onCompleted() { } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/grpc/v2/LocalGrpcIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.grpc.v2; import apache.rocketmq.v2.QueryRouteResponse; import org.apache.rocketmq.proxy.config.ConfigurationManager; import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingApplication; import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor; import org.apache.rocketmq.proxy.processor.MessagingProcessor; import org.junit.After; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.Ignore; import org.junit.runners.MethodSorters; @FixMethodOrder(value = MethodSorters.NAME_ASCENDING) public class LocalGrpcIT extends GrpcBaseIT { private MessagingProcessor messagingProcessor; private GrpcMessagingApplication grpcMessagingApplication; @Before public void setUp() throws Exception { super.setUp(); messagingProcessor = DefaultMessagingProcessor.createForLocalMode(brokerController1); messagingProcessor.start(); grpcMessagingApplication = GrpcMessagingApplication.create(messagingProcessor); grpcMessagingApplication.start(); setUpServer(grpcMessagingApplication, ConfigurationManager.getProxyConfig().getGrpcServerPort(), true); } @After public void clean() throws Exception { messagingProcessor.shutdown(); grpcMessagingApplication.shutdown(); shutdown(); } @Test public void testQueryRoute() throws Exception { String topic = initTopic(); QueryRouteResponse response = blockingStub.queryRoute(buildQueryRouteRequest(topic)); assertQueryRoute(response, brokerControllerList.size() * DEFAULT_QUEUE_NUMS); } @Test public void testQueryAssignment() throws Exception { super.testQueryAssignment(); } @Test public void testQueryFifoAssignment() throws Exception { super.testQueryFifoAssignment(); } @Test @Ignore public void testTransactionCheckThenCommit() { super.testTransactionCheckThenCommit(); } @Test @Ignore public void testSimpleConsumerSendAndRecvDelayMessage() throws Exception { super.testSimpleConsumerSendAndRecvDelayMessage(); } @Test public void testSimpleConsumerSendAndRecallDelayMessage() throws Exception { super.testSimpleConsumerSendAndRecallDelayMessage(); } @Test public void testSimpleConsumerSendAndRecvBigMessage() throws Exception { super.testSimpleConsumerSendAndRecvBigMessage(); } @Test public void testSimpleConsumerSendAndRecv() throws Exception { super.testSimpleConsumerSendAndRecv(); } @Test public void testSimpleConsumerToDLQ() throws Exception { super.testSimpleConsumerToDLQ(); } @Test public void testConsumeOrderly() throws Exception { super.testConsumeOrderly(); } @Test public void testSimpleConsumerSendAndRecvPriorityMessage() throws Exception { super.testSimpleConsumerSendAndRecvPriorityMessage(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/lmq/TestBenchLmqStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.lmq; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullCallback; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.MQClientAPIImpl; import org.apache.rocketmq.client.impl.consumer.DefaultMQPullConsumerImpl; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.test.lmq.benchmark.BenchLmqStore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestBenchLmqStore { @Test public void test() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { System.setProperty("sendThreadNum", "1"); System.setProperty("pullConsumerNum", "1"); System.setProperty("consumerThreadNum", "1"); BenchLmqStore.defaultMQProducer = mock(DefaultMQProducer.class); // SendResult sendResult = new SendResult(); // when(BenchLmqStore.defaultMQProducer.send(any(Message.class))).thenReturn(sendResult); BenchLmqStore.doSend(); Thread.sleep(100L); //verify(BenchLmqStore.defaultMQProducer, atLeastOnce()).send(any(Message.class)); BenchLmqStore.defaultMQPullConsumers = new DefaultMQPullConsumer[1]; BenchLmqStore.defaultMQPullConsumers[0] = mock(DefaultMQPullConsumer.class); BenchLmqStore.doPull(new ConcurrentHashMap<>(), new MessageQueue(), 1L); verify(BenchLmqStore.defaultMQPullConsumers[0], atLeastOnce()).pullBlockIfNotFound(any(MessageQueue.class), anyString(), anyLong(), anyInt(), any( PullCallback.class)); } @Test public void testOffset() throws RemotingException, InterruptedException, MQClientException, MQBrokerException, IllegalAccessException { System.setProperty("sendThreadNum", "1"); DefaultMQPullConsumer defaultMQPullConsumer = mock(DefaultMQPullConsumer.class); BenchLmqStore.defaultMQPullConsumers = new DefaultMQPullConsumer[1]; BenchLmqStore.defaultMQPullConsumers[0] = defaultMQPullConsumer; DefaultMQPullConsumerImpl defaultMQPullConsumerImpl = mock(DefaultMQPullConsumerImpl.class); when(defaultMQPullConsumer.getDefaultMQPullConsumerImpl()).thenReturn(defaultMQPullConsumerImpl); RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); when(defaultMQPullConsumerImpl.getRebalanceImpl()).thenReturn(rebalanceImpl); MQClientInstance mqClientInstance = mock(MQClientInstance.class); when(rebalanceImpl.getmQClientFactory()).thenReturn(mqClientInstance); MQClientAPIImpl mqClientAPI = mock(MQClientAPIImpl.class); when(mqClientInstance.getMQClientAPIImpl()).thenReturn(mqClientAPI); TopicRouteData topicRouteData = new TopicRouteData(); HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, "test"); List brokerData = Collections.singletonList(new BrokerData("test", "test", brokerAddrs)); topicRouteData.setBrokerDatas(brokerData); FieldUtils.writeStaticField(BenchLmqStore.class, "lmqTopic", "test", true); when(mqClientAPI.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(topicRouteData); BenchLmqStore.doBenchOffset(); Thread.sleep(100L); verify(mqClientAPI, atLeastOnce()).queryConsumerOffset(anyString(), any(QueryConsumerOffsetRequestHeader.class), anyLong()); verify(mqClientAPI, atLeastOnce()).updateConsumerOffset(anyString(), any(UpdateConsumerOffsetRequestHeader.class), anyLong()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/offset/LagCalculationIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.offset; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper; import org.apache.rocketmq.remoting.protocol.filter.FilterAPI; import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.DefaultMessageFilter; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQSqlConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQBlockListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.test.util.MQAdminTestUtils; import org.junit.After; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class LagCalculationIT extends BaseConf { private static final Logger LOGGER = LoggerFactory.getLogger(LagCalculationIT.class); private RMQNormalProducer producer = null; private RMQNormalConsumer consumer = null; private String topic = null; private RMQBlockListener blockListener = null; @Before public void setUp() { topic = initTopic(); LOGGER.info(String.format("use topic: %s;", topic)); for (BrokerController controller : brokerControllerList) { controller.getBrokerConfig().setLongPollingEnable(false); controller.getBrokerConfig().setShortPollingTimeMills(500); controller.getBrokerConfig().setEstimateAccumulation(true); } producer = getProducer(NAMESRV_ADDR, topic); blockListener = new RMQBlockListener(false); consumer = getConsumer(NAMESRV_ADDR, topic, "*", blockListener); } @After public void tearDown() { shutdown(); } private Pair getLag(List mqs) throws ConsumeQueueException { long lag = 0; long pullLag = 0; for (BrokerController controller : brokerControllerList) { ConsumeStats consumeStats = MQAdminTestUtils.examineConsumeStats(controller.getBrokerAddr(), topic, consumer.getConsumerGroup()); Map offsetTable = consumeStats.getOffsetTable(); for (MessageQueue mq : mqs) { if (mq.getBrokerName().equals(controller.getBrokerConfig().getBrokerName())) { long brokerOffset = controller.getMessageStore().getMaxOffsetInQueue(topic, mq.getQueueId()); long consumerOffset = controller.getConsumerOffsetManager().queryOffset(consumer.getConsumerGroup(), topic, mq.getQueueId()); long pullOffset = controller.getConsumerOffsetManager().queryPullOffset(consumer.getConsumerGroup(), topic, mq.getQueueId()); OffsetWrapper offsetWrapper = offsetTable.get(mq); assertEquals(brokerOffset, offsetWrapper.getBrokerOffset()); if (offsetWrapper.getConsumerOffset() != consumerOffset || offsetWrapper.getPullOffset() != pullOffset) { return new Pair<>(-1L, -1L); } lag += brokerOffset - consumerOffset; pullLag += brokerOffset - pullOffset; } } } return new Pair<>(lag, pullLag); } public void waitForFullyDispatched() { await().atMost(5, TimeUnit.SECONDS).until(() -> { for (BrokerController controller : brokerControllerList) { if (controller.getMessageStore().dispatchBehindBytes() != 0) { return false; } } return true; }); } @Test public void testCalculateLag() throws ConsumeQueueException { int msgSize = 10; List mqs = producer.getMessageQueue(); MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgSize); producer.send(mqMsgs.getMsgsWithMQ()); waitForFullyDispatched(); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer.getConsumer().getDefaultMQPushConsumerImpl().persistConsumerOffset(); // wait for consume all msgs await().atMost(5, TimeUnit.SECONDS).until(() -> { Pair lag = getLag(mqs); return lag.getObject1() == 0 && lag.getObject2() == 0; }); blockListener.setBlock(true); consumer.clearMsg(); producer.clearMsg(); producer.send(mqMsgs.getMsgsWithMQ()); waitForFullyDispatched(); // wait for pull all msgs await().atMost(5, TimeUnit.SECONDS).until(() -> { Pair lag = getLag(mqs); return lag.getObject1() == producer.getAllMsgBody().size() && lag.getObject2() == 0; }); blockListener.setBlock(false); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); consumer.shutdown(); producer.clearMsg(); producer.send(mqMsgs.getMsgsWithMQ()); waitForFullyDispatched(); Pair lag = getLag(mqs); assertEquals(producer.getAllMsgBody().size(), (long) lag.getObject1()); assertEquals(producer.getAllMsgBody().size(), (long) lag.getObject2()); } @Test public void testEstimateLag() throws Exception { int msgNoTagSize = 80; int msgWithTagSize = 20; int repeat = 2; String tag = "TAG_FOR_TEST_ESTIMATE"; String sql = "TAGS = 'TAG_FOR_TEST_ESTIMATE' And value < " + repeat / 2; MessageSelector selector = MessageSelector.bySql(sql); RMQBlockListener sqlListener = new RMQBlockListener(true); RMQSqlConsumer sqlConsumer = ConsumerFactory.getRMQSqlConsumer(NAMESRV_ADDR, initConsumerGroup(), topic, selector, sqlListener); RMQBlockListener tagListener = new RMQBlockListener(true); RMQNormalConsumer tagConsumer = getConsumer(NAMESRV_ADDR, topic, tag, tagListener); //init subscriptionData & consumerFilterData for sql SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, sql, ExpressionType.SQL92); for (BrokerController controller : brokerControllerList) { controller.getConsumerFilterManager().register(topic, sqlConsumer.getConsumerGroup(), sql, ExpressionType.SQL92, subscriptionData.getSubVersion()); } // wait for building filter data await().atMost(5, TimeUnit.SECONDS).until(() -> sqlListener.isBlocked() && tagListener.isBlocked()); List mqs = producer.getMessageQueue(); for (int i = 0; i < repeat; i++) { MessageQueueMsg mqMsgs = new MessageQueueMsg(mqs, msgNoTagSize); Map> msgMap = mqMsgs.getMsgsWithMQ(); mqMsgs = new MessageQueueMsg(mqs, msgWithTagSize, tag); Map> msgWithTagMap = mqMsgs.getMsgsWithMQ(); int finalI = i; msgMap.forEach((mq, msgList) -> { List msgWithTagList = msgWithTagMap.get(mq); for (Object o : msgWithTagList) { ((Message) o).putUserProperty("value", String.valueOf(finalI)); } msgList.addAll(msgWithTagList); Collections.shuffle(msgList); }); producer.send(msgMap); } // test lag estimation for tag consumer for (BrokerController controller : brokerControllerList) { for (MessageQueue mq : mqs) { if (mq.getBrokerName().equals(controller.getBrokerConfig().getBrokerName())) { long brokerOffset = controller.getMessageStore().getMaxOffsetInQueue(topic, mq.getQueueId()); long estimateMessageCount = controller.getMessageStore() .estimateMessageCount(topic, mq.getQueueId(), 0, brokerOffset, new DefaultMessageFilter(FilterAPI.buildSubscriptionData(topic, tag))); assertEquals(repeat * msgWithTagSize, estimateMessageCount); } } } // test lag estimation for sql consumer for (BrokerController controller : brokerControllerList) { for (MessageQueue mq : mqs) { if (mq.getBrokerName().equals(controller.getBrokerConfig().getBrokerName())) { long brokerOffset = controller.getMessageStore().getMaxOffsetInQueue(topic, mq.getQueueId()); ConsumerFilterData consumerFilterData = controller.getConsumerFilterManager().get(topic, sqlConsumer.getConsumerGroup()); long estimateMessageCount = controller.getMessageStore() .estimateMessageCount(topic, mq.getQueueId(), 0, brokerOffset, new ExpressionMessageFilter(subscriptionData, consumerFilterData, controller.getConsumerFilterManager())); assertEquals(repeat / 2 * msgWithTagSize, estimateMessageCount); } } } sqlConsumer.shutdown(); tagConsumer.shutdown(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/offset/OffsetNotFoundIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.offset; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class OffsetNotFoundIT extends BaseConf { private OffsetRpcHook offsetRpcHook = new OffsetRpcHook(); static class OffsetRpcHook implements RPCHook { private boolean throwException = false; private boolean addSetZeroOfNotFound = false; @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { if (request.getCode() == RequestCode.QUERY_CONSUMER_OFFSET) { if (throwException) { throw new RuntimeException("Stop by rpc hook"); } if (addSetZeroOfNotFound) { request.getExtFields().put("setZeroIfNotFound", "false"); } } } @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } } @Before public void setUp() { for (BrokerController brokerController: brokerControllerList) { brokerController.registerServerRPCHook(offsetRpcHook); } } @After public void tearDown() { super.shutdown(); } @Test public void testConsumeStopAndResume() { String topic = initTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); int msgSize = 10; producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); try { offsetRpcHook.throwException = true; RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), 15000); Assert.assertEquals(0, consumer.getListener().getAllMsgBody().size()); consumer.shutdown(); } finally { offsetRpcHook.throwException = false; } //test the normal RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), 15000); Assert.assertEquals(producer.getAllMsgBody().size(), consumer.getListener().getAllMsgBody().size()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); consumer.shutdown(); } @Test public void testOffsetNotFoundException() { String topic = initTopic(); String group = initConsumerGroup(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); int msgSize = 10; producer.send(msgSize); Assert.assertEquals("Not all sent succeeded", msgSize, producer.getAllUndupMsgBody().size()); try { offsetRpcHook.addSetZeroOfNotFound = true; //test the normal RMQNormalConsumer consumer = new RMQNormalConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener()); consumer.create(false); consumer.getConsumer().setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.start(); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), 15000); Assert.assertEquals(producer.getAllMsgBody().size(), consumer.getListener().getAllMsgBody().size()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); consumer.shutdown(); } finally { offsetRpcHook.addSetZeroOfNotFound = false; } } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/offset/OffsetResetForPopIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.offset; import com.google.common.collect.Lists; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQAdminTestUtils; import org.apache.rocketmq.test.util.MQRandomUtils; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.command.CommandUtil; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; public class OffsetResetForPopIT extends BaseConf { private static final Logger LOGGER = LoggerFactory.getLogger(OffsetResetForPopIT.class); private String topic; private String group; private RMQNormalProducer producer = null; private RMQPopConsumer consumer = null; private DefaultMQAdminExt adminExt; @Before public void setUp() throws Exception { // reset pop offset rely on server side offset brokerController1.getBrokerConfig().setUseServerSideResetOffset(true); brokerController1.getBrokerConfig().setPopConsumerKVServiceEnable(false); // force disable before fifo resetOffset issue fixed adminExt = BaseConf.getAdmin(NAMESRV_ADDR); adminExt.start(); topic = MQRandomUtils.getRandomTopic(); this.createAndWaitTopicRegister(BROKER1_NAME, topic); group = initConsumerGroup(); LOGGER.info(String.format("use topic: %s, group: %s", topic, group)); producer = getProducer(NAMESRV_ADDR, topic); } @After public void tearDown() { shutdown(); } private void createAndWaitTopicRegister(String brokerName, String topic) throws Exception { String brokerAddress = CommandUtil.fetchMasterAddrByBrokerName(adminExt, brokerName); TopicConfig topicConfig = new TopicConfig(topic); topicConfig.setReadQueueNums(1); topicConfig.setWriteQueueNums(1); adminExt.createAndUpdateTopicConfig(brokerAddress, topicConfig); await().atMost(30, TimeUnit.SECONDS).until( () -> MQAdminTestUtils.checkTopicExist(adminExt, topic)); } private void resetOffsetInner(long resetOffset) { try { // reset offset by queue adminExt.resetOffsetByQueueId(brokerController1.getBrokerAddr(), consumer.getConsumerGroup(), consumer.getTopic(), 0, resetOffset); } catch (Exception ignore) { } } private void ackMessageSync(MessageExt messageExt) { try { consumer.ackAsync(brokerController1.getBrokerAddr(), messageExt.getProperty(MessageConst.PROPERTY_POP_CK)).get(); } catch (Exception e) { e.printStackTrace(); } } private void ackMessageSync(List messageExtList) { if (messageExtList != null) { messageExtList.forEach(this::ackMessageSync); } } @Test public void testResetOffsetAfterPop() throws Exception { int messageCount = 10; int resetOffset = 4; producer.send(messageCount); consumer = new RMQPopConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener()); consumer.start(); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, 0); PopResult popResult = consumer.pop(brokerController1.getBrokerAddr(), mq); Assert.assertEquals(10, popResult.getMsgFoundList().size()); resetOffsetInner(resetOffset); popResult = consumer.pop(brokerController1.getBrokerAddr(), mq); Assert.assertTrue(popResult != null && popResult.getMsgFoundList() != null); Assert.assertEquals(messageCount - resetOffset, popResult.getMsgFoundList().size()); } @Test public void testResetOffsetThenAckOldForPopOrderly() throws Exception { int messageCount = 10; int resetOffset = 2; producer.send(messageCount); consumer = new RMQPopConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener()); consumer.start(); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, 0); PopResult popResult1 = consumer.popOrderly(brokerController1.getBrokerAddr(), mq); Assert.assertEquals(10, popResult1.getMsgFoundList().size()); resetOffsetInner(resetOffset); ConsumeStats consumeStats = adminExt.examineConsumeStats(group, topic); Assert.assertEquals(resetOffset, consumeStats.getOffsetTable().get(mq).getConsumerOffset()); PopResult popResult2 = consumer.popOrderly(brokerController1.getBrokerAddr(), mq); Assert.assertTrue(popResult2 != null && popResult2.getMsgFoundList() != null); Assert.assertEquals(messageCount - resetOffset, popResult2.getMsgFoundList().size()); // ack old msg, expect has no effect ackMessageSync(popResult1.getMsgFoundList()); Assert.assertTrue(brokerController1.getConsumerOrderInfoManager() .checkBlock(null, topic, group, 0, RMQPopConsumer.DEFAULT_INVISIBLE_TIME)); // ack new msg ackMessageSync(popResult2.getMsgFoundList()); Assert.assertFalse(brokerController1.getConsumerOrderInfoManager() .checkBlock(null, topic, group, 0, RMQPopConsumer.DEFAULT_INVISIBLE_TIME)); } @Test public void testRestOffsetToSkipMsgForPopOrderly() throws Exception { int messageCount = 10; int resetOffset = 4; producer.send(messageCount); consumer = new RMQPopConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener()); resetOffsetInner(resetOffset); consumer.start(); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, 0); PopResult popResult = consumer.popOrderly(brokerController1.getBrokerAddr(), mq); Assert.assertEquals(messageCount - resetOffset, popResult.getMsgFoundList().size()); Assert.assertTrue(brokerController1.getConsumerOrderInfoManager() .checkBlock(null, topic, group, 0, RMQPopConsumer.DEFAULT_INVISIBLE_TIME)); ackMessageSync(popResult.getMsgFoundList()); TimeUnit.SECONDS.sleep(1); Assert.assertFalse(brokerController1.getConsumerOrderInfoManager() .checkBlock(null, topic, group, 0, RMQPopConsumer.DEFAULT_INVISIBLE_TIME)); } @Test public void testResetOffsetAfterPopWhenOpenBufferAndWait() throws Exception { int messageCount = 10; int resetOffset = 4; brokerController1.getBrokerConfig().setEnablePopBufferMerge(true); producer.send(messageCount); consumer = new RMQPopConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener()); consumer.start(); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, 0); PopResult popResult = consumer.pop(brokerController1.getBrokerAddr(), mq); Assert.assertEquals(10, popResult.getMsgFoundList().size()); resetOffsetInner(resetOffset); TimeUnit.MILLISECONDS.sleep(brokerController1.getBrokerConfig().getPopCkStayBufferTimeOut()); popResult = consumer.pop(brokerController1.getBrokerAddr(), mq); Assert.assertTrue(popResult != null && popResult.getMsgFoundList() != null); Assert.assertEquals(messageCount - resetOffset, popResult.getMsgFoundList().size()); } @Test public void testResetOffsetWhilePopWhenOpenBuffer() { testResetOffsetWhilePop(8, false, false, 5); } @Test public void testResetOffsetWhilePopWhenOpenBufferAndAck() { testResetOffsetWhilePop(8, false, true, 5); } @Test public void testMultipleResetOffsetWhilePopWhenOpenBufferAndAck() { testResetOffsetWhilePop(8, false, true, 3, 5); } @Test public void testResetFutureOffsetWhilePopWhenOpenBufferAndAck() { testResetOffsetWhilePop(2, true, true, 8); } @Test public void testMultipleResetFutureOffsetWhilePopWhenOpenBufferAndAck() { testResetOffsetWhilePop(2, true, true, 5, 8); } private void testResetOffsetWhilePop(int targetCount, boolean resetFuture, boolean needAck, int... resetOffset) { brokerController1.getBrokerConfig().setEnablePopBufferMerge(true); producer.send(10); // max pop one message per request consumer = new RMQPopConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener(), 1); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, 0); AtomicInteger counter = new AtomicInteger(0); consumer.start(); Executors.newSingleThreadScheduledExecutor().execute(() -> { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start <= 30 * 1000L) { try { PopResult popResult = consumer.pop(brokerController1.getBrokerAddr(), mq); if (popResult == null || popResult.getMsgFoundList() == null) { continue; } int count = counter.addAndGet(popResult.getMsgFoundList().size()); if (needAck) { ackMessageSync(popResult.getMsgFoundList()); } if (count == targetCount) { for (int offset : resetOffset) { resetOffsetInner(offset); } } } catch (Exception e) { e.printStackTrace(); } } }); await().atMost(10, TimeUnit.SECONDS).until(() -> { boolean result = true; if (resetFuture) { result = counter.get() < 10; } result &= counter.get() >= targetCount + 10 - resetOffset[resetOffset.length - 1]; return result; }); } @Test public void testResetFutureOffsetWhilePopOrderlyAndAck() { testResetOffsetWhilePopOrderly(1, Lists.newArrayList(0, 5, 6, 7, 8, 9), Lists.newArrayList(5), 6); } @Test public void testMultipleResetFutureOffsetWhilePopOrderlyAndAck() { testResetOffsetWhilePopOrderly(1, Lists.newArrayList(0, 5, 6, 7, 8, 9), Lists.newArrayList(3, 5), 6); } @Test public void testResetOffsetWhilePopOrderlyAndAck() { testResetOffsetWhilePopOrderly(5, Lists.newArrayList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), Lists.newArrayList(3), 12); } @Test public void testMultipleResetOffsetWhilePopOrderlyAndAck() { testResetOffsetWhilePopOrderly(5, Lists.newArrayList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), Lists.newArrayList(3, 1), 14); } private void testResetOffsetWhilePopOrderly(int targetCount, List expectMsgReceive, List resetOffset, int expectCount) { brokerController1.getBrokerConfig().setEnablePopBufferMerge(true); for (int i = 0; i < 10; i++) { Message msg = new Message(topic, (String.valueOf(i)).getBytes()); producer.send(msg); } consumer = new RMQPopConsumer(NAMESRV_ADDR, topic, "*", group, new RMQNormalListener(), 1); MessageQueue mq = new MessageQueue(topic, BROKER1_NAME, 0); Set msgReceive = Collections.newSetFromMap(new ConcurrentHashMap<>()); AtomicInteger counter = new AtomicInteger(0); consumer.start(); Executors.newSingleThreadScheduledExecutor().execute(() -> { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start <= 30 * 1000L) { try { PopResult popResult = consumer.popOrderly(brokerController1.getBrokerAddr(), mq); if (popResult == null || popResult.getMsgFoundList() == null) { continue; } int count = counter.addAndGet(popResult.getMsgFoundList().size()); for (MessageExt messageExt : popResult.getMsgFoundList()) { msgReceive.add(Integer.valueOf(new String(messageExt.getBody()))); ackMessageSync(messageExt); } if (count == targetCount) { for (int offset : resetOffset) { resetOffsetInner(offset); } } } catch (Exception e) { // do nothing; } } }); await().atMost(10, TimeUnit.SECONDS).until(() -> { boolean result = true; if (expectMsgReceive.size() != msgReceive.size()) { return false; } if (counter.get() != expectCount) { return false; } for (Integer expectMsg : expectMsgReceive) { result &= msgReceive.contains(expectMsg); } return result; }); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/offset/OffsetResetIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.offset; import java.time.Duration; import java.util.List; import java.util.Map; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper; import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.message.MessageQueueMsg; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import static org.awaitility.Awaitility.await; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class OffsetResetIT extends BaseConf { private static final Logger LOGGER = LoggerFactory.getLogger(OffsetResetIT.class); private RMQNormalListener listener = null; private RMQNormalProducer producer = null; private RMQNormalConsumer consumer = null; private DefaultMQAdminExt defaultMQAdminExt = null; private String topic = null; @Before public void init() throws MQClientException { topic = initTopic(); LOGGER.info(String.format("use topic: %s;", topic)); for (BrokerController controller : brokerControllerList) { controller.getBrokerConfig().setLongPollingEnable(false); controller.getBrokerConfig().setShortPollingTimeMills(500); controller.getBrokerConfig().setUseServerSideResetOffset(true); } listener = new RMQNormalListener(); producer = getProducer(NAMESRV_ADDR, topic); consumer = getConsumer(NAMESRV_ADDR, topic, "*", listener); defaultMQAdminExt = BaseConf.getAdmin(NAMESRV_ADDR); defaultMQAdminExt.start(); } @After public void tearDown() { shutdown(); } @Test public void testEncodeOffsetHeader() { ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(consumer.getConsumerGroup()); requestHeader.setTimestamp(System.currentTimeMillis()); requestHeader.setForce(false); RemotingCommand.createRequestCommand(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, requestHeader); } /** * use mq admin tool to query remote offset */ private long getConsumerLag(String topic, String group) throws Exception { long consumerLag = 0L; for (BrokerController controller : brokerControllerList) { ConsumeStats consumeStats = defaultMQAdminExt.getDefaultMQAdminExtImpl() .getMqClientInstance().getMQClientAPIImpl() .getConsumeStats(controller.getBrokerAddr(), group, topic, 3000); Map offsetTable = consumeStats.getOffsetTable(); for (Map.Entry entry : offsetTable.entrySet()) { MessageQueue messageQueue = entry.getKey(); OffsetWrapper offsetWrapper = entry.getValue(); Assert.assertEquals(messageQueue.getBrokerName(), controller.getBrokerConfig().getBrokerName()); long brokerOffset = controller.getMessageStore().getMaxOffsetInQueue(topic, messageQueue.getQueueId()); long consumerOffset = controller.getConsumerOffsetManager().queryOffset( consumer.getConsumerGroup(), topic, messageQueue.getQueueId()); Assert.assertEquals(brokerOffset, offsetWrapper.getBrokerOffset()); Assert.assertEquals(consumerOffset, offsetWrapper.getConsumerOffset()); consumerLag += brokerOffset - consumerOffset; } } return consumerLag; } @Test public void testResetOffsetSingleQueue() throws Exception { int msgSize = 100; List mqs = producer.getMessageQueue(); MessageQueueMsg messageQueueMsg = new MessageQueueMsg(mqs, msgSize); producer.send(messageQueueMsg.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofMinutes(3)).until( () -> 0L == this.getConsumerLag(topic, consumer.getConsumerGroup())); for (BrokerController controller : brokerControllerList) { defaultMQAdminExt.resetOffsetByQueueId(controller.getBrokerAddr(), consumer.getConsumerGroup(), consumer.getTopic(), 3, 0); } int hasConsumeBefore = listener.getMsgIndex().get(); int expectAfterReset = brokerControllerList.size() * msgSize; await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofMinutes(3)).until(() -> { long receive = listener.getMsgIndex().get(); long expect = hasConsumeBefore + expectAfterReset; return receive >= expect; }); } @Test public void testResetOffsetTotal() throws Exception { int msgSize = 100; long start = System.currentTimeMillis(); List mqs = producer.getMessageQueue(); MessageQueueMsg messageQueueMsg = new MessageQueueMsg(mqs, msgSize); producer.send(messageQueueMsg.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofMinutes(3)).until( () -> 0L == this.getConsumerLag(topic, consumer.getConsumerGroup())); for (BrokerController controller : brokerControllerList) { defaultMQAdminExt.getDefaultMQAdminExtImpl().getMqClientInstance().getMQClientAPIImpl() .invokeBrokerToResetOffset(controller.getBrokerAddr(), consumer.getTopic(), consumer.getConsumerGroup(), start, true, 3 * 1000); } int hasConsumeBefore = listener.getMsgIndex().get(); int expectAfterReset = mqs.size() * msgSize; await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofMinutes(3)).until(() -> { long receive = listener.getMsgIndex().get(); long expect = hasConsumeBefore + expectAfterReset; return receive >= expect; }); } @Test public void testPullOffsetTotal() throws Exception { int msgSize = 100; List mqs = producer.getMessageQueue(); MessageQueueMsg messageQueueMsg = new MessageQueueMsg(mqs, msgSize); producer.send(messageQueueMsg.getMsgsWithMQ()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofMinutes(3)).until( () -> 0L == this.getConsumerLag(topic, consumer.getConsumerGroup())); long expectInflight = 0L; for (BrokerController controller : brokerControllerList) { ConsumeStats consumeStats = defaultMQAdminExt.getDefaultMQAdminExtImpl().getMqClientInstance() .getMQClientAPIImpl().getConsumeStats(controller.getBrokerAddr(), consumer.getConsumerGroup(), consumer.getTopic(), 3 * 1000); expectInflight += consumeStats.computeInflightTotalDiff(); } Assert.assertEquals(0L, expectInflight); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/recall/RecallWithTraceIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.recall; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.client.trace.TraceDataEncoder; import org.apache.rocketmq.client.trace.TraceType; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQRandomUtils; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static org.awaitility.Awaitility.await; public class RecallWithTraceIT extends BaseConf { private static String topic; private static String group; private static DefaultMQProducer producer; private static RMQPopConsumer popConsumer; @BeforeClass public static void init() throws MQClientException { System.setProperty("com.rocketmq.recall.default.trace.enable", Boolean.TRUE.toString()); topic = MQRandomUtils.getRandomTopic(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 1, CQType.SimpleCQ, TopicMessageType.NORMAL); group = initConsumerGroup(); producer = new DefaultMQProducer(group, true, topic); producer.setNamesrvAddr(NAMESRV_ADDR); producer.start(); popConsumer = ConsumerFactory.getRMQPopConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener()); mqClients.add(popConsumer); mqClients.add(producer); } @AfterClass public static void tearDown() { shutdown(); } @Test public void testRecallTrace() throws MQBrokerException, RemotingException, InterruptedException, MQClientException { String msgId = MessageClientIDSetter.createUniqID(); String recallHandle = RecallMessageHandle.HandleV1.buildHandle(topic, BROKER1_NAME, String.valueOf(System.currentTimeMillis() + 30000), msgId); producer.recallMessage(topic, recallHandle); MessageQueue messageQueue = new MessageQueue(topic, BROKER1_NAME, 0); String brokerAddress = brokerController1.getBrokerAddr(); AtomicReference traceMessage = new AtomicReference(); await() .pollInterval(1, TimeUnit.SECONDS) .atMost(15, TimeUnit.SECONDS) .until(() -> { PopResult popResult = popConsumer.pop(brokerAddress, messageQueue, 60 * 1000, -1); boolean found = popResult.getPopStatus().equals(PopStatus.FOUND); traceMessage.set(found ? popResult.getMsgFoundList().get(0) : null); return found; }); Assert.assertNotNull(traceMessage.get()); TraceContext context = TraceDataEncoder.decoderFromTraceDataString(new String(traceMessage.get().getBody())).get(0); Assert.assertEquals(TraceType.Recall, context.getTraceType()); Assert.assertEquals(group, context.getGroupName()); Assert.assertTrue(context.isSuccess()); Assert.assertEquals(msgId, context.getTraceBeans().get(0).getMsgId()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/recall/SendAndRecallDelayMessageIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.recall; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.consumer.PopResult; import org.apache.rocketmq.client.consumer.PopStatus; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.producer.RecallMessageHandle; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQRandomUtils; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class SendAndRecallDelayMessageIT extends BaseConf { private static String initTopic; private static String consumerGroup; private static RMQNormalProducer producer; private static RMQPopConsumer popConsumer; private final boolean appendTopicForTimerDeleteKey; public SendAndRecallDelayMessageIT(boolean appendTopicForTimerDeleteKey) { this.appendTopicForTimerDeleteKey = appendTopicForTimerDeleteKey; } @Parameterized.Parameters public static List params() { List result = new ArrayList<>(); result.add(new Object[] {false}); result.add(new Object[] {true}); return result; } @Before public void init() { brokerController1.getMessageStoreConfig().setAppendTopicForTimerDeleteKey(appendTopicForTimerDeleteKey); initTopic = initTopic(); consumerGroup = initConsumerGroup(); producer = getProducer(NAMESRV_ADDR, initTopic); popConsumer = ConsumerFactory.getRMQPopConsumer(NAMESRV_ADDR, consumerGroup, initTopic, "*", new RMQNormalListener()); mqClients.add(popConsumer); } @AfterClass public static void tearDown() { shutdown(); } @Test public void testSendAndRecv() throws Exception { int delaySecond = 1; String topic = MQRandomUtils.getRandomTopic(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 1, CQType.SimpleCQ, TopicMessageType.DELAY); MessageQueue messageQueue = new MessageQueue(topic, BROKER1_NAME, 0); String brokerAddress = brokerController1.getBrokerAddr(); List sendList = buildSendMessageList(topic, delaySecond); List recvList = new ArrayList<>(); for (Message message : sendList) { producer.getProducer().send(message); } await() .pollInterval(1, TimeUnit.SECONDS) .atMost(delaySecond + 15, TimeUnit.SECONDS) .until(() -> { PopResult popResult = popConsumer.pop(brokerAddress, messageQueue, 60 * 1000, -1); processPopResult(recvList, popResult); return recvList.size() == sendList.size(); }); } @Test public void testSendAndRecall() throws Exception { int delaySecond = 5; String topic = MQRandomUtils.getRandomTopic(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 1, CQType.SimpleCQ, TopicMessageType.DELAY); MessageQueue messageQueue = new MessageQueue(topic, BROKER1_NAME, 0); String brokerAddress = brokerController1.getBrokerAddr(); List sendList = buildSendMessageList(topic, delaySecond); List recvList = new ArrayList<>(); int recallCount = 0; for (Message message : sendList) { SendResult sendResult = producer.getProducer().send(message); if (sendResult.getRecallHandle() != null) { String messageId = producer.getProducer().recallMessage(topic, sendResult.getRecallHandle()); assertEquals(sendResult.getMsgId(), messageId); recallCount += 1; } } assertEquals(sendList.size() - 2, recallCount); // one normal and one delay-level message try { await() .pollInterval(1, TimeUnit.SECONDS) .atMost(delaySecond + 15, TimeUnit.SECONDS) .until(() -> { PopResult popResult = popConsumer.pop(brokerAddress, messageQueue, 60 * 1000, -1); processPopResult(recvList, popResult); return recvList.size() == sendList.size(); }); } catch (Exception e) { } assertEquals(sendList.size() - recallCount, recvList.size()); } @Test public void testSendAndRecall_ukCollision() throws Exception { if (!appendTopicForTimerDeleteKey) { // skip return; } int delaySecond = 5; String topic = MQRandomUtils.getRandomTopic(); String collisionTopic = MQRandomUtils.getRandomTopic(); IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 1, CQType.SimpleCQ, TopicMessageType.DELAY); IntegrationTestBase.initTopic(collisionTopic, NAMESRV_ADDR, BROKER1_NAME, 1, CQType.SimpleCQ, TopicMessageType.DELAY); MessageQueue messageQueue = new MessageQueue(topic, BROKER1_NAME, 0); String brokerAddress = brokerController1.getBrokerAddr(); List sendList = buildSendMessageList(topic, delaySecond); List recvList = new ArrayList<>(); int recallCount = 0; for (Message message : sendList) { SendResult sendResult = producer.getProducer().send(message); if (sendResult.getRecallHandle() != null) { RecallMessageHandle.HandleV1 handleEntity = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(sendResult.getRecallHandle()); String collisionHandle = RecallMessageHandle.HandleV1.buildHandle(collisionTopic, handleEntity.getBrokerName(), handleEntity.getTimestampStr(), handleEntity.getMessageId()); String messageId = producer.getProducer().recallMessage(collisionTopic, collisionHandle); assertEquals(sendResult.getMsgId(), messageId); recallCount += 1; } } assertEquals(sendList.size() - 2, recallCount); // one normal and one delay-level message try { await() .pollInterval(1, TimeUnit.SECONDS) .atMost(delaySecond + 15, TimeUnit.SECONDS) .until(() -> { PopResult popResult = popConsumer.pop(brokerAddress, messageQueue, 60 * 1000, -1); processPopResult(recvList, popResult); return recvList.size() == sendList.size(); }); } catch (Exception e) { } assertEquals(sendList.size(), recvList.size()); } private void processPopResult(List recvList, PopResult popResult) { if (popResult.getPopStatus() == PopStatus.FOUND && popResult.getMsgFoundList() != null) { recvList.addAll(popResult.getMsgFoundList()); } } private List buildSendMessageList(String topic, int delaySecond) { Message msg0 = new Message(topic, "tag", "Hello RocketMQ".getBytes()); // not supported Message msg1 = new Message(topic, "tag", "Hello RocketMQ".getBytes()); // not supported msg1.setDelayTimeLevel(2); Message msg2 = new Message(topic, "tag", "Hello RocketMQ".getBytes()); msg2.setDelayTimeMs(delaySecond * 1000L); Message msg3 = new Message(topic, "tag", "Hello RocketMQ".getBytes()); msg3.setDelayTimeSec(delaySecond); Message msg4 = new Message(topic, "tag", "Hello RocketMQ".getBytes()); msg4.setDeliverTimeMs(System.currentTimeMillis() + delaySecond * 1000L); return Arrays.asList(msg0, msg1, msg2, msg3, msg4); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/retry/PopConsumerRetryIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.retry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.commons.lang3.RandomStringUtils; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.attribute.CQType; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo; import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.offset.OffsetResetIT; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.awaitility.Awaitility.await; public class PopConsumerRetryIT extends BaseConf { private static final Logger LOGGER = LoggerFactory.getLogger(OffsetResetIT.class); private DefaultMQAdminExt defaultMQAdminExt = null; private String topicName = null; private String groupName = null; @Before public void init() throws MQClientException { topicName = "topic-" + RandomStringUtils.randomAlphabetic(72).toUpperCase(); groupName = "group-" + RandomStringUtils.randomAlphabetic(72).toUpperCase(); LOGGER.info(String.format("use topic: %s, group: %s", topicName, groupName)); IntegrationTestBase.initTopic(topicName, NAMESRV_ADDR, CLUSTER_NAME, CQType.SimpleCQ); defaultMQAdminExt = getAdmin(NAMESRV_ADDR); defaultMQAdminExt.start(); } @After public void tearDown() { shutdown(); } private void switchPop(String groupName, String topicName) throws Exception { ClusterInfo clusterInfo = defaultMQAdminExt.examineBrokerClusterInfo(); Set brokerAddrs = clusterInfo.getBrokerAddrTable().values() .stream().map(BrokerData::selectBrokerAddr).collect(Collectors.toSet()); for (String brokerAddr : brokerAddrs) { TopicConfig topicConfig = new TopicConfig(topicName, 1, 1, 6); defaultMQAdminExt.createAndUpdateTopicConfig(brokerAddr, topicConfig); defaultMQAdminExt.setMessageRequestMode(brokerAddr, topicName, groupName, MessageRequestMode.POP, 8, 3000L); } } @Test public void testNormalMessageUseMessageVersionV2() throws Exception { switchPop(groupName, topicName); AtomicInteger successCount = new AtomicInteger(); AtomicInteger retryCount = new AtomicInteger(); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName); consumer.subscribe(topicName, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); consumer.setConsumeThreadMin(1); consumer.setConsumeThreadMax(1); consumer.setConsumeMessageBatchMaxSize(1); consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.setClientRebalance(false); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt message : msgs) { LOGGER.debug(String.format("messageId: %s, times: %d, topic: %s", message.getMsgId(), message.getReconsumeTimes(), message.getTopic())); if (message.getReconsumeTimes() < 2) { retryCount.incrementAndGet(); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } else { successCount.incrementAndGet(); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); LOGGER.info("Consumer Started..."); DefaultMQProducer producer = new DefaultMQProducer("PID-1", false, null); producer.setAccessChannel(AccessChannel.CLOUD); producer.setNamesrvAddr(NAMESRV_ADDR); producer.start(); LOGGER.info("Producer Started...%n"); // wait pop client register TimeUnit.SECONDS.sleep(3); int total = 10; for (int i = 0; i < total; i++) { Message msg = new Message( topicName, "*", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); } await().pollInterval(1, TimeUnit.SECONDS).atMost(90, TimeUnit.SECONDS) .until(() -> { LOGGER.debug(String.format("retry: %d, success: %d", retryCount.get(), successCount.get())); return retryCount.get() == total * 2 && successCount.get() == total; }); } @Test public void testFIFOMessageUseMessageVersionV2() throws Exception { switchPop(groupName, topicName); AtomicInteger successCount = new AtomicInteger(); AtomicInteger retryCount = new AtomicInteger(); DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName); consumer.subscribe(topicName, "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); consumer.setConsumeThreadMin(1); consumer.setConsumeThreadMax(1); consumer.setConsumeMessageBatchMaxSize(1); consumer.setNamesrvAddr(NAMESRV_ADDR); consumer.setClientRebalance(false); consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> { for (MessageExt message : msgs) { LOGGER.debug(String.format("messageId: %s, times: %d, topic: %s", message.getMsgId(), message.getReconsumeTimes(), message.getTopic())); if (message.getReconsumeTimes() < 2) { retryCount.incrementAndGet(); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } else { successCount.incrementAndGet(); return ConsumeOrderlyStatus.SUCCESS; } } return ConsumeOrderlyStatus.SUCCESS; }); consumer.start(); LOGGER.info("Consumer Started..."); DefaultMQProducer producer = new DefaultMQProducer("PID-1", false, null); producer.setAccessChannel(AccessChannel.CLOUD); producer.setNamesrvAddr(NAMESRV_ADDR); producer.start(); LOGGER.info("Producer Started...%n"); // wait pop client register TimeUnit.SECONDS.sleep(3); int total = 10; for (int i = 0; i < total; i++) { Message msg = new Message( topicName, "*", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); Assert.assertEquals(SendStatus.SEND_OK, sendResult.getSendStatus()); } await().pollInterval(1, TimeUnit.SECONDS).atMost(90, TimeUnit.SECONDS) .until(() -> { LOGGER.debug(String.format("retry: %d, success: %d", retryCount.get(), successCount.get())); return retryCount.get() == total * 2 && successCount.get() == total; }); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/route/CreateAndUpdateTopicIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.route; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.util.MQAdminTestUtils; import org.junit.Ignore; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; public class CreateAndUpdateTopicIT extends BaseConf { @Test public void testCreateOrUpdateTopic_EnableSingleTopicRegistration() { String topic = "test-topic-without-broker-registration"; brokerController1.getBrokerConfig().setEnableSingleTopicRegister(true); brokerController2.getBrokerConfig().setEnableSingleTopicRegister(true); brokerController3.getBrokerConfig().setEnableSingleTopicRegister(true); final boolean createResult = MQAdminTestUtils.createTopic(NAMESRV_ADDR, CLUSTER_NAME, topic, 8, null); assertThat(createResult).isTrue(); TopicRouteData route = MQAdminTestUtils.examineTopicRouteInfo(NAMESRV_ADDR, topic); assertThat(route.getBrokerDatas()).hasSize(3); assertThat(route.getQueueDatas()).hasSize(3); brokerController1.getBrokerConfig().setEnableSingleTopicRegister(false); brokerController2.getBrokerConfig().setEnableSingleTopicRegister(false); brokerController3.getBrokerConfig().setEnableSingleTopicRegister(false); } // Temporarily ignore the fact that this test cannot pass in the integration test pipeline due to unknown reasons @Ignore @Test public void testDeleteTopicFromNameSrvWithBrokerRegistration() { namesrvController.getNamesrvConfig().setDeleteTopicWithBrokerRegistration(true); brokerController1.getBrokerConfig().setEnableSingleTopicRegister(true); brokerController2.getBrokerConfig().setEnableSingleTopicRegister(true); brokerController3.getBrokerConfig().setEnableSingleTopicRegister(true); String testTopic1 = "test-topic-keep-route"; String testTopic2 = "test-topic-delete-route"; boolean createResult = MQAdminTestUtils.createTopic(NAMESRV_ADDR, CLUSTER_NAME, testTopic1, 8, null); assertThat(createResult).isTrue(); createResult = MQAdminTestUtils.createTopic(NAMESRV_ADDR, CLUSTER_NAME, testTopic2, 8, null); assertThat(createResult).isTrue(); TopicRouteData route = MQAdminTestUtils.examineTopicRouteInfo(NAMESRV_ADDR, testTopic2); assertThat(route.getBrokerDatas()).hasSize(3); MQAdminTestUtils.deleteTopicFromBrokerOnly(NAMESRV_ADDR, BROKER1_NAME, testTopic2); // Deletion is lazy, trigger broker registration brokerController1.registerBrokerAll(false, false, true); await().atMost(10, TimeUnit.SECONDS).until(() -> { // The route info of testTopic2 will be removed from broker1 after the registration TopicRouteData finalRoute = MQAdminTestUtils.examineTopicRouteInfo(NAMESRV_ADDR, testTopic2); return finalRoute.getBrokerDatas().size() == 2 && finalRoute.getQueueDatas().get(0).getBrokerName().equals(BROKER2_NAME) && finalRoute.getQueueDatas().get(1).getBrokerName().equals(BROKER3_NAME); }); brokerController1.getBrokerConfig().setEnableSingleTopicRegister(false); brokerController2.getBrokerConfig().setEnableSingleTopicRegister(false); brokerController3.getBrokerConfig().setEnableSingleTopicRegister(false); namesrvController.getNamesrvConfig().setDeleteTopicWithBrokerRegistration(false); } @Test public void testStaticTopicNotAffected() throws Exception { namesrvController.getNamesrvConfig().setDeleteTopicWithBrokerRegistration(true); brokerController1.getBrokerConfig().setEnableSingleTopicRegister(true); brokerController2.getBrokerConfig().setEnableSingleTopicRegister(true); brokerController3.getBrokerConfig().setEnableSingleTopicRegister(true); String testTopic = "test-topic-not-affected"; String testStaticTopic = "test-static-topic"; boolean createResult = MQAdminTestUtils.createTopic(NAMESRV_ADDR, CLUSTER_NAME, testTopic, 8, null); assertThat(createResult).isTrue(); TopicRouteData route = MQAdminTestUtils.examineTopicRouteInfo(NAMESRV_ADDR, testTopic); assertThat(route.getBrokerDatas()).hasSize(3); assertThat(route.getQueueDatas()).hasSize(3); MQAdminTestUtils.createStaticTopicWithCommand(testStaticTopic, 10, null, CLUSTER_NAME, NAMESRV_ADDR); assertThat(route.getBrokerDatas()).hasSize(3); assertThat(route.getQueueDatas()).hasSize(3); brokerController1.getBrokerConfig().setEnableSingleTopicRegister(false); brokerController2.getBrokerConfig().setEnableSingleTopicRegister(false); brokerController3.getBrokerConfig().setEnableSingleTopicRegister(false); namesrvController.getNamesrvConfig().setDeleteTopicWithBrokerRegistration(false); } @Test public void testCreateOrUpdateTopic_EnableSplitRegistration() { brokerController1.getBrokerConfig().setEnableSplitRegistration(true); brokerController2.getBrokerConfig().setEnableSplitRegistration(true); brokerController3.getBrokerConfig().setEnableSplitRegistration(true); String testTopic = "test-topic-"; for (int i = 0; i < 10; i++) { TopicConfig topicConfig = new TopicConfig(testTopic + i, 8, 8); brokerController1.getTopicConfigManager().updateTopicConfig(topicConfig); brokerController2.getTopicConfigManager().updateTopicConfig(topicConfig); brokerController3.getTopicConfigManager().updateTopicConfig(topicConfig); } brokerController1.registerBrokerAll(false, true, true); brokerController2.registerBrokerAll(false, true, true); brokerController3.registerBrokerAll(false, true, true); for (int i = 0; i < 10; i++) { TopicRouteData route = MQAdminTestUtils.examineTopicRouteInfo(NAMESRV_ADDR, testTopic + i); assertThat(route.getBrokerDatas()).hasSize(3); assertThat(route.getQueueDatas()).hasSize(3); } brokerController1.getBrokerConfig().setEnableSplitRegistration(false); brokerController2.getBrokerConfig().setEnableSplitRegistration(false); brokerController3.getBrokerConfig().setEnableSplitRegistration(false); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/schema/SchemaTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.schema; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; public class SchemaTest { private static final String BASE_SCHEMA_PATH = "src/test/resources/schema"; private static final String ADD = "ADD"; private static final String DELETE = "DELETE"; private static final String CHANGE = "CHANGE"; public void generate() throws Exception { SchemaDefiner.doLoad(); SchemaTools.write(SchemaTools.generate(SchemaDefiner.API_CLASS_LIST), BASE_SCHEMA_PATH, "api"); SchemaTools.write(SchemaTools.generate(SchemaDefiner.PROTOCOL_CLASS_LIST), BASE_SCHEMA_PATH, "protocol"); } @Test @Ignore public void checkSchema() throws Exception { SchemaDefiner.doLoad(); Map> schemaFromFile = new HashMap<>(); { schemaFromFile.putAll(SchemaTools.load(BASE_SCHEMA_PATH, SchemaTools.PATH_API)); schemaFromFile.putAll(SchemaTools.load(BASE_SCHEMA_PATH, SchemaTools.PATH_PROTOCOL)); } Map> schemaFromCode = new HashMap<>(); { schemaFromCode.putAll(SchemaTools.generate(SchemaDefiner.API_CLASS_LIST)); schemaFromCode.putAll(SchemaTools.generate(SchemaDefiner.PROTOCOL_CLASS_LIST)); } Map fileChanges = new TreeMap<>(); schemaFromFile.keySet().forEach(x -> { if (!schemaFromCode.containsKey(x)) { fileChanges.put(x, DELETE); } }); schemaFromCode.keySet().forEach(x -> { if (!schemaFromFile.containsKey(x)) { fileChanges.put(x, ADD); } }); Map> changesByFile = new HashMap<>(); schemaFromFile.forEach((file, oldSchema) -> { Map newSchema = schemaFromCode.get(file); Map schemaChanges = new TreeMap<>(); oldSchema.forEach((k, v) -> { if (!newSchema.containsKey(k)) { schemaChanges.put(k, DELETE); } else if (!newSchema.get(k).equals(v)) { schemaChanges.put(k, CHANGE); } }); newSchema.forEach((k, v) -> { if (!oldSchema.containsKey(k)) { schemaChanges.put(k, ADD); } }); if (!schemaChanges.isEmpty()) { changesByFile.put(file, schemaChanges); } }); fileChanges.forEach((k,v) -> { System.out.printf("%s file %s\n", v, k); }); changesByFile.forEach((k, v) -> { System.out.printf("%s file %s:\n", CHANGE, k); v.forEach((kk, vv) -> { System.out.printf("\t%s %s\n", vv, kk); }); }); String message = "The schema test failed, which means you have changed the API or Protocol defined in org.apache.rocketmq.test.schema.SchemaDefiner.\n" + "Please submit a pr only contains the API/Protocol changes and request at least one PMC Member's review.\n" + "For original motivation of this test, please refer to https://github.com/apache/rocketmq/pull/4565 ."; Assert.assertTrue(message, fileChanges.isEmpty() && changesByFile.isEmpty()); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/smoke/NormalMessageSendAndRecvIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.smoke; import com.google.common.collect.ImmutableList; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.base.IntegrationTestBase; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.VerifyUtils; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.time.Duration; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; import static com.google.common.truth.Truth.assertThat; public class NormalMessageSendAndRecvIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(NormalMessageSendAndRecvIT.class); private RMQNormalConsumer consumer = null; private RMQNormalProducer producer = null; private String topic = null; private String group = null; private DefaultMQAdminExt defaultMQAdminExt; @Before public void setUp() throws Exception { topic = initTopic(); group = initConsumerGroup(); logger.info(String.format("use topic: %s;", topic)); producer = getProducer(NAMESRV_ADDR, topic); consumer = getConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener()); defaultMQAdminExt = getAdmin(NAMESRV_ADDR); defaultMQAdminExt.start(); } @After public void tearDown() { BaseConf.shutdown(); } @Test public void testSynSendMessage() throws Exception { AtomicReference> messageQueueList = new AtomicReference<>(); AtomicReference consumeStats = new AtomicReference<>(); Awaitility.await().atMost(Duration.ofSeconds(120)) .until(() -> { try { consumeStats.set(defaultMQAdminExt.examineConsumeStats(group)); messageQueueList.set(producer.getProducer().fetchPublishMessageQueues(topic)); return !messageQueueList.get().isEmpty() && null != consumeStats.get() && consumeStats.get().getOffsetTable().keySet().containsAll(messageQueueList.get()); } catch (MQClientException e) { logger.debug("Exception raised while checking producer and consumer are started", e); } return false; }); int msgSize = 10; for (MessageQueue messageQueue : messageQueueList.get()) { producer.send(msgSize, messageQueue); } Assert.assertEquals("Not all sent succeeded", msgSize * messageQueueList.get().size(), producer.getAllUndupMsgBody().size()); consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), CONSUME_TIME); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); for (Object o : consumer.getListener().getAllOriginMsg()) { MessageClientExt msg = (MessageClientExt) o; assertThat(msg.getProperty(MessageConst.PROPERTY_POP_CK)).isNull(); } //shutdown to persist the offset consumer.getConsumer().shutdown(); consumeStats.set(defaultMQAdminExt.examineConsumeStats(group)); //+1 for the retry topic for (MessageQueue messageQueue : messageQueueList.get()) { Assert.assertTrue(consumeStats.get().getOffsetTable().containsKey(messageQueue)); Assert.assertEquals(msgSize, consumeStats.get().getOffsetTable().get(messageQueue).getConsumerOffset()); Assert.assertEquals(msgSize, consumeStats.get().getOffsetTable().get(messageQueue).getBrokerOffset()); } } @Test public void testSynSendMessageWhenEnableBuildConsumeQueueConcurrently() throws Exception { resetStoreConfigWithEnableBuildConsumeQueueConcurrently(true); testSynSendMessage(); resetStoreConfigWithEnableBuildConsumeQueueConcurrently(false); } void resetStoreConfigWithEnableBuildConsumeQueueConcurrently(boolean enableBuildConsumeQueueConcurrently) { { brokerController1.shutdown(); MessageStoreConfig storeConfig = brokerController1.getMessageStoreConfig(); BrokerConfig brokerConfig = brokerController1.getBrokerConfig(); storeConfig.setEnableBuildConsumeQueueConcurrently(enableBuildConsumeQueueConcurrently); brokerController1 = IntegrationTestBase.createAndStartBroker(storeConfig, brokerConfig); } { brokerController2.shutdown(); MessageStoreConfig storeConfig = brokerController2.getMessageStoreConfig(); BrokerConfig brokerConfig = brokerController2.getBrokerConfig(); storeConfig.setEnableBuildConsumeQueueConcurrently(enableBuildConsumeQueueConcurrently); brokerController2 = IntegrationTestBase.createAndStartBroker(storeConfig, brokerConfig); } { brokerController3.shutdown(); MessageStoreConfig storeConfig = brokerController3.getMessageStoreConfig(); BrokerConfig brokerConfig = brokerController3.getBrokerConfig(); storeConfig.setEnableBuildConsumeQueueConcurrently(enableBuildConsumeQueueConcurrently); brokerController3 = IntegrationTestBase.createAndStartBroker(storeConfig, brokerConfig); } brokerControllerList = ImmutableList.of(brokerController1, brokerController2, brokerController3); brokerControllerMap = brokerControllerList.stream().collect( Collectors.toMap(input -> input.getBrokerConfig().getBrokerName(), Function.identity())); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/statictopic/StaticTopicIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.statictopic; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats; import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper; import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable; import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem; import org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingOne; import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils; import org.apache.rocketmq.remoting.rpc.ClientMetadata; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQAdminTestUtils; import org.apache.rocketmq.test.util.MQRandomUtils; import org.apache.rocketmq.test.util.TestUtils; import org.apache.rocketmq.test.util.VerifyUtils; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.admin.MQAdminUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingUtils.getMappingDetailFromConfig; @FixMethodOrder public class StaticTopicIT extends BaseConf { private static Logger logger = LoggerFactory.getLogger(StaticTopicIT.class); private DefaultMQAdminExt defaultMQAdminExt; @Before public void setUp() throws Exception { System.setProperty("rocketmq.client.rebalance.waitInterval", "500"); defaultMQAdminExt = getAdmin(NAMESRV_ADDR); waitBrokerRegistered(NAMESRV_ADDR, CLUSTER_NAME, BROKER_NUM); defaultMQAdminExt.start(); } @Test public void testCommandsWithCluster() throws Exception { //This case is used to mock the env to test the command manually String topic = "static" + MQRandomUtils.getRandomTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); int queueNum = 10; int msgEachQueue = 100; { MQAdminTestUtils.createStaticTopicWithCommand(topic, queueNum, null, CLUSTER_NAME, NAMESRV_ADDR); sendMessagesAndCheck(producer, getBrokers(), topic, queueNum, msgEachQueue, 0); //consume and check consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 1); } { MQAdminTestUtils.remappingStaticTopicWithCommand(topic, null, CLUSTER_NAME, NAMESRV_ADDR); awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), consumer.getConsumer(), defaultMQAdminExt); sendMessagesAndCheck(producer, getBrokers(), topic, queueNum, msgEachQueue, msgEachQueue); } } @Test public void testCommandsWithBrokers() throws Exception { //This case is used to mock the env to test the command manually String topic = "static" + MQRandomUtils.getRandomTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); int queueNum = 10; int msgEachQueue = 10; { Set brokers = ImmutableSet.of(BROKER1_NAME); MQAdminTestUtils.createStaticTopicWithCommand(topic, queueNum, brokers, null, NAMESRV_ADDR); sendMessagesAndCheck(producer, brokers, topic, queueNum, msgEachQueue, 0); //consume and check consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 1); } { Set brokers = ImmutableSet.of(BROKER2_NAME); MQAdminTestUtils.remappingStaticTopicWithCommand(topic, brokers, null, NAMESRV_ADDR); awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), consumer.getConsumer(), defaultMQAdminExt); sendMessagesAndCheck(producer, brokers, topic, queueNum, msgEachQueue, TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE); consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 2); } } @Test public void testNoTargetBrokers() throws Exception { String topic = "static" + MQRandomUtils.getRandomTopic(); int queueNum = 10; { Set targetBrokers = new HashSet<>(); targetBrokers.add(BROKER1_NAME); MQAdminTestUtils.createStaticTopic(topic, queueNum, targetBrokers, defaultMQAdminExt); Map remoteBrokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); Assert.assertEquals(BROKER_NUM, remoteBrokerConfigMap.size()); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, remoteBrokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(getMappingDetailFromConfig(remoteBrokerConfigMap.values())), false, true); Assert.assertEquals(queueNum, globalIdMap.size()); TopicConfigAndQueueMapping configMapping = remoteBrokerConfigMap.get(BROKER2_NAME); Assert.assertEquals(0, configMapping.getWriteQueueNums()); Assert.assertEquals(0, configMapping.getReadQueueNums()); Assert.assertEquals(0, configMapping.getMappingDetail().getHostedQueues().size()); } { Set targetBrokers = new HashSet<>(); targetBrokers.add(BROKER2_NAME); MQAdminTestUtils.remappingStaticTopic(topic, targetBrokers, defaultMQAdminExt); Map remoteBrokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); Assert.assertEquals(BROKER_NUM, remoteBrokerConfigMap.size()); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, remoteBrokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(getMappingDetailFromConfig(remoteBrokerConfigMap.values())), false, true); Assert.assertEquals(queueNum, globalIdMap.size()); } } private void sendMessagesAndCheck(RMQNormalProducer producer, Set targetBrokers, String topic, int queueNum, int msgEachQueue, long baseOffset) throws Exception { ClientMetadata clientMetadata = MQAdminUtils.getBrokerAndTopicMetadata(topic, defaultMQAdminExt); List messageQueueList = producer.getMessageQueue(); Assert.assertEquals(queueNum, messageQueueList.size()); for (int i = 0; i < queueNum; i++) { MessageQueue messageQueue = messageQueueList.get(i); Assert.assertEquals(topic, messageQueue.getTopic()); Assert.assertEquals(TopicQueueMappingUtils.getMockBrokerName(MixAll.METADATA_SCOPE_GLOBAL), messageQueue.getBrokerName()); Assert.assertEquals(i, messageQueue.getQueueId()); String destBrokerName = clientMetadata.getBrokerNameFromMessageQueue(messageQueue); Assert.assertTrue(targetBrokers.contains(destBrokerName)); } for (MessageQueue messageQueue: messageQueueList) { producer.send(msgEachQueue, messageQueue); } Assert.assertEquals(0, producer.getSendErrorMsg().size()); //leave the time to build the cq Assert.assertTrue(awaitDispatchMs(500)); for (MessageQueue messageQueue : messageQueueList) { Assert.assertEquals(0, defaultMQAdminExt.minOffset(messageQueue)); Assert.assertEquals(msgEachQueue + baseOffset, defaultMQAdminExt.maxOffset(messageQueue)); } TopicStatsTable topicStatsTable = defaultMQAdminExt.examineTopicStats(topic); for (MessageQueue messageQueue : messageQueueList) { Assert.assertEquals(0, topicStatsTable.getOffsetTable().get(messageQueue).getMinOffset()); Assert.assertEquals(msgEachQueue + baseOffset, topicStatsTable.getOffsetTable().get(messageQueue).getMaxOffset()); } } private Map> computeMessageByQueue(Collection msgs) { Map> messagesByQueue = new HashMap<>(); for (Object object : msgs) { MessageExt messageExt = (MessageExt) object; if (!messagesByQueue.containsKey(messageExt.getQueueId())) { messagesByQueue.put(messageExt.getQueueId(), new ArrayList<>()); } messagesByQueue.get(messageExt.getQueueId()).add(messageExt); } for (List msgEachQueue : messagesByQueue.values()) { msgEachQueue.sort((o1, o2) -> (int) (o1.getQueueOffset() - o2.getQueueOffset())); } return messagesByQueue; } private void consumeMessagesAndCheck(RMQNormalProducer producer, RMQNormalConsumer consumer, String topic, int queueNum, int msgEachQueue, int startGen, int genNum) { consumer.getListener().waitForMessageConsume(producer.getAllMsgBody(), 60000); Assert.assertEquals(producer.getAllMsgBody().size(), consumer.getListener().getAllMsgBody().size()); assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); Map> messagesByQueue = computeMessageByQueue(consumer.getListener().getAllOriginMsg()); Assert.assertEquals(queueNum, messagesByQueue.size()); for (int i = 0; i < queueNum; i++) { List messageExts = messagesByQueue.get(i); int totalEachQueue = msgEachQueue * genNum; Assert.assertEquals(totalEachQueue, messageExts.size()); for (int j = 0; j < totalEachQueue; j++) { MessageExt messageExt = messageExts.get(j); int currGen = startGen + j / msgEachQueue; Assert.assertEquals(topic, messageExt.getTopic()); Assert.assertEquals(TopicQueueMappingUtils.getMockBrokerName(MixAll.METADATA_SCOPE_GLOBAL), messageExt.getBrokerName()); Assert.assertEquals(i, messageExt.getQueueId()); Assert.assertEquals((j % msgEachQueue) + currGen * TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE, messageExt.getQueueOffset()); } } } @Test public void testCreateProduceConsumeStaticTopic() throws Exception { String topic = "static" + MQRandomUtils.getRandomTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); int queueNum = 10; int msgEachQueue = 10; //create static topic Map localBrokerConfigMap = MQAdminTestUtils.createStaticTopic(topic, queueNum, getBrokers(), defaultMQAdminExt); //check the static topic config { Map remoteBrokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); Assert.assertEquals(BROKER_NUM, remoteBrokerConfigMap.size()); for (Map.Entry entry: remoteBrokerConfigMap.entrySet()) { String broker = entry.getKey(); TopicConfigAndQueueMapping configMapping = entry.getValue(); TopicConfigAndQueueMapping localConfigMapping = localBrokerConfigMap.get(broker); Assert.assertNotNull(localConfigMapping); Assert.assertEquals(configMapping, localConfigMapping); } TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, remoteBrokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(getMappingDetailFromConfig(remoteBrokerConfigMap.values())), false, true); Assert.assertEquals(queueNum, globalIdMap.size()); } //send and check sendMessagesAndCheck(producer, getBrokers(), topic, queueNum, msgEachQueue, 0); //consume and check consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 1); } @Test public void testRemappingProduceConsumeStaticTopic() throws Exception { String topic = "static" + MQRandomUtils.getRandomTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener()); int queueNum = 1; int msgEachQueue = 10; //create send consume { Set targetBrokers = ImmutableSet.of(BROKER1_NAME); MQAdminTestUtils.createStaticTopic(topic, queueNum, targetBrokers, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 0); consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 1); } //remapping the static topic { Set targetBrokers = ImmutableSet.of(BROKER2_NAME); MQAdminTestUtils.remappingStaticTopic(topic, targetBrokers, defaultMQAdminExt); Map remoteBrokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, remoteBrokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(getMappingDetailFromConfig(remoteBrokerConfigMap.values())), false, true); Assert.assertEquals(queueNum, globalIdMap.size()); for (TopicQueueMappingOne mappingOne: globalIdMap.values()) { Assert.assertEquals(BROKER2_NAME, mappingOne.getBname()); Assert.assertEquals(TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE, mappingOne.getItems().get(mappingOne.getItems().size() - 1).getLogicOffset()); } awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), consumer.getConsumer(), defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE); consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 2); } } public boolean awaitRefreshStaticTopicMetadata(long timeMs, String topic, DefaultMQProducer producer, DefaultMQPushConsumer consumer, DefaultMQAdminExt adminExt) throws Exception { long start = System.currentTimeMillis(); MQClientInstance currentInstance = null; while (System.currentTimeMillis() - start <= timeMs) { boolean allOk = true; if (producer != null) { currentInstance = producer.getDefaultMQProducerImpl().getmQClientFactory(); currentInstance.updateTopicRouteInfoFromNameServer(topic); if (!MQAdminTestUtils.checkStaticTopic(topic, adminExt, currentInstance)) { allOk = false; } } if (consumer != null) { currentInstance = consumer.getDefaultMQPushConsumerImpl().getmQClientFactory(); currentInstance.updateTopicRouteInfoFromNameServer(topic); if (!MQAdminTestUtils.checkStaticTopic(topic, adminExt, currentInstance)) { allOk = false; } } if (adminExt != null) { currentInstance = adminExt.getDefaultMQAdminExtImpl().getMqClientInstance(); currentInstance.updateTopicRouteInfoFromNameServer(topic); if (!MQAdminTestUtils.checkStaticTopic(topic, adminExt, currentInstance)) { allOk = false; } } if (allOk) { return true; } Thread.sleep(100); } return false; } @Test public void testDoubleReadCheckConsumerOffset() throws Exception { String topic = "static" + MQRandomUtils.getRandomTopic(); String group = initConsumerGroup(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); RMQNormalConsumer consumer = getConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener()); long start = System.currentTimeMillis(); int queueNum = 5; int msgEachQueue = 10; //create static topic { Set targetBrokers = ImmutableSet.of(BROKER1_NAME); MQAdminTestUtils.createStaticTopic(topic, queueNum, targetBrokers, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 0); consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 0, 1); } producer.shutdown(); consumer.shutdown(); //use a new producer producer = getProducer(NAMESRV_ADDR, topic); ConsumeStats consumeStats = defaultMQAdminExt.examineConsumeStats(group); List messageQueues = producer.getMessageQueue(); for (MessageQueue queue: messageQueues) { OffsetWrapper wrapper = consumeStats.getOffsetTable().get(queue); Assert.assertNotNull(wrapper); Assert.assertEquals(msgEachQueue, wrapper.getBrokerOffset()); Assert.assertEquals(msgEachQueue, wrapper.getConsumerOffset()); Assert.assertTrue(wrapper.getLastTimestamp() > start); } List brokers = ImmutableList.of(BROKER2_NAME, BROKER3_NAME, BROKER1_NAME); for (int i = 0; i < brokers.size(); i++) { Set targetBrokers = ImmutableSet.of(brokers.get(i)); MQAdminTestUtils.remappingStaticTopic(topic, targetBrokers, defaultMQAdminExt); //make the metadata awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), null, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, (i + 1) * TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE); } TestUtils.waitForSeconds(1); consumeStats = defaultMQAdminExt.examineConsumeStats(group); messageQueues = producer.getMessageQueue(); for (MessageQueue queue: messageQueues) { OffsetWrapper wrapper = consumeStats.getOffsetTable().get(queue); Assert.assertNotNull(wrapper); Assert.assertEquals(msgEachQueue + brokers.size() * TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE, wrapper.getBrokerOffset()); Assert.assertEquals(msgEachQueue, wrapper.getConsumerOffset()); Assert.assertTrue(wrapper.getLastTimestamp() > start); } consumer = getConsumer(NAMESRV_ADDR, group, topic, "*", new RMQNormalListener()); consumeMessagesAndCheck(producer, consumer, topic, queueNum, msgEachQueue, 1, brokers.size()); } @Test public void testRemappingAndClear() throws Exception { String topic = "static" + MQRandomUtils.getRandomTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); int queueNum = 10; int msgEachQueue = 100; //create to broker1Name { Set targetBrokers = ImmutableSet.of(BROKER1_NAME); MQAdminTestUtils.createStaticTopic(topic, queueNum, targetBrokers, defaultMQAdminExt); //leave the time to refresh the metadata awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), null, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 0); } //remapping to broker2Name { Set targetBrokers = ImmutableSet.of(BROKER2_NAME); MQAdminTestUtils.remappingStaticTopic(topic, targetBrokers, defaultMQAdminExt); //leave the time to refresh the metadata awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), null, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 1 * TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE); } //remapping to broker3Name { Set targetBrokers = ImmutableSet.of(BROKER3_NAME); MQAdminTestUtils.remappingStaticTopic(topic, targetBrokers, defaultMQAdminExt); //leave the time to refresh the metadata awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), null, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 2 * TopicQueueMappingUtils.DEFAULT_BLOCK_SEQ_SIZE); } // 1 -> 2 -> 3, currently 1 should not have any mappings { for (int i = 0; i < 10; i++) { for (BrokerController brokerController: brokerControllerList) { brokerController.getTopicQueueMappingCleanService().wakeup(); } Thread.sleep(100); } Map brokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); Assert.assertEquals(BROKER_NUM, brokerConfigMap.size()); TopicConfigAndQueueMapping config1 = brokerConfigMap.get(BROKER1_NAME); TopicConfigAndQueueMapping config2 = brokerConfigMap.get(BROKER2_NAME); TopicConfigAndQueueMapping config3 = brokerConfigMap.get(BROKER3_NAME); Assert.assertEquals(0, config1.getMappingDetail().getHostedQueues().size()); Assert.assertEquals(queueNum, config2.getMappingDetail().getHostedQueues().size()); Assert.assertEquals(queueNum, config3.getMappingDetail().getHostedQueues().size()); } { Set topics = new HashSet<>(brokerController1.getTopicConfigManager().getTopicConfigTable().keySet()); topics.remove(topic); brokerController1.getMessageStore().cleanUnusedTopic(topics); brokerController2.getMessageStore().cleanUnusedTopic(topics); for (int i = 0; i < 10; i++) { for (BrokerController brokerController: brokerControllerList) { brokerController.getTopicQueueMappingCleanService().wakeup(); } Thread.sleep(100); } Map brokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); Assert.assertEquals(BROKER_NUM, brokerConfigMap.size()); TopicConfigAndQueueMapping config1 = brokerConfigMap.get(BROKER1_NAME); TopicConfigAndQueueMapping config2 = brokerConfigMap.get(BROKER2_NAME); TopicConfigAndQueueMapping config3 = brokerConfigMap.get(BROKER3_NAME); Assert.assertEquals(0, config1.getMappingDetail().getHostedQueues().size()); Assert.assertEquals(queueNum, config2.getMappingDetail().getHostedQueues().size()); Assert.assertEquals(queueNum, config3.getMappingDetail().getHostedQueues().size()); //The first leader will clear it for (List items : config1.getMappingDetail().getHostedQueues().values()) { Assert.assertEquals(3, items.size()); } //The second leader do nothing for (List items : config3.getMappingDetail().getHostedQueues().values()) { Assert.assertEquals(1, items.size()); } } } @Test public void testRemappingWithNegativeLogicOffset() throws Exception { String topic = "static" + MQRandomUtils.getRandomTopic(); RMQNormalProducer producer = getProducer(NAMESRV_ADDR, topic); int queueNum = 10; int msgEachQueue = 100; //create and send { Set targetBrokers = ImmutableSet.of(BROKER1_NAME); MQAdminTestUtils.createStaticTopic(topic, queueNum, targetBrokers, defaultMQAdminExt); sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 0); } //remapping the static topic with -1 logic offset { Set targetBrokers = ImmutableSet.of(BROKER2_NAME); MQAdminTestUtils.remappingStaticTopicWithNegativeLogicOffset(topic, targetBrokers, defaultMQAdminExt); Map remoteBrokerConfigMap = MQAdminUtils.examineTopicConfigAll(topic, defaultMQAdminExt); TopicQueueMappingUtils.checkNameEpochNumConsistence(topic, remoteBrokerConfigMap); Map globalIdMap = TopicQueueMappingUtils.checkAndBuildMappingItems(new ArrayList<>(getMappingDetailFromConfig(remoteBrokerConfigMap.values())), false, true); Assert.assertEquals(queueNum, globalIdMap.size()); for (TopicQueueMappingOne mappingOne: globalIdMap.values()) { Assert.assertEquals(BROKER2_NAME, mappingOne.getBname()); Assert.assertEquals(-1, mappingOne.getItems().get(mappingOne.getItems().size() - 1).getLogicOffset()); } //leave the time to refresh the metadata awaitRefreshStaticTopicMetadata(3000, topic, producer.getProducer(), null, defaultMQAdminExt); //here the gen should be 0 sendMessagesAndCheck(producer, targetBrokers, topic, queueNum, msgEachQueue, 0); } } @After public void tearDown() { System.setProperty("rocketmq.client.rebalance.waitInterval", "20000"); super.shutdown(); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/tls/TlsIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.tls; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Before; import org.junit.Test; public class TlsIT extends BaseConf { private RMQNormalProducer producer; private RMQNormalConsumer consumer; private String topic; @Before public void setUp() { topic = initTopic(); // Send messages via TLS producer = getProducer(NAMESRV_ADDR, topic, true); // Receive messages via TLS consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener(), true); } @After public void tearDown() { shutdown(); } @Test public void testSendAndReceiveMessageOverTLS() { int numberOfMessagesToSend = 16; producer.send(numberOfMessagesToSend); boolean consumedAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer.getListener()); Assertions.assertThat(consumedAll).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/tls/TlsMix2IT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.tls; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Before; import org.junit.Test; public class TlsMix2IT extends BaseConf { private RMQNormalProducer producer; private RMQNormalConsumer consumer; private String topic; @Before public void setUp() { topic = initTopic(); // send message via TLS producer = getProducer(NAMESRV_ADDR, topic, true); // Receive message without TLS. consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener(), false); } @After public void tearDown() { shutdown(); } @Test public void testSendAndReceiveMessageOverTLS() { int numberOfMessagesToSend = 16; producer.send(numberOfMessagesToSend); boolean consumedAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer.getListener()); Assertions.assertThat(consumedAll).isEqualTo(true); } } ================================================ FILE: test/src/test/java/org/apache/rocketmq/test/tls/TlsMixIT.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.test.tls; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; import org.apache.rocketmq.test.util.MQWait; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Before; import org.junit.Test; public class TlsMixIT extends BaseConf { private RMQNormalProducer producer; private RMQNormalConsumer consumer; private String topic; @Before public void setUp() { topic = initTopic(); // send message without TLS producer = getProducer(NAMESRV_ADDR, topic); // Receive message via TLS consumer = getConsumer(NAMESRV_ADDR, topic, "*", new RMQNormalListener(), true); } @After public void tearDown() { shutdown(); } @Test public void testSendAndReceiveMessageOverTLS() { int numberOfMessagesToSend = 16; producer.send(numberOfMessagesToSend); boolean consumedAll = MQWait.waitConsumeAll(CONSUME_TIME, producer.getAllMsgBody(), consumer.getListener()); Assertions.assertThat(consumedAll).isEqualTo(true); } } ================================================ FILE: test/src/test/resources/rmq-proxy-home/conf/broker.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH ================================================ FILE: test/src/test/resources/rmq-proxy-home/conf/logback_proxy.xml ================================================ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}proxy.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}proxy.%i.log.gz 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}grpc.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}grpc.%i.log.gz 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker_default.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker_default.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}broker.%i.log.gz 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}protection.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}protection.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}watermark.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}watermark.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}store.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}store.%i.log.gz 1 10 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}remoting.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}remoting.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}storeerror.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}storeerror.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}transaction.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}transaction.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}lock.log true ${user.home}${file.separator}log${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}lock.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}filter.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}filter.%i.log.gz 1 10 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}stats.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}stats.%i.log.gz 1 5 100MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n UTF-8 ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}${brokerLogDir:-${file.separator}}${file.separator}commercial.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}${brokerLogDir:-${file.separator}}${file.separator}commercial.%i.log.gz 1 10 500MB ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}pop.log true ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}qpop.%i.log 1 20 128MB %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 true %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n UTF-8 ================================================ FILE: test/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json ================================================ { "proxyMode": "cluster" } ================================================ FILE: test/src/test/resources/rmq.logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n ================================================ FILE: test/src/test/resources/schema/api/client.consumer.AllocateMessageQueueStrategy.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method allocate(java.lang.String,java.lang.String,java.util.List,java.util.List) : public throws (java.util.List) Method getName() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.DefaultLitePullConsumer.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field MIN_AUTOCOMMIT_INTERVAL_MILLIS : private long 1000 Field SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY : public java.lang.String com.rocketmq.sendMessageWithVIPChannel Field accessChannel : protected org.apache.rocketmq.client.AccessChannel LOCAL Field allocateMessageQueueStrategy : private org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy null Field autoCommit : private boolean true Field autoCommitIntervalMillis : private long 5000 Field brokerSuspendMaxTimeMillis : private long 20000 Field clientCallbackExecutorThreads : private int null Field clientIP : private java.lang.String null Field consumeFromWhere : private org.apache.rocketmq.common.consumer.ConsumeFromWhere CONSUME_FROM_LAST_OFFSET Field consumeMaxSpan : private int 2000 Field consumeTimestamp : private java.lang.String null Field consumerGroup : private java.lang.String DEFAULT_CONSUMER Field consumerPullTimeoutMillis : private long 10000 Field consumerTimeoutMillisWhenSuspend : private long 30000 Field customizedTraceTopic : private java.lang.String null Field defaultLitePullConsumerImpl : private org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl null Field enableMsgTrace : private boolean false Field enableStreamRequestType : protected boolean true Field heartbeatBrokerInterval : private int 30000 Field instanceName : private java.lang.String DEFAULT Field language : private org.apache.rocketmq.remoting.protocol.LanguageCode JAVA Field log : private org.apache.rocketmq.logging.InternalLogger null Field messageModel : private org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel CLUSTERING Field messageQueueListener : private org.apache.rocketmq.client.consumer.MessageQueueListener null Field mqClientApiTimeout : private int 3000 Field namespace : protected java.lang.String null Field namespaceInitialized : private boolean false Field namesrvAddr : private java.lang.String null Field offsetStore : private org.apache.rocketmq.client.consumer.store.OffsetStore null Field persistConsumerOffsetInterval : private int 5000 Field pollNameServerInterval : private int 30000 Field pollTimeoutMillis : private long 5000 Field pullBatchSize : private int 10 Field pullThreadNums : private int 20 Field pullThresholdForAll : private long 10000 Field pullThresholdForQueue : private int 1000 Field pullThresholdSizeForQueue : private int 100 Field pullTimeDelayMillsWhenException : private long 1000 Field topicMetadataCheckIntervalMillis : private long 30000 Field traceDispatcher : private org.apache.rocketmq.client.trace.TraceDispatcher null Field unitMode : private boolean false Field unitName : private java.lang.String null Field useTLS : private boolean false Field vipChannelEnabled : private boolean false Method assign(java.util.Collection) : public throws (void) Method buildMQClientId() : public throws (java.lang.String) Method changeInstanceNameToPID() : public throws (void) Method cloneClientConfig() : public throws (org.apache.rocketmq.client.ClientConfig) Method commit(boolean,java.util.Set) : public throws (void) Method commitSync() : public throws (void) Method committed(org.apache.rocketmq.common.message.MessageQueue) : public throws (java.lang.Long) Method fetchMessageQueues(java.lang.String) : public throws (java.util.Collection) Method getAccessChannel() : public throws (org.apache.rocketmq.client.AccessChannel) Method getAllocateMessageQueueStrategy() : public throws (org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy) Method getAutoCommitIntervalMillis() : public throws (long) Method getBrokerSuspendMaxTimeMillis() : public throws (long) Method getClientCallbackExecutorThreads() : public throws (int) Method getClientIP() : public throws (java.lang.String) Method getConsumeFromWhere() : public throws (org.apache.rocketmq.common.consumer.ConsumeFromWhere) Method getConsumeMaxSpan() : public throws (int) Method getConsumeTimestamp() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method getConsumerPullTimeoutMillis() : public throws (long) Method getConsumerTimeoutMillisWhenSuspend() : public throws (long) Method getCustomizedTraceTopic() : public throws (java.lang.String) Method getDefaultBrokerId() : public throws (long) Method getHeartbeatBrokerInterval() : public throws (int) Method getInstanceName() : public throws (java.lang.String) Method getLanguage() : public throws (org.apache.rocketmq.remoting.protocol.LanguageCode) Method getMessageModel() : public throws (org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel) Method getMessageQueueListener() : public throws (org.apache.rocketmq.client.consumer.MessageQueueListener) Method getMqClientApiTimeout() : public throws (int) Method getNamespace() : public throws (java.lang.String) Method getNamesrvAddr() : public throws (java.lang.String) Method getOffsetStore() : public throws (org.apache.rocketmq.client.consumer.store.OffsetStore) Method getPersistConsumerOffsetInterval() : public throws (int) Method getPollNameServerInterval() : public throws (int) Method getPollTimeoutMillis() : public throws (long) Method getPullBatchSize() : public throws (int) Method getPullThreadNums() : public throws (int) Method getPullThresholdForAll() : public throws (long) Method getPullThresholdForQueue() : public throws (int) Method getPullThresholdSizeForQueue() : public throws (int) Method getPullTimeDelayMillsWhenException() : public throws (long) Method getTopicMetadataCheckIntervalMillis() : public throws (long) Method getTraceDispatcher() : public throws (org.apache.rocketmq.client.trace.TraceDispatcher) Method getUnitName() : public throws (java.lang.String) Method isAutoCommit() : public throws (boolean) Method isConnectBrokerByUser() : public throws (boolean) Method isEnableMsgTrace() : public throws (boolean) Method isEnableStreamRequestType() : public throws (boolean) Method isRunning() : public throws (boolean) Method isUnitMode() : public throws (boolean) Method isUseTLS() : public throws (boolean) Method isVipChannelEnabled() : public throws (boolean) Method offsetForTimestamp(java.lang.Long,org.apache.rocketmq.common.message.MessageQueue) : public throws (java.lang.Long) Method pause(java.util.Collection) : public throws (void) Method poll() : public throws (java.util.List) Method poll(long) : public throws (java.util.List) Method queueWithNamespace(org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.common.message.MessageQueue) Method queuesWithNamespace(java.util.Collection) : public throws (java.util.Collection) Method registerTopicMessageQueueChangeListener(java.lang.String,org.apache.rocketmq.client.consumer.TopicMessageQueueChangeListener) : public throws (void) Method resetClientConfig(org.apache.rocketmq.client.ClientConfig) : public throws (void) Method resume(java.util.Collection) : public throws (void) Method seek(long,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method seekToBegin(org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method seekToEnd(org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method setAccessChannel(org.apache.rocketmq.client.AccessChannel) : public throws (void) Method setAllocateMessageQueueStrategy(org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy) : public throws (void) Method setAutoCommit(boolean) : public throws (void) Method setAutoCommitIntervalMillis(long) : public throws (void) Method setClientCallbackExecutorThreads(int) : public throws (void) Method setClientIP(java.lang.String) : public throws (void) Method setConnectBrokerByUser(boolean) : public throws (void) Method setConsumeFromWhere(org.apache.rocketmq.common.consumer.ConsumeFromWhere) : public throws (void) Method setConsumeMaxSpan(int) : public throws (void) Method setConsumeTimestamp(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setConsumerPullTimeoutMillis(long) : public throws (void) Method setConsumerTimeoutMillisWhenSuspend(long) : public throws (void) Method setCustomizedTraceTopic(java.lang.String) : public throws (void) Method setDefaultBrokerId(long) : public throws (void) Method setEnableMsgTrace(boolean) : public throws (void) Method setEnableStreamRequestType(boolean) : public throws (void) Method setHeartbeatBrokerInterval(int) : public throws (void) Method setInstanceName(java.lang.String) : public throws (void) Method setLanguage(org.apache.rocketmq.remoting.protocol.LanguageCode) : public throws (void) Method setMessageModel(org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel) : public throws (void) Method setMessageQueueListener(org.apache.rocketmq.client.consumer.MessageQueueListener) : public throws (void) Method setMqClientApiTimeout(int) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setNamesrvAddr(java.lang.String) : public throws (void) Method setOffsetStore(org.apache.rocketmq.client.consumer.store.OffsetStore) : public throws (void) Method setPersistConsumerOffsetInterval(int) : public throws (void) Method setPollNameServerInterval(int) : public throws (void) Method setPollTimeoutMillis(long) : public throws (void) Method setPullBatchSize(int) : public throws (void) Method setPullThreadNums(int) : public throws (void) Method setPullThresholdForAll(long) : public throws (void) Method setPullThresholdForQueue(int) : public throws (void) Method setPullThresholdSizeForQueue(int) : public throws (void) Method setPullTimeDelayMillsWhenException(long) : public throws (void) Method setTopicMetadataCheckIntervalMillis(long) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method setUnitName(java.lang.String) : public throws (void) Method setUseTLS(boolean) : public throws (void) Method setVipChannelEnabled(boolean) : public throws (void) Method shutdown() : public throws (void) Method start() : public throws (void) Method subscribe(java.lang.String,java.lang.String) : public throws (void) Method subscribe(java.lang.String,org.apache.rocketmq.client.consumer.MessageSelector) : public throws (void) Method toString() : public throws (java.lang.String) Method unsubscribe(java.lang.String) : public throws (void) Method updateNameServerAddress(java.lang.String) : public throws (void) Method withNamespace(java.lang.String) : public throws (java.lang.String) Method withNamespace(java.util.Set) : public throws (java.util.Set) Method withoutNamespace(java.lang.String) : public throws (java.lang.String) Method withoutNamespace(java.util.Set) : public throws (java.util.Set) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.DefaultMQPullConsumer.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY : public java.lang.String com.rocketmq.sendMessageWithVIPChannel Field accessChannel : protected org.apache.rocketmq.client.AccessChannel LOCAL Field allocateMessageQueueStrategy : private org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy null Field brokerSuspendMaxTimeMillis : private long 20000 Field clientCallbackExecutorThreads : private int null Field clientIP : private java.lang.String null Field consumerGroup : private java.lang.String DEFAULT_CONSUMER Field consumerPullTimeoutMillis : private long 10000 Field consumerTimeoutMillisWhenSuspend : private long 30000 Field defaultMQPullConsumerImpl : protected org.apache.rocketmq.client.impl.consumer.DefaultMQPullConsumerImpl null Field enableStreamRequestType : protected boolean true Field heartbeatBrokerInterval : private int 30000 Field instanceName : private java.lang.String DEFAULT Field language : private org.apache.rocketmq.remoting.protocol.LanguageCode JAVA Field maxReconsumeTimes : private int 16 Field messageModel : private org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel CLUSTERING Field messageQueueListener : private org.apache.rocketmq.client.consumer.MessageQueueListener null Field mqClientApiTimeout : private int 3000 Field namespace : protected java.lang.String null Field namespaceInitialized : private boolean false Field namesrvAddr : private java.lang.String null Field offsetStore : private org.apache.rocketmq.client.consumer.store.OffsetStore null Field persistConsumerOffsetInterval : private int 5000 Field pollNameServerInterval : private int 30000 Field pullTimeDelayMillsWhenException : private long 1000 Field registerTopics : private java.util.Set null Field unitMode : private boolean false Field unitName : private java.lang.String null Field useTLS : private boolean false Field vipChannelEnabled : private boolean false Method buildMQClientId() : public throws (java.lang.String) Method changeInstanceNameToPID() : public throws (void) Method cloneClientConfig() : public throws (org.apache.rocketmq.client.ClientConfig) Method createTopic(int,int,java.lang.String,java.lang.String) : public throws (void) Method createTopic(int,java.lang.String,java.lang.String) : public throws (void) Method earliestMsgStoreTime(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method fetchConsumeOffset(boolean,org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method fetchMessageQueuesInBalance(java.lang.String) : public throws (java.util.Set) Method fetchSubscribeMessageQueues(java.lang.String) : public throws (java.util.Set) Method getAccessChannel() : public throws (org.apache.rocketmq.client.AccessChannel) Method getAllocateMessageQueueStrategy() : public throws (org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy) Method getBrokerSuspendMaxTimeMillis() : public throws (long) Method getClientCallbackExecutorThreads() : public throws (int) Method getClientIP() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method getConsumerPullTimeoutMillis() : public throws (long) Method getConsumerTimeoutMillisWhenSuspend() : public throws (long) Method getDefaultMQPullConsumerImpl() : public throws (org.apache.rocketmq.client.impl.consumer.DefaultMQPullConsumerImpl) Method getHeartbeatBrokerInterval() : public throws (int) Method getInstanceName() : public throws (java.lang.String) Method getLanguage() : public throws (org.apache.rocketmq.remoting.protocol.LanguageCode) Method getMaxReconsumeTimes() : public throws (int) Method getMessageModel() : public throws (org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel) Method getMessageQueueListener() : public throws (org.apache.rocketmq.client.consumer.MessageQueueListener) Method getMqClientApiTimeout() : public throws (int) Method getNamespace() : public throws (java.lang.String) Method getNamesrvAddr() : public throws (java.lang.String) Method getOffsetStore() : public throws (org.apache.rocketmq.client.consumer.store.OffsetStore) Method getPersistConsumerOffsetInterval() : public throws (int) Method getPollNameServerInterval() : public throws (int) Method getPullTimeDelayMillsWhenException() : public throws (long) Method getRegisterTopics() : public throws (java.util.Set) Method getUnitName() : public throws (java.lang.String) Method isEnableStreamRequestType() : public throws (boolean) Method isUnitMode() : public throws (boolean) Method isUseTLS() : public throws (boolean) Method isVipChannelEnabled() : public throws (boolean) Method maxOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method minOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method pull(int,java.lang.String,long,long,org.apache.rocketmq.client.consumer.PullCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method pull(int,java.lang.String,long,long,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.consumer.PullResult) Method pull(int,java.lang.String,long,org.apache.rocketmq.client.consumer.PullCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method pull(int,java.lang.String,long,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.consumer.PullResult) Method pull(int,long,long,org.apache.rocketmq.client.consumer.MessageSelector,org.apache.rocketmq.client.consumer.PullCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method pull(int,long,long,org.apache.rocketmq.client.consumer.MessageSelector,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.consumer.PullResult) Method pull(int,long,org.apache.rocketmq.client.consumer.MessageSelector,org.apache.rocketmq.client.consumer.PullCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method pull(int,long,org.apache.rocketmq.client.consumer.MessageSelector,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.consumer.PullResult) Method pullBlockIfNotFound(int,java.lang.String,long,org.apache.rocketmq.client.consumer.PullCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method pullBlockIfNotFound(int,java.lang.String,long,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.consumer.PullResult) Method queryMessage(int,java.lang.String,java.lang.String,long,long) : public throws (org.apache.rocketmq.client.QueryResult) Method queueWithNamespace(org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.common.message.MessageQueue) Method queuesWithNamespace(java.util.Collection) : public throws (java.util.Collection) Method registerMessageQueueListener(java.lang.String,org.apache.rocketmq.client.consumer.MessageQueueListener) : public throws (void) Method resetClientConfig(org.apache.rocketmq.client.ClientConfig) : public throws (void) Method searchOffset(long,org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method sendMessageBack(int,java.lang.String,java.lang.String,org.apache.rocketmq.common.message.MessageExt) : public throws (void) Method sendMessageBack(int,java.lang.String,org.apache.rocketmq.common.message.MessageExt) : public throws (void) Method sendMessageBack(int,org.apache.rocketmq.common.message.MessageExt) : public throws (void) Method setAccessChannel(org.apache.rocketmq.client.AccessChannel) : public throws (void) Method setAllocateMessageQueueStrategy(org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy) : public throws (void) Method setBrokerSuspendMaxTimeMillis(long) : public throws (void) Method setClientCallbackExecutorThreads(int) : public throws (void) Method setClientIP(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setConsumerPullTimeoutMillis(long) : public throws (void) Method setConsumerTimeoutMillisWhenSuspend(long) : public throws (void) Method setEnableStreamRequestType(boolean) : public throws (void) Method setHeartbeatBrokerInterval(int) : public throws (void) Method setInstanceName(java.lang.String) : public throws (void) Method setLanguage(org.apache.rocketmq.remoting.protocol.LanguageCode) : public throws (void) Method setMaxReconsumeTimes(int) : public throws (void) Method setMessageModel(org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel) : public throws (void) Method setMessageQueueListener(org.apache.rocketmq.client.consumer.MessageQueueListener) : public throws (void) Method setMqClientApiTimeout(int) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setNamesrvAddr(java.lang.String) : public throws (void) Method setOffsetStore(org.apache.rocketmq.client.consumer.store.OffsetStore) : public throws (void) Method setPersistConsumerOffsetInterval(int) : public throws (void) Method setPollNameServerInterval(int) : public throws (void) Method setPullTimeDelayMillsWhenException(long) : public throws (void) Method setRegisterTopics(java.util.Set) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method setUnitName(java.lang.String) : public throws (void) Method setUseTLS(boolean) : public throws (void) Method setVipChannelEnabled(boolean) : public throws (void) Method shutdown() : public throws (void) Method start() : public throws (void) Method toString() : public throws (java.lang.String) Method updateConsumeOffset(long,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method viewMessage(java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method viewMessage(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method withNamespace(java.lang.String) : public throws (java.lang.String) Method withNamespace(java.util.Set) : public throws (java.util.Set) Method withoutNamespace(java.lang.String) : public throws (java.lang.String) Method withoutNamespace(java.util.Set) : public throws (java.util.Set) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.DefaultMQPushConsumer.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY : public java.lang.String com.rocketmq.sendMessageWithVIPChannel Field accessChannel : protected org.apache.rocketmq.client.AccessChannel LOCAL Field adjustThreadPoolNumsThreshold : private long 100000 Field allocateMessageQueueStrategy : private org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy null Field awaitTerminationMillisWhenShutdown : private long 0 Field clientCallbackExecutorThreads : private int null Field clientIP : private java.lang.String null Field consumeConcurrentlyMaxSpan : private int 2000 Field consumeFromWhere : private org.apache.rocketmq.common.consumer.ConsumeFromWhere CONSUME_FROM_LAST_OFFSET Field consumeMessageBatchMaxSize : private int 1 Field consumeThreadMax : private int 20 Field consumeThreadMin : private int 20 Field consumeTimeout : private long 15 Field consumeTimestamp : private java.lang.String null Field consumerGroup : private java.lang.String DEFAULT_CONSUMER Field defaultMQPushConsumerImpl : protected org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl null Field enableStreamRequestType : protected boolean false Field heartbeatBrokerInterval : private int 30000 Field instanceName : private java.lang.String DEFAULT Field language : private org.apache.rocketmq.remoting.protocol.LanguageCode JAVA Field log : private org.apache.rocketmq.logging.InternalLogger null Field maxReconsumeTimes : private int -1 Field messageListener : private org.apache.rocketmq.client.consumer.listener.MessageListener null Field messageModel : private org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel CLUSTERING Field mqClientApiTimeout : private int 3000 Field namespace : protected java.lang.String null Field namespaceInitialized : private boolean false Field namesrvAddr : private java.lang.String null Field offsetStore : private org.apache.rocketmq.client.consumer.store.OffsetStore null Field persistConsumerOffsetInterval : private int 5000 Field pollNameServerInterval : private int 30000 Field postSubscriptionWhenPull : private boolean false Field pullBatchSize : private int 32 Field pullInterval : private long 0 Field pullThresholdForQueue : private int 1000 Field pullThresholdForTopic : private int -1 Field pullThresholdSizeForQueue : private int 100 Field pullThresholdSizeForTopic : private int -1 Field pullTimeDelayMillsWhenException : private long 1000 Field subscription : private java.util.Map null Field suspendCurrentQueueTimeMillis : private long 1000 Field traceDispatcher : private org.apache.rocketmq.client.trace.TraceDispatcher null Field unitMode : private boolean false Field unitName : private java.lang.String null Field useTLS : private boolean false Field vipChannelEnabled : private boolean false Method buildMQClientId() : public throws (java.lang.String) Method changeInstanceNameToPID() : public throws (void) Method cloneClientConfig() : public throws (org.apache.rocketmq.client.ClientConfig) Method createTopic(int,int,java.lang.String,java.lang.String) : public throws (void) Method createTopic(int,java.lang.String,java.lang.String) : public throws (void) Method earliestMsgStoreTime(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method fetchSubscribeMessageQueues(java.lang.String) : public throws (java.util.Set) Method getAccessChannel() : public throws (org.apache.rocketmq.client.AccessChannel) Method getAdjustThreadPoolNumsThreshold() : public throws (long) Method getAllocateMessageQueueStrategy() : public throws (org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy) Method getAwaitTerminationMillisWhenShutdown() : public throws (long) Method getClientCallbackExecutorThreads() : public throws (int) Method getClientIP() : public throws (java.lang.String) Method getConsumeConcurrentlyMaxSpan() : public throws (int) Method getConsumeFromWhere() : public throws (org.apache.rocketmq.common.consumer.ConsumeFromWhere) Method getConsumeMessageBatchMaxSize() : public throws (int) Method getConsumeThreadMax() : public throws (int) Method getConsumeThreadMin() : public throws (int) Method getConsumeTimeout() : public throws (long) Method getConsumeTimestamp() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method getDefaultMQPushConsumerImpl() : public throws (org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl) Method getHeartbeatBrokerInterval() : public throws (int) Method getInstanceName() : public throws (java.lang.String) Method getLanguage() : public throws (org.apache.rocketmq.remoting.protocol.LanguageCode) Method getMaxReconsumeTimes() : public throws (int) Method getMessageListener() : public throws (org.apache.rocketmq.client.consumer.listener.MessageListener) Method getMessageModel() : public throws (org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel) Method getMqClientApiTimeout() : public throws (int) Method getNamespace() : public throws (java.lang.String) Method getNamesrvAddr() : public throws (java.lang.String) Method getOffsetStore() : public throws (org.apache.rocketmq.client.consumer.store.OffsetStore) Method getPersistConsumerOffsetInterval() : public throws (int) Method getPollNameServerInterval() : public throws (int) Method getPullBatchSize() : public throws (int) Method getPullInterval() : public throws (long) Method getPullThresholdForQueue() : public throws (int) Method getPullThresholdForTopic() : public throws (int) Method getPullThresholdSizeForQueue() : public throws (int) Method getPullThresholdSizeForTopic() : public throws (int) Method getPullTimeDelayMillsWhenException() : public throws (long) Method getSubscription() : public throws (java.util.Map) Method getSuspendCurrentQueueTimeMillis() : public throws (long) Method getTraceDispatcher() : public throws (org.apache.rocketmq.client.trace.TraceDispatcher) Method getUnitName() : public throws (java.lang.String) Method isEnableStreamRequestType() : public throws (boolean) Method isPostSubscriptionWhenPull() : public throws (boolean) Method isUnitMode() : public throws (boolean) Method isUseTLS() : public throws (boolean) Method isVipChannelEnabled() : public throws (boolean) Method maxOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method minOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method queryMessage(int,java.lang.String,java.lang.String,long,long) : public throws (org.apache.rocketmq.client.QueryResult) Method queueWithNamespace(org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.common.message.MessageQueue) Method queuesWithNamespace(java.util.Collection) : public throws (java.util.Collection) Method registerMessageListener(org.apache.rocketmq.client.consumer.listener.MessageListener) : public throws (void) Method registerMessageListener(org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently) : public throws (void) Method registerMessageListener(org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly) : public throws (void) Method resetClientConfig(org.apache.rocketmq.client.ClientConfig) : public throws (void) Method resume() : public throws (void) Method searchOffset(long,org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method sendMessageBack(int,java.lang.String,org.apache.rocketmq.common.message.MessageExt) : public throws (void) Method sendMessageBack(int,org.apache.rocketmq.common.message.MessageExt) : public throws (void) Method setAccessChannel(org.apache.rocketmq.client.AccessChannel) : public throws (void) Method setAdjustThreadPoolNumsThreshold(long) : public throws (void) Method setAllocateMessageQueueStrategy(org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy) : public throws (void) Method setAwaitTerminationMillisWhenShutdown(long) : public throws (void) Method setClientCallbackExecutorThreads(int) : public throws (void) Method setClientIP(java.lang.String) : public throws (void) Method setConsumeConcurrentlyMaxSpan(int) : public throws (void) Method setConsumeFromWhere(org.apache.rocketmq.common.consumer.ConsumeFromWhere) : public throws (void) Method setConsumeMessageBatchMaxSize(int) : public throws (void) Method setConsumeThreadMax(int) : public throws (void) Method setConsumeThreadMin(int) : public throws (void) Method setConsumeTimeout(long) : public throws (void) Method setConsumeTimestamp(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setEnableStreamRequestType(boolean) : public throws (void) Method setHeartbeatBrokerInterval(int) : public throws (void) Method setInstanceName(java.lang.String) : public throws (void) Method setLanguage(org.apache.rocketmq.remoting.protocol.LanguageCode) : public throws (void) Method setMaxReconsumeTimes(int) : public throws (void) Method setMessageListener(org.apache.rocketmq.client.consumer.listener.MessageListener) : public throws (void) Method setMessageModel(org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel) : public throws (void) Method setMqClientApiTimeout(int) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setNamesrvAddr(java.lang.String) : public throws (void) Method setOffsetStore(org.apache.rocketmq.client.consumer.store.OffsetStore) : public throws (void) Method setPersistConsumerOffsetInterval(int) : public throws (void) Method setPollNameServerInterval(int) : public throws (void) Method setPostSubscriptionWhenPull(boolean) : public throws (void) Method setPullBatchSize(int) : public throws (void) Method setPullInterval(long) : public throws (void) Method setPullThresholdForQueue(int) : public throws (void) Method setPullThresholdForTopic(int) : public throws (void) Method setPullThresholdSizeForQueue(int) : public throws (void) Method setPullThresholdSizeForTopic(int) : public throws (void) Method setPullTimeDelayMillsWhenException(long) : public throws (void) Method setSubscription(java.util.Map) : public throws (void) Method setSuspendCurrentQueueTimeMillis(long) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method setUnitName(java.lang.String) : public throws (void) Method setUseTLS(boolean) : public throws (void) Method setVipChannelEnabled(boolean) : public throws (void) Method shutdown() : public throws (void) Method start() : public throws (void) Method subscribe(java.lang.String,java.lang.String) : public throws (void) Method subscribe(java.lang.String,java.lang.String,java.lang.String) : public throws (void) Method subscribe(java.lang.String,org.apache.rocketmq.client.consumer.MessageSelector) : public throws (void) Method suspend() : public throws (void) Method toString() : public throws (java.lang.String) Method unsubscribe(java.lang.String) : public throws (void) Method updateCorePoolSize(int) : public throws (void) Method viewMessage(java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method viewMessage(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method withNamespace(java.lang.String) : public throws (java.lang.String) Method withNamespace(java.util.Set) : public throws (java.util.Set) Method withoutNamespace(java.lang.String) : public throws (java.lang.String) Method withoutNamespace(java.util.Set) : public throws (java.util.Set) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.PullCallback.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method onException(java.lang.Throwable) : public throws (void) Method onSuccess(org.apache.rocketmq.client.consumer.PullResult) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.PullResult.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field maxOffset : private long 0 Field minOffset : private long 0 Field msgFoundList : private java.util.List null Field nextBeginOffset : private long 0 Field pullStatus : private org.apache.rocketmq.client.consumer.PullStatus FOUND Method getMaxOffset() : public throws (long) Method getMinOffset() : public throws (long) Method getMsgFoundList() : public throws (java.util.List) Method getNextBeginOffset() : public throws (long) Method getPullStatus() : public throws (org.apache.rocketmq.client.consumer.PullStatus) Method setMsgFoundList(java.util.List) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.PullStatus.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field FOUND : public int 0 Field NO_MATCHED_MSG : public int 2 Field NO_NEW_MSG : public int 1 Field OFFSET_ILLEGAL : public int 3 ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.ConsumeConcurrentlyContext.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field ackIndex : private int 2147483647 Field delayLevelWhenNextConsume : private int 0 Field messageQueue : private org.apache.rocketmq.common.message.MessageQueue null Method getAckIndex() : public throws (int) Method getDelayLevelWhenNextConsume() : public throws (int) Method getMessageQueue() : public throws (org.apache.rocketmq.common.message.MessageQueue) Method setAckIndex(int) : public throws (void) Method setDelayLevelWhenNextConsume(int) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.ConsumeConcurrentlyStatus.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field CONSUME_SUCCESS : public int 0 Field RECONSUME_LATER : public int 1 ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.ConsumeOrderlyContext.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field autoCommit : private boolean true Field messageQueue : private org.apache.rocketmq.common.message.MessageQueue null Field suspendCurrentQueueTimeMillis : private long -1 Method getMessageQueue() : public throws (org.apache.rocketmq.common.message.MessageQueue) Method getSuspendCurrentQueueTimeMillis() : public throws (long) Method isAutoCommit() : public throws (boolean) Method setAutoCommit(boolean) : public throws (void) Method setSuspendCurrentQueueTimeMillis(long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.ConsumeOrderlyStatus.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field COMMIT : public int 2 Field ROLLBACK : public int 1 Field SUCCESS : public int 0 Field SUSPEND_CURRENT_QUEUE_A_MOMENT : public int 3 ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.MessageListener.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.MessageListenerConcurrently.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method consumeMessage(java.util.List,org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext) : public throws (org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus) ================================================ FILE: test/src/test/resources/schema/api/client.consumer.listener.MessageListenerOrderly.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method consumeMessage(java.util.List,org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext) : public throws (org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus) ================================================ FILE: test/src/test/resources/schema/api/client.hook.CheckForbiddenHook.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkForbidden(org.apache.rocketmq.client.hook.CheckForbiddenContext) : public throws (void) Method hookName() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.hook.ConsumeMessageContext.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Field mq : private org.apache.rocketmq.common.message.MessageQueue null Field mqTraceContext : private java.lang.Object null Field msgList : private java.util.List null Field namespace : private java.lang.String null Field props : private java.util.Map null Field status : private java.lang.String null Field success : private boolean false Method getConsumerGroup() : public throws (java.lang.String) Method getMq() : public throws (org.apache.rocketmq.common.message.MessageQueue) Method getMqTraceContext() : public throws (java.lang.Object) Method getMsgList() : public throws (java.util.List) Method getNamespace() : public throws (java.lang.String) Method getProps() : public throws (java.util.Map) Method getStatus() : public throws (java.lang.String) Method isSuccess() : public throws (boolean) Method setConsumerGroup(java.lang.String) : public throws (void) Method setMq(org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method setMqTraceContext(java.lang.Object) : public throws (void) Method setMsgList(java.util.List) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setProps(java.util.Map) : public throws (void) Method setStatus(java.lang.String) : public throws (void) Method setSuccess(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.hook.ConsumeMessageHook.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method consumeMessageAfter(org.apache.rocketmq.client.hook.ConsumeMessageContext) : public throws (void) Method consumeMessageBefore(org.apache.rocketmq.client.hook.ConsumeMessageContext) : public throws (void) Method hookName() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.hook.EndTransactionContext.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerAddr : private java.lang.String null Field fromTransactionCheck : private boolean false Field message : private org.apache.rocketmq.common.message.Message null Field msgId : private java.lang.String null Field producerGroup : private java.lang.String null Field transactionId : private java.lang.String null Field transactionState : private org.apache.rocketmq.client.producer.LocalTransactionState null Method getBrokerAddr() : public throws (java.lang.String) Method getMessage() : public throws (org.apache.rocketmq.common.message.Message) Method getMsgId() : public throws (java.lang.String) Method getProducerGroup() : public throws (java.lang.String) Method getTransactionId() : public throws (java.lang.String) Method getTransactionState() : public throws (org.apache.rocketmq.client.producer.LocalTransactionState) Method isFromTransactionCheck() : public throws (boolean) Method setBrokerAddr(java.lang.String) : public throws (void) Method setFromTransactionCheck(boolean) : public throws (void) Method setMessage(org.apache.rocketmq.common.message.Message) : public throws (void) Method setMsgId(java.lang.String) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) Method setTransactionState(org.apache.rocketmq.client.producer.LocalTransactionState) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.hook.EndTransactionHook.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method endTransaction(org.apache.rocketmq.client.hook.EndTransactionContext) : public throws (void) Method hookName() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.hook.FilterMessageContext.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field arg : private java.lang.Object null Field consumerGroup : private java.lang.String null Field mq : private org.apache.rocketmq.common.message.MessageQueue null Field msgList : private java.util.List null Field unitMode : private boolean false Method getArg() : public throws (java.lang.Object) Method getConsumerGroup() : public throws (java.lang.String) Method getMq() : public throws (org.apache.rocketmq.common.message.MessageQueue) Method getMsgList() : public throws (java.util.List) Method isUnitMode() : public throws (boolean) Method setArg(java.lang.Object) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setMq(org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method setMsgList(java.util.List) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.hook.FilterMessageHook.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method filterMessage(org.apache.rocketmq.client.hook.FilterMessageContext) : public throws (void) Method hookName() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.hook.SendMessageContext.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field bornHost : private java.lang.String null Field brokerAddr : private java.lang.String null Field communicationMode : private org.apache.rocketmq.client.impl.CommunicationMode null Field exception : private java.lang.Exception null Field message : private org.apache.rocketmq.common.message.Message null Field mq : private org.apache.rocketmq.common.message.MessageQueue null Field mqTraceContext : private java.lang.Object null Field msgType : private org.apache.rocketmq.common.message.MessageType Normal_Msg Field namespace : private java.lang.String null Field producer : private org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl null Field producerGroup : private java.lang.String null Field props : private java.util.Map null Field sendResult : private org.apache.rocketmq.client.producer.SendResult null Method getBornHost() : public throws (java.lang.String) Method getBrokerAddr() : public throws (java.lang.String) Method getCommunicationMode() : public throws (org.apache.rocketmq.client.impl.CommunicationMode) Method getException() : public throws (java.lang.Exception) Method getMessage() : public throws (org.apache.rocketmq.common.message.Message) Method getMq() : public throws (org.apache.rocketmq.common.message.MessageQueue) Method getMqTraceContext() : public throws (java.lang.Object) Method getMsgType() : public throws (org.apache.rocketmq.common.message.MessageType) Method getNamespace() : public throws (java.lang.String) Method getProducer() : public throws (org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl) Method getProducerGroup() : public throws (java.lang.String) Method getProps() : public throws (java.util.Map) Method getSendResult() : public throws (org.apache.rocketmq.client.producer.SendResult) Method setBornHost(java.lang.String) : public throws (void) Method setBrokerAddr(java.lang.String) : public throws (void) Method setCommunicationMode(org.apache.rocketmq.client.impl.CommunicationMode) : public throws (void) Method setException(java.lang.Exception) : public throws (void) Method setMessage(org.apache.rocketmq.common.message.Message) : public throws (void) Method setMq(org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method setMqTraceContext(java.lang.Object) : public throws (void) Method setMsgType(org.apache.rocketmq.common.message.MessageType) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setProducer(org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setProps(java.util.Map) : public throws (void) Method setSendResult(org.apache.rocketmq.client.producer.SendResult) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.hook.SendMessageHook.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method hookName() : public throws (java.lang.String) Method sendMessageAfter(org.apache.rocketmq.client.hook.SendMessageContext) : public throws (void) Method sendMessageBefore(org.apache.rocketmq.client.hook.SendMessageContext) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.producer.DefaultMQProducer.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY : public java.lang.String com.rocketmq.sendMessageWithVIPChannel Field accessChannel : protected org.apache.rocketmq.client.AccessChannel LOCAL Field clientCallbackExecutorThreads : private int null Field clientIP : private java.lang.String null Field compressMsgBodyOverHowmuch : private int 4096 Field createTopicKey : private java.lang.String TBW102 Field defaultMQProducerImpl : protected org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl null Field defaultTopicQueueNums : private int 4 Field enableStreamRequestType : protected boolean false Field heartbeatBrokerInterval : private int 30000 Field instanceName : private java.lang.String DEFAULT Field language : private org.apache.rocketmq.remoting.protocol.LanguageCode JAVA Field log : private org.apache.rocketmq.logging.InternalLogger null Field maxMessageSize : private int 4194304 Field mqClientApiTimeout : private int 3000 Field namespace : protected java.lang.String null Field namespaceInitialized : private boolean false Field namesrvAddr : private java.lang.String null Field persistConsumerOffsetInterval : private int 5000 Field pollNameServerInterval : private int 30000 Field producerGroup : private java.lang.String DEFAULT_PRODUCER Field pullTimeDelayMillsWhenException : private long 1000 Field retryAnotherBrokerWhenNotStoreOK : private boolean false Field retryResponseCodes : private java.util.Set null Field retryTimesWhenSendAsyncFailed : private int 2 Field retryTimesWhenSendFailed : private int 2 Field sendMsgTimeout : private int 3000 Field traceDispatcher : private org.apache.rocketmq.client.trace.TraceDispatcher null Field unitMode : private boolean false Field unitName : private java.lang.String null Field useTLS : private boolean false Field vipChannelEnabled : private boolean false Method addRetryResponseCode(int) : public throws (void) Method buildMQClientId() : public throws (java.lang.String) Method changeInstanceNameToPID() : public throws (void) Method cloneClientConfig() : public throws (org.apache.rocketmq.client.ClientConfig) Method createTopic(int,int,java.lang.String,java.lang.String) : public throws (void) Method createTopic(int,java.lang.String,java.lang.String) : public throws (void) Method earliestMsgStoreTime(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method fetchPublishMessageQueues(java.lang.String) : public throws (java.util.List) Method getAccessChannel() : public throws (org.apache.rocketmq.client.AccessChannel) Method getClientCallbackExecutorThreads() : public throws (int) Method getClientIP() : public throws (java.lang.String) Method getCompressMsgBodyOverHowmuch() : public throws (int) Method getCreateTopicKey() : public throws (java.lang.String) Method getDefaultMQProducerImpl() : public throws (org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl) Method getDefaultTopicQueueNums() : public throws (int) Method getHeartbeatBrokerInterval() : public throws (int) Method getInstanceName() : public throws (java.lang.String) Method getLanguage() : public throws (org.apache.rocketmq.remoting.protocol.LanguageCode) Method getLatencyMax() : public throws ([J) Method getMaxMessageSize() : public throws (int) Method getMqClientApiTimeout() : public throws (int) Method getNamespace() : public throws (java.lang.String) Method getNamesrvAddr() : public throws (java.lang.String) Method getNotAvailableDuration() : public throws ([J) Method getPersistConsumerOffsetInterval() : public throws (int) Method getPollNameServerInterval() : public throws (int) Method getProducerGroup() : public throws (java.lang.String) Method getPullTimeDelayMillsWhenException() : public throws (long) Method getRetryResponseCodes() : public throws (java.util.Set) Method getRetryTimesWhenSendAsyncFailed() : public throws (int) Method getRetryTimesWhenSendFailed() : public throws (int) Method getSendMsgTimeout() : public throws (int) Method getTraceDispatcher() : public throws (org.apache.rocketmq.client.trace.TraceDispatcher) Method getUnitName() : public throws (java.lang.String) Method isEnableStreamRequestType() : public throws (boolean) Method isRetryAnotherBrokerWhenNotStoreOK() : public throws (boolean) Method isSendLatencyFaultEnable() : public throws (boolean) Method isSendMessageWithVIPChannel() : public throws (boolean) Method isUnitMode() : public throws (boolean) Method isUseTLS() : public throws (boolean) Method isVipChannelEnabled() : public throws (boolean) Method maxOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method minOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method queryMessage(int,java.lang.String,java.lang.String,long,long) : public throws (org.apache.rocketmq.client.QueryResult) Method queueWithNamespace(org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.common.message.MessageQueue) Method queuesWithNamespace(java.util.Collection) : public throws (java.util.Collection) Method request(java.lang.Object,long,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.client.producer.RequestCallback,org.apache.rocketmq.common.message.Message) : public throws (void) Method request(java.lang.Object,long,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.common.message.Message) Method request(long,org.apache.rocketmq.client.producer.RequestCallback,org.apache.rocketmq.common.message.Message) : public throws (void) Method request(long,org.apache.rocketmq.client.producer.RequestCallback,org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method request(long,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.common.message.Message) Method request(long,org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.common.message.Message) Method resetClientConfig(org.apache.rocketmq.client.ClientConfig) : public throws (void) Method searchOffset(long,org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method send(java.lang.Object,long,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.Message) : public throws (void) Method send(java.lang.Object,long,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(java.lang.Object,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.Message) : public throws (void) Method send(java.lang.Object,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(java.util.Collection) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(java.util.Collection,long) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(java.util.Collection,long,org.apache.rocketmq.client.producer.SendCallback) : public throws (void) Method send(java.util.Collection,long,org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method send(java.util.Collection,long,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(java.util.Collection,org.apache.rocketmq.client.producer.SendCallback) : public throws (void) Method send(java.util.Collection,org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method send(java.util.Collection,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(long,org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.Message) : public throws (void) Method send(long,org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method send(long,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(long,org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.Message) : public throws (void) Method send(org.apache.rocketmq.client.producer.SendCallback,org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method send(org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.client.producer.SendResult) Method send(org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.client.producer.SendResult) Method sendMessageInTransaction(java.lang.Object,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.client.producer.TransactionSendResult) Method sendOneway(java.lang.Object,org.apache.rocketmq.client.producer.MessageQueueSelector,org.apache.rocketmq.common.message.Message) : public throws (void) Method sendOneway(org.apache.rocketmq.common.message.Message) : public throws (void) Method sendOneway(org.apache.rocketmq.common.message.Message,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method setAccessChannel(org.apache.rocketmq.client.AccessChannel) : public throws (void) Method setAsyncSenderExecutor(java.util.concurrent.ExecutorService) : public throws (void) Method setCallbackExecutor(java.util.concurrent.ExecutorService) : public throws (void) Method setClientCallbackExecutorThreads(int) : public throws (void) Method setClientIP(java.lang.String) : public throws (void) Method setCompressMsgBodyOverHowmuch(int) : public throws (void) Method setCreateTopicKey(java.lang.String) : public throws (void) Method setDefaultTopicQueueNums(int) : public throws (void) Method setEnableStreamRequestType(boolean) : public throws (void) Method setHeartbeatBrokerInterval(int) : public throws (void) Method setInstanceName(java.lang.String) : public throws (void) Method setLanguage(org.apache.rocketmq.remoting.protocol.LanguageCode) : public throws (void) Method setLatencyMax([J) : public throws (void) Method setMaxMessageSize(int) : public throws (void) Method setMqClientApiTimeout(int) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setNamesrvAddr(java.lang.String) : public throws (void) Method setNotAvailableDuration([J) : public throws (void) Method setPersistConsumerOffsetInterval(int) : public throws (void) Method setPollNameServerInterval(int) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setPullTimeDelayMillsWhenException(long) : public throws (void) Method setRetryAnotherBrokerWhenNotStoreOK(boolean) : public throws (void) Method setRetryTimesWhenSendAsyncFailed(int) : public throws (void) Method setRetryTimesWhenSendFailed(int) : public throws (void) Method setSendLatencyFaultEnable(boolean) : public throws (void) Method setSendMessageWithVIPChannel(boolean) : public throws (void) Method setSendMsgTimeout(int) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method setUnitName(java.lang.String) : public throws (void) Method setUseTLS(boolean) : public throws (void) Method setVipChannelEnabled(boolean) : public throws (void) Method shutdown() : public throws (void) Method start() : public throws (void) Method toString() : public throws (java.lang.String) Method viewMessage(java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method viewMessage(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method withNamespace(java.lang.String) : public throws (java.lang.String) Method withNamespace(java.util.Set) : public throws (java.util.Set) Method withoutNamespace(java.lang.String) : public throws (java.lang.String) Method withoutNamespace(java.util.Set) : public throws (java.util.Set) ================================================ FILE: test/src/test/resources/schema/api/client.producer.MessageQueueSelector.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method select(java.lang.Object,java.util.List,org.apache.rocketmq.common.message.Message) : public throws (org.apache.rocketmq.common.message.MessageQueue) ================================================ FILE: test/src/test/resources/schema/api/client.producer.SendCallback.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method onException(java.lang.Throwable) : public throws (void) Method onSuccess(org.apache.rocketmq.client.producer.SendResult) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/client.producer.SendResult.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field messageQueue : private org.apache.rocketmq.common.message.MessageQueue null Field msgId : private java.lang.String null Field offsetMsgId : private java.lang.String null Field queueOffset : private long 0 Field regionId : private java.lang.String null Field sendStatus : private org.apache.rocketmq.client.producer.SendStatus null Field traceOn : private boolean true Field transactionId : private java.lang.String null Method decoderSendResultFromJson(java.lang.String) : public throws (org.apache.rocketmq.client.producer.SendResult) Method encoderSendResultToJson(java.lang.Object) : public throws (java.lang.String) Method getMessageQueue() : public throws (org.apache.rocketmq.common.message.MessageQueue) Method getMsgId() : public throws (java.lang.String) Method getOffsetMsgId() : public throws (java.lang.String) Method getQueueOffset() : public throws (long) Method getRegionId() : public throws (java.lang.String) Method getSendStatus() : public throws (org.apache.rocketmq.client.producer.SendStatus) Method getTransactionId() : public throws (java.lang.String) Method isTraceOn() : public throws (boolean) Method setMessageQueue(org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method setMsgId(java.lang.String) : public throws (void) Method setOffsetMsgId(java.lang.String) : public throws (void) Method setQueueOffset(long) : public throws (void) Method setRegionId(java.lang.String) : public throws (void) Method setSendStatus(org.apache.rocketmq.client.producer.SendStatus) : public throws (void) Method setTraceOn(boolean) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/client.producer.SendStatus.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field FLUSH_DISK_TIMEOUT : public int 1 Field FLUSH_SLAVE_TIMEOUT : public int 2 Field SEND_OK : public int 0 Field SLAVE_NOT_AVAILABLE : public int 3 ================================================ FILE: test/src/test/resources/schema/api/common.message.Message.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field body : private [B null Field flag : private int 0 Field properties : private java.util.Map null Field serialVersionUID : private long 8445773977080406428 Field topic : private java.lang.String null Field transactionId : private java.lang.String null Method getBody() : public throws ([B) Method getBuyerId() : public throws (java.lang.String) Method getDelayTimeLevel() : public throws (int) Method getFlag() : public throws (int) Method getKeys() : public throws (java.lang.String) Method getProperties() : public throws (java.util.Map) Method getProperty(java.lang.String) : public throws (java.lang.String) Method getTags() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method getTransactionId() : public throws (java.lang.String) Method getUserProperty(java.lang.String) : public throws (java.lang.String) Method isWaitStoreMsgOK() : public throws (boolean) Method putUserProperty(java.lang.String,java.lang.String) : public throws (void) Method setBody([B) : public throws (void) Method setBuyerId(java.lang.String) : public throws (void) Method setDelayTimeLevel(int) : public throws (void) Method setFlag(int) : public throws (void) Method setInstanceId(java.lang.String) : public throws (void) Method setKeys(java.lang.String) : public throws (void) Method setKeys(java.util.Collection) : public throws (void) Method setTags(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) Method setWaitStoreMsgOK(boolean) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/common.message.MessageExt.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field body : private [B null Field bodyCRC : private int 0 Field bornHost : private java.net.SocketAddress null Field bornTimestamp : private long 0 Field brokerName : private java.lang.String null Field commitLogOffset : private long 0 Field flag : private int 0 Field msgId : private java.lang.String null Field preparedTransactionOffset : private long 0 Field properties : private java.util.Map null Field queueId : private int 0 Field queueOffset : private long 0 Field reconsumeTimes : private int 0 Field serialVersionUID : private long 8445773977080406428 Field storeHost : private java.net.SocketAddress null Field storeSize : private int 0 Field storeTimestamp : private long 0 Field sysFlag : private int 0 Field topic : private java.lang.String null Field transactionId : private java.lang.String null Method getBody() : public throws ([B) Method getBodyCRC() : public throws (int) Method getBornHost() : public throws (java.net.SocketAddress) Method getBornHostBytes() : public throws (java.nio.ByteBuffer) Method getBornHostBytes(java.nio.ByteBuffer) : public throws (java.nio.ByteBuffer) Method getBornHostNameString() : public throws (java.lang.String) Method getBornHostString() : public throws (java.lang.String) Method getBornTimestamp() : public throws (long) Method getBrokerName() : public throws (java.lang.String) Method getBuyerId() : public throws (java.lang.String) Method getCommitLogOffset() : public throws (long) Method getDelayTimeLevel() : public throws (int) Method getFlag() : public throws (int) Method getKeys() : public throws (java.lang.String) Method getMsgId() : public throws (java.lang.String) Method getPreparedTransactionOffset() : public throws (long) Method getProperties() : public throws (java.util.Map) Method getProperty(java.lang.String) : public throws (java.lang.String) Method getQueueId() : public throws (int) Method getQueueOffset() : public throws (long) Method getReconsumeTimes() : public throws (int) Method getStoreHost() : public throws (java.net.SocketAddress) Method getStoreHostBytes() : public throws (java.nio.ByteBuffer) Method getStoreHostBytes(java.nio.ByteBuffer) : public throws (java.nio.ByteBuffer) Method getStoreSize() : public throws (int) Method getStoreTimestamp() : public throws (long) Method getSysFlag() : public throws (int) Method getTags() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method getTransactionId() : public throws (java.lang.String) Method getUserProperty(java.lang.String) : public throws (java.lang.String) Method isWaitStoreMsgOK() : public throws (boolean) Method parseTopicFilterType(int) : public throws (org.apache.rocketmq.common.TopicFilterType) Method putUserProperty(java.lang.String,java.lang.String) : public throws (void) Method setBody([B) : public throws (void) Method setBodyCRC(int) : public throws (void) Method setBornHost(java.net.SocketAddress) : public throws (void) Method setBornHostV6Flag() : public throws (void) Method setBornTimestamp(long) : public throws (void) Method setBrokerName(java.lang.String) : public throws (void) Method setBuyerId(java.lang.String) : public throws (void) Method setCommitLogOffset(long) : public throws (void) Method setDelayTimeLevel(int) : public throws (void) Method setFlag(int) : public throws (void) Method setInstanceId(java.lang.String) : public throws (void) Method setKeys(java.lang.String) : public throws (void) Method setKeys(java.util.Collection) : public throws (void) Method setMsgId(java.lang.String) : public throws (void) Method setPreparedTransactionOffset(long) : public throws (void) Method setQueueId(int) : public throws (void) Method setQueueOffset(long) : public throws (void) Method setReconsumeTimes(int) : public throws (void) Method setStoreHost(java.net.SocketAddress) : public throws (void) Method setStoreHostAddressV6Flag() : public throws (void) Method setStoreSize(int) : public throws (void) Method setStoreTimestamp(long) : public throws (void) Method setSysFlag(int) : public throws (void) Method setTags(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) Method setWaitStoreMsgOK(boolean) : public throws (void) Method socketAddress2ByteBuffer(java.net.SocketAddress) : public throws (java.nio.ByteBuffer) Method socketAddress2ByteBuffer(java.net.SocketAddress,java.nio.ByteBuffer) : public throws (java.nio.ByteBuffer) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/common.message.MessageQueue.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerName : private java.lang.String null Field queueId : private int 0 Field serialVersionUID : private long 6191200464116433425 Field topic : private java.lang.String null Method compareTo(java.lang.Object) : public throws (int) Method compareTo(org.apache.rocketmq.common.message.MessageQueue) : public throws (int) Method equals(java.lang.Object) : public throws (boolean) Method getBrokerName() : public throws (java.lang.String) Method getQueueId() : public throws (int) Method getTopic() : public throws (java.lang.String) Method hashCode() : public throws (int) Method setBrokerName(java.lang.String) : public throws (void) Method setQueueId(int) : public throws (void) Method setTopic(java.lang.String) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/api/remoting.RPCHook.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method doAfterResponse(java.lang.String,org.apache.rocketmq.remoting.protocol.RemotingCommand,org.apache.rocketmq.remoting.protocol.RemotingCommand) : public throws (void) Method doBeforeRequest(java.lang.String,org.apache.rocketmq.remoting.protocol.RemotingCommand) : public throws (void) ================================================ FILE: test/src/test/resources/schema/api/tools.admin.DefaultMQAdminExt.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY : public java.lang.String com.rocketmq.sendMessageWithVIPChannel Field accessChannel : protected org.apache.rocketmq.client.AccessChannel LOCAL Field adminExtGroup : private java.lang.String admin_ext_group Field clientCallbackExecutorThreads : private int null Field clientIP : private java.lang.String null Field createTopicKey : private java.lang.String TBW102 Field defaultMQAdminExtImpl : private org.apache.rocketmq.tools.admin.DefaultMQAdminExtImpl null Field enableStreamRequestType : protected boolean false Field heartbeatBrokerInterval : private int 30000 Field instanceName : private java.lang.String DEFAULT Field language : private org.apache.rocketmq.remoting.protocol.LanguageCode JAVA Field mqClientApiTimeout : private int 3000 Field namespace : protected java.lang.String null Field namespaceInitialized : private boolean false Field namesrvAddr : private java.lang.String null Field persistConsumerOffsetInterval : private int 5000 Field pollNameServerInterval : private int 30000 Field pullTimeDelayMillsWhenException : private long 1000 Field timeoutMillis : private long 5000 Field unitMode : private boolean false Field unitName : private java.lang.String null Field useTLS : private boolean false Field vipChannelEnabled : private boolean false Method addWritePermOfBroker(java.lang.String,java.lang.String) : public throws (int) Method buildMQClientId() : public throws (java.lang.String) Method changeInstanceNameToPID() : public throws (void) Method cleanExpiredConsumerQueue(java.lang.String) : public throws (boolean) Method cleanExpiredConsumerQueueByAddr(java.lang.String) : public throws (boolean) Method cleanUnusedTopic(java.lang.String) : public throws (boolean) Method cleanUnusedTopicByAddr(java.lang.String) : public throws (boolean) Method cloneClientConfig() : public throws (org.apache.rocketmq.client.ClientConfig) Method cloneGroupOffset(boolean,java.lang.String,java.lang.String,java.lang.String) : public throws (void) Method consumeMessageDirectly(java.lang.String,java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult) Method consumeMessageDirectly(java.lang.String,java.lang.String,java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult) Method createAndUpdateKvConfig(java.lang.String,java.lang.String,java.lang.String) : public throws (void) Method createAndUpdatePlainAccessConfig(java.lang.String,org.apache.rocketmq.auth.migration.plain.PlainAccessConfig) : public throws (void) Method createAndUpdateSubscriptionGroupConfig(java.lang.String,org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig) : public throws (void) Method createAndUpdateTopicConfig(java.lang.String,org.apache.rocketmq.common.TopicConfig) : public throws (void) Method createOrUpdateOrderConf(boolean,java.lang.String,java.lang.String) : public throws (void) Method createTopic(int,int,java.lang.String,java.lang.String) : public throws (void) Method createTopic(int,java.lang.String,java.lang.String) : public throws (void) Method deleteExpiredCommitLog(java.lang.String) : public throws (boolean) Method deleteExpiredCommitLogByAddr(java.lang.String) : public throws (boolean) Method deleteKvConfig(java.lang.String,java.lang.String) : public throws (void) Method deletePlainAccessConfig(java.lang.String,java.lang.String) : public throws (void) Method deleteSubscriptionGroup(boolean,java.lang.String,java.lang.String) : public throws (void) Method deleteSubscriptionGroup(java.lang.String,java.lang.String) : public throws (void) Method deleteTopicInBroker(java.lang.String,java.util.Set) : public throws (void) Method deleteTopicInNameServer(java.lang.String,java.lang.String,java.util.Set) : public throws (void) Method earliestMsgStoreTime(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method examineBrokerClusterAclConfig(java.lang.String) : public throws (org.apache.rocketmq.auth.migration.plain.AclConfig) Method examineBrokerClusterAclVersionInfo(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ClusterAclVersionInfo) Method examineBrokerClusterInfo() : public throws (org.apache.rocketmq.remoting.protocol.body.ClusterInfo) Method examineConsumeStats(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.admin.ConsumeStats) Method examineConsumeStats(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.admin.ConsumeStats) Method examineConsumerConnectionInfo(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ConsumerConnection) Method examineConsumerConnectionInfo(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ConsumerConnection) Method examineProducerConnectionInfo(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ProducerConnection) Method examineSubscriptionGroupConfig(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig) Method examineTopicConfig(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.common.TopicConfig) Method examineTopicRouteInfo(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.route.TopicRouteData) Method examineTopicStats(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable) Method fetchAllTopicList() : public throws (org.apache.rocketmq.remoting.protocol.body.TopicList) Method fetchBrokerRuntimeStats(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.KVTable) Method fetchConsumeStatsInBroker(boolean,java.lang.String,long) : public throws (org.apache.rocketmq.remoting.protocol.body.ConsumeStatsList) Method fetchTopicsByCLuster(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.TopicList) Method getAccessChannel() : public throws (org.apache.rocketmq.client.AccessChannel) Method getAdminExtGroup() : public throws (java.lang.String) Method getAllProducerInfo(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ProducerTableInfo) Method getAllSubscriptionGroup(java.lang.String,long) : public throws (org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper) Method getAllTopicConfig(java.lang.String,long) : public throws (org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper) Method getBrokerConfig(java.lang.String) : public throws (java.util.Properties) Method getClientCallbackExecutorThreads() : public throws (int) Method getClientIP() : public throws (java.lang.String) Method getClusterList(java.lang.String) : public throws (java.util.Set) Method getConsumeStatus(java.lang.String,java.lang.String,java.lang.String) : public throws (java.util.Map) Method getConsumerRunningInfo(boolean,java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo) Method getCreateTopicKey() : public throws (java.lang.String) Method getHeartbeatBrokerInterval() : public throws (int) Method getInstanceName() : public throws (java.lang.String) Method getKVConfig(java.lang.String,java.lang.String) : public throws (java.lang.String) Method getKVListByNamespace(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.KVTable) Method getLanguage() : public throws (org.apache.rocketmq.remoting.protocol.LanguageCode) Method getMqClientApiTimeout() : public throws (int) Method getNameServerAddressList() : public throws (java.util.List) Method getNameServerConfig(java.util.List) : public throws (java.util.Map) Method getNamespace() : public throws (java.lang.String) Method getNamesrvAddr() : public throws (java.lang.String) Method getPersistConsumerOffsetInterval() : public throws (int) Method getPollNameServerInterval() : public throws (int) Method getPullTimeDelayMillsWhenException() : public throws (long) Method getTopicClusterList(java.lang.String) : public throws (java.util.Set) Method getUnitName() : public throws (java.lang.String) Method getUserSubscriptionGroup(java.lang.String,long) : public throws (org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper) Method getUserTopicConfig(boolean,java.lang.String,long) : public throws (org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper) Method isEnableStreamRequestType() : public throws (boolean) Method isUnitMode() : public throws (boolean) Method isUseTLS() : public throws (boolean) Method isVipChannelEnabled() : public throws (boolean) Method maxOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method messageTrackDetail(org.apache.rocketmq.common.message.MessageExt) : public throws (java.util.List) Method minOffset(org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method putKVConfig(java.lang.String,java.lang.String,java.lang.String) : public throws (void) Method queryConsumeQueue(int,int,java.lang.String,java.lang.String,java.lang.String,long) : public throws (org.apache.rocketmq.remoting.protocol.body.QueryConsumeQueueResponseBody) Method queryConsumeTimeSpan(java.lang.String,java.lang.String) : public throws (java.util.List) Method queryMessage(int,java.lang.String,java.lang.String,long,long) : public throws (org.apache.rocketmq.client.QueryResult) Method queryMessageByUniqKey(int,java.lang.String,java.lang.String,long,long) : public throws (org.apache.rocketmq.client.QueryResult) Method queryTopicConsumeByWho(java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.GroupList) Method queueWithNamespace(org.apache.rocketmq.common.message.MessageQueue) : public throws (org.apache.rocketmq.common.message.MessageQueue) Method queuesWithNamespace(java.util.Collection) : public throws (java.util.Collection) Method resetClientConfig(org.apache.rocketmq.client.ClientConfig) : public throws (void) Method resetOffsetByTimestamp(boolean,boolean,java.lang.String,java.lang.String,long) : public throws (java.util.Map) Method resetOffsetByTimestamp(boolean,java.lang.String,java.lang.String,long) : public throws (java.util.Map) Method resetOffsetByTimestampOld(boolean,java.lang.String,java.lang.String,long) : public throws (java.util.List) Method resetOffsetNew(java.lang.String,java.lang.String,long) : public throws (void) Method resumeCheckHalfMessage(java.lang.String) : public throws (boolean) Method resumeCheckHalfMessage(java.lang.String,java.lang.String) : public throws (boolean) Method searchOffset(long,org.apache.rocketmq.common.message.MessageQueue) : public throws (long) Method setAccessChannel(org.apache.rocketmq.client.AccessChannel) : public throws (void) Method setAdminExtGroup(java.lang.String) : public throws (void) Method setClientCallbackExecutorThreads(int) : public throws (void) Method setClientIP(java.lang.String) : public throws (void) Method setCreateTopicKey(java.lang.String) : public throws (void) Method setEnableStreamRequestType(boolean) : public throws (void) Method setHeartbeatBrokerInterval(int) : public throws (void) Method setInstanceName(java.lang.String) : public throws (void) Method setLanguage(org.apache.rocketmq.remoting.protocol.LanguageCode) : public throws (void) Method setMqClientApiTimeout(int) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setNamesrvAddr(java.lang.String) : public throws (void) Method setPersistConsumerOffsetInterval(int) : public throws (void) Method setPollNameServerInterval(int) : public throws (void) Method setPullTimeDelayMillsWhenException(long) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method setUnitName(java.lang.String) : public throws (void) Method setUseTLS(boolean) : public throws (void) Method setVipChannelEnabled(boolean) : public throws (void) Method shutdown() : public throws (void) Method start() : public throws (void) Method toString() : public throws (java.lang.String) Method updateBrokerConfig(java.lang.String,java.util.Properties) : public throws (void) Method updateConsumeOffset(java.lang.String,java.lang.String,long,org.apache.rocketmq.common.message.MessageQueue) : public throws (void) Method updateGlobalWhiteAddrConfig(java.lang.String,java.lang.String) : public throws (void) Method updateGlobalWhiteAddrConfig(java.lang.String,java.lang.String,java.lang.String) : public throws (void) Method updateNameServerConfig(java.util.List,java.util.Properties) : public throws (void) Method viewBrokerStatsData(java.lang.String,java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.remoting.protocol.body.BrokerStatsData) Method viewMessage(java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method viewMessage(java.lang.String,java.lang.String) : public throws (org.apache.rocketmq.common.message.MessageExt) Method wipeWritePermOfBroker(java.lang.String,java.lang.String) : public throws (int) Method withNamespace(java.lang.String) : public throws (java.lang.String) Method withNamespace(java.util.Set) : public throws (java.util.Set) Method withoutNamespace(java.lang.String) : public throws (java.lang.String) Method withoutNamespace(java.util.Set) : public throws (java.util.Set) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.RequestCode.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field ADD_WRITE_PERM_OF_BROKER : public int 327 Field ADJUST_CONSUMER_THREAD_POOL : public int 213 Field CHECK_CLIENT_CONFIG : public int 46 Field CHECK_TRANSACTION_STATE : public int 39 Field CLEAN_EXPIRED_CONSUMEQUEUE : public int 306 Field CLEAN_UNUSED_TOPIC : public int 316 Field CLONE_GROUP_OFFSET : public int 314 Field CONSUMER_SEND_MSG_BACK : public int 36 Field CONSUME_MESSAGE_DIRECTLY : public int 309 Field DELETE_ACL_CONFIG : public int 51 Field DELETE_EXPIRED_COMMITLOG : public int 329 Field DELETE_KV_CONFIG : public int 102 Field DELETE_SUBSCRIPTIONGROUP : public int 207 Field DELETE_TOPIC_IN_BROKER : public int 215 Field DELETE_TOPIC_IN_NAMESRV : public int 216 Field END_TRANSACTION : public int 37 Field GET_ALL_CONSUMER_OFFSET : public int 43 Field GET_ALL_DELAY_OFFSET : public int 45 Field GET_ALL_PRODUCER_INFO : public int 328 Field GET_ALL_SUBSCRIPTIONGROUP_CONFIG : public int 201 Field GET_ALL_TOPIC_CONFIG : public int 21 Field GET_ALL_TOPIC_LIST_FROM_NAMESERVER : public int 206 Field GET_BROKER_CLUSTER_ACL_CONFIG : public int 54 Field GET_BROKER_CLUSTER_ACL_INFO : public int 52 Field GET_BROKER_CLUSTER_INFO : public int 106 Field GET_BROKER_CONFIG : public int 26 Field GET_BROKER_CONSUME_STATS : public int 317 Field GET_BROKER_RUNTIME_INFO : public int 28 Field GET_CONSUMER_CONNECTION_LIST : public int 203 Field GET_CONSUMER_LIST_BY_GROUP : public int 38 Field GET_CONSUMER_RUNNING_INFO : public int 307 Field GET_CONSUMER_STATUS_FROM_CLIENT : public int 221 Field GET_CONSUME_STATS : public int 208 Field GET_EARLIEST_MSG_STORETIME : public int 32 Field GET_HAS_UNIT_SUB_TOPIC_LIST : public int 312 Field GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST : public int 313 Field GET_KVLIST_BY_NAMESPACE : public int 219 Field GET_KV_CONFIG : public int 101 Field GET_MAX_OFFSET : public int 30 Field GET_MIN_OFFSET : public int 31 Field GET_NAMESRV_CONFIG : public int 319 Field GET_PRODUCER_CONNECTION_LIST : public int 204 Field GET_ROUTEINFO_BY_TOPIC : public int 105 Field GET_SYSTEM_TOPIC_LIST_FROM_BROKER : public int 305 Field GET_SYSTEM_TOPIC_LIST_FROM_NS : public int 304 Field GET_TOPICS_BY_CLUSTER : public int 224 Field GET_TOPIC_CONFIG_LIST : public int 22 Field GET_TOPIC_NAME_LIST : public int 23 Field GET_TOPIC_STATS_INFO : public int 202 Field GET_UNIT_TOPIC_LIST : public int 311 Field HEART_BEAT : public int 34 Field INVOKE_BROKER_TO_GET_CONSUMER_STATUS : public int 223 Field INVOKE_BROKER_TO_RESET_OFFSET : public int 222 Field LOCK_BATCH_MQ : public int 41 Field NOTIFY_CONSUMER_IDS_CHANGED : public int 40 Field PULL_MESSAGE : public int 11 Field PUSH_REPLY_MESSAGE_TO_CLIENT : public int 326 Field PUT_KV_CONFIG : public int 100 Field QUERY_BROKER_OFFSET : public int 13 Field QUERY_CONSUMER_OFFSET : public int 14 Field QUERY_CONSUME_QUEUE : public int 321 Field QUERY_CONSUME_TIME_SPAN : public int 303 Field QUERY_CORRECTION_OFFSET : public int 308 Field QUERY_DATA_VERSION : public int 322 Field QUERY_MESSAGE : public int 12 Field QUERY_TOPIC_CONSUME_BY_WHO : public int 300 Field REGISTER_BROKER : public int 103 Field REGISTER_FILTER_SERVER : public int 301 Field REGISTER_MESSAGE_FILTER_CLASS : public int 302 Field RESET_CONSUMER_CLIENT_OFFSET : public int 220 Field RESET_CONSUMER_OFFSET_IN_BROKER : public int 212 Field RESET_CONSUMER_OFFSET_IN_CONSUMER : public int 211 Field RESUME_CHECK_HALF_MESSAGE : public int 323 Field RESUME_CONSUMER : public int 210 Field SEARCH_OFFSET_BY_TIMESTAMP : public int 29 Field SEND_BATCH_MESSAGE : public int 320 Field SEND_MESSAGE : public int 10 Field SEND_MESSAGE_V2 : public int 310 Field SEND_REPLY_MESSAGE : public int 324 Field SEND_REPLY_MESSAGE_V2 : public int 325 Field SUSPEND_CONSUMER : public int 209 Field TRIGGER_DELETE_FILES : public int 27 Field UNLOCK_BATCH_MQ : public int 42 Field UNREGISTER_BROKER : public int 104 Field UNREGISTER_CLIENT : public int 35 Field UPDATE_AND_CREATE_ACL_CONFIG : public int 50 Field UPDATE_AND_CREATE_SUBSCRIPTIONGROUP : public int 200 Field UPDATE_AND_CREATE_TOPIC : public int 17 Field UPDATE_BROKER_CONFIG : public int 25 Field UPDATE_CONSUMER_OFFSET : public int 15 Field UPDATE_GLOBAL_WHITE_ADDRS_CONFIG : public int 53 Field UPDATE_NAMESRV_CONFIG : public int 318 Field VIEW_BROKER_STATS_DATA : public int 315 Field VIEW_MESSAGE_BY_ID : public int 33 Field WHO_CONSUME_THE_MESSAGE : public int 214 Field WIPE_WRITE_PERM_OF_BROKER : public int 205 ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.CheckTransactionStateRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field commitLogOffset : private java.lang.Long null Field msgId : private java.lang.String null Field offsetMsgId : private java.lang.String null Field tranStateTableOffset : private java.lang.Long null Field transactionId : private java.lang.String null Method checkFields() : public throws (void) Method getCommitLogOffset() : public throws (java.lang.Long) Method getMsgId() : public throws (java.lang.String) Method getOffsetMsgId() : public throws (java.lang.String) Method getTranStateTableOffset() : public throws (java.lang.Long) Method getTransactionId() : public throws (java.lang.String) Method setCommitLogOffset(java.lang.Long) : public throws (void) Method setMsgId(java.lang.String) : public throws (void) Method setOffsetMsgId(java.lang.String) : public throws (void) Method setTranStateTableOffset(java.lang.Long) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.CheckTransactionStateResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field commitLogOffset : private java.lang.Long null Field commitOrRollback : private java.lang.Integer null Field producerGroup : private java.lang.String null Field tranStateTableOffset : private java.lang.Long null Method checkFields() : public throws (void) Method getCommitLogOffset() : public throws (java.lang.Long) Method getCommitOrRollback() : public throws (java.lang.Integer) Method getProducerGroup() : public throws (java.lang.String) Method getTranStateTableOffset() : public throws (java.lang.Long) Method setCommitLogOffset(java.lang.Long) : public throws (void) Method setCommitOrRollback(java.lang.Integer) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setTranStateTableOffset(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.CloneGroupOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field destGroup : private java.lang.String null Field offline : private boolean false Field srcGroup : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getDestGroup() : public throws (java.lang.String) Method getSrcGroup() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method isOffline() : public throws (boolean) Method setDestGroup(java.lang.String) : public throws (void) Method setOffline(boolean) : public throws (void) Method setSrcGroup(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ConsumeMessageDirectlyResultRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerName : private java.lang.String null Field clientId : private java.lang.String null Field consumerGroup : private java.lang.String null Field msgId : private java.lang.String null Method checkFields() : public throws (void) Method getBrokerName() : public throws (java.lang.String) Method getClientId() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method getMsgId() : public throws (java.lang.String) Method setBrokerName(java.lang.String) : public throws (void) Method setClientId(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setMsgId(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ConsumerSendMsgBackRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field delayLevel : private java.lang.Integer null Field group : private java.lang.String null Field maxReconsumeTimes : private java.lang.Integer null Field offset : private java.lang.Long null Field originMsgId : private java.lang.String null Field originTopic : private java.lang.String null Field unitMode : private boolean false Method checkFields() : public throws (void) Method getDelayLevel() : public throws (java.lang.Integer) Method getGroup() : public throws (java.lang.String) Method getMaxReconsumeTimes() : public throws (java.lang.Integer) Method getOffset() : public throws (java.lang.Long) Method getOriginMsgId() : public throws (java.lang.String) Method getOriginTopic() : public throws (java.lang.String) Method isUnitMode() : public throws (boolean) Method setDelayLevel(java.lang.Integer) : public throws (void) Method setGroup(java.lang.String) : public throws (void) Method setMaxReconsumeTimes(java.lang.Integer) : public throws (void) Method setOffset(java.lang.Long) : public throws (void) Method setOriginMsgId(java.lang.String) : public throws (void) Method setOriginTopic(java.lang.String) : public throws (void) Method setUnitMode(boolean) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.CreateAccessConfigRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field accessKey : private java.lang.String null Field admin : private boolean false Field defaultGroupPerm : private java.lang.String null Field defaultTopicPerm : private java.lang.String null Field groupPerms : private java.lang.String null Field secretKey : private java.lang.String null Field topicPerms : private java.lang.String null Field whiteRemoteAddress : private java.lang.String null Method checkFields() : public throws (void) Method getAccessKey() : public throws (java.lang.String) Method getDefaultGroupPerm() : public throws (java.lang.String) Method getDefaultTopicPerm() : public throws (java.lang.String) Method getGroupPerms() : public throws (java.lang.String) Method getSecretKey() : public throws (java.lang.String) Method getTopicPerms() : public throws (java.lang.String) Method getWhiteRemoteAddress() : public throws (java.lang.String) Method isAdmin() : public throws (boolean) Method setAccessKey(java.lang.String) : public throws (void) Method setAdmin(boolean) : public throws (void) Method setDefaultGroupPerm(java.lang.String) : public throws (void) Method setDefaultTopicPerm(java.lang.String) : public throws (void) Method setGroupPerms(java.lang.String) : public throws (void) Method setSecretKey(java.lang.String) : public throws (void) Method setTopicPerms(java.lang.String) : public throws (void) Method setWhiteRemoteAddress(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.CreateTopicRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field defaultTopic : private java.lang.String null Field order : private java.lang.Boolean false Field perm : private java.lang.Integer null Field readQueueNums : private java.lang.Integer null Field topic : private java.lang.String null Field topicFilterType : private java.lang.String null Field topicSysFlag : private java.lang.Integer null Field writeQueueNums : private java.lang.Integer null Method checkFields() : public throws (void) Method getDefaultTopic() : public throws (java.lang.String) Method getOrder() : public throws (java.lang.Boolean) Method getPerm() : public throws (java.lang.Integer) Method getReadQueueNums() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method getTopicFilterType() : public throws (java.lang.String) Method getTopicFilterTypeEnum() : public throws (org.apache.rocketmq.common.TopicFilterType) Method getTopicSysFlag() : public throws (java.lang.Integer) Method getWriteQueueNums() : public throws (java.lang.Integer) Method setDefaultTopic(java.lang.String) : public throws (void) Method setOrder(java.lang.Boolean) : public throws (void) Method setPerm(java.lang.Integer) : public throws (void) Method setReadQueueNums(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) Method setTopicFilterType(java.lang.String) : public throws (void) Method setTopicSysFlag(java.lang.Integer) : public throws (void) Method setWriteQueueNums(java.lang.Integer) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.DeleteAccessConfigRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field accessKey : private java.lang.String null Method checkFields() : public throws (void) Method getAccessKey() : public throws (java.lang.String) Method setAccessKey(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.DeleteSubscriptionGroupRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field groupName : private java.lang.String null Field removeOffset : private boolean false Method checkFields() : public throws (void) Method getGroupName() : public throws (java.lang.String) Method isRemoveOffset() : public throws (boolean) Method setGroupName(java.lang.String) : public throws (void) Method setRemoveOffset(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.DeleteTopicRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getTopic() : public throws (java.lang.String) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.EndTransactionRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field commitLogOffset : private java.lang.Long null Field commitOrRollback : private java.lang.Integer null Field fromTransactionCheck : private java.lang.Boolean false Field msgId : private java.lang.String null Field producerGroup : private java.lang.String null Field tranStateTableOffset : private java.lang.Long null Field transactionId : private java.lang.String null Method checkFields() : public throws (void) Method getCommitLogOffset() : public throws (java.lang.Long) Method getCommitOrRollback() : public throws (java.lang.Integer) Method getFromTransactionCheck() : public throws (java.lang.Boolean) Method getMsgId() : public throws (java.lang.String) Method getProducerGroup() : public throws (java.lang.String) Method getTranStateTableOffset() : public throws (java.lang.Long) Method getTransactionId() : public throws (java.lang.String) Method setCommitLogOffset(java.lang.Long) : public throws (void) Method setCommitOrRollback(java.lang.Integer) : public throws (void) Method setFromTransactionCheck(java.lang.Boolean) : public throws (void) Method setMsgId(java.lang.String) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setTranStateTableOffset(java.lang.Long) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.EndTransactionResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetAllProducerInfoRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetAllTopicConfigResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetBrokerAclConfigResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field allAclFileVersion : private java.lang.String null Field brokerAddr : private java.lang.String null Field brokerName : private java.lang.String null Field clusterName : private java.lang.String null Field version : private java.lang.String null Method checkFields() : public throws (void) Method getAllAclFileVersion() : public throws (java.lang.String) Method getBrokerAddr() : public throws (java.lang.String) Method getBrokerName() : public throws (java.lang.String) Method getClusterName() : public throws (java.lang.String) Method getVersion() : public throws (java.lang.String) Method setAllAclFileVersion(java.lang.String) : public throws (void) Method setBrokerAddr(java.lang.String) : public throws (void) Method setBrokerName(java.lang.String) : public throws (void) Method setClusterName(java.lang.String) : public throws (void) Method setVersion(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetBrokerClusterAclConfigResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field plainAccessConfigs : private java.util.List null Method checkFields() : public throws (void) Method getPlainAccessConfigs() : public throws (java.util.List) Method setPlainAccessConfigs(java.util.List) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetBrokerConfigResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field version : private java.lang.String null Method checkFields() : public throws (void) Method getVersion() : public throws (java.lang.String) Method setVersion(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumeStatsInBrokerHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field isOrder : private boolean false Method checkFields() : public throws (void) Method isOrder() : public throws (boolean) Method setIsOrder(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumeStatsRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getConsumerGroup() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setConsumerGroup(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumerConnectionListRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Method checkFields() : public throws (void) Method getConsumerGroup() : public throws (java.lang.String) Method setConsumerGroup(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumerListByGroupRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Method checkFields() : public throws (void) Method getConsumerGroup() : public throws (java.lang.String) Method setConsumerGroup(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumerListByGroupResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumerRunningInfoRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field clientId : private java.lang.String null Field consumerGroup : private java.lang.String null Field jstackEnable : private boolean false Method checkFields() : public throws (void) Method getClientId() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method isJstackEnable() : public throws (boolean) Method setClientId(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setJstackEnable(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetConsumerStatusRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field clientAddr : private java.lang.String null Field group : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getClientAddr() : public throws (java.lang.String) Method getGroup() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setClientAddr(java.lang.String) : public throws (void) Method setGroup(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetEarliestMsgStoretimeRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field queueId : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getQueueId() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setQueueId(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetEarliestMsgStoretimeResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field timestamp : private java.lang.Long null Method checkFields() : public throws (void) Method getTimestamp() : public throws (java.lang.Long) Method setTimestamp(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetMaxOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field queueId : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getQueueId() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setQueueId(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetMaxOffsetResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field offset : private java.lang.Long null Method checkFields() : public throws (void) Method getOffset() : public throws (java.lang.Long) Method setOffset(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetMinOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field queueId : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getQueueId() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setQueueId(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetMinOffsetResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field offset : private java.lang.Long null Method checkFields() : public throws (void) Method getOffset() : public throws (java.lang.Long) Method setOffset(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetProducerConnectionListRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field producerGroup : private java.lang.String null Method checkFields() : public throws (void) Method getProducerGroup() : public throws (java.lang.String) Method setProducerGroup(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetTopicStatsInfoRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getTopic() : public throws (java.lang.String) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.GetTopicsByClusterRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field cluster : private java.lang.String null Method checkFields() : public throws (void) Method getCluster() : public throws (java.lang.String) Method setCluster(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.NotifyConsumerIdsChangedRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Method checkFields() : public throws (void) Method getConsumerGroup() : public throws (java.lang.String) Method setConsumerGroup(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.PullMessageRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field commitOffset : private java.lang.Long null Field consumerGroup : private java.lang.String null Field expressionType : private java.lang.String null Field maxMsgNums : private java.lang.Integer null Field queueId : private java.lang.Integer null Field queueOffset : private java.lang.Long null Field subVersion : private java.lang.Long null Field subscription : private java.lang.String null Field suspendTimeoutMillis : private java.lang.Long null Field sysFlag : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method decode(java.util.HashMap) : public throws (void) Method encode(io.netty.buffer.ByteBuf) : public throws (void) Method getCommitOffset() : public throws (java.lang.Long) Method getConsumerGroup() : public throws (java.lang.String) Method getExpressionType() : public throws (java.lang.String) Method getMaxMsgNums() : public throws (java.lang.Integer) Method getQueueId() : public throws (java.lang.Integer) Method getQueueOffset() : public throws (java.lang.Long) Method getSubVersion() : public throws (java.lang.Long) Method getSubscription() : public throws (java.lang.String) Method getSuspendTimeoutMillis() : public throws (java.lang.Long) Method getSysFlag() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setCommitOffset(java.lang.Long) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setExpressionType(java.lang.String) : public throws (void) Method setMaxMsgNums(java.lang.Integer) : public throws (void) Method setQueueId(java.lang.Integer) : public throws (void) Method setQueueOffset(java.lang.Long) : public throws (void) Method setSubVersion(java.lang.Long) : public throws (void) Method setSubscription(java.lang.String) : public throws (void) Method setSuspendTimeoutMillis(java.lang.Long) : public throws (void) Method setSysFlag(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.PullMessageResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field maxOffset : private java.lang.Long null Field minOffset : private java.lang.Long null Field nextBeginOffset : private java.lang.Long null Field suggestWhichBrokerId : private java.lang.Long null Method checkFields() : public throws (void) Method decode(java.util.HashMap) : public throws (void) Method encode(io.netty.buffer.ByteBuf) : public throws (void) Method getMaxOffset() : public throws (java.lang.Long) Method getMinOffset() : public throws (java.lang.Long) Method getNextBeginOffset() : public throws (java.lang.Long) Method getSuggestWhichBrokerId() : public throws (java.lang.Long) Method setMaxOffset(java.lang.Long) : public throws (void) Method setMinOffset(java.lang.Long) : public throws (void) Method setNextBeginOffset(java.lang.Long) : public throws (void) Method setSuggestWhichBrokerId(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryConsumeQueueRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Field count : private int 0 Field index : private long 0 Field queueId : private int 0 Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getConsumerGroup() : public throws (java.lang.String) Method getCount() : public throws (int) Method getIndex() : public throws (long) Method getQueueId() : public throws (int) Method getTopic() : public throws (java.lang.String) Method setConsumerGroup(java.lang.String) : public throws (void) Method setCount(int) : public throws (void) Method setIndex(long) : public throws (void) Method setQueueId(int) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryConsumeTimeSpanRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field group : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getGroup() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setGroup(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryConsumerOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field consumerGroup : private java.lang.String null Field queueId : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getConsumerGroup() : public throws (java.lang.String) Method getQueueId() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setConsumerGroup(java.lang.String) : public throws (void) Method setQueueId(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryConsumerOffsetResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field offset : private java.lang.Long null Method checkFields() : public throws (void) Method getOffset() : public throws (java.lang.Long) Method setOffset(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryCorrectionOffsetHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field compareGroup : private java.lang.String null Field filterGroups : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getCompareGroup() : public throws (java.lang.String) Method getFilterGroups() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setCompareGroup(java.lang.String) : public throws (void) Method setFilterGroups(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryMessageRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field beginTimestamp : private java.lang.Long null Field endTimestamp : private java.lang.Long null Field key : private java.lang.String null Field maxNum : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getBeginTimestamp() : public throws (java.lang.Long) Method getEndTimestamp() : public throws (java.lang.Long) Method getKey() : public throws (java.lang.String) Method getMaxNum() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setBeginTimestamp(java.lang.Long) : public throws (void) Method setEndTimestamp(java.lang.Long) : public throws (void) Method setKey(java.lang.String) : public throws (void) Method setMaxNum(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryMessageResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field indexLastUpdatePhyoffset : private java.lang.Long null Field indexLastUpdateTimestamp : private java.lang.Long null Method checkFields() : public throws (void) Method getIndexLastUpdatePhyoffset() : public throws (java.lang.Long) Method getIndexLastUpdateTimestamp() : public throws (java.lang.Long) Method setIndexLastUpdatePhyoffset(java.lang.Long) : public throws (void) Method setIndexLastUpdateTimestamp(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.QueryTopicConsumeByWhoRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getTopic() : public throws (java.lang.String) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ReplyMessageRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field bornHost : private java.lang.String null Field bornTimestamp : private java.lang.Long null Field defaultTopic : private java.lang.String null Field defaultTopicQueueNums : private java.lang.Integer null Field flag : private java.lang.Integer null Field producerGroup : private java.lang.String null Field properties : private java.lang.String null Field queueId : private java.lang.Integer null Field reconsumeTimes : private java.lang.Integer null Field storeHost : private java.lang.String null Field storeTimestamp : private long 0 Field sysFlag : private java.lang.Integer null Field topic : private java.lang.String null Field unitMode : private boolean false Method checkFields() : public throws (void) Method getBornHost() : public throws (java.lang.String) Method getBornTimestamp() : public throws (java.lang.Long) Method getDefaultTopic() : public throws (java.lang.String) Method getDefaultTopicQueueNums() : public throws (java.lang.Integer) Method getFlag() : public throws (java.lang.Integer) Method getProducerGroup() : public throws (java.lang.String) Method getProperties() : public throws (java.lang.String) Method getQueueId() : public throws (java.lang.Integer) Method getReconsumeTimes() : public throws (java.lang.Integer) Method getStoreHost() : public throws (java.lang.String) Method getStoreTimestamp() : public throws (long) Method getSysFlag() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method isUnitMode() : public throws (boolean) Method setBornHost(java.lang.String) : public throws (void) Method setBornTimestamp(java.lang.Long) : public throws (void) Method setDefaultTopic(java.lang.String) : public throws (void) Method setDefaultTopicQueueNums(java.lang.Integer) : public throws (void) Method setFlag(java.lang.Integer) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setProperties(java.lang.String) : public throws (void) Method setQueueId(java.lang.Integer) : public throws (void) Method setReconsumeTimes(java.lang.Integer) : public throws (void) Method setStoreHost(java.lang.String) : public throws (void) Method setStoreTimestamp(long) : public throws (void) Method setSysFlag(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) Method setUnitMode(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ResetOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field group : private java.lang.String null Field isForce : private boolean false Field timestamp : private long 0 Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getGroup() : public throws (java.lang.String) Method getTimestamp() : public throws (long) Method getTopic() : public throws (java.lang.String) Method isForce() : public throws (boolean) Method setForce(boolean) : public throws (void) Method setGroup(java.lang.String) : public throws (void) Method setTimestamp(long) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ResumeCheckHalfMessageRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field msgId : private java.lang.String null Method checkFields() : public throws (void) Method getMsgId() : public throws (java.lang.String) Method setMsgId(java.lang.String) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.SearchOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field queueId : private java.lang.Integer null Field timestamp : private java.lang.Long null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getQueueId() : public throws (java.lang.Integer) Method getTimestamp() : public throws (java.lang.Long) Method getTopic() : public throws (java.lang.String) Method setQueueId(java.lang.Integer) : public throws (void) Method setTimestamp(java.lang.Long) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.SearchOffsetResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field offset : private java.lang.Long null Method checkFields() : public throws (void) Method getOffset() : public throws (java.lang.Long) Method setOffset(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.SendMessageRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field batch : private boolean false Field bornTimestamp : private java.lang.Long null Field defaultTopic : private java.lang.String null Field defaultTopicQueueNums : private java.lang.Integer null Field flag : private java.lang.Integer null Field maxReconsumeTimes : private java.lang.Integer null Field producerGroup : private java.lang.String null Field properties : private java.lang.String null Field queueId : private java.lang.Integer null Field reconsumeTimes : private java.lang.Integer null Field sysFlag : private java.lang.Integer null Field topic : private java.lang.String null Field unitMode : private boolean false Method checkFields() : public throws (void) Method getBornTimestamp() : public throws (java.lang.Long) Method getDefaultTopic() : public throws (java.lang.String) Method getDefaultTopicQueueNums() : public throws (java.lang.Integer) Method getFlag() : public throws (java.lang.Integer) Method getMaxReconsumeTimes() : public throws (java.lang.Integer) Method getProducerGroup() : public throws (java.lang.String) Method getProperties() : public throws (java.lang.String) Method getQueueId() : public throws (java.lang.Integer) Method getReconsumeTimes() : public throws (java.lang.Integer) Method getSysFlag() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method isBatch() : public throws (boolean) Method isUnitMode() : public throws (boolean) Method setBatch(boolean) : public throws (void) Method setBornTimestamp(java.lang.Long) : public throws (void) Method setDefaultTopic(java.lang.String) : public throws (void) Method setDefaultTopicQueueNums(java.lang.Integer) : public throws (void) Method setFlag(java.lang.Integer) : public throws (void) Method setMaxReconsumeTimes(java.lang.Integer) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) Method setProperties(java.lang.String) : public throws (void) Method setQueueId(java.lang.Integer) : public throws (void) Method setReconsumeTimes(java.lang.Integer) : public throws (void) Method setSysFlag(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) Method setUnitMode(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.SendMessageRequestHeaderV2.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field a : private java.lang.String null Field b : private java.lang.String null Field c : private java.lang.String null Field d : private java.lang.Integer null Field e : private java.lang.Integer null Field f : private java.lang.Integer null Field g : private java.lang.Long null Field h : private java.lang.Integer null Field i : private java.lang.String null Field j : private java.lang.Integer null Field k : private boolean false Field l : private java.lang.Integer null Field m : private boolean false Method checkFields() : public throws (void) Method createSendMessageRequestHeaderV1(org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2) : public throws (org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader) Method createSendMessageRequestHeaderV2(org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader) : public throws (org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2) Method decode(java.util.HashMap) : public throws (void) Method encode(io.netty.buffer.ByteBuf) : public throws (void) Method getA() : public throws (java.lang.String) Method getB() : public throws (java.lang.String) Method getC() : public throws (java.lang.String) Method getD() : public throws (java.lang.Integer) Method getE() : public throws (java.lang.Integer) Method getF() : public throws (java.lang.Integer) Method getG() : public throws (java.lang.Long) Method getH() : public throws (java.lang.Integer) Method getI() : public throws (java.lang.String) Method getJ() : public throws (java.lang.Integer) Method getL() : public throws (java.lang.Integer) Method isK() : public throws (boolean) Method isM() : public throws (boolean) Method setA(java.lang.String) : public throws (void) Method setB(java.lang.String) : public throws (void) Method setC(java.lang.String) : public throws (void) Method setD(java.lang.Integer) : public throws (void) Method setE(java.lang.Integer) : public throws (void) Method setF(java.lang.Integer) : public throws (void) Method setG(java.lang.Long) : public throws (void) Method setH(java.lang.Integer) : public throws (void) Method setI(java.lang.String) : public throws (void) Method setJ(java.lang.Integer) : public throws (void) Method setK(boolean) : public throws (void) Method setL(java.lang.Integer) : public throws (void) Method setM(boolean) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.SendMessageResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field msgId : private java.lang.String null Field queueId : private java.lang.Integer null Field queueOffset : private java.lang.Long null Field transactionId : private java.lang.String null Method checkFields() : public throws (void) Method decode(java.util.HashMap) : public throws (void) Method encode(io.netty.buffer.ByteBuf) : public throws (void) Method getMsgId() : public throws (java.lang.String) Method getQueueId() : public throws (java.lang.Integer) Method getQueueOffset() : public throws (java.lang.Long) Method getTransactionId() : public throws (java.lang.String) Method setMsgId(java.lang.String) : public throws (void) Method setQueueId(java.lang.Integer) : public throws (void) Method setQueueOffset(java.lang.Long) : public throws (void) Method setTransactionId(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.UnregisterClientRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field clientID : private java.lang.String null Field consumerGroup : private java.lang.String null Field producerGroup : private java.lang.String null Method checkFields() : public throws (void) Method getClientID() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method getProducerGroup() : public throws (java.lang.String) Method setClientID(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setProducerGroup(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.UnregisterClientResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.UpdateConsumerOffsetRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field commitOffset : private java.lang.Long null Field consumerGroup : private java.lang.String null Field queueId : private java.lang.Integer null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getCommitOffset() : public throws (java.lang.Long) Method getConsumerGroup() : public throws (java.lang.String) Method getQueueId() : public throws (java.lang.Integer) Method getTopic() : public throws (java.lang.String) Method setCommitOffset(java.lang.Long) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setQueueId(java.lang.Integer) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.UpdateConsumerOffsetResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field aclFileFullPath : private java.lang.String null Field globalWhiteAddrs : private java.lang.String null Method checkFields() : public throws (void) Method getAclFileFullPath() : public throws (java.lang.String) Method getGlobalWhiteAddrs() : public throws (java.lang.String) Method setAclFileFullPath(java.lang.String) : public throws (void) Method setGlobalWhiteAddrs(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ViewBrokerStatsDataRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field statsKey : private java.lang.String null Field statsName : private java.lang.String null Method checkFields() : public throws (void) Method getStatsKey() : public throws (java.lang.String) Method getStatsName() : public throws (java.lang.String) Method setStatsKey(java.lang.String) : public throws (void) Method setStatsName(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ViewMessageRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field offset : private java.lang.Long null Method checkFields() : public throws (void) Method getOffset() : public throws (java.lang.Long) Method setOffset(java.lang.Long) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.ViewMessageResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Method checkFields() : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.filtersrv.RegisterFilterServerRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field filterServerAddr : private java.lang.String null Method checkFields() : public throws (void) Method getFilterServerAddr() : public throws (java.lang.String) Method setFilterServerAddr(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.filtersrv.RegisterFilterServerResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerId : private long 0 Field brokerName : private java.lang.String null Method checkFields() : public throws (void) Method getBrokerId() : public throws (long) Method getBrokerName() : public throws (java.lang.String) Method setBrokerId(long) : public throws (void) Method setBrokerName(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.filtersrv.RegisterMessageFilterClassRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field classCRC : private java.lang.Integer null Field className : private java.lang.String null Field consumerGroup : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getClassCRC() : public throws (java.lang.Integer) Method getClassName() : public throws (java.lang.String) Method getConsumerGroup() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setClassCRC(java.lang.Integer) : public throws (void) Method setClassName(java.lang.String) : public throws (void) Method setConsumerGroup(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerName : private java.lang.String null Method checkFields() : public throws (void) Method getBrokerName() : public throws (java.lang.String) Method setBrokerName(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field addTopicCount : private java.lang.Integer null Method checkFields() : public throws (void) Method getAddTopicCount() : public throws (java.lang.Integer) Method setAddTopicCount(java.lang.Integer) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.DeleteKVConfigRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field key : private java.lang.String null Field namespace : private java.lang.String null Method checkFields() : public throws (void) Method getKey() : public throws (java.lang.String) Method getNamespace() : public throws (java.lang.String) Method setKey(java.lang.String) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field clusterName : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getClusterName() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setClusterName(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.GetKVConfigRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field key : private java.lang.String null Field namespace : private java.lang.String null Method checkFields() : public throws (void) Method getKey() : public throws (java.lang.String) Method getNamespace() : public throws (java.lang.String) Method setKey(java.lang.String) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.GetKVConfigResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field value : private java.lang.String null Method checkFields() : public throws (void) Method getValue() : public throws (java.lang.String) Method setValue(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.GetKVListByNamespaceRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field namespace : private java.lang.String null Method checkFields() : public throws (void) Method getNamespace() : public throws (java.lang.String) Method setNamespace(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.GetRouteInfoRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field acceptStandardJsonOnly : private java.lang.Boolean null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getAcceptStandardJsonOnly() : public throws (java.lang.Boolean) Method getTopic() : public throws (java.lang.String) Method setAcceptStandardJsonOnly(java.lang.Boolean) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.PutKVConfigRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field key : private java.lang.String null Field namespace : private java.lang.String null Field value : private java.lang.String null Method checkFields() : public throws (void) Method getKey() : public throws (java.lang.String) Method getNamespace() : public throws (java.lang.String) Method getValue() : public throws (java.lang.String) Method setKey(java.lang.String) : public throws (void) Method setNamespace(java.lang.String) : public throws (void) Method setValue(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.QueryDataVersionRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerAddr : private java.lang.String null Field brokerId : private java.lang.Long null Field brokerName : private java.lang.String null Field clusterName : private java.lang.String null Method checkFields() : public throws (void) Method getBrokerAddr() : public throws (java.lang.String) Method getBrokerId() : public throws (java.lang.Long) Method getBrokerName() : public throws (java.lang.String) Method getClusterName() : public throws (java.lang.String) Method setBrokerAddr(java.lang.String) : public throws (void) Method setBrokerId(java.lang.Long) : public throws (void) Method setBrokerName(java.lang.String) : public throws (void) Method setClusterName(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.QueryDataVersionResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field changed : private java.lang.Boolean null Method checkFields() : public throws (void) Method getChanged() : public throws (java.lang.Boolean) Method setChanged(java.lang.Boolean) : public throws (void) Method toString() : public throws (java.lang.String) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.RegisterBrokerRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field bodyCrc32 : private java.lang.Integer 0 Field brokerAddr : private java.lang.String null Field brokerId : private java.lang.Long null Field brokerName : private java.lang.String null Field clusterName : private java.lang.String null Field compressed : private boolean false Field haServerAddr : private java.lang.String null Method checkFields() : public throws (void) Method getBodyCrc32() : public throws (java.lang.Integer) Method getBrokerAddr() : public throws (java.lang.String) Method getBrokerId() : public throws (java.lang.Long) Method getBrokerName() : public throws (java.lang.String) Method getClusterName() : public throws (java.lang.String) Method getHaServerAddr() : public throws (java.lang.String) Method isCompressed() : public throws (boolean) Method setBodyCrc32(java.lang.Integer) : public throws (void) Method setBrokerAddr(java.lang.String) : public throws (void) Method setBrokerId(java.lang.Long) : public throws (void) Method setBrokerName(java.lang.String) : public throws (void) Method setClusterName(java.lang.String) : public throws (void) Method setCompressed(boolean) : public throws (void) Method setHaServerAddr(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.RegisterBrokerResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field haServerAddr : private java.lang.String null Field masterAddr : private java.lang.String null Method checkFields() : public throws (void) Method getHaServerAddr() : public throws (java.lang.String) Method getMasterAddr() : public throws (java.lang.String) Method setHaServerAddr(java.lang.String) : public throws (void) Method setMasterAddr(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.RegisterOrderTopicRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field orderTopicString : private java.lang.String null Field topic : private java.lang.String null Method checkFields() : public throws (void) Method getOrderTopicString() : public throws (java.lang.String) Method getTopic() : public throws (java.lang.String) Method setOrderTopicString(java.lang.String) : public throws (void) Method setTopic(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.UnRegisterBrokerRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerAddr : private java.lang.String null Field brokerId : private java.lang.Long null Field brokerName : private java.lang.String null Field clusterName : private java.lang.String null Method checkFields() : public throws (void) Method getBrokerAddr() : public throws (java.lang.String) Method getBrokerId() : public throws (java.lang.Long) Method getBrokerName() : public throws (java.lang.String) Method getClusterName() : public throws (java.lang.String) Method setBrokerAddr(java.lang.String) : public throws (void) Method setBrokerId(java.lang.Long) : public throws (void) Method setBrokerName(java.lang.String) : public throws (void) Method setClusterName(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field brokerName : private java.lang.String null Method checkFields() : public throws (void) Method getBrokerName() : public throws (java.lang.String) Method setBrokerName(java.lang.String) : public throws (void) ================================================ FILE: test/src/test/resources/schema/protocol/common.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader.schema ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Field wipeTopicCount : private java.lang.Integer null Method checkFields() : public throws (void) Method getWipeTopicCount() : public throws (java.lang.Integer) Method setWipeTopicCount(java.lang.Integer) : public throws (void) ================================================ FILE: tieredstore/BUILD.bazel ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # load("//bazel:GenTestRules.bzl", "GenTestRules") java_library( name = "tieredstore", srcs = glob(["src/main/java/**/*.java"]), visibility = ["//visibility:public"], deps = [ "//common", "//remoting", "//store", "@maven//:com_github_ben_manes_caffeine_caffeine", "@maven//:com_google_code_findbugs_jsr305", "@maven//:com_google_guava_guava", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp", "@maven//:org_apache_commons_commons_lang3", "@maven//:org_apache_tomcat_annotations_api", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:org_apache_rocketmq_rocketmq_rocksdb", "@maven//:commons_collections_commons_collections", "@maven//:org_slf4j_slf4j_api", ], ) java_library( name = "tests", srcs = glob(["src/test/java/**/*.java"]), resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]), visibility = ["//visibility:public"], deps = [ ":tieredstore", "//:test_deps", "//common", "//remoting", "//store", "@maven//:com_alibaba_fastjson2_fastjson2", "@maven//:commons_io_commons_io", "@maven//:io_opentelemetry_opentelemetry_api", "@maven//:io_opentelemetry_opentelemetry_context", "@maven//:io_opentelemetry_opentelemetry_exporter_otlp", "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus", "@maven//:io_opentelemetry_opentelemetry_sdk", "@maven//:io_opentelemetry_opentelemetry_sdk_common", "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", "@maven//:org_apache_commons_commons_lang3", "@maven//:com_google_guava_guava", "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", "@maven//:io_github_aliyunmq_rocketmq_logback_classic", "@maven//:net_java_dev_jna_jna", "@maven//:org_slf4j_slf4j_api", ], ) GenTestRules( name = "GeneratedTestRules", exclude_tests = [ ], medium_tests = [ ], test_files = glob(["src/test/java/**/*Test.java"]), deps = [ ":tests", ], ) ================================================ FILE: tieredstore/README.md ================================================ # Tiered storage for RocketMQ (Technical preview) RocketMQ tiered storage allows users to offload message data from the local disk to other cheaper and larger storage mediums. So that users can extend the message reserve time at a lower cost. And different topics can flexibly specify different TTL as needed. This article is a cookbook for RocketMQ tiered storage. ## Architecture ![Tiered storage architecture](tiered_storage_arch.png) ## Quick start Use the following steps to easily use tiered storage 1. Change `messageStorePlugIn` to `org.apache.rocketmq.tieredstore.TieredMessageStore` in your `broker.conf`. 2. Configure your backend service provider. Change `tieredBackendServiceProvider` to your storage medium implementation. We provide a default implementation: POSIX provider, and you need to change `tieredStoreFilePath` to the mount point of the storage medium for tiered storage. 3. Start the broker and enjoy! ## Configuration The following are some core configurations, for more details, see [TieredMessageStoreConfig](https://github.com/apache/rocketmq/blob/develop/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredMessageStoreConfig.java) | Configuration | Default value | Unit | Function | | ------------------------------- |---------------------------------------------------------------| ----------- |---------------------------------------------------------------------------------| | messageStorePlugIn | | | Set to org.apache.rocketmq.tieredstore.TieredMessageStore to use tiered storage | | tieredMetadataServiceProvider | org.apache.rocketmq.tieredstore.metadata.DefaultMetadataStore | | Select your metadata provider | | tieredBackendServiceProvider | org.apache.rocketmq.tieredstore.provider.PosixFileSegment | | Select your backend service provider | | tieredStoreFilePath | | | Select the directory used for tiered storage, only for POSIX provider. | | tieredStorageLevel | NOT_IN_DISK | | The options are DISABLE, NOT_IN_DISK, NOT_IN_MEM, FORCE | | tieredStoreFileReservedTime | 72 | hour | Default topic TTL in tiered storage | | tieredStoreGroupCommitCount | 2500 | | The number of messages that trigger one batch transfer | | tieredStoreGroupCommitSize | 33554432 | byte | The size of messages that trigger one batch transfer, 32M by default | | tieredStoreMaxGroupCommitCount | 10000 | | The maximum number of messages waiting to be transferred per queue | | readAheadCacheExpireDuration | 1000 | millisecond | Read-ahead cache expiration time | | readAheadCacheSizeThresholdRate | 0.3 | | The maximum heap space occupied by the read-ahead cache | ## Metrics Tiered storage provides some useful metrics, see [RIP-46](https://github.com/apache/rocketmq/wiki/RIP-46-Observability-improvement-for-RocketMQ) for details. | Type | Name | Unit | | --------- | --------------------------------------------------- | ------------ | | Histogram | rocketmq_tiered_store_api_latency | milliseconds | | Histogram | rocketmq_tiered_store_provider_rpc_latency | milliseconds | | Histogram | rocketmq_tiered_store_provider_upload_bytes | byte | | Histogram | rocketmq_tiered_store_provider_download_bytes | byte | | Gauge | rocketmq_tiered_store_dispatch_behind | | | Gauge | rocketmq_tiered_store_dispatch_latency | milliseconds | | Counter | rocketmq_tiered_store_messages_dispatch_total | | | Counter | rocketmq_tiered_store_messages_out_total | | | Counter | rocketmq_tiered_store_get_message_fallback_total | | | Gauge | rocketmq_tiered_store_read_ahead_cache_count | | | Gauge | rocketmq_tiered_store_read_ahead_cache_bytes | bytes | | Counter | rocketmq_tiered_store_read_ahead_cache_access_total | | | Counter | rocketmq_tiered_store_read_ahead_cache_hit_total | | | Gauge | rocketmq_storage_message_reserve_time | milliseconds | ## How to contribute We need community participation to add more backend service providers for tiered storage. [PosixFileSegment](https://github.com/apache/rocketmq/blob/develop/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/PosixFileSegment.java), the implementation provided by default is just an example. People who want to contribute can follow it to implement their own providers, such as S3FileSegment, OSSFileSegment, and MinIOFileSegment. Here are some guidelines: 1. Extend [FileSegment](https://github.com/apache/rocketmq/blob/develop/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/FileSegment.java) and implement the methods of [FileSegmentProvider](https://github.com/apache/rocketmq/blob/develop/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/FileSegmentProvider.java) interface. 2. Record metrics where appropriate. See `rocketmq_tiered_store_provider_rpc_latency`, `rocketmq_tiered_store_provider_upload_bytes`, and `rocketmq_tiered_store_provider_download_bytes` 3. No need to maintain your own cache and avoid polluting the page cache. It already has the read-ahead cache. ================================================ FILE: tieredstore/pom.xml ================================================ org.apache.rocketmq rocketmq-all ${revision} 4.0.0 jar rocketmq-tiered-store rocketmq-tiered-store ${project.version} ${basedir}/.. org.apache.rocketmq rocketmq-store com.github.ben-manes.caffeine caffeine org.checkerframework checker-qual commons-io commons-io test org.openjdk.jmh jmh-core 1.36 provided org.openjdk.jmh jmh-generator-annprocess 1.36 provided ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/MessageStoreConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore; import java.io.File; import java.net.InetAddress; import java.net.UnknownHostException; import java.time.Duration; public class MessageStoreConfig { private String brokerName = localHostName(); private String brokerClusterName = "DefaultCluster"; private TieredStorageLevel tieredStorageLevel = TieredStorageLevel.NOT_IN_DISK; /** * All fetch requests are judged against this level first, * and if the message cannot be read from the TiredMessageStore, * these requests will still go to the next store for fallback processing. */ public enum TieredStorageLevel { /** * Disable tiered storage, all fetch request will be handled by default message store. */ DISABLE(0), /** * Only fetch request with offset not in disk will be handled by tiered storage. */ NOT_IN_DISK(1), /** * Only fetch request with offset not in memory(page cache) will be handled by tiered storage. */ NOT_IN_MEM(2), /** * All fetch request will be handled by tiered storage. */ FORCE(3); private final int value; TieredStorageLevel(int value) { this.value = value; } public int getValue() { return value; } @SuppressWarnings("DuplicatedCode") public static TieredStorageLevel valueOf(int value) { switch (value) { case 1: return NOT_IN_DISK; case 2: return NOT_IN_MEM; case 3: return FORCE; default: return DISABLE; } } public boolean isEnable() { return this.value > 0; } public boolean check(TieredStorageLevel targetLevel) { return this.value >= targetLevel.value; } } private String storePathRootDir = System.getProperty("user.home") + File.separator + "store"; private boolean messageIndexEnable = true; private boolean recordGetMessageResult = false; // CommitLog file size, default is 1G private long tieredStoreCommitLogMaxSize = 1024 * 1024 * 1024; // ConsumeQueue file size, default is 100M private long tieredStoreConsumeQueueMaxSize = 100 * 1024 * 1024; private int tieredStoreIndexFileMaxHashSlotNum = 5000000; private int tieredStoreIndexFileMaxIndexNum = 5000000 * 4; private String tieredMetadataServiceProvider = "org.apache.rocketmq.tieredstore.metadata.DefaultMetadataStore"; private String tieredBackendServiceProvider = "org.apache.rocketmq.tieredstore.provider.MemoryFileSegment"; // file reserved time, default is 72 hour private boolean tieredStoreDeleteFileEnable = true; private int tieredStoreFileReservedTime = 72; private long tieredStoreDeleteFileInterval = Duration.ofHours(1).toMillis(); // time of forcing commitLog to roll to next file, default is 24 hour private int commitLogRollingInterval = 24; private int commitLogRollingMinimumSize = 16 * 1024 * 1024; private boolean tieredStoreGroupCommit = true; private int tieredStoreGroupCommitTimeout = 30 * 1000; // Cached message count larger than this value will trigger async commit. default is 4096 private int tieredStoreGroupCommitCount = 4 * 1024; // Cached message size larger than this value will trigger async commit. default is 4M private int tieredStoreGroupCommitSize = 4 * 1024 * 1024; // Cached message count larger than this value will suspend append. default is 10000 private int tieredStoreMaxGroupCommitCount = 10000; private boolean readAheadCacheEnable = true; private int readAheadMessageCountThreshold = 4096; private int readAheadMessageSizeThreshold = 16 * 1024 * 1024; private long readAheadCacheExpireDuration = 15 * 1000; private double readAheadCacheSizeThresholdRate = 0.3; private int tieredStoreMaxPendingLimit = 10000; private boolean tieredStoreCrcCheckEnable = false; private String tieredStoreFilePath = ""; private String objectStoreEndpoint = ""; private String objectStoreBucket = ""; private String objectStoreAccessKey = ""; private String objectStoreSecretKey = ""; private boolean writeWithoutMmap = false; public static String localHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException ignore) { } return "DEFAULT_BROKER"; } public String getBrokerName() { return brokerName; } public void setBrokerName(String brokerName) { this.brokerName = brokerName; } public String getBrokerClusterName() { return brokerClusterName; } public void setBrokerClusterName(String brokerClusterName) { this.brokerClusterName = brokerClusterName; } public TieredStorageLevel getTieredStorageLevel() { return tieredStorageLevel; } public void setTieredStorageLevel(TieredStorageLevel tieredStorageLevel) { this.tieredStorageLevel = tieredStorageLevel; } public void setTieredStorageLevel(int tieredStorageLevel) { this.tieredStorageLevel = TieredStorageLevel.valueOf(tieredStorageLevel); } public void setTieredStorageLevel(String tieredStorageLevel) { this.tieredStorageLevel = TieredStorageLevel.valueOf(tieredStorageLevel); } public String getStorePathRootDir() { return storePathRootDir; } public void setStorePathRootDir(String storePathRootDir) { this.storePathRootDir = storePathRootDir; } public boolean isMessageIndexEnable() { return messageIndexEnable; } public void setMessageIndexEnable(boolean messageIndexEnable) { this.messageIndexEnable = messageIndexEnable; } public boolean isRecordGetMessageResult() { return recordGetMessageResult; } public void setRecordGetMessageResult(boolean recordGetMessageResult) { this.recordGetMessageResult = recordGetMessageResult; } public long getTieredStoreCommitLogMaxSize() { return tieredStoreCommitLogMaxSize; } public void setTieredStoreCommitLogMaxSize(long tieredStoreCommitLogMaxSize) { this.tieredStoreCommitLogMaxSize = tieredStoreCommitLogMaxSize; } public long getTieredStoreConsumeQueueMaxSize() { return tieredStoreConsumeQueueMaxSize; } public void setTieredStoreConsumeQueueMaxSize(long tieredStoreConsumeQueueMaxSize) { this.tieredStoreConsumeQueueMaxSize = tieredStoreConsumeQueueMaxSize; } public int getTieredStoreIndexFileMaxHashSlotNum() { return tieredStoreIndexFileMaxHashSlotNum; } public void setTieredStoreIndexFileMaxHashSlotNum(int tieredStoreIndexFileMaxHashSlotNum) { this.tieredStoreIndexFileMaxHashSlotNum = tieredStoreIndexFileMaxHashSlotNum; } public int getTieredStoreIndexFileMaxIndexNum() { return tieredStoreIndexFileMaxIndexNum; } public void setTieredStoreIndexFileMaxIndexNum(int tieredStoreIndexFileMaxIndexNum) { this.tieredStoreIndexFileMaxIndexNum = tieredStoreIndexFileMaxIndexNum; } public String getTieredMetadataServiceProvider() { return tieredMetadataServiceProvider; } public void setTieredMetadataServiceProvider(String tieredMetadataServiceProvider) { this.tieredMetadataServiceProvider = tieredMetadataServiceProvider; } public String getTieredBackendServiceProvider() { return tieredBackendServiceProvider; } public void setTieredBackendServiceProvider(String tieredBackendServiceProvider) { this.tieredBackendServiceProvider = tieredBackendServiceProvider; } public boolean isTieredStoreDeleteFileEnable() { return tieredStoreDeleteFileEnable; } public void setTieredStoreDeleteFileEnable(boolean tieredStoreDeleteFileEnable) { this.tieredStoreDeleteFileEnable = tieredStoreDeleteFileEnable; } public int getTieredStoreFileReservedTime() { return tieredStoreFileReservedTime; } public void setTieredStoreFileReservedTime(int tieredStoreFileReservedTime) { this.tieredStoreFileReservedTime = tieredStoreFileReservedTime; } public long getTieredStoreDeleteFileInterval() { return tieredStoreDeleteFileInterval; } public void setTieredStoreDeleteFileInterval(long tieredStoreDeleteFileInterval) { this.tieredStoreDeleteFileInterval = tieredStoreDeleteFileInterval; } public int getCommitLogRollingInterval() { return commitLogRollingInterval; } public void setCommitLogRollingInterval(int commitLogRollingInterval) { this.commitLogRollingInterval = commitLogRollingInterval; } public int getCommitLogRollingMinimumSize() { return commitLogRollingMinimumSize; } public void setCommitLogRollingMinimumSize(int commitLogRollingMinimumSize) { this.commitLogRollingMinimumSize = commitLogRollingMinimumSize; } public boolean isTieredStoreGroupCommit() { return tieredStoreGroupCommit; } public void setTieredStoreGroupCommit(boolean tieredStoreGroupCommit) { this.tieredStoreGroupCommit = tieredStoreGroupCommit; } public int getTieredStoreGroupCommitTimeout() { return tieredStoreGroupCommitTimeout; } public void setTieredStoreGroupCommitTimeout(int tieredStoreGroupCommitTimeout) { this.tieredStoreGroupCommitTimeout = tieredStoreGroupCommitTimeout; } public int getTieredStoreGroupCommitCount() { return tieredStoreGroupCommitCount; } public void setTieredStoreGroupCommitCount(int tieredStoreGroupCommitCount) { this.tieredStoreGroupCommitCount = tieredStoreGroupCommitCount; } public int getTieredStoreGroupCommitSize() { return tieredStoreGroupCommitSize; } public void setTieredStoreGroupCommitSize(int tieredStoreGroupCommitSize) { this.tieredStoreGroupCommitSize = tieredStoreGroupCommitSize; } public int getTieredStoreMaxGroupCommitCount() { return tieredStoreMaxGroupCommitCount; } public void setTieredStoreMaxGroupCommitCount(int tieredStoreMaxGroupCommitCount) { this.tieredStoreMaxGroupCommitCount = tieredStoreMaxGroupCommitCount; } public boolean isReadAheadCacheEnable() { return readAheadCacheEnable; } public void setReadAheadCacheEnable(boolean readAheadCacheEnable) { this.readAheadCacheEnable = readAheadCacheEnable; } public int getReadAheadMessageCountThreshold() { return readAheadMessageCountThreshold; } public void setReadAheadMessageCountThreshold(int readAheadMessageCountThreshold) { this.readAheadMessageCountThreshold = readAheadMessageCountThreshold; } public int getReadAheadMessageSizeThreshold() { return readAheadMessageSizeThreshold; } public void setReadAheadMessageSizeThreshold(int readAheadMessageSizeThreshold) { this.readAheadMessageSizeThreshold = readAheadMessageSizeThreshold; } public long getReadAheadCacheExpireDuration() { return readAheadCacheExpireDuration; } public void setReadAheadCacheExpireDuration(long duration) { this.readAheadCacheExpireDuration = duration; } public double getReadAheadCacheSizeThresholdRate() { return readAheadCacheSizeThresholdRate; } public void setReadAheadCacheSizeThresholdRate(double rate) { this.readAheadCacheSizeThresholdRate = rate; } public int getTieredStoreMaxPendingLimit() { return tieredStoreMaxPendingLimit; } public void setTieredStoreMaxPendingLimit(int tieredStoreMaxPendingLimit) { this.tieredStoreMaxPendingLimit = tieredStoreMaxPendingLimit; } public boolean isTieredStoreCrcCheckEnable() { return tieredStoreCrcCheckEnable; } public void setTieredStoreCrcCheckEnable(boolean tieredStoreCrcCheckEnable) { this.tieredStoreCrcCheckEnable = tieredStoreCrcCheckEnable; } public String getTieredStoreFilePath() { return tieredStoreFilePath; } public void setTieredStoreFilePath(String tieredStoreFilePath) { this.tieredStoreFilePath = tieredStoreFilePath; } public void setObjectStoreEndpoint(String objectStoreEndpoint) { this.objectStoreEndpoint = objectStoreEndpoint; } public String getObjectStoreBucket() { return objectStoreBucket; } public void setObjectStoreBucket(String objectStoreBucket) { this.objectStoreBucket = objectStoreBucket; } public String getObjectStoreAccessKey() { return objectStoreAccessKey; } public void setObjectStoreAccessKey(String objectStoreAccessKey) { this.objectStoreAccessKey = objectStoreAccessKey; } public String getObjectStoreSecretKey() { return objectStoreSecretKey; } public void setObjectStoreSecretKey(String objectStoreSecretKey) { this.objectStoreSecretKey = objectStoreSecretKey; } public String getObjectStoreEndpoint() { return objectStoreEndpoint; } public boolean isWriteWithoutMmap() { return writeWithoutMmap; } public void setWriteWithoutMmap(boolean writeWithoutMmap) { this.writeWithoutMmap = writeWithoutMmap; } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/MessageStoreExecutor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.utils.ThreadUtils; public class MessageStoreExecutor { public final BlockingQueue bufferCommitThreadPoolQueue; public final BlockingQueue bufferFetchThreadPoolQueue; public final BlockingQueue fileRecyclingThreadPoolQueue; public final ScheduledExecutorService commonExecutor; public final ExecutorService bufferCommitExecutor; public final ExecutorService bufferFetchExecutor; public final ExecutorService fileRecyclingExecutor; private static class SingletonHolder { private static final MessageStoreExecutor INSTANCE = new MessageStoreExecutor(); } public static MessageStoreExecutor getInstance() { return SingletonHolder.INSTANCE; } public MessageStoreExecutor() { this(10000); } public MessageStoreExecutor(int maxQueueCapacity) { this.commonExecutor = ThreadUtils.newScheduledThreadPool( Math.max(4, Runtime.getRuntime().availableProcessors()), new ThreadFactoryImpl("TieredCommonExecutor_")); this.bufferCommitThreadPoolQueue = new LinkedBlockingQueue<>(maxQueueCapacity); this.bufferCommitExecutor = ThreadUtils.newThreadPoolExecutor( Math.max(16, Runtime.getRuntime().availableProcessors() * 4), Math.max(16, Runtime.getRuntime().availableProcessors() * 4), TimeUnit.MINUTES.toMillis(1), TimeUnit.MILLISECONDS, this.bufferCommitThreadPoolQueue, new ThreadFactoryImpl("BufferCommitExecutor_")); this.bufferFetchThreadPoolQueue = new LinkedBlockingQueue<>(maxQueueCapacity); this.bufferFetchExecutor = ThreadUtils.newThreadPoolExecutor( Math.max(16, Runtime.getRuntime().availableProcessors() * 4), Math.max(16, Runtime.getRuntime().availableProcessors() * 4), TimeUnit.MINUTES.toMillis(1), TimeUnit.MILLISECONDS, this.bufferFetchThreadPoolQueue, new ThreadFactoryImpl("BufferFetchExecutor_")); this.fileRecyclingThreadPoolQueue = new LinkedBlockingQueue<>(maxQueueCapacity); this.fileRecyclingExecutor = ThreadUtils.newThreadPoolExecutor( Math.max(4, Runtime.getRuntime().availableProcessors()), Math.max(4, Runtime.getRuntime().availableProcessors()), TimeUnit.MINUTES.toMillis(1), TimeUnit.MILLISECONDS, this.fileRecyclingThreadPoolQueue, new ThreadFactoryImpl("BufferFetchExecutor_")); } private void shutdownExecutor(ExecutorService executor) { if (executor != null) { executor.shutdown(); } } public void shutdown() { this.shutdownExecutor(this.commonExecutor); this.shutdownExecutor(this.bufferCommitExecutor); this.shutdownExecutor(this.bufferFetchExecutor); this.shutdownExecutor(this.fileRecyclingExecutor); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore; import com.google.common.base.Stopwatch; import com.google.common.collect.Sets; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.lang.reflect.Constructor; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.QueryMessageResult; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.plugin.AbstractPluginMessageStore; import org.apache.rocketmq.store.plugin.MessageStorePluginContext; import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage; import org.apache.rocketmq.store.timer.rocksdb.TimerMessageRocksDBStore; import org.apache.rocketmq.store.transaction.TransMessageRocksDBStore; import org.apache.rocketmq.tieredstore.core.MessageStoreDispatcher; import org.apache.rocketmq.tieredstore.core.MessageStoreDispatcherImpl; import org.apache.rocketmq.tieredstore.core.MessageStoreFetcher; import org.apache.rocketmq.tieredstore.core.MessageStoreFetcherImpl; import org.apache.rocketmq.tieredstore.core.MessageStoreFilter; import org.apache.rocketmq.tieredstore.core.MessageStoreTopicFilter; import org.apache.rocketmq.tieredstore.file.FlatFileStore; import org.apache.rocketmq.tieredstore.file.FlatMessageFile; import org.apache.rocketmq.tieredstore.index.IndexService; import org.apache.rocketmq.tieredstore.index.IndexStoreService; import org.apache.rocketmq.tieredstore.metadata.MetadataStore; import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsConstant; import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsManager; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TieredMessageStore extends AbstractPluginMessageStore { protected static final Logger log = LoggerFactory.getLogger(MessageStoreUtil.TIERED_STORE_LOGGER_NAME); protected static final long MIN_STORE_TIME = -1L; protected final String brokerName; protected final MessageStore defaultStore; protected final MessageStoreConfig storeConfig; protected final MessageStorePluginContext context; protected final MetadataStore metadataStore; protected final MessageStoreExecutor storeExecutor; protected final IndexService indexService; protected final FlatFileStore flatFileStore; protected final MessageStoreFilter topicFilter; protected final MessageStoreFetcher fetcher; protected final MessageStoreDispatcher dispatcher; protected final MessageRocksDBStorage messageRocksDBStorage; protected TimerMessageRocksDBStore timerMessageRocksDBStore; protected TransMessageRocksDBStore transMessageRocksDBStore; public TieredMessageStore(MessageStorePluginContext context, MessageStore next) { super(context, next); this.storeConfig = new MessageStoreConfig(); this.context = context; this.context.registerConfiguration(this.storeConfig); this.storeConfig.setWriteWithoutMmap(context.getMessageStoreConfig().isWriteWithoutMmap()); this.brokerName = this.storeConfig.getBrokerName(); this.defaultStore = next; this.messageRocksDBStorage = defaultStore.getMessageRocksDBStorage(); this.metadataStore = this.getMetadataStore(this.storeConfig); this.topicFilter = new MessageStoreTopicFilter(this.storeConfig); this.storeExecutor = new MessageStoreExecutor(); this.flatFileStore = new FlatFileStore(this.storeConfig, this.metadataStore, this.storeExecutor); this.indexService = new IndexStoreService(this.flatFileStore.getFlatFileFactory(), MessageStoreUtil.getIndexFilePath(this.storeConfig.getBrokerName())); this.fetcher = new MessageStoreFetcherImpl(this); this.dispatcher = new MessageStoreDispatcherImpl(this); next.addDispatcher(dispatcher); } @Override public boolean load() { boolean loadFlatFile = flatFileStore.load(); boolean loadNextStore = next.load(); boolean result = loadFlatFile && loadNextStore; if (result) { indexService.start(); dispatcher.start(); storeExecutor.commonExecutor.scheduleWithFixedDelay( flatFileStore::scheduleDeleteExpireFile, storeConfig.getTieredStoreDeleteFileInterval(), storeConfig.getTieredStoreDeleteFileInterval(), TimeUnit.MILLISECONDS); } return result; } public String getBrokerName() { return brokerName; } public MessageStoreConfig getStoreConfig() { return storeConfig; } public MessageStore getDefaultStore() { return defaultStore; } private MetadataStore getMetadataStore(MessageStoreConfig storeConfig) { try { Class clazz = Class.forName(storeConfig.getTieredMetadataServiceProvider()).asSubclass(MetadataStore.class); Constructor constructor = clazz.getConstructor(MessageStoreConfig.class); return constructor.newInstance(storeConfig); } catch (Exception e) { throw new RuntimeException(e); } } public MetadataStore getMetadataStore() { return metadataStore; } public MessageStoreFilter getTopicFilter() { return topicFilter; } public MessageStoreExecutor getStoreExecutor() { return storeExecutor; } public FlatFileStore getFlatFileStore() { return flatFileStore; } public IndexService getIndexService() { return indexService; } public boolean fetchFromCurrentStore(String topic, int queueId, long offset) { return fetchFromCurrentStore(topic, queueId, offset, 1); } @SuppressWarnings("all") public boolean fetchFromCurrentStore(String topic, int queueId, long offset, int batchSize) { MessageStoreConfig.TieredStorageLevel storageLevel = storeConfig.getTieredStorageLevel(); if (storageLevel.check(MessageStoreConfig.TieredStorageLevel.FORCE)) { return true; } if (!storageLevel.isEnable()) { return false; } FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, queueId)); if (flatFile == null) { return false; } if (offset >= flatFile.getConsumeQueueCommitOffset()) { return false; } // determine whether tiered storage path conditions are met if (storageLevel.check(MessageStoreConfig.TieredStorageLevel.NOT_IN_DISK)) { // return true to read from tiered storage if the CommitLog is empty if (next != null && next.getCommitLog() != null && next.getCommitLog().getMinOffset() < 0L) { return true; } if (!next.checkInStoreByConsumeOffset(topic, queueId, offset)) { return true; } } if (storageLevel.check(MessageStoreConfig.TieredStorageLevel.NOT_IN_MEM) && !next.checkInMemByConsumeOffset(topic, queueId, offset, batchSize)) { return true; } return false; } @Override public GetMessageResult getMessage(String group, String topic, int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) { return getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter).join(); } @Override public CompletableFuture getMessageAsync(String group, String topic, int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) { // for system topic, force reading from local store if (topicFilter.filterTopic(topic)) { return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter); } if (fetchFromCurrentStore(topic, queueId, offset, maxMsgNums)) { log.trace("GetMessageAsync from remote store, " + "topic: {}, queue: {}, offset: {}, maxCount: {}", topic, queueId, offset, maxMsgNums); } else { log.trace("GetMessageAsync from next store, " + "topic: {}, queue: {}, offset: {}, maxCount: {}", topic, queueId, offset, maxMsgNums); return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter); } Stopwatch stopwatch = Stopwatch.createStarted(); return fetcher .getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter) .thenApply(result -> { Attributes latencyAttributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_OPERATION, TieredStoreMetricsConstant.OPERATION_API_GET_MESSAGE) .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .put(TieredStoreMetricsConstant.LABEL_GROUP, group) .build(); TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes); if (result.getStatus() == GetMessageStatus.OFFSET_FOUND_NULL || result.getStatus() == GetMessageStatus.NO_MATCHED_LOGIC_QUEUE) { if (next.checkInStoreByConsumeOffset(topic, queueId, offset)) { TieredStoreMetricsManager.fallbackTotal.add(1, latencyAttributes); log.debug("GetMessageAsync not found, then back to next store, result: {}, " + "topic: {}, queue: {}, queue offset: {}, offset range: {}-{}", result.getStatus(), topic, queueId, offset, result.getMinOffset(), result.getMaxOffset()); return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter); } } if (result.getStatus() != GetMessageStatus.FOUND && result.getStatus() != GetMessageStatus.NO_MESSAGE_IN_QUEUE && result.getStatus() != GetMessageStatus.NO_MATCHED_LOGIC_QUEUE && result.getStatus() != GetMessageStatus.OFFSET_TOO_SMALL && result.getStatus() != GetMessageStatus.OFFSET_OVERFLOW_ONE && result.getStatus() != GetMessageStatus.OFFSET_OVERFLOW_BADLY) { log.warn("GetMessageAsync not found and message is not in next store, result: {}, " + "topic: {}, queue: {}, queue offset: {}, offset range: {}-{}", result.getStatus(), topic, queueId, offset, result.getMinOffset(), result.getMaxOffset()); } if (result.getStatus() == GetMessageStatus.FOUND) { Attributes messagesOutAttributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .put(TieredStoreMetricsConstant.LABEL_GROUP, group) .build(); TieredStoreMetricsManager.messagesOutTotal.add(result.getMessageCount(), messagesOutAttributes); if (next.getStoreStatsService() != null) { next.getStoreStatsService().getGetMessageTransferredMsgCount().add(result.getMessageCount()); } } // Fix min or max offset according next store at last long minOffsetInQueue = next.getMinOffsetInQueue(topic, queueId); if (minOffsetInQueue >= 0 && minOffsetInQueue < result.getMinOffset()) { result.setMinOffset(minOffsetInQueue); } // In general, the local cq offset is slightly greater than the commit offset in read message, // so there is no need to update the maximum offset to the local cq offset here, // otherwise it will cause repeated consumption after next start offset over commit offset. if (storeConfig.isRecordGetMessageResult()) { log.info("GetMessageAsync result, {}, group: {}, topic: {}, queueId: {}, offset: {}, count:{}", result, group, topic, queueId, offset, maxMsgNums); } return result; }).exceptionally(e -> { log.error("GetMessageAsync from tiered store failed", e); return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter); }); } @Override public long getMinOffsetInQueue(String topic, int queueId) { long minOffsetInNextStore = next.getMinOffsetInQueue(topic, queueId); FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, queueId)); if (flatFile == null) { return minOffsetInNextStore; } long minOffsetInTieredStore = flatFile.getConsumeQueueMinOffset(); if (minOffsetInTieredStore < 0) { return minOffsetInNextStore; } return Math.min(minOffsetInNextStore, minOffsetInTieredStore); } @Override public TimerMessageRocksDBStore getTimerMessageRocksDBStore() { return timerMessageRocksDBStore; } @Override public TransMessageRocksDBStore getTransMessageRocksDBStore() { return transMessageRocksDBStore; } @Override public void setTimerMessageRocksDBStore(TimerMessageRocksDBStore timerMessageRocksDBStore) { this.timerMessageRocksDBStore = timerMessageRocksDBStore; } @Override public void setTransMessageRocksDBStore(TransMessageRocksDBStore transMessageRocksDBStore) { this.transMessageRocksDBStore = transMessageRocksDBStore; } @Override public long getEarliestMessageTime(String topic, int queueId) { return getEarliestMessageTimeAsync(topic, queueId).join(); } /** * In the original design, getting the earliest time of the first message * would generate two RPC requests. However, using the timestamp stored in the metadata * avoids these requests, although this approach might introduce some level of inaccuracy. */ @Override public CompletableFuture getEarliestMessageTimeAsync(String topic, int queueId) { long localMinTime = next.getEarliestMessageTime(topic, queueId); return fetcher.getEarliestMessageTimeAsync(topic, queueId) .thenApply(remoteMinTime -> { if (localMinTime > MIN_STORE_TIME && remoteMinTime > MIN_STORE_TIME) { return Math.min(localMinTime, remoteMinTime); } return localMinTime > MIN_STORE_TIME ? localMinTime : (remoteMinTime > MIN_STORE_TIME ? remoteMinTime : MIN_STORE_TIME); }); } @Override public long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) { return getMessageStoreTimeStampAsync(topic, queueId, consumeQueueOffset).join(); } @Override public CompletableFuture getMessageStoreTimeStampAsync(String topic, int queueId, long consumeQueueOffset) { if (fetchFromCurrentStore(topic, queueId, consumeQueueOffset)) { Stopwatch stopwatch = Stopwatch.createStarted(); return fetcher.getMessageStoreTimeStampAsync(topic, queueId, consumeQueueOffset) .thenApply(time -> { Attributes latencyAttributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_OPERATION, TieredStoreMetricsConstant.OPERATION_API_GET_TIME_BY_OFFSET) .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .build(); TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes); return time; }); } return next.getMessageStoreTimeStampAsync(topic, queueId, consumeQueueOffset); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp) { return getOffsetInQueueByTime(topic, queueId, timestamp, BoundaryType.LOWER); } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) { boolean isForce = storeConfig.getTieredStorageLevel() == MessageStoreConfig.TieredStorageLevel.FORCE; if (timestamp < next.getEarliestMessageTime() || isForce) { Stopwatch stopwatch = Stopwatch.createStarted(); long offsetInTieredStore = fetcher.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType); Attributes latencyAttributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_OPERATION, TieredStoreMetricsConstant.OPERATION_API_GET_OFFSET_BY_TIME) .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .build(); TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes); if (offsetInTieredStore == -1L && !isForce) { return next.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType); } return offsetInTieredStore; } return next.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType); } @Override public QueryMessageResult queryMessage(String topic, String key, int maxNum, long begin, long end) { return queryMessageAsync(topic, key, maxNum, begin, end).join(); } @Override public QueryMessageResult queryMessage(String topic, String key, int maxNum, long begin, long end, String keyType, String lastKey) { return queryMessageAsync(topic, key, maxNum, begin, end, keyType, lastKey).join(); } @Override public CompletableFuture queryMessageAsync(String topic, String key, int maxNum, long begin, long end) { long earliestTimeInNextStore = next.getEarliestMessageTime(); if (earliestTimeInNextStore <= 0) { log.warn("TieredMessageStore#queryMessageAsync: get earliest message time in next store failed: {}", earliestTimeInNextStore); } boolean isForce = storeConfig.getTieredStorageLevel() == MessageStoreConfig.TieredStorageLevel.FORCE; QueryMessageResult result = end < earliestTimeInNextStore || isForce ? new QueryMessageResult() : next.queryMessage(topic, key, maxNum, begin, end); int resultSize = result.getMessageBufferList().size(); if (resultSize < maxNum && begin < earliestTimeInNextStore || isForce) { Stopwatch stopwatch = Stopwatch.createStarted(); try { return fetcher.queryMessageAsync(topic, key, maxNum - resultSize, begin, isForce ? end : earliestTimeInNextStore) .thenApply(tieredStoreResult -> { Attributes latencyAttributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_OPERATION, TieredStoreMetricsConstant.OPERATION_API_QUERY_MESSAGE) .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .build(); TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes); for (SelectMappedBufferResult msg : tieredStoreResult.getMessageMapedList()) { result.addMessage(msg); } return result; }); } catch (Exception e) { log.error("TieredMessageStore#queryMessageAsync: query message in tiered store failed", e); return CompletableFuture.completedFuture(result); } } return CompletableFuture.completedFuture(result); } @Override public CompletableFuture queryMessageAsync(String topic, String key, int maxNum, long begin, long end, String indexType, String lastKey) { long earliestTimeInNextStore = next.getEarliestMessageTime(); if (earliestTimeInNextStore <= 0) { log.warn("TieredMessageStore queryMessageAsync: get earliest message time in next store failed: {}", earliestTimeInNextStore); } boolean isForce = storeConfig.getTieredStorageLevel() == MessageStoreConfig.TieredStorageLevel.FORCE; QueryMessageResult result = end < earliestTimeInNextStore || isForce ? new QueryMessageResult() : next.queryMessage(topic, key, maxNum, begin, end, indexType, lastKey); int resultSize = result.getMessageBufferList().size(); if (resultSize < maxNum && begin < earliestTimeInNextStore || isForce) { Stopwatch stopwatch = Stopwatch.createStarted(); try { return fetcher.queryMessageAsync(topic, key, maxNum - resultSize, begin, isForce ? end : earliestTimeInNextStore) .thenApply(tieredStoreResult -> { Attributes latencyAttributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_OPERATION, TieredStoreMetricsConstant.OPERATION_API_QUERY_MESSAGE) .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .build(); TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes); for (SelectMappedBufferResult msg : tieredStoreResult.getMessageMapedList()) { result.addMessage(msg); } return result; }); } catch (Exception e) { log.error("TieredMessageStore#queryMessageAsync: query message in tiered store failed", e); return CompletableFuture.completedFuture(result); } } return CompletableFuture.completedFuture(result); } @Override public List> getMetricsView() { List> res = super.getMetricsView(); res.addAll(TieredStoreMetricsManager.getMetricsView()); return res; } @Override public void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { super.initMetrics(meter, attributesBuilderSupplier); TieredStoreMetricsManager.init(meter, attributesBuilderSupplier, storeConfig, fetcher, flatFileStore, next); } @Override public MessageRocksDBStorage getMessageRocksDBStorage() { return messageRocksDBStorage; } @Override public int cleanUnusedTopic(Set retainTopics) { metadataStore.iterateTopic(topicMetadata -> { String topic = topicMetadata.getTopic(); if (retainTopics.contains(topic) || TopicValidator.isSystemTopic(topic) || MixAll.isLmq(topic)) { return; } this.deleteTopics(Sets.newHashSet(topicMetadata.getTopic())); }); return next.cleanUnusedTopic(retainTopics); } @Override public int deleteTopics(Set deleteTopics) { for (String topic : deleteTopics) { metadataStore.iterateQueue(topic, queueMetadata -> { flatFileStore.destroyFile(queueMetadata.getQueue()); }); metadataStore.deleteTopic(topic); log.info("MessageStore delete topic success, topicName={}", topic); } return next.deleteTopics(deleteTopics); } @Override public synchronized void shutdown() { if (next != null) { next.shutdown(); } if (dispatcher != null) { dispatcher.shutdown(); } if (indexService != null) { if (defaultStore.getRunningFlags() != null && defaultStore.getRunningFlags().isStoreWriteable()) { indexService.shutdown(); } else { indexService.forceShutdown(); } } if (flatFileStore != null) { flatFileStore.shutdown(); } if (storeExecutor != null) { storeExecutor.shutdown(); } } @Override public void destroy() { if (next != null) { next.destroy(); } if (indexService != null) { indexService.destroy(); } if (flatFileStore != null) { flatFileStore.destroy(); } if (metadataStore != null) { metadataStore.destroy(); } } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/AppendResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.common; public enum AppendResult { /** * The append operation was successful. */ SUCCESS, /** * The buffer used for the append operation is full. */ BUFFER_FULL, /** * The file is full and cannot accept more data. */ FILE_FULL, /** * The file is closed and cannot accept more data. */ FILE_CLOSED, /** * An unknown error occurred during the append operation. */ UNKNOWN_ERROR } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/FileSegmentType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.common; import java.util.Arrays; public enum FileSegmentType { COMMIT_LOG(0), CONSUME_QUEUE(1), INDEX(2); private final int code; FileSegmentType(int code) { this.code = code; } public int getCode() { return code; } public static FileSegmentType valueOf(int fileType) { return Arrays.stream(FileSegmentType.values()) .filter(segmentType -> segmentType.getCode() == fileType) .findFirst() .orElse(COMMIT_LOG); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/GetMessageResultExt.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.common; import java.util.ArrayList; import java.util.List; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.SelectMappedBufferResult; public class GetMessageResultExt extends GetMessageResult { private final List tagCodeList; public GetMessageResultExt() { this.tagCodeList = new ArrayList<>(); } public void addMessageExt(SelectMappedBufferResult bufferResult, long queueOffset, long tagCode) { super.addMessage(bufferResult, queueOffset); this.tagCodeList.add(tagCode); } public List getTagCodeList() { return tagCodeList; } /** * Due to the message fetched from the object storage is sequential, * do message filtering occurs after the data retrieval. */ public GetMessageResult doFilterMessage(MessageFilter messageFilter) { if (GetMessageStatus.FOUND != super.getStatus() || messageFilter == null) { return this; } GetMessageResult result = new GetMessageResult(); result.setStatus(GetMessageStatus.FOUND); result.setMinOffset(this.getMinOffset()); result.setMaxOffset(this.getMaxOffset()); result.setNextBeginOffset(this.getNextBeginOffset()); for (int i = 0; i < this.getMessageMapedList().size(); i++) { if (!messageFilter.isMatchedByConsumeQueue(this.tagCodeList.get(i), null)) { continue; } SelectMappedBufferResult bufferResult = this.getMessageMapedList().get(i); if (!messageFilter.isMatchedByCommitLog(bufferResult.getByteBuffer().slice(), null)) { continue; } long offset = this.getMessageQueueOffset().get(i); result.addMessage(new SelectMappedBufferResult(bufferResult.getStartOffset(), bufferResult.getByteBuffer().asReadOnlyBuffer(), bufferResult.getSize(), null), offset); } if (result.getBufferTotalSize() == 0) { result.setStatus(GetMessageStatus.NO_MATCHED_MESSAGE); } return result; } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/GroupCommitContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.common; import java.util.List; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; public class GroupCommitContext { private long endOffset; private List bufferList; private List dispatchRequests; public long getEndOffset() { return endOffset; } public void setEndOffset(long endOffset) { this.endOffset = endOffset; } public List getBufferList() { return bufferList; } public void setBufferList(List bufferList) { this.bufferList = bufferList; } public List getDispatchRequests() { return dispatchRequests; } public void setDispatchRequests(List dispatchRequests) { this.dispatchRequests = dispatchRequests; } public void release() { if (bufferList != null) { for (SelectMappedBufferResult bufferResult : bufferList) { bufferResult.release(); } bufferList.clear(); bufferList = null; } if (dispatchRequests != null) { dispatchRequests.clear(); dispatchRequests = null; } } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/SelectBufferResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.common; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicLong; public class SelectBufferResult { private final ByteBuffer byteBuffer; private final long startOffset; private final int size; private final long tagCode; private final AtomicLong accessCount; public SelectBufferResult(ByteBuffer byteBuffer, long startOffset, int size, long tagCode) { this.startOffset = startOffset; this.byteBuffer = byteBuffer; this.size = size; this.tagCode = tagCode; this.accessCount = new AtomicLong(); } public ByteBuffer getByteBuffer() { return byteBuffer; } public long getStartOffset() { return startOffset; } public int getSize() { return size; } public long getTagCode() { return tagCode; } public AtomicLong getAccessCount() { return accessCount; } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcher.java ================================================ [File too large to display: 1.2 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.core; import com.google.common.annotations.VisibleForTesting; import io.opentelemetry.api.common.Attributes; import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.exception.ConsumeQueueException; import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.tieredstore.MessageStoreConfig; import org.apache.rocketmq.tieredstore.MessageStoreExecutor; import org.apache.rocketmq.tieredstore.TieredMessageStore; import org.apache.rocketmq.tieredstore.common.AppendResult; import org.apache.rocketmq.tieredstore.common.FileSegmentType; import org.apache.rocketmq.tieredstore.common.GroupCommitContext; import org.apache.rocketmq.tieredstore.file.FlatFileInterface; import org.apache.rocketmq.tieredstore.file.FlatFileStore; import org.apache.rocketmq.tieredstore.index.IndexService; import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsConstant; import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsManager; import org.apache.rocketmq.tieredstore.util.MessageFormatUtil; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MessageStoreDispatcherImpl extends ServiceThread implements MessageStoreDispatcher { protected static final Logger log = LoggerFactory.getLogger(MessageStoreUtil.TIERED_STORE_LOGGER_NAME); protected final String brokerName; protected final MessageStore defaultStore; protected final MessageStoreConfig storeConfig; protected final TieredMessageStore messageStore; protected final FlatFileStore flatFileStore; protected final MessageStoreExecutor storeExecutor; protected final MessageStoreFilter topicFilter; protected final Semaphore semaphore; protected final IndexService indexService; protected final Map failedGroupCommitMap; public MessageStoreDispatcherImpl(TieredMessageStore messageStore) { this.messageStore = messageStore; this.storeConfig = messageStore.getStoreConfig(); this.defaultStore = messageStore.getDefaultStore(); this.brokerName = storeConfig.getBrokerName(); this.semaphore = new Semaphore( this.storeConfig.getTieredStoreMaxPendingLimit() / 4); this.topicFilter = messageStore.getTopicFilter(); this.flatFileStore = messageStore.getFlatFileStore(); this.storeExecutor = messageStore.getStoreExecutor(); this.indexService = messageStore.getIndexService(); this.failedGroupCommitMap = new ConcurrentHashMap<>(); } @Override public String getServiceName() { return MessageStoreDispatcher.class.getSimpleName(); } @VisibleForTesting public Map getFailedGroupCommitMap() { return failedGroupCommitMap; } public void dispatchWithSemaphore(FlatFileInterface flatFile) { try { if (stopped) { return; } semaphore.acquire(); this.doScheduleDispatch(flatFile, false) .whenComplete((future, throwable) -> semaphore.release()); } catch (Throwable t) { semaphore.release(); log.error("MessageStore dispatch error, topic={}, queueId={}", flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId(), t); } } @Override public void dispatch(DispatchRequest request) { if (stopped || topicFilter != null && topicFilter.filterTopic(request.getTopic())) { return; } flatFileStore.computeIfAbsent( new MessageQueue(request.getTopic(), brokerName, request.getQueueId())); } @Override public CompletableFuture doScheduleDispatch(FlatFileInterface flatFile, boolean force) { if (stopped) { return CompletableFuture.completedFuture(true); } String topic = flatFile.getMessageQueue().getTopic(); int queueId = flatFile.getMessageQueue().getQueueId(); // For test scenarios, we set the 'force' variable to true to // ensure that the data in the cache is directly committed successfully. force = !storeConfig.isTieredStoreGroupCommit() || force; if (force) { flatFile.getFileLock().lock(); } else { if (!flatFile.getFileLock().tryLock()) { return CompletableFuture.completedFuture(false); } } try { if (topicFilter != null && topicFilter.filterTopic(flatFile.getMessageQueue().getTopic())) { flatFileStore.destroyFile(flatFile.getMessageQueue()); return CompletableFuture.completedFuture(false); } long currentOffset = flatFile.getConsumeQueueMaxOffset(); long commitOffset = flatFile.getConsumeQueueCommitOffset(); long minOffsetInQueue = defaultStore.getMinOffsetInQueue(topic, queueId); long maxOffsetInQueue = defaultStore.getMaxOffsetInQueue(topic, queueId); // If set to max offset here, some written messages may be lost if (!flatFile.isFlatFileInit()) { currentOffset = defaultStore.getOffsetInQueueByTime( topic, queueId, System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2)); currentOffset = Math.max(currentOffset, minOffsetInQueue); currentOffset = Math.min(currentOffset, maxOffsetInQueue); flatFile.initOffset(currentOffset); log.warn("MessageDispatcher#dispatch init, topic={}, queueId={}, offset={}-{}, current={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset); return CompletableFuture.completedFuture(true); } // If the previous commit fails, attempt to trigger a commit directly. if (commitOffset < currentOffset) { this.commitAsync(flatFile).whenComplete((result, throwable) -> { if (throwable != null) { log.error("MessageDispatcher#flatFile commitOffset less than currentOffset, commitAsync again failed. topic: {}, queueId: {} ", topic, queueId, throwable); } }); return CompletableFuture.completedFuture(false); } if (failedGroupCommitMap.containsKey(flatFile)) { GroupCommitContext failedCommit = failedGroupCommitMap.get(flatFile); if (failedCommit.getEndOffset() <= commitOffset) { failedGroupCommitMap.remove(flatFile); constructIndexFile(flatFile.getTopicId(), failedCommit); } } if (currentOffset < minOffsetInQueue) { log.warn("MessageDispatcher#dispatch, current offset is too small, topic={}, queueId={}, offset={}-{}, current={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset); flatFileStore.destroyFile(flatFile.getMessageQueue()); flatFileStore.computeIfAbsent(new MessageQueue(topic, brokerName, queueId)); return CompletableFuture.completedFuture(true); } if (currentOffset > maxOffsetInQueue) { log.warn("MessageDispatcher#dispatch, current offset is too large, topic={}, queueId={}, offset={}-{}, current={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset); return CompletableFuture.completedFuture(false); } long interval = TimeUnit.HOURS.toMillis(storeConfig.getCommitLogRollingInterval()); if (flatFile.rollingFile(interval)) { log.info("MessageDispatcher#dispatch, rolling file, topic={}, queueId={}, offset={}-{}, current={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset); } if (currentOffset == maxOffsetInQueue) { return CompletableFuture.completedFuture(false); } long bufferSize = 0L; long groupCommitSize = storeConfig.getTieredStoreGroupCommitSize(); long groupCommitCount = storeConfig.getTieredStoreGroupCommitCount(); long targetOffset = Math.min(currentOffset + groupCommitCount, maxOffsetInQueue); ConsumeQueueInterface consumeQueue = defaultStore.getConsumeQueue(topic, queueId); CqUnit cqUnit = consumeQueue.get(currentOffset); if (cqUnit == null) { log.warn("MessageDispatcher#dispatch cq not found, topic={}, queueId={}, offset={}-{}, current={}, remain={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset, maxOffsetInQueue - currentOffset); return CompletableFuture.completedFuture(false); } SelectMappedBufferResult message = defaultStore.selectOneMessageByOffset(cqUnit.getPos(), cqUnit.getSize()); if (message == null) { log.warn("MessageDispatcher#dispatch message not found, topic={}, queueId={}, offset={}-{}, current={}, remain={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset, maxOffsetInQueue - currentOffset); return CompletableFuture.completedFuture(false); } boolean timeout = MessageFormatUtil.getStoreTimeStamp(message.getByteBuffer()) + storeConfig.getTieredStoreGroupCommitTimeout() < System.currentTimeMillis(); boolean bufferFull = maxOffsetInQueue - currentOffset > storeConfig.getTieredStoreGroupCommitCount(); if (!timeout && !bufferFull && !force) { log.debug("MessageDispatcher#dispatch hold, topic={}, queueId={}, offset={}-{}, current={}, remain={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset, maxOffsetInQueue - currentOffset); message.release(); return CompletableFuture.completedFuture(false); } else { if (MessageFormatUtil.getStoreTimeStamp(message.getByteBuffer()) + TimeUnit.MINUTES.toMillis(5) < System.currentTimeMillis()) { log.warn("MessageDispatcher#dispatch behind too much, topic={}, queueId={}, offset={}-{}, current={}, remain={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset, maxOffsetInQueue - currentOffset); } else { log.info("MessageDispatcher#dispatch success, topic={}, queueId={}, offset={}-{}, current={}, remain={}", topic, queueId, minOffsetInQueue, maxOffsetInQueue, currentOffset, maxOffsetInQueue - currentOffset); } message.release(); } long offset = currentOffset; List appendingBufferList = new ArrayList<>(); List dispatchRequestList = new ArrayList<>(); for (; offset < targetOffset; offset++) { cqUnit = consumeQueue.get(offset); bufferSize += cqUnit.getSize(); if (bufferSize >= groupCommitSize) { break; } message = defaultStore.selectOneMessageByOffset(cqUnit.getPos(), cqUnit.getSize()); appendingBufferList.add(message); ByteBuffer byteBuffer = message.getByteBuffer(); AppendResult result = flatFile.appendCommitLog(message); if (!AppendResult.SUCCESS.equals(result)) { break; } long mappedCommitLogOffset = flatFile.getCommitLogMaxOffset() - byteBuffer.remaining(); Map properties = MessageFormatUtil.getProperties(byteBuffer); DispatchRequest dispatchRequest = new DispatchRequest(topic, queueId, mappedCommitLogOffset, cqUnit.getSize(), cqUnit.getTagsCode(), MessageFormatUtil.getStoreTimeStamp(byteBuffer), cqUnit.getQueueOffset(), properties.getOrDefault(MessageConst.PROPERTY_KEYS, ""), properties.getOrDefault(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, ""), 0, 0, new HashMap<>()); dispatchRequest.setOffsetId(MessageFormatUtil.getOffsetId(byteBuffer)); result = flatFile.appendConsumeQueue(dispatchRequest); if (!AppendResult.SUCCESS.equals(result)) { break; } else { dispatchRequestList.add(dispatchRequest); } } GroupCommitContext groupCommitContext = new GroupCommitContext(); groupCommitContext.setEndOffset(offset); groupCommitContext.setBufferList(appendingBufferList); groupCommitContext.setDispatchRequests(dispatchRequestList); // If there are many messages waiting to be uploaded, call the upload logic immediately. boolean repeat = timeout || maxOffsetInQueue - offset > storeConfig.getTieredStoreGroupCommitCount(); if (!dispatchRequestList.isEmpty()) { Attributes attributes = TieredStoreMetricsManager.newAttributesBuilder() .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic) .put(TieredStoreMetricsConstant.LABEL_QUEUE_ID, queueId) .put(TieredStoreMetricsConstant.LABEL_FILE_TYPE, FileSegmentType.COMMIT_LOG.name().toLowerCase()) .build(); TieredStoreMetricsManager.messagesDispatchTotal.add(offset - currentOffset, attributes); this.commitAsync(flatFile).whenComplete((success, throwable) -> { if (success) { constructIndexFile(flatFile.getTopicId(), groupCommitContext); } else { //next commit async,execute constructIndexFile. GroupCommitContext oldCommit = failedGroupCommitMap.put(flatFile, groupCommitContext); if (oldCommit != null) { log.warn("MessageDispatcher#commitAsync failed,flatFile old failed commit context not release, topic={}, queueId={} ", topic, queueId); oldCommit.release(); } } if (success && repeat) { storeExecutor.commonExecutor.submit(() -> dispatchWithSemaphore(flatFile)); } } ); } } catch (ConsumeQueueException e) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(e); return future; } finally { flatFile.getFileLock().unlock(); } return CompletableFuture.completedFuture(false); } public CompletableFuture commitAsync(FlatFileInterface flatFile) { return flatFile.commitAsync(); } public void constructIndexFile(long topicId, GroupCommitContext groupCommitContext) { MessageStoreExecutor.getInstance().bufferCommitExecutor.submit(() -> { if (storeConfig.isMessageIndexEnable()) { try { groupCommitContext.getDispatchRequests().forEach(request -> constructIndexFile0(topicId, request)); } catch (Throwable e) { log.error("constructIndexFile error {}", topicId, e); } } groupCommitContext.release(); }); } /** * Building indexes with offsetId is no longer supported because offsetId has changed in tiered storage */ public void constructIndexFile0(long topicId, DispatchRequest request) { Set keySet = new HashSet<>(); if (StringUtils.isNotBlank(request.getUniqKey())) { keySet.add(request.getUniqKey()); } if (StringUtils.isNotBlank(request.getKeys())) { keySet.addAll(Arrays.asList(request.getKeys().split(MessageConst.KEY_SEPARATOR))); } indexService.putKey(request.getTopic(), (int) topicId, request.getQueueId(), keySet, request.getCommitLogOffset(), request.getMsgSize(), request.getStoreTimestamp()); } public void releaseClosedPendingGroupCommit() { Iterator> iterator = failedGroupCommitMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (entry.getKey().isClosed()) { entry.getValue().release(); iterator.remove(); } } } @Override public void run() { log.info("{} service started", this.getServiceName()); while (!this.isStopped()) { try { flatFileStore.deepCopyFlatFileToList().forEach(this::dispatchWithSemaphore); releaseClosedPendingGroupCommit(); this.waitForRunning(Duration.ofSeconds(20).toMillis()); } catch (Throwable t) { log.error("MessageStore dispatch error", t); } } log.info("{} service shutdown", this.getServiceName()); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreFetcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.core; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.QueryMessageResult; public interface MessageStoreFetcher { /** * Asynchronous get the store time of the earliest message in this store. * * @return timestamp of the earliest message in this store. */ CompletableFuture getEarliestMessageTimeAsync(String topic, int queueId); /** * Asynchronous get the store time of the message specified. * * @param topic Message topic. * @param queueId Queue ID. * @param consumeQueueOffset Consume queue offset. * @return store timestamp of the message. */ CompletableFuture getMessageStoreTimeStampAsync(String topic, int queueId, long consumeQueueOffset); /** * Look up the physical offset of the message whose store timestamp is as specified. * * @param topic Topic of the message. * @param queueId Queue ID. * @param timestamp Timestamp to look up. * @return physical offset which matches. */ long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType type); /** * Asynchronous get message * * @param group Consumer group that launches this query. * @param topic Topic to query. * @param queueId Queue ID to query. * @param offset Logical offset to start from. * @param maxCount Maximum count of messages to query. * @param messageFilter Message filter used to screen desired messages. * @return Matched messages. */ CompletableFuture getMessageAsync( String group, String topic, int queueId, long offset, int maxCount, MessageFilter messageFilter); /** * Asynchronous query messages by given key. * * @param topic Topic of the message. * @param key Message key. * @param maxCount Maximum count of the messages possible. * @param begin Begin timestamp. * @param end End timestamp. */ CompletableFuture queryMessageAsync( String topic, String key, int maxCount, long begin, long end); } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreFetcherImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.core; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Scheduler; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.QueryMessageResult; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.tieredstore.MessageStoreConfig; import org.apache.rocketmq.tieredstore.TieredMessageStore; import org.apache.rocketmq.tieredstore.common.GetMessageResultExt; import org.apache.rocketmq.tieredstore.common.SelectBufferResult; import org.apache.rocketmq.tieredstore.exception.TieredStoreException; import org.apache.rocketmq.tieredstore.file.FlatFileStore; import org.apache.rocketmq.tieredstore.file.FlatMessageFile; import org.apache.rocketmq.tieredstore.index.IndexItem; import org.apache.rocketmq.tieredstore.index.IndexService; import org.apache.rocketmq.tieredstore.metadata.MetadataStore; import org.apache.rocketmq.tieredstore.metadata.entity.TopicMetadata; import org.apache.rocketmq.tieredstore.util.MessageFormatUtil; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MessageStoreFetcherImpl implements MessageStoreFetcher { private static final Logger log = LoggerFactory.getLogger(MessageStoreUtil.TIERED_STORE_LOGGER_NAME); protected static final String CACHE_KEY_FORMAT = "%s@%d@%d"; protected static final String FETCHER_GROUP_NAME = MixAll.CID_RMQ_SYS_PREFIX + "FETCHER_TIMESTAMP"; private final String brokerName; private final MetadataStore metadataStore; private final MessageStoreConfig storeConfig; private final TieredMessageStore messageStore; private final IndexService indexService; private final FlatFileStore flatFileStore; private final long memoryMaxSize; private final Cache fetcherCache; public MessageStoreFetcherImpl(TieredMessageStore messageStore) { this(messageStore, messageStore.getStoreConfig(), messageStore.getFlatFileStore(), messageStore.getIndexService()); } public MessageStoreFetcherImpl(TieredMessageStore messageStore, MessageStoreConfig storeConfig, FlatFileStore flatFileStore, IndexService indexService) { this.storeConfig = storeConfig; this.brokerName = storeConfig.getBrokerName(); this.flatFileStore = flatFileStore; this.messageStore = messageStore; this.indexService = indexService; this.metadataStore = flatFileStore.getMetadataStore(); this.memoryMaxSize = (long) (Runtime.getRuntime().maxMemory() * storeConfig.getReadAheadCacheSizeThresholdRate()); this.fetcherCache = this.initCache(storeConfig); log.info("MessageStoreFetcher init success, brokerName={}", storeConfig.getBrokerName()); } private Cache initCache(MessageStoreConfig storeConfig) { return Caffeine.newBuilder() .scheduler(Scheduler.systemScheduler()) // Clients may repeatedly request messages at the same offset in tiered storage, // causing the request queue to become full. Using expire after read or write policy // to refresh the cache expiration time. .expireAfterAccess(storeConfig.getReadAheadCacheExpireDuration(), TimeUnit.MILLISECONDS) .maximumWeight(memoryMaxSize) // Using the buffer size of messages to calculate memory usage .weigher((String key, SelectBufferResult buffer) -> buffer.getSize()) .recordStats() .build(); } public Cache getFetcherCache() { return fetcherCache; } protected void putMessageToCache(FlatMessageFile flatFile, long offset, SelectBufferResult result) { MessageQueue mq = flatFile.getMessageQueue(); this.fetcherCache.put(String.format(CACHE_KEY_FORMAT, mq.getTopic(), mq.getQueueId(), offset), result); } protected SelectBufferResult getMessageFromCache(FlatMessageFile flatFile, long offset) { MessageQueue mq = flatFile.getMessageQueue(); SelectBufferResult buffer = this.fetcherCache.getIfPresent( String.format(CACHE_KEY_FORMAT, mq.getTopic(), mq.getQueueId(), offset)); // return duplicate buffer here if (buffer == null) { return null; } long count = buffer.getAccessCount().incrementAndGet(); if (count % 1000L == 0L) { log.warn("MessageFetcher fetch same offset message too many times, " + "topic={}, queueId={}, offset={}, count={}", mq.getTopic(), mq.getQueueId(), offset, count); } return new SelectBufferResult( buffer.getByteBuffer().asReadOnlyBuffer(), buffer.getStartOffset(), buffer.getSize(), buffer.getTagCode()); } protected GetMessageResultExt getMessageFromCache( FlatMessageFile flatFile, long offset, int maxCount, MessageFilter messageFilter) { GetMessageResultExt result = new GetMessageResultExt(); int interval = storeConfig.getReadAheadMessageCountThreshold(); for (long current = offset, end = offset + interval; current < end; current++) { SelectBufferResult buffer = getMessageFromCache(flatFile, current); if (buffer == null) { result.setNextBeginOffset(current); break; } result.setNextBeginOffset(current + 1); if (messageFilter != null) { if (!messageFilter.isMatchedByConsumeQueue(buffer.getTagCode(), null)) { continue; } if (!messageFilter.isMatchedByCommitLog(buffer.getByteBuffer().slice(), null)) { continue; } } SelectMappedBufferResult bufferResult = new SelectMappedBufferResult( buffer.getStartOffset(), buffer.getByteBuffer(), buffer.getSize(), null); result.addMessageExt(bufferResult, current, buffer.getTagCode()); if (result.getMessageCount() == maxCount) { break; } long maxTransferBytes = messageStore.getMessageStoreConfig().getMaxTransferBytesOnMessageInMemory(); if (result.getBufferTotalSize() >= maxTransferBytes) { break; } } result.setStatus(result.getMessageCount() > 0 ? GetMessageStatus.FOUND : GetMessageStatus.NO_MATCHED_MESSAGE); result.setMinOffset(flatFile.getConsumeQueueMinOffset()); result.setMaxOffset(flatFile.getConsumeQueueCommitOffset()); return result; } protected CompletableFuture fetchMessageThenPutToCache( FlatMessageFile flatFile, long queueOffset, int batchSize) { MessageQueue mq = flatFile.getMessageQueue(); return this.getMessageFromTieredStoreAsync(flatFile, queueOffset, batchSize) .thenApply(result -> { if (result.getStatus() == GetMessageStatus.OFFSET_OVERFLOW_ONE || result.getStatus() == GetMessageStatus.OFFSET_OVERFLOW_BADLY) { return -1L; } if (result.getStatus() != GetMessageStatus.FOUND) { log.warn("MessageFetcher prefetch message then put to cache failed, result={}, " + "topic={}, queue={}, queue offset={}, batch size={}", result.getStatus(), mq.getTopic(), mq.getQueueId(), queueOffset, batchSize); return -1L; } List offsetList = result.getMessageQueueOffset(); List tagCodeList = result.getTagCodeList(); List msgList = result.getMessageMapedList(); for (int i = 0; i < offsetList.size(); i++) { SelectMappedBufferResult msg = msgList.get(i); SelectBufferResult bufferResult = new SelectBufferResult( msg.getByteBuffer(), msg.getStartOffset(), msg.getSize(), tagCodeList.get(i)); this.putMessageToCache(flatFile, queueOffset + i, bufferResult); } return offsetList.get(offsetList.size() - 1); }); } public CompletableFuture getMessageFromCacheAsync( FlatMessageFile flatFile, String group, long queueOffset, int maxCount, MessageFilter messageFilter) { MessageQueue mq = flatFile.getMessageQueue(); GetMessageResultExt result = getMessageFromCache(flatFile, queueOffset, maxCount, messageFilter); if (GetMessageStatus.FOUND.equals(result.getStatus())) { log.debug("MessageFetcher cache hit, group={}, topic={}, queueId={}, offset={}, maxCount={}, resultSize={}, lag={}", group, mq.getTopic(), mq.getQueueId(), queueOffset, maxCount, result.getMessageCount(), result.getMaxOffset() - result.getNextBeginOffset()); return CompletableFuture.completedFuture(result); } // If cache miss, pull messages immediately log.debug("MessageFetcher cache miss, group={}, topic={}, queueId={}, offset={}, maxCount={}, lag={}", group, mq.getTopic(), mq.getQueueId(), queueOffset, maxCount, result.getMaxOffset() - result.getNextBeginOffset()); // To optimize the performance of pop consumption // Pop revive will cause a large number of random reads, // so the amount of pre-fetch message num needs to be reduced. int fetchSize = maxCount == 1 ? 32 : storeConfig.getReadAheadMessageCountThreshold(); // In the current design, when the min offset cache expires, // this method may trigger an RPC call, causing buffer fetch thread starvation return fetchMessageThenPutToCache(flatFile, queueOffset, fetchSize) .thenApplyAsync(maxOffset -> getMessageFromCache(flatFile, queueOffset, maxCount, messageFilter), messageStore.getStoreExecutor().commonExecutor); } public CompletableFuture getMessageFromTieredStoreAsync( FlatMessageFile flatFile, long queueOffset, int batchSize) { GetMessageResultExt result = new GetMessageResultExt(); result.setMinOffset(flatFile.getConsumeQueueMinOffset()); result.setMaxOffset(flatFile.getConsumeQueueCommitOffset()); if (queueOffset < result.getMinOffset()) { result.setStatus(GetMessageStatus.OFFSET_TOO_SMALL); result.setNextBeginOffset(result.getMinOffset()); return CompletableFuture.completedFuture(result); } else if (queueOffset == result.getMaxOffset()) { result.setStatus(GetMessageStatus.OFFSET_OVERFLOW_ONE); result.setNextBeginOffset(queueOffset); return CompletableFuture.completedFuture(result); } else if (queueOffset > result.getMaxOffset()) { result.setStatus(GetMessageStatus.OFFSET_OVERFLOW_BADLY); result.setNextBeginOffset(result.getMaxOffset()); return CompletableFuture.completedFuture(result); } if (queueOffset < result.getMaxOffset()) { batchSize = Math.min(batchSize, (int) Math.min( result.getMaxOffset() - queueOffset, storeConfig.getReadAheadMessageCountThreshold())); } CompletableFuture readConsumeQueueFuture; try { readConsumeQueueFuture = flatFile.getConsumeQueueAsync(queueOffset, batchSize); } catch (TieredStoreException e) { switch (e.getErrorCode()) { case ILLEGAL_PARAM: case ILLEGAL_OFFSET: default: result.setStatus(GetMessageStatus.OFFSET_FOUND_NULL); result.setNextBeginOffset(queueOffset); return CompletableFuture.completedFuture(result); } } int finalBatchSize = batchSize; CompletableFuture readCommitLogFuture = readConsumeQueueFuture.thenCompose(cqBuffer -> { long firstCommitLogOffset = MessageFormatUtil.getCommitLogOffsetFromItem(cqBuffer); cqBuffer.position(cqBuffer.remaining() - MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE); long lastCommitLogOffset = MessageFormatUtil.getCommitLogOffsetFromItem(cqBuffer); if (lastCommitLogOffset < firstCommitLogOffset) { log.error("MessageFetcher#getMessageFromTieredStoreAsync, last offset is smaller than first offset, " + "topic={} queueId={}, offset={}, firstOffset={}, lastOffset={}", flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId(), queueOffset, firstCommitLogOffset, lastCommitLogOffset); return CompletableFuture.completedFuture(ByteBuffer.allocate(0)); } // Get at least one message // Reducing the length limit of cq to prevent OOM long length = lastCommitLogOffset - firstCommitLogOffset + MessageFormatUtil.getSizeFromItem(cqBuffer); while (cqBuffer.limit() > MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE && length > storeConfig.getReadAheadMessageSizeThreshold()) { cqBuffer.limit(cqBuffer.position()); cqBuffer.position(cqBuffer.limit() - MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE); length = MessageFormatUtil.getCommitLogOffsetFromItem(cqBuffer) - firstCommitLogOffset + MessageFormatUtil.getSizeFromItem(cqBuffer); } int messageCount = cqBuffer.position() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE + 1; log.info("MessageFetcher#getMessageFromTieredStoreAsync, " + "topic={}, queueId={}, broker offset={}-{}, offset={}, expect={}, actually={}, lag={}", flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId(), result.getMinOffset(), result.getMaxOffset(), queueOffset, finalBatchSize, messageCount, result.getMaxOffset() - queueOffset); return flatFile.getCommitLogAsync(firstCommitLogOffset, (int) length); }); return readConsumeQueueFuture.thenCombine(readCommitLogFuture, (cqBuffer, msgBuffer) -> { List bufferList = MessageFormatUtil.splitMessageBuffer(cqBuffer, msgBuffer); int requestSize = cqBuffer.remaining() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE; // not use buffer list size to calculate next offset to prevent split error if (bufferList.isEmpty()) { result.setStatus(GetMessageStatus.NO_MATCHED_MESSAGE); result.setNextBeginOffset(queueOffset + requestSize); } else { result.setStatus(GetMessageStatus.FOUND); result.setNextBeginOffset(queueOffset + requestSize); for (SelectBufferResult bufferResult : bufferList) { ByteBuffer slice = bufferResult.getByteBuffer().slice(); slice.limit(bufferResult.getSize()); SelectMappedBufferResult msg = new SelectMappedBufferResult(bufferResult.getStartOffset(), bufferResult.getByteBuffer(), bufferResult.getSize(), null); result.addMessageExt(msg, MessageFormatUtil.getQueueOffset(slice), bufferResult.getTagCode()); } } return result; }).exceptionally(e -> { MessageQueue mq = flatFile.getMessageQueue(); log.warn("MessageFetcher#getMessageFromTieredStoreAsync failed, " + "topic={} queueId={}, offset={}, batchSize={}", mq.getTopic(), mq.getQueueId(), queueOffset, finalBatchSize, e); result.setStatus(GetMessageStatus.OFFSET_FOUND_NULL); result.setNextBeginOffset(queueOffset); return result; }); } @Override public CompletableFuture getMessageAsync( String group, String topic, int queueId, long queueOffset, int maxCount, final MessageFilter messageFilter) { GetMessageResult result = new GetMessageResult(); FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, queueId)); if (flatFile == null) { result.setNextBeginOffset(queueOffset); result.setStatus(GetMessageStatus.NO_MATCHED_LOGIC_QUEUE); return CompletableFuture.completedFuture(result); } // Max queue offset means next message put position result.setMinOffset(flatFile.getConsumeQueueMinOffset()); result.setMaxOffset(flatFile.getConsumeQueueCommitOffset()); // Fill result according file offset. // Offset range | Result | Fix to // (-oo, 0] | no message | current offset // (0, min) | too small | min offset // [min, max) | correct | // [max, max] | overflow one | max offset // (max, +oo) | overflow badly | max offset if (result.getMaxOffset() <= 0) { result.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); result.setNextBeginOffset(queueOffset); return CompletableFuture.completedFuture(result); } else if (queueOffset < result.getMinOffset()) { result.setStatus(GetMessageStatus.OFFSET_TOO_SMALL); result.setNextBeginOffset(result.getMinOffset()); return CompletableFuture.completedFuture(result); } else if (queueOffset == result.getMaxOffset()) { result.setStatus(GetMessageStatus.OFFSET_OVERFLOW_ONE); result.setNextBeginOffset(result.getMaxOffset()); return CompletableFuture.completedFuture(result); } else if (queueOffset > result.getMaxOffset()) { result.setStatus(GetMessageStatus.OFFSET_OVERFLOW_BADLY); result.setNextBeginOffset(result.getMaxOffset()); return CompletableFuture.completedFuture(result); } boolean cacheBusy = fetcherCache.estimatedSize() > memoryMaxSize * 0.8; if (storeConfig.isReadAheadCacheEnable() && !cacheBusy) { return getMessageFromCacheAsync(flatFile, group, queueOffset, maxCount, messageFilter); } else { return getMessageFromTieredStoreAsync(flatFile, queueOffset, maxCount) .thenApply(messageResultExt -> messageResultExt.doFilterMessage(messageFilter)); } } @Override public CompletableFuture getEarliestMessageTimeAsync(String topic, int queueId) { FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, queueId)); return CompletableFuture.completedFuture(flatFile == null ? -1L : flatFile.getMinStoreTimestamp()); } @Override public CompletableFuture getMessageStoreTimeStampAsync(String topic, int queueId, long queueOffset) { FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, queueId)); if (flatFile == null) { return CompletableFuture.completedFuture(-1L); } // The Metrics thread frequently retrieves the storage timestamp of the latest message; // as an alternative, return the queue's saved timestamp here. if (queueOffset + 1L == flatFile.getConsumeQueueCommitOffset()) { long timestamp = flatFile.getMaxStoreTimestamp(); return CompletableFuture.completedFuture(timestamp == Long.MAX_VALUE ? -1L : timestamp); } CompletableFuture future = new CompletableFuture<>(); try { this.getMessageAsync(FETCHER_GROUP_NAME, topic, queueId, queueOffset, 1, null) .whenComplete((result, e) -> { if (e != null) { log.error("MessageStoreFetcherImpl#getMessageStoreTimeStampAsync: " + "Get or decode message failed, topic={}, queue={}, offset={}", topic, queueId, queueOffset, e); future.completeExceptionally(e); return; } if (result != null && result.getMessageBufferList() != null && !result.getMessageBufferList().isEmpty()) { long timestamp = MessageFormatUtil.getStoreTimeStamp(result.getMessageBufferList().get(0)); log.info("MessageStoreFetcherImpl#getMessageStoreTimeStampAsync: " + "topic={}, queue={}, offset={}, timestamp={}", topic, queueId, queueOffset, timestamp); future.complete(timestamp); } else { future.complete(-1L); } }); } catch (Throwable t) { future.completeExceptionally(t); } return future; } @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType type) { FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, queueId)); if (flatFile == null) { return -1L; } return flatFile.getQueueOffsetByTimeAsync(timestamp, type).join(); } @Override public CompletableFuture queryMessageAsync( String topic, String key, int maxCount, long begin, long end) { long topicId; try { TopicMetadata topicMetadata = metadataStore.getTopic(topic); if (topicMetadata == null) { log.info("MessageFetcher#queryMessageAsync, topic metadata not found, topic={}", topic); return CompletableFuture.completedFuture(new QueryMessageResult()); } topicId = topicMetadata.getTopicId(); } catch (Exception e) { log.error("MessageFetcher#queryMessageAsync, get topic id failed, topic={}", topic, e); return CompletableFuture.completedFuture(new QueryMessageResult()); } CompletableFuture> future = indexService.queryAsync(topic, key, maxCount, begin, end); return future.thenCompose(indexItemList -> { List> futureList = new ArrayList<>(maxCount); for (IndexItem indexItem : indexItemList) { if (topicId != indexItem.getTopicId()) { continue; } FlatMessageFile flatFile = flatFileStore.getFlatFile(new MessageQueue(topic, brokerName, indexItem.getQueueId())); if (flatFile == null) { continue; } if (indexItem.getOffset() < flatFile.getCommitLogMinOffset() || indexItem.getOffset() > flatFile.getCommitLogMaxOffset()) { continue; } CompletableFuture getMessageFuture = flatFile .getCommitLogAsync(indexItem.getOffset(), indexItem.getSize()) .thenApply(messageBuffer -> new SelectMappedBufferResult( indexItem.getOffset(), messageBuffer, indexItem.getSize(), null)); futureList.add(getMessageFuture); if (futureList.size() >= maxCount) { break; } } return CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).thenApply(v -> { QueryMessageResult result = new QueryMessageResult(); futureList.forEach(f -> f.thenAccept(result::addMessage)); return result; }); }).whenComplete((result, throwable) -> { if (result != null) { log.info("MessageFetcher#queryMessageAsync, " + "query result={}, topic={}, topicId={}, key={}, maxCount={}, timestamp={}-{}", result.getMessageBufferList().size(), topic, topicId, key, maxCount, begin, end); } }); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.core; public interface MessageStoreFilter { boolean filterTopic(String topicName); void addTopicToBlackList(String topicName); } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/core/MessageStoreTopicFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.core; import java.util.HashSet; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.tieredstore.MessageStoreConfig; public class MessageStoreTopicFilter implements MessageStoreFilter { private final Set topicBlackSet; public MessageStoreTopicFilter(MessageStoreConfig storeConfig) { this.topicBlackSet = new HashSet<>(); this.topicBlackSet.add(storeConfig.getBrokerClusterName()); this.topicBlackSet.add(storeConfig.getBrokerName()); } @Override public boolean filterTopic(String topicName) { if (StringUtils.isBlank(topicName)) { return true; } return TopicValidator.isSystemTopic(topicName) || PopAckConstants.isStartWithRevivePrefix(topicName) || this.topicBlackSet.contains(topicName) || MixAll.isLmq(topicName); } @Override public void addTopicToBlackList(String topicName) { this.topicBlackSet.add(topicName); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/exception/TieredStoreErrorCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.exception; public enum TieredStoreErrorCode { /** * Error code for an invalid offset. */ ILLEGAL_OFFSET, /** * Error code for an invalid parameter. */ ILLEGAL_PARAM, /** * Error code for an incorrect download length. */ DOWNLOAD_LENGTH_NOT_CORRECT, /** * Error code for no new data found in the storage system. */ NO_NEW_DATA, /** * Error code for a storage provider error. */ STORAGE_PROVIDER_ERROR, /** * Error code for an input/output error. */ IO_ERROR, /** * Segment has been sealed */ SEGMENT_SEALED, /** * Error code for an unknown error. */ UNKNOWN } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/exception/TieredStoreException.java ================================================ [File too large to display: 1.9 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatAppendFile.java ================================================ [File too large to display: 12.3 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatCommitLogFile.java ================================================ [File too large to display: 3.7 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatConsumeQueueFile.java ================================================ [File too large to display: 1.2 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatFileFactory.java ================================================ [File too large to display: 2.6 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatFileInterface.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.file; import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.Lock; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.tieredstore.common.AppendResult; public interface FlatFileInterface { long getTopicId(); Lock getFileLock(); MessageQueue getMessageQueue(); boolean isFlatFileInit(); void initOffset(long offset); boolean rollingFile(long interval); /** * Appends a message to the commit log file * * @param message thByteBuffere message to append * @return append result */ AppendResult appendCommitLog(ByteBuffer message); AppendResult appendCommitLog(SelectMappedBufferResult message); /** * Append message to consume queue file, but does not commit it immediately * * @param request the dispatch request * @return append result */ AppendResult appendConsumeQueue(DispatchRequest request); void release(); long getMinStoreTimestamp(); long getMaxStoreTimestamp(); long getFirstMessageOffset(); long getCommitLogMinOffset(); long getCommitLogMaxOffset(); long getCommitLogCommitOffset(); long getConsumeQueueMinOffset(); long getConsumeQueueMaxOffset(); long getConsumeQueueCommitOffset(); /** * Persist commit log file and consume queue file */ CompletableFuture commitAsync(); /** * Asynchronously retrieves the message at the specified consume queue offset * * @param consumeQueueOffset consume queue offset. * @return the message inner object serialized content */ CompletableFuture getMessageAsync(long consumeQueueOffset); /** * Get message from commitLog file at specified offset and length * * @param offset the offset * @param length the length * @return the message inner object serialized content */ CompletableFuture getCommitLogAsync(long offset, int length); /** * Asynchronously retrieves the consume queue message at the specified queue offset * * @param consumeQueueOffset consume queue offset. * @return the consumer queue unit serialized content */ CompletableFuture getConsumeQueueAsync(long consumeQueueOffset); /** * Asynchronously reads the message body from the consume queue file at the specified offset and count * * @param consumeQueueOffset the message offset * @param count the number of messages to read * @return the consumer queue unit serialized content */ CompletableFuture getConsumeQueueAsync(long consumeQueueOffset, int count); /** * Gets the start offset in the consume queue based on the timestamp and boundary type. * The consume queues consist of ordered units, and their storage times are non-decreasing * sequence. If the specified message exists, it returns the offset of either the first * or last message, depending on the boundary type. If the specified message does not exist, * it returns the offset of the next message as the pull offset. For example: * ------------------------------------------------------------ * store time : 40, 50, 50, 50, 60, 60, 70 * queue offset : 10, 11, 12, 13, 14, 15, 16 * ------------------------------------------------------------ * query timestamp | boundary | result (reason) * 35 | - | 10 (minimum offset) * 45 | - | 11 (next offset) * 50 | lower | 11 * 50 | upper | 13 * 60 | - | 14 (default to lower) * 75 | - | 17 (maximum offset + 1) * ------------------------------------------------------------ * @param timestamp The search time * @param boundaryType 'lower' or 'upper' to determine the boundary * @return Returns the offset of the message in the consume queue */ CompletableFuture getQueueOffsetByTimeAsync(long timestamp, BoundaryType boundaryType); boolean isClosed(); /** * Shutdown process */ void shutdown(); /** * Destroys expired files */ void destroyExpiredFile(long timestamp); /** * Delete file */ void destroy(); } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatFileStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.file; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.tieredstore.MessageStoreConfig; import org.apache.rocketmq.tieredstore.MessageStoreExecutor; import org.apache.rocketmq.tieredstore.metadata.MetadataStore; import org.apache.rocketmq.tieredstore.metadata.entity.TopicMetadata; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FlatFileStore { private static final Logger log = LoggerFactory.getLogger(MessageStoreUtil.TIERED_STORE_LOGGER_NAME); private final MetadataStore metadataStore; private final MessageStoreConfig storeConfig; private final MessageStoreExecutor executor; private final FlatFileFactory flatFileFactory; private final ConcurrentMap flatFileConcurrentMap; public FlatFileStore(MessageStoreConfig storeConfig, MetadataStore metadataStore, MessageStoreExecutor executor) { this.storeConfig = storeConfig; this.metadataStore = metadataStore; this.executor = executor; this.flatFileFactory = new FlatFileFactory(metadataStore, storeConfig, executor); this.flatFileConcurrentMap = new ConcurrentHashMap<>(); } public boolean load() { Stopwatch stopwatch = Stopwatch.createStarted(); try { this.flatFileConcurrentMap.clear(); this.recover(); log.info("FlatFileStore recover finished, total cost={}ms", stopwatch.elapsed(TimeUnit.MILLISECONDS)); } catch (Exception e) { long costTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); log.info("FlatFileStore recover error, total cost={}ms", costTime); LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME) .error("FlatFileStore recover error, total cost={}ms", costTime, e); return false; } return true; } public void recover() { Semaphore semaphore = new Semaphore(storeConfig.getTieredStoreMaxPendingLimit() / 4); List> futures = new ArrayList<>(); metadataStore.iterateTopic(topicMetadata -> { semaphore.acquireUninterruptibly(); futures.add(this.recoverAsync(topicMetadata) .whenComplete((unused, throwable) -> { if (throwable != null) { log.error("FlatFileStore recover file error, topic={}", topicMetadata.getTopic(), throwable); } semaphore.release(); })); }); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } public CompletableFuture recoverAsync(TopicMetadata topicMetadata) { return CompletableFuture.runAsync(() -> { Stopwatch stopwatch = Stopwatch.createStarted(); AtomicLong queueCount = new AtomicLong(); metadataStore.iterateQueue(topicMetadata.getTopic(), queueMetadata -> { FlatMessageFile flatFile = this.computeIfAbsent(new MessageQueue( topicMetadata.getTopic(), storeConfig.getBrokerName(), queueMetadata.getQueue().getQueueId())); queueCount.incrementAndGet(); log.debug("FlatFileStore recover file, topicId={}, topic={}, queueId={}, cost={}ms", flatFile.getTopicId(), flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId(), stopwatch.elapsed(TimeUnit.MILLISECONDS)); }); log.info("FlatFileStore recover file, topic={}, total={}, cost={}ms", topicMetadata.getTopic(), queueCount.get(), stopwatch.elapsed(TimeUnit.MILLISECONDS)); }, executor.bufferCommitExecutor); } public void scheduleDeleteExpireFile() { if (!storeConfig.isTieredStoreDeleteFileEnable()) { return; } Stopwatch stopwatch = Stopwatch.createStarted(); ImmutableList fileList = this.deepCopyFlatFileToList(); for (FlatMessageFile flatFile : fileList) { flatFile.getFileLock().lock(); try { flatFile.destroyExpiredFile(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(flatFile.getFileReservedHours())); } catch (Exception e) { log.error("FlatFileStore delete expire file error", e); } finally { flatFile.getFileLock().unlock(); } } log.info("FlatFileStore schedule delete expired file, count={}, cost={}ms", fileList.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS)); } public MetadataStore getMetadataStore() { return metadataStore; } public MessageStoreConfig getStoreConfig() { return storeConfig; } public FlatFileFactory getFlatFileFactory() { return flatFileFactory; } public FlatMessageFile computeIfAbsent(MessageQueue messageQueue) { return flatFileConcurrentMap.computeIfAbsent(messageQueue, mq -> new FlatMessageFile(flatFileFactory, mq.getTopic(), mq.getQueueId())); } public FlatMessageFile getFlatFile(MessageQueue messageQueue) { return flatFileConcurrentMap.get(messageQueue); } public ImmutableList deepCopyFlatFileToList() { return ImmutableList.copyOf(flatFileConcurrentMap.values()); } public void shutdown() { flatFileConcurrentMap.values().forEach(FlatMessageFile::shutdown); } public void destroyFile(MessageQueue mq) { if (mq == null) { return; } FlatMessageFile flatFile = flatFileConcurrentMap.remove(mq); if (flatFile != null) { flatFile.shutdown(); flatFile.destroy(); } log.info("FlatFileStore destroy file, topic={}, queueId={}", mq.getTopic(), mq.getQueueId()); } public void destroy() { this.shutdown(); flatFileConcurrentMap.values().forEach(FlatMessageFile::destroy); flatFileConcurrentMap.clear(); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/FlatMessageFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.file; import com.alibaba.fastjson2.JSON; import com.google.common.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.BoundaryType; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.tieredstore.MessageStoreConfig; import org.apache.rocketmq.tieredstore.common.AppendResult; import org.apache.rocketmq.tieredstore.metadata.MetadataStore; import org.apache.rocketmq.tieredstore.metadata.entity.QueueMetadata; import org.apache.rocketmq.tieredstore.metadata.entity.TopicMetadata; import org.apache.rocketmq.tieredstore.provider.FileSegment; import org.apache.rocketmq.tieredstore.util.MessageFormatUtil; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FlatMessageFile implements FlatFileInterface { protected static final Logger log = LoggerFactory.getLogger(MessageStoreUtil.TIERED_STORE_LOGGER_NAME); protected volatile boolean closed = false; protected TopicMetadata topicMetadata; protected QueueMetadata queueMetadata; protected final String filePath; protected final ReentrantLock fileLock; protected final Semaphore commitLock = new Semaphore(1); protected final MessageStoreConfig storeConfig; protected final MetadataStore metadataStore; protected final FlatCommitLogFile commitLog; protected final FlatConsumeQueueFile consumeQueue; protected final ConcurrentMap> inFlightRequestMap; public FlatMessageFile(FlatFileFactory fileFactory, String topic, int queueId) { this(fileFactory, MessageStoreUtil.toFilePath( new MessageQueue(topic, fileFactory.getStoreConfig().getBrokerName(), queueId))); this.topicMetadata = this.recoverTopicMetadata(topic); this.queueMetadata = this.recoverQueueMetadata(topic, queueId); } public FlatMessageFile(FlatFileFactory fileFactory, String filePath) { this.filePath = filePath; this.fileLock = new ReentrantLock(false); this.storeConfig = fileFactory.getStoreConfig(); this.metadataStore = fileFactory.getMetadataStore(); this.commitLog = fileFactory.createFlatFileForCommitLog(filePath); this.consumeQueue = fileFactory.createFlatFileForConsumeQueue(filePath); this.inFlightRequestMap = new ConcurrentHashMap<>(); } @Override public long getTopicId() { return topicMetadata.getTopicId(); } @Override public MessageQueue getMessageQueue() { return queueMetadata != null ? queueMetadata.getQueue() : null; } @Override public boolean isFlatFileInit() { return !this.consumeQueue.fileSegmentTable.isEmpty(); } public TopicMetadata recoverTopicMetadata(String topic) { TopicMetadata topicMetadata = this.metadataStore.getTopic(topic); if (topicMetadata == null) { topicMetadata = this.metadataStore.addTopic(topic, -1L); } return topicMetadata; } public QueueMetadata recoverQueueMetadata(String topic, int queueId) { MessageQueue mq = new MessageQueue(topic, storeConfig.getBrokerName(), queueId); QueueMetadata queueMetadata = this.metadataStore.getQueue(mq); if (queueMetadata == null) { queueMetadata = this.metadataStore.addQueue(mq, -1L); } return queueMetadata; } public void flushMetadata() { if (queueMetadata != null) { queueMetadata.setMinOffset(this.getConsumeQueueMinOffset()); queueMetadata.setMaxOffset(this.getConsumeQueueCommitOffset()); queueMetadata.setUpdateTimestamp(System.currentTimeMillis()); metadataStore.updateQueue(queueMetadata); } } @Override public Lock getFileLock() { return this.fileLock; } @VisibleForTesting public Semaphore getCommitLock() { return commitLock; } @Override public boolean rollingFile(long interval) { return this.commitLog.tryRollingFile(interval); } @Override public void initOffset(long offset) { fileLock.lock(); try { this.commitLog.initOffset(0L); this.consumeQueue.initOffset(offset * MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE); } finally { fileLock.unlock(); } } @Override public AppendResult appendCommitLog(ByteBuffer message) { if (closed) { return AppendResult.FILE_CLOSED; } return commitLog.append(message, MessageFormatUtil.getStoreTimeStamp(message)); } @Override public AppendResult appendCommitLog(SelectMappedBufferResult message) { if (closed) { return AppendResult.FILE_CLOSED; } return this.appendCommitLog(message.getByteBuffer()); } @Override public AppendResult appendConsumeQueue(DispatchRequest request) { if (closed) { return AppendResult.FILE_CLOSED; } ByteBuffer buffer = ByteBuffer.allocate(MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE); buffer.putLong(request.getCommitLogOffset()); buffer.putInt(request.getMsgSize()); buffer.putLong(request.getTagsCode()); buffer.flip(); return consumeQueue.append(buffer, request.getStoreTimestamp()); } @Override public void release() { } @Override public long getMinStoreTimestamp() { long minStoreTime = -1L; if (Long.MAX_VALUE != commitLog.getMinTimestamp()) { minStoreTime = Math.max(minStoreTime, commitLog.getMinTimestamp()); } if (Long.MAX_VALUE != consumeQueue.getMinTimestamp()) { minStoreTime = Math.max(minStoreTime, consumeQueue.getMinTimestamp()); } return minStoreTime; } @Override public long getMaxStoreTimestamp() { return commitLog.getMaxTimestamp(); } @Override public long getFirstMessageOffset() { return commitLog.getMinOffsetFromFile(); } @Override public long getCommitLogMinOffset() { return commitLog.getMinOffset(); } @Override public long getCommitLogMaxOffset() { return commitLog.getAppendOffset(); } @Override public long getCommitLogCommitOffset() { return commitLog.getCommitOffset(); } @Override public long getConsumeQueueMinOffset() { long cqOffset = consumeQueue.getMinOffset() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE; long effectiveOffset = this.commitLog.getMinOffsetFromFile(); return Math.max(cqOffset, effectiveOffset); } @Override public long getConsumeQueueMaxOffset() { return consumeQueue.getAppendOffset() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE; } @Override public long getConsumeQueueCommitOffset() { return consumeQueue.getCommitOffset() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE; } @Override public CompletableFuture commitAsync() { // acquire lock if (commitLock.drainPermits() <= 0) { return CompletableFuture.completedFuture(false); } return this.commitLog.commitAsync() .thenCompose(result -> { if (result) { return consumeQueue.commitAsync(); } return CompletableFuture.completedFuture(false); }).whenComplete((result, throwable) -> commitLock.release()); } @Override public CompletableFuture getMessageAsync(long queueOffset) { return getConsumeQueueAsync(queueOffset).thenCompose(cqBuffer -> { long commitLogOffset = MessageFormatUtil.getCommitLogOffsetFromItem(cqBuffer); int length = MessageFormatUtil.getSizeFromItem(cqBuffer); return getCommitLogAsync(commitLogOffset, length); }); } @Override public CompletableFuture getCommitLogAsync(long offset, int length) { return commitLog.readAsync(offset, length); } @Override public CompletableFuture getConsumeQueueAsync(long queueOffset) { return this.getConsumeQueueAsync(queueOffset, 1); } @Override public CompletableFuture getConsumeQueueAsync(long queueOffset, int count) { return consumeQueue.readAsync( queueOffset * MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE, count * MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE); } @Override public CompletableFuture getQueueOffsetByTimeAsync(long timestamp, BoundaryType boundaryType) { long cqMin = getConsumeQueueMinOffset(); long cqMax = getConsumeQueueCommitOffset() - 1; if (cqMax == -1 || cqMax < cqMin) { return CompletableFuture.completedFuture(cqMin); } ByteBuffer buffer = getMessageAsync(cqMax).join(); long storeTime = MessageFormatUtil.getStoreTimeStamp(buffer); if (storeTime < timestamp) { log.info("FlatMessageFile getQueueOffsetByTimeAsync, exceeded maximum time, " + "filePath={}, timestamp={}, result={}", filePath, timestamp, cqMax + 1); return CompletableFuture.completedFuture(cqMax + 1); } buffer = getMessageAsync(cqMin).join(); storeTime = MessageFormatUtil.getStoreTimeStamp(buffer); if (storeTime > timestamp) { log.info("FlatMessageFile getQueueOffsetByTimeAsync, less than minimum time, " + "filePath={}, timestamp={}, result={}", filePath, timestamp, cqMin); return CompletableFuture.completedFuture(cqMin); } // get correct consume queue file by binary search List consumeQueueFileList = this.consumeQueue.getFileSegmentList(); int low = 0, high = consumeQueueFileList.size() - 1; int mid = low + (high - low) / 2; while (low <= high) { FileSegment fileSegment = consumeQueueFileList.get(mid); if (fileSegment.getMinTimestamp() <= timestamp && timestamp <= fileSegment.getMaxTimestamp()) { break; } else if (timestamp < fileSegment.getMinTimestamp()) { high = mid - 1; } else { low = mid + 1; } mid = low + (high - low) / 2; } FileSegment target = consumeQueueFileList.get(mid); // binary search lower bound index in a sorted array long minOffset = target.getBaseOffset() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE; long maxOffset = target.getCommitOffset() / MessageFormatUtil.CONSUME_QUEUE_UNIT_SIZE - 1; List queryLog = new ArrayList<>(); while (minOffset < maxOffset) { long middle = minOffset + (maxOffset - minOffset) / 2; buffer = this.getMessageAsync(middle).join(); storeTime = MessageFormatUtil.getStoreTimeStamp(buffer); queryLog.add(String.format("(range=%d-%d, middle=%d, timestamp=%d, diff=%dms)", minOffset, maxOffset, middle, storeTime, timestamp - storeTime)); if (storeTime < timestamp) { minOffset = middle + 1; } else { maxOffset = middle; } } long offset = minOffset; if (boundaryType == BoundaryType.UPPER) { while (true) { long next = offset + 1; if (next > cqMax) { break; } buffer = this.getMessageAsync(next).join(); storeTime = MessageFormatUtil.getStoreTimeStamp(buffer); if (storeTime == timestamp) { offset = next; } else { break; } } } log.info("FlatMessageFile getQueueOffsetByTimeAsync, filePath={}, timestamp={}, result={}, log={}", filePath, timestamp, offset, JSON.toJSONString(queryLog)); return CompletableFuture.completedFuture(offset); } @Override public int hashCode() { return filePath.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } return StringUtils.equals(filePath, ((FlatMessageFile) obj).filePath); } @Override public boolean isClosed() { return closed; } @Override public void shutdown() { closed = true; fileLock.lock(); try { consumeQueue.shutdown(); commitLog.shutdown(); } finally { fileLock.unlock(); } } @Override public void destroyExpiredFile(long timestamp) { fileLock.lock(); try { consumeQueue.destroyExpiredFile(timestamp); commitLog.destroyExpiredFile(timestamp); } finally { fileLock.unlock(); } } public void destroy() { this.shutdown(); fileLock.lock(); try { consumeQueue.destroyExpiredFile(Long.MAX_VALUE); commitLog.destroyExpiredFile(Long.MAX_VALUE); if (queueMetadata != null) { metadataStore.deleteQueue(queueMetadata.getQueue()); } } finally { fileLock.unlock(); } } public long getFileReservedHours() { if (topicMetadata.getReserveTime() > 0) { return topicMetadata.getReserveTime(); } return storeConfig.getTieredStoreFileReservedTime(); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/index/IndexFile.java ================================================ [File too large to display: 1.2 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/index/IndexItem.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/index/IndexService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.index; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.tieredstore.common.AppendResult; public interface IndexService { void start(); /** * Puts a key into the index. * * @param topic The topic of the key. * @param topicId The ID of the topic. * @param queueId The ID of the queue. * @param keySet The set of keys to be indexed. * @param offset The offset value of the key. * @param size The size of the key. * @param timestamp The timestamp of the key. * @return The result of the put operation. */ AppendResult putKey( String topic, int topicId, int queueId, Set keySet, long offset, int size, long timestamp); /** * Asynchronously queries the index for a specific key within a given time range. * * @param topic The topic of the key. * @param key The key to be queried. * @param beginTime The start time of the query range. * @param endTime The end time of the query range. * @return A CompletableFuture that holds the list of IndexItems matching the query. */ CompletableFuture> queryAsync(String topic, String key, int maxCount, long beginTime, long endTime); default void forceUpload() { } /** * Shutdown the index service. */ void shutdown(); /** * Force shutdown the index service. */ default void forceShutdown() { shutdown(); }; /** * Destroys the index service and releases all resources. */ void destroy(); } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/index/IndexStoreFile.java ================================================ [File too large to display: 25.4 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/index/IndexStoreService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.rocketmq.tieredstore.index; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import java.io.File; import java.nio.ByteBuffer; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.logfile.MappedFile; import org.apache.rocketmq.tieredstore.MessageStoreConfig; import org.apache.rocketmq.tieredstore.common.AppendResult; import org.apache.rocketmq.tieredstore.file.FlatAppendFile; import org.apache.rocketmq.tieredstore.file.FlatFileFactory; import org.apache.rocketmq.tieredstore.provider.FileSegment; import org.apache.rocketmq.tieredstore.util.MessageStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class IndexStoreService extends ServiceThread implements IndexService { private static final Logger log = LoggerFactory.getLogger(MessageStoreUtil.TIERED_STORE_LOGGER_NAME); public static final String FILE_DIRECTORY_NAME = "tiered_index_file"; public static final String FILE_COMPACTED_DIRECTORY_NAME = "compacting"; /** * File status in table example: * upload, upload, upload, sealed, sealed, unsealed */ private final MessageStoreConfig storeConfig; private final ConcurrentSkipListMap timeStoreTable; private final ReadWriteLock readWriteLock; private final AtomicLong compactTimestamp; private final String filePath; private final FlatFileFactory fileAllocator; private final boolean autoCreateNewFile; private volatile IndexFile currentWriteFile; private volatile FlatAppendFile flatAppendFile; public IndexStoreService(FlatFileFactory flatFileFactory, String filePath) { this(flatFileFactory, filePath, true); } public IndexStoreService(FlatFileFactory flatFileFactory, String filePath, boolean autoCreateNewFile) { this.storeConfig = flatFileFactory.getStoreConfig(); this.filePath = filePath; this.fileAllocator = flatFileFactory; this.timeStoreTable = new ConcurrentSkipListMap<>(); this.compactTimestamp = new AtomicLong(0L); this.readWriteLock = new ReentrantReadWriteLock(); this.autoCreateNewFile = autoCreateNewFile; } @Override public void start() { this.recover(); super.start(); } private void doConvertOldFormatFile(String filePath) { try { File file = new File(filePath); if (!file.exists()) { return; } MappedFile mappedFile = new DefaultMappedFile(file.getPath(), (int) file.length()); long timestamp = mappedFile.getMappedByteBuffer().getLong(IndexStoreFile.INDEX_BEGIN_TIME_STAMP); if (timestamp <= 0) { mappedFile.destroy(TimeUnit.SECONDS.toMillis(10)); } else { mappedFile.renameTo(String.valueOf(new File(file.getParent(), String.valueOf(timestamp)))); mappedFile.shutdown(TimeUnit.SECONDS.toMillis(10)); } } catch (Exception e) { log.error("IndexStoreService do convert old format error, file: {}", filePath, e); } } private void recover() { Stopwatch stopwatch = Stopwatch.createStarted(); // delete compact file directory UtilAll.deleteFile(new File(Paths.get(storeConfig.getStorePathRootDir(), FILE_DIRECTORY_NAME, FILE_COMPACTED_DIRECTORY_NAME).toString())); // recover local File dir = new File(Paths.get(storeConfig.getStorePathRootDir(), FILE_DIRECTORY_NAME).toString()); this.doConvertOldFormatFile(Paths.get(dir.getPath(), "0000").toString()); this.doConvertOldFormatFile(Paths.get(dir.getPath(), "1111").toString()); File[] files = dir.listFiles(); if (files != null) { List fileList = Arrays.asList(files); fileList.sort(Comparator.comparing(File::getName)); for (File file : fileList) { if (file.isDirectory() || !StringUtils.isNumeric(file.getName())) { continue; } try { IndexFile indexFile = new IndexStoreFile(storeConfig, Long.parseLong(file.getName())); timeStoreTable.put(indexFile.getTimestamp(), indexFile); log.info("IndexStoreService recover load local file, timestamp: {}", indexFile.getTimestamp()); } catch (Exception e) { log.error("IndexStoreService recover, load local file error", e); } } } if (this.autoCreateNewFile && this.timeStoreTable.isEmpty()) { this.createNewIndexFile(System.currentTimeMillis()); } if (this.timeStoreTable.isEmpty()) { this.setCompactTimestamp(Long.MAX_VALUE); } else { this.currentWriteFile = this.timeStoreTable.lastEntry().getValue(); this.setCompactTimestamp(this.timeStoreTable.firstKey() - 1); } // recover remote this.flatAppendFile = fileAllocator.createFlatFileForIndexFile(filePath); for (FileSegment fileSegment : flatAppendFile.getFileSegmentList()) { IndexFile indexFile = new IndexStoreFile(storeConfig, fileSegment); IndexFile localFile = timeStoreTable.get(indexFile.getTimestamp()); if (localFile != null) { localFile.destroy(); } timeStoreTable.put(indexFile.getTimestamp(), indexFile); log.info("IndexStoreService recover load remote file, timestamp: {}, end timestamp: {}", indexFile.getTimestamp(), indexFile.getEndTimestamp()); } log.info("IndexStoreService recover finished, total: {}, cost: {}ms, directory: {}", timeStoreTable.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS), dir.getAbsolutePath()); } public void createNewIndexFile(long timestamp) { try { this.readWriteLock.writeLock().lock(); IndexFile indexFile = this.currentWriteFile; if (this.timeStoreTable.containsKey(timestamp) || indexFile != null && IndexFile.IndexStatusEnum.UNSEALED.equals(indexFile.getFileStatus())) { return; } IndexStoreFile newStoreFile = new IndexStoreFile(storeConfig, timestamp); this.timeStoreTable.put(timestamp, newStoreFile); this.currentWriteFile = newStoreFile; log.info("IndexStoreService construct next file, timestamp: {}", timestamp); } catch (Exception e) { log.error("IndexStoreService construct next file, timestamp: {}", timestamp, e); } finally { this.readWriteLock.writeLock().unlock(); } } @VisibleForTesting public ConcurrentSkipListMap getTimeStoreTable() { return timeStoreTable; } @Override public AppendResult putKey( String topic, int topicId, int queueId, Set keySet, long offset, int size, long timestamp) { if (StringUtils.isBlank(topic)) { return AppendResult.UNKNOWN_ERROR; } if (keySet == null || keySet.isEmpty()) { return AppendResult.SUCCESS; } for (int i = 0; i < 3; i++) { AppendResult result = this.currentWriteFile.putKey( topic, topicId, queueId, keySet, offset, size, timestamp); if (AppendResult.SUCCESS.equals(result)) { return AppendResult.SUCCESS; } else if (AppendResult.FILE_FULL.equals(result)) { // use current time to ensure the order of file this.createNewIndexFile(System.currentTimeMillis()); } } log.error("IndexStoreService put key three times return error, topic: {}, topicId: {}, " + "queueId: {}, keySize: {}, timestamp: {}", topic, topicId, queueId, keySet.size(), timestamp); return AppendResult.SUCCESS; } @Override public CompletableFuture> queryAsync( String topic, String key, int maxCount, long beginTime, long endTime) { if (beginTime > endTime) { return CompletableFuture.completedFuture(new ArrayList<>()); } CompletableFuture> future = new CompletableFuture<>(); try { readWriteLock.readLock().lock(); ConcurrentNavigableMap pendingMap = this.timeStoreTable.subMap(beginTime, true, endTime, true); List> futureList = new ArrayList<>(pendingMap.size()); ConcurrentSkipListMap result = new ConcurrentSkipListMap<>(); for (Map.Entry entry : pendingMap.descendingMap().entrySet()) { CompletableFuture completableFuture = entry.getValue() .queryAsync(topic, key, maxCount, beginTime, endTime) .thenAccept(itemList -> itemList.forEach(indexItem -> { if (result.size() < maxCount) { result.put(String.format( "%d-%20d", indexItem.getQueueId(), indexItem.getOffset()), indexItem); } })); futureList.add(completableFuture); } CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((v, t) -> { // Try to return the query results as much as possible here // rather than directly throwing exceptions if (t != null) { log.error("IndexStoreService#queryAsync, topicId={}, key={}, maxCount={}, timestamp={}-{}", topic, key, maxCount, beginTime, endTime, t); } List resultList = new ArrayList<>(result.values()); future.complete(resultList.subList(0, Math.min(resultList.size(), maxCount))); }); } catch (Exception e) { log.error("IndexStoreService#queryAsync, topicId={}, key={}, maxCount={}, timestamp={}-{}", topic, key, maxCount, beginTime, endTime, e); future.completeExceptionally(e); } finally { readWriteLock.readLock().unlock(); } return future; } @Override public void forceUpload() { try { readWriteLock.writeLock().lock(); while (true) { Map.Entry entry = this.timeStoreTable.higherEntry(this.compactTimestamp.get()); if (entry == null) { break; } if (this.doCompactThenUploadFile(entry.getValue())) { this.setCompactTimestamp(entry.getValue().getTimestamp()); // The total number of files will not too much, prevent io too fast. TimeUnit.MILLISECONDS.sleep(50); } } } catch (Exception e) { log.error("IndexStoreService force upload error", e); throw new RuntimeException(e); } finally { readWriteLock.writeLock().unlock(); } } public boolean doCompactThenUploadFile(IndexFile indexFile) { if (IndexFile.IndexStatusEnum.UPLOAD.equals(indexFile.getFileStatus())) { log.error("IndexStoreService file status not correct, so skip, timestamp: {}, status: {}", indexFile.getTimestamp(), indexFile.getFileStatus()); indexFile.destroy(); return true; } Stopwatch stopwatch = Stopwatch.createStarted(); if (flatAppendFile.getCommitOffset() == flatAppendFile.getAppendOffset()) { ByteBuffer byteBuffer = indexFile.doCompaction(); if (byteBuffer == null) { log.error("IndexStoreService found compaction buffer is null, timestamp: {}", indexFile.getTimestamp()); return false; } flatAppendFile.rollingNewFile(Math.max(0L, flatAppendFile.getAppendOffset())); flatAppendFile.append(byteBuffer, indexFile.getTimestamp()); flatAppendFile.getFileToWrite().setMinTimestamp(indexFile.getTimestamp()); flatAppendFile.getFileToWrite().setMaxTimestamp(indexFile.getEndTimestamp()); } boolean result = flatAppendFile.commitAsync().join(); List fileSegmentList = flatAppendFile.getFileSegmentList(); FileSegment fileSegment = fileSegmentList.get(fileSegmentList.size() - 1); if (!result || fileSegment == null || fileSegment.getMinTimestamp() != indexFile.getTimestamp()) { log.warn("IndexStoreService upload compacted file error, timestamp: {}", indexFile.getTimestamp()); return false; } else { log.info("IndexStoreService upload compacted file success, timestamp: {}", indexFile.getTimestamp()); } readWriteLock.writeLock().lock(); try { IndexFile storeFile = new IndexStoreFile(storeConfig, fileSegment); timeStoreTable.put(storeFile.getTimestamp(), storeFile); indexFile.destroy(); } catch (Exception e) { log.error("IndexStoreService rolling file error, timestamp: {}, cost: {}ms", indexFile.getTimestamp(), stopwatch.elapsed(TimeUnit.MILLISECONDS), e); } finally { readWriteLock.writeLock().unlock(); } return true; } public void destroyExpiredFile(long expireTimestamp) { // delete file in time store table readWriteLock.writeLock().lock(); try { flatAppendFile.destroyExpiredFile(expireTimestamp); timeStoreTable.entrySet().removeIf(entry -> IndexFile.IndexStatusEnum.UPLOAD.equals(entry.getValue().getFileStatus()) && (flatAppendFile.getFileSegmentList().isEmpty() || entry.getKey() < flatAppendFile.getMinTimestamp())); int tableSize = (int) timeStoreTable.entrySet().stream() .filter(entry -> IndexFile.IndexStatusEnum.UPLOAD.equals(entry.getValue().getFileStatus())) .count(); log.debug("IndexStoreService delete file, timestamp={}, remote={}, table={}, all={}", expireTimestamp, flatAppendFile.getFileSegmentList().size(), tableSize, timeStoreTable.size()); } finally { readWriteLock.writeLock().unlock(); } } public void destroy() { readWriteLock.writeLock().lock(); try { // delete local store file for (Map.Entry entry : timeStoreTable.entrySet()) { IndexFile indexFile = entry.getValue(); if (IndexFile.IndexStatusEnum.UPLOAD.equals(indexFile.getFileStatus())) { continue; } indexFile.destroy(); } // delete remote if (flatAppendFile != null) { flatAppendFile.destroy(); } } catch (Exception e) { log.error("IndexStoreService destroy all file error", e); } finally { readWriteLock.writeLock().unlock(); } } @Override public String getServiceName() { return IndexStoreService.class.getSimpleName() + "_" + this.storeConfig.getBrokerName(); } public void setCompactTimestamp(long timestamp) { this.compactTimestamp.set(timestamp); log.debug("IndexStoreService set compact timestamp to: {}", timestamp); } protected IndexFile getNextSealedFile() { Map.Entry entry = this.timeStoreTable.higherEntry(this.compactTimestamp.get()); if (entry != null && entry.getKey() < this.timeStoreTable.lastKey()) { return entry.getValue(); } return null; } @Override public void shutdown() { super.shutdown(); // Wait index service upload then clear time store table while (!this.timeStoreTable.isEmpty()) { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } } } @Override public void forceShutdown() { super.shutdown(); } @Override public void run() { while (!this.isStopped()) { try { long expireTimestamp = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(storeConfig.getTieredStoreFileReservedTime()); this.destroyExpiredFile(expireTimestamp); IndexFile indexFile = this.getNextSealedFile(); if (indexFile != null) { if (this.doCompactThenUploadFile(indexFile)) { this.setCompactTimestamp(indexFile.getTimestamp()); continue; } } } catch (Throwable e) { log.error("IndexStoreService running error", e); } this.waitForRunning(TimeUnit.SECONDS.toMillis(10)); } readWriteLock.writeLock().lock(); try { if (autoCreateNewFile) { this.forceUpload(); } } catch (Exception e) { log.error("IndexStoreService shutdown error", e); } finally { this.timeStoreTable.forEach((timestamp, file) -> file.shutdown()); this.timeStoreTable.clear(); readWriteLock.writeLock().unlock(); } log.info(this.getServiceName() + " service shutdown"); } } ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/DefaultMetadataStore.java ================================================ [File too large to display: 14.8 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/MetadataStore.java ================================================ [File too large to display: 2.8 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/entity/FileSegmentMetadata.java ================================================ [File too large to display: 4.3 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/entity/QueueMetadata.java ================================================ [File too large to display: 2.2 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/entity/TopicMetadata.java ================================================ [File too large to display: 2.3 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsConstant.java ================================================ [File too large to display: 3.2 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsManager.java ================================================ [File too large to display: 18.0 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/FileSegment.java ================================================ [File too large to display: 12.3 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/FileSegmentFactory.java ================================================ [File too large to display: 3.1 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/FileSegmentProvider.java ================================================ [File too large to display: 2.5 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/MemoryFileSegment.java ================================================ [File too large to display: 3.5 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/PosixFileSegment.java ================================================ [File too large to display: 9.3 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/stream/CommitLogInputStream.java ================================================ [File too large to display: 7.0 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/stream/FileSegmentInputStream.java ================================================ [File too large to display: 6.0 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/stream/FileSegmentInputStreamFactory.java ================================================ [File too large to display: 1.9 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/util/MessageFormatUtil.java ================================================ [File too large to display: 6.9 KB] ================================================ FILE: tieredstore/src/main/java/org/apache/rocketmq/tieredstore/util/MessageStoreUtil.java ================================================ [File too large to display: 4.0 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java ================================================ [File too large to display: 18.4 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/common/FileSegmentTypeTest.java ================================================ [File too large to display: 1.5 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/common/GetMessageResultExtTest.java ================================================ [File too large to display: 3.8 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/common/GroupCommitContextTest.java ================================================ [File too large to display: 2.5 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/common/SelectBufferResultTest.java ================================================ [File too large to display: 1.5 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreDispatcherImplTest.java ================================================ [File too large to display: 16.2 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreFetcherImplTest.java ================================================ [File too large to display: 16.1 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/core/MessageStoreTopicFilterTest.java ================================================ [File too large to display: 1.5 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/exception/TieredStoreExceptionTest.java ================================================ [File too large to display: 1.7 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/FlatAppendFileTest.java ================================================ [File too large to display: 9.6 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/FlatCommitLogFileTest.java ================================================ [File too large to display: 5.6 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/FlatFileFactoryTest.java ================================================ [File too large to display: 2.1 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/FlatFileStoreTest.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/FlatMessageFileTest.java ================================================ [File too large to display: 12.1 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/index/IndexItemTest.java ================================================ [File too large to display: 3.5 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/index/IndexStoreFileTest.java ================================================ [File too large to display: 13.2 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/index/IndexStoreServiceBenchTest.java ================================================ [File too large to display: 6.2 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/index/IndexStoreServiceTest.java ================================================ [File too large to display: 16.1 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/metadata/DefaultMetadataStoreTest.java ================================================ [File too large to display: 12.2 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsManagerTest.java ================================================ [File too large to display: 7.5 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/FileSegmentFactoryTest.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/FileSegmentTest.java ================================================ [File too large to display: 23.1 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MemoryFileSegmentTest.java ================================================ [File too large to display: 2.1 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/stream/FileSegmentInputStreamTest.java ================================================ [File too large to display: 11.9 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/util/MessageFormatUtilTest.java ================================================ [File too large to display: 11.6 KB] ================================================ FILE: tieredstore/src/test/java/org/apache/rocketmq/tieredstore/util/MessageStoreUtilTest.java ================================================ [File too large to display: 3.8 KB] ================================================ FILE: tieredstore/src/test/resources/rmq.logback-test.xml ================================================ [File too large to display: 1.6 KB] ================================================ FILE: tools/BUILD.bazel ================================================ [File too large to display: 2.4 KB] ================================================ FILE: tools/pom.xml ================================================ [File too large to display: 2.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java ================================================ [File too large to display: 48.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java ================================================ [File too large to display: 106.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/MQAdminExt.java ================================================ [File too large to display: 30.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/MQAdminUtils.java ================================================ [File too large to display: 19.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/api/BrokerOperatorResult.java ================================================ [File too large to display: 1.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/api/MessageTrack.java ================================================ [File too large to display: 1.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/api/TrackType.java ================================================ [File too large to display: 1001 B] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/common/AdminToolHandler.java ================================================ [File too large to display: 939 B] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/common/AdminToolResult.java ================================================ [File too large to display: 2.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/admin/common/AdminToolsResultCodeEnum.java ================================================ [File too large to display: 1.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/CommandUtil.java ================================================ [File too large to display: 8.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java ================================================ [File too large to display: 17.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/SubCommand.java ================================================ [File too large to display: 1.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/SubCommandException.java ================================================ [File too large to display: 1.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/CopyAclsSubCommand.java ================================================ [File too large to display: 4.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/CopyUsersSubCommand.java ================================================ [File too large to display: 4.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/CreateAclSubCommand.java ================================================ [File too large to display: 5.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/CreateUserSubCommand.java ================================================ [File too large to display: 4.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/DeleteAclSubCommand.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/DeleteUserSubCommand.java ================================================ [File too large to display: 3.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/GetAclSubCommand.java ================================================ [File too large to display: 5.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/GetUserSubCommand.java ================================================ [File too large to display: 4.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/ListAclSubCommand.java ================================================ [File too large to display: 5.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/ListUserSubCommand.java ================================================ [File too large to display: 4.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/UpdateAclSubCommand.java ================================================ [File too large to display: 5.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/auth/UpdateUserSubCommand.java ================================================ [File too large to display: 4.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerConsumeStatsSubCommand.java ================================================ [File too large to display: 6.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerStatusSubCommand.java ================================================ [File too large to display: 4.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/CleanExpiredCQSubCommand.java ================================================ [File too large to display: 2.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/CleanUnusedTopicCommand.java ================================================ [File too large to display: 2.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/CommitLogSetReadAheadSubCommand.java ================================================ [File too large to display: 4.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/DeleteExpiredCommitLogSubCommand.java ================================================ [File too large to display: 3.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerConfigCommand.java ================================================ [File too large to display: 5.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerEpochSubCommand.java ================================================ [File too large to display: 5.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetColdDataFlowCtrInfoSubCommand.java ================================================ [File too large to display: 5.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/RemoveColdDataFlowCtrGroupConfigSubCommand.java ================================================ [File too large to display: 3.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/ResetMasterFlushOffsetSubCommand.java ================================================ [File too large to display: 2.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/SendMsgStatusCommand.java ================================================ [File too large to display: 3.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/SwitchTimerEngineSubCommand.java ================================================ [File too large to display: 4.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateBrokerConfigSubCommand.java ================================================ [File too large to display: 4.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateColdDataFlowCtrGroupConfigSubCommand.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java ================================================ [File too large to display: 7.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/cluster/ClusterListSubCommand.java ================================================ [File too large to display: 14.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/connection/ConsumerConnectionSubCommand.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java ================================================ [File too large to display: 15.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java ================================================ [File too large to display: 6.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerSubCommand.java ================================================ [File too large to display: 6.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/DeleteSubscriptionGroupCommand.java ================================================ [File too large to display: 4.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java ================================================ [File too large to display: 6.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/SetConsumeModeSubCommand.java ================================================ [File too large to display: 5.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/StartMonitoringSubCommand.java ================================================ [File too large to display: 2.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupListSubCommand.java ================================================ [File too large to display: 4.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupSubCommand.java ================================================ [File too large to display: 9.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/container/AddBrokerSubCommand.java ================================================ [File too large to display: 2.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/container/RemoveBrokerSubCommand.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/controller/CleanControllerBrokerMetaSubCommand.java ================================================ [File too large to display: 4.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/controller/GetControllerConfigSubCommand.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/controller/GetControllerMetaDataSubCommand.java ================================================ [File too large to display: 3.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/controller/ReElectMasterSubCommand.java ================================================ [File too large to display: 4.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/controller/UpdateControllerConfigSubCommand.java ================================================ [File too large to display: 3.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java ================================================ [File too large to display: 5.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataCommand.java ================================================ [File too large to display: 8.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java ================================================ [File too large to display: 6.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetricsCommand.java ================================================ [File too large to display: 12.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportPopRecordCommand.java ================================================ [File too large to display: 4.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/ha/GetSyncStateSetSubCommand.java ================================================ [File too large to display: 6.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/ha/HAStatusSubCommand.java ================================================ [File too large to display: 6.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/lite/GetBrokerLiteInfoSubCommand.java ================================================ [File too large to display: 5.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/lite/GetLiteClientInfoSubCommand.java ================================================ [File too large to display: 5.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/lite/GetLiteGroupInfoSubCommand.java ================================================ [File too large to display: 8.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/lite/GetLiteTopicInfoSubCommand.java ================================================ [File too large to display: 5.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/lite/GetParentTopicInfoSubCommand.java ================================================ [File too large to display: 3.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/lite/TriggerLiteDispatchSubCommand.java ================================================ [File too large to display: 4.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/CheckMsgSendRTCommand.java ================================================ [File too large to display: 5.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/ConsumeMessageCommand.java ================================================ [File too large to display: 11.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/DecodeMessageIdCommond.java ================================================ [File too large to display: 2.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/DumpCompactionLogCommand.java ================================================ [File too large to display: 4.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageByQueueCommand.java ================================================ [File too large to display: 9.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageSubCommand.java ================================================ [File too large to display: 8.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByIdSubCommand.java ================================================ [File too large to display: 12.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByKeySubCommand.java ================================================ [File too large to display: 6.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByOffsetSubCommand.java ================================================ [File too large to display: 5.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java ================================================ [File too large to display: 10.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java ================================================ [File too large to display: 7.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/message/SendMessageCommand.java ================================================ [File too large to display: 5.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java ================================================ [File too large to display: 16.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/AddWritePermSubCommand.java ================================================ [File too large to display: 3.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/DeleteKvConfigCommand.java ================================================ [File too large to display: 2.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/GetNamesrvConfigCommand.java ================================================ [File too large to display: 3.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommand.java ================================================ [File too large to display: 3.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/UpdateNamesrvConfigCommand.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/WipeWritePermSubCommand.java ================================================ [File too large to display: 3.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/offset/CloneGroupOffsetCommand.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommand.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommand.java ================================================ [File too large to display: 7.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeOldCommand.java ================================================ [File too large to display: 5.8 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/offset/SkipAccumulationSubCommand.java ================================================ [File too large to display: 5.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/producer/ProducerSubCommand.java ================================================ [File too large to display: 3.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/queue/CheckRocksdbCqWriteProgressCommand.java ================================================ [File too large to display: 4.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/queue/QueryConsumeQueueCommand.java ================================================ [File too large to display: 6.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/stats/StatsAllSubCommand.java ================================================ [File too large to display: 7.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/AllocateMQSubCommand.java ================================================ [File too large to display: 3.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/DeleteTopicSubCommand.java ================================================ [File too large to display: 3.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/RebalanceResult.java ================================================ [File too large to display: 1.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/RemappingStaticTopicSubCommand.java ================================================ [File too large to display: 9.0 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicClusterSubCommand.java ================================================ [File too large to display: 2.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicListSubCommand.java ================================================ [File too large to display: 5.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java ================================================ [File too large to display: 4.5 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicStatusSubCommand.java ================================================ [File too large to display: 4.9 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateOrderConfCommand.java ================================================ [File too large to display: 4.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateStaticTopicSubCommand.java ================================================ [File too large to display: 9.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicListSubCommand.java ================================================ [File too large to display: 4.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicPermSubCommand.java ================================================ [File too large to display: 7.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicSubCommand.java ================================================ [File too large to display: 8.1 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/DefaultMonitorListener.java ================================================ [File too large to display: 3.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/DeleteMsgsEvent.java ================================================ [File too large to display: 1.6 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/FailedMsgs.java ================================================ [File too large to display: 1.7 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/MonitorConfig.java ================================================ [File too large to display: 1.4 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/MonitorListener.java ================================================ [File too large to display: 1.3 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/MonitorService.java ================================================ [File too large to display: 13.2 KB] ================================================ FILE: tools/src/main/java/org/apache/rocketmq/tools/monitor/UndoneMsgs.java ================================================ [File too large to display: 2.3 KB] ================================================ FILE: tools/src/main/resources/rmq.tools.logback.xml ================================================ [File too large to display: 3.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImplTest.java ================================================ [File too large to display: 44.0 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtTest.java ================================================ [File too large to display: 30.4 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/CommandUtilTest.java ================================================ [File too large to display: 5.5 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/BrokerConsumeStatsSubCommandTest.java ================================================ [File too large to display: 4.6 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/BrokerStatusSubCommandTest.java ================================================ [File too large to display: 2.1 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/CleanExpiredCQSubCommandTest.java ================================================ [File too large to display: 1.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/CleanUnusedTopicCommandTest.java ================================================ [File too large to display: 1.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/DeleteExpiredCommitLogSubCommandTest.java ================================================ [File too large to display: 2.7 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/GetBrokerConfigCommandTest.java ================================================ [File too large to display: 2.4 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/SendMsgStatusCommandTest.java ================================================ [File too large to display: 3.7 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/SwitchTimerEngineSubCommandTest.java ================================================ [File too large to display: 11.0 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/broker/UpdateBrokerConfigSubCommandTest.java ================================================ [File too large to display: 1.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/connection/ConsumerConnectionSubCommandTest.java ================================================ [File too large to display: 2.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommandTest.java ================================================ [File too large to display: 2.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommandTest.java ================================================ [File too large to display: 3.3 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommandTest.java ================================================ [File too large to display: 2.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommandTest.java ================================================ [File too large to display: 4.0 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupListSubCommandTest.java ================================================ [File too large to display: 1.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/lite/GetBrokerLiteInfoSubCommandTest.java ================================================ [File too large to display: 2.4 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/lite/GetLiteClientInfoSubCommandTest.java ================================================ [File too large to display: 2.1 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/message/ConsumeMessageCommandTest.java ================================================ [File too large to display: 8.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommandTest.java ================================================ [File too large to display: 12.5 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommandTest.java ================================================ [File too large to display: 4.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/message/SendMessageCommandTest.java ================================================ [File too large to display: 4.2 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/metadata/ExportMetadataInRocksDBCommandTest.java ================================================ [File too large to display: 3.4 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/AddWritePermSubCommandTest.java ================================================ [File too large to display: 2.7 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/GetNamesrvConfigCommandTest.java ================================================ [File too large to display: 2.3 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommandTest.java ================================================ [File too large to display: 2.5 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/WipeWritePermSubCommandTest.java ================================================ [File too large to display: 2.7 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommandTest.java ================================================ [File too large to display: 2.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommandTest.java ================================================ [File too large to display: 2.6 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeOldCommandTest.java ================================================ [File too large to display: 1.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/offset/SkipAccumulationCommandTest.java ================================================ [File too large to display: 3.4 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/producer/ProducerSubCommandTest.java ================================================ [File too large to display: 3.0 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/server/NameServerMocker.java ================================================ [File too large to display: 2.2 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/server/ServerResponseMocker.java ================================================ [File too large to display: 5.6 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/AllocateMQSubCommandTest.java ================================================ [File too large to display: 1.7 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/DeleteTopicSubCommandTest.java ================================================ [File too large to display: 1.7 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/TopicClusterSubCommandTest.java ================================================ [File too large to display: 1.6 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommandTest.java ================================================ [File too large to display: 1.6 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/TopicStatusSubCommandTest.java ================================================ [File too large to display: 1.6 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/UpdateOrderConfCommandTest.java ================================================ [File too large to display: 1.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/UpdateTopicListSubCommandTest.java ================================================ [File too large to display: 1.8 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/UpdateTopicPermSubCommandTest.java ================================================ [File too large to display: 1.9 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/command/topic/UpdateTopicSubCommandTest.java ================================================ [File too large to display: 2.3 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/monitor/DefaultMonitorListenerTest.java ================================================ [File too large to display: 3.1 KB] ================================================ FILE: tools/src/test/java/org/apache/rocketmq/tools/monitor/MonitorServiceTest.java ================================================ [File too large to display: 8.9 KB] ================================================ FILE: tools/src/test/resources/rmq.logback-test.xml ================================================ [File too large to display: 1.3 KB]